@miraland-labs/conduit-bridge 0.16.112 → 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.
- package/dist/investigation.js +146 -2
- package/dist/probe-run.js +28 -0
- package/package.json +1 -1
package/dist/investigation.js
CHANGED
|
@@ -16,7 +16,7 @@ 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
21
|
import { createRequire } from "node:module";
|
|
22
22
|
import { tmpdir } from "node:os";
|
|
@@ -274,6 +274,47 @@ const PROBE_MAX = 20;
|
|
|
274
274
|
const PROBE_COMMAND_MAX = 300;
|
|
275
275
|
const PROBE_CHECKS_MAX = 500;
|
|
276
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
|
+
}
|
|
277
318
|
const PROBE_HOLDS_LINE = "PROBE HOLDS";
|
|
278
319
|
const PROBE_FAILS_LINE = "PROBE FAILS";
|
|
279
320
|
function lastNonEmptyLine(text) {
|
|
@@ -364,6 +405,7 @@ function witnessRecord(input) {
|
|
|
364
405
|
tail: probeTail(input.stderr, input.stdout),
|
|
365
406
|
checks: clipWitnessField(input.checks, PROBE_CHECKS_MAX),
|
|
366
407
|
verdict: input.verdict,
|
|
408
|
+
instrument: "running",
|
|
367
409
|
};
|
|
368
410
|
}
|
|
369
411
|
/**
|
|
@@ -438,6 +480,106 @@ export async function runDeliveryVerificationProbes(input) {
|
|
|
438
480
|
}
|
|
439
481
|
return witnesses;
|
|
440
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
|
+
}
|
|
441
583
|
async function settle(client, investigationId, body) {
|
|
442
584
|
await client.request(`/runner/v1/investigations/${investigationId}/settle`, {
|
|
443
585
|
method: "POST",
|
|
@@ -602,10 +744,12 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
602
744
|
return true;
|
|
603
745
|
}
|
|
604
746
|
const stepWitness = deliveryVerification
|
|
605
|
-
? await
|
|
747
|
+
? await runDeliveryVerificationProbesForAttempt({
|
|
606
748
|
workspace: attemptWorkspace,
|
|
607
749
|
probes: observed.probes ?? [],
|
|
750
|
+
instrumentChanged: await probeInstrumentChanged(attemptWorkspace, assignment.base_commit, commit),
|
|
608
751
|
runCommand: options.runProbeCommand,
|
|
752
|
+
runDeliveredProbes: options.runDeliveredProbes,
|
|
609
753
|
})
|
|
610
754
|
: [];
|
|
611
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.
|
|
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": {
|