@ouro.bot/cli 0.1.0-alpha.352 → 0.1.0-alpha.353

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.353",
6
+ "changes": [
7
+ "Provider failover now uses the machine-wide credential pool and local provider-state lanes: terminal errors build ready-provider choices with safe credential provenance, `switch to <provider>` carries lane/model/revision context, and the retry path updates only the failed `state/providers.json` lane instead of mutating both synced `agent.json` facings."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.352",
6
12
  "changes": [
@@ -9,6 +15,16 @@
9
15
  "Autonomous execution prompt contract added: when told to work autonomously, agents use ponder to absorb new messages and continue using tools, settling only with the final result."
10
16
  ]
11
17
  },
18
+ {
19
+ "version": "0.1.0-alpha.351",
20
+ "changes": [
21
+ "Surface tool description rewritten from 'surface progress' to 'send a message to someone' — makes it clear the tool is for interpersonal messaging, not status reporting.",
22
+ "Inner dialog prompt contract now guides agents to use rest(note) for heartbeat state and ponder(reflection) for deeper thoughts, keeping surface strictly for words meant for another person.",
23
+ "Removed [surfaced from inner dialog] prefix from synthetic session messages — provenance is tracked via captureKind: 'synthetic', the prefix was redundant and created echo loops.",
24
+ "Obligation summaries and attention queue headers reframed as structured internal data ([internal] tags) instead of surface-ready prose.",
25
+ "Shared proactive-content-guard module blocks internal content (heartbeat, check-in, task board, obligation status, meta markers) from BlueBubbles and Teams proactive sends."
26
+ ]
27
+ },
12
28
  {
13
29
  "version": "0.1.0-alpha.350",
14
30
  "changes": [
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatCredentialProvenanceLabel = formatCredentialProvenanceLabel;
4
+ exports.formatReadyProviderLabel = formatReadyProviderLabel;
3
5
  exports.buildFailoverContext = buildFailoverContext;
4
6
  exports.handleFailoverReply = handleFailoverReply;
7
+ exports.runMachineProviderFailoverInventory = runMachineProviderFailoverInventory;
8
+ const identity_1 = require("./identity");
9
+ const provider_ping_1 = require("./provider-ping");
5
10
  const provider_models_1 = require("./provider-models");
11
+ const provider_credential_pool_1 = require("./provider-credential-pool");
6
12
  const runtime_1 = require("../nerves/runtime");
7
13
  const CLASSIFICATION_LABELS = {
8
14
  "auth-failure": "authentication failed",
@@ -26,6 +32,23 @@ function formatErrorDetail(errorMessage, errorSummary) {
26
32
  return "";
27
33
  return detail.length > 300 ? `${detail.slice(0, 297)}...` : detail;
28
34
  }
35
+ function formatCredentialProvenanceLabel(candidate) {
36
+ if (candidate.contributedByAgent && candidate.source) {
37
+ return `credentials from ${candidate.contributedByAgent} via ${candidate.source}`;
38
+ }
39
+ if (candidate.contributedByAgent) {
40
+ return `credentials from ${candidate.contributedByAgent}`;
41
+ }
42
+ if (candidate.source) {
43
+ return `credentials from this machine via ${candidate.source}`;
44
+ }
45
+ return undefined;
46
+ }
47
+ function formatReadyProviderLabel(candidate) {
48
+ const provenance = formatCredentialProvenanceLabel(candidate);
49
+ const provenanceSuffix = provenance ? `; ${provenance}` : "";
50
+ return `${candidate.provider} (${candidate.model}${provenanceSuffix})`;
51
+ }
29
52
  function formatFailingProviderLine(provider, classification, agentName) {
30
53
  const authCommand = `ouro auth --agent ${agentName} --provider ${provider}`;
31
54
  switch (classification) {
@@ -43,27 +66,64 @@ function formatFailingProviderLine(provider, classification, agentName) {
43
66
  return ` - ${provider}: could not be reached. Run \`${authCommand}\` if credentials may be stale.`;
44
67
  }
45
68
  }
46
- function buildFailoverContext(errorMessage, classification, currentProvider, currentModel, agentName, inventory, providerModels) {
47
- const label = CLASSIFICATION_LABELS[classification];
48
- const providerWithModel = formatProviderWithModel(currentProvider, currentModel);
49
- const errorSummary = `${providerWithModel} ${label}`;
50
- const errorDetail = formatErrorDetail(errorMessage, errorSummary);
51
- const modelMismatch = (0, provider_models_1.getProviderModelMismatchMessage)(currentProvider, currentModel);
52
- const workingProviders = [];
53
- const unconfiguredProviders = [];
54
- const failingProviders = [];
69
+ function isProviderFailoverInventory(inventory) {
70
+ const candidate = inventory;
71
+ return Array.isArray(candidate.ready) && Array.isArray(candidate.unavailable) && Array.isArray(candidate.unconfigured);
72
+ }
73
+ function normalizeLegacyInventory(inventory, providerModels) {
74
+ const ready = [];
75
+ const unavailable = [];
76
+ const unconfigured = [];
55
77
  for (const [provider, result] of Object.entries(inventory)) {
56
78
  if (result.ok) {
57
- workingProviders.push(provider);
79
+ ready.push({
80
+ provider,
81
+ model: (0, provider_models_1.resolveModelForProviderDisplay)(provider, providerModels[provider]),
82
+ result,
83
+ });
58
84
  }
59
85
  else if (result.classification === "auth-failure" && result.message === "no credentials configured") {
60
- unconfiguredProviders.push(provider);
86
+ unconfigured.push(provider);
61
87
  }
62
88
  else {
63
89
  // Configured but ping failed (expired token, provider also down, etc.)
64
- failingProviders.push({ provider, classification: result.classification });
90
+ unavailable.push({
91
+ provider,
92
+ model: (0, provider_models_1.resolveModelForProviderDisplay)(provider, providerModels[provider]),
93
+ result,
94
+ });
65
95
  }
66
96
  }
97
+ return { ready, unavailable, unconfigured };
98
+ }
99
+ function normalizeFailoverInventory(inventory, providerModels) {
100
+ if (!isProviderFailoverInventory(inventory)) {
101
+ return normalizeLegacyInventory(inventory, providerModels);
102
+ }
103
+ return {
104
+ ready: inventory.ready.map((candidate) => ({
105
+ ...candidate,
106
+ model: (0, provider_models_1.resolveModelForProviderDisplay)(candidate.provider, candidate.model),
107
+ })),
108
+ unavailable: inventory.unavailable.map((candidate) => ({
109
+ ...candidate,
110
+ model: candidate.model ? (0, provider_models_1.resolveModelForProviderDisplay)(candidate.provider, candidate.model) : undefined,
111
+ })),
112
+ unconfigured: [...inventory.unconfigured],
113
+ };
114
+ }
115
+ function buildFailoverContext(errorMessage, classification, currentProvider, currentModel, agentName, inventory, providerModels, options = {}) {
116
+ const currentLane = options.currentLane ?? "outward";
117
+ const label = CLASSIFICATION_LABELS[classification];
118
+ const providerWithModel = formatProviderWithModel(currentProvider, currentModel);
119
+ const errorSummary = `${providerWithModel} ${label}`;
120
+ const errorDetail = formatErrorDetail(errorMessage, errorSummary);
121
+ const modelMismatch = (0, provider_models_1.getProviderModelMismatchMessage)(currentProvider, currentModel);
122
+ const normalizedInventory = normalizeFailoverInventory(inventory, providerModels);
123
+ const readyProviders = normalizedInventory.ready;
124
+ const workingProviders = readyProviders.map((candidate) => candidate.provider);
125
+ const unconfiguredProviders = normalizedInventory.unconfigured;
126
+ const failingProviders = normalizedInventory.unavailable;
67
127
  const lines = [`${errorSummary}.`];
68
128
  if (errorDetail) {
69
129
  lines.push(`provider detail: ${errorDetail}`);
@@ -79,22 +139,20 @@ function buildFailoverContext(errorMessage, classification, currentProvider, cur
79
139
  lines.push("Config warning:");
80
140
  lines.push(` - ${modelMismatch}`);
81
141
  lines.push(" - Repair the configured model with:");
82
- lines.push(` \`ouro config model --agent ${agentName} --facing human ${defaultModel}\``);
83
- lines.push(` \`ouro config model --agent ${agentName} --facing agent ${defaultModel}\``);
142
+ lines.push(` \`ouro use --agent ${agentName} --lane ${currentLane} --provider ${currentProvider} --model ${defaultModel}\``);
84
143
  }
85
- if (workingProviders.length > 0) {
144
+ if (readyProviders.length > 0) {
86
145
  lines.push("");
87
146
  lines.push("Ready providers:");
88
- for (const provider of workingProviders) {
89
- const model = (0, provider_models_1.resolveModelForProviderDisplay)(provider, providerModels[provider]);
90
- lines.push(` - ${provider} (${model}): reply "switch to ${provider}"`);
147
+ for (const candidate of readyProviders) {
148
+ lines.push(` - ${formatReadyProviderLabel(candidate)}: reply "switch to ${candidate.provider}"`);
91
149
  }
92
150
  }
93
151
  if (failingProviders.length > 0) {
94
152
  lines.push("");
95
153
  lines.push("Configured but unavailable:");
96
- for (const { provider, classification } of failingProviders) {
97
- lines.push(formatFailingProviderLine(provider, classification, agentName));
154
+ for (const candidate of failingProviders) {
155
+ lines.push(formatFailingProviderLine(candidate.provider, candidate.result.classification, agentName));
98
156
  }
99
157
  }
100
158
  if (unconfiguredProviders.length > 0) {
@@ -112,24 +170,105 @@ function buildFailoverContext(errorMessage, classification, currentProvider, cur
112
170
  component: "engine",
113
171
  event: "engine.failover_context_built",
114
172
  message: "built provider failover context",
115
- meta: { currentProvider, classification, workingProviders, unconfiguredProviders },
173
+ meta: { currentProvider, currentLane, classification, workingProviders, unconfiguredProviders },
116
174
  });
117
175
  return {
118
176
  errorSummary,
119
177
  classification,
120
178
  currentProvider,
179
+ currentLane,
121
180
  agentName,
122
181
  workingProviders,
182
+ readyProviders,
123
183
  unconfiguredProviders,
124
184
  userMessage: lines.join("\n"),
125
185
  };
126
186
  }
127
187
  function handleFailoverReply(reply, context) {
128
188
  const lower = reply.toLowerCase().trim();
129
- for (const provider of context.workingProviders) {
130
- if (lower.includes(`switch to ${provider}`) || lower === provider) {
131
- return { action: "switch", provider };
189
+ const readyProviders = context.readyProviders ?? context.workingProviders.map((provider) => ({
190
+ provider,
191
+ model: (0, provider_models_1.resolveModelForProviderDisplay)(provider),
192
+ }));
193
+ const currentLane = context.currentLane ?? "outward";
194
+ for (const candidate of readyProviders) {
195
+ if (lower.includes(`switch to ${candidate.provider}`) || lower === candidate.provider) {
196
+ return {
197
+ action: "switch",
198
+ provider: candidate.provider,
199
+ model: candidate.model,
200
+ lane: currentLane,
201
+ ...(candidate.credentialRevision ? { credentialRevision: candidate.credentialRevision } : {}),
202
+ ...(candidate.source ? { source: candidate.source } : {}),
203
+ ...(candidate.contributedByAgent ? { contributedByAgent: candidate.contributedByAgent } : {}),
204
+ };
132
205
  }
133
206
  }
134
207
  return { action: "dismiss" };
135
208
  }
209
+ function candidateFromCredentialRecord(record) {
210
+ return {
211
+ provider: record.provider,
212
+ credentialRevision: record.revision,
213
+ source: record.provenance.source,
214
+ contributedByAgent: record.provenance.contributedByAgent,
215
+ };
216
+ }
217
+ async function runMachineProviderFailoverInventory(agentName, currentProvider, options = {}) {
218
+ const ping = options.ping ?? provider_ping_1.pingProvider;
219
+ const poolResult = (0, provider_credential_pool_1.readProviderCredentialPool)(options.homeDir);
220
+ const providers = Object.keys(identity_1.PROVIDER_CREDENTIALS).filter((provider) => provider !== currentProvider);
221
+ const inventory = { ready: [], unavailable: [], unconfigured: [] };
222
+ if (!poolResult.ok) {
223
+ inventory.unconfigured.push(...providers);
224
+ (0, runtime_1.emitNervesEvent)({
225
+ component: "engine",
226
+ event: "engine.machine_failover_inventory_built",
227
+ message: "built machine provider failover inventory",
228
+ meta: { agentName, currentProvider, credentialPoolStatus: poolResult.reason, readyCount: 0, unavailableCount: 0, unconfiguredCount: inventory.unconfigured.length },
229
+ });
230
+ return inventory;
231
+ }
232
+ const results = await Promise.all(providers.map(async (provider) => {
233
+ const record = poolResult.pool.providers[provider];
234
+ if (!record)
235
+ return { provider, record: undefined, result: undefined };
236
+ const model = (0, provider_models_1.getDefaultModelForProvider)(provider);
237
+ const config = { ...record.credentials, ...record.config };
238
+ const result = await ping(provider, config, { model });
239
+ return { provider, record, model, result };
240
+ }));
241
+ for (const entry of results) {
242
+ if (!entry.record) {
243
+ inventory.unconfigured.push(entry.provider);
244
+ }
245
+ else if (entry.result.ok) {
246
+ inventory.ready.push({
247
+ ...candidateFromCredentialRecord(entry.record),
248
+ model: entry.model,
249
+ result: entry.result,
250
+ });
251
+ }
252
+ else {
253
+ inventory.unavailable.push({
254
+ ...candidateFromCredentialRecord(entry.record),
255
+ model: entry.model,
256
+ result: entry.result,
257
+ });
258
+ }
259
+ }
260
+ (0, runtime_1.emitNervesEvent)({
261
+ component: "engine",
262
+ event: "engine.machine_failover_inventory_built",
263
+ message: "built machine provider failover inventory",
264
+ meta: {
265
+ agentName,
266
+ currentProvider,
267
+ credentialPoolStatus: "present",
268
+ readyCount: inventory.ready.length,
269
+ unavailableCount: inventory.unavailable.length,
270
+ unconfiguredCount: inventory.unconfigured.length,
271
+ },
272
+ });
273
+ return inventory;
274
+ }
@@ -52,9 +52,7 @@ const active_work_1 = require("../heart/active-work");
52
52
  const delegation_1 = require("../heart/delegation");
53
53
  const obligations_1 = require("../arc/obligations");
54
54
  const provider_failover_1 = require("../heart/provider-failover");
55
- const provider_ping_1 = require("../heart/provider-ping");
56
- const auth_flow_1 = require("../heart/auth/auth-flow");
57
- const provider_models_1 = require("../heart/provider-models");
55
+ const provider_state_1 = require("../heart/provider-state");
58
56
  const tempo_1 = require("../heart/tempo");
59
57
  const temporal_view_1 = require("../heart/temporal-view");
60
58
  const start_of_turn_packet_1 = require("../heart/start-of-turn-packet");
@@ -85,6 +83,52 @@ function emitObligationTransitionEpisodes(agentRoot, preTurnObligationIds, postT
85
83
  }
86
84
  }
87
85
  }
86
+ function providerLaneForChannel(channel) {
87
+ return channel === "inner" ? "inner" : "outward";
88
+ }
89
+ function resolveCurrentFailoverBinding(agentName, lane) {
90
+ const stateResult = (0, provider_state_1.readProviderState)((0, identity_1.getAgentRoot)(agentName));
91
+ if (stateResult.ok) {
92
+ const binding = stateResult.state.lanes[lane];
93
+ return { provider: binding.provider, model: binding.model };
94
+ }
95
+ const agentConfig = (0, identity_1.loadAgentConfig)();
96
+ const fallback = lane === "inner" ? agentConfig.agentFacing : agentConfig.humanFacing;
97
+ return { provider: fallback.provider, model: fallback.model };
98
+ }
99
+ function writeFailoverProviderStateSwitch(agentName, action) {
100
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
101
+ const stateResult = (0, provider_state_1.readProviderState)(agentRoot);
102
+ if (!stateResult.ok) {
103
+ throw new Error(`Cannot switch ${action.lane} lane for ${agentName}: ${stateResult.error}`);
104
+ }
105
+ const updatedAt = new Date().toISOString();
106
+ const lanes = { ...stateResult.state.lanes };
107
+ lanes[action.lane] = {
108
+ provider: action.provider,
109
+ model: action.model,
110
+ source: "local",
111
+ updatedAt,
112
+ };
113
+ const readiness = { ...stateResult.state.readiness };
114
+ readiness[action.lane] = {
115
+ status: "ready",
116
+ provider: action.provider,
117
+ model: action.model,
118
+ checkedAt: updatedAt,
119
+ ...(action.credentialRevision ? { credentialRevision: action.credentialRevision } : {}),
120
+ };
121
+ (0, provider_state_1.writeProviderState)(agentRoot, {
122
+ ...stateResult.state,
123
+ updatedAt,
124
+ lanes,
125
+ readiness,
126
+ });
127
+ }
128
+ function formatFailoverSwitchLabel(action) {
129
+ const provenance = (0, provider_failover_1.formatCredentialProvenanceLabel)(action);
130
+ return `${action.provider} (${action.model}${provenance ? `; ${provenance}` : ""})`;
131
+ }
88
132
  function prependTurnSections(message, sections) {
89
133
  /* v8 ignore next -- defensive: only user messages with non-empty sections reach here @preserve */
90
134
  if (message.role !== "user" || sections.length === 0)
@@ -133,8 +177,7 @@ async function handleInboundTurn(input) {
133
177
  if (failoverAction.action === "switch") {
134
178
  let switchSucceeded = false;
135
179
  try {
136
- (0, auth_flow_1.writeAgentProviderSelection)(failoverAgentName, "human", failoverAction.provider);
137
- (0, auth_flow_1.writeAgentProviderSelection)(failoverAgentName, "agent", failoverAction.provider);
180
+ writeFailoverProviderStateSwitch(failoverAgentName, failoverAction);
138
181
  switchSucceeded = true;
139
182
  /* v8 ignore start -- defensive: write failure during provider switch @preserve */
140
183
  }
@@ -143,8 +186,8 @@ async function handleInboundTurn(input) {
143
186
  level: "error",
144
187
  component: "senses",
145
188
  event: "senses.failover_switch_error",
146
- message: `failed to switch provider to ${failoverAction.provider}`,
147
- meta: { agentName: failoverAgentName, provider: failoverAction.provider, error: switchError instanceof Error ? switchError.message : String(switchError) },
189
+ message: `failed to switch ${failoverAction.lane} provider lane to ${failoverAction.provider}`,
190
+ meta: { agentName: failoverAgentName, lane: failoverAction.lane, provider: failoverAction.provider, model: failoverAction.model, error: switchError instanceof Error ? switchError.message : String(switchError) },
148
191
  });
149
192
  }
150
193
  /* v8 ignore stop */
@@ -153,28 +196,24 @@ async function handleInboundTurn(input) {
153
196
  (0, runtime_1.emitNervesEvent)({
154
197
  component: "senses",
155
198
  event: "senses.failover_switch",
156
- message: `switched provider to ${failoverAction.provider} via failover`,
157
- meta: { agentName: failoverAgentName, provider: failoverAction.provider },
199
+ message: `switched ${failoverAction.lane} provider lane to ${failoverAction.provider} via failover`,
200
+ meta: {
201
+ agentName: failoverAgentName,
202
+ lane: failoverAction.lane,
203
+ provider: failoverAction.provider,
204
+ model: failoverAction.model,
205
+ credentialRevision: failoverAction.credentialRevision,
206
+ source: failoverAction.source,
207
+ contributedByAgent: failoverAction.contributedByAgent,
208
+ },
158
209
  });
159
210
  // Replace "switch to <provider>" with a context message for the agent.
160
211
  // The session already has the user's original question from the failed turn.
161
212
  // The agent needs to know what happened so it can respond appropriately.
162
- const newProviderSecrets = (() => {
163
- try {
164
- const { secrets } = (0, auth_flow_1.loadAgentSecrets)(failoverAgentName);
165
- const cfg = secrets.providers[failoverAction.provider];
166
- const hint = cfg?.model ?? cfg?.modelName;
167
- return (0, provider_models_1.resolveModelForProviderDisplay)(failoverAction.provider, typeof hint === "string" ? hint : "");
168
- /* v8 ignore next 2 -- defensive: secrets read failure @preserve */
169
- }
170
- catch {
171
- return (0, provider_models_1.resolveModelForProviderDisplay)(failoverAction.provider);
172
- }
173
- })();
174
- const newProviderLabel = `${failoverAction.provider} (${newProviderSecrets})`;
213
+ const newProviderLabel = formatFailoverSwitchLabel(failoverAction);
175
214
  input.messages = [{
176
215
  role: "user",
177
- content: `[provider switch: ${pendingContext.errorSummary}. switched to ${newProviderLabel}. your conversation history is intact — respond to the user's last message.]`,
216
+ content: `[provider switch: ${pendingContext.errorSummary}. switched ${failoverAction.lane} lane to ${newProviderLabel}. your conversation history is intact — respond to the user's last message.]`,
178
217
  }];
179
218
  input.switchedProvider = failoverAction.provider;
180
219
  }
@@ -465,23 +504,15 @@ async function handleInboundTurn(input) {
465
504
  if (result.outcome === "errored" && input.failoverState) {
466
505
  try {
467
506
  const agentName = (0, identity_1.getAgentName)();
468
- const agentConfig = (0, identity_1.loadAgentConfig)();
469
- const currentProvider = agentConfig.humanFacing.provider;
507
+ const currentLane = providerLaneForChannel(input.channel);
508
+ const currentBinding = resolveCurrentFailoverBinding(agentName, currentLane);
509
+ const currentProvider = currentBinding.provider;
470
510
  /* v8 ignore next -- defensive: errorClassification always set when errored @preserve */
471
511
  const classification = result.errorClassification ?? "unknown";
472
- const inventory = await (0, provider_ping_1.runHealthInventory)(agentName, currentProvider);
473
- const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agentName);
474
- const providerModels = {};
475
- for (const [p, cfg] of Object.entries(secrets.providers)) {
476
- const model = cfg.model ?? cfg.modelName;
477
- if (typeof model === "string" && model)
478
- providerModels[p] = model;
479
- }
480
- // Use agent.json model (source of truth), not secrets model (may be stale)
481
- const currentModel = agentConfig.humanFacing.model;
512
+ const inventory = await (0, provider_failover_1.runMachineProviderFailoverInventory)(agentName, currentProvider);
482
513
  const failoverContext = (0, provider_failover_1.buildFailoverContext)(
483
514
  /* v8 ignore next -- defensive: error always set when errored @preserve */
484
- result.error?.message ?? "unknown error", classification, currentProvider, currentModel, agentName, inventory, providerModels);
515
+ result.error?.message ?? "unknown error", classification, currentProvider, currentBinding.model, agentName, inventory, {}, { currentLane });
485
516
  input.failoverState.pending = failoverContext;
486
517
  input.postTurn(sessionMessages, session.sessionPath, result.usage);
487
518
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.352",
3
+ "version": "0.1.0-alpha.353",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",