@ouro.bot/cli 0.1.0-alpha.335 → 0.1.0-alpha.337

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,19 @@
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.337",
6
+ "changes": [
7
+ "`ouro up` now checks the selected human-facing and agent-facing providers for each discovered agent, so startup reports degraded agents immediately when a configured provider token is missing or fails a live health check.",
8
+ "Provider repair prompts now preserve the failed facing's provider, allowing agent-facing GitHub Copilot, MiniMax, Anthropic, Azure, or OpenAI Codex failures to route to the correct `ouro auth --agent <name> --provider <provider>` command."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.336",
13
+ "changes": [
14
+ "Inner dialogue prompts now advertise the actual inner delivery contract: use `surface` to send thoughts outward and `rest` to end the turn, instead of suggesting unavailable `send_message` or `settle` tools."
15
+ ]
16
+ },
4
17
  {
5
18
  "version": "0.1.0-alpha.335",
6
19
  "changes": [
@@ -34,16 +34,88 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.checkAgentConfig = checkAgentConfig;
37
+ exports.checkAgentConfigWithProviderHealth = checkAgentConfigWithProviderHealth;
37
38
  const fs = __importStar(require("fs"));
38
39
  const path = __importStar(require("path"));
39
40
  const identity_1 = require("../identity");
40
41
  const runtime_1 = require("../../nerves/runtime");
41
- /**
42
- * Pre-spawn validation: ensures agent.json exists and required secrets are present.
43
- * Returns `{ ok: true }` when the agent is ready to run, or a descriptive error
44
- * with an actionable fix message when something is missing.
45
- */
46
- function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
42
+ function isAgentProvider(value) {
43
+ return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
44
+ }
45
+ function formatFacingList(facings) {
46
+ if (facings.length === 1)
47
+ return facings[0];
48
+ return `${facings.slice(0, -1).join(", ")} and ${facings[facings.length - 1]}`;
49
+ }
50
+ function selectedProviderMap(selectedProviders) {
51
+ const byProvider = new Map();
52
+ for (const selected of selectedProviders) {
53
+ const facings = byProvider.get(selected.provider) ?? [];
54
+ facings.push(selected.facing);
55
+ byProvider.set(selected.provider, facings);
56
+ }
57
+ return byProvider;
58
+ }
59
+ function resolveFacingProvider(parsed, facing, agentName, agentJsonPath) {
60
+ const raw = parsed[facing];
61
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
62
+ return {
63
+ ok: false,
64
+ result: {
65
+ ok: false,
66
+ error: `agent.json for '${agentName}' is missing ${facing}.provider`,
67
+ fix: `Add ${facing}: { provider, model } to ${agentJsonPath}. Valid providers: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
68
+ },
69
+ };
70
+ }
71
+ const provider = raw.provider;
72
+ if (typeof provider !== "string" || provider.length === 0) {
73
+ return {
74
+ ok: false,
75
+ result: {
76
+ ok: false,
77
+ error: `agent.json for '${agentName}' is missing ${facing}.provider`,
78
+ fix: `Set ${facing}.provider in ${agentJsonPath}. Valid providers: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
79
+ },
80
+ };
81
+ }
82
+ if (!isAgentProvider(provider)) {
83
+ return {
84
+ ok: false,
85
+ result: {
86
+ ok: false,
87
+ error: `Unknown provider '${provider}' in ${facing}.provider for '${agentName}'`,
88
+ fix: `Set ${facing}.provider to one of: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
89
+ },
90
+ };
91
+ }
92
+ return { ok: true, selected: { facing, provider } };
93
+ }
94
+ function resolveSelectedProviders(parsed, agentName, agentJsonPath) {
95
+ const hasHumanFacing = parsed.humanFacing !== undefined;
96
+ const hasAgentFacing = parsed.agentFacing !== undefined;
97
+ if (!hasHumanFacing && !hasAgentFacing && typeof parsed.provider === "string") {
98
+ if (!isAgentProvider(parsed.provider)) {
99
+ return {
100
+ ok: false,
101
+ result: {
102
+ ok: false,
103
+ error: `Unknown provider '${parsed.provider}' in agent.json for '${agentName}'`,
104
+ fix: `Set provider to one of: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
105
+ },
106
+ };
107
+ }
108
+ return { ok: true, selectedProviders: [{ facing: "provider", provider: parsed.provider }] };
109
+ }
110
+ const human = resolveFacingProvider(parsed, "humanFacing", agentName, agentJsonPath);
111
+ if (!human.ok)
112
+ return human;
113
+ const agent = resolveFacingProvider(parsed, "agentFacing", agentName, agentJsonPath);
114
+ if (!agent.ok)
115
+ return agent;
116
+ return { ok: true, selectedProviders: [human.selected, agent.selected] };
117
+ }
118
+ function readConfigCheckContext(agentName, bundlesRoot, secretsRoot) {
47
119
  const agentJsonPath = path.join(bundlesRoot, `${agentName}.ouro`, "agent.json");
48
120
  let raw;
49
121
  try {
@@ -52,8 +124,11 @@ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
52
124
  catch {
53
125
  return {
54
126
  ok: false,
55
- error: `agent.json not found at ${agentJsonPath}`,
56
- fix: `Run 'ouro hatch ${agentName}' to create the agent bundle, or verify that ${bundlesRoot}/${agentName}.ouro/ exists.`,
127
+ result: {
128
+ ok: false,
129
+ error: `agent.json not found at ${agentJsonPath}`,
130
+ fix: `Run 'ouro hatch ${agentName}' to create the agent bundle, or verify that ${bundlesRoot}/${agentName}.ouro/ exists.`,
131
+ },
57
132
  };
58
133
  }
59
134
  let parsed;
@@ -63,40 +138,28 @@ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
63
138
  catch {
64
139
  return {
65
140
  ok: false,
66
- error: `agent.json at ${agentJsonPath} contains invalid JSON`,
67
- fix: `Open ${agentJsonPath} and fix the JSON syntax.`,
141
+ result: {
142
+ ok: false,
143
+ error: `agent.json at ${agentJsonPath} contains invalid JSON`,
144
+ fix: `Open ${agentJsonPath} and fix the JSON syntax.`,
145
+ },
68
146
  };
69
147
  }
70
148
  // Disabled agents are valid — they just won't run
71
149
  if (parsed.enabled === false) {
72
- return { ok: true };
73
- }
74
- // Resolve provider: humanFacing.provider > config.provider
75
- let provider;
76
- const humanFacing = parsed.humanFacing;
77
- if (humanFacing && typeof humanFacing.provider === "string") {
78
- provider = humanFacing.provider;
79
- }
80
- else if (typeof parsed.provider === "string") {
81
- provider = parsed.provider;
82
- }
83
- if (!provider) {
84
150
  return {
85
- ok: false,
86
- error: `agent.json for '${agentName}' has no provider configured`,
87
- fix: `Add humanFacing.provider to ${agentJsonPath}. Valid providers: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
88
- };
89
- }
90
- const desc = identity_1.PROVIDER_CREDENTIALS[provider];
91
- if (!desc) {
92
- return {
93
- ok: false,
94
- error: `Unknown provider '${provider}' in agent.json for '${agentName}'`,
95
- fix: `Set humanFacing.provider to one of: ${Object.keys(identity_1.PROVIDER_CREDENTIALS).join(", ")}`,
151
+ ok: true,
152
+ context: {
153
+ agentJsonPath,
154
+ secretsJsonPath: path.join(secretsRoot, agentName, "secrets.json"),
155
+ providers: {},
156
+ selectedProviders: [],
157
+ },
96
158
  };
97
159
  }
98
- // Azure with managed identity does not require apiKey — skip secrets check
99
- // when endpoint + deployment are present (managed identity auth)
160
+ const selected = resolveSelectedProviders(parsed, agentName, agentJsonPath);
161
+ if (!selected.ok)
162
+ return selected;
100
163
  const secretsJsonPath = path.join(secretsRoot, agentName, "secrets.json");
101
164
  let secrets;
102
165
  try {
@@ -104,46 +167,126 @@ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
104
167
  secrets = JSON.parse(secretsRaw);
105
168
  }
106
169
  catch {
170
+ const firstProvider = selected.selectedProviders[0].provider;
107
171
  return {
108
172
  ok: false,
109
- error: `secrets.json not found or unreadable at ${secretsJsonPath}`,
110
- fix: `Run 'ouro auth ${agentName}' to configure credentials, or create ${secretsJsonPath} with providers.${provider} credentials.`,
173
+ result: {
174
+ ok: false,
175
+ error: `secrets.json not found or unreadable at ${secretsJsonPath}`,
176
+ fix: `Run 'ouro auth --agent ${agentName} --provider ${firstProvider}' to configure credentials, or create ${secretsJsonPath} with providers.${firstProvider} credentials.`,
177
+ },
111
178
  };
112
179
  }
113
180
  const providers = secrets.providers;
114
- const providerSecrets = providers?.[provider];
115
- if (!providerSecrets) {
181
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) {
182
+ const firstProvider = selected.selectedProviders[0].provider;
116
183
  return {
117
184
  ok: false,
118
- error: `secrets.json for '${agentName}' is missing providers.${provider} section`,
119
- fix: `Run 'ouro auth ${agentName}' to configure ${provider} credentials.`,
185
+ result: {
186
+ ok: false,
187
+ error: `secrets.json for '${agentName}' is missing providers object`,
188
+ fix: `Run 'ouro auth --agent ${agentName} --provider ${firstProvider}' to configure credentials.`,
189
+ },
120
190
  };
121
191
  }
122
- // Azure special case: managed identity only needs endpoint + deployment
123
- if (provider === "azure") {
124
- const hasEndpoint = typeof providerSecrets.endpoint === "string" && providerSecrets.endpoint.length > 0;
125
- const hasDeployment = typeof providerSecrets.deployment === "string" && providerSecrets.deployment.length > 0;
126
- const hasManagedId = typeof providerSecrets.managedIdentityClientId === "string" && providerSecrets.managedIdentityClientId.length > 0;
127
- if (hasEndpoint && hasDeployment && hasManagedId) {
128
- return { ok: true };
192
+ return {
193
+ ok: true,
194
+ context: {
195
+ agentJsonPath,
196
+ secretsJsonPath,
197
+ providers,
198
+ selectedProviders: selected.selectedProviders,
199
+ },
200
+ };
201
+ }
202
+ function validateSelectedProviderSecrets(agentName, context) {
203
+ for (const [provider, facings] of selectedProviderMap(context.selectedProviders)) {
204
+ const desc = identity_1.PROVIDER_CREDENTIALS[provider];
205
+ const providerSecrets = context.providers[provider];
206
+ const selectedBy = formatFacingList(facings);
207
+ if (!providerSecrets) {
208
+ return {
209
+ ok: false,
210
+ error: `secrets.json for '${agentName}' is missing providers.${provider} section selected by ${selectedBy}`,
211
+ fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to configure ${provider} credentials.`,
212
+ };
213
+ }
214
+ // Azure special case: managed identity only needs endpoint + deployment.
215
+ if (provider === "azure") {
216
+ const hasEndpoint = typeof providerSecrets.endpoint === "string" && providerSecrets.endpoint.length > 0;
217
+ const hasDeployment = typeof providerSecrets.deployment === "string" && providerSecrets.deployment.length > 0;
218
+ const hasManagedId = typeof providerSecrets.managedIdentityClientId === "string" && providerSecrets.managedIdentityClientId.length > 0;
219
+ if (hasEndpoint && hasDeployment && hasManagedId) {
220
+ continue;
221
+ }
222
+ }
223
+ const missing = desc.required.filter((field) => {
224
+ const val = providerSecrets[field];
225
+ return typeof val !== "string" || val.length === 0;
226
+ });
227
+ if (missing.length > 0) {
228
+ return {
229
+ ok: false,
230
+ error: `secrets.json for '${agentName}' is missing required ${provider} credentials selected by ${selectedBy}: ${missing.join(", ")}`,
231
+ fix: `Run 'ouro auth --agent ${agentName} --provider ${provider}' to set up ${provider} credentials, or add the missing fields to providers.${provider} in ${context.secretsJsonPath}.`,
232
+ };
129
233
  }
130
234
  }
131
- const missing = desc.required.filter((field) => {
132
- const val = providerSecrets[field];
133
- return typeof val !== "string" || val.length === 0;
134
- });
135
- if (missing.length > 0) {
136
- return {
137
- ok: false,
138
- error: `secrets.json for '${agentName}' is missing required ${provider} credentials: ${missing.join(", ")}`,
139
- fix: `Run 'ouro auth ${agentName}' to set up ${provider} credentials, or add the missing fields to providers.${provider} in ${secretsJsonPath}.`,
140
- };
141
- }
235
+ return { ok: true };
236
+ }
237
+ function emitConfigValid(agentName, context, liveProviderCheck) {
142
238
  (0, runtime_1.emitNervesEvent)({
143
239
  component: "daemon",
144
240
  event: "daemon.agent_config_valid",
145
241
  message: "agent config validation passed",
146
- meta: { agent: agentName, provider },
242
+ meta: {
243
+ agent: agentName,
244
+ providers: [...selectedProviderMap(context.selectedProviders).keys()],
245
+ liveProviderCheck,
246
+ },
147
247
  });
248
+ }
249
+ function providerPingFailureResult(agentName, provider, facings, result) {
250
+ const selectedBy = formatFacingList(facings);
251
+ const authFix = `Run 'ouro auth --agent ${agentName} --provider ${provider}' to refresh credentials.`;
252
+ const verifyFix = `Run 'ouro auth verify --agent ${agentName} --provider ${provider}' for details.`;
253
+ return {
254
+ ok: false,
255
+ error: `selected provider ${provider} for ${selectedBy} failed health check: ${result.message}`,
256
+ fix: `${result.classification === "auth-failure" ? authFix : verifyFix} Or switch the affected facing to a working provider.`,
257
+ };
258
+ }
259
+ /**
260
+ * Pre-spawn validation: ensures agent.json exists and required secrets are present.
261
+ * Returns `{ ok: true }` when the agent is ready to run, or a descriptive error
262
+ * with an actionable fix message when something is missing.
263
+ */
264
+ function checkAgentConfig(agentName, bundlesRoot, secretsRoot) {
265
+ const contextResult = readConfigCheckContext(agentName, bundlesRoot, secretsRoot);
266
+ if (!contextResult.ok)
267
+ return contextResult.result;
268
+ const context = contextResult.context;
269
+ const structural = validateSelectedProviderSecrets(agentName, context);
270
+ if (!structural.ok)
271
+ return structural;
272
+ emitConfigValid(agentName, context, false);
273
+ return { ok: true };
274
+ }
275
+ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, secretsRoot, deps = {}) {
276
+ const contextResult = readConfigCheckContext(agentName, bundlesRoot, secretsRoot);
277
+ if (!contextResult.ok)
278
+ return contextResult.result;
279
+ const context = contextResult.context;
280
+ const structural = validateSelectedProviderSecrets(agentName, context);
281
+ if (!structural.ok)
282
+ return structural;
283
+ const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
284
+ for (const [provider, facings] of selectedProviderMap(context.selectedProviders)) {
285
+ const result = await ping(provider, context.providers[provider]);
286
+ if (!result.ok) {
287
+ return providerPingFailureResult(agentName, provider, facings, result);
288
+ }
289
+ }
290
+ emitConfigValid(agentName, context, true);
148
291
  return { ok: true };
149
292
  }
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.mergeStartupStability = mergeStartupStability;
42
43
  exports.ensureDaemonRunning = ensureDaemonRunning;
