@bolloon/bolloon-agent 0.3.34 → 0.3.36
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/pi-sdk-tools.js +34 -5
- package/dist/agents/pi-sdk.js +98 -12
- package/dist/bootstrap/context-manager.js +166 -0
- package/dist/bootstrap/memory-compressor.js +26 -16
- package/dist/bootstrap/snip-collapse.js +39 -35
- package/dist/cli/ink-app.js +44 -1
- package/dist/cli/loading-tui.js +4 -3
- package/dist/cli/mention-data.js +23 -0
- package/dist/cli-entry.js +113 -6
- package/dist/index.js +494 -18
- package/dist/web/client.js +33 -0
- package/dist/web/index.html +11 -11
- package/dist/web/server.js +26 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -354,7 +354,6 @@ function rpcErr(code, msg) {
|
|
|
354
354
|
// CLI with persistent bottom prompt
|
|
355
355
|
// 2026-07-28: 改用 readline.createInterface + replReadline 循环
|
|
356
356
|
let isRunning = false;
|
|
357
|
-
let cliContextPct = 0;
|
|
358
357
|
let queueMode = false;
|
|
359
358
|
const pendingQueue = [];
|
|
360
359
|
let cliStartTime = 0;
|
|
@@ -371,20 +370,53 @@ function fmtDuration(ms) {
|
|
|
371
370
|
const h = Math.floor(m / 60);
|
|
372
371
|
return `${h}h ${m % 60}m`;
|
|
373
372
|
}
|
|
374
|
-
/**
|
|
373
|
+
/** 2026-08-06: 从 ContextManager 读上下文用量 (CLI 状态栏数据源, 失败退化 0/1M) */
|
|
374
|
+
function getCliCtxUsage() {
|
|
375
|
+
try {
|
|
376
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
377
|
+
const cm = require('./bootstrap/context-manager.js').getContextManager();
|
|
378
|
+
const u = cm.getUsage();
|
|
379
|
+
return {
|
|
380
|
+
// 保留浮点 (0-100), 由 buildContextBar 格式化 — round 会让 <0.5% 全变 0, 状态栏像死代码
|
|
381
|
+
pct: Math.min(100, u.pct * 100),
|
|
382
|
+
usedTokens: u.usedTokens,
|
|
383
|
+
maxTokens: u.maxTokens,
|
|
384
|
+
stage: u.stage,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return { pct: 0, usedTokens: 0, maxTokens: 1_000_000, stage: 'normal' };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/** 上下文进度条: 320k/1M │ [██████░░░░] 32% (bolloon 色系: #c4d640 主色) */
|
|
392
|
+
function buildContextBar(usage) {
|
|
393
|
+
const barLen = 10;
|
|
394
|
+
const filled = Math.min(barLen, Math.max(0, Math.round((usage.pct / 100) * barLen)));
|
|
395
|
+
const barColor = usage.stage === 'warning' || usage.stage === 'compressing' ? C_WARN : C_ACCENT;
|
|
396
|
+
const bar = `${C_DIM}[${RESET}${barColor}${'█'.repeat(filled)}${RESET}${C_DIM}${'░'.repeat(barLen - filled)}${RESET}${C_DIM}]${RESET}`;
|
|
397
|
+
const fmtK = (n) => (n >= 1_000_000 ? (n % 1_000_000 === 0 ? `${n / 1_000_000}M` : `${(n / 1_000_000).toFixed(1)}M`) : n >= 1000 ? `${Math.round(n / 1000)}k` : String(n));
|
|
398
|
+
const usageTxt = `${C_TEXT}${fmtK(usage.usedTokens)}/${fmtK(usage.maxTokens)}${RESET}`;
|
|
399
|
+
// 百分比: >=10% 整数, >=1% 一位小数, <1% 两位小数 (1M 窗口下小 token 数也可见变化)
|
|
400
|
+
const pctTxt = usage.pct >= 10 ? `${Math.round(usage.pct)}%` : usage.pct >= 1 ? `${usage.pct.toFixed(1)}%` : `${usage.pct.toFixed(2)}%`;
|
|
401
|
+
let suffix = '';
|
|
402
|
+
if (usage.stage === 'warning')
|
|
403
|
+
suffix = ` ${C_WARN}⚠ 即将压缩${RESET}`;
|
|
404
|
+
else if (usage.stage === 'compressing')
|
|
405
|
+
suffix = ` ${C_WARN}🗜️ 压缩中...${RESET}`;
|
|
406
|
+
else if (usage.stage === 'compressed')
|
|
407
|
+
suffix = ` ${C_OK}✓ 已压缩${RESET}`;
|
|
408
|
+
return `${usageTxt} ${C_DIM}│${RESET} ${bar} ${barColor}${pctTxt}${RESET}${suffix}`;
|
|
409
|
+
}
|
|
410
|
+
/** 状态栏: 模型 │ 当前智能体 (含 channel) │ ⏱ 时间 │ 320k/1M │ [██████░░░░] 32% (bolloon 色系) */
|
|
375
411
|
function getStatus() {
|
|
376
|
-
const
|
|
377
|
-
const filled = Math.round((cliContextPct / 100) * barLen);
|
|
378
|
-
const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
|
|
412
|
+
const usage = getCliCtxUsage();
|
|
379
413
|
const agentPart = cliActiveChannelId ? `${cliAgentName} ${C_DIM}(ch:${cliActiveChannelId.slice(0, 10)})${RESET}` : cliAgentName;
|
|
380
|
-
return `${C_ACCENT}${cliModelName}
|
|
414
|
+
return `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${agentPart} ${C_DIM}│${RESET} ⏱ ${C_TEXT}${fmtDuration(Date.now() - cliStartTime)}${RESET}${C_DIM} │${RESET} ${buildContextBar(usage)}`;
|
|
381
415
|
}
|
|
382
416
|
function statusBarLine() {
|
|
383
417
|
const dur = cliStartTime ? fmtDuration(Date.now() - cliStartTime) : '0s';
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
const bar = C_OK + '█'.repeat(filled) + C_DIM + '░'.repeat(barLen - filled) + RESET;
|
|
387
|
-
return `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ ${C_ACCENT}${dur}${RESET} ${C_DIM}│${RESET} ${bar} ${C_DIM}${cliContextPct}%${RESET}`;
|
|
418
|
+
const usage = getCliCtxUsage();
|
|
419
|
+
return `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ ${C_ACCENT}${dur}${RESET} ${C_DIM}│${RESET} ${buildContextBar(usage)}`;
|
|
388
420
|
}
|
|
389
421
|
async function startCLI(comm) {
|
|
390
422
|
isRunning = true;
|
|
@@ -436,7 +468,8 @@ async function startCLI(comm) {
|
|
|
436
468
|
/* 无 channels/active 记录时保持默认 */
|
|
437
469
|
}
|
|
438
470
|
// 进入 Ink TUI 输入循环
|
|
439
|
-
|
|
471
|
+
// 2026-08-06: 初始状态栏也带上下文显示 (0/1M │ [░░░░░░░░░░] 0%)
|
|
472
|
+
const initialStatus = `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ 0s${C_DIM} │${RESET} ${buildContextBar(getCliCtxUsage())}`;
|
|
440
473
|
startInk((text) => { processInput(text, comm); }, initialStatus, getStatus);
|
|
441
474
|
// Wait on a promise that resolves on Ctrl+C / 双击 Esc
|
|
442
475
|
// (ink-app 的 requestExit 调 __inkRequestExit → resolve, 清理后 process.exit)
|
|
@@ -548,12 +581,438 @@ async function processInput(input, comm) {
|
|
|
548
581
|
appendLine(`${C_WARN}[队列 ${pendingQueue.length}]${RESET} 已入队, 执行完当前后自动运行`);
|
|
549
582
|
return;
|
|
550
583
|
}
|
|
584
|
+
// ==================== 2026-08-06: 系统命令组 (/model /now /ipfs /memory ...) ====================
|
|
585
|
+
const cmd = trimmed.toLowerCase();
|
|
586
|
+
// /model /login — 模型供应商选择器 (ink 交互渲染, 复用 MentionPopup)
|
|
587
|
+
if (cmd === '/model' || cmd === '/login') {
|
|
588
|
+
try {
|
|
589
|
+
const { llmConfigStore, PROVIDER_INFO } = await import('./llm/config-store.js');
|
|
590
|
+
await llmConfigStore.initialize();
|
|
591
|
+
const config = await llmConfigStore.getConfig();
|
|
592
|
+
const items = Object.entries(config.providers).map(([name, p]) => ({
|
|
593
|
+
kind: 'command',
|
|
594
|
+
label: name,
|
|
595
|
+
hint: `${String(PROVIDER_INFO[name]?.name || '').padEnd(14)} ${p.apiKey ? '🔑' : p.requiresApiKey ? '⚠ 无key' : ''} ${p.model || ''}`,
|
|
596
|
+
insert: name,
|
|
597
|
+
}));
|
|
598
|
+
globalThis.__inkOpenPicker?.(items, '选择模型供应商 (↑↓ 选择 · Enter 确认 · Esc 取消)', async (it) => {
|
|
599
|
+
try {
|
|
600
|
+
await llmConfigStore.setActiveProvider(it.label);
|
|
601
|
+
const active = await llmConfigStore.getActiveProvider();
|
|
602
|
+
appendLine(`${C_OK}✓ 已切换到 ${it.label} (${String(PROVIDER_INFO[it.label]?.name || '')})${RESET}`);
|
|
603
|
+
appendLine(`${C_DIM} 当前模型: ${config.providers[it.label]?.model || '默认'}${RESET}`);
|
|
604
|
+
}
|
|
605
|
+
catch (e) {
|
|
606
|
+
appendLine(`${C_ERROR}✗ 切换失败: ${String(e.message || e).slice(0, 150)}${RESET}`);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
catch (e) {
|
|
611
|
+
appendLine(`${C_ERROR}/model 失败: ${String(e.message || e).slice(0, 150)}${RESET}`);
|
|
612
|
+
}
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
// /logout — 显示当前供应商 (减法: 登出 = 查看当前, 切换走 /model)
|
|
616
|
+
if (cmd === '/logout') {
|
|
617
|
+
try {
|
|
618
|
+
const { llmConfigStore, PROVIDER_INFO } = await import('./llm/config-store.js');
|
|
619
|
+
await llmConfigStore.initialize();
|
|
620
|
+
const active = await llmConfigStore.getActiveProvider();
|
|
621
|
+
const cfg = await llmConfigStore.getActiveProviderConfig();
|
|
622
|
+
appendLine(`${C_DIM}当前供应商:${RESET} ${C_ACCENT}${active}${RESET} (${String(PROVIDER_INFO[active]?.name || '')})`);
|
|
623
|
+
appendLine(`${C_DIM} 模型: ${cfg?.model || '默认'}${RESET}`);
|
|
624
|
+
appendLine(`${C_DIM} 切换: /model 打开选择器${RESET}`);
|
|
625
|
+
}
|
|
626
|
+
catch { /* 静默 */ }
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
// /now — 当前状态总览
|
|
630
|
+
if (cmd === '/now') {
|
|
631
|
+
try {
|
|
632
|
+
const cm = require('./bootstrap/context-manager.js').getContextManager();
|
|
633
|
+
const usage = cm.getUsage();
|
|
634
|
+
appendLine(`${C_ACCENT}● 当前状态${RESET}`);
|
|
635
|
+
appendLine(` ${C_DIM}智能体:${RESET} ${cliAgentName} ${cliActiveChannelId ? `(${C_DIM}ch:${cliActiveChannelId.slice(0, 12)}${RESET})` : ''}`);
|
|
636
|
+
appendLine(` ${C_DIM}运行:${RESET} ${fmtDuration(Date.now() - cliStartTime)}`);
|
|
637
|
+
appendLine(` ${C_DIM}上下文:${RESET} ${(usage.usedTokens / 1000).toFixed(0)}k / ${(usage.maxTokens / 1000).toFixed(0)}k tokens (${Math.round(usage.pct * 100)}%)${usage.stage === 'warning' ? ` ${C_WARN}⚠ 即将压缩${RESET}` : ''}`);
|
|
638
|
+
const a = await getAgent();
|
|
639
|
+
appendLine(` ${C_DIM}消息:${RESET} ${a.messageHistory?.length ?? 0} 条`);
|
|
640
|
+
}
|
|
641
|
+
catch { /* 静默 */ }
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
// /tools — 可用工具列表
|
|
645
|
+
if (cmd === '/tools') {
|
|
646
|
+
try {
|
|
647
|
+
const a = await getAgent();
|
|
648
|
+
const defs = (a.getToolDefinitions?.() ?? []);
|
|
649
|
+
const names = Array.isArray(defs) ? defs.map((d) => d.name || d.function?.name).filter(Boolean) : Object.keys(defs || {});
|
|
650
|
+
appendLine(`${C_ACCENT}可用工具 (${names.length}):${RESET}`);
|
|
651
|
+
for (const n of names.slice(0, 40))
|
|
652
|
+
appendLine(` ${C_DIM}·${RESET} ${n}`);
|
|
653
|
+
if (names.length > 40)
|
|
654
|
+
appendLine(` ${C_DIM}... 共 ${names.length} 个${RESET}`);
|
|
655
|
+
}
|
|
656
|
+
catch { /* 静默 */ }
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
// /session — 当前会话信息
|
|
660
|
+
if (cmd === '/session') {
|
|
661
|
+
try {
|
|
662
|
+
const a = await getAgent();
|
|
663
|
+
const h = a.messageHistory ?? [];
|
|
664
|
+
appendLine(`${C_ACCENT}会话:${RESET}`);
|
|
665
|
+
appendLine(` ${C_DIM}channel:${RESET} ${a.currentChannelId || '—'}`);
|
|
666
|
+
appendLine(` ${C_DIM}agent:${RESET} ${a.currentAgentId || '—'}`);
|
|
667
|
+
appendLine(` ${C_DIM}消息:${RESET} ${h.length} 条 (${h.length > 15 ? `${h.length - 15} 条已压缩` : '窗口内'})`);
|
|
668
|
+
}
|
|
669
|
+
catch { /* 静默 */ }
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
// /memory — 记忆摘要 (memory-compressor 落盘文件)
|
|
673
|
+
if (cmd === '/memory') {
|
|
674
|
+
try {
|
|
675
|
+
const { getMemoryDir } = await import('./bootstrap/memory-compressor.js');
|
|
676
|
+
const { readdir, readFile } = await import('fs/promises');
|
|
677
|
+
const { join } = await import('path');
|
|
678
|
+
const dir = getMemoryDir(cliAgentName === 'bolloon' ? 'agent' : cliAgentName);
|
|
679
|
+
const files = (await readdir(join(dir, 'sessions')).catch(() => [])).filter((f) => f.endsWith('.summary.md'));
|
|
680
|
+
appendLine(`${C_ACCENT}记忆摘要 (${files.length} 个 session):${RESET}`);
|
|
681
|
+
for (const f of files.slice(-5)) {
|
|
682
|
+
try {
|
|
683
|
+
const raw = await readFile(join(dir, 'sessions', f), 'utf-8');
|
|
684
|
+
const tail = raw.trim().split('\n').slice(-6).join(' ').slice(0, 180);
|
|
685
|
+
appendLine(` ${C_DIM}·${RESET} ${f.replace('.summary.md', '').slice(-30)}`);
|
|
686
|
+
appendLine(` ${C_DIM}${tail}${RESET}`);
|
|
687
|
+
}
|
|
688
|
+
catch { /* 跳过 */ }
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch { /* 静默 */ }
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
// /resume — 恢复: 最近记忆摘要 + 进行中计划
|
|
695
|
+
if (cmd === '/resume' || cmd.startsWith('/resume ')) {
|
|
696
|
+
try {
|
|
697
|
+
const { getMemoryDir, getSessionSummaryPath } = await import('./bootstrap/memory-compressor.js');
|
|
698
|
+
const { readFile, readdir } = await import('fs/promises');
|
|
699
|
+
const { join } = await import('path');
|
|
700
|
+
const dir = getMemoryDir(cliAgentName === 'bolloon' ? 'agent' : cliAgentName);
|
|
701
|
+
const files = (await readdir(join(dir, 'sessions')).catch(() => [])).filter((f) => f.endsWith('.summary.md'));
|
|
702
|
+
appendLine(`${C_ACCENT}↻ 恢复上下文:${RESET}`);
|
|
703
|
+
if (files.length > 0) {
|
|
704
|
+
const f = files[files.length - 1];
|
|
705
|
+
const raw = await readFile(join(dir, 'sessions', f), 'utf-8');
|
|
706
|
+
const block = raw.trim().split('\n').slice(-12).join('\n').slice(-1200);
|
|
707
|
+
appendLine(` ${C_DIM}最近记忆 (${f.slice(0, 24)}...):${RESET}`);
|
|
708
|
+
for (const line of block.split('\n').slice(-8))
|
|
709
|
+
appendLine(` ${C_DIM}${line.slice(0, 100)}${RESET}`);
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
appendLine(` ${C_DIM}暂无记忆摘要${RESET}`);
|
|
713
|
+
}
|
|
714
|
+
const { listActivePlans } = await import('./agents/plan-store.js');
|
|
715
|
+
const plans = await listActivePlans();
|
|
716
|
+
if (plans.length > 0) {
|
|
717
|
+
appendLine(` ${C_DIM}进行中计划 (${plans.length}):${RESET}`);
|
|
718
|
+
for (const p of plans.slice(0, 3))
|
|
719
|
+
appendLine(` ${C_ACCENT}·${RESET} ${p.goal || p.planId} ${C_DIM}${p.status || ''}${RESET}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
catch { /* 静默 */ }
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
// /goal — 进行中的目标/计划
|
|
726
|
+
if (cmd === '/goal') {
|
|
727
|
+
try {
|
|
728
|
+
const { listActivePlans, planToContext } = await import('./agents/plan-store.js');
|
|
729
|
+
const plans = await listActivePlans();
|
|
730
|
+
appendLine(`${C_ACCENT}目标 (${plans.length} 个进行中):${RESET}`);
|
|
731
|
+
if (plans.length === 0) {
|
|
732
|
+
appendLine(` ${C_DIM}无进行中计划 — 可用 /plan 创建${RESET}`);
|
|
733
|
+
}
|
|
734
|
+
for (const p of plans.slice(0, 5)) {
|
|
735
|
+
appendLine(` ${C_ACCENT}●${RESET} ${p.goal || p.planId} ${C_DIM}[${p.status || 'active'}]${RESET}`);
|
|
736
|
+
const steps = Array.isArray(p.steps) ? p.steps : [];
|
|
737
|
+
const done = steps.filter((s) => s.done || s.status === 'done').length;
|
|
738
|
+
if (steps.length > 0)
|
|
739
|
+
appendLine(` ${C_DIM}${done}/${steps.length} 步完成${RESET}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch { /* 静默 */ }
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
// /skill — 技能候选 (skill-writer 落盘)
|
|
746
|
+
if (cmd === '/skill') {
|
|
747
|
+
try {
|
|
748
|
+
const { listSkillCandidates } = await import('./agents/skill-writer.js');
|
|
749
|
+
const cands = await listSkillCandidates();
|
|
750
|
+
appendLine(`${C_ACCENT}技能候选 (${cands.length}):${RESET}`);
|
|
751
|
+
if (cands.length === 0) {
|
|
752
|
+
appendLine(` ${C_DIM}无候选 — 连续成功工具调用 ≥2 自动生成${RESET}`);
|
|
753
|
+
}
|
|
754
|
+
for (const c of cands.slice(0, 8)) {
|
|
755
|
+
appendLine(` ${C_DIM}·${RESET} ${c.name || '?'} ${C_DIM}(${c.source || ''})${RESET}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch { /* 静默 */ }
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
// /mcp — MCP 插件/工具列表
|
|
762
|
+
if (cmd === '/mcp') {
|
|
763
|
+
try {
|
|
764
|
+
const { readFile } = await import('fs/promises');
|
|
765
|
+
const { join } = await import('path');
|
|
766
|
+
let servers = {};
|
|
767
|
+
try {
|
|
768
|
+
servers = JSON.parse(await readFile(join(process.env.HOME || '/tmp', '.mcp.json'), 'utf-8')).mcpServers || {};
|
|
769
|
+
}
|
|
770
|
+
catch { /* 无 */ }
|
|
771
|
+
appendLine(`${C_ACCENT}MCP 服务器 (${Object.keys(servers).length}):${RESET}`);
|
|
772
|
+
if (Object.keys(servers).length === 0) {
|
|
773
|
+
appendLine(` ${C_DIM}无 (~/.mcp.json 未配置)${RESET}`);
|
|
774
|
+
}
|
|
775
|
+
for (const [name, s] of Object.entries(servers)) {
|
|
776
|
+
const cmdStr = s?.command || '';
|
|
777
|
+
appendLine(` ${C_DIM}·${RESET} ${name} ${C_DIM}(${String(cmdStr).slice(0, 40)})${RESET}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch { /* 静默 */ }
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
// /agent — 当前智能体身份
|
|
784
|
+
if (cmd === '/agent') {
|
|
785
|
+
try {
|
|
786
|
+
const a = await getAgent();
|
|
787
|
+
appendLine(`${C_ACCENT}智能体:${RESET}`);
|
|
788
|
+
appendLine(` ${C_DIM}名称:${RESET} ${cliAgentName}`);
|
|
789
|
+
appendLine(` ${C_DIM}agentId:${RESET} ${a.currentAgentId || '—'}`);
|
|
790
|
+
appendLine(` ${C_DIM}channel:${RESET} ${cliActiveChannelId || '—'}`);
|
|
791
|
+
}
|
|
792
|
+
catch { /* 静默 */ }
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
// /did — DID 身份
|
|
796
|
+
if (cmd === '/did') {
|
|
797
|
+
try {
|
|
798
|
+
const { loadOrCreateAgentIdentity } = await import('./agents/agent-identity.js');
|
|
799
|
+
const identity = loadOrCreateAgentIdentity(cliAgentName === 'bolloon' ? 'default-agent' : cliAgentName);
|
|
800
|
+
appendLine(`${C_ACCENT}DID 身份:${RESET}`);
|
|
801
|
+
appendLine(` ${C_DIM}did:${RESET} ${identity.did}`);
|
|
802
|
+
appendLine(` ${C_DIM}publicKey:${RESET} ${identity.publicKey?.slice(0, 32) || '—'}...`);
|
|
803
|
+
appendLine(` ${C_DIM}发布:${RESET} 可用 publish_did 工具发布到 IPFS+IPNS`);
|
|
804
|
+
}
|
|
805
|
+
catch (e) {
|
|
806
|
+
appendLine(`${C_ERROR}/did 失败: ${String(e.message || e).slice(0, 120)}${RESET}`);
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
// /ipfs — Kubo 状态
|
|
811
|
+
if (cmd === '/ipfs') {
|
|
812
|
+
try {
|
|
813
|
+
const { kuboApi } = await import('./agents/pi-sdk-tools.js');
|
|
814
|
+
const id = await kuboApi('/api/v0/id');
|
|
815
|
+
const peers = await kuboApi('/api/v0/swarm/peers');
|
|
816
|
+
const pins = await kuboApi('/api/v0/pin/ls?type=recursive');
|
|
817
|
+
appendLine(`${C_ACCENT}IPFS (Kubo):${RESET}`);
|
|
818
|
+
appendLine(` ${C_DIM}节点:${RESET} ${String(id.ID || '').slice(0, 24)}...`);
|
|
819
|
+
appendLine(` ${C_DIM}版本:${RESET} ${id.AgentVersion || ''}`);
|
|
820
|
+
appendLine(` ${C_DIM}peers:${RESET} ${peers?.Peers?.length ?? 0}`);
|
|
821
|
+
appendLine(` ${C_DIM}pins:${RESET} ${pins?.Keys ? Object.keys(pins.Keys).length : 0}`);
|
|
822
|
+
}
|
|
823
|
+
catch (e) {
|
|
824
|
+
appendLine(`${C_ERROR}/ipfs 失败: ${String(e.message || e).slice(0, 120)}${RESET}`);
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
// /ipns — IPNS 状态 (keys + self 解析)
|
|
829
|
+
if (cmd === '/ipns') {
|
|
830
|
+
try {
|
|
831
|
+
const { kuboApi } = await import('./agents/pi-sdk-tools.js');
|
|
832
|
+
const keys = await kuboApi('/api/v0/key/list');
|
|
833
|
+
const keyList = keys?.Keys || [];
|
|
834
|
+
appendLine(`${C_ACCENT}IPNS keys (${keyList.length}):${RESET}`);
|
|
835
|
+
for (const k of keyList.slice(0, 10)) {
|
|
836
|
+
appendLine(` ${C_DIM}·${RESET} ${k.Name} ${C_DIM}${String(k.Id).slice(0, 20)}...${RESET}`);
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
const r = await kuboApi('/api/v0/name/resolve?arg=ui-deploy&recursive=true&nocache=true', undefined, 15000);
|
|
840
|
+
appendLine(` ${C_DIM}ui-deploy →${RESET} ${r.Path || ''}`);
|
|
841
|
+
}
|
|
842
|
+
catch { /* 无 ui-deploy */ }
|
|
843
|
+
}
|
|
844
|
+
catch (e) {
|
|
845
|
+
appendLine(`${C_ERROR}/ipns 失败: ${String(e.message || e).slice(0, 120)}${RESET}`);
|
|
846
|
+
}
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
// /wallet — 钱包状态
|
|
850
|
+
if (cmd === '/wallet') {
|
|
851
|
+
try {
|
|
852
|
+
const { readFile } = await import('fs/promises');
|
|
853
|
+
const { join } = await import('path');
|
|
854
|
+
let wallets = [];
|
|
855
|
+
try {
|
|
856
|
+
wallets = JSON.parse(await readFile(join(process.env.HOME || '/tmp', '.bolloon', 'wallets.json'), 'utf-8'));
|
|
857
|
+
}
|
|
858
|
+
catch { /* 无 */ }
|
|
859
|
+
appendLine(`${C_ACCENT}钱包 (${Array.isArray(wallets) ? wallets.length : 0}):${RESET}`);
|
|
860
|
+
if (!Array.isArray(wallets) || wallets.length === 0) {
|
|
861
|
+
appendLine(` ${C_DIM}无 — 可用 wallet_create 工具创建 EVM 钱包${RESET}`);
|
|
862
|
+
}
|
|
863
|
+
for (const w of (Array.isArray(wallets) ? wallets : []).slice(0, 5)) {
|
|
864
|
+
appendLine(` ${C_DIM}·${RESET} ${w.name || w.address?.slice(0, 12) || '?'} ${C_DIM}${String(w.address || '').slice(0, 16)}...${RESET}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
catch { /* 静默 */ }
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
// /email — 邮件配置状态
|
|
871
|
+
if (cmd === '/email') {
|
|
872
|
+
try {
|
|
873
|
+
const { readFile } = await import('fs/promises');
|
|
874
|
+
const { join } = await import('path');
|
|
875
|
+
let cfg = null;
|
|
876
|
+
try {
|
|
877
|
+
cfg = JSON.parse(await readFile(join(process.env.HOME || '/tmp', '.bolloon', 'smtp.json'), 'utf-8'));
|
|
878
|
+
}
|
|
879
|
+
catch { /* 无 */ }
|
|
880
|
+
appendLine(`${C_ACCENT}邮件 (SMTP):${RESET}`);
|
|
881
|
+
if (!cfg) {
|
|
882
|
+
appendLine(` ${C_DIM}未配置 smtp.json — 发件人: 天墟星剑 <2844169590@qq.com>${RESET}`);
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
appendLine(` ${C_DIM}host:${RESET} ${cfg.host || 'smtp.qq.com'}`);
|
|
886
|
+
appendLine(` ${C_DIM}发件人:${RESET} ${cfg.from || cfg.user || '—'}`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
catch { /* 静默 */ }
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
// /loop — 当前循环状态 (消息数 + token 估算)
|
|
893
|
+
if (cmd === '/loop') {
|
|
894
|
+
try {
|
|
895
|
+
const a = await getAgent();
|
|
896
|
+
const h = a.messageHistory ?? [];
|
|
897
|
+
let tokens = 0;
|
|
898
|
+
try {
|
|
899
|
+
const { estimateTokens } = require('./context-compaction/index.js');
|
|
900
|
+
tokens = estimateTokens(h);
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
tokens = Math.round(JSON.stringify(h).length / 4);
|
|
904
|
+
}
|
|
905
|
+
appendLine(`${C_ACCENT}Loop 状态:${RESET}`);
|
|
906
|
+
appendLine(` ${C_DIM}消息:${RESET} ${h.length} 条 (窗口 15, ${Math.max(0, h.length - 15)} 条早期压缩)`);
|
|
907
|
+
appendLine(` ${C_DIM}token:${RESET} ${(tokens / 1000).toFixed(1)}k / 1M (${((tokens / 1_000_000) * 100).toFixed(2)}%)`);
|
|
908
|
+
}
|
|
909
|
+
catch { /* 静默 */ }
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
// /judgement — 判断力列表
|
|
913
|
+
if (cmd === '/judgement' || cmd === '/judgments') {
|
|
914
|
+
try {
|
|
915
|
+
const { loadAllJudgments } = await import('./pi-ecosystem-judgment/human-value-store.js');
|
|
916
|
+
const all = await loadAllJudgments().catch(() => []);
|
|
917
|
+
appendLine(`${C_ACCENT}判断力 (${all.length} 条):${RESET}`);
|
|
918
|
+
for (const j of all.slice(0, 8)) {
|
|
919
|
+
appendLine(` ${C_DIM}·${RESET} ${String(j.decision || '').slice(0, 70)}`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
catch { /* 静默 */ }
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
// /insight — Context OS 08-Insights 资产
|
|
926
|
+
if (cmd === '/insight') {
|
|
927
|
+
try {
|
|
928
|
+
const { readContextAssets, readAssetBody } = await import('./bootstrap/context-os.js');
|
|
929
|
+
const listings = await readContextAssets('08-Insights');
|
|
930
|
+
const files = listings[0]?.files || [];
|
|
931
|
+
appendLine(`${C_ACCENT}洞察 (${files.length} 篇):${RESET}`);
|
|
932
|
+
if (files.length === 0) {
|
|
933
|
+
appendLine(` ${C_DIM}无 — 价值点路由自动沉淀 insight 到 08-Insights${RESET}`);
|
|
934
|
+
}
|
|
935
|
+
for (const f of files.slice(0, 6)) {
|
|
936
|
+
const body = await readAssetBody('08-Insights', f.file).catch(() => null);
|
|
937
|
+
const firstLine = (body?.body || '').split('\n').filter(l => l.trim() && !l.startsWith('---')).slice(0, 2).join(' ').slice(0, 90);
|
|
938
|
+
appendLine(` ${C_ACCENT}·${RESET} ${f.title} ${C_DIM}${firstLine ? '— ' + firstLine : ''}${RESET}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
catch { /* 静默 */ }
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
// /wiki — wiki 状态
|
|
945
|
+
if (cmd === '/wiki') {
|
|
946
|
+
try {
|
|
947
|
+
const { readFile } = await import('fs/promises');
|
|
948
|
+
const { join } = await import('path');
|
|
949
|
+
const root = process.cwd();
|
|
950
|
+
const statusPath = join(root, 'docs', 'wiki', 'current-status.md');
|
|
951
|
+
const raw = await readFile(statusPath, 'utf-8');
|
|
952
|
+
const title = raw.match(/^title:\s*(.+)$/m)?.[1] || 'current-status';
|
|
953
|
+
const confirmed = raw.match(/^last_confirmed:\s*(.+)$/m)?.[1] || '?';
|
|
954
|
+
const supported = (raw.match(/\|\|+/g) || []).length;
|
|
955
|
+
appendLine(`${C_ACCENT}Wiki:${RESET} ${title}`);
|
|
956
|
+
appendLine(` ${C_DIM}last_confirmed:${RESET} ${confirmed}`);
|
|
957
|
+
appendLine(` ${C_DIM}已支持条目:${RESET} ${supported}`);
|
|
958
|
+
appendLine(` ${C_DIM}位置:${RESET} docs/wiki/ (wiki-first 范式)`);
|
|
959
|
+
}
|
|
960
|
+
catch { /* 静默 */ }
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
// /dream — 随机灵感 (从 Insights + Knowledge 资产随机取一条)
|
|
964
|
+
if (cmd === '/dream') {
|
|
965
|
+
try {
|
|
966
|
+
const { readContextAssets, readAssetBody } = await import('./bootstrap/context-os.js');
|
|
967
|
+
const layers = ['08-Insights', '07-Knowledge', '12-Analysis'];
|
|
968
|
+
const pool = [];
|
|
969
|
+
for (const layer of layers) {
|
|
970
|
+
const listings = await readContextAssets(layer);
|
|
971
|
+
for (const f of (listings[0]?.files || []).slice(0, 5)) {
|
|
972
|
+
const body = await readAssetBody(layer, f.file).catch(() => null);
|
|
973
|
+
const lines = (body?.body || '').split('\n').filter(l => l.trim() && !l.startsWith('---') && !l.startsWith('#') && !l.startsWith('>') && !l.startsWith('未来'));
|
|
974
|
+
if (lines[0])
|
|
975
|
+
pool.push(lines[0].trim().slice(0, 100));
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (pool.length === 0) {
|
|
979
|
+
appendLine(`${C_DIM}🌙 梦境空空 — 多对话让记忆沉淀出洞察后, /dream 就有素材了${RESET}`);
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
const pick = pool[Math.floor(Math.random() * pool.length)];
|
|
983
|
+
appendLine(`${C_DIM}🌙 ${pick}${RESET}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
catch { /* 静默 */ }
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
551
989
|
if (trimmed.toLowerCase() === '/help' || trimmed === 'help') {
|
|
552
990
|
appendLine(`${C_DIM}命令:${RESET}`);
|
|
553
991
|
appendLine(` ${C_ACCENT}!<cmd>${RESET} 执行终端命令 ${C_DIM}如 !ls -la${RESET}`);
|
|
554
992
|
appendLine(` ${C_ACCENT}/queue${RESET} 切换队列模式 ${C_DIM}输入排队, 当前结束后自动执行${RESET}`);
|
|
555
993
|
appendLine(` ${C_ACCENT}/dequeue${RESET} 出队一条`);
|
|
556
994
|
appendLine(` ${C_ACCENT}/channel [名字|id|序号]${RESET} 切换当前智能体 ${C_DIM}无参列出所有; 支持名字/ID/序号三种解析${RESET}`);
|
|
995
|
+
appendLine(` ${C_ACCENT}/model${RESET} / ${C_ACCENT}/login${RESET} 模型供应商选择器 ${C_DIM}↑↓ 选择 · Enter 确认 · Esc 取消${RESET}`);
|
|
996
|
+
appendLine(` ${C_ACCENT}/logout${RESET} 查看当前供应商`);
|
|
997
|
+
appendLine(` ${C_ACCENT}/now${RESET} 当前状态总览 ${C_DIM}智能体/运行时间/上下文 tokens/消息数${RESET}`);
|
|
998
|
+
appendLine(` ${C_ACCENT}/session${RESET} 当前会话信息 ${C_DIM}channel/agent/消息窗口${RESET}`);
|
|
999
|
+
appendLine(` ${C_ACCENT}/loop${RESET} 循环状态 ${C_DIM}消息数 + token 估算${RESET}`);
|
|
1000
|
+
appendLine(` ${C_ACCENT}/memory${RESET} 记忆摘要 ${C_DIM}memory-compressor 落盘摘要${RESET}`);
|
|
1001
|
+
appendLine(` ${C_ACCENT}/resume${RESET} 恢复上下文 ${C_DIM}最近记忆 + 进行中计划${RESET}`);
|
|
1002
|
+
appendLine(` ${C_ACCENT}/goal${RESET} 进行中目标 ${C_DIM}plan-store active plans${RESET}`);
|
|
1003
|
+
appendLine(` ${C_ACCENT}/tools${RESET} 可用工具列表`);
|
|
1004
|
+
appendLine(` ${C_ACCENT}/skill${RESET} 技能候选 ${C_DIM}skill-writer 沉淀候选${RESET}`);
|
|
1005
|
+
appendLine(` ${C_ACCENT}/mcp${RESET} MCP 服务器列表`);
|
|
1006
|
+
appendLine(` ${C_ACCENT}/agent${RESET} 当前智能体身份`);
|
|
1007
|
+
appendLine(` ${C_ACCENT}/did${RESET} DID 身份`);
|
|
1008
|
+
appendLine(` ${C_ACCENT}/ipfs${RESET} Kubo 状态 ${C_DIM}节点/peers/pins${RESET}`);
|
|
1009
|
+
appendLine(` ${C_ACCENT}/ipns${RESET} IPNS keys + resolve`);
|
|
1010
|
+
appendLine(` ${C_ACCENT}/wallet${RESET} 钱包状态`);
|
|
1011
|
+
appendLine(` ${C_ACCENT}/email${RESET} 邮件配置`);
|
|
1012
|
+
appendLine(` ${C_ACCENT}/judgement${RESET} 判断力列表`);
|
|
1013
|
+
appendLine(` ${C_ACCENT}/insight${RESET} Context OS 洞察 (08-Insights)`);
|
|
1014
|
+
appendLine(` ${C_ACCENT}/wiki${RESET} wiki 状态`);
|
|
1015
|
+
appendLine(` ${C_ACCENT}/dream${RESET} 随机灵感`);
|
|
557
1016
|
appendLine(` ${C_ACCENT}@名字${RESET} @ 命中智能体 ${C_DIM}弹出窗选择后发送给智能体${RESET}`);
|
|
558
1017
|
appendLine(` ${C_ACCENT}/名字${RESET} / 命中命令/技能/插件 ${C_DIM}输入 / 自动弹出${RESET}`);
|
|
559
1018
|
appendLine(` ${C_ACCENT}#路径${RESET} # 命中文件 ${C_DIM}输入 # 自动弹出文件列表${RESET}`);
|
|
@@ -678,14 +1137,31 @@ async function processInput(input, comm) {
|
|
|
678
1137
|
catch { /* 非致命, 静默 */ }
|
|
679
1138
|
});
|
|
680
1139
|
}
|
|
681
|
-
// 更新状态栏: 上下文进度
|
|
1140
|
+
// 更新状态栏: 上下文进度 (2026-08-06: 每轮按当前 messageHistory 重算并写回 ContextManager,
|
|
1141
|
+
// 保证状态栏按需更新 — 不依赖 pi-sdk loop 内部上报, 1s 定时器读到的一定是最新值)
|
|
682
1142
|
try {
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
1143
|
+
const { getContextManager } = await import('./bootstrap/context-manager.js');
|
|
1144
|
+
const cm = getContextManager();
|
|
1145
|
+
// 用 context-compaction 的估算器 (与 pi-sdk estimateHistoryTokens 同源: 4 字符 ≈ 1 token)
|
|
1146
|
+
const history = a.messageHistory ?? [];
|
|
1147
|
+
let usedTokens = 0;
|
|
1148
|
+
try {
|
|
1149
|
+
const { estimateTokens } = require('./context-compaction/index.js');
|
|
1150
|
+
usedTokens = estimateTokens(history);
|
|
1151
|
+
}
|
|
1152
|
+
catch {
|
|
1153
|
+
usedTokens = Math.max(0, Math.round(JSON.stringify(history).length / 4));
|
|
1154
|
+
}
|
|
1155
|
+
// 写回数据源 — 状态栏/Web/任何订阅方都拿到新鲜值
|
|
1156
|
+
const usage = cm.updateUsage(usedTokens);
|
|
1157
|
+
const usageView = {
|
|
1158
|
+
// 保留浮点 (0-100), buildContextBar 内部格式化
|
|
1159
|
+
pct: Math.min(100, (usage.usedTokens / Math.max(1, usage.maxTokens)) * 100),
|
|
1160
|
+
usedTokens: usage.usedTokens,
|
|
1161
|
+
maxTokens: usage.maxTokens,
|
|
1162
|
+
stage: usage.stage,
|
|
1163
|
+
};
|
|
1164
|
+
const statusText = `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ ${fmtDuration(Date.now() - cliStartTime)}${C_DIM} │${RESET} ${buildContextBar(usageView)}`;
|
|
689
1165
|
inkSetStatus(statusText);
|
|
690
1166
|
}
|
|
691
1167
|
catch { /* 降级容忍 */ }
|
package/dist/web/client.js
CHANGED
|
@@ -1672,6 +1672,15 @@
|
|
|
1672
1672
|
async function loadChannels() {
|
|
1673
1673
|
try {
|
|
1674
1674
|
const res = await fetch("/channels");
|
|
1675
|
+
const ct = res.headers.get("content-type") || "";
|
|
1676
|
+
if (!ct.includes("application/json")) {
|
|
1677
|
+
const text = await res.text().catch(() => "");
|
|
1678
|
+
if (!text.trim().startsWith("[") && !text.trim().startsWith("{")) {
|
|
1679
|
+
console.warn("[\u52A0\u8F7D\u9891\u9053] \u68C0\u6D4B\u5230 IPFS \u9759\u6001\u6A21\u5F0F (\u65E0\u540E\u7AEF server), \u529F\u80FD\u53D7\u9650");
|
|
1680
|
+
showStaticModeNotice();
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1675
1684
|
channels = await res.json();
|
|
1676
1685
|
console.log("[\u52A0\u8F7D\u9891\u9053] \u4ECE\u670D\u52A1\u5668\u83B7\u53D6\u5230", channels.length, "\u4E2A\u9891\u9053");
|
|
1677
1686
|
channels.forEach((ch, i) => {
|
|
@@ -1694,6 +1703,17 @@
|
|
|
1694
1703
|
console.error("[\u52A0\u8F7D\u9891\u9053] \u5931\u8D25:", err);
|
|
1695
1704
|
}
|
|
1696
1705
|
}
|
|
1706
|
+
function showStaticModeNotice() {
|
|
1707
|
+
if (document.getElementById("ipfs-static-notice")) return;
|
|
1708
|
+
const notice = document.createElement("div");
|
|
1709
|
+
notice.id = "ipfs-static-notice";
|
|
1710
|
+
notice.style.cssText = "position:fixed;bottom:60px;left:50%;transform:translateX(-50%);z-index:9999;background:#1a1a18;border:1px solid #c4d640;color:#d8d8c8;padding:10px 16px;border-radius:8px;font-size:12px;box-shadow:0 4px 20px rgba(0,0,0,.5);max-width:560px;text-align:center;";
|
|
1711
|
+
notice.innerHTML = `\u{1F4E1} <b style="color:#c4d640">IPFS \u9759\u6001\u6A21\u5F0F</b> \u2014 \u6B64\u9875\u9762\u901A\u8FC7 IPNS \u4ECE\u53BB\u4E2D\u5FC3\u5316\u7F51\u7EDC\u52A0\u8F7D.<br>\u5B8C\u6574\u529F\u80FD (\u5BF9\u8BDD/\u5DE5\u5177/\u5224\u65AD\u529B) \u9700\u8FDE\u63A5\u672C\u5730 Bolloon server: <code style="color:#c4d640">bolloon --web</code>`;
|
|
1712
|
+
document.body.appendChild(notice);
|
|
1713
|
+
setTimeout(() => {
|
|
1714
|
+
notice.remove();
|
|
1715
|
+
}, 15e3);
|
|
1716
|
+
}
|
|
1697
1717
|
var v3GlobalEventSource = null;
|
|
1698
1718
|
function startV3GlobalSSE() {
|
|
1699
1719
|
if (v3GlobalEventSource) return;
|
|
@@ -1937,6 +1957,19 @@ ${msg.text || ""}`, "ai", false, log);
|
|
|
1937
1957
|
console.log(`[v3-friend] \u2705 ack \u6536\u5230: ${name} \u5DF2\u6536\u5230\u597D\u53CB\u7533\u8BF7`);
|
|
1938
1958
|
showSimpleToast(`\u{1F4EC} ${name} \u5DF2\u6536\u5230\u4F60\u7684\u597D\u53CB\u7533\u8BF7, \u7B49\u5BF9\u65B9\u63A5\u53D7`);
|
|
1939
1959
|
}
|
|
1960
|
+
} else if (msg.type === "context_event") {
|
|
1961
|
+
try {
|
|
1962
|
+
const evt = msg.evt || {};
|
|
1963
|
+
if (evt.type === "context.warning") {
|
|
1964
|
+
showSimpleToast(`\u26A0\uFE0F \u4E0A\u4E0B\u6587\u4F7F\u7528\u7387 ${Math.round((evt.usage?.pct || 0) * 100)}%, \u5373\u5C06\u81EA\u52A8\u538B\u7F29`);
|
|
1965
|
+
} else if (evt.type === "context.compress.start") {
|
|
1966
|
+
showSimpleToast(`\u{1F5DC}\uFE0F \u4E0A\u4E0B\u6587\u538B\u7F29\u5F00\u59CB (${(evt.beforeTokens || 0).toLocaleString()} tokens)`);
|
|
1967
|
+
} else if (evt.type === "context.compress.complete") {
|
|
1968
|
+
const s = evt.snapshot || {};
|
|
1969
|
+
showSimpleToast(`\u2713 \u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29: ${((s.beforeTokens || 0) / 1e3).toFixed(0)}k \u2192 ${((s.afterTokens || 0) / 1e3).toFixed(0)}k tokens`);
|
|
1970
|
+
}
|
|
1971
|
+
} catch (ctxErr) {
|
|
1972
|
+
}
|
|
1940
1973
|
}
|
|
1941
1974
|
} catch (err) {
|
|
1942
1975
|
console.error("[v3] \u5168\u5C40 SSE \u89E3\u6790\u5931\u8D25:", err);
|
package/dist/web/index.html
CHANGED
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
<title>Bolloon Agent</title>
|
|
7
7
|
|
|
8
8
|
<!-- Favicon -->
|
|
9
|
-
<link rel="icon" type="image/x-icon" href="
|
|
10
|
-
<link rel="icon" type="image/png" sizes="32x32" href="
|
|
11
|
-
<link rel="icon" type="image/png" sizes="16x16" href="
|
|
9
|
+
<link rel="icon" type="image/x-icon" href="./icons/favicon.ico">
|
|
10
|
+
<link rel="icon" type="image/png" sizes="32x32" href="./icons/favicon-32x32.png">
|
|
11
|
+
<link rel="icon" type="image/png" sizes="16x16" href="./icons/favicon-16x16.png">
|
|
12
12
|
|
|
13
13
|
<!-- Apple Touch Icon -->
|
|
14
|
-
<link rel="apple-touch-icon" href="
|
|
14
|
+
<link rel="apple-touch-icon" href="./icons/apple-touch-icon.png">
|
|
15
15
|
|
|
16
16
|
<!-- PWA Manifest -->
|
|
17
|
-
<link rel="manifest" href="
|
|
17
|
+
<link rel="manifest" href="./manifest.json">
|
|
18
18
|
|
|
19
19
|
<!-- 2026-06-11: 移除 Google Fonts + jsdelivr 外部 CDN 阻塞 — 在大陆/跨公网 timeout 拖慢首屏/返回主页
|
|
20
20
|
字体: style.css 只用字面量 'JetBrains Mono', 系统有就用, 没有自动 fall back monospace
|
|
21
21
|
marked/qrcode: 改 async 不阻塞 (下载失败时本地降级到 escape 文本, 不影响主聊天) -->
|
|
22
|
-
<link rel="stylesheet" href="
|
|
22
|
+
<link rel="stylesheet" href="./style.css">
|
|
23
23
|
<script async src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
24
24
|
<script async src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>
|
|
25
25
|
</head>
|
|
@@ -414,11 +414,11 @@
|
|
|
414
414
|
</main>
|
|
415
415
|
</div>
|
|
416
416
|
|
|
417
|
-
<script type="module" src="
|
|
418
|
-
<script type="module" src="
|
|
419
|
-
<script type="module" src="
|
|
420
|
-
<script type="module" src="
|
|
417
|
+
<script type="module" src="./components/wallet-viem.mjs"></script>
|
|
418
|
+
<script type="module" src="./components/p2p/index.js"></script>
|
|
419
|
+
<script type="module" src="./ui/step-timeline.js"></script>
|
|
420
|
+
<script type="module" src="./ui/message-renderer.js"></script>
|
|
421
421
|
<!-- 2026-07-06: client.js 含 import 语句 (safeChannelName 兜底), 必须 type="module" -->
|
|
422
|
-
<script type="module" src="
|
|
422
|
+
<script type="module" src="./client.js"></script>
|
|
423
423
|
</body>
|
|
424
424
|
</html>
|