@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 +31 -3
- package/dist/on-shift-apply.js +52 -0
- package/dist/ops.js +12 -1
- package/package.json +1 -1
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({
|
|
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.
|
|
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": {
|