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

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 CHANGED
@@ -11,7 +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
- - Secrets live outside the repo at `~/.agentsecrets/<agent>/secrets.json`.
14
+ - Provider credentials live outside the repo at `~/.agentsecrets/providers.json`.
15
+ - Sense-specific secrets live outside the repo at `~/.agentsecrets/<agent>/secrets.json`.
15
16
  - Machine-scoped test and runtime spillover lives under `~/.agentstate/...`.
16
17
 
17
18
  Current first-class senses:
@@ -90,8 +91,10 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
90
91
 
91
92
  ## Runtime Truths
92
93
 
93
- - `agent.json` is the source of truth for provider+model selection per facing (`humanFacing` and `agentFacing`), phrase pools, context settings, and enabled senses.
94
- - `configPath` must point to `~/.agentsecrets/<agent>/secrets.json`.
94
+ - `agent.json` is the source of truth for phrase pools, context settings, enabled senses, and the agent's `configPath`. Legacy `humanFacing`/`agentFacing` values are bootstrap inputs, not live machine fallback.
95
+ - `configPath` must point to `~/.agentsecrets/<agent>/secrets.json` for sense-specific secrets.
96
+ - `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
+ - `~/.agentsecrets/providers.json` is the machine credential pool. It stores provider credentials only, records which agent contributed them, and is shared by all agents on that machine.
95
98
  - The daemon discovers bundles dynamically from `~/AgentBundles`.
96
99
  - `ouro status` reports version, last-updated time, discovered agents, senses, and workers.
97
100
  - `bundle-meta.json` tracks the runtime version that last touched a bundle.
@@ -104,17 +107,22 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
104
107
  - `running`
105
108
  - `error`
106
109
 
107
- When a model provider needs first-time setup, reauth, or an explicit switch, use:
110
+ When a model provider needs first-time setup or reauth, use:
108
111
 
109
112
  ```bash
110
113
  ouro auth --agent <name>
111
114
  ouro auth --agent <name> --provider <provider>
