@bolloon/bolloon-agent 0.2.12 → 0.2.14

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 (284) hide show
  1. package/README.md +495 -467
  2. package/bin/bolloon-cli.cjs +183 -183
  3. package/bin/bolloon-daemon.sh +207 -0
  4. package/bin/bolloon.cjs +0 -0
  5. package/bin/ipfs +0 -0
  6. package/dist/agents/agent-manifest-protocol.js +52 -0
  7. package/dist/agents/chat-segmenter.js +298 -0
  8. package/dist/agents/constraint-layer.js +19 -19
  9. package/dist/agents/judgment-protocol.js +479 -0
  10. package/dist/agents/parse-tool-call.js +304 -0
  11. package/dist/agents/peer-manifest-loader.js +210 -0
  12. package/dist/agents/pi-sdk-session-factory.js +110 -0
  13. package/dist/agents/pi-sdk-session-manager.js +297 -0
  14. package/dist/agents/pi-sdk-tools.js +1288 -0
  15. package/dist/agents/pi-sdk-types.js +13 -0
  16. package/dist/agents/pi-sdk.js +553 -1306
  17. package/dist/agents/react-loop.js +120 -0
  18. package/dist/agents/session-store.js +171 -0
  19. package/dist/agents/shell-guard.js +2 -2
  20. package/dist/agents/tool-registry.js +136 -0
  21. package/dist/agents/workflow-pivot-loop.js +200 -22
  22. package/dist/bollharness/src/scripts/context_router.js +7 -0
  23. package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
  24. package/dist/bollharness-integration/context-router-judgment.js +4 -4
  25. package/dist/bollharness-integration/context-router.js +292 -292
  26. package/dist/bollharness-integration/gate-state-machine.js +26 -13
  27. package/dist/bollharness-integration/integration.js +71 -0
  28. package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
  29. package/dist/bollharness-integration/skill-adapter.js +57 -127
  30. package/dist/bootstrap/chat-archiver.js +276 -0
  31. package/dist/bootstrap/context-collector.js +6 -3
  32. package/dist/bootstrap/event-log.js +160 -0
  33. package/dist/bootstrap/lifecycle-hooks.js +15 -1
  34. package/dist/bootstrap/memory-compressor.js +170 -0
  35. package/dist/bootstrap/persona-loader.js +94 -0
  36. package/dist/bootstrap/project-state.js +159 -0
  37. package/dist/bootstrap/remote-mirror.js +97 -0
  38. package/dist/bootstrap/session-window.js +121 -0
  39. package/dist/bootstrap/vector-index.js +248 -0
  40. package/dist/cli/loading-tui.js +40 -0
  41. package/dist/cli-entry.js +41 -37
  42. package/dist/context-compaction/auto-compact.js +7 -7
  43. package/dist/electron/config.js +21 -0
  44. package/dist/electron/config.js.map +1 -0
  45. package/dist/electron/dialogs.js +108 -0
  46. package/dist/electron/dialogs.js.map +1 -0
  47. package/dist/electron/first-run.js +170 -0
  48. package/dist/electron/first-run.js.map +1 -0
  49. package/dist/electron/ipc.js +20 -0
  50. package/dist/electron/ipc.js.map +1 -0
  51. package/dist/electron/logger.js +114 -0
  52. package/dist/electron/logger.js.map +1 -0
  53. package/dist/electron/main.js +63 -0
  54. package/dist/electron/main.js.map +1 -0
  55. package/dist/electron/menu.js +145 -0
  56. package/dist/electron/menu.js.map +1 -0
  57. package/dist/electron/paths.js +75 -0
  58. package/dist/electron/paths.js.map +1 -0
  59. package/dist/electron/server.js +119 -0
  60. package/dist/electron/server.js.map +1 -0
  61. package/dist/electron/tray.js +111 -0
  62. package/dist/electron/tray.js.map +1 -0
  63. package/dist/electron/window.js +111 -0
  64. package/dist/electron/window.js.map +1 -0
  65. package/dist/electron-preload.js +26 -6
  66. package/dist/electron-preload.js.map +1 -1
  67. package/dist/electron.js +6 -168
  68. package/dist/electron.js.map +1 -1
  69. package/dist/index.js +323 -266
  70. package/dist/llm/config-store.js +29 -8
  71. package/dist/llm/llm-judgment-client.js +102 -102
  72. package/dist/llm/pi-ai.js +139 -85
  73. package/dist/llm/system-prompt/layers/channel/local.md +14 -14
  74. package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
  75. package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
  76. package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
  77. package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
  78. package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
  79. package/dist/llm/system-prompt/layers/core/identity.md +37 -37
  80. package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
  81. package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
  82. package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
  83. package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
  84. package/dist/llm/system-prompt/layers/core/tone.md +31 -31
  85. package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
  86. package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
  87. package/dist/llm/system-prompt/layers/role/architect.md +20 -20
  88. package/dist/llm/system-prompt/layers/role/expert.md +19 -19
  89. package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
  90. package/dist/llm/system-prompt/layers/role/security.md +15 -15
  91. package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
  92. package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
  93. package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
  94. package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
  95. package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
  96. package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
  97. package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
  98. package/dist/llm/tool-manifest/ask_user_input.js +1 -1
  99. package/dist/llm/tool-manifest/bash.js +3 -3
  100. package/dist/llm/tool-manifest/create_file.js +4 -4
  101. package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
  102. package/dist/llm/tool-manifest/image_search.js +2 -2
  103. package/dist/llm/tool-manifest/mcp.js +2 -2
  104. package/dist/llm/tool-manifest/message_compose.js +3 -3
  105. package/dist/llm/tool-manifest/places.js +2 -2
  106. package/dist/llm/tool-manifest/present_files.js +1 -1
  107. package/dist/llm/tool-manifest/recipe.js +2 -2
  108. package/dist/llm/tool-manifest/str_replace.js +5 -5
  109. package/dist/llm/tool-manifest/view.js +3 -3
  110. package/dist/llm/tool-manifest/weather.js +4 -4
  111. package/dist/llm/tool-manifest/web.js +4 -4
  112. package/dist/network/local-inbox-bus.js +73 -0
  113. package/dist/network/p2p-outbox.js +161 -0
  114. package/dist/network/peer-fs.js +420 -0
  115. package/dist/network/peer-resource-bridge.js +216 -0
  116. package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
  117. package/dist/pi-ecosystem-judgment/cleanup.js +126 -0
  118. package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
  119. package/dist/pi-ecosystem-judgment/distillation.js +14 -14
  120. package/dist/pi-ecosystem-judgment/human-value-store.js +45 -11
  121. package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
  122. package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
  123. package/dist/security/context-router-tool.js +15 -15
  124. package/dist/security/tool-gate.js +11 -0
  125. package/dist/web/api-config.html +520 -520
  126. package/dist/web/client-loop-status.js +220 -0
  127. package/dist/web/client.js +22 -5
  128. package/dist/web/client.js.map +7 -0
  129. package/dist/web/components/wallet-viem.mjs +118 -118
  130. package/dist/web/index.html +375 -375
  131. package/dist/web/input-validator.js +103 -0
  132. package/dist/web/manifest.json +20 -20
  133. package/dist/web/routes-judgments.js +738 -0
  134. package/dist/web/routes-llm-config.js +279 -0
  135. package/dist/web/routes-tasks.js +222 -0
  136. package/dist/web/server-sse.js +121 -0
  137. package/dist/web/server-storage.js +154 -0
  138. package/dist/web/server-types.js +15 -0
  139. package/dist/web/server-v3-p2p.js +224 -0
  140. package/dist/web/server.js +1070 -1307
  141. package/dist/web/style.css +4742 -4742
  142. package/dist/web/util/safe-name.js +32 -0
  143. package/package.json +162 -160
  144. package/scripts/build-cli.js +215 -215
  145. package/scripts/build-web.ts +130 -130
  146. package/scripts/postinstall.js +152 -152
  147. package/bin/bolloon.js +0 -157
  148. package/dist/constraint-runtime/src/_archive_helper.js +0 -9
  149. package/dist/constraint-runtime/src/agent/coordinator.js +0 -48
  150. package/dist/constraint-runtime/src/agent/index.js +0 -1
  151. package/dist/constraint-runtime/src/assistant/index.js +0 -12
  152. package/dist/constraint-runtime/src/bootstrap/index.js +0 -12
  153. package/dist/constraint-runtime/src/bootstrap_graph.js +0 -13
  154. package/dist/constraint-runtime/src/bridge/index.js +0 -12
  155. package/dist/constraint-runtime/src/buddy/index.js +0 -12
  156. package/dist/constraint-runtime/src/cli/index.js +0 -12
  157. package/dist/constraint-runtime/src/command_graph.js +0 -10
  158. package/dist/constraint-runtime/src/commands.js +0 -60
  159. package/dist/constraint-runtime/src/components/index.js +0 -12
  160. package/dist/constraint-runtime/src/constants/index.js +0 -12
  161. package/dist/constraint-runtime/src/constraint/budget.js +0 -22
  162. package/dist/constraint-runtime/src/constraint/index.js +0 -2
  163. package/dist/constraint-runtime/src/constraint/permission.js +0 -20
  164. package/dist/constraint-runtime/src/context.js +0 -30
  165. package/dist/constraint-runtime/src/coordinator/index.js +0 -12
  166. package/dist/constraint-runtime/src/cost_hook.js +0 -4
  167. package/dist/constraint-runtime/src/cost_tracker.js +0 -8
  168. package/dist/constraint-runtime/src/deferred_init.js +0 -10
  169. package/dist/constraint-runtime/src/direct_modes.js +0 -6
  170. package/dist/constraint-runtime/src/dynamic-tool-loader.js +0 -85
  171. package/dist/constraint-runtime/src/entrypoints/index.js +0 -12
  172. package/dist/constraint-runtime/src/execution_registry.js +0 -44
  173. package/dist/constraint-runtime/src/history.js +0 -9
  174. package/dist/constraint-runtime/src/hooks/index.js +0 -12
  175. package/dist/constraint-runtime/src/index.js +0 -26
  176. package/dist/constraint-runtime/src/ink.js +0 -4
  177. package/dist/constraint-runtime/src/keybindings/index.js +0 -12
  178. package/dist/constraint-runtime/src/memdir/index.js +0 -12
  179. package/dist/constraint-runtime/src/migrations/index.js +0 -12
  180. package/dist/constraint-runtime/src/models.js +0 -1
  181. package/dist/constraint-runtime/src/moreright/index.js +0 -12
  182. package/dist/constraint-runtime/src/native_ts/index.js +0 -12
  183. package/dist/constraint-runtime/src/output_styles/index.js +0 -12
  184. package/dist/constraint-runtime/src/parity_audit.js +0 -12
  185. package/dist/constraint-runtime/src/plugins/index.js +0 -12
  186. package/dist/constraint-runtime/src/port_manifest.js +0 -11
  187. package/dist/constraint-runtime/src/prefetch.js +0 -9
  188. package/dist/constraint-runtime/src/query.js +0 -1
  189. package/dist/constraint-runtime/src/remote/index.js +0 -12
  190. package/dist/constraint-runtime/src/remote_runtime.js +0 -9
  191. package/dist/constraint-runtime/src/runtime/index.js +0 -1
  192. package/dist/constraint-runtime/src/runtime/session.js +0 -35
  193. package/dist/constraint-runtime/src/schemas/index.js +0 -12
  194. package/dist/constraint-runtime/src/screens/index.js +0 -12
  195. package/dist/constraint-runtime/src/server/index.js +0 -12
  196. package/dist/constraint-runtime/src/services/index.js +0 -12
  197. package/dist/constraint-runtime/src/session_store.js +0 -22
  198. package/dist/constraint-runtime/src/setup.js +0 -30
  199. package/dist/constraint-runtime/src/skills/index.js +0 -1
  200. package/dist/constraint-runtime/src/skills/skill-registry.js +0 -28
  201. package/dist/constraint-runtime/src/state/index.js +0 -12
  202. package/dist/constraint-runtime/src/system_init.js +0 -20
  203. package/dist/constraint-runtime/src/thinking/engine.js +0 -42
  204. package/dist/constraint-runtime/src/thinking/index.js +0 -1
  205. package/dist/constraint-runtime/src/tool_pool.js +0 -8
  206. package/dist/constraint-runtime/src/tools/OpenCLI/execAdapter.js +0 -7
  207. package/dist/constraint-runtime/src/tools/OpenCLI/listAdapters.js +0 -7
  208. package/dist/constraint-runtime/src/tools/OpenCLI/runCommand.js +0 -7
  209. package/dist/constraint-runtime/src/tools/SafeSDK/confirmTransaction.js +0 -6
  210. package/dist/constraint-runtime/src/tools/SafeSDK/createTransaction.js +0 -8
  211. package/dist/constraint-runtime/src/tools/SafeSDK/executeTransaction.js +0 -6
  212. package/dist/constraint-runtime/src/tools/SafeSDK/getBalance.js +0 -6
  213. package/dist/constraint-runtime/src/tools/SafeSDK/getPendingTransactions.js +0 -6
  214. package/dist/constraint-runtime/src/tools/SafeSDK/proposeTransaction.js +0 -6
  215. package/dist/constraint-runtime/src/tools.js +0 -80
  216. package/dist/constraint-runtime/src/transcript.js +0 -19
  217. package/dist/constraint-runtime/src/types/index.js +0 -12
  218. package/dist/constraint-runtime/src/upstream_proxy/index.js +0 -12
  219. package/dist/constraint-runtime/src/utils/index.js +0 -12
  220. package/dist/constraint-runtime/src/vim/index.js +0 -12
  221. package/dist/constraint-runtime/src/voice/index.js +0 -12
  222. package/dist/constraint-runtime/tests/agent.test.js +0 -16
  223. package/dist/constraint-runtime/tests/constraint.test.js +0 -41
  224. package/dist/constraint-runtime/tests/skill.test.js +0 -19
  225. package/dist/constraint-runtime/tests/thinking.test.js +0 -22
  226. package/dist/pi-ecosystem-colony/index.js +0 -365
  227. package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
  228. package/dist/social/ant-colony/PheromoneEngine.js +0 -227
  229. package/dist/social/ant-colony/index.js +0 -6
  230. package/dist/social/ant-colony/types.js +0 -24
  231. package/dist/test/ai-judgment-test.js +0 -80
  232. package/dist/test/bollharness-integration.test.js +0 -318
  233. package/dist/test/channel-agent-multi-dialogue.js +0 -205
  234. package/dist/test/channel-heartbeat-agent-test.js +0 -201
  235. package/dist/test/constraint-layer.test.js +0 -164
  236. package/dist/test/diap-identity-test.js +0 -172
  237. package/dist/test/diap-quick-test.js +0 -62
  238. package/dist/test/global-shared-context.test.js +0 -315
  239. package/dist/test/harness-judgment-injection.test.js +0 -246
  240. package/dist/test/harness-workflow-integrator-test.js +0 -228
  241. package/dist/test/human-value-store.test.js +0 -243
  242. package/dist/test/hybrid-integration-test.js +0 -118
  243. package/dist/test/hybrid-messenger-verify.js +0 -55
  244. package/dist/test/iroh-bistream-debug.js +0 -38
  245. package/dist/test/iroh-communication.test.js +0 -66
  246. package/dist/test/iroh-debug-test.js +0 -57
  247. package/dist/test/iroh-diap-test.js +0 -71
  248. package/dist/test/iroh-direct-connect.js +0 -55
  249. package/dist/test/iroh-e2e-fixed.js +0 -89
  250. package/dist/test/iroh-e2e-same-process.js +0 -63
  251. package/dist/test/iroh-e2e.js +0 -66
  252. package/dist/test/iroh-final-e2e.js +0 -72
  253. package/dist/test/iroh-relay-test.js +0 -37
  254. package/dist/test/iroh-simple-test.js +0 -41
  255. package/dist/test/iroh-transport-verify.js +0 -54
  256. package/dist/test/iroh-transport.test.js +0 -37
  257. package/dist/test/iroh-two-nodes.js +0 -70
  258. package/dist/test/iroh-verify.js +0 -44
  259. package/dist/test/judgment-decision.test.js +0 -219
  260. package/dist/test/llm-judgment-integration.test.js +0 -220
  261. package/dist/test/p2p-agent-complex-dialogue.js +0 -385
  262. package/dist/test/p2p-agent-dialogue.js +0 -341
  263. package/dist/test/p2p-agent-full-bidirectional.js +0 -510
  264. package/dist/test/p2p-agent-harness-flow.js +0 -437
  265. package/dist/test/p2p-agent-harness-single.js +0 -143
  266. package/dist/test/p2p-ai-dialogue-test.js +0 -318
  267. package/dist/test/p2p-cid-connect-test.js +0 -195
  268. package/dist/test/p2p-connect-receiver.js +0 -69
  269. package/dist/test/p2p-doc-transfer.js +0 -110
  270. package/dist/test/p2p-identity-page-test.js +0 -77
  271. package/dist/test/p2p-iroh-test.js +0 -171
  272. package/dist/test/p2p-minimal-test.js +0 -241
  273. package/dist/test/p2p-node-1.js +0 -148
  274. package/dist/test/p2p-node-2.js +0 -148
  275. package/dist/test/p2p-server.js +0 -281
  276. package/dist/test/p2p-two-nodes-test.js +0 -438
  277. package/dist/test/pi-sdk.test.js +0 -44
  278. package/dist/test/set-persona.js +0 -40
  279. package/dist/test/simple.test.js +0 -9
  280. package/dist/test/storage-integration.test.js +0 -150
  281. package/dist/test/subagent-manager.test.js +0 -276
  282. package/dist/test/test-gate-flow.test.js +0 -81
  283. package/dist/test/workflow-engine.test.js +0 -87
  284. package/dist/test/workflow-pivot-loop.test.js +0 -246
