@ouro.bot/cli 0.1.0-alpha.714 → 0.1.0-alpha.716

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.716",
6
+ "changes": [
7
+ "Add durable MCP bridge doctor and repair guidance."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.715",
12
+ "changes": [
13
+ "Keep RSVP routine-specific report copy in bundle metadata while leaving harness fixtures and help generic."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.714",
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") {
@@ -148,7 +148,7 @@ exports.COMMAND_REGISTRY = {
148
148
  category: "Habits",
149
149
  description: "AislePlanner-backed RSVP habit operations, triage, receipts, ledgers, migration checks, refreshes, and smoke tests",
150
150
  usage: "ouro rsvp <doctor|incident|cutover|legacy-render|replay|config|habit|import-legacy|refresh|compare|smoke> ...",
151
- example: "ouro rsvp doctor --agent slugger --json",
151
+ example: "ouro rsvp doctor --agent rsvp-agent --json",
152
152
  subcommands: [
153
153
  "doctor",
154
154
  "incident",
@@ -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",
@@ -456,12 +456,12 @@ const SUBCOMMAND_HELP = {
456
456
  "rsvp doctor": {
457
457
  description: "Check native RSVP configuration, credentials, BlueBubbles attachment, ledgers, receipts, and migration readiness without sending messages; use --json --strict in triage",
458
458
  usage: "ouro rsvp doctor [--agent <name>] [--strict] [--output <path>] [--json]",
459
- example: "ouro rsvp doctor --agent slugger --strict --json",
459
+ example: "ouro rsvp doctor --agent rsvp-agent --strict --json",
460
460
  },
461
461
  "rsvp incident": {
462
462
  description: "Collect a redacted local RSVP diagnostic incident bundle with doctor status, receipts, and ledger pointers without contacting the daemon",
463
463
  usage: "ouro rsvp incident [--agent <name>] [--output <path>] [--json]",
464
- example: "ouro rsvp incident --agent slugger --output /tmp/rsvp-incident.json",
464
+ example: "ouro rsvp incident --agent rsvp-agent --output /tmp/rsvp-incident.json",
465
465
  },
466
466
  "rsvp cutover": {
467
467
  description: "Inspect or retire legacy RSVP sender state before native live send",
@@ -486,7 +486,7 @@ const SUBCOMMAND_HELP = {
486
486
  "rsvp habit stage": {
487
487
  description: "Stage an RSVP refresh habit with bundle-owned routine naming and report copy",
488
488
  usage: "ouro rsvp habit stage [--agent <name>] [--name <rsvp-name>] [--title <title>] [--report-title <title>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]",
489
- example: "ouro rsvp habit stage --agent rsvp-agent --name rsvp-wedding --title 'Wedding RSVPs' --mode shadow --cadence '0 10 * * *'",
489
+ example: "ouro rsvp habit stage --agent rsvp-agent --name rsvp-updates --title 'RSVP Updates' --mode shadow --cadence '0 10 * * *'",
490
490
  },
491
491
  "rsvp import-legacy": {
492
492
  description: "Compatibility alias for `ouro rsvp config import-legacy`; mutates only with --yes",
@@ -496,17 +496,17 @@ const SUBCOMMAND_HELP = {
496
496
  "rsvp refresh": {
497
497
  description: "Run the native RSVP refresh path and write receipts; defaults to dry-run behavior unless --allow-send is explicit",
498
498
  usage: "ouro rsvp refresh [--agent <name>] [--habit <rsvp-name>] --mode shadow|live [--no-send|--allow-send] [--output <path>] [--json]",
499
- example: "ouro rsvp refresh --agent rsvp-agent --habit rsvp-wedding --mode shadow --no-send --json",
499
+ example: "ouro rsvp refresh --agent rsvp-agent --habit rsvp-updates --mode shadow --no-send --json",
500
500
  },
501
501
  "rsvp compare": {
502
502
  description: "Compare native and legacy RSVP render outputs for migration verification",
503
503
  usage: "ouro rsvp compare [--agent <name>] --native <path> --legacy <path> [--output <path>] [--json]",
504
- example: "ouro rsvp compare --agent slugger --native /tmp/native.json --legacy /tmp/legacy.json",
504
+ example: "ouro rsvp compare --agent rsvp-agent --native /tmp/native.json --legacy /tmp/legacy.json",
505
505
  },
506
506
  "rsvp smoke": {
507
507
  description: "Smoke-test RSVP follow-up behavior through a target sense; preflight is no-send and live mode requires --allow-send",
508
508
  usage: "ouro rsvp smoke [--agent <name>] --mode preflight|live --surface bluebubbles [--question <text>] [--allow-send] [--output <path>] [--replay-output <path>] [--json]",
509
- example: "ouro rsvp smoke --agent slugger --mode preflight --surface bluebubbles --question 'who is pending?'",
509
+ example: "ouro rsvp smoke --agent rsvp-agent --mode preflight --surface bluebubbles --question 'who is pending?'",
510
510
  },
511
511
  "mail import-mbox": {
512
512
  description: "Import a HEY or other MBOX export into an existing delegated Mailroom source grant",
@@ -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,9 +33,14 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildMcpBridgeRepairGuidance = buildMcpBridgeRepairGuidance;
37
+ exports.formatMcpBridgeRepairDetails = formatMcpBridgeRepairDetails;
38
+ exports.buildMcpDoctorNextSteps = buildMcpDoctorNextSteps;
39
+ exports.formatMcpDoctorNextStepDetails = formatMcpDoctorNextStepDetails;
36
40
  exports.parseMcpStatusText = parseMcpStatusText;
37
41
  exports.runMcpStatusCanary = runMcpStatusCanary;
38
42
  exports.formatMcpStatusCanaryResult = formatMcpStatusCanaryResult;
43
+ exports.formatMcpStatusDoctorResult = formatMcpStatusDoctorResult;
39
44
  exports.createMcpStatusCanaryProbe = createMcpStatusCanaryProbe;
40
45
  const child_process_1 = require("child_process");
41
46
  const path = __importStar(require("path"));
@@ -75,6 +80,65 @@ function parseFields(line) {
75
80
  }
76
81
  return parsed;
77
82
  }
83
+ function shellArg(value) {
84
+ return /^[A-Za-z0-9._/-]+$/.test(value)
85
+ ? value
86
+ : `'${value.replace(/'/g, "'\\''")}'`;
87
+ }
88
+ function buildMcpBridgeRepairGuidance(agent) {
89
+ const agentArg = shellArg(agent);
90
+ return {
91
+ actor: "agent-runnable",
92
+ commands: [
93
+ `ouro setup --tool codex --agent ${agentArg}`,
94
+ `ouro setup --tool claude-code --agent ${agentArg}`,
95
+ ],
96
+ reload: "open a fresh dev-tool session after setup; existing MCP processes keep their old runtime",
97
+ verify: `ouro mcp doctor --agent ${agentArg}`,
98
+ };
99
+ }
100
+ function formatMcpBridgeRepairDetails(repair) {
101
+ return [
102
+ `repair actor=${repair.actor}`,
103
+ ...repair.commands.map((command) => `repair command=${command}`),
104
+ `reload required: ${repair.reload}`,
105
+ `verify command=${repair.verify}`,
106
+ ];
107
+ }
108
+ function buildMcpDoctorNextSteps(result, agent) {
109
+ const agentArg = shellArg(agent);
110
+ const text = [result.summary, ...result.details].join("\n");
111
+ const daemonUnreachable = text.includes("daemon=unreachable")
112
+ || text.includes("ECONNREFUSED")
113
+ || text.includes("ENOENT");
114
+ if (daemonUnreachable) {
115
+ return {
116
+ actor: "agent-runnable",
117
+ commands: [
118
+ "ouro up",
119
+ `ouro mcp doctor --agent ${agentArg}`,
120
+ ],
121
+ note: "start or refresh the daemon before trusting bridge status",
122
+ };
123
+ }
124
+ return {
125
+ actor: "agent-runnable",
126
+ commands: [
127
+ "ouro doctor",
128
+ `ouro status --agent ${agentArg}`,
129
+ `ouro repair --agent ${agentArg}`,
130
+ `ouro mcp doctor --agent ${agentArg}`,
131
+ ],
132
+ note: "fix reported daemon or sense health before rerunning bridge registration repair",
133
+ };
134
+ }
135
+ function formatMcpDoctorNextStepDetails(nextSteps) {
136
+ return [
137
+ `next actor=${nextSteps.actor}`,
138
+ ...nextSteps.commands.map((command) => `next command=${command}`),
139
+ `next note=${nextSteps.note}`,
140
+ ];
141
+ }
78
142
  function parseMcpStatusText(text) {
79
143
  const daemon = {};
80
144
  const senses = {};
@@ -105,9 +169,10 @@ function validateMcpStatus(parsed, requiredSenses, options = {}) {
105
169
  if (!options.ignoreOverviewHealth && parsed.daemon.health !== "ok") {
106
170
  failures.push(`health=${parsed.daemon.health ?? "missing"}`);
107
171
  }
108
- if (parsed.daemon.daemonVersion &&
172
+ const hasVersionMismatch = !!(parsed.daemon.daemonVersion &&
109
173
  parsed.daemon.mcpVersion &&
110
- parsed.daemon.daemonVersion !== parsed.daemon.mcpVersion) {
174
+ parsed.daemon.daemonVersion !== parsed.daemon.mcpVersion);
175
+ if (hasVersionMismatch) {
111
176
  failures.push(`version mismatch daemon=${parsed.daemon.daemonVersion} mcp=${parsed.daemon.mcpVersion}`);
112
177
  }
113
178
  for (const [sense, row] of Object.entries(parsed.senses)) {
@@ -133,11 +198,16 @@ function validateMcpStatus(parsed, requiredSenses, options = {}) {
133
198
  const summary = failures.length === 0
134
199
  ? `mcp canary ok: daemon=${parsed.daemon.daemon} health=${parsed.daemon.health}${options.ignoreOverviewHealth ? " (overview ignored)" : ""} senses=${senseSummary}`
135
200
  : `mcp canary failed: ${failures.join("; ")}`;
201
+ const repair = hasVersionMismatch && options.agent
202
+ ? buildMcpBridgeRepairGuidance(options.agent)
203
+ : undefined;
204
+ const repairDetails = repair ? formatMcpBridgeRepairDetails(repair) : [];
136
205
  return {
137
206
  ok: failures.length === 0,
138
207
  summary,
139
- details: failures.length === 0 ? [parsed.raw] : [...failures, parsed.raw],
208
+ details: failures.length === 0 ? [parsed.raw] : [...failures, ...repairDetails, parsed.raw],
140
209
  parsed,
210
+ ...(repair ? { repair } : {}),
141
211
  };
142
212
  }
143
213
  async function runMcpStatusCanary(options) {
@@ -252,6 +322,7 @@ async function runMcpStatusCanary(options) {
252
322
  }
253
323
  const parsed = parseMcpStatusText(responseText(statusResponse));
254
324
  const canary = validateMcpStatus(parsed, requiredSenses, {
325
+ agent: options.agent,
255
326
  ignoreOverviewHealth: options.ignoreOverviewHealth,
256
327
  });
257
328
  (0, runtime_1.emitNervesEvent)({
@@ -286,6 +357,26 @@ function formatMcpStatusCanaryResult(result) {
286
357
  ...result.details.map((line) => ` ${line}`),
287
358
  ].join("\n");
288
359
  }
360
+ function formatMcpStatusDoctorResult(result, agent) {
361
+ const rawDetails = result.details.filter((line) => !line.startsWith("repair ")
362
+ && !line.startsWith("reload required:")
363
+ && !line.startsWith("verify command="));
364
+ const lines = [
365
+ result.ok ? "mcp doctor: ok" : "mcp doctor: failed",
366
+ result.summary,
367
+ ...rawDetails.map((line) => ` ${line}`),
368
+ ];
369
+ if (result.ok || result.repair) {
370
+ const repair = result.repair ?? buildMcpBridgeRepairGuidance(agent);
371
+ lines.push("bridge registration path:");
372
+ lines.push(...formatMcpBridgeRepairDetails(repair).map((line) => ` ${line}`));
373
+ }
374
+ else {
375
+ lines.push("next checks:");
376
+ lines.push(...formatMcpDoctorNextStepDetails(buildMcpDoctorNextSteps(result, agent)).map((line) => ` ${line}`));
377
+ }
378
+ return lines.join("\n");
379
+ }
289
380
  function createMcpStatusCanaryProbe(options) {
290
381
  return {
291
382
  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.714",
3
+ "version": "0.1.0-alpha.716",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",