@bolloon/bolloon-agent 0.4.24 → 0.4.26
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 +446 -0
- package/dist/agents/external-events.js +162 -0
- package/dist/agents/goal-criteria.js +124 -0
- package/dist/agents/goal-store.js +526 -0
- package/dist/agents/pi-harness.js +263 -0
- package/dist/agents/pi-sdk.js +607 -126
- package/dist/agents/run-store.js +772 -0
- package/dist/agents/runner-resolver.js +225 -0
- package/dist/agents/skill-readiness.js +133 -0
- package/dist/agents/skill-supervisor-link.js +70 -0
- package/dist/agents/skills-manager.js +717 -0
- package/dist/agents/supervisor-host.js +249 -0
- package/dist/cli/setup-wizard.js +96 -127
- package/dist/cron/tick-lock.js +1 -1
- 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 +549 -26
- package/dist/ios/agent-delegate-server.js +58 -12
- package/dist/ios/icons/icon-1024x1024.png +0 -0
- package/dist/ios/icons/icon-1024x1024.webp +0 -0
- package/dist/ios/icons/icon-216x216.png +0 -0
- package/dist/ios/icons/icon-216x216.webp +0 -0
- package/dist/ios/index.html +21 -1
- package/dist/ios/manifest.json +1 -1
- package/dist/ios/mobile-agent.js +195 -1
- package/dist/ios/mobile-core.js +24876 -24723
- package/dist/ios/mobile.css +15 -0
- package/dist/ios/mobile.html +21 -1
- package/dist/ios/mobile.js +143 -0
- package/dist/ios/server.js +51 -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/icons/icon-1024x1024.png +0 -0
- package/dist/web/icons/icon-1024x1024.webp +0 -0
- package/dist/web/icons/icon-216x216.png +0 -0
- package/dist/web/icons/icon-216x216.webp +0 -0
- package/dist/web/manifest.json +1 -1
- package/dist/web/mobile-agent.js +2 -2
- package/dist/web/mobile-core.js +24884 -24726
- package/dist/web/mobile-privacy.js +185 -0
- package/dist/web/mobile.css +15 -0
- package/dist/web/mobile.html +21 -1
- package/dist/web/mobile.js +179 -6
- package/dist/web/server.js +633 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1267,6 +1267,314 @@ async function processInputInner(input, comm) {
|
|
|
1267
1267
|
appendLine(`${C_DIM}用法: /net join <链接> | /net status | /net ctx <文本>${RESET}`);
|
|
1268
1268
|
return;
|
|
1269
1269
|
}
|
|
1270
|
+
// /runs — 持久化运行记录 (跨重载可读): 谁在跑 / 跑完了 / 被打断 / 卡住了
|
|
1271
|
+
if (cmd === '/runs' || cmd.startsWith('/runs ')) {
|
|
1272
|
+
const arg = trimmed.slice('/runs'.length).trim();
|
|
1273
|
+
try {
|
|
1274
|
+
const { listRuns, formatRunLine, readRun } = await import('./agents/run-store.js');
|
|
1275
|
+
if (arg) {
|
|
1276
|
+
const rec = await readRun(arg);
|
|
1277
|
+
if (!rec) {
|
|
1278
|
+
appendLine(`${C_ERROR}没有这个运行: ${arg}${RESET}`);
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
appendLine(`${C_DIM}run ${rec.runId} [${rec.surface}] ${rec.status} · ${rec.steps.length} 步 · pid ${rec.pid}${RESET}`);
|
|
1282
|
+
appendLine(`${C_DIM}目标: ${rec.goal.slice(0, 120)}${RESET}`);
|
|
1283
|
+
for (const s of rec.steps) {
|
|
1284
|
+
appendLine(` ${s.ok ? '✓' : '✗'} ${String(s.n).padStart(2)} ${s.tool}${s.ms ? ` (${s.ms}ms)` : ''} ${C_DIM}${(s.summary || s.error || '').slice(0, 80)}${RESET}`);
|
|
1285
|
+
}
|
|
1286
|
+
if (rec.error)
|
|
1287
|
+
appendLine(`${C_ERROR}结束原因: ${rec.error}${RESET}`);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const runs = await listRuns({ limit: 15 });
|
|
1291
|
+
if (!runs.length) {
|
|
1292
|
+
appendLine(`${C_DIM}还没有运行记录 (每次 agent 运行都会落盘到 ~/.bolloon/runs/)${RESET}`);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
appendLine(`${C_DIM}最近 ${runs.length} 次运行 (落盘记录, 重开也还在):${RESET}`);
|
|
1296
|
+
for (const r of runs)
|
|
1297
|
+
appendLine(` ${formatRunLine(r)}`);
|
|
1298
|
+
appendLine(`${C_DIM}/runs <runId> 看逐步明细${RESET}`);
|
|
1299
|
+
}
|
|
1300
|
+
catch (e) {
|
|
1301
|
+
appendLine(`${C_ERROR}/runs 失败: ${String(e?.message || e).slice(0, 150)}${RESET}`);
|
|
1302
|
+
}
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
// 2026-09-16 (M2/M5): /resume <runId> · /pause <runId> · /approve <runId> · /goals
|
|
1306
|
+
// 恢复走的是同一条 run (从 checkpoint 继续), 不是"重发原 prompt"; 非幂等动作由重放守卫挡住。
|
|
1307
|
+
if (cmd === '/resume' || cmd.startsWith('/resume ')) {
|
|
1308
|
+
const runId = trimmed.slice('/resume'.length).trim();
|
|
1309
|
+
if (!runId) {
|
|
1310
|
+
const { listRuns, formatRunLine, RESUMABLE_STATUSES } = await import('./agents/run-store.js');
|
|
1311
|
+
const runs = await listRuns({ limit: 30 });
|
|
1312
|
+
const ok = runs.filter((r) => RESUMABLE_STATUSES.includes(r.status));
|
|
1313
|
+
if (!ok.length) {
|
|
1314
|
+
appendLine(`${C_DIM}没有可恢复的运行 (可恢复状态: ${RESUMABLE_STATUSES.join('/')})${RESET}`);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
appendLine(`${C_DIM}可恢复的运行 (用 /resume <runId> 继续):${RESET}`);
|
|
1318
|
+
for (const r of ok)
|
|
1319
|
+
appendLine(` ${formatRunLine(r)}`);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
try {
|
|
1323
|
+
const { readRun, prepareResume, buildResumeInstruction } = await import('./agents/run-store.js');
|
|
1324
|
+
const rec = await readRun(runId);
|
|
1325
|
+
if (!rec) {
|
|
1326
|
+
appendLine(`${C_ERROR}没有这个运行: ${runId}${RESET}`);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const agent = await getAgent();
|
|
1330
|
+
const active = String(agent?.currentChannelId || '');
|
|
1331
|
+
if (rec.channelId && active && rec.channelId !== active) {
|
|
1332
|
+
appendLine(`${C_ERROR}该运行属于 channel ${rec.channelId} (当前 ${active}) — 先 /channel 切过去再 /resume${RESET}`);
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
const prep = await prepareResume(runId);
|
|
1336
|
+
if (!prep.ok || !prep.plan) {
|
|
1337
|
+
appendLine(`${C_ERROR}无法恢复: ${prep.reason}${RESET}`);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
appendLine(`${C_ACCENT}♻️ 从 checkpoint 恢复 ${runId} (已完成 ${prep.plan.completedSteps.length} 步, 非幂等守卫 ${prep.plan.replayGuards.length} 条)${RESET}`);
|
|
1341
|
+
appendLine(`${C_DIM}${buildResumeInstruction(prep.plan).split('\n').slice(0, 6).join('\n')}${RESET}`);
|
|
1342
|
+
if (!agent?.resumeRun) {
|
|
1343
|
+
appendLine(`${C_ERROR}当前 agent 不支持 resumeRun${RESET}`);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const r = await agent.resumeRun(runId);
|
|
1347
|
+
appendLine(r.ok ? `${C_ACCENT}✅ 恢复执行完成${RESET}` : `${C_ERROR}恢复失败: ${r.reason}${RESET}`);
|
|
1348
|
+
}
|
|
1349
|
+
catch (e) {
|
|
1350
|
+
appendLine(`${C_ERROR}/resume 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1351
|
+
}
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (cmd === '/pause' || cmd.startsWith('/pause ')) {
|
|
1355
|
+
const runId = trimmed.slice('/pause'.length).trim();
|
|
1356
|
+
if (!runId) {
|
|
1357
|
+
const { listRuns, formatRunLine } = await import('./agents/run-store.js');
|
|
1358
|
+
const runs = (await listRuns({ status: 'running', limit: 10 }));
|
|
1359
|
+
if (!runs.length) {
|
|
1360
|
+
appendLine(`${C_DIM}没有正在运行的 run${RESET}`);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
appendLine(`${C_DIM}正在运行 (用 /pause <runId> 暂停):${RESET}`);
|
|
1364
|
+
for (const r of runs)
|
|
1365
|
+
appendLine(` ${formatRunLine(r)}`);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
try {
|
|
1369
|
+
const { setRunStatus } = await import('./agents/run-store.js');
|
|
1370
|
+
const r = await setRunStatus(runId, 'paused', { error: '外部请求 (pause)' });
|
|
1371
|
+
appendLine(r.ok ? `${C_ACCENT}⏸️ 已请求暂停 ${runId} (运行中的 agent 会在下一轮循环停下)${RESET}` : `${C_ERROR}暂停被拒: ${r.reason}${RESET}`);
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
appendLine(`${C_ERROR}/pause 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1375
|
+
}
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (cmd === '/approve' || cmd.startsWith('/approve ')) {
|
|
1379
|
+
const runId = trimmed.slice('/approve'.length).trim();
|
|
1380
|
+
if (!runId) {
|
|
1381
|
+
const { listRuns, formatRunLine } = await import('./agents/run-store.js');
|
|
1382
|
+
const runs = (await listRuns({ status: 'needs_human', limit: 10 }));
|
|
1383
|
+
if (!runs.length) {
|
|
1384
|
+
appendLine(`${C_DIM}没有等待人工处置的 run${RESET}`);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
appendLine(`${C_DIM}等待人工处置 (用 /approve <runId> 批准继续):${RESET}`);
|
|
1388
|
+
for (const r of runs)
|
|
1389
|
+
appendLine(` ${formatRunLine(r)}`);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
try {
|
|
1393
|
+
const { readRun, recordRecovery } = await import('./agents/run-store.js');
|
|
1394
|
+
const rec = await readRun(runId);
|
|
1395
|
+
if (!rec) {
|
|
1396
|
+
appendLine(`${C_ERROR}没有这个运行: ${runId}${RESET}`);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (rec.status !== 'needs_human') {
|
|
1400
|
+
appendLine(`${C_ERROR}只有 needs_human 的运行需要批准 (当前 ${rec.status})${RESET}`);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
await recordRecovery(runId, { errorClass: rec.errorClass || 'unknown', message: '人工批准后继续', action: 'resume' });
|
|
1404
|
+
const agent = await getAgent();
|
|
1405
|
+
if (!agent?.resumeRun) {
|
|
1406
|
+
appendLine(`${C_ERROR}当前 agent 不支持 resumeRun${RESET}`);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const r = await agent.resumeRun(runId);
|
|
1410
|
+
appendLine(r.ok ? `${C_ACCENT}✅ 已批准并继续执行${RESET}` : `${C_ERROR}批准后恢复失败: ${r.reason}${RESET}`);
|
|
1411
|
+
}
|
|
1412
|
+
catch (e) {
|
|
1413
|
+
appendLine(`${C_ERROR}/approve 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1414
|
+
}
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (cmd === '/goals' || cmd.startsWith('/goals ')) {
|
|
1418
|
+
const arg = trimmed.slice('/goals'.length).trim();
|
|
1419
|
+
try {
|
|
1420
|
+
const { listGoals, readGoal, formatGoalLine, evaluateGoalCompletion } = await import('./agents/goal-store.js');
|
|
1421
|
+
if (arg) {
|
|
1422
|
+
const g = await readGoal(arg);
|
|
1423
|
+
if (!g) {
|
|
1424
|
+
appendLine(`${C_ERROR}没有这个目标: ${arg}${RESET}`);
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
appendLine(`${C_DIM}goal ${g.goalId} [${g.status}] 创建 ${g.createdAt}${RESET}`);
|
|
1428
|
+
appendLine(` 目标: ${g.objective}`);
|
|
1429
|
+
g.successCriteria.forEach((c, i) => appendLine(` ${g.completedCriteria.includes(i) ? '✓' : '·'} [${i}] ${c}`));
|
|
1430
|
+
appendLine(` runs: ${g.runs.join(', ') || '(无)'} 当前: ${g.currentRunId || '-'}`);
|
|
1431
|
+
if (g.evidence.length)
|
|
1432
|
+
appendLine(` 证据: ${g.evidence.slice(-3).join(' | ')}`);
|
|
1433
|
+
const v = evaluateGoalCompletion(g);
|
|
1434
|
+
appendLine(` 完成门: ${v.complete ? '✅ 可判完成' : `❌ ${v.reason}`}`);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const goals = await listGoals({ limit: 15 });
|
|
1438
|
+
if (!goals.length) {
|
|
1439
|
+
appendLine(`${C_DIM}还没有目标记录 (每次 prompt 都会建/续一个 Goal, 落盘 ~/.bolloon/goals/)${RESET}`);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
appendLine(`${C_DIM}最近 ${goals.length} 个目标:${RESET}`);
|
|
1443
|
+
for (const g of goals)
|
|
1444
|
+
appendLine(` ${formatGoalLine(g)}`);
|
|
1445
|
+
appendLine(`${C_DIM}/goals <goalId> 看判据与证据${RESET}`);
|
|
1446
|
+
}
|
|
1447
|
+
catch (e) {
|
|
1448
|
+
appendLine(`${C_ERROR}/goals 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1449
|
+
}
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
// 2026-09-16 (2-F): /criteria <goalId> [confirm|propose <text...>] —— 判据:看/确认/改/让 agent 提候选
|
|
1453
|
+
if (cmd === '/criteria' || cmd.startsWith('/criteria ')) {
|
|
1454
|
+
const rest = trimmed.slice('/criteria'.length).trim();
|
|
1455
|
+
try {
|
|
1456
|
+
const { longTermStatus, confirmCriteria, proposeForGoal } = await import('./agents/goal-criteria.js');
|
|
1457
|
+
const { readGoal } = await import('./agents/goal-store.js');
|
|
1458
|
+
const [goalId, action, ...words] = rest.split(/\s+/).filter(Boolean);
|
|
1459
|
+
if (!goalId) {
|
|
1460
|
+
appendLine(`${C_DIM}用法: /criteria <goalId> [confirm | propose | set <判据用;分>]${RESET}`);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const g = await readGoal(goalId);
|
|
1464
|
+
if (!g) {
|
|
1465
|
+
appendLine(`${C_ERROR}没有这个目标: ${goalId}${RESET}`);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
if (action === 'propose') {
|
|
1469
|
+
const p = await proposeForGoal(goalId);
|
|
1470
|
+
appendLine(p.ok ? `${C_DIM}候选判据 (待确认):\n${p.criteria.map((c, i) => ` [${i}] ${c}`).join('\n')}${RESET}` : `${C_ERROR}无法生成判据: ${p.reason}${RESET}`);
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const list = action === 'set' && words.length ? words.join(' ').split(';').map((x) => x.trim()).filter(Boolean) : undefined;
|
|
1474
|
+
if (action === 'confirm' || list) {
|
|
1475
|
+
const r = await confirmCriteria(goalId, { criteria: list, by: 'cli' });
|
|
1476
|
+
appendLine(r.ok ? `${C_DIM}判据已确认 (v${r.goal?.criteriaVersion}): ${(r.goal?.successCriteria || []).join(' | ')}${RESET}` : `${C_ERROR}${r.reason}${RESET}`);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
const st = await longTermStatus(goalId);
|
|
1480
|
+
appendLine(`${C_DIM}goal ${g.goalId} [${g.status}] 判据来源=${g.criteriaSource || 'unknown'} 已确认=${g.criteriaConfirmed === true} v${g.criteriaVersion || 1}${RESET}`);
|
|
1481
|
+
g.successCriteria.forEach((c, i) => appendLine(` ${g.completedCriteria.includes(i) ? '✓' : '·'} [${i}] ${c}`));
|
|
1482
|
+
if (g.proposedCriteria?.length)
|
|
1483
|
+
appendLine(` ${C_DIM}候选 (未确认): ${g.proposedCriteria.join(' | ')}${RESET}`);
|
|
1484
|
+
appendLine(` 长期完成: ${st.canComplete ? '✅ 可判完成' : `❌ ${st.reason}`}`);
|
|
1485
|
+
appendLine(` ${C_DIM}检查: ${Object.entries(st.checks).map(([k, v]) => `${k}=${v ? '✓' : '✗'}`).join(' ')}${RESET}`);
|
|
1486
|
+
}
|
|
1487
|
+
catch (e) {
|
|
1488
|
+
appendLine(`${C_ERROR}/criteria 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1489
|
+
}
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
// 2026-09-16 (M2-B): /supervise — 长期执行层 (状态/唤醒原因/手动推进一个周期) · /wake <goalId> — 外部事件唤醒
|
|
1493
|
+
if (cmd === '/supervise' || cmd.startsWith('/supervise ')) {
|
|
1494
|
+
const arg = trimmed.slice('/supervise'.length).trim();
|
|
1495
|
+
try {
|
|
1496
|
+
const { getSupervisor } = await import('./agents/execution-supervisor.js');
|
|
1497
|
+
const { wakeReport, listRunnableGoals, formatGoalLine } = await import('./agents/goal-store.js');
|
|
1498
|
+
const { runnable, skipped } = await listRunnableGoals({ now: Date.now() });
|
|
1499
|
+
const st = getSupervisor().status();
|
|
1500
|
+
appendLine(`${C_DIM}supervisor: owner=${st.owner} running=${st.running ? 'yes' : 'no'} tick=${st.tickIntervalMs}ms lease=${st.leaseTtlMs}ms dryRun=${st.dryRun ? 'yes' : 'no'} ticks=${st.ticks}${RESET}`);
|
|
1501
|
+
// 2026-09-16 (2-C.2): 宿主状态里的最近一次"执行器解析"阶段报告 —— 回答"这个 Goal 为什么没被执行、卡在哪一阶段"
|
|
1502
|
+
try {
|
|
1503
|
+
const { readSupervisorState } = await import('./agents/supervisor-host.js');
|
|
1504
|
+
const hostState = await readSupervisorState();
|
|
1505
|
+
if (hostState) {
|
|
1506
|
+
appendLine(`${C_DIM}宿主: worker=${hostState.workerId} pid=${hostState.pid} ticks=${hostState.ticks ?? 0}${hostState.stoppedAt ? ` 已停止(${hostState.stopReason})` : ''}${RESET}`);
|
|
1507
|
+
appendLine(`${C_DIM}最近一轮: ${hostState.lastSummary || '(无)'}${RESET}`);
|
|
1508
|
+
const lr = hostState.lastResolution;
|
|
1509
|
+
if (lr) {
|
|
1510
|
+
appendLine(`${lr.ok ? C_ACCENT : C_WARN}解析 ${lr.goalId} ${lr.ok ? '✅ 可执行' : `⛔ 卡在 ${lr.failedStage}`}${RESET}${lr.reason ? ` — ${lr.reason}` : ''}`);
|
|
1511
|
+
appendLine(` ${C_DIM}阶段: ${lr.stages}${RESET}`);
|
|
1512
|
+
for (const d of (lr.detail || []))
|
|
1513
|
+
if (d.note || d.error)
|
|
1514
|
+
appendLine(` ${C_DIM}· ${d.stage}: ${d.error || d.note}${RESET}`);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
catch { /* 宿主状态可读性不影响诊断 */ }
|
|
1519
|
+
if (arg === 'tick' || arg === 'start') {
|
|
1520
|
+
// CLI 侧执行器: 用当前会话的 agent (没人注入 agent 时只诊断, 不假装跑过)
|
|
1521
|
+
const runner = agent
|
|
1522
|
+
? async (req) => {
|
|
1523
|
+
if (req.kind === 'resume' && req.prevRunId && typeof agent.resumeRun === 'function') {
|
|
1524
|
+
const r = await agent.resumeRun(req.prevRunId);
|
|
1525
|
+
return { runId: req.prevRunId, status: r?.ok ? 'done' : 'failed', error: r?.ok ? undefined : r?.reason };
|
|
1526
|
+
}
|
|
1527
|
+
agent.setGoalId?.(req.goal.goalId);
|
|
1528
|
+
agent.setContinuationGuards?.(req.guards || []);
|
|
1529
|
+
await agent.prompt(req.instruction);
|
|
1530
|
+
return { runId: agent.getLastRunId?.() || agent.getRunId?.(), status: 'done' };
|
|
1531
|
+
}
|
|
1532
|
+
: undefined;
|
|
1533
|
+
const { ExecutionSupervisor } = await import('./agents/execution-supervisor.js');
|
|
1534
|
+
const sup = new ExecutionSupervisor({ runner: runner, maxPerTick: 1, log: (m) => appendLine(`${C_DIM}${m}${RESET}`) });
|
|
1535
|
+
const rep = await sup.tickOnce();
|
|
1536
|
+
appendLine(`${C_ACCENT}调度周期 #${rep.tick}${RESET} 认领 ${rep.claimed.length} · 执行 ${rep.executed.length}${rep.skipped.length ? ` · 跳过 ${rep.skipped.length}` : ''}`);
|
|
1537
|
+
for (const s of rep.skipped.slice(0, 6))
|
|
1538
|
+
appendLine(` ${C_DIM}跳过 ${s.goalId}: ${s.reason}${RESET}`);
|
|
1539
|
+
for (const e of rep.executed)
|
|
1540
|
+
appendLine(` ▶ ${e.goalId} → run=${e.runId || '-'} ${e.status || ''}${e.error ? ` (${e.error})` : ''}`);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
const rows = await wakeReport();
|
|
1544
|
+
if (!rows.length) {
|
|
1545
|
+
appendLine(`${C_DIM}还没有目标。${RESET}`);
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
for (const r of rows.slice(0, 12))
|
|
1549
|
+
appendLine(` ${r.goalId} [${r.status}] 唤醒: ${r.wake}${r.lease ? ` (lease ${r.lease})` : ''}`);
|
|
1550
|
+
if (runnable.length) {
|
|
1551
|
+
appendLine(`${C_DIM}现在可推进:${RESET}`);
|
|
1552
|
+
for (const g of runnable.slice(0, 5))
|
|
1553
|
+
appendLine(` ${formatGoalLine(g)}`);
|
|
1554
|
+
}
|
|
1555
|
+
appendLine(`${C_DIM}/supervise tick 手动推进一个周期${RESET}`);
|
|
1556
|
+
}
|
|
1557
|
+
catch (e) {
|
|
1558
|
+
appendLine(`${C_ERROR}/supervise 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1559
|
+
}
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (cmd === '/wake' || cmd.startsWith('/wake ')) {
|
|
1563
|
+
const goalId = trimmed.slice('/wake'.length).trim();
|
|
1564
|
+
if (!goalId) {
|
|
1565
|
+
appendLine(`${C_ERROR}用法: /wake <goalId> (外部事件到达后唤醒在等它的目标)${RESET}`);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
try {
|
|
1569
|
+
const { getSupervisor } = await import('./agents/execution-supervisor.js');
|
|
1570
|
+
const woke = await getSupervisor().notifyExternal(goalId);
|
|
1571
|
+
appendLine(woke ? `${C_ACCENT}✅ 已唤醒 ${goalId} (下一次 tick 推进; 不重发已成功的请求)${RESET}` : `${C_DIM}${goalId} 不在等待外部事件 (未改动)${RESET}`);
|
|
1572
|
+
}
|
|
1573
|
+
catch (e) {
|
|
1574
|
+
appendLine(`${C_ERROR}/wake 失败: ${String(e?.message || e).slice(0, 200)}${RESET}`);
|
|
1575
|
+
}
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1270
1578
|
// /model — 无参: 交互选择器 (ink 渲染, 复用 MentionPopup); 有参: 直接切换/测连通/看状态
|
|
1271
1579
|
if (cmd === '/model' || cmd.startsWith('/model ')) {
|
|
1272
1580
|
const modelArg = trimmed.slice('/model'.length).trim();
|
|
@@ -1870,40 +2178,39 @@ async function processInputInner(input, comm) {
|
|
|
1870
2178
|
// /skills [名] — 查看正式技能 (2026-08-12 Task5): 无参列全部, 带名看详情. 运行时开始前的技能 view.
|
|
1871
2179
|
if (cmd === '/skills' || cmd.startsWith('/skills ')) {
|
|
1872
2180
|
try {
|
|
1873
|
-
|
|
1874
|
-
const
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
const m = await loadSkillsDir(d);
|
|
1878
|
-
for (const s of m)
|
|
1879
|
-
if (s.status === 'active' && !metas.some(x => x.name === s.name))
|
|
1880
|
-
metas.push(s);
|
|
1881
|
-
}
|
|
2181
|
+
// 2026-09-16 (2-G.1): 改走统一 Skills Manager —— CLI / Web / agent 看的是同一份事实
|
|
2182
|
+
const { getSkillsManager, formatSkillLine } = await import('./agents/skills-manager.js');
|
|
2183
|
+
const sm = getSkillsManager();
|
|
2184
|
+
const list = await sm.view();
|
|
1882
2185
|
const q = cmd.startsWith('/skills ') ? cmd.slice('/skills '.length).trim().toLowerCase() : '';
|
|
1883
2186
|
if (!q) {
|
|
1884
|
-
appendLine(`${C_ACCENT}技能 (${
|
|
1885
|
-
if (
|
|
1886
|
-
appendLine(` ${C_DIM}
|
|
1887
|
-
for (const s of
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
}
|
|
1891
|
-
appendLine(`${C_DIM}用法: /skills <名>
|
|
2187
|
+
appendLine(`${C_ACCENT}技能 (${list.length}) [status/source/trust/版本/hash]:${RESET}`);
|
|
2188
|
+
if (list.length === 0)
|
|
2189
|
+
appendLine(` ${C_DIM}暂无技能 — run-end 经验可沉淀为 skill${RESET}`);
|
|
2190
|
+
for (const s of list.slice(0, 20))
|
|
2191
|
+
appendLine(` ${C_DIM}·${RESET} ${formatSkillLine(s)}`);
|
|
2192
|
+
const h = await sm.health();
|
|
2193
|
+
appendLine(`${C_DIM}健康: ${JSON.stringify(h.byStatus)}${h.drifted.length ? ` · 内容漂移 ${h.drifted.length}` : ''}${h.invalid.length ? ` · 不合格 ${h.invalid.length}` : ''}${RESET}`);
|
|
2194
|
+
appendLine(`${C_DIM}用法: /skills <名> 详情 · /skill health | inspect|enable|disable|approve|validate|import|export <名|链接>${RESET}`);
|
|
1892
2195
|
}
|
|
1893
2196
|
else {
|
|
1894
|
-
const hit =
|
|
2197
|
+
const hit = list.find(s => s.name.toLowerCase() === q) || list.find(s => s.name.toLowerCase().includes(q));
|
|
1895
2198
|
if (!hit) {
|
|
1896
2199
|
appendLine(`${C_WARN}未找到技能: '${q}'${RESET}`);
|
|
1897
2200
|
return;
|
|
1898
2201
|
}
|
|
1899
2202
|
appendLine(`${C_ACCENT}═ ${hit.name} ═${RESET}`);
|
|
2203
|
+
appendLine(` ${C_DIM}状态:${RESET} ${hit.status} ${C_DIM}来源:${RESET} ${hit.source}${hit.sourceRef ? ` (${hit.sourceRef.slice(0, 60)})` : ''} ${C_DIM}信任:${RESET} ${hit.trust}`);
|
|
2204
|
+
appendLine(` ${C_DIM}版本:${RESET} v${hit.version} ${C_DIM}内容哈希:${RESET} ${hit.contentHash}${hit.registryHash ? ` (registry ${hit.registryHash.slice(0, 10)}${hit.registryHash !== hit.contentHash ? ' ⚠ 已漂移' : ''})` : ''}`);
|
|
2205
|
+
appendLine(` ${C_DIM}目录:${RESET} ${hit.dir} ${hit.fileCount} 个文件 / ${Math.round(hit.bytes / 1024)}KB`);
|
|
1900
2206
|
if (hit.description)
|
|
1901
2207
|
appendLine(` ${C_DIM}描述:${RESET} ${hit.description}`);
|
|
1902
|
-
if (hit.triggers
|
|
2208
|
+
if (hit.triggers.length)
|
|
1903
2209
|
appendLine(` ${C_DIM}触发:${RESET} ${hit.triggers.join(', ')}`);
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2210
|
+
if (hit.issues.length)
|
|
2211
|
+
appendLine(` ${C_WARN}问题:${RESET} ${hit.issues.join('; ')}`);
|
|
2212
|
+
const body = (await import('fs/promises')).readFile(hit.skillFile, 'utf-8').catch(() => '');
|
|
2213
|
+
void body;
|
|
1907
2214
|
}
|
|
1908
2215
|
}
|
|
1909
2216
|
catch (e) {
|
|
@@ -1911,6 +2218,101 @@ async function processInputInner(input, comm) {
|
|
|
1911
2218
|
}
|
|
1912
2219
|
return;
|
|
1913
2220
|
}
|
|
2221
|
+
// 2026-09-16 (2-G.1): /skill <子命令> —— 统一管理面 (enable/disable/approve/validate/quarantine/import/export/inspect/health)
|
|
2222
|
+
if (cmd === '/skill' || cmd.startsWith('/skill ')) {
|
|
2223
|
+
const rest = trimmed.slice('/skill'.length).trim();
|
|
2224
|
+
const [sub, ...args] = rest.split(/\s+/).filter(Boolean);
|
|
2225
|
+
const arg = args.join(' ').trim();
|
|
2226
|
+
try {
|
|
2227
|
+
const { getSkillsManager, formatSkillLine } = await import('./agents/skills-manager.js');
|
|
2228
|
+
const sm = getSkillsManager();
|
|
2229
|
+
if (!sub || sub === 'list') {
|
|
2230
|
+
for (const s of await sm.view())
|
|
2231
|
+
appendLine(` ${formatSkillLine(s)}`);
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
if (sub === 'health') {
|
|
2235
|
+
const h = await sm.health();
|
|
2236
|
+
appendLine(`${C_ACCENT}技能健康:${RESET} 共 ${h.total} 状态 ${JSON.stringify(h.byStatus)} 来源 ${JSON.stringify(h.bySource)}`);
|
|
2237
|
+
if (h.drifted.length) {
|
|
2238
|
+
appendLine(`${C_WARN}内容漂移 (SKILL.md 被改过, 与 registry 基线不一致):${RESET}`);
|
|
2239
|
+
for (const d of h.drifted)
|
|
2240
|
+
appendLine(` ${d.name} registry=${String(d.expected).slice(0, 10)} 现在=${d.actual.slice(0, 10)}`);
|
|
2241
|
+
}
|
|
2242
|
+
if (h.invalid.length) {
|
|
2243
|
+
appendLine(`${C_WARN}不合格:${RESET}`);
|
|
2244
|
+
for (const i of h.invalid)
|
|
2245
|
+
appendLine(` ${i.name}: ${i.issues.join('; ')}`);
|
|
2246
|
+
}
|
|
2247
|
+
if (h.duplicates.length) {
|
|
2248
|
+
appendLine(`${C_WARN}同名多处:${RESET}`);
|
|
2249
|
+
for (const d of h.duplicates)
|
|
2250
|
+
appendLine(` ${d.name}: ${d.dirs.join(' | ')}`);
|
|
2251
|
+
}
|
|
2252
|
+
if (h.missing.length)
|
|
2253
|
+
appendLine(`${C_WARN}registry 里记着但盘上没有:${RESET} ${h.missing.join(', ')}`);
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
if (sub === 'import') {
|
|
2257
|
+
if (!arg) {
|
|
2258
|
+
appendLine(`${C_ERROR}用法: /skill import <bolloon://skill/<cid> | ipfs://<cid> | <cid>>${RESET}`);
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
appendLine(`${C_DIM}导入中: ${arg.slice(0, 80)} …${RESET}`);
|
|
2262
|
+
const r = await sm.import(arg);
|
|
2263
|
+
appendLine(r.ok ? `${C_ACCENT}✅ 已导入 ${r.name}@${r.version} (状态 installed, 信任 unverified)${RESET}` : `${C_ERROR}导入失败: ${r.error}${RESET}`);
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (sub === 'export') {
|
|
2267
|
+
if (!arg) {
|
|
2268
|
+
appendLine(`${C_ERROR}用法: /skill export <名>${RESET}`);
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const r = await sm.export(arg);
|
|
2272
|
+
if (!r.ok) {
|
|
2273
|
+
appendLine(`${C_ERROR}导出失败: ${r.error}${RESET}`);
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
appendLine(`${C_ACCENT}技能包 JSON (${Object.keys(r.bundle.files).length} 个文件):${RESET}`);
|
|
2277
|
+
appendLine(JSON.stringify(r.bundle).slice(0, 400));
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
if (!arg) {
|
|
2281
|
+
appendLine(`${C_ERROR}用法: /skill ${sub} <名>${RESET}`);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const act = {
|
|
2285
|
+
enable: () => sm.enable(arg),
|
|
2286
|
+
disable: () => sm.disable(arg),
|
|
2287
|
+
approve: () => sm.approve(arg, 'cli'),
|
|
2288
|
+
validate: () => sm.validate(arg),
|
|
2289
|
+
quarantine: () => sm.quarantine(arg, 'cli 手动隔离'),
|
|
2290
|
+
inspect: async () => ({ ok: true, skill: await sm.inspect(arg) }),
|
|
2291
|
+
};
|
|
2292
|
+
const fn = act[sub];
|
|
2293
|
+
if (!fn) {
|
|
2294
|
+
appendLine(`${C_ERROR}未知子命令: ${sub} (可用: health|import|export|inspect|enable|disable|approve|validate|quarantine)${RESET}`);
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
const r = await fn();
|
|
2298
|
+
if (!r.ok) {
|
|
2299
|
+
appendLine(`${C_WARN}${sub} 未完成: ${r.reason || (r.issues || []).join('; ') || '未知原因'}${RESET}`);
|
|
2300
|
+
}
|
|
2301
|
+
if (r.skill)
|
|
2302
|
+
appendLine(` ${formatSkillLine(r.skill)}`);
|
|
2303
|
+
else if (r.ok)
|
|
2304
|
+
appendLine(`${C_ACCENT}✅ ${sub} ${arg}${RESET}`);
|
|
2305
|
+
if (sub === 'inspect' && r.skill) {
|
|
2306
|
+
appendLine(` ${C_DIM}目录:${RESET} ${r.skill.dir}`);
|
|
2307
|
+
appendLine(` ${C_DIM}SKILL.md:${RESET} ${r.skill.skillFile}`);
|
|
2308
|
+
appendLine(` ${C_DIM}问题:${RESET} ${r.skill.issues.length ? r.skill.issues.join('; ') : '无'}`);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
catch (e) {
|
|
2312
|
+
appendLine(`${C_ERROR}/skill 失败: ${String(e?.message || e).slice(0, 160)}${RESET}`);
|
|
2313
|
+
}
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
1914
2316
|
// /mcp — MCP 插件/工具列表
|
|
1915
2317
|
if (cmd === '/mcp') {
|
|
1916
2318
|
try {
|
|
@@ -2260,6 +2662,13 @@ async function processInputInner(input, comm) {
|
|
|
2260
2662
|
appendLine(` ${C_ACCENT}/dequeue${RESET} 出队一条`);
|
|
2261
2663
|
appendLine(` ${C_ACCENT}/channel [名字|id|序号]${RESET} 切换当前智能体 ${C_DIM}无参列出所有; 支持名字/ID/序号三种解析${RESET}`);
|
|
2262
2664
|
appendLine(` ${C_ACCENT}/model${RESET} 模型供应商选择器 ${C_DIM}无参=选择器 · /model <名> [模型] 直接切换 · /model test 测连通${RESET}`);
|
|
2665
|
+
appendLine(` ${C_ACCENT}/runs${RESET} 运行记录 (落盘, 跨重载可读) ${C_DIM}/runs · /runs <runId> 看逐步明细${RESET}`);
|
|
2666
|
+
appendLine(` ${C_ACCENT}/resume${RESET} 从 checkpoint 继续一次运行 ${C_DIM}/resume · /resume <runId> (不是重发原 prompt)${RESET}`);
|
|
2667
|
+
appendLine(` ${C_ACCENT}/pause${RESET} 暂停一次运行 ${C_DIM}/pause · /pause <runId>${RESET}`);
|
|
2668
|
+
appendLine(` ${C_ACCENT}/approve${RESET} 批准等待人工处置的运行 ${C_DIM}/approve · /approve <runId>${RESET}`);
|
|
2669
|
+
appendLine(` ${C_ACCENT}/supervise${RESET} 长期执行层状态与唤醒原因 ${C_DIM}/supervise · /supervise tick${RESET}`);
|
|
2670
|
+
appendLine(` ${C_ACCENT}/wake${RESET} 外部事件到达 → 唤醒在等的目标 ${C_DIM}/wake <goalId>${RESET}`);
|
|
2671
|
+
appendLine(` ${C_ACCENT}/goals${RESET} 目标 (判据/证据/完成门) ${C_DIM}/goals · /goals <goalId>${RESET}`);
|
|
2263
2672
|
appendLine(` ${C_ACCENT}/setup${RESET} 初始化 / 配置总览 ${C_DIM}身份 + 供应商 + 配置文件路径${RESET}`);
|
|
2264
2673
|
appendLine(` ${C_ACCENT}/questions${RESET} 待回答的问题 ${C_DIM}智能体 clarify 提问时, 直接输入即回答 (或 /answer <文本>)${RESET}`);
|
|
2265
2674
|
appendLine(` ${C_ACCENT}/login${RESET} 登录 GitHub/Google 账号 (骨架) ${C_DIM}暂无真实 OAuth${RESET}`);
|
|
@@ -3528,6 +3937,33 @@ function parseArgs() {
|
|
|
3528
3937
|
case '--web':
|
|
3529
3938
|
result.web = true;
|
|
3530
3939
|
break;
|
|
3940
|
+
// 2026-09-16 (2-C.1): 独立 Supervisor 宿主 —— 长期执行不依附 web 进程
|
|
3941
|
+
case '--supervise':
|
|
3942
|
+
result.supervise = true;
|
|
3943
|
+
break;
|
|
3944
|
+
case '--setup-status':
|
|
3945
|
+
result.setupStatus = true;
|
|
3946
|
+
break;
|
|
3947
|
+
case '--setup-resume':
|
|
3948
|
+
result.setupResume = true;
|
|
3949
|
+
break;
|
|
3950
|
+
case '--setup-repair':
|
|
3951
|
+
result.setupRepair = true;
|
|
3952
|
+
break;
|
|
3953
|
+
case '--setup-reconfigure':
|
|
3954
|
+
result.setupReconfigure = true;
|
|
3955
|
+
break;
|
|
3956
|
+
case '--setup-test':
|
|
3957
|
+
result.setupTest = true;
|
|
3958
|
+
break;
|
|
3959
|
+
case '--supervise-once':
|
|
3960
|
+
result.supervise = true;
|
|
3961
|
+
result.superviseOnce = true;
|
|
3962
|
+
break;
|
|
3963
|
+
case '--supervise-dry-run':
|
|
3964
|
+
result.supervise = true;
|
|
3965
|
+
result.superviseDryRun = true;
|
|
3966
|
+
break;
|
|
3531
3967
|
case '--help':
|
|
3532
3968
|
case '-h':
|
|
3533
3969
|
result.help = true;
|
|
@@ -3950,6 +4386,70 @@ async function main() {
|
|
|
3950
4386
|
})();
|
|
3951
4387
|
}
|
|
3952
4388
|
const mode = args.web ? 'web' : 'cli';
|
|
4389
|
+
// 2026-09-16 (2-C.1): 独立 Supervisor 宿主 —— `bolloon --supervise [--supervise-once|--supervise-dry-run]`
|
|
4390
|
+
// 长期执行不再依附 web 进程 (页面关掉/CLI 没开也能继续推进 Goal)。
|
|
4391
|
+
// 2026-09-16 (Phase 4): Onboard 统一入口 —— CLI / Web 共用同一条执行器 (src/setup/onboard.ts)
|
|
4392
|
+
// --setup-status 只读; --setup-resume 从失败阶段继续; --setup-repair 迁移/备份坏文件后就地修;
|
|
4393
|
+
// --setup-reconfigure 只改选中项 (新配置测通才切 active); --setup-test 重跑连通性 + 运行时。
|
|
4394
|
+
if (args.setupStatus || args.setupResume || args.setupRepair || args.setupReconfigure || args.setupTest) {
|
|
4395
|
+
const mode = args.setupStatus ? 'status' : args.setupRepair ? 'repair' : args.setupReconfigure ? 'reconfigure' : args.setupTest ? 'test' : 'resume';
|
|
4396
|
+
if (mode === 'status') {
|
|
4397
|
+
const { evaluateSetup, describeSetup } = await import('./setup/setup-store.js');
|
|
4398
|
+
const ev = await evaluateSetup();
|
|
4399
|
+
process.stdout.write(describeSetup(ev) + '\n');
|
|
4400
|
+
process.exit(ev.gate === 'ready' ? 0 : 1);
|
|
4401
|
+
}
|
|
4402
|
+
const { defaultWizardIO } = await import('./cli/setup-wizard.js');
|
|
4403
|
+
const { runOnboard } = await import('./setup/onboard.js');
|
|
4404
|
+
const wizIO = defaultWizardIO();
|
|
4405
|
+
const res = await runOnboard({
|
|
4406
|
+
mode: mode,
|
|
4407
|
+
io: {
|
|
4408
|
+
print: (m) => process.stdout.write(m + '\n'),
|
|
4409
|
+
ask: async (q, o) => wizIO.ask(q, o?.defaultValue ? { default: o.defaultValue } : undefined),
|
|
4410
|
+
askHidden: async (q) => wizIO.ask(q, { hidden: true }),
|
|
4411
|
+
confirm: async (q, d = true) => {
|
|
4412
|
+
const a = String(await wizIO.ask(`${q} (y/n)`, { default: d ? 'y' : 'n' })).trim().toLowerCase();
|
|
4413
|
+
return a === '' ? d : /^(y|yes|1|true|是)$/.test(a);
|
|
4414
|
+
},
|
|
4415
|
+
select: async (q, choices) => {
|
|
4416
|
+
process.stdout.write(q + '\n');
|
|
4417
|
+
choices.forEach((c, i) => process.stdout.write(` ${i + 1}) ${c.label}${c.hint ? ` — ${c.hint}` : ''}\n`));
|
|
4418
|
+
const a = String(await wizIO.ask('选择 (序号或名称)', { default: '1' })).trim();
|
|
4419
|
+
if (!a)
|
|
4420
|
+
return choices[0]?.value || '';
|
|
4421
|
+
if (/^\d+$/.test(a) && choices[Number(a) - 1])
|
|
4422
|
+
return choices[Number(a) - 1].value;
|
|
4423
|
+
const hit = choices.find((c) => c.value === a) || choices.find((c) => a && c.value.startsWith(a));
|
|
4424
|
+
return hit?.value || a;
|
|
4425
|
+
},
|
|
4426
|
+
},
|
|
4427
|
+
});
|
|
4428
|
+
process.exit(res.ok ? 0 : 1);
|
|
4429
|
+
}
|
|
4430
|
+
if (args.supervise) {
|
|
4431
|
+
const { runStandaloneSupervisorHost } = await import('./agents/supervisor-host.js');
|
|
4432
|
+
const res = await runStandaloneSupervisorHost({
|
|
4433
|
+
once: !!args.superviseOnce,
|
|
4434
|
+
dryRun: !!args.superviseDryRun,
|
|
4435
|
+
log: (m) => console.log(m),
|
|
4436
|
+
});
|
|
4437
|
+
if (args.superviseOnce) {
|
|
4438
|
+
const rep = res.lastReport;
|
|
4439
|
+
if (rep) {
|
|
4440
|
+
console.log(`调度周期 #${rep.tick}: 认领 ${rep.claimed.length} · 执行 ${rep.executed.length} · 跳过 ${rep.skipped.length}${rep.errors.length ? ` · 错误 ${rep.errors.length}` : ''}`);
|
|
4441
|
+
for (const s of rep.skipped.slice(0, 8))
|
|
4442
|
+
console.log(` 跳过 ${s.goalId}: ${s.reason}`);
|
|
4443
|
+
for (const e of rep.executed)
|
|
4444
|
+
console.log(` ▶ ${e.goalId} → run=${e.runId || '-'} ${e.status || ''}${e.error ? ` (${e.error})` : ''}`);
|
|
4445
|
+
}
|
|
4446
|
+
console.log(`supervisor 宿主: owner=${res.state.owner} worker=${res.state.workerId} ticks=${res.ticks} 状态文件=~/.bolloon/supervisor.json`);
|
|
4447
|
+
}
|
|
4448
|
+
else {
|
|
4449
|
+
console.log(`[supervisor] 常驻宿主已启动 (owner=${res.state.owner}, worker=${res.state.workerId}); Ctrl-C 优雅停止`);
|
|
4450
|
+
}
|
|
4451
|
+
return;
|
|
4452
|
+
}
|
|
3953
4453
|
const isNonInteractive = !!(args.tool || args.prompt);
|
|
3954
4454
|
const originalLog = console.log;
|
|
3955
4455
|
const originalInfo = console.info;
|
|
@@ -3975,14 +4475,37 @@ async function main() {
|
|
|
3975
4475
|
// 2026-09-13: 首次运行引导 — 没有可用模型供应商 / 还没有用户身份时, 先走初始化向导
|
|
3976
4476
|
// (放在 CLI 启动前: 用户先回答"你是谁 / 用哪个模型", 再进 TUI 面板)
|
|
3977
4477
|
if (isCLIInteractive) {
|
|
4478
|
+
// 2026-09-16 (M4 启动硬门禁): 先把初始化事实读出来 —— 未就绪就**先修**, 修不好就非零退出。
|
|
4479
|
+
// 旧行为是"向导失败也只 warn, 照常进对话", 结果是半成品配置能进运行态 (看起来启动成功、实际不可执行)。
|
|
4480
|
+
const w = (m) => process.stderr.write(m.endsWith('\n') ? m : m + '\n');
|
|
4481
|
+
let gateEv = null;
|
|
3978
4482
|
try {
|
|
3979
|
-
const {
|
|
3980
|
-
|
|
3981
|
-
|
|
4483
|
+
const { refreshSetupState, describeSetup } = await import('./setup/setup-store.js');
|
|
4484
|
+
gateEv = await refreshSetupState({ light: true });
|
|
4485
|
+
if (gateEv.gate !== 'ready') {
|
|
4486
|
+
w(describeSetup(gateEv));
|
|
4487
|
+
if (process.env.BOLLOON_SKIP_SETUP === '1') {
|
|
4488
|
+
w('⚠ BOLLOON_SKIP_SETUP=1 → 诊断模式: 可以看状态/修配置, 但 agent 执行被门禁拦住 (不能绕过)');
|
|
4489
|
+
}
|
|
4490
|
+
else {
|
|
4491
|
+
const { runSetupWizard } = await import('./cli/setup-wizard.js');
|
|
4492
|
+
await runSetupWizard({ interactive: true });
|
|
4493
|
+
const after = await refreshSetupState({});
|
|
4494
|
+
if (after.gate !== 'ready') {
|
|
4495
|
+
w(describeSetup(after));
|
|
4496
|
+
w('⛔ 初始化未完成 → 退出 (不会以"看起来能跑"的状态进入对话)');
|
|
4497
|
+
process.exit(1);
|
|
4498
|
+
}
|
|
4499
|
+
w('✅ 初始化完成, 进入正常模式');
|
|
4500
|
+
}
|
|
3982
4501
|
}
|
|
3983
4502
|
}
|
|
3984
4503
|
catch (e) {
|
|
3985
|
-
|
|
4504
|
+
// 评估/向导自身异常: fail-closed —— 不假装就绪
|
|
4505
|
+
w(`⛔ 初始化流程失败 (fail-closed): ${String(e?.message || e).slice(0, 200)}`);
|
|
4506
|
+
w(` 当前状态评估: ${gateEv ? gateEv.gate : '未知 (评估都没跑通)'}`);
|
|
4507
|
+
w(' 排查: `bolloon setup --status` (或删掉 ~/.bolloon/setup-state.json 后重跑 setup)');
|
|
4508
|
+
process.exit(1);
|
|
3986
4509
|
}
|
|
3987
4510
|
}
|
|
3988
4511
|
if (isNonInteractive) {
|