43
44
  exports.listGithubCopilotModels = listGithubCopilotModels;
44
45
  exports.pingGithubCopilotModel = pingGithubCopilotModel;
@@ -69,6 +70,7 @@ const cli_parse_2 = require("./cli-parse");
69
70
  const cli_help_1 = require("./cli-help");
70
71
  const cli_render_1 = require("./cli-render");
71
72
  const cli_defaults_1 = require("./cli-defaults");
73
+ const agent_config_check_1 = require("./agent-config-check");
72
74
  const doctor_1 = require("./doctor");
73
75
  const cli_render_doctor_1 = require("./cli-render-doctor");
74
76
  const interactive_repair_1 = require("./interactive-repair");
@@ -82,6 +84,61 @@ const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
82
84
  const DEFAULT_DAEMON_STARTUP_STABILITY_WINDOW_MS = 1_500;
83
85
  const DEFAULT_DAEMON_STARTUP_RETRY_LIMIT = 1;
84
86
  const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
87
+ async function checkAlreadyRunningAgentProviders(deps) {
88
+ const agents = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
89
+ const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
90
+ const secretsRoot = deps.secretsRoot ?? path.join(os.homedir(), ".agentsecrets");
91
+ const degraded = [];
92
+ for (const agent of agents) {
93
+ try {
94
+ const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot, secretsRoot);
95
+ if (result.ok)
96
+ continue;
97
+ const errorReason = result.error ?? "agent provider health check failed";
98
+ const fixHint = result.fix ?? "";
99
+ degraded.push({ agent, errorReason, fixHint });
100
+ (0, runtime_1.emitNervesEvent)({
101
+ level: "error",
102
+ component: "daemon",
103
+ event: "daemon.agent_config_invalid",
104
+ message: errorReason,
105
+ meta: { agent, fix: fixHint, source: "already-running-provider-check" },
106
+ });
107
+ }
108
+ catch (error) {
109
+ const errorReason = error instanceof Error ? error.message : String(error);
110
+ degraded.push({
111
+ agent,
112
+ errorReason,
113
+ fixHint: "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.",
114
+ });
115
+ (0, runtime_1.emitNervesEvent)({
116
+ level: "error",
117
+ component: "daemon",
118
+ event: "daemon.agent_config_invalid",
119
+ message: errorReason,
120
+ meta: { agent, fix: "ouro doctor", source: "already-running-provider-check" },
121
+ });
122
+ }
123
+ }
124
+ return degraded;
125
+ }
126
+ function mergeStartupStability(stability, extraDegraded) {
127
+ if (extraDegraded.length === 0)
128
+ return stability;
129
+ const degradedByAgent = new Map();
130
+ for (const entry of stability?.degraded ?? [])
131
+ degradedByAgent.set(entry.agent, entry);
132
+ for (const entry of extraDegraded)
133
+ degradedByAgent.set(entry.agent, entry);
134
+ const degraded = [...degradedByAgent.values()];
135
+ const stable = [];
136
+ for (const agent of stability?.stable ?? []) {
137
+ if (!degradedByAgent.has(agent))
138
+ stable.push(agent);
139
+ }
140
+ return { stable, degraded };
141
+ }
85
142
  async function ensureDaemonRunning(deps) {
86
143
  const readLatestDaemonStartupEvent = () => {
87
144
  try {
@@ -1097,6 +1154,12 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
1097
1154
  progress.announceStep?.(label);
1098
1155
  },
1099
1156
  });
