@bolloon/bolloon-agent 0.4.5 → 0.4.7

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 (208) hide show
  1. package/README.md +135 -135
  2. package/bin/bolloon-cli.cjs +183 -183
  3. package/bin/bolloon.cjs +0 -0
  4. package/bin/bolloon.js +157 -0
  5. package/dist/agents/constraint-layer.js +19 -19
  6. package/dist/agents/judgment-protocol.js +14 -14
  7. package/dist/agents/pi-sdk-tools.js +128 -46
  8. package/dist/agents/pi-sdk-types.js +12 -12
  9. package/dist/agents/pi-sdk.js +103 -90
  10. package/dist/agents/process-runner.js +105 -0
  11. package/dist/agents/skill-organizer.js +19 -19
  12. package/dist/agents/skill-writer.js +15 -3
  13. package/dist/agents/workflow-pivot-loop.js +13 -3
  14. package/dist/agents/write-staging.js +85 -0
  15. package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
  16. package/dist/bollharness-integration/context-router-judgment.js +4 -4
  17. package/dist/bollharness-integration/context-router.js +292 -292
  18. package/dist/bollharness-integration/gate-state-machine.js +13 -13
  19. package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
  20. package/dist/bollharness-integration/skill-adapter.js +21 -21
  21. package/dist/bootstrap/chat-archiver.js +14 -14
  22. package/dist/bootstrap/context-os.js +24 -24
  23. package/dist/bootstrap/memory-compressor.js +14 -14
  24. package/dist/cli/ink-app.js +18 -0
  25. package/dist/cli-entry.js +36 -36
  26. package/dist/context-compaction/auto-compact.js +7 -7
  27. package/dist/electron/first-run.js +54 -54
  28. package/dist/electron-build/electron/first-run.js +54 -54
  29. package/dist/electron-preload.js.map +1 -1
  30. package/dist/electron.js.map +1 -1
  31. package/dist/index.js +230 -137
  32. package/dist/llm/llm-judgment-client.js +102 -102
  33. package/dist/llm/pi-ai.js +48 -48
  34. package/dist/llm/system-prompt/layers/channel/human-async.md +41 -41
  35. package/dist/llm/system-prompt/layers/channel/local.md +14 -14
  36. package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
  37. package/dist/llm/system-prompt/layers/channel/p2p-peer-sync.md +51 -51
  38. package/dist/llm/system-prompt/layers/channel/p2p-proactive.md +43 -43
  39. package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
  40. package/dist/llm/system-prompt/layers/channel/session-handoff.md +61 -61
  41. package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
  42. package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
  43. package/dist/llm/system-prompt/layers/core/external-engagement.md +73 -73
  44. package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
  45. package/dist/llm/system-prompt/layers/core/identity.md +48 -48
  46. package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
  47. package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
  48. package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
  49. package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
  50. package/dist/llm/system-prompt/layers/core/tone.md +31 -31
  51. package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
  52. package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
  53. package/dist/llm/system-prompt/layers/role/architect.md +20 -20
  54. package/dist/llm/system-prompt/layers/role/expert.md +19 -19
  55. package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
  56. package/dist/llm/system-prompt/layers/role/security.md +15 -15
  57. package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
  58. package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
  59. package/dist/llm/system-prompt/layers/tool/goal_handoff.md +77 -77
  60. package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
  61. package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
  62. package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
  63. package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
  64. package/dist/llm/system-prompt/layers/tool/p2p_request.md +61 -61
  65. package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
  66. package/dist/llm/tool-manifest/ask_user_input.js +1 -1
  67. package/dist/llm/tool-manifest/bash.js +3 -3
  68. package/dist/llm/tool-manifest/create_file.js +4 -4
  69. package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
  70. package/dist/llm/tool-manifest/image_search.js +2 -2
  71. package/dist/llm/tool-manifest/index.js +0 -59
  72. package/dist/llm/tool-manifest/mcp.js +2 -2
  73. package/dist/llm/tool-manifest/message_compose.js +3 -3
  74. package/dist/llm/tool-manifest/places.js +2 -2
  75. package/dist/llm/tool-manifest/present_files.js +1 -1
  76. package/dist/llm/tool-manifest/recipe.js +2 -2
  77. package/dist/llm/tool-manifest/str_replace.js +5 -5
  78. package/dist/llm/tool-manifest/view.js +3 -3
  79. package/dist/llm/tool-manifest/weather.js +4 -4
  80. package/dist/llm/tool-manifest/web.js +4 -4
  81. package/dist/orbitdb/agent-tools.js +139 -0
  82. package/dist/orbitdb/kanban-store.js +259 -0
  83. package/dist/orbitdb/task-store.js +112 -0
  84. package/dist/pi-ecosystem-colony/index.js +365 -0
  85. package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
  86. package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
  87. package/dist/pi-ecosystem-judgment/distillation.js +14 -14
  88. package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
  89. package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
  90. package/dist/security/context-router-tool.js +15 -15
  91. package/dist/security/tool-gate.js +2 -0
  92. package/dist/social/ant-colony/AdaptiveHeartbeat.js +101 -0
  93. package/dist/social/ant-colony/PheromoneEngine.js +227 -0
  94. package/dist/social/ant-colony/index.js +6 -0
  95. package/dist/social/ant-colony/types.js +24 -0
  96. package/dist/storage/trajectory.js +101 -0
  97. package/dist/test/ai-judgment-test.js +80 -0
  98. package/dist/test/bollharness-integration.test.js +318 -0
  99. package/dist/test/channel-agent-multi-dialogue.js +205 -0
  100. package/dist/test/channel-heartbeat-agent-test.js +201 -0
  101. package/dist/test/constraint-layer.test.js +164 -0
  102. package/dist/test/diap-identity-test.js +172 -0
  103. package/dist/test/diap-quick-test.js +62 -0
  104. package/dist/test/global-shared-context.test.js +315 -0
  105. package/dist/test/harness-judgment-injection.test.js +246 -0
  106. package/dist/test/harness-workflow-integrator-test.js +228 -0
  107. package/dist/test/human-value-store.test.js +243 -0
  108. package/dist/test/hybrid-integration-test.js +118 -0
  109. package/dist/test/hybrid-messenger-verify.js +55 -0
  110. package/dist/test/iroh-bistream-debug.js +38 -0
  111. package/dist/test/iroh-communication.test.js +66 -0
  112. package/dist/test/iroh-debug-test.js +57 -0
  113. package/dist/test/iroh-diap-test.js +71 -0
  114. package/dist/test/iroh-direct-connect.js +55 -0
  115. package/dist/test/iroh-e2e-fixed.js +89 -0
  116. package/dist/test/iroh-e2e-same-process.js +63 -0
  117. package/dist/test/iroh-e2e.js +66 -0
  118. package/dist/test/iroh-final-e2e.js +72 -0
  119. package/dist/test/iroh-relay-test.js +37 -0
  120. package/dist/test/iroh-simple-test.js +41 -0
  121. package/dist/test/iroh-transport-verify.js +54 -0
  122. package/dist/test/iroh-transport.test.js +37 -0
  123. package/dist/test/iroh-two-nodes.js +70 -0
  124. package/dist/test/iroh-verify.js +44 -0
  125. package/dist/test/judgment-decision.test.js +219 -0
  126. package/dist/test/llm-judgment-integration.test.js +220 -0
  127. package/dist/test/p2p-agent-complex-dialogue.js +385 -0
  128. package/dist/test/p2p-agent-dialogue.js +341 -0
  129. package/dist/test/p2p-agent-full-bidirectional.js +510 -0
  130. package/dist/test/p2p-agent-harness-flow.js +437 -0
  131. package/dist/test/p2p-agent-harness-single.js +143 -0
  132. package/dist/test/p2p-ai-dialogue-test.js +318 -0
  133. package/dist/test/p2p-cid-connect-test.js +195 -0
  134. package/dist/test/p2p-connect-receiver.js +69 -0
  135. package/dist/test/p2p-doc-transfer.js +110 -0
  136. package/dist/test/p2p-identity-page-test.js +77 -0
  137. package/dist/test/p2p-iroh-test.js +171 -0
  138. package/dist/test/p2p-minimal-test.js +241 -0
  139. package/dist/test/p2p-node-1.js +148 -0
  140. package/dist/test/p2p-node-2.js +148 -0
  141. package/dist/test/p2p-server.js +281 -0
  142. package/dist/test/p2p-two-nodes-test.js +438 -0
  143. package/dist/test/pi-sdk.test.js +44 -0
  144. package/dist/test/set-persona.js +40 -0
  145. package/dist/test/simple.test.js +9 -0
  146. package/dist/test/storage-integration.test.js +150 -0
  147. package/dist/test/subagent-manager.test.js +276 -0
  148. package/dist/test/test-gate-flow.test.js +81 -0
  149. package/dist/test/workflow-engine.test.js +87 -0
  150. package/dist/test/workflow-pivot-loop.test.js +246 -0
  151. package/dist/web/api-config.html +779 -779
  152. package/dist/web/client-hearth.js +10 -10
  153. package/dist/web/components/wallet-viem.mjs +118 -118
  154. package/dist/web/index.html +472 -472
  155. package/dist/web/manifest.json +20 -20
  156. package/dist/web/server-storage.js +29 -0
  157. package/dist/web/server.js +30 -14
  158. package/dist/web/style.css +4946 -4946
  159. package/package.json +195 -195
  160. package/scripts/build-cli.js +215 -215
  161. package/scripts/build-web.ts +130 -130
  162. package/scripts/postinstall.js +152 -152
  163. package/bin/bolloon-daemon.sh +0 -207
  164. package/dist/constraint-runtime/reference_data/archive_surface_snapshot.json +0 -63
  165. package/dist/constraint-runtime/reference_data/commands_snapshot.json +0 -1037
  166. package/dist/constraint-runtime/reference_data/subsystems/assistant.json +0 -8
  167. package/dist/constraint-runtime/reference_data/subsystems/bootstrap.json +0 -8
  168. package/dist/constraint-runtime/reference_data/subsystems/bridge.json +0 -32
  169. package/dist/constraint-runtime/reference_data/subsystems/buddy.json +0 -13
  170. package/dist/constraint-runtime/reference_data/subsystems/cli.json +0 -26
  171. package/dist/constraint-runtime/reference_data/subsystems/components.json +0 -32
  172. package/dist/constraint-runtime/reference_data/subsystems/constants.json +0 -28
  173. package/dist/constraint-runtime/reference_data/subsystems/coordinator.json +0 -8
  174. package/dist/constraint-runtime/reference_data/subsystems/entrypoints.json +0 -15
  175. package/dist/constraint-runtime/reference_data/subsystems/hooks.json +0 -32
  176. package/dist/constraint-runtime/reference_data/subsystems/keybindings.json +0 -21
  177. package/dist/constraint-runtime/reference_data/subsystems/memdir.json +0 -15
  178. package/dist/constraint-runtime/reference_data/subsystems/migrations.json +0 -18
  179. package/dist/constraint-runtime/reference_data/subsystems/moreright.json +0 -8
  180. package/dist/constraint-runtime/reference_data/subsystems/native_ts.json +0 -11
  181. package/dist/constraint-runtime/reference_data/subsystems/outputStyles.json +0 -8
  182. package/dist/constraint-runtime/reference_data/subsystems/plugins.json +0 -9
  183. package/dist/constraint-runtime/reference_data/subsystems/remote.json +0 -11
  184. package/dist/constraint-runtime/reference_data/subsystems/schemas.json +0 -8
  185. package/dist/constraint-runtime/reference_data/subsystems/screens.json +0 -10
  186. package/dist/constraint-runtime/reference_data/subsystems/server.json +0 -10
  187. package/dist/constraint-runtime/reference_data/subsystems/services.json +0 -32
  188. package/dist/constraint-runtime/reference_data/subsystems/skills.json +0 -27
  189. package/dist/constraint-runtime/reference_data/subsystems/state.json +0 -13
  190. package/dist/constraint-runtime/reference_data/subsystems/types.json +0 -18
  191. package/dist/constraint-runtime/reference_data/subsystems/upstreamproxy.json +0 -9
  192. package/dist/constraint-runtime/reference_data/subsystems/utils.json +0 -32
  193. package/dist/constraint-runtime/reference_data/subsystems/vim.json +0 -12
  194. package/dist/constraint-runtime/reference_data/subsystems/voice.json +0 -8
  195. package/dist/constraint-runtime/reference_data/tools_snapshot.json +0 -922
  196. package/dist/electron/config.js.map +0 -1
  197. package/dist/electron/dialogs.js.map +0 -1
  198. package/dist/electron/first-run.js.map +0 -1
  199. package/dist/electron/ipc.js.map +0 -1
  200. package/dist/electron/logger.js.map +0 -1
  201. package/dist/electron/main.js.map +0 -1
  202. package/dist/electron/menu.js.map +0 -1
  203. package/dist/electron/paths.js.map +0 -1
  204. package/dist/electron/server.js.map +0 -1
  205. package/dist/electron/tray.js.map +0 -1
  206. package/dist/electron/window.js.map +0 -1
  207. package/dist/utils/auto-update.js.map +0 -1
  208. package/dist/web/client.js.map +0 -7
