@miraland-labs/conduit-bridge 0.11.9 → 0.11.10

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/dist/cli.js CHANGED
@@ -15,9 +15,10 @@ import { DRIVERS } from "./driver.js";
15
15
  import { driversHeartbeatReport, driverLabel, heartbeatStatusForDrivers, isSupportedDriverId, listDriverLanes, onlineDriverIds, seedDriversFromDetection, seedDriverLanes, setDriverFuel, setDriversOnline, } from "./drivers.js";
16
16
  import { buildWorkspaceBrief } from "./brief.js";
17
17
  import { ensureCheckout } from "./checkout.js";
18
+ import { maybeApplyOnShiftIntent } from "./on-shift-apply.js";
19
+ import { loadOpsEnv, OPS_VERBS, runOps } from "./ops.js";
18
20
  import { pumpExecutionSlots, renewLeases } from "./execution.js";
19
21
  import { installRunnerService, uninstallRunnerService } from "./service.js";
20
- import { OPS_VERBS, runOps } from "./ops.js";
21
22
  import { BRIDGE_PROTOCOL_VERSION, cachedBridgePreflight, unavailableWorkspacePreflight } from "./preflight.js";
22
23
  const [command] = process.argv.slice(2);
23
24
  /** Read our own package version so every runner start logs exactly which build is live. */
