@miraland-labs/conduit-bridge 0.16.23 → 0.16.24

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
@@ -14,6 +14,7 @@ import { detectInstalledClients, localFuelOnlyClients, suggestFuelSource } from
14
14
  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
+ import { BOOTSTRAP_RESULTS, buildSovereignExecutionFacts } from "./execution-facts.js";
17
18
  import { ensureCheckout } from "./checkout.js";
18
19
  import { buildOnShiftIntentProof, maybeApplyOnShiftIntent } from "./on-shift-apply.js";
19
20
  import { loadOpsEnv, OPS_VERBS, runOps } from "./ops.js";
@@ -37,6 +38,12 @@ function parseFuelSource(value) {
37
38
  return value;
38
39
  throw new Error("Fuel source must be conduit or local");
39
40
  }
41
+ function parseBootstrapResult(value) {
42
+ const result = BOOTSTRAP_RESULTS.find((candidate) => candidate === value);
43
+ if (!result)
44
+ throw new Error(`Bootstrap result must be one of: ${BOOTSTRAP_RESULTS.join(", ")}`);
45
+ return result;
46
+ }
40
47
  async function connect() {
41
48
  const { values } = parseArgs({ args: process.argv.slice(3), options: { url: { type: "string" }, code: { type: "string" }, fuel: { type: "string" } } });
42
49
  if (!values.url || !values.code)
@@ -319,7 +326,7 @@ async function installService() {
319
326
  let config = await loadConfig();
320
327
  const { values } = parseArgs({ args: process.argv.slice(3), options: {
321
328
  workspace: { type: "string" }, interval: { type: "string" }, "agent-timeout-minutes": { type: "string" },
322
- "ensure-checkout": { type: "string" },
329
+ "ensure-checkout": { type: "string" }, "bootstrap-result": { type: "string" },
323
330
  } });
324
331
  if (!values.workspace) {
325
332
  throw new Error(`Usage: ${bridgeUsage("install-service", "--workspace", "<repository-path>", "[--ensure-checkout <repository-url>]")} (workspace required; uses online driver lanes)`);
@@ -329,6 +336,9 @@ async function installService() {
329
336
  throw new Error(`No online driver lanes. Run \`${bridgeUsage("drivers", "online", AGENT_PLACEHOLDER)}\` first.`);
330
337
  }
331
338
  const workspace = resolve(values.workspace);
339
+ const bootstrapResult = values["bootstrap-result"]
340
+ ? parseBootstrapResult(values["bootstrap-result"])
341
+ : undefined;
332
342
  if (values["ensure-checkout"]) {
333
343
  const result = await ensureCheckout(workspace, values["ensure-checkout"]);
334
344
  console.log(result === "cloned"
@@ -340,6 +350,7 @@ async function installService() {
340
350
  interval: values.interval,
341
351
  agentTimeoutMinutes: values["agent-timeout-minutes"],
342
352
  ensureCheckout: values["ensure-checkout"],
353
+ bootstrapResult,
343
354
  });
344
355
  console.log(`Installed Conduit Bridge v${bridgeVersion()} (protocol ${BRIDGE_PROTOCOL_VERSION}) runner service (${result.platform}): ${result.path}`);
345
356
  console.log(`Online lanes: ${onlineDriverIds(config).join(", ")}. Toggle with \`${bridgeUsage("drivers", "online|offline", "…")}\`.`);
@@ -466,9 +477,13 @@ async function runner() {
466
477
  const { values } = parseArgs({ args: process.argv.slice(3), options: {
467
478
  workspace: { type: "string" }, interval: { type: "string" }, once: { type: "boolean" },
468
479
  "agent-timeout-minutes": { type: "string" }, fuel: { type: "string" }, "ensure-checkout": { type: "string" },
480
+ "bootstrap-result": { type: "string" },
469
481
  } });
470
482
  let config = await loadConfig();
471
483
  const fuelOverride = parseFuelSource(values.fuel);
484
+ const bootstrapResult = values["bootstrap-result"]
485
+ ? parseBootstrapResult(values["bootstrap-result"])
486
+ : undefined;
472
487
  if (fuelOverride) {
473
488
  config.fuelSource = fuelOverride;
474
489
  await saveConfigPrefs(config);
@@ -511,7 +526,7 @@ async function runner() {
511
526
  const preflight = workspace
512
527
  ? await cachedBridgePreflight({ config, workspace, brief: currentBrief ?? undefined })
513
528
  : unavailableWorkspacePreflight({ config });
514
- const hb = await heartbeat(client, config, workspace, currentBrief, preflight);
529
+ const hb = await heartbeat(client, config, workspace, currentBrief, preflight, undefined, bootstrapResult);
515
530
  if (hb.staleBridge) {
516
531
  console.error(`Bridge update required: running protocol ${BRIDGE_PROTOCOL_VERSION}; control plane requires protocol ${hb.requiredProtocol ?? "unknown"} (minimum Bridge ${hb.minimumVersion ?? "unknown"}). Reinstall the latest Bridge, then restart this runner.`);
517
532
  await new Promise((resolveSleep) => setTimeout(resolveSleep, Math.max(intervalMs, 60_000)));
@@ -548,7 +563,7 @@ async function runner() {
548
563
  }
549
564
  const intentProof = buildOnShiftIntentProof({ currentWorkspace: workspace, onShift: hb.on_shift });
550
565
  if (intentProof) {
551
- await heartbeat(client, config, workspace, currentBrief, preflight, intentProof);
566
+ await heartbeat(client, config, workspace, currentBrief, preflight, intentProof, bootstrapResult);
552
567
  }
553
568
  await renewLeases(client, config);
554
569
  if (workspace && onlineDriverIds(config).length) {
@@ -561,7 +576,7 @@ async function runner() {
561
576
  heartbeat: async () => {
562
577
  const currentBrief = await buildWorkspaceBrief(workspace).catch(() => brief);
563
578
  const preflight = await cachedBridgePreflight({ config, workspace, brief: currentBrief ?? undefined });
564
- await heartbeat(client, config, workspace, currentBrief, preflight);
579
+ await heartbeat(client, config, workspace, currentBrief, preflight, undefined, bootstrapResult);
565
580
  },
566
581
  beforeClaim: async (driverId) => {
567
582
  // Never let the five-minute heartbeat cache span a CLI upgrade into a certified claim.
@@ -569,7 +584,7 @@ async function runner() {
569
584
  // exact version frozen onto the claim response and event.
570
585
  const currentBrief = await buildWorkspaceBrief(workspace).catch(() => brief);
571
586
  const preflight = await cachedBridgePreflight({ config, workspace, brief: currentBrief ?? undefined }, { force: true });
572
- const hb = await heartbeat(client, config, workspace, currentBrief, preflight);
587
+ const hb = await heartbeat(client, config, workspace, currentBrief, preflight, undefined, bootstrapResult);
573
588
  if (hb.staleBridge)
574
589
  return false;
575
590
  const lane = preflight.drivers.find((candidate) => candidate.id === driverId);
@@ -590,19 +605,29 @@ async function runner() {
590
605
  await new Promise((resolveSleep) => setTimeout(resolveSleep, intervalMs));
591
606
  }
592
607
  }
593
- async function heartbeat(client, config, workspacePath, brief, preflight, intentProof) {
608
+ async function heartbeat(client, config, workspacePath, brief, preflight, intentProof, bootstrapResult) {
594
609
  const online = onlineDriverIds(config);
595
610
  const status = heartbeatStatusForDrivers(online);
611
+ const version = bridgeVersion();
612
+ const executionFacts = await buildSovereignExecutionFacts({
613
+ workspace: workspacePath,
614
+ brief,
615
+ workspaceClean: preflight.workspace_clean,
616
+ bridgeVersion: version,
617
+ bridgeProtocol: BRIDGE_PROTOCOL_VERSION,
618
+ bootstrapResult,
619
+ });
596
620
  const response = await client.request("/runner/v1/heartbeat", { method: "POST", body: JSON.stringify({
597
621
  status,
598
622
  capabilities: config.capabilities,
599
623
  lease_capacity: config.leaseCapacity,
600
624
  workspace_path: workspacePath,
601
625
  fuel_source: config.fuelSource === "local" ? "local" : "conduit",
602
- bridge_version: bridgeVersion(),
626
+ bridge_version: version,
603
627
  bridge_protocol: BRIDGE_PROTOCOL_VERSION,
604
628
  drivers: driversHeartbeatReport(config, config.activeAttempts),
605
629
  preflight,
630
+ execution_facts: executionFacts,
606
631
  ...(intentProof ? { on_shift_intent_proof: intentProof } : {}),
607
632
  ...(brief ? { workspace_brief: brief } : {}),
608
633
  }) });
@@ -0,0 +1,127 @@
1
+ import { createHash } from "node:crypto";
2
+ import { access, readFile, stat } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ import { normalizeRepositoryUrl } from "./brief.js";
7
+ const execFileAsync = promisify(execFile);
8
+ export const EXECUTION_CLASSES = ["observe", "observe_network", "publish_artifact", "verify", "mutate_repo"];
9
+ export const BOOTSTRAP_RESULTS = ["not_applicable", "not_run", "installed", "failed"];
10
+ /**
11
+ * Lockfiles are hashed, never uploaded. The set is intentionally explicit and root-scoped so a
12
+ * dependency change becomes a durable identity without moving repository contents onto Conduit.
13
+ */
14
+ const LOCKFILE_NAMES = [
15
+ "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml",
16
+ "Cargo.lock", "poetry.lock", "Pipfile.lock", "go.sum", "Gemfile.lock", "composer.lock",
17
+ ];
18
+ /** Stable identity for the exact bounded verification command list used by Bridge. */
19
+ export function verificationCommandsDigest(commands) {
20
+ const normalized = [...new Set(commands.map((command) => command.trim()).filter(Boolean))];
21
+ if (!normalized.length)
22
+ return null;
23
+ return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
24
+ }
25
+ /**
26
+ * Hash the root lockfiles in deterministic name order. Missing or unreadable lockfiles are simply
27
+ * absent from the identity; a workspace with no readable lockfile reports null rather than an
28
+ * invented digest.
29
+ */
30
+ export async function lockfileDigest(workspace) {
31
+ const hash = createHash("sha256");
32
+ let found = false;
33
+ for (const name of LOCKFILE_NAMES) {
34
+ try {
35
+ const contents = await readFile(join(workspace, name));
36
+ hash.update(name);
37
+ hash.update("\0");
38
+ hash.update(contents);
39
+ hash.update("\0");
40
+ found = true;
41
+ }
42
+ catch {
43
+ // A missing or unreadable optional lockfile contributes no claim.
44
+ }
45
+ }
46
+ return found ? hash.digest("hex") : null;
47
+ }
48
+ /**
49
+ * Report whether a JavaScript workspace has dependencies installed. This is an observed state, not
50
+ * a promise that Bridge will run a package manager during execution. Managed installs already run
51
+ * `npm ci` before the service is advertised, so a present node_modules directory is the durable
52
+ * local evidence available on later heartbeats.
53
+ */
54
+ export async function bootstrapResultForWorkspace(workspace) {
55
+ if (!workspace)
56
+ return "not_applicable";
57
+ try {
58
+ await access(join(workspace, "package.json"));
59
+ }
60
+ catch {
61
+ return "not_applicable";
62
+ }
63
+ try {
64
+ const lockfile = await Promise.any(LOCKFILE_NAMES.map(async (name) => {
65
+ await access(join(workspace, name));
66
+ return true;
67
+ }));
68
+ void lockfile;
69
+ }
70
+ catch {
71
+ return "not_run";
72
+ }
73
+ try {
74
+ const dependencies = await stat(join(workspace, "node_modules"));
75
+ return dependencies.isDirectory() ? "installed" : "not_run";
76
+ }
77
+ catch {
78
+ return "not_run";
79
+ }
80
+ }
81
+ /** Read the source/attempt tree's Git cleanliness without returning its contents. */
82
+ export async function workspaceIsClean(workspace) {
83
+ try {
84
+ const result = await execFileAsync("git", ["-C", workspace, "status", "--porcelain", "--untracked-files=normal"], {
85
+ timeout: 15_000,
86
+ windowsHide: true,
87
+ });
88
+ return result.stdout.trim().length === 0;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ /** Read only the bounded HEAD identity retained when an attempt terminates. */
95
+ export async function finalCommitForWorkspace(workspace) {
96
+ try {
97
+ const result = await execFileAsync("git", ["-C", workspace, "rev-parse", "HEAD"], {
98
+ timeout: 15_000,
99
+ windowsHide: true,
100
+ });
101
+ return commitOrNull(result.stdout.trim());
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ function commitOrNull(value) {
108
+ return value && /^[0-9a-f]{40,64}$/i.test(value) ? value.toLowerCase() : null;
109
+ }
110
+ /** Build one self-contained heartbeat/attempt snapshot from existing Bridge observations. */
111
+ export async function buildSovereignExecutionFacts(input) {
112
+ const repository = input.brief?.repository?.trim();
113
+ return {
114
+ os: process.platform,
115
+ architecture: process.arch,
116
+ repository_fingerprint: repository ? normalizeRepositoryUrl(repository) : null,
117
+ claimed_head: commitOrNull(input.claimedHead ?? input.brief?.base_commit),
118
+ lockfile_digest: input.workspace ? await lockfileDigest(input.workspace) : null,
119
+ bootstrap_result: input.bootstrapResult ?? await bootstrapResultForWorkspace(input.workspace),
120
+ bridge_version: input.bridgeVersion,
121
+ bridge_protocol: input.bridgeProtocol,
122
+ execution_class: input.executionClass ?? null,
123
+ verification_commands_digest: verificationCommandsDigest(input.brief?.verification ?? []),
124
+ workspace_clean: input.workspaceClean,
125
+ final_commit: commitOrNull(input.finalCommit),
126
+ };
127
+ }
package/dist/execution.js CHANGED
@@ -10,6 +10,9 @@ import { attemptWorktreePath, createAttemptWorktree, proveResumeWorktree, quaran
10
10
  import { execFile } from "node:child_process";
11
11
  import { promisify } from "node:util";
12
12
  import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl, resolveAttemptStartCommit } from "./brief.js";
13
+ import { buildSovereignExecutionFacts, bootstrapResultForWorkspace, finalCommitForWorkspace, workspaceIsClean } from "./execution-facts.js";
14
+ import { BRIDGE_PROTOCOL_VERSION } from "./preflight.js";
15
+ import { bridgeVersion } from "./version.js";
13
16
  import { ensureDeliveryPullRequest } from "./ensure-pull-request.js";
14
17
  import { ensureLandCommit } from "./ensure-land-commit.js";
15
18
  import { AgentNoLandCommitError, agentNoLandCommitMessage, requiresLandCommit } from "./land-contract.js";
@@ -907,8 +910,20 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
907
910
  normativeRefs: normativeMaterialized,
908
911
  });
909
912
  const resuming = Boolean(options.forceResumeSessionId);
913
+ const executionFacts = await buildSovereignExecutionFacts({
914
+ workspace: attemptWorkspace,
915
+ brief: attemptBrief,
916
+ workspaceClean: await workspaceIsClean(workspace),
917
+ bridgeVersion: bridgeVersion(),
918
+ bridgeProtocol: BRIDGE_PROTOCOL_VERSION,
919
+ executionClass,
920
+ claimedHead: executionContract.claimed_head ?? startCommit,
921
+ bootstrapResult: await bootstrapResultForWorkspace(workspace),
922
+ });
923
+ await client.updateAttempt(taskId, { executionFacts });
910
924
  await client.attemptRequest(taskId, "progress", {
911
925
  phase: diagnosis ? "inspecting" : "changing",
926
+ execution_facts: executionFacts,
912
927
  message: resuming
913
928
  ? `Resuming ${driver.name} after Bridge restart with proven session and worktree.`
914
929
  : diagnosis
@@ -1595,6 +1610,10 @@ async function submitFinishedDelivery(client, taskId) {
1595
1610
  const active = client.attempt(taskId);
1596
1611
  if (!active.delivery)
1597
1612
  throw new Error("Finished agent run is missing its persisted Delivery data");
1613
+ const finalCommit = active.delivery.report.head_commit?.toLowerCase() ?? null;
1614
+ if (finalCommit && active.executionFacts) {
1615
+ await client.updateAttempt(taskId, { executionFacts: { ...active.executionFacts, final_commit: finalCommit } });
1616
+ }
1598
1617
  const terminal = await prepareDelivery(client, active.attemptId, taskId, active.delivery.report);
1599
1618
  await client.attemptRequest(taskId, "progress", { phase: "preparing_delivery", message: "Agent finished; submitting the Delivery.", idempotency_key: `bridge:progress:${active.attemptId}:delivery` });
1600
1619
  return queueTerminal(client, taskId, terminal);
@@ -1870,7 +1889,19 @@ function retainDiagnosticWorktree(response) {
1870
1889
  return response.retain_worktree === true || response.status === "invalid_lease";
1871
1890
  }
1872
1891
  async function queueTerminal(client, taskId, terminal) {
1873
- await client.updateAttempt(taskId, { phase: "terminal_pending", terminal });
1892
+ const active = client.attempt(taskId);
1893
+ let executionFacts = active.executionFacts;
1894
+ if (executionFacts && executionFacts.final_commit === null && active.worktreePath) {
1895
+ const finalCommit = await finalCommitForWorkspace(active.worktreePath);
1896
+ if (finalCommit) {
1897
+ executionFacts = { ...executionFacts, final_commit: finalCommit };
1898
+ await client.updateAttempt(taskId, { executionFacts });
1899
+ }
1900
+ }
1901
+ const body = executionFacts && !terminal.body.execution_facts
1902
+ ? { ...terminal.body, execution_facts: executionFacts }
1903
+ : terminal.body;
1904
+ await client.updateAttempt(taskId, { phase: "terminal_pending", terminal: { ...terminal, body } });
1874
1905
  return flushTerminal(client, taskId);
1875
1906
  }
1876
1907
  export async function flushTerminal(client, taskId) {
package/dist/ops.js CHANGED
@@ -570,6 +570,7 @@ export async function runOps(verb, argv = [], deps = {}) {
570
570
  }
571
571
  const workspace = resolve(expandOpsValue(installEnv.CONDUIT_WORKSPACE));
572
572
  const drivers = await (deps.resolveInstallDrivers ?? resolveInstallDrivers)(installEnv, installArgv);
573
+ let bootstrapResult;
573
574
  if (installEnv.CONDUIT_REPO) {
574
575
  const checkout = deps.ensureCheckout ?? ensureCheckout;
575
576
  const result = await checkout(workspace, installEnv.CONDUIT_REPO);
@@ -587,7 +588,7 @@ export async function runOps(verb, argv = [], deps = {}) {
587
588
  && !isAbsolute(managedRelative));
588
589
  if (managedCheckout) {
589
590
  const bootstrap = deps.bootstrapWorkspace ?? bootstrapManagedWorkspace;
590
- const bootstrapResult = bootstrap(workspace);
591
+ bootstrapResult = bootstrap(workspace);
591
592
  if (bootstrapResult === "installed")
592
593
  console.log(`Installed locked project dependencies in ${workspace}`);
593
594
  }
@@ -616,6 +617,8 @@ export async function runOps(verb, argv = [], deps = {}) {
616
617
  const installArgs = ["install-service", "--workspace", workspace];
617
618
  if (installEnv.CONDUIT_REPO)
618
619
  installArgs.push("--ensure-checkout", installEnv.CONDUIT_REPO);
620
+ if (bootstrapResult)
621
+ installArgs.push("--bootstrap-result", bootstrapResult);
619
622
  console.log(`Bridge: v${bridgeVersion()} (protocol ${BRIDGE_PROTOCOL_VERSION})`);
620
623
  console.log(`Installing runner for ${workspace} (drivers: ${drivers.join(", ")})`);
621
624
  runBridge(installArgs);
package/dist/service.js CHANGED
@@ -30,6 +30,8 @@ export function runnerProgramArguments(options = {}) {
30
30
  args.push("--interval", options.interval);
31
31
  if (options.agentTimeoutMinutes)
32
32
  args.push("--agent-timeout-minutes", options.agentTimeoutMinutes);
33
+ if (options.bootstrapResult)
34
+ args.push("--bootstrap-result", options.bootstrapResult);
33
35
  return args;
34
36
  }
35
37
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.23",
3
+ "version": "0.16.24",
4
4
  "description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {