#!/usr/bin/env python3 """Снимок состояния харнеса Simbiot → публичный дашборд (t-261018220236-7d5851). Запуск из корня simbiot-harness: ./venv/bin/python scripts/export_dashboard_snapshot.py /path/to/agent-dashboard/site/ Что делает: - читает проекции стора (та же проекция, что у scheduler/CLI — консистентно); - выгружает site/data.json: задачи, лестница, эскалации, дайджесты, AUM-историю, траты по уровням, uptime; - НЕ выносит секреты: только агрегаты и открытые тексты (публичная страница). Дашборд — статика (nginx), обновление снимка = повторный запуск скрипта + push в репо + deploy_app на портале. Автообновление живых данных — roadmap. """ import json import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from harness import es # noqa: E402 LEVELS = ["sec", "min", "hour", "day"] LEVEL_META = { "sec": {"interval": "1 с", "ctx": "0 (без LLM)", "job": "рефлексы: телеметрия, алерты"}, "min": {"interval": "1 мин", "ctx": "256K", "job": "S-триаж: входящие, быстрые фиксы"}, "hour": {"interval": "1 ч", "ctx": "512K", "job": "S/M-задачи, дайджест часа"}, "day": {"interval": "1 д", "ctx": "1M", "job": "M/L-задачи, манифест дня"}, } DIGEST_LEVELS = {"day", "hour"} # публично показываем только содержательные уровни def build() -> dict: st = es.run_projections() tasks = sorted( st["tasks"].values(), key=lambda t: t.get("created", ""), ) tasks_public = [ { "id": t.get("id"), "title": t.get("title"), "size": t.get("size"), "status": t.get("status"), "level": t.get("level"), "created": t.get("created"), } for t in tasks ] ladder = {} for lv in LEVELS: c = st["cycle_state"].get(lv, {}) ladder[lv] = { "busy": bool(c.get("busy")), "ticks": c.get("ticks", 0), "last_tick": c.get("last_tick"), **LEVEL_META[lv], } escalations = [ { "id": e.get("id"), "severity": e.get("severity"), "status": e.get("status"), "from_level": e.get("from_level"), "to_level": e.get("to_level"), "reason": e.get("reason"), "ts": e.get("ts"), } for e in st.get("escalations", {}).values() if e.get("status") == "open" ] digests = [ { "level": d.get("level"), "period": d.get("period"), "text": (d.get("text") or "").strip(), "ts": d.get("ts"), } for d in st.get("digests", {}).values() if d.get("level") in DIGEST_LEVELS ] digests.sort(key=lambda d: d.get("period") or "", reverse=True) spend = {} for s in st.get("spend", []): lv = s.get("level", "?") agg = spend.setdefault(lv, {"calls": 0, "prompt_tokens": 0, "completion_tokens": 0}) agg["calls"] += 1 agg["prompt_tokens"] += s.get("prompt_tokens", 0) or 0 agg["completion_tokens"] += s.get("completion_tokens", 0) or 0 import datetime snapshot = { "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), "harness": "simbiot-harness", "agent": "alex-shkor-6af5", "portal": "https://cil.by", "ladder": ladder, "tasks": tasks_public, "tasks_stats": { "total": len(tasks_public), "by_status": { s: sum(1 for t in tasks_public if t["status"] == s) for s in ("ready", "running", "blocked", "failed", "done", "closed") }, }, "escalations_open": escalations, "digests": digests[:20], "aum_history": st.get("aum_history", []), "spend_by_level": spend, } return snapshot def main() -> int: dest = sys.argv[1] if len(sys.argv) > 1 else "site" os.makedirs(dest, exist_ok=True) snap = build() path = os.path.join(dest, "data.json") with open(path, "w", encoding="utf-8") as f: json.dump(snap, f, ensure_ascii=False, indent=1) print(f"snapshot → {path} (tasks={snap['tasks_stats']['total']}, " f"escalations_open={len(snap['escalations_open'])}, digests={len(snap['digests'])})") return 0 if __name__ == "__main__": raise SystemExit(main())