ppxans-harness 2.4.0 → 3.2.0

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.
Files changed (186) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +218 -265
  3. package/bin/ppx-channels.js +2 -2
  4. package/bin/ppx-serve.js +5 -5
  5. package/bin/ppx-setup.js +124 -0
  6. package/bin/ppx-web.js +140 -0
  7. package/bin/ppx.js +2 -2
  8. package/config/identity.md +6 -6
  9. package/config/ishiki.md +16 -16
  10. package/config/ppx.json +15 -151
  11. package/config/ppx.json.example +143 -0
  12. package/package.json +17 -10
  13. package/skills/.usage.json +6 -0
  14. package/skills/agent-professional-training/SKILL.md +94 -0
  15. package/skills/brainstorm/SKILL.md +24 -0
  16. package/skills/cupid-lover-comms/SKILL.md +37 -0
  17. package/skills/debug/SKILL.md +26 -0
  18. package/skills/plan/SKILL.md +25 -0
  19. package/skills/ponytail/SKILL.md +25 -0
  20. package/skills/ppx-memory/SKILL.md +91 -0
  21. package/skills/ppx-memory/scripts/cli.js +192 -0
  22. package/skills/ppx-memory/scripts/experience.js +133 -0
  23. package/skills/ppx-memory/scripts/fact-store.js +842 -0
  24. package/skills/ppx-memory/scripts/l0.js +52 -0
  25. package/skills/ppx-memory/scripts/l2.js +146 -0
  26. package/skills/ppx-memory/scripts/l3.js +112 -0
  27. package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
  28. package/skills/ppx-memory/scripts/pii.js +42 -0
  29. package/skills/ppx-memory/scripts/schema.js +80 -0
  30. package/skills/ppx-memory/scripts/session.js +398 -0
  31. package/skills/ppx-memory/scripts/similarity.js +43 -0
  32. package/skills/ppx-memory/scripts/store.js +116 -0
  33. package/skills/ppx-memory/scripts/wal.js +38 -0
  34. package/skills/ppx-selfheal/SKILL.md +24 -0
  35. package/skills/ppx-selfheal/scripts/cli.js +80 -0
  36. package/skills/ppx-selfheal/scripts/healer.js +184 -0
  37. package/skills/ppx-selfheal/scripts/logger.js +17 -0
  38. package/skills/ppx-selfheal/scripts/store.js +116 -0
  39. package/skills/prompt-depth-kit/SKILL.md +28 -0
  40. package/skills/session-naming/SKILL.md +36 -0
  41. package/skills/verify/SKILL.md +25 -0
  42. package/src/agent/index.js +1340 -717
  43. package/src/agent/prompts.js +47 -3
  44. package/src/aml-server.js +197 -151
  45. package/src/ans/eviction.js +123 -143
  46. package/src/ans/guard.js +159 -120
  47. package/src/ans/lifecycle.js +96 -93
  48. package/src/ans/proactive.js +112 -129
  49. package/src/ans/reward.js +95 -111
  50. package/src/ans/values.js +15 -15
  51. package/src/audit/audit-chain.js +43 -7
  52. package/src/audit/verifier.js +157 -120
  53. package/src/bus/circuit-breaker.js +9 -1
  54. package/src/bus/runtime-bus.js +107 -93
  55. package/src/channels/base.js +57 -34
  56. package/src/channels/feishu.js +118 -126
  57. package/src/channels/http.js +1090 -592
  58. package/src/channels/index.js +111 -110
  59. package/src/channels/log.js +29 -29
  60. package/src/channels/wechat-crypto.js +73 -74
  61. package/src/channels/wechat.js +191 -197
  62. package/src/channels/workspace.js +94 -0
  63. package/src/channels-cli.js +126 -124
  64. package/src/cli.js +129 -120
  65. package/src/commands/index.js +142 -0
  66. package/src/config/channels.js +137 -170
  67. package/src/config/index.js +277 -224
  68. package/src/config/placeholder.js +31 -0
  69. package/src/config/providers.js +148 -188
  70. package/src/config/settings.js +156 -182
  71. package/src/core/policy.js +69 -18
  72. package/src/core/trace.js +8 -6
  73. package/src/edit/editblock.js +266 -0
  74. package/src/edit/snapshot.js +67 -0
  75. package/src/evidence/index.js +153 -0
  76. package/src/evolve/playbook.js +9 -10
  77. package/src/hooks/index.js +113 -0
  78. package/src/llm/client.js +188 -446
  79. package/src/llm/dsml.js +74 -74
  80. package/src/llm/embedder.js +41 -35
  81. package/src/llm/fence.js +52 -105
  82. package/src/llm/index.js +4 -4
  83. package/src/llm/local-embedder.js +94 -0
  84. package/src/llm/presets.js +113 -0
  85. package/src/llm/pricing.js +93 -0
  86. package/src/llm/retry.js +73 -73
  87. package/src/llm/router.js +92 -97
  88. package/src/mcp/admin.js +326 -0
  89. package/src/mcp/client.js +487 -375
  90. package/src/mcp/http.js +203 -0
  91. package/src/mcp/index.js +116 -116
  92. package/src/mcp/server.js +392 -0
  93. package/src/mcp/tasks.js +133 -0
  94. package/src/memory/asset-hub.js +6 -11
  95. package/src/memory/canvas.js +2 -5
  96. package/src/memory/compaction.js +28 -28
  97. package/src/memory/experience.js +133 -122
  98. package/src/memory/fact-store.js +914 -698
  99. package/src/memory/failure-episode.js +20 -11
  100. package/src/memory/fork.js +17 -8
  101. package/src/memory/index.js +8 -6
  102. package/src/memory/l0.js +53 -52
  103. package/src/memory/l2.js +145 -130
  104. package/src/memory/l3.js +111 -111
  105. package/src/memory/legion-board.js +71 -0
  106. package/src/memory/memory-ticker.js +239 -240
  107. package/src/memory/session.js +398 -397
  108. package/src/memory/sqlite-store.js +581 -0
  109. package/src/mode/blackboard.js +49 -49
  110. package/src/mode/graph.js +42 -41
  111. package/src/mode/index.js +64 -64
  112. package/src/mode/legion.js +54 -51
  113. package/src/mode/plan-exec.js +50 -50
  114. package/src/mode/router.js +28 -40
  115. package/src/orchestrator/agent-worker.js +69 -69
  116. package/src/orchestrator/dag.js +90 -83
  117. package/src/orchestrator/experts.js +76 -0
  118. package/src/orchestrator/index.js +1 -1
  119. package/src/orchestrator/legion.js +179 -187
  120. package/src/orchestrator/supervisor.js +6 -8
  121. package/src/permissions/index.js +378 -0
  122. package/src/persona/index.js +28 -29
  123. package/src/plugin/builtin.js +313 -212
  124. package/src/plugin/context.js +80 -79
  125. package/src/plugin/index.js +62 -62
  126. package/src/plugin/v3.js +73 -0
  127. package/src/protocol/index.js +148 -0
  128. package/src/repomap/index.js +309 -0
  129. package/src/review/index.js +393 -0
  130. package/src/seam/registry.js +3 -0
  131. package/src/seam/shell.js +55 -55
  132. package/src/security/injection.js +79 -0
  133. package/src/selfheal/evolve.js +67 -67
  134. package/src/selfheal/healer.js +184 -167
  135. package/src/selfheal/run.js +9 -9
  136. package/src/server.js +63 -60
  137. package/src/services/diagnose.js +180 -0
  138. package/src/services/learning-service.js +9 -0
  139. package/src/services/memory-health.js +34 -6
  140. package/src/services/memory-service.js +42 -9
  141. package/src/services/triage.js +138 -0
  142. package/src/session/parts.js +76 -0
  143. package/src/session/projection.js +73 -0
  144. package/src/session/rollout.js +54 -0
  145. package/src/session/turn.js +137 -0
  146. package/src/skills/lint.js +72 -0
  147. package/src/skills/loader.js +231 -150
  148. package/src/skills/search.js +58 -0
  149. package/src/skills/verify.js +95 -100
  150. package/src/tools/advanced.js +388 -352
  151. package/src/tools/builtin.js +384 -297
  152. package/src/tools/catalog.js +283 -159
  153. package/src/tools/command-guard.js +112 -112
  154. package/src/tools/custom.js +47 -47
  155. package/src/tools/delegate.js +383 -297
  156. package/src/tools/document.js +254 -253
  157. package/src/tools/git.js +151 -0
  158. package/src/tools/governance.js +47 -20
  159. package/src/tools/index.js +16 -11
  160. package/src/tools/methods.js +178 -178
  161. package/src/tools/ocr.js +59 -59
  162. package/src/tools/sandbox-worker.js +40 -0
  163. package/src/tools/sandbox.js +92 -0
  164. package/src/tools/seam.js +162 -125
  165. package/src/tools/selfmod.js +196 -176
  166. package/src/tools/v3.js +225 -0
  167. package/src/tools/vad.js +176 -0
  168. package/src/tools/voice.js +238 -0
  169. package/src/utils/async.js +14 -0
  170. package/src/utils/config-file.js +53 -0
  171. package/src/utils/crashguard.js +88 -0
  172. package/src/utils/http.js +53 -0
  173. package/src/utils/id.js +8 -0
  174. package/src/utils/json-state.js +33 -0
  175. package/src/utils/logger.js +17 -17
  176. package/src/utils/ndjson.js +25 -0
  177. package/src/utils/pii.js +42 -42
  178. package/src/utils/rate-limit.js +50 -0
  179. package/src/utils/schema.js +80 -0
  180. package/src/utils/similarity.js +43 -0
  181. package/src/utils/store.js +170 -108
  182. package/src/utils/text.js +15 -15
  183. package/src/utils/trace.js +153 -153
  184. package/src/utils/wal.js +39 -0
  185. package/src/utils/winutf8.js +16 -15
  186. package/src/wiki/index.js +170 -0
