@bolloon/bolloon-agent 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-identity-store.js +41 -5
- package/dist/agents/background-notices.js +124 -0
- package/dist/agents/channel-identity.js +54 -0
- package/dist/agents/code-write-gate.js +47 -0
- package/dist/agents/deny-pipeline.js +11 -2
- package/dist/agents/egress-sanitize.js +69 -0
- package/dist/agents/error-classifier.js +67 -5
- package/dist/agents/experience-review.js +226 -0
- package/dist/agents/gateway-group.js +153 -18
- package/dist/agents/iteration-budget.js +64 -0
- package/dist/agents/lesson-to-skill.js +211 -0
- package/dist/agents/managed-services.js +64 -0
- package/dist/agents/parse-tool-call.js +38 -32
- package/dist/agents/pi-sdk-session-manager.js +72 -3
- package/dist/agents/pi-sdk-tools.js +268 -47
- package/dist/agents/pi-sdk.js +470 -22
- package/dist/agents/plan-store.js +36 -0
- package/dist/agents/process-runner.js +64 -2
- package/dist/agents/session-store.js +144 -3
- package/dist/agents/shell-guard.js +109 -13
- package/dist/agents/skill-health.js +90 -0
- package/dist/agents/skill-ledger.js +129 -0
- package/dist/agents/tool-loop-guard.js +303 -0
- package/dist/agents/tool-path-args.js +53 -0
- package/dist/agents/tool-result-gate.js +50 -0
- package/dist/agents/tool-subset.js +109 -0
- package/dist/agents/tool-telemetry.js +97 -0
- package/dist/agents/wallet-tools.js +63 -0
- package/dist/bootstrap/context-collector.js +37 -7
- package/dist/bootstrap/lifecycle-hooks.js +12 -3
- package/dist/bootstrap/persona-init.js +174 -0
- package/dist/bootstrap/persona-loader.js +5 -1
- package/dist/cli/ink-app.js +231 -32
- package/dist/cli/input-history.js +119 -0
- package/dist/cli/input-paste.js +218 -0
- package/dist/cli/loading-tui.js +69 -7
- package/dist/cli/log-gate.js +46 -1
- package/dist/cli/mention-data.js +30 -0
- package/dist/cli/reasoning-view.js +103 -0
- package/dist/cli/session-summary.js +129 -0
- package/dist/cli/session-tree.js +141 -0
- package/dist/cli/startup-notice.js +25 -0
- package/dist/cli/status-segments.js +155 -0
- package/dist/cli/stores.js +16 -0
- package/dist/cli/trace-line.js +54 -0
- package/dist/cli/wallet-store.js +159 -0
- package/dist/cli/wallet-table.js +46 -0
- package/dist/cli-entry.js +22 -0
- package/dist/context-compaction/auto-compact.js +37 -2
- package/dist/electron-build/agents/agent-identity-store.js +43 -5
- package/dist/electron-build/agents/agent-identity-store.js.map +1 -1
- package/dist/electron-build/agents/background-notices.js +138 -0
- package/dist/electron-build/agents/background-notices.js.map +1 -0
- package/dist/electron-build/agents/channel-identity.js +64 -0
- package/dist/electron-build/agents/channel-identity.js.map +1 -0
- package/dist/electron-build/agents/code-write-gate.js +55 -0
- package/dist/electron-build/agents/code-write-gate.js.map +1 -0
- package/dist/electron-build/agents/deny-pipeline.js +11 -2
- package/dist/electron-build/agents/deny-pipeline.js.map +1 -1
- package/dist/electron-build/agents/egress-sanitize.js +76 -0
- package/dist/electron-build/agents/egress-sanitize.js.map +1 -0
- package/dist/electron-build/agents/error-classifier.js +68 -5
- package/dist/electron-build/agents/error-classifier.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +243 -0
- package/dist/electron-build/agents/experience-review.js.map +1 -0
- package/dist/electron-build/agents/iteration-budget.js +73 -0
- package/dist/electron-build/agents/iteration-budget.js.map +1 -0
- package/dist/electron-build/agents/lesson-to-skill.js +228 -0
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -0
- package/dist/electron-build/agents/managed-services.js +69 -0
- package/dist/electron-build/agents/managed-services.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +38 -32
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +75 -3
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +268 -46
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +471 -22
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/plan-store.js +37 -0
- package/dist/electron-build/agents/plan-store.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +71 -2
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/session-store.js +144 -3
- package/dist/electron-build/agents/session-store.js.map +1 -1
- package/dist/electron-build/agents/shell-guard.js +110 -14
- package/dist/electron-build/agents/shell-guard.js.map +1 -1
- package/dist/electron-build/agents/skill-health.js +103 -0
- package/dist/electron-build/agents/skill-health.js.map +1 -0
- package/dist/electron-build/agents/skill-ledger.js +146 -0
- package/dist/electron-build/agents/skill-ledger.js.map +1 -0
- package/dist/electron-build/agents/tool-loop-guard.js +319 -0
- package/dist/electron-build/agents/tool-loop-guard.js.map +1 -0
- package/dist/electron-build/agents/tool-path-args.js +63 -0
- package/dist/electron-build/agents/tool-path-args.js.map +1 -0
- package/dist/electron-build/agents/tool-result-gate.js +59 -0
- package/dist/electron-build/agents/tool-result-gate.js.map +1 -0
- package/dist/electron-build/agents/tool-subset.js +116 -0
- package/dist/electron-build/agents/tool-subset.js.map +1 -0
- package/dist/electron-build/agents/tool-telemetry.js +108 -0
- package/dist/electron-build/agents/tool-telemetry.js.map +1 -0
- package/dist/electron-build/agents/wallet-tools.js +102 -0
- package/dist/electron-build/agents/wallet-tools.js.map +1 -0
- package/dist/electron-build/bootstrap/context-collector.js +38 -7
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/lifecycle-hooks.js +12 -3
- package/dist/electron-build/bootstrap/lifecycle-hooks.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-init.js +216 -0
- package/dist/electron-build/bootstrap/persona-init.js.map +1 -0
- package/dist/electron-build/bootstrap/persona-loader.js +5 -1
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/cli/wallet-store.js +200 -0
- package/dist/electron-build/cli/wallet-store.js.map +1 -0
- package/dist/electron-build/context-compaction/auto-compact.js +38 -2
- package/dist/electron-build/context-compaction/auto-compact.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js +22 -0
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/llm/pi-ai.js +25 -0
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/orbitdb/cid-database.js +55 -0
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
- package/dist/electron-build/orbitdb/ipfs-node.js +36 -11
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
- package/dist/electron-build/web/server-storage.js +26 -5
- package/dist/electron-build/web/server-storage.js.map +1 -1
- package/dist/external-engines/delegate.js +21 -0
- package/dist/index.js +1324 -173
- package/dist/llm/pi-ai.js +23 -0
- package/dist/orbitdb/cid-database.js +55 -0
- package/dist/orbitdb/event-index.js +370 -0
- package/dist/orbitdb/event-query.js +131 -0
- package/dist/orbitdb/event-shell.js +510 -0
- package/dist/orbitdb/group-access.js +381 -0
- package/dist/orbitdb/group-shards.js +140 -0
- package/dist/orbitdb/ipfs-node.js +36 -11
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +3 -2
- package/dist/web/mobile-core.js +7762 -6289
- package/dist/web/routes-mobile-tasks.js +2 -1
- package/dist/web/server-storage.js +26 -5
- package/dist/web/server.js +2 -0
- package/dist/web/sw.js +1 -1
- package/package.json +2 -1
|
@@ -37,13 +37,16 @@ exports.IdempotencyCache = exports.SIDE_EFFECT_TOOLS = void 0;
|
|
|
37
37
|
exports.runCodeSnippet = runCodeSnippet;
|
|
38
38
|
exports.runTerminalCommand = runTerminalCommand;
|
|
39
39
|
exports.registerBuiltinTools = registerBuiltinTools;
|
|
40
|
+
exports.kuboAlreadyRunning = kuboAlreadyRunning;
|
|
40
41
|
exports.ensureKuboReady = ensureKuboReady;
|
|
41
42
|
exports.kuboApi = kuboApi;
|
|
42
43
|
exports.registerWalletTools = registerWalletTools;
|
|
43
44
|
exports.setupInboxListener = setupInboxListener;
|
|
44
45
|
const fs = __importStar(require("fs/promises"));
|
|
46
|
+
const wallet_tools_js_1 = require("./wallet-tools.js");
|
|
45
47
|
const fsSync = __importStar(require("fs"));
|
|
46
48
|
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
47
50
|
const version_info_js_1 = require("../utils/version-info.js");
|
|
48
51
|
const reader_js_1 = require("../documents/reader.js");
|
|
49
52
|
const p2p_js_1 = require("../network/p2p.js");
|
|
@@ -53,6 +56,8 @@ const p2p_document_tools_js_1 = require("./p2p-document-tools.js");
|
|
|
53
56
|
const pi_sdk_session_factory_js_1 = require("./pi-sdk-session-factory.js");
|
|
54
57
|
const index_js_1 = require("../constraints/index.js");
|
|
55
58
|
const delegate_js_1 = require("../external-engines/delegate.js");
|
|
59
|
+
const process_runner_js_1 = require("./process-runner.js");
|
|
60
|
+
const background_notices_js_1 = require("./background-notices.js");
|
|
56
61
|
// 2026-09-13: 下一代工具集 (人机问答 / 精确补丁 / 桌面操作 / 技能分享)
|
|
57
62
|
const user_questions_js_1 = require("./user-questions.js");
|
|
58
63
|
const patch_tool_js_1 = require("./patch-tool.js");
|
|
@@ -104,6 +109,13 @@ async function detectRunner(candidates) {
|
|
|
104
109
|
}
|
|
105
110
|
return null;
|
|
106
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* 技能文件路径(账本/快照用) —— 与 skill-writer 的落盘规则一致: <root>/<name>/SKILL.md ✓
|
|
114
|
+
*/
|
|
115
|
+
function skillFilePath(name, scope) {
|
|
116
|
+
const root = scope === 'project' ? path.join(process.cwd(), '.bolloon', 'skills') : path.join(os.homedir(), '.bolloon', 'skills');
|
|
117
|
+
return path.join(root, name, 'SKILL.md');
|
|
118
|
+
}
|
|
107
119
|
async function runCodeSnippet(opts) {
|
|
108
120
|
const code = String(opts.code ?? '').trim();
|
|
109
121
|
if (!code)
|
|
@@ -238,7 +250,7 @@ function registerBuiltinTools(ctx) {
|
|
|
238
250
|
});
|
|
239
251
|
ctx.tools.set('summarize_document', {
|
|
240
252
|
name: 'summarize_document',
|
|
241
|
-
description: '
|
|
253
|
+
description: '读一篇文档并生成摘要. 只想要摘要 ⇒ 这个; 想按你的要求改写 ⇒ improve_document; 想自己读原文 ⇒ read_document.',
|
|
242
254
|
parameters: { path: '文件路径 (必填)', context: '可选, 总结上下文提示' },
|
|
243
255
|
execute: async (args) => {
|
|
244
256
|
try {
|
|
@@ -265,7 +277,7 @@ function registerBuiltinTools(ctx) {
|
|
|
265
277
|
});
|
|
266
278
|
ctx.tools.set('improve_document', {
|
|
267
279
|
name: 'improve_document',
|
|
268
|
-
description: '
|
|
280
|
+
description: '按你的要求**改写**已有文档 (保留原意、改表达). 纯改写 ⇒ 用这个; 只想要摘要 ⇒ summarize_document; 全新写一份 ⇒ write_file.',
|
|
269
281
|
parameters: { path: '文件路径 (必填)', requirements: '改进要求 (必填)' },
|
|
270
282
|
execute: async (args) => {
|
|
271
283
|
try {
|
|
@@ -293,7 +305,7 @@ function registerBuiltinTools(ctx) {
|
|
|
293
305
|
});
|
|
294
306
|
ctx.tools.set('list_peers', {
|
|
295
307
|
name: 'list_peers',
|
|
296
|
-
description: '
|
|
308
|
+
description: '列**已连接**的 P2P 对等节点. 想找远端可发消息的 channel ⇒ list_remote_channels; 想看好友申请 ⇒ list_pending_friend_requests.',
|
|
297
309
|
parameters: {},
|
|
298
310
|
execute: async () => {
|
|
299
311
|
const peers = p2p_js_1.p2pNetwork.getPeers();
|
|
@@ -305,7 +317,7 @@ function registerBuiltinTools(ctx) {
|
|
|
305
317
|
});
|
|
306
318
|
ctx.tools.set('send_message', {
|
|
307
319
|
name: 'send_message',
|
|
308
|
-
description: '
|
|
320
|
+
description: '给**一个**指定节点发消息 (需要它的 peerId). 不知道发谁 ⇒ 先 list_peers; 要广播 ⇒ broadcast_message.',
|
|
309
321
|
parameters: { peer_id: '对等节点ID', message: '消息内容' },
|
|
310
322
|
execute: async (args) => {
|
|
311
323
|
try {
|
|
@@ -319,7 +331,7 @@ function registerBuiltinTools(ctx) {
|
|
|
319
331
|
});
|
|
320
332
|
ctx.tools.set('broadcast_message', {
|
|
321
333
|
name: 'broadcast_message',
|
|
322
|
-
description: '
|
|
334
|
+
description: '向**所有**已连接节点广播一条消息. 只发给某一个 ⇒ send_message; 发到某个远程 channel ⇒ send_to_remote_channel. 广播面广, 别拿它当日常通知用.',
|
|
323
335
|
parameters: { message: '消息内容' },
|
|
324
336
|
execute: async (args) => {
|
|
325
337
|
try {
|
|
@@ -417,7 +429,7 @@ function registerBuiltinTools(ctx) {
|
|
|
417
429
|
});
|
|
418
430
|
ctx.tools.set('p2p_broadcast', {
|
|
419
431
|
name: 'p2p_broadcast',
|
|
420
|
-
description: '
|
|
432
|
+
description: '把一个 p2p channel 的地址**广播**给已连接节点. 单点发消息用 send_message, 别广播.',
|
|
421
433
|
parameters: { message: '消息内容', type: '可选, 消息类型标签 (默认 agent-broadcast)' },
|
|
422
434
|
execute: async (args) => {
|
|
423
435
|
const msg = String(args.message || '').trim();
|
|
@@ -651,6 +663,8 @@ function registerBuiltinTools(ctx) {
|
|
|
651
663
|
}
|
|
652
664
|
});
|
|
653
665
|
// delegate_to_engine — 把编码任务委派给本机已安装的其他 AI 编码智能体 CLI
|
|
666
|
+
// 2026-10-01: **它是同步等待** (默认 120s 超时, 超时杀进程) ⇒ 长活别用它 ✗ —
|
|
667
|
+
// 要"起完就走、以后回来收"的长任务 ⇒ `terminal(background:true)` + `process poll/wait/kill` ✓(今天已给后台进程加了落盘/懒恢复 ✓)。
|
|
654
668
|
// (codex / claude-code / opencode / openclaw / hermes). 它们必须已安装且可达 PATH.
|
|
655
669
|
// 实验 API 引擎 (experiment:xxx) 是供应商不是 CLI, 不支持委派, 工具会提示改用 import.
|
|
656
670
|
ctx.tools.set('delegate_to_engine', {
|
|
@@ -660,7 +674,9 @@ function registerBuiltinTools(ctx) {
|
|
|
660
674
|
engine: "引擎 id: codex / claude-code / opencode / openclaw / hermes (实验 API 不支持委派)",
|
|
661
675
|
prompt: '派发的任务描述 (作为单参数传给该引擎 CLI)',
|
|
662
676
|
model: '可选, 强制指定模型 (如 deepseek/deepseek-v4-flash), 需引擎支持',
|
|
663
|
-
cwd: '可选, 工作目录, 默认当前目录'
|
|
677
|
+
cwd: '可选, 工作目录, 默认当前目录',
|
|
678
|
+
timeoutMs: '可选, 等待上限毫秒(默认 120000=2 分钟, 超时会**终止**该引擎进程). 小活给 20000~30000, 大活别用委派(见描述)',
|
|
679
|
+
background: '可选, **默认 true** —— 起完就走(后台 session), 结果会在完成后的**下一轮自动回灌**给你; 想拿完整输出用 process poll <session>。只有确实要"现在就等它的结果"才传 false'
|
|
664
680
|
},
|
|
665
681
|
execute: async (args) => {
|
|
666
682
|
const engine = String(args.engine || '').trim();
|
|
@@ -675,14 +691,44 @@ function registerBuiltinTools(ctx) {
|
|
|
675
691
|
// correlationId 幂等去重 (同一 agent 重复 correlation 只应出现一次)
|
|
676
692
|
const ownerDid = ctx.identity?.did || 'unknown';
|
|
677
693
|
const correlationId = `delegate:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
|
|
694
|
+
// 2026-10-01 (用户: 「开展子智能体后,bolloon 没有回归」/「还是卡住了,学 hermes」):
|
|
695
|
+
// **默认后台** ✓ —— 起完立刻返回(带 session id), 父智能体继续干别的 ✓;
|
|
696
|
+
// 它跑完的结果由 background-notices 在下一轮**自动回灌** ✓ ⇒ 不再"卡满 120s 然后白干" ✗。
|
|
697
|
+
const wantBackground = String(args.background ?? '') !== 'false'; // 默认 true(起完就走 ✓)
|
|
698
|
+
if (wantBackground) {
|
|
699
|
+
try {
|
|
700
|
+
const cmd = await (0, delegate_js_1.buildDelegateCommand)(engine, prompt, { ...(model ? { model } : {}), ...(cwd ? { cwd } : {}) });
|
|
701
|
+
if (cmd) {
|
|
702
|
+
const shellCmd = `${background_notices_js_1.DELEGATE_ENV_MARK} ${JSON.stringify(cmd.cliPath)} ${cmd.args.map((a) => JSON.stringify(a)).join(' ')}`;
|
|
703
|
+
const sess = (0, process_runner_js_1.spawnBackground)(shellCmd, cmd.cwd);
|
|
704
|
+
return {
|
|
705
|
+
success: true,
|
|
706
|
+
output: `🚀 已在**后台**起 ${engine} (session ${sess.id}) —— 你可以继续干别的 ✓。`
|
|
707
|
+
+ `\n它跑完的结果会**自动回灌**到下一轮(或随时 \`process poll ${sess.id}\` 取完整输出; \`process kill ${sess.id}\` 可停)。`
|
|
708
|
+
+ `\n❗别在原地等它, 也别重复发同一条委派。`
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
/* 造不出命令/起不来 ⇒ 退回下面的同步委派 ✓ */
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const timeoutMs = Number(args.timeoutMs) || undefined;
|
|
678
717
|
const result = await (0, delegate_js_1.delegateToEngine)(engine, prompt, {
|
|
679
718
|
cwd: cwd || undefined,
|
|
680
719
|
...(model ? { model } : {}),
|
|
720
|
+
...(timeoutMs ? { timeoutMs } : {}),
|
|
681
721
|
ownerDid,
|
|
682
722
|
correlationId,
|
|
683
723
|
});
|
|
684
724
|
if (!result.success) {
|
|
685
|
-
|
|
725
|
+
// 2026-10-01 (用户: 「开展子智能体后,bolloon 没有回归」):
|
|
726
|
+
// 实测体验 = 卡整整 2 分钟(默认超时)然后**白干**(进程被杀、活丢了) ✗ ⇒ 这里把话说清、给出可操作下一步 ✓
|
|
727
|
+
const timedOut = /超时/.test(String(result.error || ''));
|
|
728
|
+
const hint = timedOut
|
|
729
|
+
? '\n⇒ 这不是"坏了", 是**该换法**: ① 拆成更小的委派(每次 ≤ 1~2 分钟); ② 或改用 `terminal(background:true)` 起长活, 再用 `process poll/wait/kill` 管(后台进程能一直活着, 不会因为超时被丢 ✓); ③ 别在原样重试同一条委派。'
|
|
730
|
+
: '';
|
|
731
|
+
return { success: false, error: `${result.error}${hint}`, output: result.output };
|
|
686
732
|
}
|
|
687
733
|
const handleLine = result.handle
|
|
688
734
|
? `\n[delegate handle] contractVersion=${result.handle.contractVersion} delegateId=${result.handle.delegateId} ownerDid=${result.handle.ownerDid} correlationId=${result.handle.correlationId ?? '-'} capability=${result.handle.capability.slice(0, 16)}...`
|
|
@@ -695,7 +741,7 @@ function registerBuiltinTools(ctx) {
|
|
|
695
741
|
});
|
|
696
742
|
ctx.tools.set('get_identity', {
|
|
697
743
|
name: 'get_identity',
|
|
698
|
-
description: '
|
|
744
|
+
description: '取当前智能体身份 (名称 + DID). 结果在**同一会话内恒定** —— 问过一次就够了, 重复调用不会变; 要改身份用 set_persona.',
|
|
699
745
|
parameters: {},
|
|
700
746
|
execute: async () => {
|
|
701
747
|
const id = ctx.identity;
|
|
@@ -707,7 +753,7 @@ function registerBuiltinTools(ctx) {
|
|
|
707
753
|
});
|
|
708
754
|
ctx.tools.set('set_persona', {
|
|
709
755
|
name: 'set_persona',
|
|
710
|
-
description: '
|
|
756
|
+
description: '设置**当前 agent** 的人格(名称/描述/性格). 只在用户明确要求改人格时用; 查身份用 get_identity.',
|
|
711
757
|
parameters: { persona_json: 'Persona JSON 对象' },
|
|
712
758
|
execute: async (args) => {
|
|
713
759
|
try {
|
|
@@ -733,7 +779,7 @@ function registerBuiltinTools(ctx) {
|
|
|
733
779
|
});
|
|
734
780
|
ctx.tools.set('get_operation_logs', {
|
|
735
781
|
name: 'get_operation_logs',
|
|
736
|
-
description: '
|
|
782
|
+
description: '取约束层(护栏)的操作日志: 最近哪些动作被拦、按什么规则拦的. 排查"我明明做了却没生效"时先看这里.',
|
|
737
783
|
parameters: {},
|
|
738
784
|
execute: async () => {
|
|
739
785
|
const logs = ctx.constraintLayer.getLogs();
|
|
@@ -749,7 +795,7 @@ function registerBuiltinTools(ctx) {
|
|
|
749
795
|
// 文件系统工具
|
|
750
796
|
ctx.tools.set('list_files', {
|
|
751
797
|
name: 'list_files',
|
|
752
|
-
description: '
|
|
798
|
+
description: '列目录 (看**有哪些**文件). 只需要**找**特定文件 ⇒ glob_files; 要在文件**内容**里搜 ⇒ grep_files; 要读某个文件内容 ⇒ read_file.',
|
|
753
799
|
parameters: { path: '目录路径(可选,默认为当前目录)' },
|
|
754
800
|
execute: async (args) => {
|
|
755
801
|
try {
|
|
@@ -768,7 +814,7 @@ function registerBuiltinTools(ctx) {
|
|
|
768
814
|
});
|
|
769
815
|
ctx.tools.set('read_directory', {
|
|
770
816
|
name: 'read_directory',
|
|
771
|
-
description: '
|
|
817
|
+
description: '列一个目录下的条目(含子目录). 只要文件名清单 ⇒ list_files; 按通配找 ⇒ glob_files.',
|
|
772
818
|
parameters: { path: '目录路径(可选,默认为当前目录)' },
|
|
773
819
|
execute: async (args) => {
|
|
774
820
|
try {
|
|
@@ -845,9 +891,93 @@ function registerBuiltinTools(ctx) {
|
|
|
845
891
|
// 2026-08-10: terminal — 灵活终端写命令 (用户要求: bolloon 自己写命令进 terminal, 少围栏).
|
|
846
892
|
// 2026-08-12 (Task2): 支持 commands 数组并行执行; 与 shell_exec 统一走 runTerminalCommand.
|
|
847
893
|
// 与 shell_exec 的区别: 直接接受完整 shell 命令字符串, 更适合模型自主写命令.
|
|
894
|
+
// 2026-10-01 (优化 #2 v1): **工具发现** —— 125 个工具不可能都记住, 给一个"按关键词找工具"的入口。
|
|
895
|
+
// 空关键词只回**分类索引**(名称清单), 有关键词才回"名称+用途摘要" ⇒ 不刷屏。
|
|
896
|
+
// 2026-10-01 (用户: 「skills 教训学习, bolloon 有吗, 从 hermes 触发的方法是什么, 学一下」):
|
|
897
|
+
// **实测缺口**: `skill-loader` 把 1300 个技能**注册**进注册表 ✓, 但 `describeSkill` 只 import 从没调用 ✗
|
|
898
|
+
// ⇒ 技能**描述从不进提示** ⇒ 模型不知道它们存在 ⇒ 技能触发链**断在这里** ✗。
|
|
899
|
+
// 她那边的做法: 启动自动装载 + 按**描述**在提示里露面 + 需要时用 `skill_view` 读全文 ✓。
|
|
900
|
+
// bolloon 有 1300 个(她百来号) ⇒ 全量注入会撑爆提示 ✗ ⇒ 改成**两个发现工具**(和 `list_tools` 同一思路 ✓)。
|
|
901
|
+
ctx.tools.set('list_skills', {
|
|
902
|
+
name: 'list_skills',
|
|
903
|
+
description: '用于**找技能**: 给关键词(中文/英文都行)⇒ 返回已装载技能的名字+说明(≤20 条); 不给关键词 ⇒ 先给总数与前若干条. 找到后用 read_skill 读全文再照做.',
|
|
904
|
+
parameters: { keyword: '可选: 关键词(名字或说明里出现的词); 省略则给概览' },
|
|
905
|
+
async execute(args) {
|
|
906
|
+
const kw = String(args?.keyword ?? '').trim().toLowerCase();
|
|
907
|
+
const all = typeof ctx.listSkills === 'function' ? ctx.listSkills() : [];
|
|
908
|
+
if (!all.length)
|
|
909
|
+
return { success: true, output: '(当前没有装载技能)' };
|
|
910
|
+
if (!kw) {
|
|
911
|
+
return { success: true, output: `已装载 ${all.length} 个技能. 前 20 个:\n` + all.slice(0, 20).map((s) => `- ${s.name}: ${String(s.description || '').slice(0, 80)}`).join('\n') + '\n\n用 list_skills {keyword:"…"} 找具体的.' };
|
|
912
|
+
}
|
|
913
|
+
const hits = all.filter((s) => `${s.name} ${s.description || ''}`.toLowerCase().includes(kw));
|
|
914
|
+
if (!hits.length)
|
|
915
|
+
return { success: true, output: `没有技能匹配 "${kw}"(共 ${all.length} 个). 换个更短的词试试.` };
|
|
916
|
+
return { success: true, output: hits.slice(0, 20).map((s) => `- ${s.name}: ${String(s.description || '').slice(0, 100)}`).join('\n') + (hits.length > 20 ? `\n…还有 ${hits.length - 20} 个` : '') };
|
|
917
|
+
},
|
|
918
|
+
});
|
|
919
|
+
ctx.tools.set('read_skill', {
|
|
920
|
+
name: 'read_skill',
|
|
921
|
+
description: '用于**读技能全文**(先 list_skills 找名字). 按里面的步骤/判据做事, 别只读个大概就动手; 内容长的会截断.',
|
|
922
|
+
parameters: { name: '技能名(必填, 来自 list_skills)' },
|
|
923
|
+
async execute(args) {
|
|
924
|
+
const name = String(args?.name ?? '').trim();
|
|
925
|
+
if (!name)
|
|
926
|
+
return { success: false, error: '缺 name; 先用 list_skills 找' };
|
|
927
|
+
let body = null;
|
|
928
|
+
try {
|
|
929
|
+
body = ctx.getSkillBody ? await ctx.getSkillBody(name) : null;
|
|
930
|
+
}
|
|
931
|
+
catch {
|
|
932
|
+
body = null;
|
|
933
|
+
}
|
|
934
|
+
if (!body)
|
|
935
|
+
return { success: false, error: `没有这个技能: ${name}(用 list_skills 看有哪些)` };
|
|
936
|
+
const clipped = body.length > 8000 ? body.slice(0, 8000) + `\n…[已截断, 原文 ${body.length} 字符]` : body;
|
|
937
|
+
// 2026-10-01: 用量遥测(旁挂文件 ✓ 绝不写进用户 SKILL.md ✗)—— best-effort, 失败不影响本次读取 ✓
|
|
938
|
+
try {
|
|
939
|
+
const { bumpUsage } = await Promise.resolve().then(() => __importStar(require('./skill-health.js')));
|
|
940
|
+
bumpUsage(name);
|
|
941
|
+
}
|
|
942
|
+
catch { /* best-effort */ }
|
|
943
|
+
return { success: true, output: clipped };
|
|
944
|
+
},
|
|
945
|
+
});
|
|
946
|
+
ctx.tools.set('list_tools', {
|
|
947
|
+
name: 'list_tools',
|
|
948
|
+
description: '找不到该用哪个工具时用这个: 给关键词(如 "文件"/"钱包"/"群" / "grep")⇒ 返回匹配工具的名字+用途摘要; 不给关键词 ⇒ 回全部分类索引. 你**先** list_tools 摸清工具, 再动手.',
|
|
949
|
+
parameters: { keyword: '可选: 关键词 (工具名或用途里出现的词); 省略则回分类索引' },
|
|
950
|
+
async execute(args) {
|
|
951
|
+
const kw = String(args?.keyword ?? '').trim().toLowerCase();
|
|
952
|
+
const all = [...ctx.tools.entries()].map(([name, t]) => ({ name, desc: String(t?.description ?? '') }));
|
|
953
|
+
if (!kw) {
|
|
954
|
+
const groups = {};
|
|
955
|
+
for (const { name } of all) {
|
|
956
|
+
const k = /wallet|chain|token|balance|tx|polymarket|safe|x402/.test(name) ? '链上/钱包'
|
|
957
|
+
: /^(read|list|glob|grep|search)/.test(name) || /file|dir|doc/.test(name) ? '读/找文件'
|
|
958
|
+
: /^(write|edit|mkdir|move|delete|copy|patch)/.test(name) ? '写/改文件'
|
|
959
|
+
: /task|plan|todo|goal|run/.test(name) ? '任务/计划'
|
|
960
|
+
: /peer|channel|friend|send|broadcast|group|inbox|p2p/.test(name) ? 'P2P/沟通'
|
|
961
|
+
: /web|fetch|http|url/.test(name) ? '网络'
|
|
962
|
+
: '其它';
|
|
963
|
+
(groups[k] ||= []).push(name);
|
|
964
|
+
}
|
|
965
|
+
const lines = Object.entries(groups).map(([k, v]) => `- ${k} (${v.length}): ${v.slice(0, 14).join(' ')}${v.length > 14 ? ' …' : ''}`);
|
|
966
|
+
return { success: true, output: `共 ${all.length} 个工具, 按类:\n${lines.join('\n')}\n\n用 list_tools {keyword:"…"} 查具体用途.` };
|
|
967
|
+
}
|
|
968
|
+
const hits = all.filter((t) => t.name.toLowerCase().includes(kw) || t.desc.toLowerCase().includes(kw));
|
|
969
|
+
if (!hits.length)
|
|
970
|
+
return { success: true, output: `没有工具匹配 "${kw}". 试试更短的词, 或 list_tools 不带关键词看分类.` };
|
|
971
|
+
return { success: true, output: hits.slice(0, 20).map((t) => `- ${t.name}: ${t.desc.replace(/\s+/g, ' ').slice(0, 90)}`).join('\n') };
|
|
972
|
+
},
|
|
973
|
+
});
|
|
848
974
|
ctx.tools.set('terminal', {
|
|
849
975
|
name: 'terminal',
|
|
850
|
-
description: '
|
|
976
|
+
description: '执行 shell 命令 (管道/重定向/跑脚本/装依赖/查系统状态). '
|
|
977
|
+
+ '**上手前先看一眼这张对照表 —— 下面这些"终端习惯"都有专用工具, 用它们比敲 shell 更稳(有护栏、结果可核对)**: '
|
|
978
|
+
+ 'grep/rg ⇒ grep_files · find/ls ⇒ glob_files / list_files · cat/head/tail ⇒ read_file · '
|
|
979
|
+
+ 'sed/awk ⇒ edit_file · echo > 文件 / cat <<EOF ⇒ write_file(要一次性生成大量代码 ⇒ execute_code) · '
|
|
980
|
+
+ 'curl 取网页正文 ⇒ fetch_url. 造文件请优先用 write_file / execute_code. 护栏只挡高危破坏操作 (sudo/格式化/rm -rf 根目录/写 ~/.bolloon 数据), 其余灵活放行. 适合: 写 HTML 文件、跑 python/node 脚本、查系统状态、装依赖. 多条命令用 commands 数组并行执行. 长命令 (服务器/构建/后台任务) 设 background=true 后台执行不阻塞对话. 也可直接传 code+language 自动写脚本执行 (便捷代码运行: python/js/ts/shell/html).',
|
|
851
981
|
parameters: { command: '完整 shell 命令 (可选, 如: echo "<html>" > /tmp/site/index.html && ls /tmp/site)', commands: '可选: 多条命令数组 (并行执行), 每条独立字符串', code: '可选: 一段代码, 传 code+language 时自动写脚本执行 (便捷代码运行)', language: '可选: code 的语言 (python/js/ts/shell/html), 默认自动', timeoutMs: '超时毫秒, 默认 30000', background: '可选: true 后台执行, 立即返回 session_id (用 process 工具 poll/wait/kill)' },
|
|
852
982
|
execute: async (args) => {
|
|
853
983
|
const timeoutMs = Number(args.timeoutMs) || 30000;
|
|
@@ -868,10 +998,25 @@ function registerBuiltinTools(ctx) {
|
|
|
868
998
|
// 长命令不阻塞对话: terminal(background=true) 启动 → process 工具轮询/等待/终止.
|
|
869
999
|
ctx.tools.set('process', {
|
|
870
1000
|
name: 'process',
|
|
871
|
-
description: '
|
|
1001
|
+
description: '管理后台进程与常驻服务. action: poll(查状态, 不阻塞) / wait(等结束, 最多 timeoutMs) / kill(终止) / list(列全部 shell 后台进程) / **services(列常驻服务: 社交心跳·P2P·OrbitDB 群聊·文档接收)** / **service(启停某个常驻服务, 需 name + op)**. 长期运行命令与去中心化交流进程都用它管.',
|
|
872
1002
|
parameters: { session_id: '后台进程 session_id (必填, poll/wait/kill 用)', action: 'poll | wait | kill | list (默认 poll)', timeoutMs: 'wait 模式等待上限毫秒, 默认 30000' },
|
|
873
1003
|
execute: async (args) => {
|
|
874
1004
|
const action = String(args.action || 'poll').trim().toLowerCase();
|
|
1005
|
+
// 2026-10-01 (用户: 「process 也要可以管理群聊和去中心化交流进程」):
|
|
1006
|
+
// services ⇒ 看常驻服务(社交心跳/P2P/OrbitDB 群聊/文档接收); service ⇒ 启停(只对真能启停的放行)
|
|
1007
|
+
if (action === 'services') {
|
|
1008
|
+
return { success: true, output: ctx.managedServices ? ctx.managedServices.describe() : '(没有注册常驻服务)' };
|
|
1009
|
+
}
|
|
1010
|
+
if (action === 'service') {
|
|
1011
|
+
const nm = String(args.name || '').trim();
|
|
1012
|
+
const op = String(args.op || '').trim().toLowerCase();
|
|
1013
|
+
if (!nm || (op !== 'start' && op !== 'stop'))
|
|
1014
|
+
return { success: false, error: 'action=service 需要 name + op(start|stop)' };
|
|
1015
|
+
if (!ctx.managedServices)
|
|
1016
|
+
return { success: false, error: '本进程没有注册常驻服务' };
|
|
1017
|
+
const r = await ctx.managedServices.control(nm, op);
|
|
1018
|
+
return r.ok ? { success: true, output: r.output } : { success: false, error: r.output };
|
|
1019
|
+
}
|
|
875
1020
|
try {
|
|
876
1021
|
const { pollSession, waitSession, killSession, listSessions, isValidSessionId } = await Promise.resolve().then(() => __importStar(require('./process-runner.js')));
|
|
877
1022
|
if (action === 'list') {
|
|
@@ -921,15 +1066,19 @@ function registerBuiltinTools(ctx) {
|
|
|
921
1066
|
// write_file / edit_file / git_*
|
|
922
1067
|
ctx.tools.set('write_file', {
|
|
923
1068
|
name: 'write_file',
|
|
924
|
-
description: '
|
|
925
|
-
|
|
1069
|
+
description: '写入**一个**文件 (整文件覆盖). 路径须在白名单内, 命中护栏黑名单会拒. '
|
|
1070
|
+
+ '**要写大量代码时按这个分工**: 一个文件 ⇒ 就用本工具(单文件上限 2MB) · '
|
|
1071
|
+
+ '**多个文件 / 要生成成套代码** ⇒ 用 execute_code 一次写完(一次调用写 N 个文件, 不逐个来回) · '
|
|
1072
|
+
+ '别用 terminal 的 heredoc/echo 来造文件(容易转义出错、也看不出写了什么).',
|
|
1073
|
+
parameters: { path: '相对路径 (必填, 相对 cwd)', content: '文件内容 (必填; 单文件上限 2MB)' },
|
|
926
1074
|
execute: async (args) => {
|
|
927
1075
|
const relPath = String(args.path || '').trim();
|
|
928
1076
|
const content = String(args.content ?? '');
|
|
929
1077
|
if (!relPath)
|
|
930
1078
|
return { success: false, error: 'path 必填' };
|
|
931
|
-
|
|
932
|
-
|
|
1079
|
+
// 2026-10-01: 上限 100KB → 2MB —— 原来的上限让"写一个大文件"只能分块, 反而促成了碎片化的多次调用
|
|
1080
|
+
if (content.length > 2_000_000)
|
|
1081
|
+
return { success: false, error: `内容过大 (${content.length} > 2000000 字节): 请用 execute_code 在脚本里分块写, 或拆成多个文件` };
|
|
933
1082
|
const pathResult = (0, shell_guard_js_1.checkWritePath)(relPath);
|
|
934
1083
|
if (!pathResult.allowed) {
|
|
935
1084
|
return { success: false, error: `路径被护栏拒: ${pathResult.reason}` };
|
|
@@ -1131,7 +1280,7 @@ function registerBuiltinTools(ctx) {
|
|
|
1131
1280
|
});
|
|
1132
1281
|
ctx.tools.set('mkdir', {
|
|
1133
1282
|
name: 'mkdir',
|
|
1134
|
-
description: '
|
|
1283
|
+
description: '新建一个目录(可带 -p 建多级). 只是**列**目录用 list_files / read_directory, 别用这个.',
|
|
1135
1284
|
parameters: { path: '目录路径 (必填, 相对 cwd)' },
|
|
1136
1285
|
execute: async (args) => {
|
|
1137
1286
|
const relPath = String(args.path || '').trim();
|
|
@@ -1193,7 +1342,7 @@ function registerBuiltinTools(ctx) {
|
|
|
1193
1342
|
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
1194
1343
|
const pExecFile = promisify(execFile);
|
|
1195
1344
|
const argv = ['-rn', '--include=' + (args.filePattern || '*'), pattern, searchPath];
|
|
1196
|
-
const { stdout, stderr } = await pExecFile('grep', argv, { cwd: ctx.cwd, maxBuffer: 1024 * 1024 });
|
|
1345
|
+
const { stdout, stderr } = await pExecFile('grep', argv, { cwd: ctx.cwd, maxBuffer: 32 * 1024 * 1024 });
|
|
1197
1346
|
const lines = stdout.split('\n').filter(Boolean).slice(0, 50);
|
|
1198
1347
|
return { success: true, output: `🔍 grep "${pattern}" in ${searchPath} (${args.filePattern || '*'}, 最多 50 行):\n${lines.join('\n')}${lines.length === 50 ? '\n... (truncated)' : ''}` };
|
|
1199
1348
|
}
|
|
@@ -1506,7 +1655,7 @@ function registerBuiltinTools(ctx) {
|
|
|
1506
1655
|
});
|
|
1507
1656
|
ctx.tools.set('update_task', {
|
|
1508
1657
|
name: 'update_task',
|
|
1509
|
-
description: '
|
|
1658
|
+
description: '更新任务的某一步 (标记 done/blocked + 备注). 改**整条计划**(目标/加步骤) ⇒ 用计划类工具; 只是查进度 ⇒ get_task.',
|
|
1510
1659
|
parameters: { task_id: '任务 id', step_id: '步骤 id', status: '新状态', result_summary: '可选', error: '可选' },
|
|
1511
1660
|
execute: async (args) => {
|
|
1512
1661
|
const taskId = String(args.task_id || '').trim();
|
|
@@ -1540,7 +1689,7 @@ function registerBuiltinTools(ctx) {
|
|
|
1540
1689
|
});
|
|
1541
1690
|
ctx.tools.set('get_task', {
|
|
1542
1691
|
name: 'get_task',
|
|
1543
|
-
description: '
|
|
1692
|
+
description: '查一个任务的当前状态与步骤进度. 要看**每一步的细节输出** ⇒ get_operation_logs; 想改状态 ⇒ update_task.',
|
|
1544
1693
|
parameters: { task_id: '任务 id (必填)' },
|
|
1545
1694
|
execute: async (args) => {
|
|
1546
1695
|
const taskId = String(args.task_id || '').trim();
|
|
@@ -1572,7 +1721,7 @@ function registerBuiltinTools(ctx) {
|
|
|
1572
1721
|
});
|
|
1573
1722
|
ctx.tools.set('list_tasks', {
|
|
1574
1723
|
name: 'list_tasks',
|
|
1575
|
-
description: '
|
|
1724
|
+
description: '列任务清单(默认只看未完成). 要看某个任务的步骤 ⇒ get_task; 想看执行细节 ⇒ get_operation_logs.',
|
|
1576
1725
|
parameters: { limit: '可选, 默认 10' },
|
|
1577
1726
|
execute: async (args) => {
|
|
1578
1727
|
const limit = Number(args.limit) || 10;
|
|
@@ -1724,10 +1873,22 @@ function registerBuiltinTools(ctx) {
|
|
|
1724
1873
|
triggers = t.map(String);
|
|
1725
1874
|
}
|
|
1726
1875
|
catch { /* triggers 解析失败忽略 */ }
|
|
1876
|
+
// 2026-10-01: 变更前先留快照(覆盖已有技能时才有内容 ✓)+ 记"谁建的"(决定自审有没有资格治理它 ✓)
|
|
1877
|
+
const { captureSkillBefore, appendLedger, currentWriteOrigin } = await Promise.resolve().then(() => __importStar(require('./skill-ledger.js')));
|
|
1878
|
+
const { markCreatedBy } = await Promise.resolve().then(() => __importStar(require('./skill-health.js')));
|
|
1879
|
+
const before = captureSkillBefore(skillFilePath(name, args.scope === 'project' ? 'project' : 'user'));
|
|
1727
1880
|
const r = await createSkill(name, String(args.description || ''), body, {
|
|
1728
1881
|
scope: args.scope === 'project' ? 'project' : 'user',
|
|
1729
1882
|
triggers,
|
|
1730
1883
|
});
|
|
1884
|
+
if (r.ok) {
|
|
1885
|
+
const after = captureSkillBefore(r.path);
|
|
1886
|
+
appendLedger({ origin: currentWriteOrigin(), tool: 'create_skill', skill: name, file: r.path, beforeSha: before.beforeSha, beforeBytes: undefined, afterSha: after.beforeSha, bytesBefore: before.bytesBefore, bytesAfter: after.bytesBefore });
|
|
1887
|
+
try {
|
|
1888
|
+
markCreatedBy(name, currentWriteOrigin());
|
|
1889
|
+
}
|
|
1890
|
+
catch { /* best-effort */ }
|
|
1891
|
+
}
|
|
1731
1892
|
return r.ok
|
|
1732
1893
|
? { success: true, output: `✅ skill '${name}' 已写入 ${r.path}` }
|
|
1733
1894
|
: { success: false, error: r.error };
|
|
@@ -1750,9 +1911,15 @@ function registerBuiltinTools(ctx) {
|
|
|
1750
1911
|
execute: async (args) => {
|
|
1751
1912
|
try {
|
|
1752
1913
|
const { updateSkill } = await Promise.resolve().then(() => __importStar(require('./skill-writer.js')));
|
|
1914
|
+
const { reviewMayTouch, captureSkillBefore, appendLedger, currentWriteOrigin } = await Promise.resolve().then(() => __importStar(require('./skill-ledger.js')));
|
|
1753
1915
|
const name = String(args.name || '').trim();
|
|
1754
1916
|
if (!name)
|
|
1755
1917
|
return { success: false, error: 'name 必填' };
|
|
1918
|
+
// 2026-10-01 **写来源隔离**: 自审只许治理自己造出来的技能 ✓ —— 用户点名要的归用户 ✓
|
|
1919
|
+
const may = reviewMayTouch(name);
|
|
1920
|
+
if (!may.ok)
|
|
1921
|
+
return { success: false, error: `拒绝(写来源隔离): ${may.reason}` };
|
|
1922
|
+
const before = captureSkillBefore(skillFilePath(name, 'user'));
|
|
1756
1923
|
let triggers;
|
|
1757
1924
|
try {
|
|
1758
1925
|
const t = JSON.parse(String(args.triggers || '[]'));
|
|
@@ -1766,6 +1933,10 @@ function registerBuiltinTools(ctx) {
|
|
|
1766
1933
|
body: args.body ? String(args.body) : undefined,
|
|
1767
1934
|
triggers,
|
|
1768
1935
|
});
|
|
1936
|
+
if (r.ok) {
|
|
1937
|
+
const after = captureSkillBefore(r.path || skillFilePath(name, 'user'));
|
|
1938
|
+
appendLedger({ origin: currentWriteOrigin(), tool: 'update_skill', skill: name, file: r.path || skillFilePath(name, 'user'), beforeSha: before.beforeSha, afterSha: after.beforeSha, bytesBefore: before.bytesBefore, bytesAfter: after.bytesBefore });
|
|
1939
|
+
}
|
|
1769
1940
|
return r.ok
|
|
1770
1941
|
? { success: true, output: `✅ skill '${name}' 已更新 ${r.path}` }
|
|
1771
1942
|
: { success: false, error: r.error };
|
|
@@ -3023,13 +3194,16 @@ function registerBuiltinTools(ctx) {
|
|
|
3023
3194
|
const { KeyManager } = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3024
3195
|
const kp = KeyManager.fromPrivateKey(Buffer.from(identity.privateKey, 'hex'));
|
|
3025
3196
|
const displayName = args.name ? String(args.name) : agentId || 'bolloon-agent';
|
|
3026
|
-
// 1. 确保本地 Kubo (自动安装 + 启动)
|
|
3197
|
+
// 1. 确保本地 Kubo (自动安装 + 启动) —— **已经在跑就别调 SDK 那次注定失败的 init**
|
|
3198
|
+
// (`sdk` 后面发布 IPNS 还要用, 所以模块导入留在函数作用域, 只把这一次调用关进短路里)
|
|
3027
3199
|
const sdk = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3200
|
+
if (!(await kuboAlreadyRunning())) {
|
|
3201
|
+
const checkKuboSetup = sdk.checkKuboSetup;
|
|
3202
|
+
if (typeof checkKuboSetup === 'function') {
|
|
3203
|
+
const setup = await checkKuboSetup(true, true);
|
|
3204
|
+
if (!setup?.ready || !setup?.daemonRunning) {
|
|
3205
|
+
return { success: false, error: '本地 Kubo 不可用 (自动安装失败), 无法发布到 IPFS' };
|
|
3206
|
+
}
|
|
3033
3207
|
}
|
|
3034
3208
|
}
|
|
3035
3209
|
// 2. 注册 agent → 上传 DID 文档 → CID
|
|
@@ -3403,7 +3577,7 @@ function registerBuiltinTools(ctx) {
|
|
|
3403
3577
|
// execute_code — 独立代码执行工具 (与 terminal 的 code 便捷路径同源, 这里给独立入口)
|
|
3404
3578
|
ctx.tools.set('execute_code', {
|
|
3405
3579
|
name: 'execute_code',
|
|
3406
|
-
description: '写一段代码并执行, 拿回 stdout/stderr
|
|
3580
|
+
description: '写一段代码并执行, 拿回 stdout/stderr/退出码。适合: 计算 · 数据处理 · 试算法 · 快速验证想法 · **一次生成大量代码**(在脚本里循环写多个文件 —— 这是"成套代码/多文件"的**推荐路径**, 一次调用顶几十次 write_file)。支持 python / js / ts / shell / html。需要多步 shell 操作用 terminal; 需要长期进程用 terminal background=true。',
|
|
3407
3581
|
parameters: {
|
|
3408
3582
|
code: '要执行的代码 (必填)',
|
|
3409
3583
|
language: '语言: python | js | ts | shell (默认 python)',
|
|
@@ -3485,7 +3659,26 @@ function registerBuiltinTools(ctx) {
|
|
|
3485
3659
|
}
|
|
3486
3660
|
// ─── IPFS/IPNS 通用 helper (2026-08-04) ─────────────────────────────────────
|
|
3487
3661
|
// 复用 publish_did 的 checkKuboSetup 自动安装/启动本地 Kubo (darwin-arm64 v0.28.0)
|
|
3662
|
+
/**
|
|
3663
|
+
* Kubo 本地节点已经在跑吗 (只读探测: POST /api/v0/version —— Kubo 只接受 POST).
|
|
3664
|
+
*
|
|
3665
|
+
* 2026-09-30 新增 (leo 报「开启过程的日志没去掉」): SDK 的 `checkKuboSetup(true, true)` 即使守护进程
|
|
3666
|
+
* 已经在跑, 仍会跑一遍 `ipfs init` ⇒ 抛 "daemon is running" ⇒ **打一行 [warn](消息里含"失败"二字,
|
|
3667
|
+
* 于是被启动日志门按"错误不许被吞"放行到屏上)** 外加子进程输出, 两行都落在启动面板之前.
|
|
3668
|
+
* 启动路径上所有调用点都先用这个探测短路: 已经在跑就没有任何可装配的.
|
|
3669
|
+
*/
|
|
3670
|
+
async function kuboAlreadyRunning(timeoutMs = 1500) {
|
|
3671
|
+
try {
|
|
3672
|
+
await kuboApi('/api/v0/version', undefined, timeoutMs);
|
|
3673
|
+
return true;
|
|
3674
|
+
}
|
|
3675
|
+
catch {
|
|
3676
|
+
return false;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3488
3679
|
async function ensureKuboReady() {
|
|
3680
|
+
if (await kuboAlreadyRunning())
|
|
3681
|
+
return; // 已在跑 ⇒ 没有可装配的, 也别触发那次注定失败的 init
|
|
3489
3682
|
const sdk = await Promise.resolve().then(() => __importStar(require('@diap/sdk')));
|
|
3490
3683
|
const checkKuboSetup = sdk.checkKuboSetup;
|
|
3491
3684
|
if (typeof checkKuboSetup === 'function') {
|
|
@@ -3525,9 +3718,24 @@ function registerWalletTools(ctx) {
|
|
|
3525
3718
|
parameters: {},
|
|
3526
3719
|
execute: async () => {
|
|
3527
3720
|
try {
|
|
3528
|
-
const { createWallet } = await
|
|
3721
|
+
const { createWallet } = await (0, wallet_tools_js_1.loadWalletTool)('createWallet');
|
|
3529
3722
|
const r = await createWallet();
|
|
3530
|
-
|
|
3723
|
+
// 2026-09-30: **落台账** (以前只生成不存 ⇒ /wallet 永远「钱包 (0)」)。
|
|
3724
|
+
// 落 ~/.bolloon/wallets/<名字>.json (0600); 输出只给地址 + 路径, 私钥/助记词不回显。
|
|
3725
|
+
let savedPath = '';
|
|
3726
|
+
try {
|
|
3727
|
+
const { addWallet } = await Promise.resolve().then(() => __importStar(require('../cli/wallet-store.js')));
|
|
3728
|
+
const { file } = await addWallet({
|
|
3729
|
+
name: `agent-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`,
|
|
3730
|
+
address: r.address, network: '—', source: 'created',
|
|
3731
|
+
createdAt: r.createdAt || new Date().toISOString(), privateKey: r.privateKey, mnemonic: r.mnemonic,
|
|
3732
|
+
});
|
|
3733
|
+
savedPath = file;
|
|
3734
|
+
}
|
|
3735
|
+
catch (e) {
|
|
3736
|
+
return { success: true, output: `⚠️ 钱包已生成但**没落台账**: ${String(e?.message || e).slice(0, 120)}\n address: ${r.address}\n (私钥只在本次输出里, 请立刻自己存好)` };
|
|
3737
|
+
}
|
|
3738
|
+
return { success: true, output: `✅ 钱包创建成功 (已落台账)\n address: ${r.address}\n 台账: ${savedPath} (0600 — 私钥/助记词已存, 不回显)\n createdAt: ${r.createdAt}` };
|
|
3531
3739
|
}
|
|
3532
3740
|
catch (e) {
|
|
3533
3741
|
return { success: false, error: `创建失败: ${String(e.message || e)}` };
|
|
@@ -3536,13 +3744,27 @@ function registerWalletTools(ctx) {
|
|
|
3536
3744
|
});
|
|
3537
3745
|
ctx.tools.set('wallet_import', {
|
|
3538
3746
|
name: 'wallet_import',
|
|
3539
|
-
description: '
|
|
3747
|
+
description: '从文件导入 EVM 钱包到本机台账(私钥值不会回显). 新建钱包 ⇒ 用创建类工具, 别拿它当通用入口.',
|
|
3540
3748
|
parameters: { mnemonic: '可选, 12/15/18/21/24 词助记词', privateKey: '可选, 0x 开头的私钥' },
|
|
3541
3749
|
execute: async (args) => {
|
|
3542
3750
|
try {
|
|
3543
|
-
const { importWallet } = await
|
|
3751
|
+
const { importWallet } = await (0, wallet_tools_js_1.loadWalletTool)('importWallet');
|
|
3544
3752
|
const r = await importWallet({ mnemonic: args.mnemonic, privateKey: args.privateKey });
|
|
3545
|
-
|
|
3753
|
+
// 2026-09-30: 同 wallet_create —— 导入也落台账 (0600), 输出不回显私钥
|
|
3754
|
+
let impPath = '';
|
|
3755
|
+
try {
|
|
3756
|
+
const { addWallet } = await Promise.resolve().then(() => __importStar(require('../cli/wallet-store.js')));
|
|
3757
|
+
const { file } = await addWallet({
|
|
3758
|
+
name: `imported-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`,
|
|
3759
|
+
address: r.address, network: '—', source: 'imported',
|
|
3760
|
+
createdAt: new Date().toISOString(), privateKey: args.privateKey, mnemonic: args.mnemonic,
|
|
3761
|
+
});
|
|
3762
|
+
impPath = file;
|
|
3763
|
+
}
|
|
3764
|
+
catch (e) {
|
|
3765
|
+
return { success: true, output: `⚠️ 钱包已导入但**没落台账**: ${String(e?.message || e).slice(0, 120)}\n address: ${r.address}\n (私钥请自己存好, 不会再有第二遍)` };
|
|
3766
|
+
}
|
|
3767
|
+
return { success: true, output: `✅ 钱包导入成功 (已落台账)\n address: ${r.address}\n 台账: ${impPath} (0600 — 私钥已存, 不回显)\n source: ${r.source}` };
|
|
3546
3768
|
}
|
|
3547
3769
|
catch (e) {
|
|
3548
3770
|
return { success: false, error: `导入失败: ${String(e.message || e)}` };
|
|
@@ -3551,11 +3773,11 @@ function registerWalletTools(ctx) {
|
|
|
3551
3773
|
});
|
|
3552
3774
|
ctx.tools.set('wallet_get_balance', {
|
|
3553
3775
|
name: 'wallet_get_balance',
|
|
3554
|
-
description: '查 EVM 钱包 ETH 余额.',
|
|
3776
|
+
description: '查 EVM 钱包 ETH 余额. 想看**全部**资产/代币 ⇒ 用钱包总览类工具; 想转账 ⇒ 转账工具.',
|
|
3555
3777
|
parameters: { address: '0x 开头的 EVM 地址 (必填)', rpcUrl: '可选 RPC URL (默认 eth.llamarpc.com)' },
|
|
3556
3778
|
execute: async (args) => {
|
|
3557
3779
|
try {
|
|
3558
|
-
const { getBalance } = await
|
|
3780
|
+
const { getBalance } = await (0, wallet_tools_js_1.loadWalletTool)('getBalance');
|
|
3559
3781
|
const r = await getBalance({ address: String(args.address), rpcUrl: args.rpcUrl });
|
|
3560
3782
|
return { success: true, output: `💰 ${r.address}\n ${r.balanceEth} ${r.symbol} (${r.balance} wei)` };
|
|
3561
3783
|
}
|
|
@@ -3570,7 +3792,7 @@ function registerWalletTools(ctx) {
|
|
|
3570
3792
|
parameters: { message: '要签名的消息 (必填)', privateKey: '0x 开头的私钥 (必填)' },
|
|
3571
3793
|
execute: async (args) => {
|
|
3572
3794
|
try {
|
|
3573
|
-
const { signMessage } = await
|
|
3795
|
+
const { signMessage } = await (0, wallet_tools_js_1.loadWalletTool)('signMessage');
|
|
3574
3796
|
const r = await signMessage({ message: String(args.message), privateKey: String(args.privateKey) });
|
|
3575
3797
|
return { success: true, output: `✅ 签名完成:\n address: ${r.address}\n message: ${r.message}\n signature: ${r.signature}` };
|
|
3576
3798
|
}
|
|
@@ -3585,7 +3807,7 @@ function registerWalletTools(ctx) {
|
|
|
3585
3807
|
parameters: { privateKey: '私钥 (必填)', to: '接收地址 (必填)', value: '发送 wei 数量 (必填)', data: '可选 0x 开头的 calldata', rpcUrl: '可选 RPC URL' },
|
|
3586
3808
|
execute: async (args) => {
|
|
3587
3809
|
try {
|
|
3588
|
-
const { sendTransaction } = await
|
|
3810
|
+
const { sendTransaction } = await (0, wallet_tools_js_1.loadWalletTool)('sendTransaction');
|
|
3589
3811
|
const r = await sendTransaction({
|
|
3590
3812
|
privateKey: String(args.privateKey),
|
|
3591
3813
|
to: String(args.to),
|
|
@@ -3602,11 +3824,11 @@ function registerWalletTools(ctx) {
|
|
|
3602
3824
|
});
|
|
3603
3825
|
ctx.tools.set('wallet_transfer_token', {
|
|
3604
3826
|
name: 'wallet_transfer_token',
|
|
3605
|
-
description: '
|
|
3827
|
+
description: '转 ERC20 代币到指定地址(要合约地址+金额). 转**原生币**(ETH)用另一个工具; 先确认链和余额.',
|
|
3606
3828
|
parameters: { privateKey: '私钥 (必填)', tokenAddress: 'ERC20 合约地址 (必填)', to: '接收地址 (必填)', amount: 'token 数量', decimals: '可选 token decimals', rpcUrl: '可选 RPC URL' },
|
|
3607
3829
|
execute: async (args) => {
|
|
3608
3830
|
try {
|
|
3609
|
-
const { transferToken } = await
|
|
3831
|
+
const { transferToken } = await (0, wallet_tools_js_1.loadWalletTool)('transferToken');
|
|
3610
3832
|
const r = await transferToken({
|
|
3611
3833
|
privateKey: String(args.privateKey),
|
|
3612
3834
|
tokenAddress: String(args.tokenAddress),
|
|
@@ -3628,7 +3850,7 @@ function registerWalletTools(ctx) {
|
|
|
3628
3850
|
parameters: { from: '付款方地址 (必填)', to: '收款方地址 (必填)', amount: '金额 (必填)', interval: '周期 (e.g. daily/weekly/monthly)', token: '可选, 默认 ETH' },
|
|
3629
3851
|
execute: async (args) => {
|
|
3630
3852
|
try {
|
|
3631
|
-
const mod = await
|
|
3853
|
+
const mod = await (0, wallet_tools_js_1.loadWalletTool)('autoPay');
|
|
3632
3854
|
const fn = mod.autoPay || mod.setAutoPay || mod.default;
|
|
3633
3855
|
if (!fn)
|
|
3634
3856
|
return { success: false, error: 'autoPay 接口未找到' };
|
|
@@ -3646,7 +3868,7 @@ function registerWalletTools(ctx) {
|
|
|
3646
3868
|
// Polymarket
|
|
3647
3869
|
ctx.tools.set('polymarket_list_markets', {
|
|
3648
3870
|
name: 'polymarket_list_markets',
|
|
3649
|
-
description: '
|
|
3871
|
+
description: '列 Polymarket 预测市场(可按分类/状态筛). 查某个市场的**价格/盘口** ⇒ 用对应详情工具.',
|
|
3650
3872
|
parameters: { limit: '可选 数量 (默认 50)', offset: '可选 偏移', closed: '可选 是否只显示已关闭' },
|
|
3651
3873
|
execute: async (args) => {
|
|
3652
3874
|
try {
|
|
@@ -3669,7 +3891,7 @@ function registerWalletTools(ctx) {
|
|
|
3669
3891
|
});
|
|
3670
3892
|
ctx.tools.set('polymarket_get_market', {
|
|
3671
3893
|
name: 'polymarket_get_market',
|
|
3672
|
-
description: '
|
|
3894
|
+
description: '查某个 Polymarket 市场的详情(题目/结算/盘口). 只是**列**市场 ⇒ polymarket_list_markets.',
|
|
3673
3895
|
parameters: { marketId: '市场 ID (必填)' },
|
|
3674
3896
|
execute: async (args) => {
|
|
3675
3897
|
try {
|
|
@@ -3940,7 +4162,7 @@ function registerWalletTools(ctx) {
|
|
|
3940
4162
|
// Safe
|
|
3941
4163
|
ctx.tools.set('safe_deploy', {
|
|
3942
4164
|
name: 'safe_deploy',
|
|
3943
|
-
description: '部署 Safe
|
|
4165
|
+
description: '部署 Gnosis Safe 多签钱包 (建钱包/加签名人). 只是**查**多签状态别用这个.',
|
|
3944
4166
|
parameters: { owners: 'JSON 数组 owner 地址 (必填)', threshold: '需要几个签名 (必填)' },
|
|
3945
4167
|
execute: async (args) => {
|
|
3946
4168
|
try {
|