From 81c6dd231494fd89b3da7681df9fc236b60f1c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Fri, 17 Jul 2026 23:13:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4deploy/profile-scripts?= =?UTF-8?q?=E4=B8=AD=E9=94=99=E6=94=BE=E7=9A=84bot=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/profile-scripts/xmpp_agent_core.py | 385 ---------------------- deploy/profile-scripts/xmpp_zhiwei_bot.py | 25 -- 2 files changed, 410 deletions(-) delete mode 100644 deploy/profile-scripts/xmpp_agent_core.py delete mode 100644 deploy/profile-scripts/xmpp_zhiwei_bot.py diff --git a/deploy/profile-scripts/xmpp_agent_core.py b/deploy/profile-scripts/xmpp_agent_core.py deleted file mode 100644 index e5e6e8a4..00000000 --- a/deploy/profile-scripts/xmpp_agent_core.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env python3 -""" -Core XMPP Agent — shared logic for zhiwei / mohe / xxm bots. -Imports by xmpp_zhiwei_bot.py / xmpp_mohe_bot.py with --agent flag. -""" -import os, sys, json, time, logging, threading, traceback -from http.server import HTTPServer, BaseHTTPRequestHandler -from urllib.parse import urlparse, parse_qs -from hashlib import md5 - -import slixmpp -from slixmpp import JID -import asyncio - -# ── Per-agent configuration ── -PER_AGENT = { - "mohe": { - "jid": "mohe@yoin.fun", - "password": "hermes123", - "nick": "mohe", - "http_port": 5808, - "gateway_url": "http://localhost:8642/v1/chat/completions", - "gateway_api_key": "hermes123", - "session_id": "xmpp-mohe-v2", - "name_cn": "莫荷", - "mention": "@mohe/@莫荷", - }, - "zhiwei": { - "jid": "zhiwei@yoin.fun", - "password": "2nw4psra", - "nick": "zhiwei", - "http_port": 5805, - "gateway_url": "http://localhost:8643/v1/chat/completions", - "gateway_api_key": "hermes123", - "session_id": "xmpp-zhiwei-v3", - "name_cn": "知微", - "mention": "@知微/zhiwei", - }, -} -_DEFAULT_AGENT = "mohe" - -# ── Module-level config (populated by _apply_config after agent detection) ── -AGENT_NAME = "" -XMPP_JID = "" -XMPP_PASSWORD = "" -MUC_ROOM = "coregroup@conference.yoin.fun" -MUC_NICK = "" -AGENT_MENTION = "" -HTTP_PORT = 5808 -AGENT_NICK = "" -ACK_DELAY = 15 -GATEWAY_URL = "" -GATEWAY_API_KEY = "" -GATEWAY_SESSION_ID = "" -GATEWAY_DEADLINE_SECONDS = 180 -CALL_HERMES_TIMEOUT = 180 -FALLBACK_REPLY = "请稍等,我在处理..." - -# ── 全局队列 ── -_outbound_queue = [] -_outbound_lock = threading.Lock() -_inbound_queue = [] -_inbound_lock = threading.Lock() - -RECENT_SENT_MAX = 50 - - -def _rs(p): - """Parse agent arg from sys.argv, returns agent name string.""" - agent = _DEFAULT_AGENT - skip_next = False - for i, a in enumerate(sys.argv[1:]): - if skip_next: - skip_next = False - continue - if a.startswith('--agent='): - agent = a.split('=', 1)[1] - elif a == '--agent' and i + 1 < len(sys.argv[1:]): - agent = sys.argv[i + 2] - skip_next = True - return agent - - -agent, is_mohe = _rs(None), None - -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s %(levelname)s %(message)s', - stream=sys.stdout, -) -log = logging.getLogger('xmpp_agent') - - -def _apply_config(agent_name): - """Set module-level config variables from PER_AGENT dict + env overrides.""" - global AGENT_NAME, XMPP_JID, XMPP_PASSWORD, MUC_NICK, AGENT_MENTION - global HTTP_PORT, AGENT_NICK, GATEWAY_URL, GATEWAY_API_KEY, GATEWAY_SESSION_ID - cfg = PER_AGENT.get(agent_name, PER_AGENT.get(_DEFAULT_AGENT, {})) - AGENT_NAME = agent_name - XMPP_JID = os.environ.get('XMPP_JID', cfg.get('jid', '')) - XMPP_PASSWORD = os.environ.get('XMPP_PASSWORD', cfg.get('password', '')) - MUC_NICK = os.environ.get('MUC_NICK', cfg.get('nick', agent_name)) - AGENT_MENTION = os.environ.get('AGENT_MENTION', cfg.get('mention', '')) - HTTP_PORT = int(os.environ.get('HTTP_PORT', cfg.get('http_port', 5808))) - AGENT_NICK = os.environ.get('AGENT_NICK', cfg.get('nick', agent_name)) - GATEWAY_URL = os.environ.get('GATEWAY_URL', cfg.get('gateway_url', '')) - GATEWAY_API_KEY = os.environ.get('GATEWAY_API_KEY', cfg.get('gateway_api_key', '')) - GATEWAY_SESSION_ID = os.environ.get('GATEWAY_SESSION_ID', cfg.get('session_id', '')) - - -# ── Periodic ACK task ── -class AckManager: - def __init__(self): - self._active = {} - self._lock = threading.Lock() - - def start(self, session_id, to_jid, msg_body): - """Record an active LLM analysis and schedule the ACK.""" - with self._lock: - self._active[session_id] = { - 'to_jid': to_jid, - 'body': msg_body[:80], - 'started': time.time(), - 'acked': False, - } - - def ack(self, session_id): - with self._lock: - self._active.pop(session_id, None) - - def tick(self, bot): - now = time.time() - to_send = [] - with self._lock: - for sid, info in list(self._active.items()): - if not info['acked'] and now - info['started'] >= ACK_DELAY: - info['acked'] = True - to_send.append((info['to_jid'], FALLBACK_REPLY)) - for jid, msg in to_send: - try: - bot.send_message(mto=jid, mbody=msg, mtype='chat') - except Exception: - pass - - -ack_mgr = AckManager() - - -# ── Slixmpp Bot ── -class XmppAgent(slixmpp.ClientXMPP): - def __init__(self, jid, password, room, nick): - super().__init__(jid, password) - self._room = room - self._nick = nick - self._muc_joined = False - self._recent_sent = [] - self.add_event_handler('session_start', self.on_start) - self.add_event_handler('message', self.on_msg) - self.add_event_handler('disconnected', self.on_disconnect) - self.register_plugin('xep_0030') - self.register_plugin('xep_0045') - self.register_plugin('xep_0199') - - async def on_start(self, event): - self.send_presence() - await self.get_roster() - try: - await self.plugin['xep_0045'].join_muc(self._room, self._nick) - self._muc_joined = True - log.info(f"{AGENT_NAME} XMPP 就绪 (已加入 {self._room})") - except Exception as e: - log.error(f"{AGENT_NAME} MUC加入失败: {e}") - - def on_disconnect(self, event): - self._muc_joined = False - log.info(f"{AGENT_NAME} XMPP 断开") - # 自动重连:slixmpp 1.15.0 没有 auto_reconnect 属性,需手动 - try: - self.reconnect(wait=5.0, reason="断线自动重连") - except Exception as e: - log.warning(f"{AGENT_NAME} 重连失败: {e}") - - def on_msg(self, msg): - if msg['type'] in ('chat', 'groupchat'): - body = str(msg['body']).strip() - if not body: - return - log.info(f"📩 收到: from={msg['from']} type={msg['type']} body={body[:60]}") - if ('[executor]' in body and 'gateway_zhiwei' in body): - log.info(f"过滤 executor 消息: {body[:80]}...") - return - sender = str(msg['from']) - msg_type = msg['type'] - for s in self._recent_sent: - if body[:50] in s or s in body[:50]: - return - if msg_type == 'groupchat': - nick = sender.split('/')[-1] if '/' in sender else '' - if nick == AGENT_NICK: - return - mention_list = AGENT_MENTION.replace('@', '').split('/') - is_for_me = any(m in body for m in ['@' + m for m in mention_list] + mention_list) - if not is_for_me: - return - with _inbound_lock: - _inbound_queue.append((sender, body, msg_type)) - - def mark_sent(self, body: str): - self._recent_sent.append(body[:80]) - if len(self._recent_sent) > RECENT_SENT_MAX * 2: - self._recent_sent = self._recent_sent[-RECENT_SENT_MAX:] - - -# ── Deliver loop ── -def _deliver_loop(bot): - global _outbound_queue - while True: - try: - items = [] - with _outbound_lock: - items, _outbound_queue = _outbound_queue[:], [] - for target, text, msg_type in items: - try: - async def _send(to, body, mtype): - bot.send_message(mto=to, mbody=body, mtype=mtype) - asyncio.run(_send(target, text, msg_type)) - bot.mark_sent(text) - log.info(f" 已发送到 {target}: {text[:80]}") - except Exception as e: - log.error(f" 发送到 {target} 失败: {e}") - time.sleep(0.3) - except Exception as e: - log.error(f"_deliver_loop error: {e}") - time.sleep(1) - - -# ── Inbound processing loop ── -def _inbound_loop(bot): - global _inbound_queue - while True: - try: - time.sleep(0.2) - with _inbound_lock: - if not _inbound_queue: - continue - sender, body, msg_type = _inbound_queue.pop(0) - log.info(f"🔄 inbound处理: {body[:40]}") - ack_mgr.start(body[:40], sender, body) - reply = call_hermes(body) - ack_mgr.ack(body[:40]) - if reply: - with _outbound_lock: - _outbound_queue.append((sender, reply, 'chat')) - except Exception as e: - log.error(f"_inbound_loop error: {e}") - time.sleep(1) - - -# ── HTTP SendHandler ── -class SendHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_len = int(self.headers.get('Content-Length', 0)) - post_body = self.rfile.read(content_len) - try: - data = json.loads(post_body) - target = data.get('to', '') - text = data.get('body', '') - if not target or not text: - self.send_response(400) - self.end_headers() - self.wfile.write(b'{"error":"missing to or body"}') - return - if '修复失败' in text and 'gateway_zhiwei' in text: - self.send_response(200) - self.end_headers() - self.wfile.write(b'{"ok":true,"filtered":true}') - return - msg_type = data.get('type', 'chat') - if text: - _outbound_queue.append((target, text, msg_type)) - self.send_response(200) - self.end_headers() - self.wfile.write(b'{"ok":true}') - log.info(f"SendHandler: enqueued -> {target}: {text[:80]}") - except Exception as e: - traceback.print_exc() - self.send_response(500) - self.end_headers() - self.wfile.write(str({'error': str(e)}).encode()) - - def log_message(self, format, *args): - pass - - -def _run_http_server(): - server = HTTPServer(('127.0.0.1', HTTP_PORT), SendHandler) - log.info(f"HTTP SendHandler listening on 127.0.0.1:{HTTP_PORT}") - server.serve_forever() - - -# ── call_hermes ── -def call_hermes(content: str, session_id=None) -> str: - now_str = time.strftime("[%Y-%m-%d %H:%M %A]", time.localtime()) - timed_content = f"{now_str}\n{content}" - payload = { - 'model': 'hermes-agent', - 'messages': [ - {'role': 'user', 'content': timed_content}, - ], - 'stream': False, - } - if session_id is None: - session_id = GATEWAY_SESSION_ID - headers = { - 'Content-Type': 'application/json', - 'X-Hermes-Session-Id': session_id, - } - if GATEWAY_API_KEY: - headers['Authorization'] = f'Bearer {GATEWAY_API_KEY}' - - import urllib.request - data_bytes = json.dumps(payload).encode('utf-8') - req = urllib.request.Request(GATEWAY_URL, data=data_bytes, headers=headers, method='POST') - try: - resp = urllib.request.urlopen(req, timeout=CALL_HERMES_TIMEOUT) - resp_data = json.loads(resp.read().decode('utf-8')) - reply = '' - if 'choices' in resp_data and len(resp_data['choices']) > 0: - choice = resp_data['choices'][0] - if 'message' in choice and 'content' in choice['message']: - reply = choice['message']['content'] - elif 'delta' in choice and 'content' in choice['delta']: - reply = choice['delta']['content'] - if not reply: - reply = resp_data.get('response', '') - if not reply: - reply = str(resp_data) - return reply.strip() - except urllib.request.HTTPError as e: - err_body = e.read().decode('utf-8', errors='replace') - log.error(f"call_hermes HTTP {e.code}: {err_body[:200]}") - return '' - except Exception as e: - log.error(f"call_hermes error: {type(e).__name__}: {e}") - return '' - - -# ── ACK manager tick ── -def _ack_tick(bot): - while True: - try: - ack_mgr.tick(bot) - time.sleep(5) - except Exception: - time.sleep(5) - - -# ── Main ── -def main(): - global is_mohe - agent_name = _rs(None) - _apply_config(agent_name) - is_mohe = (agent_name == 'mohe') - - log.info(f"Starting XMPP Agent: {agent_name} (mohe={is_mohe})") - log.info(f" JID={XMPP_JID} HTTP_PORT={HTTP_PORT}") - log.info(f" GATEWAY={GATEWAY_URL}") - log.info(f" SESSION_ID={GATEWAY_SESSION_ID}") - - bot = XmppAgent(XMPP_JID, XMPP_PASSWORD, MUC_ROOM, MUC_NICK) - bot.connect(host='127.0.0.1', port=5222) - t_deliver = threading.Thread(target=_deliver_loop, args=(bot,), daemon=True) - t_deliver.start() - t_inbound = threading.Thread(target=_inbound_loop, args=(bot,), daemon=True) - t_inbound.start() - t_ack = threading.Thread(target=_ack_tick, args=(bot,), daemon=True) - t_ack.start() - t_http = threading.Thread(target=_run_http_server, daemon=True) - t_http.start() - - bot.loop.run_forever() - - -if __name__ == '__main__': - main() diff --git a/deploy/profile-scripts/xmpp_zhiwei_bot.py b/deploy/profile-scripts/xmpp_zhiwei_bot.py deleted file mode 100644 index 774812e5..00000000 --- a/deploy/profile-scripts/xmpp_zhiwei_bot.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -"""Wrapper for xmpp_agent_core.py --agent zhiwei""" -import sys, os, signal - -PID_FILE = "/tmp/xmpp_zhiwei_bot.pid" - -if os.path.exists(PID_FILE): - with open(PID_FILE) as f: - try: - old_pid = int(f.read().strip()) - os.kill(old_pid, 0) - print(f"xmpp_zhiwei_bot already running (PID {old_pid}), exiting.") - sys.exit(0) - except (ValueError, ProcessLookupError): - pass - -with open(PID_FILE, "w") as f: - f.write(str(os.getpid())) - -sys.argv = [sys.argv[0], '--agent', 'zhiwei'] -try: - exec(open(os.path.join(os.path.dirname(__file__), 'xmpp_agent_core.py')).read()) -finally: - if os.path.exists(PID_FILE): - os.remove(PID_FILE)