910ba465d7
Voice Room lets humans talk with agents via Whisper/TTS; chat typing no longer blocked by a full-screen overlay, attachments/avatars are larger, and Settings exposes five vivid mid-dark cyan/blue themes for the whole cockpit.
181 lines
6.2 KiB
Python
181 lines
6.2 KiB
Python
"""Daily Herman War Room digest → Telegram (Aïssa + Mo)."""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from datetime import datetime, timezone
|
||
from typing import Any
|
||
|
||
import httpx
|
||
|
||
from app.config import settings
|
||
from app.db import execute, fetch_all, fetch_one
|
||
from app.services import llm_router
|
||
|
||
log = logging.getLogger("cockpit.warroom_digest")
|
||
|
||
|
||
def _recipients() -> list[tuple[str, str]]:
|
||
"""(chat_id, display_name)"""
|
||
return [
|
||
(str(getattr(settings, "HERMES_CEO_CHAT_ID", None) or "8859782446"), "Aïssa"),
|
||
(str(getattr(settings, "HERMES_CTO_CHAT_ID", None) or "789036463"), "Mo"),
|
||
]
|
||
|
||
|
||
def collect_warroom_day(*, hours: int = 24, limit: int = 120) -> list[dict[str, Any]]:
|
||
hours = max(1, min(int(hours), 72))
|
||
rows = fetch_all(
|
||
"""
|
||
SELECT m.id, m.sender_type, m.agent_key, m.username, m.content, m.created_at, r.kind, r.title
|
||
FROM warroom_messages m
|
||
JOIN warroom_rooms r ON r.id = m.room_id
|
||
WHERE m.created_at >= NOW() - make_interval(hours => %s)
|
||
AND r.kind = 'warroom'
|
||
ORDER BY m.created_at ASC
|
||
LIMIT %s
|
||
""",
|
||
(hours, limit),
|
||
)
|
||
out = []
|
||
for r in rows or []:
|
||
item = dict(r)
|
||
if item.get("created_at") and hasattr(item["created_at"], "isoformat"):
|
||
item["created_at"] = item["created_at"].isoformat()
|
||
out.append(item)
|
||
return out
|
||
|
||
|
||
def _transcript(messages: list[dict[str, Any]], *, max_chars: int = 6000) -> str:
|
||
lines: list[str] = []
|
||
for m in messages:
|
||
st = m.get("sender_type")
|
||
if st == "user":
|
||
who = m.get("username") or "user"
|
||
elif st == "agent":
|
||
who = m.get("agent_key") or "agent"
|
||
else:
|
||
who = "system"
|
||
content = (m.get("content") or "").strip().replace("\n", " ")
|
||
if not content:
|
||
continue
|
||
lines.append(f"- {who}: {content[:280]}")
|
||
text = "\n".join(lines)
|
||
return text[-max_chars:]
|
||
|
||
|
||
async def generate_digest_text(messages: list[dict[str, Any]]) -> str:
|
||
day = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||
if not messages:
|
||
return (
|
||
f"📋 War Room dagrapport {day}\n\n"
|
||
"Geen activiteit in #war-room vandaag. Agents stonden klaar; geen overleg gelogd."
|
||
)
|
||
transcript = _transcript(messages)
|
||
system = (
|
||
"Je bent Herman, Co-CEO van Foodlinkk. Schrijf een beknopte Nederlandse "
|
||
"Telegram-samenvatting van het War Room-overleg voor Aïssa (CEO) en Mo (CTO).\n"
|
||
"Structuur:\n"
|
||
f"1) Kop: 📋 War Room dagrapport {day}\n"
|
||
"2) 3–6 bullets: wat agents bespraken (pipeline, deals, acties, blockers)\n"
|
||
"3) Sectie 'Acties / open punten' met concrete next steps\n"
|
||
"4) 1 zin afsluiting\n"
|
||
"Max ~350 woorden. Geen @zelf-prefix. Geen 'agents staan uit'."
|
||
)
|
||
user = f"War Room transcript (nieuwste onderaan):\n{transcript}"
|
||
reply, _meta = await llm_router.chat_messages(
|
||
[{"role": "system", "content": system}, {"role": "user", "content": user}],
|
||
timeout=90.0,
|
||
user=str(getattr(settings, "HERMES_CEO_CHAT_ID", None) or "8859782446"),
|
||
max_tokens=900,
|
||
temperature=0.3,
|
||
)
|
||
text = (reply or "").strip()
|
||
if not text.startswith("📋"):
|
||
text = f"📋 War Room dagrapport {day}\n\n{text}"
|
||
return text[:3500]
|
||
|
||
|
||
async def _deliver_telegram(chat_id: str, name: str, digest: str) -> dict[str, Any]:
|
||
"""Ask Hermes agent to deliver the digest into that Telegram chat."""
|
||
system = (
|
||
f"Je bent Herman. Bezorg het volgende War Room-dagrapport NU via Telegram "
|
||
f"aan {name} (chat_id={chat_id}). Stuur de tekst zo letterlijk mogelijk. "
|
||
"Bevestig kort of verzonden."
|
||
)
|
||
try:
|
||
reply, meta = await llm_router.chat_messages(
|
||
[
|
||
{"role": "system", "content": system},
|
||
{"role": "user", "content": digest},
|
||
],
|
||
timeout=120.0,
|
||
user=str(chat_id),
|
||
max_tokens=200,
|
||
temperature=0.1,
|
||
)
|
||
return {
|
||
"ok": True,
|
||
"chat_id": chat_id,
|
||
"name": name,
|
||
"confirm": (reply or "")[:240],
|
||
"provider": (meta or {}).get("provider_type"),
|
||
}
|
||
except Exception as exc:
|
||
log.exception("warroom digest deliver failed %s", chat_id)
|
||
return {"ok": False, "chat_id": chat_id, "name": name, "error": str(exc)}
|
||
|
||
|
||
def _persist(digest: str, message_count: int, deliveries: list[dict[str, Any]]) -> None:
|
||
try:
|
||
execute(
|
||
"""
|
||
INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel, metadata)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)
|
||
""",
|
||
(
|
||
"herman",
|
||
"warroom_digest",
|
||
"warroom_daily_digest",
|
||
"War Room dagrapport verstuurd",
|
||
digest[:4000],
|
||
"completed",
|
||
"telegram",
|
||
__import__("json").dumps(
|
||
{
|
||
"message_count": message_count,
|
||
"deliveries": deliveries,
|
||
}
|
||
),
|
||
),
|
||
)
|
||
except Exception:
|
||
log.exception("persist digest event failed")
|
||
|
||
try:
|
||
from app.services import agent_chat
|
||
|
||
agent_chat.post_system_line(
|
||
"📤 Dagrapport verstuurd naar Aïssa + Mo via Telegram.\n\n" + digest[:1500],
|
||
agent_key="herman",
|
||
metadata={"kind": "warroom_digest", "deliveries": deliveries},
|
||
)
|
||
except Exception:
|
||
log.exception("post digest to warroom failed")
|
||
|
||
|
||
async def run_daily_digest(*, hours: int = 24, deliver: bool = True) -> dict[str, Any]:
|
||
messages = collect_warroom_day(hours=hours)
|
||
digest = await generate_digest_text(messages)
|
||
deliveries: list[dict[str, Any]] = []
|
||
if deliver:
|
||
for chat_id, name in _recipients():
|
||
deliveries.append(await _deliver_telegram(chat_id, name, digest))
|
||
_persist(digest, len(messages), deliveries)
|
||
return {
|
||
"ok": True,
|
||
"message_count": len(messages),
|
||
"digest": digest,
|
||
"deliveries": deliveries,
|
||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||
}
|