coordinator: use XMPP in-band signaling instead of shared DB (cross-machine compatible)

This commit is contained in:
2026-06-20 19:02:04 +08:00
parent 457ad27044
commit 90429116c7
+27 -50
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""XMPP Bot - 统一版,支持 --agent mohe|zhiwei|xiao 参数""" """XMPP Bot - 统一版,支持 --agent mohe|zhiwei|xiao 参数"""
import asyncio, logging, ssl, json, urllib.request, os, time, sys, re, sqlite3 import asyncio, logging, ssl, json, urllib.request, os, time, sys, re
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
# ── Agent 配置 ────────────────────────────────────────────── # ── Agent 配置 ──────────────────────────────────────────────
@@ -104,6 +104,8 @@ class AgentBot(ClientXMPP):
self._call_seq = 0 self._call_seq = 0
self._muc_joined = False self._muc_joined = False
self._recent_sent = [] self._recent_sent = []
self._coordinator = 'mohe' # 默认协调者
self._granted = None
async def on_connected(self, event): async def on_connected(self, event):
logging.info(f"🔗 {AGENT_NAME} TCP连接已建立") logging.info(f"🔗 {AGENT_NAME} TCP连接已建立")
@@ -139,50 +141,31 @@ class AgentBot(ClientXMPP):
if nickname == AGENT_NICK: if nickname == AGENT_NICK:
return return
# Coordinator 模式:读取当前协调者和被授权发言者 # Coordinator 模式 — 全走 XMPP 消息,不依赖共享 DB
_coordinator = None
_granted = None
try:
_cdb = sqlite3.connect('/home/hmo/.hermes/state.db')
_crow = _cdb.execute("SELECT value FROM state_meta WHERE key='coregroup_coordinator'").fetchone()
_grow = _cdb.execute("SELECT value FROM state_meta WHERE key='coregroup_granted_speaker'").fetchone()
_cdb.close()
if _crow: _coordinator = _crow[0]
if _grow: _granted = _grow[0]
except Exception:
pass
# hmo 可以动态切换 coordinator # 1. hmo 切换 coordinator → 所有 bot 检测,各自更新本地状态
if nickname == 'hmo' and 'coordinator=' in body.lower(): if nickname == 'hmo' and 'coordinator=' in body.lower():
for _name in ['mohe', 'zhiwei', 'xxm']: for _name in ['mohe', 'zhiwei', 'xxm']:
if f'coordinator={_name}' in body.lower(): if f'coordinator={_name}' in body.lower():
try: self._coordinator = _name
_wdb = sqlite3.connect('/home/hmo/.hermes/state.db') self._granted = None
_wdb.execute("INSERT OR REPLACE INTO state_meta (key, value) VALUES ('coregroup_coordinator', ?)", (_name,))
_wdb.execute("DELETE FROM state_meta WHERE key='coregroup_granted_speaker'")
_wdb.commit()
_wdb.close()
_coordinator = _name
_granted = None
logging.info(f"👑 Coordinator 切换为 {_name}") logging.info(f"👑 Coordinator 切换为 {_name}")
except Exception:
pass
break break
# 判断当前 bot 能否处理这条消息 # 2. 检测其他 bot 发出的授权信号 [GRANT:xxx]
_can_speak = False _grant_match = re.search(r'\[GRANT:(\w+)\]', body)
if _coordinator == AGENT_NICK: if _grant_match:
_can_speak = True # 协调者始终能说 self._granted = _grant_match.group(1)
if _granted == AGENT_NICK: logging.info(f"🎤 收到授权:{self._granted} 获得发言权")
_can_speak = True # 被授权者也能说
# 用完后自动清除授权(一次性) # 3. 判断当前 bot 能否处理这条消息
try: _coordinator = getattr(self, '_coordinator', AGENT_NICK)
_wdb = sqlite3.connect('/home/hmo/.hermes/state.db') _granted = getattr(self, '_granted', None)
_wdb.execute("DELETE FROM state_meta WHERE key='coregroup_granted_speaker'") _can_speak = (_coordinator == AGENT_NICK) or (_granted == AGENT_NICK)
_wdb.commit()
_wdb.close() if _can_speak and _granted == AGENT_NICK:
except Exception: # 被授权者用完即收回
pass self._granted = None
if not _can_speak: if not _can_speak:
return # 代码层拦截,不走 LLM return # 代码层拦截,不走 LLM
@@ -208,9 +191,9 @@ class AgentBot(ClientXMPP):
"3. 别人说错了关键事实,不纠正会有后果\n" "3. 别人说错了关键事实,不纠正会有后果\n"
"如果以上都不符合,你的回复必须只包含 __SILENT__ 这10个字符," "如果以上都不符合,你的回复必须只包含 __SILENT__ 这10个字符,"
"不要有任何其他内容(不要前缀、不要解释、不要标点、不要空格)。\n\n" "不要有任何其他内容(不要前缀、不要解释、不要标点、不要空格)。\n\n"
"注意:你是当前群聊的协调者(coordinator)。如果你认为个问题应该由其他 Agent 回答," "注意:你是协调者(coordinator)。如果你认为个问题应该由其他 Agent 回答,"
"可以在回复中包含 [GRANT:agent名] 标记(例如 [GRANT:zhiwei]),bot 会自动授权该 Agent 发言一次。" "可以在回复中加入 [GRANT:agent名](例如 [GRANT:zhiwei]),"
"授权后该 Agent 的回复会出现在群里,你不需要自己回答\n\n" "该 Agent 会在看到标记后获得发言权。标记会显示在消息中\n\n"
f"[核心群 {room}] {nickname} 说: {body}" f"[核心群 {room}] {nickname} 说: {body}"
) )
await self.call_hermes(ctx_body, room, is_group=True) await self.call_hermes(ctx_body, room, is_group=True)
@@ -266,15 +249,9 @@ class AgentBot(ClientXMPP):
_grant_match = re.search(r'\[GRANT:(\w+)\]', reply) _grant_match = re.search(r'\[GRANT:(\w+)\]', reply)
if _grant_match: if _grant_match:
_grant_name = _grant_match.group(1) _grant_name = _grant_match.group(1)
try: self._granted = _grant_name
_gdb = sqlite3.connect('/home/hmo/.hermes/state.db') logging.info(f"🎤 授权 {_grant_name} 发言(通过 XMPP 发送)")
_gdb.execute("INSERT OR REPLACE INTO state_meta (key, value) VALUES ('coregroup_granted_speaker', ?)", (_grant_name,)) # 不剥离标记,让其他 bot 从 XMPP 消息中解析
_gdb.commit()
_gdb.close()
logging.info(f"🎤 Coordinator 授权 {_grant_name} 发言")
except Exception:
pass
reply = reply.replace(_grant_match.group(0), '').strip()
if msg_type == 'groupchat': if msg_type == 'groupchat':
self.send_message(mto=sender, mbody=reply, mtype='groupchat') self.send_message(mto=sender, mbody=reply, mtype='groupchat')