feat: TODO迁移到DB + no_agent自愈执行器

- 创建 mofin.db → todos 表(含fix_action/verification_check/retry机制)
- 创建 scripts/self_todo_executor.py(no_agent,纯代码逻辑,无LLM)
- 修改 morning_health_check.py:TODO写入DB而非JSON,新增derive_fix_action()
- cron替换:LLM cron → no_agent脚本,*/10 8-22高频轮询
- 成本:无pending时仅sqlite查询,约0.01s/次

处理链:
  health_check(8:00) → 可修直接自动修 → 不可修写DB(todos表) → 自愈执行器(每10分) → 有fix_action就执行 → 无fix_action标blocked留待人工
This commit is contained in:
知微
2026-06-24 20:44:36 +08:00
parent 26993c1d41
commit a76240b52d
3 changed files with 1007 additions and 951 deletions
+65 -41
View File
@@ -40,6 +40,21 @@ HERMES_CRON_DIR = Path("/home/hmo/.hermes/profiles/position-analyst/cron")
TODO_PATH = Path("/home/hmo/.hermes/profiles/position-analyst/todo.json")
def derive_fix_action(detail, msg):
"""根据issue信息推导可执行的修复命令"""
# 小果扫描 error → 脚本已更新,只验证
if "xiaoguo_scanner" in msg or "小果扫描" in msg:
return f"cd {BASE} && python3 xiaoguo_scanner.py --test 2>&1 | head -5"
# system-audit error → 验证拷贝
if "system_audit" in msg or "系统审计" in msg:
return f"ls -la /home/hmo/.hermes/profiles/position-analyst/scripts/system_audit.py 2>&1"
# 港股汇率 → 刷新
if "港股汇率" in msg:
return f"cd {BASE} && python3 hk_rate.py 2>&1"
# delivery目标 → 无自动修复
return None
def auto_fix_issue(issue):
"""对明确可自动修复的问题执行修复,返回 (fixed, fix_msg)"""
item_id = issue.get("detail", "")
@@ -105,52 +120,61 @@ def write_todos_for_issues():
for issue, fix_msg in fixed_issues:
print(f"{issue['category']}: {fix_msg}")
# 剩余的无法自动修复的→写TODO
# 剩余的无法自动修复的→写TODO到数据库
if not remaining:
return
# 读现有 TODO
existing = []
if TODO_PATH.exists():
try:
existing = json.loads(TODO_PATH.read_text())
except:
existing = []
existing_titles = {t.get("title", "") for t in existing}
todo_priority = {"critical": "high", "error": "medium", "warn": "low"}
new_items = []
for issue in remaining:
title = f"[体检发现] {issue['msg']}"
# 去重
if title in existing_titles:
for t in existing:
if t.get("title") == title:
if t.get("status") == "completed":
t["status"] = "pending"
t["priority"] = todo_priority.get(issue["level"], "medium")
t["note"] = f"重新打开: {ctx['started_at'].isoformat()}"
continue
try:
conn = sqlite3.connect(str(DB_PATH))
todo_priority = {"critical": "high", "error": "medium", "warn": "low"}
new_count = 0
existing_titles.add(title)
new_items.append({
"title": title,
"desc": f"体检发现于 {ctx['started_at'].strftime('%Y-%m-%d %H:%M')},分类: {issue['category']},详情: {issue.get('detail', '')}",
"status": "pending",
"priority": todo_priority.get(issue["level"], "medium"),
"created": ctx["started_at"].isoformat(),
"target": "health_check_fix",
})
if new_items:
existing.extend(new_items)
TODO_PATH.write_text(json.dumps(existing, ensure_ascii=False, indent=2))
for issue in remaining:
title = f"[体检发现] {issue['msg']}"
level = issue["level"]
pri = todo_priority.get(level, "medium")
# 去重:检查是否已有相同 title
existing = conn.execute(
"SELECT id, status FROM todos WHERE title=? AND status IN ('pending','in_progress','blocked')",
(title,)
).fetchone()
if existing:
if existing["status"] == "blocked":
# 已阻塞的重新打开
conn.execute(
"UPDATE todos SET status='pending', priority=?, note='已重新打开', updated_at=CURRENT_TIMESTAMP WHERE id=?",
(pri, existing["id"])
)
else:
# 生成fix_action(如果可推导)
fix_action = derive_fix_action(issue.get("detail", ""), issue.get("msg", ""))
conn.execute(
"INSERT INTO todos (title, description, priority, source, status, fix_action) "
"VALUES (?, ?, ?, 'health_check', 'pending', ?)",
(title,
f"体检发现于 {ctx['started_at'].strftime('%Y-%m-%d %H:%M')}\n分类: {issue['category']}\n详情: {issue.get('detail', '')}",
pri, fix_action)
)
new_count += 1
print()
print("📋 已加入TODO(待处理):")
for item in new_items:
print(f" [{item['priority']}] {item['title'][:70]}")
conn.commit()
conn.close()
if new_count > 0:
print()
print(f"📋 已加入TODO({new_count}条):")
# 重新查刚插入的pending记录
cur2 = conn.cursor()
for r2 in cur2.execute(
"SELECT title, priority FROM todos WHERE status='pending' AND source='health_check' "
"ORDER BY created_at DESC LIMIT ?", (new_count,)
).fetchall():
print(f" [{r2['priority']}] {r2['title'][:70]}")
conn.close()
except Exception as e:
print(f" TODO写入异常: {e}")
except Exception as e:
pass # TODO 写入失败不阻碍体检主流程