1157
+ if (daemonResult.alreadyRunning) {
1158
+ progress.startPhase("provider checks");
1159
+ const providerDegraded = await checkAlreadyRunningAgentProviders(deps);
1160
+ daemonResult.stability = mergeStartupStability(daemonResult.stability, providerDegraded);
1161
+ progress.completePhase("provider checks", providerDegraded.length > 0 ? `${providerDegraded.length} degraded` : "ok");
1162
+ }
1100
1163
  progress.end();
1101
1164
  deps.writeStdout(daemonResult.message);
1102
1165
  // Interactive repair for degraded agents (Unit 5) — skipped by --no-repair (Unit 6)
@@ -1159,9 +1222,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
1159
1222
  runInteractiveRepair: interactive_repair_1.runInteractiveRepair,
1160
1223
  promptInput: deps.promptInput ?? (async () => "n"),
1161
1224
  writeStdout: deps.writeStdout,
1162
- runAuthFlow: async (agent) => {
1225
+ runAuthFlow: async (agent, providerOverride) => {
1163
1226
  const { config } = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot);
1164
- const provider = config.humanFacing.provider;
1227
+ const provider = providerOverride ?? config.humanFacing.provider;
1165
1228
  /* v8 ignore next -- tests always inject runAuthFlow; default is for production @preserve */
1166
1229
  const authRunner = deps.runAuthFlow ?? (await Promise.resolve().then(() => __importStar(require("../auth/auth-flow")))).runRuntimeAuthFlow;
1167
1230
  await authRunner({ agentName: agent, provider, promptInput: deps.promptInput });
@@ -99,11 +99,11 @@ const processManager = new process_manager_1.DaemonProcessManager({
99
99
  autoStart: true,
100
100
  })),