@@ -0,0 +1,1288 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as fsSync from 'fs';
3
+ import * as path from 'path';
4
+ import { documentReader } from '../documents/reader.js';
5
+ import { p2pNetwork } from '../network/p2p.js';
6
+ import { shellExec } from './shell-tool.js';
7
+ import { checkWritePath } from './shell-guard.js';
8
+ import { p2pDocumentTools } from './p2p-document-tools.js';
9
+ import { runSelfImproveLoop } from './pi-sdk-session-factory.js';
10
+ import { getMinimax } from '../constraints/index.js';
11
+ /**
12
+ * Tools 模块 — 从 pi-sdk.ts 抽出的 registerTools() / _registerWalletTools() / _setupInboxListener()
13
+ * / wrapToolsWithIdempotency() (2026-07-06).
14
+ *
15
+ * 三个工具来源:
16
+ * 1. 内置工具: read_document, summarize_document, improve_document, list_peers, send_message,
17
+ * broadcast_message, list_local_channels, send_to_channel, check_inbox, send_to_peer,
18
+ * p2p_broadcast, agent_call, get_identity, set_persona, get_operation_logs, list_files,
19
+ * read_directory, shell_exec, self_improve, list_skills, use_skill, write_file, edit_file,
20
+ * git_diff, git_commit, git_push, git_branch, read_file, delete_file, mkdir, move_file,
21
+ * grep_files, glob_files, git_log, git_show, git_stash, vitest_run, tsc_check, create_task,
22
+ * update_task, get_task, list_tasks, p2pDocumentTools
23
+ * 2. 钱包工具 (EVM / Polymarket / Safe) — 单独的 _registerWalletTools
24
+ * 3. P2P 消息 inbox 监听 — _setupInboxListener
25
+ *
26
+ * 调用方: PiAgentSession.registerTools() 会调下面的三个函数, 并执行
27
+ * wrapToolsWithIdempotency + 镜像到 ToolRegistry.
28
+ *
29
+ * 工具缓存策略: SIDE_EFFECT_TOOLS 子集 (write_file / edit_file / shell_exec / git_commit 等)
30
+ * 走幂等性 cache, 防止 LLM loop 重试时副作用执行两次.
31
+ */
32
+ export const SIDE_EFFECT_TOOLS = new Set([
33
+ 'write_file', 'edit_file', 'shell_exec', 'git_commit', 'git_push', 'git_branch',
34
+ 'create_task', 'update_task',
35
+ ]);
36
+ export function registerBuiltinTools(ctx) {
37
+ ctx.tools.set('read_document', {
38
+ name: 'read_document',
39
+ description: '读取文档内容,支持 .txt, .md, .pdf, .docx 格式',
40
+ parameters: { path: 'string' },
41
+ execute: async (args) => {
42
+ try {
43
+ const content = await documentReader.read(args.path);
44
+ return {
45
+ success: true,
46
+ output: `📄 ${content.metadata.filename}\n大小: ${content.metadata.size} 字节\n\n${content.text.substring(0, 1000)}${content.text.length > 1000 ? '...' : ''}`
47
+ };
48
+ }
49
+ catch (e) {
50
+ return { success: false, error: String(e) };
51
+ }
52
+ }
53
+ });
54
+ ctx.tools.set('summarize_document', {
55
+ name: 'summarize_document',
56
+ description: '总结文档内容,分析并生成摘要',
57
+ parameters: { path: 'string', context: 'string' },
58
+ execute: async (args) => {
59
+ try {
60
+ if (!ctx.minimaxAvailable) {
61
+ return { success: false, error: 'LLM未初始化,请设置 MINIMAX_API_KEY' };
62
+ }
63
+ // summarizeDocument 走 PiAgentSession.summarizeDocument, 这里通过 tools 反向调用不好处理
64
+ // 简化: 让 LLM 自己直接走 shell_exec / use_skill 路径
65
+ const llm = getMinimax();
66
+ const content = await documentReader.read(args.path);
67
+ const r = await llm.summarize(content.text, args.context);
68
+ return {
69
+ success: true,
70
+ output: `📝 摘要:\n${r.summary}\n\n质量评分: ${(r.qualityScore * 10).toFixed(1)}/10`
71
+ };
72
+ }
73
+ catch (e) {
74
+ return { success: false, error: String(e) };
75
+ }
76
+ }
77
+ });
78
+ ctx.tools.set('improve_document', {
79
+ name: 'improve_document',
80
+ description: '根据要求改进文档内容',
81
+ parameters: { path: 'string', requirements: 'string' },
82
+ execute: async (args) => {
83
+ try {
84
+ if (!ctx.minimaxAvailable) {
85
+ return { success: false, error: 'LLM未初始化,请设置 MINIMAX_API_KEY' };
86
+ }
87
+ const llm = getMinimax();
88
+ const content = await documentReader.read(args.path);
89
+ const improved = await llm.summarize(content.text + '\n\n改进要求: ' + args.requirements, undefined);
90
+ return {
91
+ success: true,
92
+ output: `✅ 改进完成\n质量评分: ${(improved.qualityScore * 10).toFixed(1)}/10\n${improved.summary ? '\n改进内容:\n' + improved.summary.substring(0, 500) + '...' : ''}`
93
+ };
94
+ }
95
+ catch (e) {
96
+ return { success: false, error: String(e) };
97
+ }
98
+ }
99
+ });
100
+ ctx.tools.set('list_peers', {
101
+ name: 'list_peers',
102
+ description: '列出已连接的对等节点',
103
+ parameters: {},
104
+ execute: async () => {
105
+ const peers = p2pNetwork.getPeers();
106
+ if (peers.length === 0) {
107
+ return { success: true, output: '当前无连接的对等节点' };
108
+ }
109
+ return { success: true, output: `已连接节点 (${peers.length}):\n${peers.map(p => ` - ${p}`).join('\n')}` };
110
+ }
111
+ });
112
+ ctx.tools.set('send_message', {
113
+ name: 'send_message',
114
+ description: '向指定对等节点发送消息',
115
+ parameters: { peer_id: '对等节点ID', message: '消息内容' },
116
+ execute: async (args) => {
117
+ try {
118
+ await p2pNetwork.sendMessage(args.peer_id, 'message', args.message);
119
+ return { success: true, output: `消息已发送到 ${args.peer_id}` };
120
+ }
121
+ catch (e) {
122
+ return { success: false, error: String(e) };
123
+ }
124
+ }
125
+ });
126
+ ctx.tools.set('broadcast_message', {
127
+ name: 'broadcast_message',
128
+ description: '向所有对等节点广播消息',
129
+ parameters: { message: '消息内容' },
130
+ execute: async (args) => {
131
+ try {
132
+ await p2pNetwork.broadcast('message', args.message);
133
+ return { success: true, output: '消息已广播到所有节点' };
134
+ }
135
+ catch (e) {
136
+ return { success: false, error: String(e) };
137
+ }
138
+ }
139
+ });
140
+ // Agent Mesh 工具集
141
+ ctx.tools.set('list_local_channels', {
142
+ name: 'list_local_channels',
143
+ description: '列出当前 PiAgentSession 注册的所有 channel. 每个 channel 是 bolloon 内的智能体会话, 可含 messages[] / peerDid(对端 DID) / peerName. 供 send_to_channel 选 channel_id.',
144
+ parameters: {},
145
+ execute: async () => {
146
+ const channels = ctx.sessionManager ? ctx.sessionManager.getAllChannels?.() || [] : [];
147
+ if (channels.length === 0) {
148
+ return { success: true, output: '📭 当前 session 没有注册任何 channel (用 send_to_channel 留空 channel_id 会自动创建一个)' };
149
+ }
150
+ const lines = channels.map((ch, i) => {
151
+ const peer = ch.peerDid ? ` peer=${ch.peerDid.substring(0, 20)}` : (ch.peerName ? ` peer="${ch.peerName}"` : '');
152
+ const msgCount = ch.messages?.length || 0;
153
+ return ` ${i + 1}. ${ch.id} name="${ch.name}" msgs=${msgCount}${peer}`;
154
+ });
155
+ return { success: true, output: `📬 当前 session 有 ${channels.length} 个 channel:\n${lines.join('\n')}` };
156
+ }
157
+ });
158
+ ctx.tools.set('send_to_channel', {
159
+ name: 'send_to_channel',
160
+ description: '发送消息到指定 channel. channel_id 留空会自动创建一个. 如果 channel 已关联 peerDid, 会同时通过 P2P 转发到对端 agent.',
161
+ parameters: { channel_id: '目标 channel id (留空自动创建)', message: '消息内容 (必填)', peer_did: '可选 创建新 channel 时绑定的对端 DID (e.g. did:key:...)' },
162
+ execute: async (args) => {
163
+ const message = String(args.message || '').trim();
164
+ if (!message)
165
+ return { success: false, error: 'message 必填' };
166
+ let channelId = String(args.channel_id || '').trim();
167
+ const peerDid = args.peer_did ? String(args.peer_did) : undefined;
168
+ if (!channelId) {
169
+ const ch = await ctx.sessionManager.getOrCreatePeerChannel(peerDid || 'auto-created', 'auto-created');
170
+ channelId = ch.id;
171
+ }
172
+ const ch = ctx.sessionManager.getAllChannels().find((c) => c.id === channelId);
173
+ if (ch?.peerDid) {
174
+ try {
175
+ const peerId = ch.peerDid.replace(/^did:key:/, '').replace(/^did:pi:/, '');
176
+ await p2pNetwork.sendMessage(peerId, 'channel-message', JSON.stringify({ channelId, content: message, from: ctx.identity.did, timestamp: new Date().toISOString() }));
177
+ return { success: true, output: `📨 消息已存到 channel ${channelId} + P2P 转发到 ${ch.peerDid.substring(0, 30)}...` };
178
+ }
179
+ catch (e) {
180
+ return { success: true, output: `📨 消息已存到 channel ${channelId} (P2P 转发失败: ${String(e).slice(0, 100)})` };
181
+ }
182
+ }
183
+ return { success: true, output: `📨 消息已存到 channel ${channelId}` };
184
+ }
185
+ });
186
+ ctx.tools.set('check_inbox', {
187
+ name: 'check_inbox',
188
+ description: '检查 P2P + 本地 inbox 收到的所有消息. 跨 channel 视角, 按时间倒序.',
189
+ parameters: { max: '可选, 最多返回 N 条 (默认 50)' },
190
+ execute: async (args) => {
191
+ const max = Number(args.max) || 50;
192
+ if (ctx._inboxMessages.length === 0) {
193
+ return { success: true, output: '📭 当前 inbox 空' };
194
+ }
195
+ const slice = ctx._inboxMessages.slice(-max).reverse();
196
+ const lines = slice.map((m, i) => {
197
+ const fromTxt = m.fromDid ? `${m.from} (${m.fromDid.substring(0, 20)}...)` : m.from;
198
+ return ` ${i + 1}. [${m.type}] from=${fromTxt} time=${new Date(m.timestamp).toISOString()} src=${m.source}\n ${String(m.payload).substring(0, 200)}`;
199
+ });
200
+ return { success: true, output: `📬 inbox 有 ${ctx._inboxMessages.length} 条消息 (最近 ${slice.length}):\n${lines.join('\n')}` };
201
+ }
202
+ });
203
+ ctx.tools.set('send_to_peer', {
204
+ name: 'send_to_peer',
205
+ description: '发送结构化消息到指定 P2P 节点 (远端 bolloon 实例). 对方会通过远端 channel 收到.',
206
+ parameters: { peer_id: '目标 P2P 节点 publicKey (用 list_peers 查)', message: '消息内容 (任意字符串)', type: '可选, 消息类型标签 (默认 agent-message)' },
207
+ execute: async (args) => {
208
+ const peerId = String(args.peer_id || '').trim();
209
+ const msg = String(args.message || '').trim();
210
+ const type = String(args.type || 'agent-message').trim();
211
+ if (!peerId)
212
+ return { success: false, error: 'peer_id 必填' };
213
+ if (!msg)
214
+ return { success: false, error: 'message 必填' };
215
+ try {
216
+ await p2pNetwork.sendMessage(peerId, type, msg);
217
+ return { success: true, output: `✅ 消息已发送到 ${peerId.substring(0, 16)}...` };
218
+ }
219
+ catch (e) {
220
+ return { success: false, error: `发送失败: ${String(e)}` };
221
+ }
222
+ }
223
+ });
224
+ ctx.tools.set('p2p_broadcast', {
225
+ name: 'p2p_broadcast',
226
+ description: '广播消息到所有连接的 P2P 节点',
227
+ parameters: { message: '消息内容', type: '可选, 消息类型标签 (默认 agent-broadcast)' },
228
+ execute: async (args) => {
229
+ const msg = String(args.message || '').trim();
230
+ const type = String(args.type || 'agent-broadcast').trim();
231
+ if (!msg)
232
+ return { success: false, error: 'message 必填' };
233
+ try {
234
+ await p2pNetwork.broadcast(type, msg);
235
+ return { success: true, output: `📡 已广播 type=${type}` };
236
+ }
237
+ catch (e) {
238
+ return { success: false, error: `广播失败: ${String(e)}` };
239
+ }
240
+ }
241
+ });
242
+ ctx.tools.set('agent_call', {
243
+ name: 'agent_call',
244
+ description: 'RPC: 让远端 P2P agent 跑一个任务, 等待结果返回. 远端 agent 会基于 task 描述自主完成, 完成后回复. (RPC 结果回收机制待实现)',
245
+ parameters: { peer_id: '目标 P2P 节点', task: '任务描述 (远端 agent 收到的 prompt)', timeoutMs: '可选 超时 (ms, 默认 30000)' },
246
+ execute: async (args) => {
247
+ const peerId = String(args.peer_id || '').trim();
248
+ const task = String(args.task || '').trim();
249
+ if (!peerId)
250
+ return { success: false, error: 'peer_id 必填' };
251
+ if (!task)
252
+ return { success: false, error: 'task 必填' };
253
+ const requestId = `rpc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
254
+ await p2pNetwork.sendMessage(peerId, 'agent-call', JSON.stringify({ requestId, task, from: ctx.identity.did }));
255
+ return { success: true, output: `📞 RPC 任务已发送给 ${peerId.substring(0, 16)}...\n requestId=${requestId}` };
256
+ }
257
+ });
258
+ ctx.tools.set('get_identity', {
259
+ name: 'get_identity',
260
+ description: '获取当前智能体身份信息',
261
+ parameters: {},
262
+ execute: async () => {
263
+ const id = ctx.identity;
264
+ return {
265
+ success: true,
266
+ output: `DID: ${id.did}\n名称: ${id.name}\n`
267
+ };
268
+ }
269
+ });
270
+ ctx.tools.set('set_persona', {
271
+ name: 'set_persona',
272
+ description: '更新智能体的 persona 信息',
273
+ parameters: { persona_json: 'Persona JSON 对象' },
274
+ execute: async (args) => {
275
+ try {
276
+ const personaData = typeof args.persona_json === 'string' ? JSON.parse(args.persona_json) : args.persona_json;
277
+ const now = new Date().toISOString();
278
+ const newPersona = {
279
+ name: personaData.name || ctx.identity.name,
280
+ description: personaData.description || '',
281
+ capabilities: personaData.capabilities || [],
282
+ personality: personaData.personality || '',
283
+ greeting: personaData.greeting || '',
284
+ interests: personaData.interests || [],
285
+ createdAt: ctx.persona?.createdAt || now,
286
+ updatedAt: now
287
+ };
288
+ await ctx.setPersona(newPersona);
289
+ return { success: true, output: `Persona 已更新:\n名称: ${newPersona.name}\n描述: ${newPersona.description}` };
290
+ }
291
+ catch (e) {
292
+ return { success: false, error: `更新 persona 失败: ${String(e)}` };
293
+ }
294
+ }
295
+ });
296
+ ctx.tools.set('get_operation_logs', {
297
+ name: 'get_operation_logs',
298
+ description: '获取约束层的操作日志',
299
+ parameters: {},
300
+ execute: async () => {
301
+ const logs = ctx.constraintLayer.getLogs();
302
+ if (logs.length === 0) {
303
+ return { success: true, output: '暂无操作日志' };
304
+ }
305
+ return {
306
+ success: true,
307
+ output: `操作日志 (${logs.length} 条):\n${logs.slice(-10).map((l) => `[${new Date(l.timestamp).toISOString()}] ${l.action} - ${l.status}`).join('\n')}`
308
+ };
309
+ }
310
+ });
311
+ // 文件系统工具
312
+ ctx.tools.set('list_files', {
313
+ name: 'list_files',
314
+ description: '列出目录中的文件',
315
+ parameters: { path: '目录路径(可选,默认为当前目录)' },
316
+ execute: async (args) => {
317
+ try {
318
+ const fs = await import('fs');
319
+ const targetPath = args.path || ctx.cwd;
320
+ const files = fs.readdirSync(targetPath);
321
+ return {
322
+ success: true,
323
+ output: `📁 目录 ${targetPath} 中的文件 (${files.length} 个):\n${files.slice(0, 20).map((f) => ` - ${f}`).join('\n')}${files.length > 20 ? '\n ...' : ''}`
324
+ };
325
+ }
326
+ catch (e) {
327
+ return { success: false, error: String(e) };
328
+ }
329
+ }
330
+ });
331
+ ctx.tools.set('read_directory', {
332
+ name: 'read_directory',
333
+ description: '读取目录内容,返回文件列表和目录结构',
334
+ parameters: { path: '目录路径(可选,默认为当前目录)' },
335
+ execute: async (args) => {
336
+ try {
337
+ const fs = await import('fs');
338
+ const pathModule = await import('path');
339
+ const targetPath = args.path || ctx.cwd;
340
+ const items = fs.readdirSync(targetPath);
341
+ const result = [];
342
+ for (const item of items.slice(0, 30)) {
343
+ const fullPath = pathModule.join(targetPath, item);
344
+ try {
345
+ const stat = fs.statSync(fullPath);
346
+ const type = stat.isDirectory() ? '📁' : '📄';
347
+ result.push(`${type} ${item}${stat.isDirectory() ? '/' : ''}`);
348
+ }
349
+ catch {
350
+ result.push(`📄 ${item}`);
351
+ }
352
+ }
353
+ return {
354
+ success: true,
355
+ output: `📂 ${targetPath} (${items.length} 项):\n${result.join('\n')}${items.length > 30 ? '\n... 还有更多文件' : ''}`
356
+ };
357
+ }
358
+ catch (e) {
359
+ return { success: false, error: `无法读取目录: ${String(e)}` };
360
+ }
361
+ }
362
+ });
363
+ // P2P Document Tools
364
+ for (const tool of p2pDocumentTools) {
365
+ ctx.tools.set(tool.name, tool);
366
+ }
367
+ // shell_exec
368
+ ctx.tools.set('shell_exec', {
369
+ name: 'shell_exec',
370
+ description: '在 cwd 跑 shell 命令. 仅支持白名单内命令: git, npm, npx, tsx, tsc, vitest, cat, head, tail, ls, wc, echo, pwd, date, mkdir, touch. 禁止管道/重定向/rm -rf/sudo. 命中护栏黑名单会被拒.',
371
+ parameters: { command: '可执行文件 (必填, 必须在白名单)', args: '参数数组, 逗号分隔', timeoutMs: '超时毫秒, 默认 30000' },
372
+ execute: async (args) => {
373
+ const cmd = String(args.command || '').trim();
374
+ if (!cmd)
375
+ return { success: false, error: 'command 必填' };
376
+ let argList = [];
377
+ const rawArgs = args.args;
378
+ if (Array.isArray(rawArgs)) {
379
+ argList = rawArgs.map((s) => String(s).trim()).filter(Boolean);
380
+ }
381
+ else if (typeof rawArgs === 'string') {
382
+ const trimmed = rawArgs.trim();
383
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
384
+ try {
385
+ const parsed = JSON.parse(trimmed);
386
+ if (Array.isArray(parsed)) {
387
+ argList = parsed.map((s) => String(s).trim()).filter(Boolean);
388
+ }
389
+ }
390
+ catch { /* fall through to comma split */ }
391
+ }
392
+ if (argList.length === 0) {
393
+ argList = trimmed.split(',').map(s => s.trim()).filter(Boolean);
394
+ }
395
+ }
396
+ const timeoutMs = Number(args.timeoutMs) || 30000;
397
+ const result = await shellExec(cmd, argList, { timeoutMs });
398
+ if (result.deniedByGuard) {
399
+ return { success: false, error: result.error };
400
+ }
401
+ if (!result.success) {
402
+ return { success: false, error: result.error, output: result.output };
403
+ }
404
+ return { success: true, output: result.output };
405
+ }
406
+ });
407
+ // self_improve
408
+ ctx.tools.set('self_improve', {
409
+ name: 'self_improve',
410
+ description: '触发自我改进循环. AI 会在沙箱分支上工作, 跑 tsc + vitest 验证, 通过后输出分支名给用户审.',
411
+ parameters: { goal: '本轮改进目标 (1 句话)' },
412
+ execute: async (args) => {
413
+ const goal = String(args.goal || '').trim();
414
+ if (!goal)
415
+ return { success: false, error: 'goal 必填' };
416
+ return await runSelfImproveLoop(goal);
417
+ }
418
+ });
419
+ // list_skills / use_skill — 走 skillRegistry
420
+ // (由 registerBuiltinTools 调用方单独注册, 因为依赖 skillRegistry 实例)
421
+ // write_file / edit_file / git_*
422
+ ctx.tools.set('write_file', {
423
+ name: 'write_file',
424
+ description: '写入一个文件. 路径必须在白名单. 大文件 (> 100KB) 会被拒. 命中护栏黑名单会拒.',
425
+ parameters: { path: '相对路径 (必填, 相对 cwd)', content: '文件内容 (必填)' },
426
+ execute: async (args) => {
427
+ const relPath = String(args.path || '').trim();
428
+ const content = String(args.content ?? '');
429
+ if (!relPath)
430
+ return { success: false, error: 'path 必填' };
431
+ if (content.length > 100_000)
432
+ return { success: false, error: `内容过大 (${content.length} > 100000 字节), 请分块写` };
433
+ const pathResult = checkWritePath(relPath);
434
+ if (!pathResult.allowed) {
435
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
436
+ }
437
+ try {
438
+ const absPath = path.resolve(ctx.cwd, relPath);
439
+ await fs.mkdir(path.dirname(absPath), { recursive: true });
440
+ await fs.writeFile(absPath, content, 'utf-8');
441
+ return { success: true, output: `✅ wrote ${relPath} (${content.length} bytes)` };
442
+ }
443
+ catch (e) {
444
+ return { success: false, error: `写文件失败: ${String(e)}` };
445
+ }
446
+ }
447
+ });
448
+ ctx.tools.set('edit_file', {
449
+ name: 'edit_file',
450
+ description: '编辑一个文件: 在 path 处查找 old_text, 替换为 new_text. 找不到 old_text 会失败.',
451
+ parameters: { path: '相对路径 (必填)', old_text: '要替换的文本 (必填, 全文匹配)', new_text: '新文本 (必填)' },
452
+ execute: async (args) => {
453
+ const relPath = String(args.path || '').trim();
454
+ const oldText = String(args.old_text ?? '');
455
+ const newText = String(args.new_text ?? '');
456
+ if (!relPath)
457
+ return { success: false, error: 'path 必填' };
458
+ if (!oldText)
459
+ return { success: false, error: 'old_text 必填' };
460
+ const pathResult = checkWritePath(relPath);
461
+ if (!pathResult.allowed) {
462
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
463
+ }
464
+ try {
465
+ const absPath = path.resolve(ctx.cwd, relPath);
466
+ const original = await fs.readFile(absPath, 'utf-8');
467
+ if (!original.includes(oldText)) {
468
+ return { success: false, error: `old_text 在 ${relPath} 中未找到, 拒绝静默写入. 请先用 read_document 读最新内容.` };
469
+ }
470
+ const updated = original.replace(oldText, newText);
471
+ await fs.writeFile(absPath, updated, 'utf-8');
472
+ return { success: true, output: `✅ edited ${relPath} (${oldText.length} → ${newText.length} 字节)` };
473
+ }
474
+ catch (e) {
475
+ return { success: false, error: `编辑文件失败: ${String(e)}` };
476
+ }
477
+ }
478
+ });
479
+ ctx.tools.set('git_diff', {
480
+ name: 'git_diff',
481
+ description: '查看 git diff. 默认显示未提交改动 (staged + unstaged), 可指定 ref1..ref2 看两个 commit/分支之间的 diff. 输出会截到 8000 字符避免超长.',
482
+ parameters: { range: '可选. e.g. "HEAD~3..HEAD". 省略则看未提交改动.' },
483
+ execute: async (args) => {
484
+ const range = String(args.range || '').trim();
485
+ const argv = range ? ['diff', range] : ['diff'];
486
+ const result = await shellExec('git', argv, { timeoutMs: 10_000 });
487
+ if (result.deniedByGuard)
488
+ return { success: false, error: result.error };
489
+ if (!result.success)
490
+ return { success: false, error: result.error, output: result.output };
491
+ const out = (result.output || '').slice(0, 8000);
492
+ return { success: true, output: out || '(空 diff — 没有未提交改动)' };
493
+ }
494
+ });
495
+ ctx.tools.set('git_commit', {
496
+ name: 'git_commit',
497
+ description: 'git add -A + git commit. 提交信息由 LLM 提供. 不会 push.',
498
+ parameters: { message: 'commit message (必填)' },
499
+ execute: async (args) => {
500
+ const message = String(args.message || '').trim();
501
+ if (!message)
502
+ return { success: false, error: 'message 必填' };
503
+ const addResult = await shellExec('git', ['add', '-A'], { timeoutMs: 10_000 });
504
+ if (addResult.deniedByGuard)
505
+ return { success: false, error: addResult.error };
506
+ if (!addResult.success)
507
+ return { success: false, error: `git add 失败: ${addResult.error}` };
508
+ try {
509
+ const { spawn: spawnFn } = await import('child_process');
510
+ const env = { ...process.env, BOLLOON_AUTO_EVOLVE: '1' };
511
+ const output = await new Promise((resolve, reject) => {
512
+ const proc = spawnFn('git', ['commit', '-m', message], {
513
+ cwd: ctx.cwd, env, stdio: ['ignore', 'pipe', 'pipe'],
514
+ });
515
+ let stdout = '';
516
+ let stderr = '';
517
+ proc.stdout.on('data', (d) => stdout += d.toString());
518
+ proc.stderr.on('data', (d) => stderr += d.toString());
519
+ proc.on('close', (code) => code === 0 ? resolve(stdout) : reject(new Error(stderr || `git commit exited ${code}`)));
520
+ proc.on('error', reject);
521
+ });
522
+ return { success: true, output: `✅ committed: ${message.split('\n')[0]}\n${output}` };
523
+ }
524
+ catch (e) {
525
+ return { success: false, error: `git commit 失败: ${String(e.message || e).slice(0, 500)}` };
526
+ }
527
+ }
528
+ });
529
+ ctx.tools.set('git_push', {
530
+ name: 'git_push',
531
+ description: 'git push 当前分支到 origin. 命中护栏 (push to master/main, --force) 仍会被拒.',
532
+ parameters: { remote: '可选, 默认 origin', branch: '可选, 默认当前分支' },
533
+ execute: async (args) => {
534
+ const remote = String(args.remote || 'origin').trim();
535
+ const branch = String(args.branch || '').trim();
536
+ const argv = branch ? ['push', remote, branch] : ['push', remote];
537
+ const result = await shellExec('git', argv, { timeoutMs: 60_000 });
538
+ if (result.deniedByGuard)
539
+ return { success: false, error: result.error };
540
+ if (!result.success)
541
+ return { success: false, error: result.error, output: result.output };
542
+ return { success: true, output: `✅ pushed to ${remote}${branch ? `/${branch}` : ''}\n${result.output || ''}` };
543
+ }
544
+ });
545
+ ctx.tools.set('git_branch', {
546
+ name: 'git_branch',
547
+ description: 'git checkout -b <name> 或 git checkout <name>.',
548
+ parameters: { name: '分支名 (必填)', create: '可选, "true" 表示创建新分支' },
549
+ execute: async (args) => {
550
+ const name = String(args.name || '').trim();
551
+ const create = String(args.create || 'false') === 'true';
552
+ if (!name)
553
+ return { success: false, error: 'name 必填' };
554
+ const argv = create ? ['checkout', '-b', name] : ['checkout', name];
555
+ const result = await shellExec('git', argv, { timeoutMs: 10_000 });
556
+ if (result.deniedByGuard)
557
+ return { success: false, error: result.error };
558
+ if (!result.success)
559
+ return { success: false, error: result.error, output: result.output };
560
+ return { success: true, output: `✅ ${create ? 'created + checked out' : 'checked out'} ${name}\n${result.output || ''}` };
561
+ }
562
+ });
563
+ // 通用文件读取 (M4)
564
+ ctx.tools.set('read_file', {
565
+ name: 'read_file',
566
+ description: '读取任意文件内容 (相对 cwd). 受 shell-guard 路径白名单保护.',
567
+ parameters: { path: '相对路径 (必填)', startLine: '起始行号 (可选, 默认 0)', maxLines: '最大行数 (可选, 默认 500)' },
568
+ execute: async (args) => {
569
+ const relPath = String(args.path || '').trim();
570
+ if (!relPath)
571
+ return { success: false, error: 'path 必填' };
572
+ const pathResult = checkWritePath(relPath);
573
+ if (!pathResult.allowed)
574
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
575
+ try {
576
+ const absPath = path.resolve(ctx.cwd, relPath);
577
+ const content = fsSync.readFileSync(absPath, 'utf-8');
578
+ const start = Math.max(0, parseInt(String(args.startLine || '0')) || 0);
579
+ const max = parseInt(String(args.maxLines || '500')) || 500;
580
+ const lines = content.split('\n');
581
+ const slice = lines.slice(start, start + max);
582
+ return { success: true, output: `📄 ${relPath} (第 ${start + 1}-${start + slice.length} 行, 共 ${lines.length} 行):\n${slice.map((l, i) => `${String(start + i + 1).padStart(4)} | ${l}`).join('\n')}` };
583
+ }
584
+ catch (e) {
585
+ return { success: false, error: `读取失败: ${e.message}` };
586
+ }
587
+ }
588
+ });
589
+ ctx.tools.set('delete_file', {
590
+ name: 'delete_file',
591
+ description: '删除一个文件. 受 shell-guard 路径白名单保护. 不可恢复, 调用前请确认.',
592
+ parameters: { path: '相对路径 (必填)' },
593
+ execute: async (args) => {
594
+ const relPath = String(args.path || '').trim();
595
+ if (!relPath)
596
+ return { success: false, error: 'path 必填' };
597
+ const pathResult = checkWritePath(relPath);
598
+ if (!pathResult.allowed)
599
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
600
+ try {
601
+ const absPath = path.resolve(ctx.cwd, relPath);
602
+ if (!fsSync.existsSync(absPath))
603
+ return { success: false, error: `文件不存在: ${relPath}` };
604
+ fsSync.unlinkSync(absPath);
605
+ return { success: true, output: `✅ deleted ${relPath}` };
606
+ }
607
+ catch (e) {
608
+ return { success: false, error: `删除失败: ${e.message}` };
609
+ }
610
+ }
611
+ });
612
+ ctx.tools.set('mkdir', {
613
+ name: 'mkdir',
614
+ description: '创建一个或多个目录. 自动 mkdir -p.',
615
+ parameters: { path: '目录路径 (必填, 相对 cwd)' },
616
+ execute: async (args) => {
617
+ const relPath = String(args.path || '').trim();
618
+ if (!relPath)
619
+ return { success: false, error: 'path 必填' };
620
+ const pathResult = checkWritePath(relPath);
621
+ if (!pathResult.allowed)
622
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
623
+ try {
624
+ const absPath = path.resolve(ctx.cwd, relPath);
625
+ fsSync.mkdirSync(absPath, { recursive: true });
626
+ return { success: true, output: `✅ mkdir ${relPath}` };
627
+ }
628
+ catch (e) {
629
+ return { success: false, error: `创建失败: ${e.message}` };
630
+ }
631
+ }
632
+ });
633
+ ctx.tools.set('move_file', {
634
+ name: 'move_file',
635
+ description: '移动或重命名文件. 源和目标路径都在白名单内才允许.',
636
+ parameters: { from: '源路径 (必填)', to: '目标路径 (必填)' },
637
+ execute: async (args) => {
638
+ const from = String(args.from || '').trim();
639
+ const to = String(args.to || '').trim();
640
+ if (!from || !to)
641
+ return { success: false, error: 'from 和 to 都必填' };
642
+ const fromCheck = checkWritePath(from);
643
+ if (!fromCheck.allowed)
644
+ return { success: false, error: `from 路径被护栏拒: ${fromCheck.reason}` };
645
+ const toCheck = checkWritePath(to);
646
+ if (!toCheck.allowed)
647
+ return { success: false, error: `to 路径被护栏拒: ${toCheck.reason}` };
648
+ try {
649
+ const fromAbs = path.resolve(ctx.cwd, from);
650
+ const toAbs = path.resolve(ctx.cwd, to);
651
+ if (!fsSync.existsSync(fromAbs))
652
+ return { success: false, error: `源文件不存在: ${from}` };
653
+ fsSync.mkdirSync(path.dirname(toAbs), { recursive: true });
654
+ fsSync.renameSync(fromAbs, toAbs);
655
+ return { success: true, output: `✅ ${from} → ${to}` };
656
+ }
657
+ catch (e) {
658
+ return { success: false, error: `移动失败: ${e.message}` };
659
+ }
660
+ }
661
+ });
662
+ ctx.tools.set('grep_files', {
663
+ name: 'grep_files',
664
+ description: '在文件中搜索匹配 pattern 的行. 类似 grep -rn. 路径必须在白名单.',
665
+ parameters: { pattern: '搜索 pattern (必填, 字符串, 不是正则)', path: '搜索目录 (可选, 默认 .)', filePattern: '文件名 glob (可选)' },
666
+ execute: async (args) => {
667
+ const pattern = String(args.pattern || '').trim();
668
+ if (!pattern)
669
+ return { success: false, error: 'pattern 必填' };
670
+ const searchPath = String(args.path || '.').trim();
671
+ const pathResult = checkWritePath(searchPath);
672
+ if (!pathResult.allowed)
673
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
674
+ try {
675
+ const { execFile } = await import('child_process');
676
+ const { promisify } = await import('util');
677
+ const pExecFile = promisify(execFile);
678
+ const argv = ['-rn', '--include=' + (args.filePattern || '*'), pattern, searchPath];
679
+ const { stdout, stderr } = await pExecFile('grep', argv, { cwd: ctx.cwd, maxBuffer: 1024 * 1024 });
680
+ const lines = stdout.split('\n').filter(Boolean).slice(0, 50);
681
+ return { success: true, output: `🔍 grep "${pattern}" in ${searchPath} (${args.filePattern || '*'}, 最多 50 行):\n${lines.join('\n')}${lines.length === 50 ? '\n... (truncated)' : ''}` };
682
+ }
683
+ catch (e) {
684
+ if (e.code === 1)
685
+ return { success: true, output: `🔍 grep "${pattern}" in ${searchPath}: 0 matches` };
686
+ return { success: false, error: `grep 失败: ${e.message}` };
687
+ }
688
+ }
689
+ });
690
+ ctx.tools.set('glob_files', {
691
+ name: 'glob_files',
692
+ description: '用 glob pattern 找文件. 例如 "**/*.test.ts".',
693
+ parameters: { pattern: 'glob pattern (必填, e.g. "src/**/*.ts")' },
694
+ execute: async (args) => {
695
+ const pattern = String(args.pattern || '').trim();
696
+ if (!pattern)
697
+ return { success: false, error: 'pattern 必填' };
698
+ try {
699
+ const pathResult = checkWritePath(pattern.replace(/\*\*.*$/, '').replace(/\/\*.*$/, '') || '.');
700
+ if (!pathResult.allowed && pattern !== '**/*' && pattern !== '*')
701
+ return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
702
+ const { execFile } = await import('child_process');
703
+ const { promisify } = await import('util');
704
+ const pExecFile = promisify(execFile);
705
+ const { stdout } = await pExecFile('find', [ctx.cwd, '-path', `*${pattern.replace(/\*\*\//, '*').replace(/\*\*$/, '*').replace(/\*$/, '*')}`, '-type', 'f'], { maxBuffer: 1024 * 1024 });
706
+ const files = stdout.split('\n').filter(Boolean).slice(0, 100);
707
+ const relFiles = files.map((f) => path.relative(ctx.cwd, f));
708
+ return { success: true, output: `🔍 glob "${pattern}" 找到 ${relFiles.length} 个文件${relFiles.length === 100 ? ' (truncated)' : ''}:\n${relFiles.join('\n')}` };
709
+ }
710
+ catch (e) {
711
+ return { success: false, error: `glob 失败: ${e.message}` };
712
+ }
713
+ }
714
+ });
715
+ ctx.tools.set('git_log', {
716
+ name: 'git_log',
717
+ description: '查看 git log. 默认 --oneline -10. 支持过滤和范围.',
718
+ parameters: { range: '可选, e.g. "HEAD~5..HEAD" 或分支名', maxCount: '可选, 默认 10', oneline: '可选, "true" 用 --oneline (默认 true)' },
719
+ execute: async (args) => {
720
+ const range = String(args.range || '').trim();
721
+ const maxCount = parseInt(String(args.maxCount || '10')) || 10;
722
+ const oneline = String(args.oneline || 'true') === 'true';
723
+ const argv = ['log'];
724
+ if (oneline)
725
+ argv.push('--oneline');
726
+ if (maxCount > 0)
727
+ argv.push(`-n`, String(maxCount));
728
+ if (range)
729
+ argv.push(range);
730
+ const result = await shellExec('git', argv, { timeoutMs: 10_000 });
731
+ if (result.deniedByGuard)
732
+ return { success: false, error: result.error };
733
+ if (!result.success)
734
+ return { success: false, error: result.error, output: result.output };
735
+ return { success: true, output: `📜 git log:\n${result.output || '(empty)'}` };
736
+ }
737
+ });
738
+ ctx.tools.set('git_show', {
739
+ name: 'git_show',
740
+ description: '查看 commit 内容. 默认 HEAD. 支持 --stat 看统计, --patch 看 diff.',
741
+ parameters: { ref: '可选, 默认 HEAD', stat: '可选, "true" 只看 stat (默认 false)' },
742
+ execute: async (args) => {
743
+ const ref = String(args.ref || 'HEAD').trim();
744
+ const argv = ['show', ref];
745
+ if (String(args.stat || 'false') === 'true')
746
+ argv.push('--stat');
747
+ const result = await shellExec('git', argv, { timeoutMs: 15_000 });
748
+ if (result.deniedByGuard)
749
+ return { success: false, error: result.error };
750
+ if (!result.success)
751
+ return { success: false, error: result.error, output: result.output };
752
+ return { success: true, output: `📜 git show ${ref}:\n${result.output || '(empty)'}` };
753
+ }
754
+ });
755
+ ctx.tools.set('git_stash', {
756
+ name: 'git_stash',
757
+ description: 'git stash 暂存当前未提交改动. action: save/pop/list/apply/drop. 支持 message.',
758
+ parameters: { action: '动作 (必填)', message: '可选, save 时的描述', index: '可选, apply/pop/drop 的 stash index' },
759
+ execute: async (args) => {
760
+ const action = String(args.action || '').trim();
761
+ if (!['save', 'pop', 'list', 'apply', 'drop'].includes(action)) {
762
+ return { success: false, error: `action 必须是 save/pop/list/apply/drop 之一, 收到: ${action}` };
763
+ }
764
+ const argv = ['stash', action];
765
+ if (action === 'save' && args.message)
766
+ argv.push('-m', String(args.message));
767
+ if ((action === 'apply' || action === 'drop' || action === 'pop') && args.index) {
768
+ argv.push('stash@{' + String(args.index) + '}');
769
+ }
770
+ const result = await shellExec('git', argv, { timeoutMs: 10_000 });
771
+ if (result.deniedByGuard)
772
+ return { success: false, error: result.error };
773
+ if (!result.success)
774
+ return { success: false, error: result.error, output: result.output };
775
+ return { success: true, output: `✅ git stash ${action}\n${result.output || ''}` };
776
+ }
777
+ });
778
+ // 测试 & 类型检查
779
+ ctx.tools.set('vitest_run', {
780
+ name: 'vitest_run',
781
+ description: '跑 vitest 测试. 自动 bail (失败就停). 默认 60s timeout.',
782
+ parameters: { pattern: '可选, 文件 glob', timeoutMs: '可选, 默认 60000' },
783
+ execute: async (args) => {
784
+ const argv = ['vitest', 'run', '--reporter=default', '--no-color', '--bail=1'];
785
+ if (args.pattern)
786
+ argv.push(String(args.pattern));
787
+ const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
788
+ const result = await shellExec('npx', argv, { timeoutMs });
789
+ if (result.deniedByGuard)
790
+ return { success: false, error: result.error };
791
+ if (!result.success)
792
+ return { success: false, error: result.error || 'vitest 失败', output: result.output };
793
+ return { success: true, output: `✅ vitest 通过:\n${(result.output || '').slice(0, 2000)}` };
794
+ }
795
+ });
796
+ ctx.tools.set('tsc_check', {
797
+ name: 'tsc_check',
798
+ description: '跑 tsc --noEmit 检查 TypeScript 编译. 默认 60s timeout.',
799
+ parameters: { project: '可选, tsconfig 路径 (默认 tsconfig.json)', timeoutMs: '可选, 默认 60000' },
800
+ execute: async (args) => {
801
+ const argv = ['tsc', '--noEmit'];
802
+ if (args.project)
803
+ argv.push('-p', String(args.project));
804
+ else
805
+ argv.push('-p', 'tsconfig.json');
806
+ const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
807
+ const result = await shellExec('npx', argv, { timeoutMs });
808
+ if (result.deniedByGuard)
809
+ return { success: false, error: result.error };
810
+ if (!result.success)
811
+ return { success: false, error: result.error || 'tsc 失败', output: result.output };
812
+ return { success: true, output: `✅ tsc 通过:\n${(result.output || 'no errors').slice(0, 1000)}` };
813
+ }
814
+ });
815
+ // Task 工具
816
+ ctx.tools.set('create_task', {
817
+ name: 'create_task',
818
+ description: '创建一个新多步任务, 初始 steps 列表由 LLM 给出.',
819
+ parameters: { goal: '任务目标 (必填)', steps: '步骤列表 (必填)', sessionKey: '可选', branch: '可选' },
820
+ execute: async (args) => {
821
+ const goal = String(args.goal || '').trim();
822
+ const stepsRaw = args.steps;
823
+ if (!goal)
824
+ return { success: false, error: 'goal 必填' };
825
+ let steps = [];
826
+ if (Array.isArray(stepsRaw)) {
827
+ steps = stepsRaw.map((s) => String(s).trim()).filter(Boolean);
828
+ }
829
+ else if (typeof stepsRaw === 'string') {
830
+ steps = stepsRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean);
831
+ }
832
+ if (steps.length === 0)
833
+ return { success: false, error: 'steps 必填且至少 1 步' };
834
+ const sessionKey = String(args.sessionKey || '').trim() || undefined;
835
+ const branch = String(args.branch || '').trim() || undefined;
836
+ try {
837
+ const { createTask } = await import('./task-state.js');
838
+ const task = await createTask({ goal, steps, sessionKey, branch });
839
+ return { success: true, output: `✅ task created: ${task.id}\nbranch: ${branch || '(未指定)'}\nsteps:\n${steps.map((s, i) => ` ${i + 1}. ${s}`).join('\n')}` };
840
+ }
841
+ catch (e) {
842
+ return { success: false, error: `create_task 失败: ${String(e)}` };
843
+ }
844
+ }
845
+ });
846
+ ctx.tools.set('update_task', {
847
+ name: 'update_task',
848
+ description: '更新任务的某一步状态.',
849
+ parameters: { task_id: '任务 id', step_id: '步骤 id', status: '新状态', result_summary: '可选', error: '可选' },
850
+ execute: async (args) => {
851
+ const taskId = String(args.task_id || '').trim();
852
+ const stepId = String(args.step_id || '').trim();
853
+ const status = String(args.status || '').trim();
854
+ if (!taskId || !stepId)
855
+ return { success: false, error: 'task_id + step_id 必填' };
856
+ if (!['running', 'done', 'failed', 'skipped'].includes(status)) {
857
+ return { success: false, error: `status 必须是 running|done|failed|skipped` };
858
+ }
859
+ try {
860
+ const { updateStep } = await import('./task-state.js');
861
+ const patch = { status };
862
+ if (args.result_summary)
863
+ patch.resultSummary = String(args.result_summary);
864
+ if (args.error)
865
+ patch.error = String(args.error);
866
+ const updated = await updateStep(taskId, stepId, patch);
867
+ if (!updated)
868
+ return { success: false, error: `任务 ${taskId} 未找到` };
869
+ const nextRunning = updated.steps.find((s) => s.status === 'running');
870
+ return {
871
+ success: true,
872
+ output: `✅ step ${stepId} → ${status}\ntask 状态: ${updated.status}${nextRunning ? `\n下一步: ${nextRunning.id} — ${nextRunning.description}` : ''}`,
873
+ };
874
+ }
875
+ catch (e) {
876
+ return { success: false, error: `update_task 失败: ${String(e)}` };
877
+ }
878
+ }
879
+ });
880
+ ctx.tools.set('get_task', {
881
+ name: 'get_task',
882
+ description: '查任务的当前状态和步骤进度.',
883
+ parameters: { task_id: '任务 id (必填)' },
884
+ execute: async (args) => {
885
+ const taskId = String(args.task_id || '').trim();
886
+ if (!taskId)
887
+ return { success: false, error: 'task_id 必填' };
888
+ try {
889
+ const { getTask } = await import('./task-state.js');
890
+ const t = await getTask(taskId);
891
+ if (!t)
892
+ return { success: false, error: `任务 ${taskId} 未找到` };
893
+ const lines = [
894
+ `任务: ${t.id}`,
895
+ `目标: ${t.goal}`,
896
+ `状态: ${t.status}`,
897
+ `branch: ${t.branch || '(未指定)'}`,
898
+ `sessionKey: ${t.sessionKey || '(未指定)'}`,
899
+ `创建: ${t.createdAt}`,
900
+ `更新: ${t.updatedAt}`,
901
+ ``,
902
+ `步骤:`,
903
+ ...t.steps.map((s) => ` ${s.status === 'done' ? '✅' : s.status === 'running' ? '🔄' : s.status === 'failed' ? '❌' : s.status === 'skipped' ? '⏭️' : '⏳'} ${s.id} — ${s.description}${s.resultSummary ? `\n 结果: ${s.resultSummary}` : ''}${s.error ? `\n 错误: ${s.error}` : ''}`),
904
+ ];
905
+ return { success: true, output: lines.join('\n') };
906
+ }
907
+ catch (e) {
908
+ return { success: false, error: `get_task 失败: ${String(e)}` };
909
+ }
910
+ }
911
+ });
912
+ ctx.tools.set('list_tasks', {
913
+ name: 'list_tasks',
914
+ description: '列出最近 N 个任务 (默认 10).',
915
+ parameters: { limit: '可选, 默认 10' },
916
+ execute: async (args) => {
917
+ const limit = Number(args.limit) || 10;
918
+ try {
919
+ const { listTasks } = await import('./task-state.js');
920
+ const tasks = await listTasks(limit);
921
+ if (tasks.length === 0) {
922
+ return { success: true, output: '当前没有任务. 用 create_task 创建一个.' };
923
+ }
924
+ const lines = tasks.map((t) => {
925
+ const done = t.steps.filter((s) => s.status === 'done').length;
926
+ const total = t.steps.length;
927
+ return `${t.status === 'completed' ? '✅' : t.status === 'failed' ? '❌' : '🔄'} ${t.id} — ${t.goal} (${done}/${total} steps, branch: ${t.branch || '-'})`;
928
+ });
929
+ return { success: true, output: `最近 ${tasks.length} 个任务:\n${lines.join('\n')}` };
930
+ }
931
+ catch (e) {
932
+ return { success: false, error: `list_tasks 失败: ${String(e)}` };
933
+ }
934
+ }
935
+ });
936
+ }
937
+ /**
938
+ * 注册 Wallet + Polymarket + Safe 工具 (基于 constraint-runtime/src/tools/).
939
+ */
940
+ export function registerWalletTools(ctx) {
941
+ ctx.tools.set('wallet_create', {
942
+ name: 'wallet_create',
943
+ description: '创建新 EVM 钱包 (BIP-39 12 词助记词 + 私钥 + 地址).',
944
+ parameters: {},
945
+ execute: async () => {
946
+ try {
947
+ const { createWallet } = await import('../constraint-runtime/dist/tools/WalletTools/createWallet.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/createWallet.js'));
948
+ const r = await createWallet();
949
+ return { success: true, output: `✅ 钱包创建成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n mnemonic: ${r.mnemonic}\n createdAt: ${r.createdAt}` };
950
+ }
951
+ catch (e) {
952
+ return { success: false, error: `创建失败: ${String(e.message || e)}` };
953
+ }
954
+ }
955
+ });
956
+ ctx.tools.set('wallet_import', {
957
+ name: 'wallet_import',
958
+ description: '导入已有 EVM 钱包 (用助记词或私钥)',
959
+ parameters: { mnemonic: '可选, 12/15/18/21/24 词助记词', privateKey: '可选, 0x 开头的私钥' },
960
+ execute: async (args) => {
961
+ try {
962
+ const { importWallet } = await import('../constraint-runtime/dist/tools/WalletTools/importWallet.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/importWallet.js'));
963
+ const r = await importWallet({ mnemonic: args.mnemonic, privateKey: args.privateKey });
964
+ return { success: true, output: `✅ 钱包导入成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n source: ${r.source}` };
965
+ }
966
+ catch (e) {
967
+ return { success: false, error: `导入失败: ${String(e.message || e)}` };
968
+ }
969
+ }
970
+ });
971
+ ctx.tools.set('wallet_get_balance', {
972
+ name: 'wallet_get_balance',
973
+ description: '查 EVM 钱包 ETH 余额.',
974
+ parameters: { address: '0x 开头的 EVM 地址 (必填)', rpcUrl: '可选 RPC URL (默认 eth.llamarpc.com)' },
975
+ execute: async (args) => {
976
+ try {
977
+ const { getBalance } = await import('../constraint-runtime/dist/tools/WalletTools/getBalance.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/getBalance.js'));
978
+ const r = await getBalance({ address: String(args.address), rpcUrl: args.rpcUrl });
979
+ return { success: true, output: `💰 ${r.address}\n ${r.balanceEth} ${r.symbol} (${r.balance} wei)` };
980
+ }
981
+ catch (e) {
982
+ return { success: false, error: `查余额失败: ${String(e.message || e)}` };
983
+ }
984
+ }
985
+ });
986
+ ctx.tools.set('wallet_sign_message', {
987
+ name: 'wallet_sign_message',
988
+ description: '用私钥对消息做 EIP-191 personal_sign 签名',
989
+ parameters: { message: '要签名的消息 (必填)', privateKey: '0x 开头的私钥 (必填)' },
990
+ execute: async (args) => {
991
+ try {
992
+ const { signMessage } = await import('../constraint-runtime/dist/tools/WalletTools/signMessage.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/signMessage.js'));
993
+ const r = await signMessage({ message: String(args.message), privateKey: String(args.privateKey) });
994
+ return { success: true, output: `✅ 签名完成:\n address: ${r.address}\n message: ${r.message}\n signature: ${r.signature}` };
995
+ }
996
+ catch (e) {
997
+ return { success: false, error: `签名失败: ${String(e.message || e)}` };
998
+ }
999
+ }
1000
+ });
1001
+ ctx.tools.set('wallet_send_tx', {
1002
+ name: 'wallet_send_tx',
1003
+ description: '用 EVM 钱包发送交易 (转账 ETH 或调用合约).',
1004
+ parameters: { privateKey: '私钥 (必填)', to: '接收地址 (必填)', value: '发送 wei 数量 (必填)', data: '可选 0x 开头的 calldata', rpcUrl: '可选 RPC URL' },
1005
+ execute: async (args) => {
1006
+ try {
1007
+ const { sendTransaction } = await import('../constraint-runtime/dist/tools/WalletTools/sendTransaction.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/sendTransaction.js'));
1008
+ const r = await sendTransaction({
1009
+ privateKey: String(args.privateKey),
1010
+ to: String(args.to),
1011
+ value: String(args.value),
1012
+ data: args.data ? String(args.data) : undefined,
1013
+ rpcUrl: args.rpcUrl,
1014
+ });
1015
+ return { success: true, output: `✅ 交易已发送:\n hash: ${r.hash}\n from: ${r.from} → to: ${r.to}\n value: ${r.value} wei` };
1016
+ }
1017
+ catch (e) {
1018
+ return { success: false, error: `交易失败: ${String(e.message || e)}` };
1019
+ }
1020
+ }
1021
+ });
1022
+ ctx.tools.set('wallet_transfer_token', {
1023
+ name: 'wallet_transfer_token',
1024
+ description: '用 EVM 钱包转 ERC20 token.',
1025
+ parameters: { privateKey: '私钥 (必填)', tokenAddress: 'ERC20 合约地址 (必填)', to: '接收地址 (必填)', amount: 'token 数量', decimals: '可选 token decimals', rpcUrl: '可选 RPC URL' },
1026
+ execute: async (args) => {
1027
+ try {
1028
+ const { transferToken } = await import('../constraint-runtime/dist/tools/WalletTools/transferToken.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/transferToken.js'));
1029
+ const r = await transferToken({
1030
+ privateKey: String(args.privateKey),
1031
+ tokenAddress: String(args.tokenAddress),
1032
+ to: String(args.to),
1033
+ amount: String(args.amount),
1034
+ decimals: args.decimals ? Number(args.decimals) : undefined,
1035
+ rpcUrl: args.rpcUrl,
1036
+ });
1037
+ return { success: true, output: `✅ Token 转账完成:\n hash: ${r.hash}\n ${r.amount} ${r.tokenAddress.substring(0, 10)}...` };
1038
+ }
1039
+ catch (e) {
1040
+ return { success: false, error: `转账失败: ${String(e.message || e)}` };
1041
+ }
1042
+ }
1043
+ });
1044
+ ctx.tools.set('wallet_autopay', {
1045
+ name: 'wallet_autopay',
1046
+ description: '设置自动付款 (订阅/定期扣款, 用 SafeSDK 自动执行).',
1047
+ parameters: { from: '付款方地址 (必填)', to: '收款方地址 (必填)', amount: '金额 (必填)', interval: '周期 (e.g. daily/weekly/monthly)', token: '可选, 默认 ETH' },
1048
+ execute: async (args) => {
1049
+ try {
1050
+ const mod = await import('../constraint-runtime/dist/tools/WalletTools/autoPay.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/autoPay.js'));
1051
+ const fn = mod.autoPay || mod.setAutoPay || mod.default;
1052
+ if (!fn)
1053
+ return { success: false, error: 'autoPay 接口未找到' };
1054
+ const r = await fn({
1055
+ from: String(args.from), to: String(args.to), amount: String(args.amount),
1056
+ interval: String(args.interval || 'monthly'), token: args.token,
1057
+ });
1058
+ return { success: true, output: `✅ 自动付款已设置: ${JSON.stringify(r)}` };
1059
+ }
1060
+ catch (e) {
1061
+ return { success: false, error: `设置失败: ${String(e.message || e)}` };
1062
+ }
1063
+ }
1064
+ });
1065
+ // Polymarket
1066
+ ctx.tools.set('polymarket_list_markets', {
1067
+ name: 'polymarket_list_markets',
1068
+ description: '列出 Polymarket 预测市场.',
1069
+ parameters: { limit: '可选 数量 (默认 50)', offset: '可选 偏移', closed: '可选 是否只显示已关闭' },
1070
+ execute: async (args) => {
1071
+ try {
1072
+ const { listMarkets } = await import('../constraint-runtime/dist/tools/PolymarketSDK/listMarkets.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/listMarkets.js'));
1073
+ const markets = await listMarkets({
1074
+ limit: args.limit ? Number(args.limit) : 50,
1075
+ offset: args.offset ? Number(args.offset) : 0,
1076
+ closed: args.closed ? String(args.closed) === 'true' : false,
1077
+ });
1078
+ if (!markets || markets.length === 0) {
1079
+ return { success: true, output: '📊 Polymarket 当前没有活跃市场' };
1080
+ }
1081
+ const lines = markets.slice(0, 10).map((m, i) => ` ${i + 1}. [${m.id}] ${m.question}`);
1082
+ return { success: true, output: `📊 Polymarket 找到 ${markets.length} 个市场 (前 10):\n${lines.join('\n')}` };
1083
+ }
1084
+ catch (e) {
1085
+ return { success: false, error: `查询失败: ${String(e.message || e)}` };
1086
+ }
1087
+ }
1088
+ });
1089
+ ctx.tools.set('polymarket_get_market', {
1090
+ name: 'polymarket_get_market',
1091
+ description: '获取单个 Polymarket 市场的详情.',
1092
+ parameters: { marketId: '市场 ID (必填)' },
1093
+ execute: async (args) => {
1094
+ try {
1095
+ const mod = await import('../constraint-runtime/dist/tools/PolymarketSDK/getMarket.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getMarket.js'));
1096
+ const fn = mod.getMarket || mod.default;
1097
+ const m = await fn(String(args.marketId));
1098
+ if (!m)
1099
+ return { success: false, error: '市场不存在' };
1100
+ return { success: true, output: `📊 市场详情:\n ${JSON.stringify(m, null, 2).substring(0, 1500)}` };
1101
+ }
1102
+ catch (e) {
1103
+ return { success: false, error: `查询失败: ${String(e.message || e)}` };
1104
+ }
1105
+ }
1106
+ });
1107
+ ctx.tools.set('polymarket_get_orders', {
1108
+ name: 'polymarket_get_orders',
1109
+ description: '查询 Polymarket 订单.',
1110
+ parameters: { marketId: '可选 按市场 ID 过滤' },
1111
+ execute: async (args) => {
1112
+ try {
1113
+ const { getOrders } = await import('../constraint-runtime/dist/tools/PolymarketSDK/getOrders.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getOrders.js'));
1114
+ const orders = await getOrders(args.marketId ? { marketId: String(args.marketId) } : {});
1115
+ return { success: true, output: `📋 订单列表: ${JSON.stringify(orders, null, 2).substring(0, 1500)}` };
1116
+ }
1117
+ catch (e) {
1118
+ return { success: false, error: `查询失败: ${String(e.message || e)}` };
1119
+ }
1120
+ }
1121
+ });
1122
+ ctx.tools.set('polymarket_create_order', {
1123
+ name: 'polymarket_create_order',
1124
+ description: '在 Polymarket 下单 (BUY/SELL).',
1125
+ parameters: { marketId: '市场 ID (必填)', side: 'BUY 或 SELL (必填)', price: '价格 0-1 (必填)', size: '数量 USDC (必填)' },
1126
+ execute: async (args) => {
1127
+ try {
1128
+ const { createOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/createOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/createOrder.js'));
1129
+ const r = await createOrder({
1130
+ marketId: String(args.marketId),
1131
+ side: String(args.side).toUpperCase() === 'SELL' ? 'SELL' : 'BUY',
1132
+ price: Number(args.price),
1133
+ size: Number(args.size),
1134
+ });
1135
+ if (r.success)
1136
+ return { success: true, output: `✅ 订单: ${r.message}` };
1137
+ return { success: false, error: r.message, output: r.message };
1138
+ }
1139
+ catch (e) {
1140
+ return { success: false, error: `下单失败: ${String(e.message || e)}` };
1141
+ }
1142
+ }
1143
+ });
1144
+ ctx.tools.set('polymarket_cancel_order', {
1145
+ name: 'polymarket_cancel_order',
1146
+ description: '取消 Polymarket 订单.',
1147
+ parameters: { orderId: '订单 ID (必填)' },
1148
+ execute: async (args) => {
1149
+ try {
1150
+ const { cancelOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/cancelOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js'));
1151
+ const r = await cancelOrder({ orderId: String(args.orderId) });
1152
+ if (r.success)
1153
+ return { success: true, output: `✅ 取消订单: ${r.message}` };
1154
+ return { success: false, error: r.message, output: r.message };
1155
+ }
1156
+ catch (e) {
1157
+ return { success: false, error: `取消失败: ${String(e.message || e)}` };
1158
+ }
1159
+ }
1160
+ });
1161
+ // Safe
1162
+ ctx.tools.set('safe_deploy', {
1163
+ name: 'safe_deploy',
1164
+ description: '部署 Safe 多签钱包.',
1165
+ parameters: { owners: 'JSON 数组 owner 地址 (必填)', threshold: '需要几个签名 (必填)' },
1166
+ execute: async (args) => {
1167
+ try {
1168
+ const { deploySafe } = await import('../constraint-runtime/dist/tools/SafeSDK/deploySafe.js').catch(() => import('../constraint-runtime/src/tools/SafeSDK/deploySafe.js'));
1169
+ const owners = Array.isArray(args.owners) ? args.owners : JSON.parse(String(args.owners));
1170
+ const r = await deploySafe({ owners, threshold: Number(args.threshold) });
1171
+ return { success: true, output: `✅ Safe 部署: ${JSON.stringify(r)}` };
1172
+ }
1173
+ catch (e) {
1174
+ return { success: false, error: `部署失败: ${String(e.message || e)}` };
1175
+ }
1176
+ }
1177
+ });
1178
+ }
1179
+ /**
1180
+ * 监听 P2P 消息 (远端 + 本地 inbox bus), 把消息存到 _inboxMessages 供 check_inbox 读.
1181
+ * 失败静默, 不阻塞 PiAgentSession 构造.
1182
+ */
1183
+ export function setupInboxListener(ctx) {
1184
+ try {
1185
+ p2pNetwork.onMessage('*', (msg, from, did) => {
1186
+ try {
1187
+ const text = new TextDecoder().decode(msg);
1188
+ let type = 'message';
1189
+ let payload = text;
1190
+ if (text.startsWith('DID:')) {
1191
+ const pipeIdx = text.indexOf('|');
1192
+ if (pipeIdx > 0) {
1193
+ const rest = text.substring(pipeIdx + 1);
1194
+ const colonIdx = rest.indexOf(':');
1195
+ if (colonIdx > 0) {
1196
+ type = rest.substring(0, colonIdx);
1197
+ payload = rest.substring(colonIdx + 1);
1198
+ }
1199
+ else {
1200
+ type = rest;
1201
+ payload = '';
1202
+ }
1203
+ }
1204
+ }
1205
+ else {
1206
+ const colonIdx = text.indexOf(':');
1207
+ if (colonIdx > 0) {
1208
+ type = text.substring(0, colonIdx);
1209
+ payload = text.substring(colonIdx + 1);
1210
+ }
1211
+ }
1212
+ ctx._inboxMessages.push({
1213
+ id: `p2p-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1214
+ from: from.substring(0, 32),
1215
+ fromDid: did,
1216
+ type,
1217
+ payload,
1218
+ timestamp: Date.now(),
1219
+ source: 'p2p',
1220
+ });
1221
+ if (ctx._inboxMessages.length > 1000) {
1222
+ ctx._inboxMessages.splice(0, ctx._inboxMessages.length - 1000);
1223
+ }
1224
+ }
1225
+ catch (err) {
1226
+ console.warn('[PiAgent] inbox listener decode error:', err);
1227
+ }
1228
+ });
1229
+ }
1230
+ catch (err) {
1231
+ console.warn('[PiAgent] setupInboxListener (p2pNetwork) failed (non-fatal):', err);
1232
+ }
1233
+ try {
1234
+ const { LocalInboxBus } = require('../network/local-inbox-bus.js');
1235
+ const myRole = process.env.BOLLOON_ROLE || 'default';
1236
+ LocalInboxBus.getInstance().subscribe(myRole, (msg) => {
1237
+ ctx._inboxMessages.push({
1238
+ id: `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1239
+ from: msg.from || 'unknown',
1240
+ fromDid: msg.fromDid,
1241
+ type: msg.type || 'agent-local-message',
1242
+ payload: msg.payload || '',
1243
+ timestamp: msg.timestamp || Date.now(),
1244
+ source: 'local',
1245
+ });
1246
+ if (ctx._inboxMessages.length > 1000) {
1247
+ ctx._inboxMessages.splice(0, ctx._inboxMessages.length - 1000);
1248
+ }
1249
+ });
1250
+ }
1251
+ catch (err) {
1252
+ // LocalInboxBus 可能还没创建, 不阻塞
1253
+ }
1254
+ }
1255
+ /**
1256
+ * M3.3: 工具结果缓存 — 防止 loop 重试时副作用 (写文件 / 改代码) 执行多次.
1257
+ * 5 分钟 TTL, 容量 200, 只缓存 success 结果.
1258
+ */
1259
+ export class IdempotencyCache {
1260
+ cache = new Map();
1261
+ TTL_MS = 5 * 60 * 1000;
1262
+ MAX = 200;
1263
+ wrap(tools) {
1264
+ for (const [name, tool] of tools.entries()) {
1265
+ if (!SIDE_EFFECT_TOOLS.has(name))
1266
+ continue;
1267
+ const original = tool.execute;
1268
+ tool.execute = async (args) => {
1269
+ const key = `${name}|${JSON.stringify(args)}`;
1270
+ const cached = this.cache.get(key);
1271
+ if (cached && Date.now() - cached.ts < this.TTL_MS) {
1272
+ return { ...cached.result, output: (cached.result.output || '') + '\n[↻ idempotency cache hit]' };
1273
+ }
1274
+ const result = await original(args);
1275
+ if (result && result.success) {
1276
+ if (this.cache.size >= this.MAX) {
1277
+ this.cache.clear();
1278
+ }
1279
+ this.cache.set(key, { result, ts: Date.now() });
1280
+ }
1281
+ return result;
1282
+ };
1283
+ }
1284
+ }
1285
+ clear() {
1286
+ this.cache.clear();
1287
+ }
1288
+ }