diff --git a/gateway/scripts/agents_daily_health.py b/gateway/scripts/agents_daily_health.py index 7551941..b90ca8d 100644 --- a/gateway/scripts/agents_daily_health.py +++ b/gateway/scripts/agents_daily_health.py @@ -37,7 +37,7 @@ SERVICES = [ {"name": "hermes_gateway_mohe", "host": "127.0.0.1", "port": 8642, "health_url": "http://127.0.0.1:8642/v1/health", "depends_on": [], "desc": "莫荷 AI Gateway"}, {"name": "hermes_gateway_zhiwei","host": "127.0.0.1", "port": 8643, "health_url": "http://127.0.0.1:8643/v1/health", "depends_on": [], "desc": "知微 AI Gateway"}, {"name": "wechat_bridge", "host": "127.0.0.1", "port": 3001, "health_url": None, "depends_on": [], "desc": "微信桥接 Docker"}, - {"name": "xmpp_bot_xxm", "host": "192.168.1.16", "port": 5802, "health_url": "http://192.168.1.16:5802/health", "depends_on": ["ejabberd"],"desc": "小小莫 XMPP Bot"}, + {"name": "xmpp_bot_xxm", "host": "192.168.1.16", "port": 5807, "health_url": "http://192.168.1.16:5807/health", "depends_on": ["ejabberd"],"desc": "小小莫 XMPP Bot"}, {"name": "article_processor", "host": "192.168.1.16", "port": 5810, "health_url": "http://192.168.1.16:5810/health", "depends_on": [], "desc": "文章抓取服务"}, ] diff --git a/gateway/scripts/agents_health_check.py b/gateway/scripts/agents_health_check.py index c41c63c..379feef 100644 --- a/gateway/scripts/agents_health_check.py +++ b/gateway/scripts/agents_health_check.py @@ -80,8 +80,8 @@ SERVICES = [ { "name": "xmpp_bot_xxm", "host": "192.168.1.16", - "port": 5802, - "health_url": "http://192.168.1.16:5802/health", + "port": 5807, + "health_url": "http://192.168.1.16:5807/health", "fix_cmd": None, # 远程服务无法自动修复 "remote": True, "critical": True, diff --git a/gateway/scripts/dashboard.py b/gateway/scripts/dashboard.py index 4923882..3b9384b 100644 --- a/gateway/scripts/dashboard.py +++ b/gateway/scripts/dashboard.py @@ -4,7 +4,7 @@ dashboard.py - AgentsMeeting management dashboard backend ========================================================= Flask app on :5803. Monitors agents across platforms via: - SSH + ejabberdctl connected_users (cross-platform, authoritative) - - xmpp_bot HTTP API :5802 (/health, /muc - fallback) + - xmpp_bot HTTP API :5807 (/health, /muc - fallback) - Local process/port checks (Windows only) Auto-recovery: restarts local Windows agents after 3 consecutive offline checks. @@ -22,7 +22,7 @@ TEMPLATES_DIR = _SCRIPT_DIR / "templates" CONFIG_DIR = _PROJECT_DIR / "config" LOGS_DIR = _GATEWAY_DIR / "logs" TEMP_DIR = _GATEWAY_DIR / "temp" -XMPP_BRIDGE_URL = os.environ.get("XMPP_BRIDGE_URL", "http://192.168.1.16:5802") +XMPP_BRIDGE_URL = os.environ.get("XMPP_BRIDGE_URL", "http://192.168.1.16:5807") EJABBERD_HOST = os.environ.get("EJABBERD_HOST", "192.168.1.246") # On Linux (mohe), dashboard runs from systemd with different paths. Support explicit override. @@ -104,7 +104,7 @@ def _default_agents(): "id": "agent-001", "name": "R&D Assistant", "display_name": "xxm", "jid": "xxm@yoin.fun", "platform": "windows", "host": "192.168.1.16", "bot_type": "xmpp", "provider": "volcengine", - "services": [{"type": "xmpp_bot", "port": 5802}], + "services": [{"type": "xmpp_bot", "port": 5807}], }, { "id": "agent-002", "name": "Automation Manager", "display_name": "mohe", @@ -900,7 +900,7 @@ def api_services(): services = [] # xmpp_bot (跨平台:通过 XMPP_BRIDGE_URL 检测 Windows .16) xmpp_url = f"{XMPP_BRIDGE_URL}/health" - svc1 = {"name": "xmpp_bot", "port": 5802, "health": _health_status(xmpp_url), + svc1 = {"name": "xmpp_bot", "port": 5807, "health": _health_status(xmpp_url), "watchdog": True, "pid_lock": True, "type": "core", "depends_on": ["ejabberd"]} services.append(svc1) # article_processor (跨平台:通过 PLATFORM_SERVICES 中的 health_url) diff --git a/gateway/scripts/health_check_xxm.py b/gateway/scripts/health_check_xxm.py index a54fe98..ec6ced7 100644 --- a/gateway/scripts/health_check_xxm.py +++ b/gateway/scripts/health_check_xxm.py @@ -1,4 +1,4 @@ -""" +""" xxm health check. Runs every 5 min via Task Scheduler. Checks: 1. Is xmpp_bot process alive? If not → restart @@ -117,7 +117,7 @@ def main(): # This catches "process alive but XMPP dead" — the most common failure mode. try: import urllib.request, json - req = urllib.request.Request("http://127.0.0.1:5802/health") + req = urllib.request.Request("http://127.0.0.1:5807/health") resp = json.loads(urllib.request.urlopen(req, timeout=5).read()) if not resp.get("xmpp_connected", True): wlog("CRIT: xmpp_connected=false via /health — forcing restart") diff --git a/gateway/scripts/health_service.py b/gateway/scripts/health_service.py index 9fc2c87..5e0b39d 100644 --- a/gateway/scripts/health_service.py +++ b/gateway/scripts/health_service.py @@ -1,10 +1,10 @@ -""" +""" Windows Service — xxm Bot Health Monitor ========================================= Replaces Task Scheduler-based health_check_xxm.py. Design: - - HTTP health check (GET :5802/health), NO subprocess polling + - HTTP health check (GET :5807/health), NO subprocess polling - Zero console windows (WMIC/tasklist-free during normal operation) - Manageable via services.msc or `net start/stop xxm-health` @@ -42,11 +42,11 @@ _STARTUP_WAIT = 5 # seconds to wait after start before first check class HealthService(win32serviceutil.ServiceFramework): - """Windows Service: polls :5802/health, restarts bot on failure.""" + """Windows Service: polls :5807/health, restarts bot on failure.""" _svc_name_ = "xxm-health" _svc_display_name_ = "AgentsMeeting xxm Health Service" - _svc_description_ = "Monitors xxm bot via HTTP :5802/health, auto-restarts on failure. No console windows." + _svc_description_ = "Monitors xxm bot via HTTP :5807/health, auto-restarts on failure. No console windows." def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) diff --git a/gateway/scripts/mohe_watcher.py b/gateway/scripts/mohe_watcher.py index 1a235d2..358acb7 100644 --- a/gateway/scripts/mohe_watcher.py +++ b/gateway/scripts/mohe_watcher.py @@ -1,4 +1,4 @@ -""" +""" Mohe reply watcher — polls HTTP bridge every 30s for new mohe messages. Logs new replies to logs/mohe_inbox.log. Runs as persistent background process. @@ -21,7 +21,7 @@ log("mohe_watcher started") while True: try: - url = "http://127.0.0.1:5802/messages?from=mohe" + url = "http://127.0.0.1:5807/messages?from=mohe" resp = urllib.request.urlopen(url, timeout=5) data = json.loads(resp.read()) msgs = data.get("messages", []) diff --git a/gateway/scripts/specs/api_proxy.json b/gateway/scripts/specs/api_proxy.json index eb5f23f..fb7710c 100644 --- a/gateway/scripts/specs/api_proxy.json +++ b/gateway/scripts/specs/api_proxy.json @@ -1,20 +1,29 @@ { "module": "api_proxy", - "version": "1.0", + "version": "1.1", "status": "deprecated", - "purpose": "API Proxy(:8787)原用于代理 VolcEngine / opencode-go 等外部 API。已不再使用:火山模型已停用,OpenCode 连接 bug 已修复。代码保留但不再运行。", + "purpose": "API Proxy(:8787)多 upstream API 反向代理,支持错误码吞掉、重试降级、fake 200 兜底。当前已停用:火山模型已停用,OpenCode 连接 bug 已修复。代码保留但不再运行(246/16 均无此进程监听 8787)。", "ui_location": "已从 Infrastructure → Platform 中移除", "human_help": { "title": "API Proxy — ⛔ 已停用", "description": [ "此服务已不再使用。原功能:", - "• 代理 VolcEngine DeepSeek API → 火山已停用", - "• 代理 opencode-go-new/old → 原 bug 已修复", + "• 多 upstream API 反向代理(volcengine / opencode-go 等)", + "• 错误吞掉:upstream 失败重试 N 次后返回提示消息或 fake 200,避免调用方崩溃", + "• 代理 volcengine 原生模型名(如 deepseek-v4-flash)与 proxy 安全名(如 deepseek-v4-flash-go-safe)", "代码保留在 gateway/scripts/api_proxy.py 但已不运行。" ] }, "ai_spec": { "status": "deprecated", - "note": "不再使用,dashboard 中已移除监控。如需恢复:重新加入 PLATFORM_SERVICES 并启动 api_proxy.py。" + "service_ports": [ + {"port": 8787, "service": "api_proxy.py", "host": "0.0.0.0", "purpose": "多 upstream API 反向代理(已停用)"} + ], + "apis": [ + {"method": "GET", "path": "/*", "port": 8787, "returns": "upstream 响应或降级响应", "desc": "任意路径反向代理到配置的 upstream base_url + path"}, + {"method": "POST", "path": "/*", "port": 8787, "returns": "upstream 响应或降级响应", "desc": "同上 — 支持 /v1/chat/completions 等 OpenAI 兼容端点"}, + {"method": "DELETE", "path": "/*", "port": 8787, "returns": "upstream 响应或降级响应", "desc": "同上"} + ], + "note": "不再使用,dashboard 中已移除监控。如需恢复:重新加入 PLATFORM_SERVICES 并启动 api_proxy.py。核心能力:多 upstream 路由(配置化 base_url 映射)、错误吞掉(MAX_RETRIES 重试)、fake 200 兜底(模型不可用时返回 mock 响应避免调用方崩溃)。" } } diff --git a/gateway/scripts/specs/article_processor.json b/gateway/scripts/specs/article_processor.json index 0bb15d4..b2aa459 100644 --- a/gateway/scripts/specs/article_processor.json +++ b/gateway/scripts/specs/article_processor.json @@ -1,6 +1,6 @@ { "module": "article_processor", - "version": "1.1", + "version": "1.2", "purpose": "文章抓取服务运行在 Windows (192.168.1.16:5810),负责抓取微信公众号全文链接并转换为纯文本,支持 OCR 图片识别。", "ui_location": "Infrastructure tab → Platform → 文章抓取服务 (5810)", @@ -30,9 +30,10 @@ "ai_spec": { "apis": [ - {"method": "GET", "path": "/health", "returns": "{ok, service, port, ocr_model, status}", "desc": "服务状态 + OCR 模型信息"}, - {"method": "GET", "path": "/logs?lines=N", "returns": "{ok, lines[]}", "desc": "最近 N 行日志"}, - {"method": "POST", "path": "/process", "body": "{\"url\":\"...\"}", "returns": "{ok, title, content, images[]}", "desc": "抓取并处理文章(主端点)"} + {"method": "GET", "path": "/health", "returns": "{ok, service, port, ocr_model, status, last_fetch}", "desc": "服务状态 + OCR 模型信息 + 上次抓取结果"}, + {"method": "GET", "path": "/logs", "returns": "{ok, lines[]}", "desc": "最近 N 行日志 — N 通过 header 'X-Lines' 指定(默认 200),不是 query 参数!示例: curl -H 'X-Lines: 50' :5810/logs"}, + {"method": "POST", "path": "/process", "body": "{\"url\":\"...\"}", "returns": "{ok, title, content, images[]}", "desc": "抓取并处理文章(主端点)"}, + {"method": "POST", "path": "/fetch-and-ocr", "body": "{\"url\":\"...\"}", "returns": "同 /process", "desc": "抓取 + OCR 一体化端点(/process 的变体,spec 1.0 未声明)"} ], "dependencies": [ "依赖本地 Chrome + 持久 profile(DrissionPage 驱动)", diff --git a/gateway/scripts/specs/dashboard.json b/gateway/scripts/specs/dashboard.json index bafd859..509b1b1 100644 --- a/gateway/scripts/specs/dashboard.json +++ b/gateway/scripts/specs/dashboard.json @@ -1,6 +1,6 @@ { "module": "dashboard", - "version": "2.0", + "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)", @@ -16,7 +16,7 @@ ], "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": "小小莫", "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"} ], @@ -34,7 +34,7 @@ "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 是否匹配", + "如果 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 文件是否存在,格式是否正确", @@ -74,11 +74,14 @@ {"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:5802 — EasyTier/RDP 代理 + /health + /muc", + "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 自身)", @@ -99,7 +102,7 @@ "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 前端渲染", + "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专用)", @@ -126,7 +129,7 @@ "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 — 可通过环境变量覆盖", + "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, 不提示密码", diff --git a/gateway/scripts/specs/health.json b/gateway/scripts/specs/health.json index 7a24f3c..2365bb4 100644 --- a/gateway/scripts/specs/health.json +++ b/gateway/scripts/specs/health.json @@ -61,7 +61,7 @@ ], "known_issues": [ "scheduled tasks 在 Linux 246 上无法检测 Windows 计划任务状态,统一显示 not_deployed", - "部分服务的 HTTP health check 端点可能返回 Connection refused(如 xmpp_bot 在 5802 仅 Windows 端监听)" + "部分服务的 HTTP health check 端点可能返回 Connection refused(如 xmpp_bot 在 5807 仅 Windows 端监听)" ], "related_files": [ "gateway/scripts/templates/dashboard.html — fHealth() 渲染函数", diff --git a/gateway/scripts/specs/health_check.json b/gateway/scripts/specs/health_check.json index f0088a6..38757cb 100644 --- a/gateway/scripts/specs/health_check.json +++ b/gateway/scripts/specs/health_check.json @@ -21,7 +21,7 @@ ], "troubleshooting": [ "如果定时任务未运行:检查 Windows Task Scheduler → agents-health-check", - "如果报告未更新:检查 C:\Users\hmo\AppData\Local\Programs\Python\Python310\python.exe 是否可用", + "如果报告未更新:检查 C:\\Users\\hmo\\AppData\\Local\\Programs\\Python\\Python310\\python.exe 是否可用", "如果 TODO 堆积:检查 self_todo_executor 是否正常消费" ], "related": "依赖 service_registry.py(服务定义); 输出给 self_todo_executor.py(修复消费); 报告被 dashboard.py 读取" diff --git a/gateway/scripts/specs/kanban.json b/gateway/scripts/specs/kanban.json index 5b91eda..3ee5401 100644 --- a/gateway/scripts/specs/kanban.json +++ b/gateway/scripts/specs/kanban.json @@ -1,8 +1,8 @@ { "module": "kanban", - "version": "1.0", - "purpose": "Dashboard 'Kanban' tab 展示看板任务列表,支持按状态(Ready/In Progress/Blocked/Done)和负责人筛选,实时刷新。", - "ui_location": "主菜单 → Kanban tab", + "version": "1.1", + "purpose": "看板任务系统。两个接口层:(1) kanban_api.py (:9580) 提供跨机器 Agent 完整读写 REST API;(2) dashboard.py (:5803) 'Kanban' tab 只读展示看板任务列表,支持按状态和负责人筛选,实时刷新。", + "ui_location": "主菜单 → Kanban tab (dashboard :5803)", "human_help": { "title": "Kanban 看板", @@ -18,32 +18,53 @@ "4. 页面每 15s 自动刷新任务列表" ], "troubleshooting": [ - "如果任务列表为空:检查 Kanban API 是否正常运行", + "如果任务列表为空:检查 Hermes kanban.db 是否存在(~/.hermes/kanban.db)", "如果筛选器无反应:页面自动刷新后会重置筛选状态 — 请确保在刷新间隔内操作" ], - "related": "G 规范定义开发流程和任务管理规范" + "related": "G 规范定义开发流程和任务管理规范;Agent 处理卡片流程见 docs/KANBAN-HANDLER-PROTOCOL.md" }, "ai_spec": { + "service_ports": [ + {"port": 9580, "service": "kanban_api.py", "host": "0.0.0.0", "purpose": "跨机器 Agent 完整读写 API(直接操作 ~/.hermes/kanban.db)"}, + {"port": 5803, "service": "dashboard.py", "host": "246", "purpose": "Dashboard Kanban tab 只读展示"} + ], "apis": [ - {"method": "GET", "path": "/api/kanban", "returns": "{ok, tasks[{id,title,body,status,assignee,created_at}]}"} + {"method": "GET", "path": "/api/kanban", "port": 5803, "returns": "{ok, tasks[{id,title,body,status,assignee,created_at}]}", "description": "Dashboard 只读:返回全部任务列表"}, + {"method": "POST", "path": "/kanban/create", "port": 9580, "returns": "{ok, task_id, raw}", "description": "创建卡片 (title 必填, assignee/body 可选)"}, + {"method": "POST", "path": "/kanban/create_xmpp", "port": 9580, "returns": "{ok, task_id, notified}", "description": "创建卡片并 XMPP 通知核心群 (coregroup@conference.yoin.fun)"}, + {"method": "POST", "path": "/kanban/list", "port": 9580, "returns": "{tasks[]}", "description": "列表 (可选 status/assignee 筛选)"}, + {"method": "POST", "path": "/kanban/show", "port": 9580, "returns": "{id,title,body,status,assignee,created_by,created_at}", "description": "单卡详情"}, + {"method": "POST", "path": "/kanban/update", "port": 9580, "returns": "{ok}", "description": "更新 status/assignee (id 必填)"}, + {"method": "POST", "path": "/kanban/comment", "port": 9580, "returns": "{ok}", "description": "评论卡片 (id/body 必填, author 可选) + XMPP 通知"}, + {"method": "POST", "path": "/kanban/comments", "port": 9580, "returns": "{comments[]}", "description": "读某卡片评论 (id 必填)"} ], "dependencies": [ - "看板数据由 dashboard.py 从后端 Kanban 服务/数据库获取" + "看板数据存于 ~/.hermes/kanban.db (SQLite, 表: tasks / task_comments)", + "dashboard.py 直接读同一 kanban.db 做只读展示", + "kanban_api.py 的 create/comment 通过 hermes CLI 或 XMPP 桥 (127.0.0.1:5804) 通知核心群" ], "constraints": [ "页面每 15s 轮询刷新 (setInterval(fK,15000))", "筛选器状态通过 DOM 直接读取 (document.getElementById)", - "任务卡片展开使用 onclick 切换 expanded class — max-height 从 60px ↔ none 切换" + "任务卡片展开使用 onclick 切换 expanded class — max-height 从 60px ↔ none 切换", + "Agent 处理卡片:拉详情用 POST /kanban/show(不是 GET /api/kanban/t_xxx,该端点不存在)", + "kanban_api.py 全部端点均为 POST(含 list/show/comments 这类读操作)" ], "tests": [ - {"id": "KB01", "name": "/api/kanban 返回任务列表", "endpoint": "GET /api/kanban"}, - {"id": "KB02", "name": "筛选器过滤正确", "endpoint": "n/a (frontend)"} + {"id": "KB01", "name": "/api/kanban 返回任务列表", "endpoint": "GET /api/kanban (:5803)"}, + {"id": "KB02", "name": "筛选器过滤正确", "endpoint": "n/a (frontend)"}, + {"id": "KB03", "name": "kanban_api 创建任务", "endpoint": "POST /kanban/create (:9580)"}, + {"id": "KB04", "name": "kanban_api 更新状态", "endpoint": "POST /kanban/update (:9580)"}, + {"id": "KB05", "name": "kanban_api 评论", "endpoint": "POST /kanban/comment (:9580)"} ], "related_files": [ + "scripts/kanban_api.py — :9580 完整读写 API", "gateway/scripts/templates/dashboard.html — fK() 渲染函数", - "gateway/scripts/dashboard.py — /api/kanban 端点", - "gateway/scripts/specs/kanban.json — 本 spec 文件" + "gateway/scripts/dashboard.py — /api/kanban 只读端点", + "gateway/scripts/specs/kanban.json — 本 spec 文件", + "docs/KANBAN-HANDLER-PROTOCOL.md — Agent 处理卡片协议", + "skills/kanban-handler/SKILL.md — handler session 工作流" ] } } diff --git a/gateway/scripts/specs/todo_executor.json b/gateway/scripts/specs/todo_executor.json index 51237fa..28140e0 100644 --- a/gateway/scripts/specs/todo_executor.json +++ b/gateway/scripts/specs/todo_executor.json @@ -10,18 +10,18 @@ "每 10 分钟由 Windows Task Scheduler 触发,自动修复健康检查发现的异常。", "读取 health_todos.jsonl 中的 pending 条目,执行对应的 fix_script。", "修复成功 → 标记 completed。", - "修复失败 → 通过 XMPP HTTP 桥(:5802/send)发送告警到群聊。", + "修复失败 → 通过 XMPP HTTP 桥(:5807/send)发送告警到群聊。", "特殊情况:如果错误信息为 'another instance is already running',说明服务已在正常运行,标记 completed 跳过。" ], "usage": [ "TODO 文件:gateway/temp/health_todos.jsonl(输入+输出)", "日志:gateway/logs/todo_executor.log", - "告警通道:HTTP POST → 127.0.0.1:5802/send → XMPP 群聊" + "告警通道:HTTP POST → 127.0.0.1:5807/send → XMPP 群聊" ], "troubleshooting": [ "如果 TODO 堆积不减少:检查 agents-todo-executor 定时任务是否运行", "如果修复失败:检查 fix_script 路径是否存在(远程服务的 fix_script 为 null,无法自动修复)", - "如果告警未发出:检查 xmpp_bot HTTP 桥 :5802/send 是否可达" + "如果告警未发出:检查 xmpp_bot HTTP 桥 :5807/send 是否可达" ], "related": "消费 agents_health_check.py 的 TODO 输出; 通过 xmpp_bot HTTP 桥发送告警" }, @@ -29,7 +29,7 @@ "ai_spec": { "dependencies": [ "health_todos.jsonl — TODO 输入(由 agents_health_check.py 写入)", - "xmpp_bot HTTP 桥 (:5802/send) — 告警输出通道", + "xmpp_bot HTTP 桥 (:5807/send) — 告警输出通道", "Python 3.10", "Windows Task Scheduler — 每10分钟触发" ], diff --git a/gateway/scripts/specs/xmpp_bot.json b/gateway/scripts/specs/xmpp_bot.json index 9ff4144..309936f 100644 --- a/gateway/scripts/specs/xmpp_bot.json +++ b/gateway/scripts/specs/xmpp_bot.json @@ -1,37 +1,37 @@ { "module": "xmpp_bot", - "version": "2.0", - "purpose": "XMPP Bot — 笑笑(xxm@yoin.fun)。连接 ejabberd via slixmpp,桥接 XMPP 消息到 OpenCode serve session,提供 HTTP bridge (:5802) 给 dashboard 代理 EasyTier/RDP/health 查询。支持 MUC 群聊、私有聊天、消息去重、协调者协议、MAM 恢复守护。", + "version": "3.0", + "purpose": "XMPP Bot — 笑笑(xxm@yoin.fun)。统一 Bot 核心 xmpp_agent_core.py(--agent xxm)连接 ejabberd via slixmpp,桥接 XMPP 消息到 OpenCode serve session,提供 HTTP bridge (:5807) 给 dashboard 代理 EasyTier/RDP/health 查询。支持 MUC 群聊、私有聊天、消息去重、协调者协议、MAM 恢复守护。", "ui_location": "Agents tab → xxm Agent 卡片 + Services 标签", "human_help": { "title": "XMPP Bot — xxm 消息收发", "description": [ - "xmpp_bot.py 是 Windows 开发机上运行的 XMPP 消息机器人,JID: xxm@yoin.fun。它连接 ejabberd XMPP 服务器,把群聊和私聊消息桥接到 OpenCode serve session,让 AI 能参与 XMPP 群聊对话。", - "核心功能:XMPP 消息收发(MUC 群聊 + 私聊)、HTTP bridge 服务(:5802,提供 /health /muc /easytier /rdp /usage 端点)、消息去重(100条缓存)、协调者协议(coordinator/GRANT/REVOKE in-band 信令)、5分钟沉默 cooldown。", + "xxm 消息机器人(JID: xxm@yoin.fun)运行在 Windows 开发机,由统一 Bot 核心 xmpp_agent_core.py 以 --agent xxm 启动(兼容入口 xxm_bot.py)。它连接 ejabberd XMPP 服务器,把群聊和私聊消息桥接到 OpenCode serve session,让 AI 能参与 XMPP 群聊对话。", + "核心功能:XMPP 消息收发(MUC 群聊 + 私聊)、HTTP bridge 服务(:5807,提供 /health /muc /easytier /rdp /usage /send 端点)、消息去重(100条缓存)、协调者协议(coordinator/GRANT/REVOKE in-band 信令)、5分钟沉默 cooldown。", "去重机制:通过 XMPP stanza message ID 去重,100 条缓存用完自动清空。防止 MAM 恢复和实时消息重复处理。", "协调者协议:mohe 是默认协调者。hmo 可切换 coordinator={name}。支持 GRANT(授权一次发言)和 REVOKE(禁言5分钟)。全部通过 XMPP in-band 消息信令,无需外部 DB。", - "HTTP bridge 安全:/easytier /rdp /usage 等端点需要 X-Api-Key 认证(与 dashboard 的 _BRIDGE_KEY 匹配)。" + "HTTP bridge 安全:/easytier /rdp /usage 等端点需要 X-Api-Key 认证(与 dashboard 的 _BRIDGE_KEY 匹配)。/health 免认证供监控系统使用。" ], "participants": [ - {"name": "小小莫", "device": "Windows 192.168.1.16", "role": "xmpp_bot 运行者 + OpenCode serve session 所有者", "note": "bot 跑在本地 Python 进程中"}, + {"name": "小小莫", "device": "Windows 192.168.1.16", "role": "xmpp bot 运行者 + OpenCode serve session 所有者", "note": "bot 跑在本地 Python 进程中(xmpp_agent_core.py --agent xxm)"}, {"name": "ejabberd", "device": "246 Docker", "role": "XMPP 服务器 — 消息路由", "note": "192.168.1.246:5222"}, - {"name": "莫荷", "device": "Linux 192.168.1.246", "role": "默认协调者 + dashboard 使用者", "note": "dashboard 通过 _bridge_post 代理到 xmpp_bot HTTP bridge"} + {"name": "莫荷", "device": "Linux 192.168.1.246", "role": "默认协调者 + dashboard 使用者", "note": "dashboard 通过 _bridge_post 代理到 xmpp bot HTTP bridge (:5807)"} ], "usage": [ - "1. 启动 bot: python xmpp_bot.py(自动获取 PID 锁,防重复)", + "1. 启动 bot: python xmpp_agent_core.py --agent xxm(自动获取 PID 锁,防重复)", "2. bot 自动连接 ejabberd 并加入 MUC 群聊: coregroup + jujidina", "3. 私聊消息: 直接发送到 OpenCode session → AI 处理 → 通过 XMPP 回复", "4. 群聊消息: 通过 MUC 接收 → 去重 + 协调者检查 → AI 处理 → MUC 回复", "5. 协调者控制: hmo 在群里发 '[GRANT:xxm]' 授权/取消禁言,'[REVOKE:xxm]' 禁言5分钟", "6. 沉默命令: 对 bot 说 '闭嘴'/'别说话'/'安静' → 5分钟静默", - "7. HTTP bridge: GET :5802/health 查连接状态,POST :5802/easytier 控制 VPN" + "7. HTTP bridge: GET :5807/health 查连接状态,POST :5807/easytier 控制 VPN" ], "troubleshooting": [ "如果 bot 不回复消息: 检查 OpenCode serve session 是否在线 (localhost:4096) — session_attach 超时5min", "如果群聊收不到: 检查 MUC join 是否成功 — 看日志是否有 'joined MUC' 或 MAM recovery 是否卡住", - "如果 HTTP bridge 不可达: 检查 5802 端口 — netstat -ano | findstr 5802", - "如果 dashboard EasyTier/RDP 无响应: 检查 _BRIDGE_KEY 是否匹配 — dashboard 和 xmpp_bot 必须一致", + "如果 HTTP bridge 不可达: 检查 5807 端口 — netstat -ano | findstr 5807", + "如果 dashboard EasyTier/RDP 无响应: 检查 _BRIDGE_KEY 是否匹配 — dashboard 和 bot 必须一致;再确认 dashboard 的 XMPP_BRIDGE_URL 指向 :5807", "如果消息重复: 去重缓存100条上限后自动清空 — 极少数情况下可能漏过去,通常无害", "如果 bot 启动失败: 检查 proc_guard PID 锁 — 删除 temp/.xmpp_bot.pid 后重试" ], @@ -40,12 +40,12 @@ "ai_spec": { "apis": [ - {"method": "GET", "path": "/health", "returns": "{ok, xmpp_connected, ejabberd_alive, bot_jid, uptime_seconds}", "note": "XMPP 连接状态 + bot 自检"}, - {"method": "GET", "path": "/muc", "returns": "{rooms: {room_name: {participants: [{jid, nick}]}}}", "note": "MUC 群聊参与者列表(R01 已知不稳定)"}, - {"method": "POST", "path": "/easytier", "body": "{\"action\":\"status|start|stop\"}", "returns": "{ok, running, ...}", "note": "EasyTier VPN 控制 — 需 X-Api-Key"}, - {"method": "POST", "path": "/rdp", "body": "{\"action\":\"status|start|stop\"}", "returns": "{ok, rdp_enabled, tunnel_running}", "note": "RDP 远程桌面控制 — 需 X-Api-Key"}, - {"method": "POST", "path": "/usage", "body": "{\"action\":\"status|collect_now\"}", "returns": "{ok, accounts, ...}", "note": "OpenCode Go 用量查询/采集 — 需 X-Api-Key"}, - {"method": "POST", "path": "/send", "body": "{\"message\":\"text\"}", "returns": "{ok}", "note": "发送群聊消息"} + {"method": "GET", "path": "/health", "port": 5807, "returns": "{ok, xmpp_connected, ejabberd_alive, bot_jid, uptime_seconds}", "note": "XMPP 连接状态 + bot 自检 — 免认证(供监控系统使用)"}, + {"method": "GET", "path": "/muc", "port": 5807, "returns": "{rooms: {room_name: {participants: [{jid, nick}]}}}", "note": "MUC 群聊参与者列表(R01 已知不稳定)— 需 X-Api-Key"}, + {"method": "POST", "path": "/easytier", "port": 5807, "body": "{\"action\":\"status|start|stop\"}", "returns": "{ok, running, ...}", "note": "EasyTier VPN 控制 — 需 X-Api-Key"}, + {"method": "POST", "path": "/rdp", "port": 5807, "body": "{\"action\":\"status|start|stop\"}", "returns": "{ok, rdp_enabled, tunnel_running}", "note": "RDP 远程桌面控制 — 需 X-Api-Key"}, + {"method": "POST", "path": "/usage", "port": 5807, "body": "{\"action\":\"status|collect_now\"}", "returns": "{ok, accounts, ...}", "note": "OpenCode Go 用量查询/采集 — 需 X-Api-Key"}, + {"method": "POST", "path": "/send", "port": 5807, "body": "{\"message\":\"text\"}", "returns": "{ok}", "note": "发送群聊消息 — 需 X-Api-Key"} ], "dependencies": [ "slixmpp — XMPP 客户端库 (slixmpp.ClientXMPP)", @@ -54,11 +54,14 @@ "proc_guard.py — PID 锁 (guard('xmpp_bot'))", "ejabberd 192.168.1.246:5222 — XMPP 服务器", "OpenCode serve session (localhost:4096) — AI 处理后端", - "MUC rooms: coregroup@conference.yoin.fun + jujidina@conference.yoin.fun" + "MUC rooms: coregroup@conference.yoin.fun + jujidina@conference.yoin.fun", + "HTTP bridge 由 xmpp_agent_core.py 内嵌 (_BridgeHandler),非独立进程" ], "architecture": { + "entrypoint": "xmpp_agent_core.py --agent xxm(兼容入口 xxm_bot.py → xmpp_agent_core.py --agent xxm)", "flow": "XMPP消息 → slixmpp → 去重 (_is_duplicate) → MAM恢复守护 → 协调者信令解析 → SessionRouter → SessionBridge → OpenCode serve → AI回复 → XMPP发送", - "http_bridge": "HTTP server on :5802 (独立线程) — /health /muc /easytier /rdp /usage /send", + "http_bridge": "HTTP server on :5807(_BridgeHandler 线程)— /health /muc /easytier /rdp /usage /send。历史端口 5802 已被 wechat-hermes-gateway 占用(见 xmpp_agent_core.py L79-82 注释)", + "bridge_auth": "_bridge_auth() 校验 X-Api-Key header(/health 免认证,L957-958)", "dedup": "threading.Lock 保护的 set — 100条缓存,满了清空", "mam_recovery": "启动后30s内收到的群消息被丢弃 (MAM历史回放保护) — 超时后强制禁用", "coordinator": "in-band XMPP 信令 — hmo切换coordinator / GRANT临时授权 / REVOKE禁言5min", @@ -66,39 +69,44 @@ }, "constraints": [ "PID 锁必须获取成功才能启动 — guard('xmpp_bot')", - "HTTP bridge 的 /easytier /rdp /usage 需要 X-Api-Key (与 dashboard _BRIDGE_KEY 一致)", + "HTTP bridge 的 /muc /easytier /rdp /usage /send 需要 X-Api-Key (与 dashboard _BRIDGE_KEY 一致);/health 免认证", + "端口 5807 — 5802 已被 wechat-hermes-gateway 的 xmpp_bot.py 占用,不要改回 5802(曾导致 ~50% /easytier /rdp 请求 400)", + "dashboard 侧必须设 XMPP_BRIDGE_URL=http://192.168.1.16:5807(246 systemd 已配置)", "MUC join 时需处理 DNS 解析超时 (conference.yoin.fun) — 用 try/except 包裹", "去重缓存上限 100 — 足够处理正常流量,极端情况下清空后可能漏1-2条", "MAM 恢复超时 30s — 如果 ejabberd 没发 MAM history,不会永远卡住", "不要用 threading 处理消息 — slixmpp 是 asyncio 驱动的" ], "must_not": [ - "不要把 _BRIDGE_KEY 硬编码到 dashboard.py 和 xmpp_bot.py 之外的任何文件", + "不要把 _BRIDGE_KEY 硬编码到 dashboard.py 和 bot 之外的任何文件", "不要在 MAM 恢复期间处理群消息 — 会导致 AI 收到重复/过时上下文", - "不要修改去重逻辑的锁结构 — threading.Lock 必须保持一致" + "不要修改去重逻辑的锁结构 — threading.Lock 必须保持一致", + "不要把端口改回 5802 — 与 wechat-hermes-gateway 冲突" ], "related_modules": [ - {"module": "dashboard", "relation": "dashboard 通过 _bridge_post 代理 EasyTier/RDP/usage 到 xmpp_bot HTTP bridge"}, - {"module": "easytier", "relation": "xmpp_bot 提供 /easytier 端点给 dashboard 代理 — bot 是 EasyTier 控制的实际执行者"}, - {"module": "rdp", "relation": "xmpp_bot 提供 /rdp 端点 — bot 是 RDP 控制的实际执行者"}, - {"module": "usage_monitor", "relation": "xmpp_bot 提供 /usage 端点 — 旧版用法,新版 dashboard 直接读本地 usage_stats.json"}, - {"module": "agents", "relation": "xxm agent 的核心通信层 — dashboard 通过 ejabberdctl 查询 xmpp_bot 的 JID 在线状态"} + {"module": "dashboard", "relation": "dashboard 通过 _bridge_post 代理 EasyTier/RDP/usage 到 bot HTTP bridge (:5807)"}, + {"module": "easytier", "relation": "bot 提供 /easytier 端点给 dashboard 代理 — bot 是 EasyTier 控制的实际执行者"}, + {"module": "rdp", "relation": "bot 提供 /rdp 端点 — bot 是 RDP 控制的实际执行者"}, + {"module": "usage_monitor", "relation": "bot 提供 /usage 端点 — 旧版用法,新版 dashboard 直接读本地 usage_stats.json"}, + {"module": "agents", "relation": "xxm agent 的核心通信层 — dashboard 通过 ejabberdctl 查询 bot 的 JID 在线状态"} ], "tests": [ - {"id": "XB01", "name": "GET /health 返回 xmpp_connected + ejabberd_alive", "endpoint": "GET :5802/health"}, - {"id": "XB02", "name": "GET /muc 返回群聊参与者列表", "endpoint": "GET :5802/muc"}, - {"id": "XB03", "name": "POST /easytier status 需认证", "endpoint": "POST :5802/easytier → 无 key 应 401"}, - {"id": "XB04", "name": "POST /send 发送消息成功", "endpoint": "POST :5802/send → ok=true"}, + {"id": "XB01", "name": "GET /health 返回 xmpp_connected + ejabberd_alive", "endpoint": "GET :5807/health"}, + {"id": "XB02", "name": "GET /muc 返回群聊参与者列表", "endpoint": "GET :5807/muc(带 X-Api-Key)"}, + {"id": "XB03", "name": "POST /easytier status 需认证", "endpoint": "POST :5807/easytier → 无 key 应 401"}, + {"id": "XB04", "name": "POST /send 发送消息成功", "endpoint": "POST :5807/send(带 X-Api-Key)→ ok=true"}, {"id": "XB05", "name": "去重: 相同 message ID 第二次被跳过", "endpoint": "注入重复 msg_id → _is_duplicate 返回 True"} ], "known_issues": [ "R01: MUC join 超时 — conference.yoin.fun DNS 偶尔不可达,需 raw presence 双保险", "slixmpp 依赖 Python asyncio — 与 Flask (同步 threading) 混跑时偶尔死锁", "HTTP bridge 无 HTTPS — 仅局域网内使用,不暴露公网", - "去重清空时极少数重复消息可能漏过 — 100条缓存未命中时当做新消息处理" + "去重清空时极少数重复消息可能漏过 — 100条缓存未命中时当做新消息处理", + "历史端口 5802 与 wechat-hermes-gateway 冲突导致 400(已迁移 5807,勿回退)" ], "related_files": [ - "gateway/scripts/xmpp_bot.py — 本文件 (943行)", + "xmpp_agent_core.py — 统一 Bot 核心(HTTP bridge 实现在此,xxm http_port=5807 L82)", + "xxm_bot.py — 兼容入口 → xmpp_agent_core.py --agent xxm", "gateway/scripts/chat_bridge.py — SessionBridge", "gateway/scripts/session_router.py — SessionRouter + 协调者逻辑", "gateway/scripts/proc_guard.py — PID 锁", diff --git a/gateway/scripts/tests_api.py b/gateway/scripts/tests_api.py index caaeea2..0649035 100644 --- a/gateway/scripts/tests_api.py +++ b/gateway/scripts/tests_api.py @@ -1,4 +1,4 @@ -""" +""" tests_api.py — 自动测试接口 从 dev-spec.md + PRD.md 提取测试用例,对系统进行实时检测。 通过 service_registry 的 BASE 定位文档。 @@ -93,15 +93,15 @@ def run_tests(): # ─── B: 服务架构(直接检查各服务,不走 dashboard 路由避免死锁)─── # xmpp_bot: 只要端口开放且返回响应即认为存活(新旧版 API 不同) try: - s = urllib.request.urlopen("http://127.0.0.1:5802/health", timeout=3) - add("B1: xmpp_bot :5802/health", True, f"HTTP {s.getcode()}") + s = urllib.request.urlopen("http://127.0.0.1:5807/health", timeout=3) + add("B1: xmpp_bot :5807/health", True, f"HTTP {s.getcode()}") except urllib.error.HTTPError as e: # HTTP error means bot responded (alive), just doesn't support GET - add("B1: xmpp_bot :5802/health", True, f"HTTP {e.code}(存活,旧版API)") + add("B1: xmpp_bot :5807/health", True, f"HTTP {e.code}(存活,旧版API)") except Exception as e: # Connection refused → bot is DOWN. Linux 上 bot 跑在 Windows, expected is_linux = sys.platform != "win32" - add("B1: xmpp_bot :5802/health", is_linux, str(e)[:50] + ("(仅Windows,生产环境正常)" if is_linux else ""), expected=is_linux) + add("B1: xmpp_bot :5807/health", is_linux, str(e)[:50] + ("(仅Windows,生产环境正常)" if is_linux else ""), expected=is_linux) try: s = urllib.request.urlopen("http://127.0.0.1:5810/health", timeout=3) @@ -157,7 +157,7 @@ def run_tests(): try: payload = json.dumps({"message": "test from tests_api"}).encode() - req = urllib.request.Request("http://127.0.0.1:5802/send", + req = urllib.request.Request("http://127.0.0.1:5807/send", data=payload, headers={"Content-Type": "application/json", "X-Api-Key": "xxm_bridge_8f3a2c"}, method="POST") resp = urllib.request.urlopen(req, timeout=5) @@ -165,7 +165,7 @@ def run_tests(): except urllib.error.HTTPError as e: add("D2: /send 端点可达", e.code in (200, 401, 400), f"HTTP {e.code}" + ("(需认证)" if e.code == 401 else "(生产旧版bot无此端点)"), expected=e.code != 200) except Exception as e: - # D2 依赖 xmpp_bot (:5802), Linux 上 expected + # D2 依赖 xmpp_bot (:5807), Linux 上 expected add("D2: /send 端点可达", sys.platform != "win32", str(e)[:60], expected=sys.platform != "win32") # ─── F: 可视化(直接检查文件和服务)─── @@ -207,7 +207,7 @@ def run_tests(): add("E: 元成长 — reflog", False, "reflog不存在", expected=True) # F: 端口可达(由 B1-B3 覆盖,此处用 socket 交叉验证) fp = 0 - for pn, pp in [("xmpp_bot",5802),("dashboard",5803),("article_processor",5810)]: + for pn, pp in [("xmpp_bot",5807),("dashboard",5803),("article_processor",5810)]: try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1)