{
  "module": "dashboard",
  "version": "2.0",
  "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 (:5802) + 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 (:5802) 是否在线 + _BRIDGE_KEY 是否匹配",
      "如果 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/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:5802 — 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 :5802 → ③ 本地进程扫描 → 汇总 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:5802 — 可通过环境变量覆盖",
      "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 等）"
    ]
  }
}
