fix: spec audit — align specs with reality (xmpp_bot 5807, dashboard missing endpoints, article_processor X-Lines, api_proxy capability, health_check JSON escape); fix 5802->5807 across dashboard/health_check/watchdog consumers
This commit is contained in:
@@ -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": "文章抓取服务"},
|
||||
]
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", [])
|
||||
|
||||
@@ -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 响应避免调用方崩溃)。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 驱动)",
|
||||
|
||||
@@ -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, 不提示密码",
|
||||
|
||||
@@ -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() 渲染函数",
|
||||
|
||||
@@ -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 读取"
|
||||
|
||||
@@ -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 工作流"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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分钟触发"
|
||||
],
|
||||
|
||||
@@ -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 锁",
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user