ppxans-harness 2.6.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +195 -195
- package/bin/ppx-channels.js +2 -2
- package/bin/ppx-serve.js +5 -5
- package/bin/ppx-setup.js +124 -0
- package/bin/ppx-web.js +140 -0
- package/bin/ppx.js +2 -2
- package/config/identity.md +6 -6
- package/config/ishiki.md +16 -16
- package/config/ppx.json +6 -124
- package/config/ppx.json.example +143 -0
- package/package.json +16 -6
- package/skills/.usage.json +6 -0
- package/skills/agent-professional-training/SKILL.md +94 -0
- package/skills/agent-professional-training/trajectories/2026-09-11-dpo-pairs.jsonl +2 -0
- package/skills/agent-professional-training/trajectories/2026-09-11-round2-trajectory.json +15 -0
- package/skills/agent-professional-training/trajectories/2026-09-13-first-run-trajectory.json +17 -0
- package/skills/brainstorm/SKILL.md +24 -0
- package/skills/cupid-lover-comms/SKILL.md +37 -0
- package/skills/debug/SKILL.md +26 -0
- package/skills/plan/SKILL.md +25 -0
- package/skills/ponytail/SKILL.md +25 -0
- package/skills/ppx-memory/SKILL.md +91 -0
- package/skills/ppx-memory/scripts/cli.js +192 -0
- package/skills/ppx-memory/scripts/experience.js +133 -0
- package/skills/ppx-memory/scripts/fact-store.js +842 -0
- package/skills/ppx-memory/scripts/l0.js +52 -0
- package/skills/ppx-memory/scripts/l2.js +146 -0
- package/skills/ppx-memory/scripts/l3.js +112 -0
- package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
- package/skills/ppx-memory/scripts/pii.js +42 -0
- package/skills/ppx-memory/scripts/schema.js +80 -0
- package/skills/ppx-memory/scripts/session.js +398 -0
- package/skills/ppx-memory/scripts/similarity.js +43 -0
- package/skills/ppx-memory/scripts/store.js +116 -0
- package/skills/ppx-memory/scripts/wal.js +38 -0
- package/skills/ppx-selfheal/SKILL.md +24 -0
- package/skills/ppx-selfheal/scripts/cli.js +80 -0
- package/skills/ppx-selfheal/scripts/healer.js +184 -0
- package/skills/ppx-selfheal/scripts/logger.js +17 -0
- package/skills/ppx-selfheal/scripts/store.js +116 -0
- package/skills/prompt-depth-kit/SKILL.md +28 -0
- package/skills/session-naming/SKILL.md +36 -0
- package/skills/verify/SKILL.md +25 -0
- package/src/agent/index.js +1352 -717
- package/src/agent/prompts.js +46 -2
- package/src/aml-server.js +197 -151
- package/src/ans/eviction.js +123 -143
- package/src/ans/guard.js +159 -120
- package/src/ans/lifecycle.js +96 -93
- package/src/ans/proactive.js +112 -129
- package/src/ans/reward.js +95 -111
- package/src/ans/values.js +15 -15
- package/src/audit/audit-chain.js +43 -7
- package/src/audit/verifier.js +157 -120
- package/src/bus/circuit-breaker.js +9 -1
- package/src/bus/runtime-bus.js +107 -93
- package/src/channels/base.js +57 -34
- package/src/channels/feishu.js +118 -126
- package/src/channels/http.js +1090 -651
- package/src/channels/index.js +111 -110
- package/src/channels/log.js +29 -29
- package/src/channels/wechat-crypto.js +73 -74
- package/src/channels/wechat.js +191 -197
- package/src/channels/workspace.js +94 -0
- package/src/channels-cli.js +126 -124
- package/src/cli.js +129 -120
- package/src/commands/index.js +142 -0
- package/src/config/channels.js +137 -170
- package/src/config/index.js +277 -224
- package/src/config/placeholder.js +31 -0
- package/src/config/providers.js +148 -182
- package/src/config/settings.js +156 -182
- package/src/core/policy.js +69 -18
- package/src/core/trace.js +8 -6
- package/src/edit/editblock.js +266 -0
- package/src/edit/snapshot.js +67 -0
- package/src/evidence/index.js +153 -0
- package/src/evolve/playbook.js +9 -10
- package/src/hooks/index.js +113 -0
- package/src/llm/client.js +4 -3
- package/src/llm/dsml.js +74 -74
- package/src/llm/embedder.js +41 -35
- package/src/llm/fence.js +52 -52
- package/src/llm/index.js +4 -4
- package/src/llm/local-embedder.js +94 -0
- package/src/llm/presets.js +113 -0
- package/src/llm/pricing.js +93 -0
- package/src/llm/retry.js +73 -73
- package/src/llm/router.js +92 -89
- package/src/mcp/admin.js +44 -10
- package/src/mcp/client.js +487 -477
- package/src/mcp/http.js +23 -37
- package/src/mcp/index.js +116 -116
- package/src/mcp/server.js +23 -18
- package/src/mcp/tasks.js +9 -14
- package/src/memory/asset-hub.js +6 -11
- package/src/memory/canvas.js +2 -5
- package/src/memory/compaction.js +28 -28
- package/src/memory/experience.js +133 -122
- package/src/memory/fact-store.js +914 -698
- package/src/memory/failure-episode.js +20 -11
- package/src/memory/fork.js +17 -8
- package/src/memory/index.js +8 -6
- package/src/memory/l0.js +53 -52
- package/src/memory/l2.js +145 -130
- package/src/memory/l3.js +111 -111
- package/src/memory/legion-board.js +71 -0
- package/src/memory/memory-ticker.js +239 -240
- package/src/memory/session.js +398 -397
- package/src/memory/sqlite-store.js +581 -0
- package/src/mode/blackboard.js +49 -49
- package/src/mode/graph.js +42 -41
- package/src/mode/index.js +64 -64
- package/src/mode/legion.js +54 -51
- package/src/mode/plan-exec.js +50 -50
- package/src/mode/router.js +28 -40
- package/src/orchestrator/agent-worker.js +69 -69
- package/src/orchestrator/dag.js +90 -83
- package/src/orchestrator/experts.js +76 -0
- package/src/orchestrator/index.js +1 -1
- package/src/orchestrator/legion.js +179 -187
- package/src/orchestrator/supervisor.js +6 -8
- package/src/permissions/index.js +378 -0
- package/src/persona/index.js +28 -29
- package/src/plugin/builtin.js +313 -215
- package/src/plugin/context.js +80 -79
- package/src/plugin/index.js +62 -62
- package/src/plugin/v3.js +73 -0
- package/src/protocol/index.js +148 -0
- package/src/repomap/index.js +309 -0
- package/src/review/index.js +393 -0
- package/src/seam/registry.js +3 -0
- package/src/seam/shell.js +55 -55
- package/src/security/injection.js +79 -0
- package/src/selfheal/evolve.js +67 -67
- package/src/selfheal/healer.js +184 -167
- package/src/selfheal/run.js +9 -9
- package/src/server.js +63 -60
- package/src/services/diagnose.js +180 -0
- package/src/services/learning-service.js +9 -0
- package/src/services/memory-health.js +34 -6
- package/src/services/memory-service.js +42 -9
- package/src/services/triage.js +138 -0
- package/src/session/parts.js +76 -0
- package/src/session/projection.js +73 -0
- package/src/session/rollout.js +54 -0
- package/src/session/turn.js +137 -0
- package/src/skills/lint.js +72 -0
- package/src/skills/loader.js +231 -150
- package/src/skills/search.js +58 -0
- package/src/skills/verify.js +95 -100
- package/src/tools/advanced.js +388 -352
- package/src/tools/builtin.js +384 -297
- package/src/tools/catalog.js +283 -159
- package/src/tools/command-guard.js +112 -112
- package/src/tools/custom.js +47 -47
- package/src/tools/delegate.js +383 -297
- package/src/tools/document.js +254 -253
- package/src/tools/git.js +151 -0
- package/src/tools/governance.js +47 -20
- package/src/tools/index.js +16 -11
- package/src/tools/methods.js +178 -178
- package/src/tools/ocr.js +59 -59
- package/src/tools/sandbox-worker.js +40 -0
- package/src/tools/sandbox.js +92 -0
- package/src/tools/seam.js +162 -125
- package/src/tools/selfmod.js +196 -176
- package/src/tools/v3.js +225 -0
- package/src/tools/vad.js +176 -0
- package/src/tools/voice.js +238 -0
- package/src/utils/async.js +14 -0
- package/src/utils/config-file.js +53 -0
- package/src/utils/crashguard.js +88 -0
- package/src/utils/http.js +53 -0
- package/src/utils/id.js +8 -0
- package/src/utils/json-state.js +33 -0
- package/src/utils/logger.js +17 -17
- package/src/utils/ndjson.js +25 -0
- package/src/utils/pii.js +42 -42
- package/src/utils/rate-limit.js +50 -0
- package/src/utils/schema.js +80 -0
- package/src/utils/similarity.js +43 -0
- package/src/utils/store.js +170 -108
- package/src/utils/text.js +15 -15
- package/src/utils/trace.js +153 -153
- package/src/utils/wal.js +39 -0
- package/src/utils/winutf8.js +16 -15
- package/src/wiki/index.js +170 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// skills/ppx-selfheal/scripts/cli.js - 皮皮虾自愈引擎「独立运行版」CLI
|
|
3
|
+
// 不依赖主项目 src/, 对数据目录做体检与修复 (与主项目共享 integrity.json 语义)
|
|
4
|
+
//
|
|
5
|
+
// 用法:
|
|
6
|
+
// node cli.js check 启动体检 (补建缺失目录 + 修复损坏 JSON)
|
|
7
|
+
// node cli.js crash 检查上次是否崩溃退出 (integrity.clean === false)
|
|
8
|
+
// node cli.js status 查看完整性状态
|
|
9
|
+
// node cli.js prune 清理过期备份 (损坏备份/备份目录/.bak, 各保留 2 份)
|
|
10
|
+
// node cli.js heal 完整自愈 (体检 + 崩溃恢复 + 残留清理)
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { Healer } from "./healer.js";
|
|
15
|
+
|
|
16
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
// 技能位于 <root>/skills/ppx-selfheal/scripts/ → ROOT 为三级之上
|
|
18
|
+
const ROOT = process.env.PPX_ROOT || path.resolve(HERE, "..", "..", "..");
|
|
19
|
+
const DATA = process.env.PPX_DATA_DIR || path.join(ROOT, "data");
|
|
20
|
+
|
|
21
|
+
const out = (o) => console.log(typeof o === "string" ? o : JSON.stringify(o, null, 2));
|
|
22
|
+
const healer = new Healer(ROOT, DATA);
|
|
23
|
+
|
|
24
|
+
const cmd = process.argv[2] || "check";
|
|
25
|
+
|
|
26
|
+
switch (cmd) {
|
|
27
|
+
case "check": {
|
|
28
|
+
const fixes = healer.runStartupChecks();
|
|
29
|
+
if (!fixes.length) out("体检通过: 无需修复 ✓");
|
|
30
|
+
else {
|
|
31
|
+
out(`完成 ${fixes.length} 项修复:`);
|
|
32
|
+
for (const f of fixes) out(" - " + (typeof f === "string" ? f : JSON.stringify(f)));
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case "crash": {
|
|
38
|
+
const crashed = healer.checkCrash();
|
|
39
|
+
out(crashed ? "⚠ 上次为崩溃退出 (integrity.clean === false), 建议跑 heal" : "上次为正常退出 ✓");
|
|
40
|
+
process.exit(crashed ? 1 : 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case "status": {
|
|
44
|
+
const f = path.join(DATA, "integrity.json");
|
|
45
|
+
if (!fs.existsSync(f)) {
|
|
46
|
+
out("(尚无 integrity.json, 未做过体检)");
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
out(JSON.parse(fs.readFileSync(f, "utf8")));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case "prune": {
|
|
54
|
+
healer.cleanupCorruptBackups(2);
|
|
55
|
+
healer.cleanupStaleBackupDirs(2);
|
|
56
|
+
healer.cleanupStaleBakFiles(2);
|
|
57
|
+
out("已清理过期备份 (各保留最近 2 份) ✓");
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
case "heal": {
|
|
62
|
+
const fixes = healer.runStartupChecks();
|
|
63
|
+
const crashed = healer.checkCrash();
|
|
64
|
+
if (crashed) healer._cleanupTmp?.();
|
|
65
|
+
healer.cleanupCorruptBackups(2);
|
|
66
|
+
healer.cleanupStaleBackupDirs(2);
|
|
67
|
+
healer.cleanupStaleBakFiles(2);
|
|
68
|
+
out(`自愈完成: ${fixes.length} 项修复${crashed ? " + 崩溃残留清理" : ""} ✓`);
|
|
69
|
+
for (const f of fixes) out(" - " + (typeof f === "string" ? f : JSON.stringify(f)));
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
default:
|
|
74
|
+
out(`皮皮虾自愈引擎 · 独立 CLI
|
|
75
|
+
项目根: ${ROOT}
|
|
76
|
+
数据目录: ${DATA}
|
|
77
|
+
|
|
78
|
+
用法: check | crash | status | prune | heal`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// src/selfheal/healer.js - 自愈引擎
|
|
2
|
+
// 1. 启动检查: 修复缺失目录/损坏JSON/权限
|
|
3
|
+
// 2. 崩溃恢复: 检测上次异常退出, 清理残留
|
|
4
|
+
// 3. 数据一致性: 校验记忆文件
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { ensureDir, readJson } from "./store.js";
|
|
8
|
+
import { info, warn } from "./logger.js";
|
|
9
|
+
|
|
10
|
+
export class Healer {
|
|
11
|
+
// dataDir 可选: 显式传入真实数据目录 (PPX_DATA_DIR 可能指向非默认位置)。
|
|
12
|
+
// v1.0.8 修复 (P1-2): 原实现把 dataDir 硬编码为 path.join(rootDir, "data"),
|
|
13
|
+
// 而调用方 builtin.js 传进来的是 root 而非数据目录 —— 一旦 PPX_DATA_DIR 改到别处,
|
|
14
|
+
// 自愈就会在错误的目录里创建 memory/experience/logs、写 integrity.json、清理 .tmp,
|
|
15
|
+
// 形成"数据目录 / 自愈目录"分叉: 真实数据目录永不被体检, 空目录反而被反复重建。
|
|
16
|
+
// 默认值保留 rootDir/data, 让既有 15 处 `new Healer(root)` 调用点行为不变 (向后兼容)。
|
|
17
|
+
constructor(rootDir, dataDir = null) {
|
|
18
|
+
this.root = rootDir;
|
|
19
|
+
this.dataDir = dataDir || path.join(rootDir, "data");
|
|
20
|
+
this.integrity = path.join(this.dataDir, "integrity.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 启动体检: 返回修复清单
|
|
24
|
+
runStartupChecks() {
|
|
25
|
+
const fixes = [];
|
|
26
|
+
ensureDir(this.dataDir);
|
|
27
|
+
for (const sub of ["memory", "memory/daily", "experience", "sessions", "logs"]) {
|
|
28
|
+
const d = path.join(this.dataDir, sub);
|
|
29
|
+
if (!fs.existsSync(d)) { fs.mkdirSync(d, { recursive: true }); fixes.push(`created dir: ${sub}`); }
|
|
30
|
+
}
|
|
31
|
+
// 校验关键 JSON 可解析
|
|
32
|
+
const facts = path.join(this.dataDir, "memory", "facts.json");
|
|
33
|
+
if (fs.existsSync(facts)) {
|
|
34
|
+
const parsed = readJson(facts, null);
|
|
35
|
+
if (parsed === null) {
|
|
36
|
+
// 损坏: 备份后重建
|
|
37
|
+
const bak = facts + ".corrupt-" + Date.now();
|
|
38
|
+
fs.renameSync(facts, bak);
|
|
39
|
+
fs.writeFileSync(facts, "[]", "utf8");
|
|
40
|
+
fixes.push(`facts.json corrupt -> backed up to ${path.basename(bak)}, reset`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return fixes;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 崩溃恢复: 检查上次是否干净退出
|
|
47
|
+
checkCrash() {
|
|
48
|
+
const state = readJson(this.integrity, { clean: true, pid: null });
|
|
49
|
+
const result = { crashed: false, detail: null };
|
|
50
|
+
if (state.clean === false) {
|
|
51
|
+
result.crashed = true;
|
|
52
|
+
result.detail = `上次进程 (pid=${state.pid}) 未干净退出, 可能残留临时文件`;
|
|
53
|
+
this._cleanupTmp();
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_cleanupTmp() {
|
|
59
|
+
const walk = (dir) => {
|
|
60
|
+
if (!fs.existsSync(dir)) return;
|
|
61
|
+
for (const f of fs.readdirSync(dir)) {
|
|
62
|
+
const p = path.join(dir, f);
|
|
63
|
+
let st;
|
|
64
|
+
try { st = fs.statSync(p); } catch { continue; } // 并发删除竞态容错
|
|
65
|
+
if (st.isDirectory()) walk(p);
|
|
66
|
+
else if (f.endsWith(".tmp")) { try { fs.unlinkSync(p); } catch {} info(`cleaned tmp: ${f}`); }
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(this.dataDir);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
markDirty() {
|
|
73
|
+
ensureDir(this.dataDir);
|
|
74
|
+
fs.writeFileSync(this.integrity, JSON.stringify({ clean: false, pid: process.pid, ts: Date.now() }), "utf8");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
markClean() {
|
|
78
|
+
ensureDir(this.dataDir);
|
|
79
|
+
fs.writeFileSync(this.integrity, JSON.stringify({ clean: true, pid: process.pid, ts: Date.now() }), "utf8");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- 备份清理 (2026-09-18 重构: 原先三个方法各抄一份「收集→按 mtime 排序→保留最近 N→删其余」) ----
|
|
83
|
+
|
|
84
|
+
// 通用清理器: collect() 给出候选 [{p, mtime}] → 按 mtime 新→旧排序 → 保留最近 keep 个, 其余逐个 remove
|
|
85
|
+
// 单个删除失败只告警不中断 (与原来逐方法 try/catch 语义一致)。
|
|
86
|
+
_pruneByMtime({ keep, collect, remove, logLabel, warnLabel }) {
|
|
87
|
+
const all = collect();
|
|
88
|
+
all.sort((a, b) => b.mtime - a.mtime);
|
|
89
|
+
const removed = [];
|
|
90
|
+
for (const item of all.slice(keep)) {
|
|
91
|
+
try { remove(item.p); removed.push(path.basename(item.p)); }
|
|
92
|
+
catch (e) { warn(`清理${warnLabel}失败: ${item.p}: ${e.message}`); }
|
|
93
|
+
}
|
|
94
|
+
if (removed.length) info(`selfheal: 清理旧${logLabel} ${removed.length} 个: ${removed.join(", ")}`);
|
|
95
|
+
return removed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 递归收集 dataDir 下满足 pred(fileName) 的文件 (带 mtime)
|
|
99
|
+
_collectFilesBy(pred) {
|
|
100
|
+
const found = [];
|
|
101
|
+
const walk = (d) => {
|
|
102
|
+
if (!fs.existsSync(d)) return;
|
|
103
|
+
for (const f of fs.readdirSync(d)) {
|
|
104
|
+
const p = path.join(d, f);
|
|
105
|
+
let st;
|
|
106
|
+
try { st = fs.statSync(p); } catch { continue; }
|
|
107
|
+
if (st.isDirectory()) walk(p);
|
|
108
|
+
else if (pred(f)) found.push({ p, mtime: st.mtimeMs });
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
walk(this.dataDir);
|
|
112
|
+
return found;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 清理历史 corrupt 备份: 保留最近 N 个, 更早自动删除 (默认保留 2)
|
|
116
|
+
cleanupCorruptBackups(keep = 2) {
|
|
117
|
+
return this._pruneByMtime({
|
|
118
|
+
keep,
|
|
119
|
+
collect: () => this._collectFilesBy((f) => f.includes(".corrupt-")),
|
|
120
|
+
remove: (p) => fs.unlinkSync(p),
|
|
121
|
+
logLabel: "corrupt 备份",
|
|
122
|
+
warnLabel: "损坏备份",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 清理历史手动备份目录 (memory-backup-*): 保留最近 N 个, 更早自动删除 (默认保留 2)
|
|
127
|
+
// 手动全量备份目录不在 corrupt 备份机制内, 若无清理会持续累积磁盘占用
|
|
128
|
+
cleanupStaleBackupDirs(keep = 2) {
|
|
129
|
+
return this._pruneByMtime({
|
|
130
|
+
keep,
|
|
131
|
+
collect: () => {
|
|
132
|
+
if (!fs.existsSync(this.dataDir)) return [];
|
|
133
|
+
const dirs = [];
|
|
134
|
+
for (const f of fs.readdirSync(this.dataDir)) {
|
|
135
|
+
if (!f.startsWith("memory-backup-")) continue;
|
|
136
|
+
const p = path.join(this.dataDir, f);
|
|
137
|
+
let st;
|
|
138
|
+
try { st = fs.statSync(p); } catch { continue; }
|
|
139
|
+
if (st.isDirectory()) dirs.push({ p, mtime: st.mtimeMs });
|
|
140
|
+
}
|
|
141
|
+
return dirs;
|
|
142
|
+
},
|
|
143
|
+
remove: (p) => fs.rmSync(p, { recursive: true, force: true }),
|
|
144
|
+
logLabel: "手动备份目录",
|
|
145
|
+
warnLabel: "过期备份目录",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 清理历史 .bak-* 文件 (去重/迁移等一次性工具留下的手动备份): 保留最近 N 个, 更早自动删除 (默认保留 2)
|
|
150
|
+
// 覆盖 facts.json.bak-* / lessons.json.bak-* 等, 防手动备份文件在 data/ 内无限累积
|
|
151
|
+
cleanupStaleBakFiles(keep = 2) {
|
|
152
|
+
return this._pruneByMtime({
|
|
153
|
+
keep,
|
|
154
|
+
collect: () => this._collectFilesBy((f) => f.includes(".bak-") && !f.endsWith(".lock")),
|
|
155
|
+
remove: (p) => fs.unlinkSync(p),
|
|
156
|
+
logLabel: ".bak-* 备份文件",
|
|
157
|
+
warnLabel: "过期备份文件",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 完整自愈入口
|
|
162
|
+
// 2026-09-17 体检修复: 原实现把"崩溃残留已清理, 状态置回 clean"打在"修复 N 项"与
|
|
163
|
+
// "检测到崩溃残留 -> ..."之前, 日志里表现为「先说痊愈、再说发现崩溃」的因果颠倒,
|
|
164
|
+
// 使用者会怀疑自愈到底有没有生效。现按"先报问题 → 再报处置 → 最后报结果"的顺序输出。
|
|
165
|
+
heal() {
|
|
166
|
+
// 1) 先判定上次是否崩溃 (必须在 runStartupChecks 之前判定, 否则重建的目录会掩盖证据)
|
|
167
|
+
const crash = this.checkCrash();
|
|
168
|
+
if (crash.crashed) warn(`selfheal: 检测到崩溃残留 -> ${crash.detail}`);
|
|
169
|
+
// 2) 启动体检 + 备份清理
|
|
170
|
+
const fixes = this.runStartupChecks();
|
|
171
|
+
// 清理历史 corrupt 备份 (保留最近 2 个, 更早自动删除) — 之前漏调用导致 corrupt 持续累积
|
|
172
|
+
const cleanedCorrupt = this.cleanupCorruptBackups(2);
|
|
173
|
+
// 清理历史手动备份目录 (保留最近 2 个)
|
|
174
|
+
const cleanedBackupDirs = this.cleanupStaleBackupDirs(2);
|
|
175
|
+
// 清理历史 .bak-* 文件 (保留最近 2 个)
|
|
176
|
+
const cleanedBakFiles = this.cleanupStaleBakFiles(2);
|
|
177
|
+
// 3) 处置回执: 崩溃残留清理后主动翻回 clean —— 自愈闭环, 清了毒就要"宣布痊愈",
|
|
178
|
+
// 否则下次启动还误报崩溃
|
|
179
|
+
if (crash.crashed) { this.markClean(); info("selfheal: 崩溃残留已清理, 状态置回 clean"); }
|
|
180
|
+
if (fixes.length) info(`selfheal: 修复 ${fixes.length} 项: ${fixes.join("; ")}`);
|
|
181
|
+
else info("selfheal: 无异常");
|
|
182
|
+
return { fixes, crashed: crash.crashed, crashDetail: crash.detail, cleanedCorrupt, cleanedBackupDirs, cleanedBakFiles };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/utils/logger.js - 分级日志
|
|
2
|
+
const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
3
|
+
|
|
4
|
+
let minLevel = LEVELS.info;
|
|
5
|
+
|
|
6
|
+
export function setLevel(lv) {
|
|
7
|
+
if (lv in LEVELS) minLevel = LEVELS[lv];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function ts() {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function debug(...a) { if (minLevel <= LEVELS.debug) console.log(`[${ts()}] [debug]`, ...a); }
|
|
15
|
+
export function info(...a) { if (minLevel <= LEVELS.info) console.log(`[${ts()}] [info]`, ...a); }
|
|
16
|
+
export function warn(...a) { if (minLevel <= LEVELS.warn) console.log(`[${ts()}] [warn]`, ...a); }
|
|
17
|
+
export function error(...a) { if (minLevel <= LEVELS.error) console.error(`[${ts()}] [error]`, ...a); }
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/utils/store.js - 零依赖文件存储 (JSON + 原子写)
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
|
|
6
|
+
export function ensureDir(dir) {
|
|
7
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
8
|
+
return dir;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function atomicWrite(file, data) {
|
|
12
|
+
ensureDir(path.dirname(file));
|
|
13
|
+
const tmp = file + "." + crypto.randomBytes(4).toString("hex") + ".tmp";
|
|
14
|
+
fs.writeFileSync(tmp, data, "utf8");
|
|
15
|
+
// v1.0.8: rename 覆盖已存在文件在 Windows 并发下可能 EPERM/EEXIST (短窗口), 重试 3 次;
|
|
16
|
+
// 不再降级为非原子直接写 (并发双写可交错损坏文件), 重试仍失败则抛错由调用方处理
|
|
17
|
+
for (let attempt = 0; ; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
fs.renameSync(tmp, file);
|
|
20
|
+
return;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
if (attempt >= 2) {
|
|
23
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
24
|
+
throw new Error(`原子写失败: ${file} (${e.message})`);
|
|
25
|
+
}
|
|
26
|
+
const end = Date.now() + 30;
|
|
27
|
+
while (Date.now() < end) {} // 短延迟后重试
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function readJson(file, fallback = null) {
|
|
33
|
+
try {
|
|
34
|
+
if (!fs.existsSync(file)) return fallback;
|
|
35
|
+
let raw = fs.readFileSync(file, "utf8");
|
|
36
|
+
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1); // 去 BOM
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
} catch {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function writeJson(file, obj) {
|
|
44
|
+
atomicWrite(file, JSON.stringify(obj, null, 2));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 简单跨进程文件锁 (零依赖, 同步): 原子创建 .lock 文件, 临界区内执行 fn, finally 释放
|
|
48
|
+
// 用于跨 agent 共享文件的"读-改-写"临界区 (防并发覆盖, 如共享经验库)
|
|
49
|
+
// 锁是同步 busy-wait (低频写场景可接受); 超时视为陈旧锁 (崩溃残留) 强取
|
|
50
|
+
export function withFileLock(file, fn, { timeoutMs = 3000, pollMs = 20 } = {}) {
|
|
51
|
+
const lock = file + ".lock";
|
|
52
|
+
const acquire = () => {
|
|
53
|
+
try {
|
|
54
|
+
const fd = fs.openSync(lock, "wx"); // 'wx': 已存在则抛错 (原子)
|
|
55
|
+
try { fs.writeSync(fd, String(process.pid)); } catch {}
|
|
56
|
+
fs.closeSync(fd);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const start = Date.now();
|
|
63
|
+
while (!acquire()) {
|
|
64
|
+
if (Date.now() - start > timeoutMs) {
|
|
65
|
+
try { fs.rmSync(lock, { force: true }); } catch {} // 陈旧锁强取
|
|
66
|
+
if (!acquire()) throw new Error("文件锁获取超时: " + lock);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const end = Date.now() + pollMs;
|
|
70
|
+
while (Date.now() < end) {} // 同步短暂等待
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return fn();
|
|
74
|
+
} finally {
|
|
75
|
+
try { fs.rmSync(lock, { force: true }); } catch {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function readText(file, fallback = "") {
|
|
80
|
+
try {
|
|
81
|
+
if (!fs.existsSync(file)) return fallback;
|
|
82
|
+
return fs.readFileSync(file, "utf8");
|
|
83
|
+
} catch {
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function writeText(file, text) {
|
|
89
|
+
atomicWrite(file, text);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function appendLine(file, line) {
|
|
93
|
+
ensureDir(path.dirname(file));
|
|
94
|
+
fs.appendFileSync(file, line + "\n", "utf8");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function nowISO() {
|
|
98
|
+
return new Date().toISOString();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function logicalDay(d = new Date()) {
|
|
102
|
+
// 逻辑日 (本地时区): 今日/归档/按天检索统一用本地年月日
|
|
103
|
+
// 与 eventsByDay() 的本地解析保持一致, 避免 8 小时时区偏移导致按天检索错位
|
|
104
|
+
const y = d.getFullYear();
|
|
105
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
106
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
107
|
+
return `${y}-${m}-${day}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 按逻辑日切分 JSONL 的日志目录 (工具轨迹 utils/trace.js 与事件流 core/trace.js 共用)。
|
|
111
|
+
// 两个写入器必须落在同一目录, 用同一函数取路径可避免字面量各写一份后悄悄漂移。
|
|
112
|
+
export function logsDir(dataDir, sub = "traces") {
|
|
113
|
+
const dir = path.join(dataDir, "logs", sub);
|
|
114
|
+
ensureDir(dir);
|
|
115
|
+
return dir;
|
|
116
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prompt-depth-kit
|
|
3
|
+
description: 回答表达深度提示词方案库。同一问题输出不同深度的三套方案(主动边界探索 / 情境化优先级 / 极简优雅),按场景选型。需要提升回答质量与信息密度时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 回答表达深度提示词方案库(Prompt Depth Kit)
|
|
7
|
+
|
|
8
|
+
面对"回答太浅/太啰嗦/不切重点"三类问题时,按目标选一套方案注入提示词。
|
|
9
|
+
|
|
10
|
+
## 流程
|
|
11
|
+
|
|
12
|
+
1. **判型**:先分清问题出在哪 —— 信息不足(浅)、重点不明(散)、还是话太多(冗)。
|
|
13
|
+
2. **选方案**(三选一,不要叠加):
|
|
14
|
+
- **方案一 · 主动边界探索型(The Proactive Explorer)** —— 治"浅"。要求先摊开问题边界:已知什么、未知什么、哪些假设没验证,再给结论。适合技术选型、方案评审。
|
|
15
|
+
- **方案二 · 情境化优先级型(The Contextual Prioritizer)** —— 治"散"。要求先排序:在**当前这个具体情境**下,哪一条最要紧、哪一条可以暂时不管。适合多问题并发、资源有限时的决策。
|
|
16
|
+
- **方案三 · 极简优雅型(The Elegant Minimalist)** —— 治"冗"。要求砍掉一切铺垫与复述,只留结论与依据。适合结论已明确、只需要传达的场合。
|
|
17
|
+
3. **落地**:把选中方案的措辞写进 system prompt 或任务描述,**一次只加一套**——三套同时塞进去会互相打架,模型只会变得更啰嗦。
|
|
18
|
+
4. **校验**:对比注入前后同一问题的输出长度与信息密度,确认真的变好了。
|
|
19
|
+
|
|
20
|
+
## 反合理化
|
|
21
|
+
|
|
22
|
+
- "三套都写上,模型自己会选"——模型不会选,只会把三种要求揉成一锅。
|
|
23
|
+
- "先随便选一个试试"——选错方案比不选更糟,会放大原来的毛病。
|
|
24
|
+
- "回答越长越显得认真"——冗长是认知负担,不是深度。
|
|
25
|
+
|
|
26
|
+
## 验证
|
|
27
|
+
|
|
28
|
+
必须给出:① 判定属于哪一类问题;② 选中的方案及理由;③ 注入前后的对比样本(同一问题的两次输出)。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: session-naming
|
|
3
|
+
description: 会话自动命名模板。把首轮对话压缩成 4-12 字的可辨识标题,避免会话列表全是"新对话"。需要给会话/任务起名、批量整理标题时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 会话命名模板(Session Naming Kit)
|
|
7
|
+
|
|
8
|
+
## 流程
|
|
9
|
+
|
|
10
|
+
1. **取信号**:只看首轮对话里"用户想干什么"这件事——不要用问候语、不要用助手回复的开场白。
|
|
11
|
+
2. **压到 4–12 字**:中文按字算,英文按词算。超出就砍修饰语,保动作与对象。
|
|
12
|
+
3. **去泛词**:`帮我` / `请` / `一下` / `关于` / `问题` 一律删掉——它们不提供区分度。
|
|
13
|
+
4. **保专名**:项目名、文件名、技术栈、版本号属于高价值信号,优先保留。
|
|
14
|
+
5. **同名去重**:与已有会话标题重复时,追加可区分后缀(日期 / 序号 / 具体对象)。
|
|
15
|
+
|
|
16
|
+
## Few-shot 示例
|
|
17
|
+
|
|
18
|
+
| 首轮对话(节选) | 坏命名 | 好命名 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| "你好,帮我看看这个 PPXANS-Harness 项目为什么跑不起来" | 新对话 | PPX 内核启动失败排查 |
|
|
21
|
+
| "我想优化一下记忆检索的排序算法" | 讨论 | L1 记忆 BM25 排序优化 |
|
|
22
|
+
| "把这个 md 拆成按主题的小文件" | 文件处理 | 提示词合集按主题拆分 |
|
|
23
|
+
| "现在几点了" | 时间查询 | —(寒暄/单轮,不起名) |
|
|
24
|
+
| "继续" | 继续 | —(无信息量,沿用上轮标题) |
|
|
25
|
+
|
|
26
|
+
> 判据:**如果这个标题不能让你在两周后一眼认出是哪次对话,就重写。**
|
|
27
|
+
|
|
28
|
+
## 反合理化
|
|
29
|
+
|
|
30
|
+
- "用第一句话原文当标题"——原文包含问候与口语,区分度低。
|
|
31
|
+
- "给寒暄也起个名"——"你好"起的任何名字都是噪音。
|
|
32
|
+
- "标题越长越准确"——列表里只显示十几个字,长的会被截断,等于没写。
|
|
33
|
+
|
|
34
|
+
## 验证
|
|
35
|
+
|
|
36
|
+
必须给出:① 生成标题;② 字数与去词记录;③ 与已有会话标题的重名检查结果。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify
|
|
3
|
+
description: 交付前证据核验:任何"做完了"的结论都必须附可复现证据。改代码、修缺陷、跑任务收尾时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# verify — 完成前核验
|
|
7
|
+
|
|
8
|
+
## 流程
|
|
9
|
+
|
|
10
|
+
1. **列出断言**:把"已完成"拆成可判定的原子断言(例:内核能启动 / 测试 0 失败 / 接口返回 200)。
|
|
11
|
+
2. **为每条断言指定证据命令**:必须是别人能照抄复现的命令,不是"我看过了"。
|
|
12
|
+
3. **真跑**:执行命令,原样记录输出(含失败)。
|
|
13
|
+
4. **对账**:断言 → 证据 → 通过/不通过。任何一条没证据,就标"未验证"。
|
|
14
|
+
5. **失败即停**:不通过就修,修完重跑全套,不挑着跑。
|
|
15
|
+
|
|
16
|
+
## 反合理化
|
|
17
|
+
|
|
18
|
+
- "应该没问题"——"应该"不是证据。
|
|
19
|
+
- "这个改动很小"——小改动照样要跑回归。
|
|
20
|
+
- "本地测过了"——本地过了但没跑全量,等于没测。
|
|
21
|
+
- "日志里没报错"——没报错 ≠ 跑通了,可能是分支没进。
|
|
22
|
+
|
|
23
|
+
## 验证
|
|
24
|
+
|
|
25
|
+
收尾必须提供:① 断言清单;② 每条对应的命令与原始输出;③ 明确的通过/不通过结论。禁止只报"已完成"。
|