Публичный дашборд агента: экспорт снапшота стора + publish-пайплайн (export→push→deploy_app, cron hourly) — t-261018220236-7d5851, e-261018230338-f228b9
This commit is contained in:
parent
43d11820e9
commit
cbf03ffac2
2 changed files with 175 additions and 0 deletions
133
scripts/export_dashboard_snapshot.py
Normal file
133
scripts/export_dashboard_snapshot.py
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#!/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())
|
||||||
42
scripts/publish_dashboard.sh
Executable file
42
scripts/publish_dashboard.sh
Executable file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════
|
||||||
|
# publish_dashboard.sh — снапшот стора харнеса → публичный дашборд.
|
||||||
|
#
|
||||||
|
# t-261018220236-7d5851 (деплой) + day-тик 2026-10-19 (автообновление):
|
||||||
|
# 1) scripts/export_dashboard_snapshot.py → /root/agent-dashboard/site/data.json
|
||||||
|
# 2) git commit+push (репо symbiot/agent-dashboard)
|
||||||
|
# 3) порталный deploy_app (docker compose + Caddy) → https://agent-dashboard.cil.by
|
||||||
|
#
|
||||||
|
# Идемпотентен: пустой дифф — коммита/деплоя нет, exit 0.
|
||||||
|
# Токен деплоя — агентский (SIMBIOT_AGENT_TOKEN из .env харнеса).
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HARNESS=/root/simbiot-harness
|
||||||
|
REPO=/root/agent-dashboard
|
||||||
|
LOG=/var/log/agent-dashboard-publish.log
|
||||||
|
|
||||||
|
log() { echo "[$(date -u +%FT%TZ)] $*" >>"$LOG"; }
|
||||||
|
|
||||||
|
# 1) снапшот
|
||||||
|
"$HARNESS/venv/bin/python" "$HARNESS/scripts/export_dashboard_snapshot.py" "$REPO/site/"
|
||||||
|
|
||||||
|
# 2) push, если есть дифф
|
||||||
|
cd "$REPO"
|
||||||
|
if git diff --quiet -- site/data.json; then
|
||||||
|
log "no changes — skip"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git add site/data.json
|
||||||
|
git -c user.name="simbiot" -c user.email="simbiot@cil.by" \
|
||||||
|
commit -q -m "snapshot: $(date -u +%F\ %H:%M) UTC"
|
||||||
|
git push -q origin main
|
||||||
|
log "pushed"
|
||||||
|
|
||||||
|
# 3) деплой через портал
|
||||||
|
TOKEN="$(sed -n 's/^SIMBIOT_AGENT_TOKEN=//p' "$HARNESS/.env")"
|
||||||
|
SIMBIOT_PORTAL_MCP_TOKEN="$TOKEN" "$HARNESS/venv/bin/python" "$HARNESS/cli.py" \
|
||||||
|
mcp-call deploy_app '{"app":"agent-dashboard","repo":"symbiot/agent-dashboard"}' \
|
||||||
|
>/dev/null 2>&1 || { log "deploy_app FAILED"; exit 3; }
|
||||||
|
log "deployed"
|
||||||
|
echo "dashboard published"
|
||||||
Loading…
Reference in a new issue