@miraland-labs/conduit-bridge 0.16.156 → 0.16.159

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/client.js CHANGED
@@ -69,10 +69,16 @@ export class ConduitClient {
69
69
  signal: init.signal ?? AbortSignal.timeout(CONTROL_PLANE_TIMEOUT_MS),
70
70
  headers: { authorization: `Bearer ${this.config.runnerKey}`, "content-type": "application/json", ...init.headers },
71
71
  });
72
- const data = await response.json();
73
- if (!response.ok)
74
- throw new ConduitRequestError(data.error?.message ?? `Conduit request failed (${response.status})`, response.status, data.error?.code, data.error?.details);
75
- return data;
72
+ if (!response.ok) {
73
+ const text = await response.text();
74
+ let data = null;
75
+ try {
76
+ data = JSON.parse(text);
77
+ }
78
+ catch { /* A proxy may answer with a non-JSON error page. */ }
79
+ throw new ConduitRequestError(data?.error?.message ?? `Conduit request failed (${response.status})`, response.status, data?.error?.code, data?.error?.details);
80
+ }
81
+ return await response.json();
76
82
  }
77
83
  async claim(taskId, attemptId, extras) {
78
84
  // The lease token is this machine's bearer credential for the attempt. The server stores only
package/dist/driver.js CHANGED
@@ -1895,7 +1895,7 @@ const OWN_PROCESS_GROUP = process.platform !== "win32";
1895
1895
  /** Process groups of the agents running now, stopped when the runner itself is stopped. */
1896
1896
  const runningGroups = new Set();
1897
1897
  let shutdownHookInstalled = false;
1898
- function signalGroup(pid, signal) {
1898
+ export function signalGroup(pid, signal) {
1899
1899
  try {
1900
1900
  process.kill(-pid, signal);
1901
1901
  }
@@ -37,7 +37,7 @@ export async function ensureLandCommit(input) {
37
37
  return { kind: "skipped" };
38
38
  const countAhead = input.countCommitsAhead ?? commitsAheadOfBase;
39
39
  const readHead = input.readHeadCommit ?? readHeadCommit;
40
- const listPending = input.listUncommittedPaths ?? uncommittedPaths;
40
+ const listPending = input.listUncommittedPaths ?? ((workspace) => uncommittedPaths(workspace, { untracked: "all" }));
41
41
  const commitPaths = input.commitPaths ?? commitScopedPaths;
42
42
  const ahead = await countAhead(input.workspace, base);
43
43
  if (ahead !== null && ahead > 0) {
@@ -69,7 +69,7 @@ async function currentBranch(workspace) {
69
69
  export async function checkpointWorkInProgress(input) {
70
70
  if (!requiresLandCommit({ grants: input.grants, spec: input.spec }))
71
71
  return null;
72
- const pending = filterPathsInScope(await uncommittedPaths(input.workspace), input.spec.change_scope ?? []);
72
+ const pending = filterPathsInScope(await uncommittedPaths(input.workspace, { untracked: "all" }), input.spec.change_scope ?? []);
73
73
  if (pending.length) {
74
74
  await commitScopedPaths(input.workspace, pending, "Conduit: work in progress at the package budget");
75
75
  }
@@ -8,10 +8,13 @@ import { promisify } from "node:util";
8
8
  import { normalizeRepositoryUrl } from "./brief.js";
9
9
  import { readHeadCommit } from "./git.js";
10
10
  import { forgeTransportFailure } from "./transport-fault.js";
11
+ import { ConduitRequestError } from "./client.js";
12
+ import { redactSecrets } from "./secret-redaction.js";
11
13
  import { agentNoLandCommitMessage } from "./land-contract.js";
12
14
  const execFileAsync = promisify(execFile);
13
15
  /** Keep the pull request body inside forge limits, even for a very large delivery packet. */
14
16
  const PULL_REQUEST_BODY_MAX_BYTES = 60_000;
17
+ export const CONTROL_PLANE_TRANSIENT_PREFIX = "Control plane did not answer the change request open: ";
15
18
  /**
16
19
  * What the packet says when the control plane refused to open the change request.
17
20
  *
@@ -289,14 +292,16 @@ export async function ensureDeliveryPullRequest(input) {
289
292
  head_branch: headBranch,
290
293
  head_commit: report.head_commit,
291
294
  title: (input.title ?? "Conduit delivery").slice(0, 200),
292
- body: composePullRequestBody(report),
295
+ body: redactSecrets(composePullRequestBody(report)),
293
296
  idempotency_key: `bridge:open-pr:${input.attemptId}`,
294
297
  });
295
298
  }
296
299
  catch (error) {
297
300
  const message = error instanceof Error ? error.message : String(error);
298
- const code = typeof error.code === "string" ? error.code : "";
299
- throw new Error(`${PULL_REQUEST_OPEN_FAILURE_PREFIX}: ${message}${code ? ` (${code})` : ""}`);
301
+ if (error instanceof ConduitRequestError && error.status < 500) {
302
+ throw new Error(`${PULL_REQUEST_OPEN_FAILURE_PREFIX}: ${message}${error.code ? ` (${error.code})` : ""}`);
303
+ }
304
+ throw new Error(`${CONTROL_PLANE_TRANSIENT_PREFIX}${message}`);
300
305
  }
301
306
  const pullRequestUrl = typeof response.pull_request_url === "string" ? response.pull_request_url : "";
302
307
  // Any https URL: the control plane resolved which forge this project uses and parsed the response
@@ -304,7 +309,7 @@ export async function ensureDeliveryPullRequest(input) {
304
309
  // GitLab merge request — after the branch was already pushed. The head-commit cross-check below is
305
310
  // what actually proves the returned change request is this delivery's.
306
311
  if (!isAbsoluteHttpsUrl(pullRequestUrl)) {
307
- throw new Error("Control plane did not return a pull_request_url");
312
+ throw new Error("Control plane answered without a change request link");
308
313
  }
309
314
  const openedHead = typeof response.head_commit === "string" ? response.head_commit : "";
310
315
  const deliveredHead = report.head_commit ?? "";
@@ -3,15 +3,13 @@
3
3
  * commands and attaches their verbatim results. Agent prose may supplement those runs;
4
4
  * it never substitutes for it.
5
5
  */
6
- import { execFile } from "node:child_process";
7
- import { promisify } from "node:util";
8
- import { boundedEnvironment, criterionKey, resolveCriterionReference } from "./driver.js";
6
+ import { spawn } from "node:child_process";
7
+ import { boundedEnvironment, criterionKey, resolveCriterionReference, signalGroup, STDIO_DRAIN_MS } from "./driver.js";
9
8
  import { isBoundedVerificationCommand, isRunnableVerificationCommand } from "./execution-class.js";
10
- const execFileAsync = promisify(execFile);
11
9
  const TEST_EVIDENCE_DETAILS_MIN = 32;
12
10
  /** Cold `cargo test` in an attempt worktree routinely exceeds two minutes of compile. */
13
11
  export const VERIFICATION_TIMEOUT_MS = 600_000;
14
- /** rustc + cargo logs routinely exceed 2 MiB; Node kills the child when maxBuffer is hit. */
12
+ /** rustc + cargo logs routinely exceed 2 MiB; keep a bounded tail per stream. */
15
13
  export const VERIFICATION_MAX_BUFFER_BYTES = 32_000_000;
16
14
  /**
17
15
  * Contract requires test + test_run — Bridge always runs; agent paste is not a substitute.
@@ -203,6 +201,10 @@ export async function captureVerificationFailure(input) {
203
201
  // Diagnosis is best-effort: try the next command rather than failing the attempt again.
204
202
  continue;
205
203
  }
204
+ if (result.timedOut) {
205
+ return { command, detail: boundedTail(outputLines(result).join("\n"), DIAGNOSIS_DETAIL_MAX_CHARS),
206
+ timedOut: true, stdout: result.stdout, stderr: result.stderr };
207
+ }
206
208
  if (result.code === 0)
207
209
  continue;
208
210
  // Same rule as the pre-delivery gate: this is the diagnosis a repair brief is written from, so
@@ -375,55 +377,128 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
375
377
  throw new Error("Verification command is empty");
376
378
  // Chunks are not whole lines. Each stream keeps its partial last line until the line ends, so a
377
379
  // stderr write between two stdout chunks cannot split a stdout line.
378
- const lines = [];
379
- const partial = { stdout: "", stderr: "" };
380
+ const boundedOutput = (limit) => {
381
+ const chunks = [];
382
+ let bytes = 0;
383
+ return {
384
+ append(text) {
385
+ const chunk = Buffer.from(text);
386
+ if (!chunk.length)
387
+ return;
388
+ const last = chunks.at(-1);
389
+ if (last && last.length + chunk.length <= 64 * 1024)
390
+ chunks[chunks.length - 1] = Buffer.concat([last, chunk]);
391
+ else
392
+ chunks.push(chunk);
393
+ bytes += chunk.length;
394
+ while (bytes > limit) {
395
+ const first = chunks[0];
396
+ const excess = bytes - limit;
397
+ if (first.length <= excess) {
398
+ bytes -= first.length;
399
+ chunks.shift();
400
+ }
401
+ else {
402
+ chunks[0] = first.subarray(excess);
403
+ bytes = limit;
404
+ }
405
+ }
406
+ },
407
+ text() {
408
+ const buffer = Buffer.concat(chunks, bytes);
409
+ let start = 0;
410
+ while (start < buffer.length && (buffer[start] & 0xc0) === 0x80)
411
+ start += 1;
412
+ return buffer.subarray(start).toString("utf8");
413
+ },
414
+ clear() { chunks.length = 0; bytes = 0; },
415
+ };
416
+ };
417
+ const lines = boundedOutput(2 * VERIFICATION_MAX_BUFFER_BYTES);
418
+ const partial = { stdout: boundedOutput(VERIFICATION_MAX_BUFFER_BYTES), stderr: boundedOutput(VERIFICATION_MAX_BUFFER_BYTES) };
380
419
  const collect = (stream) => (chunk) => {
381
- const parts = `${partial[stream]}${String(chunk)}`.split("\n");
382
- partial[stream] = parts.pop() ?? "";
383
- lines.push(...parts);
420
+ const parts = String(chunk).split("\n");
421
+ partial[stream].append(parts[0]);
422
+ if (parts.length === 1)
423
+ return;
424
+ lines.append(`${partial[stream].text()}\n`);
425
+ partial[stream].clear();
426
+ if (parts.length > 2)
427
+ lines.append(`${parts.slice(1, -1).join("\n")}\n`);
428
+ partial[stream].append(parts.at(-1));
384
429
  };
385
- const output = () => [...lines, ...[partial.stdout, partial.stderr].filter(Boolean)].join("\n");
386
- try {
387
- const pending = execFileAsync(bin, argv.slice(1), {
388
- cwd: workspace,
389
- timeout: timeoutMs,
390
- maxBuffer: VERIFICATION_MAX_BUFFER_BYTES,
391
- // The agent's environment, not the runner's: a gate that passes only with a variable the
392
- // agent never sees ties the project to this computer (acme-sim DATABASE_URL, 2026-10-10).
393
- env: boundedEnvironment(),
394
- });
395
- pending.child.stdout?.on("data", collect("stdout"));
396
- pending.child.stderr?.on("data", collect("stderr"));
397
- const { stdout, stderr } = await pending;
398
- return { stdout: String(stdout), stderr: String(stderr), output: output(), code: 0 };
399
- }
400
- catch (error) {
401
- const err = error;
402
- // A spawn failure carries a string code (`ENOENT` when the tool is not on PATH); a gate that
403
- // ran carries its numeric exit code. Reporting a missing tool as exit 1 made the dry run read
404
- // it as a red base and put the computer's missing tool on the initiative author. A command that
405
- // never started says nothing, so it is thrown: the dry run skips it, and the delivery gate's
406
- // message reaches the `enoent` rule that Holds it on the computer operator.
407
- if (typeof err.code === "string")
408
- throw error;
409
- if (err.killed === true) {
430
+ const output = () => [lines.text().replace(/\n$/, ""), partial.stdout.text(), partial.stderr.text()].filter(Boolean).join("\n");
431
+ return new Promise((resolve, reject) => {
432
+ const ownGroup = process.platform !== "win32";
433
+ const child = spawn(bin, argv.slice(1), { cwd: workspace, env: boundedEnvironment(),
434
+ stdio: ["ignore", "pipe", "pipe"], detached: ownGroup });
435
+ const stop = (signal) => {
436
+ if (ownGroup && child.pid !== undefined)
437
+ signalGroup(child.pid, signal);
438
+ else
439
+ child.kill(signal);
440
+ };
441
+ const stdout = boundedOutput(VERIFICATION_MAX_BUFFER_BYTES);
442
+ const stderr = boundedOutput(VERIFICATION_MAX_BUFFER_BYTES);
443
+ let timedOut = false;
444
+ let settled = false;
445
+ let exited = false;
446
+ let drainTimer = null;
447
+ let graceTimer = null;
448
+ const cleanup = () => {
449
+ clearTimeout(timer);
450
+ if (drainTimer)
451
+ clearTimeout(drainTimer);
452
+ if (graceTimer)
453
+ clearTimeout(graceTimer);
454
+ child.stdout?.destroy();
455
+ child.stderr?.destroy();
456
+ stop("SIGTERM");
457
+ setTimeout(() => stop("SIGKILL"), 5_000).unref?.();
458
+ };
459
+ const settle = (code) => {
460
+ if (settled)
461
+ return;
462
+ settled = true;
463
+ cleanup();
410
464
  const stopped = `Bridge stopped the gate: it did not exit within ${Math.round(timeoutMs / 1_000)} seconds.`;
411
- return {
412
- stdout: String(err.stdout ?? ""),
413
- stderr: `${String(err.stderr ?? "")}\n${stopped}`.trimStart(),
414
- output: `${output()}\n${stopped}`.trimStart(),
415
- code: 1,
416
- timedOut: true,
417
- };
418
- }
419
- const stderr = String(err.stderr || err.message || "verification failed");
420
- return {
421
- stdout: String(err.stdout ?? ""),
422
- stderr,
423
- output: output() || stderr,
424
- code: typeof err.code === "number" ? err.code : 1,
465
+ resolve(timedOut ? { stdout: stdout.text(), stderr: `${stderr.text()}\n${stopped}`.trimStart(),
466
+ output: `${output()}\n${stopped}`.trimStart(), code: 1, timedOut: true }
467
+ : { stdout: stdout.text(), stderr: stderr.text(), output: output(), code: code ?? 1 });
425
468
  };
426
- }
469
+ const timer = setTimeout(() => {
470
+ if (exited)
471
+ return;
472
+ timedOut = true;
473
+ stop("SIGTERM");
474
+ setTimeout(() => stop("SIGKILL"), 10_000).unref?.();
475
+ graceTimer = setTimeout(() => settle(null), 20_000);
476
+ }, timeoutMs);
477
+ child.stdout?.setEncoding("utf8");
478
+ child.stderr?.setEncoding("utf8");
479
+ child.stdout?.on("data", (chunk) => { stdout.append(chunk); collect("stdout")(chunk); });
480
+ child.stderr?.on("data", (chunk) => { stderr.append(chunk); collect("stderr")(chunk); });
481
+ child.on("error", (error) => {
482
+ if (settled)
483
+ return;
484
+ if (typeof error.code === "string") {
485
+ settled = true;
486
+ cleanup();
487
+ reject(error);
488
+ }
489
+ else {
490
+ if (!stderr.text())
491
+ stderr.append(error.message);
492
+ settle(1);
493
+ }
494
+ });
495
+ child.on("close", (code) => settle(code));
496
+ child.on("exit", (code) => {
497
+ exited = true;
498
+ clearTimeout(timer);
499
+ drainTimer = setTimeout(() => settle(code), STDIO_DRAIN_MS);
500
+ });
501
+ });
427
502
  }
428
503
  /**
429
504
  * Bridge may attach genuine command output; it must not invent which criteria that output proves.
package/dist/execution.js CHANGED
@@ -23,7 +23,7 @@ import { formatResetMinute, parseQuotaResetAt } from "./quota-reset.js";
23
23
  import { headCommitOrNull } from "./git.js";
24
24
  import { BRIDGE_PROTOCOL_VERSION } from "./preflight.js";
25
25
  import { bridgeVersion } from "./version.js";
26
- import { PULL_REQUEST_OPEN_FAILURE_PREFIX, commitsMatch, ensureDeliveryPullRequest } from "./ensure-pull-request.js";
26
+ import { CONTROL_PLANE_TRANSIENT_PREFIX, PULL_REQUEST_OPEN_FAILURE_PREFIX, commitsMatch, ensureDeliveryPullRequest } from "./ensure-pull-request.js";
27
27
  import { checkpointWorkInProgress, 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";
@@ -219,6 +219,8 @@ const FINALIZE_CONTRACT_PATTERN = /missing required evidence|without the repo_wr
219
219
  export function classifyFinalizeFailure(message) {
220
220
  if (forgeTransportFailure(message))
221
221
  return { retryable: true, error: message, failure: bridgeCause("forge_unreachable", message) };
222
+ if (message.startsWith(CONTROL_PLANE_TRANSIENT_PREFIX))
223
+ return { retryable: true, error: message };
222
224
  // The control plane refused to open the change request — no forge connection resolves for this
223
225
  // organization or project, or the task's repository is not one that connection can open a change
224
226
  // request on. No retry and no rework can supply a connection. The cause travels as an envelope so
@@ -1800,6 +1802,19 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
1800
1802
  acceptance: spec.acceptance ?? [],
1801
1803
  })
1802
1804
  : null;
1805
+ if (capturedVerification?.timedOut) {
1806
+ retainAttemptWorktree = true;
1807
+ const response = await queueTerminal(client, taskId, { action: "fail", body: {
1808
+ error: capturedVerification.detail,
1809
+ retryable: false,
1810
+ signal: verificationGateTimeoutSignal({ command: capturedVerification.command,
1811
+ stderr: capturedVerification.stderr ?? "", stdout: capturedVerification.stdout ?? "" }),
1812
+ idempotency_key: `bridge:verification-gate-timeout:${active.attemptId}`,
1813
+ } });
1814
+ retainAttemptWorktree = response.retain_worktree === true;
1815
+ console.error(`Assignment ${taskId} verification gate did not finish: ${redactSecrets(capturedVerification.detail)}`);
1816
+ return;
1817
+ }
1803
1818
  const verificationDetail = capturedVerification?.detail ?? null;
1804
1819
  // The vendor named the model as the cause, so the model is what the retry must change. The
1805
1820
  // model rides in the prefix: the control plane cannot read Bridge's selection any other way.
@@ -2750,7 +2765,8 @@ async function prepareDelivery(client, attemptId, taskId, report) {
2750
2765
  const fuelMode = client.attempt(taskId).fuelMode;
2751
2766
  const evidence = [];
2752
2767
  const evidenceByCriterion = new Map();
2753
- for (const [index, item] of report.evidence.entries()) {
2768
+ for (const [index, evidenceItem] of report.evidence.entries()) {
2769
+ const item = redactSecretsDeep(evidenceItem);
2754
2770
  const uri = item.uri ?? `urn:conduit:attempt:${attemptId}:evidence:${index + 1}`;
2755
2771
  // Publication receipts require a digest on every acceptance artifact. Textual agent
2756
2772
  // evidence has no file to hash, so default to a canonical content digest of the
@@ -3001,6 +3017,7 @@ export async function runDeliveryVerificationGates(input) {
3001
3017
  results.push({
3002
3018
  command,
3003
3019
  exitCode: result.code,
3020
+ timedOut: result.timedOut === true,
3004
3021
  stdoutTail: boundedTail(result.stdout, DELIVERY_VERIFICATION_GATE_TAIL_CHARS),
3005
3022
  stderrTail: boundedTail(result.stderr, DELIVERY_VERIFICATION_GATE_TAIL_CHARS),
3006
3023
  });
@@ -3011,6 +3028,7 @@ export async function runDeliveryVerificationGates(input) {
3011
3028
  results.push({
3012
3029
  command,
3013
3030
  exitCode: -1,
3031
+ timedOut: false,
3014
3032
  stdoutTail: "",
3015
3033
  stderrTail: boundedTail(message, DELIVERY_VERIFICATION_GATE_TAIL_CHARS),
3016
3034
  });
@@ -599,7 +599,10 @@ export function classifyFailureSignal(signal) {
599
599
  code: "verification_red_base",
600
600
  class: "contract",
601
601
  disposition: "hold",
602
- responsible_party: "initiative_author",
602
+ // The base commit is the project's main: the sum of deliveries already accepted. A gate that
603
+ // fails there is a fact about the repository, not about this brief or its author (acme-sim
604
+ // SV1b, 2026-10-10: one flaky test on main held a brief its author could not have fixed).
605
+ responsible_party: "project_owner",
603
606
  message: `The verification gate \`${command}\` already fails on the base commit ${base}, before any change was made.`,
604
607
  next_action: "If the base commit passes the gate when run alone, press Try again: Bridge runs the gate again before any agent starts. Otherwise make the gate pass on the base commit (fix or revert the offending commit), then submit again.",
605
608
  diagnostic_detail: [`$ ${command}`, `exit ${signal.exit_code ?? "unknown"}`, signal.stderr_tail, signal.stdout_tail].filter(Boolean).join("\n") || undefined,
@@ -41,7 +41,7 @@ export function parseIntegrationObligations(value) {
41
41
  async function symbolReferencedFrom(workspace, symbol, globs) {
42
42
  const pathspecs = globs.map((glob) => `:(glob)${glob}`);
43
43
  try {
44
- const { stdout } = await execFileAsync("git", ["-C", workspace, "grep", "-l", "-F", "--untracked", symbol, "--", ...pathspecs], { timeout: 30_000, maxBuffer: 4_000_000 });
44
+ const { stdout } = await execFileAsync("git", ["-C", workspace, "grep", "-l", "-F", "--untracked", "-e", symbol, "--", ...pathspecs], { timeout: 30_000, maxBuffer: 4_000_000 });
45
45
  return stdout.trim().length > 0;
46
46
  }
47
47
  catch (error) {
@@ -5,7 +5,7 @@
5
5
  * held to repo_read + test_run by the same driver projection that enforces read-only diagnosis, and
6
6
  * the worktree it reads is deleted whichever way the run ends.
7
7
  */
8
- import { redactSecrets } from "./secret-redaction.js";
8
+ import { redactSecrets, redactSecretsDeep } from "./secret-redaction.js";
9
9
  import { z } from "zod";
10
10
  import { boundedTail, runBoundedVerificationCommand } from "./ensure-test-evidence.js";
11
11
  import { createAttemptWorktree, removeAttemptWorktree } from "./attempt-worktree.js";
@@ -191,9 +191,10 @@ function formatGatesRunBlock(gates) {
191
191
  return `${header}\n- none`;
192
192
  return `${header}\n${gates.map((gate) => {
193
193
  const tail = gate.stderrTail.trim() || gate.stdoutTail.trim();
194
+ const verdict = gate.timedOut ? "did not finish (stopped by Bridge)" : `exit ${gate.exitCode}`;
194
195
  return tail
195
- ? `- ${gate.command} — exit ${gate.exitCode}\n ${tail.replace(/\n/g, "\n ")}`
196
- : `- ${gate.command} — exit ${gate.exitCode}`;
196
+ ? `- ${gate.command} — ${verdict}\n ${tail.replace(/\n/g, "\n ")}`
197
+ : `- ${gate.command} — ${verdict}`;
197
198
  }).join("\n")}`;
198
199
  }
199
200
  function gateFailureLine(tail) {
@@ -444,8 +445,8 @@ function sourceProbeVerdict(exitCode, stdout) {
444
445
  return "fails";
445
446
  return "broken";
446
447
  }
447
- function testProbeVerdict(ran, exitCode) {
448
- if (!ran)
448
+ function testProbeVerdict(ran, exitCode, timedOut) {
449
+ if (!ran || timedOut)
449
450
  return "broken";
450
451
  return exitCode === 0 ? "holds" : "fails";
451
452
  }
@@ -553,12 +554,14 @@ export async function runDeliveryVerificationProbes(input) {
553
554
  let stdout = "";
554
555
  let stderr = "";
555
556
  let ran = false;
557
+ let timedOut = false;
556
558
  try {
557
559
  const result = await (input.runCommand ?? runBoundedVerificationCommand)(named, input.workspace);
558
560
  exitCode = result.code;
559
561
  stdout = result.stdout;
560
562
  stderr = result.stderr;
561
563
  ran = true;
564
+ timedOut = result.timedOut === true;
562
565
  }
563
566
  catch (error) {
564
567
  stderr = error instanceof Error ? error.message : String(error);
@@ -566,7 +569,7 @@ export async function runDeliveryVerificationProbes(input) {
566
569
  }
567
570
  witnesses.push(witnessRecord({
568
571
  key: probe.key, kind: "test", command: named, exitCode, stdout, stderr, checks,
569
- verdict: testProbeVerdict(ran, exitCode),
572
+ verdict: testProbeVerdict(ran, exitCode, timedOut),
570
573
  }));
571
574
  continue;
572
575
  }
@@ -688,7 +691,10 @@ export async function runDeliveryVerificationProbesForAttempt(input) {
688
691
  async function settle(client, investigationId, body) {
689
692
  await client.request(`/runner/v1/investigations/${investigationId}/settle`, {
690
693
  method: "POST",
691
- body: JSON.stringify(body),
694
+ body: JSON.stringify({ ...body,
695
+ ...(body.result !== undefined ? { result: redactSecretsDeep(body.result) } : {}),
696
+ ...(body.error !== undefined ? { error: redactSecrets(body.error) } : {}),
697
+ }),
692
698
  });
693
699
  }
694
700
  /**
@@ -901,7 +907,7 @@ export async function executeNextInvestigation(client, config, workspace, brief,
901
907
  limitations: observed.limitations,
902
908
  criteria: observed.criteria,
903
909
  command_failures: deliveryVerification
904
- ? gatesRun.filter((gate) => gate.exitCode !== 0).map(gateFailureFact).slice(0, 10)
910
+ ? gatesRun.filter((gate) => gate.exitCode !== 0 && !gate.timedOut).map(gateFailureFact).slice(0, 10)
905
911
  : observed.command_failures,
906
912
  step_witness: stepWitness,
907
913
  no_behaviour: deliveryVerification ? observed.no_behaviour : [],
@@ -18,7 +18,7 @@ export async function isLandTreeEmpty(input) {
18
18
  const ahead = await countAhead(input.workspace, base);
19
19
  if (ahead !== null && ahead > 0)
20
20
  return false;
21
- const pending = filterPathsInScope(await (input.listUncommittedPaths ?? listUncommittedPaths)(input.workspace), input.spec.change_scope ?? []);
21
+ const pending = filterPathsInScope(await (input.listUncommittedPaths ?? ((workspace) => listUncommittedPaths(workspace, { untracked: "all" })))(input.workspace), input.spec.change_scope ?? []);
22
22
  return pending.length === 0;
23
23
  }
24
24
  function deliveryObjectClaimsWork(raw) {
@@ -197,7 +197,7 @@ function isConduitPath(path) {
197
197
  return normalized === ".conduit" || normalized.startsWith(".conduit/");
198
198
  }
199
199
  async function markerPresent(workspace, marker, paths) {
200
- const args = ["-C", workspace, "grep", "-l", "-F", "--untracked", marker, "--", ...paths];
200
+ const args = ["-C", workspace, "grep", "-l", "-F", "--untracked", "-e", marker, "--", ...paths];
201
201
  try {
202
202
  const { stdout } = await execFileAsync("git", args, {
203
203
  timeout: 30_000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.156",
3
+ "version": "0.16.159",
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": {