@ouro.bot/cli 0.1.0-alpha.715 → 0.1.0-alpha.717

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,18 @@
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.717",
6
+ "changes": [
7
+ "Raise MCP bridge canary timeout for slow runtime status paths."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.716",
12
+ "changes": [
13
+ "Add durable MCP bridge doctor and repair guidance."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.715",
6
18
  "changes": [
@@ -60,6 +60,7 @@ const diary_1 = require("../../mind/diary");
60
60
  const record_paths_1 = require("../../mind/record-paths");
61
61
  const runtime_1 = require("../../nerves/runtime");
62
62
  const socket_client_1 = require("./socket-client");
63
+ const mcp_canary_1 = require("./mcp-canary");
63
64
  /** Format diary hits the same way the search_facts tool does. */
64
65
  function formatDiaryHits(hits) {
65
66
  return hits.map((f) => `[diary] ${f.text} (source=${f.source}, createdAt=${f.createdAt})`);
@@ -250,7 +251,7 @@ async function readRuntimeStatus(socketPath, agent) {
250
251
  };
251
252
  }
252
253
  }
253
- function formatRuntimeStatusLines(runtime, mcpVersion) {
254
+ function formatRuntimeStatusLines(runtime, mcpVersion, agent) {
254
255
  if (!runtime)
255
256
  return mcpVersion ? [`mcpVersion=${mcpVersion}`] : [];
256
257
  if (!runtime.daemonReachable) {
@@ -264,10 +265,16 @@ function formatRuntimeStatusLines(runtime, mcpVersion) {
264
265
  const versionPart = runtime.overview.version ? `\tdaemonVersion=${runtime.overview.version}` : "";
265
266
  const modePart = runtime.overview.mode ? `\tmode=${runtime.overview.mode}` : "";
266
267
  const mcpVersionPart = mcpVersion ? `\tmcpVersion=${mcpVersion}` : "";
267
- const mismatchPart = mcpVersion && runtime.overview.version && mcpVersion !== runtime.overview.version
268
+ const hasVersionMismatch = !!(mcpVersion && runtime.overview.version && mcpVersion !== runtime.overview.version);
269
+ const mismatchPart = hasVersionMismatch
268
270
  ? `\tversionMismatch=mcp:${mcpVersion},daemon:${runtime.overview.version}`
269
271
  : "";
270
272
  lines.push(`daemon=${runtime.overview.daemon}\thealth=${runtime.overview.health}${versionPart}${modePart}${mcpVersionPart}${mismatchPart}`);
273
+ if (hasVersionMismatch) {
274
+ const repair = (0, mcp_canary_1.buildMcpBridgeRepairGuidance)(agent);
275
+ lines.push(`mcpRepair=${repair.actor}\tcodex=${repair.commands[0]}\tclaudeCode=${repair.commands[1]}`);
276
+ lines.push(`mcpReload=required\treason=${repair.reload}`);
277
+ }
271
278
  }
272
279
  for (const worker of runtime.workers) {
273
280
  lines.push(`worker=${worker.worker}:${worker.status}`);
@@ -297,6 +304,16 @@ function formatRuntimeStatusLines(runtime, mcpVersion) {
297
304
  lines.push(`mcpVersion=${mcpVersion}`);
298
305
  return lines;
299
306
  }
307
+ function summarizeMcpBridgeHealth(agent, runtime, mcpVersion) {
308
+ const daemonVersion = runtime?.daemonReachable ? runtime.overview?.version ?? null : null;
309
+ const versionMismatch = !!(mcpVersion && daemonVersion && mcpVersion !== daemonVersion);
310
+ return {
311
+ mcpVersion,
312
+ daemonVersion,
313
+ versionMismatch,
314
+ ...(versionMismatch ? { repair: (0, mcp_canary_1.buildMcpBridgeRepairGuidance)(agent) } : {}),
315
+ };
316
+ }
300
317
  // ─── Handlers ────────────────────────────────────────────────────────────────
301
318
  async function handleAgentStatus(params) {
302
319
  (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.agent_service_start", message: "handling agent.status", meta: { agent: params.agent } });
@@ -316,7 +333,8 @@ async function handleAgentStatus(params) {
316
333
  `sessionCount=${sessions.length}`,
317
334
  `diaryEntries=${facts.length}`,
318
335
  ].join("\t");
319
- const runtimeLines = formatRuntimeStatusLines(runtime, mcpVersion);
336
+ const runtimeLines = formatRuntimeStatusLines(runtime, mcpVersion, params.agent);
337
+ const mcpHealth = summarizeMcpBridgeHealth(params.agent, runtime, mcpVersion);
320
338
  const message = [agentLine, ...runtimeLines].join("\n");
321
339
  return {
322
340
  ok: true,
@@ -330,6 +348,7 @@ async function handleAgentStatus(params) {
330
348
  factCount: facts.length,
331
349
  runtime,
332
350
  mcpVersion,
351
+ mcpHealth,
333
352
  },
334
353
  };
335
354
  }
@@ -125,6 +125,10 @@ const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 60_000;
125
125
  const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
126
126
  const DEFAULT_DAEMON_STARTUP_STABILITY_WINDOW_MS = 1_500;
127
127
  const DEFAULT_DAEMON_STARTUP_RETRY_LIMIT = 1;
128
+ function setupReloadNotice(tool) {
129
+ const host = tool === "codex" ? "Codex" : "Claude Code";
130
+ return ` reload required: open a fresh ${host} session so the host launches the newly registered MCP bridge; existing MCP processes keep their old runtime`;
131
+ }
128
132
  const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
129
133
  const RSVP_CLI_KINDS = new Set([
130
134
  "rsvp.doctor",
@@ -6940,7 +6944,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6940
6944
  if (!existingClaudeMd.includes("Agent conversations (ouro)")) {
6941
6945
  fs.writeFileSync(claudeMdPath, existingClaudeMd + agentInstructions);
6942
6946
  }
6943
- const message = `setup complete: claude-code + ${setupAgent}\n MCP server registered\n hooks configured\n conversation formatting instructions added`;
6947
+ const message = `setup complete: claude-code + ${setupAgent}\n MCP server registered\n hooks configured\n conversation formatting instructions added\n${setupReloadNotice("claude-code")}`;
6944
6948
  deps.writeStdout(message);
6945
6949
  return message;
6946
6950
  }
@@ -6954,7 +6958,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6954
6958
  message: "dev tool setup complete",
6955
6959
  meta: { tool, agent: setupAgent, runtimeMode, platform },
6956
6960
  });
6957
- const message = `setup complete: codex + ${setupAgent}\n MCP server registered`;
6961
+ const message = `setup complete: codex + ${setupAgent}\n MCP server registered\n${setupReloadNotice("codex")}`;
6958
6962
  deps.writeStdout(message);
6959
6963
  return message;
6960
6964
  }
@@ -6981,6 +6985,32 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6981
6985
  deps.writeStdout(message);
6982
6986
  return message;
6983
6987
  }
6988
+ if (command.kind === "mcp.doctor") {
6989
+ const canarySocketPath = command.socketOverride ?? deps.socketPath;
6990
+ const result = await (0, mcp_canary_1.runMcpStatusCanary)({
6991
+ agent: command.agent,
6992
+ socketPath: canarySocketPath,
6993
+ command: process.execPath,
6994
+ commandArgs: [
6995
+ path.join(__dirname, "ouro-bot-entry.js"),
6996
+ "mcp-serve",
6997
+ "--agent",
6998
+ command.agent,
6999
+ "--socket",
7000
+ canarySocketPath,
7001
+ ],
7002
+ });
7003
+ if (!result.ok)
7004
+ deps.setExitCode?.(1);
7005
+ const jsonPayload = result.ok || result.repair
7006
+ ? { ...result, repair: result.repair ?? (0, mcp_canary_1.buildMcpBridgeRepairGuidance)(command.agent) }
7007
+ : { ...result, nextSteps: (0, mcp_canary_1.buildMcpDoctorNextSteps)(result, command.agent) };
7008
+ const message = command.json
7009
+ ? JSON.stringify(jsonPayload, null, 2)
7010
+ : (0, mcp_canary_1.formatMcpStatusDoctorResult)(result, command.agent);
7011
+ deps.writeStdout(message);
7012
+ return message;
7013
+ }
6984
7014
  /* v8 ignore start — mcp-serve block binds to process.stdin/stdout; tested via mcp-server unit tests */
6985
7015
  // ── mcp-serve: start MCP server in-process on stdin/stdout ──
6986
7016
  if (command.kind === "mcp-serve") {
@@ -335,8 +335,8 @@ exports.COMMAND_REGISTRY = {
335
335
  category: "System",
336
336
  description: "Interact with MCP servers",
337
337
  usage: "ouro mcp <subcommand>",
338
- example: "ouro mcp canary --agent ouroboros",
339
- subcommands: ["list", "call", "canary"],
338
+ example: "ouro mcp doctor --agent ouroboros",
339
+ subcommands: ["list", "call", "canary", "doctor"],
340
340
  },
341
341
  "mcp-serve": {
342
342
  category: "System",
@@ -134,6 +134,7 @@ function usage() {
134
134
  " ouro session list [--agent <name>]",
135
135
  " ouro mcp list",
136
136
  " ouro mcp call <server> <tool> [--args '{...}']",
137
+ " ouro mcp doctor --agent <name> [--json]",
137
138
  " ouro rollback [<version>]",
138
139
  " ouro versions",
139
140
  " ouro clone <remote> [--agent <name>]",
@@ -1458,6 +1459,31 @@ function parseMcpCommand(args) {
1458
1459
  ...(json ? { json: true } : {}),
1459
1460
  };
1460
1461
  }
1462
+ if (sub === "doctor") {
1463
+ let socketOverride;
1464
+ let json = false;
1465
+ for (let i = 0; i < rest.length; i++) {
1466
+ if (rest[i] === "--socket") {
1467
+ if (!rest[i + 1])
1468
+ throw new Error("mcp doctor requires a value after --socket");
1469
+ socketOverride = rest[++i];
1470
+ continue;
1471
+ }
1472
+ if (rest[i] === "--json") {
1473
+ json = true;
1474
+ continue;
1475
+ }
1476
+ throw new Error(`Unknown mcp doctor flag: ${rest[i]}`);
1477
+ }
1478
+ if (!agent)
1479
+ throw new Error("mcp doctor requires --agent <name>");
1480
+ return {
1481
+ kind: "mcp.doctor",
1482
+ agent,
1483
+ ...(socketOverride ? { socketOverride } : {}),
1484
+ ...(json ? { json: true } : {}),
1485
+ };
1486
+ }
1461
1487
  throw new Error(`Usage\n${usage()}`);
1462
1488
  }
1463
1489
  function inferAgentNameFromRemote(remote) {
@@ -33,14 +33,20 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEFAULT_CANARY_TIMEOUT_MS = void 0;
37
+ exports.buildMcpBridgeRepairGuidance = buildMcpBridgeRepairGuidance;
38
+ exports.formatMcpBridgeRepairDetails = formatMcpBridgeRepairDetails;
39
+ exports.buildMcpDoctorNextSteps = buildMcpDoctorNextSteps;
40
+ exports.formatMcpDoctorNextStepDetails = formatMcpDoctorNextStepDetails;
36
41
  exports.parseMcpStatusText = parseMcpStatusText;
37
42
  exports.runMcpStatusCanary = runMcpStatusCanary;
38
43
  exports.formatMcpStatusCanaryResult = formatMcpStatusCanaryResult;
44
+ exports.formatMcpStatusDoctorResult = formatMcpStatusDoctorResult;
39
45
  exports.createMcpStatusCanaryProbe = createMcpStatusCanaryProbe;
40
46
  const child_process_1 = require("child_process");
41
47
  const path = __importStar(require("path"));
42
48
  const runtime_1 = require("../../nerves/runtime");
43
- const DEFAULT_CANARY_TIMEOUT_MS = 10_000;
49
+ exports.DEFAULT_CANARY_TIMEOUT_MS = 60_000;
44
50
  const MCP_PROTOCOL_VERSION = "2024-11-05";
45
51
  function defaultCommandArgs(agent, socketPath) {
46
52
  const entryPath = path.join(__dirname, "ouro-bot-entry.js");
@@ -75,6 +81,65 @@ function parseFields(line) {
75
81
  }
76
82
  return parsed;
77
83
  }
84
+ function shellArg(value) {
85
+ return /^[A-Za-z0-9._/-]+$/.test(value)
86
+ ? value
87
+ : `'${value.replace(/'/g, "'\\''")}'`;
88
+ }
89
+ function buildMcpBridgeRepairGuidance(agent) {
90
+ const agentArg = shellArg(agent);
91
+ return {
92
+ actor: "agent-runnable",
93
+ commands: [
94
+ `ouro setup --tool codex --agent ${agentArg}`,
95
+ `ouro setup --tool claude-code --agent ${agentArg}`,
96
+ ],
97
+ reload: "open a fresh dev-tool session after setup; existing MCP processes keep their old runtime",
98
+ verify: `ouro mcp doctor --agent ${agentArg}`,
99
+ };
100
+ }
101
+ function formatMcpBridgeRepairDetails(repair) {
102
+ return [
103
+ `repair actor=${repair.actor}`,
104
+ ...repair.commands.map((command) => `repair command=${command}`),
105
+ `reload required: ${repair.reload}`,
106
+ `verify command=${repair.verify}`,
107
+ ];
108
+ }
109
+ function buildMcpDoctorNextSteps(result, agent) {
110
+ const agentArg = shellArg(agent);
111
+ const text = [result.summary, ...result.details].join("\n");
112
+ const daemonUnreachable = text.includes("daemon=unreachable")
113
+ || text.includes("ECONNREFUSED")
114
+ || text.includes("ENOENT");
115
+ if (daemonUnreachable) {
116
+ return {
117
+ actor: "agent-runnable",
118
+ commands: [
119
+ "ouro up",
120
+ `ouro mcp doctor --agent ${agentArg}`,
121
+ ],
122
+ note: "start or refresh the daemon before trusting bridge status",
123
+ };
124
+ }
125
+ return {
126
+ actor: "agent-runnable",
127
+ commands: [
128
+ "ouro doctor",
129
+ `ouro status --agent ${agentArg}`,
130
+ `ouro repair --agent ${agentArg}`,
131
+ `ouro mcp doctor --agent ${agentArg}`,
132
+ ],
133
+ note: "fix reported daemon or sense health before rerunning bridge registration repair",
134
+ };
135
+ }
136
+ function formatMcpDoctorNextStepDetails(nextSteps) {
137
+ return [
138
+ `next actor=${nextSteps.actor}`,
139
+ ...nextSteps.commands.map((command) => `next command=${command}`),
140
+ `next note=${nextSteps.note}`,
141
+ ];
142
+ }
78
143
  function parseMcpStatusText(text) {
79
144
  const daemon = {};
80
145
  const senses = {};
@@ -105,9 +170,10 @@ function validateMcpStatus(parsed, requiredSenses, options = {}) {
105
170
  if (!options.ignoreOverviewHealth && parsed.daemon.health !== "ok") {
106
171
  failures.push(`health=${parsed.daemon.health ?? "missing"}`);
107
172
  }
108
- if (parsed.daemon.daemonVersion &&
173
+ const hasVersionMismatch = !!(parsed.daemon.daemonVersion &&
109
174
  parsed.daemon.mcpVersion &&
110
- parsed.daemon.daemonVersion !== parsed.daemon.mcpVersion) {
175
+ parsed.daemon.daemonVersion !== parsed.daemon.mcpVersion);
176
+ if (hasVersionMismatch) {
111
177
  failures.push(`version mismatch daemon=${parsed.daemon.daemonVersion} mcp=${parsed.daemon.mcpVersion}`);
112
178
  }
113
179
  for (const [sense, row] of Object.entries(parsed.senses)) {
@@ -133,15 +199,20 @@ function validateMcpStatus(parsed, requiredSenses, options = {}) {
133
199
  const summary = failures.length === 0
134
200
  ? `mcp canary ok: daemon=${parsed.daemon.daemon} health=${parsed.daemon.health}${options.ignoreOverviewHealth ? " (overview ignored)" : ""} senses=${senseSummary}`
135
201
  : `mcp canary failed: ${failures.join("; ")}`;
202
+ const repair = hasVersionMismatch && options.agent
203
+ ? buildMcpBridgeRepairGuidance(options.agent)
204
+ : undefined;
205
+ const repairDetails = repair ? formatMcpBridgeRepairDetails(repair) : [];
136
206
  return {
137
207
  ok: failures.length === 0,
138
208
  summary,
139
- details: failures.length === 0 ? [parsed.raw] : [...failures, parsed.raw],
209
+ details: failures.length === 0 ? [parsed.raw] : [...failures, ...repairDetails, parsed.raw],
140
210
  parsed,
211
+ ...(repair ? { repair } : {}),
141
212
  };
142
213
  }
143
214
  async function runMcpStatusCanary(options) {
144
- const timeoutMs = options.timeoutMs ?? DEFAULT_CANARY_TIMEOUT_MS;
215
+ const timeoutMs = options.timeoutMs ?? exports.DEFAULT_CANARY_TIMEOUT_MS;
145
216
  /* v8 ignore next -- default spawn is exercised by live canaries, while unit tests inject a fake child @preserve */
146
217
  const spawnImpl = options.spawnImpl ?? child_process_1.spawn;
147
218
  const command = options.command ?? process.execPath;
@@ -252,6 +323,7 @@ async function runMcpStatusCanary(options) {
252
323
  }
253
324
  const parsed = parseMcpStatusText(responseText(statusResponse));
254
325
  const canary = validateMcpStatus(parsed, requiredSenses, {
326
+ agent: options.agent,
255
327
  ignoreOverviewHealth: options.ignoreOverviewHealth,
256
328
  });
257
329
  (0, runtime_1.emitNervesEvent)({
@@ -286,6 +358,26 @@ function formatMcpStatusCanaryResult(result) {
286
358
  ...result.details.map((line) => ` ${line}`),
287
359
  ].join("\n");
288
360
  }
361
+ function formatMcpStatusDoctorResult(result, agent) {
362
+ const rawDetails = result.details.filter((line) => !line.startsWith("repair ")
363
+ && !line.startsWith("reload required:")
364
+ && !line.startsWith("verify command="));
365
+ const lines = [
366
+ result.ok ? "mcp doctor: ok" : "mcp doctor: failed",
367
+ result.summary,
368
+ ...rawDetails.map((line) => ` ${line}`),
369
+ ];
370
+ if (result.ok || result.repair) {
371
+ const repair = result.repair ?? buildMcpBridgeRepairGuidance(agent);
372
+ lines.push("bridge registration path:");
373
+ lines.push(...formatMcpBridgeRepairDetails(repair).map((line) => ` ${line}`));
374
+ }
375
+ else {
376
+ lines.push("next checks:");
377
+ lines.push(...formatMcpDoctorNextStepDetails(buildMcpDoctorNextSteps(result, agent)).map((line) => ` ${line}`));
378
+ }
379
+ return lines.join("\n");
380
+ }
289
381
  function createMcpStatusCanaryProbe(options) {
290
382
  return {
291
383
  name: `mcp-canary:${options.agent}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.715",
3
+ "version": "0.1.0-alpha.717",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",