@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
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ Ouroboros is a TypeScript harness for daemon-managed agents that live in externa
|
|
|
11
11
|
- `ouro up` starts the daemon from the installed production version, syncs the launcher, installs workflow helpers, and reconciles stale runtime state.
|
|
12
12
|
- `ouro dev` starts the daemon from a local repo build. It auto-builds from source, disables launchd auto-restart (so the installed daemon doesn't respawn underneath you), persists the repo path in `~/.ouro-cli/dev-config.json` for next time, and force-restarts the daemon. If you run `ouro dev` from inside the repo, it detects the CWD automatically. Run `ouro up` to return to production mode (this also cleans up `dev-config.json`).
|
|
13
13
|
- Agent bundles live outside the repo at `~/AgentBundles/<agent>.ouro/`.
|
|
14
|
-
- Provider credentials live
|
|
15
|
-
-
|
|
14
|
+
- Provider credentials live in the owning agent's Bitwarden/Vaultwarden vault. Tool/sense credential vault migration is a follow-up.
|
|
15
|
+
- Vault coordinates and local runtime state live in the agent bundle; raw credentials do not.
|
|
16
16
|
- Machine-scoped test and runtime spillover lives under `~/.agentstate/...`.
|
|
17
17
|
|
|
18
18
|
Current first-class senses:
|
|
@@ -91,10 +91,11 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
91
91
|
|
|
92
92
|
## Runtime Truths
|
|
93
93
|
|
|
94
|
-
- `agent.json` is the source of truth for phrase pools, context settings, enabled senses, and
|
|
95
|
-
- `configPath` must point to `~/.agentsecrets/<agent>/secrets.json` for sense-specific secrets.
|
|
94
|
+
- `agent.json` is the source of truth for identity, phrase pools, context settings, enabled senses, and vault coordinates. Legacy `humanFacing`/`agentFacing` values are bootstrap inputs, not live machine fallback.
|
|
96
95
|
- `state/providers.json` is the local source of truth for provider+model selection on this machine. It has two lanes: `outward` for CLI, Teams, and BlueBubbles turns, and `inner` for inner dialogue.
|
|
97
|
-
-
|
|
96
|
+
- Each agent has one credential vault for provider credentials. There is no machine-wide provider credential pool.
|
|
97
|
+
- Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
|
|
98
|
+
- Provider credentials are loaded into daemon memory at startup/auth/unlock/refresh and reused. The remote vault is not queried for every model request.
|
|
98
99
|
- The daemon discovers bundles dynamically from `~/AgentBundles`.
|
|
99
100
|
- `ouro status` reports version, last-updated time, discovered agents, senses, and workers.
|
|
100
101
|
- `bundle-meta.json` tracks the runtime version that last touched a bundle.
|
|
@@ -114,7 +115,7 @@ ouro auth --agent <name>
|
|
|
114
115
|
ouro auth --agent <name> --provider <provider>
|
|
115
116
|
```
|
|
116
117
|
|
|
117
|
-
`ouro auth` stores credentials
|
|
118
|
+
`ouro auth` stores credentials in the owning agent's vault. It does not switch a lane or write provider/model selection.
|
|
118
119
|
|
|
119
120
|
When you want this machine to use a provider/model for a lane, use:
|
|
120
121
|
|
|
@@ -124,6 +125,8 @@ ouro use --agent <name> --lane <outward|inner> --provider <provider> --model <mo
|
|
|
124
125
|
|
|
125
126
|
The outward lane handles user-facing senses. The inner lane handles the agent's private thinking. `ouro use` performs the provider/model check before committing the lane, so a broken local choice fails fast with a repair path instead of surprising the next turn.
|
|
126
127
|
|
|
128
|
+
For the full locked auth/provider contract, including refresh, repair actors, caching, and SerpentGuide hatch bootstrap, see `docs/auth-and-providers.md`.
|
|
129
|
+
|
|
127
130
|
## Quickstart
|
|
128
131
|
|
|
129
132
|
### Use The Published Runtime
|
|
@@ -165,8 +168,12 @@ ouro dev --clone # clone repo to ~/Projects/ouroboros, build, st
|
|
|
165
168
|
ouro status
|
|
166
169
|
ouro logs
|
|
167
170
|
ouro stop
|
|
171
|
+
ouro vault unlock --agent <name>
|
|
172
|
+
ouro vault status --agent <name>
|
|
168
173
|
ouro auth --agent <name>
|
|
169
174
|
ouro auth --agent <name> --provider <provider>
|
|
175
|
+
ouro auth verify --agent <name> [--provider <provider>]
|
|
176
|
+
ouro provider refresh --agent <name>
|
|
170
177
|
ouro use --agent <name> --lane <outward|inner> --provider <provider> --model <model>
|
|
171
178
|
ouro hatch
|
|
172
179
|
ouro clone <remote> [--agent <name>] # clone an existing agent from a git remote (see docs/cross-machine-setup.md)
|
|
@@ -233,6 +240,8 @@ See `skills/configure-dev-tools.md` for the full tool inventory and troubleshoot
|
|
|
233
240
|
Current daemon, bundle, sense, and update model.
|
|
234
241
|
- `docs/testing-guide.md`
|
|
235
242
|
Operator smoke flow for bootstrap, daemon, hatch, chat, and messaging.
|
|
243
|
+
- `docs/auth-and-providers.md`
|
|
244
|
+
Locked credential, provider selection, refresh, repair, and hatch bootstrap contract.
|
|
236
245
|
|
|
237
246
|
## A Note To Future Maintainers
|
|
238
247
|
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.366",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro up` now treats locked per-agent credential vaults as an unlock problem instead of a provider-auth problem, prompting `ouro vault unlock --agent <agent>` before any `ouro auth` repair flow.",
|
|
8
|
+
"Provider startup checks now preserve credential-pool `unavailable` state and show concise locked-vault guidance instead of nesting duplicate vault errors and secondary auth instructions.",
|
|
9
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the vault unlock repair release."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.365",
|
|
14
|
+
"changes": [
|
|
15
|
+
"Provider credentials now live in the owning agent's Bitwarden/Vaultwarden vault instead of a machine-wide provider pool; runtime uses daemon-memory credential snapshots and machine-local `state/providers.json` provider/model lanes.",
|
|
16
|
+
"`ouro auth`, `ouro use`, provider status/check/refresh, startup health checks, interactive repair, and failover guidance now share the same agent-vault/local-lane contract with clearer human repair paths.",
|
|
17
|
+
"SerpentGuide bootstrap no longer persists its own provider credentials; hatch flows keep bootstrap credentials in memory and store them only in the hatchling agent vault.",
|
|
18
|
+
"Vault unlock bootstrap supports macOS Keychain, Windows DPAPI, Linux Secret Service, and explicit plaintext fallback, with docs aligned across AGENTS, README, OAuth setup, testing guide, and auth/provider reference."
|
|
19
|
+
]
|
|
20
|
+
},
|
|
4
21
|
{
|
|
5
22
|
"version": "0.1.0-alpha.364",
|
|
6
23
|
"changes": [
|
|
@@ -35,8 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.readAgentConfigForAgent = readAgentConfigForAgent;
|
|
37
37
|
exports.writeAgentProviderSelection = writeAgentProviderSelection;
|
|
38
|
-
exports.
|
|
39
|
-
exports.writeProviderCredentials = writeProviderCredentials;
|
|
38
|
+
exports.storeProviderCredentials = storeProviderCredentials;
|
|
40
39
|
exports.writeAgentModel = writeAgentModel;
|
|
41
40
|
exports.collectRuntimeAuthCredentials = collectRuntimeAuthCredentials;
|
|
42
41
|
exports.resolveHatchCredentials = resolveHatchCredentials;
|
|
@@ -49,71 +48,13 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
49
48
|
const identity_1 = require("../identity");
|
|
50
49
|
const migrate_config_1 = require("../migrate-config");
|
|
51
50
|
const provider_models_1 = require("../provider-models");
|
|
51
|
+
const provider_credentials_1 = require("../provider-credentials");
|
|
52
52
|
const ANTHROPIC_SETUP_TOKEN_PREFIX = "sk-ant-oat01-";
|
|
53
53
|
const ANTHROPIC_SETUP_TOKEN_MIN_LENGTH = 80;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
apiKey: "",
|
|
58
|
-
endpoint: "",
|
|
59
|
-
deployment: "",
|
|
60
|
-
apiVersion: "2025-04-01-preview",
|
|
61
|
-
},
|
|
62
|
-
minimax: {
|
|
63
|
-
apiKey: "",
|
|
64
|
-
},
|
|
65
|
-
anthropic: {
|
|
66
|
-
setupToken: "",
|
|
67
|
-
refreshToken: "",
|
|
68
|
-
expiresAt: 0,
|
|
69
|
-
},
|
|
70
|
-
"openai-codex": {
|
|
71
|
-
oauthAccessToken: "",
|
|
72
|
-
},
|
|
73
|
-
"github-copilot": {
|
|
74
|
-
githubToken: "",
|
|
75
|
-
baseUrl: "",
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
teams: {
|
|
79
|
-
clientId: "",
|
|
80
|
-
clientSecret: "",
|
|
81
|
-
tenantId: "",
|
|
82
|
-
},
|
|
83
|
-
oauth: {
|
|
84
|
-
graphConnectionName: "graph",
|
|
85
|
-
adoConnectionName: "ado",
|
|
86
|
-
githubConnectionName: "",
|
|
87
|
-
},
|
|
88
|
-
teamsChannel: {
|
|
89
|
-
skipConfirmation: true,
|
|
90
|
-
port: 3978,
|
|
91
|
-
},
|
|
92
|
-
vault: {
|
|
93
|
-
masterPassword: "",
|
|
94
|
-
},
|
|
95
|
-
integrations: {
|
|
96
|
-
perplexityApiKey: "",
|
|
97
|
-
openaiEmbeddingsApiKey: "",
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
function deepMerge(defaults, partial) {
|
|
101
|
-
const result = { ...defaults };
|
|
102
|
-
for (const key of Object.keys(partial)) {
|
|
103
|
-
const left = result[key];
|
|
104
|
-
const right = partial[key];
|
|
105
|
-
if (right !== null &&
|
|
106
|
-
typeof right === "object" &&
|
|
107
|
-
!Array.isArray(right) &&
|
|
108
|
-
left !== null &&
|
|
109
|
-
typeof left === "object" &&
|
|
110
|
-
!Array.isArray(left)) {
|
|
111
|
-
result[key] = deepMerge(left, right);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
result[key] = right;
|
|
54
|
+
function assertPersistentProviderCredentialsAllowed(agentName) {
|
|
55
|
+
if (agentName === "SerpentGuide") {
|
|
56
|
+
throw new Error("SerpentGuide uses provider credentials in memory during hatch bootstrap; persistent SerpentGuide auth is not supported.");
|
|
115
57
|
}
|
|
116
|
-
return result;
|
|
117
58
|
}
|
|
118
59
|
function readJsonRecord(filePath, label) {
|
|
119
60
|
try {
|
|
@@ -196,38 +137,18 @@ function writeAgentProviderSelection(agentName, facing, provider, bundlesRoot =
|
|
|
196
137
|
});
|
|
197
138
|
return configPath;
|
|
198
139
|
}
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
onDisk = readJsonRecord(secretsPath, "secrets config");
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
const message = error.message;
|
|
215
|
-
if (!message.includes("ENOENT"))
|
|
216
|
-
throw error;
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
secretsPath,
|
|
220
|
-
secrets: deepMerge(DEFAULT_SECRETS_TEMPLATE, onDisk),
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
function writeSecrets(secretsPath, secrets) {
|
|
224
|
-
fs.writeFileSync(secretsPath, `${JSON.stringify(secrets, null, 2)}\n`, "utf8");
|
|
225
|
-
}
|
|
226
|
-
function writeProviderCredentials(agentName, provider, credentials, deps = {}) {
|
|
227
|
-
const { secretsPath, secrets } = loadAgentSecrets(agentName, deps);
|
|
228
|
-
applyCredentials(secrets, provider, credentials);
|
|
229
|
-
writeSecrets(secretsPath, secrets);
|
|
230
|
-
return { secretsPath, secrets };
|
|
140
|
+
async function storeProviderCredentials(agentName, provider, credentials, deps = {}) {
|
|
141
|
+
assertPersistentProviderCredentialsAllowed(agentName);
|
|
142
|
+
const split = (0, provider_credentials_1.splitProviderCredentialFields)(provider, credentials);
|
|
143
|
+
await (0, provider_credentials_1.upsertProviderCredential)({
|
|
144
|
+
agentName,
|
|
145
|
+
provider,
|
|
146
|
+
credentials: split.credentials,
|
|
147
|
+
config: split.config,
|
|
148
|
+
provenance: { source: "auth-flow" },
|
|
149
|
+
now: deps.now,
|
|
150
|
+
});
|
|
151
|
+
return { credentialPath: (0, provider_credentials_1.providerCredentialItemName)(provider) };
|
|
231
152
|
}
|
|
232
153
|
function writeAgentModel(agentName, facing, modelName, deps = {}) {
|
|
233
154
|
const { configPath, config } = readAgentConfigForAgent(agentName, deps.bundlesRoot);
|
|
@@ -427,36 +348,30 @@ async function resolveHatchCredentials(input) {
|
|
|
427
348
|
}
|
|
428
349
|
return credentials;
|
|
429
350
|
}
|
|
430
|
-
function applyCredentials(secrets, provider, credentials) {
|
|
431
|
-
const target = secrets.providers[provider];
|
|
432
|
-
// Copy all non-empty credential fields to the provider's secrets block
|
|
433
|
-
for (const [key, value] of Object.entries(credentials)) {
|
|
434
|
-
/* v8 ignore next -- guard: skip null/empty fields from partial credential objects @preserve */
|
|
435
|
-
if (value != null && value !== "") {
|
|
436
|
-
target[key] = typeof value === "string" ? value.trim() : value;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
351
|
async function runRuntimeAuthFlow(input, deps = {}) {
|
|
352
|
+
assertPersistentProviderCredentialsAllowed(input.agentName);
|
|
441
353
|
(0, runtime_1.emitNervesEvent)({
|
|
442
354
|
component: "daemon",
|
|
443
355
|
event: "daemon.auth_flow_start",
|
|
444
356
|
message: "starting runtime auth flow",
|
|
445
357
|
meta: { agentName: input.agentName, provider: input.provider },
|
|
446
358
|
});
|
|
447
|
-
const
|
|
359
|
+
const vault = await (0, provider_credentials_1.refreshProviderCredentialPool)(input.agentName);
|
|
360
|
+
if (!vault.ok && vault.reason === "unavailable") {
|
|
361
|
+
throw new Error(`${vault.error}\nRun \`ouro vault unlock --agent ${input.agentName}\`, then retry auth.`);
|
|
362
|
+
}
|
|
448
363
|
const credentials = await collectRuntimeAuthCredentials(input, deps);
|
|
449
|
-
const {
|
|
364
|
+
const { credentialPath } = await storeProviderCredentials(input.agentName, input.provider, credentials);
|
|
450
365
|
(0, runtime_1.emitNervesEvent)({
|
|
451
366
|
component: "daemon",
|
|
452
367
|
event: "daemon.auth_flow_end",
|
|
453
368
|
message: "completed runtime auth flow",
|
|
454
|
-
meta: { agentName: input.agentName, provider: input.provider,
|
|
369
|
+
meta: { agentName: input.agentName, provider: input.provider, credentialPath },
|
|
455
370
|
});
|
|
456
371
|
return {
|
|
457
372
|
agentName: input.agentName,
|
|
458
373
|
provider: input.provider,
|
|
459
|
-
|
|
374
|
+
credentialPath,
|
|
460
375
|
message: `authenticated ${input.agentName} with ${input.provider}`,
|
|
461
376
|
credentials,
|
|
462
377
|
};
|
package/dist/heart/config.js
CHANGED
|
@@ -41,7 +41,6 @@ exports.getMinimaxConfig = getMinimaxConfig;
|
|
|
41
41
|
exports.getAnthropicConfig = getAnthropicConfig;
|
|
42
42
|
exports.getOpenAICodexConfig = getOpenAICodexConfig;
|
|
43
43
|
exports.getGithubCopilotConfig = getGithubCopilotConfig;
|
|
44
|
-
exports.getProviderConfig = getProviderConfig;
|
|
45
44
|
exports.getTeamsConfig = getTeamsConfig;
|
|
46
45
|
exports.getTeamsSecondaryConfig = getTeamsSecondaryConfig;
|
|
47
46
|
exports.getContextConfig = getContextConfig;
|
|
@@ -62,30 +61,9 @@ exports.logPath = logPath;
|
|
|
62
61
|
const fs = __importStar(require("fs"));
|
|
63
62
|
const path = __importStar(require("path"));
|
|
64
63
|
const identity_1 = require("./identity");
|
|
64
|
+
const provider_credentials_1 = require("./provider-credentials");
|
|
65
65
|
const runtime_1 = require("../nerves/runtime");
|
|
66
|
-
const
|
|
67
|
-
providers: {
|
|
68
|
-
azure: {
|
|
69
|
-
apiKey: "",
|
|
70
|
-
endpoint: "",
|
|
71
|
-
deployment: "",
|
|
72
|
-
apiVersion: "2025-04-01-preview",
|
|
73
|
-
managedIdentityClientId: "",
|
|
74
|
-
},
|
|
75
|
-
minimax: {
|
|
76
|
-
apiKey: "",
|
|
77
|
-
},
|
|
78
|
-
anthropic: {
|
|
79
|
-
setupToken: "",
|
|
80
|
-
},
|
|
81
|
-
"openai-codex": {
|
|
82
|
-
oauthAccessToken: "",
|
|
83
|
-
},
|
|
84
|
-
"github-copilot": {
|
|
85
|
-
githubToken: "",
|
|
86
|
-
baseUrl: "",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
66
|
+
const DEFAULT_LOCAL_RUNTIME_CONFIG = {
|
|
89
67
|
teams: {
|
|
90
68
|
clientId: "",
|
|
91
69
|
clientSecret: "",
|
|
@@ -127,26 +105,20 @@ const DEFAULT_SECRETS_TEMPLATE = {
|
|
|
127
105
|
};
|
|
128
106
|
function defaultRuntimeConfig() {
|
|
129
107
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
anthropic: { ...DEFAULT_SECRETS_TEMPLATE.providers.anthropic },
|
|
134
|
-
"openai-codex": { ...DEFAULT_SECRETS_TEMPLATE.providers["openai-codex"] },
|
|
135
|
-
"github-copilot": { ...DEFAULT_SECRETS_TEMPLATE.providers["github-copilot"] },
|
|
136
|
-
},
|
|
137
|
-
teams: { ...DEFAULT_SECRETS_TEMPLATE.teams },
|
|
138
|
-
teamsSecondary: { ...DEFAULT_SECRETS_TEMPLATE.teamsSecondary },
|
|
139
|
-
oauth: { ...DEFAULT_SECRETS_TEMPLATE.oauth },
|
|
108
|
+
teams: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.teams },
|
|
109
|
+
teamsSecondary: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.teamsSecondary },
|
|
110
|
+
oauth: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.oauth },
|
|
140
111
|
context: { ...identity_1.DEFAULT_AGENT_CONTEXT },
|
|
141
|
-
teamsChannel: { ...
|
|
142
|
-
bluebubbles: { ...
|
|
143
|
-
bluebubblesChannel: { ...
|
|
144
|
-
vault: { ...
|
|
145
|
-
integrations: { ...
|
|
112
|
+
teamsChannel: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.teamsChannel },
|
|
113
|
+
bluebubbles: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.bluebubbles },
|
|
114
|
+
bluebubblesChannel: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.bluebubblesChannel },
|
|
115
|
+
vault: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.vault },
|
|
116
|
+
integrations: { ...DEFAULT_LOCAL_RUNTIME_CONFIG.integrations },
|
|
146
117
|
};
|
|
147
118
|
}
|
|
148
119
|
let _runtimeConfigOverride = null;
|
|
149
120
|
let _testContextOverride = null;
|
|
121
|
+
let _providerConfigOverride = null;
|
|
150
122
|
function resolveConfigPath() {
|
|
151
123
|
return (0, identity_1.getAgentSecretsPath)();
|
|
152
124
|
}
|
|
@@ -185,7 +157,7 @@ function loadConfig() {
|
|
|
185
157
|
: undefined;
|
|
186
158
|
if (errorCode === "ENOENT") {
|
|
187
159
|
try {
|
|
188
|
-
fs.writeFileSync(configPath, JSON.stringify(
|
|
160
|
+
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_LOCAL_RUNTIME_CONFIG, null, 2) + "\n", "utf-8");
|
|
189
161
|
}
|
|
190
162
|
catch (writeError) {
|
|
191
163
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -214,6 +186,19 @@ function loadConfig() {
|
|
|
214
186
|
// ENOENT or parse error -- use defaults
|
|
215
187
|
}
|
|
216
188
|
const sanitizedFileData = { ...fileData };
|
|
189
|
+
if ("providers" in sanitizedFileData) {
|
|
190
|
+
delete sanitizedFileData.providers;
|
|
191
|
+
(0, runtime_1.emitNervesEvent)({
|
|
192
|
+
level: "warn",
|
|
193
|
+
event: "config_identity.error",
|
|
194
|
+
component: "config/identity",
|
|
195
|
+
message: "ignored legacy providers block in secrets config",
|
|
196
|
+
meta: {
|
|
197
|
+
phase: "loadConfig",
|
|
198
|
+
path: configPath,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
217
202
|
if ("context" in sanitizedFileData) {
|
|
218
203
|
delete sanitizedFileData.context;
|
|
219
204
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -246,38 +231,67 @@ function loadConfig() {
|
|
|
246
231
|
function resetConfigCache() {
|
|
247
232
|
_runtimeConfigOverride = null;
|
|
248
233
|
_testContextOverride = null;
|
|
234
|
+
_providerConfigOverride = null;
|
|
235
|
+
(0, provider_credentials_1.resetProviderCredentialCache)();
|
|
236
|
+
}
|
|
237
|
+
function seedProviderCredentialCache(providers) {
|
|
238
|
+
if (!providers)
|
|
239
|
+
return;
|
|
240
|
+
_providerConfigOverride = deepMerge((_providerConfigOverride ?? {}), providers);
|
|
241
|
+
const records = Object.entries(_providerConfigOverride).map(([provider, rawConfig]) => {
|
|
242
|
+
const split = (0, provider_credentials_1.splitProviderCredentialFields)(provider, rawConfig);
|
|
243
|
+
return (0, provider_credentials_1.createProviderCredentialRecord)({
|
|
244
|
+
provider: provider,
|
|
245
|
+
credentials: split.credentials,
|
|
246
|
+
config: split.config,
|
|
247
|
+
provenance: { source: "manual" },
|
|
248
|
+
now: new Date(0),
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
(0, provider_credentials_1.cacheProviderCredentialRecords)((0, identity_1.getAgentName)(), records, new Date(0));
|
|
249
252
|
}
|
|
250
253
|
function patchRuntimeConfig(partial) {
|
|
254
|
+
const { providers, ...runtimePartial } = partial;
|
|
255
|
+
seedProviderCredentialCache(providers);
|
|
251
256
|
const contextPatch = partial.context;
|
|
252
257
|
if (contextPatch) {
|
|
253
258
|
const base = _testContextOverride ?? identity_1.DEFAULT_AGENT_CONTEXT;
|
|
254
259
|
_testContextOverride = deepMerge(base, contextPatch);
|
|
255
260
|
}
|
|
256
|
-
_runtimeConfigOverride = deepMerge((_runtimeConfigOverride ?? {}),
|
|
261
|
+
_runtimeConfigOverride = deepMerge((_runtimeConfigOverride ?? {}), runtimePartial);
|
|
262
|
+
}
|
|
263
|
+
function readProviderConfig(provider) {
|
|
264
|
+
const cached = (0, provider_credentials_1.readCachedProviderCredentialRecord)((0, identity_1.getAgentName)(), provider);
|
|
265
|
+
return cached.ok ? { ...cached.record.config, ...cached.record.credentials } : {};
|
|
257
266
|
}
|
|
258
267
|
function getAzureConfig() {
|
|
259
|
-
const
|
|
260
|
-
return {
|
|
268
|
+
const raw = readProviderConfig("azure");
|
|
269
|
+
return {
|
|
270
|
+
apiKey: typeof raw.apiKey === "string" ? raw.apiKey : "",
|
|
271
|
+
endpoint: typeof raw.endpoint === "string" ? raw.endpoint : "",
|
|
272
|
+
deployment: typeof raw.deployment === "string" ? raw.deployment : "",
|
|
273
|
+
apiVersion: typeof raw.apiVersion === "string" ? raw.apiVersion : "2025-04-01-preview",
|
|
274
|
+
managedIdentityClientId: typeof raw.managedIdentityClientId === "string" ? raw.managedIdentityClientId : "",
|
|
275
|
+
};
|
|
261
276
|
}
|
|
262
277
|
function getMinimaxConfig() {
|
|
263
|
-
const
|
|
264
|
-
return {
|
|
278
|
+
const raw = readProviderConfig("minimax");
|
|
279
|
+
return { apiKey: typeof raw.apiKey === "string" ? raw.apiKey : "" };
|
|
265
280
|
}
|
|
266
281
|
function getAnthropicConfig() {
|
|
267
|
-
const
|
|
268
|
-
return {
|
|
282
|
+
const raw = readProviderConfig("anthropic");
|
|
283
|
+
return { setupToken: typeof raw.setupToken === "string" ? raw.setupToken : "" };
|
|
269
284
|
}
|
|
270
285
|
function getOpenAICodexConfig() {
|
|
271
|
-
const
|
|
272
|
-
return {
|
|
286
|
+
const raw = readProviderConfig("openai-codex");
|
|
287
|
+
return { oauthAccessToken: typeof raw.oauthAccessToken === "string" ? raw.oauthAccessToken : "" };
|
|
273
288
|
}
|
|
274
289
|
function getGithubCopilotConfig() {
|
|
275
|
-
const
|
|
276
|
-
return {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return { ...config.providers[provider] };
|
|
290
|
+
const raw = readProviderConfig("github-copilot");
|
|
291
|
+
return {
|
|
292
|
+
githubToken: typeof raw.githubToken === "string" ? raw.githubToken : "",
|
|
293
|
+
baseUrl: typeof raw.baseUrl === "string" ? raw.baseUrl : "",
|
|
294
|
+
};
|
|
281
295
|
}
|
|
282
296
|
function getTeamsConfig() {
|
|
283
297
|
const config = loadConfig();
|
package/dist/heart/core.js
CHANGED
|
@@ -32,43 +32,80 @@ const tool_loop_1 = require("./tool-loop");
|
|
|
32
32
|
const packets_1 = require("../arc/packets");
|
|
33
33
|
const tool_friction_1 = require("./tool-friction");
|
|
34
34
|
const provider_models_1 = require("./provider-models");
|
|
35
|
+
const provider_credentials_1 = require("./provider-credentials");
|
|
36
|
+
const provider_state_1 = require("./provider-state");
|
|
35
37
|
const provider_attempt_1 = require("./provider-attempt");
|
|
36
38
|
const _providerRuntimes = {
|
|
37
39
|
human: null,
|
|
38
40
|
agent: null,
|
|
39
41
|
};
|
|
40
|
-
function
|
|
42
|
+
function providerLaneForFacing(facing) {
|
|
43
|
+
return facing === "human" ? "outward" : "inner";
|
|
44
|
+
}
|
|
45
|
+
function resolveRuntimeProviderBinding(facing) {
|
|
46
|
+
const agentName = (0, identity_2.getAgentName)();
|
|
47
|
+
const lane = providerLaneForFacing(facing);
|
|
48
|
+
const stateResult = (0, provider_state_1.readProviderState)((0, identity_2.getAgentRoot)(agentName));
|
|
49
|
+
if (stateResult.ok) {
|
|
50
|
+
const binding = stateResult.state.lanes[lane];
|
|
51
|
+
return { lane, provider: binding.provider, model: binding.model };
|
|
52
|
+
}
|
|
53
|
+
if (stateResult.reason === "invalid") {
|
|
54
|
+
throw new Error(`provider state for ${agentName} is invalid at ${stateResult.statePath}: ${stateResult.error}`);
|
|
55
|
+
}
|
|
56
|
+
// First-run and SerpentGuide bootstrap path. Daemon startup normally
|
|
57
|
+
// bootstraps state/providers.json from agent.json before model calls.
|
|
41
58
|
const config = (0, identity_1.loadAgentConfig)();
|
|
42
59
|
const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
|
|
43
|
-
|
|
44
|
-
const model = facingConfig.model;
|
|
45
|
-
const providerConfig = (0, config_1.getProviderConfig)(provider);
|
|
46
|
-
return JSON.stringify({ provider, model, ...providerConfig });
|
|
60
|
+
return { lane, provider: facingConfig.provider, model: facingConfig.model };
|
|
47
61
|
}
|
|
48
|
-
function
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
async function getProviderRuntimeFingerprint(facing) {
|
|
63
|
+
const agentName = (0, identity_2.getAgentName)();
|
|
64
|
+
const binding = resolveRuntimeProviderBinding(facing);
|
|
65
|
+
const credential = await (0, provider_credentials_1.readProviderCredentialRecord)(agentName, binding.provider);
|
|
66
|
+
if (!credential.ok) {
|
|
67
|
+
throw new Error([
|
|
68
|
+
`${binding.lane} provider ${binding.provider} (${binding.model}) has no credentials for ${agentName}.`,
|
|
69
|
+
credential.error,
|
|
70
|
+
`Run \`ouro auth --agent ${agentName} --provider ${binding.provider}\`.`,
|
|
71
|
+
].join("\n"));
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
binding,
|
|
75
|
+
fingerprint: JSON.stringify({
|
|
76
|
+
lane: binding.lane,
|
|
77
|
+
provider: binding.provider,
|
|
78
|
+
model: binding.model,
|
|
79
|
+
credentialRevision: credential.record.revision,
|
|
80
|
+
}),
|
|
81
|
+
credential: credential.record,
|
|
55
82
|
};
|
|
83
|
+
}
|
|
84
|
+
function createProviderRegistry() {
|
|
56
85
|
return {
|
|
57
|
-
resolve(provider, model) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
86
|
+
resolve(provider, model, credential) {
|
|
87
|
+
const providerConfig = { ...credential.config, ...credential.credentials };
|
|
88
|
+
switch (provider) {
|
|
89
|
+
case "azure":
|
|
90
|
+
return (0, azure_1.createAzureProviderRuntime)(model, providerConfig);
|
|
91
|
+
case "anthropic":
|
|
92
|
+
return (0, anthropic_1.createAnthropicProviderRuntime)(model, providerConfig);
|
|
93
|
+
case "minimax":
|
|
94
|
+
return (0, minimax_1.createMinimaxProviderRuntime)(model, providerConfig);
|
|
95
|
+
case "openai-codex":
|
|
96
|
+
return (0, openai_codex_1.createOpenAICodexProviderRuntime)(model, providerConfig);
|
|
97
|
+
case "github-copilot":
|
|
98
|
+
return (0, github_copilot_1.createGithubCopilotProviderRuntime)(model, providerConfig);
|
|
99
|
+
}
|
|
61
100
|
},
|
|
62
101
|
};
|
|
63
102
|
}
|
|
64
|
-
function getProviderRuntime(facing = "human") {
|
|
103
|
+
async function getProviderRuntime(facing = "human") {
|
|
65
104
|
try {
|
|
66
|
-
const fingerprint = getProviderRuntimeFingerprint(facing);
|
|
105
|
+
const { binding, fingerprint, credential } = await getProviderRuntimeFingerprint(facing);
|
|
67
106
|
const cached = _providerRuntimes[facing];
|
|
68
107
|
if (!cached || cached.fingerprint !== fingerprint) {
|
|
69
|
-
const
|
|
70
|
-
const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
|
|
71
|
-
const runtime = createProviderRegistry().resolve(facingConfig.provider, facingConfig.model);
|
|
108
|
+
const runtime = createProviderRegistry().resolve(binding.provider, binding.model, credential);
|
|
72
109
|
_providerRuntimes[facing] = runtime ? { fingerprint, runtime } : null;
|
|
73
110
|
}
|
|
74
111
|
}
|
|
@@ -109,14 +146,14 @@ function resetProviderRuntime() {
|
|
|
109
146
|
_providerRuntimes.agent = null;
|
|
110
147
|
}
|
|
111
148
|
function getModel(facing = "human") {
|
|
112
|
-
return
|
|
149
|
+
return resolveRuntimeProviderBinding(facing).model;
|
|
113
150
|
}
|
|
114
151
|
function getProvider(facing = "human") {
|
|
115
|
-
return
|
|
152
|
+
return resolveRuntimeProviderBinding(facing).provider;
|
|
116
153
|
}
|
|
117
154
|
function createSummarize(facing = "human") {
|
|
118
155
|
return async (transcript, instruction) => {
|
|
119
|
-
const runtime = getProviderRuntime(facing);
|
|
156
|
+
const runtime = await getProviderRuntime(facing);
|
|
120
157
|
const client = runtime.client;
|
|
121
158
|
const response = await client.chat.completions.create({
|
|
122
159
|
model: runtime.model,
|
|
@@ -130,14 +167,12 @@ function createSummarize(facing = "human") {
|
|
|
130
167
|
};
|
|
131
168
|
}
|
|
132
169
|
function getProviderDisplayLabel(facing = "human") {
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const model = facingConfig.model || "unknown";
|
|
170
|
+
const binding = resolveRuntimeProviderBinding(facing);
|
|
171
|
+
const provider = binding.provider;
|
|
172
|
+
const model = binding.model || "unknown";
|
|
137
173
|
const providerLabelBuilders = {
|
|
138
174
|
azure: () => {
|
|
139
|
-
|
|
140
|
-
return `azure openai (${azureCfg.deployment || "default"}, model: ${model})`;
|
|
175
|
+
return `azure openai (model: ${model})`;
|
|
141
176
|
},
|
|
142
177
|
anthropic: () => `anthropic (${model})`,
|
|
143
178
|
minimax: () => `minimax (${model})`,
|
|
@@ -426,7 +461,7 @@ function buildAuthFailureGuidance(provider, model, agentName, detail) {
|
|
|
426
461
|
}
|
|
427
462
|
async function runAgent(messages, callbacks, channel, signal, options) {
|
|
428
463
|
const facing = (0, channel_1.channelToFacing)(channel);
|
|
429
|
-
|
|
464
|
+
let providerRuntime = await getProviderRuntime(facing);
|
|
430
465
|
const provider = providerRuntime.id;
|
|
431
466
|
const toolChoiceRequired = options?.toolChoiceRequired ?? true;
|
|
432
467
|
const traceId = options?.traceId;
|
|
@@ -485,7 +520,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
485
520
|
await (0, kept_notes_1.injectKeptNotes)(messages, {
|
|
486
521
|
channel,
|
|
487
522
|
friend: currentContext?.friend,
|
|
488
|
-
judge: async (input) => (0, kept_notes_1.createKeptNotesJudge)(getProviderRuntime("agent"), signal)(input),
|
|
523
|
+
judge: async (input) => (0, kept_notes_1.createKeptNotesJudge)(await getProviderRuntime("agent"), signal)(input),
|
|
489
524
|
signal,
|
|
490
525
|
traceId,
|
|
491
526
|
});
|
|
@@ -648,10 +683,25 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
648
683
|
model: providerRuntime.model,
|
|
649
684
|
run: callProviderTurnWithOverflowRecovery,
|
|
650
685
|
classifyError: (error) => providerRuntime.classifyError(error),
|
|
651
|
-
onRetry: (record, maxAttempts) => {
|
|
686
|
+
onRetry: async (record, maxAttempts) => {
|
|
652
687
|
const delayMs = record.delayMs;
|
|
653
688
|
const seconds = delayMs / 1000;
|
|
654
689
|
const cause = RETRY_LABELS[record.classification];
|
|
690
|
+
try {
|
|
691
|
+
await (0, provider_credentials_1.refreshProviderCredentialPool)((0, identity_2.getAgentName)(), { preserveCachedOnFailure: true });
|
|
692
|
+
_providerRuntimes[facing] = null;
|
|
693
|
+
providerRuntime = await getProviderRuntime(facing);
|
|
694
|
+
providerRuntime.resetTurnState(messages);
|
|
695
|
+
}
|
|
696
|
+
catch (refreshError) {
|
|
697
|
+
(0, runtime_1.emitNervesEvent)({
|
|
698
|
+
level: "warn",
|
|
699
|
+
component: "engine",
|
|
700
|
+
event: "engine.provider_retry_refresh_failed",
|
|
701
|
+
message: "provider credential refresh failed during retry",
|
|
702
|
+
meta: { provider: record.provider, model: record.model, reason: refreshError instanceof Error ? refreshError.message : String(refreshError) },
|
|
703
|
+
});
|
|
704
|
+
}
|
|
655
705
|
callbacks.onError(new Error(`${cause}, retrying in ${seconds}s (${record.attempt}/${maxAttempts})...`), "transient");
|
|
656
706
|
},
|
|
657
707
|
sleep: async (delayMs) => {
|