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(),
|
|||
|
|
}
|