193 lines
20 KiB
JSON
193 lines
20 KiB
JSON
{
|
||
"module": "dashboard",
|
||
"version": "2.1",
|
||
"purpose": "AgentsMeeting 管理门户 — Flask app on :5803。监控所有 Agent 的在线状态(跨平台 SSH ejabberdctl + xmpp_bot HTTP API + 本地进程检测)、提供 Web UI(tabs: Agents/Kanban/Infrastructure/开发原则)、管理 Agent 启停(systemctl on Linux, subprocess on Windows)、EasyTier/RDP 远程控制代理、OpenCode Go 用量监控、Kanban 看板、自修复流水线、git 提交历史等。",
|
||
"ui_location": "主 Dashboard UI (http://127.0.0.1:5803)",
|
||
|
||
"human_help": {
|
||
"title": "AgentsMeeting Dashboard — 管理门户",
|
||
"description": [
|
||
"AgentsMeeting Dashboard 是所有 Agent(xxm/mohe/zhiwei/xiaoguo)的统一管理门户,部署在 192.168.1.246:5803,同时运行在 Windows 开发机 127.0.0.1:5803。",
|
||
"核心功能:Agent 在线状态监控(跨平台 SSH ejabberdctl + xmpp_bot HTTP API)、Agent 一键启停、系统健康仪表板(F 健康)、Kanban 看板、Infrastructure 基础设施控制(EasyTier VPN / RDP 远程桌面 / OpenCode Go 用量监控)。",
|
||
"状态检测三层:① 权威层 — SSH docker exec ejabberdctl connected_users(查谁连着 XMPP 服务器,跨平台可信)② 桥接层 — xmpp_bot /health API(查本地 bot 连接状态)③ 本地层 — 进程扫描(查 Windows 上 python 进程是否存在)。",
|
||
"自动恢复:连续 3 次检测到离线后,自动重启本地 Agent 服务(Windows: subprocess.Popen; Linux: systemctl start)。",
|
||
"双轨文档体系:每个功能模块在 specs/{module}.json 维护 human_help(? 按钮—人类看)与 ai_spec(§ 按钮—AI 看),一源双出。",
|
||
"页面刷新:Dashboard 每 10 秒自动拉取 Agent 状态 + Infrastructure 数据;Kanban 每 15 秒独立刷新。"
|
||
],
|
||
"participants": [
|
||
{"name": "莫荷", "device": "Linux 192.168.1.246", "role": "生产环境 dashboard 部署机(systemd 管理) + 公网入口(ags.yoin.fun → nginx → :5803)", "note": "AGENTSMEETING_ROOT 环境变量覆盖项目路径(/home/hmo/projects/AgentsMeeting)"},
|
||
{"name": "小小莫", "device": "Windows 192.168.1.16", "role": "开发环境 dashboard + xmpp bot HTTP bridge (:5807) + EasyTier/RDP 控制代理", "note": "dashboard 本地运行用于开发和测试"},
|
||
{"name": "ejabberd", "device": "246 Docker", "role": "XMPP 服务器 — 权威在线状态源", "note": "通过 SSH docker exec ejabberdctl connected_users 查询(Windows)或直接 docker exec(Linux)"},
|
||
{"name": "老莫", "device": "任意浏览器", "role": "Dashboard 使用者", "note": "访问 http://192.168.1.246:5803 或 公网 https://ags.yoin.fun"}
|
||
],
|
||
"usage": [
|
||
"1. 浏览器打开 http://192.168.1.246:5803(内网)或 https://ags.yoin.fun(公网)",
|
||
"2. Agents tab:查看所有 Agent 在线状态 + 启停 + 日志。JD 显示在 XMPP 连接 → 绿点在线;显示'离线'→ 点 Restart 尝试恢复。",
|
||
"3. Kanban tab:查看团队任务看板(数据源 ~/.hermes/kanban.db),支持按状态/负责人筛选",
|
||
"4. Infrastructure tab:Platform 服务状态 + EasyTier VPN 开关 + RDP 远程桌面开关 + OpenCode Go 用量卡片",
|
||
"5. F 健康 tab:系统健康仪表板 — 服务概览 / 异常告警 / 全部服务分层展示(通信层/AI网关/辅助服务/定时任务)",
|
||
"6. G 规范 tab:开发规范文档(dev-spec.md)+ git 历史",
|
||
"7. H 需求 tab:产品需求文档(PRD.md)+ git 历史",
|
||
"8. K 测试 tab:自动测试报告(通过 tests_api.py 运行)",
|
||
"9. 点击模块旁边的 ? 按钮看人类使用说明,§ 按钮看 AI spec"
|
||
],
|
||
"troubleshooting": [
|
||
"如果 Dashboard 打不开:检查 5803 端口是否监听 — netstat -ano | findstr 5803;Linux 检查 systemctl status dashboard",
|
||
"如果 Agent 全部显示离线:检查 ejabberd SSH 连接 — ssh hmo@192.168.1.246 'docker exec ejabberd ejabberdctl connected_users'",
|
||
"如果 EasyTier/RDP 按钮无反应:检查 xmpp_bot HTTP bridge (:5807) 是否在线 + _BRIDGE_KEY 是否匹配 + XMPP_BRIDGE_URL 是否指向 5807",
|
||
"如果 OpenCode Go Usage 卡片无数据:检查 usage_collector.py 脚本是否存在 + temp/usage_stats.json 是否生成",
|
||
"如果 Kanban 无任务:检查 ~/.hermes/kanban.db 是否存在且可读",
|
||
"如果 spec 弹窗空白:检查 specs/{module}.json 文件是否存在,格式是否正确",
|
||
"Dashboard 启动失败:检查是否已有实例运行(proc_guard PID 锁)→ 删除 temp/.dashboard.pid"
|
||
],
|
||
"related": "依赖 ejabberd (XMPP 服务器), xmpp_bot (HTTP bridge), usage_collector (用量采集), tests_api (测试运行器), specs/ (双轨文档体系)"
|
||
},
|
||
|
||
"ai_spec": {
|
||
"apis": [
|
||
{"method": "GET", "path": "/", "returns": "text/html — dashboard.html 前端页面", "note": "不返回 JSON,直接 serve 模板"},
|
||
{"method": "GET", "path": "/api/agents", "returns": "[{id, name, display_name, jid, platform, host, status, status_reason, xmpp_connected, pid, services, message_count_5min, errors, offline_checks, restartable}]", "note": "核心端点 — 三层状态检测 + 自动恢复计数器"},
|
||
{"method": "GET", "path": "/api/ejabberd", "returns": "{alive, xmpp_bot_connected, online_jids, bot_jid}", "note": "SSH docker exec ejabberdctl connected_users"},
|
||
{"method": "GET", "path": "/api/agents/<agent_id>/logs?lines=50", "returns": "{lines:[...]}", "note": "尾读日志文件(最新 50 条)"},
|
||
{"method": "POST", "path": "/api/agents/<agent_id>/start", "returns": "{ok, started:[...] | service:...}", "note": "启动 Agent — Linux: systemctl start xmpp-{jid}; Windows: subprocess.Popen"},
|
||
{"method": "POST", "path": "/api/agents/<agent_id>/stop", "returns": "{ok, stopped:[...] | service:...}", "note": "停止 Agent — Linux: systemctl stop; Windows: taskkill /f /pid"},
|
||
{"method": "POST", "path": "/api/agents/<agent_id>/restart", "returns": "组合 stop 后 2s sleep 再 start", "note": "先调 api_agent_stop → sleep 2s → api_agent_start"},
|
||
{"method": "GET", "path": "/api/platform", "returns": "[{id, name, type, desc, status, health_data}]", "note": "Platform 服务状态 — 微信桥接 + 文章处理 (5810) 健康检查"},
|
||
{"method": "GET", "path": "/api/service/5810/logs", "returns": "{lines:[...]}", "note": "代理 article-processor 的日志(从 Windows:5810 拉到 dashboard)"},
|
||
{"method": "GET", "path": "/api/kanban", "returns": "{tasks:[{id, title, body, status, assignee, created_by, created_at}], db_exists, count}", "note": "读取 ~/.hermes/kanban.db SQLite 数据库"},
|
||
{"method": "GET", "path": "/api/git", "returns": "{ok, log:[...], dirty, branch, source, method}", "note": "最近 git 提交历史 + 分支 + 是否有未提交改动 — 用 git log 或降级到 reflog 解析"},
|
||
{"method": "GET", "path": "/api/services", "returns": "{services:[{name, port, health, watchdog, pid_lock, type, depends_on}], watched, total}", "note": "服务功能树 + watchdog 覆盖矩阵"},
|
||
{"method": "GET", "path": "/api/monitor", "returns": "{tier1, tier2, tasks:[{name, status}], platform}", "note": "Tier1/Tier2 最新检查结果 + 定时任务状态(schtasks on Windows, systemd timer/crontab on Linux)"},
|
||
{"method": "GET", "path": "/api/metagrowth", "returns": "{total_fixes, completed_fixes, commit_count, meta_growth_enabled, description}", "note": "元成长回路(Phase 3 规划中)"},
|
||
{"method": "GET", "path": "/api/todos", "returns": "{todos:[...], executor_log_tail, count}", "note": "自修复流水线 TODO 执行记录(health_todos.jsonl)+ executor 日志"},
|
||
{"method": "GET", "path": "/api/expected", "returns": "{expected:[...], actual:{name:status}}", "note": "期望状态 vs 实际状态矩阵 — 端口扫描 + 进程检测 + 定时任务检查"},
|
||
{"method": "GET", "path": "/api/easytier", "returns": "{status:{windows, 246}, virtual_ips, windows_running}", "proxied_to": "xmpp_bot /easytier action=status via _bridge_post"},
|
||
{"method": "POST", "path": "/api/easytier/toggle", "body": "{\"action\":\"start|stop\"}", "returns": "{ok, message}", "proxied_to": "xmpp_bot /easytier via _bridge_post"},
|
||
{"method": "GET", "path": "/api/rdp", "returns": "{rdp_enabled, tunnel_running, rdp_port, tunnel_port, public_endpoint}", "proxied_to": "xmpp_bot /rdp action=status via _bridge_post"},
|
||
{"method": "POST", "path": "/api/rdp/toggle", "body": "{\"action\":\"start|stop\"}", "returns": "{ok, message}", "proxied_to": "xmpp_bot /rdp via _bridge_post"},
|
||
{"method": "GET", "path": "/api/usage", "returns": "{ok, accounts:[{key_id, workspace_id, label, rolling, weekly, monthly, last_update, session_expired, error}], last_refresh_iso", "note": "直读 temp/usage_stats.json 缓存"},
|
||
{"method": "POST", "path": "/api/usage/refresh", "body": "{}", "returns": "{ok, message}", "note": "触发异步 subprocess 运行 usage_collector.py"},
|
||
{"method": "GET", "path": "/api/module-spec/<module>", "returns": "specs/{module}.json 内容", "note": "双轨文档 — 人类 help + AI spec 生成"},
|
||
{"method": "GET", "path": "/api/module-specs", "returns": "{specs:[{module, purpose, version}]}", "note": "列出所有可用 spec"},
|
||
{"method": "GET", "path": "/api/spec", "returns": "{content, path} — dev-spec.md 全文", "note": "G 规范 tab 内容"},
|
||
{"method": "GET", "path": "/api/spec/history", "returns": "{log:[...], count} — git log", "note": "dev-spec.md 的 git 提交历史"},
|
||
{"method": "GET", "path": "/api/prd", "returns": "{content, path} — PRD.md 全文", "note": "H 需求 tab 内容"},
|
||
{"method": "GET", "path": "/api/prd/history", "returns": "{log:[...], count} — git log", "note": "PRD.md 的 git 提交历史"},
|
||
{"method": "GET", "path": "/api/tests", "returns": "tests_api.run_tests() 返回的测试结果", "note": "K 测试 tab — 通过 from tests_api import run_tests 调用"},
|
||
{"method": "GET", "path": "/api/autoheal", "returns": "{time, dry_run, total_expected, anomalies_found, actions_taken[]}", "note": "读取 temp/last_auto_heal.json(auto_heal.py 每次运行写入的摘要)"},
|
||
{"method": "GET", "path": "/api/keys", "returns": "{ok, keys:[{key_id, workspace_id, label, masked_key, ...}], usage:{...}}", "note": "Key Registry — 列出所有 API key(masked)+ 用量数据(合并 accounts.json 与 usage_stats.json)"},
|
||
{"method": "GET", "path": "/api/wechat/status", "returns": "{online, message, port_ok, qr_url, qr_timestamp, session_age_hours, last_ok_at, last_err_at}", "note": "WeChat Bridge 登录状态 + QR 码(TCP check port 3001)"},
|
||
{"method": "GET", "path": "/api/health", "returns": "{ok, time, xmpp_bot_alive, ejabberd_alive}", "note": "健康检查端点 — xmpp_bot + ejabberd 双重验证"}
|
||
],
|
||
"dependencies": [
|
||
"ejabberd on 192.168.1.246:5222 — 权威在线状态源(SSH docker exec ejabberdctl connected_users)",
|
||
"xmpp_bot HTTP bridge on Windows 192.168.1.16:5807 — EasyTier/RDP 代理 + /health + /muc",
|
||
"_bridge_post() + _BRIDGE_KEY = 'xxm_bridge_8f3a2c' — 与 xmpp_bot HTTP bridge 的认证机制",
|
||
"config/agents.yaml — Agent 实例注册配置(JID/platform/host/services)",
|
||
"proc_guard.py — PID 锁(防重复启动 dashboard 自身)",
|
||
"gateway/scripts/specs/*.json — 双轨文档体系(/api/module-spec/<module> 读取)",
|
||
"docs/dev-spec.md — G 规范 tab 内容",
|
||
"docs/PRD.md — H 需求 tab 内容",
|
||
"tests_api.py — K 测试 tab(from tests_api import run_tests)",
|
||
"~/.hermes/kanban.db — Kanban 看板数据(SQLite)",
|
||
"gateway/temp/usage_stats.json — OpenCode Go 用量缓存(usage_collector.py 生成)",
|
||
"gateway/temp/health_todos.jsonl — 自修复流水线 TODO 记录",
|
||
"gateway/temp/last_health_check.json — Tier1 健康检查结果",
|
||
"gateway/temp/last_daily_health.json — Tier2 日报健康检查结果",
|
||
"gateway/logs/ — 日志聚合目录(_tail_logs 读取)",
|
||
"gateway/scripts/templates/dashboard.html — 前端 HTML(单文件,内嵌 CSS+JS)",
|
||
"Python 3.10+ — Flask + yaml + sqlite3 + stdlib only(无额外 pip 依赖)",
|
||
"SSH 免密登录(Windows: ssh hmo@192.168.1.246) — ejabberdctl 查询需要",
|
||
"systemctl(Linux) — Agent 启停管理(systemctl start/stop xmpp-{jid})",
|
||
"nginx on 246 — 公网反向代理(ags.yoin.fun → :5803)"
|
||
],
|
||
"architecture": {
|
||
"flow": "Dashboard(Flask :5803) → ① ejabberd SSH docker exec → ② xmpp_bot HTTP API :5807 → ③ 本地进程扫描 → 汇总 agent 状态 → JSON API → dashboard.html 前端渲染",
|
||
"detection_layers": [
|
||
"Layer 1 (权威): SSH docker exec ejabberdctl connected_users — 跨平台可信, 查到谁在 XMPP 登录",
|
||
"Layer 2 (桥接): xmpp_bot /health API — 本地 bot 连接状态(Windows专用)",
|
||
"Layer 3 (本地): 进程扫描 (ps aux | grep) — 进程存在性检测(Windows专用)",
|
||
"Fallback: xmpp_bot /muc API — MUC 参与者列表(备选,R01 已知不稳定)"
|
||
],
|
||
"auto_recovery": {
|
||
"mechanism": "AUTO_RECOVER_THRESHOLD=3 — 连续 3 次检测离线后自动重启",
|
||
"linux": "sudo systemctl start xmpp-{jid}",
|
||
"windows": "subprocess.Popen(python script_path) 非阻塞启动",
|
||
"counter_reset": "一旦状态变回 online,_offline_counter[agent_id] = 0"
|
||
},
|
||
"bridge_proxy": {
|
||
"mechanism": "_bridge_post(path, payload) → POST to XMPP_BRIDGE_URL with X-Api-Key → xmpp_bot on Windows",
|
||
"used_by": "EasyTier toggle (/api/easytier/toggle), RDP toggle (/api/rdp/toggle)"
|
||
},
|
||
"pid_lock": "guard('dashboard') — 从 proc_guard 导入,防重复启动。锁文件在 gateway/temp/.dashboard.pid",
|
||
"usage_auto_timer": "后台线程每 5 分钟自动触发 usage_collector.py,保持用量数据新鲜 + OpenCode Go cookies 保活",
|
||
"frontend": "dashboard.html 单文件 SPA — CSS 变量暗色主题 + 原生 JS (无框架) + tabs 切换 + 10s 自动轮询 + spec 弹窗 (?/§ 按钮)"
|
||
},
|
||
"constraints": [
|
||
"dashboard.html 是单文件 SPA — 不要引入 npm/webpack/vite 等前端框架,保持零依赖",
|
||
"后端使用 stdlib only — Flask + yaml + sqlite3,不要加额外的 pip 包(除非有充分理由)",
|
||
"PID 锁机制 — 启动前调用 guard('dashboard'),退出时自动清理锁文件",
|
||
"跨平台兼容 — sys.platform 检测 win32/linux,SSH vs 直接 docker exec 分支处理",
|
||
"AGENTSMEETING_ROOT 环境变量 — 在 Linux systemd 部署中覆盖项目路径(/home/hmo/projects/AgentsMeeting)",
|
||
"XMPP_BRIDGE_URL 默认 http://192.168.1.16:5807 — 可通过环境变量覆盖(246 systemd 已设 5807)",
|
||
"EJABBERD_HOST 默认 192.168.1.246 — 可通过环境变量覆盖",
|
||
"不要硬编码 workspace ID 或 API keys — 都从 config/accounts.json 或环境变量读取",
|
||
"SSH 免密登录是 ejabberdctl 查询的前提 — BatchMode=yes, 不提示密码",
|
||
"usage auto-timer 初始延迟 15s(等 dashboard 稳定后再启动)"
|
||
],
|
||
"must_not": [
|
||
"不要修改 wechat_agent.py 的 SCRIPT_NAMES 映射而不更新 auto-recovery 逻辑",
|
||
"不要在 status_reason 为空时显示空 div — 前端跳过 'OK' 和空字符串",
|
||
"不要在前端硬编码 agent 信息 — 全部从 /api/agents 动态加载",
|
||
"不要把 _BRIDGE_KEY 提交到 git — 虽然已在 dashboard.py 中写死,部署前应改为环境变量",
|
||
"不要在 EasyTier/RDP toggle 端点中直接操作 VPN/RDP — 必须通过 _bridge_post 代理到 xmpp_bot",
|
||
"不要让 dashboard 的 proc_guard 与 xmpp_bot 的锁冲突 — 各用各的 guard name"
|
||
],
|
||
"related_modules": [
|
||
{"module": "agents", "relation": "Agent 注册与生命周期管理 — dashboard 通过 /api/agents 展示 agent 状态,通过 /api/agents/<id>/start|stop|restart 管理启停"},
|
||
{"module": "api_proxy", "relation": "API 代理服务 (:8787) 与 dashboard 共享 Agent 概念但独立运行"},
|
||
{"module": "easytier", "relation": "EasyTier VPN 状态通过 dashboard 的 _bridge_post 代理到 xmpp_bot — 共享相同的 bridge key 和 HTTP 端口"},
|
||
{"module": "rdp", "relation": "RDP 远程桌面状态通过 dashboard 的 _bridge_post 代理 — 与 EasyTier 共用同一个 xmpp_bot HTTP bridge"},
|
||
{"module": "usage_monitor", "relation": "OpenCode Go 用量监控 — dashboard 通过 /api/usage 读取 usage_collector 生成的缓存 + /api/usage/refresh 触发采集"},
|
||
{"module": "health", "relation": "F 健康 tab — dashboard 通过 /api/services + /api/expected + /api/monitor 三个端点汇总健康数据"},
|
||
{"module": "kanban", "relation": "Kanban 看板 — dashboard 通过读取 ~/.hermes/kanban.db SQLite 数据库展示任务"},
|
||
{"module": "tests", "relation": "K 测试 tab — dashboard 通过 from tests_api import run_tests 调用测试套件"},
|
||
{"module": "dev_spec", "relation": "G 规范 tab — dashboard 通过 /api/spec 读取 dev-spec.md 全文展示"},
|
||
{"module": "prd", "relation": "H 需求 tab — dashboard 通过 /api/prd 读取 PRD.md 全文展示"}
|
||
],
|
||
"tests": [
|
||
{"id": "DB01", "name": "GET / 返回 dashboard.html", "endpoint": "GET / — 期望返回 text/html 200"},
|
||
{"id": "DB02", "name": "GET /api/agents 返回 agent 数组", "endpoint": "GET /api/agents — 期望返回数组,每项含 id/status/status_reason/services"},
|
||
{"id": "DB03", "name": "POST /api/agents/<id>/restart 返回 ok", "endpoint": "POST restart → 期望 ok=true 或符合预期错误"},
|
||
{"id": "DB04", "name": "GET /api/health 返回 xmpp_bot_alive + ejabberd_alive", "endpoint": "GET /api/health → 检查字段存在"},
|
||
{"id": "DB05", "name": "GET /api/module-spec/dashboard 返回本 spec", "endpoint": "GET /api/module-spec/dashboard → 期望 module='dashboard'"},
|
||
{"id": "DB06", "name": "GET /api/module-specs 返回包含 dashboard 的列表", "endpoint": "GET /api/module-specs → specs 数组中应有 module='dashboard'"},
|
||
{"id": "DB07", "name": "EasyTier proxy 在 xmpp_bot 不可达时正确返回错误", "endpoint": "GET /api/easytier → 即使 bridge 不可达也应返回 JSON(不 crash)"},
|
||
{"id": "DB08", "name": "RDP proxy 在 xmpp_bot 不可达时正确返回错误", "endpoint": "GET /api/rdp → 同上,graceful degradation"},
|
||
{"id": "DB09", "name": "Dashboard 不因单个 agent 检测失败而崩溃", "endpoint": "GET /api/agents → 如果 ejabberd SSH 失败,应降级到 muc_participants 或 unknown 状态"}
|
||
],
|
||
"known_issues": [
|
||
"R01: MUC join 超时 — MUC 参与者列表不可靠,但 dashboard 只用它做 fallback,所以影响有限",
|
||
"Windows 端 SSH 依赖免密登录 — 如果 key 丢失或权限不对,ejabberdctl 查询会失败,导致所有 agent 显示 unknown",
|
||
"EasyTier/RDP 代理依赖 xmpp_bot 在 Windows 上运行 — bot 挂了这些按钮就失效",
|
||
"dashboard.html 是单文件 ~490 行 JS — 未来扩展可能需要拆分但暂不考虑(保持部署简单)",
|
||
"auto-recovery 仅支持 Windows 本地进程和 Linux 本地 systemd — Mac/远程 Linux 暂不支持自动恢复",
|
||
"usage monitor 在 Linux 246 上运行时需要 CDP proxy — 如果 Chrome 没有以 --remote-debugging-port=9222 启动,采集会失败",
|
||
"Kanban 看板直接读 ~/.hermes/kanban.db — 如果 Hermes 升级后 DB schema 变更,需要同步更新 SELECT 语句"
|
||
],
|
||
"related_files": [
|
||
"gateway/scripts/dashboard.py — 本文件(Flask 后端,1377行)",
|
||
"gateway/scripts/templates/dashboard.html — 前端 UI(单文件 SPA,490行)",
|
||
"gateway/scripts/specs/dashboard.json — 本 spec 文件",
|
||
"gateway/scripts/proc_guard.py — PID 锁(dashboard 启动时调用 guard('dashboard'))",
|
||
"gateway/scripts/tests_api.py — K 测试 tab 调用的测试套件",
|
||
"gateway/scripts/usage_collector.py — OpenCode Go 用量采集脚本(dashboard auto-timer 触发)",
|
||
"config/agents.yaml — Agent 实例注册配置",
|
||
"docs/dev-spec.md — G 规范 tab 文档",
|
||
"docs/PRD.md — H 需求 tab 文档",
|
||
"~/.hermes/kanban.db — Kanban 看板数据",
|
||
"gateway/temp/ — 运行时临时文件(health_todos.jsonl, last_health_check.json, usage_stats.json, .dashboard.pid 等)"
|
||
]
|
||
}
|
||
}
|