@ouro.bot/cli 0.1.0-alpha.726 → 0.1.0-alpha.728
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/assets/bluebubbles-host +264 -0
- package/changelog.json +18 -0
- package/dist/heart/config.js +1 -2
- package/dist/heart/core.js +181 -209
- package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
- package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
- package/dist/heart/daemon/bluebubbles-host.js +273 -0
- package/dist/heart/daemon/cli-defaults.js +8 -1
- package/dist/heart/daemon/cli-exec.js +239 -34
- package/dist/heart/daemon/cli-help.js +17 -6
- package/dist/heart/daemon/cli-parse.js +69 -1
- package/dist/heart/daemon/cli-render.js +7 -0
- package/dist/heart/daemon/daemon.js +79 -14
- package/dist/heart/daemon/doctor.js +21 -1
- package/dist/heart/daemon/mcp-canary.js +160 -29
- package/dist/heart/daemon/process-manager.js +100 -18
- package/dist/heart/daemon/socket-client.js +28 -19
- package/dist/heart/providers/anthropic.js +17 -12
- package/dist/heart/streaming.js +6 -6
- package/dist/heart/versioning/ouro-path-installer.js +30 -24
- package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
- package/dist/heart/versioning/version-intent.js +114 -0
- package/dist/mind/prompt-budget.js +104 -5
- package/dist/repertoire/tools.js +0 -10
- package/dist/senses/bluebubbles/client.js +43 -57
- package/dist/senses/bluebubbles/context-packet.js +104 -19
- package/dist/senses/bluebubbles/entry.js +14 -1
- package/dist/senses/bluebubbles/index.js +1398 -719
- package/dist/senses/bluebubbles/latest-turn.js +311 -0
- package/dist/senses/bluebubbles/outbound-state.js +4 -0
- package/dist/senses/bluebubbles/reaction-policy.js +4 -12
- package/dist/senses/bluebubbles/semantic-receipts.js +178 -10
- package/dist/senses/bluebubbles/webhook-registration.js +228 -0
- package/package.json +4 -2
|
@@ -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
|
-
|
|
3655
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
7165
|
-
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
|
-
|
|
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: "
|
|
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]",
|