@tangle-network/traces 0.11.4 → 0.11.5
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/README.md +7 -0
- package/dist/{chunk-AGPOIWPQ.js → chunk-VTYOCFGF.js} +1019 -85
- package/dist/cli.js +64 -5
- package/dist/index.d.ts +288 -4
- package/dist/index.js +36 -74
- package/docs/replay-verify.md +17 -0
- package/docs/trace-analysts.md +28 -0
- package/package.json +3 -3
|
@@ -766,8 +766,8 @@ function serializeSpans(spans) {
|
|
|
766
766
|
async function writeOtlpFile(spans, outPath) {
|
|
767
767
|
const { mkdtemp: mkdtemp8, writeFile: writeFile10 } = await import("fs/promises");
|
|
768
768
|
const { tmpdir: tmpdir9 } = await import("os");
|
|
769
|
-
const { join:
|
|
770
|
-
const path = outPath ??
|
|
769
|
+
const { join: join34 } = await import("path");
|
|
770
|
+
const path = outPath ?? join34(await mkdtemp8(join34(tmpdir9(), "traces-")), "spans.otlp.jsonl");
|
|
771
771
|
await writeFile10(path, serializeSpans(spans), "utf8");
|
|
772
772
|
return path;
|
|
773
773
|
}
|
|
@@ -7419,7 +7419,7 @@ function zaiChatCaller(options) {
|
|
|
7419
7419
|
}
|
|
7420
7420
|
};
|
|
7421
7421
|
}
|
|
7422
|
-
function
|
|
7422
|
+
function clipText(text2, limit) {
|
|
7423
7423
|
if (text2.length <= limit) return text2;
|
|
7424
7424
|
const half = Math.floor(limit / 2);
|
|
7425
7425
|
return `${text2.slice(0, half)}
|
|
@@ -7428,11 +7428,11 @@ ${text2.slice(-half)}`;
|
|
|
7428
7428
|
}
|
|
7429
7429
|
function renderStep(step, marker) {
|
|
7430
7430
|
const rc = parseRecordedReturncode(step.observation);
|
|
7431
|
-
const output =
|
|
7431
|
+
const output = clipText(parseObservationOutput(step.observation).trim(), 1600);
|
|
7432
7432
|
return [
|
|
7433
7433
|
`### step ${step.step_id}${marker}`,
|
|
7434
7434
|
"```sh",
|
|
7435
|
-
|
|
7435
|
+
clipText(step.action, 2e3),
|
|
7436
7436
|
"```",
|
|
7437
7437
|
`returncode: ${rc ?? "none recorded"}`,
|
|
7438
7438
|
output.length > 0 ? `output:
|
|
@@ -7441,13 +7441,25 @@ ${output}
|
|
|
7441
7441
|
\`\`\`` : "output: (empty)"
|
|
7442
7442
|
].join("\n");
|
|
7443
7443
|
}
|
|
7444
|
-
function
|
|
7444
|
+
function promptBody(input) {
|
|
7445
7445
|
const radius = input.contextRadius ?? 3;
|
|
7446
7446
|
const target = input.steps.find((s) => s.step_id === input.k);
|
|
7447
7447
|
if (!target) throw new Error(`replay-fix: no step with step_id ${input.k}`);
|
|
7448
7448
|
const context = input.steps.filter(
|
|
7449
7449
|
(s) => s.step_id !== input.k && Math.abs(s.step_id - input.k) <= radius
|
|
7450
7450
|
);
|
|
7451
|
+
return [
|
|
7452
|
+
"## Task the agent was solving",
|
|
7453
|
+
input.taskStatement ? clipText(input.taskStatement, 4e3) : "(no task statement recorded)",
|
|
7454
|
+
"",
|
|
7455
|
+
"## Surrounding steps",
|
|
7456
|
+
...context.map((s) => renderStep(s, "")),
|
|
7457
|
+
"",
|
|
7458
|
+
"## Failing step to correct",
|
|
7459
|
+
renderStep(target, " (INCORRECT \u2014 correct this one)")
|
|
7460
|
+
];
|
|
7461
|
+
}
|
|
7462
|
+
function buildFixPrompt(input) {
|
|
7451
7463
|
const system = [
|
|
7452
7464
|
"You repair one failed shell command from a recorded coding-agent trajectory.",
|
|
7453
7465
|
"The trajectory replays inside the original docker image; every command runs as a fresh /bin/sh subshell from a fixed working directory.",
|
|
@@ -7456,19 +7468,62 @@ function buildFixPrompt(input) {
|
|
|
7456
7468
|
"The corrected command must accomplish the failing step's intent and exit 0. No prose outside the fenced block."
|
|
7457
7469
|
].join("\n");
|
|
7458
7470
|
const user = [
|
|
7459
|
-
|
|
7460
|
-
input.taskStatement ? clip(input.taskStatement, 4e3) : "(no task statement recorded)",
|
|
7471
|
+
...promptBody(input),
|
|
7461
7472
|
"",
|
|
7462
|
-
"
|
|
7463
|
-
|
|
7473
|
+
"Output the single corrected replacement for the failing step now."
|
|
7474
|
+
].join("\n");
|
|
7475
|
+
return { system, user };
|
|
7476
|
+
}
|
|
7477
|
+
function renderFailedAttempt(prior) {
|
|
7478
|
+
if (prior.command === null) {
|
|
7479
|
+
return [
|
|
7480
|
+
`### attempt ${prior.attempt}`,
|
|
7481
|
+
`model call failed before producing a command: ${prior.llmError ?? "unknown error"}`
|
|
7482
|
+
].join("\n");
|
|
7483
|
+
}
|
|
7484
|
+
const stdout = (prior.stdoutTail ?? "").trim();
|
|
7485
|
+
const stderr = (prior.stderrTail ?? "").trim();
|
|
7486
|
+
return [
|
|
7487
|
+
`### attempt ${prior.attempt}`,
|
|
7488
|
+
"```sh",
|
|
7489
|
+
clipText(prior.command, 2e3),
|
|
7490
|
+
"```",
|
|
7491
|
+
`exit code: ${prior.exitCode ?? "not executed"}`,
|
|
7492
|
+
stdout.length > 0 ? `stdout:
|
|
7493
|
+
\`\`\`
|
|
7494
|
+
${stdout}
|
|
7495
|
+
\`\`\`` : "stdout: (empty)",
|
|
7496
|
+
stderr.length > 0 ? `stderr:
|
|
7497
|
+
\`\`\`
|
|
7498
|
+
${stderr}
|
|
7499
|
+
\`\`\`` : "stderr: (empty)"
|
|
7500
|
+
].join("\n");
|
|
7501
|
+
}
|
|
7502
|
+
function buildRetryFixPrompt(input, priorAttempts, maxScriptCommands = 5) {
|
|
7503
|
+
if (priorAttempts.length === 0) {
|
|
7504
|
+
throw new Error("replay-fix: buildRetryFixPrompt requires at least one prior attempt");
|
|
7505
|
+
}
|
|
7506
|
+
const system = [
|
|
7507
|
+
"You repair one failed shell command from a recorded coding-agent trajectory.",
|
|
7508
|
+
"The trajectory replays inside the original docker image; every command runs as a fresh /bin/sh subshell from a fixed working directory.",
|
|
7509
|
+
"Earlier corrected commands were executed for real and failed; their actual output is included below.",
|
|
7510
|
+
`Reply with a corrected fix inside a single \`\`\`sh fenced block: either one command, or a short script of at most ${maxScriptCommands} commands (one per line).`,
|
|
7511
|
+
"The whole block executes as ONE /bin/sh unit from the fixed working directory and must exit 0.",
|
|
7512
|
+
"Do not repeat a command that already failed. Keep reasoning brief. No prose outside the fenced block."
|
|
7513
|
+
].join("\n");
|
|
7514
|
+
const user = [
|
|
7515
|
+
...promptBody(input),
|
|
7464
7516
|
"",
|
|
7465
|
-
"##
|
|
7466
|
-
|
|
7517
|
+
"## Previous fix attempts (executed for real \u2014 all failed)",
|
|
7518
|
+
...priorAttempts.map((prior) => renderFailedAttempt(prior)),
|
|
7467
7519
|
"",
|
|
7468
|
-
|
|
7520
|
+
`Output a corrected fix now \u2014 one \`\`\`sh block, at most ${maxScriptCommands} commands.`
|
|
7469
7521
|
].join("\n");
|
|
7470
7522
|
return { system, user };
|
|
7471
7523
|
}
|
|
7524
|
+
function countScriptCommands(script) {
|
|
7525
|
+
return script.split("\n").filter((line) => line.trim().length > 0 && !line.trim().startsWith("#")).length;
|
|
7526
|
+
}
|
|
7472
7527
|
function extractFixCommand(content) {
|
|
7473
7528
|
const blocks = [...content.matchAll(/```(?:sh|bash|shell)?\n([\s\S]*?)```/g)];
|
|
7474
7529
|
const candidate = blocks.length > 0 ? blocks[blocks.length - 1][1] : content;
|
|
@@ -7484,12 +7539,167 @@ async function generateFixCommand(caller, input) {
|
|
|
7484
7539
|
if (command === null) {
|
|
7485
7540
|
return {
|
|
7486
7541
|
succeeded: false,
|
|
7487
|
-
error: `completion carried no usable command: ${
|
|
7542
|
+
error: `completion carried no usable command: ${clipText(outcome.value.content, 300)}`
|
|
7488
7543
|
};
|
|
7489
7544
|
}
|
|
7490
7545
|
return { succeeded: true, value: { command, usage: outcome.value.usage } };
|
|
7491
7546
|
}
|
|
7492
7547
|
|
|
7548
|
+
// src/replay-fix-loop.ts
|
|
7549
|
+
function toFailedAttempt(record2) {
|
|
7550
|
+
return {
|
|
7551
|
+
attempt: record2.attempt,
|
|
7552
|
+
command: record2.command,
|
|
7553
|
+
exitCode: record2.exitCode,
|
|
7554
|
+
stdoutTail: record2.stdoutTail,
|
|
7555
|
+
stderrTail: record2.stderrTail,
|
|
7556
|
+
llmError: record2.llmError ?? record2.armBError
|
|
7557
|
+
};
|
|
7558
|
+
}
|
|
7559
|
+
async function runFixLoop(caller, input, executor, options) {
|
|
7560
|
+
if (!Number.isInteger(options.maxAttempts) || options.maxAttempts < 1) {
|
|
7561
|
+
throw new Error(
|
|
7562
|
+
`replay-fix-loop: maxAttempts must be a positive integer, got ${options.maxAttempts}`
|
|
7563
|
+
);
|
|
7564
|
+
}
|
|
7565
|
+
const scriptCap = options.maxScriptCommands ?? 5;
|
|
7566
|
+
const tailChars = options.outputTailChars ?? 1600;
|
|
7567
|
+
const attempts = [];
|
|
7568
|
+
let llmCalls = 0;
|
|
7569
|
+
let llmFailures = 0;
|
|
7570
|
+
let promptTokens = 0;
|
|
7571
|
+
let completionTokens = 0;
|
|
7572
|
+
const unexecuted = (attempt, llmError, usage, command, started) => ({
|
|
7573
|
+
attempt,
|
|
7574
|
+
command,
|
|
7575
|
+
llmError,
|
|
7576
|
+
usage,
|
|
7577
|
+
executed: false,
|
|
7578
|
+
exitCode: null,
|
|
7579
|
+
prefixExecuted: null,
|
|
7580
|
+
prefixDivergences: null,
|
|
7581
|
+
failureVanished: null,
|
|
7582
|
+
stdoutTail: null,
|
|
7583
|
+
stderrTail: null,
|
|
7584
|
+
armBError: null,
|
|
7585
|
+
wallMs: Date.now() - started
|
|
7586
|
+
});
|
|
7587
|
+
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
|
|
7588
|
+
const started = Date.now();
|
|
7589
|
+
const prompt = attempt === 1 ? buildFixPrompt(input) : buildRetryFixPrompt(input, attempts.map(toFailedAttempt), scriptCap);
|
|
7590
|
+
llmCalls += 1;
|
|
7591
|
+
const outcome = await caller.complete(prompt.system, prompt.user);
|
|
7592
|
+
if (!outcome.succeeded) {
|
|
7593
|
+
llmFailures += 1;
|
|
7594
|
+
attempts.push(unexecuted(attempt, outcome.error, null, null, started));
|
|
7595
|
+
options.onProgress?.(
|
|
7596
|
+
`fix-loop attempt ${attempt}: LLM failed \u2014 ${outcome.error.slice(0, 160)}`
|
|
7597
|
+
);
|
|
7598
|
+
continue;
|
|
7599
|
+
}
|
|
7600
|
+
const usage = outcome.value.usage;
|
|
7601
|
+
promptTokens += usage?.promptTokens ?? 0;
|
|
7602
|
+
completionTokens += usage?.completionTokens ?? 0;
|
|
7603
|
+
const command = extractFixCommand(outcome.value.content);
|
|
7604
|
+
if (command === null) {
|
|
7605
|
+
llmFailures += 1;
|
|
7606
|
+
attempts.push(
|
|
7607
|
+
unexecuted(
|
|
7608
|
+
attempt,
|
|
7609
|
+
`completion carried no usable command: ${clipText(outcome.value.content, 300)}`,
|
|
7610
|
+
usage,
|
|
7611
|
+
null,
|
|
7612
|
+
started
|
|
7613
|
+
)
|
|
7614
|
+
);
|
|
7615
|
+
continue;
|
|
7616
|
+
}
|
|
7617
|
+
if (attempt > 1) {
|
|
7618
|
+
const commandLines = countScriptCommands(command);
|
|
7619
|
+
if (commandLines > scriptCap) {
|
|
7620
|
+
llmFailures += 1;
|
|
7621
|
+
attempts.push(
|
|
7622
|
+
unexecuted(
|
|
7623
|
+
attempt,
|
|
7624
|
+
`script exceeds ${scriptCap} command lines (${commandLines})`,
|
|
7625
|
+
usage,
|
|
7626
|
+
null,
|
|
7627
|
+
started
|
|
7628
|
+
)
|
|
7629
|
+
);
|
|
7630
|
+
options.onProgress?.(
|
|
7631
|
+
`fix-loop attempt ${attempt}: rejected script with ${commandLines} command lines`
|
|
7632
|
+
);
|
|
7633
|
+
continue;
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
let execution;
|
|
7637
|
+
try {
|
|
7638
|
+
execution = await executor(command, attempt);
|
|
7639
|
+
} catch (err) {
|
|
7640
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7641
|
+
attempts.push({
|
|
7642
|
+
...unexecuted(attempt, "", usage, command, started),
|
|
7643
|
+
llmError: null,
|
|
7644
|
+
armBError: message.slice(0, 500)
|
|
7645
|
+
});
|
|
7646
|
+
options.onProgress?.(
|
|
7647
|
+
`fix-loop attempt ${attempt}: sandbox error \u2014 ${message.slice(0, 160)}`
|
|
7648
|
+
);
|
|
7649
|
+
return {
|
|
7650
|
+
flipped: false,
|
|
7651
|
+
flippedAtAttempt: null,
|
|
7652
|
+
attempts,
|
|
7653
|
+
aborted: true,
|
|
7654
|
+
llmCalls,
|
|
7655
|
+
llmFailures,
|
|
7656
|
+
promptTokens,
|
|
7657
|
+
completionTokens
|
|
7658
|
+
};
|
|
7659
|
+
}
|
|
7660
|
+
attempts.push({
|
|
7661
|
+
attempt,
|
|
7662
|
+
command,
|
|
7663
|
+
llmError: null,
|
|
7664
|
+
usage,
|
|
7665
|
+
executed: true,
|
|
7666
|
+
exitCode: execution.exitCode,
|
|
7667
|
+
prefixExecuted: execution.prefixExecuted,
|
|
7668
|
+
prefixDivergences: execution.prefixDivergences,
|
|
7669
|
+
failureVanished: execution.failureVanished,
|
|
7670
|
+
stdoutTail: clipText(execution.stdout, tailChars),
|
|
7671
|
+
stderrTail: clipText(execution.stderr, tailChars),
|
|
7672
|
+
armBError: null,
|
|
7673
|
+
wallMs: Date.now() - started
|
|
7674
|
+
});
|
|
7675
|
+
options.onProgress?.(
|
|
7676
|
+
`fix-loop attempt ${attempt}: exit=${execution.exitCode} failureVanished=${execution.failureVanished}`
|
|
7677
|
+
);
|
|
7678
|
+
if (execution.failureVanished) {
|
|
7679
|
+
return {
|
|
7680
|
+
flipped: true,
|
|
7681
|
+
flippedAtAttempt: attempt,
|
|
7682
|
+
attempts,
|
|
7683
|
+
aborted: false,
|
|
7684
|
+
llmCalls,
|
|
7685
|
+
llmFailures,
|
|
7686
|
+
promptTokens,
|
|
7687
|
+
completionTokens
|
|
7688
|
+
};
|
|
7689
|
+
}
|
|
7690
|
+
}
|
|
7691
|
+
return {
|
|
7692
|
+
flipped: false,
|
|
7693
|
+
flippedAtAttempt: null,
|
|
7694
|
+
attempts,
|
|
7695
|
+
aborted: false,
|
|
7696
|
+
llmCalls,
|
|
7697
|
+
llmFailures,
|
|
7698
|
+
promptTokens,
|
|
7699
|
+
completionTokens
|
|
7700
|
+
};
|
|
7701
|
+
}
|
|
7702
|
+
|
|
7493
7703
|
// src/replay-batch.ts
|
|
7494
7704
|
import { execFile } from "child_process";
|
|
7495
7705
|
import { createHash as createHash8 } from "crypto";
|
|
@@ -7778,9 +7988,10 @@ async function runReplayBatch(options) {
|
|
|
7778
7988
|
}
|
|
7779
7989
|
}
|
|
7780
7990
|
let llm = null;
|
|
7781
|
-
if (options.fix
|
|
7991
|
+
if (options.fix !== "none") {
|
|
7782
7992
|
const caller = options.fixCaller;
|
|
7783
|
-
if (!caller) throw new Error(
|
|
7993
|
+
if (!caller) throw new Error(`replay-verify-batch: fix=${options.fix} requires a fixCaller`);
|
|
7994
|
+
const fixAttempts = options.fixAttempts ?? 3;
|
|
7784
7995
|
const maxFixCases = options.maxFixCases ?? 30;
|
|
7785
7996
|
const seed = options.seed ?? 17;
|
|
7786
7997
|
const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
|
|
@@ -7809,20 +8020,115 @@ async function runReplayBatch(options) {
|
|
|
7809
8020
|
armBPrefixExecuted: null,
|
|
7810
8021
|
armBPrefixDivergences: null,
|
|
7811
8022
|
failureVanished: null,
|
|
7812
|
-
armBError: null
|
|
8023
|
+
armBError: null,
|
|
8024
|
+
attempts: null,
|
|
8025
|
+
flippedAtAttempt: null
|
|
7813
8026
|
}
|
|
7814
8027
|
};
|
|
7815
8028
|
continue;
|
|
7816
8029
|
}
|
|
7817
8030
|
const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
|
|
7818
8031
|
const label = `fix ${row.corpus}/${row.trajId}`;
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
const
|
|
8032
|
+
const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
|
|
8033
|
+
const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
|
|
8034
|
+
const promptInput = {
|
|
7822
8035
|
taskStatement: replayCase.taskStatement,
|
|
7823
8036
|
steps: replayCase.steps,
|
|
7824
8037
|
k: replayCase.k
|
|
7825
|
-
}
|
|
8038
|
+
};
|
|
8039
|
+
const caseOut = join21(options.out, caseOutDirName(row));
|
|
8040
|
+
if (options.fix === "loop") {
|
|
8041
|
+
onProgress(`${label}: fix loop (budget ${fixAttempts} attempts)`);
|
|
8042
|
+
const result = await runFixLoop(
|
|
8043
|
+
caller,
|
|
8044
|
+
promptInput,
|
|
8045
|
+
async (command, attempt) => {
|
|
8046
|
+
onProgress(
|
|
8047
|
+
`${label}: attempt ${attempt} arm B \u2014 ${command.split("\n")[0].slice(0, 120)}`
|
|
8048
|
+
);
|
|
8049
|
+
const armB = await executeArmB(
|
|
8050
|
+
replayCase,
|
|
8051
|
+
command,
|
|
8052
|
+
backendFactory(row.derivedImage),
|
|
8053
|
+
signature,
|
|
8054
|
+
stepTimeoutMs,
|
|
8055
|
+
options.prefixLimit,
|
|
8056
|
+
(message) => onProgress(`${label}: attempt ${attempt}: ${message}`)
|
|
8057
|
+
);
|
|
8058
|
+
mkdirSync2(caseOut, { recursive: true });
|
|
8059
|
+
writeFileSync2(
|
|
8060
|
+
join21(caseOut, `armB-attempt${attempt}-result.json`),
|
|
8061
|
+
`${JSON.stringify({ command, ...armB }, null, 2)}
|
|
8062
|
+
`
|
|
8063
|
+
);
|
|
8064
|
+
writeFileSync2(
|
|
8065
|
+
join21(caseOut, "armB-result.json"),
|
|
8066
|
+
`${JSON.stringify({ command, attempt, ...armB }, null, 2)}
|
|
8067
|
+
`
|
|
8068
|
+
);
|
|
8069
|
+
return armB;
|
|
8070
|
+
},
|
|
8071
|
+
{ maxAttempts: fixAttempts, onProgress: (message) => onProgress(`${label}: ${message}`) }
|
|
8072
|
+
);
|
|
8073
|
+
calls += result.llmCalls;
|
|
8074
|
+
failures += result.llmFailures;
|
|
8075
|
+
promptTokens += result.promptTokens;
|
|
8076
|
+
completionTokens += result.completionTokens;
|
|
8077
|
+
const usage = result.promptTokens + result.completionTokens > 0 ? { promptTokens: result.promptTokens, completionTokens: result.completionTokens } : null;
|
|
8078
|
+
const flippedRecord = result.flippedAtAttempt !== null ? result.attempts.find((a) => a.attempt === result.flippedAtAttempt) : null;
|
|
8079
|
+
const summary = flippedRecord ?? [...result.attempts].reverse().find((a) => a.executed) ?? null;
|
|
8080
|
+
const lastRecord = result.attempts.at(-1) ?? null;
|
|
8081
|
+
rows[index] = {
|
|
8082
|
+
...row,
|
|
8083
|
+
fix: summary ? {
|
|
8084
|
+
attempted: true,
|
|
8085
|
+
sampledOut: false,
|
|
8086
|
+
command: summary.command,
|
|
8087
|
+
llmError: null,
|
|
8088
|
+
usage,
|
|
8089
|
+
armBExit: summary.exitCode,
|
|
8090
|
+
armBPrefixExecuted: summary.prefixExecuted,
|
|
8091
|
+
armBPrefixDivergences: summary.prefixDivergences,
|
|
8092
|
+
failureVanished: summary.failureVanished,
|
|
8093
|
+
armBError: null,
|
|
8094
|
+
attempts: result.attempts,
|
|
8095
|
+
flippedAtAttempt: result.flippedAtAttempt
|
|
8096
|
+
} : result.aborted ? {
|
|
8097
|
+
attempted: true,
|
|
8098
|
+
sampledOut: false,
|
|
8099
|
+
command: lastRecord?.command ?? null,
|
|
8100
|
+
llmError: null,
|
|
8101
|
+
usage,
|
|
8102
|
+
armBExit: null,
|
|
8103
|
+
armBPrefixExecuted: null,
|
|
8104
|
+
armBPrefixDivergences: null,
|
|
8105
|
+
failureVanished: null,
|
|
8106
|
+
armBError: lastRecord?.armBError ?? "sandbox error",
|
|
8107
|
+
attempts: result.attempts,
|
|
8108
|
+
flippedAtAttempt: null
|
|
8109
|
+
} : {
|
|
8110
|
+
attempted: true,
|
|
8111
|
+
sampledOut: false,
|
|
8112
|
+
command: null,
|
|
8113
|
+
llmError: lastRecord?.llmError ?? "no attempt produced a runnable fix",
|
|
8114
|
+
usage,
|
|
8115
|
+
armBExit: null,
|
|
8116
|
+
armBPrefixExecuted: null,
|
|
8117
|
+
armBPrefixDivergences: null,
|
|
8118
|
+
failureVanished: null,
|
|
8119
|
+
armBError: null,
|
|
8120
|
+
attempts: result.attempts,
|
|
8121
|
+
flippedAtAttempt: null
|
|
8122
|
+
}
|
|
8123
|
+
};
|
|
8124
|
+
onProgress(
|
|
8125
|
+
`${label}: loop done \u2014 flipped=${result.flipped}` + (result.flippedAtAttempt !== null ? ` at attempt ${result.flippedAtAttempt}` : "") + ` (${result.llmCalls} calls, ${result.attempts.filter((a) => a.executed).length} arms)`
|
|
8126
|
+
);
|
|
8127
|
+
continue;
|
|
8128
|
+
}
|
|
8129
|
+
onProgress(`${label}: generating corrected command`);
|
|
8130
|
+
calls += 1;
|
|
8131
|
+
const generated = await generateFixCommand(caller, promptInput);
|
|
7826
8132
|
if (!generated.succeeded) {
|
|
7827
8133
|
failures += 1;
|
|
7828
8134
|
rows[index] = {
|
|
@@ -7837,7 +8143,9 @@ async function runReplayBatch(options) {
|
|
|
7837
8143
|
armBPrefixExecuted: null,
|
|
7838
8144
|
armBPrefixDivergences: null,
|
|
7839
8145
|
failureVanished: null,
|
|
7840
|
-
armBError: null
|
|
8146
|
+
armBError: null,
|
|
8147
|
+
attempts: null,
|
|
8148
|
+
flippedAtAttempt: null
|
|
7841
8149
|
}
|
|
7842
8150
|
};
|
|
7843
8151
|
onProgress(`${label}: LLM failed \u2014 ${generated.error.slice(0, 200)}`);
|
|
@@ -7845,8 +8153,6 @@ async function runReplayBatch(options) {
|
|
|
7845
8153
|
}
|
|
7846
8154
|
promptTokens += generated.value.usage?.promptTokens ?? 0;
|
|
7847
8155
|
completionTokens += generated.value.usage?.completionTokens ?? 0;
|
|
7848
|
-
const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
|
|
7849
|
-
const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
|
|
7850
8156
|
onProgress(`${label}: arm B \u2014 ${generated.value.command.split("\n")[0].slice(0, 120)}`);
|
|
7851
8157
|
try {
|
|
7852
8158
|
const armB = await executeArmB(
|
|
@@ -7870,10 +8176,11 @@ async function runReplayBatch(options) {
|
|
|
7870
8176
|
armBPrefixExecuted: armB.prefixExecuted,
|
|
7871
8177
|
armBPrefixDivergences: armB.prefixDivergences,
|
|
7872
8178
|
failureVanished: armB.failureVanished,
|
|
7873
|
-
armBError: null
|
|
8179
|
+
armBError: null,
|
|
8180
|
+
attempts: null,
|
|
8181
|
+
flippedAtAttempt: armB.failureVanished ? 1 : null
|
|
7874
8182
|
}
|
|
7875
8183
|
};
|
|
7876
|
-
const caseOut = join21(options.out, caseOutDirName(row));
|
|
7877
8184
|
mkdirSync2(caseOut, { recursive: true });
|
|
7878
8185
|
writeFileSync2(
|
|
7879
8186
|
join21(caseOut, "armB-result.json"),
|
|
@@ -7895,7 +8202,9 @@ async function runReplayBatch(options) {
|
|
|
7895
8202
|
armBPrefixExecuted: null,
|
|
7896
8203
|
armBPrefixDivergences: null,
|
|
7897
8204
|
failureVanished: null,
|
|
7898
|
-
armBError: message.slice(0, 500)
|
|
8205
|
+
armBError: message.slice(0, 500),
|
|
8206
|
+
attempts: null,
|
|
8207
|
+
flippedAtAttempt: null
|
|
7899
8208
|
}
|
|
7900
8209
|
};
|
|
7901
8210
|
onProgress(`${label}: arm B error \u2014 ${message.slice(0, 200)}`);
|
|
@@ -7920,6 +8229,15 @@ async function runReplayBatch(options) {
|
|
|
7920
8229
|
(r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0
|
|
7921
8230
|
);
|
|
7922
8231
|
const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
|
|
8232
|
+
const attempt1Executed = rows.filter(
|
|
8233
|
+
(r) => r.fix?.attempts?.find((a) => a.attempt === 1)?.executed === true
|
|
8234
|
+
);
|
|
8235
|
+
const flippedAt1 = rows.filter((r) => r.fix?.flippedAtAttempt === 1);
|
|
8236
|
+
const flipsByAttempt = {};
|
|
8237
|
+
for (const row of rows) {
|
|
8238
|
+
const at = row.fix?.flippedAtAttempt;
|
|
8239
|
+
if (typeof at === "number") flipsByAttempt[String(at)] = (flipsByAttempt[String(at)] ?? 0) + 1;
|
|
8240
|
+
}
|
|
7923
8241
|
const excludedByReason = {};
|
|
7924
8242
|
for (const excluded of enumeration.excluded) {
|
|
7925
8243
|
excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
|
|
@@ -7959,16 +8277,22 @@ async function runReplayBatch(options) {
|
|
|
7959
8277
|
denominator: selected.length,
|
|
7960
8278
|
value: rate(signatureStrict.length, selected.length)
|
|
7961
8279
|
},
|
|
7962
|
-
fixFlipRate: options.fix
|
|
8280
|
+
fixFlipRate: options.fix !== "none" ? {
|
|
7963
8281
|
numerator: flipped.length,
|
|
7964
8282
|
denominator: armBExecuted.length,
|
|
7965
8283
|
value: rate(flipped.length, armBExecuted.length)
|
|
7966
8284
|
} : null,
|
|
7967
|
-
fixFlipRateNonzeroRc: options.fix
|
|
8285
|
+
fixFlipRateNonzeroRc: options.fix !== "none" ? {
|
|
7968
8286
|
numerator: flippedNonzeroRc.length,
|
|
7969
8287
|
denominator: armBNonzeroRc.length,
|
|
7970
8288
|
value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
|
|
7971
|
-
} : null
|
|
8289
|
+
} : null,
|
|
8290
|
+
fixFlipAttempt1: options.fix === "loop" ? {
|
|
8291
|
+
numerator: flippedAt1.length,
|
|
8292
|
+
denominator: attempt1Executed.length,
|
|
8293
|
+
value: rate(flippedAt1.length, attempt1Executed.length)
|
|
8294
|
+
} : null,
|
|
8295
|
+
flipsByAttempt: options.fix === "loop" ? flipsByAttempt : null
|
|
7972
8296
|
},
|
|
7973
8297
|
llm,
|
|
7974
8298
|
excluded: enumeration.excluded,
|
|
@@ -8012,6 +8336,15 @@ function renderBatchReport(report) {
|
|
|
8012
8336
|
`- Fix-flip rate on recorded-rc\u22600 cases: ${pct(headline.fixFlipRateNonzeroRc.value)} (${headline.fixFlipRateNonzeroRc.numerator}/${headline.fixFlipRateNonzeroRc.denominator}; real recorded failures \u2014 a gold step recorded with rc 0 flips vacuously).`
|
|
8013
8337
|
);
|
|
8014
8338
|
}
|
|
8339
|
+
if (headline.fixFlipAttempt1) {
|
|
8340
|
+
lines.push(
|
|
8341
|
+
`- Fix-flip@1: ${pct(headline.fixFlipAttempt1.value)} (${headline.fixFlipAttempt1.numerator}/${headline.fixFlipAttempt1.denominator} cases whose attempt 1 executed \u2014 the one-shot-comparable number).`
|
|
8342
|
+
);
|
|
8343
|
+
}
|
|
8344
|
+
if (headline.flipsByAttempt && Object.keys(headline.flipsByAttempt).length > 0) {
|
|
8345
|
+
const parts = Object.entries(headline.flipsByAttempt).sort((a, b) => Number(a[0]) - Number(b[0])).map(([attempt, count2]) => `attempt ${attempt}: ${count2}`);
|
|
8346
|
+
lines.push(`- Flips by attempt: ${parts.join(", ")}.`);
|
|
8347
|
+
}
|
|
8015
8348
|
lines.push("");
|
|
8016
8349
|
lines.push("## Enumeration");
|
|
8017
8350
|
lines.push("");
|
|
@@ -8060,7 +8393,7 @@ function renderBatchReport(report) {
|
|
|
8060
8393
|
);
|
|
8061
8394
|
for (const row of report.cases) {
|
|
8062
8395
|
const fix = row.fix;
|
|
8063
|
-
const fixCell = !fix ? "\u2014" : fix.sampledOut ? "sampled-out" : fix.llmError ? "llm-failed" : fix.armBError ? "armB-error" : "generated";
|
|
8396
|
+
const fixCell = !fix ? "\u2014" : fix.sampledOut ? "sampled-out" : fix.llmError ? "llm-failed" : fix.armBError ? "armB-error" : fix.attempts ? fix.flippedAtAttempt !== null ? `flip@${fix.flippedAtAttempt}` : `exhausted(${fix.attempts.length})` : "generated";
|
|
8064
8397
|
lines.push(
|
|
8065
8398
|
[
|
|
8066
8399
|
row.corpus,
|
|
@@ -8122,13 +8455,20 @@ function parseReplayBatchArgs(argv) {
|
|
|
8122
8455
|
return n;
|
|
8123
8456
|
};
|
|
8124
8457
|
const fix = values.get("--fix") ?? "none";
|
|
8125
|
-
if (fix !== "none" && fix !== "generate") {
|
|
8126
|
-
throw new Error(`replay-verify-batch: --fix must be none or
|
|
8458
|
+
if (fix !== "none" && fix !== "generate" && fix !== "loop") {
|
|
8459
|
+
throw new Error(`replay-verify-batch: --fix must be none, generate, or loop, got ${fix}`);
|
|
8460
|
+
}
|
|
8461
|
+
const fixAttempts = optionalNumber("--fix-attempts") ?? 3;
|
|
8462
|
+
if (!Number.isInteger(fixAttempts) || fixAttempts < 1) {
|
|
8463
|
+
throw new Error(
|
|
8464
|
+
`replay-verify-batch: --fix-attempts must be a positive integer, got ${values.get("--fix-attempts")}`
|
|
8465
|
+
);
|
|
8127
8466
|
}
|
|
8128
8467
|
return {
|
|
8129
8468
|
corpora,
|
|
8130
8469
|
out: out ?? ".",
|
|
8131
8470
|
fix,
|
|
8471
|
+
fixAttempts,
|
|
8132
8472
|
fixModel: values.get("--fix-model") ?? "glm-5.2",
|
|
8133
8473
|
fixBaseUrl: values.get("--fix-base-url") ?? "https://api.z.ai/api/coding/paas/v4",
|
|
8134
8474
|
fixApiKeyEnv: values.get("--fix-api-key-env") ?? "ZAI_GLM_API_KEY",
|
|
@@ -8150,7 +8490,7 @@ function replayBatchUsage() {
|
|
|
8150
8490
|
Usage:
|
|
8151
8491
|
traces replay-verify-batch \\
|
|
8152
8492
|
--corpus NAME=<labels.json>::<prepared-dir> [--corpus ...] \\
|
|
8153
|
-
--out DIR [--fix none|generate] [--enumerate-only] \\
|
|
8493
|
+
--out DIR [--fix none|generate|loop] [--fix-attempts 3] [--enumerate-only] \\
|
|
8154
8494
|
[--fix-model glm-5.2] [--fix-base-url URL] [--fix-api-key-env ZAI_GLM_API_KEY] \\
|
|
8155
8495
|
[--max-fix-cases 30] [--seed 17] [--step-timeout MS] [--prefix-limit N] \\
|
|
8156
8496
|
[--case-filter SUBSTRING] [--case-limit N] \\
|
|
@@ -8169,6 +8509,13 @@ Usage:
|
|
|
8169
8509
|
--max-fix-cases, seeded sample beyond it) and executes the corrected command
|
|
8170
8510
|
as arm B in a fresh sandbox.
|
|
8171
8511
|
|
|
8512
|
+
--fix loop iterates: attempt 1 is the one-shot prompt; when an arm fails
|
|
8513
|
+
(nonzero exit or the failure signature persists) or the model call fails,
|
|
8514
|
+
the next prompt carries every prior command with its REAL executed
|
|
8515
|
+
stdout/stderr, up to --fix-attempts attempts (default 3). Retries may
|
|
8516
|
+
answer with a short script (<=5 commands) executed as one /bin/sh unit.
|
|
8517
|
+
Every attempt runs in its own fresh sandbox with the same replayed prefix.
|
|
8518
|
+
|
|
8172
8519
|
Outputs batch-report.json, batch-report.md, cases.jsonl (incremental), and one
|
|
8173
8520
|
directory per case with the full replay-verify artifacts.
|
|
8174
8521
|
See docs/replay-verify.md for orchestrator setup and honest limits.
|
|
@@ -8213,7 +8560,7 @@ async function cmdReplayVerifyBatch(argv) {
|
|
|
8213
8560
|
);
|
|
8214
8561
|
}
|
|
8215
8562
|
let fixCaller;
|
|
8216
|
-
if (parsed.fix
|
|
8563
|
+
if (parsed.fix !== "none") {
|
|
8217
8564
|
const fixApiKey = process.env[parsed.fixApiKeyEnv];
|
|
8218
8565
|
if (!fixApiKey) {
|
|
8219
8566
|
throw new Error(
|
|
@@ -8232,6 +8579,7 @@ async function cmdReplayVerifyBatch(argv) {
|
|
|
8232
8579
|
baseUrl: parsed.baseUrl,
|
|
8233
8580
|
apiKey,
|
|
8234
8581
|
fix: parsed.fix,
|
|
8582
|
+
fixAttempts: parsed.fixAttempts,
|
|
8235
8583
|
fixCaller,
|
|
8236
8584
|
fixModelLabel: parsed.fixModel,
|
|
8237
8585
|
maxFixCases: parsed.maxFixCases,
|
|
@@ -8251,6 +8599,573 @@ async function cmdReplayVerifyBatch(argv) {
|
|
|
8251
8599
|
);
|
|
8252
8600
|
}
|
|
8253
8601
|
|
|
8602
|
+
// src/replay-wire.ts
|
|
8603
|
+
var SUBJECT_PATTERN = /^incorrect-steps-(\d+)-(\d+)-(escaped|unescaped)-consequence-(\d+)$/;
|
|
8604
|
+
function parseIncorrectStepsSubject(subject) {
|
|
8605
|
+
const match = SUBJECT_PATTERN.exec(subject);
|
|
8606
|
+
if (!match) return null;
|
|
8607
|
+
return {
|
|
8608
|
+
firstStep: Number(match[1]),
|
|
8609
|
+
lastStep: Number(match[2]),
|
|
8610
|
+
escapeStatus: match[3],
|
|
8611
|
+
consequenceStep: Number(match[4])
|
|
8612
|
+
};
|
|
8613
|
+
}
|
|
8614
|
+
function resolveFindingInvocation(finding2, corpora) {
|
|
8615
|
+
const subject = parseIncorrectStepsSubject(finding2.subject);
|
|
8616
|
+
if (!subject) {
|
|
8617
|
+
throw new Error(
|
|
8618
|
+
`replay-wire: subject '${finding2.subject}' is not incorrect-steps-<first>-<last>-<escaped|unescaped>-consequence-<step>`
|
|
8619
|
+
);
|
|
8620
|
+
}
|
|
8621
|
+
const failures = [];
|
|
8622
|
+
for (const corpus of corpora) {
|
|
8623
|
+
const resolution = resolveCaseResources(corpus, finding2.trajId);
|
|
8624
|
+
if (resolution.resolved) {
|
|
8625
|
+
const at = subject.firstStep;
|
|
8626
|
+
const step = resolution.resources.steps.find((s) => s.step_id === at);
|
|
8627
|
+
if (!step) {
|
|
8628
|
+
throw new Error(
|
|
8629
|
+
`replay-wire: finding step ${at} is outside ${finding2.trajId} (${resolution.resources.steps.length} steps)`
|
|
8630
|
+
);
|
|
8631
|
+
}
|
|
8632
|
+
return { resources: resolution.resources, subject, at };
|
|
8633
|
+
}
|
|
8634
|
+
failures.push(`${corpus.name}: ${resolution.reason}`);
|
|
8635
|
+
}
|
|
8636
|
+
throw new Error(
|
|
8637
|
+
`replay-wire: trajectory ${finding2.trajId} is not replayable in any corpus \u2014 ${failures.join("; ")}`
|
|
8638
|
+
);
|
|
8639
|
+
}
|
|
8640
|
+
async function replayVerifyFinding(finding2, options) {
|
|
8641
|
+
if (options.fixCaller && options.fixCommand !== void 0) {
|
|
8642
|
+
throw new Error("replay-wire: pass fixCaller or fixCommand, not both");
|
|
8643
|
+
}
|
|
8644
|
+
const invocation = resolveFindingInvocation(finding2, options.corpora);
|
|
8645
|
+
let fixCommand = options.fixCommand ?? null;
|
|
8646
|
+
if (options.fixCaller) {
|
|
8647
|
+
const generated = await generateFixCommand(options.fixCaller, {
|
|
8648
|
+
taskStatement: invocation.resources.taskStatement,
|
|
8649
|
+
steps: invocation.resources.steps,
|
|
8650
|
+
k: invocation.at
|
|
8651
|
+
});
|
|
8652
|
+
if (!generated.succeeded) {
|
|
8653
|
+
throw new Error(`replay-wire: fix generation failed \u2014 ${generated.error}`);
|
|
8654
|
+
}
|
|
8655
|
+
fixCommand = generated.value.command;
|
|
8656
|
+
}
|
|
8657
|
+
const verdict = await replayVerify({
|
|
8658
|
+
stepsPath: invocation.resources.stepsPath,
|
|
8659
|
+
image: invocation.resources.image,
|
|
8660
|
+
at: invocation.at,
|
|
8661
|
+
fixCommand: fixCommand ?? void 0,
|
|
8662
|
+
cwd: invocation.resources.cwd,
|
|
8663
|
+
out: options.out,
|
|
8664
|
+
caseId: invocation.resources.trajId,
|
|
8665
|
+
stepTimeoutMs: options.stepTimeoutMs ?? invocation.resources.recordedStepTimeoutMs ?? void 0,
|
|
8666
|
+
prefixLimit: options.prefixLimit,
|
|
8667
|
+
backend: options.backend,
|
|
8668
|
+
apiKey: options.apiKey,
|
|
8669
|
+
baseUrl: options.baseUrl,
|
|
8670
|
+
onProgress: options.onProgress
|
|
8671
|
+
});
|
|
8672
|
+
return { invocation, fixCommand, verdict };
|
|
8673
|
+
}
|
|
8674
|
+
|
|
8675
|
+
// src/analyze-verify.ts
|
|
8676
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
8677
|
+
import { join as join22 } from "path";
|
|
8678
|
+
var SINGLE_STEP_SUBJECT = /^incorrect-step-(\d+)$/;
|
|
8679
|
+
function findingReplayStep(finding2) {
|
|
8680
|
+
const fromMetadata = finding2.metadata?.block_first_step;
|
|
8681
|
+
if (typeof fromMetadata === "number" && Number.isInteger(fromMetadata) && fromMetadata >= 1) {
|
|
8682
|
+
return fromMetadata;
|
|
8683
|
+
}
|
|
8684
|
+
const subject = finding2.subject ?? "";
|
|
8685
|
+
const wire = parseIncorrectStepsSubject(subject);
|
|
8686
|
+
if (wire) return wire.firstStep;
|
|
8687
|
+
const single = SINGLE_STEP_SUBJECT.exec(subject);
|
|
8688
|
+
if (single) return Number(single[1]);
|
|
8689
|
+
return null;
|
|
8690
|
+
}
|
|
8691
|
+
var TRACE_EVIDENCE_URI = /^trace:\/\/([^/]+)\//;
|
|
8692
|
+
function findingTrajectoryId(finding2) {
|
|
8693
|
+
for (const ref of finding2.evidence_refs ?? []) {
|
|
8694
|
+
if (typeof ref.uri !== "string") continue;
|
|
8695
|
+
const match = TRACE_EVIDENCE_URI.exec(ref.uri);
|
|
8696
|
+
if (match) return match[1];
|
|
8697
|
+
}
|
|
8698
|
+
return null;
|
|
8699
|
+
}
|
|
8700
|
+
function checkStep(steps, at) {
|
|
8701
|
+
const step = steps.find((s) => s.step_id === at);
|
|
8702
|
+
if (!step) {
|
|
8703
|
+
return { ok: false, reason: `step ${at} is outside the trajectory (${steps.length} steps)` };
|
|
8704
|
+
}
|
|
8705
|
+
if (isSubmitAction(step.action)) {
|
|
8706
|
+
return {
|
|
8707
|
+
ok: false,
|
|
8708
|
+
reason: `step ${at} is the submit action \u2014 a submit decision has no executable failure to replay`
|
|
8709
|
+
};
|
|
8710
|
+
}
|
|
8711
|
+
const recordedReturncode = parseRecordedReturncode(step.observation);
|
|
8712
|
+
if (recordedReturncode === null) {
|
|
8713
|
+
return {
|
|
8714
|
+
ok: false,
|
|
8715
|
+
reason: `step ${at} recorded no returncode \u2014 there is no executable failure signature to reproduce`
|
|
8716
|
+
};
|
|
8717
|
+
}
|
|
8718
|
+
return { ok: true, recordedReturncode };
|
|
8719
|
+
}
|
|
8720
|
+
function resolveFindingReplayability(finding2, source) {
|
|
8721
|
+
const at = findingReplayStep(finding2);
|
|
8722
|
+
if (at === null) {
|
|
8723
|
+
return {
|
|
8724
|
+
replayable: false,
|
|
8725
|
+
reason: `subject '${finding2.subject ?? "(none)"}' names no trajectory step (expected incorrect-step-<n>, incorrect-steps-<f>-<l>-\u2026, or metadata.block_first_step)`
|
|
8726
|
+
};
|
|
8727
|
+
}
|
|
8728
|
+
if (source.kind === "direct") {
|
|
8729
|
+
const trajId2 = findingTrajectoryId(finding2);
|
|
8730
|
+
if (trajId2 && source.caseId && trajId2 !== source.caseId) {
|
|
8731
|
+
return {
|
|
8732
|
+
replayable: false,
|
|
8733
|
+
reason: `finding cites trajectory '${trajId2}' but the supplied steps are case '${source.caseId}'`
|
|
8734
|
+
};
|
|
8735
|
+
}
|
|
8736
|
+
let steps;
|
|
8737
|
+
try {
|
|
8738
|
+
steps = JSON.parse(readFileSync4(source.stepsPath, "utf8"));
|
|
8739
|
+
} catch (err) {
|
|
8740
|
+
throw new Error(
|
|
8741
|
+
`verify-findings: cannot read steps file ${source.stepsPath} \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
8742
|
+
);
|
|
8743
|
+
}
|
|
8744
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
8745
|
+
throw new Error(`verify-findings: ${source.stepsPath} is not a non-empty steps array`);
|
|
8746
|
+
}
|
|
8747
|
+
const step = checkStep(steps, at);
|
|
8748
|
+
if (!step.ok) return { replayable: false, reason: step.reason };
|
|
8749
|
+
return {
|
|
8750
|
+
replayable: true,
|
|
8751
|
+
resolved: {
|
|
8752
|
+
caseId: source.caseId ?? trajId2 ?? source.stepsPath,
|
|
8753
|
+
stepsPath: source.stepsPath,
|
|
8754
|
+
image: source.image,
|
|
8755
|
+
cwd: source.cwd,
|
|
8756
|
+
at,
|
|
8757
|
+
recordedReturncode: step.recordedReturncode,
|
|
8758
|
+
recordedStepTimeoutMs: null
|
|
8759
|
+
}
|
|
8760
|
+
};
|
|
8761
|
+
}
|
|
8762
|
+
const trajId = findingTrajectoryId(finding2);
|
|
8763
|
+
if (!trajId) {
|
|
8764
|
+
return {
|
|
8765
|
+
replayable: false,
|
|
8766
|
+
reason: "finding carries no trace://<trajectory>/ evidence ref naming its trajectory"
|
|
8767
|
+
};
|
|
8768
|
+
}
|
|
8769
|
+
const failures = [];
|
|
8770
|
+
for (const corpus of source.corpora) {
|
|
8771
|
+
const resolution = resolveCaseResources(corpus, trajId);
|
|
8772
|
+
if (!resolution.resolved) {
|
|
8773
|
+
failures.push(`${corpus.name}: ${resolution.reason}${resolution.detail ? ` (${resolution.detail})` : ""}`);
|
|
8774
|
+
continue;
|
|
8775
|
+
}
|
|
8776
|
+
const step = checkStep(resolution.resources.steps, at);
|
|
8777
|
+
if (!step.ok) return { replayable: false, reason: step.reason };
|
|
8778
|
+
return {
|
|
8779
|
+
replayable: true,
|
|
8780
|
+
resolved: {
|
|
8781
|
+
caseId: trajId,
|
|
8782
|
+
stepsPath: resolution.resources.stepsPath,
|
|
8783
|
+
image: resolution.resources.image,
|
|
8784
|
+
cwd: resolution.resources.cwd,
|
|
8785
|
+
at,
|
|
8786
|
+
recordedReturncode: step.recordedReturncode,
|
|
8787
|
+
recordedStepTimeoutMs: resolution.resources.recordedStepTimeoutMs
|
|
8788
|
+
}
|
|
8789
|
+
};
|
|
8790
|
+
}
|
|
8791
|
+
return {
|
|
8792
|
+
replayable: false,
|
|
8793
|
+
reason: `trajectory ${trajId} is not replayable in any corpus \u2014 ${failures.join("; ")}`
|
|
8794
|
+
};
|
|
8795
|
+
}
|
|
8796
|
+
function classifyVerdict(verdict) {
|
|
8797
|
+
if (!verdict.armA.failureSignatureMatch) return "divergent";
|
|
8798
|
+
if (verdict.armB?.failureVanished) return "fix-flipped";
|
|
8799
|
+
return "reproduced";
|
|
8800
|
+
}
|
|
8801
|
+
var DEFAULT_SANDBOX_BASE_URL = "http://127.0.0.1:4097";
|
|
8802
|
+
async function assertSandboxReachable(baseUrl) {
|
|
8803
|
+
let response;
|
|
8804
|
+
try {
|
|
8805
|
+
response = await fetch(new URL("/health", baseUrl), { signal: AbortSignal.timeout(5e3) });
|
|
8806
|
+
} catch (err) {
|
|
8807
|
+
throw new Error(
|
|
8808
|
+
`verify-findings: sandbox API unreachable at ${baseUrl} \u2014 ${err instanceof Error ? err.message : String(err)}. Executing findings requires a running sandbox orchestrator + SDK adapter (see docs/replay-verify.md); start them or pass --base-url.`
|
|
8809
|
+
);
|
|
8810
|
+
}
|
|
8811
|
+
if (!response.ok) {
|
|
8812
|
+
throw new Error(
|
|
8813
|
+
`verify-findings: sandbox API at ${baseUrl} answered /health with HTTP ${response.status} \u2014 refusing to run proofs against degraded infrastructure`
|
|
8814
|
+
);
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
function receiptExecution(verdict) {
|
|
8818
|
+
return {
|
|
8819
|
+
image: verdict.image,
|
|
8820
|
+
cwd: verdict.cwd,
|
|
8821
|
+
recordedReturncode: verdict.recordedReturncode,
|
|
8822
|
+
signature: verdict.signature,
|
|
8823
|
+
signatureBasis: verdict.signatureBasis,
|
|
8824
|
+
armA: {
|
|
8825
|
+
command: verdict.armA.command,
|
|
8826
|
+
exitCode: verdict.armA.exitCode,
|
|
8827
|
+
wallMs: verdict.armA.wallMs,
|
|
8828
|
+
failureSignatureMatch: verdict.armA.failureSignatureMatch
|
|
8829
|
+
},
|
|
8830
|
+
armB: verdict.armB ? {
|
|
8831
|
+
command: verdict.armB.command,
|
|
8832
|
+
exitCode: verdict.armB.exitCode,
|
|
8833
|
+
wallMs: verdict.armB.wallMs,
|
|
8834
|
+
failureVanished: verdict.armB.failureVanished
|
|
8835
|
+
} : null,
|
|
8836
|
+
prefixExecuted: verdict.prefixExecuted,
|
|
8837
|
+
prefixDivergences: verdict.prefixDivergences.length,
|
|
8838
|
+
totalMs: verdict.timings.totalMs
|
|
8839
|
+
};
|
|
8840
|
+
}
|
|
8841
|
+
function writeReceipt(receiptDir, finding2, verification, execution) {
|
|
8842
|
+
const receipt = {
|
|
8843
|
+
schema_version: "1.0.0",
|
|
8844
|
+
produced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8845
|
+
finding_id: verification.finding_id,
|
|
8846
|
+
analyst_id: finding2.analyst_id ?? null,
|
|
8847
|
+
subject: verification.subject,
|
|
8848
|
+
claim: typeof finding2.claim === "string" ? finding2.claim.slice(0, 600) : null,
|
|
8849
|
+
trajectory_id: verification.trajectory_id,
|
|
8850
|
+
step: verification.step,
|
|
8851
|
+
verified: verification.verified,
|
|
8852
|
+
reason: verification.reason,
|
|
8853
|
+
execution,
|
|
8854
|
+
verdict_path: verification.verdict_path,
|
|
8855
|
+
deduplicated_with: verification.deduplicated_with
|
|
8856
|
+
};
|
|
8857
|
+
writeFileSync3(join22(receiptDir, "receipt.json"), `${JSON.stringify(receipt, null, 2)}
|
|
8858
|
+
`);
|
|
8859
|
+
}
|
|
8860
|
+
function receiptDirName(index, finding2) {
|
|
8861
|
+
const id = typeof finding2.finding_id === "string" && finding2.finding_id.length > 0 ? finding2.finding_id.replace(/[^A-Za-z0-9_-]/g, "_") : "finding";
|
|
8862
|
+
return `${String(index + 1).padStart(3, "0")}-${id}`;
|
|
8863
|
+
}
|
|
8864
|
+
async function verifyFindings(findings, options) {
|
|
8865
|
+
if (findings.length === 0) throw new Error("verify-findings: no findings to verify");
|
|
8866
|
+
mkdirSync3(options.out, { recursive: true });
|
|
8867
|
+
const resolutions = findings.map((finding2) => resolveFindingReplayability(finding2, options.source));
|
|
8868
|
+
const baseUrl = options.baseUrl ?? DEFAULT_SANDBOX_BASE_URL;
|
|
8869
|
+
if (resolutions.some((resolution) => resolution.replayable) && !options.backend) {
|
|
8870
|
+
if (!options.apiKey) {
|
|
8871
|
+
throw new Error(
|
|
8872
|
+
"verify-findings: replayable findings need a sandbox API key \u2014 export SANDBOX_API_KEY or pass --api-key-env"
|
|
8873
|
+
);
|
|
8874
|
+
}
|
|
8875
|
+
await assertSandboxReachable(baseUrl);
|
|
8876
|
+
}
|
|
8877
|
+
const preparer = options.source.kind === "corpus" && !options.backend ? options.source.preparer ?? dockerImagePreparer() : null;
|
|
8878
|
+
const preparedImages = /* @__PURE__ */ new Map();
|
|
8879
|
+
const executedByKey = /* @__PURE__ */ new Map();
|
|
8880
|
+
const verifications = [];
|
|
8881
|
+
let executions = 0;
|
|
8882
|
+
for (let index = 0; index < findings.length; index++) {
|
|
8883
|
+
const finding2 = findings[index];
|
|
8884
|
+
const resolution = resolutions[index];
|
|
8885
|
+
const receiptDir = join22(options.out, receiptDirName(index, finding2));
|
|
8886
|
+
mkdirSync3(receiptDir, { recursive: true });
|
|
8887
|
+
const identity = {
|
|
8888
|
+
finding_id: finding2.finding_id ?? null,
|
|
8889
|
+
subject: finding2.subject ?? null,
|
|
8890
|
+
trajectory_id: findingTrajectoryId(finding2)
|
|
8891
|
+
};
|
|
8892
|
+
if (!resolution.replayable) {
|
|
8893
|
+
const verification2 = {
|
|
8894
|
+
...identity,
|
|
8895
|
+
step: findingReplayStep(finding2),
|
|
8896
|
+
verified: "not-replayable",
|
|
8897
|
+
reason: resolution.reason,
|
|
8898
|
+
receipt: receiptDir,
|
|
8899
|
+
verdict_path: null,
|
|
8900
|
+
deduplicated_with: null
|
|
8901
|
+
};
|
|
8902
|
+
writeReceipt(receiptDir, finding2, verification2, null);
|
|
8903
|
+
verifications.push(verification2);
|
|
8904
|
+
options.onProgress?.(`${identity.finding_id ?? `finding ${index + 1}`}: not-replayable \u2014 ${resolution.reason}`);
|
|
8905
|
+
continue;
|
|
8906
|
+
}
|
|
8907
|
+
const resolved = resolution.resolved;
|
|
8908
|
+
const dedupeKey = `${resolved.caseId}::${resolved.at}::${options.fixCommand ?? ""}`;
|
|
8909
|
+
const prior = executedByKey.get(dedupeKey);
|
|
8910
|
+
if (prior) {
|
|
8911
|
+
const verification2 = {
|
|
8912
|
+
...identity,
|
|
8913
|
+
step: resolved.at,
|
|
8914
|
+
verified: prior.verified,
|
|
8915
|
+
reason: prior.reason,
|
|
8916
|
+
receipt: receiptDir,
|
|
8917
|
+
verdict_path: prior.verdict_path,
|
|
8918
|
+
deduplicated_with: prior.receipt
|
|
8919
|
+
};
|
|
8920
|
+
const priorVerdict = prior.verdict_path ? JSON.parse(readFileSync4(prior.verdict_path, "utf8")) : null;
|
|
8921
|
+
writeReceipt(receiptDir, finding2, verification2, priorVerdict ? receiptExecution(priorVerdict) : null);
|
|
8922
|
+
verifications.push(verification2);
|
|
8923
|
+
options.onProgress?.(
|
|
8924
|
+
`${identity.finding_id ?? `finding ${index + 1}`}: ${prior.verified} (shares proof with ${prior.finding_id ?? prior.receipt})`
|
|
8925
|
+
);
|
|
8926
|
+
continue;
|
|
8927
|
+
}
|
|
8928
|
+
let image = resolved.image;
|
|
8929
|
+
if (preparer) {
|
|
8930
|
+
const preparationKey = `${resolved.image}::${resolved.cwd}`;
|
|
8931
|
+
let preparation = preparedImages.get(preparationKey);
|
|
8932
|
+
if (!preparation) {
|
|
8933
|
+
const ensured = await preparer.ensure(resolved.image, resolved.cwd);
|
|
8934
|
+
preparation = ensured.succeeded ? { succeeded: true, image: ensured.value.derivedImage } : { succeeded: false, error: ensured.error };
|
|
8935
|
+
preparedImages.set(preparationKey, preparation);
|
|
8936
|
+
}
|
|
8937
|
+
if (!preparation.succeeded) {
|
|
8938
|
+
const verification2 = {
|
|
8939
|
+
...identity,
|
|
8940
|
+
step: resolved.at,
|
|
8941
|
+
verified: "not-replayable",
|
|
8942
|
+
reason: `replay image could not be prepared \u2014 ${preparation.error}`,
|
|
8943
|
+
receipt: receiptDir,
|
|
8944
|
+
verdict_path: null,
|
|
8945
|
+
deduplicated_with: null
|
|
8946
|
+
};
|
|
8947
|
+
writeReceipt(receiptDir, finding2, verification2, null);
|
|
8948
|
+
verifications.push(verification2);
|
|
8949
|
+
options.onProgress?.(`${identity.finding_id ?? `finding ${index + 1}`}: not-replayable \u2014 image preparation failed`);
|
|
8950
|
+
continue;
|
|
8951
|
+
}
|
|
8952
|
+
image = preparation.image;
|
|
8953
|
+
}
|
|
8954
|
+
options.onProgress?.(
|
|
8955
|
+
`${identity.finding_id ?? `finding ${index + 1}`}: executing arm A at step ${resolved.at} of ${resolved.caseId} on ${image}`
|
|
8956
|
+
);
|
|
8957
|
+
const verdict = await replayVerify({
|
|
8958
|
+
stepsPath: resolved.stepsPath,
|
|
8959
|
+
image,
|
|
8960
|
+
at: resolved.at,
|
|
8961
|
+
fixCommand: options.fixCommand,
|
|
8962
|
+
cwd: resolved.cwd,
|
|
8963
|
+
out: receiptDir,
|
|
8964
|
+
caseId: resolved.caseId,
|
|
8965
|
+
stepTimeoutMs: options.stepTimeoutMs ?? resolved.recordedStepTimeoutMs ?? void 0,
|
|
8966
|
+
prefixLimit: options.prefixLimit,
|
|
8967
|
+
backend: options.backend,
|
|
8968
|
+
apiKey: options.apiKey,
|
|
8969
|
+
baseUrl,
|
|
8970
|
+
maxLifetimeSeconds: options.maxLifetimeSeconds,
|
|
8971
|
+
onProgress: options.onProgress
|
|
8972
|
+
});
|
|
8973
|
+
executions += 1;
|
|
8974
|
+
const verification = {
|
|
8975
|
+
...identity,
|
|
8976
|
+
step: resolved.at,
|
|
8977
|
+
verified: classifyVerdict(verdict),
|
|
8978
|
+
reason: null,
|
|
8979
|
+
receipt: receiptDir,
|
|
8980
|
+
verdict_path: join22(receiptDir, "replay-verdict.json"),
|
|
8981
|
+
deduplicated_with: null
|
|
8982
|
+
};
|
|
8983
|
+
executedByKey.set(dedupeKey, verification);
|
|
8984
|
+
writeReceipt(receiptDir, finding2, verification, receiptExecution(verdict));
|
|
8985
|
+
verifications.push(verification);
|
|
8986
|
+
options.onProgress?.(`${identity.finding_id ?? `finding ${index + 1}`}: ${verification.verified}`);
|
|
8987
|
+
}
|
|
8988
|
+
const counts = {
|
|
8989
|
+
reproduced: 0,
|
|
8990
|
+
"fix-flipped": 0,
|
|
8991
|
+
divergent: 0,
|
|
8992
|
+
"not-replayable": 0
|
|
8993
|
+
};
|
|
8994
|
+
for (const verification of verifications) counts[verification.verified] += 1;
|
|
8995
|
+
const run = { out: options.out, verifications, counts, executions };
|
|
8996
|
+
writeFileSync3(
|
|
8997
|
+
join22(options.out, "verifications.json"),
|
|
8998
|
+
`${JSON.stringify({ schema_version: "1.0.0", ...run }, null, 2)}
|
|
8999
|
+
`
|
|
9000
|
+
);
|
|
9001
|
+
return run;
|
|
9002
|
+
}
|
|
9003
|
+
function verdictCell(verification) {
|
|
9004
|
+
switch (verification.verified) {
|
|
9005
|
+
case "reproduced":
|
|
9006
|
+
return "**VERIFIED** \u2014 reproduced";
|
|
9007
|
+
case "fix-flipped":
|
|
9008
|
+
return "**VERIFIED** \u2014 fix-flipped";
|
|
9009
|
+
case "divergent":
|
|
9010
|
+
return "DIVERGENT \u2014 recorded failure did not reproduce";
|
|
9011
|
+
case "not-replayable":
|
|
9012
|
+
return `UNVERIFIABLE \u2014 ${verification.reason ?? "no reason recorded"}`;
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
9015
|
+
function renderVerifiedFindingsSection(run) {
|
|
9016
|
+
const lines = ["## Verified findings (executed replay)", ""];
|
|
9017
|
+
const total = run.verifications.length;
|
|
9018
|
+
lines.push(
|
|
9019
|
+
`${total} finding(s) \u2192 ${run.counts.reproduced} reproduced, ${run.counts["fix-flipped"]} fix-flipped, ${run.counts.divergent} divergent, ${run.counts["not-replayable"]} not replayable (${run.executions} sandbox execution(s); findings accusing the same step share one proof).`
|
|
9020
|
+
);
|
|
9021
|
+
lines.push("");
|
|
9022
|
+
lines.push("| Finding | Subject | Step | Verdict | Receipt |");
|
|
9023
|
+
lines.push("|---|---|---:|---|---|");
|
|
9024
|
+
for (const verification of run.verifications) {
|
|
9025
|
+
const shared = verification.deduplicated_with ? " (shared proof)" : "";
|
|
9026
|
+
lines.push(
|
|
9027
|
+
`| \`${verification.finding_id ?? "\u2014"}\` | \`${verification.subject ?? "\u2014"}\` | ${verification.step ?? "\u2014"} | ${verdictCell(verification)} | \`${verification.receipt}\`${shared} |`
|
|
9028
|
+
);
|
|
9029
|
+
}
|
|
9030
|
+
lines.push("");
|
|
9031
|
+
lines.push(
|
|
9032
|
+
"VERIFIED = the accused step was re-executed in a sandbox after replaying the trajectory prefix, and the recorded failure signature reproduced (fix-flipped: a corrected command additionally made it vanish). Each receipt directory carries receipt.json and, when executed, replay-verdict.json + report.md with real stdout/stderr."
|
|
9033
|
+
);
|
|
9034
|
+
lines.push("");
|
|
9035
|
+
return lines.join("\n");
|
|
9036
|
+
}
|
|
9037
|
+
function readFindingsFile(path) {
|
|
9038
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
9039
|
+
const array = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.findings) ? parsed.findings : null;
|
|
9040
|
+
if (!array) {
|
|
9041
|
+
throw new Error(`${path} is neither a findings array nor an object with a findings array`);
|
|
9042
|
+
}
|
|
9043
|
+
for (const entry of array) {
|
|
9044
|
+
if (!entry || typeof entry !== "object") {
|
|
9045
|
+
throw new Error(`${path}: every finding must be an object, got ${JSON.stringify(entry)}`);
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
return array;
|
|
9049
|
+
}
|
|
9050
|
+
function verifyFindingsUsage() {
|
|
9051
|
+
return `traces verify-findings \u2014 execute analyst findings as sandbox replay proofs
|
|
9052
|
+
|
|
9053
|
+
Usage:
|
|
9054
|
+
traces verify-findings --findings FINDINGS.json --out DIR \\
|
|
9055
|
+
( --steps STEPS.json --image IMG --cwd DIR [--case ID]
|
|
9056
|
+
| --corpus name=<labels.json>::<preparedDir> [--corpus ...] ) \\
|
|
9057
|
+
[--fix-command CMD] [--step-timeout MS] [--prefix-limit N] \\
|
|
9058
|
+
[--base-url URL] [--api-key-env VAR] [--max-lifetime SECONDS]
|
|
9059
|
+
|
|
9060
|
+
--findings JSON array of analyst findings (or an object with a findings
|
|
9061
|
+
array) \u2014 the shape agent-eval analysts emit: subject
|
|
9062
|
+
incorrect-step-<n>, metadata.block_first_step, trace:// evidence
|
|
9063
|
+
--out receipt root; one directory per finding + verifications.json
|
|
9064
|
+
--steps/--image/--cwd
|
|
9065
|
+
verify against one trajectory; the image must be replay-ready
|
|
9066
|
+
(uid-1000 derived), exactly like traces replay-verify
|
|
9067
|
+
--case trajectory id of --steps; findings citing other trajectories
|
|
9068
|
+
are honestly marked not-replayable
|
|
9069
|
+
--corpus resolve each finding's trajectory in CodeTraceBench corpora;
|
|
9070
|
+
images are derived via the batch uid-1000 preparer (docker)
|
|
9071
|
+
--fix-command corrected step for arm B; a reproduced finding whose fix
|
|
9072
|
+
flips becomes fix-flipped
|
|
9073
|
+
--base-url sandbox API url (default $SANDBOX_API_URL or ${DEFAULT_SANDBOX_BASE_URL})
|
|
9074
|
+
--api-key-env env var holding the sandbox API key (default SANDBOX_API_KEY)
|
|
9075
|
+
|
|
9076
|
+
Every finding gets a verdict: reproduced | fix-flipped | divergent | not-replayable,
|
|
9077
|
+
with a receipt directory carrying the executed evidence or the precise reason.
|
|
9078
|
+
Requires a running sandbox orchestrator when any finding is replayable \u2014 infra
|
|
9079
|
+
absence is an error, never a silent skip. See docs/replay-verify.md for setup.
|
|
9080
|
+
`;
|
|
9081
|
+
}
|
|
9082
|
+
function parseVerifyFindingsArgs(argv) {
|
|
9083
|
+
const values = /* @__PURE__ */ new Map();
|
|
9084
|
+
const corpora = [];
|
|
9085
|
+
for (let i = 0; i < argv.length; i++) {
|
|
9086
|
+
const arg = argv[i];
|
|
9087
|
+
if (arg === "--help" || arg === "-h") return "help";
|
|
9088
|
+
if (!arg.startsWith("--")) throw new Error(`unexpected positional argument: ${arg}`);
|
|
9089
|
+
const value = argv[++i];
|
|
9090
|
+
if (value === void 0) throw new Error(`missing value for ${arg}`);
|
|
9091
|
+
if (arg === "--corpus") {
|
|
9092
|
+
corpora.push(parseCorpusFlag(value));
|
|
9093
|
+
continue;
|
|
9094
|
+
}
|
|
9095
|
+
values.set(arg, value);
|
|
9096
|
+
}
|
|
9097
|
+
const required = (flag) => {
|
|
9098
|
+
const v = values.get(flag);
|
|
9099
|
+
if (v === void 0) throw new Error(`verify-findings: ${flag} is required`);
|
|
9100
|
+
return v;
|
|
9101
|
+
};
|
|
9102
|
+
const optionalNumber = (flag) => {
|
|
9103
|
+
const v = values.get(flag);
|
|
9104
|
+
if (v === void 0) return void 0;
|
|
9105
|
+
const n = Number(v);
|
|
9106
|
+
if (!Number.isFinite(n)) throw new Error(`verify-findings: ${flag} must be a number, got ${v}`);
|
|
9107
|
+
return n;
|
|
9108
|
+
};
|
|
9109
|
+
const findingsPath = required("--findings");
|
|
9110
|
+
const out = required("--out");
|
|
9111
|
+
const direct = values.has("--steps") || values.has("--image") || values.has("--cwd");
|
|
9112
|
+
if (direct && corpora.length > 0) {
|
|
9113
|
+
throw new Error("verify-findings: pass --steps/--image/--cwd or --corpus, not both");
|
|
9114
|
+
}
|
|
9115
|
+
let source;
|
|
9116
|
+
if (direct) {
|
|
9117
|
+
source = {
|
|
9118
|
+
kind: "direct",
|
|
9119
|
+
stepsPath: required("--steps"),
|
|
9120
|
+
image: required("--image"),
|
|
9121
|
+
cwd: required("--cwd"),
|
|
9122
|
+
...values.has("--case") ? { caseId: values.get("--case") } : {}
|
|
9123
|
+
};
|
|
9124
|
+
} else if (corpora.length > 0) {
|
|
9125
|
+
source = { kind: "corpus", corpora };
|
|
9126
|
+
} else {
|
|
9127
|
+
throw new Error("verify-findings: a replay source is required \u2014 --steps/--image/--cwd or --corpus");
|
|
9128
|
+
}
|
|
9129
|
+
return {
|
|
9130
|
+
findingsPath,
|
|
9131
|
+
source,
|
|
9132
|
+
out,
|
|
9133
|
+
fixCommand: values.get("--fix-command"),
|
|
9134
|
+
stepTimeoutMs: optionalNumber("--step-timeout"),
|
|
9135
|
+
prefixLimit: optionalNumber("--prefix-limit"),
|
|
9136
|
+
baseUrl: values.get("--base-url") ?? process.env.SANDBOX_API_URL ?? DEFAULT_SANDBOX_BASE_URL,
|
|
9137
|
+
apiKeyEnv: values.get("--api-key-env") ?? "SANDBOX_API_KEY",
|
|
9138
|
+
maxLifetimeSeconds: optionalNumber("--max-lifetime")
|
|
9139
|
+
};
|
|
9140
|
+
}
|
|
9141
|
+
async function cmdVerifyFindings(argv) {
|
|
9142
|
+
const parsed = parseVerifyFindingsArgs(argv);
|
|
9143
|
+
if (parsed === "help") {
|
|
9144
|
+
process.stdout.write(verifyFindingsUsage());
|
|
9145
|
+
return;
|
|
9146
|
+
}
|
|
9147
|
+
const findings = readFindingsFile(parsed.findingsPath);
|
|
9148
|
+
const apiKey = process.env[parsed.apiKeyEnv];
|
|
9149
|
+
const run = await verifyFindings(findings, {
|
|
9150
|
+
source: parsed.source,
|
|
9151
|
+
out: parsed.out,
|
|
9152
|
+
fixCommand: parsed.fixCommand,
|
|
9153
|
+
stepTimeoutMs: parsed.stepTimeoutMs,
|
|
9154
|
+
prefixLimit: parsed.prefixLimit,
|
|
9155
|
+
apiKey,
|
|
9156
|
+
baseUrl: parsed.baseUrl,
|
|
9157
|
+
maxLifetimeSeconds: parsed.maxLifetimeSeconds,
|
|
9158
|
+
onProgress: (message) => process.stderr.write(`${message}
|
|
9159
|
+
`)
|
|
9160
|
+
});
|
|
9161
|
+
process.stdout.write(`${JSON.stringify(run, null, 2)}
|
|
9162
|
+
`);
|
|
9163
|
+
process.stderr.write(
|
|
9164
|
+
`verify-findings: ${run.verifications.length} finding(s) \u2192 ${run.counts.reproduced} reproduced, ${run.counts["fix-flipped"]} fix-flipped, ${run.counts.divergent} divergent, ${run.counts["not-replayable"]} not-replayable (${run.executions} execution(s)) \u2192 ${run.out}
|
|
9165
|
+
`
|
|
9166
|
+
);
|
|
9167
|
+
}
|
|
9168
|
+
|
|
8254
9169
|
// src/external.ts
|
|
8255
9170
|
import { spawn } from "child_process";
|
|
8256
9171
|
import { readdir as readdir14, readFile as readFile7 } from "fs/promises";
|
|
@@ -9015,18 +9930,18 @@ function commandRedactor(spec) {
|
|
|
9015
9930
|
import { createHash as createHash9 } from "crypto";
|
|
9016
9931
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, readFile as readFile8, readdir as readdir15, writeFile as writeFile4 } from "fs/promises";
|
|
9017
9932
|
import { endianness, tmpdir as tmpdir4 } from "os";
|
|
9018
|
-
import { join as
|
|
9933
|
+
import { join as join23, resolve as resolve6 } from "path";
|
|
9019
9934
|
var HODOSCOPE_VERSION = "0.2.4";
|
|
9020
9935
|
async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
9021
9936
|
const explicitDirectory = options.directory ? resolve6(options.directory) : void 0;
|
|
9022
|
-
const directory = explicitDirectory ?? await mkdtemp3(
|
|
9937
|
+
const directory = explicitDirectory ?? await mkdtemp3(join23(tmpdir4(), "traces-hodoscope-input-"));
|
|
9023
9938
|
if (explicitDirectory) {
|
|
9024
9939
|
await mkdir3(directory, { recursive: true });
|
|
9025
9940
|
if ((await readdir15(directory)).length > 0) {
|
|
9026
9941
|
throw new Error(`Hodoscope explicit directory must be empty: ${directory}`);
|
|
9027
9942
|
}
|
|
9028
9943
|
}
|
|
9029
|
-
const samples =
|
|
9944
|
+
const samples = join23(directory, "samples");
|
|
9030
9945
|
await mkdir3(samples);
|
|
9031
9946
|
const rows = [];
|
|
9032
9947
|
for await (const value of readJsonl(otlpPath)) rows.push(parseOpenInferenceRow(value));
|
|
@@ -9052,7 +9967,7 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
|
9052
9967
|
}
|
|
9053
9968
|
actionCount += actions;
|
|
9054
9969
|
const file = `${createHash9("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
|
|
9055
|
-
await writeFile4(
|
|
9970
|
+
await writeFile4(join23(samples, file), `${JSON.stringify(trajectory, null, 2)}
|
|
9056
9971
|
`, "utf8");
|
|
9057
9972
|
}
|
|
9058
9973
|
return {
|
|
@@ -9076,10 +9991,10 @@ function hodoscopeAnalyzer(options = {}) {
|
|
|
9076
9991
|
async analyze(otlpPath, runOptions2 = {}) {
|
|
9077
9992
|
const outputRoot = options.outputDir ? resolve6(options.outputDir) : tmpdir4();
|
|
9078
9993
|
await mkdir3(outputRoot, { recursive: true });
|
|
9079
|
-
const directory = await mkdtemp3(
|
|
9080
|
-
const input =
|
|
9081
|
-
const analysisFile =
|
|
9082
|
-
const samplesFile =
|
|
9994
|
+
const directory = await mkdtemp3(join23(outputRoot, "traces-hodoscope-"));
|
|
9995
|
+
const input = join23(directory, "input");
|
|
9996
|
+
const analysisFile = join23(directory, "analysis.hodoscope.json");
|
|
9997
|
+
const samplesFile = join23(directory, "samples.json");
|
|
9083
9998
|
try {
|
|
9084
9999
|
const prepared = await writeHodoscopeInput(otlpPath, {
|
|
9085
10000
|
directory: input,
|
|
@@ -9791,7 +10706,7 @@ async function applyRedactor(spans, redactor) {
|
|
|
9791
10706
|
import { createHash as createHash10 } from "crypto";
|
|
9792
10707
|
import { mkdtemp as mkdtemp4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
9793
10708
|
import { tmpdir as tmpdir5 } from "os";
|
|
9794
|
-
import { join as
|
|
10709
|
+
import { join as join24 } from "path";
|
|
9795
10710
|
import {
|
|
9796
10711
|
firstNumberAttr,
|
|
9797
10712
|
LLM_CACHED_TOKEN_ATTR_KEYS,
|
|
@@ -10387,7 +11302,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
|
|
|
10387
11302
|
}
|
|
10388
11303
|
async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
|
|
10389
11304
|
const result = await exportTraceEvidenceFile(inputPath, opts);
|
|
10390
|
-
const path = outPath ??
|
|
11305
|
+
const path = outPath ?? join24(await mkdtemp4(join24(tmpdir5(), "traces-export-")), "spans.openinference.jsonl");
|
|
10391
11306
|
await writeFile5(path, serializeSpans(result.spans), "utf8");
|
|
10392
11307
|
return { ...result, path };
|
|
10393
11308
|
}
|
|
@@ -10395,7 +11310,7 @@ async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
|
|
|
10395
11310
|
// src/inspect.ts
|
|
10396
11311
|
import { mkdtemp as mkdtemp5, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
|
|
10397
11312
|
import { tmpdir as tmpdir6 } from "os";
|
|
10398
|
-
import { basename as basename13, join as
|
|
11313
|
+
import { basename as basename13, join as join25 } from "path";
|
|
10399
11314
|
var severityRank = {
|
|
10400
11315
|
high: 0,
|
|
10401
11316
|
medium: 1,
|
|
@@ -10684,14 +11599,14 @@ async function readSessionIndexFile(path) {
|
|
|
10684
11599
|
return parsed;
|
|
10685
11600
|
}
|
|
10686
11601
|
async function writeInspectionReportFile(report, outPath) {
|
|
10687
|
-
const path = outPath ??
|
|
11602
|
+
const path = outPath ?? join25(await mkdtemp5(join25(tmpdir6(), "traces-inspect-")), "inspection-report.md");
|
|
10688
11603
|
await writeFile6(path, renderInspectionReport(report), "utf8");
|
|
10689
11604
|
return path;
|
|
10690
11605
|
}
|
|
10691
11606
|
|
|
10692
11607
|
// src/adoption.ts
|
|
10693
11608
|
import { readFile as readFile11 } from "fs/promises";
|
|
10694
|
-
import { join as
|
|
11609
|
+
import { join as join26 } from "path";
|
|
10695
11610
|
function toolName2(s) {
|
|
10696
11611
|
const n = s.attributes["tool.name"];
|
|
10697
11612
|
if (typeof n === "string") return n;
|
|
@@ -10811,7 +11726,7 @@ async function readLoopRuns(cwds, sessionIds) {
|
|
|
10811
11726
|
seenCwds.add(cwd);
|
|
10812
11727
|
const firstSourceByRun = /* @__PURE__ */ new Map();
|
|
10813
11728
|
for (const artifactDir of [".agent", ".evolve"]) {
|
|
10814
|
-
const path =
|
|
11729
|
+
const path = join26(cwd, artifactDir, "skill-runs.jsonl");
|
|
10815
11730
|
let raw;
|
|
10816
11731
|
try {
|
|
10817
11732
|
raw = await readFile11(path, "utf8");
|
|
@@ -12223,7 +13138,7 @@ function renderAdoption(ar) {
|
|
|
12223
13138
|
// src/improvement.ts
|
|
12224
13139
|
import { access, mkdir as mkdir4, mkdtemp as mkdtemp6, writeFile as writeFile7 } from "fs/promises";
|
|
12225
13140
|
import { tmpdir as tmpdir7 } from "os";
|
|
12226
|
-
import { dirname as dirname7, join as
|
|
13141
|
+
import { dirname as dirname7, join as join27, resolve as resolve7 } from "path";
|
|
12227
13142
|
import { pathToFileURL } from "url";
|
|
12228
13143
|
import {
|
|
12229
13144
|
AnalystRegistry,
|
|
@@ -12730,13 +13645,13 @@ async function runTraceStoreInvestigation(opts) {
|
|
|
12730
13645
|
};
|
|
12731
13646
|
}
|
|
12732
13647
|
async function writeTraceImprovementArtifacts(result, outDir) {
|
|
12733
|
-
const directory = outDir ? resolve7(outDir) : await mkdtemp6(
|
|
13648
|
+
const directory = outDir ? resolve7(outDir) : await mkdtemp6(join27(tmpdir7(), "traces-improvement-"));
|
|
12734
13649
|
await mkdir4(directory, { recursive: true });
|
|
12735
13650
|
const paths = {
|
|
12736
13651
|
directory,
|
|
12737
|
-
result:
|
|
12738
|
-
evidence:
|
|
12739
|
-
report:
|
|
13652
|
+
result: join27(directory, "result.json"),
|
|
13653
|
+
evidence: join27(directory, "evidence.jsonl"),
|
|
13654
|
+
report: join27(directory, "report.md"),
|
|
12740
13655
|
traces: result.otlpPath
|
|
12741
13656
|
};
|
|
12742
13657
|
const { report, ...machineResult } = result;
|
|
@@ -12748,11 +13663,11 @@ async function writeTraceImprovementArtifacts(result, outDir) {
|
|
|
12748
13663
|
return paths;
|
|
12749
13664
|
}
|
|
12750
13665
|
async function runTraceImprovement(opts) {
|
|
12751
|
-
const directory = opts.outDir ? resolve7(opts.outDir) : await mkdtemp6(
|
|
13666
|
+
const directory = opts.outDir ? resolve7(opts.outDir) : await mkdtemp6(join27(tmpdir7(), "traces-improvement-"));
|
|
12752
13667
|
await mkdir4(directory, { recursive: true });
|
|
12753
13668
|
const investigation = await runTraceInvestigation({
|
|
12754
13669
|
...opts,
|
|
12755
|
-
otlpOutPath: opts.otlpOutPath ??
|
|
13670
|
+
otlpOutPath: opts.otlpOutPath ?? join27(directory, "traces.otlp.jsonl")
|
|
12756
13671
|
});
|
|
12757
13672
|
const result = {
|
|
12758
13673
|
...investigation,
|
|
@@ -13195,7 +14110,7 @@ async function streamSessions(opts) {
|
|
|
13195
14110
|
|
|
13196
14111
|
// src/supervisor-run-context.ts
|
|
13197
14112
|
import { readFile as readFile12, stat as stat14 } from "fs/promises";
|
|
13198
|
-
import { basename as basename14, join as
|
|
14113
|
+
import { basename as basename14, join as join28 } from "path";
|
|
13199
14114
|
import { materializeTreeView } from "@tangle-network/agent-runtime/kernel";
|
|
13200
14115
|
import {
|
|
13201
14116
|
NO_SOURCE_LIMITS
|
|
@@ -13356,10 +14271,10 @@ function parseInstant(v) {
|
|
|
13356
14271
|
return Number.isFinite(t) ? t : null;
|
|
13357
14272
|
}
|
|
13358
14273
|
async function readFileRunContext(runDir, opts = {}) {
|
|
13359
|
-
const journalText = await readMaybe(
|
|
14274
|
+
const journalText = await readMaybe(join28(runDir, SPAWN_JOURNAL_FILE));
|
|
13360
14275
|
const journal = readRunContextJournal(journalText);
|
|
13361
|
-
const coordination = readCoordinationLog(await readMaybe(
|
|
13362
|
-
const result = await readMaybe(
|
|
14276
|
+
const coordination = readCoordinationLog(await readMaybe(join28(runDir, COORDINATION_LOG_FILE)));
|
|
14277
|
+
const result = await readMaybe(join28(runDir, RESULT_FILE));
|
|
13363
14278
|
return {
|
|
13364
14279
|
runRef: runDir,
|
|
13365
14280
|
instanceId: opts.instanceId !== void 0 ? opts.instanceId : journal.root,
|
|
@@ -13382,7 +14297,7 @@ async function readFileRunContext(runDir, opts = {}) {
|
|
|
13382
14297
|
harnessWorkerTokens: null,
|
|
13383
14298
|
harnessMissingReason: "the file run context journals each child\u2019s own settled spend, so no external harness join is used",
|
|
13384
14299
|
limits: sourceLimits(journal, coordination),
|
|
13385
|
-
rootTranscriptRef: journal.present ?
|
|
14300
|
+
rootTranscriptRef: journal.present ? join28(runDir, SPAWN_JOURNAL_FILE) : null,
|
|
13386
14301
|
traceCommand: `traces watch ${runDir}`
|
|
13387
14302
|
};
|
|
13388
14303
|
}
|
|
@@ -13390,7 +14305,7 @@ function fileRunContextSupervisorRunReader(runDir, opts = {}) {
|
|
|
13390
14305
|
return { runRef: runDir, read: () => readFileRunContext(runDir, opts) };
|
|
13391
14306
|
}
|
|
13392
14307
|
async function isFileRunContextDir(runDir) {
|
|
13393
|
-
return stat14(
|
|
14308
|
+
return stat14(join28(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
|
|
13394
14309
|
}
|
|
13395
14310
|
function flattenJournal(journal) {
|
|
13396
14311
|
return journal.events.map((event) => {
|
|
@@ -13536,9 +14451,9 @@ function mergeRolls(a, b) {
|
|
|
13536
14451
|
};
|
|
13537
14452
|
}
|
|
13538
14453
|
async function readRunContextSnapshot(runDir) {
|
|
13539
|
-
const journal = readRunContextJournal(await readMaybe(
|
|
13540
|
-
const coordination = readCoordinationLog(await readMaybe(
|
|
13541
|
-
const resultText = await readMaybe(
|
|
14454
|
+
const journal = readRunContextJournal(await readMaybe(join28(runDir, SPAWN_JOURNAL_FILE)));
|
|
14455
|
+
const coordination = readCoordinationLog(await readMaybe(join28(runDir, COORDINATION_LOG_FILE)));
|
|
14456
|
+
const resultText = await readMaybe(join28(runDir, RESULT_FILE));
|
|
13542
14457
|
const result = parseResult(resultText);
|
|
13543
14458
|
const view = safeTreeView(journal.events);
|
|
13544
14459
|
const nodes = view === null ? [] : orderNodes(view, journal);
|
|
@@ -13721,7 +14636,7 @@ function wrap(text2, indent) {
|
|
|
13721
14636
|
|
|
13722
14637
|
// src/run-span-tree.ts
|
|
13723
14638
|
import { readdir as readdir16 } from "fs/promises";
|
|
13724
|
-
import { join as
|
|
14639
|
+
import { join as join29 } from "path";
|
|
13725
14640
|
import {
|
|
13726
14641
|
firstNumberAttr as firstNumberAttr3,
|
|
13727
14642
|
LLM_COST_ATTR_KEYS as LLM_COST_ATTR_KEYS2,
|
|
@@ -13994,7 +14909,7 @@ async function findSpanSourceFiles(dir) {
|
|
|
13994
14909
|
const found = [];
|
|
13995
14910
|
const entries = await readdir16(dir, { withFileTypes: true }).catch(() => []);
|
|
13996
14911
|
for (const entry of entries) {
|
|
13997
|
-
const child =
|
|
14912
|
+
const child = join29(dir, entry.name);
|
|
13998
14913
|
if (entry.isFile()) {
|
|
13999
14914
|
if (SPAN_FILE_PATTERN.test(entry.name)) found.push(child);
|
|
14000
14915
|
continue;
|
|
@@ -14002,7 +14917,7 @@ async function findSpanSourceFiles(dir) {
|
|
|
14002
14917
|
if (!entry.isDirectory()) continue;
|
|
14003
14918
|
const nested = await readdir16(child, { withFileTypes: true }).catch(() => []);
|
|
14004
14919
|
for (const inner of nested) {
|
|
14005
|
-
if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(
|
|
14920
|
+
if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join29(child, inner.name));
|
|
14006
14921
|
}
|
|
14007
14922
|
}
|
|
14008
14923
|
return found.sort();
|
|
@@ -14010,7 +14925,7 @@ async function findSpanSourceFiles(dir) {
|
|
|
14010
14925
|
|
|
14011
14926
|
// src/supervisor-run-watch.ts
|
|
14012
14927
|
import { readdir as readdir17 } from "fs/promises";
|
|
14013
|
-
import { join as
|
|
14928
|
+
import { join as join30 } from "path";
|
|
14014
14929
|
function budget(node) {
|
|
14015
14930
|
const parts = [`${int(node.budget.maxIterations)}it`, `${int(node.budget.maxTokens)}tok`];
|
|
14016
14931
|
if (node.budget.maxUsd !== void 0) parts.push(`$${node.budget.maxUsd.toFixed(2)}`);
|
|
@@ -14154,7 +15069,7 @@ async function resolveRunContextDir(dir) {
|
|
|
14154
15069
|
const candidates = [];
|
|
14155
15070
|
for (const entry of entries) {
|
|
14156
15071
|
if (!entry.isDirectory()) continue;
|
|
14157
|
-
const child =
|
|
15072
|
+
const child = join30(dir, entry.name);
|
|
14158
15073
|
if (await isFileRunContextDir(child)) candidates.push(child);
|
|
14159
15074
|
}
|
|
14160
15075
|
if (candidates.length === 1) return candidates[0];
|
|
@@ -14734,7 +15649,7 @@ async function collectSessionSelection(groups, options = {}) {
|
|
|
14734
15649
|
// src/session-index.ts
|
|
14735
15650
|
import { mkdtemp as mkdtemp7, readdir as readdir18, readFile as readFile13, stat as stat16, writeFile as writeFile8 } from "fs/promises";
|
|
14736
15651
|
import { tmpdir as tmpdir8 } from "os";
|
|
14737
|
-
import { dirname as dirname8, join as
|
|
15652
|
+
import { dirname as dirname8, join as join31 } from "path";
|
|
14738
15653
|
function indexRow(record2) {
|
|
14739
15654
|
return {
|
|
14740
15655
|
session: record2.session,
|
|
@@ -14807,7 +15722,7 @@ async function findContextRoot(cwd) {
|
|
|
14807
15722
|
if (!s) return null;
|
|
14808
15723
|
if (!s.isDirectory()) current = dirname8(current);
|
|
14809
15724
|
for (; ; ) {
|
|
14810
|
-
if (await pathStat2(
|
|
15725
|
+
if (await pathStat2(join31(current, ".git")) || await dirExists(join31(current, ".evolve")) || await fileExists(join31(current, "AGENTS.md")) || await fileExists(join31(current, "CLAUDE.md"))) {
|
|
14811
15726
|
return current;
|
|
14812
15727
|
}
|
|
14813
15728
|
const parent = dirname8(current);
|
|
@@ -14875,7 +15790,7 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
14875
15790
|
}
|
|
14876
15791
|
for (const entry of entries) {
|
|
14877
15792
|
if (out.length >= maxFiles) return;
|
|
14878
|
-
const path =
|
|
15793
|
+
const path = join31(dir, entry.name);
|
|
14879
15794
|
if (entry.isDirectory()) {
|
|
14880
15795
|
await walk(path);
|
|
14881
15796
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -14890,20 +15805,20 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
14890
15805
|
async function collectContextRoot(root) {
|
|
14891
15806
|
const files = [];
|
|
14892
15807
|
for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
|
|
14893
|
-
const summary = await summarizeFile(
|
|
15808
|
+
const summary = await summarizeFile(join31(root, name), "instruction-doc");
|
|
14894
15809
|
if (summary) files.push(summary);
|
|
14895
15810
|
}
|
|
14896
|
-
const evolve =
|
|
15811
|
+
const evolve = join31(root, ".evolve");
|
|
14897
15812
|
for (const name of ["skill-runs.jsonl", "governor.jsonl", "experiments.jsonl"]) {
|
|
14898
|
-
const summary = await summarizeFile(
|
|
15813
|
+
const summary = await summarizeFile(join31(evolve, name), "evolve-jsonl");
|
|
14899
15814
|
if (summary) files.push(summary);
|
|
14900
15815
|
}
|
|
14901
15816
|
for (const name of ["scorecard.json"]) {
|
|
14902
|
-
const summary = await summarizeFile(
|
|
15817
|
+
const summary = await summarizeFile(join31(evolve, name), "evolve-json");
|
|
14903
15818
|
if (summary) files.push(summary);
|
|
14904
15819
|
}
|
|
14905
|
-
files.push(...await walkMarkdown(
|
|
14906
|
-
files.push(...await walkMarkdown(
|
|
15820
|
+
files.push(...await walkMarkdown(join31(evolve, "reflections"), "reflection"));
|
|
15821
|
+
files.push(...await walkMarkdown(join31(evolve, "handoffs"), "handoff"));
|
|
14907
15822
|
return { root, files: files.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
14908
15823
|
}
|
|
14909
15824
|
async function collectContextIndex(records) {
|
|
@@ -14949,7 +15864,7 @@ function serializeSessionIndex(index) {
|
|
|
14949
15864
|
`;
|
|
14950
15865
|
}
|
|
14951
15866
|
async function writeSessionIndexFile(index, outPath) {
|
|
14952
|
-
const path = outPath ??
|
|
15867
|
+
const path = outPath ?? join31(await mkdtemp7(join31(tmpdir8(), "traces-index-")), "session-index.json");
|
|
14953
15868
|
await writeFile8(path, serializeSessionIndex(index), "utf8");
|
|
14954
15869
|
return path;
|
|
14955
15870
|
}
|
|
@@ -14957,11 +15872,11 @@ async function writeSessionIndexFile(index, outPath) {
|
|
|
14957
15872
|
// src/upload-state.ts
|
|
14958
15873
|
import { mkdir as mkdir5, readFile as readFile14, writeFile as writeFile9 } from "fs/promises";
|
|
14959
15874
|
import { homedir as homedir12 } from "os";
|
|
14960
|
-
import { dirname as dirname9, join as
|
|
15875
|
+
import { dirname as dirname9, join as join32 } from "path";
|
|
14961
15876
|
import { contentHash } from "@tangle-network/agent-eval";
|
|
14962
15877
|
function stateFile() {
|
|
14963
|
-
const base = process.env.XDG_STATE_HOME ??
|
|
14964
|
-
return
|
|
15878
|
+
const base = process.env.XDG_STATE_HOME ?? join32(homedir12(), ".local", "state");
|
|
15879
|
+
return join32(base, "tangle-traces", "uploaded.json");
|
|
14965
15880
|
}
|
|
14966
15881
|
function uploadKey(harness, sessionId) {
|
|
14967
15882
|
return `${harness}:${sessionId}`;
|
|
@@ -14993,7 +15908,7 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
|
|
|
14993
15908
|
import { readFile as readFile15 } from "fs/promises";
|
|
14994
15909
|
import { createRequire as createRequire2 } from "module";
|
|
14995
15910
|
import { hostname as hostname2 } from "os";
|
|
14996
|
-
import { basename as basename15, join as
|
|
15911
|
+
import { basename as basename15, join as join33 } from "path";
|
|
14997
15912
|
import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
14998
15913
|
import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
|
|
14999
15914
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -15017,7 +15932,7 @@ async function planUpload(opts = {}) {
|
|
|
15017
15932
|
async function gitBranch(cwd) {
|
|
15018
15933
|
if (!cwd) return void 0;
|
|
15019
15934
|
try {
|
|
15020
|
-
const head = await readFile15(
|
|
15935
|
+
const head = await readFile15(join33(cwd, ".git", "HEAD"), "utf8");
|
|
15021
15936
|
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
|
|
15022
15937
|
return m?.[1]?.trim();
|
|
15023
15938
|
} catch {
|
|
@@ -15281,9 +16196,13 @@ export {
|
|
|
15281
16196
|
resolveCaseResources,
|
|
15282
16197
|
enumerateReplayableCases,
|
|
15283
16198
|
zaiChatCaller,
|
|
16199
|
+
clipText,
|
|
15284
16200
|
buildFixPrompt,
|
|
16201
|
+
buildRetryFixPrompt,
|
|
16202
|
+
countScriptCommands,
|
|
15285
16203
|
extractFixCommand,
|
|
15286
16204
|
generateFixCommand,
|
|
16205
|
+
runFixLoop,
|
|
15287
16206
|
derivedImageTag,
|
|
15288
16207
|
dockerImagePreparer,
|
|
15289
16208
|
seededSample,
|
|
@@ -15292,6 +16211,21 @@ export {
|
|
|
15292
16211
|
parseReplayBatchArgs,
|
|
15293
16212
|
replayBatchUsage,
|
|
15294
16213
|
cmdReplayVerifyBatch,
|
|
16214
|
+
parseIncorrectStepsSubject,
|
|
16215
|
+
resolveFindingInvocation,
|
|
16216
|
+
replayVerifyFinding,
|
|
16217
|
+
findingReplayStep,
|
|
16218
|
+
findingTrajectoryId,
|
|
16219
|
+
resolveFindingReplayability,
|
|
16220
|
+
classifyVerdict,
|
|
16221
|
+
DEFAULT_SANDBOX_BASE_URL,
|
|
16222
|
+
assertSandboxReachable,
|
|
16223
|
+
verifyFindings,
|
|
16224
|
+
renderVerifiedFindingsSection,
|
|
16225
|
+
readFindingsFile,
|
|
16226
|
+
verifyFindingsUsage,
|
|
16227
|
+
parseVerifyFindingsArgs,
|
|
16228
|
+
cmdVerifyFindings,
|
|
15295
16229
|
runCommand,
|
|
15296
16230
|
commandAnalyzer,
|
|
15297
16231
|
haloAnalyzer,
|