@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.
- package/dist/agents/execution-supervisor.js +87 -2
- package/dist/agents/external-events.js +162 -0
- package/dist/agents/goal-criteria.js +124 -0
- package/dist/agents/goal-store.js +79 -4
- package/dist/agents/p2p-info.js +175 -0
- package/dist/agents/pi-sdk.js +39 -0
- package/dist/agents/run-store.js +15 -0
- package/dist/agents/skill-readiness.js +133 -0
- package/dist/agents/skill-supervisor-link.js +70 -0
- package/dist/agents/skills-manager.js +282 -0
- package/dist/agents/trace-export.js +125 -0
- package/dist/agents/write-staging.js +12 -4
- package/dist/agents/x402/goal-run-bridge.js +105 -0
- package/dist/agents/x402/milestone-settlement.js +150 -0
- package/dist/agents/x402/paid-info-store.js +66 -12
- package/dist/agents/x402/payment-recovery.js +290 -0
- package/dist/agents/x402/resource-contract.js +473 -0
- package/dist/agents/x402/settlement-state.js +378 -0
- package/dist/agents/x402/trade.js +257 -0
- package/dist/agents/x402/transaction-protocol.js +99 -0
- package/dist/agents/x402/transaction-store.js +350 -0
- package/dist/cli/setup-wizard.js +96 -127
- package/dist/cli-entry.js +74 -0
- package/dist/electron/first-run.js +33 -2
- package/dist/electron-build/electron/first-run.js +35 -2
- package/dist/electron-build/electron/first-run.js.map +1 -1
- package/dist/index.js +224 -4
- package/dist/llm/config-store.js +35 -4
- package/dist/network/agent-network.js +10 -0
- package/dist/network/goal-event-bridge.js +57 -0
- package/dist/setup/onboard.js +549 -0
- package/dist/setup/setup-store.js +592 -0
- package/dist/web/routes-x402-info.js +1 -0
- package/dist/web/server.js +330 -1
- package/package.json +1 -1
package/dist/cli/setup-wizard.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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:
|
|
244
|
-
userName:
|
|
245
|
-
provider,
|
|
246
|
-
model,
|
|
247
|
-
identityFile:
|
|
248
|
-
identityCreated:
|
|
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
|
-
|
|
149
|
+
const fact = readSetupFact();
|
|
150
|
+
if (fact.ready && hasSeenFirstRun())
|
|
125
151
|
return;
|
|
126
|
-
|
|
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
|
-
|
|
191
|
+
const fact = readSetupFact();
|
|
192
|
+
if (fact.ready && hasSeenFirstRun())
|
|
165
193
|
return;
|
|
166
|
-
(
|
|
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":"
|
|
1
|
+
{"version":3,"file":"first-run.js","sourceRoot":"","sources":["../../../src/electron/first-run.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;GAKG;AACH,uCAAuD;AACvD,MAAY,EAAE,+BAAW;AACzB,MAAY,EAAE,+BAAW;AACzB,MAAY,IAAI,iCAAa;AAC7B,mCAA6D;AAC7D,qCAA+B;AAE/B,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDpB,CAAC;AAEF;;;GAGG;AACH;IACE,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAClE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,eAAe,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,CAAC;QAChC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrI,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACjE,CAAC;AACH,CAAC;AAED,2CAA2C;AAC3C;IACE,OAAO,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC;AAChC,CAAC;AAED;IACE,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,UAAU,CAAC,IAAA,wBAAgB,GAAE,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;IACE,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAA,wBAAgB,GAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,EAAE,CAAC,aAAa,CAAC,IAAA,wBAAgB,GAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAA,YAAG,EAAC,aAAc,GAAa,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,6BAAoC,MAAqB;IACvD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,OAAO,GAAG,IAAI,wBAAa,CAAC;YAChC,MAAM;YACN,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,KAAK;YACZ,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,GAAG;YACX,SAAS,EAAE,KAAK;YAChB,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,KAAK;YAClB,WAAW,EAAE,KAAK;YAClB,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,+BAA+B,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACnF,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEzB,8BAA8B;QAC9B,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,gBAAgB,EAAE,CAAC;YACnB,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC,CAAC;QACF,kBAAO,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QAEvC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACxB,kBAAO,CAAC,cAAc,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;YACjD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qCAAqC;AACrC;IACE,kBAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC;IAC1D,kBAAO,CAAC,MAAM,CAAC,qBAAqB,EAAE,GAAG,EAAE,GAAG,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,6EAA6E;IAC7E,kBAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,IAAA,eAAO,GAAE,CAAC,CAAC;IACtD,kBAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,IAAA,eAAO,GAAE,CAAC,CAAC;IACtD,IAAA,YAAG,EAAC,mCAAmC,CAAC,CAAC;AAC3C,CAAC;AAED,0BAA0B;AAC1B;;;GAGG;AACI,KAAK,4BAA4B,MAAqB;IAC3D,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAC7B,IAAI,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE;QAAE,OAAO;IAC5C,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,IAAA,YAAG,EAAC,iBAAiB,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,IAAI,kBAAkB,eAAe,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IAC5G,CAAC;SAAM,CAAC;QACN,IAAA,YAAG,EAAC,WAAW,CAAC,CAAC;IACnB,CAAC;IACD,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClC,cAAG,CAAC,iBAAiB,CAAC,IAAA,wBAAgB,GAAE,CAAC,CAAC,CAAC,wBAAwB;AACrE,CAAC"}
|