@ouro.bot/cli 0.1.0-alpha.364 → 0.1.0-alpha.366
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 +15 -6
- package/changelog.json +17 -0
- package/dist/heart/auth/auth-flow.js +25 -110
- package/dist/heart/config.js +69 -55
- package/dist/heart/core.js +83 -33
- package/dist/heart/daemon/agent-config-check.js +60 -239
- package/dist/heart/daemon/agentic-repair.js +2 -1
- package/dist/heart/daemon/cli-defaults.js +15 -68
- package/dist/heart/daemon/cli-exec.js +249 -89
- package/dist/heart/daemon/cli-parse.js +71 -0
- package/dist/heart/daemon/daemon-cli.js +1 -2
- package/dist/heart/daemon/daemon-entry.js +1 -3
- package/dist/heart/daemon/doctor.js +9 -29
- package/dist/heart/daemon/interactive-repair.js +37 -2
- package/dist/heart/daemon/provider-discovery.js +32 -59
- package/dist/heart/hatch/hatch-flow.js +9 -12
- package/dist/heart/hatch/specialist-prompt.js +1 -1
- package/dist/heart/hatch/specialist-tools.js +21 -1
- package/dist/heart/migrate-config.js +15 -42
- package/dist/heart/provider-binding-resolver.js +6 -7
- package/dist/heart/provider-credentials.js +379 -0
- package/dist/heart/provider-failover.js +3 -11
- package/dist/heart/provider-ping.js +13 -3
- package/dist/heart/provider-state.js +8 -0
- package/dist/heart/provider-visibility.js +3 -6
- package/dist/heart/providers/anthropic-token.js +15 -47
- package/dist/heart/providers/anthropic.js +4 -9
- package/dist/heart/providers/azure.js +3 -3
- package/dist/heart/providers/github-copilot.js +2 -2
- package/dist/heart/providers/minimax-vlm.js +2 -2
- package/dist/heart/providers/minimax.js +1 -1
- package/dist/heart/providers/openai-codex.js +4 -9
- package/dist/repertoire/bitwarden-store.js +63 -17
- package/dist/repertoire/bundle-templates.js +2 -2
- package/dist/repertoire/credential-access.js +47 -467
- package/dist/repertoire/tools-attachments.js +5 -4
- package/dist/repertoire/tools-vault.js +10 -80
- package/dist/repertoire/vault-unlock.js +359 -0
- package/dist/senses/bluebubbles/client.js +39 -4
- package/dist/senses/pipeline.js +0 -1
- package/package.json +1 -1
- package/dist/heart/provider-credential-pool.js +0 -395
|
@@ -42,7 +42,7 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
42
42
|
const provider_models_1 = require("../provider-models");
|
|
43
43
|
const machine_identity_1 = require("../machine-identity");
|
|
44
44
|
const provider_state_1 = require("../provider-state");
|
|
45
|
-
const
|
|
45
|
+
const provider_credentials_1 = require("../provider-credentials");
|
|
46
46
|
function isAgentProvider(value) {
|
|
47
47
|
return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
|
|
48
48
|
}
|
|
@@ -52,20 +52,6 @@ function agentRootFor(agentName, bundlesRoot) {
|
|
|
52
52
|
function configPathFor(agentName, bundlesRoot) {
|
|
53
53
|
return path.join(agentRootFor(agentName, bundlesRoot), "agent.json");
|
|
54
54
|
}
|
|
55
|
-
function formatFacingList(facings) {
|
|
56
|
-
if (facings.length === 1)
|
|
57
|
-
return facings[0];
|
|
58
|
-
return `${facings.slice(0, -1).join(", ")} and ${facings[facings.length - 1]}`;
|
|
59
|
-
}
|
|
60
|
-
function selectedProviderMap(selectedProviders) {
|
|
61
|
-
const byProvider = new Map();
|
|
62
|
-
for (const selected of selectedProviders) {
|
|
63
|
-
const facings = byProvider.get(selected.provider) ?? [];
|
|
64
|
-
facings.push(selected.facing);
|
|
65
|
-
byProvider.set(selected.provider, facings);
|
|
66
|
-
}
|
|
67
|
-
return byProvider;
|
|
68
|
-
}
|
|
69
55
|
function resolveFacingProvider(parsed, facing, agentName, agentJsonPath) {
|
|
70
56
|
const raw = parsed[facing];
|
|
71
57
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -101,114 +87,6 @@ function resolveFacingProvider(parsed, facing, agentName, agentJsonPath) {
|
|
|
101
87
|
}
|
|
102
88
|
return { ok: true, selected: { facing, provider } };
|
|
103
89
|
}
|
|
104
|
-
function resolveSelectedProviders(parsed, agentName, agentJsonPath) {
|
|
105
|
-
const hasHumanFacing = parsed.humanFacing !== undefined;
|
|
106
|
-
const hasAgentFacing = parsed.agentFacing !== undefined;
|
|
107
|
-
if (!hasHumanFacing && !hasAgentFacing && typeof parsed.provider === "string") {
|
|
108
|
-
if (!isAgentProvider(parsed.provider)) {
|
|
109
|
-
return {
|
|
110
|
-
ok: false,
|
|
111
|
-
result: {
|
|
112
|
-
ok: false,
|
|
113
|
-
error: `Unknown provider '${parsed.provider}' in agent.json for '${agentName}'`,
|
|
114
|
-
fix: `Set provider to one of: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return { ok: true, selectedProviders: [{ facing: "provider", provider: parsed.provider }] };
|
|
119
|
-
}
|
|
120
|
-
const human = resolveFacingProvider(parsed, "humanFacing", agentName, agentJsonPath);
|
|
121
|
-
if (!human.ok)
|
|
122
|
-
return human;
|
|
123
|
-
const agent = resolveFacingProvider(parsed, "agentFacing", agentName, agentJsonPath);
|
|
124
|
-
if (!agent.ok)
|
|
125
|
-
return agent;
|
|
126
|
-
return { ok: true, selectedProviders: [human.selected, agent.selected] };
|
|
127
|
-
}
|
|
128
|
-
function readConfigCheckContext(agentName, bundlesRoot, secretsRoot) {
|
|
129
|
-
const agentJsonPath = path.join(bundlesRoot, `${agentName}.ouro`, "agent.json");
|
|
130
|
-
let raw;
|
|
131
|
-
try {
|
|
132
|
-
raw = fs.readFileSync(agentJsonPath, "utf-8");
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return {
|
|
136
|
-
ok: false,
|
|
137
|
-
result: {
|
|
138
|
-
ok: false,
|
|
139
|
-
error: `agent.json not found at ${agentJsonPath}`,
|
|
140
|
-
fix: `Run 'ouro hatch ${agentName}' to create the agent bundle, or verify that ${bundlesRoot}/${agentName}.ouro/ exists.`,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
let parsed;
|
|
145
|
-
try {
|
|
146
|
-
parsed = JSON.parse(raw);
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
return {
|
|
150
|
-
ok: false,
|
|
151
|
-
result: {
|
|
152
|
-
ok: false,
|
|
153
|
-
error: `agent.json at ${agentJsonPath} contains invalid JSON`,
|
|
154
|
-
fix: `Open ${agentJsonPath} and fix the JSON syntax.`,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
// Disabled agents are valid — they just won't run
|
|
159
|
-
if (parsed.enabled === false) {
|
|
160
|
-
return {
|
|
161
|
-
ok: true,
|
|
162
|
-
context: {
|
|
163
|
-
agentJsonPath,
|
|
164
|
-
secretsJsonPath: path.join(secretsRoot, agentName, "secrets.json"),
|
|
165
|
-
providers: {},
|
|
166
|
-
selectedProviders: [],
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
const selected = resolveSelectedProviders(parsed, agentName, agentJsonPath);
|
|
171
|
-
if (!selected.ok)
|
|
172
|
-
return selected;
|
|
173
|
-
const secretsJsonPath = path.join(secretsRoot, agentName, "secrets.json");
|
|
174
|
-
let secrets;
|
|
175
|
-
try {
|
|
176
|
-
const secretsRaw = fs.readFileSync(secretsJsonPath, "utf-8");
|
|
177
|
-
secrets = JSON.parse(secretsRaw);
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
const firstProvider = selected.selectedProviders[0].provider;
|
|
181
|
-
return {
|
|
182
|
-
ok: false,
|
|
183
|
-
result: {
|
|
184
|
-
ok: false,
|
|
185
|
-
error: `secrets.json not found or unreadable at ${secretsJsonPath}`,
|
|
186
|
-
fix: `Run 'ouro auth --agent ${agentName} --provider ${firstProvider}' to configure credentials, or create ${secretsJsonPath} with providers.${firstProvider} credentials.`,
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
const providers = secrets.providers;
|
|
191
|
-
if (!providers || typeof providers !== "object" || Array.isArray(providers)) {
|
|
192
|
-
const firstProvider = selected.selectedProviders[0].provider;
|
|
193
|
-
return {
|
|
194
|
-
ok: false,
|
|
195
|
-
result: {
|
|
196
|
-
ok: false,
|
|
197
|
-
error: `secrets.json for '${agentName}' is missing providers object`,
|
|
198
|
-
fix: `Run 'ouro auth --agent ${agentName} --provider ${firstProvider}' to configure credentials.`,
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
ok: true,
|
|
204
|
-
context: {
|
|
205
|
-
agentJsonPath,
|
|
206
|
-
secretsJsonPath,
|
|
207
|
-
providers,
|
|
208
|
-
selectedProviders: selected.selectedProviders,
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
90
|
function readAgentConfigForProviderState(agentName, bundlesRoot) {
|
|
213
91
|
const agentJsonPath = configPathFor(agentName, bundlesRoot);
|
|
214
92
|
let raw;
|
|
@@ -270,7 +148,7 @@ function bootstrapMissingProviderState(input) {
|
|
|
270
148
|
if (!inner.ok)
|
|
271
149
|
return { ok: false, error: inner.result.error, fix: inner.result.fix };
|
|
272
150
|
const now = new Date();
|
|
273
|
-
const homeDir = (0,
|
|
151
|
+
const homeDir = (0, provider_credentials_1.providerCredentialMachineHomeDir)(input.homeDir);
|
|
274
152
|
const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)({ homeDir, now: () => now });
|
|
275
153
|
const state = (0, provider_state_1.bootstrapProviderStateFromAgentConfig)({
|
|
276
154
|
machineId: machine.machineId,
|
|
@@ -290,7 +168,7 @@ function bootstrapMissingProviderState(input) {
|
|
|
290
168
|
});
|
|
291
169
|
return { ok: true, agentRoot, state };
|
|
292
170
|
}
|
|
293
|
-
function readOrBootstrapProviderStateForCheck(agentName, bundlesRoot,
|
|
171
|
+
function readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, deps = {}) {
|
|
294
172
|
const configResult = readAgentConfigForProviderState(agentName, bundlesRoot);
|
|
295
173
|
if (!configResult.ok)
|
|
296
174
|
return { ok: false, result: configResult.result };
|
|
@@ -314,61 +192,14 @@ function readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, secretsRoo
|
|
|
314
192
|
const bootstrap = bootstrapMissingProviderState({
|
|
315
193
|
agentName,
|
|
316
194
|
bundlesRoot,
|
|
317
|
-
secretsRoot,
|
|
318
195
|
parsed: configResult.parsed,
|
|
319
196
|
agentJsonPath: configResult.agentJsonPath,
|
|
197
|
+
homeDir: deps.homeDir,
|
|
320
198
|
});
|
|
321
199
|
if (!bootstrap.ok)
|
|
322
200
|
return { ok: false, result: bootstrap };
|
|
323
201
|
return { ok: true, disabled: false, agentRoot: bootstrap.agentRoot, state: bootstrap.state };
|
|
324
202
|
}
|
|
325
|
-
function validateSelectedProviderSecrets(agentName, context) {
|
|
326
|
-
for (const [provider, facings] of selectedProviderMap(context.selectedProviders)) {
|
|
327
|
-
const desc = identity_1.PROVIDER_CREDENTIALS[provider];
|
|
328
|
-
const providerSecrets = context.providers[provider];
|
|
329
|
-
const selectedBy = formatFacingList(facings);
|
|
330
|
-
if (!providerSecrets) {
|
|
331
|
-
return {
|
|
332
|
-
ok: false,
|
|
333
|
-
error: `secrets.json for '${agentName}' is missing providers.${provider} section selected by ${selectedBy}`,
|
|
334
|
-
fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to configure ${provider} credentials.`,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
// Azure special case: managed identity only needs endpoint + deployment.
|
|
338
|
-
if (provider === "azure") {
|
|
339
|
-
const hasEndpoint = typeof providerSecrets.endpoint === "string" && providerSecrets.endpoint.length > 0;
|
|
340
|
-
const hasDeployment = typeof providerSecrets.deployment === "string" && providerSecrets.deployment.length > 0;
|
|
341
|
-
const hasManagedId = typeof providerSecrets.managedIdentityClientId === "string" && providerSecrets.managedIdentityClientId.length > 0;
|
|
342
|
-
if (hasEndpoint && hasDeployment && hasManagedId) {
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
const missing = desc.required.filter((field) => {
|
|
347
|
-
const val = providerSecrets[field];
|
|
348
|
-
return typeof val !== "string" || val.length === 0;
|
|
349
|
-
});
|
|
350
|
-
if (missing.length > 0) {
|
|
351
|
-
return {
|
|
352
|
-
ok: false,
|
|
353
|
-
error: `secrets.json for '${agentName}' is missing required ${provider} credentials selected by ${selectedBy}: ${missing.join(", ")}`,
|
|
354
|
-
fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to set up ${provider} credentials, or add the missing fields to providers.${provider} in ${context.secretsJsonPath}.`,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return { ok: true };
|
|
359
|
-
}
|
|
360
|
-
function emitConfigValid(agentName, context, liveProviderCheck) {
|
|
361
|
-
(0, runtime_1.emitNervesEvent)({
|
|
362
|
-
component: "daemon",
|
|
363
|
-
event: "daemon.agent_config_valid",
|
|
364
|
-
message: "agent config validation passed",
|
|
365
|
-
meta: {
|
|
366
|
-
agent: agentName,
|
|
367
|
-
providers: [...selectedProviderMap(context.selectedProviders).keys()],
|
|
368
|
-
liveProviderCheck,
|
|
369
|
-
},
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
203
|
function providerCredentialConfig(record) {
|
|
373
204
|
return {
|
|
374
205
|
...record.credentials,
|
|
@@ -395,65 +226,38 @@ function writeLaneReadiness(input) {
|
|
|
395
226
|
};
|
|
396
227
|
(0, provider_state_1.writeProviderState)(input.agentRoot, input.state);
|
|
397
228
|
}
|
|
398
|
-
function
|
|
399
|
-
const secretsPath = path.join(secretsRoot, agentName, "secrets.json");
|
|
400
|
-
let parsed;
|
|
401
|
-
try {
|
|
402
|
-
parsed = JSON.parse(fs.readFileSync(secretsPath, "utf-8"));
|
|
403
|
-
}
|
|
404
|
-
catch {
|
|
405
|
-
return [];
|
|
406
|
-
}
|
|
407
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
408
|
-
return [];
|
|
409
|
-
const providers = parsed.providers;
|
|
410
|
-
if (!providers || typeof providers !== "object" || Array.isArray(providers))
|
|
411
|
-
return [];
|
|
412
|
-
const candidates = [];
|
|
413
|
-
for (const [providerKey, rawConfig] of Object.entries(providers)) {
|
|
414
|
-
if (!isAgentProvider(providerKey) || !rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(providerKey, rawConfig);
|
|
418
|
-
if (Object.keys(split.credentials).length === 0 && Object.keys(split.config).length === 0)
|
|
419
|
-
continue;
|
|
420
|
-
candidates.push({ provider: providerKey, credentials: split.credentials, config: split.config });
|
|
421
|
-
}
|
|
422
|
-
return candidates;
|
|
423
|
-
}
|
|
424
|
-
function readPoolWithLegacyMigration(agentName, homeDir, secretsRoot) {
|
|
425
|
-
const initial = (0, provider_credential_pool_1.readProviderCredentialPool)(homeDir);
|
|
426
|
-
if (initial.ok || initial.reason === "invalid")
|
|
427
|
-
return initial;
|
|
428
|
-
const candidates = legacyProviderCredentialCandidates(agentName, secretsRoot);
|
|
429
|
-
for (const candidate of candidates) {
|
|
430
|
-
(0, provider_credential_pool_1.upsertProviderCredential)({
|
|
431
|
-
homeDir,
|
|
432
|
-
provider: candidate.provider,
|
|
433
|
-
credentials: candidate.credentials,
|
|
434
|
-
config: candidate.config,
|
|
435
|
-
provenance: {
|
|
436
|
-
source: "legacy-agent-secrets",
|
|
437
|
-
contributedByAgent: agentName,
|
|
438
|
-
},
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
return candidates.length > 0 ? (0, provider_credential_pool_1.readProviderCredentialPool)(homeDir) : initial;
|
|
442
|
-
}
|
|
443
|
-
function missingCredentialResult(agentName, lane, provider, model, poolPath) {
|
|
229
|
+
function missingCredentialResult(agentName, lane, provider, model, credentialPath) {
|
|
444
230
|
return {
|
|
445
231
|
ok: false,
|
|
446
|
-
error: `${lane} provider ${provider} model ${model} has no credentials in
|
|
232
|
+
error: `${lane} provider ${provider} model ${model} has no credentials in ${agentName}'s vault at ${credentialPath}`,
|
|
447
233
|
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.`,
|
|
448
234
|
};
|
|
449
235
|
}
|
|
450
236
|
function invalidPoolResult(agentName, lane, provider, model, pool) {
|
|
237
|
+
if (pool.reason === "unavailable" && isVaultLockedError(pool.error)) {
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
error: `${lane} provider ${provider} model ${model} cannot read provider credentials because ${agentName}'s credential vault is locked on this machine.`,
|
|
241
|
+
fix: `Run 'ouro vault unlock --agent ${agentName}', then run 'ouro up' again.`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (pool.reason === "invalid") {
|
|
245
|
+
return {
|
|
246
|
+
ok: false,
|
|
247
|
+
error: `${lane} provider ${provider} model ${model} cannot read provider credentials from ${agentName}'s vault at ${pool.poolPath}: ${pool.error}`,
|
|
248
|
+
fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to rewrite this provider credential, then run 'ouro up' again.`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
451
251
|
return {
|
|
452
252
|
ok: false,
|
|
453
|
-
error: `${lane} provider ${provider} model ${model} cannot read
|
|
454
|
-
fix: `
|
|
253
|
+
error: `${lane} provider ${provider} model ${model} cannot read provider credentials from ${agentName}'s vault at ${pool.poolPath}: ${pool.error}`,
|
|
254
|
+
fix: `Run 'ouro vault unlock --agent ${agentName}', then run 'ouro up' again. If the credential is missing or stale after unlock, run 'ouro auth --agent ${agentName} --provider ${provider}'.`,
|
|
455
255
|
};
|
|
456
256
|
}
|
|
257
|
+
function isVaultLockedError(error) {
|
|
258
|
+
const normalized = error.toLowerCase();
|
|
259
|
+
return /(?:ouro )?credential vault is locked|vault(?: is)? locked/.test(normalized);
|
|
260
|
+
}
|
|
457
261
|
function failedPingResult(agentName, lane, provider, model, result) {
|
|
458
262
|
return {
|
|
459
263
|
ok: false,
|
|
@@ -465,30 +269,47 @@ function credentialRecordForLane(pool, provider) {
|
|
|
465
269
|
return pool.providers[provider];
|
|
466
270
|
}
|
|
467
271
|
/**
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* with an actionable fix message when something is missing.
|
|
272
|
+
* Structural validation only. Live provider credential validation belongs to
|
|
273
|
+
* checkAgentConfigWithProviderHealth(), which reads the agent vault and pings.
|
|
471
274
|
*/
|
|
472
|
-
function checkAgentConfig(agentName, bundlesRoot
|
|
473
|
-
const
|
|
474
|
-
if (!
|
|
475
|
-
return
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
275
|
+
function checkAgentConfig(agentName, bundlesRoot) {
|
|
276
|
+
const configResult = readAgentConfigForProviderState(agentName, bundlesRoot);
|
|
277
|
+
if (!configResult.ok)
|
|
278
|
+
return configResult.result;
|
|
279
|
+
if (configResult.disabled)
|
|
280
|
+
return { ok: true };
|
|
281
|
+
const outward = readFacingForBootstrap(configResult.parsed, "humanFacing", agentName, configResult.agentJsonPath);
|
|
282
|
+
if (!outward.ok)
|
|
283
|
+
return outward.result;
|
|
284
|
+
const inner = readFacingForBootstrap(configResult.parsed, "agentFacing", agentName, configResult.agentJsonPath);
|
|
285
|
+
if (!inner.ok)
|
|
286
|
+
return inner.result;
|
|
287
|
+
(0, runtime_1.emitNervesEvent)({
|
|
288
|
+
component: "daemon",
|
|
289
|
+
event: "daemon.agent_config_valid",
|
|
290
|
+
message: "agent config validation passed",
|
|
291
|
+
meta: {
|
|
292
|
+
agent: agentName,
|
|
293
|
+
providers: [...new Set([outward.provider, inner.provider])],
|
|
294
|
+
liveProviderCheck: false,
|
|
295
|
+
},
|
|
296
|
+
});
|
|
481
297
|
return { ok: true };
|
|
482
298
|
}
|
|
483
|
-
async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot,
|
|
484
|
-
const
|
|
299
|
+
async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, secretsRootOrDeps = {}, maybeDeps = {}) {
|
|
300
|
+
const deps = typeof secretsRootOrDeps === "string"
|
|
301
|
+
? {
|
|
302
|
+
...maybeDeps,
|
|
303
|
+
homeDir: maybeDeps.homeDir ?? (path.basename(secretsRootOrDeps) === ".agentsecrets" ? path.dirname(secretsRootOrDeps) : undefined),
|
|
304
|
+
}
|
|
305
|
+
: secretsRootOrDeps;
|
|
306
|
+
const stateResult = readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, deps);
|
|
485
307
|
if (!stateResult.ok)
|
|
486
308
|
return stateResult.result;
|
|
487
309
|
if (stateResult.disabled)
|
|
488
310
|
return { ok: true };
|
|
489
311
|
const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
|
|
490
|
-
const
|
|
491
|
-
const poolResult = readPoolWithLegacyMigration(agentName, homeDir, secretsRoot);
|
|
312
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName);
|
|
492
313
|
const pingGroups = new Map();
|
|
493
314
|
const lanes = ["outward", "inner"];
|
|
494
315
|
for (const lane of lanes) {
|
|
@@ -499,7 +320,7 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, secret
|
|
|
499
320
|
}
|
|
500
321
|
return invalidPoolResult(agentName, lane, binding.provider, binding.model, {
|
|
501
322
|
...poolResult,
|
|
502
|
-
reason:
|
|
323
|
+
reason: poolResult.reason,
|
|
503
324
|
});
|
|
504
325
|
}
|
|
505
326
|
const record = credentialRecordForLane(poolResult.pool, binding.provider);
|
|
@@ -48,6 +48,7 @@ function makeInteractiveRepairDeps(deps) {
|
|
|
48
48
|
writeStdout: deps.writeStdout,
|
|
49
49
|
/* v8 ignore next -- fallback no-op: tests always inject runAuthFlow; default is for production @preserve */
|
|
50
50
|
runAuthFlow: deps.runAuthFlow ?? (async () => undefined),
|
|
51
|
+
runVaultUnlock: deps.runVaultUnlock,
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
function discoveredProviderModel(provider) {
|
|
@@ -111,7 +112,7 @@ async function runAgenticRepair(degraded, deps) {
|
|
|
111
112
|
// Try to discover a working provider for agentic diagnosis
|
|
112
113
|
let discoveredProvider = null;
|
|
113
114
|
try {
|
|
114
|
-
discoveredProvider = await deps.discoverWorkingProvider();
|
|
115
|
+
discoveredProvider = await deps.discoverWorkingProvider(degraded[0].agent);
|
|
115
116
|
}
|
|
116
117
|
catch (error) {
|
|
117
118
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -42,7 +42,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
42
42
|
exports.defaultStartDaemonProcess = defaultStartDaemonProcess;
|
|
43
43
|
exports.readFirstBundleMetaVersion = readFirstBundleMetaVersion;
|
|
44
44
|
exports.defaultListDiscoveredAgents = defaultListDiscoveredAgents;
|
|
45
|
-
exports.discoverExistingCredentials = discoverExistingCredentials;
|
|
46
45
|
exports.defaultRunSerpentGuide = defaultRunSerpentGuide;
|
|
47
46
|
exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
|
|
48
47
|
const child_process_1 = require("child_process");
|
|
@@ -73,6 +72,7 @@ const auth_flow_1 = require("../auth/auth-flow");
|
|
|
73
72
|
const provider_models_1 = require("../provider-models");
|
|
74
73
|
const cli_parse_1 = require("./cli-parse");
|
|
75
74
|
const provider_discovery_1 = require("./provider-discovery");
|
|
75
|
+
const provider_credentials_1 = require("../provider-credentials");
|
|
76
76
|
// ── Default implementations ──
|
|
77
77
|
function defaultStartDaemonProcess(socketPath) {
|
|
78
78
|
const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
@@ -228,65 +228,10 @@ function defaultListDiscoveredAgents() {
|
|
|
228
228
|
readFileSync: fs.readFileSync,
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
|
-
// ── Credential discovery ──
|
|
232
|
-
function discoverExistingCredentials(secretsRoot) {
|
|
233
|
-
const found = [];
|
|
234
|
-
let entries;
|
|
235
|
-
try {
|
|
236
|
-
entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
return found;
|
|
240
|
-
}
|
|
241
|
-
for (const entry of entries) {
|
|
242
|
-
if (!entry.isDirectory())
|
|
243
|
-
continue;
|
|
244
|
-
const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
|
|
245
|
-
let raw;
|
|
246
|
-
try {
|
|
247
|
-
raw = fs.readFileSync(secretsPath, "utf-8");
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
let parsed;
|
|
253
|
-
try {
|
|
254
|
-
parsed = JSON.parse(raw);
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (!parsed.providers)
|
|
260
|
-
continue;
|
|
261
|
-
for (const [provName, provConfig] of Object.entries(parsed.providers)) {
|
|
262
|
-
const desc = identity_1.PROVIDER_CREDENTIALS[provName];
|
|
263
|
-
/* v8 ignore next -- guard: unknown provider names in stored secrets @preserve */
|
|
264
|
-
if (!desc)
|
|
265
|
-
continue;
|
|
266
|
-
const hasRequired = desc.required.every((key) => !!provConfig[key]);
|
|
267
|
-
if (hasRequired) {
|
|
268
|
-
const credentials = {};
|
|
269
|
-
for (const key of Object.keys(provConfig))
|
|
270
|
-
credentials[key] = provConfig[key];
|
|
271
|
-
found.push({ agentName: entry.name, provider: provName, credentials, providerConfig: { ...provConfig } });
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Deduplicate by provider+credential value (keep first seen)
|
|
276
|
-
const seen = new Set();
|
|
277
|
-
return found.filter((cred) => {
|
|
278
|
-
const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
|
|
279
|
-
if (seen.has(key))
|
|
280
|
-
return false;
|
|
281
|
-
seen.add(key);
|
|
282
|
-
return true;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
231
|
// ── Serpent guide (interactive hatch) ──
|
|
286
232
|
/* v8 ignore start -- integration: interactive terminal specialist session @preserve */
|
|
287
233
|
async function defaultRunSerpentGuide() {
|
|
288
234
|
const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
289
|
-
const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
290
235
|
const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
|
|
291
236
|
const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
292
237
|
const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
|
|
@@ -301,8 +246,7 @@ async function defaultRunSerpentGuide() {
|
|
|
301
246
|
let providerConfig = {};
|
|
302
247
|
const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
|
|
303
248
|
try {
|
|
304
|
-
const
|
|
305
|
-
const discovered = discoverExistingCredentials(secretsRoot);
|
|
249
|
+
const discovered = [];
|
|
306
250
|
const existingBundleCount = (0, specialist_orchestrator_1.listExistingBundles)((0, identity_1.getAgentBundlesRoot)()).length;
|
|
307
251
|
const hatchVerb = existingBundleCount > 0 ? "let's hatch a new agent." : "let's hatch your first agent.";
|
|
308
252
|
// Default models per provider (used when entering new credentials)
|
|
@@ -367,11 +311,20 @@ async function defaultRunSerpentGuide() {
|
|
|
367
311
|
}
|
|
368
312
|
coldRl.close();
|
|
369
313
|
process.stdout.write("\n");
|
|
314
|
+
const selectedCredentialPayload = { ...providerConfig, ...credentials };
|
|
315
|
+
const split = (0, provider_credentials_1.splitProviderCredentialFields)(providerRaw, selectedCredentialPayload);
|
|
316
|
+
(0, provider_credentials_1.cacheProviderCredentialRecords)("SerpentGuide", [
|
|
317
|
+
(0, provider_credentials_1.createProviderCredentialRecord)({
|
|
318
|
+
provider: providerRaw,
|
|
319
|
+
credentials: split.credentials,
|
|
320
|
+
config: split.config,
|
|
321
|
+
provenance: { source: "manual" },
|
|
322
|
+
}),
|
|
323
|
+
]);
|
|
370
324
|
// Phase 2: configure runtime for serpent guide
|
|
371
325
|
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "SerpentGuide.ouro");
|
|
372
326
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
373
|
-
|
|
374
|
-
// Suppress non-critical log noise during hatch (no secrets.json, etc.)
|
|
327
|
+
// Suppress non-critical log noise during hatch.
|
|
375
328
|
const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
376
329
|
const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
|
|
377
330
|
setRuntimeLogger(createLogger({ level: "error" }));
|
|
@@ -394,14 +347,9 @@ async function defaultRunSerpentGuide() {
|
|
|
394
347
|
agentFacing: { provider: providerRaw, model: resolvedModel },
|
|
395
348
|
phrases,
|
|
396
349
|
});
|
|
397
|
-
patchRuntimeConfig({
|
|
398
|
-
providers: {
|
|
399
|
-
[providerRaw]: { ...providerConfig, ...credentials },
|
|
400
|
-
},
|
|
401
|
-
});
|
|
402
350
|
// Ping-verify credentials before entering the serpent guide session
|
|
403
351
|
const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
|
|
404
|
-
const pingResult = await pingProvider(providerRaw,
|
|
352
|
+
const pingResult = await pingProvider(providerRaw, selectedCredentialPayload);
|
|
405
353
|
if (!pingResult.ok) {
|
|
406
354
|
process.stdout.write(`credentials didn't work (${pingResult.message}). run 'ouro hatch' to try again.\n`);
|
|
407
355
|
return null;
|
|
@@ -416,10 +364,9 @@ async function defaultRunSerpentGuide() {
|
|
|
416
364
|
const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
|
|
417
365
|
const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
|
|
418
366
|
tempDir,
|
|
419
|
-
credentials,
|
|
367
|
+
credentials: selectedCredentialPayload,
|
|
420
368
|
provider: providerRaw,
|
|
421
369
|
bundlesRoot,
|
|
422
|
-
secretsRoot: secretsRoot2,
|
|
423
370
|
animationWriter: (text) => process.stdout.write(text),
|
|
424
371
|
});
|
|
425
372
|
// Run the serpent guide session via runCliSession
|