package/bin/bolloon.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ const path = require("path");
3
+ const { spawn } = require("child_process");
4
+ const fs = require("fs");
5
+
6
+ const RESET = "\x1b[0m";
7
+ const BOLD = "\x1b[1m";
8
+ const CYAN = "\x1b[36m";
9
+ const GREEN = "\x1b[32m";
10
+ const MAGENTA = "\x1b[35m";
11
+
12
+ function log(msg, color) {
13
+ console.log((color || RESET) + msg + RESET);
14
+ }
15
+
16
+ function printBanner() {
17
+ console.log("\n" + CYAN + BOLD + [
18
+ " ╔═══════════════════════════════════════════╗",
19
+ " ║ 🤖 Bolloon Agent ║",
20
+ " ║ P2P AI Document Processor ║",
21
+ " ╚═══════════════════════════════════════════╝"
22
+ ].join("\n") + RESET + "\n");
23
+ }
24
+
25
+ function getMainEntry() {
26
+ const distDir = path.dirname(require.main.filename);
27
+ const distIndex = path.join(distDir, "index.js");
28
+ if (fs.existsSync(distIndex)) {
29
+ return distIndex;
30
+ }
31
+ return path.join(process.cwd(), "src", "index.ts");
32
+ }
33
+
34
+ function parseArgs() {
35
+ const args = process.argv.slice(2);
36
+ if (args.length === 0) {
37
+ return { mode: "gui", args: [] };
38
+ }
39
+ const first = args[0];
40
+ switch (first) {
41
+ case "-v":
42
+ case "--version":
43
+ return { mode: "version", args: [] };
44
+ case "-h":
45
+ case "--help":
46
+ return { mode: "help", args: [] };
47
+ case "-g":
48
+ case "--gui":
49
+ return { mode: "gui", args: args.slice(1) };
50
+ case "-w":
51
+ case "--web":
52
+ return { mode: "web", args: args.slice(1) };
53
+ case "-c":
54
+ case "--cli":
55
+ return { mode: "cli", args: args.slice(1) };
56
+ default:
57
+ return { mode: "passthrough", args };
58
+ }
59
+ }
60
+
61
+ async function startElectron(additionalArgs) {
62
+ try {
63
+ const electron = require("electron");
64
+ const distDir = path.dirname(require.main.filename);
65
+ let mainPath = path.join(distDir, "electron.js");
66
+ if (!fs.existsSync(mainPath)) {
67
+ mainPath = path.join(process.cwd(), "src", "electron.ts");
68
+ }
69
+ log("启动 Electron...", CYAN);
70
+ const child = spawn(electron, [mainPath, ...additionalArgs], {
71
+ stdio: "inherit",
72
+ env: { ...process.env, NODE_ENV: "development" }
73
+ });
74
+ child.on("error", (err) => {
75
+ log("Electron 启动失败: " + err.message, MAGENTA);
76
+ process.exit(1);
77
+ });
78
+ child.on("exit", (code) => process.exit(code || 0));
79
+ } catch (err) {
80
+ log("Electron 不可用,切换到 Web 模式...", CYAN);
81
+ await startWebServer(additionalArgs);
82
+ }
83
+ }
84
+
85
+ async function startWebServer(additionalArgs) {
86
+ const mainPath = getMainEntry();
87
+ const webArgs = ["--web", ...additionalArgs];
88
+ log("启动 Web 服务...", CYAN);
89
+ const child = spawn(process.execPath, [mainPath, ...webArgs], { stdio: "inherit" });
90
+ child.on("error", (err) => {
91
+ log("Web 服务启动失败: " + err.message, MAGENTA);
92
+ process.exit(1);
93
+ });
94
+ child.on("exit", (code) => process.exit(code || 0));
95
+ }
96
+
97
+ async function startCLI(additionalArgs) {
98
+ const mainPath = getMainEntry();
99
+ log("启动命令行界面...", CYAN);
100
+ const child = spawn(process.execPath, [mainPath, ...additionalArgs], { stdio: "inherit" });
101
+ child.on("error", (err) => {
102
+ log("CLI 启动失败: " + err.message, MAGENTA);
103
+ process.exit(1);
104
+ });
105
+ child.on("exit", (code) => process.exit(code || 0));
106
+ }
107
+
108
+ async function main() {
109
+ const { mode, args } = parseArgs();
110
+ switch (mode) {
111
+ case "version":
112
+ console.log("Bolloon Agent v0.1.1");
113
+ break;
114
+ case "help":
115
+ printBanner();
116
+ console.log(BOLD + "用法:" + RESET + " bolloon [选项] [命令] [参数]");
117
+ console.log(BOLD + "选项:" + RESET + " --gui, -g 启动图形界面");
118
+ console.log(" --web, -w 启动 Web UI");
119
+ console.log(" --cli, -c 启动命令行界面");
120
+ console.log(" --version, -v 显示版本");
121
+ console.log(" --help, -h 显示帮助");
122
+ console.log(BOLD + "示例:" + RESET + " bolloon # 启动图形界面");
123
+ console.log(" bolloon --web # 启动 Web UI");
124
+ console.log(" bolloon --read file # 读取文档");
125
+ break;
126
+ case "gui":
127
+ printBanner();
128
+ await startElectron(args);
129
+ break;
130
+ case "web":
131
+ printBanner();
132
+ await startWebServer(args);
133
+ break;
134
+ case "cli":
135
+ await startCLI(args);
136
+ break;
137
+ case "passthrough":
138
+ const mainPath = getMainEntry();
139
+ const child = spawn(process.execPath, [mainPath, ...args], { stdio: "inherit" });
140
+ child.on("error", (err) => {
141
+ log("执行失败: " + err.message, MAGENTA);
142
+ process.exit(1);
143
+ });
144
+ child.on("exit", (code) => process.exit(code || 0));
145
+ break;
146
+ default:
147
+ log("未知模式: " + mode, MAGENTA);
148
+ printBanner();
149
+ console.log("输入 --help 查看帮助");
150
+ process.exit(1);
151
+ }
152
+ }
153
+
154
+ main().catch((err) => {
155
+ console.error("Fatal error:", err);
156
+ process.exit(1);
157
+ });
@@ -7,25 +7,25 @@ import { ToolPermissionContext, BudgetTracker } from '@bolloon/constraint-runtim
7
7
  * System prompt for constraint layer
