@ouro.bot/cli 0.1.0-alpha.448 → 0.1.0-alpha.449

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/README.md CHANGED
@@ -99,7 +99,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
99
99
  - Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
100
100
  - New vault unlock secrets are confirmed before use and rejected if they do not meet the minimum strength requirements.
101
101
  - Provider and runtime credentials are loaded into process memory at startup/auth/unlock/refresh and reused. The remote vault is not queried for every model or sense request.
102
- - Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` reuse the same readiness truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls.
102
+ - Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` agree on provider and vault truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls. Orientation commands such as root `ouro connect` may use shorter live probes, while startup and verification commands own durable readiness updates.
103
103
  - Human-facing CLI commands that can wait on browser auth, vault IO, daemon startup, daemon restart, provider checks, or connector setup use a shared progress checklist. If a cursor may blink for more than a few seconds, the command should print or animate the current step instead of going quiet.
104
104
  - CLI commands that mutate bundle config, such as vault setup or `ouro connect bluebubbles`, run bundle sync after the change when `sync.enabled` is true and report a compact `bundle sync:` line.
105
105
  - The daemon discovers bundles dynamically from `~/AgentBundles`.
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.449",
6
+ "changes": [
7
+ "`ouro connect` now uses a bounded live provider probe before opening the connection menu: it still checks the real selected providers, but it makes one orientation attempt with a 5-second hard timeout instead of spending the full startup retry budget before the human can choose a setup path.",
8
+ "Provider ping timeouts are now hard timeouts even when an SDK ignores the abort signal. The failing attempt is classified through the same shared retry machinery and reports a clear `provider ping timed out after <ms>ms` message instead of leaving the CLI stuck behind a blinking cursor.",
9
+ "Root connect provider checks no longer overwrite durable provider readiness. A quick menu probe can show `needs attention` for the current screen, while `ouro up`, `ouro check`, auth verification, and chat startup remain the flows that record lasting ready/failed provider state."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.448",
6
14
  "changes": [
@@ -389,6 +389,7 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
389
389
  return { ok: true };
390
390
  deps.onProgress?.(selectedProviderPlan(agentName, stateResult.state));
391
391
  const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
392
+ const shouldRecordReadiness = deps.recordReadiness ?? true;
392
393
  const providers = selectedProvidersForState(stateResult.state);
393
394
  const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, {
394
395
  ...(deps.onProgress ? { onProgress: mapVaultRefreshProgress(agentName, deps.onProgress) } : {}),
@@ -430,6 +431,7 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
430
431
  const pingResults = await Promise.all(groups.map(async (group) => {
431
432
  const result = await ping(group.provider, providerCredentialConfig(group.record), {
432
433
  model: group.model,
434
+ ...(deps.providerPingOptions ?? {}),
433
435
  ...(deps.onProgress
434
436
  ? (0, provider_ping_progress_1.createProviderPingProgressReporter)({
435
437
  provider: group.provider,
@@ -443,29 +445,33 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
443
445
  let firstFailure = null;
444
446
  for (const { group, result } of pingResults) {
445
447
  if (!result.ok) {
448
+ if (shouldRecordReadiness) {
449
+ for (const lane of group.lanes) {
450
+ writeLaneReadiness({
451
+ agentRoot: stateResult.agentRoot,
452
+ state: stateResult.state,
453
+ lane,
454
+ status: "failed",
455
+ credentialRevision: group.record.revision,
456
+ error: result.message,
457
+ attempts: pingAttemptCount(result),
458
+ });
459
+ }
460
+ }
461
+ firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
462
+ continue;
463
+ }
464
+ if (shouldRecordReadiness) {
446
465
  for (const lane of group.lanes) {
447
466
  writeLaneReadiness({
448
467
  agentRoot: stateResult.agentRoot,
449
468
  state: stateResult.state,
450
469
  lane,
451
- status: "failed",
470
+ status: "ready",
452
471
  credentialRevision: group.record.revision,
453
- error: result.message,
454
472
  attempts: pingAttemptCount(result),
455
473
  });
456
474
  }
457
- firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
458
- continue;
459
- }
460
- for (const lane of group.lanes) {
461
- writeLaneReadiness({
462
- agentRoot: stateResult.agentRoot,
463
- state: stateResult.state,
464
- lane,
465
- status: "ready",
466
- credentialRevision: group.record.revision,
467
- attempts: pingAttemptCount(result),
468
- });
469
475
  }
470
476
  }
471
477
  if (firstFailure)
@@ -106,6 +106,10 @@ const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
106
106
  const DEFAULT_DAEMON_STARTUP_STABILITY_WINDOW_MS = 1_500;
107
107
  const DEFAULT_DAEMON_STARTUP_RETRY_LIMIT = 1;
108
108
  const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
109
+ const CONNECT_PROVIDER_ORIENTATION_PING_OPTIONS = {
110
+ attemptPolicy: { maxAttempts: 1, baseDelayMs: 0, backoffMultiplier: 2 },
111
+ timeoutMs: 5_000,
112
+ };
109
113
  function summarizeCliUpdateCheckStatus(error, timedOut = false) {
110
114
  const normalized = error.trim().toLowerCase();
111
115
  if (timedOut || normalized.includes("timed out") || normalized.includes("abort")) {
@@ -218,13 +222,20 @@ async function checkAgentProviders(deps, agentsOverride, onProgress) {
218
222
  }
219
223
  return degraded;
220
224
  }
221
- async function checkAgentProviderHealth(agentName, bundlesRoot, deps, onProgress) {
225
+ async function checkAgentProviderHealth(agentName, bundlesRoot, deps, onProgress, options = {}) {
222
226
  const liveDeps = {};
223
227
  if (deps.homeDir)
224
228
  liveDeps.homeDir = deps.homeDir;
225
229
  if (onProgress)
226
230
  liveDeps.onProgress = onProgress;
227
- if (liveDeps.homeDir || liveDeps.onProgress) {
231
+ if (options.providerPingOptions)
232
+ liveDeps.providerPingOptions = options.providerPingOptions;
233
+ if (options.recordReadiness !== undefined)
234
+ liveDeps.recordReadiness = options.recordReadiness;
235
+ if (liveDeps.homeDir ||
236
+ liveDeps.onProgress ||
237
+ liveDeps.providerPingOptions ||
238
+ liveDeps.recordReadiness !== undefined) {
228
239
  return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot, liveDeps);
229
240
  }
230
241
  return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot);
@@ -2040,7 +2051,10 @@ async function buildConnectMenu(agent, deps, onProgress) {
2040
2051
  let providerHealth;
2041
2052
  try {
2042
2053
  onProgress?.("checking selected providers");
2043
- providerHealth = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress);
2054
+ providerHealth = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress, {
2055
+ providerPingOptions: CONNECT_PROVIDER_ORIENTATION_PING_OPTIONS,
2056
+ recordReadiness: false,
2057
+ });
2044
2058
  }
2045
2059
  catch (error) {
2046
2060
  providerHealth = {
@@ -34,6 +34,8 @@ function providerRetryTiming(delayMs) {
34
34
  }
35
35
  function formatProviderAttemptProgress(context, attempt, maxAttempts) {
36
36
  const prefix = context.subject ? `${context.subject}: ` : "";
37
+ if (maxAttempts <= 1)
38
+ return `${prefix}checking ${formatProviderPingLabel(context)}...`;
37
39
  return `${prefix}checking ${formatProviderPingLabel(context)} (attempt ${attempt} of ${maxAttempts})...`;
38
40
  }
39
41
  function formatProviderRetryProgress(context, record, maxAttempts) {
@@ -83,6 +83,37 @@ async function readGithubCopilotModelPingError(response) {
83
83
  function createStatusError(message, status) {
84
84
  return Object.assign(new Error(message), { status });
85
85
  }
86
+ function normalizePingTimeoutMs(timeoutMs) {
87
+ if (timeoutMs === undefined)
88
+ return PING_TIMEOUT_MS;
89
+ if (!Number.isFinite(timeoutMs))
90
+ return PING_TIMEOUT_MS;
91
+ return Math.max(1, Math.floor(timeoutMs));
92
+ }
93
+ function createPingTimeoutError(timeoutMs) {
94
+ return Object.assign(new Error(`provider ping timed out after ${timeoutMs}ms`), {
95
+ code: "ETIMEDOUT",
96
+ });
97
+ }
98
+ async function runPingWithHardTimeout(runtime, timeoutMs) {
99
+ const controller = new AbortController();
100
+ let timeout;
101
+ const timeoutPromise = new Promise((_, reject) => {
102
+ timeout = setTimeout(() => {
103
+ controller.abort();
104
+ reject(createPingTimeoutError(timeoutMs));
105
+ }, timeoutMs);
106
+ });
107
+ try {
108
+ await Promise.race([
109
+ Promise.resolve().then(() => runtime.ping(controller.signal)),
110
+ timeoutPromise,
111
+ ]);
112
+ }
113
+ finally {
114
+ clearTimeout(timeout);
115
+ }
116
+ }
86
117
  async function pingGithubCopilotModel(baseUrl, token, model, fetchImpl = fetch) {
87
118
  const base = baseUrl.replace(/\/+$/, "");
88
119
  const isClaude = model.startsWith("claude");
@@ -183,15 +214,7 @@ async function pingProvider(provider, config, options = {}) {
183
214
  onAttemptStart: options.onAttemptStart,
184
215
  onRetry: options.onRetry,
185
216
  run: async () => {
186
- const controller = new AbortController();
187
- /* v8 ignore next -- timeout callback: only fires after 10s, tests resolve faster @preserve */
188
- const timeout = setTimeout(() => controller.abort(), PING_TIMEOUT_MS);
189
- try {
190
- await runtime.ping(controller.signal);
191
- }
192
- finally {
193
- clearTimeout(timeout);
194
- }
217
+ await runPingWithHardTimeout(runtime, normalizePingTimeoutMs(options.timeoutMs));
195
218
  },
196
219
  });
197
220
  if (attempt.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.448",
3
+ "version": "0.1.0-alpha.449",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",