@miraland-labs/conduit-bridge 0.16.207 → 0.16.210
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/driver.js +26 -6
- package/dist/ensure-land-commit.js +5 -4
- package/dist/ensure-test-evidence.js +20 -3
- package/dist/execution.js +16 -9
- package/dist/investigation.js +5 -1
- package/dist/probe-run.js +7 -1
- package/package.json +1 -1
package/dist/driver.js
CHANGED
|
@@ -244,6 +244,12 @@ export function buildAssignmentPrompt(context) {
|
|
|
244
244
|
if (packageContext?.decisions?.length) {
|
|
245
245
|
lines.push("", "PROJECT DECISIONS (explicit human decisions — treat as binding context)", ...packageContext.decisions.map((item) => `- Q: ${item.question}\n A: ${item.decision}`));
|
|
246
246
|
}
|
|
247
|
+
// The earlier work comes before the brief: the brief names a fix to that work.
|
|
248
|
+
if (context.resumeHead) {
|
|
249
|
+
lines.push("", context.repairMode === "briefed"
|
|
250
|
+
? `${RESUME_PROMPT_HEADING} — an earlier run of this same package failed its verification before delivery.`
|
|
251
|
+
: `${RESUME_PROMPT_HEADING} — an earlier run of this same package ran out of time before it finished.`, `Its work is already on this branch at commit ${context.resumeHead}. Continue from that work: read what is there, keep what is correct, and finish the package. Do not start over.`);
|
|
252
|
+
}
|
|
247
253
|
if (rework) {
|
|
248
254
|
// T55 row 9: a briefed repair never delivered, so "an independent review returned this
|
|
249
255
|
// delivery" is false for it — the brief's own first line already says the opposite. Head it by
|
|
@@ -253,9 +259,6 @@ export function buildAssignmentPrompt(context) {
|
|
|
253
259
|
: "REWORK FEEDBACK — an independent review returned this delivery; address every point";
|
|
254
260
|
lines.push("", `${heading}\n${rework}`);
|
|
255
261
|
}
|
|
256
|
-
if (context.resumeHead) {
|
|
257
|
-
lines.push("", `${RESUME_PROMPT_HEADING} — an earlier run of this same package ran out of time before it finished.`, `Its work is already on this branch at commit ${context.resumeHead}. Continue from that work: read what is there, keep what is correct, and finish the package. Do not start over.`);
|
|
258
|
-
}
|
|
259
262
|
const mustCommit = context.grants.includes("repo_write") && context.grants.includes("branch_create");
|
|
260
263
|
const mustOpenPr = mustCommit && context.grants.includes("pr_create");
|
|
261
264
|
const executionClass = context.executionClass
|
|
@@ -728,7 +731,7 @@ export function claudeCodeFuelEnv(fuelSource, model) {
|
|
|
728
731
|
return undefined;
|
|
729
732
|
return { ANTHROPIC_MODEL: model, ANTHROPIC_SMALL_FAST_MODEL: model, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
|
|
730
733
|
}
|
|
731
|
-
export function codexSandboxFacts(projection) {
|
|
734
|
+
export function codexSandboxFacts(projection, writableRoots = []) {
|
|
732
735
|
return [
|
|
733
736
|
"SANDBOX FACTS",
|
|
734
737
|
`Sandbox mode: ${projection.sandbox}.`,
|
|
@@ -736,7 +739,9 @@ export function codexSandboxFacts(projection) {
|
|
|
736
739
|
? "Network is on."
|
|
737
740
|
: "Network is off, including loopback listen.",
|
|
738
741
|
projection.sandbox === "workspace-write"
|
|
739
|
-
?
|
|
742
|
+
? writableRoots.length
|
|
743
|
+
? `Writes are allowed only in the workspace, TMPDIR and ${writableRoots.join(", ")}. HOME and the rest of the filesystem are read-only.`
|
|
744
|
+
: "Writes are allowed only in the workspace and TMPDIR. HOME and the rest of the filesystem are read-only."
|
|
740
745
|
: "No writes are allowed. The filesystem is read-only.",
|
|
741
746
|
"Child processes inherit these limits.",
|
|
742
747
|
"A registered gate that fails only with EPERM, EROFS, or a refused listen does not test the change.",
|
|
@@ -765,11 +770,22 @@ export function codexExecArgs(input, sandbox) {
|
|
|
765
770
|
if (sandbox === "workspace-write" && networkAccess) {
|
|
766
771
|
args.push("-c", "sandbox_workspace_write.network_access=true");
|
|
767
772
|
}
|
|
773
|
+
if (sandbox === "workspace-write" && input.writableRoots?.length) {
|
|
774
|
+
// `exec resume` takes no --add-dir, so both forms set the roots as a config override.
|
|
775
|
+
args.push("-c", `sandbox_workspace_write.writable_roots=${JSON.stringify(input.writableRoots)}`);
|
|
776
|
+
}
|
|
768
777
|
if (input.resumeSessionId)
|
|
769
778
|
args.push(input.resumeSessionId);
|
|
770
779
|
args.push("-");
|
|
771
780
|
return args;
|
|
772
781
|
}
|
|
782
|
+
/** The machine's Cargo cache and build directory that exist, for a Codex agent to write. */
|
|
783
|
+
export function codexCargoRoots(env) {
|
|
784
|
+
return ["CARGO_HOME", "CARGO_TARGET_DIR"].flatMap((name) => {
|
|
785
|
+
const path = env[name];
|
|
786
|
+
return path && existsSync(path) ? [path] : [];
|
|
787
|
+
});
|
|
788
|
+
}
|
|
773
789
|
/**
|
|
774
790
|
* Since the July 2026 desktop merge, macOS machines often carry the codex CLI
|
|
775
791
|
* only inside the ChatGPT app bundle rather than on PATH.
|
|
@@ -866,8 +882,12 @@ export const codexDriver = {
|
|
|
866
882
|
capabilities: input.capabilities,
|
|
867
883
|
diagnosis: input.workRole === "diagnose",
|
|
868
884
|
});
|
|
885
|
+
// The agent shares the gate's Cargo cache and build directory (boundedEnvironment). Codex
|
|
886
|
+
// workspace-write made them read-only, so no cargo command the agent started could run
|
|
887
|
+
// (acme-sim FH17a, 2026-11-04: EROFS on .cargo-lock); the gate sandbox already writes them.
|
|
888
|
+
const writableRoots = projected.sandbox === "workspace-write" ? codexCargoRoots(process.env) : [];
|
|
869
889
|
// Prompt via stdin avoids ARG_MAX limits on large assignment contracts.
|
|
870
|
-
const { code, stdout, stderr } = await execute(driverExecutable(codexDriver.name, input.executable), codexExecArgs({ ...input, networkAccess: projected.networkAccess }, projected.sandbox), input.workspace, agentTurnTimeoutMs(input), fuelSource === "conduit" ? input.fuel : undefined, fuelSource, `${input.prompt}\n\n${codexSandboxFacts(projected)}`, input.signal);
|
|
890
|
+
const { code, stdout, stderr } = await execute(driverExecutable(codexDriver.name, input.executable), codexExecArgs({ ...input, networkAccess: projected.networkAccess, writableRoots }, projected.sandbox), input.workspace, agentTurnTimeoutMs(input), fuelSource === "conduit" ? input.fuel : undefined, fuelSource, `${input.prompt}\n\n${codexSandboxFacts(projected, writableRoots)}`, input.signal);
|
|
871
891
|
const parsed = parseCodexJsonl(stdout);
|
|
872
892
|
const resultText = parsed.resultText ?? (stdout || null);
|
|
873
893
|
if (code !== 0) {
|
|
@@ -63,16 +63,17 @@ async function currentBranch(workspace) {
|
|
|
63
63
|
return stdout.trim();
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* A run that spent Conduit's package budget keeps its work for the
|
|
67
|
-
* agent left uncommitted, push the attempt branch where the grants allow
|
|
68
|
-
* Null when the attempt has nothing after base — the
|
|
66
|
+
* A run that spent Conduit's package budget or failed its gate keeps its work for the next attempt:
|
|
67
|
+
* commit the scoped files the agent left uncommitted, push the attempt branch where the grants allow
|
|
68
|
+
* a push, and name the head. Null when the attempt has nothing after base — the next attempt then
|
|
69
|
+
* starts fresh.
|
|
69
70
|
*/
|
|
70
71
|
export async function checkpointWorkInProgress(input) {
|
|
71
72
|
if (!requiresLandCommit({ grants: input.grants, spec: input.spec }))
|
|
72
73
|
return null;
|
|
73
74
|
const pending = filterPathsInScope(await uncommittedPaths(input.workspace), input.spec.change_scope ?? []);
|
|
74
75
|
if (pending.length) {
|
|
75
|
-
await commitScopedPaths(input.workspace, pending, "Conduit: work in progress
|
|
76
|
+
await commitScopedPaths(input.workspace, pending, "Conduit: work in progress for the next attempt");
|
|
76
77
|
}
|
|
77
78
|
const ahead = await commitsAheadOfBase(input.workspace, input.baseCommit);
|
|
78
79
|
if (!ahead)
|
|
@@ -572,6 +572,11 @@ async function missingNpmLockReason(directory) {
|
|
|
572
572
|
return "pnpm-lock.yaml is not an npm install";
|
|
573
573
|
return "no package-lock.json or npm-shrinkwrap.json";
|
|
574
574
|
}
|
|
575
|
+
/** Lock-pinned crates and npm packages, fetched outside the sandbox before a gate runs inside it. */
|
|
576
|
+
export async function prepareVerificationDependencies(workspace) {
|
|
577
|
+
await fetchLockedDependencies(workspace);
|
|
578
|
+
await installLockedPackages(workspace);
|
|
579
|
+
}
|
|
575
580
|
export async function installLockedPackages(workspace, options = {}) {
|
|
576
581
|
const installed = [];
|
|
577
582
|
const skipped = [];
|
|
@@ -612,7 +617,19 @@ export async function installLockedPackages(workspace, options = {}) {
|
|
|
612
617
|
* `output` is stdout and stderr together, in the order the lines arrived: a failure detail reads its
|
|
613
618
|
* tail from it.
|
|
614
619
|
*/
|
|
615
|
-
export
|
|
620
|
+
export function runBoundedVerificationCommand(command, workspace, timeoutMs = VERIFICATION_TIMEOUT_MS) {
|
|
621
|
+
return runVerificationCommand(command, workspace, timeoutMs, true);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* The same run without the dependency preparation, for a caller that is itself inside the sandbox
|
|
625
|
+
* (the delivered probe instrument). It has no network, so its own install would fail and leave a
|
|
626
|
+
* partial `node_modules` that hides the complete one. Bridge prepares that workspace before it
|
|
627
|
+
* starts the caller (CR31).
|
|
628
|
+
*/
|
|
629
|
+
export function runPreparedVerificationCommand(command, workspace, timeoutMs = VERIFICATION_TIMEOUT_MS) {
|
|
630
|
+
return runVerificationCommand(command, workspace, timeoutMs, false);
|
|
631
|
+
}
|
|
632
|
+
async function runVerificationCommand(command, workspace, timeoutMs, prepare) {
|
|
616
633
|
const argv = argvForBoundedCommand(command);
|
|
617
634
|
const bin = argv[0];
|
|
618
635
|
if (!bin)
|
|
@@ -670,8 +687,8 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
|
|
|
670
687
|
partial[stream].append(parts.at(-1));
|
|
671
688
|
};
|
|
672
689
|
const output = () => [lines.text().replace(/\n$/, ""), partial.stdout.text(), partial.stderr.text()].filter(Boolean).join("\n");
|
|
673
|
-
|
|
674
|
-
|
|
690
|
+
if (prepare)
|
|
691
|
+
await prepareVerificationDependencies(workspace);
|
|
675
692
|
const tempRoot = untrustedCodeTempRoot(workspace);
|
|
676
693
|
await mkdir(tempRoot, { recursive: true });
|
|
677
694
|
const scratchHome = await mkdtemp(join(tempRoot, "gate-home-"));
|
package/dist/execution.js
CHANGED
|
@@ -1734,16 +1734,17 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1734
1734
|
clearInterval(heartbeatTimer);
|
|
1735
1735
|
};
|
|
1736
1736
|
attemptDisposers.set(taskId, disposeTimers);
|
|
1737
|
-
// A run that spent Conduit's package budget keeps its work: the
|
|
1738
|
-
//
|
|
1739
|
-
//
|
|
1737
|
+
// A run that spent Conduit's package budget or failed its gate keeps its work: the next attempt
|
|
1738
|
+
// resumes from the head reported here instead of starting over at the base. The tree stays on this
|
|
1739
|
+
// computer, where that head is always reachable.
|
|
1740
|
+
const checkpointForNextAttempt = () => diagnosis ? Promise.resolve(null) : checkpointWorkInProgress({
|
|
1741
|
+
workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit,
|
|
1742
|
+
}).catch((error) => {
|
|
1743
|
+
console.error(`Work in progress was not committed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`);
|
|
1744
|
+
return null;
|
|
1745
|
+
});
|
|
1740
1746
|
const reportTurnTimeout = async (message) => {
|
|
1741
|
-
const resume =
|
|
1742
|
-
workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit,
|
|
1743
|
-
}).catch((error) => {
|
|
1744
|
-
console.error(`Work in progress was not committed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`);
|
|
1745
|
-
return null;
|
|
1746
|
-
});
|
|
1747
|
+
const resume = machineBoundTimeout(agentTimeout) ? null : await checkpointForNextAttempt();
|
|
1747
1748
|
retainAttemptWorktree = true;
|
|
1748
1749
|
const response = await queueTerminal(client, taskId, {
|
|
1749
1750
|
action: "fail",
|
|
@@ -1829,9 +1830,12 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1829
1830
|
? { ...result.signal, phase: "verification", witness: "bridge", kind: "verification_failed", command: capturedVerification.command, stderr_tail: undefined, stdout_tail: undefined, ...(diffStat ? { diff_stat: diffStat } : {}) }
|
|
1830
1831
|
: result.signal
|
|
1831
1832
|
: undefined;
|
|
1833
|
+
// A repair brief describes a fix to this work, so the briefed attempt must start from it.
|
|
1834
|
+
const resume = verificationDetail ? await checkpointForNextAttempt() : null;
|
|
1832
1835
|
retainAttemptWorktree = true;
|
|
1833
1836
|
const response = await queueTerminal(client, taskId, { action: "fail", body: {
|
|
1834
1837
|
error: message,
|
|
1838
|
+
...(resume ? { resume } : {}),
|
|
1835
1839
|
retryable: bindingRequired ? false : retryableAgentFailure(agentMessage),
|
|
1836
1840
|
// Bridge's own readings of the vendor's words name the cause, in the order the control plane
|
|
1837
1841
|
// always applied: a computer fact (sign-in, incompatible or missing CLI), then a model abort,
|
|
@@ -2490,12 +2494,15 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2490
2494
|
// classifyFinalizeFailure: forge transport → retryable; contract/environment/unknown → fail
|
|
2491
2495
|
// closed (unknown prefixed as "Bridge finalize interrupted", not laundered as contract).
|
|
2492
2496
|
: classifyFinalizeFailure(message);
|
|
2497
|
+
// A repair brief describes a fix to this work, so the briefed attempt must start from it.
|
|
2498
|
+
const resume = verificationFailed ? await checkpointForNextAttempt() : null;
|
|
2493
2499
|
retainAttemptWorktree = true;
|
|
2494
2500
|
const response = await queueTerminal(client, taskId, {
|
|
2495
2501
|
action: "fail",
|
|
2496
2502
|
body: {
|
|
2497
2503
|
error: classified.error,
|
|
2498
2504
|
retryable: classified.retryable,
|
|
2505
|
+
...(resume ? { resume } : {}),
|
|
2499
2506
|
...(!verificationFailed && classified.failure ? { failure: classified.failure } : {}),
|
|
2500
2507
|
...(verificationFailed ? { signal: {
|
|
2501
2508
|
phase: "verification",
|
package/dist/investigation.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { redactSecrets, redactSecretsDeep } from "./secret-redaction.js";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import { boundedTail, runBoundedVerificationCommand, VERIFICATION_TIMEOUT_MS } from "./ensure-test-evidence.js";
|
|
10
|
+
import { boundedTail, prepareVerificationDependencies, runBoundedVerificationCommand, VERIFICATION_TIMEOUT_MS } from "./ensure-test-evidence.js";
|
|
11
11
|
import { markAttemptActive } from "./preflight.js";
|
|
12
12
|
import { createAttemptWorktree, removeAttemptWorktree } from "./attempt-worktree.js";
|
|
13
13
|
import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl } from "./brief.js";
|
|
@@ -690,6 +690,10 @@ async function execDeliveredProbeRun(argv, stdin, workspace, timeoutMs) {
|
|
|
690
690
|
}
|
|
691
691
|
}
|
|
692
692
|
export async function runDeliveredProbeInstrument(input) {
|
|
693
|
+
// The delivered instrument runs inside the sandbox and cannot install; its registered commands
|
|
694
|
+
// read what Bridge installs here, outside it (CR31).
|
|
695
|
+
if (input.probes.some((probe) => probe.test?.trim()))
|
|
696
|
+
await (input.prepare ?? prepareVerificationDependencies)(input.workspace);
|
|
693
697
|
const entry = join(input.workspace, PROBE_RUN_ENTRY);
|
|
694
698
|
const timeoutMs = deliveredProbeTimeoutMs(input.probes);
|
|
695
699
|
const stdin = JSON.stringify({ probes: input.probes });
|
package/dist/probe-run.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Delivered probe instrument entry. `{ "probes": [...] }` on stdin, `{ witnesses }` on stdout. */
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { runPreparedVerificationCommand } from "./ensure-test-evidence.js";
|
|
3
4
|
import { runDeliveryVerificationProbes } from "./investigation.js";
|
|
4
5
|
const probeRunInputSchema = z.object({
|
|
5
6
|
probes: z.array(z.object({
|
|
@@ -19,7 +20,12 @@ async function readStdin() {
|
|
|
19
20
|
}
|
|
20
21
|
try {
|
|
21
22
|
const parsed = probeRunInputSchema.parse(JSON.parse(await readStdin()));
|
|
22
|
-
|
|
23
|
+
// This process is inside the sandbox; Bridge prepared the workspace's dependencies before it started.
|
|
24
|
+
const witnesses = await runDeliveryVerificationProbes({
|
|
25
|
+
workspace: process.cwd(),
|
|
26
|
+
probes: parsed.probes,
|
|
27
|
+
runCommand: (command, workspace) => runPreparedVerificationCommand(command, workspace),
|
|
28
|
+
});
|
|
23
29
|
process.stdout.write(`${JSON.stringify({ witnesses })}\n`);
|
|
24
30
|
}
|
|
25
31
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.210",
|
|
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": {
|