feat: kanban session routing for xxm + xiaoguo

This commit is contained in:
hmo
2026-06-22 11:39:27 +08:00
parent 20389a40ac
commit 5a5cc1b45d
+49 -6
View File
@@ -65,6 +65,7 @@ AGENTS = {
"http_port": 5806, "http_port": 5806,
"gateway": "http://localhost:8645/v1/chat/completions", "gateway": "http://localhost:8645/v1/chat/completions",
"session_id": "xmpp-xiaoguo", "session_id": "xmpp-xiaoguo",
"kanban_session_id": "xmpp-xiaoguo-kanban",
"server": "127.0.0.1", "server": "127.0.0.1",
"port": 5222, "port": 5222,
"muc_rooms": ["coregroup@conference.yoin.fun"], "muc_rooms": ["coregroup@conference.yoin.fun"],
@@ -78,6 +79,7 @@ AGENTS = {
"http_port": 5802, "http_port": 5802,
"bridge": "chat_bridge", # use local chat_bridge instead of Hermes API "bridge": "chat_bridge", # use local chat_bridge instead of Hermes API
"session_id": "ses_xxm_xmpp", "session_id": "ses_xxm_xmpp",
"kanban_session_id": "xmpp-xxm-kanban",
"server": "192.168.1.246", # LAN direct connect "server": "192.168.1.246", # LAN direct connect
"port": 5222, "port": 5222,
"muc_rooms": [ "muc_rooms": [
@@ -130,27 +132,61 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
_IS_CHAT_BRIDGE = cfg.get("bridge") == "chat_bridge" _IS_CHAT_BRIDGE = cfg.get("bridge") == "chat_bridge"
_router = None # set only for chat_bridge (xxm) _router = None # set only for chat_bridge (xxm)
# ── Kanban session support ──
_CALL_SEQ = 0
_KANBAN_SESSION_ID = cfg.get("kanban_session_id", None)
if _IS_CHAT_BRIDGE: if _IS_CHAT_BRIDGE:
from chat_bridge import SessionBridge from chat_bridge import SessionBridge
from session_router import SessionRouter from session_router import SessionRouter
_bridge = SessionBridge(session_id=cfg["session_id"]) _bridge = SessionBridge(session_id=cfg["session_id"])
_router = SessionRouter(bridge=_bridge, default_session=cfg["session_id"]) _router = SessionRouter(bridge=_bridge, default_session=cfg["session_id"])
# Kanban-dedicated bridge + router for separate session
_kanban_sid = _KANBAN_SESSION_ID or f"{cfg['session_id']}-kanban"
_kanban_bridge = SessionBridge(session_id=_kanban_sid)
_kanban_router = SessionRouter(bridge=_kanban_bridge, default_session=_kanban_sid)
log(f"LLM: chat_bridge (session={cfg['session_id']})") log(f"LLM: chat_bridge (session={cfg['session_id']})")
log(f"Kanban: chat_bridge (session={_kanban_sid})")
else: else:
_opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) _opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
log(f"LLM: Hermes API ({cfg['gateway']})") log(f"LLM: Hermes API ({cfg['gateway']})")
def _call_llm(content: str, sender: str, is_group: bool = False) -> str: def _call_llm(content: str, sender: str, is_group: bool = False,
"""Abstract LLM call. Returns raw response text (or empty string).""" session_id: str | None = None) -> str:
"""Abstract LLM call. Returns raw response text (or empty string).
If session_id provided and differs from default, route to kanban handler."""
if _IS_CHAT_BRIDGE: if _IS_CHAT_BRIDGE:
if session_id and session_id != cfg["session_id"]:
# Prepends kanban-handler instructions so LLM knows how to handle
kanban_content = (
"【看板处理协议】\n"
"收到卡片后三步判断:\n"
" A) 任务明确 → 直接执行 → 评论结果 + 更新状态\n"
" B) 信息不足 → 评论提问 + 设为 blocked\n"
" C) 不是我的活 → 评论说明 + 转派(如果能判断)\n"
"\n"
"汇报规则:\n"
" - 任务完成 → 简短 DM 给老莫摘要\n"
" - 评论/状态变更 → 不汇报\n"
" - 追问/转派 → 不汇报\n"
"\n"
"可用 API\n"
" curl http://192.168.1.246:5803/api/kanban/t_xxx 查看卡片详情\n"
" 更新操作走 Kanban Dashboard UI\n"
"\n"
"卡片上下文不够?→ 用 session_search 查历史\n"
f"---\n{content}"
)
return _kanban_router.route("xmpp", sender, kanban_content) or ""
return _router.route("xmpp", sender, content) or "" return _router.route("xmpp", sender, content) or ""
else: else:
return _call_hermes_api(content) return _call_hermes_api(content, session_id)
def _call_hermes_api(content: str) -> str: def _call_hermes_api(content: str, session_id: str | None = None) -> str:
"""POST to Hermes API, return response text or empty string.""" """POST to Hermes API, return response text or empty string."""
target_sid = session_id or cfg["session_id"]
try: try:
payload = json.dumps({ payload = json.dumps({
"model": "hermes-agent", "model": "hermes-agent",
@@ -159,7 +195,7 @@ def _call_hermes_api(content: str) -> str:
req = urllib.request.Request(cfg["gateway"], data=payload, method="POST") req = urllib.request.Request(cfg["gateway"], data=payload, method="POST")
req.add_header("Content-Type", "application/json") req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer hermes123") req.add_header("Authorization", "Bearer hermes123")
req.add_header("X-Hermes-Session-Id", cfg["session_id"]) req.add_header("X-Hermes-Session-Id", target_sid)
result = _opener.open(req, timeout=600) result = _opener.open(req, timeout=600)
data = json.loads(result.read()) data = json.loads(result.read())
reply = data.get("choices", [{}])[0].get("message", {}).get("content", "") reply = data.get("choices", [{}])[0].get("message", {}).get("content", "")
@@ -692,6 +728,7 @@ def _handle_group_message(msg):
def _handle_private_message(msg): def _handle_private_message(msg):
"""Process a private chat message.""" """Process a private chat message."""
global _CALL_SEQ
if msg["type"] == "groupchat": if msg["type"] == "groupchat":
return return
msg_id = msg.get("id", "") msg_id = msg.get("id", "")
@@ -708,7 +745,13 @@ def _handle_private_message(msg):
return return
if _check_shutup(body): if _check_shutup(body):
return return
raw = _call_llm(body, sender, is_group=False) # ── Kanban routing ──
_CALL_SEQ += 1
is_kanban = body.startswith('[Kanban]')
target_sid = _KANBAN_SESSION_ID if is_kanban else cfg["session_id"]
if is_kanban:
log(f"📋 看板通知(#{_CALL_SEQ}): {body[:80]}")
raw = _call_llm(body, sender, is_group=False, session_id=target_sid)
if raw: if raw:
reply = _extract_response(raw) reply = _extract_response(raw)
if reply: if reply: