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,231 @@
"""
Session Context Layer — Humanized Stepan v3 / v3.1 / v3.2 / v3.5.
In-memory, per-chat сесійний контекст з TTL 15 хвилин.
Не персистується між рестартами контейнера (це очікувано — сесія коротка).
Структура SessionContext:
{
"last_messages": list[str] (max 3, найновіші),
"last_depth": "light" | "deep" | None,
"last_agents": list[str] (max 5),
"last_question": str | None,
"pending_action": dict | None — Confirmation Gate (v3.1),
"doc_facts": dict | None — Fact Lock Layer (v3.2):
числові факти з документу (profit_uah, area_ha тощо),
зберігаються між запитами щоб уникнути RAG-інконсистентності,
"fact_claims": list[dict] — Self-Correction (v3.2):
останні 3 твердження агента, напр.
[{"key":"profit_present","value":False,"ts":1234}],
"active_doc_id": str | None — Doc Anchor (v3.3):
doc_id поточного активного документу;
при зміні → скидаємо doc_facts і fact_claims,
"doc_focus": bool — Doc Focus Gate (v3.5):
True = документ "приклеєний" до діалогу (активний режим).
False = документ є, але не нав'язуємо його контекст.
"doc_focus_ts": float — timestamp активації doc_focus (time.time()),
"updated_at": float (time.time())
}
doc_focus TTL: DOC_FOCUS_TTL (600 с = 10 хв).
Скидається автоматично при photo/URL/vision-інтенті або вручну через /doc off.
Telemetry:
AGX_STEPAN_METRIC session_loaded chat_id=h:...
AGX_STEPAN_METRIC session_expired chat_id=h:...
AGX_STEPAN_METRIC session_updated chat_id=h:... depth=... agents=...
"""
from __future__ import annotations
import logging
import time
from copy import deepcopy
from typing import Any
from crews.agromatrix_crew.telemetry import tlog
logger = logging.getLogger(__name__)
# TTL 15 хвилин
SESSION_TTL: float = 900.0
# Doc Focus Gate TTL: 10 хвилин після останньої активації
DOC_FOCUS_TTL: float = 600.0
# v3.6: Cooldown після auto-clear — 2 хв блокування implicit doc re-activate
DOC_FOCUS_COOLDOWN_S: float = 120.0
_STORE: dict[str, dict] = {}
def _default_session() -> dict:
return {
"last_messages": [],
"last_depth": None,
"last_agents": [],
"last_question": None,
"pending_action": None, # v3.1: Confirmation Gate
"doc_facts": None, # v3.2: Fact Lock Layer
"fact_claims": [], # v3.2: Self-Correction Policy
"active_doc_id": None, # v3.3: Doc Anchor Reset
"doc_focus": False, # v3.5: Doc Focus Gate
"doc_focus_ts": 0.0, # v3.5: timestamp активації doc_focus
"doc_focus_cooldown_until": 0.0, # v3.6: epoch seconds, 0=inactive
"last_photo_ts": 0.0, # v3.5 fix: timestamp останнього фото
"updated_at": 0.0,
}
def is_doc_focus_cooldown_active(session: dict, now_ts: float | None = None) -> bool:
"""
Повертає True якщо cooldown активний (після auto-clear по web/vision домену).
Поки cooldown — implicit doc re-activate заблокований.
Fail-safe: будь-яка помилка → False.
"""
try:
until = float(session.get("doc_focus_cooldown_until") or 0.0)
now = now_ts if now_ts is not None else time.time()
return until > now
except Exception:
return False
def is_doc_focus_active(session: dict, now_ts: float | None = None) -> bool:
"""
Повертає True якщо doc_focus увімкнений і TTL ще не минув.
Використовується в run.py для вирішення чи підмішувати doc_context в промпт.
Fail-safe: будь-яка помилка → False.
"""
try:
if not session.get("doc_focus"):
return False
ts = session.get("doc_focus_ts") or 0.0
now = now_ts if now_ts is not None else time.time()
return (now - ts) <= DOC_FOCUS_TTL
except Exception:
return False
def load_session(chat_id: str) -> dict:
"""
Завантажити SessionContext для chat_id.
- Якщо нема → повернути default (порожній).
- Якщо протух (now - updated_at > TTL) → очистити, повернути default.
- Fail-safe: ніяких винятків назовні.
"""
try:
if not chat_id:
return _default_session()
existing = _STORE.get(chat_id)
if existing is None:
tlog(logger, "session_loaded", chat_id=chat_id, status="new")
return _default_session()
age = time.time() - existing.get("updated_at", 0.0)
if age > SESSION_TTL:
_STORE.pop(chat_id, None)
tlog(logger, "session_expired", chat_id=chat_id, age_s=round(age))
return _default_session()
tlog(logger, "session_loaded", chat_id=chat_id, status="hit",
last_depth=existing.get("last_depth"))
return deepcopy(existing)
except Exception as exc:
logger.warning("load_session error (returning default): %s", exc)
return _default_session()
def update_session(
chat_id: str,
message: str,
depth: str,
agents: list[str] | None = None,
last_question: str | None = None,
pending_action: dict | None = None, # v3.1: Confirmation Gate
doc_facts: dict | None = None, # v3.2: Fact Lock
fact_claims: list | None = None, # v3.2: Self-Correction
active_doc_id: str | None = None, # v3.3: Doc Anchor Reset
doc_focus: bool | None = None, # v3.5: Doc Focus Gate
doc_focus_ts: float | None = None, # v3.5: timestamp активації
doc_focus_cooldown_until: float | None = None, # v3.6: cooldown epoch
last_photo_ts: float | None = None, # v3.5 fix: timestamp фото
) -> None:
"""
Оновити SessionContext для chat_id.
- last_messages: append + trim до 3 (зберігає найновіші).
- last_agents: встановити нові; trim до 5.
- updated_at: time.time()
- Fail-safe: не кидає назовні.
"""
try:
if not chat_id:
return
current = _STORE.get(chat_id) or _default_session()
session = deepcopy(current)
# last_messages: append + keep last 3
msgs: list[str] = session.get("last_messages") or []
if message:
msgs.append(message[:500]) # guard against huge messages
session["last_messages"] = msgs[-3:]
# depth, agents, question, pending_action
session["last_depth"] = depth
new_agents = list(agents or [])[:5]
session["last_agents"] = new_agents
session["last_question"] = last_question
# pending_action: зберігаємо якщо є; якщо None і питання немає — скидаємо
if pending_action is not None:
session["pending_action"] = pending_action
elif not last_question:
session["pending_action"] = None
# v3.2: Fact Lock — merge якщо нові факти є
if doc_facts is not None:
session["doc_facts"] = doc_facts
# v3.2: Self-Correction — append новий claim, тримати max 3
if fact_claims is not None:
existing_claims: list = session.get("fact_claims") or []
existing_claims.extend(fact_claims)
session["fact_claims"] = existing_claims[-3:]
# v3.3: Doc Anchor — зберегти active_doc_id
if active_doc_id is not None:
session["active_doc_id"] = active_doc_id
# v3.5: Doc Focus Gate
if doc_focus is not None:
session["doc_focus"] = doc_focus
if doc_focus_ts is not None:
session["doc_focus_ts"] = doc_focus_ts
# v3.6: Cooldown
if doc_focus_cooldown_until is not None:
session["doc_focus_cooldown_until"] = doc_focus_cooldown_until
# v3.5 fix: Photo timestamp
if last_photo_ts is not None:
session["last_photo_ts"] = last_photo_ts
session["updated_at"] = time.time()
_STORE[chat_id] = session
tlog(logger, "session_updated", chat_id=chat_id, depth=depth,
agents=new_agents)
except Exception as exc:
logger.warning("update_session error: %s", exc)
def clear_session(chat_id: str) -> None:
"""Примусово очистити сесію (для тестів та ops-команд)."""
_STORE.pop(chat_id, None)