@ouro.bot/cli 0.1.0-alpha.415 → 0.1.0-alpha.417
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,23 @@
|
|
|
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.417",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`isTransientError` now recognizes timeout errors after `formatBwCliError` transforms them, so retries fire correctly even when the raw `ETIMEDOUT` code is lost during formatting.",
|
|
8
|
+
"New `withTransientRetry` wrapper on read operations (`get`, `getRawSecret`, `list`) retries up to 3 times with exponential backoff (500ms, 1s, 2s) and emits `repertoire.bw_transient_retry` nerves events, so transient vault hiccups resolve silently.",
|
|
9
|
+
"`bw sync` now runs after every login/unlock in `loginAttempt()`, ensuring vault data is synchronized with the server before any read operations.",
|
|
10
|
+
"Timeout error messages changed from a generic 'timed out while waiting for a vault response' to actionable guidance: 'timed out -- usually resolves on retry. If it persists, check network connectivity to the vault server.'",
|
|
11
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the Bitwarden store resilience release."
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.1.0-alpha.416",
|
|
16
|
+
"changes": [
|
|
17
|
+
"Post-repair provider health re-check in `ouro up` now shows a dedicated progress phase (`post-repair check`) with per-agent detail instead of running silently after interactive repair completes.",
|
|
18
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the post-repair progress release."
|
|
19
|
+
]
|
|
20
|
+
},
|
|
4
21
|
{
|
|
5
22
|
"version": "0.1.0-alpha.415",
|
|
6
23
|
"changes": [
|
|
@@ -179,8 +179,8 @@ function providerRepairCountSummary(count) {
|
|
|
179
179
|
return "ok";
|
|
180
180
|
return `${count} ${count === 1 ? "needs" : "need"} attention`;
|
|
181
181
|
}
|
|
182
|
-
async function reportPostRepairProviderHealth(deps, repairedAgents) {
|
|
183
|
-
const remainingDegraded = await checkAgentProviders(deps, repairedAgents);
|
|
182
|
+
async function reportPostRepairProviderHealth(deps, repairedAgents, onProgress) {
|
|
183
|
+
const remainingDegraded = await checkAgentProviders(deps, repairedAgents, onProgress);
|
|
184
184
|
(0, runtime_1.emitNervesEvent)({
|
|
185
185
|
level: remainingDegraded.length > 0 ? "warn" : "info",
|
|
186
186
|
component: "daemon",
|
|
@@ -2700,7 +2700,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2700
2700
|
const repairedAgents = daemonResult.stability.degraded
|
|
2701
2701
|
.filter(interactive_repair_1.hasRunnableInteractiveRepair)
|
|
2702
2702
|
.map((entry) => entry.agent);
|
|
2703
|
-
|
|
2703
|
+
progress.startPhase("post-repair check");
|
|
2704
|
+
await reportPostRepairProviderHealth(deps, repairedAgents, (msg) => progress.updateDetail(msg));
|
|
2705
|
+
progress.completePhase("post-repair check", providerRepairCountSummary(repairedAgents.length));
|
|
2704
2706
|
}
|
|
2705
2707
|
}
|
|
2706
2708
|
}
|
|
@@ -116,7 +116,7 @@ function sanitizeBwErrorDetail(message) {
|
|
|
116
116
|
function formatBwCliError(err, stderr = "", args = []) {
|
|
117
117
|
const operation = formatBwOperation(args);
|
|
118
118
|
if (isBwTimeoutError(err)) {
|
|
119
|
-
return new Error(`bw CLI error: ${operation} timed out
|
|
119
|
+
return new Error(`bw CLI error: ${operation} timed out -- usually resolves on retry. If it persists, check network connectivity to the vault server.`);
|
|
120
120
|
}
|
|
121
121
|
const detail = sanitizeBwErrorDetail(stderr.trim() || err.message);
|
|
122
122
|
if (detail === "command failed") {
|
|
@@ -274,10 +274,13 @@ function isTransientError(err) {
|
|
|
274
274
|
msg.includes("enotfound") ||
|
|
275
275
|
msg.includes("socket hang up") ||
|
|
276
276
|
msg.includes("503") ||
|
|
277
|
-
msg.includes("server unavailable")
|
|
277
|
+
msg.includes("server unavailable") ||
|
|
278
|
+
msg.includes("timed out"));
|
|
278
279
|
}
|
|
279
280
|
const MAX_RETRIES = 3;
|
|
280
281
|
const BASE_BACKOFF_MS = 1000;
|
|
282
|
+
const TRANSIENT_MAX_RETRIES = 3;
|
|
283
|
+
const TRANSIENT_RETRY_BASE_MS = 500;
|
|
281
284
|
function delay(ms) {
|
|
282
285
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
283
286
|
}
|
|
@@ -469,6 +472,9 @@ class BitwardenCredentialStore {
|
|
|
469
472
|
const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"], undefined, this.appDataDir);
|
|
470
473
|
this.sessionToken = unlockOutput.trim();
|
|
471
474
|
}
|
|
475
|
+
// Sync vault data after obtaining a fresh session token
|
|
476
|
+
/* v8 ignore next -- defensive: loginAttempt always sets sessionToken before sync @preserve */
|
|
477
|
+
await execBw(["sync"], this.sessionToken ?? undefined, this.appDataDir);
|
|
472
478
|
}
|
|
473
479
|
async ensureSession() {
|
|
474
480
|
if (!this.sessionToken) {
|
|
@@ -494,6 +500,32 @@ class BitwardenCredentialStore {
|
|
|
494
500
|
}
|
|
495
501
|
}
|
|
496
502
|
}
|
|
503
|
+
async withTransientRetry(operation) {
|
|
504
|
+
let lastError;
|
|
505
|
+
for (let attempt = 0; attempt < TRANSIENT_MAX_RETRIES; attempt++) {
|
|
506
|
+
try {
|
|
507
|
+
return await operation();
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
/* v8 ignore next -- defensive: operation always throws Error instances @preserve */
|
|
511
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
512
|
+
if (!isTransientError(lastError)) {
|
|
513
|
+
throw lastError;
|
|
514
|
+
}
|
|
515
|
+
if (attempt === TRANSIENT_MAX_RETRIES - 1)
|
|
516
|
+
break;
|
|
517
|
+
const backoffMs = TRANSIENT_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
518
|
+
(0, runtime_1.emitNervesEvent)({
|
|
519
|
+
event: "repertoire.bw_transient_retry",
|
|
520
|
+
component: "repertoire",
|
|
521
|
+
message: `transient bw error, retrying in ${backoffMs}ms`,
|
|
522
|
+
meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
|
|
523
|
+
});
|
|
524
|
+
await delay(backoffMs);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
throw lastError;
|
|
528
|
+
}
|
|
497
529
|
async get(domain) {
|
|
498
530
|
(0, runtime_1.emitNervesEvent)({
|
|
499
531
|
event: "repertoire.bw_credential_get_start",
|
|
@@ -501,7 +533,7 @@ class BitwardenCredentialStore {
|
|
|
501
533
|
message: `getting credential via bw for ${domain}`,
|
|
502
534
|
meta: { domain, backend: "bitwarden" },
|
|
503
535
|
});
|
|
504
|
-
const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
|
|
536
|
+
const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session)));
|
|
505
537
|
if (!item) {
|
|
506
538
|
(0, runtime_1.emitNervesEvent)({
|
|
507
539
|
event: "repertoire.bw_credential_get_end",
|
|
@@ -525,7 +557,7 @@ class BitwardenCredentialStore {
|
|
|
525
557
|
};
|
|
526
558
|
}
|
|
527
559
|
async getRawSecret(domain, field) {
|
|
528
|
-
const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
|
|
560
|
+
const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session)));
|
|
529
561
|
if (!item) {
|
|
530
562
|
throw new Error(`no credential found for domain "${domain}"`);
|
|
531
563
|
}
|
|
@@ -595,7 +627,7 @@ class BitwardenCredentialStore {
|
|
|
595
627
|
message: "listing bw credentials",
|
|
596
628
|
meta: { backend: "bitwarden" },
|
|
597
629
|
});
|
|
598
|
-
const stdout = await this.withSessionRetry((session) => execBw(["list", "items"], session, this.appDataDir));
|
|
630
|
+
const stdout = await this.withTransientRetry(() => this.withSessionRetry((session) => execBw(["list", "items"], session, this.appDataDir)));
|
|
599
631
|
const items = parseBwItems(stdout, "bw list items");
|
|
600
632
|
const results = items.map((item) => ({
|
|
601
633
|
domain: item.name,
|