@bolloon/bolloon-agent 0.3.20 → 0.3.21

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.
@@ -63,7 +63,7 @@ export const BOLLOON_BANNER = [
63
63
  `${C_DIM}Bolloon Agent v${BOLLOON_VERSION}${RESET}`,
64
64
  ].join('\n');
65
65
  /** 艺术字全部行 (图标在左, BOLLOON 艺术字在右), 供框内渲染 */
66
- function brandArtLines() {
66
+ export function brandArtLines() {
67
67
  const icon = BOLLOON_ICON.split('\n');
68
68
  const banner = BOLLOON_BANNER.split('\n');
69
69
  const gap = 2;
@@ -107,7 +107,7 @@ function stripAnsi(s) {
107
107
  return s.replace(/\x1b\[[0-9;]*m/g, '');
108
108
  }
109
109
  /** 显示宽度: CJK / 全角符号算 2, 其余算 1 (ANSI 转义不计入) */
110
- function dispWidth(s) {
110
+ export function dispWidth(s) {
111
111
  const clean = s.replace(/\x1b\[[0-9;]*m/g, '');
112
112
  let n = 0;
113
113
  for (const ch of clean) {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ 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, renderToolCall, flowConnector, termWidth } from './cli/loading-tui.js';
18
+ import { printBanner, renderDialog, renderUserMessage, renderAgentMessage, renderToolCall, flowConnector, termWidth, brandArtLines, boxTop, boxRow, boxBottom, dispWidth } from './cli/loading-tui.js';
19
19
  // 启动自动检查更新:后台、节流、检测到新版本自动安装(可被 --no-update / BOLLOON_SKIP_UPDATE 关闭)
20
20
  import { createRequire } from 'module';
21
21
  const _require = createRequire(import.meta.url);
@@ -345,16 +345,8 @@ function rpcErr(code, msg) {
345
345
  }
346
346
  // ---------------------------------------------------------------------------
347
347
  // CLI with persistent bottom prompt
348
- // ---------------------------------------------------------------------------
349
- const SAVE_CURSOR = '\x1b[s';
350
- const RESTORE_CURSOR = '\x1b[u';
351
- const MOVE_TO_BOTTOM = '\x1b[999A';
352
- const HIDE_CURSOR_SEQ = '\x1b[?25l';
353
- const SHOW_CURSOR_SEQ = '\x1b[?25h';
354
- const MOVE_UP_1 = '\x1b[1A';
348
+ // 2026-07-28: 改用 readline.createInterface + replReadline 循环
355
349
  let isRunning = false;
356
- let currentInput = '';
357
- let promptVisible = false;
358
350
  let queueMode = false;
359
351
  const pendingQueue = [];
360
352
  // 底部状态栏数据
@@ -379,40 +371,14 @@ function statusBarLine() {
379
371
  const bar = C_OK + '█'.repeat(filled) + C_DIM + '░'.repeat(barLen - filled) + RESET;
380
372
  return `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ ${C_ACCENT}${dur}${RESET} ${C_DIM}│${RESET} ${bar} ${C_DIM}${cliContextPct}%${RESET}`;
381
373
  }
382
- function showBottomPrompt() {
383
- if (!isRunning)
384
- return;
385
- promptVisible = true;
386
- const tw = process.stdout.columns || 80;
387
- const sep = C_DIM + '─'.repeat(tw) + RESET;
388
- const hint = currentInput.length === 0 && !queueMode
389
- ? ` ${C_DIM}!cmd${RESET} ${C_DIM}/queue${RESET} ${C_DIM}/help${RESET}`
390
- : queueMode ? ` ${C_WARN}[队列${pendingQueue.length}]${RESET}` : '';
391
- const prefix = queueMode ? `${C_WARN}▸${RESET}` : `${C_ACCENT}❯${RESET}`;
392
- process.stdout.write(`\n${sep}\n${statusBarLine()}\n${sep}\n${C_DIM}${'─'.repeat(tw)}${RESET}\n${prefix} ${currentInput}${hint}`);
393
- }
394
- function clearPromptLine() {
395
- promptVisible = false;
396
- }
397
374
  function startCLI(comm) {
398
375
  isRunning = true;
399
- currentInput = '';
400
- promptVisible = false;
401
- try {
402
- process.stdin.setRawMode(true);
403
- }
404
- catch {
405
- }
406
- readline.emitKeypressEvents(process.stdin);
407
- process.stdout.write(CLEAR_LINE);
408
- // ── Bolloon Agent 仪表盘 (ASCII 艺术字 + 品牌图标) ──
409
- printBanner(_BOLLOON_VERSION);
410
376
  let peerCount = 0;
411
377
  try {
412
378
  peerCount = comm.getConnections().length;
413
379
  }
414
380
  catch { /* */ }
415
- // 读取 LLM 模型名 (支持多 provider)
381
+ // 读取 LLM 模型名
416
382
  const providerNames = [
417
383
  ['OPENAI_API_KEY', 'OpenAI'],
418
384
  ['ANTHROPIC_API_KEY', 'Anthropic'],
@@ -427,64 +393,39 @@ function startCLI(comm) {
427
393
  cliModelName = foundProvider ? foundProvider[1] : '未配置';
428
394
  cliAgentName = agentIdentity?.name || 'bolloon';
429
395
  cliStartTime = Date.now();
430
- process.stdout.write(`${C_DIM}Bolloon CLI ready. 输入 !cmd /queue /help${RESET}\n\n`);
431
- showBottomPrompt();
432
- const handleInput = (chunk, key) => {
396
+ process.stdout.write(`${C_DIM}输入 !cmd 执行终端 · /queue 入队 · /help 帮助${RESET}\n\n`);
397
+ // 启动框: logo 左对齐(无状态栏 — 状态栏在输入框上方)
398
+ const art = brandArtLines();
399
+ const maxArt = art.reduce((m, l) => Math.max(m, dispWidth(l)), 0);
400
+ const boxW = Math.min(termWidth() - 2, Math.max(40, maxArt) + 4);
401
+ process.stdout.write(boxTop('Bolloon Agent', boxW) + '\n');
402
+ for (const l of art)
403
+ process.stdout.write(boxRow(l, boxW, 'left') + '\n');
404
+ process.stdout.write(boxBottom(boxW) + '\n\n');
405
+ // readline 输入循环(底部没有独立状态栏)
406
+ replReadline(comm);
407
+ }
408
+ async function replReadline(comm) {
409
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
410
+ const SEP = '\x1b[90m─';
411
+ const RST = '\x1b[0m';
412
+ while (isRunning) {
413
+ const tw = process.stdout.columns || 80;
414
+ const sepLine = SEP.repeat(tw) + RST;
415
+ const prefix = queueMode ? `${C_WARN}▸${RST}` : `${C_ACCENT}❯${RST}`;
416
+ const raw = await new Promise(resolve => rl.question(`\n${sepLine}\n${statusBarLine()}\n${sepLine}\n${prefix} `, resolve));
417
+ const trimmed = raw.trim();
418
+ process.stdout.write(`\n${sepLine}\n\n\n\n\n`);
419
+ if (!trimmed)
420
+ continue;
433
421
  if (!isRunning)
434
- return;
435
- if (!key)
436
- return;
437
- if (key.ctrl && key.name === 'c') {
438
- clearPromptLine();
439
- process.stdout.write(`\n${CYAN}👋 再见!${RESET}\n`);
440
- try {
441
- process.stdin.setRawMode(false);
442
- }
443
- catch { }
444
- process.exit(0);
445
- return;
446
- }
447
- if (key.name === 'return') {
448
- const trimmed = currentInput.trim();
449
- currentInput = '';
450
- clearPromptLine();
451
- if (trimmed) {
452
- process.stdout.write('\n');
453
- processInput(trimmed, comm).then(() => {
454
- if (isRunning)
455
- showBottomPrompt();
456
- });
457
- }
458
- else {
459
- showBottomPrompt();
460
- }
461
- return;
462
- }
463
- if (key.name === 'backspace') {
464
- if (currentInput.length > 0) {
465
- currentInput = currentInput.slice(0, -1);
466
- showBottomPrompt();
467
- }
468
- return;
469
- }
470
- if (key.name === 'escape' || (key.ctrl && key.name === 'u')) {
471
- currentInput = '';
472
- showBottomPrompt();
473
- return;
474
- }
475
- if (key.name === 'tab') {
476
- return;
477
- }
478
- if (key.name && key.name.length === 1) {
479
- currentInput += chunk.toString();
480
- showBottomPrompt();
481
- }
482
- };
483
- process.stdin.on('data', handleInput);
484
- process.on('exit', () => {
485
- isRunning = false;
486
- process.stdout.write(SHOW_CURSOR_SEQ);
487
- });
422
+ break;
423
+ await processInput(trimmed, comm);
424
+ }
425
+ rl.close();
426
+ process.stdout.write(`\n${CYAN}👋 再见!${RESET}\n`);
427
+ process.stdin.destroy();
428
+ comm.stop();
488
429
  }
489
430
  async function processInput(input, comm) {
490
431
  const trimmed = input.trim();
@@ -546,11 +487,8 @@ async function processInput(input, comm) {
546
487
  return;
547
488
  }
548
489
  if (trimmed === '退出' || trimmed === 'exit' || trimmed === 'quit') {
549
- clearPromptLine();
550
- process.stdout.write(`${CYAN}👋 再见!${RESET}\n`);
490
+ process.stdout.write(`\n${CYAN}👋 再见!${RESET}\n`);
551
491
  isRunning = false;
552
- process.stdin.destroy();
553
- comm.stop();
554
492
  return;
555
493
  }
556
494
  if (trimmed.toLowerCase() === 'peers') {