@ouro.bot/cli 0.1.0-alpha.364 → 0.1.0-alpha.365
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 +9 -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 +41 -238
- package/dist/heart/daemon/agentic-repair.js +1 -1
- package/dist/heart/daemon/cli-defaults.js +15 -68
- package/dist/heart/daemon/cli-exec.js +246 -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/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
|
@@ -62,10 +62,13 @@ const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
|
62
62
|
const agent_config_v2_1 = require("./hooks/agent-config-v2");
|
|
63
63
|
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
64
64
|
const tasks_1 = require("../../repertoire/tasks");
|
|
65
|
+
const credential_access_1 = require("../../repertoire/credential-access");
|
|
66
|
+
const vault_setup_1 = require("../../repertoire/vault-setup");
|
|
67
|
+
const vault_unlock_1 = require("../../repertoire/vault-unlock");
|
|
65
68
|
const thoughts_1 = require("./thoughts");
|
|
66
69
|
const launchd_1 = require("./launchd");
|
|
67
70
|
const auth_flow_1 = require("../auth/auth-flow");
|
|
68
|
-
const
|
|
71
|
+
const provider_credentials_1 = require("../provider-credentials");
|
|
69
72
|
const provider_binding_resolver_1 = require("../provider-binding-resolver");
|
|
70
73
|
const provider_state_1 = require("../provider-state");
|
|
71
74
|
const machine_identity_1 = require("../machine-identity");
|
|
@@ -94,11 +97,10 @@ const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
|
|
|
94
97
|
async function checkAlreadyRunningAgentProviders(deps) {
|
|
95
98
|
const agents = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
|
|
96
99
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
97
|
-
const secretsRoot = deps.secretsRoot ?? path.join(os.homedir(), ".agentsecrets");
|
|
98
100
|
const degraded = [];
|
|
99
101
|
for (const agent of agents) {
|
|
100
102
|
try {
|
|
101
|
-
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot
|
|
103
|
+
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
|
|
102
104
|
if (result.ok)
|
|
103
105
|
continue;
|
|
104
106
|
const errorReason = result.error ?? "agent provider health check failed";
|
|
@@ -132,8 +134,7 @@ async function checkAlreadyRunningAgentProviders(deps) {
|
|
|
132
134
|
}
|
|
133
135
|
async function checkProviderHealthBeforeChat(agentName, deps) {
|
|
134
136
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
135
|
-
const
|
|
136
|
-
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot, secretsRoot);
|
|
137
|
+
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot);
|
|
137
138
|
if (!result.ok) {
|
|
138
139
|
const output = `${result.error}\n${result.fix ? ` fix: ${result.fix}` : ""}`;
|
|
139
140
|
deps.writeStdout(output);
|
|
@@ -546,7 +547,7 @@ async function resolveHatchInput(command, deps) {
|
|
|
546
547
|
}
|
|
547
548
|
// ── Provider state CLI helpers ──
|
|
548
549
|
function providerCliHomeDir(deps) {
|
|
549
|
-
return (0,
|
|
550
|
+
return (0, provider_credentials_1.providerCredentialMachineHomeDir)(deps.homeDir);
|
|
550
551
|
}
|
|
551
552
|
function providerCliAgentRoot(command, deps) {
|
|
552
553
|
return path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
@@ -554,6 +555,144 @@ function providerCliAgentRoot(command, deps) {
|
|
|
554
555
|
function providerCliNow(deps) {
|
|
555
556
|
return new Date((deps.now ?? Date.now)());
|
|
556
557
|
}
|
|
558
|
+
function writeAgentVaultConfig(agentName, configPath, config, vault) {
|
|
559
|
+
const nextConfig = {
|
|
560
|
+
...config,
|
|
561
|
+
vault: {
|
|
562
|
+
email: vault.email,
|
|
563
|
+
serverUrl: vault.serverUrl,
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
567
|
+
(0, runtime_1.emitNervesEvent)({
|
|
568
|
+
component: "daemon",
|
|
569
|
+
event: "daemon.vault_config_written",
|
|
570
|
+
message: "wrote credential vault locator to agent config",
|
|
571
|
+
meta: { agentName, configPath, email: vault.email, serverUrl: vault.serverUrl },
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
async function executeVaultUnlock(command, deps) {
|
|
575
|
+
if (command.agent === "SerpentGuide") {
|
|
576
|
+
throw new Error("SerpentGuide does not have a persistent credential vault. Hatch bootstrap uses selected provider credentials in memory only.");
|
|
577
|
+
}
|
|
578
|
+
const prompt = deps.promptInput;
|
|
579
|
+
if (!prompt)
|
|
580
|
+
throw new Error("vault unlock requires an interactive prompt to capture the vault unlock secret");
|
|
581
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
582
|
+
const vault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
583
|
+
const unlockSecret = await prompt(`Ouro vault unlock secret for ${vault.email}: `);
|
|
584
|
+
const store = (0, vault_unlock_1.storeVaultUnlockSecret)({
|
|
585
|
+
agentName: command.agent,
|
|
586
|
+
email: vault.email,
|
|
587
|
+
serverUrl: vault.serverUrl,
|
|
588
|
+
}, unlockSecret, { homeDir: deps.homeDir, store: command.store });
|
|
589
|
+
(0, credential_access_1.resetCredentialStore)();
|
|
590
|
+
await (0, credential_access_1.getCredentialStore)(command.agent).get("__ouro_vault_probe__");
|
|
591
|
+
const message = [
|
|
592
|
+
`vault unlocked for ${command.agent} on this machine`,
|
|
593
|
+
`vault: ${vault.email} at ${vault.serverUrl}`,
|
|
594
|
+
`local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
|
|
595
|
+
].join("\n");
|
|
596
|
+
deps.writeStdout(message);
|
|
597
|
+
return message;
|
|
598
|
+
}
|
|
599
|
+
async function executeVaultCreate(command, deps) {
|
|
600
|
+
if (command.agent === "SerpentGuide") {
|
|
601
|
+
throw new Error("SerpentGuide does not have a persistent credential vault. Create a vault for the hatchling agent, not SerpentGuide.");
|
|
602
|
+
}
|
|
603
|
+
const prompt = deps.promptInput;
|
|
604
|
+
const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
605
|
+
const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
606
|
+
const email = command.email ?? config.vault?.email ?? (prompt ? (await prompt("Ouro credential vault email: ")).trim() : "");
|
|
607
|
+
if (!email) {
|
|
608
|
+
throw new Error("vault create requires --email <email> when the agent bundle has no vault.email");
|
|
609
|
+
}
|
|
610
|
+
const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
|
|
611
|
+
const requestedUnlockSecret = command.generateUnlockSecret
|
|
612
|
+
? ""
|
|
613
|
+
: prompt
|
|
614
|
+
? (await prompt(`Choose Ouro vault unlock secret for ${email}: `)).trim()
|
|
615
|
+
: "";
|
|
616
|
+
if (!requestedUnlockSecret && !command.generateUnlockSecret) {
|
|
617
|
+
throw new Error("vault create requires an unlock secret. Re-run with an interactive terminal or pass --generate-unlock-secret.");
|
|
618
|
+
}
|
|
619
|
+
const unlockSecret = requestedUnlockSecret || (0, crypto_1.randomBytes)(32).toString("base64");
|
|
620
|
+
const result = await (0, vault_setup_1.createVaultAccount)("Ouro credential vault", serverUrl, email, unlockSecret);
|
|
621
|
+
if (!result.success) {
|
|
622
|
+
const message = [
|
|
623
|
+
`vault create failed for ${command.agent}: ${result.error}`,
|
|
624
|
+
"",
|
|
625
|
+
"If this vault account already exists, run:",
|
|
626
|
+
` ouro vault unlock --agent ${command.agent}`,
|
|
627
|
+
].join("\n");
|
|
628
|
+
deps.writeStdout(message);
|
|
629
|
+
return message;
|
|
630
|
+
}
|
|
631
|
+
writeAgentVaultConfig(command.agent, configPath, config, { email, serverUrl });
|
|
632
|
+
const store = (0, vault_unlock_1.storeVaultUnlockSecret)({
|
|
633
|
+
agentName: command.agent,
|
|
634
|
+
email,
|
|
635
|
+
serverUrl,
|
|
636
|
+
}, unlockSecret, { homeDir: deps.homeDir, store: command.store });
|
|
637
|
+
(0, credential_access_1.resetCredentialStore)();
|
|
638
|
+
await (0, credential_access_1.getCredentialStore)(command.agent).get("__ouro_vault_probe__");
|
|
639
|
+
const message = [
|
|
640
|
+
`vault created for ${command.agent}`,
|
|
641
|
+
`vault: ${email} at ${serverUrl}`,
|
|
642
|
+
`local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
|
|
643
|
+
"Provider credentials will be stored in this agent's Ouro credential vault.",
|
|
644
|
+
...(command.generateUnlockSecret
|
|
645
|
+
? [
|
|
646
|
+
"",
|
|
647
|
+
`vault unlock secret: ${unlockSecret}`,
|
|
648
|
+
"",
|
|
649
|
+
"Store this in the operator password manager now. Another machine cannot unlock the vault without it.",
|
|
650
|
+
]
|
|
651
|
+
: ["Store the vault unlock secret in the operator password manager. Another machine will need it once."]),
|
|
652
|
+
].join("\n");
|
|
653
|
+
deps.writeStdout(message);
|
|
654
|
+
return message;
|
|
655
|
+
}
|
|
656
|
+
async function executeVaultStatus(command, deps) {
|
|
657
|
+
if (command.agent === "SerpentGuide") {
|
|
658
|
+
const message = "SerpentGuide has no persistent credential vault. Hatch bootstrap uses selected provider credentials in memory only.";
|
|
659
|
+
deps.writeStdout(message);
|
|
660
|
+
return message;
|
|
661
|
+
}
|
|
662
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
663
|
+
const vault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
664
|
+
const status = (0, vault_unlock_1.getVaultUnlockStatus)({
|
|
665
|
+
agentName: command.agent,
|
|
666
|
+
email: vault.email,
|
|
667
|
+
serverUrl: vault.serverUrl,
|
|
668
|
+
}, { homeDir: deps.homeDir, store: command.store });
|
|
669
|
+
const lines = [
|
|
670
|
+
`agent: ${command.agent}`,
|
|
671
|
+
`vault: ${vault.email} at ${vault.serverUrl}`,
|
|
672
|
+
`local unlock store: ${status.store ? `${status.store.kind}${status.store.secure ? "" : " (explicit plaintext fallback)"}` : "unavailable"}`,
|
|
673
|
+
`local unlock: ${status.stored ? "available" : "missing"}`,
|
|
674
|
+
];
|
|
675
|
+
if (status.stored) {
|
|
676
|
+
const pool = await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
677
|
+
if (pool.ok) {
|
|
678
|
+
const summary = (0, provider_credentials_1.summarizeProviderCredentialPool)(pool.pool);
|
|
679
|
+
lines.push(`provider credentials: ${summary.providers.length === 0 ? "none stored" : ""}`);
|
|
680
|
+
for (const provider of summary.providers) {
|
|
681
|
+
lines.push(` ${provider.provider}: credential fields ${provider.credentialFields.join(", ") || "none"}, config fields ${provider.configFields.join(", ") || "none"}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
lines.push(`provider credentials: unavailable (${pool.error})`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
lines.push("");
|
|
690
|
+
lines.push(status.fix);
|
|
691
|
+
}
|
|
692
|
+
const message = lines.join("\n");
|
|
693
|
+
deps.writeStdout(message);
|
|
694
|
+
return message;
|
|
695
|
+
}
|
|
557
696
|
function readOrBootstrapProviderState(agentName, deps) {
|
|
558
697
|
const agentRoot = providerCliAgentRoot({ agent: agentName }, deps);
|
|
559
698
|
const readResult = (0, provider_state_1.readProviderState)(agentRoot);
|
|
@@ -604,56 +743,15 @@ function pingAttemptCount(result) {
|
|
|
604
743
|
return result.attempts.length;
|
|
605
744
|
return undefined;
|
|
606
745
|
}
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
const legacyCandidates = (0, provider_credential_pool_1.readLegacyAgentProviderCredentials)({
|
|
610
|
-
homeDir: providerCliHomeDir(deps),
|
|
611
|
-
agentName: agent,
|
|
612
|
-
});
|
|
613
|
-
const candidate = legacyCandidates.find((entry) => entry.provider === provider);
|
|
614
|
-
if (candidate)
|
|
615
|
-
return { credentials: candidate.credentials, config: candidate.config };
|
|
616
|
-
}
|
|
617
|
-
catch {
|
|
618
|
-
// Fall through to the injected/secretsRoot-aware legacy reader below.
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agent, { secretsRoot: deps.secretsRoot });
|
|
622
|
-
const providerSecrets = secrets.providers[provider];
|
|
623
|
-
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, providerSecrets);
|
|
624
|
-
if (Object.keys(split.credentials).length === 0 && Object.keys(split.config).length === 0)
|
|
625
|
-
return null;
|
|
626
|
-
return split;
|
|
627
|
-
}
|
|
628
|
-
catch {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
function readProviderCredentialRecord(agent, provider, deps) {
|
|
633
|
-
const homeDir = providerCliHomeDir(deps);
|
|
634
|
-
const poolResult = (0, provider_credential_pool_1.readProviderCredentialPool)(homeDir);
|
|
746
|
+
async function readProviderCredentialRecord(agent, provider, _deps) {
|
|
747
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agent);
|
|
635
748
|
if (poolResult.ok) {
|
|
636
749
|
const existing = poolResult.pool.providers[provider];
|
|
637
750
|
if (existing)
|
|
638
751
|
return { ok: true, record: existing };
|
|
639
752
|
}
|
|
640
|
-
else if (poolResult.reason === "invalid") {
|
|
641
|
-
return { ok: false, reason:
|
|
642
|
-
}
|
|
643
|
-
const legacy = providerCliLegacyRecord(agent, provider, deps);
|
|
644
|
-
if (legacy) {
|
|
645
|
-
const record = (0, provider_credential_pool_1.upsertProviderCredential)({
|
|
646
|
-
homeDir,
|
|
647
|
-
provider,
|
|
648
|
-
credentials: legacy.credentials,
|
|
649
|
-
config: legacy.config,
|
|
650
|
-
provenance: {
|
|
651
|
-
source: "legacy-agent-secrets",
|
|
652
|
-
contributedByAgent: agent,
|
|
653
|
-
},
|
|
654
|
-
now: providerCliNow(deps),
|
|
655
|
-
});
|
|
656
|
-
return { ok: true, record };
|
|
753
|
+
else if (poolResult.reason === "invalid" || poolResult.reason === "unavailable") {
|
|
754
|
+
return { ok: false, reason: poolResult.reason, poolPath: poolResult.poolPath, error: poolResult.error };
|
|
657
755
|
}
|
|
658
756
|
return {
|
|
659
757
|
ok: false,
|
|
@@ -703,7 +801,7 @@ async function executeProviderUse(command, deps, options = {}) {
|
|
|
703
801
|
return message;
|
|
704
802
|
};
|
|
705
803
|
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
706
|
-
const credential = readProviderCredentialRecord(command.agent, command.provider, deps);
|
|
804
|
+
const credential = await readProviderCredentialRecord(command.agent, command.provider, deps);
|
|
707
805
|
if (!credential.ok) {
|
|
708
806
|
if (!command.force) {
|
|
709
807
|
const message = [
|
|
@@ -763,7 +861,7 @@ async function executeProviderUse(command, deps, options = {}) {
|
|
|
763
861
|
async function executeProviderCheck(command, deps) {
|
|
764
862
|
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
765
863
|
const binding = state.lanes[command.lane];
|
|
766
|
-
const credential = readProviderCredentialRecord(command.agent, binding.provider, deps);
|
|
864
|
+
const credential = await readProviderCredentialRecord(command.agent, binding.provider, deps);
|
|
767
865
|
if (!credential.ok) {
|
|
768
866
|
const message = [
|
|
769
867
|
`${command.agent} ${command.lane} ${binding.provider} / ${binding.model}: unknown (${credential.error})`,
|
|
@@ -803,18 +901,18 @@ async function executeProviderCheck(command, deps) {
|
|
|
803
901
|
}
|
|
804
902
|
function renderProviderCredentialLine(credential) {
|
|
805
903
|
if (credential.status === "present") {
|
|
806
|
-
const contributor = credential.contributedByAgent ? ` by ${credential.contributedByAgent}` : "";
|
|
807
904
|
const credentialFields = credential.credentialFields.length > 0 ? ` credentials: ${credential.credentialFields.join(", ")}` : " credentials: none";
|
|
808
905
|
const configFields = credential.configFields.length > 0 ? ` config: ${credential.configFields.join(", ")}` : " config: none";
|
|
809
|
-
return `credentials: present (${credential.source}
|
|
906
|
+
return `credentials: present in vault (${credential.source}; ${credential.revision};${credentialFields};${configFields})`;
|
|
810
907
|
}
|
|
811
908
|
if (credential.status === "invalid-pool") {
|
|
812
|
-
return `credentials:
|
|
909
|
+
return `credentials: vault unavailable (${credential.error}); repair: ${credential.repair.command}`;
|
|
813
910
|
}
|
|
814
911
|
return `credentials: missing; repair: ${credential.repair.command}`;
|
|
815
912
|
}
|
|
816
|
-
function executeProviderStatus(command, deps) {
|
|
913
|
+
async function executeProviderStatus(command, deps) {
|
|
817
914
|
const agentRoot = providerCliAgentRoot(command, deps);
|
|
915
|
+
await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
818
916
|
const homeDir = providerCliHomeDir(deps);
|
|
819
917
|
const lines = [`provider status: ${command.agent}`];
|
|
820
918
|
for (const lane of ["outward", "inner"]) {
|
|
@@ -841,6 +939,45 @@ function executeProviderStatus(command, deps) {
|
|
|
841
939
|
deps.writeStdout(message);
|
|
842
940
|
return message;
|
|
843
941
|
}
|
|
942
|
+
async function executeProviderRefresh(command, deps) {
|
|
943
|
+
if (command.agent === "SerpentGuide") {
|
|
944
|
+
const message = "SerpentGuide has no persistent provider credentials to refresh. Hatch bootstrap uses selected credentials in memory only.";
|
|
945
|
+
deps.writeStdout(message);
|
|
946
|
+
return message;
|
|
947
|
+
}
|
|
948
|
+
const pool = await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
949
|
+
const lines = [];
|
|
950
|
+
if (pool.ok) {
|
|
951
|
+
const summary = (0, provider_credentials_1.summarizeProviderCredentialPool)(pool.pool);
|
|
952
|
+
lines.push(`refreshed provider credential snapshot for ${command.agent}`);
|
|
953
|
+
lines.push(`providers: ${summary.providers.map((provider) => provider.provider).join(", ") || "none"}`);
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
lines.push(`provider credential refresh failed for ${command.agent}: ${pool.error}`);
|
|
957
|
+
lines.push(`Run \`ouro vault unlock --agent ${command.agent}\`, then retry.`);
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
961
|
+
if (alive) {
|
|
962
|
+
const response = await deps.sendCommand(deps.socketPath, { kind: "agent.restart", agent: command.agent });
|
|
963
|
+
if (response.ok) {
|
|
964
|
+
lines.push(`restarted ${command.agent} so the running agent reloads credentials`);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
lines.push(`daemon restart skipped: ${response.error ?? response.message ?? "unknown daemon error"}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
lines.push("daemon is not running; the next start will load the refreshed snapshot");
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
lines.push(`daemon restart skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
976
|
+
}
|
|
977
|
+
const message = lines.join("\n");
|
|
978
|
+
deps.writeStdout(message);
|
|
979
|
+
return message;
|
|
980
|
+
}
|
|
844
981
|
async function executeLegacyAuthSwitch(command, deps) {
|
|
845
982
|
const { state } = readOrBootstrapProviderState(command.agent, deps);
|
|
846
983
|
const lanes = command.facing
|
|
@@ -855,6 +992,7 @@ async function executeLegacyAuthSwitch(command, deps) {
|
|
|
855
992
|
lane,
|
|
856
993
|
provider: command.provider,
|
|
857
994
|
model,
|
|
995
|
+
force: true,
|
|
858
996
|
legacyFacing: command.facing,
|
|
859
997
|
}, deps, { writeStdout: false }));
|
|
860
998
|
}
|
|
@@ -871,7 +1009,7 @@ async function executeLegacyConfigModel(command, deps) {
|
|
|
871
1009
|
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
872
1010
|
const binding = state.lanes[lane];
|
|
873
1011
|
if (binding.provider === "github-copilot") {
|
|
874
|
-
const credential = readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
1012
|
+
const credential = await readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
875
1013
|
if (credential.ok) {
|
|
876
1014
|
const ghConfig = {
|
|
877
1015
|
...credential.record.config,
|
|
@@ -1641,15 +1779,12 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1641
1779
|
else {
|
|
1642
1780
|
await (0, agentic_repair_1.runAgenticRepair)(daemonResult.stability.degraded, {
|
|
1643
1781
|
/* v8 ignore start -- production provider discovery wiring @preserve */
|
|
1644
|
-
discoverWorkingProvider: async () => {
|
|
1782
|
+
discoverWorkingProvider: async (agentName) => {
|
|
1645
1783
|
const { discoverWorkingProvider: discover } = await Promise.resolve().then(() => __importStar(require("./provider-discovery")));
|
|
1646
|
-
const { discoverExistingCredentials } = await Promise.resolve().then(() => __importStar(require("./cli-defaults")));
|
|
1647
1784
|
const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
|
|
1648
1785
|
return discover({
|
|
1649
|
-
|
|
1786
|
+
agentName,
|
|
1650
1787
|
pingProvider: pingProvider,
|
|
1651
|
-
env: process.env,
|
|
1652
|
-
secretsRoot: deps.secretsRoot ?? `${process.env["HOME"]}/.agentsecrets`,
|
|
1653
1788
|
});
|
|
1654
1789
|
},
|
|
1655
1790
|
createProviderRuntime: agentic_repair_1.createAgenticDiagnosisProviderRuntime,
|
|
@@ -2269,6 +2404,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2269
2404
|
if (command.kind === "provider.status") {
|
|
2270
2405
|
return executeProviderStatus(command, deps);
|
|
2271
2406
|
}
|
|
2407
|
+
if (command.kind === "provider.refresh") {
|
|
2408
|
+
return executeProviderRefresh(command, deps);
|
|
2409
|
+
}
|
|
2410
|
+
if (command.kind === "vault.unlock") {
|
|
2411
|
+
return executeVaultUnlock(command, deps);
|
|
2412
|
+
}
|
|
2413
|
+
if (command.kind === "vault.create") {
|
|
2414
|
+
return executeVaultCreate(command, deps);
|
|
2415
|
+
}
|
|
2416
|
+
if (command.kind === "vault.status") {
|
|
2417
|
+
return executeVaultStatus(command, deps);
|
|
2418
|
+
}
|
|
2272
2419
|
// ── auth (local, no daemon socket needed) ──
|
|
2273
2420
|
if (command.kind === "auth.run") {
|
|
2274
2421
|
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot).config.humanFacing.provider;
|
|
@@ -2279,29 +2426,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2279
2426
|
provider,
|
|
2280
2427
|
promptInput: deps.promptInput,
|
|
2281
2428
|
});
|
|
2282
|
-
const credentials = (result.credentials ?? {});
|
|
2283
|
-
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, credentials);
|
|
2284
|
-
if (Object.keys(split.credentials).length > 0 || Object.keys(split.config).length > 0) {
|
|
2285
|
-
(0, provider_credential_pool_1.upsertProviderCredential)({
|
|
2286
|
-
homeDir: providerCliHomeDir(deps),
|
|
2287
|
-
provider,
|
|
2288
|
-
credentials: split.credentials,
|
|
2289
|
-
config: split.config,
|
|
2290
|
-
provenance: {
|
|
2291
|
-
source: "auth-flow",
|
|
2292
|
-
contributedByAgent: command.agent,
|
|
2293
|
-
},
|
|
2294
|
-
now: providerCliNow(deps),
|
|
2295
|
-
});
|
|
2296
|
-
}
|
|
2297
2429
|
// Behavior: ouro auth stores credentials only — does NOT switch provider.
|
|
2298
2430
|
// Use `ouro auth switch` to change the active provider.
|
|
2299
2431
|
deps.writeStdout(result.message);
|
|
2300
2432
|
// Verify the credentials actually work by pinging the provider
|
|
2301
2433
|
/* v8 ignore start -- integration: real API ping after auth @preserve */
|
|
2302
2434
|
try {
|
|
2303
|
-
const
|
|
2304
|
-
const status =
|
|
2435
|
+
const credential = await readProviderCredentialRecord(command.agent, provider, deps);
|
|
2436
|
+
const status = credential.ok
|
|
2437
|
+
? await verifyProviderCredentials(provider, {
|
|
2438
|
+
[provider]: { ...credential.record.config, ...credential.record.credentials },
|
|
2439
|
+
})
|
|
2440
|
+
: `stored but could not be re-read from vault (${credential.error})`;
|
|
2305
2441
|
deps.writeStdout(`${provider}: ${status}`);
|
|
2306
2442
|
}
|
|
2307
2443
|
catch {
|
|
@@ -2313,19 +2449,35 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2313
2449
|
// ── auth verify (local, no daemon socket needed) ──
|
|
2314
2450
|
/* v8 ignore start -- auth verify/switch: tested in daemon-cli.test.ts but v8 traces differ in CI @preserve */
|
|
2315
2451
|
if (command.kind === "auth.verify") {
|
|
2316
|
-
const
|
|
2317
|
-
|
|
2452
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
2453
|
+
if (!poolResult.ok) {
|
|
2454
|
+
const message = `vault unavailable: ${poolResult.error}\nRun \`ouro vault unlock --agent ${command.agent}\`, then retry.`;
|
|
2455
|
+
deps.writeStdout(message);
|
|
2456
|
+
return message;
|
|
2457
|
+
}
|
|
2318
2458
|
if (command.provider) {
|
|
2319
|
-
const
|
|
2459
|
+
const record = poolResult.pool.providers[command.provider];
|
|
2460
|
+
if (!record) {
|
|
2461
|
+
const message = `${command.provider}: missing. Run \`ouro auth --agent ${command.agent} --provider ${command.provider}\`.`;
|
|
2462
|
+
deps.writeStdout(message);
|
|
2463
|
+
return message;
|
|
2464
|
+
}
|
|
2465
|
+
const status = await verifyProviderCredentials(command.provider, {
|
|
2466
|
+
[command.provider]: { ...record.config, ...record.credentials },
|
|
2467
|
+
});
|
|
2320
2468
|
const message = `${command.provider}: ${status}`;
|
|
2321
2469
|
deps.writeStdout(message);
|
|
2322
2470
|
return message;
|
|
2323
2471
|
}
|
|
2324
2472
|
const lines = [];
|
|
2325
|
-
for (const p of Object.
|
|
2326
|
-
const status = await verifyProviderCredentials(p,
|
|
2473
|
+
for (const [p, record] of Object.entries(poolResult.pool.providers)) {
|
|
2474
|
+
const status = await verifyProviderCredentials(p, {
|
|
2475
|
+
[p]: { ...record.config, ...record.credentials },
|
|
2476
|
+
});
|
|
2327
2477
|
lines.push(`${p}: ${status}`);
|
|
2328
2478
|
}
|
|
2479
|
+
if (lines.length === 0)
|
|
2480
|
+
lines.push(`no provider credentials in ${command.agent}'s vault`);
|
|
2329
2481
|
const message = lines.join("\n");
|
|
2330
2482
|
deps.writeStdout(message);
|
|
2331
2483
|
return message;
|
|
@@ -2345,9 +2497,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2345
2497
|
deps.writeStdout(message);
|
|
2346
2498
|
return message;
|
|
2347
2499
|
}
|
|
2348
|
-
const
|
|
2349
|
-
const ghConfig =
|
|
2350
|
-
|
|
2500
|
+
const credential = await readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
2501
|
+
const ghConfig = credential.ok
|
|
2502
|
+
? { ...credential.record.config, ...credential.record.credentials }
|
|
2503
|
+
: {};
|
|
2504
|
+
if (typeof ghConfig.githubToken !== "string" || typeof ghConfig.baseUrl !== "string") {
|
|
2351
2505
|
throw new Error(`github-copilot credentials not configured. Run \`ouro auth --agent ${command.agent} --provider github-copilot\` first.`);
|
|
2352
2506
|
}
|
|
2353
2507
|
const fetchFn = deps.fetchImpl ?? fetch;
|
|
@@ -2728,7 +2882,10 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2728
2882
|
await deps.startChat(hatchInput.agentName);
|
|
2729
2883
|
return "";
|
|
2730
2884
|
}
|
|
2731
|
-
const
|
|
2885
|
+
const vaultLine = result.vaultUnlockSecret
|
|
2886
|
+
? `\nvault unlock secret for ${hatchInput.agentName}: ${result.vaultUnlockSecret}\nUse this with \`ouro vault unlock --agent ${hatchInput.agentName}\` on another machine.`
|
|
2887
|
+
: "";
|
|
2888
|
+
const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}${vaultLine}`;
|
|
2732
2889
|
deps.writeStdout(message);
|
|
2733
2890
|
return message;
|
|
2734
2891
|
}
|
|
@@ -65,6 +65,7 @@ function usage() {
|
|
|
65
65
|
" ouro status --agent <name>",
|
|
66
66
|
" ouro use --agent <name> --lane outward|inner --provider <provider> --model <model> [--force]",
|
|
67
67
|
" ouro check --agent <name> --lane outward|inner",
|
|
68
|
+
" ouro provider refresh --agent <name>",
|
|
68
69
|
" ouro outlook [--json]",
|
|
69
70
|
" ouro -v|--version",
|
|
70
71
|
" ouro config model --agent <name> <model-name>",
|
|
@@ -72,6 +73,9 @@ function usage() {
|
|
|
72
73
|
" ouro auth --agent <name> [--provider <provider>]",
|
|
73
74
|
" ouro auth verify --agent <name> [--provider <provider>]",
|
|
74
75
|
" ouro auth switch --agent <name> --provider <provider>",
|
|
76
|
+
" ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>] [--generate-unlock-secret]",
|
|
77
|
+
" ouro vault unlock --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
|
|
78
|
+
" ouro vault status --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
|
|
75
79
|
" ouro chat <agent>",
|
|
76
80
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
77
81
|
" ouro poke <agent> --task <task-id>",
|
|
@@ -426,6 +430,61 @@ function parseAuthCommand(args) {
|
|
|
426
430
|
}
|
|
427
431
|
return provider ? { kind: "auth.run", agent, provider } : { kind: "auth.run", agent };
|
|
428
432
|
}
|
|
433
|
+
function isVaultUnlockStoreKind(value) {
|
|
434
|
+
return value === "auto" || value === "macos-keychain" || value === "windows-dpapi" || value === "linux-secret-service" || value === "plaintext-file";
|
|
435
|
+
}
|
|
436
|
+
function parseVaultCommand(args) {
|
|
437
|
+
const sub = args[0];
|
|
438
|
+
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
439
|
+
let email;
|
|
440
|
+
let serverUrl;
|
|
441
|
+
let store;
|
|
442
|
+
let generateUnlockSecret = false;
|
|
443
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
444
|
+
const token = rest[i];
|
|
445
|
+
if (token === "--email") {
|
|
446
|
+
email = rest[i + 1];
|
|
447
|
+
i += 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (token === "--server") {
|
|
451
|
+
serverUrl = rest[i + 1];
|
|
452
|
+
i += 1;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (token === "--store") {
|
|
456
|
+
const value = rest[i + 1];
|
|
457
|
+
if (!isVaultUnlockStoreKind(value)) {
|
|
458
|
+
throw new Error("vault --store must be auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file");
|
|
459
|
+
}
|
|
460
|
+
store = value;
|
|
461
|
+
i += 1;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (token === "--generate-unlock-secret") {
|
|
465
|
+
generateUnlockSecret = true;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
throw new Error("Usage: ouro vault create|unlock|status --agent <name>");
|
|
469
|
+
}
|
|
470
|
+
if (!agent || (sub !== "create" && sub !== "unlock" && sub !== "status")) {
|
|
471
|
+
throw new Error("Usage: ouro vault create|unlock|status --agent <name>");
|
|
472
|
+
}
|
|
473
|
+
if (sub === "create") {
|
|
474
|
+
return {
|
|
475
|
+
kind: "vault.create",
|
|
476
|
+
agent,
|
|
477
|
+
...(email ? { email } : {}),
|
|
478
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
479
|
+
...(store ? { store } : {}),
|
|
480
|
+
...(generateUnlockSecret ? { generateUnlockSecret: true } : {}),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (sub === "unlock") {
|
|
484
|
+
return { kind: "vault.unlock", agent, ...(store ? { store } : {}) };
|
|
485
|
+
}
|
|
486
|
+
return { kind: "vault.status", agent, ...(store ? { store } : {}) };
|
|
487
|
+
}
|
|
429
488
|
function parseProviderUseCommand(args) {
|
|
430
489
|
const { agent, rest: afterAgent } = extractAgentFlag(args);
|
|
431
490
|
const { facing, rest: afterFacing } = extractFacingFlag(afterAgent);
|
|
@@ -483,6 +542,14 @@ function parseProviderCheckCommand(args) {
|
|
|
483
542
|
...(facing ? { legacyFacing: facing } : {}),
|
|
484
543
|
};
|
|
485
544
|
}
|
|
545
|
+
function parseProviderCommand(args) {
|
|
546
|
+
const sub = args[0];
|
|
547
|
+
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
548
|
+
if (sub === "refresh" && agent && rest.length === 0) {
|
|
549
|
+
return { kind: "provider.refresh", agent };
|
|
550
|
+
}
|
|
551
|
+
throw new Error("Usage: ouro provider refresh --agent <name>");
|
|
552
|
+
}
|
|
486
553
|
function parseReminderCommand(args) {
|
|
487
554
|
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
488
555
|
const [sub, ...rest] = cleaned;
|
|
@@ -878,6 +945,8 @@ function parseOuroCommand(args) {
|
|
|
878
945
|
return parseProviderUseCommand(args.slice(1));
|
|
879
946
|
if (head === "check")
|
|
880
947
|
return parseProviderCheckCommand(args.slice(1));
|
|
948
|
+
if (head === "provider")
|
|
949
|
+
return parseProviderCommand(args.slice(1));
|
|
881
950
|
if (head === "logs") {
|
|
882
951
|
if (second === "prune")
|
|
883
952
|
return { kind: "daemon.logs.prune" };
|
|
@@ -889,6 +958,8 @@ function parseOuroCommand(args) {
|
|
|
889
958
|
return parseHatchCommand(args.slice(1));
|
|
890
959
|
if (head === "auth")
|
|
891
960
|
return parseAuthCommand(args.slice(1));
|
|
961
|
+
if (head === "vault")
|
|
962
|
+
return parseVaultCommand(args.slice(1));
|
|
892
963
|
if (head === "task")
|
|
893
964
|
return parseTaskCommand(args.slice(1));
|
|
894
965
|
if (head === "reminder")
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* ouro-entry.ts, ouro-bot-wrapper.ts) continue to work unchanged.
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
17
|
+
exports.readFirstBundleMetaVersion = exports.createDefaultOuroCliDeps = exports.pingGithubCopilotModel = exports.listGithubCopilotModels = exports.ensureDaemonRunning = exports.runOuroCli = exports.parseOuroCommand = void 0;
|
|
18
18
|
// ── Parsing ──
|
|
19
19
|
var cli_parse_1 = require("./cli-parse");
|
|
20
20
|
Object.defineProperty(exports, "parseOuroCommand", { enumerable: true, get: function () { return cli_parse_1.parseOuroCommand; } });
|
|
@@ -29,4 +29,3 @@ Object.defineProperty(exports, "pingGithubCopilotModel", { enumerable: true, get
|
|
|
29
29
|
var cli_defaults_1 = require("./cli-defaults");
|
|
30
30
|
Object.defineProperty(exports, "createDefaultOuroCliDeps", { enumerable: true, get: function () { return cli_defaults_1.createDefaultOuroCliDeps; } });
|
|
31
31
|
Object.defineProperty(exports, "readFirstBundleMetaVersion", { enumerable: true, get: function () { return cli_defaults_1.readFirstBundleMetaVersion; } });
|
|
32
|
-
Object.defineProperty(exports, "discoverExistingCredentials", { enumerable: true, get: function () { return cli_defaults_1.discoverExistingCredentials; } });
|
|
@@ -54,7 +54,6 @@ const habit_migration_1 = require("../habits/habit-migration");
|
|
|
54
54
|
const os_cron_deps_1 = require("./os-cron-deps");
|
|
55
55
|
const os_cron_1 = require("./os-cron");
|
|
56
56
|
const daemon_tombstone_1 = require("./daemon-tombstone");
|
|
57
|
-
const os = __importStar(require("os"));
|
|
58
57
|
const agent_config_check_1 = require("./agent-config-check");
|
|
59
58
|
const pulse_1 = require("./pulse");
|
|
60
59
|
const socket_client_1 = require("./socket-client");
|
|
@@ -102,8 +101,7 @@ const processManager = new process_manager_1.DaemonProcessManager({
|
|
|
102
101
|
/* v8 ignore next 4 -- wiring: delegates to checkAgentConfigWithProviderHealth which has full unit tests @preserve */
|
|
103
102
|
configCheck: async (agent) => {
|
|
104
103
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
105
|
-
|
|
106
|
-
return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot, secretsRoot);
|
|
104
|
+
return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
|
|
107
105
|
},
|
|
108
106
|
/* v8 ignore start -- pulse flush wiring: integration code; flushPulse itself has full unit tests @preserve */
|
|
109
107
|
onSnapshotChange: () => {
|