#!/usr/bin/env python3 """self_todo_executor.py — TODO自动执行器 (no_agent模式) 每10分钟轮询mofin.db中todos表的pending任务,执行fix_action命令。 纯代码逻辑,不调LLM。 有执行→输出摘要,无→SILENT。 """ import json, os, sqlite3, subprocess, sys, time from pathlib import Path from datetime import datetime BASE = Path("/home/hmo/MoFin") DB_PATH = BASE / "data" / "mofin.db" # 记录已报告过的blocked ID,避免重复推送 REPORTED_BLOCKED_PATH = Path("/home/hmo/.hermes/profiles/position-analyst/home/.cache/executor_reported_blocked.json") def get_conn(): conn = sqlite3.connect(str(DB_PATH)) conn.row_factory = sqlite3.Row return conn def verify_fix(verification_check): """运行验证命令,返回 (ok, output)""" if not verification_check: return True, "无验证命令" try: r = subprocess.run( verification_check, shell=True, capture_output=True, text=True, timeout=30 ) if r.returncode == 0: return True, r.stdout.strip()[:200] else: return False, f"exit={r.returncode}: {r.stderr.strip()[:200]}" except Exception as e: return False, str(e)[:200] def execute_fix(fix_action): """执行修复命令,返回 (ok, output)""" if not fix_action: return False, "无修复命令" try: r = subprocess.run( fix_action, shell=True, capture_output=True, text=True, timeout=60 ) if r.returncode == 0: return True, r.stdout.strip()[:300] or "ok" else: return False, f"exit={r.returncode}: {r.stderr.strip()[:300]}" except subprocess.TimeoutExpired: return False, "执行超时(60s)" except Exception as e: return False, str(e)[:200] def main(): start = time.time() conn = get_conn() cur = conn.cursor() # 读所有pending任务(按优先级排序) rows = cur.execute( "SELECT id, title, description, priority, fix_action, verification_check, " "retry_count, max_retries, note FROM todos " "WHERE status='pending' ORDER BY " "CASE priority WHEN 'high' THEN 0 WHEN 'medium' THEN 1 ELSE 2 END, " "created_at ASC LIMIT 10" ).fetchall() if not rows: conn.close() print("[SILENT] 无待处理TODO") return results = [] newly_blocked = [] # [(id, title)] 新阻塞的,需要推送给Dad/知微 for row in rows: todo_id = row["id"] title = row["title"] fix_action = row["fix_action"] verification = row["verification_check"] retry_count = row["retry_count"] max_retries = row["max_retries"] # 标记in_progress cur.execute( "UPDATE todos SET status='in_progress', updated_at=CURRENT_TIMESTAMP WHERE id=?", (todo_id,) ) conn.commit() if fix_action: # 执行修复 ok, output = execute_fix(fix_action) if not ok: retry_count += 1 if retry_count >= max_retries: cur.execute( "UPDATE todos SET status='blocked', retry_count=?, " "note=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", (retry_count, f"重试{retry_count}次仍失败: {output}", todo_id) ) newly_blocked.append((todo_id, title)) results.append(("⛔", f"{title}: 已阻塞({output[:60]})")) else: cur.execute( "UPDATE todos SET status='pending', retry_count=?, " "note=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", (retry_count, f"第{retry_count}次失败: {output}", todo_id) ) results.append(("🔄", f"{title}: 重试{retry_count}/{max_retries}({output[:60]})")) conn.commit() continue # 修复成功→验证 v_ok, v_out = verify_fix(verification) if v_ok: cur.execute( "UPDATE todos SET status='completed', note=?, " "updated_at=CURRENT_TIMESTAMP WHERE id=?", (f"已修复: {output[:200]}", todo_id) ) results.append(("✅", f"{title}: 已修复")) else: # 修复成功但验证失败→可能验证命令不准,标记需人工 cur.execute( "UPDATE todos SET status='completed', note=?, " "updated_at=CURRENT_TIMESTAMP WHERE id=?", (f"执行成功但验证异常: {v_out}", todo_id) ) results.append(("⚠️", f"{title}: 已执行但验证异常({v_out[:60]})")) else: # 无fix_action → 标记为无法自动修复 cur.execute( "UPDATE todos SET status='blocked', note='需人工处理(无修复命令)', " "updated_at=CURRENT_TIMESTAMP WHERE id=?", (todo_id,) ) newly_blocked.append((todo_id, title)) results.append(("⛔", f"{title}: 无修复命令,已阻塞")) conn.commit() conn.close() # 记录新报告过的blocked ID try: REPORTED_BLOCKED_PATH.parent.mkdir(parents=True, exist_ok=True) reported = json.loads(REPORTED_BLOCKED_PATH.read_text()) if REPORTED_BLOCKED_PATH.exists() else [] # 过滤出尚未报告过的blocked项 unreported_blocked = [(tid, t) for tid, t in newly_blocked if tid not in reported] if unreported_blocked: # 有未报告的阻塞项 → 输出直通知微 print() print("⛔ 需知微处理(已阻塞,无自动修复方案):") for tid, t in unreported_blocked: print(f" #{tid} {t[:70]}") # 标记已报告 reported.extend([tid for tid, _ in unreported_blocked]) REPORTED_BLOCKED_PATH.write_text(json.dumps(reported[-200:], ensure_ascii=False)) except: pass elapsed = time.time() - start if results: print(f"自愈执行器 | {datetime.now().strftime('%H:%M')} | {len(results)}条 ({elapsed:.0f}s)") for icon, msg in results: print(f" {icon} {msg}") else: print("[SILENT] 无待处理TODO") if __name__ == "__main__": main()