@bolloon/bolloon-agent 0.4.25 → 0.4.27

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.
Files changed (35) hide show
  1. package/dist/agents/execution-supervisor.js +87 -2
  2. package/dist/agents/external-events.js +162 -0
  3. package/dist/agents/goal-criteria.js +124 -0
  4. package/dist/agents/goal-store.js +79 -4
  5. package/dist/agents/p2p-info.js +175 -0
  6. package/dist/agents/pi-sdk.js +39 -0
  7. package/dist/agents/run-store.js +15 -0
  8. package/dist/agents/skill-readiness.js +133 -0
  9. package/dist/agents/skill-supervisor-link.js +70 -0
  10. package/dist/agents/skills-manager.js +282 -0
  11. package/dist/agents/trace-export.js +125 -0
  12. package/dist/agents/write-staging.js +12 -4
  13. package/dist/agents/x402/goal-run-bridge.js +105 -0
  14. package/dist/agents/x402/milestone-settlement.js +150 -0
  15. package/dist/agents/x402/paid-info-store.js +66 -12
  16. package/dist/agents/x402/payment-recovery.js +290 -0
  17. package/dist/agents/x402/resource-contract.js +473 -0
  18. package/dist/agents/x402/settlement-state.js +378 -0
  19. package/dist/agents/x402/trade.js +257 -0
  20. package/dist/agents/x402/transaction-protocol.js +99 -0
  21. package/dist/agents/x402/transaction-store.js +350 -0
  22. package/dist/cli/setup-wizard.js +96 -127
  23. package/dist/cli-entry.js +74 -0
  24. package/dist/electron/first-run.js +33 -2
  25. package/dist/electron-build/electron/first-run.js +35 -2
  26. package/dist/electron-build/electron/first-run.js.map +1 -1
  27. package/dist/index.js +224 -4
  28. package/dist/llm/config-store.js +35 -4
  29. package/dist/network/agent-network.js +10 -0
  30. package/dist/network/goal-event-bridge.js +57 -0
  31. package/dist/setup/onboard.js +549 -0
  32. package/dist/setup/setup-store.js +592 -0
  33. package/dist/web/routes-x402-info.js +1 -0
  34. package/dist/web/server.js +330 -1
  35. package/package.json +1 -1
@@ -12,10 +12,12 @@
12
12
  * - io 可注入 → 单测不碰真实 stdin/stdout
13
13
  */
14
14
  import * as readline from 'readline';
15
+ import { evaluateSetup, providerUsable, readConfigFacts, resolveBolloonHome, } from '../setup/setup-store.js';
16
+ import { runOnboard } from '../setup/onboard.js';
15
17
  import * as fs from 'fs/promises';
16
18
  import * as path from 'path';
17
19
  import * as os from 'os';
18
- import { llmConfigStore, PROVIDER_INFO, DEFAULT_PROVIDER_CONFIGS } from '../llm/config-store.js';
20
+ import { llmConfigStore, PROVIDER_INFO } from '../llm/config-store.js';
19
21
  /** 向导推荐的供应商顺序 (第一个是最省事的国内直连) */
