119 lines
10 KiB
JSON
119 lines
10 KiB
JSON
{
|
||
"module": "xmpp_bot",
|
||
"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": [
|
||
"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 匹配)。/health 免认证供监控系统使用。"
|
||
],
|
||
"participants": [
|
||
{"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 (:5807)"}
|
||
],
|
||
"usage": [
|
||
"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 :5807/health 查连接状态,POST :5807/easytier 控制 VPN"
|
||
],
|
||
"troubleshooting": [
|
||
"如果 bot 不回复消息: 检查 OpenCode serve session 是否在线 (localhost:4096) — session_attach 超时5min",
|
||
"如果群聊收不到: 检查 MUC join 是否成功 — 看日志是否有 'joined MUC' 或 MAM recovery 是否卡住",
|
||
"如果 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 后重试"
|
||
],
|
||
"related": "依赖 chat_bridge.py (SessionBridge) + session_router.py (SessionRouter) + proc_guard.py (PID 锁)"
|
||
},
|
||
|
||
"ai_spec": {
|
||
"apis": [
|
||
{"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)",
|
||
"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",
|
||
"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 :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",
|
||
"shutup": "关键词匹配 '闭嘴'/'别说话'/'安静' → 5分钟不回复"
|
||
},
|
||
"constraints": [
|
||
"PID 锁必须获取成功才能启动 — guard('xmpp_bot')",
|
||
"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 和 bot 之外的任何文件",
|
||
"不要在 MAM 恢复期间处理群消息 — 会导致 AI 收到重复/过时上下文",
|
||
"不要修改去重逻辑的锁结构 — threading.Lock 必须保持一致",
|
||
"不要把端口改回 5802 — 与 wechat-hermes-gateway 冲突"
|
||
],
|
||
"related_modules": [
|
||
{"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 :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条缓存未命中时当做新消息处理",
|
||
"历史端口 5802 与 wechat-hermes-gateway 冲突导致 400(已迁移 5807,勿回退)"
|
||
],
|
||
"related_files": [
|
||
"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 锁",
|
||
"gateway/scripts/specs/xmpp_bot.json — 本 spec",
|
||
"gateway/logs/xmpp_bot.log — 运行日志",
|
||
"gateway/temp/.xmpp_bot.pid — PID 锁文件"
|
||
]
|
||
}
|
||
}
|