@miraland-labs/conduit-bridge 0.9.7 → 0.9.8

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Local Bridge CLI for [Conduit](https://github.com/miralandlabs/conduit). Connects a computer to one organization, claims work, and drives a local agent.
4
4
 
5
- **Current npm:** `0.9.6` — **ops** helpers for macOS / Linux / Windows, LaunchAgent/systemd **PATH** for `~/.local/bin`, disconnect/disengage, multi-driver lanes, shared slots **1–4**, worktrees, optional `--ensure-checkout`.
5
+ **Package version:** `0.9.8` — heartbeat protocol 2 requires a clean workspace plus a ready, versioned driver lane before dispatch. It also includes **ops** helpers for macOS / Linux / Windows, LaunchAgent/systemd **PATH** for `~/.local/bin`, disconnect/disengage, multi-driver lanes, shared slots **1–4**, worktrees, and optional `--ensure-checkout`. Protocol 1 heartbeats remain compatible for presence but cannot receive work.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -63,6 +63,18 @@ npx @miraland-labs/conduit-bridge disconnect --yes
63
63
 
64
64
  **Capacity:** Connect-approved `lease_capacity` (1–4) is a **shared pool** for all online lanes on that computer.
65
65
 
66
+ ## Readiness and lane status
67
+
68
+ Before agent spend, Bridge checks that the workspace is readable, matches the expected repository,
69
+ and is clean; each online driver must be installed, compatible with its configured fuel, and signed
70
+ in when local fuel is used. The same bounded report is sent on heartbeat so Conduit can place an
71
+ environment failure on Hold without consuming an execution attempt. Fix the named issue and let a
72
+ fresh heartbeat land before choosing **Recheck** in Activity.
73
+
74
+ Conduit labels an exact driver/CLI version **Certified** only after two consecutive real canaries:
75
+ repository delivery with PR/evidence and a successful rework cycle. All other lanes are
76
+ **Experimental**. A lane runs only when its computer operator explicitly brings it online.
77
+
66
78
  ## Grant enforcement
67
79
 
68
80
  | Driver | Fuel | Mechanism |
package/dist/driver.js CHANGED
@@ -345,7 +345,7 @@ export function codexSandboxForGrants(grants) {
345
345
  }
346
346
  /**
347
347
  * Codex exec arguments. The sandbox is the enforcement boundary: network stays
348
- * off inside workspace-write unless the assignment actually holds pr_create.
348
+ * off inside workspace-write unless the canonical contract requires external_network.
349
349
  */
350
350
  export function codexExecArgs(input, sandbox) {
351
351
  // exec is non-interactive by design (no approval flag), and `exec resume`
@@ -355,7 +355,7 @@ export function codexExecArgs(input, sandbox) {
355
355
  : ["exec", "--json", "--sandbox", sandbox];
356
356
  if (input.model)
357
357
  args.push("-m", input.model);
358
- if (sandbox === "workspace-write" && input.grants.includes("pr_create")) {
358
+ if (sandbox === "workspace-write" && input.capabilities?.includes("external_network")) {
359
359
  args.push("-c", "sandbox_workspace_write.network_access=true");
360
360
  }
361
361
  if (input.resumeSessionId)
package/dist/execution.js CHANGED
@@ -45,6 +45,7 @@ const taskDetailSchema = z.object({
45
45
  const taskSpecSchema = z.object({
46
46
  goal: z.string().optional(), scope: z.array(z.string()).optional(), boundaries: z.array(z.string()).optional(),
47
47
  acceptance: z.array(z.string()).optional(), required_evidence: z.array(z.enum(evidenceKinds)).optional(),
48
+ required_capabilities: z.array(z.string()).optional(),
48
49
  change_scope: z.array(z.string()).optional(), work_role: z.string().optional(),
49
50
  repository: z.object({ url: z.string().optional(), base_commit: z.string().optional() }).nullable().optional(),
50
51
  risk_level: z.string().optional(),
@@ -399,6 +400,7 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
399
400
  prompt,
400
401
  workspace: attemptWorkspace,
401
402
  grants,
403
+ capabilities: spec.required_capabilities ?? [],
402
404
  verificationCommands: liveBrief?.verification ?? [],
403
405
  resumeSessionId,
404
406
  timeoutMs,
@@ -436,6 +438,7 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
436
438
  // Preserve only existing read authority. No write, test, branch, or push
437
439
  // capability is available while the agent repairs the response envelope.
438
440
  grants: grants.filter((grant) => grant === "repo_read"),
441
+ capabilities: [],
439
442
  verificationCommands: [],
440
443
  resumeSessionId: result.sessionId ?? undefined,
441
444
  timeoutMs,
package/dist/preflight.js CHANGED
@@ -3,7 +3,7 @@ import { promisify } from "node:util";
3
3
  import { buildWorkspaceBrief, normalizeRepositoryUrl } from "./brief.js";
4
4
  import { hasAntigravityLogin, hasClaudeLogin, hasOpenAiLogin, hasOpenCodeLogin, resolveCodexExecutable, } from "./driver.js";
5
5
  import { localFuelOnlyDriver, onlineDriverIds, resolveDriverFuel } from "./drivers.js";
6
- export const BRIDGE_PROTOCOL_VERSION = 1;
6
+ export const BRIDGE_PROTOCOL_VERSION = 2;
7
7
  const execFileAsync = promisify(execFile);
8
8
  async function defaultCommandRunner(command, args, cwd) {
9
9
  try {
@@ -70,22 +70,30 @@ export async function runBridgePreflight(input, deps = {}) {
70
70
  && normalizeRepositoryUrl(brief.repository) !== normalizeRepositoryUrl(input.expectedRepository)) {
71
71
  issues.push({ code: "workspace_repository_mismatch" });
72
72
  }
73
- const driverIssues = await Promise.all(online.map(async (driver) => {
73
+ let workspaceClean = false;
74
+ if (brief) {
75
+ const status = await run("git", ["status", "--porcelain", "--untracked-files=normal"], input.workspace);
76
+ workspaceClean = status.code === 0 && status.stdout.trim().length === 0;
77
+ if (!workspaceClean)
78
+ issues.push({ code: status.code === 0 ? "workspace_dirty" : "workspace_unavailable" });
79
+ }
80
+ const driverChecks = await Promise.all(online.map(async (driver) => {
74
81
  const fuel = resolveDriverFuel(input.config, driver);
75
82
  if (localFuelOnlyDriver(driver) && fuel !== "local") {
76
- return { code: "driver_fuel_mismatch", driver };
83
+ return { issue: { code: "driver_fuel_mismatch", driver }, snapshot: { id: driver, version: null, ready: false } };
77
84
  }
78
85
  const version = await run(executableFor(driver), ["--version"], input.workspace);
86
+ const versionText = `${version.stdout}${version.stderr}`.trim().slice(0, 200) || null;
79
87
  if (version.code !== 0 || !`${version.stdout}${version.stderr}`.trim()) {
80
- return { code: "driver_missing", driver };
88
+ return { issue: { code: "driver_missing", driver }, snapshot: { id: driver, version: versionText, ready: false } };
81
89
  }
82
90
  if (fuel === "local" && !await localAuthenticationReady(driver, input.workspace, run)) {
83
- return { code: "driver_not_authenticated", driver };
91
+ return { issue: { code: "driver_not_authenticated", driver }, snapshot: { id: driver, version: versionText, ready: false } };
84
92
  }
85
- return null;
93
+ return { issue: null, snapshot: { id: driver, version: versionText, ready: true } };
86
94
  }));
87
- issues.push(...driverIssues.filter((issue) => issue !== null));
88
- return { ready: issues.length === 0, checked_at: new Date().toISOString(), issues };
95
+ issues.push(...driverChecks.map((check) => check.issue).filter((issue) => issue !== null));
96
+ return { ready: issues.length === 0, checked_at: new Date().toISOString(), workspace_clean: workspaceClean, drivers: driverChecks.map((check) => check.snapshot), issues };
89
97
  }
90
98
  let cached = null;
91
99
  /** Keep auth probes off the 15-second heartbeat hot path while still expiring readiness promptly. */
@@ -108,6 +116,8 @@ export function describePreflightIssue(issue) {
108
116
  return "Workspace has no origin repository";
109
117
  if (issue.code === "workspace_repository_mismatch")
110
118
  return "Workspace origin does not match CONDUIT_REPO";
119
+ if (issue.code === "workspace_dirty")
120
+ return "Workspace has uncommitted or untracked changes";
111
121
  if (issue.code === "driver_missing")
112
122
  return `Agent CLI is missing from PATH${lane}`;
113
123
  if (issue.code === "driver_not_authenticated")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Kiro / Antigravity agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {