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,164 @@
"""
Soft Proactivity Layer — Humanized Stepan v3.
Додає РІВНО 1 коротке речення в кінець deep-відповіді за суворих умов.
Rule-based, без LLM.
Умови спрацювання (всі мають виконуватись одночасно):
1. depth == "deep"
2. reflection is None OR reflection["confidence"] >= 0.7
3. interaction_count % 10 == 0 (кожна 10-та взаємодія)
4. В known_intents один intent зустрівся >= 3 рази
5. НЕ (preferred_style == "brief" AND response вже містить "?")
Речення ≤ 120 символів, без "!".
Telemetry:
AGX_STEPAN_METRIC proactivity_added user_id=h:... intent=... style=...
AGX_STEPAN_METRIC proactivity_skipped reason=... (якщо умови не пройдені)
"""
from __future__ import annotations
import logging
import random
from typing import Any
from crews.agromatrix_crew.telemetry import tlog
logger = logging.getLogger(__name__)
# ─── Phrase banks ─────────────────────────────────────────────────────────────
_PROACTIVE_GENERIC = [
"За потреби можу швидко зібрати план/факт за вчора.",
"Якщо хочеш, можу підготувати короткий чек-лист на ранок.",
"Можу також порівняти з попереднім тижнем — скажи якщо потрібно.",
"Якщо зміниться пріоритет — одразу скажи, скорегуємо.",
"Якщо потрібна деталізація по конкретному полю — кажи.",
"Готовий зібрати зведення по полях якщо буде потреба.",
"Можу також перевірити статуси по відкритих задачах.",
]
_PROACTIVE_IOT = [
"Якщо хочеш, перевірю датчики по ключових полях.",
"Можу також відслідкувати вологість по полях у реальному часі.",
"За потреби — швидкий звіт по датчиках.",
"Якщо є аномалії на датчиках — дам знати одразу.",
]
_PROACTIVE_PLAN = [
"За потреби можу оновити план після нових даних.",
"Якщо хочеш — зведу всі задачі на тиждень в один список.",
"Можу ще раз пройтись по пріоритетах якщо щось зміниться.",
"Якщо план зміниться — оновлю фільтри автоматично.",
]
_PROACTIVE_SUSTAINABILITY = [
"Можу також подивитись показники сталості за вибраний період.",
"Якщо потрібно — порівняємо з нормою по регіону.",
]
# intent → bank mapping
_INTENT_BANK: dict[str, list[str]] = {
"iot_sensors": _PROACTIVE_IOT,
"plan_day": _PROACTIVE_PLAN,
"plan_week": _PROACTIVE_PLAN,
"plan_vs_fact": _PROACTIVE_PLAN,
"sustainability": _PROACTIVE_SUSTAINABILITY,
}
def _top_intent(known_intents: list | None) -> tuple[str | None, int]:
"""
Знаходить intent з найвищою частотою у known_intents.
known_intents = list[str] (повторення дозволені, кожен запис = 1 взаємодія).
Повертає (intent, count) або (None, 0).
"""
if not known_intents:
return None, 0
freq: dict[str, int] = {}
for item in known_intents:
if isinstance(item, str):
freq[item] = freq.get(item, 0) + 1
if not freq:
return None, 0
top = max(freq, key=lambda k: freq[k])
return top, freq[top]
def maybe_add_proactivity(
response: str,
user_profile: dict,
depth: str,
reflection: dict | None = None,
) -> tuple[str, bool]:
"""
Можливо додає 1 проактивне речення до відповіді.
Аргументи:
response — поточна відповідь Степана
user_profile — UserProfile dict
depth — "light" або "deep"
reflection — результат reflect_on_response або None
Повертає:
(new_response, was_added: bool)
"""
user_id = user_profile.get("user_id", "")
try:
# Умова 1: тільки deep
if depth != "deep":
tlog(logger, "proactivity_skipped", user_id=user_id, reason="not_deep")
return response, False
# Умова 2: confidence >= 0.7 або reflection відсутній
if reflection is not None:
confidence = reflection.get("confidence", 1.0)
if confidence < 0.7:
tlog(logger, "proactivity_skipped", user_id=user_id,
reason="low_confidence", confidence=round(confidence, 2))
return response, False
# Умова 3: interaction_count % 10 == 0
count = user_profile.get("interaction_count", 0)
if count == 0 or count % 10 != 0:
tlog(logger, "proactivity_skipped", user_id=user_id,
reason="not_tenth", interaction_count=count)
return response, False
# Умова 4: top intent зустрічався >= 3 рази
known_intents = user_profile.get("known_intents", [])
top_intent, top_count = _top_intent(known_intents)
if top_count < 3:
tlog(logger, "proactivity_skipped", user_id=user_id,
reason="intent_freq_low", top_intent=top_intent, top_count=top_count)
return response, False
# Умова 5: не нав'язувати якщо brief і вже є питання
preferred_style = user_profile.get("preferences", {}).get("report_format", "")
style = user_profile.get("style", "")
is_brief = preferred_style == "brief" or style == "concise"
if is_brief and "?" in response:
tlog(logger, "proactivity_skipped", user_id=user_id,
reason="brief_with_question", style=style)
return response, False
# Обрати банк фраз за intent
bank = _INTENT_BANK.get(top_intent or "", _PROACTIVE_GENERIC)
seed = hash(f"{user_id}:{count}") % (2**32)
rng = random.Random(seed)
phrase = rng.choice(bank)
# Гарантуємо ≤ 120 символів і без "!"
phrase = phrase[:120].replace("!", "")
new_response = response.rstrip() + "\n\n" + phrase
tlog(logger, "proactivity_added", user_id=user_id,
intent=top_intent, style=style)
return new_response, True
except Exception as exc:
logger.warning("maybe_add_proactivity error (no-op): %s", exc)
return response, False