diff --git a/xmpp_agent_core.py b/xmpp_agent_core.py index 12d95ad..9745b4b 100644 --- a/xmpp_agent_core.py +++ b/xmpp_agent_core.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """XMPP Bot - 统一版,支持 --agent mohe|zhiwei|xiao 参数""" -import asyncio, logging, ssl, json, urllib.request, os, time, sys +import asyncio, logging, ssl, json, urllib.request, os, time, sys, re, sqlite3 from slixmpp import ClientXMPP # ── Agent 配置 ────────────────────────────────────────────── @@ -139,6 +139,54 @@ class AgentBot(ClientXMPP): if nickname == AGENT_NICK: return + # Coordinator 模式:读取当前协调者和被授权发言者 + _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 + if nickname == 'hmo' and 'coordinator=' in body.lower(): + for _name in ['mohe', 'zhiwei', 'xxm']: + if f'coordinator={_name}' in body.lower(): + try: + _wdb = sqlite3.connect('/home/hmo/.hermes/state.db') + _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}") + except Exception: + pass + break + + # 判断当前 bot 能否处理这条消息 + _can_speak = False + if _coordinator == AGENT_NICK: + _can_speak = True # 协调者始终能说 + if _granted == AGENT_NICK: + _can_speak = True # 被授权者也能说 + # 用完后自动清除授权(一次性) + try: + _wdb = sqlite3.connect('/home/hmo/.hermes/state.db') + _wdb.execute("DELETE FROM state_meta WHERE key='coregroup_granted_speaker'") + _wdb.commit() + _wdb.close() + except Exception: + pass + + if not _can_speak: + return # 代码层拦截,不走 LLM + # 硬闭嘴闸门:hmo 说闭嘴类的话 → 静默 5 分钟 _silent_until = getattr(self, '_silent_until', 0) if time.time() < _silent_until: @@ -160,6 +208,9 @@ class AgentBot(ClientXMPP): "3. 别人说错了关键事实,不纠正会有后果\n" "如果以上都不符合,你的回复必须只包含 __SILENT__ 这10个字符," "不要有任何其他内容(不要前缀、不要解释、不要标点、不要空格)。\n\n" + "注意:你是当前群聊的协调者(coordinator)。如果你认为这个问题应该由其他 Agent 回答," + "可以在回复中包含 [GRANT:agent名] 标记(例如 [GRANT:zhiwei]),bot 会自动授权该 Agent 发言一次。" + "授权后该 Agent 的回复会出现在群里,你不需要自己回答。\n\n" f"[核心群 {room}] {nickname} 说: {body}" ) await self.call_hermes(ctx_body, room, is_group=True) @@ -211,8 +262,23 @@ class AgentBot(ClientXMPP): finish = data.get("choices", [{}])[0].get("finish_reason", "") if reply.strip() and finish != "silent": + # Coordinator 授权机制:检测回复中的 [GRANT:xxx] 标记 + _grant_match = re.search(r'\[GRANT:(\w+)\]', reply) + if _grant_match: + _grant_name = _grant_match.group(1) + try: + _gdb = sqlite3.connect('/home/hmo/.hermes/state.db') + _gdb.execute("INSERT OR REPLACE INTO state_meta (key, value) VALUES ('coregroup_granted_speaker', ?)", (_grant_name,)) + _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': self.send_message(mto=sender, mbody=reply, mtype='groupchat') + # 防回声:记录已发送的消息正文 sent_norm = reply.strip()[:100] self._recent_sent.append(sent_norm) if len(self._recent_sent) > 10: