@ouro.bot/cli 0.1.0-alpha.727 → 0.1.0-alpha.729

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.
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.runMcpServeCliLifecycle = runMcpServeCliLifecycle;
42
43
  exports.summarizeDaemonStartupFailure = summarizeDaemonStartupFailure;
43
44
  exports.writeSyncProbeSummary = writeSyncProbeSummary;
44
45
  exports.summarizeSyncProbeFindings = summarizeSyncProbeFindings;
@@ -47,6 +48,7 @@ exports.ensureDaemonRunning = ensureDaemonRunning;
47
48
  exports.listGithubCopilotModels = listGithubCopilotModels;
48
49
  exports.checkManualCloneBundles = checkManualCloneBundles;
49
50
  exports.resolveWorkbenchRuntimeMcp = resolveWorkbenchRuntimeMcp;
51
+ exports.setupBlueBubblesHostForConnect = setupBlueBubblesHostForConnect;
50
52
  exports.resolveMailImportFilePath = resolveMailImportFilePath;
51
53
  exports.runOuroCli = runOuroCli;
52
54
  const child_process_1 = require("child_process");
@@ -78,6 +80,7 @@ const provider_binding_resolver_1 = require("../provider-binding-resolver");
78
80
  const provider_visibility_1 = require("../provider-visibility");
79
81
  const context_loss_sentinel_1 = require("../context-loss-sentinel");
80
82
  const machine_identity_1 = require("../machine-identity");
83
+ const bluebubbles_host_1 = require("./bluebubbles-host");
81
84
  const provider_models_1 = require("../provider-models");
82
85
  const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
83
86
  const update_checker_1 = require("../versioning/update-checker");
@@ -120,6 +123,7 @@ const connect_bay_1 = require("./connect-bay");
120
123
  const runtime_capability_check_1 = require("../runtime-capability-check");
121
124
  const vault_items_1 = require("./vault-items");
122
125
  const mcp_canary_1 = require("./mcp-canary");
126
+ const bluebubbles_cli_integration_1 = require("./bluebubbles-cli-integration");
123
127
  // ── ensureDaemonRunning ──
124
128
  const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 60_000;
125
129
  const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
@@ -129,6 +133,23 @@ function setupReloadNotice(tool) {
129
133
  const host = tool === "codex" ? "Codex" : "Claude Code";
130
134
  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
135
  }
136
+ async function runMcpServeCliLifecycle(server, input) {
137
+ server.start();
138
+ await new Promise((resolve) => {
139
+ let settled = false;
140
+ const finish = () => {
141
+ if (settled)
142
+ return;
143
+ settled = true;
144
+ input.removeListener("end", finish);
145
+ input.removeListener("close", finish);
146
+ server.stop();
147
+ resolve();
148
+ };
149
+ input.once("end", finish);
150
+ input.once("close", finish);
151
+ });
152
+ }
132
153
  function commandFailureText(error) {
133
154
  const parts = [];
134
155
  if (error instanceof Error)
@@ -656,6 +677,16 @@ function daemonUnavailableStatusJsonOutput(socketPath, healthFilePath) {
656
677
  ...(healthFilePath ? { healthFilePath } : {}),
657
678
  }, null, 2);
658
679
  }
680
+ function daemonTimeoutStatusJsonOutput(socketPath, error) {
681
+ return JSON.stringify({
682
+ ok: false,
683
+ error: "daemon timeout",
684
+ classification: "timeout",
685
+ code: "ETIMEDOUT",
686
+ socketPath,
687
+ detail: error instanceof Error ? error.message : String(error),
688
+ }, null, 2);
689
+ }
659
690
  function providerRepairCountSummary(count) {
660
691
  if (count === 0)
661
692
  return "selected providers answered live checks";
@@ -3545,6 +3576,45 @@ async function executeConnectTeams(agent, deps) {
3545
3576
  ],
3546
3577
  });
3547
3578
  }
3579
+ async function setupBlueBubblesHostForConnect(bridgeUsername, serverUrl, requestTimeoutMs, deps, hostAccountDeps = {
3580
+ userInfo: os.userInfo,
3581
+ execFileSync: child_process_1.execFileSync,
3582
+ }) {
3583
+ if (deps.setupBlueBubblesHost)
3584
+ return deps.setupBlueBubblesHost({ bridgeUsername });
3585
+ (0, bluebubbles_host_1.installBlueBubblesHostSharedHelper)({
3586
+ assetPath: path.resolve(__dirname, "../../../assets/bluebubbles-host"),
3587
+ });
3588
+ const currentUser = hostAccountDeps.userInfo();
3589
+ if (bridgeUsername === currentUser.username) {
3590
+ const host = await (0, bluebubbles_host_1.runBlueBubblesHostAction)("install", (0, bluebubbles_host_1.createDefaultBlueBubblesHostDeps)({ serverUrl, requestTimeoutMs, fetchImpl: deps.fetchImpl }));
3591
+ return {
3592
+ summary: `host: native LaunchAgent ${host.state.service}; process ${host.state.process}`,
3593
+ bridgeUsername,
3594
+ bridgeUid: currentUser.uid,
3595
+ bridgeHomeDir: currentUser.homedir,
3596
+ };
3597
+ }
3598
+ const uidText = hostAccountDeps.execFileSync("id", ["-u", bridgeUsername], { encoding: "utf8" }).trim();
3599
+ const bridgeUid = Number(uidText);
3600
+ const homeRecord = hostAccountDeps.execFileSync("dscl", [".", "-read", `/Users/${bridgeUsername}`, "NFSHomeDirectory"], { encoding: "utf8" }).trim();
3601
+ const bridgeHomeDir = homeRecord.replace(/^NFSHomeDirectory:\s*/, "").trim();
3602
+ if (!Number.isInteger(bridgeUid) || !bridgeHomeDir)
3603
+ throw new Error(`could not resolve BlueBubbles macOS account ${bridgeUsername}`);
3604
+ const handoff = (0, bluebubbles_host_1.requestCrossUserBlueBubblesHostAction)({
3605
+ action: "install",
3606
+ username: bridgeUsername,
3607
+ uid: bridgeUid,
3608
+ targetHomeDir: bridgeHomeDir,
3609
+ originHomeDir: deps.homeDir ?? os.homedir(),
3610
+ });
3611
+ return {
3612
+ summary: `human-required: run in ${bridgeUsername}'s logged-in desktop Terminal: ${handoff.helperCommand}\ncollect: ${handoff.collectCommand}`,
3613
+ bridgeUsername,
3614
+ bridgeUid,
3615
+ bridgeHomeDir,
3616
+ };
3617
+ }
3548
3618
  async function executeConnectBlueBubbles(agent, deps) {
3549
3619
  if (agent === "SerpentGuide") {
3550
3620
  throw new Error("SerpentGuide has no persistent runtime credentials. Attach BlueBubbles on the hatchling agent instead.");
@@ -3600,10 +3670,14 @@ async function executeConnectBlueBubbles(agent, deps) {
3600
3670
  const ownHandles = ownHandlesRaw
3601
3671
  ? ownHandlesRaw.split(",").map((h) => h.trim()).filter((h) => h.length > 0)
3602
3672
  : [];
3673
+ const bridgeUsernameAnswer = (await promptInput(`BlueBubbles macOS account username [${os.userInfo().username}]: `)).trim();
3674
+ const bridgeUsername = bridgeUsernameAnswer || os.userInfo().username;
3603
3675
  const machineId = currentMachineId(deps);
3604
3676
  const progress = createHumanCommandProgress(deps, "connect bluebubbles");
3605
3677
  let stored;
3606
3678
  let daemonApply = "not applied";
3679
+ let hostSetup;
3680
+ let webhookRegistration;
3607
3681
  try {
3608
3682
  progress.startPhase("saving BlueBubbles attachment");
3609
3683
  progress.updateDetail("checking existing machine runtime config");
@@ -3612,6 +3686,8 @@ async function executeConnectBlueBubbles(agent, deps) {
3612
3686
  progress.end();
3613
3687
  throw new Error(`cannot read existing machine runtime credentials from ${current.itemPath}: ${current.error}`);
3614
3688
  }
3689
+ progress.updateDetail("configuring native BlueBubbles host continuity");
3690
+ hostSetup = await setupBlueBubblesHostForConnect(bridgeUsername, serverUrl, requestTimeoutMs, deps);
3615
3691
  const blueBubblesPatch = {
3616
3692
  bluebubbles: {
3617
3693
  serverUrl,
@@ -3623,6 +3699,9 @@ async function executeConnectBlueBubbles(agent, deps) {
3623
3699
  port,
3624
3700
  webhookPath,
3625
3701
  requestTimeoutMs,
3702
+ bridgeUsername: hostSetup.bridgeUsername,
3703
+ bridgeUid: hostSetup.bridgeUid,
3704
+ bridgeHomeDir: hostSetup.bridgeHomeDir,
3626
3705
  },
3627
3706
  };
3628
3707
  progress.updateDetail("storing local machine config");
@@ -3631,6 +3710,22 @@ async function executeConnectBlueBubbles(agent, deps) {
3631
3710
  enableAgentSense(agent, "bluebubbles", deps);
3632
3711
  progress.completePhase("saving BlueBubbles attachment", "secret stored");
3633
3712
  daemonApply = await runCommandProgressPhase(progress, "applying BlueBubbles to running Ouro", () => applyLocalSenseChangeToRunningDaemon(agent, "BlueBubbles", deps, (message) => progress.updateDetail(message)), (result) => result);
3713
+ progress.startPhase("verifying BlueBubbles webhook");
3714
+ const webhookInput = {
3715
+ serverUrl,
3716
+ password,
3717
+ callbackPort: port,
3718
+ callbackPath: webhookPath,
3719
+ agentName: agent,
3720
+ machineId,
3721
+ requestTimeoutMs,
3722
+ listenerReady: (0, bluebubbles_cli_integration_1.blueBubblesListenerReadyAfterApply)(daemonApply),
3723
+ };
3724
+ webhookRegistration = await (0, bluebubbles_cli_integration_1.reconcileBlueBubblesWebhookAfterConnect)(webhookInput, {
3725
+ ...(deps.reconcileBlueBubblesWebhook ? { reconcile: deps.reconcileBlueBubblesWebhook } : {}),
3726
+ ...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}),
3727
+ });
3728
+ progress.completePhase("verifying BlueBubbles webhook", webhookRegistration.detail);
3634
3729
  progress.end();
3635
3730
  }
3636
3731
  catch (error) {
@@ -3646,13 +3741,17 @@ async function executeConnectBlueBubbles(agent, deps) {
3646
3741
  `Stored: ${stored.itemPath}`,
3647
3742
  "agent.json: senses.bluebubbles.enabled = true",
3648
3743
  `Runtime: ${daemonApply}`,
3744
+ hostSetup.summary,
3745
+ (0, bluebubbles_cli_integration_1.formatBlueBubblesWebhookConnectLine)(webhookRegistration),
3649
3746
  `ownHandles: ${ownHandles.length > 0 ? ownHandles.join(", ") : "(none — group self-talk filter inactive)"}`,
3650
3747
  "secret was not printed",
3651
3748
  ...(syncSummary ? [syncSummary] : []),
3652
3749
  ],
3653
3750
  nextMoves: [
3654
- `Point BlueBubbles at this machine's webhook listener on port ${port}${webhookPath}.`,
3655
- "If BlueBubbles was already pointed there, messages can now reach the agent.",
3751
+ webhookRegistration.ok
3752
+ ? `Ouro owns and repairs the BlueBubbles [*] webhook for port ${port}${webhookPath}.`
3753
+ : "Run `ouro doctor --category Senses` after Ouro and BlueBubbles are running; the periodic reconciler will retry every 180 seconds.",
3754
+ "Run the collect command after any human-required cross-user helper handoff.",
3656
3755
  `Attach other machines separately if ${agent} should use BlueBubbles there too.`,
3657
3756
  ],
3658
3757
  fallbackLines: [
@@ -3661,9 +3760,11 @@ async function executeConnectBlueBubbles(agent, deps) {
3661
3760
  `stored: ${stored.itemPath}`,
3662
3761
  "agent.json: senses.bluebubbles.enabled = true",
3663
3762
  `runtime: ${daemonApply}`,
3763
+ hostSetup.summary,
3764
+ (0, bluebubbles_cli_integration_1.formatBlueBubblesWebhookConnectLine)(webhookRegistration),
3664
3765
  "secret was not printed",
3665
3766
  "",
3666
- `Next: point BlueBubbles at this machine's webhook listener on port ${port}${webhookPath}.`,
3767
+ `webhook target: Ouro reconciles [*] automatically on port ${port}${webhookPath}`,
3667
3768
  ...(syncSummary ? [syncSummary] : []),
3668
3769
  ],
3669
3770
  });
@@ -5648,6 +5749,24 @@ async function registerOuroBundleTypeNonBlocking(deps) {
5648
5749
  });
5649
5750
  }
5650
5751
  }
5752
+ function writeVersionIntentWithRecoveryLauncher(deps, intent) {
5753
+ if (!deps.writeVersionIntent)
5754
+ return;
5755
+ if (!deps.installOuroCommand) {
5756
+ throw new Error("cannot change Ouro version intent without a verified recovery launcher: installer unavailable");
5757
+ }
5758
+ let installResult;
5759
+ try {
5760
+ installResult = deps.installOuroCommand();
5761
+ }
5762
+ catch (error) {
5763
+ throw new Error(`cannot change Ouro version intent without a verified recovery launcher: ${error instanceof Error ? error.message : String(error)}`);
5764
+ }
5765
+ if (!installResult.scriptPath) {
5766
+ throw new Error(`cannot change Ouro version intent without a verified recovery launcher: ${installResult.skippedReason ?? "installation was not verified"}`);
5767
+ }
5768
+ deps.writeVersionIntent(intent);
5769
+ }
5651
5770
  async function performSystemSetup(deps) {
5652
5771
  // Install ouro command to PATH (non-blocking)
5653
5772
  if (deps.installOuroCommand) {
@@ -6214,6 +6333,43 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6214
6333
  deps.writeStdout(text);
6215
6334
  return text;
6216
6335
  }
6336
+ if (command.kind === "bluebubbles.host") {
6337
+ if (command.target) {
6338
+ (0, bluebubbles_host_1.installBlueBubblesHostSharedHelper)({
6339
+ assetPath: path.resolve(__dirname, "../../../assets/bluebubbles-host"),
6340
+ });
6341
+ const handoff = (0, bluebubbles_host_1.requestCrossUserBlueBubblesHostAction)({
6342
+ action: command.action,
6343
+ username: command.target.username,
6344
+ uid: command.target.uid,
6345
+ targetHomeDir: command.target.homeDir,
6346
+ originHomeDir: deps.homeDir ?? os.homedir(),
6347
+ });
6348
+ const text = command.json
6349
+ ? JSON.stringify(handoff, null, 2)
6350
+ : [
6351
+ `human-required: run in ${command.target.username}'s logged-in desktop Terminal: ${handoff.helperCommand}`,
6352
+ `collect: ${handoff.collectCommand}`,
6353
+ ].join("\n");
6354
+ deps.writeStdout(text);
6355
+ return text;
6356
+ }
6357
+ const outcome = await (0, bluebubbles_host_1.runBlueBubblesHostAction)(command.action, (0, bluebubbles_host_1.createDefaultBlueBubblesHostDeps)());
6358
+ const text = command.json
6359
+ ? JSON.stringify(outcome, null, 2)
6360
+ : (0, bluebubbles_cli_integration_1.formatBlueBubblesHostActionText)(outcome);
6361
+ deps.writeStdout(text);
6362
+ return text;
6363
+ }
6364
+ if (command.kind === "bluebubbles.host.collect") {
6365
+ const collection = (0, bluebubbles_host_1.collectCrossUserBlueBubblesHostAction)({
6366
+ requestId: command.requestId,
6367
+ originHomeDir: deps.homeDir ?? os.homedir(),
6368
+ });
6369
+ const text = command.json ? JSON.stringify(collection, null, 2) : collection.detail;
6370
+ deps.writeStdout(text);
6371
+ return text;
6372
+ }
6217
6373
  if (command.kind === "bluebubbles.context-smoke") {
6218
6374
  const { smokeBlueBubblesContext, formatBlueBubblesContextSmokeText } = await Promise.resolve().then(() => __importStar(require("../../senses/bluebubbles/context-smoke")));
6219
6375
  const smoke = await smokeBlueBubblesContext({
@@ -6287,25 +6443,43 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6287
6443
  const daemonAliveAtBootStart = await deps.checkSocketAlive(deps.socketPath);
6288
6444
  progress.setPhasePlan?.(bootPhasePlan(daemonAliveAtBootStart));
6289
6445
  // ── versioned CLI update check ──
6290
- if (deps.checkForCliUpdate) {
6446
+ const versionIntent = deps.readVersionIntent?.() ?? null;
6447
+ const pinnedUpdate = versionIntent?.mode === "pinned" && !command.latest;
6448
+ if (deps.checkForCliUpdate && pinnedUpdate) {
6449
+ progress.startPhase("update check");
6450
+ progress.completePhase("update check", `pinned to ${versionIntent.targetVersion}`);
6451
+ }
6452
+ else if (deps.checkForCliUpdate) {
6291
6453
  progress.startPhase("update check");
6292
6454
  progress.updateDetail(`current runtime: ${(0, bundle_manifest_1.getPackageVersion)()}\nchecking npm registry\ncontinuing startup if it stays quiet`);
6293
6455
  let pendingReExec = false;
6294
6456
  let updateCheckStatus = "up to date";
6295
6457
  try {
6296
6458
  const { result: updateResult, timedOut } = await runCliUpdateCheckWithTimeout(deps.checkForCliUpdate, deps.updateCheckTimeoutMs ?? update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS);
6297
- if (updateResult.available && updateResult.latestVersion) {
6459
+ const shouldAdoptLatestTarget = Boolean(updateResult.latestVersion && (updateResult.available || command.latest || !versionIntent));
6460
+ if (shouldAdoptLatestTarget && updateResult.latestVersion) {
6298
6461
  /* v8 ignore next -- fallback: getCurrentCliVersion always injected in tests @preserve */
6299
6462
  const currentVersion = linkedVersionBeforeUp ?? "unknown";
6300
- await deps.installCliVersion(updateResult.latestVersion);
6301
- deps.activateCliVersion(updateResult.latestVersion);
6302
- progress.completePhase("update check", `installed ${updateResult.latestVersion}`);
6303
- const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(currentVersion, updateResult.latestVersion);
6304
- /* v8 ignore next -- buildChangelogCommand is non-null when an actual newer version is installed @preserve */
6305
- if (changelogCommand) {
6306
- deps.writeStdout(`review changes with: ${changelogCommand}`);
6463
+ const targetVersion = updateResult.latestVersion;
6464
+ const needsActivation = updateResult.available || linkedVersionBeforeUp !== targetVersion;
6465
+ if (needsActivation) {
6466
+ await deps.installCliVersion(targetVersion);
6467
+ const validation = deps.validateCliVersionForActivation?.(targetVersion);
6468
+ if (validation && !validation.ok) {
6469
+ throw new Error(`refusing to activate ${targetVersion}: ${validation.message}`);
6470
+ }
6471
+ }
6472
+ writeVersionIntentWithRecoveryLauncher(deps, { schemaVersion: 1, mode: "latest", targetVersion });
6473
+ if (needsActivation) {
6474
+ deps.activateCliVersion(targetVersion);
6475
+ progress.completePhase("update check", `installed ${targetVersion}`);
6476
+ const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(currentVersion, targetVersion);
6477
+ /* v8 ignore next -- buildChangelogCommand is non-null when an actual newer version is installed @preserve */
6478
+ if (changelogCommand) {
6479
+ deps.writeStdout(`review changes with: ${changelogCommand}`);
6480
+ }
6481
+ pendingReExec = true;
6307
6482
  }
6308
- pendingReExec = true;
6309
6483
  }
6310
6484
  else if (updateResult.error) {
6311
6485
  updateCheckStatus = summarizeCliUpdateCheckStatus(updateResult.error, timedOut);
@@ -6755,7 +6929,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6755
6929
  /* v8 ignore start -- rollback/versions: tested via daemon-cli-rollback/versions tests @preserve */
6756
6930
  if (command.kind === "rollback") {
6757
6931
  const currentVersion = deps.getCurrentCliVersion?.() ?? "unknown";
6932
+ let targetVersion;
6758
6933
  if (command.version) {
6934
+ targetVersion = command.version;
6759
6935
  // Rollback to a specific version
6760
6936
  const installed = deps.listCliVersions?.() ?? [];
6761
6937
  if (!installed.includes(command.version)) {
@@ -6773,7 +6949,6 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6773
6949
  const message = `refusing to roll back to ${command.version}: ${validation.message}`;
6774
6950
  return returnCliFailure(deps, message);
6775
6951
  }
6776
- deps.activateCliVersion(command.version);
6777
6952
  }
6778
6953
  else {
6779
6954
  // Rollback to previous version
@@ -6788,9 +6963,10 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6788
6963
  const message = `refusing to roll back to ${previousVersion}: ${validation.message}`;
6789
6964
  return returnCliFailure(deps, message);
6790
6965
  }
6791
- deps.activateCliVersion(previousVersion);
6792
- command = { ...command, version: previousVersion };
6966
+ targetVersion = previousVersion;
6793
6967
  }
6968
+ writeVersionIntentWithRecoveryLauncher(deps, { schemaVersion: 1, mode: "pinned", targetVersion });
6969
+ deps.activateCliVersion(targetVersion);
6794
6970
  // Stop daemon (non-fatal if not running)
6795
6971
  try {
6796
6972
  await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
@@ -6798,7 +6974,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6798
6974
  catch {
6799
6975
  // Daemon may not be running — that's fine
6800
6976
  }
6801
- const message = `rolled back to ${command.version} (was ${currentVersion})`;
6977
+ const message = `rolled back to ${targetVersion} (was ${currentVersion})`;
6802
6978
  deps.writeStdout(message);
6803
6979
  return message;
6804
6980
  }
@@ -6817,7 +6993,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6817
6993
  line += " (previous)";
6818
6994
  return line;
6819
6995
  }).join("\n");
6820
- const sections = [localSection];
6996
+ const intent = deps.readVersionIntent?.() ?? null;
6997
+ const intentLine = intent
6998
+ ? `version intent: ${intent.mode === "pinned" ? `pinned to ${intent.targetVersion}` : `latest (${intent.targetVersion})`}`
6999
+ : "version intent: legacy (not yet recorded)";
7000
+ const sections = [localSection, intentLine];
6821
7001
  if (deps.checkForCliUpdate) {
6822
7002
  try {
6823
7003
  const { result: updateResult, timedOut } = await runCliUpdateCheckWithTimeout(deps.checkForCliUpdate, deps.updateCheckTimeoutMs ?? update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS);
@@ -6843,9 +7023,13 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6843
7023
  title: "Installed versions",
6844
7024
  lines: localSection.split("\n"),
6845
7025
  },
7026
+ {
7027
+ title: "Version intent",
7028
+ lines: [intentLine],
7029
+ },
6846
7030
  {
6847
7031
  title: "Published latest",
6848
- lines: sections.slice(1).length > 0 ? sections.slice(1) : ["published latest: unavailable"],
7032
+ lines: sections.slice(2).length > 0 ? sections.slice(2) : ["published latest: unavailable"],
6849
7033
  },
6850
7034
  ],
6851
7035
  })
@@ -7082,13 +7266,32 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7082
7266
  const mcpAddCmd = `codex mcp add ouro-${setupAgent} -- ${mcpServeCommand}`;
7083
7267
  const mcpRemoveCmd = `codex mcp remove ouro-${setupAgent}`;
7084
7268
  const mcpRegistration = registerMcpServer(mcpAddCmd, mcpRemoveCmd);
7269
+ const canary = await (0, mcp_canary_1.runMcpStatusCanary)({
7270
+ agent: setupAgent,
7271
+ socketPath: deps.socketPath,
7272
+ command: process.execPath,
7273
+ commandArgs: [
7274
+ path.join(__dirname, "ouro-bot-entry.js"),
7275
+ "mcp-serve",
7276
+ "--agent",
7277
+ setupAgent,
7278
+ "--socket",
7279
+ deps.socketPath,
7280
+ ],
7281
+ timeoutMs: mcp_canary_1.SETUP_CANARY_TIMEOUT_MS,
7282
+ });
7283
+ if (!canary.ok)
7284
+ deps.setExitCode?.(1);
7085
7285
  (0, runtime_1.emitNervesEvent)({
7086
7286
  component: "daemon",
7087
7287
  event: "daemon.setup_complete",
7088
7288
  message: "dev tool setup complete",
7089
- meta: { tool, agent: setupAgent, runtimeMode, platform, mcpRegistration },
7289
+ meta: { tool, agent: setupAgent, runtimeMode, platform, mcpRegistration, canary: canary.classification },
7090
7290
  });
7091
- const message = `setup complete: codex + ${setupAgent}\n MCP server ${mcpRegistration}\n${setupReloadNotice("codex")}`;
7291
+ const canarySummary = canary.ok
7292
+ ? canary.summary
7293
+ : `registration succeeded; bridge health failed — ${canary.summary}`;
7294
+ const message = `setup complete: codex + ${setupAgent}\n MCP registration: ${mcpRegistration}\n MCP server ${mcpRegistration}\n MCP canary: ${canary.classification}\n ${canarySummary}\n${setupReloadNotice("codex")}`;
7092
7295
  deps.writeStdout(message);
7093
7296
  return message;
7094
7297
  }
@@ -7123,6 +7326,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7123
7326
  command: process.execPath,
7124
7327
  ignoreOverviewHealth: true,
7125
7328
  ignoreSenseHealth: true,
7329
+ hostStallObserved: command.hostStallObserved === true,
7126
7330
  commandArgs: [
7127
7331
  path.join(__dirname, "ouro-bot-entry.js"),
7128
7332
  "mcp-serve",
@@ -7143,12 +7347,13 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7143
7347
  deps.writeStdout(message);
7144
7348
  return message;
7145
7349
  }
7146
- /* v8 ignore start — mcp-serve block binds to process.stdin/stdout; tested via mcp-server unit tests */
7147
7350
  // ── mcp-serve: start MCP server in-process on stdin/stdout ──
7148
7351
  if (command.kind === "mcp-serve") {
7149
- const { createMcpServer } = await Promise.resolve().then(() => __importStar(require("../mcp/mcp-server")));
7352
+ const createMcpServer = deps.createMcpServer ?? (await Promise.resolve().then(() => __importStar(require("../mcp/mcp-server")))).createMcpServer;
7150
7353
  const friendId = command.friendId ?? `local-${os.userInfo().username}`;
7151
7354
  const mcpSocketPath = command.socketOverride ?? deps.socketPath;
7355
+ const mcpInput = deps.mcpServeInput ?? process.stdin;
7356
+ const mcpOutput = deps.mcpServeOutput ?? process.stdout;
7152
7357
  // Resolve the optional --workbench-mcp flag into a concrete runtime MCP
7153
7358
  // override. A string is taken as the explicit binary path (falling back to
7154
7359
  // discovery if it does not exist); a boolean opt-in (true) self-discovers
@@ -7161,27 +7366,19 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7161
7366
  agent: command.agent,
7162
7367
  friendId,
7163
7368
  socketPath: mcpSocketPath,
7164
- stdin: process.stdin,
7165
- stdout: process.stdout,
7369
+ stdin: mcpInput,
7370
+ stdout: mcpOutput,
7166
7371
  ...(runtimeMcp ? { runtimeMcp } : {}),
7167
7372
  });
7168
- server.start();
7169
7373
  (0, runtime_1.emitNervesEvent)({
7170
7374
  component: "daemon",
7171
7375
  event: "daemon.mcp_serve_started",
7172
7376
  message: "MCP server started via CLI",
7173
7377
  meta: { agent: command.agent, friendId, workbenchRuntimeMcp: !!runtimeMcp },
7174
7378
  });
7175
- // Keep process alive until stdin closes
7176
- await new Promise((resolve) => {
7177
- process.stdin.on("end", () => {
7178
- server.stop();
7179
- resolve();
7180
- });
7181
- });
7379
+ await runMcpServeCliLifecycle(server, mcpInput);
7182
7380
  return "";
7183
7381
  }
7184
- /* v8 ignore stop */
7185
7382
  if (command.kind === "private.decisions") {
7186
7383
  let response;
7187
7384
  try {
@@ -8354,6 +8551,14 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
8354
8551
  deps.writeStdout(message);
8355
8552
  return message;
8356
8553
  }
8554
+ if (command.kind === "daemon.status" && (0, cli_render_1.isDaemonTimeoutError)(error)) {
8555
+ const detail = error instanceof Error ? error.message : String(error);
8556
+ const message = command.json
8557
+ ? daemonTimeoutStatusJsonOutput(deps.socketPath, error)
8558
+ : `daemon status timed out: ${detail}`;
8559
+ deps.writeStdout(message);
8560
+ return message;
8561
+ }
8357
8562
  if (command.kind === "daemon.status" && (0, cli_render_1.isDaemonUnavailableError)(error)) {
8358
8563
  const message = command.json
8359
8564
  ? daemonUnavailableStatusJsonOutput(deps.socketPath, deps.healthFilePath)
@@ -15,8 +15,8 @@ const core_1 = require("../../nerves/review/core");
15
15
  exports.COMMAND_REGISTRY = {
16
16
  up: {
17
17
  category: "Lifecycle",
18
- description: "Start and check Ouro: bring up the background runtime, refresh what this machine needs, and show anything that still needs attention. In a human TTY, bare `ouro` opens the home screen instead; noninteractive shells still route bare `ouro` to `ouro up`.",
19
- usage: "ouro [up] [--no-repair]",
18
+ description: "Start and check Ouro: bring up the background runtime, refresh what this machine needs, and show anything that still needs attention. A pinned rollback stays pinned during ordinary starts; use `--latest` to preflight and explicitly return to the latest release. In a human TTY, bare `ouro` opens the home screen instead; noninteractive shells still route bare `ouro` to `ouro up`.",
19
+ usage: "ouro [up] [--no-repair] [--latest]",
20
20
  example: "ouro up --no-repair",
21
21
  },
22
22
  stop: {
@@ -58,13 +58,13 @@ exports.COMMAND_REGISTRY = {
58
58
  },
59
59
  rollback: {
60
60
  category: "Lifecycle",
61
- description: "Roll back to a previous CLI version",
61
+ description: "Pin Ouro to an exact installed CLI version; ordinary `ouro up` preserves the pinned intent until `ouro up --latest` succeeds",
62
62
  usage: "ouro rollback [<version>]",
63
63
  example: "ouro rollback 0.1.0-alpha.250",
64
64
  },
65
65
  versions: {
66
66
  category: "Lifecycle",
67
- description: "List installed CLI versions",
67
+ description: "List installed CLI versions and show the active version intent mode and target",
68
68
  usage: "ouro versions",
69
69
  example: "ouro versions",
70
70
  },
@@ -363,9 +363,10 @@ exports.COMMAND_REGISTRY = {
363
363
  usage: [
364
364
  "ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type <type>] [--json]",
365
365
  "ouro bluebubbles context-smoke [--agent <name>] --message-guid <guid> [--persist] [--json]",
366
+ "ouro bluebubbles host <install|status|repair|remove|collect> [--username <name> --uid <uid> --home <path>] [--request-id <id>] [--json]",
366
367
  ].join("\n"),
367
368
  example: "ouro bluebubbles context-smoke --agent slugger --message-guid abc123 --persist --json",
368
- subcommands: ["replay", "context-smoke"],
369
+ subcommands: ["replay", "context-smoke", "host"],
369
370
  },
370
371
  };
371
372
  const SUBCOMMAND_HELP = {
@@ -420,10 +421,20 @@ const SUBCOMMAND_HELP = {
420
421
  example: "ouro connect teams",
421
422
  },
422
423
  "connect bluebubbles": {
423
- description: "Attach BlueBubbles iMessage to this machine only; it does not travel with the agent",
424
+ description: "Attach BlueBubbles iMessage to this machine only, install or hand off its native host lifecycle, and reconcile the Ouro-owned webhook; it does not travel with the agent",
424
425
  usage: "ouro connect bluebubbles [--agent <name>]",
425
426
  example: "ouro connect bluebubbles",
426
427
  },
428
+ "bluebubbles host": {
429
+ description: "Inspect or repair the native BlueBubbles host. Same-user actions run directly; dedicated-user actions return one nonce-bound `human-required` Terminal command and a matching collect command.",
430
+ usage: "ouro bluebubbles host <install|status|repair|remove> [--username <name> --uid <uid> --home <path>] [--json]\nouro bluebubbles host collect --request-id <uid>-<nonce> [--json]",
431
+ example: "ouro bluebubbles host status --json",
432
+ },
433
+ "mcp doctor": {
434
+ description: "Run a bounded direct MCP bridge check. Use `--host-stall-observed` only when the host app was independently observed stalled; `host-stall-unexplained` preserves that evidence but does not prove a host-app cause.",
435
+ usage: "ouro mcp doctor --agent <name> [--socket <path>] [--host-stall-observed] [--json]",
436
+ example: "ouro mcp doctor --agent slugger --json",
437
+ },
427
438
  "connect mail": {
428
439
  description: "Provision portable Agent Mail / Mailroom access and enable the Mail sense",
429
440
  usage: "ouro connect mail [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",