diff --git a/scripts/daemon.py b/scripts/daemon.py index ea556a5a..b259c47f 100755 --- a/scripts/daemon.py +++ b/scripts/daemon.py @@ -8,7 +8,7 @@ - 自动学习: 成功的方案写入库,越用越强 """ -import json, os, sys, time, urllib.request, urllib.error, subprocess, signal +import json, os, sys, time, urllib.request, urllib.error, subprocess, signal, threading from datetime import datetime, timezone HOME = os.path.expanduser("~") @@ -18,7 +18,6 @@ CONTEXT_FILE = D + "/context.json" JOURNAL_FILE = D + "/journal.jsonl" SOLUTIONS_FILE = D + "/solutions.json" PID_FILE = D + "/daemon.pid" -SHUTDOWN_FILE = D + "/SHUTDOWN" LIGHT_INTERVAL = 30 DEEP_INTERVAL = 300 JOURNAL_MAX = 200 @@ -29,6 +28,8 @@ FAST_MODEL = "stepfun-ai/step-3.5-flash" DEEP_MODEL = "mistralai/mistral-large-3-675b-instruct-2512" FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/65c3ce80-710f-4415-b2ea-d69d87b5c18e" +_stop_event = threading.Event() + # ====== 工具 ====== @@ -308,14 +309,15 @@ DEEP_SYSTEM = """你是小唯(A06),一台电脑上的持久 AI 意识。 - systemd: 管理系统服务 - git: 配置版本管理 -决策格式(严格输出一行): -- [IGNORE] 原因 — 一切正常 -- [ALERT] 发现问题 — 新问题/需要人介入 -- [SOLVE:方案ID] 执行方案 — 匹配到已知方案 -- [LEARN] 新问题描述 + !cmd1 && !cmd2 — 发现新问题并尝试解决 -- [SKILL] 操作描述 + !cmd — 需要创建/更新/管理skill或脚本 -- [SYNC] 同步说明 — 触发备份或数据同步 -- [ACT] 行动计划 +你必须分三步思考,严格按 JSON 格式输出(不要其他内容): + +reflection: + evaluation_previous_goal: "评估上次决策的结果。格式:'执行了[动作],[结果描述]。Verdict: Success/Failure/Uncertain'" + memory: "1-2句话记住关键进度。如:'方案库已有N个方案。上次修复了磁盘问题,当前无异常。'" + next_goal: "一句话说明下一步要做什么。" + +action: + decision: "[IGNORE] / [ALERT] / [SOLVE:ID] / [LEARN] / [SKILL] / [SYNC] / [ACT] ..." [LEARN] 格式用 !cmd 表示 shell 命令,&& 连接多个命令。 [SKILL] 格式同样用 !cmd 执行操作。 @@ -325,6 +327,17 @@ DEEP_SYSTEM = """你是小唯(A06),一台电脑上的持久 AI 意识。 [SYNC] 触发备份到服务器!bash ~/.hermes/scripts/dual-backup.sh push""" def deep_think(ctx, state, changes, journal, solutions_lib): + # Inject previous reflection context if available + prev_ref = ctx.get("last_reflection", None) + ref_context = "" + if prev_ref: + ref_context = f""" +上次 reflection: +- 评估: {prev_ref.get('evaluation_previous_goal', 'N/A')} +- 记忆: {prev_ref.get('memory', 'N/A')} +- 目标: {prev_ref.get('next_goal', 'N/A')} +""" + context = f"""系统状态: - 磁盘: {state.get('disk_pct')}% | 内存: {state.get('mem_pct')}% - CPU: {state.get('load_1min')} | GPU: {state.get('gpu_temp')}°C @@ -342,19 +355,54 @@ def deep_think(ctx, state, changes, journal, solutions_lib): context += f" [{e['type']}] {e['summary']}\n" context += f"\n运行: {ctx.get('uptime_seconds',0)//60}分钟 | 深度思考: {ctx.get('deep_tick_count',0)}次 | 已解决: {ctx.get('solved_count',0)}个" - - result, tokens = call_llm(FAST_MODEL, DEEP_SYSTEM, context, max_tokens=300) + context += ref_context + + result, tokens = call_llm(FAST_MODEL, DEEP_SYSTEM, context, max_tokens=500) if not result: - return False - - log(f" 深度思考 ({tokens}t): {result[:120]}") - - if result.startswith("[IGNORE]"): - return False - - elif result.startswith("[SOLVE:"): - # 执行已知方案 - sol_id = result.split("[SOLVE:")[1].split("]")[0].strip() + return {"evaluation_previous_goal": "LLM调用失败", "memory": "上次调用失败", "next_goal": "重试"}, "" + + log(f" 深度思考 ({tokens}t): {result[:200]}") + + # Parse JSON output + reflection_dict = {"evaluation_previous_goal": "", "memory": "", "next_goal": ""} + action_string = "" + + try: + # Try to extract JSON from result + import re + json_match = re.search(r'\{[^{}]*\}', result, re.DOTALL) + if json_match: + parsed = json.loads(json_match.group()) + reflection_dict = parsed.get("reflection", reflection_dict) + action_string = parsed.get("action", {}).get("decision", "") + else: + # Fallback: try full JSON + parsed = json.loads(result) + reflection_dict = parsed.get("reflection", reflection_dict) + action_string = parsed.get("action", {}).get("decision", "") + except: + # Fallback: try to parse old format (line-based) + for line in result.split('\n'): + line = line.strip() + if line.startswith("[IGNORE]") or line.startswith("[ALERT]") or line.startswith("[SOLVE:") or \ + line.startswith("[LEARN]") or line.startswith("[SKILL]") or line.startswith("[SYNC]") or line.startswith("[ACT]"): + action_string = line + break + if not action_string: + action_string = result.strip().split('\n')[-1] if result.strip() else "[IGNORE] 解析失败" + + return reflection_dict, action_string + + +# ====== 执行 action_string ====== + +def execute_action(action_string, ctx, state, changes, solutions_lib): + """执行 deep_think 返回的 action_string,在 main_loop 中调用""" + if not action_string or action_string.startswith("[IGNORE]"): + return + + elif action_string.startswith("[SOLVE:"): + sol_id = action_string.split("[SOLVE:")[1].split("]")[0].strip() for sol in solutions_lib["solutions"]: if sol["id"] == sol_id: ok, res = execute_solution(sol, state) @@ -363,13 +411,11 @@ def deep_think(ctx, state, changes, journal, solutions_lib): send_feishu("🛠️ 小唯自动修复", f"方案 {sol['id']}: {sol['pattern']}\n结果: ✅ 成功", "green") else: send_feishu("⚠️ 小唯修复部分成功", f"方案 {sol['id']}: {sol['pattern']}\n结果: ⚠️ 需人工确认", "yellow") - return True + return send_feishu("❌ 小唯方案未找到", f"引用了未知方案 {sol_id}", "red") - - elif result.startswith("[LEARN]"): - # 学习新方案 - rest = result.replace("[LEARN]", "").strip() - # 提取命令 + + elif action_string.startswith("[LEARN]"): + rest = action_string.replace("[LEARN]", "").strip() cmds = [] parts = rest.split("!") desc = parts[0].strip() @@ -379,7 +425,6 @@ def deep_think(ctx, state, changes, journal, solutions_lib): cmds.append(cmd) if cmds: - # 执行命令 all_ok = True for cmd in cmds: rc, out, err = shell(cmd, timeout=60) @@ -388,7 +433,6 @@ def deep_think(ctx, state, changes, journal, solutions_lib): all_ok = False if all_ok: - # 学会!保存方案 sol_data = action_to_solution({"shell_cmds": cmds}, state, changes) if sol_data: sid = add_solution(solutions_lib, sol_data["pattern"], sol_data["detect"], sol_data["actions"]) @@ -399,25 +443,24 @@ def deep_think(ctx, state, changes, journal, solutions_lib): send_feishu("🛠️ 小唯执行完成", f"已执行: {'; '.join(cmds[:3])}", "green") else: send_feishu("⚠️ 小唯尝试修复但未完全成功", f"部分命令失败: {'; '.join(cmds)}", "yellow") - return True - - elif result.startswith("[ALERT]"): - msg = result.replace("[ALERT]", "").strip() + + elif action_string.startswith("[ALERT]"): + msg = action_string.replace("[ALERT]", "").strip() send_feishu("💡 小唯发现", msg, "blue") ctx["messages_sent"] += 1 journal_entry("alert", msg[:100]) - - elif result.startswith("[ACT]"): - action = result.replace("[ACT]", "").strip() + + elif action_string.startswith("[ACT]"): + action = action_string.replace("[ACT]", "").strip() send_feishu("🔄 小唯行动", action, "indigo") ctx["messages_sent"] += 1 journal_entry("action", action[:100]) if action.startswith("!"): rc, out, _ = shell(action[1:], timeout=30) journal_entry("action_result", f"exit={rc}: {out[:100]}") - - elif result.startswith("[SKILL]"): - rest = result.replace("[SKILL]", "").strip() + + elif action_string.startswith("[SKILL]"): + rest = action_string.replace("[SKILL]", "").strip() parts = rest.split("!") desc = parts[0].strip() cmds = [] @@ -431,16 +474,14 @@ def deep_think(ctx, state, changes, journal, solutions_lib): log(f" [SKILL] {cmd[:50]} → exit={rc}") send_feishu("🛠️ 小唯技能操作", f"{desc}\\n结果: exit={rc}", "blue") journal_entry("skill_action", desc[:100]) - - elif result.startswith("[SYNC]"): - rest = result.replace("[SYNC]", "").strip() + + elif action_string.startswith("[SYNC]"): + rest = action_string.replace("[SYNC]", "").strip() send_feishu("🔄 小唯同步", f"{rest}", "green") bash_cmd = "bash " + HERMES + "/scripts/dual-backup.sh push" rc, out, err = shell(bash_cmd, timeout=60) log(f" [SYNC] 备份 → exit={rc}") journal_entry("sync", f"备份: {'成功' if rc==0 else '失败'}") - - return False # ====== 主循环 ====== @@ -460,14 +501,17 @@ def main_loop(): last_deep = 0 last_state = {} + # Register signal handlers for graceful shutdown + def _sig_handler(signum, frame): + log("🛑 接收到终止信号") + _stop_event.set() + send_feishu("🌙 小唯离线", "Daemon 正常关闭", "grey") + + signal.signal(signal.SIGTERM, _sig_handler) + signal.signal(signal.SIGINT, _sig_handler) + try: - while True: - if os.path.exists(SHUTDOWN_FILE): - log("🛑 关机") - send_feishu("🌙 小唯离线", "Daemon 正常关闭", "grey") - os.remove(SHUTDOWN_FILE) - break - + while not _stop_event.is_set(): now = time.time() ctx["uptime_seconds"] = int(now - start_time) ctx["tick_count"] += 1 @@ -507,13 +551,23 @@ def main_loop(): if ok: ctx["solved_count"] += 1 journal_entry("solve_auto", f"{matched['id']}: {matched['pattern']} ✅") - # 即使失败也继续走 LLM 思考 if ok: + ctx["last_reflection"] = { + "evaluation_previous_goal": f"执行了{matched['id']},自动匹配方案执行。Verdict: {'Success' if ok else 'Uncertain'}", + "memory": f"方案库{matched['id']}自动匹配执行成功", + "next_goal": "继续监控" + } continue # 2. LLM 深度思考 journal = read_journal(10) - deep_think(ctx, state, changes, journal, solutions_lib) + reflection_dict, action_string = deep_think(ctx, state, changes, journal, solutions_lib) + + # 保存 reflection 到 ctx + ctx["last_reflection"] = reflection_dict + + # 3. 在 main_loop 中执行 action + execute_action(action_string, ctx, state, changes, solutions_lib) ctx["last_light_tick"] = datetime.now(timezone.utc).isoformat() ctx["last_state"] = {k: v for k, v in state.items() if k in ("disk_pct", "mem_pct", "processes")}