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,186 @@
"""
Style Adapter для Степана.
adapt_response_style(response, user_profile) → str
Не змінює зміст відповіді, лише форму:
concise → скорочує, прибирає пояснення
checklist → переформатовує у маркери
analytical → додає блок "Причина / Наслідок"
detailed → дозволяє довшу форму (без змін)
conversational → за замовчуванням, без змін
Стиль визначається:
1. Явні слова користувача ("коротко", "списком", ...)
2. Поле user_profile["style"]
Fail-safe: будь-який виняток → повертає оригінальну відповідь.
"""
from __future__ import annotations
import logging
import re
logger = logging.getLogger(__name__)
# ─── Sentence splitter ───────────────────────────────────────────────────────
_SENT_SPLIT_RE = re.compile(r'(?<=[.!?])\s+')
def _split_sentences(text: str) -> list[str]:
return [s.strip() for s in _SENT_SPLIT_RE.split(text.strip()) if s.strip()]
# ─── Style transformers ──────────────────────────────────────────────────────
def _to_concise(text: str) -> str:
"""Скоротити до 23 речень, прибрати надлишкові вступні фрази."""
# Remove common filler openings
filler_re = re.compile(
r'^(звісно[,!]?\s*|звичайно[,!]?\s*|добре[,!]?\s*|зрозуміло[,!]?\s*'
r'|окей[,!]?\s*|ок[,!]?\s*|чудово[,!]?\s*|ось[,!]?\s*|так[,!]?\s*)',
re.IGNORECASE | re.UNICODE,
)
text = filler_re.sub('', text).strip()
sentences = _split_sentences(text)
if len(sentences) <= 3:
return text
# Keep first 3 meaningful sentences
short = ' '.join(sentences[:3])
if len(sentences) > 3:
short += ''
return short
def _to_checklist(text: str) -> str:
"""
Переформатовує відповідь у маркований список.
Якщо вже є маркери — повертає без змін.
"""
if re.search(r'^\s*[-•*]\s', text, re.MULTILINE):
return text # already formatted
sentences = _split_sentences(text)
if len(sentences) < 2:
return text # too short to convert
items = '\n'.join(f'{s}' for s in sentences)
return items
def _to_analytical(text: str) -> str:
"""
Додає короткий блок «Чому це важливо:» якщо відповідь досить довга.
Не дублює зміст — тільки додає структуру.
"""
sentences = _split_sentences(text)
if len(sentences) < 3:
return text
# First 2 sentences — основа; решта — обґрунтування
main = ' '.join(sentences[:2])
reason = ' '.join(sentences[2:4])
result = main
if reason:
result += f'\n\n*Чому це важливо:* {reason}'
return result
# ─── Style detection from text ───────────────────────────────────────────────
_STYLE_SIGNAL: dict[str, list[str]] = {
"concise": ["коротко", "без деталей", "стисло", "коротку відповідь", "кратко"],
"checklist": ["списком", "маркерами", "у списку", "по пунктах", "пунктами"],
"analytical": ["аналіз", "причини", "наслідки", "детальний аналіз", "розбери"],
"detailed": ["детально", "докладно", "розгорнуто", "повністю", "докладну"],
}
def detect_style_from_text(text: str) -> str | None:
"""Визначити бажаний стиль з тексту повідомлення."""
tl = text.lower()
for style, signals in _STYLE_SIGNAL.items():
if any(s in tl for s in signals):
return style
return None
# ─── Main adapter ────────────────────────────────────────────────────────────
def adapt_response_style(response: str, user_profile: dict | None) -> str:
"""
Адаптувати відповідь під стиль користувача.
Якщо user_profile відсутній або style не визначено — повертає оригінал.
Fail-safe: будь-який виняток → повертає оригінал.
"""
try:
if not response or not user_profile:
return response
style = user_profile.get("style") or "conversational"
if style == "concise":
adapted = _to_concise(response)
elif style == "checklist":
adapted = _to_checklist(response)
elif style == "analytical":
adapted = _to_analytical(response)
else:
# "detailed" and "conversational" — no transformation
adapted = response
if adapted != response:
logger.debug("style_adapter: style=%s original_len=%d adapted_len=%d", style, len(response), len(adapted))
return adapted
except Exception as exc:
logger.warning("style_adapter: failed (returning original): %s", exc)
return response
def build_style_prefix(user_profile: dict | None) -> str:
"""
Сформувати prefix для system prompt Степана з урахуванням профілю.
Використовується у _stepan_light_response і фінальній задачі Deep mode.
"""
if not user_profile:
return ""
parts: list[str] = []
name = user_profile.get("name")
if name:
parts.append(f"Користувача звати {name}.")
role = user_profile.get("role", "unknown")
role_labels = {
"owner": "власник/керівник господарства",
"agronomist": "агроном",
"operator": "оператор",
"mechanic": "механік",
}
if role in role_labels:
parts.append(f"Його роль: {role_labels[role]}.")
style = user_profile.get("style", "conversational")
style_instructions = {
"concise": "Відповідай стисло, 12 речення, без зайвих вступів.",
"checklist": "Якщо доречно — структуруй відповідь у маркований список.",
"analytical": "Якщо доречно — виділи причину і наслідок.",
"detailed": "Можеш відповідати розгорнуто.",
"conversational": "Говори природно, живою мовою.",
}
if style in style_instructions:
parts.append(style_instructions[style])
summary = user_profile.get("interaction_summary")
if summary:
parts.append(f"Контекст про користувача: {summary}")
return " ".join(parts)