@miraland-labs/conduit-bridge 0.16.111 → 0.16.113

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.
@@ -16,13 +16,22 @@ import { DRIVERS, extractAgentReportJsonText, parseJsonObjectCandidate } from ".
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 } from "node:child_process";
19
+ import { execFile, spawn } from "node:child_process";
20
20
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
21
+ import { createRequire } from "node:module";
21
22
  import { tmpdir } from "node:os";
22
23
  import { join } from "node:path";
23
24
  import { promisify } from "node:util";
24
25
  import { isRunnableVerificationCommand } from "./execution-class.js";
25
26
  const execFileAsync = promisify(execFile);
27
+ const tsxLoader = (() => {
28
+ try {
29
+ return createRequire(import.meta.url).resolve("tsx");
30
+ }
31
+ catch {
32
+ return "tsx";
33
+ }
34
+ })();
26
35
  const budgetSchema = z.object({
27
36
  max_files: z.number().int().min(1).max(24).optional().default(8),
28
37
  max_duration_ms: z.number().int().min(10_000).max(15 * 60_000).optional().default(5 * 60_000),
@@ -265,6 +274,70 @@ const PROBE_MAX = 20;
265
274
  const PROBE_COMMAND_MAX = 300;
266
275
  const PROBE_CHECKS_MAX = 500;
267
276
  const PROBE_KEY_MAX = 40;
277
+ export const BRIDGE_PACKAGE_NAME = "@miraland-labs/conduit-bridge";
278
+ export const BRIDGE_SRC_PREFIX = "packages/conduit-bridge/src/";
279
+ export const PROBE_RUN_ENTRY = "packages/conduit-bridge/src/probe-run.ts";
280
+ export const DELIVERED_PROBE_SELF_CHECK_TAIL = "delivered probe instrument failed its self-check";
281
+ export const KNOWN_ANSWER_PROBES = [
282
+ { key: "[known-holds]", lang: "sh", source: "echo 'PROBE HOLDS'\nexit 0", checks: "known answer: holds" },
283
+ { key: "[known-fails]", lang: "sh", source: "echo 'PROBE FAILS'\nexit 1", checks: "known answer: fails" },
284
+ ];
285
+ /**
286
+ * True when this is Conduit's own repository and a changed path between base and the delivered
287
+ * commit is under the Bridge source. Git errors are not a change: the running instrument stays.
288
+ */
289
+ export async function probeInstrumentChanged(workspace, baseCommit, commit) {
290
+ if (baseCommit === null)
291
+ return false;
292
+ try {
293
+ const { stdout: raw } = await execFileAsync("git", ["show", `${commit}:packages/conduit-bridge/package.json`], {
294
+ cwd: workspace,
295
+ timeout: 30_000,
296
+ maxBuffer: 1_000_000,
297
+ });
298
+ const name = JSON.parse(raw).name;
299
+ if (name !== BRIDGE_PACKAGE_NAME)
300
+ return false;
301
+ const { stdout: diff } = await execFileAsync("git", ["diff", "--name-only", `${baseCommit}..${commit}`], {
302
+ cwd: workspace,
303
+ timeout: 30_000,
304
+ maxBuffer: 8_000_000,
305
+ });
306
+ return diff.split("\n").some((path) => {
307
+ const trimmed = path.trim();
308
+ return trimmed === "packages/conduit-bridge/src" || trimmed.startsWith(BRIDGE_SRC_PREFIX);
309
+ });
310
+ }
311
+ catch {
312
+ return false;
313
+ }
314
+ }
315
+ export function knownAnswerProbesPassed(witnesses) {
316
+ return witnesses.length === 2 && witnesses[0]?.verdict === "holds" && witnesses[1]?.verdict === "fails";
317
+ }
318
+ const PROBE_HOLDS_LINE = "PROBE HOLDS";
319
+ const PROBE_FAILS_LINE = "PROBE FAILS";
320
+ function lastNonEmptyLine(text) {
321
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
322
+ return lines.at(-1) ?? "";
323
+ }
324
+ function sourceProbeVerdict(exitCode, stdout) {
325
+ const last = lastNonEmptyLine(stdout);
326
+ if (exitCode === 0 && last === PROBE_HOLDS_LINE)
327
+ return "holds";
328
+ if (exitCode === 1 && last === PROBE_FAILS_LINE)
329
+ return "fails";
330
+ return "broken";
331
+ }
332
+ function testProbeVerdict(ran, exitCode) {
333
+ if (!ran)
334
+ return "broken";
335
+ return exitCode === 0 ? "holds" : "fails";
336
+ }
337
+ function probeTail(stderr, stdout) {
338
+ const stderrTail = stderr.length > PROBE_TAIL_CHARS ? stderr.slice(stderr.length - PROBE_TAIL_CHARS) : stderr;
339
+ return boundedTail(`${stderrTail}\n${stdout}`, PROBE_TAIL_CHARS);
340
+ }
268
341
  function probeEnv() {
269
342
  const env = {};
270
343
  if (process.env.PATH !== undefined)
@@ -282,14 +355,14 @@ function probeInterpreter(language) {
282
355
  }
283
356
  function probeFilename(language) {
284
357
  if (language === "ts")
285
- return "probe.ts";
358
+ return "probe.mts";
286
359
  if (language === "py")
287
360
  return "probe.py";
288
361
  return "probe.sh";
289
362
  }
290
363
  function probeArgv(language, file) {
291
364
  if (language === "ts")
292
- return ["node", "--import", "tsx", file];
365
+ return ["node", "--import", tsxLoader, file];
293
366
  if (language === "py")
294
367
  return ["python3", file];
295
368
  return ["bash", file];
@@ -314,9 +387,11 @@ async function execProbe(argv, workspace) {
314
387
  const err = error;
315
388
  if (typeof err.code === "string")
316
389
  throw error;
390
+ const stdout = String(err.stdout ?? "");
391
+ const stderr = String(err.stderr ?? "");
317
392
  return {
318
- stdout: String(err.stdout ?? ""),
319
- stderr: String(err.stderr || err.message || "probe failed"),
393
+ stdout,
394
+ stderr: stdout === "" && stderr === "" ? String(err.message || "probe failed") : stderr,
320
395
  code: err.killed === true || typeof err.code !== "number" ? -1 : err.code,
321
396
  };
322
397
  }
@@ -327,8 +402,10 @@ function witnessRecord(input) {
327
402
  kind: input.kind,
328
403
  command: clipWitnessField(input.command, PROBE_COMMAND_MAX),
329
404
  exit_code: input.exitCode,
330
- tail: boundedTail(`${input.stderr.trim()}\n${input.stdout.trim()}`.trim(), PROBE_TAIL_CHARS),
405
+ tail: probeTail(input.stderr, input.stdout),
331
406
  checks: clipWitnessField(input.checks, PROBE_CHECKS_MAX),
407
+ verdict: input.verdict,
408
+ instrument: "running",
332
409
  };
333
410
  }
334
411
  /**
@@ -346,17 +423,20 @@ export async function runDeliveryVerificationProbes(input) {
346
423
  if (!isRunnableVerificationCommand(named)) {
347
424
  witnesses.push(witnessRecord({
348
425
  key: probe.key, kind: "test", command: named, exitCode: -1, stdout: "", stderr: "not a registered command", checks,
426
+ verdict: "broken",
349
427
  }));
350
428
  continue;
351
429
  }
352
430
  let exitCode = -1;
353
431
  let stdout = "";
354
432
  let stderr = "";
433
+ let ran = false;
355
434
  try {
356
435
  const result = await (input.runCommand ?? runBoundedVerificationCommand)(named, input.workspace);
357
436
  exitCode = result.code;
358
437
  stdout = result.stdout;
359
438
  stderr = result.stderr;
439
+ ran = true;
360
440
  }
361
441
  catch (error) {
362
442
  stderr = error instanceof Error ? error.message : String(error);
@@ -364,6 +444,7 @@ export async function runDeliveryVerificationProbes(input) {
364
444
  }
365
445
  witnesses.push(witnessRecord({
366
446
  key: probe.key, kind: "test", command: named, exitCode, stdout, stderr, checks,
447
+ verdict: testProbeVerdict(ran, exitCode),
367
448
  }));
368
449
  continue;
369
450
  }
@@ -394,10 +475,111 @@ export async function runDeliveryVerificationProbes(input) {
394
475
  }
395
476
  witnesses.push(witnessRecord({
396
477
  key: probe.key, kind: "probe", command, exitCode, stdout, stderr, checks,
478
+ verdict: sourceProbeVerdict(exitCode, stdout),
397
479
  }));
398
480
  }
399
481
  return witnesses;
400
482
  }
483
+ const deliveredWitnessSchema = z.object({
484
+ witnesses: z.array(z.object({
485
+ key: z.string(),
486
+ kind: z.enum(["test", "probe"]),
487
+ command: z.string(),
488
+ exit_code: z.number(),
489
+ tail: z.string(),
490
+ checks: z.string(),
491
+ verdict: z.enum(["holds", "fails", "broken"]),
492
+ instrument: z.enum(["running", "delivered"]).optional(),
493
+ }).passthrough()),
494
+ });
495
+ function stampInstrument(witnesses, instrument) {
496
+ return witnesses.map((witness) => ({ ...witness, instrument }));
497
+ }
498
+ function markSelfCheckBroken(witnesses) {
499
+ return stampInstrument(witnesses.map((witness) => ({
500
+ ...witness,
501
+ verdict: "broken",
502
+ tail: DELIVERED_PROBE_SELF_CHECK_TAIL,
503
+ })), "running");
504
+ }
505
+ function toStepWitness(witness) {
506
+ return {
507
+ key: witness.key,
508
+ kind: witness.kind,
509
+ command: witness.command,
510
+ exit_code: witness.exit_code,
511
+ tail: witness.tail,
512
+ checks: witness.checks,
513
+ verdict: witness.verdict,
514
+ instrument: witness.instrument === "delivered" ? "delivered" : "running",
515
+ };
516
+ }
517
+ async function execDeliveredProbeRun(argv, stdin, workspace, timeoutMs) {
518
+ const [file, ...args] = argv;
519
+ if (!file)
520
+ throw new Error("delivered probe command is empty");
521
+ return await new Promise((resolve, reject) => {
522
+ const child = spawn(file, args, {
523
+ cwd: workspace,
524
+ env: probeEnv(),
525
+ stdio: ["pipe", "pipe", "pipe"],
526
+ });
527
+ let stdout = "";
528
+ let stderr = "";
529
+ const timer = setTimeout(() => {
530
+ child.kill("SIGKILL");
531
+ }, timeoutMs);
532
+ child.stdout?.on("data", (chunk) => { stdout += String(chunk); });
533
+ child.stderr?.on("data", (chunk) => { stderr += String(chunk); });
534
+ child.on("error", (error) => {
535
+ clearTimeout(timer);
536
+ reject(error);
537
+ });
538
+ child.on("close", (code) => {
539
+ clearTimeout(timer);
540
+ resolve({ code: code ?? -1, stdout, stderr });
541
+ });
542
+ child.stdin?.on("error", () => undefined);
543
+ child.stdin?.end(stdin);
544
+ });
545
+ }
546
+ export async function runDeliveredProbeInstrument(input) {
547
+ const entry = join(input.workspace, PROBE_RUN_ENTRY);
548
+ const timeoutMs = PROBE_TIMEOUT_MS * (input.probes.length + 1);
549
+ const stdin = JSON.stringify({ probes: input.probes });
550
+ const argv = ["node", "--import", tsxLoader, entry];
551
+ const result = await (input.exec ?? execDeliveredProbeRun)(argv, stdin, input.workspace, timeoutMs);
552
+ if (result.code !== 0) {
553
+ throw new Error(result.stderr.trim() || `delivered probe-run exited ${result.code}`);
554
+ }
555
+ try {
556
+ return stampInstrument(deliveredWitnessSchema.parse(JSON.parse(result.stdout)).witnesses.map(toStepWitness), "delivered");
557
+ }
558
+ catch (error) {
559
+ throw error instanceof Error ? error : new Error("delivered probe instrument returned no witness list");
560
+ }
561
+ }
562
+ export async function runDeliveryVerificationProbesForAttempt(input) {
563
+ const runRunning = () => runDeliveryVerificationProbes({
564
+ workspace: input.workspace,
565
+ probes: input.probes,
566
+ runCommand: input.runCommand,
567
+ });
568
+ if (!input.instrumentChanged) {
569
+ return stampInstrument(await runRunning(), "running");
570
+ }
571
+ const runDelivered = input.runDeliveredProbes ?? ((args) => runDeliveredProbeInstrument(args));
572
+ try {
573
+ const check = await runDelivered({ workspace: input.workspace, probes: KNOWN_ANSWER_PROBES });
574
+ if (knownAnswerProbesPassed(check)) {
575
+ return stampInstrument(await runDelivered({ workspace: input.workspace, probes: input.probes }), "delivered");
576
+ }
577
+ }
578
+ catch {
579
+ // Known-answer failure and a delivered-instrument throw take the same path: running probes, then broken.
580
+ }
581
+ return markSelfCheckBroken(await runRunning());
582
+ }
401
583
  async function settle(client, investigationId, body) {
402
584
  await client.request(`/runner/v1/investigations/${investigationId}/settle`, {
403
585
  method: "POST",
@@ -562,10 +744,12 @@ export async function executeNextInvestigation(client, config, workspace, brief,
562
744
  return true;
563
745
  }
564
746
  const stepWitness = deliveryVerification
565
- ? await runDeliveryVerificationProbes({
747
+ ? await runDeliveryVerificationProbesForAttempt({
566
748
  workspace: attemptWorkspace,
567
749
  probes: observed.probes ?? [],
750
+ instrumentChanged: await probeInstrumentChanged(attemptWorkspace, assignment.base_commit, commit),
568
751
  runCommand: options.runProbeCommand,
752
+ runDeliveredProbes: options.runDeliveredProbes,
569
753
  })
570
754
  : [];
571
755
  if (deliveryVerification)
@@ -0,0 +1,28 @@
1
+ /** Delivered probe instrument entry. `{ "probes": [...] }` on stdin, `{ witnesses }` on stdout. */
2
+ import { z } from "zod";
3
+ import { runDeliveryVerificationProbes } from "./investigation.js";
4
+ const probeRunInputSchema = z.object({
5
+ probes: z.array(z.object({
6
+ key: z.string().trim().min(1).max(40),
7
+ test: z.string().max(300).optional(),
8
+ lang: z.enum(["ts", "py", "sh"]).optional(),
9
+ source: z.string().max(8_000).optional(),
10
+ checks: z.string().max(500).optional().default(""),
11
+ })).max(20),
12
+ });
13
+ async function readStdin() {
14
+ const chunks = [];
15
+ for await (const chunk of process.stdin) {
16
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
17
+ }
18
+ return Buffer.concat(chunks).toString("utf8");
19
+ }
20
+ try {
21
+ const parsed = probeRunInputSchema.parse(JSON.parse(await readStdin()));
22
+ const witnesses = await runDeliveryVerificationProbes({ workspace: process.cwd(), probes: parsed.probes });
23
+ process.stdout.write(`${JSON.stringify({ witnesses })}\n`);
24
+ }
25
+ catch (error) {
26
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
27
+ process.exit(1);
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.111",
3
+ "version": "0.16.113",
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": {