@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`
|
|
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: "
|
|
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 (
|
|
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
|
-
|
|
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) {
|