@@ -1,124 +1,126 @@
1
- #!/usr/bin/env node
2
- // src/channels-cli.js - `ppx channels` 通道管理 CLI (借鉴 openclaw `channels add`)
3
- // 让用户自己连通道: 交互式配置 → 写 config/ppx.json → 测试连通性 → 重启服务生效
4
- //
5
- // 用法:
6
- // ppx-channels list 列出全部通道 + 启用状态
7
- // ppx-channels add <name> 交互式配置通道 (默认写盘并启用)
8
- // ppx-channels test <name> 测试连通性 (网络探测)
9
- // ppx-channels enable|disable <name> 启用/禁用
10
- // ppx-channels remove <name> 移除配置 (恢复默认)
11
- // ppx-channels --root <dir> ... 指定项目根目录 (默认当前目录)
12
- import { ensureUTF8Console } from "./utils/winutf8.js";
13
- import path from "node:path";
14
- import readline from "node:readline/promises";
15
- import {
16
- ensureUTF8Console();
17
- CHANNEL_SCHEMAS, readChannels, listChannels,
18
- updateChannel, setChannelEnabled, removeChannel,
19
- } from "./config/channels.js";
20
- import { ChannelManager } from "./channels/index.js";
21
-
22
- const args = process.argv.slice(2);
23
- let root = process.cwd();
24
- if (args[0] === "--root") { root = path.resolve(args[1]); args.splice(0, 2); }
25
- const [cmd, name] = args;
26
-
27
- const HELP = `ppx-channels - 皮皮虾通道管理 (让用户自己连)
28
- 用法:
29
- ppx-channels list 列出全部通道 + 启用状态
30
- ppx-channels add <name> 交互式配置通道
31
- ppx-channels test <name> 测试连通性
32
- ppx-channels enable <name> 启用通道
33
- ppx-channels disable <name> 禁用通道
34
- ppx-channels remove <name> 移除配置 (恢复默认)
35
- ppx-channels --root <dir> <cmd> 指定项目根目录
36
- 可用通道: ${Object.keys(CHANNEL_SCHEMAS).join(" / ")}`;
37
-
38
- // ---- list ----
39
- function doList() {
40
- const rows = listChannels(root);
41
- for (const r of rows) {
42
- const marks = Object.entries(r.fields)
43
- .map(([k, v]) => (k.endsWith("_set") ? `${k}=✓` : `${k}=${v}`))
44
- .join(" ");
45
- console.log(`${r.enabled ? "✓ 启用" : "· 禁用"} ${r.name}${marks ? " " + marks : ""}`);
46
- }
47
- const enabled = rows.filter((r) => r.enabled).map((r) => r.name);
48
- console.log(`\n${enabled.length ? "已启用: " + enabled.join(", ") : "全部未启用 — 用 add <name> 配置一个"}`);
49
- }
50
-
51
- // ---- add (交互式引导, 借鉴 openclaw channels add) ----
52
- async function doAdd(name) {
53
- const schema = CHANNEL_SCHEMAS[name];
54
- if (!schema) {
55
- console.error(`未知通道: ${name} (可用: ${Object.keys(CHANNEL_SCHEMAS).join(", ")})`);
56
- process.exit(1);
57
- }
58
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
59
- const existing = readChannels(root).channels[name] || {};
60
- const patch = {};
61
- console.log(`\n配置 ${name} 通道 (回车用默认值, 需要密码的字段直接粘贴):`);
62
- for (const key of Object.keys(schema).filter((k) => k !== "enabled")) {
63
- const f = schema[key];
64
- const def = existing[key] ?? f.def ?? "";
65
- const defHint = def !== "" ? ` [${def}]` : "";
66
- const prompt = f.prompt ? ` ${f.label} — ${f.prompt}${defHint}: ` : ` ${f.label}${defHint}: `;
67
- const ans = (await rl.question(prompt)).trim();
68
- const val = ans === "" ? def : ans;
69
- if (val !== "") patch[key] = f.type === "number" ? Number(val) : val;
70
- }
71
- rl.close();
72
- try {
73
- updateChannel(root, name, { ...patch, enabled: true });
74
- console.log(`\n✓ ${name} 已配置并启用 (config/ppx.json 已原子写盘, 原文件已备份).`);
75
- console.log(` 重启服务生效: ppx-serve`);
76
- console.log(` 验证连通性: ppx-channels test ${name}`);
77
- } catch (e) {
78
- console.error(`✗ 保存失败: ${e.message}`);
79
- process.exit(1);
80
- }
81
- }
82
-
83
- // ---- test (连通性) ----
84
- async function doTest(name) {
85
- if (!(name in CHANNEL_SCHEMAS)) {
86
- console.error(`未知通道: ${name} (可用: ${Object.keys(CHANNEL_SCHEMAS).join(", ")})`);
87
- process.exit(1);
88
- }
89
- const { channels } = readChannels(root);
90
- // 轻量 agent stub: 通道 test() 只用 root 构造, 不触发完整 agent 启动
91
- const mgr = new ChannelManager({ root, config: { agent: {} } }, channels);
92
- const r = await mgr.test(name);
93
- console.log(`${r.ok ? "✓" : "✗"} ${name}: ${r.detail}`);
94
- process.exit(r.ok ? 0 : 1);
95
- }
96
-
97
- // ---- enable/disable/remove ----
98
- function doToggle(name, enabled) {
99
- if (!(name in CHANNEL_SCHEMAS)) { console.error(`未知通道: ${name}`); process.exit(1); }
100
- try {
101
- const out = setChannelEnabled(root, name, enabled);
102
- console.log(`✓ ${name} 已${enabled ? "启用" : "禁用"}${Object.keys(out).length ? ` (${Object.keys(out).join(", ")})` : ""}`);
103
- } catch (e) { console.error(`✗ 操作失败: ${e.message}`); process.exit(1); }
104
- }
105
-
106
- function doRemove(name) {
107
- if (!(name in CHANNEL_SCHEMAS)) { console.error(`未知通道: ${name}`); process.exit(1); }
108
- const ok = removeChannel(root, name);
109
- console.log(ok ? `✓ ${name} 配置已移除 (恢复默认, 默认禁用)` : `${name} 本来就没有自定义配置`);
110
- }
111
-
112
- // ---- main ----
113
- (async () => {
114
- if (!cmd || cmd === "-h" || cmd === "--help") { console.log(HELP); return; }
115
- switch (cmd) {
116
- case "list": doList(); break;
117
- case "add": await doAdd(name); break;
118
- case "test": await doTest(name); break;
119
- case "enable": doToggle(name, true); break;
120
- case "disable": doToggle(name, false); break;
121
- case "remove": doRemove(name); break;
122
- default: console.error(`未知命令: ${cmd}\n${HELP}`); process.exit(1);
123
- }
124
- })();
1
+ #!/usr/bin/env node
2
+ // src/channels-cli.js - `ppx channels` 通道管理 CLI (借鉴 openclaw `channels add`)
3
+ // 让用户自己连通道: 交互式配置 → 写 config/ppx.json → 测试连通性 → 重启服务生效
4
+ //
5
+ // 用法:
6
+ // ppx-channels list 列出全部通道 + 启用状态
7
+ // ppx-channels add <name> 交互式配置通道 (默认写盘并启用)
8
+ // ppx-channels test <name> 测试连通性 (网络探测)
9
+ // ppx-channels enable|disable <name> 启用/禁用
10
+ // ppx-channels remove <name> 移除配置 (恢复默认)
11
+ // ppx-channels --root <dir> ... 指定项目根目录 (默认当前目录)
12
+ import { ensureUTF8Console } from "./utils/winutf8.js";
13
+ import path from "node:path";
14
+ import readline from "node:readline/promises";
15
+ import {
16
+ CHANNEL_SCHEMAS, readChannels, listChannels,
17
+ updateChannel, setChannelEnabled, removeChannel,
18
+ } from "./config/channels.js";
19
+ import { ChannelManager } from "./channels/index.js";
20
+
21
+ // 必须在任何 console 输出之前完成代码页切换 (Windows 中文控制台)
22
+ ensureUTF8Console();
23
+
24
+ const args = process.argv.slice(2);
25
+ let root = process.cwd();
26
+ if (args[0] === "--root") { root = path.resolve(args[1]); args.splice(0, 2); }
27
+ const [cmd, name] = args;
28
+
29
+ const HELP = `ppx-channels - 皮皮虾通道管理 (让用户自己连)
30
+ 用法:
31
+ ppx-channels list 列出全部通道 + 启用状态
32
+ ppx-channels add <name> 交互式配置通道
33
+ ppx-channels test <name> 测试连通性
34
+ ppx-channels enable <name> 启用通道
35
+ ppx-channels disable <name> 禁用通道
36
+ ppx-channels remove <name> 移除配置 (恢复默认)
37
+ ppx-channels --root <dir> <cmd> 指定项目根目录
38
+ 可用通道: ${Object.keys(CHANNEL_SCHEMAS).join(" / ")}`;
39
+
40
+ // ---- list ----
41
+ function doList() {
42
+ const rows = listChannels(root);
43
+ for (const r of rows) {
44
+ const marks = Object.entries(r.fields)
45
+ .map(([k, v]) => (k.endsWith("_set") ? `${k}=✓` : `${k}=${v}`))
46
+ .join(" ");
47
+ console.log(`${r.enabled ? "✓ 启用" : "· 禁用"} ${r.name}${marks ? " " + marks : ""}`);
48
+ }
49
+ const enabled = rows.filter((r) => r.enabled).map((r) => r.name);
50
+ console.log(`\n${enabled.length ? "已启用: " + enabled.join(", ") : "全部未启用 — 用 add <name> 配置一个"}`);
51
+ }
52
+
53
+ // ---- add (交互式引导, 借鉴 openclaw channels add) ----
54
+ async function doAdd(name) {
55
+ const schema = CHANNEL_SCHEMAS[name];
56
+ if (!schema) {
57
+ console.error(`未知通道: ${name} (可用: ${Object.keys(CHANNEL_SCHEMAS).join(", ")})`);
58
+ process.exit(1);
59
+ }
60
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
61
+ const existing = readChannels(root).channels[name] || {};
62
+ const patch = {};
63
+ console.log(`\n配置 ${name} 通道 (回车用默认值, 需要密码的字段直接粘贴):`);
64
+ for (const key of Object.keys(schema).filter((k) => k !== "enabled")) {
65
+ const f = schema[key];
66
+ const def = existing[key] ?? f.def ?? "";
67
+ const defHint = def !== "" ? ` [${def}]` : "";
68
+ const prompt = f.prompt ? ` ${f.label} — ${f.prompt}${defHint}: ` : ` ${f.label}${defHint}: `;
69
+ const ans = (await rl.question(prompt)).trim();
70
+ const val = ans === "" ? def : ans;
71
+ if (val !== "") patch[key] = f.type === "number" ? Number(val) : val;
72
+ }
73
+ rl.close();
74
+ try {
75
+ updateChannel(root, name, { ...patch, enabled: true });
76
+ console.log(`\n✓ ${name} 已配置并启用 (config/ppx.json 已原子写盘, 原文件已备份).`);
77
+ console.log(` 重启服务生效: ppx-serve`);
78
+ console.log(` 验证连通性: ppx-channels test ${name}`);
79
+ } catch (e) {
80
+ console.error(`✗ 保存失败: ${e.message}`);
81
+ process.exit(1);
82
+ }
83
+ }
84
+
85
+ // ---- test (连通性) ----
86
+ async function doTest(name) {
87
+ if (!(name in CHANNEL_SCHEMAS)) {
88
+ console.error(`未知通道: ${name} (可用: ${Object.keys(CHANNEL_SCHEMAS).join(", ")})`);
89
+ process.exit(1);
90
+ }
91
+ const { channels } = readChannels(root);
92
+ // 轻量 agent stub: 通道 test() 只用 root 构造, 不触发完整 agent 启动
93
+ const mgr = new ChannelManager({ root, config: { agent: {} } }, channels);
94
+ const r = await mgr.test(name);
95
+ console.log(`${r.ok ? "✓" : "✗"} ${name}: ${r.detail}`);
96
+ process.exit(r.ok ? 0 : 1);
97
+ }
98
+
99
+ // ---- enable/disable/remove ----
100
+ function doToggle(name, enabled) {
101
+ if (!(name in CHANNEL_SCHEMAS)) { console.error(`未知通道: ${name}`); process.exit(1); }
102
+ try {
103
+ const out = setChannelEnabled(root, name, enabled);
104
+ console.log(`✓ ${name} 已${enabled ? "启用" : "禁用"}${Object.keys(out).length ? ` (${Object.keys(out).join(", ")})` : ""}`);
105
+ } catch (e) { console.error(`✗ 操作失败: ${e.message}`); process.exit(1); }
106
+ }
107
+
108
+ function doRemove(name) {
109
+ if (!(name in CHANNEL_SCHEMAS)) { console.error(`未知通道: ${name}`); process.exit(1); }
110
+ const ok = removeChannel(root, name);
111
+ console.log(ok ? `✓ ${name} 配置已移除 (恢复默认, 默认禁用)` : `${name} 本来就没有自定义配置`);
112
+ }
113
+
114
+ // ---- main ----
115
+ (async () => {
116
+ if (!cmd || cmd === "-h" || cmd === "--help") { console.log(HELP); return; }
117
+ switch (cmd) {
118
+ case "list": doList(); break;
119
+ case "add": await doAdd(name); break;
120
+ case "test": await doTest(name); break;
121
+ case "enable": doToggle(name, true); break;
122
+ case "disable": doToggle(name, false); break;
123
+ case "remove": doRemove(name); break;
124
+ default: console.error(`未知命令: ${cmd}\n${HELP}`); process.exit(1);
125
+ }
126
+ })();
package/src/cli.js CHANGED
@@ -1,120 +1,129 @@
1
- #!/usr/bin/env node
2
- // src/cli.js - 皮皮虾 CLI 交互入口 (readline 历史 + interrupt 中断)
3
- import { ensureUTF8Console } from "./utils/winutf8.js";
4
- import path from "node:path";
5
- import readline from "node:readline";
6
- import { fileURLToPath } from "node:url";
7
- import { PPXAgent } from "./agent/index.js";
8
- import { suggestProactive } from "./ans/proactive.js";
9
-
10
- ensureUTF8Console();
11
- const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
12
- const agent = new PPXAgent({ root: ROOT });
13
-
14
- console.log("======================================");
15
- console.log(" 皮皮虾 (PPX) - 自我修复·自我学习 Agent");
16
- console.log(` 记忆:${agent.facts.count()}条 | 经验:${agent.experience.lessons.length}条`);
17
- console.log(` 模型: ${agent.llm ? "已配置" : "未配置(离线记忆模式)"}`);
18
- console.log(" 命令: quit/exit 退出 | /stop 中断当前任务 | /reset 清空会话");
19
- console.log(" /proactive 主动提醒(扫描记忆待办) | /proactive-done <id> 标记待办完成 | ↑↓ 浏览历史 | Ctrl+C 中断(再按一次退出)");
20
- console.log("======================================");
21
-
22
- const rl = readline.createInterface({
23
- input: process.stdin,
24
- output: process.stdout,
25
- prompt: "皮皮虾> ",
26
- terminal: true,
27
- });
28
-
29
- let busy = false; // 防止任务执行中重复输入
30
-
31
- // 主动任务生成定时器 (ANS 自主性): config.agent.proactive.enabled 时启动
32
- // 有待办信号才推送 (suggestProactive 无信号返回 null 不打扰), 输出到 stdout
33
- if (agent.config.agent?.proactive?.enabled) {
34
- agent.startProactiveTicker((payload) => {
35
- console.log("\n[主动提醒] " + payload.text);
36
- rl.prompt();
37
- });
38
- console.log(` (主动提醒已开启: 每 ${Math.round(agent.config.agent.proactive.interval_ms / 60000)} 分钟扫描记忆待办)`);
39
- }
40
-
41
- rl.on("line", async (line) => {
42
- const text = line.trim();
43
- if (!text) return rl.prompt();
44
- if (busy) return rl.prompt(); // 上一轮任务未结束, 忽略输入 (可用 /stop 或 Ctrl+C 打断)
45
-
46
- // 退出
47
- if (["quit", "exit", "q"].includes(text.toLowerCase())) {
48
- agent.shutdown();
49
- console.log("皮皮虾 收工, 已保存记忆。");
50
- process.exit(0);
51
- }
52
- // 中断当前任务 (Human-in-the-loop)
53
- if (text === "/stop") {
54
- agent.interrupt();
55
- console.log("(已发送中断信号, 当前任务将尽快停下)");
56
- return rl.prompt();
57
- }
58
- // 清空会话
59
- if (text === "/reset") {
60
- agent.resetSession("default");
61
- console.log("(会话已清空)");
62
- return rl.prompt();
63
- }
64
- // 主动任务生成: 扫描记忆里的待办/偏好, 给出主动提醒 (ANS 自主性)
65
- // 输出含 id, 可用 /proactive-done <id> 标记完成 (窗口去重, 24h 内不重复提醒)
66
- if (text === "/proactive") {
67
- busy = true;
68
- try {
69
- const out = await suggestProactive(agent);
70
- if (out) {
71
- console.log("\n" + out.text + "\n");
72
- for (const it of out.items) console.log(` [${it.id}] ${it.content}`);
73
- console.log("\n(用 /proactive-done <id> 标记完成, 之后不再提醒)\n");
74
- } else {
75
- console.log("\n(暂时没有需要提醒的事项)\n");
76
- }
77
- } catch (e) {
78
- console.log("\n[错误] " + e.message + "\n");
79
- } finally {
80
- busy = false;
81
- }
82
- return rl.prompt();
83
- }
84
- // 标记待办完成: /proactive-done <factId>
85
- if (text.startsWith("/proactive-done")) {
86
- const id = text.replace("/proactive-done", "").trim();
87
- const ok = agent.proactiveMarkDone(id);
88
- console.log(ok ? "(已标记完成, 之后不再提醒)" : "(待办不存在: " + id + ")");
89
- return rl.prompt();
90
- }
91
-
92
- busy = true;
93
- try {
94
- const r = await agent.chat(text);
95
- console.log("\n" + r + "\n");
96
- } catch (e) {
97
- console.log("\n[错误] " + e.message + "\n");
98
- } finally {
99
- busy = false;
100
- }
101
- rl.prompt();
102
- });
103
-
104
- // Ctrl+C: 第一次中断任务, 第二次退出
105
- let ctrlC = 0;
106
- rl.on("SIGINT", () => {
107
- ctrlC += 1;
108
- if (ctrlC >= 2) {
109
- agent.shutdown();
110
- console.log("\n皮皮虾 收工。");
111
- process.exit(0);
112
- }
113
- agent.interrupt();
114
- console.log("\n(已中断, 再按一次 Ctrl+C 退出)");
115
- rl.prompt();
116
- // 重置计数 (若任务继续则允许再次单次中断)
117
- setTimeout(() => { ctrlC = 0; }, 1000);
118
- });
119
-
120
- rl.prompt();
1
+ #!/usr/bin/env node
2
+ // src/cli.js - 皮皮虾 CLI 交互入口 (readline 历史 + interrupt 中断)
3
+ import { ensureUTF8Console } from "./utils/winutf8.js";
4
+ import path from "node:path";
5
+ import readline from "node:readline";
6
+ import { fileURLToPath } from "node:url";
7
+ import { PPXAgent } from "./agent/index.js";
8
+ import { suggestProactive } from "./ans/proactive.js";
9
+ import { installCrashGuard } from "./utils/crashguard.js";
10
+
11
+ ensureUTF8Console();
12
+ // v3.0.1 (P1#6): CLI 直跑入口装全局异常兜底 —— 此前只有 server 入口装了, chat 直跑时
13
+ // 任何未捕获 rejection (调度器/proactive ticker/流式回调) 会按 Node>=15 默认行为直接杀进程
14
+ installCrashGuard({ tag: "ppx-cli" });
15
+ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
16
+ const agent = new PPXAgent({ root: ROOT });
17
+
18
+ console.log("======================================");
19
+ console.log(" 皮皮虾 (PPX) - 自我修复·自我学习 Agent");
20
+ console.log(` 记忆:${agent.facts.count()}条 | 经验:${agent.experience.lessons.length}条`);
21
+ console.log(` 模型: ${agent.llm ? "已配置" : "未配置(离线记忆模式)"}`);
22
+ console.log(" 命令: quit/exit 退出 | /stop 中断当前任务 | /reset 清空会话");
23
+ console.log(" /proactive 主动提醒(扫描记忆待办) | /proactive-done <id> 标记待办完成 | ↑↓ 浏览历史 | Ctrl+C 中断(再按一次退出)");
24
+ console.log("======================================");
25
+
26
+ const rl = readline.createInterface({
27
+ input: process.stdin,
28
+ output: process.stdout,
29
+ prompt: "皮皮虾> ",
30
+ terminal: true,
31
+ });
32
+
33
+ let busy = false; // 防止任务执行中重复输入
34
+
35
+ // 主动任务生成定时器 (ANS 自主性): config.agent.proactive.enabled 时启动
36
+ // 有待办信号才推送 (suggestProactive 无信号返回 null 不打扰), 输出到 stdout
37
+ if (agent.config.agent?.proactive?.enabled) {
38
+ agent.startProactiveTicker((payload) => {
39
+ console.log("\n[主动提醒] " + payload.text);
40
+ rl.prompt();
41
+ });
42
+ const proactiveMs = Number(agent.config.agent.proactive.interval_ms) || 3600000;
43
+ console.log(` (主动提醒已开启: 每 ${Math.round(proactiveMs / 60000)} 分钟扫描记忆待办)`);
44
+ }
45
+
46
+ rl.on("line", async (line) => {
47
+ const text = line.trim();
48
+ if (!text) return rl.prompt();
49
+ if (busy) {
50
+ // 2026-10-03 修复: 原 silently 忽略, 用户以为输入丢失。改为可判读提示。
51
+ console.log(" (任务执行中, 输入已忽略; /stop 可中断当前任务)");
52
+ return rl.prompt();
53
+ }
54
+
55
+ // 退出
56
+ if (["quit", "exit", "q"].includes(text.toLowerCase())) {
57
+ agent.shutdown();
58
+ console.log("皮皮虾 收工, 已保存记忆。");
59
+ process.exit(0);
60
+ }
61
+ // 中断当前任务 (Human-in-the-loop)
62
+ if (text === "/stop") {
63
+ agent.interrupt();
64
+ console.log("(已发送中断信号, 当前任务将尽快停下)");
65
+ return rl.prompt();
66
+ }
67
+ // 清空会话
68
+ if (text === "/reset") {
69
+ agent.resetSession("default");
70
+ console.log("(会话已清空)");
71
+ return rl.prompt();
72
+ }
73
+ // 主动任务生成: 扫描记忆里的待办/偏好, 给出主动提醒 (ANS 自主性)
74
+ // 输出含 id, 可用 /proactive-done <id> 标记完成 (窗口去重, 24h 内不重复提醒)
75
+ if (text === "/proactive") {
76
+ busy = true;
77
+ try {
78
+ const out = await suggestProactive(agent);
79
+ if (out) {
80
+ console.log("\n" + out.text + "\n");
81
+ for (const it of out.items) console.log(` [${it.id}] ${it.content}`);
82
+ console.log("\n(用 /proactive-done <id> 标记完成, 之后不再提醒)\n");
83
+ } else {
84
+ console.log("\n(暂时没有需要提醒的事项)\n");
85
+ }
86
+ } catch (e) {
87
+ console.log("\n[错误] " + e.message + "\n");
88
+ } finally {
89
+ busy = false;
90
+ }
91
+ return rl.prompt();
92
+ }
93
+ // 标记待办完成: /proactive-done <factId>
94
+ if (text.startsWith("/proactive-done")) {
95
+ const id = text.replace("/proactive-done", "").trim();
96
+ const ok = agent.proactiveMarkDone(id);
97
+ console.log(ok ? "(已标记完成, 之后不再提醒)" : "(待办不存在: " + id + ")");
98
+ return rl.prompt();
99
+ }
100
+
101
+ busy = true;
102
+ try {
103
+ const r = await agent.chat(text);
104
+ console.log("\n" + r + "\n");
105
+ } catch (e) {
106
+ console.log("\n[错误] " + e.message + "\n");
107
+ } finally {
108
+ busy = false;
109
+ }
110
+ rl.prompt();
111
+ });
112
+
113
+ // Ctrl+C: 第一次中断任务, 第二次退出
114
+ let ctrlC = 0;
115
+ rl.on("SIGINT", () => {
116
+ ctrlC += 1;
117
+ if (ctrlC >= 2) {
118
+ agent.shutdown();
119
+ console.log("\n皮皮虾 收工。");
120
+ process.exit(0);
121
+ }
122
+ agent.interrupt();
123
+ console.log("\n(已中断, 再按一次 Ctrl+C 退出)");
124
+ rl.prompt();
125
+ // 重置计数 (若任务继续则允许再次单次中断)
126
+ setTimeout(() => { ctrlC = 0; }, 1000);
127
+ });
128
+
129
+ rl.prompt();