112
- ouro auth --agent <name> --facing agent --provider <provider>
113
115
  ```
114
116
 
115
- The default form reauths the human-facing provider already selected in `agent.json`. The
116
- `--provider` form is for adding or switching providers. The `--facing` flag (values: `human`
117
- or `agent`) controls which facing gets updated; it defaults to `human` when omitted.
117
+ `ouro auth` stores credentials only. It does not switch a lane or write provider/model selection.
118
+
119
+ When you want this machine to use a provider/model for a lane, use:
120
+
121
+ ```bash
122
+ ouro use --agent <name> --lane <outward|inner> --provider <provider> --model <model>
123
+ ```
124
+
125
+ 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.
118
126
 
119
127
  ## Quickstart
120
128
 
@@ -159,6 +167,7 @@ ouro logs
159
167
  ouro stop
160
168
  ouro auth --agent <name>
161
169
  ouro auth --agent <name> --provider <provider>
170
+ ouro use --agent <name> --lane <outward|inner> --provider <provider> --model <model>
162
171
  ouro hatch
163
172
  ouro chat <agent>
164
173
  ouro msg --to <agent> [--session <id>] [--task <ref>] <message>
package/changelog.json CHANGED
@@ -1,6 +1,13 @@
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.354",
6
+ "changes": [
7
+ "Provider visibility now surfaces the effective local outward/inner lanes across start-of-turn context, system prompts, pulse, Outlook, and daemon status, using the shared provider binding resolver with safe credential provenance and no raw secret exposure.",
8
+ "Provider auth docs now state the runtime model plainly: `state/providers.json` selects provider/model per machine, `~/.agentsecrets/providers.json` stores machine credentials, `ouro use` switches lanes, and `ouro auth` stores credentials only."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.353",
6
13
  "changes": [
@@ -75,15 +75,18 @@ function parseStatusPayload(data) {
75
75
  const workers = raw.workers;
76
76
  const sync = raw.sync;
77
77
  const agents = raw.agents;
78
+ const providers = raw.providers;
78
79
  if (!overview || typeof overview !== "object" || Array.isArray(overview))
79
80
  return null;
80
81
  if (!Array.isArray(senses) || !Array.isArray(workers))
81
82
  return null;
82
- // sync and agents are optional for backward compatibility — older daemons may omit them
83
+ // sync, agents, and providers are optional for backward compatibility — older daemons may omit them
83
84
  if (sync !== undefined && !Array.isArray(sync))
84
85
  return null;
85
86
  if (agents !== undefined && !Array.isArray(agents))
86
87
  return null;
88
+ if (providers !== undefined && !Array.isArray(providers))
89
+ return null;
87
90
  const parsedOverview = {
88
91
  daemon: stringField(overview.daemon) ?? "unknown",
89
92
  health: stringField(overview.health) ?? "unknown",
@@ -172,10 +175,38 @@ function parseStatusPayload(data) {
172
175
  return null;
173
176
  return { name, enabled };
174
177
  });
178
+ const parsedProviders = (providers ?? []).map((entry) => {
179
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
180
+ return null;
181
+ const row = entry;
182
+ const agent = stringField(row.agent);
183
+ const lane = stringField(row.lane);
184
+ const provider = stringField(row.provider);
185
+ const model = stringField(row.model);
186
+ const source = stringField(row.source);
187
+ const readiness = stringField(row.readiness);
188
+ const credential = stringField(row.credential);
189
+ if (!agent || !lane || !provider || !model || !source || !readiness || !credential)
190
+ return null;
191
+ const parsed = {
192
+ agent,
193
+ lane,
194
+ provider,
195
+ model,
196
+ source,
197
+ readiness,
198
+ credential,
199
+ };
200
+ const detail = stringField(row.detail);
201
+ if (detail !== null)
202
+ parsed.detail = detail;
203
+ return parsed;
204
+ });
175
205
  if (parsedSenses.some((row) => row === null) ||
176
206
  parsedWorkers.some((row) => row === null) ||
177
207
  parsedSync.some((row) => row === null) ||
178
- parsedAgents.some((row) => row === null))
208
+ parsedAgents.some((row) => row === null) ||
209
+ parsedProviders.some((row) => row === null))
179
210
  return null;
180
211
  return {
181
212
  overview: parsedOverview,
@@ -183,6 +214,7 @@ function parseStatusPayload(data) {
183
214
  workers: parsedWorkers,
184
215
  sync: parsedSync,
185
216
  agents: parsedAgents,
217
+ providers: parsedProviders,
186
218
  };
187
219
  }
188
220
  // ── ANSI color helpers (private) ──
@@ -208,12 +240,15 @@ function statusDot(status) {
208
240
  case "ok":
209
241
  case "interactive":
210
242
  case "enabled":
243
+ case "ready":
211
244
  return green("●");
212
245
  case "crashed":
213
246
  case "warn":
214
247
  case "error":
248
+ case "failed":
215
249
  return red("●");
216
250
  case "needs_config":
251
+ case "stale":
217
252
  return yellow("●");
218
253
  case "disabled":
219
254
  case "stopped":
@@ -291,6 +326,18 @@ function formatDaemonStatusOutput(response, fallback) {
291
326
  }
292
327
  lines.push("");
293
328
  }
329
+ // ── Providers ──
330
+ if (payload.providers.length > 0) {
331
+ lines.push(` ${teal("──")} ${bold("Providers")} ${teal("─".repeat(34))}`);
332
+ const agentLaneWidth = Math.max(16, ...payload.providers.map((r) => `${r.agent} ${r.lane}`.length));
333
+ for (const row of payload.providers) {
334
+ const agentLane = `${row.agent} ${row.lane}`.padEnd(agentLaneWidth);
335
+ const model = `${row.provider} / ${row.model}`;
336
+ const detail = [row.readiness, row.detail, row.source, row.credential].filter(Boolean).join("; ");
337
+ lines.push(` ${agentLane} ${statusDot(row.readiness)} ${model} ${dim(detail)}`);
338
+ }
339
+ lines.push("");
340
+ }
294
341
  // ── Senses ──
295
342
  if (payload.senses.length > 0) {
296
343
  lines.push(` ${teal("──")} ${bold("Senses")} ${teal("─".repeat(37))}`);
@@ -421,6 +468,7 @@ function buildStoppedStatusPayload(socketPath, syncRows = [], agentRows = []) {
421
468
  workers: [],
422
469
  sync: syncRows,
423
470
  agents: agentRows,
471
+ providers: [],
424
472
  };
425
473
  }
426
474
  function daemonUnavailableStatusOutput(socketPath, healthFilePath) {
@@ -64,6 +64,7 @@ const outlook_http_1 = require("../outlook/outlook-http");
64
64
  const outlook_types_1 = require("../outlook/outlook-types");
65
65
  const outlook_read_1 = require("../outlook/outlook-read");
66
66
  const outlook_view_1 = require("../outlook/outlook-view");
67
+ const provider_visibility_1 = require("../provider-visibility");
67
68
  const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
68
69
  /**
69
70
  * Defense-in-depth: detect if we're running under vitest. The pidfile lives
@@ -427,6 +428,10 @@ class OuroDaemon {
427
428
  const repoRoot = (0, identity_1.getRepoRoot)();
428
429
  const sync = (0, agent_discovery_1.listBundleSyncRows)({ bundlesRoot: this.bundlesRoot });
429
430
  const agents = (0, agent_discovery_1.listAllBundleAgents)({ bundlesRoot: this.bundlesRoot });
431
+ const providers = agents.flatMap((agent) => (0, provider_visibility_1.providerVisibilityStatusRows)((0, provider_visibility_1.buildAgentProviderVisibility)({
432
+ agentName: agent.name,
433
+ agentRoot: path.join(this.bundlesRoot, `${agent.name}.ouro`),
434
+ })));
430
435
  return {
431
436
  overview: {
432
437
  daemon: "running",
@@ -443,6 +448,7 @@ class OuroDaemon {
443
448
  senses,
444
449
  sync,
445
450
  agents,
451
+ ...(providers.length > 0 ? { providers } : {}),
446
452
  };
447
453
  }
448
454
  async start() {
@@ -52,6 +52,7 @@ const fs = __importStar(require("fs"));
52
52
  const os = __importStar(require("os"));
53
53
  const path = __importStar(require("path"));
54
54
  const runtime_1 = require("../../nerves/runtime");
55
+ const provider_visibility_1 = require("../provider-visibility");
55
56
  /* v8 ignore next 3 -- path defaults: tests always inject @preserve */
56
57
  function defaultPulsePath() {
57
58
  return path.join(os.homedir(), ".ouro-cli", "pulse.json");
@@ -136,7 +137,7 @@ function readAgentActivity(bundlePath, readFile = (p) => fs.readFileSync(p, "utf
136
137
  * directly, we just compute where it lives so sibling agents have a
137
138
  * starting point if they want to navigate there manually.
138
139
  */
139
- function buildPulseState(snapshots, bundlesRoot, daemonVersion, now, readActivity = readAgentActivity) {
140
+ function buildPulseState(snapshots, bundlesRoot, daemonVersion, now, readActivity = readAgentActivity, readProviderVisibility = () => null) {
140
141
  const agents = snapshots.map((snap) => {
141
142
  const errorReason = snap.errorReason;
142
143
  const bundlePath = path.join(bundlesRoot, `${snap.name}.ouro`);
@@ -151,6 +152,7 @@ function buildPulseState(snapshots, bundlesRoot, daemonVersion, now, readActivit
151
152
  // Only read activity for agents that are actually running. For
152
153
  // crashed/stopped agents, the runtime.json is stale at best.
153
154
  currentActivity: snap.status === "running" ? readActivity(bundlePath) : null,
155
+ providerVisibility: readProviderVisibility(snap.name, bundlePath),
154
156
  };
155
157
  });
156
158
  return {
@@ -294,7 +296,17 @@ function readPulse(deps = {}) {
294
296
  return {
295
297
  generatedAt: parsed.generatedAt,
296
298
  daemonVersion: parsed.daemonVersion,
297
- agents: parsed.agents.filter(isValidPulseAgentEntry),
299
+ agents: parsed.agents.filter(isValidPulseAgentEntry).map((agent) => {
300
+ const rawAgent = agent;
301
+ if (!Object.prototype.hasOwnProperty.call(rawAgent, "providerVisibility"))
302
+ return agent;
303
+ return {
304
+ ...agent,
305
+ providerVisibility: (0, provider_visibility_1.isAgentProviderVisibility)(rawAgent.providerVisibility)
306
+ ? rawAgent.providerVisibility
307
+ : null,
308
+ };
309
+ }),
298
310
  };
299
311
  }
300
312
  catch {
@@ -390,7 +402,7 @@ function pruneDeliveredState(delivered, state) {
390
402
  * for production callers.
391
403
  */
392
404
  function flushPulse(deps) {
393
- const state = buildPulseState(deps.snapshots, deps.bundlesRoot, deps.daemonVersion, deps.now);
405
+ const state = buildPulseState(deps.snapshots, deps.bundlesRoot, deps.daemonVersion, deps.now, readAgentActivity, (agentName, bundlePath) => (0, provider_visibility_1.buildAgentProviderVisibility)({ agentName, agentRoot: bundlePath }));
394
406
  /* v8 ignore start -- dep defaults: production daemon path; the arrow functions only fire when the corresponding dep is omitted, which only happens in production code paths. Tests inject all deps explicitly. @preserve */
395
407
  const readPrev = deps.readPrev ?? (() => readPulse());
396
408
  const writeNext = deps.writeNext ?? ((s) => writePulse(s));
@@ -173,6 +173,7 @@ function buildOutlookAgentView(input) {
173
173
  agentRoot: input.agent.agentRoot,
174
174
  enabled: input.agent.enabled,
175
175
  provider: input.agent.provider,
176
+ providers: input.agent.providers ?? null,
176
177
  senses: input.agent.senses,
177
178
  freshness: input.agent.freshness,
178
179
  degraded: input.agent.degraded,
@@ -46,6 +46,7 @@ const session_activity_1 = require("../../session-activity");
46
46
  const identity_1 = require("../../identity");
47
47
  const agent_discovery_1 = require("../../daemon/agent-discovery");
48
48
  const runtime_metadata_1 = require("../../daemon/runtime-metadata");
49
+ const provider_visibility_1 = require("../../provider-visibility");
49
50
  const thoughts_1 = require("../../daemon/thoughts");
50
51
  const outlook_types_1 = require("../outlook-types");
51
52
  const shared_1 = require("./shared");
@@ -266,6 +267,7 @@ function summarizeAgent(state) {
266
267
  return {
267
268
  agentName: state.agentName,
268
269
  enabled: state.enabled,
270
+ providers: state.providers,
269
271
  freshness: state.freshness,
270
272
  degraded: state.degraded,
271
273
  tasks: {
@@ -296,6 +298,7 @@ function readOutlookAgentState(agentName, options = {}) {
296
298
  issues.push(...inner.issues);
297
299
  const coding = readCodingSummary(agentRoot);
298
300
  issues.push(...coding.issues);
301
+ const providers = (0, provider_visibility_1.buildAgentProviderVisibility)({ agentName, agentRoot, homeDir: options.homeDir });
299
302
  const latestActivityAt = collectLatestActivityTimestamps({
300
303
  obligations: obligations.items,
301
304
  sessions: sessions.items,
@@ -308,6 +311,7 @@ function readOutlookAgentState(agentName, options = {}) {
308
311
  agentRoot,
309
312
  enabled: config.summary.enabled,
310
313
  provider: config.summary.provider,
314
+ providers,
311
315
  senses: config.summary.senses,
312
316
  freshness: summarizeFreshness(latestActivityAt, now),
313
317
  degraded: summarizeDegraded(issues),
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAgentProviderVisibility = buildAgentProviderVisibility;
4
+ exports.formatProviderVisibilityLine = formatProviderVisibilityLine;
5
+ exports.formatAgentProviderVisibilityForPrompt = formatAgentProviderVisibilityForPrompt;
6
+ exports.formatAgentProviderVisibilityForStartOfTurn = formatAgentProviderVisibilityForStartOfTurn;
7
+ exports.formatAgentProviderVisibilityForPulse = formatAgentProviderVisibilityForPulse;
8
+ exports.providerVisibilityStatusRows = providerVisibilityStatusRows;
9
+ exports.isAgentProviderVisibility = isAgentProviderVisibility;
10
+ const runtime_1 = require("../nerves/runtime");
11
+ const provider_binding_resolver_1 = require("./provider-binding-resolver");
12
+ const LANES = ["outward", "inner"];
13
+ function credentialVisibility(binding) {
14
+ const credential = binding.credential;
15
+ if (credential.status === "present") {
16
+ return {
17
+ status: "present",
18
+ source: credential.source,
19
+ contributedByAgent: credential.contributedByAgent,
20
+ revision: credential.revision,
21
+ };
22
+ }
23
+ return {
24
+ status: credential.status,
25
+ repairCommand: credential.repair.command,
26
+ };
27
+ }
28
+ function readinessVisibility(binding) {
29
+ return {
30
+ status: binding.readiness.status,
31
+ ...(binding.readiness.checkedAt ? { checkedAt: binding.readiness.checkedAt } : {}),
32
+ ...(binding.readiness.error ? { error: binding.readiness.error } : {}),
33
+ ...(binding.readiness.reason ? { reason: binding.readiness.reason } : {}),
34
+ ...(binding.readiness.attempts !== undefined ? { attempts: binding.readiness.attempts } : {}),
35
+ };
36
+ }
37
+ function visibilityForLane(input, lane) {
38
+ const resolved = (0, provider_binding_resolver_1.resolveEffectiveProviderBinding)({ ...input, lane });
39
+ if (!resolved.ok) {
40
+ return {
41
+ lane,
42
+ status: "unconfigured",
43
+ provider: "unconfigured",
44
+ model: "-",
45
+ source: "missing",
46
+ readiness: {
47
+ status: "unknown",
48
+ reason: resolved.reason,
49
+ },
50
+ credential: {
51
+ status: "missing",
52
+ repairCommand: resolved.repair.command,
53
+ },
54
+ repairCommand: resolved.repair.command,
55
+ reason: resolved.reason,
56
+ warnings: resolved.warnings.map((warning) => warning.message),
57
+ };
58
+ }
59
+ return {
60
+ lane,
61
+ status: "configured",
62
+ provider: resolved.binding.provider,
63
+ model: resolved.binding.model,
64
+ source: resolved.binding.source,
65
+ readiness: readinessVisibility(resolved.binding),
66
+ credential: credentialVisibility(resolved.binding),
67
+ warnings: resolved.binding.warnings.map((warning) => warning.message),
68
+ };
69
+ }
70
+ function buildAgentProviderVisibility(input) {
71
+ const visibility = {
72
+ agentName: input.agentName,
73
+ lanes: LANES.map((lane) => visibilityForLane(input, lane)),
74
+ };
75
+ (0, runtime_1.emitNervesEvent)({
76
+ component: "config/identity",
77
+ event: "config.provider_visibility_built",
78
+ message: "built provider visibility summary",
79
+ meta: {
80
+ agentName: input.agentName,
81
+ laneStatuses: visibility.lanes.map((lane) => `${lane.lane}:${lane.status}`).join(","),
82
+ },
83
+ });
84
+ return visibility;
85
+ }
86
+ function credentialLabel(credential) {
87
+ if (credential.status === "present") {
88
+ const source = credential.source ?? "unknown";
89
+ return credential.contributedByAgent ? `${source} from ${credential.contributedByAgent}` : source;
90
+ }
91
+ if (credential.status === "invalid-pool")
92
+ return "invalid pool";
93
+ return "missing";
94
+ }
95
+ function readinessLabel(readiness) {
96
+ if (readiness.status === "failed") {
97
+ return readiness.error ? `failed: ${readiness.error}` : "failed";
98
+ }
99
+ if (readiness.status === "stale") {
100
+ return readiness.reason ? `stale: ${readiness.reason}` : "stale";
101
+ }
102
+ if (readiness.status === "unknown") {
103
+ return readiness.reason ? `unknown: ${readiness.reason}` : "unknown";
104
+ }
105
+ return readiness.status;
106
+ }
107
+ function formatProviderVisibilityLine(lane) {
108
+ if (lane.status === "unconfigured") {
109
+ return `${lane.lane}: unconfigured (${lane.reason}); repair: ${lane.repairCommand}`;
110
+ }
111
+ const parts = [
112
+ readinessLabel(lane.readiness),
113
+ `source: ${lane.source}`,
114
+ `credentials: ${credentialLabel(lane.credential)}`,
115
+ ];
116
+ if (lane.credential.revision)
117
+ parts.push(`revision: ${lane.credential.revision}`);
118
+ if (lane.credential.repairCommand)
119
+ parts.push(`repair: ${lane.credential.repairCommand}`);
120
+ if (lane.warnings.length > 0)
121
+ parts.push(`warnings: ${lane.warnings.join("; ")}`);
122
+ return `${lane.lane}: ${lane.provider} / ${lane.model} [${parts.join("; ")}]`;
123
+ }
124
+ function formatAgentProviderVisibilityForPrompt(visibility) {
125
+ if (visibility.lanes.every((lane) => lane.status === "unconfigured")) {
126
+ return [
127
+ "provider bindings are not configured on this machine.",
128
+ ...visibility.lanes.map((lane) => `- ${formatProviderVisibilityLine(lane)}`),
129
+ ].join("\n");
130
+ }
131
+ return [
132
+ "runtime uses local provider bindings for this machine:",
133
+ ...visibility.lanes.map((lane) => `- ${formatProviderVisibilityLine(lane)}`),
134
+ ].join("\n");
135
+ }
136
+ function formatAgentProviderVisibilityForStartOfTurn(visibility) {
137
+ return visibility.lanes.map((lane) => `- ${formatProviderVisibilityLine(lane)}`).join("\n");
138
+ }
139
+ function formatAgentProviderVisibilityForPulse(visibility) {
140
+ return visibility.lanes.map((lane) => formatProviderVisibilityLine(lane)).join("; ");
141
+ }
142
+ function providerVisibilityStatusRows(visibility) {
143
+ return visibility.lanes.map((lane) => {
144
+ if (lane.status === "unconfigured") {
145
+ return {
146
+ agent: visibility.agentName,
147
+ lane: lane.lane,
148
+ provider: "unconfigured",
149
+ model: "-",
150
+ source: "missing",
151
+ readiness: "unknown",
152
+ detail: lane.repairCommand,
153
+ credential: "missing",
154
+ };
155
+ }
156
+ return {
157
+ agent: visibility.agentName,
158
+ lane: lane.lane,
159
+ provider: lane.provider,
160
+ model: lane.model,
161
+ source: lane.source,
162
+ readiness: lane.readiness.status,
163
+ ...(lane.readiness.error ? { detail: lane.readiness.error } : {}),
164
+ credential: credentialLabel(lane.credential),
165
+ };
166
+ });
167
+ }
168
+ function isAgentProviderVisibility(value) {
169
+ if (!value || typeof value !== "object" || Array.isArray(value))
170
+ return false;
171
+ const record = value;
172
+ if (typeof record.agentName !== "string")
173
+ return false;
174
+ if (!Array.isArray(record.lanes))
175
+ return false;
176
+ return record.lanes.every((lane) => {
177
+ if (!lane || typeof lane !== "object" || Array.isArray(lane))
178
+ return false;
179
+ const laneRecord = lane;
180
+ return (laneRecord.lane === "outward" || laneRecord.lane === "inner")
181
+ && (laneRecord.status === "configured" || laneRecord.status === "unconfigured");
182
+ });
183
+ }
@@ -228,6 +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
232
  { label: "bundleState", content: (0, bundle_state_1.renderBundleStateHint)(packet.bundleState ?? []), priority: 7 },
232
233
  { label: "syncFailure", content: packet.syncFailure ?? "", priority: 7 },
233
234
  { label: "resume", content: packet.resumeHint, priority: 6 },
@@ -305,6 +306,9 @@ function formatSections(sections) {
305
306
  case "bundleState":
306
307
  parts.push(`**Bundle:** ${section.content}`);
307
308
  break;
309
+ case "provider":
310
+ parts.push(`**Provider:**\n${section.content}`);
311
+ break;
308
312
  }
309
313
  }
310
314
  return parts.join("\n\n");
@@ -59,6 +59,7 @@ const cares_1 = require("../arc/cares");
59
59
  const config_1 = require("./config");
60
60
  const daemon_health_1 = require("./daemon/daemon-health");
61
61
  const prompt_1 = require("../mind/prompt");
62
+ const provider_visibility_1 = require("./provider-visibility");
62
63
  // ── Helpers ─────────────────────────────────────────────────────────
63
64
  const DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
64
65
  function isLiveCodingSessionStatus(status) {
@@ -306,6 +307,7 @@ async function buildTurnContext(input) {
306
307
  /* v8 ignore stop */
307
308
  }
308
309
  const bundleMeta = readBundleMetaFile();
310
+ const providerVisibility = (0, provider_visibility_1.buildAgentProviderVisibility)({ agentName, agentRoot });
309
311
  let daemonHealth = null;
310
312
  try {
311
313
  daemonHealth = (0, daemon_health_1.readHealth)((0, daemon_health_1.getDefaultHealthPath)());
@@ -333,6 +335,7 @@ async function buildTurnContext(input) {
333
335
  bridgeCount: activeBridges.length,
334
336
  codingSessionCount: codingSessions.length,
335
337
  episodeCount: recentEpisodes.length,
338
+ providerLaneCount: providerVisibility.lanes.length,
336
339
  },
337
340
  });
338
341
  return {
@@ -349,6 +352,7 @@ async function buildTurnContext(input) {
349
352
  activeCares,
350
353
  syncConfig,
351
354
  syncFailure: undefined, // Set by pipeline after preTurnPull
355
+ providerVisibility,
352
356
  daemonRunning,
353
357
  senseStatusLines,
354
358
  bundleMeta,
@@ -78,6 +78,7 @@ const obligation_steering_1 = require("./obligation-steering");
78
78
  const daemon_health_1 = require("../heart/daemon/daemon-health");
79
79
  const scrutiny_1 = require("./scrutiny");
80
80
  const pulse_1 = require("../heart/daemon/pulse");
81
+ const provider_visibility_1 = require("../heart/provider-visibility");
81
82
  // Lazy-loaded psyche text cache
82
83
  let _psycheCache = null;
83
84
  let _senseStatusLinesCache = null;
@@ -459,7 +460,10 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
459
460
  }
460
461
  return lines;
461
462
  }
462
- function providerSection(channel) {
463
+ function providerSection(channel, options) {
464
+ if (options?.providerVisibility) {
465
+ return `## my provider\n${(0, provider_visibility_1.formatAgentProviderVisibilityForPrompt)(options.providerVisibility)}`;
466
+ }
463
467
  return `## my provider\n${(0, core_1.getProviderDisplayLabel)((0, channel_1.channelToFacing)(channel))}`;
