Files
AgentsMeeting/gateway/scripts/specs/xmpp_bot.json
T

111 lines
8.6 KiB
JSON

{
"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 恢复守护。",
"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。",
"去重机制:通过 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 匹配)。"
],
"participants": [
{"name": "小小莫", "device": "Windows 192.168.1.16", "role": "xmpp_bot 运行者 + OpenCode serve session 所有者", "note": "bot 跑在本地 Python 进程中"},
{"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"}
],
"usage": [
"1. 启动 bot: python xmpp_bot.py(自动获取 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"
],
"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 必须一致",
"如果消息重复: 去重缓存100条上限后自动清空 — 极少数情况下可能漏过去,通常无害",
"如果 bot 启动失败: 检查 proc_guard PID 锁 — 删除 temp/.xmpp_bot.pid 后重试"
],
"related": "依赖 chat_bridge.py (SessionBridge) + session_router.py (SessionRouter) + proc_guard.py (PID 锁)"
},
"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": "发送群聊消息"}
],
"dependencies": [
"slixmpp — XMPP 客户端库 (slixmpp.ClientXMPP)",
"chat_bridge.py — SessionBridge: 连接 OpenCode serve session (ses_xxm_xmpp)",
"session_router.py — SessionRouter: 消息路由 + 命令分发 + 协调者逻辑",
"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"
],
"architecture": {
"flow": "XMPP消息 → slixmpp → 去重 (_is_duplicate) → MAM恢复守护 → 协调者信令解析 → SessionRouter → SessionBridge → OpenCode serve → AI回复 → XMPP发送",
"http_bridge": "HTTP server on :5802 (独立线程) — /health /muc /easytier /rdp /usage /send",
"dedup": "threading.Lock 保护的 set — 100条缓存,满了清空",
"mam_recovery": "启动后30s内收到的群消息被丢弃 (MAM历史回放保护) — 超时后强制禁用",
"coordinator": "in-band XMPP 信令 — hmo切换coordinator / GRANT临时授权 / REVOKE禁言5min",
"shutup": "关键词匹配 '闭嘴'/'别说话'/'安静' → 5分钟不回复"
},
"constraints": [
"PID 锁必须获取成功才能启动 — guard('xmpp_bot')",
"HTTP bridge 的 /easytier /rdp /usage 需要 X-Api-Key (与 dashboard _BRIDGE_KEY 一致)",
"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 之外的任何文件",
"不要在 MAM 恢复期间处理群消息 — 会导致 AI 收到重复/过时上下文",
"不要修改去重逻辑的锁结构 — threading.Lock 必须保持一致"
],
"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 在线状态"}
],
"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": "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条缓存未命中时当做新消息处理"
],
"related_files": [
"gateway/scripts/xmpp_bot.py — 本文件 (943行)",
"gateway/scripts/chat_bridge.py — SessionBridge",
"gateway/scripts/session_router.py — SessionRouter + 协调者逻辑",
"gateway/scripts/proc_guard.py — PID 锁",
"gateway/scripts/specs/xmpp_bot.json — 本 spec",
"gateway/logs/xmpp_bot.log — 运行日志",
"gateway/temp/.xmpp_bot.pid — PID 锁文件"
]
}
}