{ "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 锁文件" ] } }