@ouro.bot/cli 0.1.0-alpha.553 → 0.1.0-alpha.554

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.
@@ -108,15 +108,6 @@ const SENSITIVE_CONFIG_KEYS = ["apiKey", "token", "secret", "password"];
108
108
  function credentialKeyLeaks(raw) {
109
109
  return SENSITIVE_CONFIG_KEYS.filter((key) => raw.includes(`"${key}"`));
110
110
  }
111
- function checkCredentialLeak(checks, label, raw) {
112
- const found = credentialKeyLeaks(raw);
113
- if (found.length > 0) {
114
- checks.push({ label, status: "warn", detail: `contains credential-looking keys: ${found.join(", ")}` });
115
- }
116
- else {
117
- checks.push({ label, status: "pass", detail: "no credential keys" });
118
- }
119
- }
120
111
  function checkAgents(deps) {
121
112
  const checks = [];
122
113
  if (!deps.existsSync(deps.bundlesRoot)) {
@@ -368,15 +359,6 @@ function checkSecurity(deps) {
368
359
  checks.push({ label: `${agentDir} credential leak`, status: "fail", detail: "could not read agent.json" });
369
360
  }
370
361
  }
371
- const providerStatePath = `${deps.bundlesRoot}/${agentDir}/state/providers.json`;
372
- if (deps.existsSync(providerStatePath)) {
373
- try {
374
- checkCredentialLeak(checks, `${agentDir} state/providers.json credential leak`, deps.readFileSync(providerStatePath));
375
- }
376
- catch {
377
- checks.push({ label: `${agentDir} state/providers.json credential leak`, status: "fail", detail: "could not read state/providers.json" });
378
- }
379
- }
380
362
  }