464
468
  }
465
469
  function dateSection() {
@@ -727,6 +731,8 @@ function pulseSection(channel = "cli") {
727
731
  if (sib.fixHint)
728
732
  lines.push(` fix: ${sib.fixHint}`);
729
733
  lines.push(` bundle: \`${sib.bundlePath}\``);
734
+ if (sib.providerVisibility)
735
+ lines.push(` provider: ${(0, provider_visibility_1.formatAgentProviderVisibilityForPulse)(sib.providerVisibility)}`);
730
736
  }
731
737
  lines.push("");
732
738
  }
@@ -737,6 +743,8 @@ function pulseSection(channel = "cli") {
737
743
  for (const sib of healthy) {
738
744
  const activity = sib.currentActivity ? ` — ${sib.currentActivity}` : "";
739
745
  lines.push(`- **${sib.name}** is running${activity}. bundle: \`${sib.bundlePath}\``);
746
+ if (sib.providerVisibility)
747
+ lines.push(` provider: ${(0, provider_visibility_1.formatAgentProviderVisibilityForPulse)(sib.providerVisibility)}`);
740
748
  }
741
749
  lines.push("");
742
750
  }
@@ -744,6 +752,8 @@ function pulseSection(channel = "cli") {
744
752
  lines.push("**idle siblings** — configured but not currently running:");
745
753
  for (const sib of idle) {
746
754
  lines.push(`- **${sib.name}** (status: ${sib.status}). bundle: \`${sib.bundlePath}\``);
755
+ if (sib.providerVisibility)
756
+ lines.push(` provider: ${(0, provider_visibility_1.formatAgentProviderVisibilityForPulse)(sib.providerVisibility)}`);
747
757
  }
748
758
  lines.push("");
749
759
  }
