implement coordinator pattern: GRANT-based turn control, code-level speak permission
This commit is contained in:
+67
-1
@@ -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
|
import asyncio, logging, ssl, json, urllib.request, os, time, sys, re, sqlite3
|
||||||
from slixmpp import ClientXMPP
|
from slixmpp import ClientXMPP
|
||||||
|
|
||||||
# ── Agent 配置 ──────────────────────────────────────────────
|
# ── Agent 配置 ──────────────────────────────────────────────
|
||||||
@@ -139,6 +139,54 @@ class AgentBot(ClientXMPP):
|
|||||||
if nickname == AGENT_NICK:
|
if nickname == AGENT_NICK:
|
||||||
return
|
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 分钟
|
# 硬闭嘴闸门:hmo 说闭嘴类的话 → 静默 5 分钟
|
||||||
_silent_until = getattr(self, '_silent_until', 0)
|
_silent_until = getattr(self, '_silent_until', 0)
|
||||||
if time.time() < _silent_until:
|
if time.time() < _silent_until:
|
||||||
@@ -160,6 +208,9 @@ class AgentBot(ClientXMPP):
|
|||||||
"3. 别人说错了关键事实,不纠正会有后果\n"
|
"3. 别人说错了关键事实,不纠正会有后果\n"
|
||||||
"如果以上都不符合,你的回复必须只包含 __SILENT__ 这10个字符,"
|
"如果以上都不符合,你的回复必须只包含 __SILENT__ 这10个字符,"
|
||||||
"不要有任何其他内容(不要前缀、不要解释、不要标点、不要空格)。\n\n"
|
"不要有任何其他内容(不要前缀、不要解释、不要标点、不要空格)。\n\n"
|
||||||
|
"注意:你是当前群聊的协调者(coordinator)。如果你认为这个问题应该由其他 Agent 回答,"
|
||||||
|
"可以在回复中包含 [GRANT:agent名] 标记(例如 [GRANT:zhiwei]),bot 会自动授权该 Agent 发言一次。"
|
||||||
|
"授权后该 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)
|
||||||
@@ -211,8 +262,23 @@ class AgentBot(ClientXMPP):
|
|||||||
finish = data.get("choices", [{}])[0].get("finish_reason", "")
|
finish = data.get("choices", [{}])[0].get("finish_reason", "")
|
||||||
|
|
||||||
if reply.strip() and finish != "silent":
|
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':
|
if msg_type == 'groupchat':
|
||||||
self.send_message(mto=sender, mbody=reply, mtype='groupchat')
|
self.send_message(mto=sender, mbody=reply, mtype='groupchat')
|
||||||
|
# 防回声:记录已发送的消息正文
|
||||||
sent_norm = reply.strip()[:100]
|
sent_norm = reply.strip()[:100]
|
||||||
self._recent_sent.append(sent_norm)
|
self._recent_sent.append(sent_norm)
|
||||||
if len(self._recent_sent) > 10:
|
if len(self._recent_sent) > 10:
|
||||||
|
|||||||
Reference in New Issue
Block a user