111 lines
8.6 KiB
JSON
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 锁文件"
|
|
]
|
|
}
|
|
}
|