8
8
  * Defines boundaries and autonomous permissions
9
9
  */
10
- export const SYSTEM_PROMPT = `
11
- 你是一个文档处理Agent,在以下规则下运行:
12
-
13
- 【边界规则】
14
- 1. 只处理:txt, md, pdf, docx 格式文档
15
- 2. 不修改原始文件,只输出改进版本
16
- 3. 发送前必须记录操作日志
17
-
18
- 【自主权限】
19
- - ✅ 自主决定:摘要详细程度、chunk分块策略
20
- - ✅ 自主决定:重试次数(最多3次)
21
- - ✅ 自主决定:是否需要补充信息
22
- - ❌ 必须确认:首次向新对等节点发送文档
23
- - ❌ 必须确认:删除操作
24
-
25
- 【敏感操作拦截】
26
- if (操作 === '发送文档' && 对等节点不在已知列表) {
27
- 拦截 → 记录 → 等待确认
28
- }
10
+ export const SYSTEM_PROMPT = `
11
+ 你是一个文档处理Agent,在以下规则下运行:
12
+
13
+ 【边界规则】
14
+ 1. 只处理:txt, md, pdf, docx 格式文档
15
+ 2. 不修改原始文件,只输出改进版本
16
+ 3. 发送前必须记录操作日志
17
+
18
+ 【自主权限】
19
+ - ✅ 自主决定:摘要详细程度、chunk分块策略
20
+ - ✅ 自主决定:重试次数(最多3次)
21
+ - ✅ 自主决定:是否需要补充信息
22
+ - ❌ 必须确认:首次向新对等节点发送文档
23
+ - ❌ 必须确认:删除操作
24
+
25
+ 【敏感操作拦截】
26
+ if (操作 === '发送文档' && 对等节点不在已知列表) {
27
+ 拦截 → 记录 → 等待确认
28
+ }
29
29
  `;
30
30
  /**
31
31
  * Actions that can be executed autonomously without confirmation
@@ -148,20 +148,20 @@ export async function distillDissent(ask) {
148
148
  .slice(-10)
149
149
  .flatMap((j) => j.reasons || [])
150
150
  .slice(0, 20);
151
- const prompt = `你是用户的判断力代理. 用户发起了一个决定: "${ask.decision}"
152
- ${ask.context ? `\n补充: ${ask.context}` : ''}
153
-
154
- [用户价值画像] ${valueHint}
155
- [相关历史判断 (${relevant.length} 条)] ${relevant.slice(0, 3).map((j) => j.decision || '').join(' | ').slice(0, 300)}
156
- [历史理由样本] ${recentReasons.join(' | ').slice(0, 400) || '(无)'}
157
-
158
- 请基于上述判断力, 生成正好 3 个"反方观点" (用户应该考虑但可能忽略的风险/盲点).
159
- 要求:
160
- - 每个反方 1-2 句, 总长 < 80 字
161
- - 必须是这个特定决定的"反对声音", 不是通用建议
162
- - 第 1 个: 历史/经验类风险
163
- - 第 2 个: 关系/信任类风险
164
- - 第 3 个: 隐藏代价/时间类风险
151
+ const prompt = `你是用户的判断力代理. 用户发起了一个决定: "${ask.decision}"
152
+ ${ask.context ? `\n补充: ${ask.context}` : ''}
153
+
154
+ [用户价值画像] ${valueHint}
155
+ [相关历史判断 (${relevant.length} 条)] ${relevant.slice(0, 3).map((j) => j.decision || '').join(' | ').slice(0, 300)}
156
+ [历史理由样本] ${recentReasons.join(' | ').slice(0, 400) || '(无)'}
157
+
158
+ 请基于上述判断力, 生成正好 3 个"反方观点" (用户应该考虑但可能忽略的风险/盲点).
159
+ 要求:
160
+ - 每个反方 1-2 句, 总长 < 80 字
161
+ - 必须是这个特定决定的"反对声音", 不是通用建议
162
+ - 第 1 个: 历史/经验类风险
163
+ - 第 2 个: 关系/信任类风险
164
+ - 第 3 个: 隐藏代价/时间类风险
165
165
  - 用 JSON 数组返回: ["反方1", "反方2", "反方3"]`;
