@bolloon/bolloon-agent 0.3.22 → 0.3.24
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/deny-pipeline.js +116 -0
- package/dist/agents/parse-tool-call.js +26 -4
- package/dist/agents/pi-sdk.js +247 -50
- package/dist/agents/session-store.js +87 -2
- package/dist/bootstrap/snip-collapse.js +135 -0
- package/dist/cli/ink-app.js +116 -0
- package/dist/cli/loading-tui.js +64 -10
- package/dist/electron/config.js +14 -9
- package/dist/electron/dialogs.js +53 -16
- package/dist/electron/first-run.js +65 -24
- package/dist/electron/ipc.js +14 -10
- package/dist/electron/logger.js +44 -7
- package/dist/electron/main.js +45 -42
- package/dist/electron/menu.js +18 -13
- package/dist/electron/paths.js +54 -12
- package/dist/electron/server.js +57 -18
- package/dist/electron/tray.js +53 -15
- package/dist/electron/window.js +61 -22
- package/dist/electron-preload.js +19 -16
- package/dist/electron.js +4 -1
- package/dist/external-engines/delegate.js +19 -0
- package/dist/hooks/hooks-engine.js +329 -0
- package/dist/index.js +130 -133
- package/dist/llm/pi-ai.js +5 -17
- package/dist/security/tool-gate.js +8 -1
- package/dist/social/dunbar-tier.js +409 -0
- package/dist/utils/auto-update.js +51 -12
- package/dist/web/client.js +4833 -4328
- package/dist/web/components/p2p/index.js +234 -276
- package/dist/web/server.js +17 -3
- package/dist/web/style.css +2 -2
- package/dist/web/ui/message-renderer.js +396 -535
- package/dist/web/ui/step-timeline.js +273 -372
- package/package.json +27 -25
- package/dist/web/components/p2p/P2PModal.js +0 -188
- package/dist/web/components/p2p/p2p-modal.js +0 -664
- package/dist/web/components/p2p/p2p-tools.js +0 -248
package/dist/index.js
CHANGED
|
@@ -15,7 +15,8 @@ import { createSubAgentManager } from './agents/subagent-manager.js';
|
|
|
15
15
|
import { getGlobalSharedContext } from './social/global-shared-context.js';
|
|
16
16
|
import { createBollharnessIntegration } from './bollharness-integration/index.js';
|
|
17
17
|
import * as readline from 'readline';
|
|
18
|
-
import { printBanner, renderDialog, renderUserMessage, renderAgentMessage,
|
|
18
|
+
import { printBanner, renderDialog, renderUserMessage, renderAgentMessage, renderToolCallListItem, termWidth } from './cli/loading-tui.js';
|
|
19
|
+
import { startInk, stopInk, inkAppendLine as appendLine, inkSetStatus, inkSetThinking } from './cli/ink-app.js';
|
|
19
20
|
// 启动自动检查更新:后台、节流、检测到新版本自动安装(可被 --no-update / BOLLOON_SKIP_UPDATE 关闭)
|
|
20
21
|
import { createRequire } from 'module';
|
|
21
22
|
const _require = createRequire(import.meta.url);
|
|
@@ -90,15 +91,21 @@ const s = {
|
|
|
90
91
|
console.log();
|
|
91
92
|
},
|
|
92
93
|
Thinking: () => {
|
|
93
|
-
const frames = ['
|
|
94
|
+
const frames = ['(`・ω・´)', '(´・_・`)', '(。•́︿•̀。)', 'ᕙ(▀̿̿Ĺ̯̿̿▀̿ ̿)ᕗ', '(◕‿◕)'];
|
|
94
95
|
let i = 0;
|
|
96
|
+
let dots = 0;
|
|
97
|
+
const frame = frames[0];
|
|
98
|
+
appendLine(` ${frame} 思考...`);
|
|
95
99
|
return setInterval(() => {
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
i = (i + 1) % frames.length;
|
|
101
|
+
dots = (dots + 1) % 4;
|
|
102
|
+
const dotStr = '.'.repeat(dots || 1);
|
|
103
|
+
appendLine(`\r ${frames[i]} 思考${dotStr} `);
|
|
104
|
+
}, 600);
|
|
98
105
|
},
|
|
99
106
|
clearThinking: (interval) => {
|
|
100
107
|
clearInterval(interval);
|
|
101
|
-
process.stdout.write('\r' + ' '.repeat(
|
|
108
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
102
109
|
},
|
|
103
110
|
dialog: async (title, promptText) => {
|
|
104
111
|
return new Promise((resolve) => {
|
|
@@ -179,7 +186,7 @@ function publishDID(name, kp) {
|
|
|
179
186
|
}
|
|
180
187
|
catch (e) {
|
|
181
188
|
// 一次失败直接放弃 — 本地模式运行就够了, 不重试
|
|
182
|
-
|
|
189
|
+
appendLine(` ${YELLOW}⚠ IPFS 发布失败 (${e?.message?.slice(0, 80) || 'unknown'}), 本地模式运行${RESET}`);
|
|
183
190
|
s.step(2, 5, '发布 DID → IPFS', 'warn');
|
|
184
191
|
resolve({});
|
|
185
192
|
}
|
|
@@ -347,13 +354,12 @@ function rpcErr(code, msg) {
|
|
|
347
354
|
// CLI with persistent bottom prompt
|
|
348
355
|
// 2026-07-28: 改用 readline.createInterface + replReadline 循环
|
|
349
356
|
let isRunning = false;
|
|
357
|
+
let cliContextPct = 0;
|
|
350
358
|
let queueMode = false;
|
|
351
359
|
const pendingQueue = [];
|
|
352
|
-
// 底部状态栏数据
|
|
353
360
|
let cliStartTime = 0;
|
|
354
361
|
let cliModelName = '…';
|
|
355
362
|
let cliAgentName = '…';
|
|
356
|
-
let cliContextPct = 0;
|
|
357
363
|
function fmtDuration(ms) {
|
|
358
364
|
const s = Math.floor(ms / 1000);
|
|
359
365
|
const m = Math.floor(s / 60);
|
|
@@ -371,16 +377,21 @@ function statusBarLine() {
|
|
|
371
377
|
const bar = C_OK + '█'.repeat(filled) + C_DIM + '░'.repeat(barLen - filled) + RESET;
|
|
372
378
|
return `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ ${C_ACCENT}${dur}${RESET} ${C_DIM}│${RESET} ${bar} ${C_DIM}${cliContextPct}%${RESET}`;
|
|
373
379
|
}
|
|
374
|
-
function startCLI(comm) {
|
|
380
|
+
async function startCLI(comm) {
|
|
375
381
|
isRunning = true;
|
|
376
|
-
// CLI
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
382
|
+
// CLI 模式下静音所有 console.log/warn
|
|
383
|
+
// (Ink 用自己的 render 引擎, console.log 输出会污染终端)
|
|
384
|
+
console.log = () => { };
|
|
385
|
+
console.warn = () => { };
|
|
386
|
+
// 同时过滤 process.stdout.write — 阻止 [xxx] 前缀的输出
|
|
387
|
+
const _origStdout = process.stdout.write.bind(process.stdout);
|
|
388
|
+
process.stdout.write = ((chunk, ...rest) => {
|
|
389
|
+
const s = typeof chunk === 'string' ? chunk : String(chunk);
|
|
390
|
+
// 过滤以 [ 开头的行 (agent 内部日志)
|
|
391
|
+
if (s.trimStart().startsWith('['))
|
|
392
|
+
return true;
|
|
393
|
+
return _origStdout(chunk, ...rest);
|
|
394
|
+
});
|
|
384
395
|
let peerCount = 0;
|
|
385
396
|
try {
|
|
386
397
|
peerCount = comm.getConnections().length;
|
|
@@ -401,109 +412,96 @@ function startCLI(comm) {
|
|
|
401
412
|
cliModelName = foundProvider ? foundProvider[1] : '未配置';
|
|
402
413
|
cliAgentName = agentIdentity?.name || 'bolloon';
|
|
403
414
|
cliStartTime = Date.now();
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const SEP = '\x1b[90m─';
|
|
419
|
-
const RST = '\x1b[0m';
|
|
420
|
-
while (isRunning) {
|
|
421
|
-
const tw = process.stdout.columns || 80;
|
|
422
|
-
const sepLine = SEP.repeat(tw) + RST;
|
|
423
|
-
const prefix = queueMode ? `${C_WARN}▸${RST}` : `${C_ACCENT}❯${RST}`;
|
|
424
|
-
const raw = await new Promise(resolve => rl.question(`\n${sepLine}\n${statusBarLine()}\n${sepLine}\n${prefix} `, resolve));
|
|
425
|
-
const trimmed = raw.trim();
|
|
426
|
-
process.stdout.write(`\n${sepLine}\n\n\n\n\n`);
|
|
427
|
-
if (!trimmed)
|
|
428
|
-
continue;
|
|
429
|
-
if (!isRunning)
|
|
430
|
-
break;
|
|
431
|
-
await processInput(trimmed, comm);
|
|
432
|
-
}
|
|
433
|
-
rl.close();
|
|
434
|
-
process.stdout.write(`\n${CYAN}👋 再见!${RESET}\n`);
|
|
435
|
-
process.stdin.destroy();
|
|
415
|
+
// 进入 Ink TUI 输入循环
|
|
416
|
+
const initialStatus = `${C_ACCENT}${cliModelName}\x1b[0m\x1b[90m │\x1b[0m ${cliAgentName} \x1b[90m│\x1b[0m ⏱ 0s`;
|
|
417
|
+
const getStatus = () => {
|
|
418
|
+
const dur = Math.floor((Date.now() - cliStartTime) / 1000);
|
|
419
|
+
const barLen = 12;
|
|
420
|
+
const filled = Math.round((cliContextPct / 100) * barLen);
|
|
421
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
|
|
422
|
+
return `${C_ACCENT}${cliModelName}\x1b[0m\x1b[90m │\x1b[0m ${cliAgentName} \x1b[90m│\x1b[0m ⏱ ${dur}s\x1b[90m │\x1b[0m ${bar} ${cliContextPct}%`;
|
|
423
|
+
};
|
|
424
|
+
startInk((text) => { processInput(text, comm); }, initialStatus, getStatus);
|
|
425
|
+
// Wait on a promise that resolves on Ctrl+C
|
|
426
|
+
await new Promise(() => { });
|
|
427
|
+
stopInk();
|
|
428
|
+
appendLine(`\n${CYAN}👋 再见!${RESET}`);
|
|
436
429
|
comm.stop();
|
|
437
430
|
}
|
|
438
431
|
async function processInput(input, comm) {
|
|
439
432
|
const trimmed = input.trim();
|
|
433
|
+
// TUI tool call state (local to this invocation)
|
|
434
|
+
const tuiToolCalls = [];
|
|
435
|
+
let tuiToolCounter = 0;
|
|
436
|
+
// each iteration
|
|
437
|
+
let lastToolEvent = null;
|
|
440
438
|
// !command — 直接执行终端命令
|
|
441
439
|
if (trimmed.startsWith('!')) {
|
|
442
440
|
const cmd = trimmed.slice(1).trim();
|
|
443
441
|
if (!cmd) {
|
|
444
|
-
|
|
442
|
+
appendLine(`${C_DIM}!<命令> 执行终端命令, 如 !ls -la${RESET}`);
|
|
445
443
|
return;
|
|
446
444
|
}
|
|
447
|
-
|
|
445
|
+
appendLine(`${C_DIM}── $ ${cmd}${RESET}`);
|
|
448
446
|
try {
|
|
449
447
|
const { execSync } = await import('child_process');
|
|
450
448
|
const out = execSync(cmd, { timeout: 30000, encoding: 'utf-8', cwd: process.cwd() });
|
|
451
|
-
|
|
449
|
+
appendLine(`${C_DIM}${out || '(无输出)'}${RESET}`);
|
|
452
450
|
}
|
|
453
451
|
catch (e) {
|
|
454
|
-
|
|
452
|
+
appendLine(`${C_ERROR}${e.stderr || e.message}${RESET}`);
|
|
455
453
|
}
|
|
456
|
-
|
|
454
|
+
appendLine(`${C_DIM}──${RESET}`);
|
|
457
455
|
return;
|
|
458
456
|
}
|
|
459
457
|
// /queue — 切换队列模式
|
|
460
458
|
if (trimmed.toLowerCase() === '/queue') {
|
|
461
459
|
queueMode = !queueMode;
|
|
462
|
-
|
|
460
|
+
appendLine(`${C_WARN}队列 ${queueMode ? '开启' : '关闭'}${RESET} (${pendingQueue.length} 条)`);
|
|
463
461
|
return;
|
|
464
462
|
}
|
|
465
463
|
// /dequeue — 出队一条
|
|
466
464
|
if (trimmed.toLowerCase() === '/dequeue' || trimmed.toLowerCase() === '/dq') {
|
|
467
465
|
const next = pendingQueue.shift();
|
|
468
466
|
if (next)
|
|
469
|
-
|
|
467
|
+
appendLine(`${C_WARN}出队:${RESET} ${next}`);
|
|
470
468
|
else
|
|
471
|
-
|
|
469
|
+
appendLine(`${C_DIM}队列为空${RESET}`);
|
|
472
470
|
return;
|
|
473
471
|
}
|
|
474
472
|
// 队列模式: 入队
|
|
475
473
|
if (queueMode) {
|
|
476
474
|
pendingQueue.push(trimmed);
|
|
477
|
-
|
|
475
|
+
appendLine(`${C_WARN}[${pendingQueue.length}]${RESET} 已入队`);
|
|
478
476
|
return;
|
|
479
477
|
}
|
|
480
478
|
// 队列非空: 也入队末尾 (排队执行)
|
|
481
479
|
if (pendingQueue.length > 0) {
|
|
482
480
|
pendingQueue.push(trimmed);
|
|
483
|
-
|
|
481
|
+
appendLine(`${C_WARN}[队列 ${pendingQueue.length}]${RESET} 已入队, 执行完当前后自动运行`);
|
|
484
482
|
return;
|
|
485
483
|
}
|
|
486
484
|
if (trimmed.toLowerCase() === '/help' || trimmed === 'help') {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
485
|
+
appendLine(`${C_DIM}命令:${RESET}`);
|
|
486
|
+
appendLine(` ${C_ACCENT}!<cmd>${RESET} 执行终端命令 ${C_DIM}如 !ls -la${RESET}`);
|
|
487
|
+
appendLine(` ${C_ACCENT}/queue${RESET} 切换队列模式 ${C_DIM}输入排队, 当前结束后自动执行${RESET}`);
|
|
488
|
+
appendLine(` ${C_ACCENT}/dequeue${RESET} 出队一条`);
|
|
489
|
+
appendLine(` ${C_ACCENT}peers${RESET} 查看 P2P 节点`);
|
|
490
|
+
appendLine(` ${C_ACCENT}iroh${RESET} 查看 iroh 状态`);
|
|
491
|
+
appendLine(` ${C_ACCENT}add_friend${RESET} 添加好友`);
|
|
492
|
+
appendLine(` ${C_ACCENT}exit${RESET} 退出`);
|
|
495
493
|
return;
|
|
496
494
|
}
|
|
497
495
|
if (trimmed === '退出' || trimmed === 'exit' || trimmed === 'quit') {
|
|
498
|
-
|
|
496
|
+
appendLine(`\n${CYAN}👋 再见!${RESET}`);
|
|
499
497
|
isRunning = false;
|
|
500
498
|
return;
|
|
501
499
|
}
|
|
502
500
|
if (trimmed.toLowerCase() === 'peers') {
|
|
503
501
|
const peers = comm.getConnections();
|
|
504
|
-
|
|
502
|
+
appendLine(`${GRAY}已连接节点: ${peers.length}${RESET}`);
|
|
505
503
|
for (const c of peers) {
|
|
506
|
-
|
|
504
|
+
appendLine(` ${GRAY}·${RESET} ${c.publicKey.substring(0, 16)}...`);
|
|
507
505
|
}
|
|
508
506
|
return;
|
|
509
507
|
}
|
|
@@ -511,25 +509,25 @@ async function processInput(input, comm) {
|
|
|
511
509
|
const nodeId = irohTransport.getNodeId();
|
|
512
510
|
const running = irohTransport.isRunning();
|
|
513
511
|
const peers = irohTransport.getPeers();
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
512
|
+
appendLine(`${GRAY}iroh 状态:${RESET}`);
|
|
513
|
+
appendLine(` ${GRAY}运行中:${RESET} ${running ? '是' : '否'}`);
|
|
514
|
+
appendLine(` ${GRAY}Node ID:${RESET} ${nodeId ? nodeId.substring(0, 24) + '...' : 'N/A'}`);
|
|
515
|
+
appendLine(` ${GRAY}已知节点:${RESET} ${peers.length}`);
|
|
518
516
|
if (hybridMessenger) {
|
|
519
|
-
|
|
517
|
+
appendLine(` ${GRAY}HybridMessenger:${RESET} 就绪`);
|
|
520
518
|
}
|
|
521
519
|
return;
|
|
522
520
|
}
|
|
523
521
|
if (trimmed.toLowerCase().startsWith('add_friend ') || trimmed.toLowerCase() === 'add_friend') {
|
|
524
522
|
const parts = trimmed.split(/\s+/);
|
|
525
523
|
if (parts.length < 2 || (parts.length === 2 && parts[1].length !== 64)) {
|
|
526
|
-
|
|
527
|
-
|
|
524
|
+
appendLine(`${GRAY}用法: add_friend <64字符hex publicKey> [备注名]\n${RESET}`);
|
|
525
|
+
appendLine(`${GRAY}示例: add_friend a1b2c3d4e5f6... 同事-张磊\n${RESET}`);
|
|
528
526
|
return;
|
|
529
527
|
}
|
|
530
528
|
const pk = parts[1];
|
|
531
529
|
const name = parts.slice(2).join(' ') || '';
|
|
532
|
-
|
|
530
|
+
appendLine(`${GRAY}正在发送好友申请给 ${pk.substring(0, 16)}...${RESET}`);
|
|
533
531
|
try {
|
|
534
532
|
const port = process.env.PORT || '54188';
|
|
535
533
|
const res = await fetch(`http://127.0.0.1:${port}/api/friend-request`, {
|
|
@@ -540,78 +538,77 @@ async function processInput(input, comm) {
|
|
|
540
538
|
const data = await res.json();
|
|
541
539
|
if (!res.ok) {
|
|
542
540
|
const reason = data.code === 'NO_CONN' ? '对方未在线, 已本地记住, 等对方上线后自动重连' : (data.error || '请求失败');
|
|
543
|
-
|
|
541
|
+
appendLine(`${MAGENTA}✗ 添加好友失败: ${reason}${RESET}`);
|
|
544
542
|
if (data.persistedAs)
|
|
545
|
-
|
|
543
|
+
appendLine(`${GRAY}本地已保存为: ${data.persistedAs}${RESET}`);
|
|
546
544
|
}
|
|
547
545
|
else {
|
|
548
|
-
|
|
546
|
+
appendLine(`${GREEN}✓ 好友申请已发送给 ${data.persistedAs || name || pk.substring(0, 12)}...${RESET}`);
|
|
549
547
|
}
|
|
550
548
|
}
|
|
551
549
|
catch (err) {
|
|
552
|
-
|
|
550
|
+
appendLine(`${MAGENTA}✗ 添加好友失败: ${err.message || String(err)}${RESET}`);
|
|
553
551
|
}
|
|
554
552
|
return;
|
|
555
553
|
}
|
|
556
554
|
try {
|
|
557
555
|
// 双横线分割
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
556
|
+
appendLine(`${C_DIM}${'─'.repeat(8)} · ${'─'.repeat(8)}${RESET}`);
|
|
557
|
+
appendLine(renderUserMessage(trimmed));
|
|
558
|
+
// 启动思考动画
|
|
559
|
+
inkSetThinking(true);
|
|
561
560
|
const a = await getAgent();
|
|
562
561
|
const boxW = Math.min(termWidth() - 2, 76);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
tool: e.tool ?? p?.tool ?? '?',
|
|
583
|
-
args: p?.args,
|
|
584
|
-
status: e.type === 'step_done' ? 'ok' : 'error',
|
|
585
|
-
output: e.output,
|
|
586
|
-
error: e.error,
|
|
587
|
-
durationMs: p ? Date.now() - p.t0 : undefined,
|
|
588
|
-
width: boxW,
|
|
589
|
-
}) + '\n');
|
|
562
|
+
// 工具调用显示由 tui-shell 的 onStream handler 处理
|
|
563
|
+
const response = await a.prompt(trimmed, {
|
|
564
|
+
onStream: (e) => {
|
|
565
|
+
if (e.type === 'step_start') {
|
|
566
|
+
tuiToolCounter++;
|
|
567
|
+
tuiToolCalls.push({ tool: e.tool || '?', args: e.args, _t: Date.now() });
|
|
568
|
+
}
|
|
569
|
+
else if (e.type === 'step_done' || e.type === 'step_error') {
|
|
570
|
+
const p = tuiToolCalls.shift();
|
|
571
|
+
const doneItem = {
|
|
572
|
+
tool: e.tool ?? (p?.tool ?? '?'),
|
|
573
|
+
args: p?.args,
|
|
574
|
+
status: e.type === 'step_done' ? 'ok' : 'error',
|
|
575
|
+
output: e.output,
|
|
576
|
+
error: e.error,
|
|
577
|
+
durationMs: p ? Date.now() - p._t : undefined,
|
|
578
|
+
};
|
|
579
|
+
appendLine(renderToolCallListItem(doneItem, tuiToolCalls.length + 1, tuiToolCounter));
|
|
580
|
+
}
|
|
590
581
|
}
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
//
|
|
582
|
+
});
|
|
583
|
+
// 智能体回复框
|
|
584
|
+
appendLine(renderAgentMessage(response));
|
|
585
|
+
// 停止思考动画
|
|
586
|
+
inkSetThinking(false);
|
|
587
|
+
// 更新状态栏: 上下文进度
|
|
597
588
|
try {
|
|
598
|
-
// 用 messageHistory 长度推断上下文占用
|
|
599
589
|
const msgLen = JSON.stringify(a.messageHistory ?? []).length;
|
|
600
590
|
cliContextPct = Math.min(100, Math.round((msgLen / 240_000) * 100));
|
|
591
|
+
const dur = Math.floor((Date.now() - cliStartTime) / 1000);
|
|
592
|
+
const barLen = 12;
|
|
593
|
+
const filled = Math.round((cliContextPct / 100) * barLen);
|
|
594
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
|
|
595
|
+
const statusText = `${C_ACCENT}${cliModelName}\x1b[0m\x1b[90m │\x1b[0m ${cliAgentName} \x1b[90m│\x1b[0m ⏱ ${dur}s \x1b[90m│\x1b[0m ${msgLen.toLocaleString()}B/240K │ ${bar} ${cliContextPct}%`;
|
|
596
|
+
inkSetStatus(statusText);
|
|
601
597
|
}
|
|
602
598
|
catch { /* 降级容忍 */ }
|
|
603
|
-
//
|
|
604
|
-
process.stdout.write(`${C_DIM}${'─'.repeat(8)} · ${'─'.repeat(8)}${RESET}\n`);
|
|
599
|
+
// 自动消费队列
|
|
605
600
|
if (pendingQueue.length > 0) {
|
|
606
601
|
const next = pendingQueue.shift();
|
|
607
|
-
|
|
602
|
+
appendLine(`${C_WARN}⏩ 自动执行队列 [${pendingQueue.length + 1}/${pendingQueue.length + 1}]${RESET}`);
|
|
608
603
|
await processInput(next, comm);
|
|
609
604
|
return;
|
|
610
605
|
}
|
|
606
|
+
inkSetThinking(false);
|
|
611
607
|
}
|
|
612
608
|
catch (e) {
|
|
609
|
+
inkSetThinking(false);
|
|
613
610
|
if (!e.message?.includes('ERR_USE_AFTER_CLOSE') && !e.message?.includes('write after end')) {
|
|
614
|
-
|
|
611
|
+
appendLine(`${MAGENTA}❌ ${e.message}${RESET}`);
|
|
615
612
|
}
|
|
616
613
|
}
|
|
617
614
|
}
|
|
@@ -855,7 +852,7 @@ async function runToolCommand(tool, args, outputJson, comm, model, prompt) {
|
|
|
855
852
|
catch { }
|
|
856
853
|
break;
|
|
857
854
|
}
|
|
858
|
-
|
|
855
|
+
appendLine(`⏳ 任务已派给 ${targetPk.slice(0, 12)}..., 等回复 (最多 90s)...`);
|
|
859
856
|
const reply = await replyPromise;
|
|
860
857
|
const lines = [
|
|
861
858
|
`✅ 协作完成 (${reply.durationMs ? Math.round(reply.durationMs / 1000) + 's' : '?'})`,
|
|
@@ -1421,8 +1418,8 @@ async function runToolCommand(tool, args, outputJson, comm, model, prompt) {
|
|
|
1421
1418
|
const { P2PDirect } = await import('./network/p2p-direct.js');
|
|
1422
1419
|
const id = await resolveIdentity();
|
|
1423
1420
|
const p2p = new P2PDirect({ name: 'cli-listen', role: id.role });
|
|
1424
|
-
|
|
1425
|
-
|
|
1421
|
+
appendLine(`[chat-p2p-listen] role=${id.role} pk=${id.publicKey.slice(0, 12)} listening on bolloon-agent-harness`);
|
|
1422
|
+
appendLine(`[chat-p2p-listen] press Ctrl-C to stop`);
|
|
1426
1423
|
const onData = (ev) => {
|
|
1427
1424
|
try {
|
|
1428
1425
|
const text = Buffer.isBuffer(ev.data) ? ev.data.toString('utf8') : String(ev.data);
|
|
@@ -1431,15 +1428,15 @@ async function runToolCommand(tool, args, outputJson, comm, model, prompt) {
|
|
|
1431
1428
|
if (env && env.v === 3 && env.op === 'agent.chat.direct') {
|
|
1432
1429
|
const { text: body, fromRole } = env.payload || {};
|
|
1433
1430
|
const ts = (env.payload?.ts || new Date().toISOString()).replace('T', ' ').replace(/\.\d+Z$/, '');
|
|
1434
|
-
|
|
1431
|
+
appendLine(`\n[${ts} ${fromRole || ev.fromPublicKey?.slice(0, 12)} → me] ${body}\n> `);
|
|
1435
1432
|
return;
|
|
1436
1433
|
}
|
|
1437
1434
|
}
|
|
1438
1435
|
catch { /* 非 v3 envelope, 当 raw 显示 */ }
|
|
1439
|
-
|
|
1436
|
+
appendLine(`\n[raw ${ev.fromPublicKey?.slice(0, 12)}] ${text.slice(0, 200)}\n> `);
|
|
1440
1437
|
}
|
|
1441
1438
|
catch (e) {
|
|
1442
|
-
|
|
1439
|
+
appendLine(`[chat-p2p-listen] decode error: ${e?.message ?? e}`);
|
|
1443
1440
|
}
|
|
1444
1441
|
};
|
|
1445
1442
|
p2p.on('data', onData);
|
|
@@ -1447,12 +1444,12 @@ async function runToolCommand(tool, args, outputJson, comm, model, prompt) {
|
|
|
1447
1444
|
const keepAlive = setInterval(() => {
|
|
1448
1445
|
const now = Date.now();
|
|
1449
1446
|
if (now - lastPing > 5 * 60_000) {
|
|
1450
|
-
|
|
1447
|
+
appendLine(`[chat-p2p-listen] alive, role=${id.role}`);
|
|
1451
1448
|
lastPing = now;
|
|
1452
1449
|
}
|
|
1453
1450
|
}, 30_000);
|
|
1454
1451
|
const stop = async () => {
|
|
1455
|
-
|
|
1452
|
+
appendLine(`\n[chat-p2p-listen] stopping...`);
|
|
1456
1453
|
try {
|
|
1457
1454
|
p2p.off('data', onData);
|
|
1458
1455
|
}
|
|
@@ -1469,7 +1466,7 @@ async function runToolCommand(tool, args, outputJson, comm, model, prompt) {
|
|
|
1469
1466
|
process.on('SIGHUP', stop);
|
|
1470
1467
|
await p2p.start();
|
|
1471
1468
|
await p2p.joinTopic(Buffer.from('bolloon-agent-harness'));
|
|
1472
|
-
|
|
1469
|
+
appendLine(`[chat-p2p-listen] joined topic ✓\n> `);
|
|
1473
1470
|
await new Promise(() => { });
|
|
1474
1471
|
break;
|
|
1475
1472
|
}
|
|
@@ -2149,7 +2146,7 @@ async function main() {
|
|
|
2149
2146
|
console.log = originalLog;
|
|
2150
2147
|
console.info = originalInfo;
|
|
2151
2148
|
process.stdout.write = originalStdoutWrite;
|
|
2152
|
-
startCLI(comm);
|
|
2149
|
+
await startCLI(comm);
|
|
2153
2150
|
}
|
|
2154
2151
|
}
|
|
2155
2152
|
catch (e) {
|
package/dist/llm/pi-ai.js
CHANGED
|
@@ -169,23 +169,11 @@ export class PiAIModel {
|
|
|
169
169
|
}
|
|
170
170
|
let openaiTools;
|
|
171
171
|
if (tools && tools.length > 0) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (manifests.length > 0) {
|
|
178
|
-
const toolPrompt = formatForPrompt(manifests);
|
|
179
|
-
finalMessages = [
|
|
180
|
-
{ role: 'system', content: toolPrompt },
|
|
181
|
-
...messages,
|
|
182
|
-
];
|
|
183
|
-
// Bug 3: 从 manifests 生成原生 OpenAI tools 格式
|
|
184
|
-
openaiTools = formatForOpenAI(manifests);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
catch (err) {
|
|
188
|
-
console.warn('[pi-ai] tool-manifest 加载失败:', err.message?.slice(0, 100));
|
|
172
|
+
// 预格式化的 tools (含参数 schema) → 直接使用
|
|
173
|
+
if (typeof tools[0] === 'object' && tools[0]?.type === 'function') {
|
|
174
|
+
openaiTools = tools;
|
|
175
|
+
const toolDescriptions = tools.map(t => `- ${t.function.name}: ${t.function.description || ''} ${Object.keys(t.function.parameters?.properties || {}).length > 0 ? `(${Object.keys(t.function.parameters.properties).join(', ')})` : ''}`).join('\n');
|
|
176
|
+
finalMessages = [{ role: 'system', content: `可用工具:\n${toolDescriptions}` }, ...messages];
|
|
189
177
|
}
|
|
190
178
|
}
|
|
191
179
|
switch (this.provider) {
|
|
@@ -38,8 +38,16 @@ const TOOL_WHITELIST = new Set([
|
|
|
38
38
|
'safe_deploy',
|
|
39
39
|
// MCP 注册的工具
|
|
40
40
|
'mcp_tool',
|
|
41
|
+
// 2026-07-29: 同步 pi-sdk-tools.ts 注册的全部工具
|
|
42
|
+
'read_directory', 'add_friend_by_id', 'delegate_to_engine',
|
|
43
|
+
'set_persona', 'get_operation_logs', 'park_goal',
|
|
44
|
+
'list_channels', 'list_local_channels',
|
|
41
45
|
]);
|
|
42
46
|
export const gateWhitelist = { gate: 'whitelist', allowed: true };
|
|
47
|
+
/**
|
|
48
|
+
* @deprecated 不再被 TOOL_GATES 调用 (2026-07-29). 保留仅供测试直接引用.
|
|
49
|
+
* 工具准入由 `tools` 参数 (OpenAI 原生格式) 控制, 不再需要第二层白名单.
|
|
50
|
+
*/
|
|
43
51
|
export function checkWhitelist(ctx) {
|
|
44
52
|
if (TOOL_WHITELIST.has(ctx.tool)) {
|
|
45
53
|
return gateWhitelist;
|
|
@@ -217,7 +225,6 @@ export function checkBlacklist(ctx) {
|
|
|
217
225
|
return { gate: 'blacklist', allowed: true };
|
|
218
226
|
}
|
|
219
227
|
const TOOL_GATES = [
|
|
220
|
-
checkWhitelist,
|
|
221
228
|
checkSchema,
|
|
222
229
|
checkChannel,
|
|
223
230
|
checkRate,
|