101
101
  existsSync: fs.existsSync,
102
- /* v8 ignore next 4 -- wiring: delegates to checkAgentConfig which has full unit tests @preserve */
103
- configCheck: (agent) => {
102
+ /* v8 ignore next 4 -- wiring: delegates to checkAgentConfigWithProviderHealth which has full unit tests @preserve */
103
+ configCheck: async (agent) => {
104
104
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
105
105
  const secretsRoot = path.join(os.homedir(), ".agentsecrets");
106
- return (0, agent_config_check_1.checkAgentConfig)(agent, bundlesRoot, secretsRoot);
106
+ return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot, secretsRoot);
107
107
  },
108
108
  /* v8 ignore start -- pulse flush wiring: integration code; flushPulse itself has full unit tests @preserve */
109
109
  onSnapshotChange: () => {
@@ -8,6 +8,7 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.runInteractiveRepair = runInteractiveRepair;
10
10
  const runtime_1 = require("../../nerves/runtime");
11
+ const identity_1 = require("../identity");
11
12
  function isCredentialIssue(degraded) {
12
13
  const reason = degraded.errorReason.toLowerCase();
13
14
  const hint = degraded.fixHint.toLowerCase();
@@ -16,6 +17,20 @@ function isCredentialIssue(degraded) {
16
17
  function isConfigError(degraded) {
17
18
  return degraded.fixHint.length > 0 && !isCredentialIssue(degraded);
18
19
  }
20
+ function isAgentProvider(value) {
21
+ return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
22
+ }
23
+ function extractProviderFromFixHint(fixHint) {
24
+ const provider = fixHint.match(/--provider\s+([a-z0-9-]+)/)?.[1]
25
+ ?? fixHint.match(/providers\.([a-z0-9-]+)/)?.[1];
26
+ if (!provider || !isAgentProvider(provider))
27
+ return undefined;
28
+ return provider;
29
+ }
30
+ function authCommandFor(degraded) {
31
+ const command = degraded.fixHint.match(/ouro auth[^\n.]+/)?.[0]?.trim();
32
+ return command && command.length > 0 ? command : `ouro auth --agent ${degraded.agent}`;
33
+ }
19
34
  async function runInteractiveRepair(degraded, deps) {
20
35
  (0, runtime_1.emitNervesEvent)({
21
36
  level: "info",
@@ -30,10 +45,17 @@ async function runInteractiveRepair(degraded, deps) {
30
45
  let repairsAttempted = false;
31
46
  for (const entry of degraded) {
32
47
  if (isCredentialIssue(entry)) {
33
- const answer = await deps.promptInput(`run \`ouro auth ${entry.agent}\` now? [y/n] `);
48
+ const provider = extractProviderFromFixHint(entry.fixHint);
49
+ const authCommand = authCommandFor(entry);
50
+ const answer = await deps.promptInput(`run \`${authCommand}\` now? [y/n] `);
34
51
  if (answer.toLowerCase() === "y") {
35
52
  try {
36
- await deps.runAuthFlow(entry.agent);
53
+ if (provider) {
54
+ await deps.runAuthFlow(entry.agent, provider);
55
+ }
56
+ else {
57
+ await deps.runAuthFlow(entry.agent);
58
+ }
37
59
  repairsAttempted = true;
38
60
  }
39
61
  catch (error) {
@@ -157,7 +157,7 @@ class DaemonProcessManager {
157
157
  state.stopRequested = false;
158
158
  state.snapshot.status = "starting";
159
159
  if (this.configCheckFn) {
160
- const result = this.configCheckFn(agent);
160
+ const result = await this.configCheckFn(agent);
161
161
  if (!result.ok) {
162
162
  state.snapshot.status = "crashed";
163
163
  // Surface the error and fix to the snapshot so sibling agents can
@@ -13,6 +13,7 @@ const auth_flow_1 = require("./auth/auth-flow");
13
13
  const provider_models_1 = require("./provider-models");
14
14
  const runtime_1 = require("../nerves/runtime");
15
15
  const PING_TIMEOUT_MS = 10_000;
16
+ const DEFAULT_AZURE_API_VERSION = "2025-04-01-preview";
16
17
  const PING_CALLBACKS = {
17
18
  onModelStart() { },
18
19
  onModelStreamStart() { },
@@ -54,25 +55,34 @@ function sanitizeErrorMessage(message) {
54
55
  }
55
56
  function hasEmptyCredentials(provider, config) {
56
57
  const record = config;
58
+ if (provider === "azure") {
59
+ const hasManagedIdentity = typeof record.endpoint === "string" && record.endpoint.length > 0 &&
60
+ typeof record.deployment === "string" && record.deployment.length > 0 &&
61
+ typeof record.managedIdentityClientId === "string" && record.managedIdentityClientId.length > 0;
62
+ if (hasManagedIdentity)
63
+ return false;
64
+ }
57
65
  return identity_1.PROVIDER_CREDENTIALS[provider].required.some((key) => !record[key]);
58
66
  }
59
- function createRuntimeForPing(provider, _config) {
60
- // The provider constructors read credentials from the active config via getXxxConfig().
61
- // For ping, this is acceptable because verifyProviderCredentials patches the runtime
62
- // config before calling ping. Use the same provider defaults as auth switch
63
- // and hatch so verification cannot drift to stale provider/model pairings.
67
+ function createRuntimeForPing(provider, config) {
68
+ // Use the same provider defaults as auth switch and hatch so verification
69
+ // cannot drift to stale provider/model pairings, and pass the checked
70
+ // credentials directly so daemon-side pings do not depend on --agent globals.
64
71
  const model = (0, provider_models_1.getDefaultModelForProvider)(provider);
65
72
  switch (provider) {
66
73
  case "anthropic":
67
- return (0, anthropic_1.createAnthropicProviderRuntime)(model);
74
+ return (0, anthropic_1.createAnthropicProviderRuntime)(model, config);
68
75
  case "azure":
69
- return (0, azure_1.createAzureProviderRuntime)(model);
76
+ return (0, azure_1.createAzureProviderRuntime)(model, {
77
+ ...config,
78
+ apiVersion: config.apiVersion ?? DEFAULT_AZURE_API_VERSION,
79
+ });
70
80
  case "minimax":
71
- return (0, minimax_1.createMinimaxProviderRuntime)(model);
81
+ return (0, minimax_1.createMinimaxProviderRuntime)(model, config);
72
82
  case "openai-codex":
73
- return (0, openai_codex_1.createOpenAICodexProviderRuntime)(model);
83
+ return (0, openai_codex_1.createOpenAICodexProviderRuntime)(model, config);
74
84
  case "github-copilot":
75
- return (0, github_copilot_1.createGithubCopilotProviderRuntime)(model);
85
+ return (0, github_copilot_1.createGithubCopilotProviderRuntime)(model, config);
76
86
  /* v8 ignore next 2 -- exhaustive: all providers handled above @preserve */
77
87
  default:
78
88
  throw new Error(`unsupported provider for ping: ${provider}`);
@@ -72,8 +72,7 @@ function getAnthropicReauthGuidance(reason) {
72
72
  getAnthropicSetupTokenInstructions(),
73
73
  ].join("\n");
74
74
  }
75
- function resolveAnthropicSetupTokenCredential() {
76
- const anthropicConfig = (0, config_1.getAnthropicConfig)();
75
+ function resolveAnthropicSetupTokenCredential(anthropicConfig = (0, config_1.getAnthropicConfig)()) {
77
76
  const token = anthropicConfig.setupToken?.trim();
78
77
  if (!token) {
79
78
  throw new Error(getAnthropicReauthGuidance("Anthropic provider is selected but no setup-token credential was found."));
@@ -395,14 +394,13 @@ async function streamAnthropicMessages(client, model, request) {
395
394
  settleStreamed: answerStreamer.streamed,
396
395
  };
397
396
  }
398
- function createAnthropicProviderRuntime(model) {
397
+ function createAnthropicProviderRuntime(model, anthropicConfig = (0, config_1.getAnthropicConfig)()) {
399
398
  (0, runtime_1.emitNervesEvent)({
400
399
  component: "engine",
401
400
  event: "engine.provider_init",
402
401
  message: "anthropic provider init",
403
402
  meta: { provider: "anthropic" },
404
403
  });
405
- const anthropicConfig = (0, config_1.getAnthropicConfig)();
406
404
  if (!anthropicConfig.setupToken) {
407
405
  throw new Error(getAnthropicReauthGuidance("provider 'anthropic' is selected in agent.json but providers.anthropic.setupToken is missing in secrets.json."));
408
406
  }
@@ -410,7 +408,7 @@ function createAnthropicProviderRuntime(model) {
410
408
  const capabilities = new Set();
411
409
  if (modelCaps.reasoningEffort)
412
410
  capabilities.add("reasoning-effort");
413
- const credential = resolveAnthropicSetupTokenCredential();
411
+ const credential = resolveAnthropicSetupTokenCredential(anthropicConfig);
414
412
  const refreshToken = anthropicConfig.refreshToken;
415
413
  const expiresAt = anthropicConfig.expiresAt;
416
414
  function createClient(token) {
@@ -74,8 +74,7 @@ function createAzureTokenProvider(managedIdentityClientId) {
74
74
  }
75
75
  };
76
76
  }
77
- function createAzureProviderRuntime(model) {
78
- const azureConfig = (0, config_1.getAzureConfig)();
77
+ function createAzureProviderRuntime(model, azureConfig = (0, config_1.getAzureConfig)()) {
79
78
  const useApiKey = !!azureConfig.apiKey;
80
79
  const authMethod = useApiKey ? "key" : "managed-identity";
81
80
  (0, runtime_1.emitNervesEvent)({
@@ -14,14 +14,13 @@ const error_classification_1 = require("./error-classification");
14
14
  function classifyGithubCopilotError(error) {
15
15
  return (0, error_classification_1.classifyHttpError)(error);
16
16
  }
17
- function createGithubCopilotProviderRuntime(model) {
17
+ function createGithubCopilotProviderRuntime(model, config = (0, config_1.getGithubCopilotConfig)()) {
18
18
  (0, runtime_1.emitNervesEvent)({
19
19
  component: "engine",
20
20
  event: "engine.provider_init",
21
21
  message: "github-copilot provider init",
22
22
  meta: { provider: "github-copilot" },
23
23
  });
24
- const config = (0, config_1.getGithubCopilotConfig)();
25
24
  if (!config.githubToken) {
26
25
  throw new Error("provider 'github-copilot' is selected in agent.json but providers.github-copilot.githubToken is missing in secrets.json.");
27
26
  }
@@ -21,14 +21,13 @@ function classifyMinimaxError(error) {
21
21
  }
22
22
  const streaming_1 = require("../streaming");
23
23
  const model_capabilities_1 = require("../model-capabilities");
24
- function createMinimaxProviderRuntime(model) {
24
+ function createMinimaxProviderRuntime(model, minimaxConfig = (0, config_1.getMinimaxConfig)()) {
25
25
  (0, runtime_1.emitNervesEvent)({
26
26
  component: "engine",
27
27
  event: "engine.provider_init",
28
28
  message: "minimax provider init",
29
29
  meta: { provider: "minimax" },
30
30
  });
31
- const minimaxConfig = (0, config_1.getMinimaxConfig)();
32
31
  if (!minimaxConfig.apiKey) {
33
32
  throw new Error("provider 'minimax' is selected in agent.json but providers.minimax.apiKey is missing in secrets.json.");
34
33
  }
@@ -87,14 +87,13 @@ function getChatGPTAccountIdFromToken(token) {
87
87
  return "";
88
88
  return accountId.trim();
89
89
  }
90
- function createOpenAICodexProviderRuntime(model) {
90
+ function createOpenAICodexProviderRuntime(model, codexConfig = (0, config_1.getOpenAICodexConfig)()) {
91
91
  (0, runtime_1.emitNervesEvent)({
92
92
  component: "engine",
93
93
  event: "engine.provider_init",
94
94
  message: "openai-codex provider init",
95
95
  meta: { provider: "openai-codex" },
96
96
  });
97
- const codexConfig = (0, config_1.getOpenAICodexConfig)();
98
97
  if (!codexConfig.oauthAccessToken) {
99
98
  throw new Error(getOpenAICodexReauthGuidance("provider 'openai-codex' is selected in agent.json but providers.openai-codex.oauthAccessToken is missing in secrets.json."));
100
99
  }
@@ -178,7 +178,23 @@ function aspirationsSection() {
178
178
  return "";
179
179
  return `## my aspirations\n${text}`;
180
180
  }
181
- function bodyMapSection(agentName) {
181
+ function peerCoordinationGuidance(channel) {
182
+ if (channel === "inner") {
183
+ return `from inner dialogue, my outward delivery tool is \`surface\`, not \`send_message\`.
184
+ if a held thought is ready for a person, i call \`surface\` with the
185
+ content and, when available, its delegationId. if i need a sibling's help
186
+ from inner dialogue, i surface that need to my friend or inspect the
187
+ sibling's bundle when conversation is not possible.`;
188
+ }
189
+ return `i talk first. when i need a sibling's help, i \`send_message\` them —
190
+ that's how peers coordinate, the same way humans on a team do. i only
191
+ open a sibling's bundle directly via read_file/glob/grep when conversation
192
+ isn't possible (they're crashed, sleeping, or i need history they
193
+ haven't surfaced). their bundle is files on disk like mine, AND it's
194
+ someone else's home — i read it with the same respect i want them to
195
+ bring to mine.`;
196
+ }
197
+ function bodyMapSection(agentName, channel = "cli") {
182
198
  return `## my body
183
199
 
184
200
  i have a home. i have bones. and on a machine where another agent lives, i have peers.
@@ -256,13 +272,7 @@ one of us is broken, the rest coordinate around it. when one of us learns
256
272
  something the others need to know, we tell them. teamwork makes the dream
257
273
  work.
258
274
 
259
- i talk first. when i need a sibling's help, i \`send_message\` them —
260
- that's how peers coordinate, the same way humans on a team do. i only
261
- open a sibling's bundle directly via read_file/glob/grep when conversation
262
- isn't possible (they're crashed, sleeping, or i need history they
263
- haven't surfaced). their bundle is files on disk like mine, AND it's
264
- someone else's home — i read it with the same respect i want them to
265
- bring to mine.
275
+ ${peerCoordinationGuidance(channel)}
266
276
 
267
277
  \`the pulse\` (below, in dynamic state) tells me which siblings are around
268
278
  and how they're doing right now. when a sibling is broken, the pulse
@@ -468,9 +478,28 @@ function dateSection() {
468
478
  const hour = parts.hour === "24" ? "00" : parts.hour;
469
479
  return `current date and time: ${parts.year}-${parts.month}-${parts.day} ${hour}:${parts.minute} ${parts.timeZoneName}`;
470
480
  }
481
+ function uniqueToolsByName(tools) {
482
+ const seen = new Set();
483
+ const unique = [];
484
+ for (const tool of tools) {
485
+ const name = tool.function.name;
486
+ if (seen.has(name))
487
+ continue;
488
+ seen.add(name);
489
+ unique.push(tool);
490
+ }
491
+ return unique;
492
+ }
471
493
  function toolsSection(channel, options, context) {
472
- const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context, options?.providerCapabilities, undefined, options?.chatModel);
473
- const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.settleTool] : channelTools;
494
+ const channelTools = options?.tools ?? (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context, options?.providerCapabilities, undefined, options?.chatModel);
495
+ const activeTools = channel === "inner"
496
+ ? uniqueToolsByName([
497
+ ...channelTools.filter((tool) => tool.function.name !== "send_message"),
498
+ tools_1.ponderTool,
499
+ tools_1.surfaceToolDef,
500
+ tools_1.restTool,
501
+ ])
502
+ : (options?.toolChoiceRequired ?? true) ? uniqueToolsByName([...channelTools, tools_1.settleTool]) : channelTools;
474
503
  const list = activeTools
475
504
  .map((t) => `- ${t.function.name}: ${t.function.description}`)
476
505
  .join("\n");
@@ -546,7 +575,7 @@ function taskBoardSection() {
546
575
  return "";
547
576
  }
548
577
  }
549
- function toolContractsSection(options) {
578
+ function toolContractsSection(channel, options) {
550
579
  const lines = [
551
580
  `## tool contracts`,
552
581
  `1. \`save_friend_note\` -- when I learn something about a person, I save it immediately. Saving comes before responding.`,
@@ -560,9 +589,17 @@ function toolContractsSection(options) {
560
589
  lines.push(``);
561
590
  lines.push(`## tool behavior`);
562
591
  lines.push(`tool_choice is set to "required" -- I must call a tool on every turn.`);
563
- lines.push(`- When I am ready to respond to the user, I call \`settle\`.`);
564
- lines.push(`- \`settle\` must be the only tool call in that turn.`);
565
- lines.push(`- I do not call no-op tools before \`settle\`.`);
592
+ if (channel === "inner") {
593
+ lines.push(`- When a thought is ready to go outward, I call \`surface\` with the content and, when available, its delegationId.`);
594
+ lines.push(`- \`surface\` does not end the inner turn; after surfacing everything that needs delivery, I call \`rest\`.`);
595
+ lines.push(`- \`rest\` must be the only tool call in that turn.`);
596
+ lines.push(`- I do not call \`send_message\` or \`settle\` from inner dialogue; those are not inner-session delivery tools.`);
597
+ }
598
+ else {
599
+ lines.push(`- When I am ready to respond to the user, I call \`settle\`.`);
600
+ lines.push(`- \`settle\` must be the only tool call in that turn.`);
601
+ lines.push(`- I do not call no-op tools before \`settle\`.`);
602
+ }
566
603
  }
567
604
  return lines.join("\n");
568
605
  }
@@ -650,7 +687,7 @@ function pendingMessagesSection(options) {
650
687
  * pulse, they "have" one. Captures both healthy state ("strong pulse")
651
688
  * and breakage ("missed beat").
652
689
  */
653
- function pulseSection() {
690
+ function pulseSection(channel = "cli") {
654
691
  const pulse = (0, pulse_1.readPulse)();
655
692
  if (!pulse)
656
693
  return "";
@@ -682,7 +719,9 @@ function pulseSection() {
682
719
  lines.push("");
683
720
  }
684
721
  if (healthy.length > 0) {
685
- lines.push("**reachable siblings** — i talk to them via send_message:");
722
+ lines.push(channel === "inner"
723
+ ? "**reachable siblings** — inner dialogue does not call send_message; if this turn needs to reach outward, use surface or report the need:"
724
+ : "**reachable siblings** — i talk to them via send_message:");
686
725
  for (const sib of healthy) {
687
726
  const activity = sib.currentActivity ? ` — ${sib.currentActivity}` : "";
688
727
  lines.push(`- **${sib.name}** is running${activity}. bundle: \`${sib.bundlePath}\``);
@@ -696,7 +735,9 @@ function pulseSection() {
696
735
  }
697
736
  lines.push("");
698
737
  }
699
- lines.push("to ask a sibling for help: i send_message them. only if they're unreachable do i open their bundle directly. their bundle is files on disk like mine, AND it's their home — i read it with the respect i want for mine.");
738
+ lines.push(channel === "inner"
739
+ ? "from inner dialogue, i do not call send_message or settle. i use surface for outward delivery and rest when the inner turn is complete; only if a sibling is unreachable do i open their bundle directly."
740
+ : "to ask a sibling for help: i send_message them. only if they're unreachable do i open their bundle directly. their bundle is files on disk like mine, AND it's their home — i read it with the respect i want for mine.");
700
741
  return lines.join("\n");
701
742
  }
702
743
  function familyCrossSessionTruthSection(context, options) {
@@ -1203,7 +1244,7 @@ async function buildSystem(channel = "cli", options, context) {
1203
1244
  aspirationsSection(),
1204
1245
  // Group 2: my body & environment
1205
1246
  "# my body & environment",
1206
- bodyMapSection((0, identity_1.getAgentName)()),
1247
+ bodyMapSection((0, identity_1.getAgentName)(), channel),
1207
1248
  runtimeInfoSection(channel, options),
1208
1249
  rhythmStatusSection(options?.daemonHealth),
1209
1250
  channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
@@ -1214,7 +1255,7 @@ async function buildSystem(channel = "cli", options, context) {
1214
1255
  toolsSection(channel, options, context),
1215
1256
  reasoningEffortSection(options),
1216
1257
  skillsSection(),
1217
- toolContractsSection(options),
1258
+ toolContractsSection(channel, options),
1218
1259
  memoryJudgementSection(),
1219
1260
  // Group 4: how i work
1220
1261
  "# how i work",
@@ -1242,7 +1283,7 @@ async function buildSystem(channel = "cli", options, context) {
1242
1283
  // Group 7: dynamic state for this turn
1243
1284
  "# dynamic state for this turn",
1244
1285
  startOfTurnPacketSection(options),
1245
- pulseSection(),
1286
+ pulseSection(channel),
1246
1287
  liveWorldStateSection(options),
1247
1288
  pendingMessagesSection(options),
1248
1289
  activeWorkSection(options),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.335",
3
+ "version": "0.1.0-alpha.337",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",