166
166
  // LLM 调用 (OpenAI 兼容, 复用 p2p-chat-tools 的方式)
167
167
  let dissents = [];
@@ -32,8 +32,58 @@ import { delegateToEngine } from '../external-engines/delegate.js';
32
32
  */
33
33
  export const SIDE_EFFECT_TOOLS = new Set([
34
34
  'write_file', 'edit_file', 'shell_exec', 'git_commit', 'git_push', 'git_branch',
35
- 'create_task', 'update_task',
35
+ 'create_task', 'update_task', 'terminal', 'process',
36
36
  ]);
37
+ export async function runTerminalCommand(raw, opts = {}) {
38
+ const { checkTerminalCommand } = await import('./shell-guard.js');
39
+ const timeoutMs = opts.timeoutMs ?? 30000;
40
+ const list = (opts.commands && opts.commands.length > 0) ? opts.commands : [raw];
41
+ for (const c of list) {
42
+ const guard = checkTerminalCommand(String(c || '').trim());
43
+ if (!guard.allowed) {
44
+ return { success: false, error: `[terminal-guard] ${guard.reason}`, deniedByGuard: true };
45
+ }
46
+ }
47
+ // 2026-08-12 (TaskD): 后台执行 — 长命令不阻塞对话.
48
+ if (opts.background) {
49
+ const cmdStr = list.join(' && ');
50
+ const { spawnBackground } = await import('./process-runner.js');
51
+ const session = spawnBackground(cmdStr, opts.cwd ?? process.cwd());
52
+ return {
53
+ success: true,
54
+ background: true,
55
+ sessionId: session.id,
56
+ cmd: cmdStr.slice(0, 200),
57
+ message: `已在后台启动 (${session.id}). 用 process 工具轮询/等待: process(session_id="${session.id}", action="poll"|"wait"|"kill")`,
58
+ };
59
+ }
60
+ const { exec } = await import('child_process');
61
+ const runOne = (cmdStr) => new Promise((resolve) => {
62
+ exec(cmdStr, {
63
+ cwd: opts.cwd ?? process.cwd(),
64
+ timeout: timeoutMs,
65
+ maxBuffer: 8 * 1024 * 1024,
66
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
67
+ }, (err, stdout, stderr) => {
68
+ const output = ((stdout || '') + (stderr ? `\n[stderr]\n${stderr}` : '')).trim().slice(0, 8000);
69
+ if (err) {
70
+ resolve({ success: false, cmd: cmdStr.slice(0, 120), error: `exit ${err.code ?? '?'}: ${String(err.message || '').slice(0, 200)}`, output: output || undefined });
71
+ }
72
+ else {
73
+ resolve({ success: true, cmd: cmdStr.slice(0, 120), output: output || '(无输出)' });
74
+ }
75
+ });
76
+ });
77
+ if (list.length === 1)
78
+ return runOne(list[0]);
79
+ const results = await Promise.all(list.map((c) => runOne(c)));
80
+ const failed = results.filter((r) => !r.success);
81
+ const allOutput = results.map((r) => `${r.cmd}\n${r.output || r.error || ''}`).join('\n\n---\n\n');
82
+ if (failed.length > 0) {
83
+ return { success: false, error: `${failed.length}/${list.length} 条命令失败: ${failed[0].error}`, output: allOutput, partial: results };
84
+ }
85
+ return { success: true, output: allOutput, count: list.length, parallel: true };
86
+ }
37
87
  export function registerBuiltinTools(ctx) {
38
88
  ctx.tools.set('read_document', {
39
89
  name: 'read_document',
@@ -621,17 +671,22 @@ export function registerBuiltinTools(ctx) {
621
671
  for (const tool of p2pDocumentTools) {
622
672
  ctx.tools.set(tool.name, tool);
623
673
  }
624
- // shell_exec
674
+ // 2026-08-12 (Task2): shell_exec / terminal 统一走模块级 runTerminalCommand (宽松护栏 + 多命令并行).
675
+ // shell_exec — 2026-08-12 (Task2): 与 terminal 统一走宽松护栏 (denylist-only).
676
+ // 兼容旧格式 (command + args 数组), 内部转成完整命令字符串交给 runTerminal,
677
+ // 不再用窄白名单 → 模型不会因 "command 不在白名单" 报错.
625
678
  ctx.tools.set('shell_exec', {
626
679
  name: 'shell_exec',
627
- description: ' cwd 跑 shell 命令. 仅支持白名单内命令: git, npm, npx, tsx, tsc, vitest, cat, head, tail, ls, wc, echo, pwd, date, mkdir, touch. 禁止管道/重定向/rm -rf/sudo. 命中护栏黑名单会被拒.',
628
- parameters: { command: '可执行文件 (必填, 必须在白名单)', args: '参数数组, 逗号分隔', timeoutMs: '超时毫秒, 默认 30000' },
680
+ description: '执行 shell 命令 (兼容模式, 参数数组或命令字符串均可). 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据). 推荐直接用 terminal 传完整命令字符串.',
681
+ parameters: { command: '可执行文件 或 完整命令 (必填)', args: '参数数组, 逗号分隔 (可选)', timeoutMs: '超时毫秒, 默认 30000' },
629
682
  execute: async (args) => {
630
683
  const cmd = String(args.command || '').trim();
631
684
  if (!cmd)
632
685
  return { success: false, error: 'command 必填' };
633
- let argList = [];
686
+ // 兼容参数数组 拼成命令字符串
687
+ let full = cmd;
634
688
  const rawArgs = args.args;
689
+ let argList = [];
635
690
  if (Array.isArray(rawArgs)) {
636
691
  argList = rawArgs.map((s) => String(s).trim()).filter(Boolean);
637
692
  }
@@ -640,61 +695,76 @@ export function registerBuiltinTools(ctx) {
640
695
  if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
641
696
  try {
642
697
  const parsed = JSON.parse(trimmed);
643
- if (Array.isArray(parsed)) {
698
+ if (Array.isArray(parsed))
644
699
  argList = parsed.map((s) => String(s).trim()).filter(Boolean);
645
- }
646
700
  }
647
- catch { /* fall through to comma split */ }
701
+ catch { /* JSON 数组 */ }
648
702
  }
649
- if (argList.length === 0) {
703
+ if (argList.length === 0)
650
704
  argList = trimmed.split(',').map(s => s.trim()).filter(Boolean);
651
- }
652
705
  }
653
- const timeoutMs = Number(args.timeoutMs) || 30000;
654
- const result = await shellExec(cmd, argList, { timeoutMs });
655
- if (result.deniedByGuard) {
656
- return { success: false, error: result.error };
706
+ // command 只是可执行文件名且带 args 拼成 "cmd arg1 arg2"; 否则原样
707
+ if (argList.length > 0 && !/\s/.test(cmd) && !cmd.includes('&&') && !cmd.includes(';') && !cmd.includes('|')) {
708
+ full = `${cmd} ${argList.map(a => (/[\s"&|<>^()%!`]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a)).join(' ')}`;
657
709
  }
658
- if (!result.success) {
659
- return { success: false, error: result.error, output: result.output };
660
- }
661
- return { success: true, output: result.output };
710
+ const timeoutMs = Number(args.timeoutMs) || 30000;
711
+ return await runTerminalCommand(full, { timeoutMs, cwd: ctx.cwd });
662
712
  }
663
713
  });
664
714
  // 2026-08-10: terminal — 灵活终端写命令 (用户要求: bolloon 自己写命令进 terminal, 少围栏).
665
- // shell_exec 的区别: 接受**完整 shell 命令字符串** (管道/重定向/写文件都行),
666
- // 护栏只挡高危破坏模式 (checkTerminalCommand: 提权/格式化/删根/.bolloon 数据), 其余放行.
715
+ // 2026-08-12 (Task2): 支持 commands 数组并行执行; shell_exec 统一走 runTerminalCommand.
716
+ // shell_exec 的区别: 直接接受完整 shell 命令字符串, 更适合模型自主写命令.
667
717
  ctx.tools.set('terminal', {
668
718
  name: 'terminal',
669
- description: '执行完整 shell 命令 (支持管道/重定向/写文件/跑脚本). 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据), 其余灵活放行. 适合: 写 HTML 文件、跑 python/node 脚本、查系统状态、装依赖.',
670
- parameters: { command: '完整 shell 命令 (必填, 如: echo "<html>" > /tmp/site/index.html && ls /tmp/site)', timeoutMs: '超时毫秒, 默认 30000' },
719
+ description: '执行完整 shell 命令 (支持管道/重定向/写文件/跑脚本). 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据), 其余灵活放行. 适合: 写 HTML 文件、跑 python/node 脚本、查系统状态、装依赖. 多条命令用 commands 数组并行执行. 长命令 (服务器/构建/后台任务) 设 background=true 后台执行不阻塞对话.',
720
+ parameters: { command: '完整 shell 命令 (必填, 如: echo "<html>" > /tmp/site/index.html && ls /tmp/site)', commands: '可选: 多条命令数组 (并行执行), 每条独立字符串', timeoutMs: '超时毫秒, 默认 30000', background: '可选: true 后台执行, 立即返回 session_id (用 process 工具 poll/wait/kill)' },
671
721
  execute: async (args) => {
672
722
  const raw = String(args.command || '').trim();
673
- if (!raw)
674
- return { success: false, error: 'command 必填' };
675
- const { checkTerminalCommand } = await import('./shell-guard.js');
676
- const guard = checkTerminalCommand(raw);
677
- if (!guard.allowed) {
678
- return { success: false, error: `[terminal-guard] ${guard.reason}`, deniedByGuard: true };
679
- }
680
- const { exec } = await import('child_process');
723
+ const commands = Array.isArray(args.commands) ? args.commands.map((c) => String(c || '').trim()).filter(Boolean) : [];
724
+ if (!raw && commands.length === 0)
725
+ return { success: false, error: 'command 或 commands 必填' };
681
726
  const timeoutMs = Number(args.timeoutMs) || 30000;
682
- return new Promise((resolve) => {
683
- exec(raw, {
684
- cwd: process.cwd(),
685
- timeout: timeoutMs,
686
- maxBuffer: 8 * 1024 * 1024,
687
- env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
688
- }, (err, stdout, stderr) => {
689
- const output = ((stdout || '') + (stderr ? `\n[stderr]\n${stderr}` : '')).trim().slice(0, 8000);
690
- if (err) {
691
- resolve({ success: false, error: `exit ${err.code ?? '?'}: ${String(err.message || '').slice(0, 200)}`, output: output || undefined });
692
- }
693
- else {
694
- resolve({ success: true, output: output || '(无输出)' });
695
- }
696
- });
697
- });
727
+ return await runTerminalCommand(raw, { timeoutMs, cwd: ctx.cwd, commands: commands.length > 0 ? commands : undefined, background: String(args.background).toLowerCase() === 'true' });
728
+ }
729
+ });
730
+ // 2026-08-12 (TaskD): process — 后台进程管理 (学 hermes terminal background session + poll/wait/kill).
731
+ // 长命令不阻塞对话: terminal(background=true) 启动 process 工具轮询/等待/终止.
732
+ ctx.tools.set('process', {
733
+ name: 'process',
734
+ description: '管理后台进程 (terminal background=true 启动的). action: poll(查状态, 不阻塞) / wait(等结束, 最多 timeoutMs) / kill(终止) / list(列全部). 长期运行命令的阻塞问题用它解决.',
735
+ parameters: { session_id: '后台进程 session_id (必填, poll/wait/kill 用)', action: 'poll | wait | kill | list (默认 poll)', timeoutMs: 'wait 模式等待上限毫秒, 默认 30000' },
736
+ execute: async (args) => {
737
+ const action = String(args.action || 'poll').trim().toLowerCase();
738
+ try {
739
+ const { pollSession, waitSession, killSession, listSessions, isValidSessionId } = await import('./process-runner.js');
740
+ if (action === 'list') {
741
+ const all = listSessions();
742
+ return { success: true, output: all.length === 0 ? '(无后台进程)' : all.map((s) => ` [${s.status}] ${s.id} ${s.cmd}`).join('\n') };
743
+ }
744
+ const sid = String(args.session_id || '').trim();
745
+ if (!sid)
746
+ return { success: false, error: 'session_id 必填' };
747
+ if (!isValidSessionId(sid))
748
+ return { success: false, error: 'session_id 非法' };
749
+ if (action === 'kill') {
750
+ const r = killSession(sid);
751
+ return { success: r.ok, output: r.reason ?? `已终止 ${sid}` };
752
+ }
753
+ if (action === 'wait') {
754
+ const t = Number(args.timeoutMs) || 30000;
755
+ const r = await waitSession(sid, t);
756
+ return { success: r.ok, output: r.session ? `${r.session.status} exit=${r.session.exitCode}${r.session.timedOut ? ' (超时)' : ''}\n${r.session.output || '(无输出)'}` : '未知 session' };
757
+ }
758
+ // poll
759
+ const r = pollSession(sid);
760
+ if (!r.ok || !r.session)
761
+ return { success: false, error: `未知 session ${sid}` };
762
+ const s = r.session;
763
+ return { success: true, output: `[${s.status}] exit=${s.exitCode}${s.status === 'running' ? ' (运行中)' : ''}\n${s.output || '(无输出)'}` };
764
+ }
765
+ catch (e) {
766
+ return { success: false, error: `process ${action} 失败: ${String(e?.message || e).slice(0, 200)}` };
767
+ }
698
768
  }
699
769
  });
700
770
  // self_improve
@@ -729,6 +799,15 @@ export function registerBuiltinTools(ctx) {
729
799
  }
730
800
  try {
731
801
  const absPath = path.resolve(ctx.cwd, relPath);
802
+ // 2026-08-12 (TaskC): 写前暂存 (准备阶段) — 记录变更前快照, 支持审计/撤销. 失败静默.
803
+ let before = '';
804
+ try {
805
+ before = await fs.readFile(absPath, 'utf-8');
806
+ }
807
+ catch { /* 新文件 */ }
808
+ const action = before.length > 0 ? 'overwrite' : 'create';
809
+ const { stageWrite } = await import('./write-staging.js');
810
+ await stageWrite(relPath, before, content, action, ctx.cwd).catch(() => { });
732
811
  await fs.mkdir(path.dirname(absPath), { recursive: true });
733
812
  await fs.writeFile(absPath, content, 'utf-8');
734
813
  return { success: true, output: `✅ wrote ${relPath} (${content.length} bytes)` };
@@ -761,6 +840,9 @@ export function registerBuiltinTools(ctx) {
761
840
  return { success: false, error: `old_text 在 ${relPath} 中未找到, 拒绝静默写入. 请先用 read_document 读最新内容.` };
762
841
  }
763
842
  const updated = original.replace(oldText, newText);
843
+ // 2026-08-12 (TaskC): 写前暂存 (准备阶段) — 记录变更前快照, 支持审计/撤销. 失败静默.
844
+ const { stageWrite } = await import('./write-staging.js');
845
+ await stageWrite(relPath, original, updated, 'edit', ctx.cwd).catch(() => { });
764
846
  await fs.writeFile(absPath, updated, 'utf-8');
765
847
  return { success: true, output: `✅ edited ${relPath} (${oldText.length} → ${newText.length} 字节)` };
766
848
  }
@@ -1,13 +1,13 @@
1
- export const TOOL_DEFINITIONS = `
2
- 可用工具:
3
- 1. read_document(path) - 读取文档内容,支持 .txt, .md, .pdf, .docx
4
- 2. summarize_document(path, context?) - 总结文档内容,可选提供上下文
5
- 3. improve_document(path, requirements) - 改进文档,需提供文件路径和改进要求
6
- 4. list_peers() - 列出已连接的对等节点
7
- 5. send_message(peer_id, message) - 向指定对等节点发送消息
8
- 6. broadcast_message(message) - 向所有对等节点广播消息
9
- 7. get_identity() - 获取当前智能体身份信息
10
- 8. set_persona(persona_json) - 更新智能体 persona,包含 name、description、personality、greeting 等
11
- 9. run_workflow(steps) - 执行预定义工作流
12
- 10. get_operation_logs() - 获取操作日志
1
+ export const TOOL_DEFINITIONS = `
2
+ 可用工具:
3
+ 1. read_document(path) - 读取文档内容,支持 .txt, .md, .pdf, .docx
4
+ 2. summarize_document(path, context?) - 总结文档内容,可选提供上下文
5
+ 3. improve_document(path, requirements) - 改进文档,需提供文件路径和改进要求
6
+ 4. list_peers() - 列出已连接的对等节点
7
+ 5. send_message(peer_id, message) - 向指定对等节点发送消息
8
+ 6. broadcast_message(message) - 向所有对等节点广播消息
9
+ 7. get_identity() - 获取当前智能体身份信息
10
+ 8. set_persona(persona_json) - 更新智能体 persona,包含 name、description、personality、greeting 等
11
+ 9. run_workflow(steps) - 执行预定义工作流
12
+ 10. get_operation_logs() - 获取操作日志
13
13
  `;