@bolloon/bolloon-agent 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import * as ed25519 from '@noble/ed25519';
5
5
  import { sha512 } from '@noble/hashes/sha2.js';
6
6
  import * as fs from 'fs/promises';
7
7
  import * as path from 'path';
8
+ import { spawn } from 'child_process';
8
9
  import { documentReader } from './documents/reader.js';
9
10
  import { initMinimax } from './constraints/index.js';
10
11
  import { createAgentSession } from './agents/pi-sdk.js';
@@ -12,8 +13,8 @@ import { createSubAgentManager } from './agents/subagent-manager.js';
12
13
  import { getGlobalSharedContext } from './social/global-shared-context.js';
13
14
  import { createBollharnessIntegration } from './bollharness-integration/index.js';
14
15
  import * as readline from 'readline';
15
- import { LoadingTUI } from './cli/loading-tui.js';
16
- // 启动时自动检查更新已禁用 (改用 --update-check / --update-now 显式触发)
16
+ import { printBanner, renderDashboard, renderDialog, renderUserMessage, renderAgentMessage, renderToolCall, flowConnector, termWidth } from './cli/loading-tui.js';
17
+ // 启动自动检查更新:后台、节流、检测到新版本自动安装(可被 --no-update / BOLLOON_SKIP_UPDATE 关闭)
17
18
  import { createRequire } from 'module';
18
19
  const _require = createRequire(import.meta.url);