20
22
  export const RECOMMENDED_PROVIDERS = [
21
23
  'deepseek', 'minimax', 'openai', 'anthropic', 'openrouter', 'gemini',
@@ -108,149 +110,116 @@ export async function writeUserIdentity(name, home = os.homedir()) {
108
110
  return { identity, created, file };
109
111
  }
110
112
  // ---------------------------------------------------------------- 首次运行判断
111
- function providerUsable(p) {
112
- if (!p?.enabled)
113
- return false;
114
- if (p.apiKey)
115
- return true;
116
- return p.requiresApiKey === false; // 本地模型 (ollama/local) 不需要 key
117
- }
113
+ // providerUsable 只有一份实现 (setup-store)
118
114
  /** 首次运行: 没有可用供应商, 或还没有用户身份 */
119
115
  export async function isFirstRun(home = os.homedir()) {
116
+ // 2026-09-16 (Phase 1/3): 不再有独立判断 —— 是否"需要引导"由 setup-store 唯一决定。
117
+ // fail-closed: 评估不出来 → 视为"需要引导"。
120
118
  try {
121
- await llmConfigStore.initialize();
122
- const cfg = await llmConfigStore.getConfig();
123
- const usable = Object.values(cfg.providers || {}).some((p) => providerUsable(p));
124
- if (!usable)
125
- return true;
126
- const user = await readUserIdentity(home);
127
- return !user;
119
+ const ev = await evaluateSetup({ bolloonHome: resolveBolloonHome(process.env, home), light: true });
120
+ return ev.gate !== 'ready';
128
121
  }
129
- catch {
130
- return false; // 判断失败不阻塞启动
122
+ catch (e) {
123
+ console.warn('[setup] 初始化状态评估失败, 按"需要引导"处理 (fail-closed):', String(e?.message || e).slice(0, 160));
124
+ return true;
131
125
  }
132
126
  }
133
- /** 运行初始化向导 (交互式或参数式) */
127
+ /** WizardIO → OnboardIO 适配 (select/confirm 用编号或值回答) */
128
+ function onboardIO(io) {
129
+ return {
130
+ print: (m) => io.print(m),
131
+ ask: async (q, opts) => {
132
+ for (let i = 0; i < 3; i++) {
133
+ const v = await io.ask(q, opts?.defaultValue ? { default: opts.defaultValue } : undefined);
134
+ const val = String(v ?? '').trim() || String(opts?.defaultValue ?? '');
135
+ if (!opts?.validate)
136
+ return val;
137
+ const r = opts.validate(val);
138
+ if (r.ok)
139
+ return val;
140
+ io.print(` ✗ ${r.error || '输入无效'}`);
141
+ }
142
+ return String(opts?.defaultValue ?? '');
143
+ },
144
+ askHidden: async (q) => {
145
+ const { askHiddenLine } = await import('./setup-wizard.js').catch(() => ({ askHiddenLine: null }));
146
+ return io.ask(q, { hidden: true }); // WizardIO 已支持 hidden (不回显)
147
+ },
148
+ confirm: async (q, d = true) => {
149
+ const a = String(await io.ask(`${q} (y/n)`, { default: d ? 'y' : 'n' })).trim().toLowerCase();
150
+ return a === '' ? d : /^(y|yes|1|true|是)$/.test(a);
151
+ },
152
+ select: async (q, choices) => {
153
+ io.print(q);
154
+ choices.forEach((c, i) => io.print(` ${i + 1}) ${c.label}${c.hint ? ` — ${c.hint}` : ''}`));
155
+ const a = String(await io.ask(`选择 (序号或名称)`, { default: '1' })).trim();
156
+ if (!a)
157
+ return choices[0]?.value || '';
158
+ if (/^\d+$/.test(a) && choices[Number(a) - 1])
159
+ return choices[Number(a) - 1].value;
160
+ const hit = choices.find((c) => c.value === a) || choices.find((c) => a && c.value.startsWith(a)) || choices.find((c) => a && c.label.includes(a));
161
+ return hit?.value || a;
162
+ },
163
+ };
164
+ }
165
+ /**
166
+ * 运行初始化向导 —— 现在是**可恢复阶段执行器**的薄包装 (Phase 2):
167
+ * load state → 显示已有 → 收集修改 → 校验 → 真实验证 → 原子提交阶段 → 推进
168
+ * 失败: 保留已完成步骤 · 不清配置 · 标失败分类 · 给重试/修改/回退入口 · **不显示配置完成**
169
+ */
134
170
  export async function runSetupWizard(opts = {}) {
135
171
  const io = opts.io ?? defaultWizardIO();
136
172
  const home = opts.home ?? os.homedir();
137
- const interactive = opts.interactive !== false;
173
+ const bolloonHome = resolveBolloonHome(process.env, home);
138
174
  const P = (s) => io.print(s);
175
+ const mode = opts.mode || 'setup';
176
+ P('');
177
+ P('╭─ Bolloon 初始化 (可中断, 下次从失败阶段继续) ─────╮');
178
+ P('│ 身份 → 供应商 → 凭证 → 模型 → 连通性 → 运行时 │');
179
+ P('╰──────────────────────────────────────────────────╯');
180
+ // 参数式输入 (非交互/脚本): 先落盘再跑阶段, 保证"输入已保存"
139
181
  try {
140
- await llmConfigStore.initialize();
182
+ if (opts.name)
183
+ await writeUserIdentity(opts.name, home);
184
+ const prov = opts.provider;
185
+ if (prov) {
186
+ const store = llmConfigStore;
187
+ await store.initialize();
188
+ await store.updateProvider(prov, { enabled: true });
189
+ if (opts.apiKey)
190
+ await store.updateProvider(prov, { apiKey: opts.apiKey });
191
+ if (opts.model)
192
+ await store.updateProvider(prov, { model: opts.model });
193
+ if (mode !== 'reconfigure')
194
+ await store.setActiveProvider(prov);
195
+ }
141
196
  }
142
197
  catch (e) {
143
- return { ok: false, error: `读取配置失败: ${String(e?.message || e).slice(0, 200)}` };
144
- }
145
- P('');
146
- P('╭─ Bolloon 初始化 ─────────────────────────────╮');
147
- P('│ 三步: 你的称呼 → 模型供应商 → API key + 模型 │');
148
- P('╰──────────────────────────────────────────────╯');
149
- // ---- 1) 用户称呼 ----
150
- const existingUser = await readUserIdentity(home);
151
- let name = String(opts.name || '').trim();
152
- if (!name && interactive) {
153
- name = await io.ask('① 我该怎么称呼你?', { default: existingUser?.name || os.userInfo().username });
198
+ return { ok: false, error: `参数落盘失败: ${String(e?.message || e).slice(0, 160)}` };
154
199
  }
155
- if (!name)
156
- name = existingUser?.name || os.userInfo().username;
157
- const idWrite = await writeUserIdentity(name, home);
158
- P(` 身份: ${idWrite.identity.name}${idWrite.created ? ' (已生成新 DID)' : ' (复用已有 DID)'}`);
159
- P(` DID: ${idWrite.identity.did.slice(0, 42)}...`);
160
- P(` 文件: ${idWrite.file}`);
161
- // ---- 2) 供应商 ----
162
- const cfg = await llmConfigStore.getConfig();
163
- const providers = cfg.providers;
164
- let provider = String(opts.provider || '').toLowerCase().trim();
165
- if (!provider && interactive) {
166
- P('');
167
- P('② 选一个模型供应商:');
168
- RECOMMENDED_PROVIDERS.forEach((p, i) => {
169
- const info = PROVIDER_INFO[p] || {};
170
- const st = providers[p];
171
- const mark = providerUsable(st) ? '🔑 已配置' : (st?.requiresApiKey === false ? '免 key' : '');
172
- P(` ${String(i + 1).padStart(2)}. ${p.padEnd(11)} ${String(info.name || '').padEnd(16)} ${mark}`);
173
- });
174
- const ans = await io.ask(' 序号或名字', { default: cfg.activeProvider });
175
- const idx = Number(ans);
176
- provider = Number.isFinite(idx) && idx >= 1 && idx <= RECOMMENDED_PROVIDERS.length
177
- ? RECOMMENDED_PROVIDERS[idx - 1]
178
- : ans.trim().toLowerCase();
179
- }
180
- if (!provider)
181
- provider = cfg.activeProvider;
182
- if (!providers[provider]) {
183
- return { ok: false, error: `未知供应商 '${provider}'. 可用: ${Object.keys(providers).join(', ')}` };
184
- }
185
- const info = PROVIDER_INFO[provider] || {};
186
- const current = providers[provider];
187
- const needsKey = current?.requiresApiKey !== false;
188
- // ---- 3) API key ----
189
- let apiKey = String(opts.apiKey || '').trim();
190
- if (!apiKey)
191
- apiKey = String(current?.apiKey || '').trim(); // 已配置的沿用
192
- if (!apiKey && needsKey && interactive) {
193
- P('');
194
- P(`③ ${info.name || provider} 需要 API key (输入不回显, 只写本地 ~/.bolloon/bolloon-config.json)`);
195
- apiKey = await io.ask(` 粘贴 ${provider} API key`, { hidden: true });
196
- if (!apiKey)
197
- P(' ⚠ 未输入 key — 该供应商会保持不可用 (可用 bolloon model key <provider> 之后再补)');
198
- }
199
- // ---- 4) 模型 ----
200
- const models = Array.isArray(info.models) ? info.models : [];
201
- let model = String(opts.model || '').trim();
202
- if (!model && interactive) {
203
- if (models.length > 0)
204
- P(` 可选模型: ${models.slice(0, 8).join(' / ')}`);
205
- model = await io.ask('④ 用哪个模型?', { default: current?.model || models[0] || '' });
206
- }
207
- if (!model)
208
- model = current?.model || models[0] || '';
209
- // ---- 5) 落盘 ----
210
- const patch = { enabled: true };
211
- if (apiKey)
212
- patch.apiKey = apiKey;
213
- if (model)
214
- patch.model = model;
215
- if (!current?.baseUrl)
216
- patch.baseUrl = DEFAULT_PROVIDER_CONFIGS[provider]?.baseUrl;
217
- await llmConfigStore.updateProvider(provider, patch);
218
- const usableNow = apiKey || !needsKey;
219
- if (usableNow) {
220
- await llmConfigStore.setActiveProvider(provider);
221
- }
222
- // ---- 6) 连通性测试 ----
223
- let test;
224
- if (!opts.skipTest && usableNow) {
225
- P('');
226
- P('⑤ 测试连通性...');
227
- try {
228
- const r = await llmConfigStore.testProvider(provider);
229
- test = { success: !!r.success, latency: r.latency, error: r.error };
230
- P(r.success ? ` ✅ 连通 (${r.latency ?? '?'} ms)` : ` ⚠ 未通过: ${String(r.error || '').slice(0, 160)}`);
231
- }
232
- catch (e) {
233
- test = { success: false, error: String(e?.message || e).slice(0, 200) };
234
- P(` ⚠ 测试失败: ${test.error}`);
235
- }
236
- }
237
- P('');
238
- P(`✅ 配置完成 — 当前供应商: ${provider}${model ? `, 模型: ${model}` : ''}`);
239
- P(` 配置文件: ${path.join(home, '.bolloon', 'bolloon-config.json')}`);
240
- P(' 开始对话: bolloon --cli');
241
- P('');
200
+ const res = await runOnboard({
201
+ mode,
202
+ io: onboardIO(io),
203
+ home,
204
+ bolloonHome,
205
+ targets: opts.targets,
206
+ skipSteps: opts.skipTest ? ['connectivity'] : undefined,
207
+ oneShot: opts.interactive === false,
208
+ });
209
+ const cfg = await readConfigFacts(bolloonHome).catch(() => null);
242
210
  return {
243
- ok: true,
244
- userName: idWrite.identity.name,
245
- provider,
246
- model,
247
- identityFile: idWrite.file,
248
- identityCreated: idWrite.created,
249
- test,
211
+ ok: res.ok,
212
+ userName: res.state.inputs.name,
213
+ provider: res.state.inputs.provider,
214
+ model: res.state.inputs.model,
215
+ identityFile: getUserIdentityFile(home),
216
+ identityCreated: !!res.state.inputs.identityDid,
217
+ test: res.state.checks.connectivityOk ? { success: true } : { success: false, error: res.state.lastError?.message },
218
+ error: res.ok ? undefined : (res.message || res.actions[0]),
219
+ // 新增: 结构化状态 (旧调用方看不到也不影响)
220
+ ...{ gate: res.gate, stage: res.state.stage, summary: res.summary },
250
221
  };
251
222
  }
252
- // ---------------------------------------------------------------- /model 命令
253
- /** 配置状态一览 (供 CLI 会话内 /model 与 bolloon model 共用) */
254
223
  export async function formatProviderStatus() {
255
224
  await llmConfigStore.initialize();
256
225
  const cfg = await llmConfigStore.getConfig();
package/dist/cli-entry.js CHANGED
@@ -156,6 +156,11 @@ function parseArgs() {
156
156
  return { mode: 'update', args: args.slice(1) };
157
157
  case 'model':
158
158
  return { mode: 'model', args: args.slice(1) };
159
+ // 2026-09-18: 智能体工具执行轨迹 + 本机 P2P 连接信息 (递给名片/小工具用)
160
+ case 'trace':
161
+ return { mode: 'trace', args: args.slice(1) };
162
+ case 'p2p':
163
+ return { mode: 'p2p', args: args.slice(1) };
159
164
  // 2026-09-13: 初始化向导 (用户身份 + 模型供应商 + API key)
160
165
  case 'setup':
161
166
  case 'init':
@@ -321,6 +326,68 @@ async function handleUpdateCommand(updateArgs) {
321
326
  }
322
327
  }
323
328
  /** model 子命令: 列出 / 切换模型供应商 (bolloon model [name] [model]) */
329
+ /**
330
+ * `bolloon trace [runId] [--json] [--last N]`
331
+ * 把 Run 的**工具执行轨迹**(真跑过什么工具、结果、耗时)导出来:
332
+ * 无参 → 列出最近几次运行 + 每步摘要; 带 runId → 输出完整轨迹 (文本或 JSON)。
333
+ * 文本格式与小工具/别的智能体对齐, 可直接复制粘贴交换。
334
+ */
335
+ async function handleTraceCommand(traceArgs) {
336
+ const { listRuns, readRun } = await import('./agents/run-store.js');
337
+ const { runToTraceText, runToTraceJson, summarizeTrace } = await import('./agents/trace-export.js');
338
+ const wantJson = traceArgs.includes('--json');
339
+ const lastIdx = traceArgs.indexOf('--last');
340
+ const last = lastIdx >= 0 ? Number(traceArgs[lastIdx + 1]) : undefined;
341
+ const runId = traceArgs.find((a) => !a.startsWith('--') && a !== String(last));
342
+ if (!runId) {
343
+ const runs = await listRuns({ limit: 20 });
344
+ if (wantJson) {
345
+ console.log(JSON.stringify(runs.map((r) => runToTraceJson(r)), null, 2));
346
+ return;
347
+ }
348
+ console.log(`\n${BOLD}最近 ${runs.length} 次运行的执行轨迹${RESET}\n`);
349
+ if (!runs.length) {
350
+ console.log(' 还没有运行记录 (落盘在 ~/.bolloon/runs/)');
351
+ console.log(` ${CYAN}先让智能体干点活: bolloon --prompt "列出当前目录文件"${RESET}\n`);
352
+ return;
353
+ }
354
+ console.log('─'.repeat(72));
355
+ for (const r of runs) {
356
+ console.log(` ${r.runId} [${r.status}] ${summarizeTrace(r)}`);
357
+ }
358
+ console.log('─'.repeat(72));
359
+ console.log(` ${CYAN}bolloon trace <runId> 看完整轨迹 (文本, 可复制交换)`);
360
+ console.log(` bolloon trace <runId> --json 机器可读${RESET}\n`);
361
+ return;
362
+ }
363
+ const run = await readRun(runId);
364
+ if (!run) {
365
+ console.error(`${MAGENTA}没有这个运行: ${runId}${RESET}`);
366
+ process.exitCode = 1;
367
+ return;
368
+ }
369
+ console.log(wantJson ? JSON.stringify(runToTraceJson(run), null, 2) : runToTraceText(run, { limit: last }));
370
+ if (!wantJson) {
371
+ const j = runToTraceJson(run);
372
+ console.error(`\n${CYAN}(${j.counts.total} 步 · ✓${j.counts.ok} / ✗${j.counts.fail} · ${j.counts.totalMs}ms · 状态 ${j.status})${RESET}`);
373
+ }
374
+ }
375
+ /**
376
+ * `bolloon p2p [--json]`
377
+ * 打印本机 P2P 连接信息 (peerId + 可拨入 multiaddr), 直接可抄进名片/小工具/递给对方智能体。
378
+ * 只报真实拿到的: 节点没跑就说明原因与下一步, 不编造 peerId。
379
+ */
380
+ async function handleP2pCommand(p2pArgs) {
381
+ const { getLocalP2pInfo, formatP2pInfoText, formatP2pInfoJson } = await import('./agents/p2p-info.js');
382
+ const info = await getLocalP2pInfo();
383
+ if (p2pArgs.includes('--json'))
384
+ console.log(formatP2pInfoJson(info));
385
+ else
386
+ console.log(formatP2pInfoText(info));
387
+ // 一次性信息命令必须自己收尾: 读运行中节点会 import network/p2p (libp2p),
388
+ // 那是常驻模块, 句柄不会自己关 → 不显式退出会"输出完了还挂着"。
389
+ process.exit(info.ok ? 0 : 1);
390
+ }
324
391
  async function handleModelCommand(modelArgs) {
325
392
  const { llmConfigStore, PROVIDER_INFO } = await import('./llm/config-store.js');
326
393
  await llmConfigStore.initialize();
@@ -603,6 +670,13 @@ async function main() {
603
670
  case 'model':
604
671
  await handleModelCommand(args);
605
672
  break;
673
+ // 2026-09-18: 工具执行轨迹 / P2P 连接信息
674
+ case 'trace':
675
+ await handleTraceCommand(args);
676
+ break;
677
+ case 'p2p':
678
+ await handleP2pCommand(args);
679
+ break;
606
680
  // 2026-09-13: bolloon setup — 首次运行初始化向导
607
681
  case 'setup':
608
682
  await handleSetupCommand(args);
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { BrowserWindow, app, ipcMain } from 'electron';
8
8
  import * as fs from 'fs';
9
+ import * as os from 'os';
9
10
  import * as path from 'path';
10
11
  import { firstRunFlagPath, dataDir, logsDir } from './paths';
11
12
  import { log } from './logger';
@@ -64,6 +65,26 @@ const OVERLAY_HTML = `
64
65
  </body>
65
66
  </html>
66
67
  `;
68
+ /**
69
+ * 2026-09-16 (Phase 3): 首启**事实**来自 SetupStore 的状态文件 (setup-state.json),
70
+ * first-run flag 只决定"要不要自动弹窗"。Electron 不能用 flag 当初始化事实。
71
+ */
72
+ export function readSetupFact() {
73
+ try {
74
+ const p = path.join(os.homedir(), '.bolloon', 'setup-state.json');
75
+ const js = JSON.parse(fs.readFileSync(p, 'utf8'));
76
+ const stage = String(js?.stage || 'uninitialized');
77
+ const ready = stage === 'ready';
78
+ return { ready, stage, gate: ready ? 'ready' : (stage === 'needs_repair' ? 'repair' : stage === 'blocked' ? 'blocked' : 'setup') };
79
+ }
80
+ catch {
81
+ return { ready: false, stage: 'uninitialized', gate: 'setup' };
82
+ }
83
+ }
84
+ /** 未 ready → 显示 Onboard (无论 flag 是否已看过) */
85
+ export function shouldShowOnboard() {
86
+ return !readSetupFact().ready;
87
+ }
67
88
  export function hasSeenFirstRun() {
68
89
  try {
69
90
  return fs.existsSync(firstRunFlagPath());
@@ -120,10 +141,20 @@ export function registerFirstRunIpc() {
120
141
  log('first-run IPC handlers registered');
121
142
  }
122
143
  /** 包装 — 决定要不要弹 overlay */
144
+ /**
145
+ * 2026-09-16 (Phase 3): 是否弹引导 —— **事实来自 SetupStore** (setup-state.json), flag 只控制"已 ready 后是否还提示"。
146
+ * 未 ready 时无论 flag 有没有都弹 (初始化没完成的机器不能装作已配置)。
147
+ */
123
148
  export async function maybeShowFirstRun(parent) {
124
- if (hasSeenFirstRun())
149
+ const fact = readSetupFact();
150
+ if (fact.ready && hasSeenFirstRun())
125
151
  return;
126
- log('首启 — 弹出引导');
152
+ if (!fact.ready) {
153
+ log(`初始化未就绪 (stage=${fact.stage}, gate=${fact.gate}) — 弹出引导 (flag=${hasSeenFirstRun() ? '已看过' : '未看过'})`);
154
+ }
155
+ else {
156
+ log('首启 — 弹出引导');
157
+ }
127
158
  await showFirstRunOverlay(parent);
128
159
  app.addRecentDocument(firstRunFlagPath()); // 跟踪最近文档, 让 user 知道有这文件
129
160
  }
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readSetupFact = readSetupFact;
37
+ exports.shouldShowOnboard = shouldShowOnboard;
36
38
  exports.hasSeenFirstRun = hasSeenFirstRun;
37
39
  exports.markFirstRunSeen = markFirstRunSeen;
38
40
  exports.showFirstRunOverlay = showFirstRunOverlay;
@@ -46,6 +48,7 @@ exports.maybeShowFirstRun = maybeShowFirstRun;
46
48
  */
47
49
  const electron_1 = require("electron");
48
50
  const fs = __importStar(require("fs"));
51
+ const os = __importStar(require("os"));
49
52
  const path = __importStar(require("path"));
50
53
  const paths_1 = require("./paths");
51
54
  const logger_1 = require("./logger");
@@ -104,6 +107,26 @@ const OVERLAY_HTML = `
104
107
  </body>
105
108
  </html>
106
109
  `;
110
+ /**
111
+ * 2026-09-16 (Phase 3): 首启**事实**来自 SetupStore 的状态文件 (setup-state.json),
112
+ * first-run flag 只决定"要不要自动弹窗"。Electron 不能用 flag 当初始化事实。
113
+ */
114
+ function readSetupFact() {
115
+ try {
116
+ const p = path.join(os.homedir(), '.bolloon', 'setup-state.json');
117
+ const js = JSON.parse(fs.readFileSync(p, 'utf8'));
118
+ const stage = String(js?.stage || 'uninitialized');
119
+ const ready = stage === 'ready';
120
+ return { ready, stage, gate: ready ? 'ready' : (stage === 'needs_repair' ? 'repair' : stage === 'blocked' ? 'blocked' : 'setup') };
121
+ }
122
+ catch {
123
+ return { ready: false, stage: 'uninitialized', gate: 'setup' };
124
+ }
125
+ }
126
+ /** 未 ready → 显示 Onboard (无论 flag 是否已看过) */
127
+ function shouldShowOnboard() {
128
+ return !readSetupFact().ready;
129
+ }
107
130
  function hasSeenFirstRun() {
108
131
  try {
109
132
  return fs.existsSync((0, paths_1.firstRunFlagPath)());
@@ -160,10 +183,20 @@ function registerFirstRunIpc() {
160
183
  (0, logger_1.log)('first-run IPC handlers registered');
161
184
  }
162
185
  /** 包装 — 决定要不要弹 overlay */
186
+ /**
187
+ * 2026-09-16 (Phase 3): 是否弹引导 —— **事实来自 SetupStore** (setup-state.json), flag 只控制"已 ready 后是否还提示"。
188
+ * 未 ready 时无论 flag 有没有都弹 (初始化没完成的机器不能装作已配置)。
189
+ */
163
190
  async function maybeShowFirstRun(parent) {
164
- if (hasSeenFirstRun())
191
+ const fact = readSetupFact();
192
+ if (fact.ready && hasSeenFirstRun())
165
193
  return;
166
- (0, logger_1.log)('首启 — 弹出引导');
194
+ if (!fact.ready) {
195
+ (0, logger_1.log)(`初始化未就绪 (stage=${fact.stage}, gate=${fact.gate}) — 弹出引导 (flag=${hasSeenFirstRun() ? '已看过' : '未看过'})`);
196
+ }
197
+ else {
198
+ (0, logger_1.log)('首启 — 弹出引导');
199
+ }
167
200
  await showFirstRunOverlay(parent);
168
201
  electron_1.app.addRecentDocument((0, paths_1.firstRunFlagPath)()); // 跟踪最近文档, 让 user 知道有这文件
169
202
  }
@@ -1 +1 @@
1
- {"version":3,"file":"first-run.js","sourceRoot":"","sources":["../../../src/electron/first-run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"first-run.js","sourceRoot":"","sources":["../../../src/electron/first-run.ts"],"names":[],"mappings":"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"}