@@ -1260,7 +1270,7 @@ async function buildSystem(channel = "cli", options, context) {
1260
1270
  runtimeInfoSection(channel, options),
1261
1271
  rhythmStatusSection(options?.daemonHealth),
1262
1272
  channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
1263
- providerSection(channel),
1273
+ providerSection(channel, options),
1264
1274
  dateSection(),
1265
1275
  // Group 3: my tools & capabilities
1266
1276
  "# my tools & capabilities",
@@ -63,6 +63,7 @@ const session_events_1 = require("../heart/session-events");
63
63
  const presence_1 = require("../arc/presence");
64
64
  const episodes_1 = require("../arc/episodes");
65
65
  const turn_context_1 = require("../heart/turn-context");
66
+ const provider_visibility_1 = require("../heart/provider-visibility");
66
67
  /**
67
68
  * Emit episodes for obligation state transitions detected during a turn.
68
69
  * Exported for direct testability (avoids v8 coverage merge issues in multi-file test suites).
@@ -435,6 +436,9 @@ async function handleInboundTurn(input) {
435
436
  if (syncFailure) {
436
437
  startOfTurnPacket.syncFailure = syncFailure;
437
438
  }
439
+ if (ctx.providerVisibility) {
440
+ startOfTurnPacket.providerState = (0, provider_visibility_1.formatAgentProviderVisibilityForStartOfTurn)(ctx.providerVisibility);
441
+ }
438
442
  // Structured bundle state detection — surfaces discrete issues the
439
443
  // agent can remediate via the bundle_* tools. Runs independently of
440
444
  // syncFailure so the two signals coexist during the transition away
@@ -489,6 +493,7 @@ async function handleInboundTurn(input) {
489
493
  bundleMeta: ctx.bundleMeta,
490
494
  daemonHealth: ctx.daemonHealth,
491
495
  journalFiles: ctx.journalFiles,
496
+ ...(ctx.providerVisibility ? { providerVisibility: ctx.providerVisibility } : {}),
492
497
  toolContext: {
493
498
  /* v8 ignore next -- default no-op signin satisfies interface; real signin injected by sense adapter @preserve */
494
499
  signin: async () => undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.353",
3
+ "version": "0.1.0-alpha.354",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",