From 5a5cc1b45d43a59610583af92ac9d7ed7dc8d7d9 Mon Sep 17 00:00:00 2001 From: hmo Date: Mon, 22 Jun 2026 11:39:27 +0800 Subject: [PATCH] feat: kanban session routing for xxm + xiaoguo --- xmpp_agent_core.py | 55 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/xmpp_agent_core.py b/xmpp_agent_core.py index 578c328..e5413d8 100644 --- a/xmpp_agent_core.py +++ b/xmpp_agent_core.py @@ -65,6 +65,7 @@ AGENTS = { "http_port": 5806, "gateway": "http://localhost:8645/v1/chat/completions", "session_id": "xmpp-xiaoguo", + "kanban_session_id": "xmpp-xiaoguo-kanban", "server": "127.0.0.1", "port": 5222, "muc_rooms": ["coregroup@conference.yoin.fun"], @@ -78,6 +79,7 @@ AGENTS = { "http_port": 5802, "bridge": "chat_bridge", # use local chat_bridge instead of Hermes API "session_id": "ses_xxm_xmpp", + "kanban_session_id": "xmpp-xxm-kanban", "server": "192.168.1.246", # LAN direct connect "port": 5222, "muc_rooms": [ @@ -130,27 +132,61 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') _IS_CHAT_BRIDGE = cfg.get("bridge") == "chat_bridge" _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: from chat_bridge import SessionBridge from session_router import SessionRouter _bridge = SessionBridge(session_id=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"Kanban: chat_bridge (session={_kanban_sid})") else: _opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) log(f"LLM: Hermes API ({cfg['gateway']})") -def _call_llm(content: str, sender: str, is_group: bool = False) -> str: - """Abstract LLM call. Returns raw response text (or empty string).""" +def _call_llm(content: str, sender: str, is_group: bool = False, + 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 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 "" 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.""" + target_sid = session_id or cfg["session_id"] try: payload = json.dumps({ "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.add_header("Content-Type", "application/json") 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) data = json.loads(result.read()) reply = data.get("choices", [{}])[0].get("message", {}).get("content", "") @@ -692,6 +728,7 @@ def _handle_group_message(msg): def _handle_private_message(msg): """Process a private chat message.""" + global _CALL_SEQ if msg["type"] == "groupchat": return msg_id = msg.get("id", "") @@ -708,7 +745,13 @@ def _handle_private_message(msg): return if _check_shutup(body): 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: reply = _extract_response(raw) if reply: