@miraland-labs/conduit-bridge 0.11.11 → 0.11.12

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.
Files changed (2) hide show
  1. package/dist/service.js +29 -6
  2. package/package.json +1 -1
package/dist/service.js CHANGED
@@ -2,7 +2,7 @@ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
2
2
  import { homedir, platform } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  import { realpathSync } from "node:fs";
5
- import { spawnSync } from "node:child_process";
5
+ import { spawn, spawnSync } from "node:child_process";
6
6
  export const SERVICE_LABEL = "io.miraland.conduit-runner";
7
7
  function configDir() {
8
8
  return join(homedir(), ".config", "conduit");
@@ -118,6 +118,22 @@ function run(command, args) {
118
118
  throw new Error(`${command} ${args.join(" ")} failed: ${detail}`);
119
119
  }
120
120
  }
121
+ function shellQuote(value) {
122
+ return `'${value.replaceAll("'", `'\\''`)}'`;
123
+ }
124
+ /**
125
+ * `launchctl bootout` of a loaded service kills that service's whole process group — including
126
+ * this process when install-service runs from inside the runner it replaces (Connect on-shift
127
+ * auto-apply). Observed in the field (2026-08-05): the runner died at bootout, the bootstrap on
128
+ * the next line never ran, and the machine stayed Binding incomplete with the service unloaded.
129
+ * The swap therefore runs in a detached helper that survives the bootout; failures append to the
130
+ * runner log.
131
+ */
132
+ export function launchdSwapCommand(domain, plistPath, log) {
133
+ const plist = shellQuote(plistPath);
134
+ const logQ = shellQuote(log);
135
+ return `sleep 1; launchctl bootout ${domain}/${SERVICE_LABEL} 2>>${logQ}; launchctl bootstrap ${domain} ${plist} 2>>${logQ} || launchctl load -w ${plist} 2>>${logQ}`;
136
+ }
121
137
  export async function installRunnerService(options = {}) {
122
138
  const host = platform();
123
139
  if (host !== "darwin" && host !== "linux") {
@@ -129,12 +145,19 @@ export async function installRunnerService(options = {}) {
129
145
  await mkdir(dirname(plistPath), { recursive: true });
130
146
  await mkdir(dirname(logPath()), { recursive: true });
131
147
  await writeFile(plistPath, launchdPlist(programArguments, logPath()), { mode: 0o644 });
132
- // Prefer modern bootout/bootstrap; fall back to load for older macOS.
133
- spawnSync("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${SERVICE_LABEL}`], { encoding: "utf8" });
134
- const boot = spawnSync("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? 501}`, plistPath], { encoding: "utf8" });
135
- if (boot.status !== 0) {
136
- run("launchctl", ["load", "-w", plistPath]);
148
+ // Detached helper (bootout → bootstrap → load fallback): see launchdSwapCommand for why the
149
+ // swap must survive this process dying at bootout.
150
+ const domain = `gui/${process.getuid?.() ?? 501}`;
151
+ spawn("/bin/sh", ["-c", launchdSwapCommand(domain, plistPath, logPath())], { detached: true, stdio: "ignore" }).unref();
152
+ // Confirm the reloaded service when this process survives to see it (an interactive install).
153
+ // A self-replacing runner dies at the helper's bootout and the helper still finishes the swap.
154
+ let loaded = false;
155
+ for (let attempt = 0; attempt < 16 && !loaded; attempt++) {
156
+ await new Promise((resolveSleep) => setTimeout(resolveSleep, attempt === 0 ? 2000 : 500));
157
+ loaded = spawnSync("launchctl", ["print", `${domain}/${SERVICE_LABEL}`], { encoding: "utf8" }).status === 0;
137
158
  }
159
+ if (!loaded)
160
+ throw new Error(`launchd did not load ${SERVICE_LABEL}; see ${logPath()}`);
138
161
  await writeFile(serviceStatePath(), `${JSON.stringify({ platform: "darwin", programArguments, options, installed_at: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
139
162
  return { path: plistPath, platform: "darwin" };
140
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.11.11",
3
+ "version": "0.11.12",
4
4
  "description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {