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