From 0190c0206462e3a9ecf4951b3075e021306c2c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Mon, 20 Jul 2026 08:50:29 +0800 Subject: [PATCH] chore: capture live morning state before session merge --- analyst-knowledge-log.md | 21 +- data/mofin.db-shm | Bin 0 -> 32768 bytes data/mofin.db-wal | 0 data/portfolio.json | 320 +++---- data/price_events.json | 10 + data/price_history.json | 624 ++++++++++++++ deploy/bot/xmpp_agent_core.py | 623 +++++++------- deploy/profile-scripts/batch_reassess.py | 793 ++++++++++-------- .../profile-scripts/premarket_full_review.py | 104 ++- .../profile-scripts/watchlist_12d_backfill.py | 13 + gateway/logs/health_check.log | 39 + gateway/logs/health_check_cron.log | 39 + gateway/logs/last_bot_restart.txt | 1 + gateway/logs/last_restart.txt | 2 +- gateway/logs/xmpp_health_log.jsonl | 142 ++++ gateway/logs/xmpp_messages.jsonl | 3 + gateway/temp/health_todos.jsonl | 1 + gateway/temp/last_health_check.json | 2 +- server.py | 66 ++ static/index.html | 242 +++++- xmpp_logger.py | 328 ++++++-- 21 files changed, 2409 insertions(+), 964 deletions(-) create mode 100644 data/mofin.db-shm create mode 100644 data/mofin.db-wal create mode 100644 deploy/profile-scripts/watchlist_12d_backfill.py create mode 100644 gateway/logs/last_bot_restart.txt diff --git a/analyst-knowledge-log.md b/analyst-knowledge-log.md index b7d42d0c..7756a285 100644 --- a/analyst-knowledge-log.md +++ b/analyst-knowledge-log.md @@ -290,4 +290,23 @@ cron prompt for 系统健康检查-开盘前 (job_id: 37c02f4d7df9) - 过期信号不再堆积 → 健康检查正常 - 消费者只处理时效内信号 → 最新信号优先 - 无信号时自动清理过期堆积 → 不积压 -- 区间说明格式改为: "→ 进入操作区间,但重评结果: {reason}" \ No newline at end of file +- 区间说明格式改为: "→ 进入操作区间,但重评结果: {reason}"### 2026-07-19 22:32 Dad资产更新 +- **来源**: Dad XMPP截图+文字确认 +- **现金**: 0→241,330 (Dad确认) +- **持仓**: 12只,与DB及截图一致(OCR确认) +- **总资产**: 783,321 CNY +- **仓位**: 69.19% +- **变更**: cash_log id=17, portfolio_summary已更新, portfolio.json已同步 +- **问题**: XMPP bot此前3次超时/丢图导致Dad截图未处理,已人工补偿 +### 2026-07-19 23:35 Dad重导holding.xls全量更新 +- **来源**: Dad从券商重导holding.xls(23:30更新) +- **持仓变化**: 12只→14只 + - 新增: 300308 中际旭创 (100股, MV 97,946) + - 新增: 300035 中科电气 (1400股, MV 16,912) + - 之前我OCR截图漏了这两只,原因是字幕混淆(中科电气与宁德时代代码相邻被合并,中际旭创在截图折叠区) +- **现金**: 241,330.80 (不变) +- **冻结**: 0.05 (不变) +- **总市值**: 655,231 (holding.xls当前价) +- **总资产**: 896,562 +- **仓位**: 73.08% +- **截图头部659,354与xls的655,231差异来源**: 截图是13:01价格,xls是23:30价格,中间价格波动导致 diff --git a/data/mofin.db-shm b/data/mofin.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3= 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 + super().__init__(AGENT_JID, cfg["password"]) + self.ready = asyncio.Event() + self._call_seq = 0 self._recent_sent = [] - self._reconnecting = False # 防递归重连 - self.add_event_handler('session_start', self.on_start) + self._muc_joined = False + global _xmpp_resource + + self.add_event_handler('session_bind', self.on_bind) + self.add_event_handler('session_start', self.on_session_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') + self.add_event_handler('connected', self.on_connected) - 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_connected(self, event): + self.ready.clear() + + def on_bind(self, event): + global _xmpp_resource + bound_jid = str(self.boundjid) + if "/" in bound_jid: + _xmpp_resource = bound_jid.split("/", 1)[1] + logging.info(f"XMPP resource captured: {_xmpp_resource}") + logging.info(f"JID set to: {bound_jid}") + + async def on_session_start(self, event): + self.send_presence() # 发送上线presence,否则收不到私聊消息 + self.plugin['xep_0045'].join_muc('coregroup@conference.yoin.fun', AGENT_NICK) + logging.info(f"✅ {AGENT_NAME} 加入群聊 coregroup") + self.ready.set() def on_disconnect(self, event): self._muc_joined = False - log.info(f"{AGENT_NAME} XMPP 断开") - if self._reconnecting: - log.warning(f"{AGENT_NAME} 已在重连中,跳过递归重连") - return - self._reconnecting = True - try: - self.reconnect(wait=5.0, reason="断线自动重连") - except Exception as e: - log.warning(f"{AGENT_NAME} 重连失败: {e}") - finally: - self._reconnecting = False + # ── 入站消息:先入队,异步处理 ── 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: @@ -208,184 +161,218 @@ class XmppAgent(slixmpp.ClientXMPP): 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)) + logging.info(f"💬 群聊(#{self._call_seq}): {body[:60]}") + else: + logging.info(f"📩 老爸(#{self._call_seq}): {body[:60]}") - 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:] + self._call_seq += 1 + # 入队,不直接调gateway + _inbound_queue.put_nowait({ + "seq": self._call_seq, + "content": body, + "sender": sender, + "is_group": (msg_type == 'groupchat'), + "session_id": SESSION_ID, + "ts": time.time(), + "bot_ref": self, # 保留bot引用用于回复 + }) + + async def call_hermes(self, content, sender, is_group=False, seq=None, session_id=None): + """调gateway处理消息,返回回复文本""" + msg_type = 'groupchat' if is_group else 'chat' + sid = session_id or SESSION_ID + try: + payload = json.dumps({ + "model": "hermes-agent", + "messages": [{"role": "user", "content": content}] + }).encode() + req = urllib.request.Request(GATEWAY, data=payload, method="POST") + req.add_header("Content-Type", "application/json") + req.add_header("Authorization", f"Bearer {API_KEY}") + req.add_header("X-Hermes-Session-Id", sid) + + loop = asyncio.get_event_loop() + result = await loop.run_in_executor(None, lambda: _opener.open(req, timeout=600)) + + data = json.loads(result.read()) + reply = data.get("choices", [{}])[0].get("message", {}).get("content", "") + return reply.strip() + except Exception as e: + logging.error(f"❌ {AGENT_NAME} gateway调用失败: {e}") + return None + + def send_reply(self, sender, reply, msg_type): + """通过bot发送回复""" + if not reply: + return + if reply.startswith('__SILENT__') or '__SILENT__' in reply: + logging.info(f"⏭️ {AGENT_NAME} 沉默,不发送") + return + for phrase in ['我沉默', '我不说', '不说了', '不回复', '不插嘴', '我闭嘴']: + if phrase in reply: + logging.info(f"⏭️ {AGENT_NAME} 宣布沉默,拦截") + return + + if msg_type == 'groupchat': + self.send_message(mto=sender, mbody=reply, mtype='groupchat') + else: + # 私聊:发到具体resource(Dad发消息时绑定的客户端) + self.send_message(mto=sender, mbody=reply, mtype='chat') + + sent_norm = reply[:100] + self._recent_sent.append(sent_norm) + if len(self._recent_sent) > 10: + self._recent_sent.pop(0) + logging.info(f"✅ {AGENT_NAME} 回复: {reply[:80]}") -# ── Deliver loop ── -def _deliver_loop(bot): - global _outbound_queue +# ── 入站消息处理循环(独立任务,崩了自动重启) ── +async def process_inbound(): + """从队列消费入站消息,调gateway处理,发回响应。 + 崩了会自动重启,不会丢消息(队列中的消息会等待下一轮处理)。""" + pending_tasks = set() 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) + item = await _inbound_queue.get() + bot = item["bot_ref"] + + # 创建处理任务,保留引用防止GC + task = asyncio.create_task(handle_one(item, bot)) + pending_tasks.add(task) + task.add_done_callback(pending_tasks.discard) + + except asyncio.CancelledError: + break except Exception as e: - log.error(f"_deliver_loop error: {e}") - time.sleep(1) + logging.error(f"❌ 入站处理循环异常(已恢复): {e}") + await asyncio.sleep(1) + continue -# ── 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') +async def handle_one(item, bot): + """处理单条入站消息""" 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 '' + reply = await bot.call_hermes( + item["content"], item["sender"], + is_group=item["is_group"], + seq=item["seq"], session_id=item["session_id"] + ) + if reply: + if seq := item.get("seq"): + if seq < bot._call_seq - 5: + return # 太旧的消息,跳过 + msg_type = 'groupchat' if item["is_group"] else 'chat' + bot.send_reply(item["sender"], reply, msg_type) + else: + logging.warning(f"⚠️ 消息#{item['seq']} gateway返回空,保留在队列") + # 放回队尾等重试(最多3次) + retry_count = item.get("retry", 0) + if retry_count < 3: + item["retry"] = retry_count + 1 + item["ts"] = time.time() + _inbound_queue.put_nowait(item) except Exception as e: - log.error(f"call_hermes error: {type(e).__name__}: {e}") - return '' + logging.error(f"❌ 处理消息#{item.get('seq','?')}异常: {e}") -# ── ACK manager tick ── -def _ack_tick(bot): +# ── 出站消息发送循环 ── +async def drain_outbound(bot): + """从_outbound_queue取消息发送。独立循环,崩了自动重启。""" while True: try: - ack_mgr.tick(bot) - time.sleep(5) - except Exception: - time.sleep(5) + await asyncio.sleep(0.5) + while _outbound_queue: + target, text, msg_type = _outbound_queue.pop(0) + try: + bot.send_message(mto=target, mbody=text, mtype=msg_type) + sent_norm = text.strip()[:100] + bot._recent_sent.append(sent_norm) + if len(bot._recent_sent) > 10: + bot._recent_sent.pop(0) + logging.info(f"📤 主动发送到 {target}: {text[:60]}") + except Exception as e: + logging.error(f"❌ 主动发送失败: {e}") + _outbound_queue.insert(0, (target, text, msg_type)) # 放回队首重试 + await asyncio.sleep(3) + break + except asyncio.CancelledError: + break + except Exception as e: + logging.error(f"❌ 出站循环异常(已恢复): {e}") + await asyncio.sleep(1) + continue -# ── Main ── -def main(): - global is_mohe - agent_name = _rs(None) - _apply_config(agent_name) - is_mohe = (agent_name == 'mohe') +# ── 主入口 ─────────────────────────────────────────────── +async def main(): + retry_delay = 1 + max_delay = 60 + while True: + bot = None + inbound_task = None + outbound_task = None + try: + bot = AgentBot() + bot.register_plugin('xep_0030') + bot.register_plugin('xep_0045') + bot.register_plugin('xep_0199') - 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.connect(host='127.0.0.1', port=5222) + await asyncio.wait_for(bot.ready.wait(), timeout=30) + logging.info(f"{AGENT_NAME} XMPP 就绪") + retry_delay = 1 - 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() + # 启动独立处理循环 + inbound_task = asyncio.create_task(process_inbound()) + outbound_task = asyncio.create_task(drain_outbound(bot)) - bot.loop.run_forever() + while True: + await asyncio.sleep(15) + if not bot.is_connected(): + logging.warning("检测到断线,准备重连...") + break + # XMPP心跳检测:如果run_filters任务挂了,is_connected可能仍为True + try: + ping_ok = await asyncio.wait_for( + bot.plugin['xep_0199'].send_ping(AGENT_JID, timeout=5), + timeout=8 + ) + if not ping_ok: + logging.warning("XMPP心跳超时(XEP-0199),准备重连...") + break + except asyncio.TimeoutError: + logging.warning("XMPP心跳超时,准备重连...") + break + except Exception: + # xep_0199可能因run_filters已死而抛出异常 + logging.warning("XMPP心跳异常(run_filters可能已死),准备重连...") + break + except asyncio.TimeoutError: + logging.warning("连接超时,准备重连...") + except Exception as e: + logging.error(f"❌ 主循环错误: {e}") + finally: + # 取消任务但不丢队列 + for t in [inbound_task, outbound_task]: + if t and not t.done(): + t.cancel() + + if bot: + try: + bot.disconnect() + except: + pass + + # 等待旧session完全释放,防止两个bot抢资源 + await asyncio.sleep(2) + + logging.info(f"⏳ 等待 {retry_delay} 秒后重连...") + await asyncio.sleep(retry_delay) + retry_delay = min(retry_delay * 2, max_delay) if __name__ == '__main__': - main() + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass diff --git a/deploy/profile-scripts/batch_reassess.py b/deploy/profile-scripts/batch_reassess.py index 196dab1b..4a4f52c2 100644 --- a/deploy/profile-scripts/batch_reassess.py +++ b/deploy/profile-scripts/batch_reassess.py @@ -1,369 +1,424 @@ -#!/usr/bin/env python3 -"""batch_reassess.py — 批量补全九维分析(逐只处理,间隔防限流) - -用法: python3 batch_reassess.py [--all] [--code XXXXXX] - -流程:收集最新数据 → 调LLM(gateway)写九维分析+策略 → 保存到DB -""" -import sys, json, subprocess, sqlite3, re, time -from datetime import datetime - -DB = "/home/hmo/MoFin/data/mofin.db" -GATEWAY = "http://127.0.0.1:8643/v1/chat/completions" -COOLDOWN_HOURS = 1 - -def has_llm_analysis(code): - """检查是否为LLM生成的九维分析(>500字)""" - conn = sqlite3.connect(DB) - r = conn.execute("SELECT LENGTH(full_analysis) FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() - conn.close() - return r and r[0] and r[0] > 500 - -def in_cooldown(code): - """冷却期检查""" - conn = sqlite3.connect(DB) - r = conn.execute("SELECT reassessed_at FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() - conn.close() - if not r or not r[0]: - return False - try: - last = datetime.fromisoformat(r[0]) - diff = (datetime.now() - last).total_seconds() / 3600 - return diff < COOLDOWN_HOURS - except: - return False - -def collect_data(code): - """收集最新数据""" - data = {"code": code} - - # 从DB读策略 - conn = sqlite3.connect(DB) - r = conn.execute("SELECT name, entry_low, entry_high, stop_loss, take_profit, timing_signal, action, rr_ratio, tech_snapshot, sector_context, stock_category FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() - if r: - data["name"] = r[0] - data["entry_low"] = r[1] or 0 - data["entry_high"] = r[2] or 0 - data["stop_loss"] = r[3] or 0 - data["take_profit"] = r[4] or 0 - data["timing_signal"] = r[5] or "" - data["action"] = r[6] or "" - data["rr_ratio"] = r[7] or 0 - data["tech_snapshot"] = r[8] or "" - data["sector_context"] = r[9] or "" - data["stock_category"] = r[10] or "" - conn.close() - - # 从腾讯API拉最新价和基本面 - prefix = "sh" if str(code).startswith(("6","9")) else "sz" - try: - r = subprocess.run(["curl", "-s", f"http://qt.gtimg.cn/q={prefix}{code}"], capture_output=True, timeout=10) - parts = r.stdout.decode("gbk", errors="ignore").split("~") - data["price"] = float(parts[3]) if len(parts) > 3 and parts[3] else 0 - data["pe"] = parts[39] if len(parts) > 39 and parts[39] else "" - data["mcap"] = parts[44] if len(parts) > 44 and parts[44] else "" - data["change_pct"] = parts[32] if len(parts) > 32 and parts[32] else "0" - except: - data["price"] = 0 - - # 大盘 - try: - conn = sqlite3.connect(DB) - mr = conn.execute("SELECT structure FROM macro_context_log ORDER BY id DESC LIMIT 1").fetchone() - if mr and mr[0]: - s = json.loads(mr[0]) - data["macro"] = s.get("description", "大盘震荡") - conn.close() - except: - data["macro"] = "大盘震荡" - - return data - -def build_prompt(data): - """构建LLM prompt,要求输出完整策略""" - cash = 321271 # 可用现金(从DB读取) - total = 952879 # 总资产 - - # 拉取资金流数据 - _flow_note = "暂无资金流数据" - try: - import sqlite3 as _sq, json as _j - _db = _sq.connect("/home/hmo/MoFin/data/mofin.db") - _fr = _db.execute("SELECT cache_json FROM capital_flow_cache ORDER BY id DESC LIMIT 1").fetchone() - if _fr and _fr[0]: - _fc = _j.loads(_fr[0]) - _stocks = _fc.get("stocks", {}) - _s = _stocks.get(data['code'], {}) - if _s and _s.get("analysis"): - _a = _s["analysis"] - _net = _a.get("net_flow", 0) - _main = _a.get("main_force", 0) - _retail = _a.get("retail_flow", 0) - _trend = _a.get("trend", "中性") - _flow_note = f"净流入{_net:.0f}万 主力{_main:.0f}万 散户{_retail:.0f}万 趋势{_trend}" - _db.close() - except: - pass - - # 拉取近期消息面 - _news_note = "暂无近期消息" - try: - import sqlite3 as _sq - _db = _sq.connect("/home/hmo/MoFin/data/mofin.db") - _nr = _db.execute( - "SELECT summary, overall_sentiment, created_at FROM signal_news " - "WHERE (code=? OR sector LIKE ?) AND overall_sentiment IN ('利好','利空') " - "ORDER BY id DESC LIMIT 3", - (data['code'], f'%{data.get("name","")[:4]}%') - ).fetchall() - if _nr: - _news_note = " | ".join([f"{r[2][:10]} {r[1]} {r[0][:40]}" for r in _nr]) - _db.close() - except: - pass - - return f"""你是一个资深A股分析师。请对{data['code']} {data.get('name','')}做一个完整的九维矩阵分析,并输出策略参数。 - -⚠️ 重要:以下9个维度不是独立分析的,你必须交叉对比后给出综合结论。 -例如:如果消息面利好但资金流在流出,说明利好可能是出货;如果基本面强但技术面破位,说明估值可能还没到底。 - -当前数据(以下数据均来自实时API,每条标注时间窗口,禁止使用模型内部训练数据): -大盘:{data.get('macro','震荡')}(当日实时) -最新价:{data.get('price',0)} 涨跌:{data.get('change_pct','0')}%(当日实时) -PE={data.get('pe','?')}(最新财报) 市值={data.get('mcap','?')}亿 -行业:{data.get('sector_context','?')}(当日实时) -技术面:{data.get('tech_snapshot','')[:300]}(MA=5/10/20/60日 支撑阻力=近20日 量价=当日+近5日趋势) -资金流:{_flow_note}(近5日累计) -消息面:{_news_note}(最近3条,自动标注抓取时间) -当前信号:{data.get('timing_signal','?')} 分类:{data.get('stock_category','?')} -原策略:{(data.get('action','') or '')[:200]} - -我的总资产={total}元,可用现金={cash}元。 - -请严格按以下格式输出: - -【交叉分析】用2-3句话说明哪些维度出现矛盾/共振,最关键的信号是什么 -① 大盘×基本面 [一句话,说明矛盾关系] -② 大盘×消息面 [一句话] -③ 大盘×技术面 [一句话] -④ 大盘×资金面 [一句话] -⑤ 行业×基本面 [一句话] -⑥ 行业×消息面 [一句话] -⑦ 行业×技术面 [一句话] -⑧ 行业×资金面 [一句话] -⑨ 个股×基本面 [一句话] -⑩ 个股×消息面 [一句话] -⑪ 个股×技术面 [一句话] -⑫ 个股×资金面 [一句话] - -【综合结论】(买入/关注/观望/卖出) -【操作建议】具体操作建议 -【买入区间】最低价~最高价 -【建议止损】数字 -【建议止盈】数字 - -【建议仓位】只有综合结论为"买入"时才输出此项。仓位计算公式: -基础仓位按RR确定:RR<1.5→不推荐,RR1.5~3→8%,RR3~5→12%,RR5+→15% -大盘偏弱×0.8,大盘偏强×1.15 -蓝筹/白马×1.2,成长×0.85,题材/短线×0.6 -最终仓位范围:5%~20% -同时考虑:现金{cash}元足够买多少手。 -输出格式:"X%(理由:一句话说明为什么这个仓位)""" -def parse_response(text): - """从LLM回复中提取策略参数""" - result = {"signal": "", "entry_low": 0, "entry_high": 0, "stop_loss": 0, "take_profit": 0, "position": ""} - - # 信号 - sl = [l for l in text.split("\n") if "综合结论" in l] - if sl: - for kw in ["买入","关注","观望","卖出"]: - if kw in sl[0]: - result["signal"] = kw - break - - # 买入区间 - zl = [l for l in text.split("\n") if "买入区间" in l] - if zl: - nums = re.findall(r'[\d.]+', zl[0]) - if len(nums) >= 2: - result["entry_low"] = float(nums[0]) - result["entry_high"] = float(nums[1]) - - # 止损 - for l in text.split("\n"): - if "建议止损" in l: - nums = re.findall(r'[\d.]+', l) - if nums: result["stop_loss"] = float(nums[0]) - - # 止盈 - for l in text.split("\n"): - if "建议止盈" in l: - nums = re.findall(r'[\d.]+', l) - if nums: result["take_profit"] = float(nums[0]) - - # 仓位:只有买入信号才需要,提取百分比数字 - result["position"] = "" - if result["signal"] == "买入": - for l in text.split("\n"): - if "建议仓位" in l: - nums = re.findall(r'[\d.]+', l) - for n in nums: - f = float(n) - if 1 <= f <= 30: # 合理的仓位范围 - result["position"] = f"{f:.0f}%" - break - break - - return result - -def save_result(code, full_text, parsed): - """保存LLM结果到DB""" - conn = sqlite3.connect(DB) - now = datetime.now().isoformat() - - updates = ["full_analysis=?", "reassessed_at=?"] - params = [full_text, now] - - if parsed["signal"]: - updates.append("timing_signal=?") - params.append(parsed["signal"]) - if parsed["entry_low"] > 0: - updates.append("entry_low=?") - params.append(parsed["entry_low"]) - if parsed["entry_high"] > 0: - updates.append("entry_high=?") - params.append(parsed["entry_high"]) - if parsed["stop_loss"] > 0: - updates.append("stop_loss=?") - params.append(parsed["stop_loss"]) - if parsed["take_profit"] > 0: - updates.append("take_profit=?") - params.append(parsed["take_profit"]) - if parsed["position"]: - updates.append("position_advice=?") - params.append(parsed["position"]) - - params.append(code) - sql = f"UPDATE holding_strategies SET {', '.join(updates)} WHERE code=? AND status='active'" - conn.execute(sql, params) - conn.commit() - - # 买入信号→推XMPP通知(在conn close前执行) - if parsed.get("signal") == "买入": - try: - _nr = conn.execute("SELECT name, price FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() - _name = _nr[0] if _nr else code - _p = _nr[1] if _nr else 0 - _el = parsed.get("entry_low", 0) - _eh = parsed.get("entry_high", 0) - _sl = parsed.get("stop_loss", 0) - _tp = parsed.get("take_profit", 0) - _pos = parsed.get("position", "") - _msg = f"📈 {_name}({code}) 价{_p}→12维分析生成买入信号!区间{_el}~{_eh} 损{_sl} 盈{_tp} 仓位{_pos}" - import urllib.request, json as _jj - _req = urllib.request.Request("http://127.0.0.1:5805/", - data=_jj.dumps({"body": _msg, "to": "hmo@yoin.fun", "type": "chat"}).encode(), - headers={"Content-Type": "application/json"}) - urllib.request.urlopen(_req, timeout=5) - print(f" 📨 XMPP推送成功: {_msg[:60]}") - except Exception as _e: - print(f" ⚠️ XMPP推送失败: {_e}") - - conn.close() - -def process_stock(code): - """处理单只股票""" - print(f"\n{'='*50}") - print(f"处理: {code}") - print(f"{'='*50}") - - if has_llm_analysis(code): - print(f" ⏭ 已有LLM九维分析,跳过") - return False - - if in_cooldown(code): - print(f" ⏭ 冷却期内,跳过") - return False - - print(f" 收集数据...", flush=True) - data = collect_data(code) - if not data.get("price"): - print(f" ⚠️ 无价格数据,跳过") - return False - - print(f" 调LLM生成九维分析...", flush=True) - prompt = build_prompt(data) - - try: - r = subprocess.run(["curl", "-s", "--max-time", "300", - "-H", "Content-Type: application/json", - "-H", "Authorization: Bearer hermes123", - "-d", json.dumps({"model":"deepseek-v4-flash","messages":[{"role":"user","content":prompt}],"max_tokens":2048}), - GATEWAY], capture_output=True, timeout=310) - - if r.returncode != 0: - print(f" ❌ curl失败: {r.stderr.decode()[:100]}") - return False - - resp = json.loads(r.stdout) - if "choices" not in resp: - print(f" ❌ API异常: {str(resp)[:200]}") - return False - - full_text = resp["choices"][0]["message"]["content"] - print(f" ✅ LLM返回({len(full_text)}字)", flush=True) - - parsed = parse_response(full_text) - print(f" 信号={parsed['signal']} 区间={parsed['entry_low']}~{parsed['entry_high']} 损={parsed['stop_loss']} 盈={parsed['take_profit']} 仓位={parsed['position']}") - - save_result(code, full_text, parsed) - print(f" ✅ 已保存到DB") - return True - - except subprocess.TimeoutExpired: - print(f" ❌ 超时") - return False - except Exception as e: - print(f" ❌ 错误: {e}") - return False - -def main(): - codes = [] - if "--code" in sys.argv: - idx = sys.argv.index("--code") - codes = [sys.argv[idx+1]] - else: - # 所有自选策略 - conn = sqlite3.connect(DB) - rows = conn.execute("SELECT code FROM holding_strategies WHERE status='active' AND decision_type='自选策略' ORDER BY code").fetchall() - conn.close() - codes = [r[0] for r in rows] - - print(f"待处理: {len(codes)}只") - - ok = 0 - fail = 0 - skip = 0 - for i, code in enumerate(codes): - if has_llm_analysis(code): - print(f" [{i+1}/{len(codes)}] ⏭ {code} 已有LLM分析") - skip += 1 - continue - - print(f" [{i+1}/{len(codes)}] ", end="", flush=True) - if process_stock(code): - ok += 1 - else: - fail += 1 - - # 间隔15秒(防gateway过载) - if i < len(codes) - 1: - print(f" 等待15秒...", flush=True) - time.sleep(15) - - print(f"\n{'='*50}") - print(f"完成: {ok}成功, {fail}失败, {skip}跳过") - print(f"{'='*50}") - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""batch_reassess.py — 批量补全12维(九维矩阵)LLM分析(逐只处理,间隔防限流) + +用法: + python3 batch_reassess.py # 所有缺分析/过期的 active 策略 + python3 batch_reassess.py --type holding # 只处理持仓策略 + python3 batch_reassess.py --type watchlist # 只处理自选策略 + python3 batch_reassess.py --type holding --today # 持仓每日刷新(今早未评过的强制重评) + python3 batch_reassess.py --code XXXXXX # 单只 + +流程:收集最新数据 → 调LLM(gateway)写12维分析+策略 → 保存到DB +""" +import sys, json, subprocess, sqlite3, re, time +from datetime import datetime + +DB = "/home/hmo/MoFin/data/mofin.db" +GATEWAY = "http://127.0.0.1:8643/v1/chat/completions" +COOLDOWN_HOURS = 1 +STALE_HOURS = 20 # 分析超过20小时视为过期,需要重评 + +def has_llm_analysis(code): + """检查是否为LLM生成的12维分析(>500字)""" + conn = sqlite3.connect(DB) + r = conn.execute("SELECT LENGTH(full_analysis) FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() + conn.close() + return r and r[0] and r[0] > 500 + +def in_cooldown(code): + """冷却期检查""" + conn = sqlite3.connect(DB) + r = conn.execute("SELECT reassessed_at FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() + conn.close() + if not r or not r[0]: + return False + try: + last = datetime.fromisoformat(r[0]) + diff = (datetime.now() - last).total_seconds() / 3600 + return diff < COOLDOWN_HOURS + except: + return False + +def analysis_stale(code, force_today=False): + """分析是否过期(>STALE_HOURS 或 force_today 时今早4点前未重评)""" + conn = sqlite3.connect(DB) + r = conn.execute("SELECT reassessed_at FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() + conn.close() + if not r or not r[0]: + return True + try: + last = datetime.fromisoformat(r[0]) + if force_today: + today4am = datetime.now().replace(hour=4, minute=0, second=0, microsecond=0) + return last < today4am + return (datetime.now() - last).total_seconds() / 3600 > STALE_HOURS + except: + return True + +def get_portfolio(): + """从 portfolio_summary 读实时现金/总资产(不再硬编码)""" + try: + conn = sqlite3.connect(DB) + r = conn.execute("SELECT cash, total_assets FROM portfolio_summary WHERE id=1").fetchone() + conn.close() + if r and r[1]: + return int(r[0] or 0), int(r[1]) + except Exception: + pass + return 0, 0 + +def collect_data(code): + """收集最新数据""" + data = {"code": code} + + # 从DB读策略 + conn = sqlite3.connect(DB) + r = conn.execute("SELECT name, entry_low, entry_high, stop_loss, take_profit, timing_signal, action, rr_ratio, tech_snapshot, sector_context, stock_category FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() + if r: + data["name"] = r[0] + data["entry_low"] = r[1] or 0 + data["entry_high"] = r[2] or 0 + data["stop_loss"] = r[3] or 0 + data["take_profit"] = r[4] or 0 + data["timing_signal"] = r[5] or "" + data["action"] = r[6] or "" + data["rr_ratio"] = r[7] or 0 + data["tech_snapshot"] = r[8] or "" + data["sector_context"] = r[9] or "" + data["stock_category"] = r[10] or "" + conn.close() + + # 从腾讯API拉最新价和基本面 + # 代码前缀:5位=港股(hk),6/9开头=沪(sh),其他=深(sz) + _c = str(code) + if len(_c) == 5: + prefix = "hk" + elif _c.startswith(("6", "9")): + prefix = "sh" + else: + prefix = "sz" + try: + r = subprocess.run(["curl", "-s", f"http://qt.gtimg.cn/q={prefix}{code}"], capture_output=True, timeout=10) + parts = r.stdout.decode("gbk", errors="ignore").split("~") + data["price"] = float(parts[3]) if len(parts) > 3 and parts[3] else 0 + data["pe"] = parts[39] if len(parts) > 39 and parts[39] else "" + data["mcap"] = parts[44] if len(parts) > 44 and parts[44] else "" + data["change_pct"] = parts[32] if len(parts) > 32 and parts[32] else "0" + except: + data["price"] = 0 + + # 大盘 + try: + conn = sqlite3.connect(DB) + mr = conn.execute("SELECT structure FROM macro_context_log ORDER BY id DESC LIMIT 1").fetchone() + if mr and mr[0]: + s = json.loads(mr[0]) + data["macro"] = s.get("description", "大盘震荡") + conn.close() + except: + data["macro"] = "大盘震荡" + + return data + +def build_prompt(data): + """构建LLM prompt,要求输出完整策略""" + cash, total = get_portfolio() # 实时从 portfolio_summary 读 + if not total: + cash, total = 241330, 929727 # 兜底(DB读不到时) + + # 拉取资金流数据 + _flow_note = "暂无资金流数据" + try: + import sqlite3 as _sq, json as _j + _db = _sq.connect("/home/hmo/MoFin/data/mofin.db") + _fr = _db.execute("SELECT cache_json FROM capital_flow_cache ORDER BY id DESC LIMIT 1").fetchone() + if _fr and _fr[0]: + _fc = _j.loads(_fr[0]) + _stocks = _fc.get("stocks", {}) + _s = _stocks.get(data['code'], {}) + if _s and _s.get("analysis"): + _a = _s["analysis"] + _net = _a.get("net_flow", 0) + _main = _a.get("main_force", 0) + _retail = _a.get("retail_flow", 0) + _trend = _a.get("trend", "中性") + _flow_note = f"净流入{_net:.0f}万 主力{_main:.0f}万 散户{_retail:.0f}万 趋势{_trend}" + _db.close() + except: + pass + + # 拉取近期消息面 + _news_note = "暂无近期消息" + try: + import sqlite3 as _sq + _db = _sq.connect("/home/hmo/MoFin/data/mofin.db") + _nr = _db.execute( + "SELECT summary, overall_sentiment, created_at FROM signal_news " + "WHERE (code=? OR sector LIKE ?) AND overall_sentiment IN ('利好','利空') " + "ORDER BY id DESC LIMIT 3", + (data['code'], f'%{data.get("name","")[:4]}%') + ).fetchall() + if _nr: + _news_note = " | ".join([f"{r[2][:10]} {r[1]} {r[0][:40]}" for r in _nr]) + _db.close() + except: + pass + + return f"""你是一个资深A股分析师。请对{data['code']} {data.get('name','')}做一个完整的九维矩阵分析,并输出策略参数。 + +⚠️ 重要:以下9个维度不是独立分析的,你必须交叉对比后给出综合结论。 +例如:如果消息面利好但资金流在流出,说明利好可能是出货;如果基本面强但技术面破位,说明估值可能还没到底。 + +当前数据(以下数据均来自实时API,每条标注时间窗口,禁止使用模型内部训练数据): +大盘:{data.get('macro','震荡')}(当日实时) +最新价:{data.get('price',0)} 涨跌:{data.get('change_pct','0')}%(当日实时) +PE={data.get('pe','?')}(最新财报) 市值={data.get('mcap','?')}亿 +行业:{data.get('sector_context','?')}(当日实时) +技术面:{data.get('tech_snapshot','')[:300]}(MA=5/10/20/60日 支撑阻力=近20日 量价=当日+近5日趋势) +资金流:{_flow_note}(近5日累计) +消息面:{_news_note}(最近3条,自动标注抓取时间) +当前信号:{data.get('timing_signal','?')} 分类:{data.get('stock_category','?')} +原策略:{(data.get('action','') or '')[:200]} + +我的总资产={total}元,可用现金={cash}元。 + +请严格按以下格式输出: + +【交叉分析】用2-3句话说明哪些维度出现矛盾/共振,最关键的信号是什么 +① 大盘×基本面 [一句话,说明矛盾关系] +② 大盘×消息面 [一句话] +③ 大盘×技术面 [一句话] +④ 大盘×资金面 [一句话] +⑤ 行业×基本面 [一句话] +⑥ 行业×消息面 [一句话] +⑦ 行业×技术面 [一句话] +⑧ 行业×资金面 [一句话] +⑨ 个股×基本面 [一句话] +⑩ 个股×消息面 [一句话] +⑪ 个股×技术面 [一句话] +⑫ 个股×资金面 [一句话] + +【综合结论】(买入/关注/观望/卖出) +【操作建议】具体操作建议 +【买入区间】最低价~最高价 +【建议止损】数字 +【建议止盈】数字 + +【建议仓位】只有综合结论为"买入"时才输出此项。仓位计算公式: +基础仓位按RR确定:RR<1.5→不推荐,RR1.5~3→8%,RR3~5→12%,RR5+→15% +大盘偏弱×0.8,大盘偏强×1.15 +蓝筹/白马×1.2,成长×0.85,题材/短线×0.6 +最终仓位范围:5%~20% +同时考虑:现金{cash}元足够买多少手。 +输出格式:"X%(理由:一句话说明为什么这个仓位)""" +def parse_response(text): + """从LLM回复中提取策略参数""" + result = {"signal": "", "entry_low": 0, "entry_high": 0, "stop_loss": 0, "take_profit": 0, "position": ""} + + # 信号 + sl = [l for l in text.split("\n") if "综合结论" in l] + if sl: + for kw in ["买入","关注","观望","卖出"]: + if kw in sl[0]: + result["signal"] = kw + break + + # 买入区间 + zl = [l for l in text.split("\n") if "买入区间" in l] + if zl: + nums = re.findall(r'[\d.]+', zl[0]) + if len(nums) >= 2: + result["entry_low"] = float(nums[0]) + result["entry_high"] = float(nums[1]) + + # 止损 + for l in text.split("\n"): + if "建议止损" in l: + nums = re.findall(r'[\d.]+', l) + if nums: result["stop_loss"] = float(nums[0]) + + # 止盈 + for l in text.split("\n"): + if "建议止盈" in l: + nums = re.findall(r'[\d.]+', l) + if nums: result["take_profit"] = float(nums[0]) + + # 仓位:只有买入信号才需要,提取百分比数字 + result["position"] = "" + if result["signal"] == "买入": + for l in text.split("\n"): + if "建议仓位" in l: + nums = re.findall(r'[\d.]+', l) + for n in nums: + f = float(n) + if 1 <= f <= 30: # 合理的仓位范围 + result["position"] = f"{f:.0f}%" + break + break + + return result + +def save_result(code, full_text, parsed): + """保存LLM结果到DB""" + conn = sqlite3.connect(DB) + now = datetime.now().isoformat() + + updates = ["full_analysis=?", "reassessed_at=?"] + params = [full_text, now] + + if parsed["signal"]: + updates.append("timing_signal=?") + params.append(parsed["signal"]) + if parsed["entry_low"] > 0: + updates.append("entry_low=?") + params.append(parsed["entry_low"]) + if parsed["entry_high"] > 0: + updates.append("entry_high=?") + params.append(parsed["entry_high"]) + if parsed["stop_loss"] > 0: + updates.append("stop_loss=?") + params.append(parsed["stop_loss"]) + if parsed["take_profit"] > 0: + updates.append("take_profit=?") + params.append(parsed["take_profit"]) + if parsed["position"]: + updates.append("position_advice=?") + params.append(parsed["position"]) + + params.append(code) + sql = f"UPDATE holding_strategies SET {', '.join(updates)} WHERE code=? AND status='active'" + conn.execute(sql, params) + conn.commit() + + # 买入信号→推XMPP通知(在conn close前执行) + if parsed.get("signal") == "买入": + try: + _nr = conn.execute("SELECT name, price FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone() + _name = _nr[0] if _nr else code + _p = _nr[1] if _nr else 0 + _el = parsed.get("entry_low", 0) + _eh = parsed.get("entry_high", 0) + _sl = parsed.get("stop_loss", 0) + _tp = parsed.get("take_profit", 0) + _pos = parsed.get("position", "") + _msg = f"📈 {_name}({code}) 价{_p}→12维分析生成买入信号!区间{_el}~{_eh} 损{_sl} 盈{_tp} 仓位{_pos}" + import urllib.request, json as _jj + _req = urllib.request.Request("http://127.0.0.1:5805/", + data=_jj.dumps({"body": _msg, "to": "hmo@yoin.fun", "type": "chat"}).encode(), + headers={"Content-Type": "application/json"}) + urllib.request.urlopen(_req, timeout=5) + print(f" 📨 XMPP推送成功: {_msg[:60]}") + except Exception as _e: + print(f" ⚠️ XMPP推送失败: {_e}") + + conn.close() + +def process_stock(code, force_today=False): + """处理单只股票""" + print(f"\n{'='*50}") + print(f"处理: {code}") + print(f"{'='*50}") + + if in_cooldown(code): + print(f" ⏭ 冷却期内,跳过") + return False + + # 有分析且未过期 → 跳过(除非 force_today 且今早未评) + if has_llm_analysis(code) and not analysis_stale(code, force_today): + print(f" ⏭ 已有12维分析且未过期,跳过") + return False + + print(f" 收集数据...", flush=True) + data = collect_data(code) + if not data.get("price"): + print(f" ⚠️ 无价格数据,跳过") + return False + + print(f" 调LLM生成九维分析...", flush=True) + prompt = build_prompt(data) + + try: + r = subprocess.run(["curl", "-s", "--max-time", "300", + "-H", "Content-Type: application/json", + "-H", "Authorization: Bearer hermes123", + "-d", json.dumps({"model":"deepseek-v4-flash","messages":[{"role":"user","content":prompt}],"max_tokens":2048}), + GATEWAY], capture_output=True, timeout=310) + + if r.returncode != 0: + print(f" ❌ curl失败: {r.stderr.decode()[:100]}") + return False + + resp = json.loads(r.stdout) + if "choices" not in resp: + print(f" ❌ API异常: {str(resp)[:200]}") + return False + + full_text = resp["choices"][0]["message"]["content"] + print(f" ✅ LLM返回({len(full_text)}字)", flush=True) + + parsed = parse_response(full_text) + print(f" 信号={parsed['signal']} 区间={parsed['entry_low']}~{parsed['entry_high']} 损={parsed['stop_loss']} 盈={parsed['take_profit']} 仓位={parsed['position']}") + + save_result(code, full_text, parsed) + print(f" ✅ 已保存到DB") + return True + + except subprocess.TimeoutExpired: + print(f" ❌ 超时") + return False + except Exception as e: + print(f" ❌ 错误: {e}") + return False + +def main(): + codes = [] + force_today = "--today" in sys.argv + dtype = None + if "--type" in sys.argv: + idx = sys.argv.index("--type") + dtype = sys.argv[idx + 1] # holding | watchlist | all + if "--code" in sys.argv: + idx = sys.argv.index("--code") + codes = [sys.argv[idx+1]] + else: + # 按类型筛选 active 策略 + type_map = {"holding": "持仓策略", "watchlist": "自选策略"} + conn = sqlite3.connect(DB) + if dtype in type_map: + rows = conn.execute( + "SELECT code FROM holding_strategies WHERE status='active' AND decision_type=? ORDER BY code", + (type_map[dtype],)).fetchall() + else: + rows = conn.execute( + "SELECT code FROM holding_strategies WHERE status='active' ORDER BY decision_type, code").fetchall() + conn.close() + codes = [r[0] for r in rows] + + print(f"待处理: {len(codes)}只 (type={dtype or 'all'}, force_today={force_today})") + + ok = 0 + fail = 0 + skip = 0 + for i, code in enumerate(codes): + if has_llm_analysis(code) and not analysis_stale(code, force_today): + print(f" [{i+1}/{len(codes)}] ⏭ {code} 已有12维分析且未过期") + skip += 1 + continue + + print(f" [{i+1}/{len(codes)}] ", end="", flush=True) + if process_stock(code, force_today): + ok += 1 + else: + fail += 1 + + # 间隔15秒(防gateway过载) + if i < len(codes) - 1: + print(f" 等待15秒...", flush=True) + time.sleep(15) + + print(f"\n{'='*50}") + print(f"完成: {ok}成功, {fail}失败, {skip}跳过") + print(f"{'='*50}") + +if __name__ == "__main__": + main() diff --git a/deploy/profile-scripts/premarket_full_review.py b/deploy/profile-scripts/premarket_full_review.py index 1cebcd1c..e777a3f9 100644 --- a/deploy/profile-scripts/premarket_full_review.py +++ b/deploy/profile-scripts/premarket_full_review.py @@ -1,40 +1,64 @@ -#!/usr/bin/env python3 -"""premarket_full_review.py — 盘前全量重评 - -执行顺序: -1. regenerate_all() 全量技术分析重评(持仓+自选) -2. watchlist_auto_exit() 自选退出检查 -3. 输出摘要 - -调度:交易日 08:10(A股09:30开盘) -""" -import sys, os, json -sys.path.insert(0, '/home/hmo/MoFin') - -# Step 1: 全量重评 -print("=" * 50) -print("📊 盘前全量重评开始") -print("=" * 50) -from strategy_lifecycle import regenerate_all -result = regenerate_all(stdout=True) -print(f"\n重评完成: {result.get('ok',0)}/{result.get('total',0)}成功") - -# Step 2: 自选退出 -print("\n" + "=" * 50) -print("🔍 自选退出检查") -print("=" * 50) -from scripts.watchlist_auto_exit import main as auto_exit -exited = auto_exit(dry_run=False) - -# Step 3: 写入摘要供开盘简报引用 -summary = { - "premarket_at": __import__('datetime').datetime.now().isoformat(), - "reassess": result, - "auto_exit": [{"code": c, "name": n, "reason": r} for c, n, s, r in exited], - "total_kept": result.get('total', 0) - len(exited), -} -os.makedirs("/tmp/mofin_premarket", exist_ok=True) -with open("/tmp/mofin_premarket/summary.json", "w") as f: - json.dump(summary, f, ensure_ascii=False, indent=2) - -print(f"\n✅ 盘前重评完毕") +#!/usr/bin/env python3 +"""premarket_full_review.py — 盘前全量重评 + +执行顺序: +1. regenerate_all() 全量技术参数重评(持仓+自选) +2. batch_reassess.py --type holding --today 持仓12维LLM分析(每日强制刷新) +3. watchlist_auto_exit() 自选退出检查 +4. 输出摘要 + +调度:交易日 08:10(A股09:30开盘) +""" +import sys, os, json +sys.path.insert(0, '/home/hmo/MoFin') + +# Step 1: 全量技术参数重评 +print("=" * 50) +print("📊 盘前全量重评开始") +print("=" * 50) +from strategy_lifecycle import regenerate_all +result = regenerate_all(stdout=True) +print(f"\n重评完成: {result.get('ok',0)}/{result.get('total',0)}成功") + +# Step 1.5: 持仓 12 维 LLM 深度分析(每日强制,14只约8-10分钟) +print("\n" + "=" * 50) +print("🧠 持仓12维LLM分析(每日强制刷新)") +print("=" * 50) +import subprocess as _sp +analysis_result = {"ok": 0, "fail": 0, "skip": 0} +try: + r = _sp.run( + ["python3", "/home/hmo/.hermes/profiles/position-analyst/scripts/batch_reassess.py", + "--type", "holding", "--today"], + capture_output=True, text=True, timeout=3600) + print(r.stdout[-2000:] if len(r.stdout) > 2000 else r.stdout) + if r.returncode != 0 and r.stderr: + print(f"⚠️ stderr: {r.stderr[:300]}") + # 从输出尾部解析统计 + import re as _re + m = _re.search(r"完成: (\d+)成功, (\d+)失败, (\d+)跳过", r.stdout) + if m: + analysis_result = {"ok": int(m.group(1)), "fail": int(m.group(2)), "skip": int(m.group(3))} +except Exception as e: + print(f"⚠️ 12维分析步骤异常: {e}") + +# Step 2: 自选退出 +print("\n" + "=" * 50) +print("🔍 自选退出检查") +print("=" * 50) +from scripts.watchlist_auto_exit import main as auto_exit +exited = auto_exit(dry_run=False) + +# Step 3: 写入摘要供开盘简报引用 +summary = { + "premarket_at": __import__('datetime').datetime.now().isoformat(), + "reassess": result, + "llm_analysis_12d": analysis_result, + "auto_exit": [{"code": c, "name": n, "reason": r} for c, n, s, r in exited], + "total_kept": result.get('total', 0) - len(exited), +} +os.makedirs("/tmp/mofin_premarket", exist_ok=True) +with open("/tmp/mofin_premarket/summary.json", "w") as f: + json.dump(summary, f, ensure_ascii=False, indent=2) + +print(f"\n✅ 盘前重评完毕") diff --git a/deploy/profile-scripts/watchlist_12d_backfill.py b/deploy/profile-scripts/watchlist_12d_backfill.py new file mode 100644 index 00000000..b95a9ad9 --- /dev/null +++ b/deploy/profile-scripts/watchlist_12d_backfill.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""watchlist_12d_backfill.py — 自选12维LLM分析补全(每日午间) + +包装 batch_reassess.py --type watchlist,供 hermes cron 调度(不支持参数)。 +调度:交易日 12:30(午间低峰,自选补全约80分钟) +""" +import subprocess, sys + +r = subprocess.run( + [sys.executable, "/home/hmo/MoFin/deploy/profile-scripts/batch_reassess.py", + "--type", "watchlist"], + timeout=7200) +sys.exit(r.returncode) \ No newline at end of file diff --git a/gateway/logs/health_check.log b/gateway/logs/health_check.log index 5de28a2e..3adc0f78 100644 --- a/gateway/logs/health_check.log +++ b/gateway/logs/health_check.log @@ -88,3 +88,42 @@ [2026-07-19 20:40:01] Gateway DOWN → systemctl restart triggered (async, cooldown set) [2026-07-19 20:40:01] ISSUES: 1 failed - 知微 Gateway: timed out +[2026-07-19 22:30:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restarted"} +[2026-07-19 22:35:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restarted"} +[2026-07-19 22:40:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restarted"} +[2026-07-19 22:45:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restarted"} +[2026-07-19 22:50:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restarted"} +[2026-07-19 23:35:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restarted"} +[2026-07-19 23:40:01] AUTO-HEAL actions: + {"action": "bot_busy_not_stuck", "inbound_age_sec": 313, "reason": "inbound < 10min ago — LLM still processing, no restart"} +[2026-07-19 23:45:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restart triggered (async)"} +[2026-07-19 23:50:01] AUTO-HEAL actions: + {"action": "bot_busy_not_stuck", "inbound_age_sec": 130, "reason": "inbound < 10min ago — LLM still processing, no restart"} +[2026-07-19 23:55:01] AUTO-HEAL actions: + {"action": "bot_busy_not_stuck", "inbound_age_sec": 446, "reason": "inbound < 10min ago — LLM still processing, no restart"} +[2026-07-20 00:00:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restart triggered (async)"} +[2026-07-20 00:05:01] AUTO-HEAL actions: + {"action": "skip_bot_restart", "reason": "bot restart cooldown 331s < 600s"} +[2026-07-20 00:10:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restart triggered (async)"} +[2026-07-20 00:15:02] AUTO-HEAL actions: + {"action": "skip_bot_restart", "reason": "bot restart cooldown 284s < 600s"} +[2026-07-20 08:35:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restart triggered (async)"} +[2026-07-20 08:40:01] AUTO-HEAL actions: + {"action": "skip_bot_restart", "reason": "bot restart cooldown 299s < 600s"} +[2026-07-20 08:45:01] AUTO-HEAL actions: + {"action": "skip_bot_restart", "reason": "bot restart cooldown 599s < 600s"} +[2026-07-20 08:45:01] Gateway DOWN → systemctl restart triggered (async, cooldown set) +[2026-07-20 08:45:01] ISSUES: 1 failed + - 知微 Gateway: +[2026-07-20 08:50:01] AUTO-HEAL actions: + {"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": true, "detail": "restart triggered (async)"} diff --git a/gateway/logs/health_check_cron.log b/gateway/logs/health_check_cron.log index 2953d755..44205f59 100644 --- a/gateway/logs/health_check_cron.log +++ b/gateway/logs/health_check_cron.log @@ -100,3 +100,42 @@ NameError: name '_collect_xmpp_health' is not defined [20:40] Gateway DOWN → systemctl restart triggered [20:40] Health check: 1/4 services failed FAIL: 知微 Gateway (zhiwei_gateway) — timed out +[22:30] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[22:35] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[22:40] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[22:45] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[22:50] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[23:35] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[23:40] Auto-heal: 1 action(s) — degraded + → bot_busy_not_stuck: success=? +[23:45] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[23:50] Auto-heal: 1 action(s) — degraded + → bot_busy_not_stuck: success=? +[23:55] Auto-heal: 1 action(s) — degraded + → bot_busy_not_stuck: success=? +[00:00] Auto-heal: 1 action(s) — degraded + → restart_zhiwei_bot: success=True +[00:05] Auto-heal: 1 action(s) — critical + → skip_bot_restart: success=? +[00:10] Auto-heal: 1 action(s) — critical + → restart_zhiwei_bot: success=True +[00:15] Auto-heal: 1 action(s) — critical + → skip_bot_restart: success=? +[08:35] Auto-heal: 1 action(s) — critical + → restart_zhiwei_bot: success=True +[08:40] Auto-heal: 1 action(s) — critical + → skip_bot_restart: success=? +[08:45] Auto-heal: 1 action(s) — ok + → skip_bot_restart: success=? +[08:45] Gateway DOWN → systemctl restart triggered +[08:45] Health check: 1/4 services failed + FAIL: 知微 Gateway (zhiwei_gateway) — +[08:50] Auto-heal: 1 action(s) — ok + → restart_zhiwei_bot: success=True diff --git a/gateway/logs/last_bot_restart.txt b/gateway/logs/last_bot_restart.txt new file mode 100644 index 00000000..66aba8ff --- /dev/null +++ b/gateway/logs/last_bot_restart.txt @@ -0,0 +1 @@ +1784508601.5944698 \ No newline at end of file diff --git a/gateway/logs/last_restart.txt b/gateway/logs/last_restart.txt index 9ac47169..d046f029 100644 --- a/gateway/logs/last_restart.txt +++ b/gateway/logs/last_restart.txt @@ -1 +1 @@ -1784464840.6959908 \ No newline at end of file +1784508301.8403428 \ No newline at end of file diff --git a/gateway/logs/xmpp_health_log.jsonl b/gateway/logs/xmpp_health_log.jsonl index 0212c7f9..b234ab61 100644 --- a/gateway/logs/xmpp_health_log.jsonl +++ b/gateway/logs/xmpp_health_log.jsonl @@ -69,3 +69,145 @@ {"timestamp": "2026-07-19 20:50:01", "status": "critical", "last_message_age_sec": 15363, "error_rate_1h": 0, "bot_activity": {"error": "journalctl failed"}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8732, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 40217, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2469913, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} {"timestamp": "2026-07-19 20:55:01", "status": "critical", "last_message_age_sec": 15661, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8423, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 39908, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2469604, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} {"timestamp": "2026-07-19 21:00:01", "status": "critical", "last_message_age_sec": 15958, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8114, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 39599, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2469295, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:05:01", "status": "critical", "last_message_age_sec": 16259, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7806, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 39291, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2468987, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:10:01", "status": "critical", "last_message_age_sec": 16559, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7501, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 38986, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2468682, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:15:01", "status": "critical", "last_message_age_sec": 16858, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7501, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 38986, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2468682, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:20:02", "status": "critical", "last_message_age_sec": 17159, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7189, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 38674, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2468370, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:25:01", "status": "critical", "last_message_age_sec": 17459, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6878, "status": "ok", "usage_percent": 79}, "weekly": {"reset_in_sec": 38363, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2468059, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:30:01", "status": "critical", "last_message_age_sec": 17758, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:35:01", "status": "critical", "last_message_age_sec": 18059, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:40:01", "status": "critical", "last_message_age_sec": 18359, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:45:01", "status": "critical", "last_message_age_sec": 18658, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:50:01", "status": "critical", "last_message_age_sec": 18958, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 21:55:02", "status": "critical", "last_message_age_sec": 19259, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:00:01", "status": "critical", "last_message_age_sec": 19558, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:05:01", "status": "critical", "last_message_age_sec": 19859, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:10:01", "status": "critical", "last_message_age_sec": 20159, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:15:01", "status": "degraded", "last_message_age_sec": 20458, "error_rate_1h": 0, "bot_activity": {"inbound": 1, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 22:12:29,344 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=其实我的现金和持仓都发生了变化。但是之前给你发截图,你好像并没有回应", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 39543, "status": "ok", "usage_percent": 16.36}, "weekly": {"reset_in_sec": 626343, "status": "ok", "usage_percent": 3.27}, "monthly": {"reset_in_sec": 2543643, "status": "ok", "usage_percent": 0.69}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:20:01", "status": "degraded", "last_message_age_sec": 20758, "error_rate_1h": 0, "bot_activity": {"inbound": 1, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 22:12:29,344 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=其实我的现金和持仓都发生了变化。但是之前给你发截图,你好像并没有回应", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:25:01", "status": "degraded", "last_message_age_sec": 21059, "error_rate_1h": 0, "bot_activity": {"inbound": 3, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 22:24:46,921 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=上面截图是持仓。现金是241330cny", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:30:01", "status": "degraded", "last_message_age_sec": 21359, "error_rate_1h": 0, "bot_activity": {"inbound": 3, "outbound": 0, "errors": 2, "last_error": "2026-07-19 22:29:56,134 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 22:24:46,921 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=上面截图是持仓。现金是241330cny", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:35:01", "status": "degraded", "last_message_age_sec": 21658, "error_rate_1h": 0, "bot_activity": {"inbound": 3, "outbound": 0, "errors": 2, "last_error": "2026-07-19 22:29:56,134 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 22:24:46,921 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=上面截图是持仓。现金是241330cny", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:40:01", "status": "degraded", "last_message_age_sec": 21960, "error_rate_1h": 0, "bot_activity": {"inbound": 3, "outbound": 0, "errors": 2, "last_error": "2026-07-19 22:29:56,134 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 22:24:46,921 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=上面截图是持仓。现金是241330cny", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:45:01", "status": "degraded", "last_message_age_sec": 22259, "error_rate_1h": 0, "bot_activity": {"inbound": 2, "outbound": 0, "errors": 2, "last_error": "2026-07-19 22:29:56,134 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 22:24:46,921 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=上面截图是持仓。现金是241330cny", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:50:01", "status": "degraded", "last_message_age_sec": 22558, "error_rate_1h": 0, "bot_activity": {"inbound": 2, "outbound": 0, "errors": 2, "last_error": "2026-07-19 22:29:56,134 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 22:24:46,921 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=上面截图是持仓。现金是241330cny", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 22:55:01", "status": "degraded", "last_message_age_sec": 22859, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 2, "last_error": "2026-07-19 22:29:56,134 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:00:01", "status": "critical", "last_message_age_sec": 23159, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:05:01", "status": "critical", "last_message_age_sec": 23459, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 36349, "status": "ok", "usage_percent": 29.38}, "weekly": {"reset_in_sec": 623149, "status": "ok", "usage_percent": 5.96}, "monthly": {"reset_in_sec": 2540449, "status": "ok", "usage_percent": 1.23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:10:01", "status": "critical", "last_message_age_sec": 23759, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 33400, "status": "ok", "usage_percent": 36.79}, "weekly": {"reset_in_sec": 620200, "status": "ok", "usage_percent": 7.36}, "monthly": {"reset_in_sec": 2537500, "status": "ok", "usage_percent": 1.54}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:15:01", "status": "critical", "last_message_age_sec": 24059, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 33280, "status": "ok", "usage_percent": 37.36}, "weekly": {"reset_in_sec": 620080, "status": "ok", "usage_percent": 7.49}, "monthly": {"reset_in_sec": 2537380, "status": "ok", "usage_percent": 1.57}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:20:01", "status": "critical", "last_message_age_sec": 24359, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17997, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 31477, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2461173, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:25:01", "status": "critical", "last_message_age_sec": 24658, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17682, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 31162, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2460858, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:30:02", "status": "degraded", "last_message_age_sec": 24959, "error_rate_1h": 0, "bot_activity": {"inbound": 5, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 23:26:24,985 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=以上两张图是持仓和现金", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17367, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 30847, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2460543, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:35:01", "status": "degraded", "last_message_age_sec": 25259, "error_rate_1h": 0, "bot_activity": {"inbound": 7, "outbound": 0, "errors": 1, "last_error": "2026-07-19 23:34:07,470 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 23:34:59,820 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=上面的地址是windows的,246上实际的文件路径你应该知道", "last_outbound": null}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17045, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 30525, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2460221, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:40:01", "status": "degraded", "last_message_age_sec": 25559, "error_rate_1h": 0, "bot_activity": {"inbound": 7, "outbound": 0, "errors": 1, "last_error": "2026-07-19 23:34:07,470 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 23:34:59,820 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=上面的地址是windows的,246上实际的文件路径你应该知道", "last_outbound": null, "last_inbound_age_sec": 302, "last_outbound_age_sec": -1}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16736, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 30216, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2459912, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:45:01", "status": "degraded", "last_message_age_sec": 25858, "error_rate_1h": 0, "bot_activity": {"inbound": 7, "outbound": 0, "errors": 1, "last_error": "2026-07-19 23:34:07,470 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 23:34:59,820 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=上面的地址是windows的,246上实际的文件路径你应该知道", "last_outbound": null, "last_inbound_age_sec": 602, "last_outbound_age_sec": -1}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16426, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 29906, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2459602, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:50:01", "status": "degraded", "last_message_age_sec": 26159, "error_rate_1h": 0, "bot_activity": {"inbound": 9, "outbound": 0, "errors": 1, "last_error": "2026-07-19 23:34:07,470 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 23:48:13,262 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=没有变化?那如果你总市值是对的,加上我的现金,你怎么算出来的总资产才783k?", "last_outbound": null, "last_inbound_age_sec": 108, "last_outbound_age_sec": -1}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16116, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 29596, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2459292, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-19 23:55:01", "status": "degraded", "last_message_age_sec": 26459, "error_rate_1h": 0, "bot_activity": {"inbound": 9, "outbound": 0, "errors": 1, "last_error": "2026-07-19 23:34:07,470 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 23:48:13,262 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=没有变化?那如果你总市值是对的,加上我的现金,你怎么算出来的总资产才783k?", "last_outbound": null, "last_inbound_age_sec": 409, "last_outbound_age_sec": -1}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 15803, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 29283, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2458979, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:00:01", "status": "degraded", "last_message_age_sec": 26759, "error_rate_1h": 0, "bot_activity": {"inbound": 4, "outbound": 0, "errors": 1, "last_error": "2026-07-19 23:34:07,470 ERROR call_hermes error: TimeoutError: timed out", "last_inbound": "2026-07-19 23:48:13,262 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=没有变化?那如果你总市值是对的,加上我的现金,你怎么算出来的总资产才783k?", "last_outbound": null, "last_inbound_age_sec": 708, "last_outbound_age_sec": -1}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 15490, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 28970, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2458666, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:05:01", "status": "critical", "last_message_age_sec": 27059, "error_rate_1h": 0, "bot_activity": {"inbound": 2, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 23:48:13,262 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=没有变化?那如果你总市值是对的,加上我的现金,你怎么算出来的总资产才783k?", "last_outbound": null, "last_inbound_age_sec": 1008, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 15179, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 28659, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2458355, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:10:01", "status": "critical", "last_message_age_sec": 27359, "error_rate_1h": 0, "bot_activity": {"inbound": 2, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 23:48:13,262 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=没有变化?那如果你总市值是对的,加上我的现金,你怎么算出来的总资产才783k?", "last_outbound": null, "last_inbound_age_sec": 1308, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 14867, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 28347, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2458043, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:15:02", "status": "critical", "last_message_age_sec": 27662, "error_rate_1h": 0, "bot_activity": {"inbound": 2, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-19 23:48:13,262 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=没有变化?那如果你总市值是对的,加上我的现金,你怎么算出来的总资产才783k?", "last_outbound": null, "last_inbound_age_sec": 1612, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "fast"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 14552, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 28032, "status": "ok", "usage_percent": 45}, "monthly": {"reset_in_sec": 2457728, "status": "ok", "usage_percent": 22}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:20:01", "status": "critical", "last_message_age_sec": 27959, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.3s", "age_sec": 1, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 14239, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 27719, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2457415, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:25:01", "status": "critical", "last_message_age_sec": 28258, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.0s", "age_sec": 114, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 13930, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 27410, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2457106, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:30:01", "status": "critical", "last_message_age_sec": 28559, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.0s", "age_sec": 415, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 13620, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 27100, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2456796, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:35:01", "status": "critical", "last_message_age_sec": 28858, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.0s", "age_sec": 714, "source": "agent.log"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 46491, "status": "ok", "usage_percent": 2.38}, "weekly": {"reset_in_sec": 615291, "status": "ok", "usage_percent": 12.75}, "monthly": {"reset_in_sec": 2532591, "status": "ok", "usage_percent": 2.67}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:40:01", "status": "critical", "last_message_age_sec": 29159, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.0s", "age_sec": 1015, "source": "agent.log"}, "best_key": {"key_id": "key7", "label": "key7 (Kimi)", "masked": "sk-kimi-...Rt2T", "rolling": {"reset_in_sec": 46491, "status": "ok", "usage_percent": 2.38}, "weekly": {"reset_in_sec": 615291, "status": "ok", "usage_percent": 12.75}, "monthly": {"reset_in_sec": 2532591, "status": "ok", "usage_percent": 2.67}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:45:01", "status": "critical", "last_message_age_sec": 29459, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.0s", "age_sec": 1315, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 12689, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 26169, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2455865, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:50:01", "status": "critical", "last_message_age_sec": 29759, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.0s", "age_sec": 1614, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 12381, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 25861, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2455557, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 00:55:01", "status": "critical", "last_message_age_sec": 30059, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "unknown", "error": "no LLM calls in log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 12068, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 25548, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2455244, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:00:01", "status": "critical", "last_message_age_sec": 30359, "error_rate_1h": 0, "bot_activity": {"error": "journalctl failed"}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": false}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.0s", "age_sec": 46, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 11753, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 25233, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2454929, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:05:01", "status": "critical", "last_message_age_sec": 30659, "error_rate_1h": 0, "bot_activity": {"error": "journalctl failed"}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.0s", "age_sec": 347, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 11443, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 24923, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2454619, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:10:01", "status": "critical", "last_message_age_sec": 30959, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.0s", "age_sec": 647, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 11132, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 24612, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2454308, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:15:01", "status": "critical", "last_message_age_sec": 31259, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "7.1s", "age_sec": 272, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "3.1s", "age_sec": 252, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10822, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 24302, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2453998, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:20:01", "status": "critical", "last_message_age_sec": 31559, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "7.1s", "age_sec": 571, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "3.1s", "age_sec": 551, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10822, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 24302, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2453998, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:25:01", "status": "critical", "last_message_age_sec": 31859, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 221, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.4s", "age_sec": 242, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10512, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 23992, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2453688, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:30:01", "status": "critical", "last_message_age_sec": 32159, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 521, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.4s", "age_sec": 542, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10202, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 23682, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2453378, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:35:01", "status": "critical", "last_message_age_sec": 32459, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "6.1s", "age_sec": 139, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "5.4s", "age_sec": 14, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9894, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 23374, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2453070, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:40:01", "status": "critical", "last_message_age_sec": 32759, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "6.1s", "age_sec": 439, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "12.5s", "age_sec": 9, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9584, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 23064, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2452760, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:45:01", "status": "critical", "last_message_age_sec": 33059, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 58, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 203, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9276, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 22756, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2452452, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:50:01", "status": "critical", "last_message_age_sec": 33359, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 358, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 503, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8967, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 22447, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2452143, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 01:55:01", "status": "critical", "last_message_age_sec": 33659, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 658, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "3.0s", "age_sec": 275, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8659, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 22139, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2451835, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:00:01", "status": "critical", "last_message_age_sec": 33960, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 270, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "3.0s", "age_sec": 576, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8351, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 21831, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2451527, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:05:01", "status": "ok", "last_message_age_sec": 58, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 570, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "49.1s", "age_sec": 76, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8042, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 21522, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2451218, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:10:01", "status": "ok", "last_message_age_sec": 358, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.0s", "age_sec": 208, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "49.1s", "age_sec": 376, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7735, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 21215, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2450911, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:15:01", "status": "critical", "last_message_age_sec": 657, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.0s", "age_sec": 507, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "3.0s", "age_sec": 239, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7426, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 20906, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2450602, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:20:01", "status": "critical", "last_message_age_sec": 957, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "2.8s", "age_sec": 154, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "3.0s", "age_sec": 540, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7116, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 20596, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2450292, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:25:01", "status": "critical", "last_message_age_sec": 1258, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "2.8s", "age_sec": 454, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 244, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6809, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 20289, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2449985, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:30:01", "status": "critical", "last_message_age_sec": 1557, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.6s", "age_sec": 81, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 544, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6502, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 19982, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2449678, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:35:01", "status": "critical", "last_message_age_sec": 1856, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.6s", "age_sec": 380, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "50.5s", "age_sec": 129, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6195, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 19675, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2449371, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:40:01", "status": "critical", "last_message_age_sec": 2157, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.5s", "age_sec": 25, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "50.5s", "age_sec": 429, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5889, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 19369, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2449065, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:45:01", "status": "critical", "last_message_age_sec": 2456, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.5s", "age_sec": 325, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 239, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5582, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 19062, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2448758, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:50:02", "status": "critical", "last_message_age_sec": 2757, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.5s", "age_sec": 626, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 540, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5275, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 18755, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2448451, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 02:55:01", "status": "critical", "last_message_age_sec": 3057, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.2s", "age_sec": 254, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 210, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4968, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 18448, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2448144, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:00:01", "status": "critical", "last_message_age_sec": 3356, "error_rate_1h": 0.0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.2s", "age_sec": 554, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 510, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4661, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 18141, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2447837, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:05:01", "status": "critical", "last_message_age_sec": 3657, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 200, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "39.6s", "age_sec": 4, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4354, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 17834, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2447530, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:10:02", "status": "critical", "last_message_age_sec": 3957, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 501, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "20.8s", "age_sec": 241, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4047, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 17527, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2447223, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:15:01", "status": "critical", "last_message_age_sec": 4257, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.0s", "age_sec": 137, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.5s", "age_sec": 284, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 3740, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 17220, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2446916, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:20:01", "status": "critical", "last_message_age_sec": 4557, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.0s", "age_sec": 437, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.5s", "age_sec": 584, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 3433, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 16913, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2446609, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:25:01", "status": "critical", "last_message_age_sec": 4856, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.4s", "age_sec": 76, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.5s", "age_sec": 282, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 3127, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 16607, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2446303, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:30:01", "status": "critical", "last_message_age_sec": 5157, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.4s", "age_sec": 376, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.5s", "age_sec": 582, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 2820, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 16300, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2445996, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:35:01", "status": "critical", "last_message_age_sec": 5457, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 13, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "17.0s", "age_sec": 241, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 2514, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 15994, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2445690, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:40:01", "status": "critical", "last_message_age_sec": 5757, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 314, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "17.0s", "age_sec": 542, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 2207, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 15687, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2445383, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:45:02", "status": "critical", "last_message_age_sec": 6057, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 614, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "156.5s", "age_sec": 22, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 1900, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 15380, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2445076, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:50:01", "status": "critical", "last_message_age_sec": 6357, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.4s", "age_sec": 245, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "156.5s", "age_sec": 321, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 1594, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 15074, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2444770, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 03:55:01", "status": "critical", "last_message_age_sec": 6657, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.4s", "age_sec": 545, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "156.5s", "age_sec": 621, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 1287, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 14767, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2444463, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:00:02", "status": "critical", "last_message_age_sec": 6957, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.1s", "age_sec": 193, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "159.8s", "age_sec": 94, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 979, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 14459, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2444155, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:05:01", "status": "critical", "last_message_age_sec": 7257, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.1s", "age_sec": 492, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "159.8s", "age_sec": 393, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 672, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 14152, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2443848, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:10:01", "status": "critical", "last_message_age_sec": 7557, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 132, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "130.5s", "age_sec": 27, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 366, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 13846, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2443542, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:15:02", "status": "critical", "last_message_age_sec": 7857, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 433, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "29.3s", "age_sec": 145, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 59, "status": "ok", "usage_percent": 1}, "weekly": {"reset_in_sec": 13539, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2443235, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:20:01", "status": "critical", "last_message_age_sec": 8157, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 73, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "29.3s", "age_sec": 445, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17955, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 13232, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2442928, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:25:01", "status": "critical", "last_message_age_sec": 8457, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 372, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 265, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17649, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 12926, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2442622, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:30:01", "status": "critical", "last_message_age_sec": 8757, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 9, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 566, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17342, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 12619, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2442315, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:35:01", "status": "critical", "last_message_age_sec": 9057, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 308, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "7.5s", "age_sec": 249, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 17035, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 12312, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2442008, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:40:02", "status": "critical", "last_message_age_sec": 9357, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 609, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "7.5s", "age_sec": 550, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16728, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 12005, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2441701, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:45:01", "status": "critical", "last_message_age_sec": 9657, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 238, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 260, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16728, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 12005, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2441701, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:50:01", "status": "critical", "last_message_age_sec": 9956, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.6s", "age_sec": 538, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 560, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16422, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 11699, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2441395, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 04:55:01", "status": "critical", "last_message_age_sec": 10257, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 187, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 255, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 16115, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 11392, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2441088, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:00:01", "status": "critical", "last_message_age_sec": 10556, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 487, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 555, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 15808, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 11085, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2440781, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:05:01", "status": "critical", "last_message_age_sec": 10857, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.8s", "age_sec": 123, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "189.9s", "age_sec": 72, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 15502, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 10779, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2440475, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:10:01", "status": "critical", "last_message_age_sec": 11157, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.8s", "age_sec": 422, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "82.6s", "age_sec": 186, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 15195, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 10472, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2440168, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:15:01", "status": "critical", "last_message_age_sec": 11456, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 65, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 252, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 14888, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 10165, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2439861, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:20:01", "status": "critical", "last_message_age_sec": 11757, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 365, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 552, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 14581, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 9858, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2439554, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:25:01", "status": "critical", "last_message_age_sec": 12056, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.9s", "age_sec": 1, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 246, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 14274, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 9551, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2439247, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:30:01", "status": "critical", "last_message_age_sec": 12358, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.9s", "age_sec": 303, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 548, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 13967, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 9244, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2438940, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:35:01", "status": "critical", "last_message_age_sec": 12657, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.9s", "age_sec": 601, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "9.5s", "age_sec": 225, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 13660, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 8937, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2438633, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:40:01", "status": "critical", "last_message_age_sec": 12956, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 234, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "9.5s", "age_sec": 525, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 13354, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 8631, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2438327, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:45:02", "status": "critical", "last_message_age_sec": 13257, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 535, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.5s", "age_sec": 243, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 13047, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 8324, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2438020, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:50:01", "status": "critical", "last_message_age_sec": 13557, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 175, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.5s", "age_sec": 542, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 12740, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 8017, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2437713, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 05:55:01", "status": "critical", "last_message_age_sec": 13856, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 475, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 238, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 12434, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 7711, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2437407, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:00:01", "status": "critical", "last_message_age_sec": 14157, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 120, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 538, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 12127, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 7404, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2437100, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:05:01", "status": "critical", "last_message_age_sec": 14456, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.2s", "age_sec": 420, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "92.9s", "age_sec": 5, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 11820, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 7097, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2436793, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:10:01", "status": "critical", "last_message_age_sec": 14757, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "8.1s", "age_sec": 52, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "60.4s", "age_sec": 243, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 11513, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 6790, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2436486, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:15:01", "status": "critical", "last_message_age_sec": 15056, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "8.1s", "age_sec": 352, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "6.4s", "age_sec": 223, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 11206, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 6483, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2436179, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:20:02", "status": "critical", "last_message_age_sec": 15357, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.0s", "age_sec": 4, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "6.4s", "age_sec": 524, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10899, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 6176, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2435872, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:25:01", "status": "critical", "last_message_age_sec": 15657, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.4s", "age_sec": 296, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.6s", "age_sec": 231, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10592, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 5869, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2435565, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:30:01", "status": "critical", "last_message_age_sec": 15957, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.4s", "age_sec": 596, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.6s", "age_sec": 531, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 10286, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 5563, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2435259, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:35:01", "status": "critical", "last_message_age_sec": 16256, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "2.9s", "age_sec": 231, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "32.0s", "age_sec": 218, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9979, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 5256, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2434952, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:40:01", "status": "critical", "last_message_age_sec": 16557, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "2.9s", "age_sec": 531, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "32.0s", "age_sec": 518, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9672, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 4949, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2434645, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:45:01", "status": "critical", "last_message_age_sec": 16856, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.3s", "age_sec": 173, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 282, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9366, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 4643, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2434339, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:50:01", "status": "critical", "last_message_age_sec": 17157, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.3s", "age_sec": 473, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.8s", "age_sec": 582, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 9059, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 4336, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2434032, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 06:55:01", "status": "critical", "last_message_age_sec": 17457, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.0s", "age_sec": 111, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 286, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8752, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 4029, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2433725, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:00:01", "status": "critical", "last_message_age_sec": 17757, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.0s", "age_sec": 411, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.7s", "age_sec": 586, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8446, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 3723, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2433419, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:05:02", "status": "critical", "last_message_age_sec": 18057, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.1s", "age_sec": 54, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "138.3s", "age_sec": 35, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 8139, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 3416, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2433112, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:10:01", "status": "critical", "last_message_age_sec": 18357, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.1s", "age_sec": 353, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "92.0s", "age_sec": 242, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7833, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 3110, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2432806, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:15:01", "status": "critical", "last_message_age_sec": 18656, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "4.1s", "age_sec": 653, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "17.1s", "age_sec": 249, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7526, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 2803, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2432499, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:20:01", "status": "critical", "last_message_age_sec": 18957, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 292, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "17.1s", "age_sec": 550, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 7219, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 2496, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2432192, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:25:01", "status": "critical", "last_message_age_sec": 19257, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.1s", "age_sec": 591, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 279, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6913, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 2190, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2431886, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:30:02", "status": "critical", "last_message_age_sec": 19557, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 231, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.9s", "age_sec": 580, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6606, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 1883, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2431579, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:35:01", "status": "critical", "last_message_age_sec": 19857, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.5s", "age_sec": 530, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "64.9s", "age_sec": 95, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 6300, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 1577, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2431273, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:40:01", "status": "critical", "last_message_age_sec": 20157, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.0s", "age_sec": 168, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "64.9s", "age_sec": 396, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5993, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 1270, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2430966, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:45:01", "status": "critical", "last_message_age_sec": 20457, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.0s", "age_sec": 467, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.6s", "age_sec": 266, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5686, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 963, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2430659, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:50:01", "status": "critical", "last_message_age_sec": 20757, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "2.9s", "age_sec": 105, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "2.6s", "age_sec": 566, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5379, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 656, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2430352, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 07:55:01", "status": "critical", "last_message_age_sec": 21056, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "2.9s", "age_sec": 405, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "6.2s", "age_sec": 261, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 5073, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 350, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2430046, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:00:01", "status": "critical", "last_message_age_sec": 21357, "error_rate_1h": 0, "bot_activity": {"inbound": 0, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": null, "last_outbound": null, "last_inbound_age_sec": -1, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "3.0s", "age_sec": 44, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "6.2s", "age_sec": 562, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4766, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 43, "status": "ok", "usage_percent": 46}, "monthly": {"reset_in_sec": 2429739, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:05:01", "status": "critical", "last_message_age_sec": 21657, "error_rate_1h": 0, "bot_activity": {"inbound": 1, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:01:40,718 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=一会就要开盘了。你检查一下系统是不是正常。", "last_outbound": null, "last_inbound_age_sec": 201, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "6.8s", "age_sec": 12, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "214.2s", "age_sec": 60, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4460, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 604537, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2429433, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:10:01", "status": "critical", "last_message_age_sec": 21957, "error_rate_1h": 0, "bot_activity": {"inbound": 3, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:07:38,478 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=我说的是持仓和自选的股,每个交易日开盘前都应重评一次", "last_outbound": null, "last_inbound_age_sec": 143, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.9s", "age_sec": 61, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "211.9s", "age_sec": 151, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 4153, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 604230, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2429126, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:15:01", "status": "critical", "last_message_age_sec": 22257, "error_rate_1h": 0, "bot_activity": {"inbound": 5, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:12:32,867 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=> [08:10] [08:08] 盘前全量重评已完成,39/39 全部成功。**结果是:持仓14只全部持有状态,无买入", "last_outbound": null, "last_inbound_age_sec": 149, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.3s", "age_sec": 2, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "121.2s", "age_sec": 132, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 3846, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 603923, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2428819, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:20:01", "status": "critical", "last_message_age_sec": 22557, "error_rate_1h": 0, "bot_activity": {"inbound": 5, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:12:32,867 INFO 📩 收到: from=hmo@yoin.fun/Conversations.gJgBWWNBNE type=chat body=> [08:10] [08:08] 盘前全量重评已完成,39/39 全部成功。**结果是:持仓14只全部持有状态,无买入", "last_outbound": null, "last_inbound_age_sec": 449, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.3s", "age_sec": 204, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "121.2s", "age_sec": 432, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 3540, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 603617, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2428513, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:25:02", "status": "critical", "last_message_age_sec": 22857, "error_rate_1h": 0, "bot_activity": {"inbound": 6, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:22:14,972 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=我要的不是你手动的缝缝补补,我要的是系统能全功能就位。这样,我让笑笑来做这个改动。你这里别动", "last_outbound": null, "last_inbound_age_sec": 168, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.7s", "age_sec": 161, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "34.0s", "age_sec": 189, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 3233, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 603310, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2428206, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:30:02", "status": "critical", "last_message_age_sec": 23157, "error_rate_1h": 0, "bot_activity": {"inbound": 6, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:22:14,972 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=我要的不是你手动的缝缝补补,我要的是系统能全功能就位。这样,我让笑笑来做这个改动。你这里别动", "last_outbound": null, "last_inbound_age_sec": 468, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "5.7s", "age_sec": 461, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "34.0s", "age_sec": 489, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 2926, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 603003, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2427899, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:35:01", "status": "critical", "last_message_age_sec": 23457, "error_rate_1h": 0, "bot_activity": {"inbound": 5, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:22:14,972 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=我要的不是你手动的缝缝补补,我要的是系统能全功能就位。这样,我让笑笑来做这个改动。你这里别动", "last_outbound": null, "last_inbound_age_sec": 768, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "109.6s", "age_sec": 58, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "92.5s", "age_sec": 65, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 2926, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 603003, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2427899, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:40:01", "status": "critical", "last_message_age_sec": 23757, "error_rate_1h": 0, "bot_activity": {"inbound": 3, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:22:14,972 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=我要的不是你手动的缝缝补补,我要的是系统能全功能就位。这样,我让笑笑来做这个改动。你这里别动", "last_outbound": null, "last_inbound_age_sec": 1067, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "40.0s", "age_sec": 6, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "49.8s", "age_sec": 13, "source": "agent.log"}, "best_key": {"key_id": "key6", "label": "key6 (ycdennismo@163.com)", "masked": "sk-oF1WG...hCS3", "rolling": {"reset_in_sec": 2620, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 602697, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 2427593, "status": "ok", "usage_percent": 23}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:45:01", "status": "ok", "last_message_age_sec": 58, "error_rate_1h": 0.0, "bot_activity": {"inbound": 1, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:22:14,972 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=我要的不是你手动的缝缝补补,我要的是系统能全功能就位。这样,我让笑笑来做这个改动。你这里别动", "last_outbound": null, "last_inbound_age_sec": 1367, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": false}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "11.4s", "age_sec": 4, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "19.1s", "age_sec": 142, "source": "agent.log"}, "best_key": {"key_id": "key2", "label": "key2 (Google, hua65111@gmail.com)", "masked": "sk-5miR8...tGTB", "rolling": {"reset_in_sec": 18000, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 602394, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 1765184, "status": "ok", "usage_percent": 50}, "session_expired": false, "issues": [], "total_keys": 7}} +{"timestamp": "2026-07-20 08:50:01", "status": "ok", "last_message_age_sec": 357, "error_rate_1h": 0.0, "bot_activity": {"inbound": 1, "outbound": 0, "errors": 0, "last_error": null, "last_inbound": "2026-07-20 08:22:14,972 INFO 📩 收到: from=hmo@yoin.fun/gajim.02ZMOMIM type=chat body=我要的不是你手动的缝缝补补,我要的是系统能全功能就位。这样,我让笑笑来做这个改动。你这里别动", "last_outbound": null, "last_inbound_age_sec": 1667, "last_outbound_age_sec": -1, "last_error_age_sec": -1, "last_error_resolved": false}, "gateways": {"zhiwei": {"port": 8643, "alive": true}, "mohe": {"port": 8642, "alive": true}, "xiaoguo": {"port": 8645, "alive": true}}, "llm_provider": {"status": "ok", "latency": "20.8s", "age_sec": 13, "source": "agent.log"}, "llm_provider_default": {"status": "ok", "latency": "19.1s", "age_sec": 442, "source": "agent.log"}, "best_key": {"key_id": "key2", "label": "key2 (Google, hua65111@gmail.com)", "masked": "sk-5miR8...tGTB", "rolling": {"reset_in_sec": 18000, "status": "ok", "usage_percent": 0}, "weekly": {"reset_in_sec": 602088, "status": "ok", "usage_percent": 0}, "monthly": {"reset_in_sec": 1764878, "status": "ok", "usage_percent": 50}, "session_expired": false, "issues": [], "total_keys": 7}} diff --git a/gateway/logs/xmpp_messages.jsonl b/gateway/logs/xmpp_messages.jsonl index 3c2174c2..164144c8 100644 --- a/gateway/logs/xmpp_messages.jsonl +++ b/gateway/logs/xmpp_messages.jsonl @@ -1,3 +1,6 @@ {"timestamp": "2026-07-19 16:30:03", "direction": "out", "from": "zhiwei@yoin.fun", "to": "hmo@yoin.fun", "body_preview": "【每日汇总】今日以下cron报告未送达(已拦截): • 脚本输出 99次 无操作信号的报告正常静默,有操作信号的都已送达。", "status": "ok", "error": null, "latency_ms": 1436, "epoch": 1784449803.6249352} {"timestamp": "2026-07-19 16:32:02", "direction": "out", "from": "zhiwei@yoin.fun", "to": "hmo@yoin.fun", "body_preview": "【每日汇总】今日以下cron报告未送达(已拦截): • 脚本输出 99次 无操作信号的报告正常静默,有操作信号的都已送达。", "status": "ok", "error": null, "latency_ms": 401, "epoch": 1784449922.3636103} {"timestamp": "2026-07-19 16:34:02", "direction": "out", "from": "zhiwei@yoin.fun", "to": "hmo@yoin.fun", "body_preview": "【每日汇总】今日以下cron报告未送达(已拦截): • 脚本输出 99次 无操作信号的报告正常静默,有操作信号的都已送达。", "status": "ok", "error": null, "latency_ms": 430, "epoch": 1784450042.5418942} +{"timestamp": "2026-07-20 02:04:04", "direction": "out", "from": "zhiwei@yoin.fun", "to": "hmo@yoin.fun", "body_preview": "没有 accepted 提案,正常工作。", "status": "ok", "error": null, "latency_ms": 2424, "epoch": 1784484244.5065012} +{"timestamp": "2026-07-20 08:44:02", "direction": "out", "from": "zhiwei@yoin.fun", "to": "hmo@yoin.fun", "body_preview": "Now I have everything. Here's the report: --- 【⚡ 重点推荐操作】 ① 中芯国际(688981) 现价139.9 |仓位6.4%|止损131.6 技术面:弱,阻力155.5/支撑139(周五低) 操作:开盘若跳空破135减半仓,科创50续跌-7%大概率触发止损 ② 腾讯控股(00700) 现价461.6 |仓位6.1%|entry区间450-4", "status": "ok", "error": null, "latency_ms": 1185, "epoch": 1784508242.9271429} +{"timestamp": "2026-07-20 08:44:03", "direction": "out", "from": "zhiwei@yoin.fun", "to": "hmo@yoin.fun", "body_preview": "【⚠️宏观高风险】2026-07-20 08:32 — 全面系统性大跌进行中 整体风险:HIGH(90%置信度)— 确认全面系统性大跌 核心判断: 周一A股延续上周五暴跌,所有8个指数全数下跌超过2%。催化剂是美伊冲突进入第9晚且升级至打击核电站+能源供给中断风险。但存在有序吸收信号(黄金反跌/美股期货上涨/国家队增持),这不是纯恐慌场景。 --- 催化剂: 美军连续第九晚袭击伊朗,已打击", "status": "ok", "error": null, "latency_ms": 409, "epoch": 1784508243.488295} diff --git a/gateway/temp/health_todos.jsonl b/gateway/temp/health_todos.jsonl index 8c13a428..46a3b78d 100644 --- a/gateway/temp/health_todos.jsonl +++ b/gateway/temp/health_todos.jsonl @@ -41,3 +41,4 @@ {"service": "zhiwei_gateway", "label": "知微 Gateway", "reason": "", "timestamp": "2026-07-19T19:10:01.565114"} {"service": "zhiwei_gateway", "label": "知微 Gateway", "reason": "", "timestamp": "2026-07-19T19:30:01.210250"} {"service": "zhiwei_gateway", "label": "知微 Gateway", "reason": "timed out", "timestamp": "2026-07-19T20:40:01.598513"} +{"service": "zhiwei_gateway", "label": "知微 Gateway", "reason": "", "timestamp": "2026-07-20T08:45:01.667438"} diff --git a/gateway/temp/last_health_check.json b/gateway/temp/last_health_check.json index 0b78a8d8..6a924f29 100644 --- a/gateway/temp/last_health_check.json +++ b/gateway/temp/last_health_check.json @@ -45,5 +45,5 @@ "ok": 4, "total": 4 }, - "generated_at": "2026-07-19 21:00:01" + "generated_at": "2026-07-20 08:50:01" } \ No newline at end of file diff --git a/server.py b/server.py index baa15f4a..99b2648b 100644 --- a/server.py +++ b/server.py @@ -1340,6 +1340,72 @@ def api_xmpp_keys(): return jsonify({"error": "xmpp_logger not available"}), 500 +# ── 开发原则 Tab 端点(仿 AgentsMeeting: G规范/K测试/H需求)── + +DOCS_DIR = Path(__file__).resolve().parent / "docs" +HEALTH_REPORT = Path(__file__).resolve().parent / "gateway" / "temp" / "last_health_check.json" + + +@app.route("/api/spec") +def api_spec(): + """G 规范:docs/dev-spec.md 内容""" + f = DOCS_DIR / "dev-spec.md" + if not f.exists(): + return jsonify({"ok": False, "error": "dev-spec.md not found"}) + return jsonify({"ok": True, "content": f.read_text(encoding="utf-8")}) + + +@app.route("/api/spec/history") +def api_spec_history(): + """dev-spec.md 的 git 历史""" + import subprocess + try: + r = subprocess.run( + ["git", "log", "--oneline", "-20", "--", "docs/dev-spec.md"], + capture_output=True, timeout=10, text=True, + cwd=str(Path(__file__).resolve().parent)) + lines = [l for l in r.stdout.splitlines() if l.strip()] + return jsonify({"ok": True, "log": lines, "count": len(lines)}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)[:100]}) + + +@app.route("/api/tests") +def api_tests(): + """K 测试:agents_health_check 服务检查结果,渲染为 pass/fail 测试报告""" + if not HEALTH_REPORT.exists(): + return jsonify({"ok": False, "error": "health report not found (cron not run yet)"}) + try: + rep = json.loads(HEALTH_REPORT.read_text(encoding="utf-8")) + except Exception as e: + return jsonify({"ok": False, "error": str(e)[:100]}) + tests = [] + for svc in rep.get("services", []): + ok = svc.get("health", {}).get("ok", False) + tests.append({ + "name": f"{svc.get('label', svc.get('name'))} ({svc.get('name')})", + "ok": ok, + "expected": False, + "detail": svc.get("detail", ""), + }) + passed = sum(1 for t in tests if t["ok"]) + return jsonify({ + "ok": True, + "tests": tests, + "summary": {"total": len(tests), "passed": passed, "failed": len(tests) - passed}, + "time": rep.get("generated_at", ""), + }) + + +@app.route("/api/prd") +def api_prd(): + """H 需求:docs/prd.md(不存在则返回未建立)""" + f = DOCS_DIR / "prd.md" + if not f.exists(): + return jsonify({"ok": False, "error": "prd.md 尚未建立"}) + return jsonify({"ok": True, "content": f.read_text(encoding="utf-8")}) + + # 注册提示词管理路由 register_routes(app) diff --git a/static/index.html b/static/index.html index 3b5131b4..bb9fb0f8 100644 --- a/static/index.html +++ b/static/index.html @@ -12,6 +12,8 @@ body { background: #0f0f13; color: #e2e8f0; } .tab-btn { transition: all 0.15s; } .tab-btn.active { background: #1e293b; color: #60a5fa; border-color: #60a5fa; } +.princ-btn { transition: all 0.15s; color: #8b949e; } +.princ-btn.active { background: #1e293b; color: #60a5fa; } .card { background: #1a1a24; border: 1px solid #2a2a3a; border-radius: 12px; } .badge-up { color: #22c55e; } .badge-down { color: #ef4444; } @@ -63,6 +65,7 @@ body { background: #0f0f13; color: #e2e8f0; } + 📸 上传 @@ -78,6 +81,18 @@ body { background: #0f0f13; color: #e2e8f0; } + '; + + // ── 最近 XMPP 对话日志 ── + html += '
'; + html += '
💬 最近对话(' + xmppMsgs.length + ' 条)
'; + if (xmppMsgs.length === 0) { + html += '
暂无记录 — bot 重启后对话开始采集
'; + } else { + html += '
'; + xmppMsgs.slice(0, 10).forEach(m => { + const isIn = m.direction === 'in'; + const arrow = isIn ? '📩' : '📤'; + const dirColor = isIn ? 'text-[#58a6ff]' : 'text-[#3fb950]'; + const statusMark = m.status === 'ok' ? '' : ' [' + m.status + ']'; + const who = isIn ? (m.from || '').split('@')[0] : '知微'; + const t = (m.timestamp || '').slice(11, 16); + const preview = (m.body_preview || '').replace(/' + arrow + ' ' + who + '' + preview + statusMark + '' + t + '
'; + }); + html += '
'; + } + html += ''; html += ''; } + + // ── 原有健康监控(功能树/Cron/数据实体/数据流)—— 恢复自 mofin_health.html ── + html += '
'; + html += '
📊 系统健康监控(功能树 · Cron · 数据实体 · 数据流)
'; + html += ''; + html += '
'; + el.innerHTML = html; el.dataset.rendered = '1'; // 标记已首渲 } catch(e) { @@ -1563,9 +1615,10 @@ async function refreshHealth() { const el = document.getElementById('tab-health'); if (!el || el.classList.contains('hidden') || !el.dataset.rendered) return; try { - const [services, xmpp] = await Promise.all([ + const [services, xmpp, msgData] = await Promise.all([ fetchJSON('/api/services'), - fetchJSON('/api/xmpp/health') + fetchJSON('/api/xmpp/health'), + fetchJSON('/api/xmpp/messages').catch(() => null) ]); const svcs = (services.services || []); const sum = services.summary || {}; @@ -1613,12 +1666,40 @@ async function refreshHealth() { if (bi) bi.textContent = ba.inbound || 0; if (bo) { bo.textContent = ba.outbound || 0; bo.className = 'font-mono ' + (ba.outbound ? 'text-[#3fb950]' : (ba.inbound ? 'text-[#f85149]' : 'text-slate-500')); } if (be) { be.textContent = ba.errors || 0; be.className = 'font-mono ' + (ba.errors ? 'text-[#f85149]' : 'text-slate-500'); } - if (bl) bl.textContent = ba.last_error || ''; + if (bl) { + const errAge = ba.last_error_age_sec >= 0 ? Math.round(ba.last_error_age_sec/60) + '分钟前' : ''; + if (ba.last_error && ba.last_error_resolved) { + bl.textContent = '⚠️ 历史错误(' + errAge + ',已恢复): ' + ba.last_error; + bl.className = 'text-xs text-slate-500 mt-2 p-2 bg-slate-800/30 rounded'; + } else if (ba.last_error) { + bl.textContent = '⚠️ ' + errAge + ': ' + ba.last_error; + bl.className = 'text-xs text-[#f85149] mt-2 p-2 bg-red-900/20 rounded'; + } else { + bl.textContent = ''; + } + } // LLM Provider const llm = xmpp.llm_provider || {}; const ls = document.getElementById('hLlmStatus'), le = document.getElementById('hLlmErr'); if (ls) { ls.textContent = llm.status || '?'; ls.className = 'font-mono ' + (llm.status === 'ok' ? 'text-[#3fb950]' : 'text-[#f85149]'); } if (le) le.textContent = llm.error || ''; + + // 最近对话列表 + const msgBox = document.getElementById('hXmppMsgs'); + if (msgBox && msgData && msgData.messages) { + let mh = ''; + msgData.messages.slice(0, 10).forEach(m => { + const isIn = m.direction === 'in'; + const arrow = isIn ? '📩' : '📤'; + const dirColor = isIn ? 'text-[#58a6ff]' : 'text-[#3fb950]'; + const statusMark = m.status === 'ok' ? '' : ' [' + m.status + ']'; + const who = isIn ? (m.from || '').split('@')[0] : '知微'; + const t = (m.timestamp || '').slice(11, 16); + const preview = (m.body_preview || '').replace(/' + arrow + ' ' + who + '' + preview + statusMark + '' + t + ''; + }); + msgBox.innerHTML = mh; + } } // 更新时间戳 @@ -1690,6 +1771,159 @@ async function renderWatch() { watchTimer = setInterval(renderWatch, 60000); } } + +// ── 开发原则 Tab(仿 AgentsMeeting: G规范/K测试/F健康/H需求)── +let _princLoaded = {}; +function renderPrinciples() { + // 绑定子tab按钮 + document.querySelectorAll('.princ-btn').forEach(btn => { + btn.onclick = () => { + document.querySelectorAll('.princ-btn').forEach(b => b.classList.remove('active')); + document.querySelectorAll('.princ-pane').forEach(p => p.classList.add('hidden')); + btn.classList.add('active'); + const id = btn.dataset.princ; + document.getElementById('princ-' + id).classList.remove('hidden'); + _loadPrinc(id); + }; + }); + // 默认加载当前激活的子tab + const active = document.querySelector('.princ-btn.active'); + _loadPrinc(active ? active.dataset.princ : 'spec'); +} + +function _loadPrinc(id) { + if (id === 'spec') return _renderPrincSpec(); + if (id === 'tests') return _renderPrincTests(); + if (id === 'health') return _renderPrincHealth(); + if (id === 'prd') return _renderPrincPrd(); +} + +async function _renderPrincSpec() { + const el = document.getElementById('princ-spec'); + el.innerHTML = '
加载中...
'; + try { + const d = await fetchJSON('/api/spec'); + if (!d.ok) { el.innerHTML = '
' + (d.error || '加载失败') + '
'; return; } + let h = '
' + mdRender(d.content) + '
'; + try { + const dh = await fetchJSON('/api/spec/history'); + if (dh.ok && dh.log && dh.log.length) { + h += '
历史版本 (' + dh.count + ' commits)
'; + } + } catch(e) {} + el.innerHTML = h; + } catch(e) { el.innerHTML = '
加载失败: ' + e.message + '
'; } +} + +async function _renderPrincTests() { + const el = document.getElementById('princ-tests'); + el.innerHTML = '
加载中...
'; + try { + const d = await fetchJSON('/api/tests'); + if (!d.ok) { el.innerHTML = '
' + (d.error || '加载失败') + '
'; return; } + const s = d.summary || {}; + let h = '
'; + h += '
' + (s.total||0) + '
Total
'; + h += '
' + (s.passed||0) + '
Passed
'; + h += '
' + (s.failed||0) + '
Failed
'; + h += '
'; + const fails = (d.tests||[]).filter(t => !t.ok); + if (fails.length) { + h += '
Failed (' + fails.length + ')
'; + fails.forEach(t => { + h += '
FAIL ' + t.name.replace(/
' + (t.detail||'').replace(/
'; + }); + } + const passes = (d.tests||[]).filter(t => t.ok); + h += '
Passed (' + passes.length + ')
'; + passes.forEach(t => { + h += '
PASS ' + t.name.replace(/ ' + (t.detail||'').replace(/
'; + }); + h += '
数据源: agents_health_check (cron */5min) · 最后运行: ' + (d.time||'?') + '
'; + el.innerHTML = h; + } catch(e) { el.innerHTML = '
加载失败: ' + e.message + '
'; } +} + +function _renderPrincHealth() { + const el = document.getElementById('princ-health'); + if (_princLoaded.health) return; + el.innerHTML = '
完整健康监控与 🏥 健康 Tab 同源。快捷入口:🏥 健康 Tab
' + + '
'; + _princLoaded.health = true; +} + +async function _renderPrincPrd() { + const el = document.getElementById('princ-prd'); + el.innerHTML = '
加载中...
'; + try { + const d = await fetchJSON('/api/prd'); + if (!d.ok) { el.innerHTML = '
' + (d.error || 'prd.md 尚未建立') + '
'; return; } + el.innerHTML = '
' + mdRender(d.content) + '
'; + } catch(e) { el.innerHTML = '
加载失败: ' + e.message + '
'; } +} + +// markdown 渲染(移植自 AgentsMeeting dashboard) +function mdRender(md) { + if (!md) return ''; + let h = ''; + const blocks = md.split('\n\n'); + for (const raw of blocks) { + const block = raw.trim(); + if (!block) continue; + if (block.startsWith('```')) { + const code = block.replace(/^```[\s\S]*?\n/, '').replace(/```$/, '').trim(); + h += '
' + code.replace(/';
+      continue;
+    }
+    if (block.startsWith('#### ')) { h += '

' + inMd(block.slice(5).replace(/'; continue; } + if (block.startsWith('### ')) { h += '

' + inMd(block.slice(4).replace(/'; continue; } + if (block.startsWith('## ')) { h += '

' + inMd(block.slice(3).replace(/'; continue; } + if (block.startsWith('# ')) { h += '

' + inMd(block.slice(2).replace(/'; continue; } + if (block.startsWith('> ')) { h += '
' + inMd(block.slice(2).replace(/'; continue; } + if (block.startsWith('---') || block.startsWith('***')) { h += '
'; continue; } + if (block.startsWith('- ') || block.startsWith('* ')) { + const items = block.split('\n'); + h += '
    '; + items.forEach(li => { const t = li.replace(/^[-*]\s*/, '').trim(); if (t) h += '
  • ' + inMd(t.replace(/'; }); + h += '
'; + continue; + } + if (/^\d+\.\s/.test(block)) { + const items = block.split('\n'); + h += '
    '; + items.forEach(li => { const t = li.replace(/^\d+\.\s*/, '').trim(); if (t) h += '
  1. ' + inMd(t.replace(/'; }); + h += '
'; + continue; + } + if (block.indexOf('|') >= 0 && block.indexOf('\n|') >= 0) { + const rows = block.split('\n'); + let inTable = false; + rows.forEach(row => { + if (row.indexOf('---') >= 0) return; + const cells = row.split('|').filter(c => c.trim()); + if (cells.length < 2) return; + if (!inTable) { + h += '' + cells.map(c => ''; + inTable = true; + } else { + h += '' + cells.map(c => ''; + } + }); + if (inTable) h += '
' + inMd(c.trim().replace(/').join('') + '
' + inMd(c.trim().replace(/').join('') + '
'; + continue; + } + h += '

' + inMd(block.replace(/'; + } + return h; +} +function inMd(t) { + return t.replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/\*([^*]+)\*/g, '$1') + .replace(/`([^`]+)`/g, '$1') + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); +} \ No newline at end of file diff --git a/xmpp_logger.py b/xmpp_logger.py index fa4b76a6..5d329ca5 100644 --- a/xmpp_logger.py +++ b/xmpp_logger.py @@ -19,13 +19,32 @@ LOG_DIR = Path(__file__).resolve().parent / "gateway" / "logs" LOG_FILE = LOG_DIR / "xmpp_messages.jsonl" MAX_AGE_DAYS = 7 -# Position-analyst profile config path (where model.provider is set) -HERMES_CONFIG = Path("/home/hmo/.hermes/profiles/position-analyst/config.yaml") -GATEWAY_SERVICE = "hermes-gateway-zhiwei.service" +# 监控的 Hermes Gateway profiles +PROFILES = { + "zhiwei": { + "config": Path("/home/hmo/.hermes/profiles/position-analyst/config.yaml"), + "agent_log": Path("/home/hmo/.hermes/profiles/position-analyst/logs/agent.log"), + "service": "hermes-gateway-zhiwei.service", + "user_service": False, + "gateway_port": 8643, + }, + "default": { + "config": Path("/home/hmo/.hermes/config.yaml"), + "agent_log": Path("/home/hmo/.hermes/logs/agent.log"), + "service": "hermes-gateway.service", + "user_service": True, + "gateway_port": 8642, + }, +} +# 向后兼容(旧引用) +HERMES_CONFIG = PROFILES["zhiwei"]["config"] +GATEWAY_SERVICE = PROFILES["zhiwei"]["service"] RESTART_COOLDOWN_FILE = LOG_DIR / "last_restart.txt" RESTART_COOLDOWN_SEC = 180 # 3 min: don't trigger another restart within 3 min of last # Map AgentsMeeting key_id -> Hermes provider name +# NOTE: key7 (kimi) is intentionally NOT in this map — different model provider. +# zhiwei uses deepseek-v4-flash via OCG; auto_heal must never switch to kimi. KEY_TO_PROVIDER = { "key1": "ocg-new", "key2": "ocg-old", @@ -36,15 +55,16 @@ KEY_TO_PROVIDER = { } -def current_provider() -> str | None: +def current_provider(profile="zhiwei") -> str | None: """Read current model.provider from Hermes config. Walks the file tracking the model: block to find its nested `provider:` (avoiding other blocks like `agent.alerts[0].provider:`). """ + cfg_path = PROFILES.get(profile, PROFILES["zhiwei"])["config"] try: in_model = False - for line in HERMES_CONFIG.read_text().splitlines(): + for line in cfg_path.read_text().splitlines(): stripped = line.rstrip() # Detect top-level "model:" at column 0 if stripped == "model:" or stripped.startswith("model:") and not line.startswith(" "): @@ -63,50 +83,74 @@ def current_provider() -> str | None: return None -def switch_key(key_id: str) -> dict: +def _ensure_provider_block(target_cfg: Path, provider: str) -> bool: + """若目标 config 缺少 provider 凭据块,从 zhiwei config(凭据单一事实源)复制注入。""" + txt = target_cfg.read_text() + if _re.search(rf"^ {provider}:\s*$", txt, flags=_re.MULTILINE): + return True + src = PROFILES["zhiwei"]["config"].read_text() + m = _re.search(rf"(^ {provider}:\n(?: .+\n)+)", src, flags=_re.MULTILINE) + if not m: + return False + block = m.group(1) + # 插到 providers: 区块第一个 provider 之前 + m2 = _re.search(r"^providers:\s*$", txt, flags=_re.MULTILINE) + if not m2: + return False + txt = txt[:m2.end()] + "\n" + block + txt[m2.end():] + target_cfg.write_text(txt) + return True + + +def _restart_gateway(profile: str) -> dict: + """异步重启指定 profile 的 gateway。""" + p = PROFILES[profile] + try: + if p["user_service"]: + _sp.Popen(["systemctl", "--user", "restart", p["service"]], + stdout=_sp.DEVNULL, stderr=_sp.DEVNULL, start_new_session=True) + else: + _sp.Popen(["sudo", "-n", "systemctl", "restart", p["service"]], + stdout=_sp.DEVNULL, stderr=_sp.DEVNULL, start_new_session=True) + return {"success": True, "detail": "restart triggered (async)"} + except Exception as e: + return {"success": False, "detail": str(e)[:100]} + + +def switch_key(key_id: str, profile: str = "zhiwei") -> dict: """Switch Hermes config to use the given key_id's provider, then restart Gateway.""" provider = KEY_TO_PROVIDER.get(key_id) if not provider: return {"switched": False, "detail": f"unknown key_id {key_id}"} - old = current_provider() + cfg_path = PROFILES[profile]["config"] + old = current_provider(profile) if old == provider: return {"switched": False, "old": old, "new": provider, "detail": "already on this key"} try: - txt = HERMES_CONFIG.read_text() + if not _ensure_provider_block(cfg_path, provider): + return {"switched": False, "detail": f"provider {provider} 凭据块缺失且无法注入"} + txt = cfg_path.read_text() new_txt, n = _re.subn(r"^ provider:\s*\S+\s*$", f" provider: {provider}", txt, count=1, flags=_re.MULTILINE) if n == 0: return {"switched": False, "detail": "no provider: line found"} - HERMES_CONFIG.write_text(new_txt) + cfg_path.write_text(new_txt) except Exception as e: return {"switched": False, "detail": f"config edit failed: {e}"} - # systemctl restart blocks until gateway drain completes (60+s). - # Fire asynchronously via Popen to avoid blocking cron; verify later. - try: - _sp.Popen(["sudo", "-n", "systemctl", "restart", GATEWAY_SERVICE], - stdout=_sp.DEVNULL, stderr=_sp.DEVNULL, start_new_session=True) - RESTART_COOLDOWN_FILE.write_text(str(_time.time())) - ok = True - detail = "restart triggered (async)" - except Exception as e: - ok = False - detail = f"restart trigger failed: {e}" - - # Wait long enough for systemd to drain + restart the gateway (max 90s) - _time.sleep(45) - verify = _verify_llm() + r = _restart_gateway(profile) + RESTART_COOLDOWN_FILE.write_text(str(_time.time())) return { "switched": True, "old": old, "new": provider, "key_id": key_id, - "restart": ok, - "verify": verify, - "detail": detail, + "profile": profile, + "restart": r["success"], + "detail": r["detail"], } @@ -290,14 +334,36 @@ def health(): inbound = [l for l in lines if "📩 收到" in l] outbound = [l for l in lines if "📤 发送" in l or "📤" in l] errors = [l for l in lines if "ERROR" in l or "TimeoutError" in l or "timed out" in l] + + # 解析最后一条 inbound 的时间(行首 python logging 时间戳 "2026-07-19 23:31:07,342") + import re as _re2 + def _line_epoch(line): + m = _re2.search(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", line) + if not m: + return 0 + try: + return datetime.strptime(m.group(1), "%Y-%m-%d %H:%M:%S").timestamp() + except Exception: + return 0 + + last_inbound_ts = max((_line_epoch(l) for l in inbound), default=0) + last_outbound_ts = max((_line_epoch(l) for l in outbound), default=0) + last_error_ts = max((_line_epoch(l) for l in errors), default=0) result["bot_activity"] = { "inbound": len(inbound), "outbound": len(outbound), "errors": len(errors), "last_error": errors[-1][:250] if errors else None, "last_inbound": inbound[-1][:250] if inbound else None, "last_outbound": outbound[-1][:200] if outbound else None, + "last_inbound_age_sec": int(now_epoch - last_inbound_ts) if last_inbound_ts else -1, + "last_outbound_age_sec": int(now_epoch - last_outbound_ts) if last_outbound_ts else -1, + "last_error_age_sec": int(now_epoch - last_error_ts) if last_error_ts else -1, + # 错误之后已有成功出站 → 该错误已被覆盖,不算当前问题 + "last_error_resolved": bool(last_error_ts and last_outbound_ts > last_error_ts), } - if errors and not outbound: result["status"] = "degraded" - if inbound and not outbound: result["status"] = "degraded" + # 只有未覆盖的错误才降级状态 + unresolved_error = last_error_ts and not (last_outbound_ts > last_error_ts) + if errors and not outbound and unresolved_error: result["status"] = "degraded" + if inbound and not outbound and unresolved_error: result["status"] = "degraded" except Exception: result["bot_activity"] = {"error": "journalctl failed"} @@ -314,20 +380,11 @@ def health(): except Exception: pass result["gateways"][name] = {"port": port, "alive": ok} - # 测试知微的 LLM 调用是否可达 - if result["gateways"].get("zhiwei", {}).get("alive"): - try: - import urllib.request - payload = json.dumps({"model": "deepseek-v4-flash", "messages": [{"role": "user", "content": "ping"}], - "max_tokens": 5, "stream": False}).encode() - req = urllib.request.Request("http://127.0.0.1:8643/v1/chat/completions", - data=payload, headers={"Content-Type": "application/json", - "Authorization": "Bearer hermes123"}) - urllib.request.urlopen(req, timeout=90) - result["llm_provider"] = {"status": "ok", "latency": "fast"} - except Exception as e: - result["llm_provider"] = {"status": "timeout" if "timeout" in str(e).lower() else "error", - "error": str(e)[:150]} + # LLM 健康:扫 gateway agent.log 最近一次真实调用结果(零成本,不发 LLM 请求) + # agent.log 里每次真实调用都有记录:成功 "API call #N: ... latency=Xs" / 失败 "HTTP 429..." + result["llm_provider"] = _scan_agent_log(now_epoch, "zhiwei") + # default profile(莫荷 8642)同样监控——它的 429 曾导致整组 cron 失败 + result["llm_provider_default"] = _scan_agent_log(now_epoch, "default") except Exception as e: result["gateways"] = {"error": str(e)[:100]} @@ -352,6 +409,59 @@ def health(): return result +def _scan_agent_log(now_epoch, profile="zhiwei", tail_lines=300): + """扫 gateway agent.log 尾部,取最近一次真实 LLM 调用的结果。 + + 成功行: "2026-07-19 20:04:42,880 INFO ... API call #4: ... latency=16.0s ..." + 失败行: "2026-07-19 19:35:59 WARNING ... API call failed ... HTTP 429: Weekly usage limit reached ..." + 返回 {"status": "ok"|"error"|"unknown", "latency"/"error", "age_sec"} + """ + log_path = PROFILES.get(profile, PROFILES["zhiwei"])["agent_log"] + if not log_path.exists(): + return {"status": "unknown", "error": "agent.log not found"} + try: + # 只读尾部(大文件不全读) + size = log_path.stat().st_size + with open(log_path, "rb") as f: + f.seek(max(0, size - 64 * 1024)) + chunk = f.read().decode("utf-8", errors="replace") + lines = [l for l in chunk.splitlines() if l.strip()][-tail_lines:] + except Exception as e: + return {"status": "unknown", "error": str(e)[:80]} + + last_ok = None # (epoch, latency_str) + last_fail = None # (epoch, summary) + for line in lines: + m_ts = _re.search(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", line) + if not m_ts: + continue + try: + ts = datetime.strptime(m_ts.group(1), "%Y-%m-%d %H:%M:%S").timestamp() + except Exception: + continue + m_ok = _re.search(r"API call #\d+.*latency=([\d.]+)s", line) + if m_ok: + last_ok = (ts, m_ok.group(1)) + continue + if "API call failed" in line and ("summary=" in line or "after" in line): + m_err = _re.search(r"summary=(.+?)$", line) + summary = (m_err.group(1) if m_err else line)[:150] + last_fail = (ts, summary) + + if not last_ok and not last_fail: + return {"status": "unknown", "error": "no LLM calls in log"} + + if last_fail and (not last_ok or last_fail[0] > last_ok[0]): + return {"status": "error", + "error": last_fail[1], + "age_sec": int(now_epoch - last_fail[0]), + "source": "agent.log"} + return {"status": "ok", + "latency": f"{last_ok[1]}s", + "age_sec": int(now_epoch - last_ok[0]), + "source": "agent.log"} + + def _fetch_keys(): """从 AgentsMeeting Dashboard 获取可用 API Key 列表""" try: @@ -416,46 +526,62 @@ def auto_heal(): h = health() actions = [] - # 0. LLM Provider 异常 → 先查是否有更好的 Key 可切换 - llm_status = h.get("llm_provider", {}).get("status") - llm_error = h.get("llm_provider", {}).get("error", "") + # 0. 检查每个 profile 的 LLM 状态(zhiwei + default),429 → 切 key + bk_cache = None + for profile in ("zhiwei", "default"): + llm = h.get("llm_provider" if profile == "zhiwei" else "llm_provider_default", {}) + llm_status = llm.get("status") + llm_error = llm.get("error", "") or "" - # Detect rate-limit / timeout / error - is_rate_limited = (llm_status == "rate_limited" or - "429" in llm_error or "Weekly usage" in llm_error or - "RateLimit" in llm_error) + is_rate_limited = (llm_status == "rate_limited" or + "429" in llm_error or "Weekly usage" in llm_error or + "RateLimit" in llm_error) + if not is_rate_limited: + continue + + if bk_cache is None: + bk_cache = best_key() + bk = bk_cache + if not bk: + continue + actions.append({ + "action": "check_keys", + "profile": profile, + "best_key": bk["key_id"], + "current_provider": current_provider(profile), + "best_provider": KEY_TO_PROVIDER.get(bk["key_id"]), + "weekly_pct": bk["weekly"]["usage_percent"], + }) + + target_provider = KEY_TO_PROVIDER.get(bk["key_id"]) + current = current_provider(profile) + if target_provider is None: + actions.append({ + "action": "skip_switch", + "profile": profile, + "reason": f"best key {bk['key_id']} is non-OCG (kimi), stay on deepseek", + }) + elif target_provider != current: + if bk["weekly"]["status"] == "ok": + action = switch_key(bk["key_id"], profile=profile) + action["action_group"] = "switch_key" + action["profile"] = profile + actions.append(action) + else: + actions.append({ + "action": "skip_switch", + "profile": profile, + "reason": f"best key {bk['key_id']} weekly status {bk['weekly']['status']}, no improvement", + }) + + # zhiwei LLM timeout/error(非429)→ 重启 Gateway + llm_status = h.get("llm_provider", {}).get("status") + llm_error = h.get("llm_provider", {}).get("error", "") or "" + is_rate_limited_zw = ("429" in llm_error or "Weekly usage" in llm_error or "RateLimit" in llm_error) is_timeout_or_error = llm_status in ("timeout", "error") - if is_rate_limited or is_timeout_or_error: - bk = best_key() - if bk: - actions.append({ - "action": "check_keys", - "best_key": bk["key_id"], - "current_provider": current_provider(), - "best_provider": KEY_TO_PROVIDER.get(bk["key_id"]), - "rolling_pct": bk["rolling"]["usage_percent"], - "weekly_pct": bk["weekly"]["usage_percent"], - "issues": bk["issues"], - }) - - # 如果 best key 对应的 provider 跟当前不同 → 切换 - target_provider = KEY_TO_PROVIDER.get(bk["key_id"]) - current = current_provider() - if target_provider and target_provider != current: - # 只有当 best key 自身至少 weekly ok 才切,否则切过去也白搭 - if bk["weekly"]["status"] == "ok": - action = switch_key(bk["key_id"]) - action["action_group"] = "switch_key" - actions.append(action) - else: - actions.append({ - "action": "skip_switch", - "reason": f"best key {bk['key_id']} weekly status {bk['weekly']['status']}, no improvement", - }) - # 1. LLM Provider timeout/error(非429)→ 重启 Gateway(不是切 key) - if is_timeout_or_error and not is_rate_limited: + if is_timeout_or_error and not is_rate_limited_zw: gw = h.get("gateways", {}).get("zhiwei", {}) if gw.get("alive"): # Check cooldown to avoid restart loops @@ -485,16 +611,44 @@ def auto_heal(): "success": False, "detail": str(e)[:100]}) - # 2. Bot 无出站 → 重启知微 Bot + # 2. Bot 真卡死才重启:最后一条 inbound 超过 10 分钟仍无出站,才算死(不是 LLM 慢) + # LLM 正常冷启动/工具调用需要 1-10 分钟,inbound 后立即重启会杀掉进行中的调用。 ba = h.get("bot_activity", {}) - if ba.get("inbound", 0) > 0 and ba.get("outbound", 0) == 0 and ba.get("errors", 0) > 0: + inbound_age = ba.get("last_inbound_age_sec", -1) + outbound_age = ba.get("last_outbound_age_sec", -1) + bot_truly_stuck = ( + inbound_age > 600 # 最后一条用户消息已超过10分钟 + and (outbound_age < 0 or outbound_age > inbound_age - 600) # 且其间没有任何出站 + and ba.get("inbound", 0) > 0 + ) + if bot_truly_stuck: + # cooldown: bot 重启不频繁于10分钟一次 try: - r = _sp.run(["sudo", "-n", "systemctl", "restart", "xmpp-zhiwei"], capture_output=True, timeout=30, text=True) - actions.append({"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", - "success": r.returncode == 0, "detail": "restarted" if r.returncode == 0 else r.stderr[:100]}) - except Exception as e: - actions.append({"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", - "success": False, "detail": str(e)[:100]}) + last_bot_restart = 0 + bf = LOG_DIR / "last_bot_restart.txt" + if bf.exists(): + last_bot_restart = float(bf.read_text().strip() or 0) + except Exception: + last_bot_restart = 0 + bot_elapsed = _time.time() - last_bot_restart + if bot_elapsed < 600: + actions.append({"action": "skip_bot_restart", + "reason": f"bot restart cooldown {int(bot_elapsed)}s < 600s"}) + else: + try: + _sp.Popen(["sudo", "-n", "systemctl", "restart", "xmpp-zhiwei"], + stdout=_sp.DEVNULL, stderr=_sp.DEVNULL, start_new_session=True) + (LOG_DIR / "last_bot_restart.txt").write_text(str(_time.time())) + actions.append({"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", + "success": True, "detail": "restart triggered (async)"}) + except Exception as e: + actions.append({"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", + "success": False, "detail": str(e)[:100]}) + elif ba.get("inbound", 0) > 0 and ba.get("outbound", 0) == 0 and ba.get("errors", 0) > 0: + # inbound 后仍在处理窗口内(LLM 慢但正常),不动 bot + actions.append({"action": "bot_busy_not_stuck", + "inbound_age_sec": inbound_age, + "reason": "inbound < 10min ago — LLM still processing, no restart"}) # 3. ejabberd → 重启容器 if h.get("ejabberd", "") and "Up" not in str(h["ejabberd"]):