#!/usr/bin/env python3 """ self_todo_executor.py — TODO 自修复执行器(Linux/246 部署版) ============================================================= 每 10 分钟触发。轮询 health_todos.jsonl 中 pending 条目,执行修复,标记结果。 部署:Linux crontab: */10 * * * * cd /home/hmo/AgentsMeeting/gateway/scripts && /usr/bin/python3 self_todo_executor.py >> ../logs/todo_executor.log 2>&1 """ import json, os, sys, subprocess 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) TEMP = os.path.join(GATEWAY_DIR, "temp") LOGS = os.path.join(GATEWAY_DIR, "logs") os.makedirs(LOGS, exist_ok=True) TODO_FILE = os.path.join(TEMP, "health_todos.jsonl") EXECUTOR_LOG = os.path.join(LOGS, "todo_executor.log") # ── 服务修复命令映射(key = service name)───────────────── FIX_MAP = { "dashboard": ["sudo", "systemctl", "restart", "agentsmeeting-dashboard"], "hermes_gateway_mohe": ["sudo", "systemctl", "restart", "hermes-gateway@mohe"], "hermes_gateway_zhiwei": ["sudo", "systemctl", "restart", "hermes-gateway@zhiwei"], "wechat_bridge": None, # 禁用自动重启: 重启=重登触发风控, 人工处理 # 远程服务无本地修复命令 "xmpp_bot_xxm": None, "article_processor": None, } 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(): 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=""): 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) 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(service_name): """执行修复命令。返回 (success, detail)。""" cmd = FIX_MAP.get(service_name) if not cmd: return (False, "无本地修复命令(远程服务)") log(f"执行修复: {service_name} → {' '.join(cmd)}") try: r = subprocess.run(cmd, capture_output=True, text=True, timeout=30) if r.returncode == 0: return (True, "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 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(service) if not success and "another instance" in detail.lower(): mark_todo(entry, "completed", "already_running") log(f" ✅ {service}: 已在运行") elif success: mark_todo(entry, "completed", detail) log(f" ✅ {service}: 修复成功") else: mark_todo(entry, "failed", detail) log(f" ❌ {service}: 修复失败 ({detail})") log(f"=== TODO Executor 完成 ({len(todos)} 条处理) ===") if __name__ == "__main__": main()