@miraland-labs/conduit-bridge 0.16.140 → 0.16.142
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/attempt-worktree.js +11 -1
- package/dist/driver.js +17 -12
- package/dist/ensure-test-evidence.js +13 -1
- package/dist/execution.js +15 -9
- package/dist/failure-signal.js +9 -6
- package/dist/investigation.js +22 -34
- package/package.json +1 -1
package/dist/attempt-worktree.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { access, appendFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { constants } from "node:fs";
|
|
7
|
-
import { join, resolve } from "node:path";
|
|
7
|
+
import { basename, join, resolve } from "node:path";
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
10
|
import { headCommitOrNull } from "./git.js";
|
|
@@ -106,6 +106,16 @@ export async function removeAttemptWorktree(sourceWorkspace, worktreePath) {
|
|
|
106
106
|
maxBuffer: 1_000_000,
|
|
107
107
|
}).catch(() => undefined);
|
|
108
108
|
}
|
|
109
|
+
// The attempt branch goes with its worktree: left behind, every attempt added one local branch to
|
|
110
|
+
// the source checkout for ever. Only the branch Bridge created for this path, and only the local
|
|
111
|
+
// ref: a delivered branch was pushed before the release and lives on in the forge.
|
|
112
|
+
const attemptId = basename(worktreePath);
|
|
113
|
+
if (resolve(worktreePath) === resolve(attemptWorktreePath(sourceWorkspace, attemptId))) {
|
|
114
|
+
await execFileAsync("git", ["-C", sourceWorkspace, "branch", "-D", attemptBranchName(attemptId)], {
|
|
115
|
+
timeout: 30_000,
|
|
116
|
+
maxBuffer: 1_000_000,
|
|
117
|
+
}).catch(() => undefined);
|
|
118
|
+
}
|
|
109
119
|
return true;
|
|
110
120
|
}
|
|
111
121
|
/**
|
package/dist/driver.js
CHANGED
|
@@ -739,7 +739,9 @@ export const claudeCodeDriver = {
|
|
|
739
739
|
};
|
|
740
740
|
}
|
|
741
741
|
const maxTurns = resolveMaxTurns(input.maxTurns);
|
|
742
|
-
|
|
742
|
+
// The prompt goes on stdin (`claude -p` reads it there when no prompt argument is given): Linux
|
|
743
|
+
// caps one argument at 128 KiB (MAX_ARG_STRLEN) and an assignment contract exceeds that.
|
|
744
|
+
const args = ["-p", "--output-format", "json", "--max-turns", String(maxTurns)];
|
|
743
745
|
args.push(...modelArgs(claudeCodeDriver, input.model));
|
|
744
746
|
if (input.resumeSessionId)
|
|
745
747
|
args.push("--resume", input.resumeSessionId);
|
|
@@ -747,7 +749,7 @@ export const claudeCodeDriver = {
|
|
|
747
749
|
args.push("--disallowedTools", projected.disallowedTools.join(","));
|
|
748
750
|
if (projected.acceptEdits)
|
|
749
751
|
args.push("--permission-mode", "acceptEdits");
|
|
750
|
-
const { code, stdout, stderr } = await execute(driverExecutable(claudeCodeDriver.name, input.executable), args, input.workspace, agentTurnTimeoutMs(input), fuelSource === "conduit" ? input.fuel : undefined, fuelSource,
|
|
752
|
+
const { code, stdout, stderr } = await execute(driverExecutable(claudeCodeDriver.name, input.executable), args, input.workspace, agentTurnTimeoutMs(input), fuelSource === "conduit" ? input.fuel : undefined, fuelSource, input.prompt, input.signal, claudeCodeFuelEnv(fuelSource, input.model));
|
|
751
753
|
let message = null;
|
|
752
754
|
try {
|
|
753
755
|
message = JSON.parse(stdout);
|
|
@@ -1229,9 +1231,9 @@ export const openCodeDriver = {
|
|
|
1229
1231
|
if (!agent) {
|
|
1230
1232
|
return { status: "failed", resultText: null, sessionId: null, error: "No Bridge-mapped OpenCode agent for active grants; refusing to start agent" };
|
|
1231
1233
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const { code, stdout, stderr } = await withOpenCodePermissions(input.workspace, () => execute(driverExecutable(openCodeDriver.name, input.executable),
|
|
1234
|
+
// The prompt goes on stdin (`opencode run` reads a piped stdin as the message), as it does for
|
|
1235
|
+
// codex: Linux caps one argument at 128 KiB (MAX_ARG_STRLEN).
|
|
1236
|
+
const { code, stdout, stderr } = await withOpenCodePermissions(input.workspace, () => execute(driverExecutable(openCodeDriver.name, input.executable), openCodeRunArgs(input, agent), input.workspace, agentTurnTimeoutMs(input), fuelSource === "conduit" ? input.fuel : undefined, fuelSource, input.prompt, input.signal));
|
|
1235
1237
|
const parsed = parseOpenCodeOutput(stdout);
|
|
1236
1238
|
if (code !== 0 || parsed.isError) {
|
|
1237
1239
|
return { status: "failed", resultText: parsed.resultText, sessionId: parsed.sessionId, signal: agentExitSignal({ exit_code: code, stderr, stdout, model: input.model }), error: boundedTail(stderr || parsed.resultText || `opencode exited with code ${code}`, 20_000) };
|
|
@@ -1313,9 +1315,9 @@ export const kiroDriver = {
|
|
|
1313
1315
|
return { status: "failed", resultText: null, sessionId: null, error: "kiro has no login (set KIRO_API_KEY or run `kiro-cli login`)" };
|
|
1314
1316
|
}
|
|
1315
1317
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const { code, stdout, stderr } = await execute(executable,
|
|
1318
|
+
// The prompt goes on stdin (`kiro-cli chat` takes a piped stdin as its input), as it does for
|
|
1319
|
+
// codex: Linux caps one argument at 128 KiB (MAX_ARG_STRLEN).
|
|
1320
|
+
const { code, stdout, stderr } = await execute(executable, kiroChatArgs(input, trusted), input.workspace, agentTurnTimeoutMs(input), undefined, "local", input.prompt, input.signal);
|
|
1319
1321
|
if (code !== 0) {
|
|
1320
1322
|
return { status: "failed", resultText: stdout || null, sessionId: null, signal: agentExitSignal({ exit_code: code, stderr, stdout, model: input.model }), error: boundedTail(stderr || stdout || `kiro-cli exited with code ${code}`, 20_000) };
|
|
1321
1323
|
}
|
|
@@ -1638,7 +1640,8 @@ export const antigravityDriver = {
|
|
|
1638
1640
|
export function piRunArgs(input) {
|
|
1639
1641
|
const args = ["--mode", "json", "-p", "--no-session", "--tools", input.tools];
|
|
1640
1642
|
args.push(...modelArgs(piDriver, input.model));
|
|
1641
|
-
|
|
1643
|
+
// The prompt goes on stdin (pi reads a piped stdin as its initial message), as it does for codex:
|
|
1644
|
+
// Linux caps one argument at 128 KiB (MAX_ARG_STRLEN).
|
|
1642
1645
|
return args;
|
|
1643
1646
|
}
|
|
1644
1647
|
/**
|
|
@@ -1703,7 +1706,7 @@ export const piDriver = {
|
|
|
1703
1706
|
if (version.code !== 0 || !(version.stdout || version.stderr).trim()) {
|
|
1704
1707
|
return { status: "failed", resultText: null, sessionId: null, error: "pi preflight could not verify the installed CLI version" };
|
|
1705
1708
|
}
|
|
1706
|
-
const { code, stdout, stderr } = await execute(executable, piRunArgs({
|
|
1709
|
+
const { code, stdout, stderr } = await execute(executable, piRunArgs({ tools: projected.tools, model: input.model }), input.workspace, agentTurnTimeoutMs(input), undefined, "local", input.prompt, input.signal);
|
|
1707
1710
|
const parsed = parsePiJsonl(stdout);
|
|
1708
1711
|
if (code !== 0) {
|
|
1709
1712
|
return {
|
|
@@ -1894,12 +1897,14 @@ const MAX_AGENT_OUTPUT = 8 * 1024 * 1024;
|
|
|
1894
1897
|
export const STDIO_DRAIN_MS = 2_000;
|
|
1895
1898
|
/** How long a stopped child may take to die before the run reports what it already has. */
|
|
1896
1899
|
const CHILD_STOP_GRACE_MS = 20_000;
|
|
1897
|
-
export function execute(executable, args, cwd, timeoutMs, fuel, fuelSource = "conduit", stdinText, signal, extraEnv
|
|
1900
|
+
export function execute(executable, args, cwd, timeoutMs, fuel, fuelSource = "conduit", stdinText, signal, extraEnv,
|
|
1901
|
+
/** The whole environment, for a run that must see less than an agent does (a probe instrument). */
|
|
1902
|
+
env) {
|
|
1898
1903
|
return new Promise((resolve, reject) => {
|
|
1899
1904
|
const child = spawn(executable, args, {
|
|
1900
1905
|
cwd,
|
|
1901
1906
|
stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
1902
|
-
env: boundedEnvironment(fuel, fuelSource, extraEnv),
|
|
1907
|
+
env: env ?? boundedEnvironment(fuel, fuelSource, extraEnv),
|
|
1903
1908
|
});
|
|
1904
1909
|
let stdout = "";
|
|
1905
1910
|
let stderr = "";
|
|
@@ -456,6 +456,18 @@ export function detectGateRanNothing(command, stdout, stderr, code) {
|
|
|
456
456
|
}
|
|
457
457
|
return null;
|
|
458
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* A delivery report that falls short of the delivery contract while the work itself may be
|
|
461
|
+
* complete: a required evidence kind is missing, a met criterion cites no evidence, test evidence is
|
|
462
|
+
* a summary, or the pull request or published artifact is missing. The type, not the sentence, is
|
|
463
|
+
* what tells the control plane this is a report defect for repair.
|
|
464
|
+
*/
|
|
465
|
+
export class DeliveryReportDefectError extends Error {
|
|
466
|
+
constructor(message) {
|
|
467
|
+
super(message);
|
|
468
|
+
this.name = "DeliveryReportDefectError";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
459
471
|
/**
|
|
460
472
|
* A declared gate that ran and failed. Carries the command as a field so the verification signal
|
|
461
473
|
* can name the gate without reading it back out of this error's text.
|
|
@@ -501,7 +513,7 @@ export async function ensureTestEvidence(input) {
|
|
|
501
513
|
...named.commands,
|
|
502
514
|
])];
|
|
503
515
|
if (commands.length === 0) {
|
|
504
|
-
throw new
|
|
516
|
+
throw new DeliveryReportDefectError("Agent report is missing required evidence: test (no bounded verification command available for Bridge to run)");
|
|
505
517
|
}
|
|
506
518
|
const run = input.runCommand ?? runBoundedVerificationCommand;
|
|
507
519
|
const witnessed = [];
|
package/dist/execution.js
CHANGED
|
@@ -28,7 +28,7 @@ import { ensureLandCommit } from "./ensure-land-commit.js";
|
|
|
28
28
|
import { AGENT_NO_LAND_COMMIT_PREFIX, AgentNoLandCommitError, agentNoLandCommitMessage, requiresLandCommit } from "./land-contract.js";
|
|
29
29
|
import { agentClaimsRepositoryWork, claimsVsGitMismatch, isLandTreeEmpty, reportClaimsRepositoryWork, } from "./land-git-reality.js";
|
|
30
30
|
const execFileAsync = promisify(execFile);
|
|
31
|
-
import { boundedTail, captureVerificationFailure, detectGateRanNothing, ensureTestEvidence, needsTestEvidence, runBoundedVerificationCommand, verificationEvidenceCommands, VerificationFailedError, VerificationGateRanNothingError } from "./ensure-test-evidence.js";
|
|
31
|
+
import { boundedTail, captureVerificationFailure, DeliveryReportDefectError, detectGateRanNothing, ensureTestEvidence, needsTestEvidence, runBoundedVerificationCommand, verificationEvidenceCommands, VerificationFailedError, VerificationGateRanNothingError } from "./ensure-test-evidence.js";
|
|
32
32
|
import { ensureChangeEvidence } from "./ensure-change-evidence.js";
|
|
33
33
|
import { ensureDemonstrationEvidence } from "./ensure-demonstration-evidence.js";
|
|
34
34
|
import { ensureNormativeEvidence } from "./ensure-normative-evidence.js";
|
|
@@ -2264,11 +2264,16 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2264
2264
|
.then((stat) => stat?.slice(0, 8_000) ?? null)
|
|
2265
2265
|
.catch(() => null)
|
|
2266
2266
|
: null;
|
|
2267
|
+
// A report that fell short of the delivery contract is named by its type and travels as the
|
|
2268
|
+
// delivery_report signal, so repair routing never reads this message (K3, debt 18 item 3c).
|
|
2269
|
+
const reportDefect = error instanceof DeliveryReportDefectError;
|
|
2267
2270
|
const classified = verificationFailed
|
|
2268
2271
|
? { retryable: true, error: `Verification failed after the agent run.\n${message}${diffStat ? `\n\nChanged files:\n${diffStat}` : ""}` }
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
+
: reportDefect
|
|
2273
|
+
? { retryable: false, error: message }
|
|
2274
|
+
// classifyFinalizeFailure: forge transport → retryable; contract/environment/unknown → fail
|
|
2275
|
+
// closed (unknown prefixed as "Bridge finalize interrupted", not laundered as contract).
|
|
2276
|
+
: classifyFinalizeFailure(message);
|
|
2272
2277
|
if (verificationFailed)
|
|
2273
2278
|
retainAttemptWorktree = true;
|
|
2274
2279
|
const response = await queueTerminal(client, taskId, {
|
|
@@ -2284,6 +2289,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2284
2289
|
command: error.command,
|
|
2285
2290
|
...(diffStat ? { diff_stat: diffStat } : {}),
|
|
2286
2291
|
} } : {}),
|
|
2292
|
+
...(reportDefect ? { signal: { phase: "delivery_report", witness: "bridge", kind: "contract" } } : {}),
|
|
2287
2293
|
// A rework's own PR may already be open (ensureDeliveryPullRequest ran before this
|
|
2288
2294
|
// gate). Carry the URL so the control plane can close it instead of littering.
|
|
2289
2295
|
...(report.pull_request_url ? { pull_request_url: report.pull_request_url } : {}),
|
|
@@ -2759,7 +2765,7 @@ export function validateDeliveryReport(report, spec, grants = [], actualPaths, b
|
|
|
2759
2765
|
}
|
|
2760
2766
|
});
|
|
2761
2767
|
if (!published) {
|
|
2762
|
-
throw new
|
|
2768
|
+
throw new DeliveryReportDefectError("Artifact delivery requires a published HTTP(S) URL and sha256 digest");
|
|
2763
2769
|
}
|
|
2764
2770
|
}
|
|
2765
2771
|
// Invariant 9: grants bound what an attempt may do. Cursor/plan-mode are soft hints the headless
|
|
@@ -2773,7 +2779,7 @@ export function validateDeliveryReport(report, spec, grants = [], actualPaths, b
|
|
|
2773
2779
|
if (grants.includes("pr_create")
|
|
2774
2780
|
&& report.head_commit
|
|
2775
2781
|
&& !report.pull_request_url) {
|
|
2776
|
-
throw new
|
|
2782
|
+
throw new DeliveryReportDefectError("Repository changes require a pull_request_url when pr_create is granted");
|
|
2777
2783
|
}
|
|
2778
2784
|
const requiredEvidence = spec.required_evidence ?? [];
|
|
2779
2785
|
const supplied = new Set(report.evidence.map((item) => item.kind));
|
|
@@ -2782,20 +2788,20 @@ export function validateDeliveryReport(report, spec, grants = [], actualPaths, b
|
|
|
2782
2788
|
// Name the base Bridge diffed against: an empty path list is a fact about that base, not proof
|
|
2783
2789
|
// that the agent did no work. Without the base the repair brief can only guess the symptom.
|
|
2784
2790
|
const since = missing.includes("change") && baseCommit ? ` (no paths changed since ${baseCommit})` : "";
|
|
2785
|
-
throw new
|
|
2791
|
+
throw new DeliveryReportDefectError(`Agent report is missing required evidence: ${missing.join(", ")}${since}`);
|
|
2786
2792
|
}
|
|
2787
2793
|
const unsupported = report.acceptance_results
|
|
2788
2794
|
.filter((result) => result.status === "met" && !report.evidence.some((item) => item.acceptance_criteria.includes(result.criterion)))
|
|
2789
2795
|
.map((result) => result.criterion);
|
|
2790
2796
|
if (unsupported.length)
|
|
2791
|
-
throw new
|
|
2797
|
+
throw new DeliveryReportDefectError(`Met acceptance criteria require mapped evidence: ${unsupported.join("; ")}`);
|
|
2792
2798
|
// Plan-time normalizeRequiredEvidence puts "test" on verify packages; reject summary-only details.
|
|
2793
2799
|
if (requiredEvidence.includes("test")) {
|
|
2794
2800
|
const thin = report.evidence
|
|
2795
2801
|
.filter((item) => item.kind === "test")
|
|
2796
2802
|
.filter((item) => item.details.join("\n").trim().length < TEST_EVIDENCE_DETAILS_MIN);
|
|
2797
2803
|
if (thin.length) {
|
|
2798
|
-
throw new
|
|
2804
|
+
throw new DeliveryReportDefectError("test evidence must include verbatim command output in details (not a summary)");
|
|
2799
2805
|
}
|
|
2800
2806
|
}
|
|
2801
2807
|
}
|
package/dist/failure-signal.js
CHANGED
|
@@ -228,9 +228,10 @@ export function contractScopeConflictFailure(detail) {
|
|
|
228
228
|
function resetClock(resetsAt) {
|
|
229
229
|
return `${resetsAt.slice(0, 16).replace("T", " ")} UTC`;
|
|
230
230
|
}
|
|
231
|
-
/** Signal tails are bounded: one runaway log cannot fill the wire or the card.
|
|
231
|
+
/** Signal tails are bounded: one runaway log cannot fill the wire or the card. A tail keeps the
|
|
232
|
+
* end of the output, because the decisive error is printed last. */
|
|
232
233
|
function bounded2k(text) {
|
|
233
|
-
return text.length > 2_000 ?
|
|
234
|
+
return text.length > 2_000 ? `…${text.slice(-1_999)}` : text;
|
|
234
235
|
}
|
|
235
236
|
/**
|
|
236
237
|
* Vendor text that names the model as the reason the CLI refused the run. The model name is the one
|
|
@@ -402,10 +403,12 @@ export function verificationRedBaseSignal(input) {
|
|
|
402
403
|
function factValue(value) {
|
|
403
404
|
return value === null ? "none" : value;
|
|
404
405
|
}
|
|
405
|
-
/** The vendor's own reason, kept inside the 2,000-char message bound. */
|
|
406
|
+
/** The vendor's own reason, kept inside the 2,000-char message bound; a stderr tail keeps its end. */
|
|
406
407
|
function quotedVendorText(signal) {
|
|
407
|
-
|
|
408
|
-
|
|
408
|
+
if (signal.vendor_message)
|
|
409
|
+
return signal.vendor_message.length > 400 ? `${signal.vendor_message.slice(0, 399)}…` : signal.vendor_message;
|
|
410
|
+
const tail = signal.stderr_tail ?? "";
|
|
411
|
+
return tail.length > 400 ? `…${tail.slice(-399)}` : tail;
|
|
409
412
|
}
|
|
410
413
|
/**
|
|
411
414
|
* Classify one typed signal. Null when the signal carries no typed verdict (`kind: "unknown"`):
|
|
@@ -614,7 +617,7 @@ export function classifyFailureSignal(signal) {
|
|
|
614
617
|
disposition: "rework",
|
|
615
618
|
responsible_party: "conduit",
|
|
616
619
|
message: "The delivery report did not satisfy the delivery contract.",
|
|
617
|
-
next_action: "
|
|
620
|
+
next_action: "Conductor will prepare bounded recovery guidance. You do not need to edit technical constraints.",
|
|
618
621
|
diagnostic_detail: signal.vendor_message ?? signal.stderr_tail,
|
|
619
622
|
}
|
|
620
623
|
: {
|
package/dist/investigation.js
CHANGED
|
@@ -12,11 +12,11 @@ import { createAttemptWorktree, removeAttemptWorktree } from "./attempt-worktree
|
|
|
12
12
|
import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl } from "./brief.js";
|
|
13
13
|
import { headCommitOrNull } from "./git.js";
|
|
14
14
|
import { learnDriverFuel, resolveAssignmentModel, changedPathsSince, runDeliveryVerificationGates, discardWorktreeWrites } from "./execution.js";
|
|
15
|
-
import { DRIVERS, extractAgentReportJsonText, parseJsonObjectCandidate } from "./driver.js";
|
|
15
|
+
import { DRIVERS, execute, extractAgentReportJsonText, parseJsonObjectCandidate } from "./driver.js";
|
|
16
16
|
import { pickDriverForClaim, resolveDriverFuel, supportsReadOnlyDiagnosis } from "./drivers.js";
|
|
17
17
|
import { diffStatSince } from "./git-witness.js";
|
|
18
18
|
import { switchManagedWorkspace } from "./on-shift-apply.js";
|
|
19
|
-
import { execFile
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
20
|
import { rm, writeFile } from "node:fs/promises";
|
|
21
21
|
import { createRequire } from "node:module";
|
|
22
22
|
import { join } from "node:path";
|
|
@@ -95,15 +95,20 @@ const investigationBriefSchema = z.object({
|
|
|
95
95
|
source: z.string().max(8_000).optional(),
|
|
96
96
|
checks: z.string().max(500).optional().default(""),
|
|
97
97
|
})).max(20).default([]),
|
|
98
|
+
/** K12: brief keys the lane names as stating no behaviour, so no probe is owed. Passed through, never run. */
|
|
99
|
+
no_behaviour: z.array(z.object({
|
|
100
|
+
key: z.string().trim().min(1).max(40),
|
|
101
|
+
reason: z.string().trim().min(1).max(500),
|
|
102
|
+
})).max(40).default([]),
|
|
98
103
|
});
|
|
99
104
|
/** T122: a delivery verification is an investigation whose question carries this header. */
|
|
100
105
|
export const DELIVERY_VERIFICATION_HEADER = "DELIVERY VERIFICATION";
|
|
101
106
|
/** Owner-verbatim: the lane writes probe source; Bridge executes. Shown only when BRIEF KEYS are present. */
|
|
102
|
-
export const DELIVERY_VERIFICATION_PROBE_INSTRUCTION = 'For every BRIEF KEY that states behaviour, add one entry to probes: either {"key":"[sN]","test":"<registered command that asserts it>","checks":"<one line: what it proves>"} or {"key":"[sN]","lang":"ts|py|sh","source":"<a short script run from the repository root that prints PROBE HOLDS as its last line and exits 0 when the key holds at this commit and prints PROBE FAILS as its last line and exits 1 when it does not; any other ending is recorded as broken; it may import repository modules by paths relative to the repository root>","checks":"<one line>"}. You do not run probes; Bridge runs them after you finish.
|
|
107
|
+
export const DELIVERY_VERIFICATION_PROBE_INSTRUCTION = 'For every BRIEF KEY that states behaviour, add one entry to probes: either {"key":"[sN]","test":"<registered command that asserts it>","checks":"<one line: what it proves>"} or {"key":"[sN]","lang":"ts|py|sh","source":"<a short script run from the repository root that prints PROBE HOLDS as its last line and exits 0 when the key holds at this commit and prints PROBE FAILS as its last line and exits 1 when it does not; any other ending is recorded as broken; it may import repository modules by paths relative to the repository root>","checks":"<one line>"}. You do not run probes; Bridge runs them after you finish. Every other BRIEF KEY states no behaviour (wording, scope, docs): add it to no_behaviour as {"key":"[sN]","reason":"<one line: why it states no behaviour>"}. Name every BRIEF KEY once, in probes or in no_behaviour.';
|
|
103
108
|
const BRIEF_JSON_EXAMPLE = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
104
|
-
const BRIEF_JSON_EXAMPLE_WITH_PROBES = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"probes":[],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
109
|
+
const BRIEF_JSON_EXAMPLE_WITH_PROBES = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"probes":[],"no_behaviour":[],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
105
110
|
/** Named in both prompts so the lane knows each list's maximum before it writes. */
|
|
106
|
-
export const BRIEF_LIMITS_SENTENCE = "Limits: findings at most 12, verification at most 8, limitations at most 8, criteria at most 40, command_failures at most 10, commands_run at most 10, probes at most 20; each text entry at most 1000 characters, commands_run entries at most 200 characters. Keep the most decisive entries.";
|
|
111
|
+
export const BRIEF_LIMITS_SENTENCE = "Limits: findings at most 12, verification at most 8, limitations at most 8, criteria at most 40, command_failures at most 10, commands_run at most 10, probes at most 20, no_behaviour at most 40; each text entry at most 1000 characters, commands_run entries at most 200 characters. Keep the most decisive entries.";
|
|
107
112
|
export function isDeliveryVerification(assignment) {
|
|
108
113
|
return assignment.question.trimStart().startsWith(DELIVERY_VERIFICATION_HEADER);
|
|
109
114
|
}
|
|
@@ -232,6 +237,7 @@ const BRIEF_LIST_MAX = {
|
|
|
232
237
|
command_failures: 10,
|
|
233
238
|
commands_run: 10,
|
|
234
239
|
probes: 20,
|
|
240
|
+
no_behaviour: 40,
|
|
235
241
|
};
|
|
236
242
|
function clipBriefText(value, max) {
|
|
237
243
|
return value.length > max ? value.slice(0, max) : value;
|
|
@@ -281,6 +287,9 @@ export function fitBriefToContract(candidate) {
|
|
|
281
287
|
fitNamedList("command_failures", (items) => fitBriefStrings(items, BRIEF_ITEM_MAX));
|
|
282
288
|
fitNamedList("commands_run", (items) => fitBriefStrings(items, BRIEF_COMMAND_RUN_MAX));
|
|
283
289
|
fitNamedList("probes", (items) => items);
|
|
290
|
+
fitNamedList("no_behaviour", (items) => items.map((item) => item && typeof item === "object" && typeof item.reason === "string"
|
|
291
|
+
? { ...item, reason: clipBriefText(item.reason, 500) }
|
|
292
|
+
: item));
|
|
284
293
|
clipBriefStringField(brief, "summary", BRIEF_SUMMARY_MAX);
|
|
285
294
|
if (typeof brief.files_read === "number" && brief.files_read > BRIEF_FILES_READ_MAX) {
|
|
286
295
|
brief.files_read = BRIEF_FILES_READ_MAX;
|
|
@@ -625,34 +634,17 @@ function toStepWitness(witness) {
|
|
|
625
634
|
instrument: witness.instrument === "delivered" ? "delivered" : "running",
|
|
626
635
|
};
|
|
627
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* The delivered instrument runs through the agent runs' own process helper (T90): bounded output,
|
|
639
|
+
* an answer on the child's exit even when a process it left behind holds the pipes, and a stop
|
|
640
|
+
* that escalates to SIGKILL. Its environment stays the probe's own, PATH and HOME only.
|
|
641
|
+
*/
|
|
628
642
|
async function execDeliveredProbeRun(argv, stdin, workspace, timeoutMs) {
|
|
629
643
|
const [file, ...args] = argv;
|
|
630
644
|
if (!file)
|
|
631
645
|
throw new Error("delivered probe command is empty");
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
cwd: workspace,
|
|
635
|
-
env: probeEnv(),
|
|
636
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
637
|
-
});
|
|
638
|
-
let stdout = "";
|
|
639
|
-
let stderr = "";
|
|
640
|
-
const timer = setTimeout(() => {
|
|
641
|
-
child.kill("SIGKILL");
|
|
642
|
-
}, timeoutMs);
|
|
643
|
-
child.stdout?.on("data", (chunk) => { stdout += String(chunk); });
|
|
644
|
-
child.stderr?.on("data", (chunk) => { stderr += String(chunk); });
|
|
645
|
-
child.on("error", (error) => {
|
|
646
|
-
clearTimeout(timer);
|
|
647
|
-
reject(error);
|
|
648
|
-
});
|
|
649
|
-
child.on("close", (code) => {
|
|
650
|
-
clearTimeout(timer);
|
|
651
|
-
resolve({ code: code ?? -1, stdout, stderr });
|
|
652
|
-
});
|
|
653
|
-
child.stdin?.on("error", () => undefined);
|
|
654
|
-
child.stdin?.end(stdin);
|
|
655
|
-
});
|
|
646
|
+
const result = await execute(file, args, workspace, timeoutMs, undefined, "conduit", stdin, undefined, undefined, probeEnv());
|
|
647
|
+
return { code: result.code ?? -1, stdout: result.stdout, stderr: result.stderr };
|
|
656
648
|
}
|
|
657
649
|
export async function runDeliveredProbeInstrument(input) {
|
|
658
650
|
const entry = join(input.workspace, PROBE_RUN_ENTRY);
|
|
@@ -715,10 +707,6 @@ export const INVESTIGATION_RENEW_INTERVAL_MS = 2 * 60_000;
|
|
|
715
707
|
export function investigationRunId(investigationId) {
|
|
716
708
|
return `${investigationId}-${crypto.randomUUID().slice(0, 8)}`;
|
|
717
709
|
}
|
|
718
|
-
async function deleteRunBranch(workspace, runId) {
|
|
719
|
-
await execFileAsync("git", ["-C", workspace, "branch", "-D", `conduit/attempt/${runId}`], { timeout: 30_000, maxBuffer: 1_000_000 })
|
|
720
|
-
.catch(() => undefined);
|
|
721
|
-
}
|
|
722
710
|
export async function executeNextInvestigation(client, config, workspace, brief, timeoutMs, options = {}) {
|
|
723
711
|
const data = await client.request("/runner/v1/investigations");
|
|
724
712
|
const assignments = z.array(investigationAssignmentSchema).parse(data.investigations ?? []);
|
|
@@ -916,6 +904,7 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
916
904
|
? gatesRun.filter((gate) => gate.exitCode !== 0).map(gateFailureFact).slice(0, 10)
|
|
917
905
|
: observed.command_failures,
|
|
918
906
|
step_witness: stepWitness,
|
|
907
|
+
no_behaviour: deliveryVerification ? observed.no_behaviour : [],
|
|
919
908
|
witness: {
|
|
920
909
|
repository_fingerprint: assignment.repository_fingerprint ?? liveRepository ?? "unknown",
|
|
921
910
|
commit,
|
|
@@ -949,7 +938,6 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
949
938
|
if (attemptWorkspace) {
|
|
950
939
|
await removeAttemptWorktree(workspace, attemptWorkspace)
|
|
951
940
|
.catch((error) => console.error(`Investigation worktree release failed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`));
|
|
952
|
-
await deleteRunBranch(workspace, runId);
|
|
953
941
|
}
|
|
954
942
|
}
|
|
955
943
|
return true;
|
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.142",
|
|
4
4
|
"description": "Conduit Bridge CLI \u2014 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": {
|