@ouro.bot/cli 0.1.0-alpha.349 → 0.1.0-alpha.350
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,12 @@
|
|
|
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.350",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro up`, hatch, and doctor now use the same local provider-state and machine-credential readiness path: new agents get bootstrapped `state/providers.json`, startup checks ping the selected lane/provider/model with machine-wide credentials, readiness is persisted per lane, legacy per-agent secrets migrate into `~/.agentsecrets/providers.json`, and doctor checks provider-pool/state safety without exposing secrets."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.349",
|
|
6
12
|
"changes": [
|
|
@@ -39,9 +39,19 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const identity_1 = require("../identity");
|
|
41
41
|
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
+
const provider_models_1 = require("../provider-models");
|
|
43
|
+
const machine_identity_1 = require("../machine-identity");
|
|
44
|
+
const provider_state_1 = require("../provider-state");
|
|
45
|
+
const provider_credential_pool_1 = require("../provider-credential-pool");
|
|
42
46
|
function isAgentProvider(value) {
|
|
43
47
|
return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
|
|
44
48
|
}
|
|
49
|
+
function agentRootFor(agentName, bundlesRoot) {
|
|
50
|
+
return path.join(bundlesRoot, `${agentName}.ouro`);
|
|
51
|
+
}
|
|
52
|
+
function configPathFor(agentName, bundlesRoot) {
|
|
53
|
+
return path.join(agentRootFor(agentName, bundlesRoot), "agent.json");
|
|
54
|
+
}
|
|
45
55
|
function formatFacingList(facings) {
|
|
46
56
|
if (facings.length === 1)
|
|
47
57
|
return facings[0];
|
|
@@ -199,6 +209,119 @@ function readConfigCheckContext(agentName, bundlesRoot, secretsRoot) {
|
|
|
199
209
|
},
|
|
200
210
|
};
|
|
201
211
|
}
|
|
212
|
+
function readAgentConfigForProviderState(agentName, bundlesRoot) {
|
|
213
|
+
const agentJsonPath = configPathFor(agentName, bundlesRoot);
|
|
214
|
+
let raw;
|
|
215
|
+
try {
|
|
216
|
+
raw = fs.readFileSync(agentJsonPath, "utf-8");
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
result: {
|
|
222
|
+
ok: false,
|
|
223
|
+
error: `agent.json not found at ${agentJsonPath}`,
|
|
224
|
+
fix: `Run 'ouro hatch ${agentName}' to create the agent bundle, or verify that ${bundlesRoot}/${agentName}.ouro/ exists.`,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
let parsed;
|
|
229
|
+
try {
|
|
230
|
+
parsed = JSON.parse(raw);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
result: {
|
|
236
|
+
ok: false,
|
|
237
|
+
error: `agent.json at ${agentJsonPath} contains invalid JSON`,
|
|
238
|
+
fix: `Open ${agentJsonPath} and fix the JSON syntax.`,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (parsed.enabled === false) {
|
|
243
|
+
return { ok: true, disabled: true, agentJsonPath, parsed };
|
|
244
|
+
}
|
|
245
|
+
return { ok: true, disabled: false, agentJsonPath, parsed };
|
|
246
|
+
}
|
|
247
|
+
function readFacingForBootstrap(parsed, facing, agentName, agentJsonPath) {
|
|
248
|
+
const providerResult = resolveFacingProvider(parsed, facing, agentName, agentJsonPath);
|
|
249
|
+
if (!providerResult.ok) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
result: {
|
|
253
|
+
ok: false,
|
|
254
|
+
error: providerResult.result.error,
|
|
255
|
+
fix: `Run 'ouro use --agent ${agentName} --lane ${facing === "humanFacing" ? "outward" : "inner"} --provider <provider> --model <model>' to configure this machine's provider binding.`,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const raw = parsed[facing];
|
|
260
|
+
const model = typeof raw.model === "string" && raw.model.trim().length > 0
|
|
261
|
+
? raw.model.trim()
|
|
262
|
+
: (0, provider_models_1.getDefaultModelForProvider)(providerResult.selected.provider);
|
|
263
|
+
return { ok: true, provider: providerResult.selected.provider, model };
|
|
264
|
+
}
|
|
265
|
+
function bootstrapMissingProviderState(input) {
|
|
266
|
+
const outward = readFacingForBootstrap(input.parsed, "humanFacing", input.agentName, input.agentJsonPath);
|
|
267
|
+
if (!outward.ok)
|
|
268
|
+
return { ok: false, error: outward.result.error, fix: outward.result.fix };
|
|
269
|
+
const inner = readFacingForBootstrap(input.parsed, "agentFacing", input.agentName, input.agentJsonPath);
|
|
270
|
+
if (!inner.ok)
|
|
271
|
+
return { ok: false, error: inner.result.error, fix: inner.result.fix };
|
|
272
|
+
const now = new Date();
|
|
273
|
+
const homeDir = (0, provider_credential_pool_1.providerCredentialHomeDirFromSecretsRoot)(input.secretsRoot);
|
|
274
|
+
const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)({ homeDir, now: () => now });
|
|
275
|
+
const state = (0, provider_state_1.bootstrapProviderStateFromAgentConfig)({
|
|
276
|
+
machineId: machine.machineId,
|
|
277
|
+
now,
|
|
278
|
+
agentConfig: {
|
|
279
|
+
humanFacing: { provider: outward.provider, model: outward.model },
|
|
280
|
+
agentFacing: { provider: inner.provider, model: inner.model },
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
const agentRoot = agentRootFor(input.agentName, input.bundlesRoot);
|
|
284
|
+
(0, provider_state_1.writeProviderState)(agentRoot, state);
|
|
285
|
+
(0, runtime_1.emitNervesEvent)({
|
|
286
|
+
component: "daemon",
|
|
287
|
+
event: "daemon.provider_state_bootstrapped",
|
|
288
|
+
message: "bootstrapped local provider state from agent config",
|
|
289
|
+
meta: { agent: input.agentName, agentRoot },
|
|
290
|
+
});
|
|
291
|
+
return { ok: true, agentRoot, state };
|
|
292
|
+
}
|
|
293
|
+
function readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, secretsRoot) {
|
|
294
|
+
const configResult = readAgentConfigForProviderState(agentName, bundlesRoot);
|
|
295
|
+
if (!configResult.ok)
|
|
296
|
+
return { ok: false, result: configResult.result };
|
|
297
|
+
if (configResult.disabled)
|
|
298
|
+
return { ok: true, disabled: true };
|
|
299
|
+
const agentRoot = agentRootFor(agentName, bundlesRoot);
|
|
300
|
+
const stateResult = (0, provider_state_1.readProviderState)(agentRoot);
|
|
301
|
+
if (stateResult.ok) {
|
|
302
|
+
return { ok: true, disabled: false, agentRoot, state: stateResult.state };
|
|
303
|
+
}
|
|
304
|
+
if (stateResult.reason === "invalid") {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
result: {
|
|
308
|
+
ok: false,
|
|
309
|
+
error: `provider state for ${agentName} is invalid at ${stateResult.statePath}: ${stateResult.error}`,
|
|
310
|
+
fix: `Run 'ouro use --agent ${agentName} --lane outward --provider <provider> --model <model> --force' to rewrite this machine's provider binding.`,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const bootstrap = bootstrapMissingProviderState({
|
|
315
|
+
agentName,
|
|
316
|
+
bundlesRoot,
|
|
317
|
+
secretsRoot,
|
|
318
|
+
parsed: configResult.parsed,
|
|
319
|
+
agentJsonPath: configResult.agentJsonPath,
|
|
320
|
+
});
|
|
321
|
+
if (!bootstrap.ok)
|
|
322
|
+
return { ok: false, result: bootstrap };
|
|
323
|
+
return { ok: true, disabled: false, agentRoot: bootstrap.agentRoot, state: bootstrap.state };
|
|
324
|
+
}
|
|
202
325
|
function validateSelectedProviderSecrets(agentName, context) {
|
|
203
326
|
for (const [provider, facings] of selectedProviderMap(context.selectedProviders)) {
|
|
204
327
|
const desc = identity_1.PROVIDER_CREDENTIALS[provider];
|
|
@@ -246,16 +369,101 @@ function emitConfigValid(agentName, context, liveProviderCheck) {
|
|
|
246
369
|
},
|
|
247
370
|
});
|
|
248
371
|
}
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
372
|
+
function providerCredentialConfig(record) {
|
|
373
|
+
return {
|
|
374
|
+
...record.credentials,
|
|
375
|
+
...record.config,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function pingAttemptCount(result) {
|
|
379
|
+
if (Array.isArray(result.attempts))
|
|
380
|
+
return result.attempts.length;
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
function writeLaneReadiness(input) {
|
|
384
|
+
const binding = input.state.lanes[input.lane];
|
|
385
|
+
const checkedAt = new Date().toISOString();
|
|
386
|
+
input.state.updatedAt = checkedAt;
|
|
387
|
+
input.state.readiness[input.lane] = {
|
|
388
|
+
status: input.status,
|
|
389
|
+
provider: binding.provider,
|
|
390
|
+
model: binding.model,
|
|
391
|
+
checkedAt,
|
|
392
|
+
credentialRevision: input.credentialRevision,
|
|
393
|
+
...(input.error ? { error: input.error } : {}),
|
|
394
|
+
...(input.attempts !== undefined ? { attempts: input.attempts } : {}),
|
|
395
|
+
};
|
|
396
|
+
(0, provider_state_1.writeProviderState)(input.agentRoot, input.state);
|
|
397
|
+
}
|
|
398
|
+
function legacyProviderCredentialCandidates(agentName, secretsRoot) {
|
|
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) {
|
|
444
|
+
return {
|
|
445
|
+
ok: false,
|
|
446
|
+
error: `${lane} provider ${provider} model ${model} has no credentials in the machine provider pool at ${poolPath}`,
|
|
447
|
+
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
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function invalidPoolResult(agentName, lane, provider, model, pool) {
|
|
451
|
+
return {
|
|
452
|
+
ok: false,
|
|
453
|
+
error: `${lane} provider ${provider} model ${model} cannot read machine provider credentials at ${pool.poolPath}: ${pool.error}`,
|
|
454
|
+
fix: `Fix ${pool.poolPath}, then run 'ouro auth --agent ${agentName} --provider ${provider}' or 'ouro use --agent ${agentName} --lane ${lane} --provider <provider> --model <model> --force'.`,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function failedPingResult(agentName, lane, provider, model, result) {
|
|
253
458
|
return {
|
|
254
459
|
ok: false,
|
|
255
|
-
error:
|
|
256
|
-
fix:
|
|
460
|
+
error: `${lane} provider ${provider} model ${model} failed live check: ${result.message}`,
|
|
461
|
+
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.`,
|
|
257
462
|
};
|
|
258
463
|
}
|
|
464
|
+
function credentialRecordForLane(pool, provider) {
|
|
465
|
+
return pool.providers[provider];
|
|
466
|
+
}
|
|
259
467
|
/**
|
|
260
468
|
* Pre-spawn validation: ensures agent.json exists and required secrets are present.
|
|
261
469
|
* Returns `{ ok: true }` when the agent is ready to run, or a descriptive error
|
|
@@ -273,20 +481,81 @@ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
|
|
|
273
481
|
return { ok: true };
|
|
274
482
|
}
|
|
275
483
|
async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, secretsRoot, deps = {}) {
|
|
276
|
-
const
|
|
277
|
-
if (!
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (!structural.ok)
|
|
282
|
-
return structural;
|
|
484
|
+
const stateResult = readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, secretsRoot);
|
|
485
|
+
if (!stateResult.ok)
|
|
486
|
+
return stateResult.result;
|
|
487
|
+
if (stateResult.disabled)
|
|
488
|
+
return { ok: true };
|
|
283
489
|
const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
|
|
284
|
-
|
|
285
|
-
|
|
490
|
+
const homeDir = (0, provider_credential_pool_1.providerCredentialHomeDirFromSecretsRoot)(secretsRoot);
|
|
491
|
+
const poolResult = readPoolWithLegacyMigration(agentName, homeDir, secretsRoot);
|
|
492
|
+
const pingGroups = new Map();
|
|
493
|
+
const lanes = ["outward", "inner"];
|
|
494
|
+
for (const lane of lanes) {
|
|
495
|
+
const binding = stateResult.state.lanes[lane];
|
|
496
|
+
if (!poolResult.ok) {
|
|
497
|
+
if (poolResult.reason === "missing") {
|
|
498
|
+
return missingCredentialResult(agentName, lane, binding.provider, binding.model, poolResult.poolPath);
|
|
499
|
+
}
|
|
500
|
+
return invalidPoolResult(agentName, lane, binding.provider, binding.model, {
|
|
501
|
+
...poolResult,
|
|
502
|
+
reason: "invalid",
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
const record = credentialRecordForLane(poolResult.pool, binding.provider);
|
|
506
|
+
if (!record) {
|
|
507
|
+
return missingCredentialResult(agentName, lane, binding.provider, binding.model, poolResult.poolPath);
|
|
508
|
+
}
|
|
509
|
+
const key = `${binding.provider}\0${binding.model}\0${record.revision}`;
|
|
510
|
+
const group = pingGroups.get(key);
|
|
511
|
+
if (group) {
|
|
512
|
+
group.lanes.push(lane);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
pingGroups.set(key, {
|
|
516
|
+
provider: binding.provider,
|
|
517
|
+
model: binding.model,
|
|
518
|
+
record,
|
|
519
|
+
lanes: [lane],
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
for (const group of pingGroups.values()) {
|
|
524
|
+
const result = await ping(group.provider, providerCredentialConfig(group.record), { model: group.model });
|
|
286
525
|
if (!result.ok) {
|
|
287
|
-
|
|
526
|
+
for (const lane of group.lanes) {
|
|
527
|
+
writeLaneReadiness({
|
|
528
|
+
agentRoot: stateResult.agentRoot,
|
|
529
|
+
state: stateResult.state,
|
|
530
|
+
lane,
|
|
531
|
+
status: "failed",
|
|
532
|
+
credentialRevision: group.record.revision,
|
|
533
|
+
error: result.message,
|
|
534
|
+
attempts: pingAttemptCount(result),
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
|
|
538
|
+
}
|
|
539
|
+
for (const lane of group.lanes) {
|
|
540
|
+
writeLaneReadiness({
|
|
541
|
+
agentRoot: stateResult.agentRoot,
|
|
542
|
+
state: stateResult.state,
|
|
543
|
+
lane,
|
|
544
|
+
status: "ready",
|
|
545
|
+
credentialRevision: group.record.revision,
|
|
546
|
+
attempts: pingAttemptCount(result),
|
|
547
|
+
});
|
|
288
548
|
}
|
|
289
549
|
}
|
|
290
|
-
|
|
550
|
+
(0, runtime_1.emitNervesEvent)({
|
|
551
|
+
component: "daemon",
|
|
552
|
+
event: "daemon.agent_config_valid",
|
|
553
|
+
message: "agent config validation passed",
|
|
554
|
+
meta: {
|
|
555
|
+
agent: agentName,
|
|
556
|
+
providers: [...new Set([...pingGroups.values()].map((group) => group.provider))],
|
|
557
|
+
liveProviderCheck: true,
|
|
558
|
+
},
|
|
559
|
+
});
|
|
291
560
|
return { ok: true };
|
|
292
561
|
}
|
|
@@ -461,11 +461,7 @@ async function resolveHatchInput(command, deps) {
|
|
|
461
461
|
}
|
|
462
462
|
// ── Provider state CLI helpers ──
|
|
463
463
|
function providerCliHomeDir(deps) {
|
|
464
|
-
|
|
465
|
-
return os.homedir();
|
|
466
|
-
return path.basename(deps.secretsRoot) === ".agentsecrets"
|
|
467
|
-
? path.dirname(deps.secretsRoot)
|
|
468
|
-
: deps.secretsRoot;
|
|
464
|
+
return (0, provider_credential_pool_1.providerCredentialHomeDirFromSecretsRoot)(deps.secretsRoot);
|
|
469
465
|
}
|
|
470
466
|
function providerCliAgentRoot(command, deps) {
|
|
471
467
|
return path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
@@ -95,6 +95,19 @@ function readJsonObject(deps, filePath) {
|
|
|
95
95
|
return null;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
const SENSITIVE_CONFIG_KEYS = ["apiKey", "token", "secret", "password"];
|
|
99
|
+
function credentialKeyLeaks(raw) {
|
|
100
|
+
return SENSITIVE_CONFIG_KEYS.filter((key) => raw.includes(`"${key}"`));
|
|
101
|
+
}
|
|
102
|
+
function checkCredentialLeak(checks, label, raw) {
|
|
103
|
+
const found = credentialKeyLeaks(raw);
|
|
104
|
+
if (found.length > 0) {
|
|
105
|
+
checks.push({ label, status: "warn", detail: `contains credential-looking keys: ${found.join(", ")}` });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
checks.push({ label, status: "pass", detail: "no credential keys" });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
98
111
|
function checkAgents(deps) {
|
|
99
112
|
const checks = [];
|
|
100
113
|
if (!deps.existsSync(deps.bundlesRoot)) {
|
|
@@ -287,6 +300,23 @@ function checkHabits(deps) {
|
|
|
287
300
|
function checkSecurity(deps) {
|
|
288
301
|
const checks = [];
|
|
289
302
|
const agents = discoverAgents(deps);
|
|
303
|
+
const providerPoolPath = `${deps.secretsRoot}/providers.json`;
|
|
304
|
+
if (deps.existsSync(providerPoolPath)) {
|
|
305
|
+
const stat = deps.statSync(providerPoolPath);
|
|
306
|
+
const worldReadable = (stat.mode & 0o004) !== 0;
|
|
307
|
+
checks.push({
|
|
308
|
+
label: "machine provider credentials perms",
|
|
309
|
+
status: worldReadable ? "warn" : "pass",
|
|
310
|
+
detail: worldReadable ? "world-readable — consider chmod 600" : "not world-readable",
|
|
311
|
+
});
|
|
312
|
+
try {
|
|
313
|
+
JSON.parse(deps.readFileSync(providerPoolPath));
|
|
314
|
+
checks.push({ label: "machine provider credentials", status: "pass", detail: "readable JSON" });
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
checks.push({ label: "machine provider credentials", status: "fail", detail: "unparseable JSON" });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
290
320
|
for (const agentDir of agents) {
|
|
291
321
|
const agentName = agentDir.replace(/\.ouro$/, "");
|
|
292
322
|
const secretsPath = `${deps.secretsRoot}/${agentName}/secrets.json`;
|
|
@@ -308,8 +338,7 @@ function checkSecurity(deps) {
|
|
|
308
338
|
if (deps.existsSync(configPath)) {
|
|
309
339
|
try {
|
|
310
340
|
const raw = deps.readFileSync(configPath);
|
|
311
|
-
const
|
|
312
|
-
const found = sensitiveKeys.filter((key) => raw.includes(`"${key}"`));
|
|
341
|
+
const found = credentialKeyLeaks(raw);
|
|
313
342
|
if (found.length > 0) {
|
|
314
343
|
checks.push({ label: `${agentDir} credential leak`, status: "warn", detail: `agent.json contains keys: ${found.join(", ")}` });
|
|
315
344
|
}
|
|
@@ -321,6 +350,15 @@ function checkSecurity(deps) {
|
|
|
321
350
|
checks.push({ label: `${agentDir} credential leak`, status: "fail", detail: "could not read agent.json" });
|
|
322
351
|
}
|
|
323
352
|
}
|
|
353
|
+
const providerStatePath = `${deps.bundlesRoot}/${agentDir}/state/providers.json`;
|
|
354
|
+
if (deps.existsSync(providerStatePath)) {
|
|
355
|
+
try {
|
|
356
|
+
checkCredentialLeak(checks, `${agentDir} state/providers.json credential leak`, deps.readFileSync(providerStatePath));
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
checks.push({ label: `${agentDir} state/providers.json credential leak`, status: "fail", detail: "could not read state/providers.json" });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
324
362
|
}
|
|
325
363
|
if (checks.length === 0) {
|
|
326
364
|
checks.push({ label: "security", status: "warn", detail: "no agents found" });
|
|
@@ -44,6 +44,9 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
44
44
|
const auth_flow_1 = require("../auth/auth-flow");
|
|
45
45
|
const provider_models_1 = require("../provider-models");
|
|
46
46
|
const habit_parser_1 = require("../habits/habit-parser");
|
|
47
|
+
const machine_identity_1 = require("../machine-identity");
|
|
48
|
+
const provider_credential_pool_1 = require("../provider-credential-pool");
|
|
49
|
+
const provider_state_1 = require("../provider-state");
|
|
47
50
|
const hatch_specialist_1 = require("./hatch-specialist");
|
|
48
51
|
function requiredCredentialKeys(provider) {
|
|
49
52
|
return identity_1.PROVIDER_CREDENTIALS[provider].required;
|
|
@@ -132,6 +135,22 @@ function writeHatchlingAgentConfig(bundleRoot, input) {
|
|
|
132
135
|
template.enabled = true;
|
|
133
136
|
fs.writeFileSync(path.join(bundleRoot, "agent.json"), `${JSON.stringify(template, null, 2)}\n`, "utf-8");
|
|
134
137
|
}
|
|
138
|
+
function writeHatchlingProviderState(bundleRoot, input, secretsRoot, now) {
|
|
139
|
+
const model = (0, provider_models_1.getDefaultModelForProvider)(input.provider);
|
|
140
|
+
const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)({
|
|
141
|
+
homeDir: (0, provider_credential_pool_1.providerCredentialHomeDirFromSecretsRoot)(secretsRoot),
|
|
142
|
+
now: () => now,
|
|
143
|
+
});
|
|
144
|
+
const state = (0, provider_state_1.bootstrapProviderStateFromAgentConfig)({
|
|
145
|
+
machineId: machine.machineId,
|
|
146
|
+
now,
|
|
147
|
+
agentConfig: {
|
|
148
|
+
humanFacing: { provider: input.provider, model },
|
|
149
|
+
agentFacing: { provider: input.provider, model },
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
(0, provider_state_1.writeProviderState)(bundleRoot, state);
|
|
153
|
+
}
|
|
135
154
|
async function runHatchFlow(input, deps = {}) {
|
|
136
155
|
(0, runtime_1.emitNervesEvent)({
|
|
137
156
|
component: "daemon",
|
|
@@ -170,6 +189,7 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
170
189
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
171
190
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
172
191
|
writeHatchlingAgentConfig(bundleRoot, input);
|
|
192
|
+
writeHatchlingProviderState(bundleRoot, input, secretsRoot, now);
|
|
173
193
|
writeDiaryScaffold(bundleRoot);
|
|
174
194
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
175
195
|
writeHeartbeatHabit(bundleRoot, now);
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.splitProviderCredentialFields = splitProviderCredentialFields;
|
|
37
37
|
exports.getProviderCredentialPoolPath = getProviderCredentialPoolPath;
|
|
38
|
+
exports.providerCredentialHomeDirFromSecretsRoot = providerCredentialHomeDirFromSecretsRoot;
|
|
38
39
|
exports.validateProviderCredentialPool = validateProviderCredentialPool;
|
|
39
40
|
exports.readProviderCredentialPool = readProviderCredentialPool;
|
|
40
41
|
exports.writeProviderCredentialPool = writeProviderCredentialPool;
|
|
@@ -130,6 +131,13 @@ function makeRevision() {
|
|
|
130
131
|
function getProviderCredentialPoolPath(homeDir = os.homedir()) {
|
|
131
132
|
return path.join(homeDir, ".agentsecrets", "providers.json");
|
|
132
133
|
}
|
|
134
|
+
function providerCredentialHomeDirFromSecretsRoot(secretsRoot) {
|
|
135
|
+
if (!secretsRoot)
|
|
136
|
+
return os.homedir();
|
|
137
|
+
return path.basename(secretsRoot) === ".agentsecrets"
|
|
138
|
+
? path.dirname(secretsRoot)
|
|
139
|
+
: secretsRoot;
|
|
140
|
+
}
|
|
133
141
|
function validateProviderCredentialPool(value) {
|
|
134
142
|
if (!isRecord(value)) {
|
|
135
143
|
throw new Error("provider credential pool must be an object");
|