@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.
@@ -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
- const args = ["-p", input.prompt, "--output-format", "json", "--max-turns", String(maxTurns)];
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, undefined, input.signal, claudeCodeFuelEnv(fuelSource, input.model));
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
- const args = openCodeRunArgs(input, agent);
1233
- args.push(input.prompt);
1234
- const { code, stdout, stderr } = await withOpenCodePermissions(input.workspace, () => execute(driverExecutable(openCodeDriver.name, input.executable), args, input.workspace, agentTurnTimeoutMs(input), fuelSource === "conduit" ? input.fuel : undefined, fuelSource, undefined, input.signal));
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
- const args = kiroChatArgs(input, trusted);
1317
- args.push(input.prompt);
1318
- const { code, stdout, stderr } = await execute(executable, args, input.workspace, agentTurnTimeoutMs(input), undefined, "local", undefined, input.signal);
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
- args.push(input.prompt);
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({ prompt: input.prompt, tools: projected.tools, model: input.model }), input.workspace, agentTurnTimeoutMs(input), undefined, "local", undefined, input.signal);
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 Error("Agent report is missing required evidence: test (no bounded verification command available for Bridge to run)");
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
- // classifyFinalizeFailure: forge transport → retryable; contract/environment/unknown → fail
2270
- // closed (unknown prefixed as "Bridge finalize interrupted", not laundered as contract).
2271
- : classifyFinalizeFailure(message);
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 Error("Artifact delivery requires a published HTTP(S) URL and sha256 digest");
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 Error("Repository changes require a pull_request_url when pr_create is granted");
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 Error(`Agent report is missing required evidence: ${missing.join(", ")}${since}`);
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 Error(`Met acceptance criteria require mapped evidence: ${unsupported.join("; ")}`);
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 Error("test evidence must include verbatim command output in details (not a summary)");
2804
+ throw new DeliveryReportDefectError("test evidence must include verbatim command output in details (not a summary)");
2799
2805
  }
2800
2806
  }
2801
2807
  }
@@ -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 ? `${text.slice(0, 1_999)}…` : text;
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
- const text = signal.vendor_message ?? signal.stderr_tail ?? "";
408
- return text.length > 400 ? `${text.slice(0, 399)}…` : text;
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: "Bridge's report-only repair turns ran; review the report defect and rework it.",
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
  : {
@@ -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, spawn } from "node:child_process";
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. Keys that state no behaviour (wording, scope, docs) get no probe.';
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
- return await new Promise((resolve, reject) => {
633
- const child = spawn(file, args, {
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.140",
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": {