""" self_todo_executor.py — TODO 自修复执行器 - 轮询 health_todos.jsonl 中 status=pending 的条目 - 执行 fix_action(启动脚本) - 成功→标记 completed - 失败→ escalation 到 LLM(通过 xmpp_bot 通知) MoFin self_todo_executor.py 的 AgentsMeeting 适配版。 """ import json, os, sys, time, subprocess, shlex, urllib.request from datetime import datetime try: sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") except Exception: pass SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) GATEWAY_DIR = os.path.dirname(SCRIPT_DIR) BASE = os.path.dirname(GATEWAY_DIR) TEMP = os.path.join(BASE, "gateway", "temp") LOGS = os.path.join(BASE, "gateway", "logs") PYTHON = r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\python.exe" TODO_FILE = os.path.join(TEMP, "health_todos.jsonl") EXECUTOR_LOG = os.path.join(LOGS, "todo_executor.log") def log(msg): ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") line = f"{ts} [executor] {msg}" print(line, flush=True) with open(EXECUTOR_LOG, "a", encoding="utf-8") as f: f.write(line + "\n") def read_pending_todos(): """读取 health_todos.jsonl 中所有 status=pending 的条目。""" todos = [] if not os.path.exists(TODO_FILE): return todos try: with open(TODO_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: entry = json.loads(line) if entry.get("status") == "pending": todos.append(entry) except json.JSONDecodeError: continue except Exception as e: log(f"读取 TODO 文件失败: {e}") return todos def mark_todo(entry, status, result=""): """在 JSONL 中将条目标记为 completed 或 failed。""" if not os.path.exists(TODO_FILE): return entry["status"] = status entry["resolved_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if result: entry["result"] = result # 重写整个文件,标记匹配条目 lines = [] try: with open(TODO_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: existing = json.loads(line) # 按 created + service 匹配 if (existing.get("created") == entry.get("created") and existing.get("service") == entry.get("service")): lines.append(json.dumps(entry, ensure_ascii=False)) else: lines.append(line) except json.JSONDecodeError: lines.append(line) except Exception as e: log(f"重写 TODO 文件失败: {e}") return try: with open(TODO_FILE, "w", encoding="utf-8") as f: f.write("\n".join(lines) + "\n") except Exception as e: log(f"写入 TODO 文件失败: {e}") def execute_fix(entry): """执行修复操作。返回 (success, detail)。""" script = entry.get("fix_script", "") args = entry.get("fix_args", []) cwd = entry.get("fix_cwd", None) service = entry.get("service", "unknown") if not script or not os.path.exists(script): return (False, f"修复脚本不存在: {script}") cmd = [PYTHON, script] + list(args) log(f"执行修复: {service} → {' '.join(cmd[-3:])}") try: r = subprocess.run( cmd, cwd=cwd, capture_output=True, text=True, timeout=30, creationflags=subprocess.CREATE_NO_WINDOW, ) if r.returncode == 0: return (True, f"exit=0") else: detail = r.stderr[:200] if r.stderr else r.stdout[:200] return (False, f"exit={r.returncode}: {detail}") except subprocess.TimeoutExpired: return (False, "timeout (30s)") except Exception as e: return (False, str(e)[:100]) def escalate_to_xmpp(entry, result): """通过 xmpp_bot 的 HTTP 桥发送告警到群聊(失败 escalation)。""" service = entry.get("service", "?") issue = entry.get("issue", "?") detail = result[:200] payload = json.dumps({ "message": f"[executor] 修复失败: {service}\n问题: {issue}\n结果: {detail}\n需要人工介入" }).encode("utf-8") try: req = urllib.request.Request( "http://127.0.0.1:5802/send", data=payload, headers={ "Content-Type": "application/json", "X-Api-Key": "xxm_bridge_8f3a2c", }, ) urllib.request.urlopen(req, timeout=5) log(f"已通过 XMPP 发送 escalation: {service}") except Exception as e: log(f"XMPP escalation 失败: {e}") def main(): log("=== TODO Executor 启动 ===") todos = read_pending_todos() log(f"待处理 TODO: {len(todos)} 条") for entry in todos: service = entry.get("service", "?") issue = entry.get("issue", "?") log(f"处理: {service} — {issue}") success, detail = execute_fix(entry) # "another instance is already running" = 服务已在正常运行,不算失败 if not success and "another instance is already running" in detail: mark_todo(entry, "completed", "already_running") log(f" ✅ {service}: 已在运行,无需修复 ({detail[:60]})") elif success: mark_todo(entry, "completed", detail) log(f" ✅ {service}: 修复成功 ({detail})") else: mark_todo(entry, "failed", detail) log(f" ❌ {service}: 修复失败 ({detail}) → escalation") try: escalate_to_xmpp(entry, detail) except Exception as e: log(f" escalation 异常: {e}") log(f"=== TODO Executor 完成 ({len(todos)} 条处理) ===") if __name__ == "__main__": main()