19
20
  const _BOLLOON_VERSION = (() => {
@@ -43,14 +44,7 @@ const HIDE_CURSOR = '\x1b[?25l';
43
44
  const SHOW_CURSOR = '\x1b[?25h';
44
45
  const s = {
45
46
  banner: () => {
46
- const verStr = ` v${_BOLLOON_VERSION}`;
47
- const pad = Math.max(0, 39 - 17 - verStr.length);
48
- const spaces = ' '.repeat(pad);
49
- console.log(`\n${CYAN}${BOLD}
50
- ╔═══════════════════════════════════════════╗
51
- ║ ${WHITE}🤖 Bolloon ${CYAN}${verStr}${spaces}║
52
- ║ ${WHITE}P2P AI Document Processor${CYAN} ║
53
- ╚═══════════════════════════════════════════╝${RESET}\n`);
47
+ printBanner(_BOLLOON_VERSION);
54
48
  },
55
49
  step: (num, total, text, status) => {
56
50
  const check = status === 'ok' ? `${GREEN}✓` :
@@ -97,6 +91,7 @@ const s = {
97
91
  },
98
92
  dialog: async (title, promptText) => {
99
93
  return new Promise((resolve) => {
94
+ console.log(renderDialog({ title, prompt: promptText }));
100
95
  const rl = readline.createInterface({
101
96
  input: process.stdin,
102
97
  output: process.stdout
@@ -369,6 +364,30 @@ function startCLI(comm) {
369
364
  }
370
365
  readline.emitKeypressEvents(process.stdin);
371
366
  process.stdout.write(CLEAR_LINE);
367
+ // ── Bolloon Agent 仪表盘 (ASCII 艺术字 + 品牌图标) ──
368
+ printBanner(_BOLLOON_VERSION);
369
+ let peerCount = 0;
370
+ try {
371
+ peerCount = comm.getConnections().length;
372
+ }
373
+ catch { /* */ }
374
+ const llmName = process.env.MINIMAX_API_KEY ? 'MiniMax'
375
+ : process.env.OPENAI_API_KEY ? 'OpenAI'
376
+ : process.env.ANTHROPIC_API_KEY ? 'Anthropic'
377
+ : process.env.DEEPSEEK_API_KEY ? 'DeepSeek'
378
+ : '未配置';
379
+ process.stdout.write(renderDashboard({
380
+ title: '系统状态',
381
+ rows: [
382
+ { label: 'LLM Provider', status: llmName === '未配置' ? 'warn' : 'ok', detail: llmName },
383
+ { label: 'P2P 节点', status: peerCount > 0 ? 'ok' : 'warn', detail: `${peerCount} 个` },
384
+ { label: '输入方式', status: 'info', detail: '底部对话框' },
385
+ ],
386
+ }) + '\n');
387
+ process.stdout.write(renderDialog({
388
+ title: 'Bolloon Agent',
389
+ prompt: '输入消息开始对话 · 输入 help 查看命令',
390
+ }) + '\n');
372
391
  showBottomPrompt();
373
392
  const promptTimer = setInterval(() => {
374
393
  if (isRunning && promptVisible) {
@@ -463,16 +482,43 @@ async function processInput(input, comm) {
463
482
  return;
464
483
  }
465
484
  try {
485
+ // 已发送消息框
486
+ process.stdout.write(renderUserMessage(trimmed) + '\n');
466
487
  const a = await getAgent();
467
- const thinking = setInterval(() => {
468
- if (!isRunning)
469
- return;
470
- process.stdout.write('\r \x1b[33m⟳\x1b[0m 思考中... \x1b[?25l');
471
- }, 80);
472
- const response = await a.prompt(trimmed);
473
- clearInterval(thinking);
474
- process.stdout.write('\r' + ' '.repeat(30) + '\r');
475
- process.stdout.write(`${response}\n`);
488
+ const boxW = Math.min(termWidth() - 2, 76);
489
+ const pending = [];
490
+ let firstEvent = true;
491
+ const clearThinking = () => {
492
+ if (firstEvent) {
493
+ process.stdout.write('\r' + ' '.repeat(30) + '\r');
494
+ firstEvent = false;
495
+ }
496
+ };
497
+ const onStream = (e) => {
498
+ if (e.type === 'step_start') {
499
+ pending.push({ tool: e.tool, args: e.args, t0: Date.now() });
500
+ }
501
+ else if (e.type === 'step_done' || e.type === 'step_error') {
502
+ const p = pending.shift();
503
+ clearThinking();
504
+ // 连接线只在第 2 个及之后的工具框前出现
505
+ if (!firstEvent)
506
+ process.stdout.write(flowConnector(boxW) + '\n');
507
+ process.stdout.write(renderToolCall({
508
+ tool: e.tool ?? p?.tool ?? '?',
509
+ args: p?.args,
510
+ status: e.type === 'step_done' ? 'ok' : 'error',
511
+ output: e.output,
512
+ error: e.error,
513
+ durationMs: p ? Date.now() - p.t0 : undefined,
514
+ width: boxW,
515
+ }) + '\n');
516
+ }
517
+ };
518
+ const response = await a.prompt(trimmed, { onStream });
519
+ clearThinking();
520
+ // 智能体回复框 (圆角)
521
+ process.stdout.write(renderAgentMessage(response) + '\n');
476
522
  }
477
523
  catch (e) {
478
524
  if (!e.message?.includes('ERR_USE_AFTER_CLOSE') && !e.message?.includes('write after end')) {
@@ -1809,70 +1855,66 @@ function printHelp() {
1809
1855
  // ---------------------------------------------------------------------------
1810
1856
  // Entry point
1811
1857
  // ---------------------------------------------------------------------------
1858
+ /**
1859
+ * 以相同参数重新启动当前 Node 进程(用于更新后自动应用新版本)。
1860
+ * 先 detached 拉起新进程,再退出旧进程。
1861
+ */
1862
+ function restartCurrentProcess() {
1863
+ try {
1864
+ const entry = process.argv[1];
1865
+ const child = spawn(process.execPath, [entry, ...process.argv.slice(2)], {
1866
+ stdio: 'inherit',
1867
+ detached: true,
1868
+ env: { ...process.env },
1869
+ });
1870
+ child.unref();
1871
+ }
1872
+ catch {
1873
+ // 拉起失败则退回手动重启
1874
+ }
1875
+ process.exit(0);
1876
+ }
1812
1877
  async function main() {
1813
- let loading = null;
1814
1878
  try {
1815
1879
  const args = parseArgs();
1816
1880
  if (args.help) {
1817
1881
  printHelp();
1818
1882
  process.exit(0);
1819
1883
  }
1820
- // 自动更新已禁用: 启动时不再自动检查/安装更新.
1821
- // 想手动检查: bolloon --update-check
1822
- // 想手动更新: bolloon --update-now [package]
1823
- // 想完全屏蔽 (CI / sandbox): BOLLOON_SKIP_UPDATE=true
1884
+ // 启动自动更新检查(后台执行,不阻塞主流程)。
1885
+ // 检测到新版本会自动安装;安装成功后自动重启以应用新版本。
1886
+ // 可用 --no-update / BOLLOON_SKIP_UPDATE 关闭,
1887
+ // 或用 config.json 的 autoUpdate:false / autoRestart:false / BOLLOON_AUTO_UPDATE=1 控制。
1888
+ // 手动检查: bolloon --update-check
1889
+ // 手动更新: bolloon --update-now [package]
1890
+ if (!args.updateCheck && !args.updateNow) {
1891
+ void (async () => {
1892
+ try {
1893
+ const { checkAndUpdate } = await import('./utils/auto-update.js');
1894
+ await checkAndUpdate({ onUpdated: restartCurrentProcess });
1895
+ }
1896
+ catch {
1897
+ // 自动更新失败不影响主程序启动
1898
+ }
1899
+ })();
1900
+ }
1824
1901
  const mode = args.web ? 'web' : 'cli';
1825
1902
  const isNonInteractive = !!(args.tool || args.prompt);
1826
- const isTuiMode = mode === 'cli' && !isNonInteractive && args.tui;
1827
1903
  const originalLog = console.log;
1828
1904
  const originalInfo = console.info;
1829
1905
  const originalStdoutWrite = process.stdout.write.bind(process.stdout);
1830
1906
  const isSdkLog = (msg) => {
1831
1907
  return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(msg);
1832
1908
  };
1833
- // CLI interactive mode: suppress all startup output, show minimal spinner
1834
1909
  const isCLIInteractive = mode === 'cli' && !isNonInteractive;
1835
- loading = isCLIInteractive ? new LoadingTUI() : null;
1836
- if (loading) {
1837
- loading.setSteps([
1838
- 'LLM provider 检测',
1839
- 'DIAP 身份生成',
1840
- 'DID 发布到 IPFS',
1841
- 'P2P 网络启动',
1842
- 'iroh transport 启动',
1843
- 'Bolloon 上下文 bootstrap',
1844
- 'Web 服务启动',
1845
- ]);
1846
- loading.start('启动中...');
1910
+ if (isCLIInteractive) {
1847
1911
  console.log = () => { };
1848
1912
  console.info = () => { };
1849
1913
  process.stdout.write = () => true;
1850
1914
  }
1851
- else if (isTuiMode) {
1852
- console.log = (...args) => {
1853
- const msg = args.join(' ');
1854
- if (isSdkLog(msg))
1855
- return;
1856
- originalLog.apply(console, args);
1857
- };
1858
- console.info = (...args) => {
1859
- const msg = args.join(' ');
1860
- if (isSdkLog(msg))
1861
- return;
1862
- originalInfo.apply(console, args);
1863
- };
1864
- process.stdout.write = (chunk, ...args) => {
1865
- const msg = String(chunk);
1866
- if (isSdkLog(msg))
1867
- return true;
1868
- return originalStdoutWrite(chunk, ...args);
1869
- };
1870
- }
1871
1915
  if (isNonInteractive) {
1872
1916
  console.error = () => { };
1873
1917
  }
1874
- s.banner();
1875
- s.section('系统初始化');
1876
1918
  const hasOpenAI = !!process.env.OPENAI_API_KEY;
1877
1919
  // 2026-06-15: 修复 — 之前 anthropic 401 是因为 shell env 残留的旧 ANTHROPIC_API_KEY 抢了 provider 选择
1878
1920
  // 用 BOLLOON_LLM_PROVIDER env 显式覆盖, 否则还是按 env hasXxx 顺序自动选
@@ -1896,32 +1938,21 @@ async function main() {
1896
1938
  hasGlm ? 'GLM' :
1897
1939
  hasQwen ? 'Qwen' : null;
1898
1940
  if (llmProvider) {
1899
- s.step(0, 4, `LLM: ${llmProvider}`, 'ok');
1900
- loading?.completeStep(0, 'ok', `LLM: ${llmProvider}`);
1901
1941
  initMinimax({ provider: llmProvider.toLowerCase() });
1902
1942
  }
1903
1943
  else {
1904
- s.step(0, 4, 'LLM: 未配置', 'warn');
1905
- loading?.completeStep(0, 'warn', 'LLM: 未配置');
1906
1944
  if (isNonInteractive) {
1907
1945
  s.warn('未设置任何 LLM API Key,功能受限');
1908
1946
  }
1909
1947
  }
1910
- loading?.startStep(1, '生成 DIAP 身份...');
1911
1948
  const { keypair, did, name } = await bootstrapIdentity();
1912
1949
  agentIdentity = { did, name, publicKey: Buffer.from(keypair.publicKey).toString('hex') };
1913
- loading?.completeStep(1, 'ok', `身份 ${name}`);
1914
- loading?.startStep(2, '发布 DID 到 IPFS...');
1915
1950
  publishDID(name, keypair).then(({ cid, ipnsName }) => {
1916
1951
  if (cid)
1917
1952
  agentIdentity.cid = cid;
1918
1953
  if (ipnsName)
1919
1954
  agentIdentity.ipnsName = ipnsName;
1920
- loading?.completeStep(2, cid ? 'ok' : 'warn', cid ? 'DID 已发布' : 'DID 本地模式');
1921
- }).catch(() => {
1922
- loading?.completeStep(2, 'warn', 'DID 本地模式');
1923
- });
1924
- loading?.startStep(3, '启动 P2P 网络...');
1955
+ }).catch(() => { });
1925
1956
  const verifier = createVerificationManager();
1926
1957
  let comm = null;
1927
1958
  try {
@@ -1933,9 +1964,7 @@ async function main() {
1933
1964
  agentIdentity.peerId = connections[0].publicKey;
1934
1965
  agentIdentity.p2pChannel = 'bolloon-agent-harness';
1935
1966
  }
1936
- loading?.completeStep(3, 'ok', 'P2P 已连接');
1937
1967
  }).catch(err => {
1938
- loading?.completeStep(3, 'warn', 'P2P Web 模式启动失败');
1939
1968
  s.warn(`P2P Web 模式启动失败: ${err.message}`);
1940
1969
  });
1941
1970
  }
@@ -1946,32 +1975,24 @@ async function main() {
1946
1975
  agentIdentity.peerId = connections[0].publicKey;
1947
1976
  agentIdentity.p2pChannel = 'bolloon-agent-harness';
1948
1977
  }
1949
- loading?.completeStep(3, 'ok', 'P2P 已连接');
1950
1978
  }
1951
1979
  }
1952
1980
  catch (err) {
1953
1981
  s.warn(`P2P 初始化失败: ${err.message}`);
1954
1982
  s.warn('将使用无 P2P 模式运行');
1955
- loading?.completeStep(3, 'error', 'P2P 初始化失败');
1956
1983
  }
1957
- loading?.startStep(4, '启动 iroh transport...');
1958
1984
  await bootstrapIroh(keypair, name);
1959
- loading?.completeStep(4, 'ok', 'iroh 已就绪');
1960
1985
  // Bolloon Bootstrap: 启动扫描 + Context 收集 + 挂定时任务
1961
1986
  // 失败静默 (主流程不被阻塞)
1962
- loading?.startStep(5, '正在 bootstrap bolloon 上下文...');
1963
1987
  try {
1964
1988
  const { bootstrapBolloon } = await import('./pi-ecosystem-judgment/human-value-pipeline.js');
1965
1989
  s.info('正在 bootstrap bolloon 上下文...');
1966
1990
  const bs = await bootstrapBolloon({ cwd: process.cwd() });
1967
1991
  s.info(`Bootstrap 完成 (${bs.durationMs}ms, ${bs.errors.length} 个非致命错误)`);
1968
- loading?.completeStep(5, 'ok', `Bootstrap 完成 (${bs.durationMs}ms)`);
1969
1992
  }
1970
1993
  catch (err) {
1971
1994
  s.warn(`Bootstrap 失败 (非致命, 主流程继续): ${err.message}`);
1972
- loading?.completeStep(5, 'warn', 'Bootstrap 失败 (已跳过)');
1973
1995
  }
1974
- s.divider();
1975
1996
  if (mode === 'web') {
1976
1997
  const port = parseInt(process.env.PORT || '54188');
1977
1998
  // 2026-06-16: BOLLOON_DEV_MODE=1 或 selfImprove=true 启动项 → 开发者模式, 启用自迭代 (健康监控+自改总线)
@@ -1981,12 +2002,9 @@ async function main() {
1981
2002
  console.log('[startup] BOLLOON_DEV_MODE=1, 开发者模式: 自迭代已启用');
1982
2003
  }
1983
2004
  const { createWebServer, openBrowser } = await import('./web/server.js');
1984
- loading?.startStep(6, `启动 Web 服务端口 ${port}...`);
1985
- s.info(`启动 Web 服务端口 ${port}...`);
1986
2005
  // 2026-06-24: CLI 默认 loopback bind (安全), LAN 访问需 BOLLOON_HOST=0.0.0.0
1987
2006
  const bindHost = process.env.BOLLOON_HOST;
1988
2007
  const { port: actualPort } = await createWebServer(port, { selfImprove, ...(bindHost ? { host: bindHost } : {}) });
1989
- loading?.completeStep(6, 'ok', `Web 服务 :${actualPort}`);
1990
2008
  const displayHost = bindHost ?? '127.0.0.1';
1991
2009
  s.success(`浏览器已打开 → http://${displayHost}:${actualPort}`);
1992
2010
  openBrowser(`http://${displayHost}:${actualPort}`);
@@ -2005,46 +2023,13 @@ async function main() {
2005
2023
  }
2006
2024
  }
2007
2025
  else {
2008
- // Restore logging and stop loading spinner
2009
- if (loading) {
2010
- console.log = originalLog;
2011
- console.info = originalInfo;
2012
- process.stdout.write = originalStdoutWrite;
2013
- loading.stop(true);
2014
- }
2015
- else {
2016
- // For non-loading TUI CLI mode: apply SDK filtering if needed
2017
- if (isTuiMode) {
2018
- console.log = (...args) => {
2019
- const msg = args.join(' ');
2020
- if (isSdkLog(msg))
2021
- return;
2022
- originalLog.apply(console, args);
2023
- };
2024
- console.info = (...args) => {
2025
- const msg = args.join(' ');
2026
- if (isSdkLog(msg))
2027
- return;
2028
- originalInfo.apply(console, args);
2029
- };
2030
- process.stdout.write = (chunk, ...args) => {
2031
- const msg = String(chunk);
2032
- if (isSdkLog(msg))
2033
- return true;
2034
- return originalStdoutWrite(chunk, ...args);
2035
- };
2036
- }
2037
- else {
2038
- console.log = originalLog;
2039
- console.info = originalInfo;
2040
- process.stdout.write = originalStdoutWrite;
2041
- }
2042
- }
2026
+ console.log = originalLog;
2027
+ console.info = originalInfo;
2028
+ process.stdout.write = originalStdoutWrite;
2043
2029
  startCLI(comm);
2044
2030
  }
2045
2031
  }
2046
2032
  catch (e) {
2047
- loading?.stop(false);
2048
2033
  throw e;
2049
2034
  }
2050
2035
  }