@@ -473,7 +474,30 @@ async function runner() {
473
474
  const preflight = workspace
474
475
  ? await cachedBridgePreflight({ config, workspace, brief: currentBrief ?? undefined })
475
476
  : unavailableWorkspacePreflight({ config });
476
- await heartbeat(client, config, currentBrief, preflight);
477
+ const hb = await heartbeat(client, config, currentBrief, preflight);
478
+ const applied = await maybeApplyOnShiftIntent({
479
+ client,
480
+ currentWorkspace: workspace,
481
+ onShift: hb.on_shift,
482
+ runInstall: async (nextWorkspace, repo) => {
483
+ let env;
484
+ try {
485
+ env = loadOpsEnv();
486
+ }
487
+ catch {
488
+ env = undefined;
489
+ }
490
+ await runOps("install", [], {
491
+ env: env
492
+ ? { ...env, CONDUIT_WORKSPACE: nextWorkspace, CONDUIT_REPO: repo || env.CONDUIT_REPO }
493
+ : undefined,
494
+ });
495
+ },
496
+ });
497
+ if (applied) {
498
+ console.log("On-shift workspace applied; exiting so the background service restarts on the new checkout.");
499
+ process.exit(0);
500
+ }
477
501
  await renewLeases(client, config);
478
502
  if (workspace && onlineDriverIds(config).length) {
479
503
  progressed = await pumpExecutionSlots(client, config, workspace, brief, timeoutMs, {
@@ -500,7 +524,7 @@ async function runner() {
500
524
  async function heartbeat(client, config, brief, preflight) {
501
525
  const online = onlineDriverIds(config);
502
526
  const status = heartbeatStatusForDrivers(online);
503
- await client.request("/runner/v1/heartbeat", { method: "POST", body: JSON.stringify({
527
+ const response = await client.request("/runner/v1/heartbeat", { method: "POST", body: JSON.stringify({
504
528
  status,
505
529
  capabilities: config.capabilities,
506
530
  lease_capacity: config.leaseCapacity,
@@ -511,6 +535,10 @@ async function heartbeat(client, config, brief, preflight) {
511
535
  preflight,
512
536
  ...(brief ? { workspace_brief: brief } : {}),
513
537
  }) });
538
+ const onShift = response.on_shift && typeof response.on_shift === "object"
539
+ ? response.on_shift
540
+ : null;
541
+ return { on_shift: onShift };
514
542
  }
515
543
  async function disconnect() {
516
544
  const { values } = parseArgs({ args: process.argv.slice(3), options: { yes: { type: "boolean", short: "y" } } });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Phase 2: when Connect queued a workspace intent, Bridge applies it once
3
+ * (ops.env + install/restart) then acks so the next LaunchAgent start uses the new path.
4
+ */
5
+ import { resolve } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { defaultOpsEnvPath, expandOpsValue, loadOpsEnv, writeOpsEnvFile } from "./ops.js";
8
+ function pathsEqual(left, right) {
9
+ return resolve(expandOpsValue(left)) === resolve(expandOpsValue(right));
10
+ }
11
+ /**
12
+ * If control-plane intent names a different workspace than this runner, apply and return true
13
+ * (caller should exit so install-service / LaunchAgent restarts).
14
+ */
15
+ export async function maybeApplyOnShiftIntent(input) {
16
+ const intentPath = input.onShift?.on_shift_intent?.workspace_path?.trim();
17
+ if (!intentPath)
18
+ return false;
19
+ const target = resolve(expandOpsValue(intentPath));
20
+ const current = input.currentWorkspace ? resolve(expandOpsValue(input.currentWorkspace)) : null;
21
+ if (current && pathsEqual(current, target)) {
22
+ // Already on the declared path — leave intent for Bound clear on heartbeat.
23
+ return false;
24
+ }
25
+ const repo = (input.onShift?.on_shift_intent?.repository_url
26
+ ?? input.onShift?.expected_repository
27
+ ?? "").trim();
28
+ if (input.writeEnv) {
29
+ input.writeEnv(target, repo);
30
+ }
31
+ else {
32
+ let envPath = defaultOpsEnvPath(homedir());
33
+ try {
34
+ envPath = loadOpsEnv().loadedFrom ?? envPath;
35
+ }
36
+ catch {
37
+ // First apply may create ops.env at the default path.
38
+ }
39
+ writeOpsEnvFile(envPath, {
40
+ CONDUIT_WORKSPACE: target,
41
+ ...(repo ? { CONDUIT_REPO: repo } : {}),
42
+ });
43
+ }
44
+ // Ack before restart so a rebound service does not re-apply forever.
45
+ await input.client.request("/runner/v1/machine/ack-on-shift-intent", {
46
+ method: "POST",
47
+ body: "{}",
48
+ });
49
+ console.log(`Applying on-shift workspace intent: ${target}${repo ? ` (${repo})` : ""}`);
50
+ await input.runInstall(target, repo);
51
+ return true;
52
+ }
package/dist/ops.js CHANGED
@@ -232,13 +232,24 @@ export async function runOps(verb, argv = [], deps = {}) {
232
232
  const client = new ConduitClient(config);
233
233
  const result = await client.request("/runner/v1/machine/switch-project", {
234
234
  method: "POST",
235
- body: JSON.stringify({ project_id: projectId }),
235
+ body: JSON.stringify({
236
+ project_id: projectId,
237
+ workspace_path: workspace,
238
+ ...(repo ? { repository_url: repo } : {}),
239
+ }),
236
240
  });
237
241
  console.log(`Control plane on-shift: ${String(result.project_id ?? projectId)} (${String(result.binding_status ?? "pending")})`);
238
242
  writeOpsEnvFile(envPath, {
239
243
  CONDUIT_WORKSPACE: workspace,
240
244
  ...(repo ? { CONDUIT_REPO: repo } : {}),
241
245
  });
246
+ // Local apply owns the checkout; clear queued intent so heartbeat does not re-apply.
247
+ try {
248
+ await client.request("/runner/v1/machine/ack-on-shift-intent", { method: "POST", body: "{}" });
249
+ }
250
+ catch {
251
+ // Older control planes omit ack — local ops.env still wins.
252
+ }
242
253
  console.log(`Updated ${envPath}`);
243
254
  console.log(`On-shift workspace: ${workspace}${repo ? ` (${repo})` : ""}`);
244
255
  // Same install path as ops install — restart runner against the new checkout.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.11.9",
3
+ "version": "0.11.10",
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": {