@ouro.bot/cli 0.1.0-alpha.442 → 0.1.0-alpha.443

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/changelog.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.443",
6
+ "changes": [
7
+ "`ouro up` now renders as a real boot checklist instead of a loose progress wall: the screen shows the full startup path up front, marks what is running now, and keeps pending steps visible so humans can see where Ouro is in the bring-up flow.",
8
+ "Provider checks during startup now narrate the providers each selected lane is actually using, translate noisy vault chatter into plain language, and keep the live-check copy truthful instead of implying that every configured provider was checked.",
9
+ "`ouro up` no longer reports success just because the daemon answered once during startup. Before handing control back, Ouro now performs one final daemon-status handoff check and fails with a clear diagnosis if the background service stopped before boot actually finished."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.442",
6
14
  "changes": [
@@ -318,6 +318,33 @@ function failedPingResult(agentName, lane, provider, model, result) {
318
318
  function credentialRecordForLane(pool, provider) {
319
319
  return pool.providers[provider];
320
320
  }
321
+ function laneAudienceLabel(lane) {
322
+ return lane === "outward" ? "chat" : "inner dialog";
323
+ }
324
+ function bindingLabel(binding) {
325
+ return `${binding.provider} / ${binding.model}`;
326
+ }
327
+ function selectedProviderPlan(agentName, state) {
328
+ return [
329
+ `${agentName}: checking the providers this agent uses right now`,
330
+ ...["outward", "inner"].map((lane) => `- ${laneAudienceLabel(lane)}: ${bindingLabel(state.lanes[lane])}`),
331
+ ].join("\n");
332
+ }
333
+ function mapVaultRefreshProgress(agentName, onProgress) {
334
+ return (message) => {
335
+ if (message.startsWith("reading vault items for ")) {
336
+ onProgress(`${agentName}: opening saved provider credentials in the vault`);
337
+ return;
338
+ }
339
+ if (message === "parsing provider credentials...") {
340
+ onProgress(`${agentName}: organizing saved provider credentials`);
341
+ }
342
+ };
343
+ }
344
+ function providerPingSubject(agentName, lanes) {
345
+ const laneList = lanes.map((lane) => laneAudienceLabel(lane)).join(" + ");
346
+ return `${agentName} (${laneList})`;
347
+ }
321
348
  /**
322
349
  * Structural validation only. Live provider credential validation belongs to
323
350
  * checkAgentConfigWithProviderHealth(), which reads the agent vault and pings.
@@ -352,8 +379,9 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
352
379
  return stateResult.result;
353
380
  if (stateResult.disabled)
354
381
  return { ok: true };
382
+ deps.onProgress?.(selectedProviderPlan(agentName, stateResult.state));
355
383
  const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
356
- const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, deps.onProgress ? { onProgress: deps.onProgress } : undefined);
384
+ const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, deps.onProgress ? { onProgress: mapVaultRefreshProgress(agentName, deps.onProgress) } : undefined);
357
385
  const pingGroups = new Map();
358
386
  const lanes = ["outward", "inner"];
359
387
  for (const lane of lanes) {
@@ -390,7 +418,11 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
390
418
  const result = await ping(group.provider, providerCredentialConfig(group.record), {
391
419
  model: group.model,
392
420
  ...(deps.onProgress
393
- ? (0, provider_ping_progress_1.createProviderPingProgressReporter)({ provider: group.provider, model: group.model }, deps.onProgress)
421
+ ? (0, provider_ping_progress_1.createProviderPingProgressReporter)({
422
+ provider: group.provider,
423
+ model: group.model,
424
+ subject: providerPingSubject(agentName, group.lanes),
425
+ }, deps.onProgress)
394
426
  : {}),
395
427
  });
396
428
  return { group, result };
@@ -160,7 +160,6 @@ async function checkAgentProviders(deps, agentsOverride, onProgress) {
160
160
  const degraded = [];
161
161
  for (const agent of [...new Set(agents)]) {
162
162
  try {
163
- onProgress?.(`${agent}: checking providers...`);
164
163
  const result = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress);
165
164
  if (result.ok)
166
165
  continue;
@@ -358,9 +357,14 @@ function writeProviderRepairSummary(deps, title, degraded) {
358
357
  }
359
358
  function providerRepairCountSummary(count) {
360
359
  if (count === 0)
361
- return "ok";
360
+ return "selected providers answered live checks";
362
361
  return `${count} ${count === 1 ? "needs" : "need"} attention`;
363
362
  }
363
+ function bootPhasePlan(daemonAlive) {
364
+ return daemonAlive
365
+ ? ["update check", "system setup", "starting daemon", "provider checks", "final daemon check"]
366
+ : ["update check", "system setup", "provider checks", "starting daemon", "final daemon check"];
367
+ }
364
368
  function createHumanCommandProgress(deps, commandName) {
365
369
  return new up_progress_1.CommandProgress({
366
370
  write: deps.writeRaw ?? deps.writeStdout,
@@ -387,8 +391,67 @@ function daemonProgressSummary(result) {
387
391
  if (result.alreadyRunning)
388
392
  return "already running";
389
393
  if (result.message.includes("replaced"))
390
- return "replacement ready";
391
- return "ready";
394
+ return "replacement answered";
395
+ return "background service answered";
396
+ }
397
+ function finalDaemonFailureMessage(deps, reason) {
398
+ const lines = [`background service stopped before boot finished: ${reason}`];
399
+ const recentLogLines = deps.readRecentDaemonLogLines?.(DEFAULT_DAEMON_STARTUP_LOG_LINES) ?? [];
400
+ if (recentLogLines.length > 0) {
401
+ lines.push("recent daemon logs:");
402
+ lines.push(...recentLogLines.map((line) => ` ${line}`));
403
+ }
404
+ lines.push("Run `ouro up` again or `ouro doctor` for a deeper diagnosis.");
405
+ return lines.join("\n");
406
+ }
407
+ async function verifyDaemonReadyForHandoff(deps) {
408
+ const socketAlive = await deps.checkSocketAlive(deps.socketPath);
409
+ if (!socketAlive) {
410
+ return {
411
+ ok: false,
412
+ summary: "background service stopped",
413
+ message: finalDaemonFailureMessage(deps, "the daemon socket is no longer answering"),
414
+ };
415
+ }
416
+ try {
417
+ const response = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
418
+ if (!response.ok) {
419
+ const reason = response.error ?? response.message ?? "daemon status did not answer cleanly";
420
+ return {
421
+ ok: false,
422
+ summary: "daemon status did not answer",
423
+ message: finalDaemonFailureMessage(deps, reason),
424
+ };
425
+ }
426
+ const payload = (0, cli_render_1.parseStatusPayload)(response.data);
427
+ if (!payload) {
428
+ return {
429
+ ok: true,
430
+ summary: "daemon answered",
431
+ };
432
+ }
433
+ if (payload.overview.daemon !== "running") {
434
+ return {
435
+ ok: false,
436
+ summary: `daemon reported ${payload.overview.daemon}`,
437
+ message: finalDaemonFailureMessage(deps, `the daemon reported state ${payload.overview.daemon}`),
438
+ };
439
+ }
440
+ const workerCount = payload.workers.length;
441
+ return {
442
+ ok: true,
443
+ summary: workerCount === 0
444
+ ? "daemon answered"
445
+ : `${workerCount} worker${workerCount === 1 ? "" : "s"} still answering`,
446
+ };
447
+ }
448
+ catch (error) {
449
+ return {
450
+ ok: false,
451
+ summary: "daemon status did not answer",
452
+ message: finalDaemonFailureMessage(deps, error instanceof Error ? error.message : String(error)),
453
+ };
454
+ }
392
455
  }
393
456
  async function reportPostRepairProviderHealth(deps, repairedAgents, onProgress) {
394
457
  const remainingDegraded = await checkAgentProviders(deps, repairedAgents, onProgress);
@@ -3939,6 +4002,8 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
3939
4002
  now: deps.now ?? (() => Date.now()),
3940
4003
  autoRender: true,
3941
4004
  });
4005
+ const daemonAliveAtBootStart = await deps.checkSocketAlive(deps.socketPath);
4006
+ progress.setPhasePlan?.(bootPhasePlan(daemonAliveAtBootStart));
3942
4007
  // ── versioned CLI update check ──
3943
4008
  if (deps.checkForCliUpdate) {
3944
4009
  progress.startPhase("update check");
@@ -4067,6 +4132,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
4067
4132
  promptInput: deps.promptInput,
4068
4133
  });
4069
4134
  const daemonAliveBeforeStart = await deps.checkSocketAlive(deps.socketPath);
4135
+ progress.setPhasePlan?.(bootPhasePlan(daemonAliveBeforeStart));
4070
4136
  let providerChecksAlreadyRun = false;
4071
4137
  if (!daemonAliveBeforeStart) {
4072
4138
  progress.startPhase("provider checks");
@@ -4120,6 +4186,17 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
4120
4186
  daemonResult.stability = mergeStartupStability(daemonResult.stability, providerDegraded);
4121
4187
  progress.completePhase("provider checks", providerRepairCountSummary(providerDegraded.length));
4122
4188
  }
4189
+ progress.startPhase("final daemon check");
4190
+ const finalDaemonCheck = await verifyDaemonReadyForHandoff(deps);
4191
+ if (!finalDaemonCheck.ok) {
4192
+ ;
4193
+ progress.failPhase?.("final daemon check", finalDaemonCheck.summary);
4194
+ progress.end();
4195
+ const message = finalDaemonCheck.message ?? "background service stopped before boot finished";
4196
+ deps.writeStdout(message);
4197
+ return message;
4198
+ }
4199
+ progress.completePhase("final daemon check", finalDaemonCheck.summary);
4123
4200
  progress.end();
4124
4201
  // Interactive repair for degraded agents (Unit 5) — skipped by --no-repair (Unit 6)
4125
4202
  if (daemonResult.stability?.degraded && daemonResult.stability.degraded.length > 0) {
@@ -33,11 +33,16 @@ function providerRetryTiming(delayMs) {
33
33
  return `in ${rounded}`;
34
34
  }
35
35
  function formatProviderAttemptProgress(context, attempt, maxAttempts) {
36
- return `checking ${formatProviderPingLabel(context)} (attempt ${attempt} of ${maxAttempts})...`;
36
+ const prefix = context.subject ? `${context.subject}: ` : "";
37
+ return `${prefix}checking ${formatProviderPingLabel(context)} (attempt ${attempt} of ${maxAttempts})...`;
37
38
  }
38
- function formatProviderRetryProgress(record, maxAttempts) {
39
+ function formatProviderRetryProgress(context, record, maxAttempts) {
39
40
  const nextAttempt = Math.min(record.attempt + 1, maxAttempts);
40
- return `${formatProviderPingLabel(record)}: ${providerRetryReason(record)}; retrying ${providerRetryTiming(record.delayMs)} (attempt ${nextAttempt} of ${maxAttempts})`;
41
+ const retryDetail = `${providerRetryReason(record)}; retrying ${providerRetryTiming(record.delayMs)} (attempt ${nextAttempt} of ${maxAttempts})`;
42
+ if (context.subject) {
43
+ return `${context.subject}: ${retryDetail} while checking ${formatProviderPingLabel(context)}`;
44
+ }
45
+ return `${formatProviderPingLabel(record)}: ${retryDetail}`;
41
46
  }
42
47
  function createProviderPingProgressReporter(context, onProgress) {
43
48
  return {
@@ -70,7 +75,7 @@ function createProviderPingProgressReporter(context, onProgress) {
70
75
  classification: record.classification ?? "unknown",
71
76
  },
72
77
  });
73
- onProgress(formatProviderRetryProgress(record, maxAttempts));
78
+ onProgress(formatProviderRetryProgress(context, record, maxAttempts));
74
79
  },
75
80
  };
76
81
  }
@@ -201,11 +201,11 @@ function renderTerminalOperation(options) {
201
201
  summary: options.summary,
202
202
  sections: [
203
203
  {
204
- title: "Right now",
204
+ title: options.currentTitle ?? "Right now",
205
205
  lines: currentLines,
206
206
  },
207
207
  {
208
- title: "Progress",
208
+ title: options.stepsTitle ?? "Progress",
209
209
  lines: progressLines,
210
210
  },
211
211
  ],
@@ -22,6 +22,22 @@ const BOLD = "\x1b[1m";
22
22
  const DIM = "\x1b[2m";
23
23
  const GREEN = "\x1b[38;2;46;204;64m";
24
24
  const RED = "\x1b[38;2;255;106;106m";
25
+ const BASE_UP_PHASE_PLAN = [
26
+ "update check",
27
+ "system setup",
28
+ "provider checks",
29
+ "starting daemon",
30
+ "final daemon check",
31
+ ];
32
+ const FRIENDLY_UP_PHASE_LABELS = {
33
+ "update check": "Check for updates",
34
+ "system setup": "Prepare this machine",
35
+ "agent updates": "Update installed agents",
36
+ "bundle cleanup": "Clean up stale bundles",
37
+ "provider checks": "Check the providers your agents use right now",
38
+ "starting daemon": "Start the background service",
39
+ "final daemon check": "Confirm the background service stayed up",
40
+ };
25
41
  function splitDetailLines(detail) {
26
42
  if (!detail)
27
43
  return [];
@@ -45,6 +61,7 @@ class UpProgress {
45
61
  completed = [];
46
62
  currentPhase = null;
47
63
  currentDetail = null;
64
+ upPhasePlan = BASE_UP_PHASE_PLAN;
48
65
  prevLineCount = 0;
49
66
  ended = false;
50
67
  renderTimer = null;
@@ -96,6 +113,13 @@ class UpProgress {
96
113
  return;
97
114
  this.write(` ${label}\n`);
98
115
  }
116
+ setPhasePlan(labels) {
117
+ const nextPlan = [...new Set(labels.map((label) => label.trim()).filter((label) => label.length > 0))];
118
+ this.upPhasePlan = nextPlan.length > 0 ? nextPlan : BASE_UP_PHASE_PLAN;
119
+ if (this.isTTY && this.eventScope === "up") {
120
+ this.flushRender();
121
+ }
122
+ }
99
123
  /**
100
124
  * Update the sub-step detail on the current spinner phase. Rendered as
101
125
  * "label (Xs) -- detail" in TTY mode. In non-TTY mode, writes changed
@@ -272,39 +296,61 @@ class UpProgress {
272
296
  }
273
297
  return lines;
274
298
  }
299
+ renderUpStepLabel(label) {
300
+ return FRIENDLY_UP_PHASE_LABELS[label] ?? label;
301
+ }
275
302
  renderUpScreen(now) {
276
- const steps = this.completed.map((phase) => ({
277
- label: phase.label,
278
- status: phase.status === "failure" ? "failed" : "done",
279
- detail: phase.detail,
280
- }));
281
- let currentStepLabel = "Standing by.";
303
+ const seenLabels = new Set();
304
+ const steps = this.completed.map((phase) => {
305
+ seenLabels.add(phase.label);
306
+ return {
307
+ label: this.renderUpStepLabel(phase.label),
308
+ status: phase.status === "failure" ? "failed" : "done",
309
+ detail: phase.detail,
310
+ };
311
+ });
312
+ let currentStepLabel = this.completed.some((phase) => phase.status === "failure")
313
+ ? "Boot paused."
314
+ : this.completed.length > 0
315
+ ? "Boot checklist complete."
316
+ : "Waiting to begin.";
282
317
  let currentStepDetails = [];
283
318
  if (this.currentPhase) {
284
319
  const elapsed = now - this.currentPhase.startedAt;
285
320
  const elapsedSec = (elapsed / 1000).toFixed(1);
286
321
  const frameIndex = Math.floor(elapsed / 80) % SPINNER_FRAMES.length;
287
322
  const spinner = SPINNER_FRAMES[frameIndex];
288
- currentStepLabel = `${spinner} ${this.currentPhase.label} (${elapsedSec}s)`;
323
+ currentStepLabel = `${spinner} ${this.renderUpStepLabel(this.currentPhase.label)} (${elapsedSec}s)`;
289
324
  currentStepDetails = splitDetailLines(this.currentPhase.detail);
290
325
  steps.push({
291
- label: this.currentPhase.label,
326
+ label: this.renderUpStepLabel(this.currentPhase.label),
292
327
  status: "active",
293
328
  });
329
+ seenLabels.add(this.currentPhase.label);
330
+ }
331
+ for (const label of this.upPhasePlan) {
332
+ if (!seenLabels.has(label)) {
333
+ steps.push({
334
+ label: this.renderUpStepLabel(label),
335
+ status: "pending",
336
+ });
337
+ }
294
338
  }
295
339
  return (0, terminal_ui_1.renderTerminalOperation)({
296
340
  isTTY: true,
297
341
  columns: this.columns,
298
342
  masthead: {
299
- subtitle: "Starting the local agent runtime.",
343
+ subtitle: "Booting the local agent runtime.",
300
344
  },
301
- title: "Starting Ouro",
302
- summary: "Ouro is starting the background runtime, checking credentials, and surfacing anything that needs attention before chat begins.",
345
+ title: "Ouro boot checklist",
346
+ summary: "Ouro will check for updates, prepare this machine, verify the providers your agents use right now, start the background service, and make sure it stays up.",
303
347
  currentStep: {
304
348
  label: currentStepLabel,
305
349
  detailLines: currentStepDetails,
306
350
  },
307
351
  steps,
352
+ currentTitle: "Doing now",
353
+ stepsTitle: "Boot checklist",
308
354
  suppressEvent: true,
309
355
  }).trimEnd().split("\n");
310
356
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.442",
3
+ "version": "0.1.0-alpha.443",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",