feat(platform): add new services, tools, tests and crews modules

New router intelligence modules (26 files): alert_ingest/store, audit_store,
architecture_pressure, backlog_generator/store, cost_analyzer, data_governance,
dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment,
platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files),
signature_state_store, sofiia_auto_router, tool_governance

New services:
- sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static
- memory-service: integration_endpoints, integrations, voice_endpoints, static UI
- aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents)
- sofiia-supervisor: new supervisor service
- aistalk-bridge-lite: Telegram bridge lite
- calendar-service: CalDAV calendar service with reminders
- mlx-stt-service / mlx-tts-service: Apple Silicon speech services
- binance-bot-monitor: market monitor service
- node-worker: STT/TTS memory providers

New tools (9): agent_email, browser_tool, contract_tool, observability_tool,
oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault

New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus,
farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine,
session_context, style_adapter, telemetry)

Tests: 85+ test files for all new modules
Made-with: Cursor
This commit is contained in:
Apple
2026-03-03 07:14:14 -08:00
parent e9dedffa48
commit 129e4ea1fc
241 changed files with 69349 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
"""
Reflection Engine для Степана (Deep mode only).
reflect_on_response(user_input, final_response, user_profile, farm_profile)
→ dict з полями: new_facts, style_shift, confidence, clarifying_question
Правила:
- НЕ генерує нову відповідь, тільки аналізує
- НЕ запускається в Light mode
- НЕ запускається рекурсивно (_REFLECTING flag)
- При будь-якій помилці → повертає safe_fallback()
- confidence < 0.6 → викликаючий код може додати clarifying_question до відповіді
Anti-recursion:
Три рівні захисту:
1. Модульний boolean _REFLECTING (per-process, cleared у finally)
2. Caller у run.py передає depth="deep" — reflection ніколи не викличе handle_message
3. Reflection не імпортує run.py, не використовує Crew/Agent
Fail-safe: повертає safe_fallback() при будь-якому винятку.
"""
from __future__ import annotations
import logging
import re
from typing import Any
logger = logging.getLogger(__name__)
# ─── Anti-recursion guard ─────────────────────────────────────────────────────
_REFLECTING: bool = False
def _safe_fallback() -> dict[str, Any]:
return {
"new_facts": {},
"style_shift": None,
"confidence": 1.0,
"clarifying_question": None,
}
# ─── Fact extraction (rule-based) ────────────────────────────────────────────
_CROP_RE = re.compile(
r'\b(пшениця|кукурудза|соняшник|ріпак|соя|ячмінь|жито|гречка|овес|цукровий\s+буряк)\b',
re.IGNORECASE | re.UNICODE,
)
_REGION_RE = re.compile(
r'\b(область|район|село|місто|регіон|зона)\s+([\w-]+)',
re.IGNORECASE | re.UNICODE,
)
_ROLE_RE = re.compile(
r'\b(я\s+)?(агроном|власник|господар|оператор|механік|агрономка|директор)\b',
re.IGNORECASE | re.UNICODE,
)
_NAME_RE = re.compile(
r'\b(мене\s+звуть|я\s+[-—]?\s*|мене\s+кличуть)\s+([АІЇЄA-Z][аіїєa-z]{2,})',
re.UNICODE,
)
_STYLE_SIGNAL: dict[str, list[str]] = {
"concise": ["коротко", "стисло", "без деталей"],
"checklist": ["списком", "маркерами", "пунктами"],
"analytical": ["аналіз", "причини", "наслідки"],
"detailed": ["детально", "докладно", "розгорнуто"],
}
_UNCERTAINTY_PHRASES = [
"не впевнений", "не зрозуміло", "не знаю", "можливо", "мабуть",
"не ясно", "незрозуміло", "не зрозумів", "не визначив", "відсутні дані",
"потрібно уточнити", "уточніть",
]
def _extract_new_facts(user_input: str, response: str, user_profile: dict | None, farm_profile: dict | None) -> dict:
facts: dict[str, Any] = {}
up = user_profile or {}
fp = farm_profile or {}
# Name
m = _NAME_RE.search(user_input)
if m and not up.get("name"):
facts["name"] = m.group(2)
# Role
m = _ROLE_RE.search(user_input)
if m and up.get("role") == "unknown":
role_map = {
"агроном": "agronomist", "агрономка": "agronomist",
"власник": "owner", "господар": "owner", "директор": "owner",
"оператор": "operator", "механік": "mechanic",
}
raw = m.group(2).lower()
for k, v in role_map.items():
if k in raw:
facts["role"] = v
break
# Crops (new ones not yet in farm profile)
existing_crops = set(fp.get("crops", []))
found_crops = {m.group(0).lower() for m in _CROP_RE.finditer(user_input)}
new_crops = found_crops - existing_crops
if new_crops:
facts["new_crops"] = list(new_crops)
# Style shift from user phrasing
tl = user_input.lower()
for style, signals in _STYLE_SIGNAL.items():
if any(s in tl for s in signals) and up.get("style") != style:
facts["style_shift"] = style
break
return facts
def _compute_confidence(user_input: str, response: str) -> float:
"""
Оцінити впевненість відповіді (0..1).
Низька впевненість якщо відповідь містить ознаки невизначеності.
"""
resp_lower = response.lower()
uncertainty_count = sum(1 for ph in _UNCERTAINTY_PHRASES if ph in resp_lower)
if uncertainty_count >= 3:
return 0.4
if uncertainty_count >= 1:
return 0.55
# Response too short for the complexity of the question
if len(response) < 80 and len(user_input) > 150:
return 0.5
return 0.85
def _build_clarifying_question(user_input: str, response: str, facts: dict) -> str | None:
"""
Сформувати одне уточнювальне питання якщо потрібно.
Повертає None якщо питання не потрібне.
"""
if facts.get("new_crops"):
crops_str = ", ".join(facts["new_crops"])
return f"Уточніть: ці культури ({crops_str}) відносяться до поточного сезону?"
resp_lower = response.lower()
if "потрібно уточнити" in resp_lower or "уточніть" in resp_lower:
# Response itself already asks; no need to double
return None
if "не зрозуміло" in resp_lower or "не визначив" in resp_lower:
return "Чи можете уточнити — що саме вас цікавить найбільше?"
return None
# ─── Public API ───────────────────────────────────────────────────────────────
def reflect_on_response(
user_input: str,
final_response: str,
user_profile: dict | None,
farm_profile: dict | None,
) -> dict[str, Any]:
"""
Аналізує відповідь після Deep mode.
Повертає:
{
"new_facts": dict — нові факти для запису в профіль
"style_shift": str | None — новий стиль якщо виявлено
"confidence": float 0..1 — впевненість відповіді
"clarifying_question": str | None — питання для користувача якщо confidence < 0.6
}
НЕ запускається рекурсивно.
Fail-safe: будь-який виняток → _safe_fallback().
"""
global _REFLECTING
if _REFLECTING:
from crews.agromatrix_crew.telemetry import tlog as _tlog
_tlog(logger, "reflection_skip", reason="recursion_guard")
logger.warning("reflection_engine: recursion guard active — skipping")
return _safe_fallback()
_REFLECTING = True
try:
if not user_input or not final_response:
return _safe_fallback()
facts = _extract_new_facts(user_input, final_response, user_profile, farm_profile)
confidence = _compute_confidence(user_input, final_response)
style_shift = facts.pop("style_shift", None)
clarifying_question: str | None = None
from crews.agromatrix_crew.telemetry import tlog as _tlog
if confidence < 0.6:
clarifying_question = _build_clarifying_question(user_input, final_response, facts)
_tlog(logger, "reflection_done", confidence=round(confidence, 2),
clarifying=bool(clarifying_question), new_facts=list(facts.keys()))
logger.info(
"reflection_engine: low confidence=%.2f clarifying=%s",
confidence,
bool(clarifying_question),
)
else:
_tlog(logger, "reflection_done", confidence=round(confidence, 2),
clarifying=False, new_facts=list(facts.keys()))
logger.debug("reflection_engine: confidence=%.2f no clarification needed", confidence)
if facts:
logger.info("reflection_engine: new_facts=%s", list(facts.keys()))
return {
"new_facts": facts,
"style_shift": style_shift,
"confidence": confidence,
"clarifying_question": clarifying_question,
}
except Exception as exc:
from crews.agromatrix_crew.telemetry import tlog as _tlog
_tlog(logger, "reflection_skip", reason="error", error=str(exc))
logger.warning("reflection_engine: error (fallback): %s", exc)
return _safe_fallback()
finally:
_REFLECTING = False