@bolloon/bolloon-agent 0.5.4 → 0.6.0
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 +43 -32
- package/dist/agents/pi-sdk-session-factory.js +36 -3
- package/dist/agents/pi-sdk-session-manager.js +72 -3
- package/dist/agents/pi-sdk-tools.js +201 -32
- package/dist/agents/pi-sdk.js +798 -206
- package/dist/agents/plan-store.js +36 -0
- package/dist/agents/process-runner.js +64 -2
- package/dist/agents/run-context.js +20 -0
- package/dist/agents/runner-resolver.js +2 -1
- 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/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 +153 -6
- package/dist/cli/input-paste.js +218 -0
- package/dist/cli/loading-tui.js +3 -1
- package/dist/cli/log-gate.js +7 -0
- package/dist/cli/mention-data.js +30 -0
- package/dist/cli/reasoning-view.js +103 -0
- package/dist/cli/startup-notice.js +25 -0
- package/dist/cli/status-segments.js +87 -2
- package/dist/cli/trace-line.js +54 -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 +43 -32
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-factory.js +36 -3
- package/dist/electron-build/agents/pi-sdk-session-factory.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 +200 -31
- 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 +799 -206
- 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/run-context.js +24 -0
- package/dist/electron-build/agents/run-context.js.map +1 -0
- package/dist/electron-build/agents/runner-resolver.js +2 -1
- package/dist/electron-build/agents/runner-resolver.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/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/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/kernel/channel-actor.js +314 -0
- package/dist/electron-build/kernel/channel-actor.js.map +1 -0
- 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 +526 -95
- package/dist/kernel/channel-actor.js +297 -0
- package/dist/kernel/control.js +95 -0
- package/dist/kernel/gate-scan.js +1036 -0
- package/dist/kernel/model-runtime.js +472 -0
- package/dist/kernel/plan-channel-actor.js +298 -0
- package/dist/kernel/plan-constraint.js +165 -0
- package/dist/kernel/plan-deletion.js +32 -0
- package/dist/kernel/plan-modelruntime.js +60 -0
- package/dist/kernel/plan-runcontext.js +163 -0
- package/dist/kernel/plan.js +153 -0
- package/dist/kernel/roster.js +242 -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 +4 -2
- package/dist/web/mobile-core.js +7762 -6289
- package/dist/web/routes-mobile-tasks.js +2 -1
- package/dist/web/routes-tasks.js +5 -1
- package/dist/web/server-storage.js +26 -5
- package/dist/web/server.js +60 -18
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +473 -29
- package/dist/web/ui/step-timeline.js +13 -12
- package/package.json +2 -1
- package/scripts/build-web.ts +74 -1
- package/dist/constraint-runtime/_archive_helper.d.ts +0 -1
- package/dist/constraint-runtime/_archive_helper.js +0 -9
- package/dist/constraint-runtime/assistant/index.d.ts +0 -11
- package/dist/constraint-runtime/assistant/index.js +0 -12
- package/dist/constraint-runtime/bootstrap/index.d.ts +0 -11
- package/dist/constraint-runtime/bootstrap/index.js +0 -12
- package/dist/constraint-runtime/bridge/index.d.ts +0 -11
- package/dist/constraint-runtime/bridge/index.js +0 -12
- package/dist/constraint-runtime/buddy/index.d.ts +0 -11
- package/dist/constraint-runtime/buddy/index.js +0 -12
- package/dist/constraint-runtime/cli/index.d.ts +0 -11
- package/dist/constraint-runtime/cli/index.js +0 -12
- package/dist/constraint-runtime/components/index.d.ts +0 -11
- package/dist/constraint-runtime/components/index.js +0 -12
- package/dist/constraint-runtime/constants/index.d.ts +0 -11
- package/dist/constraint-runtime/constants/index.js +0 -12
- package/dist/constraint-runtime/coordinator/index.d.ts +0 -11
- package/dist/constraint-runtime/coordinator/index.js +0 -12
- package/dist/constraint-runtime/cost_hook.d.ts +0 -2
- package/dist/constraint-runtime/cost_hook.js +0 -4
- package/dist/constraint-runtime/entrypoints/index.d.ts +0 -11
- package/dist/constraint-runtime/entrypoints/index.js +0 -12
- package/dist/constraint-runtime/execution_registry.d.ts +0 -20
- package/dist/constraint-runtime/execution_registry.js +0 -44
- package/dist/constraint-runtime/hooks/index.d.ts +0 -11
- package/dist/constraint-runtime/hooks/index.js +0 -12
- package/dist/constraint-runtime/ink.d.ts +0 -1
- package/dist/constraint-runtime/ink.js +0 -4
- package/dist/constraint-runtime/keybindings/index.d.ts +0 -11
- package/dist/constraint-runtime/keybindings/index.js +0 -12
- package/dist/constraint-runtime/memdir/index.d.ts +0 -11
- package/dist/constraint-runtime/memdir/index.js +0 -12
- package/dist/constraint-runtime/migrations/index.d.ts +0 -11
- package/dist/constraint-runtime/migrations/index.js +0 -12
- package/dist/constraint-runtime/moreright/index.d.ts +0 -11
- package/dist/constraint-runtime/moreright/index.js +0 -12
- package/dist/constraint-runtime/native_ts/index.d.ts +0 -11
- package/dist/constraint-runtime/native_ts/index.js +0 -12
- package/dist/constraint-runtime/output_styles/index.d.ts +0 -11
- package/dist/constraint-runtime/output_styles/index.js +0 -12
- package/dist/constraint-runtime/plugins/index.d.ts +0 -11
- package/dist/constraint-runtime/plugins/index.js +0 -12
- package/dist/constraint-runtime/port_manifest.d.ts +0 -7
- package/dist/constraint-runtime/port_manifest.js +0 -11
- package/dist/constraint-runtime/query.d.ts +0 -6
- package/dist/constraint-runtime/query.js +0 -1
- package/dist/constraint-runtime/remote/index.d.ts +0 -11
- package/dist/constraint-runtime/remote/index.js +0 -12
- package/dist/constraint-runtime/schemas/index.d.ts +0 -11
- package/dist/constraint-runtime/schemas/index.js +0 -12
- package/dist/constraint-runtime/screens/index.d.ts +0 -11
- package/dist/constraint-runtime/screens/index.js +0 -12
- package/dist/constraint-runtime/server/index.d.ts +0 -11
- package/dist/constraint-runtime/server/index.js +0 -12
- package/dist/constraint-runtime/services/index.d.ts +0 -11
- package/dist/constraint-runtime/services/index.js +0 -12
- package/dist/constraint-runtime/state/index.d.ts +0 -11
- package/dist/constraint-runtime/state/index.js +0 -12
- package/dist/constraint-runtime/system_init.d.ts +0 -1
- package/dist/constraint-runtime/system_init.js +0 -20
- package/dist/constraint-runtime/types/index.d.ts +0 -11
- package/dist/constraint-runtime/types/index.js +0 -12
- package/dist/constraint-runtime/upstream_proxy/index.d.ts +0 -11
- package/dist/constraint-runtime/upstream_proxy/index.js +0 -12
- package/dist/constraint-runtime/utils/index.d.ts +0 -11
- package/dist/constraint-runtime/utils/index.js +0 -12
- package/dist/constraint-runtime/vim/index.d.ts +0 -11
- package/dist/constraint-runtime/vim/index.js +0 -12
- package/dist/constraint-runtime/voice/index.d.ts +0 -11
- package/dist/constraint-runtime/voice/index.js +0 -12
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { deliverThroughActor } from './kernel/channel-actor.js';
|
|
1
2
|
import { createHyperswarmCommunicator, createTopic, KeyManager, AgentAuthManager, createVerificationManager, } from '@diap/sdk';
|
|
2
3
|
import { irohTransport } from './network/iroh-transport.js';
|
|
3
4
|
import { getLastAiTiming, getLastAiUsage } from './llm/pi-ai.js';
|
|
4
5
|
import { setHistoryScope } from './cli/input-history.js';
|
|
5
|
-
import { statusSegments, fitSegments, rightAlignPad } from './cli/status-segments.js';
|
|
6
|
+
import { statusSegments, fitSegments, rightAlignPad, dispWidthSafe, statusLineBudget } from './cli/status-segments.js';
|
|
7
|
+
import { renderReasoning } from './cli/reasoning-view.js';
|
|
8
|
+
import { traceLabel } from './cli/trace-line.js';
|
|
6
9
|
import { loadWalletTool } from './agents/wallet-tools.js';
|
|
7
10
|
import { HybridMessenger } from './network/hybrid-messenger.js';
|
|
8
11
|
import * as ed25519 from '@noble/ed25519';
|
|
@@ -29,10 +32,83 @@ import { installStartupLogGate, isStartupVerbose, startupLogPath, VERBOSE_ENV, c
|
|
|
29
32
|
// 2026-09-27: 回复流卫生 — 内部运行日志 (循环推进/运行登记/收尾计数…) 不进对话回复流, 改落日志文件
|
|
30
33
|
import { isInternalRunLog, appendInternalRunLog } from './cli/reply-hygiene.js';
|
|
31
34
|
// 2026-09-27: 启动**前言 / 就绪度报告**闸门 (默认不上屏 → 面板一行 + 显式查询命令; 失败折成面板提示)
|
|
32
|
-
import { noticeLine as noticePreamble, flushStartupNotices, setStartupReadiness, alertsFromSetup, readinessLine, preambleHintLine, startupPreambleVisible, pushStartupAlert, takeStartupPanelNotes, clearBufferedNotices, } from './cli/startup-notice.js';
|
|
35
|
+
import { onlyConnectivityRetest, SETUP_STATUS_CMD, noticeLine as noticePreamble, flushStartupNotices, setStartupReadiness, alertsFromSetup, readinessLine, preambleHintLine, startupPreambleVisible, pushStartupAlert, takeStartupPanelNotes, clearBufferedNotices, } from './cli/startup-notice.js';
|
|
33
36
|
import { clipboardWrite } from './cli/clipboard.js';
|
|
34
37
|
// 启动自动检查更新:后台、节流、检测到新版本自动安装(可被 --no-update / BOLLOON_SKIP_UPDATE 关闭)
|
|
35
38
|
import { createRequire } from 'module';
|
|
39
|
+
export function parseGroupSub(input) {
|
|
40
|
+
const rest = String(input || '').replace(/^\/group\b/i, '').trim();
|
|
41
|
+
if (!rest)
|
|
42
|
+
return { sub: 'list', arg: '' };
|
|
43
|
+
const sp = rest.indexOf(' ');
|
|
44
|
+
const head = (sp === -1 ? rest : rest.slice(0, sp)).toLowerCase();
|
|
45
|
+
const arg = sp === -1 ? '' : rest.slice(sp + 1).trim();
|
|
46
|
+
const aliases = { ls: 'list', l: 'list', n: 'new', create: 'new', add: 'join', msg: 'send', history: 'log', use_: 'use' };
|
|
47
|
+
const sub = aliases[head] || head;
|
|
48
|
+
// 2026-10-01 (用户: 「group 命令无法选中进群, 要可以选中」): 裸序号 ⇒ 直接选中该群
|
|
49
|
+
// 与 `/channel <序号>` 同一个习惯 ✓ (列表里就带序号)
|
|
50
|
+
if (/^\d+$/.test(head))
|
|
51
|
+
return { sub: 'use', arg: head };
|
|
52
|
+
const known = ['list', 'new', 'join', 'leave', 'use', 'send', 'log', 'members', 'help'];
|
|
53
|
+
return { sub: known.includes(sub) ? sub : 'help', arg: known.includes(sub) ? arg : rest };
|
|
54
|
+
}
|
|
55
|
+
export const CLI_KNOWN_COMMAND_HEADS = new Set([
|
|
56
|
+
'/group',
|
|
57
|
+
'/agent',
|
|
58
|
+
'/answer',
|
|
59
|
+
'/approve',
|
|
60
|
+
'/contacts',
|
|
61
|
+
'/copy',
|
|
62
|
+
'/criteria',
|
|
63
|
+
'/cron',
|
|
64
|
+
'/dequeue',
|
|
65
|
+
'/did',
|
|
66
|
+
'/dq',
|
|
67
|
+
'/dream',
|
|
68
|
+
'/email',
|
|
69
|
+
'/fork',
|
|
70
|
+
'/goal',
|
|
71
|
+
'/goals',
|
|
72
|
+
'/help',
|
|
73
|
+
'/insight',
|
|
74
|
+
'/ipfs',
|
|
75
|
+
'/ipns',
|
|
76
|
+
'/judgement',
|
|
77
|
+
'/judgments',
|
|
78
|
+
'/login',
|
|
79
|
+
'/logout',
|
|
80
|
+
'/loop',
|
|
81
|
+
'/mcp',
|
|
82
|
+
'/memory',
|
|
83
|
+
'/model',
|
|
84
|
+
'/net',
|
|
85
|
+
'/now',
|
|
86
|
+
'/p2p',
|
|
87
|
+
'/pause',
|
|
88
|
+
'/payments',
|
|
89
|
+
'/plan',
|
|
90
|
+
'/q',
|
|
91
|
+
'/questions',
|
|
92
|
+
'/queue',
|
|
93
|
+
'/reject',
|
|
94
|
+
'/resume',
|
|
95
|
+
'/runs',
|
|
96
|
+
'/session',
|
|
97
|
+
'/sessions',
|
|
98
|
+
'/setup',
|
|
99
|
+
'/skill',
|
|
100
|
+
'/skills',
|
|
101
|
+
'/suggestions',
|
|
102
|
+
'/supervise',
|
|
103
|
+
'/todo',
|
|
104
|
+
'/tools',
|
|
105
|
+
'/trace',
|
|
106
|
+
'/tx',
|
|
107
|
+
'/wake',
|
|
108
|
+
'/wallet',
|
|
109
|
+
'/wiki',
|
|
110
|
+
'/x402',
|
|
111
|
+
]);
|
|
36
112
|
const _require = createRequire(import.meta.url);
|
|
37
113
|
const _BOLLOON_VERSION = (() => {
|
|
38
114
|
try {
|
|
@@ -68,6 +144,75 @@ const MOVE_UP = '\x1b[A';
|
|
|
68
144
|
const CLEAR_LINE = '\x1b[2K';
|
|
69
145
|
const HIDE_CURSOR = '\x1b[?25l';
|
|
70
146
|
const SHOW_CURSOR = '\x1b[?25h';
|
|
147
|
+
/**
|
|
148
|
+
* 统一出口 (2026-10-01, 用户: 「还是UI有污染」)。
|
|
149
|
+
* 为什么: Ink 被显式设成 `patchConsole: false` ⇒ **不接管 console** ✗ ⇒ 运行期任何 `console.log`
|
|
150
|
+
* 都写在 Ink 托管区**之外** ⇒ 每次直写都把正在渲染的 footer(提示行 + 分界线)往下推一份 ✗
|
|
151
|
+
* ⇒ 屏幕上重复出现「· 回车发送 · ↑↓ 历史 · PgUp 回看 · Esc 双击退出」✓(真机实测就是这个)。
|
|
152
|
+
* 判据复用已有的 `startupPanelReady`(启动面板画好了 = Ink 已在跑 ✓):
|
|
153
|
+
* Ink 在跑 ⇒ **一律走它自己的消息流**; 没跑(boot 期)⇒ 才直写 ✓。
|
|
154
|
+
*/
|
|
155
|
+
function writeOut(line) {
|
|
156
|
+
// 2026-10-01 (用户: 「时间好像是面板后面启动的」+「继续,还没去掉」):
|
|
157
|
+
// **关键**: 启动那几步**不是 await 的** ✗(fire-and-forget)⇒ 它们常在 `startupPanelReady = true`
|
|
158
|
+
// **之后**才打印 ✓ ⇒ 用"面板好了没"当判据必然漏 ✗(我上一版就栽在这 ✓)。
|
|
159
|
+
// 改成**独立的启动期标志** `bootPhase` ✓: 启动期内的**进度类**输出一律只落盘 ✓, 与面板状态无关 ✓。
|
|
160
|
+
if (bootPhase) {
|
|
161
|
+
bootLogOnly(line);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (startupPanelReady) {
|
|
165
|
+
try {
|
|
166
|
+
appendLine(line);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
catch { /* 落到落盘 */ }
|
|
170
|
+
}
|
|
171
|
+
// 2026-10-01 (用户: 「启动那几行……继续,还没去掉」): **启动期的"进度类"输出直接不进屏** ✓
|
|
172
|
+
// 上一版我把它们**搬进对话流**了 ✗ —— 但用户要的是**去掉** ✓(面板已经把版本/模型/技能这些要点画了 ✓,
|
|
173
|
+
// 而 `复用 DID` / `iroh:` / `主题:` / `[N/5]` 这类属于**过程细节** ✓)。
|
|
174
|
+
// 现在: 启动期 ⇒ **只落日志**(~/.bolloon/logs/startup.log ✓ 事后照样能查 ✓), 不上屏 ✓。
|
|
175
|
+
bootLogOnly(line);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 启动期**警告/错误**仍然要上屏 ✓ (不许因为"想干净"把问题藏起来 ✗):
|
|
179
|
+
* Ink 没起 ⇒ 先缓冲, `flushBootBuffer()` 在 Ink 起来后灌进对话流 ✓; Ink 已在跑 ⇒ 直接进 ✓。
|
|
180
|
+
*/
|
|
181
|
+
function writeOutWarn(line) {
|
|
182
|
+
if (startupPanelReady) {
|
|
183
|
+
try {
|
|
184
|
+
appendLine(line);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
catch { /* 落到缓冲 */ }
|
|
188
|
+
}
|
|
189
|
+
if (bootBuffer.length < 500) {
|
|
190
|
+
bootBuffer.push(line);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
console.log(line);
|
|
194
|
+
}
|
|
195
|
+
/** 启动期进度类输出: 只落盘(不上屏 ✓) */
|
|
196
|
+
function bootLogOnly(line) {
|
|
197
|
+
try {
|
|
198
|
+
const clean = String(line).replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
199
|
+
fsSync.appendFileSync(startupLogPath(), `[boot] ${clean}\n`);
|
|
200
|
+
}
|
|
201
|
+
catch { /* 落盘失败也不上屏(它本来就是过程细节 ✓) */ }
|
|
202
|
+
}
|
|
203
|
+
/** 启动期"警告/错误"缓冲: Ink 起来之前攒着, `flushBootBuffer()` 一次性灌进对话流 ✓ */
|
|
204
|
+
const bootBuffer = [];
|
|
205
|
+
export function flushBootBuffer() {
|
|
206
|
+
while (bootBuffer.length) {
|
|
207
|
+
const line = bootBuffer.shift();
|
|
208
|
+
try {
|
|
209
|
+
appendLine(line);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
console.log(line);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
71
216
|
const s = {
|
|
72
217
|
banner: () => {
|
|
73
218
|
printBanner(_BOLLOON_VERSION);
|
|
@@ -78,52 +223,48 @@ const s = {
|
|
|
78
223
|
status === 'warn' ? `${YELLOW}⚠` :
|
|
79
224
|
status === 'error' ? `${MAGENTA}✗` :
|
|
80
225
|
`${CYAN}●`;
|
|
81
|
-
|
|
226
|
+
writeOut(` ${check} ${WHITE}[${num}/${total}]${GRAY} ${text}${RESET}`);
|
|
82
227
|
},
|
|
83
|
-
success: (text) =>
|
|
84
|
-
warn: (text) =>
|
|
85
|
-
error: (text) =>
|
|
86
|
-
info: (text) =>
|
|
228
|
+
success: (text) => writeOut(` ${GREEN}✓${RESET} ${text}`),
|
|
229
|
+
warn: (text) => writeOutWarn(` ${YELLOW}⚠${RESET} ${text}`),
|
|
230
|
+
error: (text) => writeOutWarn(` ${MAGENTA}✗${RESET} ${text}`),
|
|
231
|
+
info: (text) => writeOut(` ${CYAN}●${RESET} ${text}`),
|
|
87
232
|
section: (title) => {
|
|
88
|
-
|
|
233
|
+
writeOut(`\n${BLUE}━━━ ${WHITE}${BOLD}${title}${RESET} ${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
89
234
|
},
|
|
90
|
-
divider: () =>
|
|
91
|
-
prompt: (text) =>
|
|
235
|
+
divider: () => writeOut(`\n${GRAY}${'─'.repeat(50)}${RESET}\n`),
|
|
236
|
+
prompt: (text) => writeOut(`\n${CYAN}❯ ${WHITE}${text}${RESET}`),
|
|
92
237
|
response: (label, content) => {
|
|
93
|
-
|
|
238
|
+
writeOut(`\n${GREEN}${label}${RESET}\n${content}\n`);
|
|
94
239
|
},
|
|
95
240
|
agentCard: (agent) => {
|
|
96
241
|
const statusColor = agent.status === 'active' ? GREEN :
|
|
97
242
|
agent.status === 'idle' ? YELLOW :
|
|
98
243
|
agent.status === 'busy' ? MAGENTA : GRAY;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
244
|
+
writeOut(` ${WHITE}${BOLD}${agent.name}${RESET}`);
|
|
245
|
+
writeOut(` ${GRAY}ID:${RESET} ${agent.id}`);
|
|
246
|
+
writeOut(` ${GRAY}状态:${RESET} ${statusColor}${agent.status}${RESET}`);
|
|
247
|
+
writeOut(` ${GRAY}能力:${RESET} ${agent.capabilities.join(', ')}`);
|
|
103
248
|
if (agent.did)
|
|
104
|
-
|
|
105
|
-
|
|
249
|
+
writeOut(` ${GRAY}DID:${RESET} ${agent.did}`);
|
|
250
|
+
writeOut('');
|
|
106
251
|
},
|
|
107
252
|
Thinking: () => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
dots = (dots + 1) % 4;
|
|
116
|
-
const dotStr = '.'.repeat(dots || 1);
|
|
117
|
-
appendLine(`\r ${frames[i]} 思考${dotStr} `);
|
|
118
|
-
}, 600);
|
|
253
|
+
// 2026-10-01 (用户: 「颜文字被加载进去了」/「可能是 ink 渲染的问题」): **真凶在这里** ✗ ——
|
|
254
|
+
// 原来每一帧都走 `appendLine` ⇒ 而对话流是 Ink 的 `<Static>`(**写一次永不重绘** ✓)
|
|
255
|
+
// ⇒ 于是每 600ms 就有一条「思考中...」被**永久烙进滚动区** ✗, 带 `\r` 也擦不掉 ✗,
|
|
256
|
+
// 滚出来的残影还能被框选/拖进输入框 ⇒ 混进上下文 ✓。
|
|
257
|
+
// 现在改成走 **Ink 的瞬时行**(footer 自己帧进动画 ✓, 只重绘一行 ✓, 不落进 Static ✓)。
|
|
258
|
+
inkSetThinking(true);
|
|
259
|
+
return setInterval(() => { }, 600);
|
|
119
260
|
},
|
|
120
261
|
clearThinking: (interval) => {
|
|
121
262
|
clearInterval(interval);
|
|
122
|
-
|
|
263
|
+
inkSetThinking(false);
|
|
123
264
|
},
|
|
124
265
|
dialog: async (title, promptText) => {
|
|
125
266
|
return new Promise((resolve) => {
|
|
126
|
-
|
|
267
|
+
writeOut(renderDialog({ title, prompt: promptText }));
|
|
127
268
|
const rl = readline.createInterface({
|
|
128
269
|
input: process.stdin,
|
|
129
270
|
output: process.stdout
|
|
@@ -149,6 +290,14 @@ ed25519.hashes.sha512 = sha512;
|
|
|
149
290
|
let startupPanelFirst = false;
|
|
150
291
|
/** 面板第一帧已经画出来了 (startCLI 取走面板提示之后): 晚到的降级提示直接追加进面板 */
|
|
151
292
|
let startupPanelReady = false;
|
|
293
|
+
/**
|
|
294
|
+
* **启动期**标志 (2026-10-01): 与 `startupPanelReady` **不是一回事** ✗ ——
|
|
295
|
+
* 启动那几步是 fire-and-forget, 打印时间常在面板之后 ✓ ⇒ 只有用独立标志才拦得住 ✓。
|
|
296
|
+
* 结束条件: **用户第一次发消息** ✓(他真正接管了)或启动后 **20s 兜底** ✓。
|
|
297
|
+
*/
|
|
298
|
+
let bootPhase = true;
|
|
299
|
+
function endBootPhase() { bootPhase = false; }
|
|
300
|
+
setTimeout(() => { bootPhase = false; }, 20_000).unref?.();
|
|
152
301
|
/**
|
|
153
302
|
* 2026-09-30: 启动面板 / 就绪行**各只许追加一次**.
|
|
154
303
|
* 防任何重入路径 (会话切换 / 重复 boot / 恢复) 把面板再叠一条 —— leo 报的「顶框重复 14 次」那一类
|
|
@@ -235,8 +384,8 @@ async function bootstrapIdentity() {
|
|
|
235
384
|
const username = getUserName();
|
|
236
385
|
const suffix = did.split(':').pop()?.substring(0, 4);
|
|
237
386
|
const name = `blln-${username}-${suffix}`;
|
|
238
|
-
|
|
239
|
-
|
|
387
|
+
writeOut(` ${reused ? GRAY + '复用 ' : ''}${GRAY}DID:${RESET} ${did}`);
|
|
388
|
+
writeOut(` ${GRAY}名称:${RESET} ${name}`);
|
|
240
389
|
s.step(1, 5, reused ? '复用 DIAP 身份' : '生成 DIAP 身份', 'ok');
|
|
241
390
|
return { keypair: kp, did, name };
|
|
242
391
|
}
|
|
@@ -297,7 +446,7 @@ async function bootstrapP2P(verifier) {
|
|
|
297
446
|
await comm.start();
|
|
298
447
|
const topic = createTopic('bolloon-agent-harness');
|
|
299
448
|
await comm.joinTopic(topic);
|
|
300
|
-
|
|
449
|
+
writeOut(` ${GRAY}主题:${RESET} ${topic.slice(0, 8).toString('hex')}...`);
|
|
301
450
|
s.step(3, 5, '启动 P2P 网络', 'ok');
|
|
302
451
|
return comm;
|
|
303
452
|
}
|
|
@@ -315,20 +464,20 @@ async function bootstrapIroh(keypair, name) {
|
|
|
315
464
|
s.step(4, 5, '启动 iroh P2P', 'loading');
|
|
316
465
|
try {
|
|
317
466
|
const node = await irohTransport.start();
|
|
318
|
-
|
|
467
|
+
writeOut(` ${GRAY}iroh:${RESET} ${node.nodeId.substring(0, 16)}...`);
|
|
319
468
|
hybridMessenger = new HybridMessenger({
|
|
320
469
|
preferIrohForLarge: true,
|
|
321
470
|
largeThresholdBytes: 64 * 1024,
|
|
322
471
|
enableRelay: true,
|
|
323
472
|
});
|
|
324
473
|
hybridMessenger.onMessage('task', async (msg) => {
|
|
325
|
-
|
|
474
|
+
writeOut(`[iroh] Task from ${msg.from.substring(0, 12)}...: ${new TextDecoder().decode(msg.payload).substring(0, 50)}...`);
|
|
326
475
|
});
|
|
327
476
|
hybridMessenger.onMessage('blob', async (msg) => {
|
|
328
|
-
|
|
477
|
+
writeOut(`[iroh] Blob from ${msg.from.substring(0, 12)}...: ${msg.payload.length} bytes`);
|
|
329
478
|
});
|
|
330
479
|
hybridMessenger.onMessage('response', async (msg) => {
|
|
331
|
-
|
|
480
|
+
writeOut(`[iroh] Response from ${msg.from.substring(0, 12)}...`);
|
|
332
481
|
});
|
|
333
482
|
if (agentIdentity) {
|
|
334
483
|
agentIdentity.irohNodeId = node.nodeId;
|
|
@@ -337,13 +486,15 @@ async function bootstrapIroh(keypair, name) {
|
|
|
337
486
|
}
|
|
338
487
|
catch (e) {
|
|
339
488
|
s.step(4, 5, '启动 iroh P2P', 'warn');
|
|
340
|
-
|
|
341
|
-
|
|
489
|
+
writeOut(` ${YELLOW}iroh 启动失败: ${e.message}${RESET}`);
|
|
490
|
+
writeOut(` ${GRAY}继续使用 Hyperswarm P2P${RESET}`);
|
|
342
491
|
}
|
|
343
492
|
}
|
|
344
493
|
// ---------------------------------------------------------------------------
|
|
345
494
|
// Agent 懒加载
|
|
346
495
|
// ---------------------------------------------------------------------------
|
|
496
|
+
// 2026-10-01: /group 的"当前群"(进程内; 重启后需重新 /group use)
|
|
497
|
+
let currentGroupId;
|
|
347
498
|
let agent = null;
|
|
348
499
|
/**
|
|
349
500
|
* 退出存档 (2026-09-30, leo: 「按 ctrl+c / esc 退出的时候, 自动存档 session 进程, 最后显示再见👋」)
|
|
@@ -488,15 +639,34 @@ async function getAgent() {
|
|
|
488
639
|
catch { /* 读不到就退默认 */ }
|
|
489
640
|
}
|
|
490
641
|
let identityDoc;
|
|
491
|
-
|
|
492
|
-
|
|
642
|
+
// 2026-10-01 (用户实测: 两个不同 channel 都答同一个名字, get_identity 的 DID 为空):
|
|
643
|
+
// 旧逻辑要求 channel 记录**同时**有 did + publicKey, 否则落到 `agentIdentity`(进程级**共享**) ⇒
|
|
644
|
+
// 多个 channel 共用一个身份 ✗; 且 channel 里可能存着假 DID (did:local:…) 或干脆没有 did。
|
|
645
|
+
// 现在: 归属以 channel 的 agentId 为准 —— 现取现造**该 agent 的**真 did:key, 名字用**它自己的** persona。
|
|
646
|
+
const { channelIdentityUsable, nameForChannel } = await import('./agents/channel-identity.js');
|
|
647
|
+
let perAgentIdentity;
|
|
648
|
+
if (chIdentity?.agentId) {
|
|
649
|
+
try {
|
|
650
|
+
const { loadOrCreateAgentIdentity } = await import('./agents/agent-identity.js');
|
|
651
|
+
const mine = loadOrCreateAgentIdentity(chIdentity.agentId);
|
|
652
|
+
if (mine?.did)
|
|
653
|
+
perAgentIdentity = { did: mine.did, publicKey: mine.publicKey, name: nameForChannel(chIdentity, chIdentity.agentId) };
|
|
654
|
+
}
|
|
655
|
+
catch { /* 退回落 */ }
|
|
656
|
+
}
|
|
657
|
+
if (channelIdentityUsable(chIdentity)) {
|
|
658
|
+
// channel 自带**可用**的持久 DID (真 did:key) → 用它, 名字仍按该 agent 取 (不再用共享名)
|
|
493
659
|
identityDoc = {
|
|
494
660
|
did: chIdentity.did,
|
|
495
|
-
name: chIdentity
|
|
661
|
+
name: nameForChannel(chIdentity, chIdentity.agentId),
|
|
496
662
|
publicKey: chIdentity.publicKey,
|
|
497
663
|
createdAt: Date.now(),
|
|
498
664
|
};
|
|
499
665
|
}
|
|
666
|
+
else if (perAgentIdentity) {
|
|
667
|
+
// channel 缺身份 / 只有假 DID ⇒ 用**该 agent 自己的**真身份 (按 agentId 落盘密钥, 稳定)
|
|
668
|
+
identityDoc = { ...perAgentIdentity, createdAt: Date.now() };
|
|
669
|
+
}
|
|
500
670
|
else if (agentIdentity) {
|
|
501
671
|
identityDoc = {
|
|
502
672
|
did: agentIdentity.did,
|
|
@@ -512,15 +682,60 @@ async function getAgent() {
|
|
|
512
682
|
else {
|
|
513
683
|
identityDoc = undefined;
|
|
514
684
|
}
|
|
685
|
+
// 2026-10-01: **身份取值自证** —— 只在"可疑"时打一行事实 (不是刷屏):
|
|
686
|
+
// 缺 did / 假 did / channel 有 agentId 却没被用上 ⇒ 打出来, 免得靠猜。
|
|
687
|
+
// 用户实测过 DID 为空、名字串台、以及一个数据里根本不存在的 did ⇒ 这行能直接指认哪条分支被走了。
|
|
688
|
+
try {
|
|
689
|
+
const d = String(identityDoc?.did ?? '');
|
|
690
|
+
const suspicious = !d || /^did:(local|pi):/i.test(d) || (!chIdentity?.agentId && !!targetChannelId);
|
|
691
|
+
if (suspicious) {
|
|
692
|
+
console.warn(`[identity] 身份可疑: channel=${targetChannelId ?? '(无)'} · channel.agentId=${chIdentity?.agentId ?? '(无)'} · ` +
|
|
693
|
+
`分支=${channelIdentityUsable(chIdentity) ? 'channel 自带' : perAgentIdentity ? 'per-agent' : agentIdentity ? '**共享 agentIdentity**' : '默认'} · ` +
|
|
694
|
+
`did=${d ? d.slice(0, 30) : '(空)'} · name=${String(identityDoc?.name ?? '(空)')}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
catch { /* 自证失败不影响主流程 */ }
|
|
698
|
+
// 2026-10-01: 把当前 agent 告诉进程 —— 项目上下文(context-collector)据此取**该 agent 的** persona,
|
|
699
|
+
// 否则它会读全局 persona.json, 于是 get_identity 已是「小龙」而上下文里还写着「小宝」✗。
|
|
700
|
+
if (chIdentity?.agentId)
|
|
701
|
+
process.env.BOLLOON_ACTIVE_AGENT_ID = chIdentity.agentId;
|
|
702
|
+
// 2026-10-01: **身份取值一行事实** —— 每次**建/切 agent** 打一行(不是每回合, 不刷屏)。
|
|
703
|
+
// 用户报「xiaomi 频道却拿到 233 的 DID + 小龙」而可疑判据没触发 ⇒ 猜不动了, 让代码直说。
|
|
704
|
+
try {
|
|
705
|
+
console.warn(`[identity] channel=${targetChannelId ?? '(无)'} · 查到=${chIdentity ? 'yes' : '**no**'} · ` +
|
|
706
|
+
`channel.agentId=${chIdentity?.agentId ?? '(无)'} · channel.did=${String(chIdentity?.did ?? '(无)').slice(0, 26)} · ` +
|
|
707
|
+
`分支=${channelIdentityUsable(chIdentity) ? 'channel自带' : perAgentIdentity ? 'per-agent' : agentIdentity ? '**共享' : '默认'} · ` +
|
|
708
|
+
`结果 did=${String(identityDoc?.did ?? '(空)').slice(0, 26)} name=${String(identityDoc?.name ?? '(空)')}`);
|
|
709
|
+
}
|
|
710
|
+
catch { /* 自证失败不影响主流程 */ }
|
|
515
711
|
const loadSessionKey = targetChannelId
|
|
516
712
|
? `${targetChannelId}:${chIdentity?.currentSessionId || 'default'}`
|
|
517
713
|
: undefined;
|
|
714
|
+
// 2026-10-01: 确保该 agent 名下有身份文档 (此前仓里没有任何"建文档"的路径 ⇒
|
|
715
|
+
// 用户四个 agent 的 persona/<agentId>/ 全空 ⇒ loadPersonaDocs 读回空 ⇒ 所有智能体一个样)。
|
|
716
|
+
// 挂在这里 = 建 agent / 切 channel 两条路都会走到; 幂等, 已存在的不覆盖。
|
|
717
|
+
if (chIdentity?.agentId) {
|
|
718
|
+
try {
|
|
719
|
+
const { ensurePersonaDocs } = await import('./bootstrap/persona-init.js');
|
|
720
|
+
// 2026-10-01: 名字必须和该 agent 的 persona.json 一致 —— 之前传的是**渠道名**,
|
|
721
|
+
// 于是 soul.md 写「我是 233」而 persona.json 写「小龙」⇒ 用户看到"身份没匹配上" ✗。
|
|
722
|
+
const r = await ensurePersonaDocs(chIdentity.agentId, { name: nameForChannel(chIdentity, chIdentity.agentId) });
|
|
723
|
+
if (r.created.length)
|
|
724
|
+
console.warn(`[persona] 已为 ${r.agentId} 生成 ${r.created.length} 份起步身份文档: ${r.created.join(' · ')} → ${r.dir}`);
|
|
725
|
+
}
|
|
726
|
+
catch { /* 非致命 */ }
|
|
727
|
+
}
|
|
518
728
|
agent = await createAgentSession({
|
|
519
729
|
cwd: process.cwd(),
|
|
520
730
|
peerId: targetChannelId ?? 'harness',
|
|
521
|
-
identityDoc,
|
|
522
731
|
// 2026-08-09: 透传 channel.agentId → persona docs 按 agent 加载 (身份真正变化)
|
|
523
732
|
agentId: chIdentity?.agentId || (targetChannelId ? undefined : agentIdentity?.name),
|
|
733
|
+
// 2026-10-01 ★ **必须传 identityDoc** —— 之前漏了 ✗, 后果有两层:
|
|
734
|
+
// ① 上面按频道算好的身份(名字 + 该 agent 自己的 did)被**丢掉**, session 用自己造/复用的;
|
|
735
|
+
// ② session 工厂的更新路径 (pi-sdk-session-factory.updateIdentity) 以 `config.identityDoc?.did`
|
|
736
|
+
// 为条件 ⇒ 缺它 **永不触发** ⇒ 切频道时复用同一个 session 实例, 身份却**不换** ✗✗
|
|
737
|
+
// (实测: 233→智能体 后答 233 的身份; 智能体→233 后答 智能体的身份 = 身份"差一格" ✓ 根因在此)
|
|
738
|
+
identityDoc,
|
|
524
739
|
loadSessionKey,
|
|
525
740
|
});
|
|
526
741
|
agentBoundChannelId = targetChannelId;
|
|
@@ -548,7 +763,7 @@ async function dispatchTask(raw) {
|
|
|
548
763
|
const task = safeParse(body);
|
|
549
764
|
if (!task)
|
|
550
765
|
return `ERR|${JSON.stringify({ code: 'bad_format' })}`;
|
|
551
|
-
|
|
766
|
+
writeOut(`\n📥 [${task.type}] from=${task.from?.substring(0, 18)}... id=${task.id}`);
|
|
552
767
|
try {
|
|
553
768
|
switch (task.type) {
|
|
554
769
|
case 'summarize':
|
|
@@ -567,8 +782,9 @@ async function handleSummarize(task) {
|
|
|
567
782
|
if (!task.documentPath)
|
|
568
783
|
return `ERR|${JSON.stringify({ code: 'no_path' })}`;
|
|
569
784
|
const a = await getAgent();
|
|
570
|
-
const
|
|
571
|
-
|
|
785
|
+
const docPath = task.documentPath; // 闭包里 TS 不保留收窄 ⇒ 先收成局部 const
|
|
786
|
+
const { summary, qualityScore } = await deliverThroughActor(a, () => a.summarizeDocument(docPath));
|
|
787
|
+
writeOut(` ✅ 质量=${(qualityScore * 10).toFixed(1)}/10`);
|
|
572
788
|
return `OK|${JSON.stringify({ id: task.id, type: 'summarize', qualityScore, summary })}`;
|
|
573
789
|
}
|
|
574
790
|
async function handleImprove(task) {
|
|
@@ -576,13 +792,16 @@ async function handleImprove(task) {
|
|
|
576
792
|
return `ERR|${JSON.stringify({ code: 'no_path_or_req' })}`;
|
|
577
793
|
}
|
|
578
794
|
const a = await getAgent();
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
795
|
+
// 闭包里 TS 不保留收窄 ⇒ 先把两个字段收成局部 const
|
|
796
|
+
const docPath = task.documentPath;
|
|
797
|
+
const reqs = task.requirements;
|
|
798
|
+
const res = await deliverThroughActor(a, () => a.improveDocument({
|
|
799
|
+
originalPath: docPath,
|
|
800
|
+
requirements: reqs,
|
|
582
801
|
context: `来自节点: ${task.from}`,
|
|
583
|
-
});
|
|
802
|
+
}));
|
|
584
803
|
const ok = res.improved ?? false;
|
|
585
|
-
|
|
804
|
+
writeOut(` ✅ 改进${ok ? '成功' : '失败'} 质量=${(res.qualityScore * 10).toFixed(1)}/10 自动发送=${res.shouldAutoSend}`);
|
|
586
805
|
return `OK|${JSON.stringify({
|
|
587
806
|
id: task.id, type: 'improve', improved: ok,
|
|
588
807
|
qualityScore: res.qualityScore, shouldAutoSend: res.shouldAutoSend,
|
|
@@ -731,7 +950,12 @@ function getStatus() {
|
|
|
731
950
|
return `${C_DIM}${icon}${RESET} ${color}${val}${RESET}`;
|
|
732
951
|
});
|
|
733
952
|
// 按剩余宽度取舍 (真机 112 列时右段被截 ⇒ 看着像没出现)
|
|
734
|
-
|
|
953
|
+
// 2026-10-01: 底栏一律用**保守测宽** (Ambiguous 字符按 2 列) —— 超 1 列就会让终端折行、
|
|
954
|
+
// 进而打破 Ink 光标数学, 表现是整块底栏被重复打印 (见 status-segments.dispWidthSafe 注释)
|
|
955
|
+
// 2026-10-01: 预算按**保守宽度**给 (left + ≥1 格 + title ≤ width-1) ⇒ 任何字体下都不折行
|
|
956
|
+
const __notePlain = (cliSessionPreview && cliSessionTitleKey === cliSessionKey) ? `▸ ${cliSessionPreview}` : '';
|
|
957
|
+
const __budget = statusLineBudget(termWidth(), __leftPlainLen, String(__notePlain || ''));
|
|
958
|
+
const __segsFit = fitSegments(__segs.map(x => x.replace(/\x1b\[[0-9;]*m/g, '')), __budget, dispWidthSafe);
|
|
735
959
|
const __live = __segsFit.length
|
|
736
960
|
? ` ${C_DIM}│${RESET} ` + __segsFit.map(x => {
|
|
737
961
|
const m = /^(\S+)\s(.*)$/.exec(x);
|
|
@@ -742,13 +966,13 @@ function getStatus() {
|
|
|
742
966
|
: '';
|
|
743
967
|
// 2026-09-30 (leo: 「这个 title 需要顶格右侧」): 会话 Title **右对齐**贴右边缘 ——
|
|
744
968
|
// 先算左半边+活数据段的显示宽, 再补空格把 `▸ 标题` 推到 width-1 处; 放不下就不显示 (不挤坏这一行)。
|
|
745
|
-
const __notePlain = (cliSessionPreview && cliSessionTitleKey === cliSessionKey) ? `▸ ${cliSessionPreview}` : '';
|
|
746
969
|
let __noteStr = '';
|
|
747
970
|
if (__notePlain) {
|
|
748
971
|
// 宽度必须用 dispWidth (East Asian Width 表): 行内的 `│ ░ ◷ ▸ ◎ ✓ ↑ ⚙` 都是**单宽**,
|
|
749
972
|
// 用 "charCode > 255 ⇒ 2" 的启发式会每行多算十几列 ⇒ 右对齐留一大截 (leo: 「没有完全右对齐」)。
|
|
750
|
-
const used =
|
|
751
|
-
|
|
973
|
+
const used = dispWidthSafe(`${__left}${__live}`);
|
|
974
|
+
// 保守测宽 (同上): 右对齐后整行最多到 width-1, 在任何终端字体下都不会折行
|
|
975
|
+
const pad = rightAlignPad(used, dispWidthSafe(__notePlain), termWidth());
|
|
752
976
|
if (pad !== null)
|
|
753
977
|
__noteStr = ' '.repeat(pad) + `${C_DIM}${__notePlain}${RESET}`;
|
|
754
978
|
}
|
|
@@ -1150,6 +1374,8 @@ async function startCLI(commReady) {
|
|
|
1150
1374
|
startupPanelReady = true;
|
|
1151
1375
|
// 2026-09-30: 面板在起帧前已进 store ⇒ 首帧里与输入框一起出现
|
|
1152
1376
|
startInk((text) => { processInput(text, comm); }, initialStatus, getStatus);
|
|
1377
|
+
// 2026-10-01: Ink 起来了 ⇒ 把启动期缓冲的日志一次性灌进对话流(不丢, 也不跟面板交错 ✓)
|
|
1378
|
+
flushBootBuffer();
|
|
1153
1379
|
// 2026-08-10: 自动整理心跳 (CLI 侧, 与社交心跳并列) — 启动后立即"固定看一下 skills view"
|
|
1154
1380
|
// (扫描遗留 skills), 之后按周期 (默认 30min, env BOLLOON_ORGANIZE_HEARTBEAT_MS) 完整进化经验.
|
|
1155
1381
|
// 显示走 transient 颜文字行: 触发时显示, 结束后清空 (显示为空).
|
|
@@ -1200,7 +1426,7 @@ async function startCLI(commReady) {
|
|
|
1200
1426
|
new Promise((res) => setTimeout(() => res(null), 8000)),
|
|
1201
1427
|
]);
|
|
1202
1428
|
if (a && typeof a.promptStream === 'function') {
|
|
1203
|
-
llm = (p) => a.promptStream(p, () => { }, undefined, cliActiveChannelId || undefined);
|
|
1429
|
+
llm = (p) => deliverThroughActor(a, () => a.promptStream(p, () => { }, undefined, cliActiveChannelId || undefined));
|
|
1204
1430
|
}
|
|
1205
1431
|
}
|
|
1206
1432
|
catch { /* 无 agent → 仅扫描 */ }
|
|
@@ -1303,6 +1529,7 @@ async function startCLI(commReady) {
|
|
|
1303
1529
|
* scheduler 的每个 tick 会先查 resolveDnd(), 处于勿扰时把 due job 记 deferred 并留到下一轮.
|
|
1304
1530
|
*/
|
|
1305
1531
|
async function processInput(input, comm) {
|
|
1532
|
+
endBootPhase(); // 用户已接管 ⇒ 启动期结束(之后的输出照常上屏 ✓)
|
|
1306
1533
|
const gate = globalThis.__bolloonMainTask;
|
|
1307
1534
|
if (gate?.enterMainTask) {
|
|
1308
1535
|
try {
|
|
@@ -1440,7 +1667,31 @@ async function processInputInner(input, comm) {
|
|
|
1440
1667
|
const extra = prev && prev.name !== r.identity.name ? ` (从 ${prev.name} 切换)` : '';
|
|
1441
1668
|
appendLine(`${C_ACCENT}→ 当前智能体: ${r.identity.name}${RESET}${extra}`);
|
|
1442
1669
|
appendLine(`${C_DIM} channel: ${r.channel.id} [${r.match}]${RESET}`);
|
|
1443
|
-
|
|
1670
|
+
// 2026-10-01 (用户报「切换身份还没有成功」): 把**CLI 认为的身份**打进对话流 —— 之前用 console.warn,
|
|
1671
|
+
// 在这套 TUI 里被吞掉, 用户从来看不到 ✗。切频道才一行, 不刷屏。
|
|
1672
|
+
try {
|
|
1673
|
+
const live = agent?.getIdentity?.() ?? null;
|
|
1674
|
+
appendLine(`${C_DIM} [identity] 频道身份=${r.identity.name} (did=${String(r.identity?.did ?? '(无)').slice(0, 26)})` +
|
|
1675
|
+
` · agent 当前身份=${live?.name ?? '(读不到)'} (did=${String(live?.did ?? '(无)').slice(0, 26)})` +
|
|
1676
|
+
` · agentId=${r.channel.agentId ?? '(无)'}${RESET}`);
|
|
1677
|
+
}
|
|
1678
|
+
catch { /* 自证失败不影响切换 */ }
|
|
1679
|
+
// 2026-10-01: 这行原先读 channels.json 里**内联的 metadata** (你四条 channel 都空 ⇒ 恒打"无"),
|
|
1680
|
+
// 而身份真源是 ~/.bolloon/persona/<agentId>/ 的 6 份文档 ⇒ 改读真源, 如实报"加载到几份"。
|
|
1681
|
+
try {
|
|
1682
|
+
const { loadPersonaDocs, PERSONA_DOC_FILES_HINT } = await import('./bootstrap/persona-loader.js').then(async (m) => ({ loadPersonaDocs: m.loadPersonaDocs, PERSONA_DOC_FILES_HINT: ['soul', 'identity', 'project', 'user', 'agent', 'wiki'] }));
|
|
1683
|
+
const agentId = r.channel.agentId || '';
|
|
1684
|
+
const docs = agentId ? await loadPersonaDocs(agentId) : null;
|
|
1685
|
+
const loaded = docs ? PERSONA_DOC_FILES_HINT.filter((k) => String(docs[k] || '').trim().length > 0) : [];
|
|
1686
|
+
appendLine(agentId
|
|
1687
|
+
? (loaded.length
|
|
1688
|
+
? `${C_DIM} persona: ${loaded.length}/6 份身份文档 (${loaded.join(' · ')}) · agentId=${agentId}${RESET}`
|
|
1689
|
+
: `${C_DIM} persona: 未初始化 (agentId=${agentId}, 目录 ${'~/.bolloon/persona/' + agentId}/ 为空 — 下次加载会自动生成起步模板)${RESET}`)
|
|
1690
|
+
: `${C_DIM} persona: 该 channel 没有 agentId ⇒ 无法按 agent 加载身份文档${RESET}`);
|
|
1691
|
+
}
|
|
1692
|
+
catch {
|
|
1693
|
+
appendLine(`${C_DIM} persona: (读身份文档失败, 不影响本次切换)${RESET}`);
|
|
1694
|
+
}
|
|
1444
1695
|
}
|
|
1445
1696
|
catch (e) {
|
|
1446
1697
|
appendLine(`${C_ERROR}/channel 失败: ${String(e.message || e).slice(0, 200)}${RESET}`);
|
|
@@ -1522,7 +1773,7 @@ async function processInputInner(input, comm) {
|
|
|
1522
1773
|
});
|
|
1523
1774
|
}
|
|
1524
1775
|
await fs.writeFile(agentsPath, JSON.stringify(arr, null, 2), 'utf-8');
|
|
1525
|
-
|
|
1776
|
+
writeOut(`[创建频道] agent 同步进 agents.json: ${agentId} → channel ${id}`);
|
|
1526
1777
|
}
|
|
1527
1778
|
catch { /* agents.json 写失败不阻塞创建 */ }
|
|
1528
1779
|
// 刷新 store 缓存 (updateChannels 走了 server-storage, store 内存还是旧的)
|
|
@@ -1619,6 +1870,14 @@ async function processInputInner(input, comm) {
|
|
|
1619
1870
|
// 不能全局改成"首词" —— 同文件里 `/email clear` 这类多词命令就是靠整行匹配的。
|
|
1620
1871
|
// 所以加一个**首词**给"带参数的命令"用 (cmd 保持原样给多词命令用)。
|
|
1621
1872
|
const cmdHead = cmd.split(/\s+/)[0];
|
|
1873
|
+
// 2026-10-01: **未知命令兜底** —— 以 / 开头但不在白名单里的输入, 不当消息发下去 ✗
|
|
1874
|
+
// (用户实测: /group 不是命令 ⇒ 却发给模型, 白烧一轮工具调用)
|
|
1875
|
+
if (cmd.startsWith('/') && !CLI_KNOWN_COMMAND_HEADS.has(cmdHead)) {
|
|
1876
|
+
const near = [...CLI_KNOWN_COMMAND_HEADS].filter((h) => h.includes(cmdHead.slice(1)) || cmdHead.includes(h.slice(1))).slice(0, 6);
|
|
1877
|
+
appendLine(`${C_WARN}未知命令 ${cmdHead}${RESET}${C_DIM} —— 输入${RESET} / ${C_DIM}看命令列表, 或 /help 看用法${RESET}`
|
|
1878
|
+
+ (near.length ? `${C_DIM} · 你是不是想用: ${near.join(' / ')}${RESET}` : ''));
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1622
1881
|
// /net — Agent 网络快捷命令 (join/status/ctx, 2026-09-08)
|
|
1623
1882
|
if (cmd === '/net' || cmd.startsWith('/net ')) {
|
|
1624
1883
|
const arg = trimmed.slice(4).trim();
|
|
@@ -1747,7 +2006,7 @@ async function processInputInner(input, comm) {
|
|
|
1747
2006
|
return;
|
|
1748
2007
|
}
|
|
1749
2008
|
const agent = await getAgent();
|
|
1750
|
-
const active = String(agent?.
|
|
2009
|
+
const active = String(agent?.actor?.state?.channelId || ''); // K5 步骤⑧: 本体在 actor (删访问器后这里曾静默变空 ⇒ 安全检查失效)
|
|
1751
2010
|
if (rec.channelId && active && rec.channelId !== active) {
|
|
1752
2011
|
appendLine(`${C_ERROR}该运行属于 channel ${rec.channelId} (当前 ${active}) — 先 /channel 切过去再 /resume${RESET}`);
|
|
1753
2012
|
return;
|
|
@@ -2056,7 +2315,7 @@ async function processInputInner(input, comm) {
|
|
|
2056
2315
|
}
|
|
2057
2316
|
agent.setGoalId?.(req.goal.goalId);
|
|
2058
2317
|
agent.setContinuationGuards?.(req.guards || []);
|
|
2059
|
-
await agent.prompt(req.instruction);
|
|
2318
|
+
await deliverThroughActor(agent, () => agent.prompt(req.instruction));
|
|
2060
2319
|
return { runId: agent.getLastRunId?.() || agent.getRunId?.(), status: 'done' };
|
|
2061
2320
|
}
|
|
2062
2321
|
: undefined;
|
|
@@ -2615,7 +2874,7 @@ async function processInputInner(input, comm) {
|
|
|
2615
2874
|
const a = await getAgent();
|
|
2616
2875
|
const h = a.messageHistory ?? [];
|
|
2617
2876
|
appendLine(`${C_ACCENT}当前会话:${RESET} ${C_DIM}${cliSessionKey || '—'}${RESET}`);
|
|
2618
|
-
appendLine(` ${C_DIM}channel:${RESET} ${a.
|
|
2877
|
+
appendLine(` ${C_DIM}channel:${RESET} ${a.actor?.state?.channelId || cliActiveChannelId || '—'} ${C_DIM}agent:${RESET} ${a.currentAgentId || cliAgentId || '—'}`);
|
|
2619
2878
|
appendLine(` ${C_DIM}消息:${RESET} ${h.length} 条 ${h.length > 15 ? `${C_DIM}(${h.length - 15} 条已压缩)${RESET}` : ''}`);
|
|
2620
2879
|
}
|
|
2621
2880
|
catch (e) {
|
|
@@ -3144,7 +3403,7 @@ async function processInputInner(input, comm) {
|
|
|
3144
3403
|
const a = await getAgent();
|
|
3145
3404
|
appendLine(`${C_ACCENT}智能体:${RESET}`);
|
|
3146
3405
|
appendLine(` ${C_DIM}名称:${RESET} ${cliAgentName}`);
|
|
3147
|
-
appendLine(` ${C_DIM}agentId:${RESET} ${a.
|
|
3406
|
+
appendLine(` ${C_DIM}agentId:${RESET} ${a.actor?.state?.agentId || '—'}`); // K5 步骤⑧: 本体在 actor
|
|
3148
3407
|
appendLine(` ${C_DIM}channel:${RESET} ${cliActiveChannelId || '—'}`);
|
|
3149
3408
|
}
|
|
3150
3409
|
catch { /* 静默 */ }
|
|
@@ -3576,6 +3835,153 @@ async function processInputInner(input, comm) {
|
|
|
3576
3835
|
}
|
|
3577
3836
|
return;
|
|
3578
3837
|
}
|
|
3838
|
+
// 「当前群」的取用: 没选过时, 只有恰好一个群就自动用它; 否则提示先 /group use
|
|
3839
|
+
const ensureCurrentGroup = async (gg) => {
|
|
3840
|
+
if (currentGroupId)
|
|
3841
|
+
return currentGroupId;
|
|
3842
|
+
const gs = await gg.listGroups();
|
|
3843
|
+
if (gs.length === 1) {
|
|
3844
|
+
currentGroupId = gs[0].id;
|
|
3845
|
+
return currentGroupId;
|
|
3846
|
+
}
|
|
3847
|
+
if (!gs.length) {
|
|
3848
|
+
appendLine(`${C_DIM}本地还没有群 —— 用 /group new <名字> 建一个${RESET}`);
|
|
3849
|
+
return undefined;
|
|
3850
|
+
}
|
|
3851
|
+
appendLine(`${C_DIM}本地有 ${gs.length} 个群, 先 /group use <名字|id> 选一个${RESET}`);
|
|
3852
|
+
return undefined;
|
|
3853
|
+
};
|
|
3854
|
+
// /group — OrbitDB 群聊 (2026-10-01; 接 gateway-group 那套: 建群/加入/收发/成员/日志)
|
|
3855
|
+
if (cmd === '/group' || cmd.startsWith('/group ')) {
|
|
3856
|
+
const { sub, arg } = parseGroupSub(trimmed);
|
|
3857
|
+
const gg = await import('./agents/gateway-group.js');
|
|
3858
|
+
const dim = (t) => appendLine(`${C_DIM}${t}${RESET}`);
|
|
3859
|
+
try {
|
|
3860
|
+
if (sub === 'help') {
|
|
3861
|
+
appendLine(`${C_ACCENT}/group${RESET} —— OrbitDB 群聊`);
|
|
3862
|
+
dim(' /group 列出本地群');
|
|
3863
|
+
dim(' /group new <名字> 建群 (默认创建者独占写; 会打印可分享地址)');
|
|
3864
|
+
dim(' /group join <链接|地址> 加入');
|
|
3865
|
+
dim(' /group leave <名字|id> 退出');
|
|
3866
|
+
dim(' /group use <名字|id> 选定当前群 (之后 send/log/members 都指它)');
|
|
3867
|
+
dim(' /group send <文本> 往当前群发一条');
|
|
3868
|
+
dim(' /group log [N] 看当前群最近 N 条 (默认 10)');
|
|
3869
|
+
dim(' /group members 列当前群成员');
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
if (sub === 'list') {
|
|
3873
|
+
const gs = await gg.listGroups();
|
|
3874
|
+
if (!gs.length) {
|
|
3875
|
+
dim('本地还没有群 —— 用 /group new <名字> 建一个');
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3878
|
+
const rows = [];
|
|
3879
|
+
gs.forEach((g, i) => {
|
|
3880
|
+
const cur = currentGroupId === g.id ? '●' : '○';
|
|
3881
|
+
const n = String(i + 1).padStart(2, ' ');
|
|
3882
|
+
rows.push(`${n}. ${cur} ${g.name} ${String(g.id).slice(0, 18)}…`);
|
|
3883
|
+
});
|
|
3884
|
+
rows.push('');
|
|
3885
|
+
rows.push('选中进群: /group <序号> (或 /group use <名字|id>)');
|
|
3886
|
+
appendLine(renderMessageBox({ title: 'OrbitDB 群', body: rows.join('\n'), color: C_ACCENT, maxLines: 24 }));
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
if (sub === 'new') {
|
|
3890
|
+
const r = await gg.createGroup(arg || '');
|
|
3891
|
+
if (!r.ok) {
|
|
3892
|
+
appendLine(`${C_WARN}建群失败: ${r.error}${RESET}`);
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
currentGroupId = r.group?.id || currentGroupId;
|
|
3896
|
+
appendLine(renderMessageBox({ title: '✅ 群已建立', body: `名字: ${r.group?.name || arg}\nid: ${r.group?.id || '-'}\n地址: ${r.group?.link || r.group?.address || '(未返回地址)'}\n\n把地址发给同伴, 用 /group join <地址> 加入`, color: C_ACCENT, maxLines: 12 }));
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
if (sub === 'join') {
|
|
3900
|
+
if (!arg) {
|
|
3901
|
+
dim('用法: /group join <链接|地址>');
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
const r = await gg.joinGroup(arg);
|
|
3905
|
+
if (!r.ok) {
|
|
3906
|
+
appendLine(`${C_WARN}加入失败: ${r.error}${RESET}`);
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
currentGroupId = r.group?.id || currentGroupId;
|
|
3910
|
+
appendLine(`${C_ACCENT}✓ 已加入 ${r.group?.name || arg}${RESET}`);
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
if (sub === 'leave') {
|
|
3914
|
+
if (!arg) {
|
|
3915
|
+
dim('用法: /group leave <名字|id>');
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
const r = await gg.leaveGroup(arg);
|
|
3919
|
+
appendLine(r.ok ? `${C_ACCENT}✓ 已退出 ${r.removed || arg}${RESET}` : `${C_WARN}退出失败: ${r.error}${RESET}`);
|
|
3920
|
+
if (r.ok && currentGroupId === arg)
|
|
3921
|
+
currentGroupId = undefined;
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
if (sub === 'use') {
|
|
3925
|
+
if (!arg) {
|
|
3926
|
+
dim('用法: /group use <名字|id>');
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
const gs = await gg.listGroups();
|
|
3930
|
+
// 序号 (与 /group 列表里的编号一致) → 名字 → id → 名字子串
|
|
3931
|
+
let hit = /^\d+$/.test(arg) ? gs[parseInt(arg, 10) - 1] : undefined;
|
|
3932
|
+
if (!hit)
|
|
3933
|
+
hit = gs.find((g) => g.id === arg || g.name === arg) || gs.find((g) => String(g.name).includes(arg));
|
|
3934
|
+
if (!hit) {
|
|
3935
|
+
appendLine(`${C_WARN}没找到群: ${arg}${RESET}${C_DIM} —— 先 /group 看编号${RESET}`);
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
currentGroupId = hit.id;
|
|
3939
|
+
appendLine(`${C_ACCENT}✓ 当前群: ${hit.name} (${String(hit.id).slice(0, 18)}…)${RESET}${C_DIM} 现在可以 /group send <文本> 或 /group log${RESET}`);
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
if (sub === 'send') {
|
|
3943
|
+
if (!arg) {
|
|
3944
|
+
dim('用法: /group send <文本>');
|
|
3945
|
+
return;
|
|
3946
|
+
}
|
|
3947
|
+
const gid = await ensureCurrentGroup(gg);
|
|
3948
|
+
if (!gid)
|
|
3949
|
+
return;
|
|
3950
|
+
const r = await gg.groupSend(gid, arg, String(agentIdentity?.did || 'cli')).catch((e) => ({ ok: false, error: String(e?.message || e) }));
|
|
3951
|
+
appendLine(r.ok === false ? `${C_WARN}发送失败: ${r.error}${RESET}` : `${C_ACCENT}✓ 已发到群${RESET}`);
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
if (sub === 'log') {
|
|
3955
|
+
const gid = await ensureCurrentGroup(gg);
|
|
3956
|
+
if (!gid)
|
|
3957
|
+
return;
|
|
3958
|
+
const n = Math.max(1, Math.min(100, parseInt(arg || '10', 10) || 10));
|
|
3959
|
+
const msgs = await gg.groupMessages(gid, n);
|
|
3960
|
+
if (!msgs.length) {
|
|
3961
|
+
dim('这个群还没有消息');
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
appendLine(renderMessageBox({ title: `群消息 (最近 ${msgs.length})`, body: msgs.map((m) => `${m.from ? String(m.from).slice(0, 10) : '?'}: ${String(m.text || '').slice(0, 200)}`).join('\n'), color: C_ACCENT, maxLines: 24 }));
|
|
3965
|
+
return;
|
|
3966
|
+
}
|
|
3967
|
+
if (sub === 'members') {
|
|
3968
|
+
const gid = await ensureCurrentGroup(gg);
|
|
3969
|
+
if (!gid)
|
|
3970
|
+
return;
|
|
3971
|
+
const ms = await gg.groupMembers(gid);
|
|
3972
|
+
dim(ms.length ? ms.join('\n') : '(暂无成员记录)');
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
dim('用法: /group help');
|
|
3976
|
+
}
|
|
3977
|
+
catch (e) {
|
|
3978
|
+
// GroupStoreUnreachableError 等: 说清"本地群存储没起来", 不要笼统报错
|
|
3979
|
+
const msg = String(e?.message || e);
|
|
3980
|
+
const unreachable = e?.name === 'GroupStoreUnreachableError' || /unreachable|not open|未就绪/i.test(msg);
|
|
3981
|
+
appendLine(`${C_WARN}${unreachable ? '本地群存储未就绪 —— 需要本机服务/OrbitDB 起来后才行' : '群操作失败'}: ${msg}${RESET}`);
|
|
3982
|
+
}
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3579
3985
|
if (trimmed.toLowerCase() === '/help' || trimmed === 'help') {
|
|
3580
3986
|
appendLine(`${C_DIM}命令:${RESET}`);
|
|
3581
3987
|
appendLine(` ${C_ACCENT}!<cmd>${RESET} 执行终端命令 ${C_DIM}如 !ls -la${RESET}`);
|
|
@@ -3710,12 +4116,24 @@ async function processInputInner(input, comm) {
|
|
|
3710
4116
|
cliTurnStartedAt = Date.now();
|
|
3711
4117
|
cliTurnReplyBytes = 0;
|
|
3712
4118
|
cliTurnToolCount = 0;
|
|
3713
|
-
const response = await a.prompt(trimmed, {
|
|
4119
|
+
const response = await deliverThroughActor(a, () => a.prompt(trimmed, {
|
|
3714
4120
|
onStream: (e) => {
|
|
3715
4121
|
// 2026-08-07: 中间思考/状态显示 — 之前只显示工具步骤, LLM 的思考过程
|
|
3716
4122
|
// (thinking / status / phase / Reflection) 全被丢弃 → 用户只能看到输入和最终输出
|
|
3717
4123
|
// 用户偏好 (2026-08-07): 思考过程 = 圆角框渲染 (和回复同路径 renderMessageBox),
|
|
3718
4124
|
// 颜文字动画 (inkSetThinking) 只表示"正在运行", 不承载思考内容
|
|
4125
|
+
// 2026-10-01: 思考记录 (模型的思维链) —— 暗色块 + 明确标注"未验证", 与正式回答分开 ✓
|
|
4126
|
+
if (e.type === 'reasoning' && e.content) {
|
|
4127
|
+
// 2026-10-01 (用户: 「我要的是那种**短的**思考, 长思维链可以不显示」): 默认只显示**一句**"在想什么";
|
|
4128
|
+
// 想看全链 ⇒ BOLLOON_SHOW_THINKING=full; 不想看 ⇒ =0。
|
|
4129
|
+
const r = renderReasoning(String(e.content));
|
|
4130
|
+
if (r.text) {
|
|
4131
|
+
if (r.mode === 'short')
|
|
4132
|
+
appendLine(`${C_DIM}💭 ${r.text}${RESET}`);
|
|
4133
|
+
else
|
|
4134
|
+
appendLine(renderMessageBox({ title: '💭 思考 (未验证)', body: r.text, color: C_DIM, maxLines: 16 }));
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
3719
4137
|
if (e.type === 'thinking' && e.content) {
|
|
3720
4138
|
// thinking 事件只有 "🤔 开始思考..." 占位 → 不 appendLine, 运行过程由动画表示;
|
|
3721
4139
|
// 真正思考内容在 status 的 Reflection/💡 事件 → 下方框渲染
|
|
@@ -3742,7 +4160,10 @@ async function processInputInner(input, comm) {
|
|
|
3742
4160
|
// Reflection / 反思 / 💡 → 圆角思考框 (和回复一样走 renderMessageBox, 白字+亮边框)
|
|
3743
4161
|
else if (content.includes('Reflection') || content.includes('反思') || content.includes('💡')) {
|
|
3744
4162
|
const body = content.replace(/^💡\s*/, '').slice(0, 1500);
|
|
3745
|
-
|
|
4163
|
+
// 2026-10-01: 只有标记词 (Reflection: / 反思 / 💡) 而无正文 ⇒ **不渲染**, 免得出空框 ✗
|
|
4164
|
+
const plain = body.replace(/\x1b\[[0-9;]*m/g, '').trim(); // 2026-10-01: 先剥 ANSI —— 只有彩色控制符的正文看着就是**空框** ✗
|
|
4165
|
+
const onlyMarker = /^(?:reflection|反思)\s*[::\-—]?\s*$/i.test(plain);
|
|
4166
|
+
if (plain && !onlyMarker)
|
|
3746
4167
|
appendLine(renderMessageBox({ title: '💡 反思', body, color: C_WARN }));
|
|
3747
4168
|
}
|
|
3748
4169
|
else if (!content.includes('🔄 循环') && !content.includes('📋 参数')
|
|
@@ -3763,7 +4184,9 @@ async function processInputInner(input, comm) {
|
|
|
3763
4184
|
// 改用 transient 行显示"正在执行"(消息流只在 done 时出现一次完成行).
|
|
3764
4185
|
if (toolName !== 'system' && toolName !== 'loop' && toolName !== '?') {
|
|
3765
4186
|
const activeNames = tuiToolCalls.map(c => c.tool).filter(t => t !== 'system' && t !== 'loop' && t !== '?');
|
|
3766
|
-
const label = activeNames.length > 1
|
|
4187
|
+
const label = activeNames.length > 1
|
|
4188
|
+
? `执行 ${activeNames.length} 个工具: ${activeNames.join(', ')}`
|
|
4189
|
+
: `🔧 ${traceLabel(toolName)}`; // trace 执行描述: 写清这一步在做什么
|
|
3767
4190
|
inkSetTransient(`${C_DIM}${label} 运行中...${RESET}`);
|
|
3768
4191
|
}
|
|
3769
4192
|
}
|
|
@@ -3793,7 +4216,7 @@ async function processInputInner(input, comm) {
|
|
|
3793
4216
|
const remaining = tuiToolCalls.filter(c => c.tool !== 'system' && c.tool !== 'loop' && c.tool !== '?');
|
|
3794
4217
|
cliTurnToolCount = remaining.length; // ⚙ 状态栏用 (模块级)
|
|
3795
4218
|
if (remaining.length > 0) {
|
|
3796
|
-
const label = remaining.length > 1 ? `执行 ${remaining.length} 个工具: ${remaining.map(c => c.tool).join(', ')}` : `🔧 ${remaining[0].tool}`;
|
|
4219
|
+
const label = remaining.length > 1 ? `执行 ${remaining.length} 个工具: ${remaining.map(c => c.tool).join(', ')}` : `🔧 ${traceLabel(remaining[0].tool)}`; // trace 执行描述
|
|
3797
4220
|
inkSetTransient(`${C_DIM}${label} 运行中...${RESET}`);
|
|
3798
4221
|
}
|
|
3799
4222
|
else {
|
|
@@ -3801,7 +4224,7 @@ async function processInputInner(input, comm) {
|
|
|
3801
4224
|
}
|
|
3802
4225
|
}
|
|
3803
4226
|
}
|
|
3804
|
-
});
|
|
4227
|
+
}));
|
|
3805
4228
|
// 智能体回复框
|
|
3806
4229
|
appendLine(renderAgentMessage(response));
|
|
3807
4230
|
try {
|
|
@@ -4071,7 +4494,7 @@ goalFlag) {
|
|
|
4071
4494
|
error = response;
|
|
4072
4495
|
break;
|
|
4073
4496
|
}
|
|
4074
|
-
const result = await a.summarizeDocument(filePath, ctx.join(' '));
|
|
4497
|
+
const result = await deliverThroughActor(a, () => a.summarizeDocument(filePath, ctx.join(' ')));
|
|
4075
4498
|
response = `📝 摘要:\n${result.summary}\n\n质量评分: ${(result.qualityScore * 10).toFixed(1)}/10`;
|
|
4076
4499
|
metadata.qualityScore = result.qualityScore;
|
|
4077
4500
|
break;
|
|
@@ -4083,10 +4506,10 @@ goalFlag) {
|
|
|
4083
4506
|
error = response;
|
|
4084
4507
|
break;
|
|
4085
4508
|
}
|
|
4086
|
-
const result = await a.improveDocument({
|
|
4509
|
+
const result = await deliverThroughActor(a, () => a.improveDocument({
|
|
4087
4510
|
originalPath: filePath,
|
|
4088
4511
|
requirements: req.join(' ')
|
|
4089
|
-
});
|
|
4512
|
+
}));
|
|
4090
4513
|
response = result.newContent || '';
|
|
4091
4514
|
if (!result.improved) {
|
|
4092
4515
|
response = '错误: 改进失败';
|
|
@@ -4267,7 +4690,7 @@ goalFlag) {
|
|
|
4267
4690
|
error = response;
|
|
4268
4691
|
break;
|
|
4269
4692
|
}
|
|
4270
|
-
response = await a.prompt(text);
|
|
4693
|
+
response = await deliverThroughActor(a, () => a.prompt(text));
|
|
4271
4694
|
break;
|
|
4272
4695
|
}
|
|
4273
4696
|
case 'tools': {
|
|
@@ -4895,18 +5318,18 @@ goalFlag) {
|
|
|
4895
5318
|
error,
|
|
4896
5319
|
metadata
|
|
4897
5320
|
};
|
|
4898
|
-
|
|
5321
|
+
writeOut(JSON.stringify(result, null, 2));
|
|
4899
5322
|
}
|
|
4900
5323
|
else {
|
|
4901
5324
|
if (error) {
|
|
4902
5325
|
s.divider();
|
|
4903
|
-
|
|
5326
|
+
writeOut(`${MAGENTA}${error}${RESET}\n`);
|
|
4904
5327
|
}
|
|
4905
5328
|
else {
|
|
4906
5329
|
s.divider();
|
|
4907
|
-
|
|
5330
|
+
writeOut(`${response}\n`);
|
|
4908
5331
|
}
|
|
4909
|
-
|
|
5332
|
+
writeOut(`${GRAY}耗时: ${metadata.duration}ms${RESET}`);
|
|
4910
5333
|
}
|
|
4911
5334
|
}
|
|
4912
5335
|
async function runNonInteractive(args, comm) {
|
|
@@ -4922,11 +5345,11 @@ async function runNonInteractive(args, comm) {
|
|
|
4922
5345
|
}
|
|
4923
5346
|
else if (prompt) {
|
|
4924
5347
|
const a = await getAgent();
|
|
4925
|
-
|
|
5348
|
+
writeOut(await deliverThroughActor(a, () => a.prompt(prompt)));
|
|
4926
5349
|
}
|
|
4927
5350
|
console.log = originalLog;
|
|
4928
5351
|
await fs.writeFile(output, outputBuffer.trim(), 'utf-8');
|
|
4929
|
-
|
|
5352
|
+
writeOut(`✅ 结果已保存到: ${output}`);
|
|
4930
5353
|
return;
|
|
4931
5354
|
}
|
|
4932
5355
|
if (tool === 'prompt' && prompt) {
|
|
@@ -4935,23 +5358,23 @@ async function runNonInteractive(args, comm) {
|
|
|
4935
5358
|
const startTime = Date.now();
|
|
4936
5359
|
const a = await getAgent();
|
|
4937
5360
|
try {
|
|
4938
|
-
const response = await a.prompt(prompt);
|
|
5361
|
+
const response = await deliverThroughActor(a, () => a.prompt(prompt));
|
|
4939
5362
|
const elapsed = Date.now() - startTime;
|
|
4940
5363
|
if (json) {
|
|
4941
|
-
|
|
5364
|
+
writeOut(JSON.stringify({ success: true, response, elapsedMs: elapsed }, null, 2));
|
|
4942
5365
|
}
|
|
4943
5366
|
else {
|
|
4944
|
-
|
|
4945
|
-
|
|
5367
|
+
writeOut(response);
|
|
5368
|
+
writeOut(`\n耗时: ${elapsed}ms`);
|
|
4946
5369
|
}
|
|
4947
5370
|
}
|
|
4948
5371
|
catch (e) {
|
|
4949
5372
|
const error = e?.message || String(e);
|
|
4950
5373
|
if (json) {
|
|
4951
|
-
|
|
5374
|
+
writeOut(JSON.stringify({ success: false, error }, null, 2));
|
|
4952
5375
|
}
|
|
4953
5376
|
else {
|
|
4954
|
-
|
|
5377
|
+
writeOut(`\n错误: ${error}`);
|
|
4955
5378
|
}
|
|
4956
5379
|
process.exit(1);
|
|
4957
5380
|
}
|
|
@@ -4965,7 +5388,7 @@ async function runNonInteractive(args, comm) {
|
|
|
4965
5388
|
const a = await getAgent();
|
|
4966
5389
|
let response;
|
|
4967
5390
|
try {
|
|
4968
|
-
response = await a.prompt(prompt);
|
|
5391
|
+
response = await deliverThroughActor(a, () => a.prompt(prompt));
|
|
4969
5392
|
}
|
|
4970
5393
|
catch (e) {
|
|
4971
5394
|
response = `错误: ${e.message}`;
|
|
@@ -4979,10 +5402,10 @@ async function runNonInteractive(args, comm) {
|
|
|
4979
5402
|
error: response.startsWith('错误:') ? response : undefined,
|
|
4980
5403
|
metadata: { duration, peers }
|
|
4981
5404
|
};
|
|
4982
|
-
|
|
5405
|
+
writeOut(JSON.stringify(result, null, 2));
|
|
4983
5406
|
}
|
|
4984
5407
|
else {
|
|
4985
|
-
|
|
5408
|
+
writeOut(response);
|
|
4986
5409
|
}
|
|
4987
5410
|
}
|
|
4988
5411
|
}
|
|
@@ -5300,7 +5723,7 @@ function parseArgs() {
|
|
|
5300
5723
|
return result;
|
|
5301
5724
|
}
|
|
5302
5725
|
function printHelp() {
|
|
5303
|
-
|
|
5726
|
+
writeOut(`
|
|
5304
5727
|
🤖 Bolloon Agent - AI 可调用文档处理智能体
|
|
5305
5728
|
|
|
5306
5729
|
用法:
|
|
@@ -5528,21 +5951,21 @@ async function main() {
|
|
|
5528
5951
|
const res = await runStandaloneSupervisorHost({
|
|
5529
5952
|
once: !!args.superviseOnce,
|
|
5530
5953
|
dryRun: !!args.superviseDryRun,
|
|
5531
|
-
log: (m) =>
|
|
5954
|
+
log: (m) => writeOut(m),
|
|
5532
5955
|
});
|
|
5533
5956
|
if (args.superviseOnce) {
|
|
5534
5957
|
const rep = res.lastReport;
|
|
5535
5958
|
if (rep) {
|
|
5536
|
-
|
|
5959
|
+
writeOut(`调度周期 #${rep.tick}: 认领 ${rep.claimed.length} · 执行 ${rep.executed.length} · 跳过 ${rep.skipped.length}${rep.errors.length ? ` · 错误 ${rep.errors.length}` : ''}`);
|
|
5537
5960
|
for (const s of rep.skipped.slice(0, 8))
|
|
5538
|
-
|
|
5961
|
+
writeOut(` 跳过 ${s.goalId}: ${s.reason}`);
|
|
5539
5962
|
for (const e of rep.executed)
|
|
5540
|
-
|
|
5963
|
+
writeOut(` ▶ ${e.goalId} → run=${e.runId || '-'} ${e.status || ''}${e.error ? ` (${e.error})` : ''}`);
|
|
5541
5964
|
}
|
|
5542
|
-
|
|
5965
|
+
writeOut(`supervisor 宿主: owner=${res.state.owner} worker=${res.state.workerId} ticks=${res.ticks} 状态文件=~/.bolloon/supervisor.json`);
|
|
5543
5966
|
}
|
|
5544
5967
|
else {
|
|
5545
|
-
|
|
5968
|
+
writeOut(`[supervisor] 常驻宿主已启动 (owner=${res.state.owner}, worker=${res.state.workerId}); Ctrl-C 优雅停止`);
|
|
5546
5969
|
}
|
|
5547
5970
|
return;
|
|
5548
5971
|
}
|
|
@@ -5593,6 +6016,14 @@ async function main() {
|
|
|
5593
6016
|
if (process.env.BOLLOON_SKIP_SETUP === '1') {
|
|
5594
6017
|
noticePreamble('⚠ BOLLOON_SKIP_SETUP=1 → 诊断模式: 可以看状态/修配置, 但 agent 执行被门禁拦住 (不能绕过)');
|
|
5595
6018
|
}
|
|
6019
|
+
else if (onlyConnectivityRetest(gateEv)) {
|
|
6020
|
+
// 2026-10-01 (用户报「启动时候的日志也没去掉」):
|
|
6021
|
+
// 只差"连通性结果过期(需重测)"这一类 ⇒ **不在启动时跑 setup 向导** (跑一次要刷约 30 行前言)。
|
|
6022
|
+
// 改为: 面板内一行告警 + 让用户按需跑 `bolloon setup --test`; 门禁照旧拦住 agent 执行 (不绕过)。
|
|
6023
|
+
const a = `⚠ 连通性结果过期 → 跑 \`bolloon setup --test\` 复测 (启动时不自动跑), 全量: ${SETUP_STATUS_CMD}`;
|
|
6024
|
+
pushStartupAlert(a);
|
|
6025
|
+
noticePreamble(a);
|
|
6026
|
+
}
|
|
5596
6027
|
else {
|
|
5597
6028
|
const { runSetupWizard } = await import('./cli/setup-wizard.js');
|
|
5598
6029
|
// quietStartup: 向导的续跑前言默认不上屏 (它自己会在"要人回答/有失败"时先 flush)
|
|
@@ -5793,7 +6224,7 @@ async function main() {
|
|
|
5793
6224
|
// 默认用户模式: 不自迭代, 自改卡片不自动出现, 仍可 POST /api/self-improve/trigger 手动触发
|
|
5794
6225
|
const selfImprove = process.env.BOLLOON_DEV_MODE === '1' || process.env.BOLLOON_DEV_MODE === 'true';
|
|
5795
6226
|
if (selfImprove) {
|
|
5796
|
-
|
|
6227
|
+
writeOut('[startup] BOLLOON_DEV_MODE=1, 开发者模式: 自迭代已启用');
|
|
5797
6228
|
}
|
|
5798
6229
|
const { createWebServer, openBrowser } = await import('./web/server.js');
|
|
5799
6230
|
// 2026-06-24: CLI 默认 loopback bind (安全), LAN 访问需 BOLLOON_HOST=0.0.0.0
|
|
@@ -5808,7 +6239,7 @@ async function main() {
|
|
|
5808
6239
|
console.info = originalInfo;
|
|
5809
6240
|
process.stdout.write = originalStdoutWrite;
|
|
5810
6241
|
s.info('执行命令...');
|
|
5811
|
-
|
|
6242
|
+
writeOut('');
|
|
5812
6243
|
await runNonInteractive(args, comm);
|
|
5813
6244
|
comm?.stop();
|
|
5814
6245
|
// chat-watch / chat-p2p-listen 是长循环, 不会自然 return, 走 SIGINT 自然退出
|