@ouro.bot/cli 0.1.0-alpha.394 → 0.1.0-alpha.396
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 +21 -0
- package/dist/heart/daemon/agent-config-check.js +16 -0
- package/dist/heart/daemon/agentic-repair.js +8 -0
- package/dist/heart/daemon/cli-exec.js +200 -47
- package/dist/heart/daemon/cli-help.js +8 -2
- package/dist/heart/daemon/cli-parse.js +7 -0
- package/dist/heart/daemon/interactive-repair.js +24 -0
- package/dist/heart/daemon/readiness-repair.js +205 -0
- package/dist/heart/identity.js +9 -1
- package/dist/repertoire/vault-unlock.js +1 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
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.396",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Added `ouro repair [--agent <agent>]`, a deterministic vault/provider readiness guide that uses typed repair issues and clear human choices instead of asking for AI diagnosis on known failures.",
|
|
8
|
+
"Provider health checks now attach shared typed repair metadata for locked vaults, missing provider credentials, and failed live provider/model pings, keeping CLI repair, `ouro up`, and agent-facing guidance aligned.",
|
|
9
|
+
"`ouro up` now always runs the live provider-check phase after startup, not only when the daemon was already running, so broken selected providers are surfaced during startup instead of later through confusing agent crashes.",
|
|
10
|
+
"`ouro vault create --agent <agent>` now defaults to the stable agent vault email when no vault locator exists, while still requiring a non-echoing human-provided unlock secret.",
|
|
11
|
+
"Cross-machine and auth/provider docs now include `ouro repair --agent <agent>` in the continuation path for existing bundles and old auth-style agents.",
|
|
12
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the typed readiness repair release."
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"version": "0.1.0-alpha.395",
|
|
17
|
+
"changes": [
|
|
18
|
+
"`ouro vault replace` and `ouro vault recover` now default to the stable agent vault email, `<agent>@ouro.bot`, instead of timestamped `+replaced` or `+recovered` addresses.",
|
|
19
|
+
"Vault repair now treats previously generated repair emails as stale defaults and repairs back to the stable agent email unless the human explicitly supplies `--email <email>`.",
|
|
20
|
+
"Existing-account repair guidance now tells operators to unlock the stable vault when possible and use `--email` only when intentionally moving an agent to a different vault account.",
|
|
21
|
+
"Auth/provider docs and CLI help now describe stable vault identity repair for old auth-style agents without implying that Ouro can recover a forgotten unlock secret.",
|
|
22
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the stable vault identity repair release."
|
|
23
|
+
]
|
|
24
|
+
},
|
|
4
25
|
{
|
|
5
26
|
"version": "0.1.0-alpha.394",
|
|
6
27
|
"changes": [
|
|
@@ -45,6 +45,7 @@ const machine_identity_1 = require("../machine-identity");
|
|
|
45
45
|
const provider_state_1 = require("../provider-state");
|
|
46
46
|
const provider_credentials_1 = require("../provider-credentials");
|
|
47
47
|
const vault_unlock_1 = require("../../repertoire/vault-unlock");
|
|
48
|
+
const readiness_repair_1 = require("./readiness-repair");
|
|
48
49
|
function isAgentProvider(value) {
|
|
49
50
|
return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
|
|
50
51
|
}
|
|
@@ -233,6 +234,13 @@ function missingCredentialResult(agentName, lane, provider, model, credentialPat
|
|
|
233
234
|
ok: false,
|
|
234
235
|
error: `${lane} provider ${provider} model ${model} has no credentials in ${agentName}'s vault at ${credentialPath}`,
|
|
235
236
|
fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to authenticate this machine, or run 'ouro use --agent ${agentName} --lane ${lane} --provider <provider> --model <model>' to choose a working provider/model.`,
|
|
237
|
+
issue: (0, readiness_repair_1.providerCredentialMissingIssue)({
|
|
238
|
+
agentName,
|
|
239
|
+
lane,
|
|
240
|
+
provider,
|
|
241
|
+
model,
|
|
242
|
+
credentialPath,
|
|
243
|
+
}),
|
|
236
244
|
};
|
|
237
245
|
}
|
|
238
246
|
function invalidPoolResult(agentName, lane, provider, model, pool) {
|
|
@@ -241,6 +249,7 @@ function invalidPoolResult(agentName, lane, provider, model, pool) {
|
|
|
241
249
|
ok: false,
|
|
242
250
|
error: `${lane} provider ${provider} model ${model} cannot read provider credentials because ${agentName}'s credential vault is locked on this machine.`,
|
|
243
251
|
fix: vaultUnlockOrRecoverFix(agentName),
|
|
252
|
+
issue: (0, readiness_repair_1.vaultLockedIssue)(agentName),
|
|
244
253
|
};
|
|
245
254
|
}
|
|
246
255
|
if (pool.reason === "invalid") {
|
|
@@ -268,6 +277,13 @@ function failedPingResult(agentName, lane, provider, model, result) {
|
|
|
268
277
|
ok: false,
|
|
269
278
|
error: `${lane} provider ${provider} model ${model} failed live check: ${result.message}`,
|
|
270
279
|
fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to refresh credentials, or run 'ouro use --agent ${agentName} --lane ${lane} --provider <provider> --model <model>' to switch this lane.`,
|
|
280
|
+
issue: (0, readiness_repair_1.providerLiveCheckFailedIssue)({
|
|
281
|
+
agentName,
|
|
282
|
+
lane,
|
|
283
|
+
provider,
|
|
284
|
+
model,
|
|
285
|
+
message: result.message,
|
|
286
|
+
}),
|
|
271
287
|
};
|
|
272
288
|
}
|
|
273
289
|
function credentialRecordForLane(pool, provider) {
|
|
@@ -12,6 +12,7 @@ exports.runAgenticRepair = runAgenticRepair;
|
|
|
12
12
|
const runtime_1 = require("../../nerves/runtime");
|
|
13
13
|
const interactive_repair_1 = require("./interactive-repair");
|
|
14
14
|
const provider_ping_1 = require("../provider-ping");
|
|
15
|
+
const readiness_repair_1 = require("./readiness-repair");
|
|
15
16
|
function buildSystemPrompt(degraded) {
|
|
16
17
|
const agentList = degraded
|
|
17
18
|
.map((d) => `- ${d.agent}: error="${d.errorReason}", hint="${d.fixHint}"`)
|
|
@@ -114,11 +115,18 @@ async function runAgenticRepair(degraded, deps) {
|
|
|
114
115
|
return { repairsAttempted: false, usedAgentic: false };
|
|
115
116
|
}
|
|
116
117
|
const hasLocalRepair = degraded.some(interactive_repair_1.hasRunnableInteractiveRepair);
|
|
118
|
+
const hasKnownTypedRepair = degraded.some((entry) => (0, readiness_repair_1.isKnownReadinessIssue)(entry.issue));
|
|
117
119
|
if (hasLocalRepair) {
|
|
118
120
|
const interactiveResult = await runDeterministicRepair(degraded, deps);
|
|
119
121
|
if (interactiveResult.repairsAttempted) {
|
|
120
122
|
return { repairsAttempted: true, usedAgentic: false };
|
|
121
123
|
}
|
|
124
|
+
if (hasKnownTypedRepair) {
|
|
125
|
+
return { repairsAttempted: false, usedAgentic: false };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (hasKnownTypedRepair) {
|
|
129
|
+
return { repairsAttempted: false, usedAgentic: false };
|
|
122
130
|
}
|
|
123
131
|
// Try to discover a working provider for agentic diagnosis
|
|
124
132
|
let discoveredProvider = null;
|
|
@@ -85,10 +85,12 @@ const doctor_1 = require("./doctor");
|
|
|
85
85
|
const cli_render_doctor_1 = require("./cli-render-doctor");
|
|
86
86
|
const interactive_repair_1 = require("./interactive-repair");
|
|
87
87
|
const agentic_repair_1 = require("./agentic-repair");
|
|
88
|
+
const readiness_repair_1 = require("./readiness-repair");
|
|
88
89
|
const startup_tui_1 = require("./startup-tui");
|
|
89
90
|
const stale_bundle_prune_1 = require("./stale-bundle-prune");
|
|
90
91
|
const up_progress_1 = require("./up-progress");
|
|
91
92
|
const provider_ping_1 = require("../provider-ping");
|
|
93
|
+
const agent_discovery_1 = require("./agent-discovery");
|
|
92
94
|
// ── ensureDaemonRunning ──
|
|
93
95
|
const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 10_000;
|
|
94
96
|
const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
|
|
@@ -96,23 +98,23 @@ const DEFAULT_DAEMON_STARTUP_STABILITY_WINDOW_MS = 1_500;
|
|
|
96
98
|
const DEFAULT_DAEMON_STARTUP_RETRY_LIMIT = 1;
|
|
97
99
|
const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
|
|
98
100
|
async function checkAgentProviders(deps, agentsOverride) {
|
|
99
|
-
const agents = agentsOverride ?? await
|
|
101
|
+
const agents = agentsOverride ?? await listCliAgents(deps);
|
|
100
102
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
101
103
|
const degraded = [];
|
|
102
104
|
for (const agent of [...new Set(agents)]) {
|
|
103
105
|
try {
|
|
104
|
-
const result = await (
|
|
106
|
+
const result = await checkAgentProviderHealth(agent, bundlesRoot, deps);
|
|
105
107
|
if (result.ok)
|
|
106
108
|
continue;
|
|
107
109
|
const errorReason = result.error ?? "agent provider health check failed";
|
|
108
110
|
const fixHint = result.fix ?? "";
|
|
109
|
-
degraded.push({ agent, errorReason, fixHint });
|
|
111
|
+
degraded.push({ agent, errorReason, fixHint, ...(result.issue ? { issue: result.issue } : {}) });
|
|
110
112
|
(0, runtime_1.emitNervesEvent)({
|
|
111
113
|
level: "error",
|
|
112
114
|
component: "daemon",
|
|
113
115
|
event: "daemon.agent_config_invalid",
|
|
114
116
|
message: errorReason,
|
|
115
|
-
meta: { agent,
|
|
117
|
+
meta: { agent, fixProvided: fixHint.length > 0, source: "already-running-provider-check" },
|
|
116
118
|
});
|
|
117
119
|
}
|
|
118
120
|
catch (error) {
|
|
@@ -127,12 +129,27 @@ async function checkAgentProviders(deps, agentsOverride) {
|
|
|
127
129
|
component: "daemon",
|
|
128
130
|
event: "daemon.agent_config_invalid",
|
|
129
131
|
message: errorReason,
|
|
130
|
-
meta: { agent,
|
|
132
|
+
meta: { agent, fixProvided: true, source: "already-running-provider-check" },
|
|
131
133
|
});
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
return degraded;
|
|
135
137
|
}
|
|
138
|
+
async function checkAgentProviderHealth(agentName, bundlesRoot, deps) {
|
|
139
|
+
if (deps.homeDir) {
|
|
140
|
+
return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot, { homeDir: deps.homeDir });
|
|
141
|
+
}
|
|
142
|
+
return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot);
|
|
143
|
+
}
|
|
144
|
+
async function listCliAgents(deps) {
|
|
145
|
+
if (deps.listDiscoveredAgents) {
|
|
146
|
+
return Promise.resolve(deps.listDiscoveredAgents());
|
|
147
|
+
}
|
|
148
|
+
if (deps.bundlesRoot) {
|
|
149
|
+
return (0, agent_discovery_1.listEnabledBundleAgents)({ bundlesRoot: deps.bundlesRoot });
|
|
150
|
+
}
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
136
153
|
async function checkAlreadyRunningAgentProviders(deps) {
|
|
137
154
|
return checkAgentProviders(deps);
|
|
138
155
|
}
|
|
@@ -149,7 +166,7 @@ async function reportPostRepairProviderHealth(deps, repairedAgents) {
|
|
|
149
166
|
});
|
|
150
167
|
if (remainingDegraded.length === 0) {
|
|
151
168
|
deps.writeStdout("provider checks recovered after repair");
|
|
152
|
-
return;
|
|
169
|
+
return remainingDegraded;
|
|
153
170
|
}
|
|
154
171
|
deps.writeStdout("provider checks still degraded after repair:");
|
|
155
172
|
for (const d of remainingDegraded) {
|
|
@@ -159,10 +176,11 @@ async function reportPostRepairProviderHealth(deps, repairedAgents) {
|
|
|
159
176
|
}
|
|
160
177
|
}
|
|
161
178
|
deps.writeStdout("run `ouro up` again after applying the remaining fixes.");
|
|
179
|
+
return remainingDegraded;
|
|
162
180
|
}
|
|
163
181
|
async function checkProviderHealthBeforeChat(agentName, deps) {
|
|
164
182
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
165
|
-
const result = await (
|
|
183
|
+
const result = await checkAgentProviderHealth(agentName, bundlesRoot, deps);
|
|
166
184
|
if (!result.ok) {
|
|
167
185
|
const output = `${result.error}\n${result.fix ? ` fix: ${result.fix}` : ""}`;
|
|
168
186
|
deps.writeStdout(output);
|
|
@@ -186,7 +204,7 @@ function mergeStartupStability(stability, extraDegraded) {
|
|
|
186
204
|
}
|
|
187
205
|
return { stable, degraded };
|
|
188
206
|
}
|
|
189
|
-
async function ensureDaemonRunning(deps) {
|
|
207
|
+
async function ensureDaemonRunning(deps, options = {}) {
|
|
190
208
|
const readLatestDaemonStartupEvent = () => {
|
|
191
209
|
try {
|
|
192
210
|
// The daemon writes structured events to daemon.ndjson in the first
|
|
@@ -230,7 +248,7 @@ async function ensureDaemonRunning(deps) {
|
|
|
230
248
|
}
|
|
231
249
|
return null;
|
|
232
250
|
};
|
|
233
|
-
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
251
|
+
const alive = options.initialAlive ?? await deps.checkSocketAlive(deps.socketPath);
|
|
234
252
|
if (alive) {
|
|
235
253
|
const localRuntime = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
236
254
|
let runningRuntimePromise = null;
|
|
@@ -686,19 +704,15 @@ function readVaultRecoverSource(sourcePath) {
|
|
|
686
704
|
runtimeConfig: recoverRuntimeConfig(parsed),
|
|
687
705
|
};
|
|
688
706
|
}
|
|
689
|
-
function
|
|
690
|
-
const local =
|
|
691
|
-
|
|
692
|
-
.replace(/[^a-z0-9._-]+/g, "-")
|
|
693
|
-
.replace(/^-+|-+$/g, "") || "agent";
|
|
694
|
-
const stamp = now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
695
|
-
return `${local}+${action}-${stamp}@ouro.bot`;
|
|
696
|
-
}
|
|
697
|
-
function defaultRecoveredVaultEmail(agentName, now) {
|
|
698
|
-
return defaultReplacementVaultEmail(agentName, now, "recovered");
|
|
707
|
+
function isGeneratedRepairVaultEmail(email) {
|
|
708
|
+
const [local, domain] = email.trim().split("@");
|
|
709
|
+
return domain?.toLowerCase() === "ouro.bot" && /\+(?:replaced|recovered)-\d{14}(?:$|\+)/i.test(local);
|
|
699
710
|
}
|
|
700
|
-
function
|
|
701
|
-
|
|
711
|
+
function defaultRepairVaultEmail(agentName, config) {
|
|
712
|
+
const configuredEmail = config.vault?.email?.trim();
|
|
713
|
+
if (configuredEmail && !isGeneratedRepairVaultEmail(configuredEmail))
|
|
714
|
+
return configuredEmail;
|
|
715
|
+
return (0, identity_1.defaultStableVaultEmail)(agentName);
|
|
702
716
|
}
|
|
703
717
|
function ensureVaultSecretPrompt(promptSecret, action) {
|
|
704
718
|
if (promptSecret)
|
|
@@ -708,13 +722,17 @@ function ensureVaultSecretPrompt(promptSecret, action) {
|
|
|
708
722
|
function rejectGeneratedVaultUnlockSecret(action) {
|
|
709
723
|
throw new Error(`vault ${action} no longer supports --generate-unlock-secret. Re-run without that flag and enter a human-chosen unlock secret; Ouro will not print vault unlock secrets.`);
|
|
710
724
|
}
|
|
711
|
-
async function
|
|
725
|
+
async function createRepairVaultForAgent(input) {
|
|
712
726
|
const result = await (0, vault_setup_1.createVaultAccount)("Ouro credential vault", input.serverUrl, input.email, input.unlockSecret);
|
|
713
727
|
if (!result.success) {
|
|
714
728
|
const message = [
|
|
715
729
|
`vault ${input.action} failed for ${input.agentName}: ${result.error}`,
|
|
716
730
|
"",
|
|
717
|
-
"
|
|
731
|
+
"Could not create the selected vault account.",
|
|
732
|
+
"If this is the existing vault, run:",
|
|
733
|
+
` ouro vault unlock --agent ${input.agentName}`,
|
|
734
|
+
"If the unlock secret is lost and you intentionally need a different vault account, rerun with --email <email>.",
|
|
735
|
+
"If this looks like a server or network issue, check --server and retry.",
|
|
718
736
|
].join("\n");
|
|
719
737
|
input.deps.writeStdout(message);
|
|
720
738
|
return { ok: false, message };
|
|
@@ -758,13 +776,9 @@ async function executeVaultCreate(command, deps) {
|
|
|
758
776
|
}
|
|
759
777
|
if (command.generateUnlockSecret)
|
|
760
778
|
rejectGeneratedVaultUnlockSecret("create");
|
|
761
|
-
const prompt = deps.promptInput;
|
|
762
779
|
const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
763
780
|
const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
764
|
-
const email = command.email ?? config.vault?.email ??
|
|
765
|
-
if (!email) {
|
|
766
|
-
throw new Error("vault create requires --email <email> when the agent bundle has no vault.email");
|
|
767
|
-
}
|
|
781
|
+
const email = command.email ?? config.vault?.email ?? configuredVault.email;
|
|
768
782
|
const promptSecret = ensureVaultSecretPrompt(deps.promptSecret, "create");
|
|
769
783
|
const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
|
|
770
784
|
const unlockSecret = (await promptSecret(`Choose Ouro vault unlock secret for ${email}: `)).trim();
|
|
@@ -807,16 +821,15 @@ async function executeVaultReplace(command, deps) {
|
|
|
807
821
|
if (command.generateUnlockSecret)
|
|
808
822
|
rejectGeneratedVaultUnlockSecret("replace");
|
|
809
823
|
const promptSecret = ensureVaultSecretPrompt(deps.promptSecret, "replace");
|
|
810
|
-
const now = providerCliNow(deps);
|
|
811
824
|
const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
812
825
|
const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
813
|
-
const email = command.email ??
|
|
826
|
+
const email = command.email ?? defaultRepairVaultEmail(command.agent, config);
|
|
814
827
|
const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
|
|
815
|
-
const unlockSecret = (await promptSecret(`Choose
|
|
828
|
+
const unlockSecret = (await promptSecret(`Choose Ouro vault unlock secret for ${email}: `)).trim();
|
|
816
829
|
if (!unlockSecret) {
|
|
817
|
-
throw new Error("vault replace requires
|
|
830
|
+
throw new Error("vault replace requires an unlock secret. Re-run in an interactive terminal and enter a human-chosen unlock secret.");
|
|
818
831
|
}
|
|
819
|
-
const
|
|
832
|
+
const repair = await createRepairVaultForAgent({
|
|
820
833
|
action: "replace",
|
|
821
834
|
agentName: command.agent,
|
|
822
835
|
email,
|
|
@@ -827,12 +840,12 @@ async function executeVaultReplace(command, deps) {
|
|
|
827
840
|
configPath,
|
|
828
841
|
config,
|
|
829
842
|
});
|
|
830
|
-
if (!
|
|
831
|
-
return
|
|
843
|
+
if (!repair.ok)
|
|
844
|
+
return repair.message;
|
|
832
845
|
const message = [
|
|
833
846
|
`vault replaced for ${command.agent}`,
|
|
834
847
|
`vault: ${email} at ${serverUrl}`,
|
|
835
|
-
`local unlock store: ${
|
|
848
|
+
`local unlock store: ${repair.store.kind}${repair.store.secure ? "" : " (explicit plaintext fallback)"}`,
|
|
836
849
|
"credentials imported: none",
|
|
837
850
|
"This is the no-export path for agents that predate vault auth or lost an unsaved unlock secret.",
|
|
838
851
|
"Re-auth/re-enter the credentials this agent should use:",
|
|
@@ -840,7 +853,7 @@ async function executeVaultReplace(command, deps) {
|
|
|
840
853
|
` ouro vault config set --agent ${command.agent} --key <field>`,
|
|
841
854
|
` ouro provider refresh --agent ${command.agent}`,
|
|
842
855
|
` ouro auth verify --agent ${command.agent}`,
|
|
843
|
-
"Keep the
|
|
856
|
+
"Keep the vault unlock secret saved outside Ouro. Another machine will need it once.",
|
|
844
857
|
].join("\n");
|
|
845
858
|
deps.writeStdout(message);
|
|
846
859
|
return message;
|
|
@@ -856,13 +869,13 @@ async function executeVaultRecover(command, deps) {
|
|
|
856
869
|
const now = providerCliNow(deps);
|
|
857
870
|
const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
858
871
|
const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
859
|
-
const email = command.email ??
|
|
872
|
+
const email = command.email ?? defaultRepairVaultEmail(command.agent, config);
|
|
860
873
|
const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
|
|
861
|
-
const unlockSecret = (await promptSecret(`Choose
|
|
874
|
+
const unlockSecret = (await promptSecret(`Choose Ouro vault unlock secret for ${email}: `)).trim();
|
|
862
875
|
if (!unlockSecret) {
|
|
863
|
-
throw new Error("vault recover requires
|
|
876
|
+
throw new Error("vault recover requires an unlock secret. Re-run in an interactive terminal and enter a human-chosen unlock secret.");
|
|
864
877
|
}
|
|
865
|
-
const
|
|
878
|
+
const repair = await createRepairVaultForAgent({
|
|
866
879
|
action: "recover",
|
|
867
880
|
agentName: command.agent,
|
|
868
881
|
email,
|
|
@@ -873,8 +886,8 @@ async function executeVaultRecover(command, deps) {
|
|
|
873
886
|
configPath,
|
|
874
887
|
config,
|
|
875
888
|
});
|
|
876
|
-
if (!
|
|
877
|
-
return
|
|
889
|
+
if (!repair.ok)
|
|
890
|
+
return repair.message;
|
|
878
891
|
const importedProviders = new Set();
|
|
879
892
|
let mergedRuntimeConfig = {};
|
|
880
893
|
for (const source of sourceImports) {
|
|
@@ -899,12 +912,12 @@ async function executeVaultRecover(command, deps) {
|
|
|
899
912
|
const message = [
|
|
900
913
|
`vault recovered for ${command.agent}`,
|
|
901
914
|
`vault: ${email} at ${serverUrl}`,
|
|
902
|
-
`local unlock store: ${
|
|
915
|
+
`local unlock store: ${repair.store.kind}${repair.store.secure ? "" : " (explicit plaintext fallback)"}`,
|
|
903
916
|
`sources imported: ${sourceImports.length}`,
|
|
904
917
|
`provider credentials imported: ${providerList.length === 0 ? "none" : providerList.join(", ")}`,
|
|
905
918
|
`runtime credentials imported: ${runtimeFields.length === 0 ? "none" : runtimeFields.join(", ")}`,
|
|
906
919
|
"credential values were not printed",
|
|
907
|
-
"Keep the
|
|
920
|
+
"Keep the vault unlock secret saved outside Ouro. Another machine will need it once.",
|
|
908
921
|
].join("\n");
|
|
909
922
|
deps.writeStdout(message);
|
|
910
923
|
return message;
|
|
@@ -1350,6 +1363,108 @@ async function executeProviderRefresh(command, deps) {
|
|
|
1350
1363
|
deps.writeStdout(message);
|
|
1351
1364
|
return message;
|
|
1352
1365
|
}
|
|
1366
|
+
async function readinessReportForAgent(agent, deps) {
|
|
1367
|
+
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
1368
|
+
try {
|
|
1369
|
+
const result = await checkAgentProviderHealth(agent, bundlesRoot, deps);
|
|
1370
|
+
if (result.ok) {
|
|
1371
|
+
return { agent, ok: true, issues: [] };
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1374
|
+
const issue = result.issue ?? (0, readiness_repair_1.genericReadinessIssue)({
|
|
1375
|
+
summary: result.error ?? `${agent} is not ready.`,
|
|
1376
|
+
...(result.fix ? { fix: result.fix } : {}),
|
|
1377
|
+
});
|
|
1378
|
+
return { agent, ok: false, issues: [issue] };
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
catch (error) {
|
|
1382
|
+
return {
|
|
1383
|
+
agent,
|
|
1384
|
+
ok: false,
|
|
1385
|
+
issues: [(0, readiness_repair_1.genericReadinessIssue)({
|
|
1386
|
+
summary: `${agent} readiness check failed.`,
|
|
1387
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1388
|
+
fix: "Run 'ouro doctor' for diagnostics, then retry 'ouro repair'.",
|
|
1389
|
+
})],
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async function executeReadinessRepairAction(agent, action, deps) {
|
|
1394
|
+
if (action.kind === "vault-unlock") {
|
|
1395
|
+
await executeVaultUnlock({ kind: "vault.unlock", agent }, deps);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (action.kind === "vault-replace") {
|
|
1399
|
+
await executeVaultReplace({ kind: "vault.replace", agent }, deps);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (action.kind === "provider-auth") {
|
|
1403
|
+
const provider = action.provider;
|
|
1404
|
+
const authRunner = deps.runAuthFlow ?? auth_flow_1.runRuntimeAuthFlow;
|
|
1405
|
+
const result = await authRunner({
|
|
1406
|
+
agentName: agent,
|
|
1407
|
+
provider,
|
|
1408
|
+
promptInput: deps.promptInput,
|
|
1409
|
+
});
|
|
1410
|
+
deps.writeStdout(result.message);
|
|
1411
|
+
await executeProviderRefresh({ kind: "provider.refresh", agent }, deps);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
deps.writeStdout(`manual step for ${agent}: ${action.command}`);
|
|
1415
|
+
}
|
|
1416
|
+
async function executeRepair(command, deps) {
|
|
1417
|
+
const agents = command.agent
|
|
1418
|
+
? [command.agent]
|
|
1419
|
+
: await listCliAgents(deps);
|
|
1420
|
+
const uniqueAgents = [...new Set(agents)];
|
|
1421
|
+
const lines = [];
|
|
1422
|
+
const repairDeps = {
|
|
1423
|
+
...deps,
|
|
1424
|
+
writeStdout: (text) => {
|
|
1425
|
+
lines.push(text);
|
|
1426
|
+
deps.writeStdout(text);
|
|
1427
|
+
},
|
|
1428
|
+
};
|
|
1429
|
+
if (uniqueAgents.length === 0) {
|
|
1430
|
+
const message = "no agents found to repair.";
|
|
1431
|
+
repairDeps.writeStdout(message);
|
|
1432
|
+
return message;
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
const reports = await Promise.all(uniqueAgents.map((agent) => readinessReportForAgent(agent, repairDeps)));
|
|
1436
|
+
for (const report of reports) {
|
|
1437
|
+
if (report.ok) {
|
|
1438
|
+
repairDeps.writeStdout(`${report.agent}: ready`);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
await (0, readiness_repair_1.runGuidedReadinessRepair)(reports, {
|
|
1442
|
+
promptInput: deps.promptInput,
|
|
1443
|
+
writeStdout: repairDeps.writeStdout,
|
|
1444
|
+
runRepairAction: async (agentName, action) => executeReadinessRepairAction(agentName, action, repairDeps),
|
|
1445
|
+
});
|
|
1446
|
+
return lines.join("\n");
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
function readinessReportsFromDegraded(degraded) {
|
|
1450
|
+
return degraded.map((entry) => ({
|
|
1451
|
+
agent: entry.agent,
|
|
1452
|
+
ok: false,
|
|
1453
|
+
issues: [
|
|
1454
|
+
entry.issue ?? (0, readiness_repair_1.genericReadinessIssue)({
|
|
1455
|
+
summary: entry.errorReason,
|
|
1456
|
+
...(entry.fixHint ? { fix: entry.fixHint } : {}),
|
|
1457
|
+
}),
|
|
1458
|
+
],
|
|
1459
|
+
}));
|
|
1460
|
+
}
|
|
1461
|
+
async function runReadinessRepairForDegraded(degraded, deps) {
|
|
1462
|
+
return (0, readiness_repair_1.runGuidedReadinessRepair)(readinessReportsFromDegraded(degraded), {
|
|
1463
|
+
promptInput: deps.promptInput,
|
|
1464
|
+
writeStdout: deps.writeStdout,
|
|
1465
|
+
runRepairAction: async (agentName, action) => executeReadinessRepairAction(agentName, action, deps),
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1353
1468
|
async function executeLegacyAuthSwitch(command, deps) {
|
|
1354
1469
|
const { state } = readOrBootstrapProviderState(command.agent, deps);
|
|
1355
1470
|
const lanes = command.facing
|
|
@@ -2113,6 +2228,41 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2113
2228
|
bundlesRoot: deps.bundlesRoot ?? bundlesRoot,
|
|
2114
2229
|
promptInput: deps.promptInput,
|
|
2115
2230
|
});
|
|
2231
|
+
const daemonAliveBeforeStart = await deps.checkSocketAlive(deps.socketPath);
|
|
2232
|
+
let providerChecksAlreadyRun = false;
|
|
2233
|
+
if (!daemonAliveBeforeStart) {
|
|
2234
|
+
progress.startPhase("provider checks");
|
|
2235
|
+
const preflightProviderDegraded = await checkAgentProviders(deps);
|
|
2236
|
+
providerChecksAlreadyRun = true;
|
|
2237
|
+
progress.completePhase("provider checks", preflightProviderDegraded.length > 0 ? `${preflightProviderDegraded.length} degraded` : "ok");
|
|
2238
|
+
if (preflightProviderDegraded.length > 0) {
|
|
2239
|
+
progress.end();
|
|
2240
|
+
if (command.noRepair) {
|
|
2241
|
+
deps.writeStdout("degraded agents:");
|
|
2242
|
+
for (const d of preflightProviderDegraded) {
|
|
2243
|
+
deps.writeStdout(` ${d.agent}: ${d.errorReason}`);
|
|
2244
|
+
if (d.fixHint) {
|
|
2245
|
+
deps.writeStdout(` fix: ${d.fixHint}`);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
const message = "daemon not started because provider checks are degraded; run `ouro repair` after applying the fixes.";
|
|
2249
|
+
deps.writeStdout(message);
|
|
2250
|
+
return message;
|
|
2251
|
+
}
|
|
2252
|
+
const repairResult = await runReadinessRepairForDegraded(preflightProviderDegraded, deps);
|
|
2253
|
+
if (!repairResult.repairsAttempted) {
|
|
2254
|
+
const message = "daemon not started because provider checks are degraded; run `ouro repair` after applying the fixes.";
|
|
2255
|
+
deps.writeStdout(message);
|
|
2256
|
+
return message;
|
|
2257
|
+
}
|
|
2258
|
+
const remainingDegraded = await reportPostRepairProviderHealth(deps, preflightProviderDegraded.map((entry) => entry.agent));
|
|
2259
|
+
if (remainingDegraded.length > 0) {
|
|
2260
|
+
const message = "daemon not started because provider checks are still degraded.";
|
|
2261
|
+
deps.writeStdout(message);
|
|
2262
|
+
return message;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2116
2266
|
progress.startPhase("starting daemon");
|
|
2117
2267
|
const daemonResult = await ensureDaemonRunning({
|
|
2118
2268
|
...deps,
|
|
@@ -2120,8 +2270,8 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2120
2270
|
;
|
|
2121
2271
|
progress.announceStep?.(label);
|
|
2122
2272
|
},
|
|
2123
|
-
});
|
|
2124
|
-
if (daemonResult.alreadyRunning) {
|
|
2273
|
+
}, { initialAlive: daemonAliveBeforeStart });
|
|
2274
|
+
if (!providerChecksAlreadyRun || daemonResult.alreadyRunning) {
|
|
2125
2275
|
progress.startPhase("provider checks");
|
|
2126
2276
|
const providerDegraded = await checkAlreadyRunningAgentProviders(deps);
|
|
2127
2277
|
daemonResult.stability = mergeStartupStability(daemonResult.stability, providerDegraded);
|
|
@@ -2788,6 +2938,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2788
2938
|
if (command.kind === "provider.refresh") {
|
|
2789
2939
|
return executeProviderRefresh(command, deps);
|
|
2790
2940
|
}
|
|
2941
|
+
if (command.kind === "repair") {
|
|
2942
|
+
return executeRepair(command, deps);
|
|
2943
|
+
}
|
|
2791
2944
|
if (command.kind === "vault.unlock") {
|
|
2792
2945
|
return executeVaultUnlock(command, deps);
|
|
2793
2946
|
}
|
|
@@ -175,6 +175,12 @@ exports.COMMAND_REGISTRY = {
|
|
|
175
175
|
usage: "ouro check --agent <name> --lane outward|inner",
|
|
176
176
|
example: "ouro check --agent ouroboros --lane outward",
|
|
177
177
|
},
|
|
178
|
+
repair: {
|
|
179
|
+
category: "Auth",
|
|
180
|
+
description: "Guide vault and provider readiness repair without invoking AI diagnosis for known issues",
|
|
181
|
+
usage: "ouro repair [--agent <name>]",
|
|
182
|
+
example: "ouro repair --agent ouroboros",
|
|
183
|
+
},
|
|
178
184
|
provider: {
|
|
179
185
|
category: "Auth",
|
|
180
186
|
description: "Refresh daemon provider credentials from an agent vault",
|
|
@@ -270,12 +276,12 @@ const SUBCOMMAND_HELP = {
|
|
|
270
276
|
example: "ouro vault create --agent ouroboros --email ouroboros@ouro.bot",
|
|
271
277
|
},
|
|
272
278
|
"vault replace": {
|
|
273
|
-
description: "Create an empty
|
|
279
|
+
description: "Create an empty agent vault at the stable agent email when no unlock secret or JSON export exists",
|
|
274
280
|
usage: "ouro vault replace --agent <name> [--email <email>] [--server <url>] [--store <store>]",
|
|
275
281
|
example: "ouro vault replace --agent ouroboros",
|
|
276
282
|
},
|
|
277
283
|
"vault recover": {
|
|
278
|
-
description: "Create
|
|
284
|
+
description: "Create an agent vault at the stable agent email and import local JSON credential exports",
|
|
279
285
|
usage: "ouro vault recover --agent <name> --from <json> [--from <json>] [--email <email>] [--server <url>] [--store <store>]",
|
|
280
286
|
example: "ouro vault recover --agent ouroboros --from ./credentials.json",
|
|
281
287
|
},
|
|
@@ -76,6 +76,7 @@ function usage() {
|
|
|
76
76
|
" ouro status --agent <name>",
|
|
77
77
|
" ouro use --agent <name> --lane outward|inner --provider <provider> --model <model> [--force]",
|
|
78
78
|
" ouro check --agent <name> --lane outward|inner",
|
|
79
|
+
" ouro repair [--agent <name>]",
|
|
79
80
|
" ouro provider refresh --agent <name>",
|
|
80
81
|
" ouro outlook [--json]",
|
|
81
82
|
" ouro -v|--version",
|
|
@@ -1033,6 +1034,12 @@ function parseOuroCommand(args) {
|
|
|
1033
1034
|
return parseProviderUseCommand(args.slice(1));
|
|
1034
1035
|
if (head === "check")
|
|
1035
1036
|
return parseProviderCheckCommand(args.slice(1));
|
|
1037
|
+
if (head === "repair") {
|
|
1038
|
+
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
1039
|
+
if (rest.length > 0)
|
|
1040
|
+
throw new Error("Usage: ouro repair [--agent <name>]");
|
|
1041
|
+
return agent ? { kind: "repair", agent } : { kind: "repair" };
|
|
1042
|
+
}
|
|
1036
1043
|
if (head === "provider")
|
|
1037
1044
|
return parseProviderCommand(args.slice(1));
|
|
1038
1045
|
if (head === "logs") {
|
|
@@ -24,6 +24,9 @@ function isConfigError(degraded) {
|
|
|
24
24
|
return degraded.fixHint.length > 0 && !isVaultUnlockIssue(degraded) && !isCredentialIssue(degraded);
|
|
25
25
|
}
|
|
26
26
|
function hasRunnableInteractiveRepair(degraded) {
|
|
27
|
+
if (degraded.issue?.actions.some((action) => typedActionToRunnable(degraded, action) !== undefined)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
27
30
|
return isVaultUnlockIssue(degraded) || isCredentialIssue(degraded);
|
|
28
31
|
}
|
|
29
32
|
function isAgentProvider(value) {
|
|
@@ -68,6 +71,11 @@ function writeDeclinedRepair(degraded, command, deps) {
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
function runnableRepairActionFor(degraded) {
|
|
74
|
+
const typedAction = degraded.issue?.actions
|
|
75
|
+
.map((action) => typedActionToRunnable(degraded, action))
|
|
76
|
+
.find((action) => action !== undefined);
|
|
77
|
+
if (typedAction)
|
|
78
|
+
return typedAction;
|
|
71
79
|
if (isVaultUnlockIssue(degraded)) {
|
|
72
80
|
return { kind: "vault-unlock", label: "vault unlock", command: vaultUnlockCommandFor(degraded) };
|
|
73
81
|
}
|
|
@@ -81,6 +89,22 @@ function runnableRepairActionFor(degraded) {
|
|
|
81
89
|
}
|
|
82
90
|
return undefined;
|
|
83
91
|
}
|
|
92
|
+
function typedActionToRunnable(degraded, action) {
|
|
93
|
+
if (action.executable === false || action.command.includes("<"))
|
|
94
|
+
return undefined;
|
|
95
|
+
if (action.kind === "vault-unlock") {
|
|
96
|
+
return { kind: "vault-unlock", label: "vault unlock", command: action.command };
|
|
97
|
+
}
|
|
98
|
+
if (action.kind === "provider-auth") {
|
|
99
|
+
return {
|
|
100
|
+
kind: "provider-auth",
|
|
101
|
+
label: "provider auth",
|
|
102
|
+
command: action.command || `ouro auth --agent ${degraded.agent}`,
|
|
103
|
+
provider: action.provider,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
84
108
|
function writeRepairQueueSummary(degraded, deps) {
|
|
85
109
|
const repairable = degraded
|
|
86
110
|
.map((entry) => ({ entry, action: runnableRepairActionFor(entry) }))
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.vaultLockedIssue = vaultLockedIssue;
|
|
4
|
+
exports.providerCredentialMissingIssue = providerCredentialMissingIssue;
|
|
5
|
+
exports.providerLiveCheckFailedIssue = providerLiveCheckFailedIssue;
|
|
6
|
+
exports.genericReadinessIssue = genericReadinessIssue;
|
|
7
|
+
exports.isKnownReadinessIssue = isKnownReadinessIssue;
|
|
8
|
+
exports.renderReadinessIssue = renderReadinessIssue;
|
|
9
|
+
exports.runGuidedReadinessRepair = runGuidedReadinessRepair;
|
|
10
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
11
|
+
function vaultLockedIssue(agentName) {
|
|
12
|
+
return {
|
|
13
|
+
kind: "vault-locked",
|
|
14
|
+
severity: "blocked",
|
|
15
|
+
actor: "human-required",
|
|
16
|
+
summary: `${agentName} needs its vault unlocked on this machine.`,
|
|
17
|
+
detail: "Choose the path that matches what the human actually has. Ouro will not print or store a portable copy of the unlock secret.",
|
|
18
|
+
actions: [
|
|
19
|
+
{
|
|
20
|
+
kind: "vault-unlock",
|
|
21
|
+
label: "I have the saved vault unlock secret",
|
|
22
|
+
command: `ouro vault unlock --agent ${agentName}`,
|
|
23
|
+
actor: "human-required",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
kind: "vault-replace",
|
|
27
|
+
label: "Nobody saved it; create an empty vault and re-enter credentials",
|
|
28
|
+
command: `ouro vault replace --agent ${agentName}`,
|
|
29
|
+
actor: "human-required",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
kind: "vault-recover",
|
|
33
|
+
label: "I have an old JSON credential export",
|
|
34
|
+
command: `ouro vault recover --agent ${agentName} --from <json>`,
|
|
35
|
+
actor: "human-required",
|
|
36
|
+
executable: false,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function providerCredentialMissingIssue(input) {
|
|
42
|
+
return {
|
|
43
|
+
kind: "provider-credentials-missing",
|
|
44
|
+
severity: "blocked",
|
|
45
|
+
actor: "human-required",
|
|
46
|
+
summary: `${input.agentName} is missing ${input.provider} credentials for the ${input.lane} lane.`,
|
|
47
|
+
detail: `Selected model: ${input.model}. Credential source: ${input.credentialPath}.`,
|
|
48
|
+
actions: [
|
|
49
|
+
{
|
|
50
|
+
kind: "provider-auth",
|
|
51
|
+
label: `Authenticate ${input.provider} for ${input.agentName}`,
|
|
52
|
+
command: `ouro auth --agent ${input.agentName} --provider ${input.provider}`,
|
|
53
|
+
actor: "human-required",
|
|
54
|
+
provider: input.provider,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
kind: "provider-use",
|
|
58
|
+
label: "Choose a different working provider/model for this lane",
|
|
59
|
+
command: `ouro use --agent ${input.agentName} --lane ${input.lane} --provider <provider> --model <model>`,
|
|
60
|
+
actor: "human-choice",
|
|
61
|
+
executable: false,
|
|
62
|
+
lane: input.lane,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function providerLiveCheckFailedIssue(input) {
|
|
68
|
+
return {
|
|
69
|
+
kind: "provider-live-check-failed",
|
|
70
|
+
severity: "blocked",
|
|
71
|
+
actor: "human-choice",
|
|
72
|
+
summary: `${input.agentName}'s ${input.lane} lane provider ${input.provider} / ${input.model} failed its live check.`,
|
|
73
|
+
detail: input.message,
|
|
74
|
+
actions: [
|
|
75
|
+
{
|
|
76
|
+
kind: "provider-auth",
|
|
77
|
+
label: `Refresh ${input.provider} credentials`,
|
|
78
|
+
command: `ouro auth --agent ${input.agentName} --provider ${input.provider}`,
|
|
79
|
+
actor: "human-required",
|
|
80
|
+
provider: input.provider,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
kind: "provider-use",
|
|
84
|
+
label: "Choose a different working provider/model for this lane",
|
|
85
|
+
command: `ouro use --agent ${input.agentName} --lane ${input.lane} --provider <provider> --model <model>`,
|
|
86
|
+
actor: "human-choice",
|
|
87
|
+
executable: false,
|
|
88
|
+
lane: input.lane,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function genericReadinessIssue(input) {
|
|
94
|
+
return {
|
|
95
|
+
kind: "generic",
|
|
96
|
+
severity: "degraded",
|
|
97
|
+
actor: "human-choice",
|
|
98
|
+
summary: input.summary,
|
|
99
|
+
...(input.detail ? { detail: input.detail } : {}),
|
|
100
|
+
actions: input.fix
|
|
101
|
+
? [{
|
|
102
|
+
kind: "provider-use",
|
|
103
|
+
label: "Follow the printed fix",
|
|
104
|
+
command: input.fix,
|
|
105
|
+
actor: "human-choice",
|
|
106
|
+
executable: false,
|
|
107
|
+
}]
|
|
108
|
+
: [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function isKnownReadinessIssue(issue) {
|
|
112
|
+
return !!issue && issue.kind !== "generic";
|
|
113
|
+
}
|
|
114
|
+
function renderReadinessIssue(issue) {
|
|
115
|
+
const lines = [issue.summary];
|
|
116
|
+
if (issue.detail) {
|
|
117
|
+
lines.push(` ${issue.detail}`);
|
|
118
|
+
}
|
|
119
|
+
issue.actions.forEach((action, index) => {
|
|
120
|
+
lines.push(`${index + 1}. ${action.label}`);
|
|
121
|
+
lines.push(` runs: ${action.command}`);
|
|
122
|
+
});
|
|
123
|
+
lines.push(`${issue.actions.length + 1}. Skip for now`);
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
function selectedActionFor(answer, issue) {
|
|
127
|
+
const selected = Number.parseInt(answer.trim(), 10);
|
|
128
|
+
if (!Number.isFinite(selected))
|
|
129
|
+
return null;
|
|
130
|
+
if (selected === issue.actions.length + 1)
|
|
131
|
+
return "skip";
|
|
132
|
+
if (selected < 1 || selected > issue.actions.length)
|
|
133
|
+
return null;
|
|
134
|
+
/* v8 ignore next -- defensive: bounds were checked above, but keep sparse arrays harmless @preserve */
|
|
135
|
+
return issue.actions[selected - 1] ?? null;
|
|
136
|
+
}
|
|
137
|
+
function isExecutableAction(action) {
|
|
138
|
+
if (action.executable === false)
|
|
139
|
+
return false;
|
|
140
|
+
return !action.command.includes("<");
|
|
141
|
+
}
|
|
142
|
+
async function runGuidedReadinessRepair(reports, deps) {
|
|
143
|
+
(0, runtime_1.emitNervesEvent)({
|
|
144
|
+
level: "info",
|
|
145
|
+
component: "daemon",
|
|
146
|
+
event: "daemon.readiness_repair_start",
|
|
147
|
+
message: "guided readiness repair started",
|
|
148
|
+
meta: { reportCount: reports.length },
|
|
149
|
+
});
|
|
150
|
+
let repairsAttempted = false;
|
|
151
|
+
for (const report of reports) {
|
|
152
|
+
if (report.ok || report.issues.length === 0)
|
|
153
|
+
continue;
|
|
154
|
+
for (const issue of report.issues) {
|
|
155
|
+
deps.writeStdout(renderReadinessIssue(issue));
|
|
156
|
+
if (!deps.promptInput) {
|
|
157
|
+
deps.writeStdout(`manual repair required for ${report.agent}; run one of the commands above.`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const answer = await deps.promptInput(`Choose [1-${issue.actions.length + 1}]: `);
|
|
161
|
+
const action = selectedActionFor(answer, issue);
|
|
162
|
+
if (action === "skip") {
|
|
163
|
+
deps.writeStdout(`repair skipped for ${report.agent}.`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!action) {
|
|
167
|
+
deps.writeStdout(`invalid repair choice for ${report.agent}; no repair attempted.`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!isExecutableAction(action)) {
|
|
171
|
+
deps.writeStdout(`manual step for ${report.agent}: ${action.command}`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (!deps.runRepairAction) {
|
|
175
|
+
deps.writeStdout(`repair runner unavailable for ${report.agent}; run \`${action.command}\` manually.`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
await deps.runRepairAction(report.agent, action, issue);
|
|
180
|
+
repairsAttempted = true;
|
|
181
|
+
deps.writeStdout(`repair attempted for ${report.agent}`);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
+
repairsAttempted = true;
|
|
186
|
+
deps.writeStdout(`repair error for ${report.agent}: ${message}`);
|
|
187
|
+
(0, runtime_1.emitNervesEvent)({
|
|
188
|
+
level: "error",
|
|
189
|
+
component: "daemon",
|
|
190
|
+
event: "daemon.readiness_repair_error",
|
|
191
|
+
message: "guided readiness repair action failed",
|
|
192
|
+
meta: { agent: report.agent, issue: issue.kind, action: action.kind, error: message },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
(0, runtime_1.emitNervesEvent)({
|
|
198
|
+
level: "info",
|
|
199
|
+
component: "daemon",
|
|
200
|
+
event: "daemon.readiness_repair_end",
|
|
201
|
+
message: "guided readiness repair completed",
|
|
202
|
+
meta: { repairsAttempted },
|
|
203
|
+
});
|
|
204
|
+
return { repairsAttempted };
|
|
205
|
+
}
|
package/dist/heart/identity.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.HARNESS_CANONICAL_REPO_URL = exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_VAULT_SERVER_URL = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = exports.PROVIDER_CREDENTIALS = void 0;
|
|
37
|
+
exports.defaultStableVaultEmail = defaultStableVaultEmail;
|
|
37
38
|
exports.resolveVaultConfig = resolveVaultConfig;
|
|
38
39
|
exports.normalizeSenses = normalizeSenses;
|
|
39
40
|
exports.buildDefaultAgentTemplate = buildDefaultAgentTemplate;
|
|
@@ -76,13 +77,20 @@ exports.DEFAULT_AGENT_PHRASES = {
|
|
|
76
77
|
followup: ["processing"],
|
|
77
78
|
};
|
|
78
79
|
exports.DEFAULT_VAULT_SERVER_URL = "https://vault.ouroboros.bot";
|
|
80
|
+
function defaultStableVaultEmail(agentName) {
|
|
81
|
+
const local = agentName
|
|
82
|
+
.toLowerCase()
|
|
83
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
84
|
+
.replace(/^-+|-+$/g, "") || "agent";
|
|
85
|
+
return `${local}@ouro.bot`;
|
|
86
|
+
}
|
|
79
87
|
/**
|
|
80
88
|
* Resolve the vault config for an agent, applying defaults.
|
|
81
89
|
* If vault is not configured in agent.json, returns default values.
|
|
82
90
|
*/
|
|
83
91
|
function resolveVaultConfig(agentName, config) {
|
|
84
92
|
return {
|
|
85
|
-
email: config?.email ??
|
|
93
|
+
email: config?.email ?? defaultStableVaultEmail(agentName),
|
|
86
94
|
serverUrl: config?.serverUrl ?? exports.DEFAULT_VAULT_SERVER_URL,
|
|
87
95
|
};
|
|
88
96
|
}
|
|
@@ -100,7 +100,7 @@ function vaultUnlockReplaceRecoverFix(agentName, nextStep = "Then run 'ouro up'
|
|
|
100
100
|
return [
|
|
101
101
|
`Run 'ouro vault unlock --agent ${agentName}' if you have the saved vault unlock secret.`,
|
|
102
102
|
`If this agent predates vault auth or nobody saved the unlock secret, run 'ouro vault replace --agent ${agentName}' to create a new empty vault, then re-auth/re-enter credentials.`,
|
|
103
|
-
`If you do have a local JSON credential export, run 'ouro vault recover --agent ${agentName} --from <json>' to create
|
|
103
|
+
`If you do have a local JSON credential export, run 'ouro vault recover --agent ${agentName} --from <json>' to create the agent vault and import it.`,
|
|
104
104
|
nextStep,
|
|
105
105
|
].join(" ");
|
|
106
106
|
}
|