381
363
  if (checks.length === 0) {
382
364
  checks.push({ label: "security", status: "warn", detail: "no agents found" });
@@ -74,18 +74,6 @@ function buildInnerStatusOutput(input) {
74
74
  // Attention
75
75
  const thoughtWord = attentionCount === 1 ? "thought" : "thoughts";
76
76
  lines.push(` attention: ${attentionCount} held ${thoughtWord}`);
77
- // Layer 4 drift advisory. Renders one line per finding with the
78
- // lane, intent vs observed binding, and the copy-pasteable repair
79
- // command. Suppressed entirely when no findings exist (or the field
80
- // is absent — pre-Layer-4 callers).
81
- const driftFindings = input.driftFindings ?? [];
82
- if (driftFindings.length > 0) {
83
- lines.push(" drift advisory:");
84
- for (const finding of driftFindings) {
85
- lines.push(` - ${finding.lane}: intent ${finding.intentProvider}/${finding.intentModel} vs observed ${finding.observedProvider}/${finding.observedModel}`);
86
- lines.push(` repair: ${finding.repairCommand}`);
87
- }
88
- }
89
77
  (0, runtime_1.emitNervesEvent)({
90
78
  component: "daemon",
91
79
  event: "daemon.inner_status_read",
@@ -95,7 +83,6 @@ function buildInnerStatusOutput(input) {
95
83
  status: runtimeState?.status ?? "unknown",
96
84
  journalCount: journalFiles.length,
97
85
  attentionCount,
98
- driftCount: driftFindings.length,
99
86
  },
100
87
  });
101
88
  return lines.join("\n");
@@ -44,9 +44,6 @@ 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_credentials_1 = require("../provider-credentials");
49
- const provider_state_1 = require("../provider-state");
50
47
  const hatch_specialist_1 = require("./hatch-specialist");
51
48
  function requiredCredentialKeys(provider) {
52
49
  return identity_1.PROVIDER_CREDENTIALS[provider].required;
@@ -134,22 +131,6 @@ function writeHatchlingAgentConfig(bundleRoot, input) {
134
131
  template.enabled = true;
135
132
  fs.writeFileSync(path.join(bundleRoot, "agent.json"), `${JSON.stringify(template, null, 2)}\n`, "utf-8");
136
133
  }
137
- function writeHatchlingProviderState(bundleRoot, input, now) {
138
- const model = (0, provider_models_1.getDefaultModelForProvider)(input.provider);
139
- const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)({
140
- homeDir: (0, provider_credentials_1.providerCredentialMachineHomeDir)(),
141
- now: () => now,
142
- });
143
- const state = (0, provider_state_1.bootstrapProviderStateFromAgentConfig)({
144
- machineId: machine.machineId,
145
- now,
146
- agentConfig: {
147
- humanFacing: { provider: input.provider, model },
148
- agentFacing: { provider: input.provider, model },
149
- },
150
- });
151
- (0, provider_state_1.writeProviderState)(bundleRoot, state);
152
- }
153
134
  async function runHatchFlow(input, deps = {}) {
154
135
  (0, runtime_1.emitNervesEvent)({
155
136
  component: "daemon",
@@ -186,7 +167,6 @@ async function runHatchFlow(input, deps = {}) {
186
167
  writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
187
168
  writeHatchlingAgentConfig(bundleRoot, input);
188
169
  const credentialPath = await storeHatchlingProviderCredentials(input.agentName, input.provider, input.credentials);
189
- writeHatchlingProviderState(bundleRoot, input, now);
190
170
  writeDiaryScaffold(bundleRoot);
191
171
  writeFriendImprint(bundleRoot, input.humanName, now);
192
172
  writeHeartbeatHabit(bundleRoot, now);
@@ -1,16 +1,55 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.normalizeProviderLane = normalizeProviderLane;
4
37
  exports.resolveEffectiveProviderBinding = resolveEffectiveProviderBinding;
38
+ const path = __importStar(require("path"));
5
39
  const runtime_1 = require("../nerves/runtime");
40
+ const identity_1 = require("./identity");
41
+ const auth_flow_1 = require("./auth/auth-flow");
6
42
  const provider_credentials_1 = require("./provider-credentials");
7
- const provider_state_1 = require("./provider-state");
43
+ const provider_readiness_cache_1 = require("./provider-readiness-cache");
8
44
  function legacyLaneWarning(selector, lane) {
9
45
  return {
10
46
  code: "legacy-lane-selector",
11
47
  message: `${selector} is legacy provider wording; using ${lane} lane`,
12
48
  };
13
49
  }
50
+ function facingKeyForProviderLane(lane) {
51
+ return lane === "outward" ? "humanFacing" : "agentFacing";
52
+ }
14
53
  function normalizeProviderLane(selector) {
15
54
  switch (selector) {
16
55
  case "outward":
@@ -25,12 +64,10 @@ function normalizeProviderLane(selector) {
25
64
  return { lane: "inner", warnings: [legacyLaneWarning(selector, "inner")] };
26
65
  }
27
66
  }
28
- function buildUseRepair(agentName, lane, force = false) {
67
+ function buildUseRepair(agentName, lane) {
29
68
  return {
30
- command: `ouro use --agent ${agentName} --lane ${lane} --provider <provider> --model <model>${force ? " --force" : ""}`,
31
- message: force
32
- ? `Rewrite this machine's ${lane} provider binding for ${agentName}.`
33
- : `Choose the provider/model this machine should use for ${agentName}'s ${lane} lane.`,
69
+ command: `ouro use --agent ${agentName} --lane ${lane} --provider <provider> --model <model>`,
70
+ message: `Choose the provider/model ${agentName}'s ${lane} lane should use in agent.json.`,
34
71
  };
35
72
  }
36
73
  function buildAuthRepair(agentName, provider) {
@@ -39,16 +76,10 @@ function buildAuthRepair(agentName, provider) {
39
76
  message: `Store ${provider} credentials in ${agentName}'s vault.`,
40
77
  };
41
78
  }
42
- function missingProviderStateWarning(agentName) {
43
- return {
44
- code: "provider-state-missing",
45
- message: `No local provider binding exists for ${agentName} on this machine.`,
46
- };
47
- }
48
- function invalidProviderStateWarning(agentName) {
79
+ function invalidAgentConfigWarning(agentName, error) {
49
80
  return {
50
- code: "provider-state-invalid",
51
- message: `Local provider binding state for ${agentName} is invalid.`,
81
+ code: "agent-config-invalid",
82
+ message: `agent.json provider selection for ${agentName} is invalid: ${error}`,
52
83
  };
53
84
  }
54
85
  function missingCredentialWarning(provider) {
@@ -121,120 +152,101 @@ function resolveCredential(poolResult, provider, agentName) {
121
152
  warnings: [missingCredentialWarning(provider)],
122
153
  };
123
154
  }
124
- function readinessFromState(readiness) {
125
- return {
126
- status: readiness.status,
127
- checkedAt: readiness.checkedAt,
128
- credentialRevision: readiness.credentialRevision,
129
- error: readiness.error,
130
- attempts: readiness.attempts,
131
- };
155
+ function resolveReadiness(agentName, lane, provider, model, credential) {
156
+ if (credential.status === "missing") {
157
+ return { status: "unknown", reason: "credential-missing" };
158
+ }
159
+ if (credential.status === "invalid-pool") {
160
+ return { status: "unknown", reason: "credential-pool-invalid" };
161
+ }
162
+ if (credential.status === "present") {
163
+ const cached = (0, provider_readiness_cache_1.readProviderLaneReadiness)({
164
+ agentName,
165
+ lane,
166
+ provider,
167
+ model,
168
+ credentialRevision: credential.revision,
169
+ });
170
+ if (cached) {
171
+ return {
172
+ status: cached.status,
173
+ checkedAt: cached.checkedAt,
174
+ ...(cached.error ? { error: cached.error } : {}),
175
+ ...(cached.attempts !== undefined ? { attempts: cached.attempts } : {}),
176
+ };
177
+ }
178
+ }
179
+ return { status: "unknown" };
132
180
  }
133
- function staleReadiness(readiness, reason) {
134
- return {
135
- ...readinessFromState(readiness),
136
- status: "stale",
137
- previousStatus: readiness.status,
138
- reason,
139
- };
181
+ function isAgentProvider(value) {
182
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
140
183
  }
141
- function resolveReadiness(input) {
142
- if (!input.readiness) {
143
- if (input.credential.status === "not-loaded") {
144
- return { readiness: { status: "unknown" }, warnings: [] };
145
- }
146
- if (input.credential.status === "missing") {
147
- return { readiness: { status: "unknown", reason: "credential-missing" }, warnings: [] };
184
+ function resolveAgentConfigLane(input, lane) {
185
+ const configPath = path.join(input.agentRoot, "agent.json");
186
+ try {
187
+ const { config, configPath: resolvedConfigPath } = (0, auth_flow_1.readAgentConfigForAgent)(input.agentName, path.dirname(input.agentRoot));
188
+ const facingKey = facingKeyForProviderLane(lane);
189
+ const binding = config[facingKey];
190
+ /* v8 ignore next -- readAgentConfigForAgent rejects unsupported providers before this defensive guard @preserve */
191
+ if (!isAgentProvider(binding.provider)) {
192
+ return { ok: false, configPath: resolvedConfigPath, error: `${facingKey}.provider must be a supported provider` };
148
193
  }
149
- if (input.credential.status === "invalid-pool") {
150
- return { readiness: { status: "unknown", reason: "credential-pool-invalid" }, warnings: [] };
194
+ const model = typeof binding.model === "string" ? binding.model.trim() : "";
195
+ if (model.length === 0) {
196
+ return { ok: false, configPath: resolvedConfigPath, error: `${facingKey}.model must be a non-empty string` };
151
197
  }
152
- return { readiness: { status: "unknown" }, warnings: [] };
153
- }
154
- if (input.readiness.provider !== input.provider || input.readiness.model !== input.model) {
155
198
  return {
156
- readiness: staleReadiness(input.readiness, "provider-model-changed"),
157
- warnings: [{
158
- code: "readiness-stale",
159
- message: `${input.provider}/${input.model} readiness is stale because the last check was for ${input.readiness.provider}/${input.readiness.model}.`,
160
- }],
161
- };
162
- }
163
- if (input.credential.status === "present"
164
- && input.readiness.credentialRevision !== undefined
165
- && input.readiness.credentialRevision !== input.credential.revision) {
166
- return {
167
- readiness: staleReadiness(input.readiness, "credential-revision-changed"),
168
- warnings: [{
169
- code: "readiness-stale",
170
- message: `${input.provider}/${input.model} readiness is stale because credential revision changed from ${input.readiness.credentialRevision} to ${input.credential.revision}.`,
171
- }],
172
- };
173
- }
174
- if (input.credential.status === "missing") {
175
- return {
176
- readiness: staleReadiness(input.readiness, "credential-missing"),
177
- warnings: [],
199
+ ok: true,
200
+ configPath: resolvedConfigPath,
201
+ provider: binding.provider,
202
+ model,
178
203
  };
179
204
  }
180
- if (input.credential.status === "invalid-pool") {
205
+ catch (error) {
181
206
  return {
182
- readiness: staleReadiness(input.readiness, "credential-pool-invalid"),
183
- warnings: [],
207
+ ok: false,
208
+ configPath,
209
+ /* v8 ignore next -- readAgentConfigForAgent/file IO failures are Error instances in supported runtimes @preserve */
210
+ error: error instanceof Error ? error.message : String(error),
184
211
  };
185
212
  }
186
- if (input.credential.status === "not-loaded") {
187
- return { readiness: readinessFromState(input.readiness), warnings: [] };
188
- }
189
- return { readiness: readinessFromState(input.readiness), warnings: [] };
190
213
  }
191
214
  function resolveEffectiveProviderBinding(input) {
192
215
  const laneResolution = normalizeProviderLane(input.lane);
193
- const stateResult = (0, provider_state_1.readProviderState)(input.agentRoot);
194
- if (!stateResult.ok) {
195
- const reason = stateResult.reason === "missing" ? "provider-state-missing" : "provider-state-invalid";
196
- const stateWarning = stateResult.reason === "missing"
197
- ? missingProviderStateWarning(input.agentName)
198
- : invalidProviderStateWarning(input.agentName);
216
+ const agentConfigResult = resolveAgentConfigLane(input, laneResolution.lane);
217
+ if (!agentConfigResult.ok) {
199
218
  const result = {
200
219
  ok: false,
201
220
  lane: laneResolution.lane,
202
- reason,
203
- statePath: stateResult.statePath,
204
- warnings: [...laneResolution.warnings, stateWarning],
205
- repair: buildUseRepair(input.agentName, laneResolution.lane, stateResult.reason === "invalid"),
221
+ reason: "agent-config-invalid",
222
+ configPath: agentConfigResult.configPath,
223
+ error: agentConfigResult.error,
224
+ warnings: [...laneResolution.warnings, invalidAgentConfigWarning(input.agentName, agentConfigResult.error)],
225
+ repair: buildUseRepair(input.agentName, laneResolution.lane),
206
226
  };
207
227
  (0, runtime_1.emitNervesEvent)({
208
228
  component: "config/identity",
209
229
  event: "config.provider_binding_resolution_failed",
210
230
  message: "provider binding resolution failed",
211
- meta: { agentName: input.agentName, lane: laneResolution.lane, reason },
231
+ meta: { agentName: input.agentName, lane: laneResolution.lane, reason: result.reason },
212
232
  });
213
233
  return result;
214
234
  }
215
- const laneBinding = stateResult.state.lanes[laneResolution.lane];
216
235
  const poolResult = (0, provider_credentials_1.readProviderCredentialPool)(input.agentName);
217
- const credentialResult = resolveCredential(poolResult, laneBinding.provider, input.agentName);
218
- const readinessResult = resolveReadiness({
219
- provider: laneBinding.provider,
220
- model: laneBinding.model,
221
- readiness: stateResult.state.readiness[laneResolution.lane],
222
- credential: credentialResult.credential,
223
- });
236
+ const credentialResult = resolveCredential(poolResult, agentConfigResult.provider, input.agentName);
237
+ const readiness = resolveReadiness(input.agentName, laneResolution.lane, agentConfigResult.provider, agentConfigResult.model, credentialResult.credential);
224
238
  const warnings = [
225
239
  ...laneResolution.warnings,
226
240
  ...credentialResult.warnings,
227
- ...readinessResult.warnings,
228
241
  ];
229
242
  const binding = {
230
243
  lane: laneResolution.lane,
231
- provider: laneBinding.provider,
232
- model: laneBinding.model,
233
- source: laneBinding.source,
234
- machineId: stateResult.state.machineId,
235
- statePath: stateResult.statePath,
244
+ provider: agentConfigResult.provider,
245
+ model: agentConfigResult.model,
246
+ source: "agent.json",
247
+ configPath: agentConfigResult.configPath,
236
248
  credential: credentialResult.credential,
237
- readiness: readinessResult.readiness,
249
+ readiness,
238
250
  warnings,
239
251
  };
240
252
  (0, runtime_1.emitNervesEvent)({
@@ -372,13 +372,13 @@ async function upsertProviderCredential(input) {
372
372
  password: JSON.stringify(payload),
373
373
  notes: "Ouro provider credentials. The vault item password is a versioned JSON payload.",
374
374
  });
375
- input.onProgress?.(`refreshing local provider snapshot from ${input.agentName}'s vault...`);
375
+ input.onProgress?.(`refreshing in-memory provider credential pool from ${input.agentName}'s vault...`);
376
376
  const refreshResult = await refreshProviderCredentialPool(input.agentName, {
377
377
  providers: [input.provider],
378
378
  onProgress: input.onProgress,
379
379
  });
380
380
  if (!refreshResult.ok) {
381
- throw new Error(`credential stored in vault, but the local provider snapshot could not be refreshed: ${refreshResult.error}. ` +
381
+ throw new Error(`credential stored in vault, but the in-memory provider credential pool could not be refreshed: ${refreshResult.error}. ` +
382
382
  `Run 'ouro provider refresh --agent ${input.agentName}' after fixing vault access, then run 'ouro auth verify --agent ${input.agentName}'.`);
383
383
  }
384
384
  (0, runtime_1.emitNervesEvent)({
@@ -268,7 +268,7 @@ async function runMachineProviderFailoverInventory(agentName, currentProvider, o
268
268
  }
269
269
  /**
270
270
  * Re-verify a failover candidate is actually reachable right before we mutate
271
- * provider state. The inventory ping that produced the candidate may be stale
271
+ * agent.json. The inventory ping that produced the candidate may be stale
272
272
  * (creds revoked between inventory and reply); without this preflight, an
273
273
  * agent-driven "switch to <provider>" can move the lane onto an unreachable
274
274
  * provider and brick the next turn.
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recordProviderLaneReadiness = recordProviderLaneReadiness;
4
+ exports.readProviderLaneReadiness = readProviderLaneReadiness;
5
+ exports.clearProviderReadinessCache = clearProviderReadinessCache;
6
+ const runtime_1 = require("../nerves/runtime");
7
+ const readinessByLane = new Map();
8
+ function cacheKey(agentName, lane) {
9
+ return `${agentName}\0${lane}`;
10
+ }
11
+ function recordProviderLaneReadiness(entry) {
12
+ readinessByLane.set(cacheKey(entry.agentName, entry.lane), { ...entry });
13
+ (0, runtime_1.emitNervesEvent)({
14
+ component: "config/identity",
15
+ event: "config.provider_readiness_recorded",
16
+ message: "recorded in-memory provider readiness",
17
+ meta: {
18
+ agentName: entry.agentName,
19
+ lane: entry.lane,
20
+ provider: entry.provider,
21
+ model: entry.model,
22
+ status: entry.status,
23
+ },
24
+ });
25
+ }
26
+ function readProviderLaneReadiness(input) {
27
+ const entry = readinessByLane.get(cacheKey(input.agentName, input.lane));
28
+ if (!entry)
29
+ return null;
30
+ if (entry.provider !== input.provider)
31
+ return null;
32
+ if (entry.model !== input.model)
33
+ return null;
34
+ if (entry.credentialRevision !== input.credentialRevision)
35
+ return null;
36
+ return { ...entry };
37
+ }
38
+ function clearProviderReadinessCache() {
39
+ readinessByLane.clear();
40
+ }
@@ -128,12 +128,12 @@ function formatProviderVisibilityLine(lane) {
128
128
  function formatAgentProviderVisibilityForPrompt(visibility) {
129
129
  if (visibility.lanes.every((lane) => lane.status === "unconfigured")) {
130
130
  return [
131
- "provider bindings are not configured on this machine.",
131
+ "provider bindings are not configured in agent.json.",
132
132
  ...visibility.lanes.map((lane) => `- ${formatProviderVisibilityLine(lane)}`),
133
133
  ].join("\n");
134
134
  }
135
135
  return [
136
- "runtime uses local provider bindings for this machine:",
136
+ "runtime uses provider bindings from agent.json:",
137
137
  ...visibility.lanes.map((lane) => `- ${formatProviderVisibilityLine(lane)}`),
138
138
  ].join("\n");
139
139
  }
@@ -228,7 +228,7 @@ function renderStartOfTurnPacket(packet) {
228
228
  // are actionable "fix your git" signals. bundleState is preferred
229
229
  // because it's structured (array of enum values) while syncFailure is
230
230
  // a legacy free-form string; both render when populated.
231
- { label: "provider", content: packet.providerState ?? "", priority: 8 },
231
+ { label: "provider", content: packet.providerSelection ?? "", priority: 8 },
232
232
  { label: "bundleState", content: (0, bundle_state_1.renderBundleStateHint)(packet.bundleState ?? []), priority: 7 },
233
233
  { label: "syncFailure", content: packet.syncFailure ?? "", priority: 7 },
234
234
  { label: "resume", content: packet.resumeHint, priority: 6 },
@@ -47,12 +47,12 @@ const commands_1 = require("./commands");
47
47
  const continuity_1 = require("./continuity");
48
48
  const manager_1 = require("../heart/bridges/manager");
49
49
  const identity_1 = require("../heart/identity");
50
+ const auth_flow_1 = require("../heart/auth/auth-flow");
50
51
  const socket_client_1 = require("../heart/daemon/socket-client");
51
52
  const active_work_1 = require("../heart/active-work");
52
53
  const delegation_1 = require("../heart/delegation");
53
54
  const obligations_1 = require("../arc/obligations");
54
55
  const provider_failover_1 = require("../heart/provider-failover");
55
- const provider_state_1 = require("../heart/provider-state");
56
56
  const tempo_1 = require("../heart/tempo");
57
57
  const temporal_view_1 = require("../heart/temporal-view");
58
58
  const start_of_turn_packet_1 = require("../heart/start-of-turn-packet");
@@ -88,17 +88,13 @@ function providerLaneForChannel(channel) {
88
88
  return channel === "inner" ? "inner" : "outward";
89
89
  }
90
90
  function resolveCurrentFailoverBinding(agentName, lane) {
91
- const stateResult = (0, provider_state_1.readProviderState)((0, identity_1.getAgentRoot)(agentName));
92
- if (stateResult.ok) {
93
- const binding = stateResult.state.lanes[lane];
94
- return { provider: binding.provider, model: binding.model };
95
- }
96
- const agentConfig = (0, identity_1.loadAgentConfig)();
91
+ const agentRoot = (0, identity_1.getAgentRoot)();
92
+ const { config: agentConfig } = (0, auth_flow_1.readAgentConfigForAgent)(agentName, path.dirname(agentRoot));
97
93
  const fallback = lane === "inner" ? agentConfig.agentFacing : agentConfig.humanFacing;
98
94
  return { provider: fallback.provider, model: fallback.model };
99
95
  }
100
96
  /**
101
- * Apply an agent-driven failover switch to provider state, but only after
97
+ * Apply an agent-driven failover switch to agent.json, but only after
102
98
  * re-pinging the candidate. The inventory ping that produced the candidate
103
99
  * may be stale by the time the agent replies — without this preflight, a
104
100
  * "switch to <provider>" reply can move the lane onto an unreachable provider.
@@ -109,38 +105,23 @@ function resolveCurrentFailoverBinding(agentName, lane) {
109
105
  * surface the refusal to the agent
110
106
  * Throws on disk errors only (caught by caller as before).
111
107
  */
112
- async function writeFailoverProviderStateSwitch(agentName, action) {
108
+ async function writeFailoverAgentConfigSwitch(agentName, action) {
113
109
  const validation = await (0, provider_failover_1.validateFailoverSwitchCandidate)(agentName, { provider: action.provider, model: action.model });
114
110
  if (!validation.ok) {
115
111
  return { ok: false, refused: true, classification: validation.classification, message: validation.message };
116
112
  }
117
- const agentRoot = (0, identity_1.getAgentRoot)(agentName);
118
- const stateResult = (0, provider_state_1.readProviderState)(agentRoot);
119
- if (!stateResult.ok) {
120
- throw new Error(`Cannot switch ${action.lane} lane for ${agentName}: ${stateResult.error}`);
121
- }
122
- const updatedAt = new Date().toISOString();
123
- const lanes = { ...stateResult.state.lanes };
124
- lanes[action.lane] = {
125
- provider: action.provider,
126
- model: action.model,
127
- source: "local",
128
- updatedAt,
129
- };
130
- const readiness = { ...stateResult.state.readiness };
131
- readiness[action.lane] = {
132
- status: "ready",
133
- provider: action.provider,
134
- model: action.model,
135
- checkedAt: updatedAt,
136
- ...(action.credentialRevision ? { credentialRevision: action.credentialRevision } : {}),
113
+ const agentRoot = (0, identity_1.getAgentRoot)();
114
+ const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(agentName, path.dirname(agentRoot));
115
+ const facingKey = action.lane === "inner" ? "agentFacing" : "humanFacing";
116
+ const nextConfig = {
117
+ ...config,
118
+ [facingKey]: {
119
+ ...config[facingKey],
120
+ provider: action.provider,
121
+ model: action.model,
122
+ },
137
123
  };
138
- (0, provider_state_1.writeProviderState)(agentRoot, {
139
- ...stateResult.state,
140
- updatedAt,
141
- lanes,
142
- readiness,
143
- });
124
+ fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
144
125
  return { ok: true };
145
126
  }
146
127
  function formatFailoverSwitchLabel(action) {
@@ -219,7 +200,7 @@ async function handleInboundTurn(input) {
219
200
  if (failoverAction.action === "switch") {
220
201
  let switchOutcome = null;
221
202
  try {
222
- switchOutcome = await writeFailoverProviderStateSwitch(failoverAgentName, failoverAction);
203
+ switchOutcome = await writeFailoverAgentConfigSwitch(failoverAgentName, failoverAction);
223
204
  /* v8 ignore start -- defensive: write failure during provider switch @preserve */
224
205
  }
225
206
  catch (switchError) {
@@ -499,7 +480,7 @@ async function handleInboundTurn(input) {
499
480
  startOfTurnPacket.syncFailure = syncFailure;
500
481
  }
501
482
  if (ctx.providerVisibility) {
502
- startOfTurnPacket.providerState = (0, provider_visibility_1.formatAgentProviderVisibilityForStartOfTurn)(ctx.providerVisibility);
483
+ startOfTurnPacket.providerSelection = (0, provider_visibility_1.formatAgentProviderVisibilityForStartOfTurn)(ctx.providerVisibility);
503
484
  }
504
485
  // Structured bundle state detection — surfaces discrete issues the
505
486
  // agent can remediate via the bundle_* tools. Runs independently of
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.553",
3
+ "version": "0.1.0-alpha.554",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -1,54 +0,0 @@
1
- # diagnose-bootstrap-drift
2
-
3
- Bootstrap drift fires when the agent's declared intent (`agent.json`) and its observed runtime binding (`state/providers.json`) disagree on provider or model.
4
-
5
- ## Inputs from the finding inventory
6
-
7
- The user message includes a `driftFindings` JSON block when drift was detected during boot. Each entry is a `DriftFinding` from `src/heart/daemon/drift-detection.ts`:
8
-
9
- ```ts
10
- interface DriftFinding {
11
- agent: string
12
- lane: "outward" | "inner" // outward = human-facing, inner = agent-to-agent
13
- intentProvider: string // what agent.json declares
14
- intentModel: string // what agent.json declares
15
- observedProvider: string // what state/providers.json records
16
- observedModel: string // what state/providers.json records
17
- reason: "provider-model-changed"
18
- repairCommand: string // copy-pasteable `ouro use ...` invocation
19
- }
20
- ```
21
-
22
- A finding fires when `intentProvider !== observedProvider` OR `intentModel !== observedModel`. `state/providers.json` missing entirely is treated as "no observation, nothing to drift against" (initialization in flight, not drift) and emits no finding.
23
-
24
- ## Diagnosis
25
-
26
- Each `DriftFinding` indicates per-lane disagreement. Compare the intent and observed fields to characterize the situation:
27
-
28
- | Pattern | What it means | Proposed action |
29
- |---|---|---|
30
- | `intentProvider !== observedProvider` | Different providers on the same lane | `provider-use` pinning the intent provider+model |
31
- | `intentProvider === observedProvider` AND `intentModel !== observedModel` | Same provider, different model | `provider-use` pinning the intent model |
32
- | Either side carries an unexpected/legacy value | Likely stale state from a pre-rename bootstrap | `provider-use` with `--force` to rewrite the binding |
33
-
34
- The `repairCommand` field on each finding already contains the canonical `ouro use --agent X --lane Y --provider Z --model M` invocation that resolves the drift. Surface that command in the proposal; the operator runs it after confirmation in `interactive-repair.ts`.
35
-
36
- ## Proposed action shape
37
-
38
- ```json
39
- {
40
- "kind": "provider-use",
41
- "agent": "slugger",
42
- "lane": "outward",
43
- "provider": "anthropic",
44
- "model": "claude-opus-4-7",
45
- "reason": "drift on outward lane: agent.json declares anthropic/claude-opus-4-7 but state/providers.json recorded openai-codex/claude-sonnet-4.6"
46
- }
47
- ```
48
-
49
- The `kind: "provider-use"` action is one of the seven typed `RepairAction` variants in `src/heart/daemon/readiness-repair.ts`; the parser in `agentic-repair.ts:parseRepairProposals` validates it.
50
-
51
- ## When NOT to fire
52
-
53
- - `driftFindings` is empty / not present in the user message — nothing to diagnose.
54
- - A finding's `intentProvider` or `intentModel` is empty / missing — the intent itself is malformed; surface in `notes` rather than proposing an action.