@tangle-network/traces 0.11.2 → 0.11.4

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.
@@ -765,9 +765,9 @@ function serializeSpans(spans) {
765
765
  }
766
766
  async function writeOtlpFile(spans, outPath) {
767
767
  const { mkdtemp: mkdtemp8, writeFile: writeFile10 } = await import("fs/promises");
768
- const { tmpdir: tmpdir8 } = await import("os");
769
- const { join: join31 } = await import("path");
770
- const path = outPath ?? join31(await mkdtemp8(join31(tmpdir8(), "traces-")), "spans.otlp.jsonl");
768
+ const { tmpdir: tmpdir9 } = await import("os");
769
+ const { join: join33 } = await import("path");
770
+ const path = outPath ?? join33(await mkdtemp8(join33(tmpdir9(), "traces-")), "spans.otlp.jsonl");
771
771
  await writeFile10(path, serializeSpans(spans), "utf8");
772
772
  return path;
773
773
  }
@@ -5503,7 +5503,7 @@ var OpencodeAdapter = class {
5503
5503
  // src/adapters/pi.ts
5504
5504
  import { readdir as readdir12, stat as stat11 } from "fs/promises";
5505
5505
  import { homedir as homedir10 } from "os";
5506
- import { basename as basename11, join as join15 } from "path";
5506
+ import { basename as basename11, join as join15, resolve as resolve5 } from "path";
5507
5507
  var SERVICE9 = "pi";
5508
5508
  function terminalStatus(message) {
5509
5509
  const stopReason = message?.stopReason ?? "missing";
@@ -5548,45 +5548,77 @@ function completeToolSpan(toolSpan, result) {
5548
5548
  }
5549
5549
  var PiAdapter = class {
5550
5550
  harness = "pi";
5551
+ sessionsRoot;
5552
+ constructor(options = {}) {
5553
+ this.sessionsRoot = resolve5(options.sessionsRoot ?? join15(homedir10(), ".pi", "agent", "sessions"));
5554
+ }
5551
5555
  root() {
5552
- return join15(homedir10(), ".pi", "agent", "sessions");
5556
+ return this.sessionsRoot;
5553
5557
  }
5554
5558
  async locate(opts = {}) {
5555
5559
  const root = this.root();
5556
- let dirs;
5560
+ let entries;
5557
5561
  try {
5558
- dirs = await readdir12(root);
5562
+ entries = await readdir12(root, { withFileTypes: true });
5559
5563
  } catch (error) {
5560
5564
  if (isMissingPathError(error)) return [];
5561
5565
  throw error;
5562
5566
  }
5563
- const refs = [];
5564
- for (const dir of dirs) {
5565
- const dp = join15(root, dir);
5567
+ const paths = [];
5568
+ for (const entry of entries) {
5569
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
5570
+ paths.push(join15(root, entry.name));
5571
+ continue;
5572
+ }
5573
+ if (!entry.isDirectory()) continue;
5574
+ const dir = join15(root, entry.name);
5566
5575
  let files;
5567
5576
  try {
5568
- files = await readdir12(dp);
5577
+ files = await readdir12(dir);
5569
5578
  } catch (error) {
5570
5579
  if (isMissingPathError(error)) continue;
5571
5580
  throw error;
5572
5581
  }
5573
- const cwd = `/${dir.replace(/^-+/, "").replace(/-+$/, "").replace(/-/g, "/")}`;
5574
- if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
5575
- for (const f of files) {
5576
- if (!f.endsWith(".jsonl")) continue;
5577
- const path = join15(dp, f);
5578
- let st;
5579
- try {
5580
- st = await stat11(path);
5581
- } catch (error) {
5582
- if (isMissingPathError(error)) continue;
5583
- throw error;
5584
- }
5585
- if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
5586
- const id = basename11(f, ".jsonl").replace(/^[\dTZ.-]+_/, "");
5587
- refs.push({ harness: this.harness, sessionId: id, path, cwd, mtimeMs: st.mtimeMs });
5582
+ for (const file of files) {
5583
+ if (file.endsWith(".jsonl")) paths.push(join15(dir, file));
5588
5584
  }
5589
5585
  }
5586
+ const refs = [];
5587
+ for (const path of paths) {
5588
+ const f = basename11(path);
5589
+ let st;
5590
+ try {
5591
+ st = await stat11(path);
5592
+ } catch (error) {
5593
+ if (isMissingPathError(error)) continue;
5594
+ throw error;
5595
+ }
5596
+ if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
5597
+ let id = basename11(f, ".jsonl").replace(/^[\dTZ.-]+_/, "");
5598
+ let cwd = null;
5599
+ const ref = {
5600
+ harness: this.harness,
5601
+ sessionId: id,
5602
+ path,
5603
+ cwd,
5604
+ mtimeMs: st.mtimeMs
5605
+ };
5606
+ const [session] = await takeJsonl(path, 1, sessionJsonlOptions(ref));
5607
+ if (session?.type === "session") {
5608
+ if (session.id) id = session.id;
5609
+ if (session.cwd) cwd = session.cwd;
5610
+ }
5611
+ ref.sessionId = id;
5612
+ ref.cwd = cwd;
5613
+ if (ref.integrity) {
5614
+ ref.integrity.corruptions = ref.integrity.corruptions.map((receipt) => ({
5615
+ ...receipt,
5616
+ sessionId: id
5617
+ }));
5618
+ }
5619
+ if (opts.cwd && (!cwd || !cwd.startsWith(opts.cwd))) continue;
5620
+ refs.push(ref);
5621
+ }
5590
5622
  return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
5591
5623
  }
5592
5624
  async parse(ref, options = {}) {
@@ -5997,9 +6029,9 @@ function normalizeRemote(url) {
5997
6029
  }
5998
6030
  async function readGit(cwd) {
5999
6031
  await stat13(`${cwd}/.git`);
6000
- const { execFile } = await import("child_process");
6001
- const { promisify } = await import("util");
6002
- const run = promisify(execFile);
6032
+ const { execFile: execFile2 } = await import("child_process");
6033
+ const { promisify: promisify2 } = await import("util");
6034
+ const run = promisify2(execFile2);
6003
6035
  const git = async (args) => {
6004
6036
  try {
6005
6037
  const { stdout } = await run("git", ["-C", cwd, ...args], { timeout: 5e3 });
@@ -6025,9 +6057,9 @@ async function readGit(cwd) {
6025
6057
  };
6026
6058
  }
6027
6059
  async function gitOutput(cwd, args) {
6028
- const { execFile } = await import("child_process");
6029
- const { promisify } = await import("util");
6030
- const run = promisify(execFile);
6060
+ const { execFile: execFile2 } = await import("child_process");
6061
+ const { promisify: promisify2 } = await import("util");
6062
+ const run = promisify2(execFile2);
6031
6063
  try {
6032
6064
  const { stdout } = await run("git", ["-C", cwd, ...args], { timeout: 5e3 });
6033
6065
  const v = stdout.trim();
@@ -6332,7 +6364,7 @@ async function stampSpanWorkdirRepoAttrs(spans) {
6332
6364
  async function awaitWithAbort(operation, signal) {
6333
6365
  if (!signal) return operation;
6334
6366
  signal.throwIfAborted();
6335
- return new Promise((resolve7, reject) => {
6367
+ return new Promise((resolve8, reject) => {
6336
6368
  const stop = () => {
6337
6369
  signal.removeEventListener("abort", stop);
6338
6370
  reject(signal.reason);
@@ -6341,7 +6373,7 @@ async function awaitWithAbort(operation, signal) {
6341
6373
  operation.then(
6342
6374
  (value) => {
6343
6375
  signal.removeEventListener("abort", stop);
6344
- resolve7(value);
6376
+ resolve8(value);
6345
6377
  },
6346
6378
  (error) => {
6347
6379
  signal.removeEventListener("abort", stop);
@@ -7141,6 +7173,1084 @@ async function cmdReplayVerify(argv) {
7141
7173
  );
7142
7174
  }
7143
7175
 
7176
+ // src/replay-corpus.ts
7177
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
7178
+ import { join as join20 } from "path";
7179
+ function parseCorpusFlag(value) {
7180
+ const eq = value.indexOf("=");
7181
+ if (eq <= 0) throw new Error(`--corpus must be name=<labels>::<prepared>, got: ${value}`);
7182
+ const name = value.slice(0, eq);
7183
+ const rest = value.slice(eq + 1);
7184
+ const sep2 = rest.indexOf("::");
7185
+ if (sep2 <= 0 || sep2 === rest.length - 2) {
7186
+ throw new Error(`--corpus must be name=<labels>::<prepared>, got: ${value}`);
7187
+ }
7188
+ return { name, labelsPath: rest.slice(0, sep2), preparedDir: rest.slice(sep2 + 2) };
7189
+ }
7190
+ var SUBMIT_ACTION_SIGNATURE = "COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT";
7191
+ function isSubmitAction(action) {
7192
+ return action.includes(SUBMIT_ACTION_SIGNATURE);
7193
+ }
7194
+ function readLabelEntries(labelsPath) {
7195
+ const parsed = JSON.parse(readFileSync3(labelsPath, "utf8"));
7196
+ if (!Array.isArray(parsed)) throw new Error(`${labelsPath} is not a JSON array of label entries`);
7197
+ for (const entry of parsed) {
7198
+ if (typeof entry.traj_id !== "string") {
7199
+ throw new Error(`${labelsPath} has an entry without a string traj_id`);
7200
+ }
7201
+ }
7202
+ return parsed;
7203
+ }
7204
+ function goldIncorrectSteps(entry) {
7205
+ const ids = /* @__PURE__ */ new Set();
7206
+ for (const stage of entry.incorrect_stages ?? []) {
7207
+ for (const id of stage.incorrect_step_ids ?? []) ids.add(id);
7208
+ }
7209
+ return [...ids].sort((a, b) => a - b);
7210
+ }
7211
+ function findRawTrajFiles(sweRawDir) {
7212
+ if (!existsSync2(sweRawDir)) return [];
7213
+ const entries = readdirSync2(sweRawDir, { recursive: true, encoding: "utf8" });
7214
+ return entries.filter((relative5) => relative5.endsWith(".traj.json")).map((relative5) => join20(sweRawDir, relative5)).sort();
7215
+ }
7216
+ function cwdFromPwdObservation(steps) {
7217
+ for (const step of steps) {
7218
+ if (step.action.trim() !== "pwd") continue;
7219
+ const line = parseObservationOutput(step.observation).split("\n").map((l) => l.trim()).find((l) => l.startsWith("/"));
7220
+ if (line) return line;
7221
+ }
7222
+ return null;
7223
+ }
7224
+ function taskStatementOf(preparedDir, trajId, raw) {
7225
+ const taskPath = join20(preparedDir, "normalized", trajId, "task.md");
7226
+ if (existsSync2(taskPath)) {
7227
+ const text2 = readFileSync3(taskPath, "utf8").trim();
7228
+ if (text2.length > 0) return text2;
7229
+ }
7230
+ const userMessage = raw.messages?.find((m) => m.role === "user");
7231
+ if (typeof userMessage?.content === "string" && userMessage.content.trim().length > 0) {
7232
+ return userMessage.content.trim();
7233
+ }
7234
+ return null;
7235
+ }
7236
+ function resolveCaseResources(corpus, trajId) {
7237
+ const sweRawDir = join20(corpus.preparedDir, "extracted", trajId, "swe_raw");
7238
+ const rawFiles = findRawTrajFiles(sweRawDir);
7239
+ if (rawFiles.length === 0) {
7240
+ return { resolved: false, reason: "no-swe-raw-trajectory", detail: sweRawDir };
7241
+ }
7242
+ if (rawFiles.length > 1) {
7243
+ return { resolved: false, reason: "ambiguous-swe-raw-trajectory", detail: rawFiles.join(", ") };
7244
+ }
7245
+ let raw;
7246
+ try {
7247
+ raw = JSON.parse(readFileSync3(rawFiles[0], "utf8"));
7248
+ } catch (err) {
7249
+ return {
7250
+ resolved: false,
7251
+ reason: "unreadable-raw-trajectory",
7252
+ detail: `${rawFiles[0]}: ${err instanceof Error ? err.message : String(err)}`
7253
+ };
7254
+ }
7255
+ const dockerConfig = raw.info?.docker_config;
7256
+ const image = dockerConfig?.base_image;
7257
+ if (typeof image !== "string" || image.length === 0) {
7258
+ return { resolved: false, reason: "no-docker-image", detail: rawFiles[0] };
7259
+ }
7260
+ const stepsPath = join20(corpus.preparedDir, "normalized", trajId, "steps.json");
7261
+ if (!existsSync2(stepsPath)) {
7262
+ return { resolved: false, reason: "missing-steps-json", detail: stepsPath };
7263
+ }
7264
+ const steps = JSON.parse(readFileSync3(stepsPath, "utf8"));
7265
+ const runConfigCwd = raw.info?.config?.environment?.cwd;
7266
+ const dockerCwd = dockerConfig?.cwd;
7267
+ let cwd;
7268
+ let cwdSource;
7269
+ if (typeof runConfigCwd === "string" && runConfigCwd.length > 0) {
7270
+ cwd = runConfigCwd;
7271
+ cwdSource = "run-config";
7272
+ } else if (typeof dockerCwd === "string" && dockerCwd.length > 0) {
7273
+ cwd = dockerCwd;
7274
+ cwdSource = "docker-config";
7275
+ } else {
7276
+ const observed = cwdFromPwdObservation(steps);
7277
+ if (!observed) return { resolved: false, reason: "cwd-underivable" };
7278
+ cwd = observed;
7279
+ cwdSource = "pwd-observation";
7280
+ }
7281
+ const recordedTimeout = raw.info?.config?.environment?.timeout;
7282
+ return {
7283
+ resolved: true,
7284
+ resources: {
7285
+ corpus: corpus.name,
7286
+ trajId,
7287
+ stepsPath,
7288
+ steps,
7289
+ taskStatement: taskStatementOf(corpus.preparedDir, trajId, raw),
7290
+ image,
7291
+ cwd,
7292
+ cwdSource,
7293
+ recordedStepTimeoutMs: typeof recordedTimeout === "number" && recordedTimeout > 0 ? recordedTimeout * 1e3 : null
7294
+ }
7295
+ };
7296
+ }
7297
+ function enumerateReplayableCases(corpora) {
7298
+ const replayable = [];
7299
+ const excluded = [];
7300
+ let labelEntryCount = 0;
7301
+ for (const corpus of corpora) {
7302
+ for (const entry of readLabelEntries(corpus.labelsPath)) {
7303
+ labelEntryCount += 1;
7304
+ const trajId = entry.traj_id;
7305
+ const resolution = resolveCaseResources(corpus, trajId);
7306
+ if (!resolution.resolved) {
7307
+ excluded.push({ corpus: corpus.name, trajId, reason: resolution.reason, detail: resolution.detail });
7308
+ continue;
7309
+ }
7310
+ const gold = goldIncorrectSteps(entry);
7311
+ if (gold.length === 0) {
7312
+ excluded.push({ corpus: corpus.name, trajId, reason: "no-gold-incorrect-step" });
7313
+ continue;
7314
+ }
7315
+ const resources = resolution.resources;
7316
+ let submitGoldsSkipped = 0;
7317
+ let target = null;
7318
+ let missingGoldId = null;
7319
+ for (const goldId of gold) {
7320
+ const step = resources.steps.find((s) => s.step_id === goldId);
7321
+ if (!step) {
7322
+ missingGoldId = goldId;
7323
+ break;
7324
+ }
7325
+ if (isSubmitAction(step.action)) {
7326
+ submitGoldsSkipped += 1;
7327
+ continue;
7328
+ }
7329
+ target = step;
7330
+ break;
7331
+ }
7332
+ if (missingGoldId !== null) {
7333
+ excluded.push({
7334
+ corpus: corpus.name,
7335
+ trajId,
7336
+ reason: "gold-step-outside-steps",
7337
+ detail: `k=${missingGoldId}, steps=${resources.steps.length}`
7338
+ });
7339
+ continue;
7340
+ }
7341
+ if (!target) {
7342
+ excluded.push({
7343
+ corpus: corpus.name,
7344
+ trajId,
7345
+ reason: "gold-only-submit-step",
7346
+ detail: `${submitGoldsSkipped} gold step(s), all submit commands`
7347
+ });
7348
+ continue;
7349
+ }
7350
+ replayable.push({
7351
+ ...resources,
7352
+ goldIncorrectSteps: gold,
7353
+ k: target.step_id,
7354
+ submitGoldsSkipped,
7355
+ recordedReturncodeAtK: parseRecordedReturncode(target.observation)
7356
+ });
7357
+ }
7358
+ }
7359
+ return { replayable, excluded, labelEntryCount };
7360
+ }
7361
+
7362
+ // src/replay-fix.ts
7363
+ function zaiChatCaller(options) {
7364
+ const fetchImpl = options.fetchImpl ?? fetch;
7365
+ return {
7366
+ async complete(system, user) {
7367
+ const baseCap = options.maxTokens ?? 16384;
7368
+ let lastError = "empty completion content";
7369
+ for (const cap of [baseCap, baseCap * 2]) {
7370
+ const outcome = await callOnce(cap);
7371
+ if (outcome.succeeded) return outcome;
7372
+ lastError = outcome.error;
7373
+ if (!/finish_reason":"length/.test(outcome.error)) return outcome;
7374
+ }
7375
+ return { succeeded: false, error: lastError };
7376
+ async function callOnce(maxTokens) {
7377
+ const controller = new AbortController();
7378
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 18e4);
7379
+ try {
7380
+ const response = await fetchImpl(`${options.baseUrl.replace(/\/$/, "")}/chat/completions`, {
7381
+ method: "POST",
7382
+ headers: {
7383
+ authorization: `Bearer ${options.apiKey}`,
7384
+ "content-type": "application/json"
7385
+ },
7386
+ body: JSON.stringify({
7387
+ model: options.model,
7388
+ messages: [
7389
+ { role: "system", content: system },
7390
+ { role: "user", content: user }
7391
+ ],
7392
+ temperature: 0,
7393
+ max_tokens: maxTokens
7394
+ }),
7395
+ signal: controller.signal
7396
+ });
7397
+ const bodyText = await response.text();
7398
+ if (!response.ok) {
7399
+ return { succeeded: false, error: `HTTP ${response.status}: ${bodyText.slice(0, 400)}` };
7400
+ }
7401
+ const body = JSON.parse(bodyText);
7402
+ if (body.error) {
7403
+ return { succeeded: false, error: String(body.error.message ?? "provider error") };
7404
+ }
7405
+ const content = body.choices?.[0]?.message?.content;
7406
+ if (typeof content !== "string" || content.length === 0) {
7407
+ return { succeeded: false, error: `empty completion content: ${bodyText.slice(0, 400)}` };
7408
+ }
7409
+ const promptTokens = body.usage?.prompt_tokens;
7410
+ const completionTokens = body.usage?.completion_tokens;
7411
+ const usage = typeof promptTokens === "number" && typeof completionTokens === "number" ? { promptTokens, completionTokens } : null;
7412
+ return { succeeded: true, value: { content, usage } };
7413
+ } catch (err) {
7414
+ return { succeeded: false, error: err instanceof Error ? err.message : String(err) };
7415
+ } finally {
7416
+ clearTimeout(timer);
7417
+ }
7418
+ }
7419
+ }
7420
+ };
7421
+ }
7422
+ function clip(text2, limit) {
7423
+ if (text2.length <= limit) return text2;
7424
+ const half = Math.floor(limit / 2);
7425
+ return `${text2.slice(0, half)}
7426
+ \u2026 [${text2.length - limit} chars elided] \u2026
7427
+ ${text2.slice(-half)}`;
7428
+ }
7429
+ function renderStep(step, marker) {
7430
+ const rc = parseRecordedReturncode(step.observation);
7431
+ const output = clip(parseObservationOutput(step.observation).trim(), 1600);
7432
+ return [
7433
+ `### step ${step.step_id}${marker}`,
7434
+ "```sh",
7435
+ clip(step.action, 2e3),
7436
+ "```",
7437
+ `returncode: ${rc ?? "none recorded"}`,
7438
+ output.length > 0 ? `output:
7439
+ \`\`\`
7440
+ ${output}
7441
+ \`\`\`` : "output: (empty)"
7442
+ ].join("\n");
7443
+ }
7444
+ function buildFixPrompt(input) {
7445
+ const radius = input.contextRadius ?? 3;
7446
+ const target = input.steps.find((s) => s.step_id === input.k);
7447
+ if (!target) throw new Error(`replay-fix: no step with step_id ${input.k}`);
7448
+ const context = input.steps.filter(
7449
+ (s) => s.step_id !== input.k && Math.abs(s.step_id - input.k) <= radius
7450
+ );
7451
+ const system = [
7452
+ "You repair one failed shell command from a recorded coding-agent trajectory.",
7453
+ "The trajectory replays inside the original docker image; every command runs as a fresh /bin/sh subshell from a fixed working directory.",
7454
+ "You are given the failing step, its recorded output, surrounding steps, and the task statement.",
7455
+ "Reply with exactly ONE corrected shell command (compound commands with && or pipes are fine) inside a single ```sh fenced block.",
7456
+ "The corrected command must accomplish the failing step's intent and exit 0. No prose outside the fenced block."
7457
+ ].join("\n");
7458
+ const user = [
7459
+ "## Task the agent was solving",
7460
+ input.taskStatement ? clip(input.taskStatement, 4e3) : "(no task statement recorded)",
7461
+ "",
7462
+ "## Surrounding steps",
7463
+ ...context.map((s) => renderStep(s, "")),
7464
+ "",
7465
+ "## Failing step to correct",
7466
+ renderStep(target, " (INCORRECT \u2014 correct this one)"),
7467
+ "",
7468
+ "Output the single corrected replacement for the failing step now."
7469
+ ].join("\n");
7470
+ return { system, user };
7471
+ }
7472
+ function extractFixCommand(content) {
7473
+ const blocks = [...content.matchAll(/```(?:sh|bash|shell)?\n([\s\S]*?)```/g)];
7474
+ const candidate = blocks.length > 0 ? blocks[blocks.length - 1][1] : content;
7475
+ const command = candidate.trim();
7476
+ if (command.length === 0 || command.includes("```")) return null;
7477
+ return command;
7478
+ }
7479
+ async function generateFixCommand(caller, input) {
7480
+ const { system, user } = buildFixPrompt(input);
7481
+ const outcome = await caller.complete(system, user);
7482
+ if (!outcome.succeeded) return outcome;
7483
+ const command = extractFixCommand(outcome.value.content);
7484
+ if (command === null) {
7485
+ return {
7486
+ succeeded: false,
7487
+ error: `completion carried no usable command: ${clip(outcome.value.content, 300)}`
7488
+ };
7489
+ }
7490
+ return { succeeded: true, value: { command, usage: outcome.value.usage } };
7491
+ }
7492
+
7493
+ // src/replay-batch.ts
7494
+ import { execFile } from "child_process";
7495
+ import { createHash as createHash8 } from "crypto";
7496
+ import { appendFileSync, mkdirSync as mkdirSync2, mkdtempSync, writeFileSync as writeFileSync2 } from "fs";
7497
+ import { tmpdir as tmpdir3 } from "os";
7498
+ import { join as join21 } from "path";
7499
+ import { promisify } from "util";
7500
+ import {
7501
+ InMemoryTraceStore as InMemoryTraceStore3,
7502
+ runCounterfactual as runCounterfactual2
7503
+ } from "@tangle-network/agent-eval";
7504
+ var execFileAsync = promisify(execFile);
7505
+ function derivedImageTag(image, cwd) {
7506
+ const digest = createHash8("sha256").update(`${image}
7507
+ ${cwd}`).digest("hex").slice(0, 12);
7508
+ return `ctb-replay:${digest}-uid1000`;
7509
+ }
7510
+ function dockerImagePreparer(options = {}) {
7511
+ const pullTimeoutMs = options.pullTimeoutMs ?? 12e5;
7512
+ const buildTimeoutMs = options.buildTimeoutMs ?? 9e5;
7513
+ const imageExists = async (tag) => {
7514
+ try {
7515
+ await execFileAsync("docker", ["image", "inspect", tag], { maxBuffer: 8 * 1024 * 1024 });
7516
+ return true;
7517
+ } catch {
7518
+ return false;
7519
+ }
7520
+ };
7521
+ const errorTail = (err) => {
7522
+ const raw = err && typeof err === "object" && "stderr" in err && typeof err.stderr === "string" && err.stderr.length > 0 ? err.stderr : err instanceof Error ? err.message : String(err);
7523
+ return raw.trim().split("\n").slice(-3).join(" | ").slice(0, 400);
7524
+ };
7525
+ return {
7526
+ async ensure(image, cwd) {
7527
+ if (cwd === "/") {
7528
+ if (await imageExists(image)) {
7529
+ return { succeeded: true, value: { derivedImage: image, pulled: false, built: false } };
7530
+ }
7531
+ try {
7532
+ await execFileAsync("docker", ["pull", image], {
7533
+ timeout: pullTimeoutMs,
7534
+ maxBuffer: 32 * 1024 * 1024
7535
+ });
7536
+ } catch (err) {
7537
+ return { succeeded: false, error: `pull ${image}: ${errorTail(err)}` };
7538
+ }
7539
+ return { succeeded: true, value: { derivedImage: image, pulled: true, built: false } };
7540
+ }
7541
+ const derived = derivedImageTag(image, cwd);
7542
+ if (await imageExists(derived)) {
7543
+ return { succeeded: true, value: { derivedImage: derived, pulled: false, built: false } };
7544
+ }
7545
+ let pulled = false;
7546
+ if (!await imageExists(image)) {
7547
+ try {
7548
+ await execFileAsync("docker", ["pull", image], {
7549
+ timeout: pullTimeoutMs,
7550
+ maxBuffer: 32 * 1024 * 1024
7551
+ });
7552
+ pulled = true;
7553
+ } catch (err) {
7554
+ return { succeeded: false, error: `pull ${image}: ${errorTail(err)}` };
7555
+ }
7556
+ }
7557
+ const contextDir = mkdtempSync(join21(tmpdir3(), "ctb-replay-image-"));
7558
+ const quotedCwd = `'${cwd.replaceAll("'", `'\\''`)}'`;
7559
+ writeFileSync2(
7560
+ join21(contextDir, "Dockerfile"),
7561
+ `FROM ${image}
7562
+ RUN chown -R 1000:1000 ${quotedCwd}
7563
+ `
7564
+ );
7565
+ try {
7566
+ await execFileAsync("docker", ["build", "-t", derived, contextDir], {
7567
+ timeout: buildTimeoutMs,
7568
+ maxBuffer: 32 * 1024 * 1024
7569
+ });
7570
+ } catch (err) {
7571
+ return { succeeded: false, error: `build ${derived} from ${image}: ${errorTail(err)}` };
7572
+ }
7573
+ return { succeeded: true, value: { derivedImage: derived, pulled, built: true } };
7574
+ }
7575
+ };
7576
+ }
7577
+ function mulberry32(seed) {
7578
+ let state = seed >>> 0;
7579
+ return () => {
7580
+ state = state + 1831565813 >>> 0;
7581
+ let t = state;
7582
+ t = Math.imul(t ^ t >>> 15, t | 1);
7583
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
7584
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
7585
+ };
7586
+ }
7587
+ function seededSample(items, size, seed) {
7588
+ const pool = [...items];
7589
+ const random = mulberry32(seed);
7590
+ for (let i = pool.length - 1; i > 0; i--) {
7591
+ const j = Math.floor(random() * (i + 1));
7592
+ [pool[i], pool[j]] = [pool[j], pool[i]];
7593
+ }
7594
+ return new Set(pool.slice(0, size));
7595
+ }
7596
+ function caseOutDirName(row) {
7597
+ return `${row.corpus}--${row.trajId}`.replaceAll(/[^A-Za-z0-9._-]/g, "_").slice(0, 180);
7598
+ }
7599
+ async function executeArmB(replayCase, fixCommand, backend, signature, stepTimeoutMs, prefixLimit, onProgress) {
7600
+ const store = new InMemoryTraceStore3();
7601
+ const { runId } = await ingestCodeTraceBenchSteps(store, replayCase.steps, replayCase.trajId);
7602
+ const runner = new SandboxCounterfactualRunner(backend, {
7603
+ cwd: replayCase.cwd,
7604
+ stepTimeoutMs,
7605
+ prefixLimit,
7606
+ onProgress
7607
+ });
7608
+ await runCounterfactual2(
7609
+ store,
7610
+ runId,
7611
+ {
7612
+ kind: "custom",
7613
+ at: replayCase.k - 1,
7614
+ describe: "arm-B corrected step",
7615
+ apply: (step) => ({
7616
+ ...step,
7617
+ span: { ...step.span, args: { command: fixCommand } }
7618
+ })
7619
+ },
7620
+ runner
7621
+ );
7622
+ const exec = runner.lastArm;
7623
+ if (!exec) throw new Error("arm B finished without executing the corrected step");
7624
+ const output = `${exec.stdout}
7625
+ ${exec.stderr}`;
7626
+ return {
7627
+ exitCode: exec.exitCode,
7628
+ prefixExecuted: runner.lastPrefix?.prefixExecuted ?? 0,
7629
+ prefixDivergences: runner.lastPrefix?.prefixDivergences.length ?? 0,
7630
+ failureVanished: exec.exitCode === 0 && (signature ? !output.includes(signature) : true),
7631
+ stdout: exec.stdout,
7632
+ stderr: exec.stderr
7633
+ };
7634
+ }
7635
+ function rate(numerator, denominator) {
7636
+ return denominator > 0 ? numerator / denominator : null;
7637
+ }
7638
+ async function runReplayBatch(options) {
7639
+ const onProgress = options.onProgress ?? (() => {
7640
+ });
7641
+ const enumeration = enumerateReplayableCases(options.corpora);
7642
+ let selected = enumeration.replayable;
7643
+ if (options.caseFilter) {
7644
+ selected = selected.filter((c) => c.trajId.includes(options.caseFilter));
7645
+ }
7646
+ if (options.caseLimit !== void 0) selected = selected.slice(0, options.caseLimit);
7647
+ onProgress(
7648
+ `enumerated ${enumeration.labelEntryCount} label entries \u2192 ${enumeration.replayable.length} replayable, ${enumeration.excluded.length} excluded; executing ${selected.length}`
7649
+ );
7650
+ const preparer = options.preparer ?? dockerImagePreparer();
7651
+ const backendFactory = options.backendFactory ?? ((derivedImage) => sandboxReplayBackend({
7652
+ image: derivedImage,
7653
+ apiKey: requireOption2(options.apiKey, "apiKey"),
7654
+ baseUrl: requireOption2(options.baseUrl, "baseUrl"),
7655
+ maxLifetimeSeconds: options.maxLifetimeSeconds ?? 3600
7656
+ }));
7657
+ mkdirSync2(options.out, { recursive: true });
7658
+ const progressPath = join21(options.out, "cases.jsonl");
7659
+ const rows = [];
7660
+ const pullFailures = [];
7661
+ const verdictByTraj = /* @__PURE__ */ new Map();
7662
+ for (const [index, replayCase] of selected.entries()) {
7663
+ const caseStart = Date.now();
7664
+ const label = `[${index + 1}/${selected.length}] ${replayCase.corpus}/${replayCase.trajId}`;
7665
+ onProgress(`${label}: preparing image ${replayCase.image}`);
7666
+ const preparation = await preparer.ensure(replayCase.image, replayCase.cwd);
7667
+ const base = {
7668
+ corpus: replayCase.corpus,
7669
+ trajId: replayCase.trajId,
7670
+ image: replayCase.image,
7671
+ cwd: replayCase.cwd,
7672
+ cwdSource: replayCase.cwdSource,
7673
+ k: replayCase.k,
7674
+ stepCount: replayCase.steps.length,
7675
+ goldIncorrectSteps: replayCase.goldIncorrectSteps,
7676
+ submitGoldsSkipped: replayCase.submitGoldsSkipped,
7677
+ recordedReturncodeAtK: replayCase.recordedReturncodeAtK
7678
+ };
7679
+ if (!preparation.succeeded) {
7680
+ pullFailures.push({
7681
+ corpus: replayCase.corpus,
7682
+ trajId: replayCase.trajId,
7683
+ image: replayCase.image,
7684
+ error: preparation.error
7685
+ });
7686
+ const row = {
7687
+ ...base,
7688
+ derivedImage: null,
7689
+ signature: null,
7690
+ status: "image-unavailable",
7691
+ error: preparation.error,
7692
+ imagePulled: false,
7693
+ imageBuilt: false,
7694
+ prefixExecuted: null,
7695
+ prefixDivergences: null,
7696
+ prefixDivergencePct: null,
7697
+ armAExit: null,
7698
+ armAReturncodeMatch: false,
7699
+ armASignatureMatch: false,
7700
+ replayed: false,
7701
+ fix: null,
7702
+ wallMs: Date.now() - caseStart
7703
+ };
7704
+ rows.push(row);
7705
+ appendFileSync(progressPath, `${JSON.stringify(row)}
7706
+ `);
7707
+ onProgress(`${label}: image unavailable \u2014 ${preparation.error}`);
7708
+ continue;
7709
+ }
7710
+ const { derivedImage, pulled, built } = preparation.value;
7711
+ const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
7712
+ const caseOut = join21(options.out, caseOutDirName(replayCase));
7713
+ onProgress(`${label}: arm A on ${derivedImage} (k=${replayCase.k}, timeout ${stepTimeoutMs}ms)`);
7714
+ try {
7715
+ const verdict = await replayVerify({
7716
+ stepsPath: replayCase.stepsPath,
7717
+ image: derivedImage,
7718
+ at: replayCase.k,
7719
+ cwd: replayCase.cwd,
7720
+ out: caseOut,
7721
+ caseId: replayCase.trajId,
7722
+ stepTimeoutMs,
7723
+ prefixLimit: options.prefixLimit,
7724
+ backend: backendFactory(derivedImage),
7725
+ onProgress: (message) => onProgress(`${label}: ${message}`)
7726
+ });
7727
+ verdictByTraj.set(replayCase.trajId, verdict);
7728
+ const divergencePct = verdict.prefixExecuted > 0 ? verdict.prefixDivergences.length / verdict.prefixExecuted * 100 : 0;
7729
+ const returncodeMatch = verdict.recordedReturncode !== null && verdict.armA.exitCode === verdict.recordedReturncode;
7730
+ const row = {
7731
+ ...base,
7732
+ derivedImage,
7733
+ signature: verdict.signature,
7734
+ status: "ok",
7735
+ error: null,
7736
+ imagePulled: pulled,
7737
+ imageBuilt: built,
7738
+ prefixExecuted: verdict.prefixExecuted,
7739
+ prefixDivergences: verdict.prefixDivergences.length,
7740
+ prefixDivergencePct: Number(divergencePct.toFixed(1)),
7741
+ armAExit: verdict.armA.exitCode,
7742
+ armAReturncodeMatch: returncodeMatch,
7743
+ armASignatureMatch: verdict.armA.failureSignatureMatch,
7744
+ replayed: divergencePct <= 10 && returncodeMatch,
7745
+ fix: null,
7746
+ wallMs: Date.now() - caseStart
7747
+ };
7748
+ rows.push(row);
7749
+ appendFileSync(progressPath, `${JSON.stringify(row)}
7750
+ `);
7751
+ onProgress(
7752
+ `${label}: armA exit=${verdict.armA.exitCode} rcMatch=${returncodeMatch} divergences=${verdict.prefixDivergences.length}/${verdict.prefixExecuted}`
7753
+ );
7754
+ } catch (err) {
7755
+ const message = err instanceof Error ? err.message : String(err);
7756
+ const row = {
7757
+ ...base,
7758
+ derivedImage,
7759
+ signature: null,
7760
+ status: "replay-error",
7761
+ error: message.slice(0, 500),
7762
+ imagePulled: pulled,
7763
+ imageBuilt: built,
7764
+ prefixExecuted: null,
7765
+ prefixDivergences: null,
7766
+ prefixDivergencePct: null,
7767
+ armAExit: null,
7768
+ armAReturncodeMatch: false,
7769
+ armASignatureMatch: false,
7770
+ replayed: false,
7771
+ fix: null,
7772
+ wallMs: Date.now() - caseStart
7773
+ };
7774
+ rows.push(row);
7775
+ appendFileSync(progressPath, `${JSON.stringify(row)}
7776
+ `);
7777
+ onProgress(`${label}: replay error \u2014 ${message.slice(0, 200)}`);
7778
+ }
7779
+ }
7780
+ let llm = null;
7781
+ if (options.fix === "generate") {
7782
+ const caller = options.fixCaller;
7783
+ if (!caller) throw new Error("replay-verify-batch: fix=generate requires a fixCaller");
7784
+ const maxFixCases = options.maxFixCases ?? 30;
7785
+ const seed = options.seed ?? 17;
7786
+ const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
7787
+ const sampled = eligible.length > maxFixCases ? seededSample(eligible, maxFixCases, seed) : new Set(eligible);
7788
+ if (eligible.length > maxFixCases) {
7789
+ onProgress(
7790
+ `fix phase: ${eligible.length} eligible > cap ${maxFixCases}; seeded sample (seed=${seed})`
7791
+ );
7792
+ }
7793
+ let calls = 0;
7794
+ let failures = 0;
7795
+ let promptTokens = 0;
7796
+ let completionTokens = 0;
7797
+ for (const row of eligible) {
7798
+ const index = rows.indexOf(row);
7799
+ if (!sampled.has(row)) {
7800
+ rows[index] = {
7801
+ ...row,
7802
+ fix: {
7803
+ attempted: false,
7804
+ sampledOut: true,
7805
+ command: null,
7806
+ llmError: null,
7807
+ usage: null,
7808
+ armBExit: null,
7809
+ armBPrefixExecuted: null,
7810
+ armBPrefixDivergences: null,
7811
+ failureVanished: null,
7812
+ armBError: null
7813
+ }
7814
+ };
7815
+ continue;
7816
+ }
7817
+ const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
7818
+ const label = `fix ${row.corpus}/${row.trajId}`;
7819
+ onProgress(`${label}: generating corrected command`);
7820
+ calls += 1;
7821
+ const generated = await generateFixCommand(caller, {
7822
+ taskStatement: replayCase.taskStatement,
7823
+ steps: replayCase.steps,
7824
+ k: replayCase.k
7825
+ });
7826
+ if (!generated.succeeded) {
7827
+ failures += 1;
7828
+ rows[index] = {
7829
+ ...row,
7830
+ fix: {
7831
+ attempted: true,
7832
+ sampledOut: false,
7833
+ command: null,
7834
+ llmError: generated.error,
7835
+ usage: null,
7836
+ armBExit: null,
7837
+ armBPrefixExecuted: null,
7838
+ armBPrefixDivergences: null,
7839
+ failureVanished: null,
7840
+ armBError: null
7841
+ }
7842
+ };
7843
+ onProgress(`${label}: LLM failed \u2014 ${generated.error.slice(0, 200)}`);
7844
+ continue;
7845
+ }
7846
+ promptTokens += generated.value.usage?.promptTokens ?? 0;
7847
+ completionTokens += generated.value.usage?.completionTokens ?? 0;
7848
+ const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
7849
+ const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
7850
+ onProgress(`${label}: arm B \u2014 ${generated.value.command.split("\n")[0].slice(0, 120)}`);
7851
+ try {
7852
+ const armB = await executeArmB(
7853
+ replayCase,
7854
+ generated.value.command,
7855
+ backendFactory(row.derivedImage),
7856
+ signature,
7857
+ stepTimeoutMs,
7858
+ options.prefixLimit,
7859
+ (message) => onProgress(`${label}: ${message}`)
7860
+ );
7861
+ rows[index] = {
7862
+ ...row,
7863
+ fix: {
7864
+ attempted: true,
7865
+ sampledOut: false,
7866
+ command: generated.value.command,
7867
+ llmError: null,
7868
+ usage: generated.value.usage,
7869
+ armBExit: armB.exitCode,
7870
+ armBPrefixExecuted: armB.prefixExecuted,
7871
+ armBPrefixDivergences: armB.prefixDivergences,
7872
+ failureVanished: armB.failureVanished,
7873
+ armBError: null
7874
+ }
7875
+ };
7876
+ const caseOut = join21(options.out, caseOutDirName(row));
7877
+ mkdirSync2(caseOut, { recursive: true });
7878
+ writeFileSync2(
7879
+ join21(caseOut, "armB-result.json"),
7880
+ `${JSON.stringify({ command: generated.value.command, ...armB }, null, 2)}
7881
+ `
7882
+ );
7883
+ onProgress(`${label}: armB exit=${armB.exitCode} failureVanished=${armB.failureVanished}`);
7884
+ } catch (err) {
7885
+ const message = err instanceof Error ? err.message : String(err);
7886
+ rows[index] = {
7887
+ ...row,
7888
+ fix: {
7889
+ attempted: true,
7890
+ sampledOut: false,
7891
+ command: generated.value.command,
7892
+ llmError: null,
7893
+ usage: generated.value.usage,
7894
+ armBExit: null,
7895
+ armBPrefixExecuted: null,
7896
+ armBPrefixDivergences: null,
7897
+ failureVanished: null,
7898
+ armBError: message.slice(0, 500)
7899
+ }
7900
+ };
7901
+ onProgress(`${label}: arm B error \u2014 ${message.slice(0, 200)}`);
7902
+ }
7903
+ }
7904
+ llm = {
7905
+ model: options.fixModelLabel ?? "unknown",
7906
+ calls,
7907
+ failures,
7908
+ promptTokens,
7909
+ completionTokens
7910
+ };
7911
+ }
7912
+ const executedRows = rows.filter((r) => r.status === "ok");
7913
+ const replayed = executedRows.filter((r) => r.replayed);
7914
+ const signatureStrict = executedRows.filter(
7915
+ (r) => r.replayed && r.armASignatureMatch
7916
+ );
7917
+ const armBExecuted = rows.filter((r) => r.fix?.attempted && r.fix.failureVanished !== null);
7918
+ const flipped = armBExecuted.filter((r) => r.fix.failureVanished === true);
7919
+ const armBNonzeroRc = armBExecuted.filter(
7920
+ (r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0
7921
+ );
7922
+ const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
7923
+ const excludedByReason = {};
7924
+ for (const excluded of enumeration.excluded) {
7925
+ excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
7926
+ }
7927
+ const submitGoldsByCorpus = {};
7928
+ const submitEntry = (corpus) => submitGoldsByCorpus[corpus] ??= { submitOnlyCases: 0, goldsSkippedWithinReplayable: 0 };
7929
+ for (const excluded of enumeration.excluded) {
7930
+ if (excluded.reason === "gold-only-submit-step") submitEntry(excluded.corpus).submitOnlyCases += 1;
7931
+ }
7932
+ for (const replayCase of enumeration.replayable) {
7933
+ if (replayCase.submitGoldsSkipped > 0) {
7934
+ submitEntry(replayCase.corpus).goldsSkippedWithinReplayable += replayCase.submitGoldsSkipped;
7935
+ }
7936
+ }
7937
+ const report = {
7938
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7939
+ corpora: options.corpora.map((c) => ({
7940
+ name: c.name,
7941
+ labelsPath: c.labelsPath,
7942
+ preparedDir: c.preparedDir
7943
+ })),
7944
+ totals: {
7945
+ labelEntries: enumeration.labelEntryCount,
7946
+ replayable: enumeration.replayable.length,
7947
+ executed: selected.length,
7948
+ excludedByReason,
7949
+ submitGoldsByCorpus
7950
+ },
7951
+ headline: {
7952
+ replayabilityRate: {
7953
+ numerator: replayed.length,
7954
+ denominator: selected.length,
7955
+ value: rate(replayed.length, selected.length)
7956
+ },
7957
+ signatureStrictRate: {
7958
+ numerator: signatureStrict.length,
7959
+ denominator: selected.length,
7960
+ value: rate(signatureStrict.length, selected.length)
7961
+ },
7962
+ fixFlipRate: options.fix === "generate" ? {
7963
+ numerator: flipped.length,
7964
+ denominator: armBExecuted.length,
7965
+ value: rate(flipped.length, armBExecuted.length)
7966
+ } : null,
7967
+ fixFlipRateNonzeroRc: options.fix === "generate" ? {
7968
+ numerator: flippedNonzeroRc.length,
7969
+ denominator: armBNonzeroRc.length,
7970
+ value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
7971
+ } : null
7972
+ },
7973
+ llm,
7974
+ excluded: enumeration.excluded,
7975
+ pullFailures,
7976
+ cases: rows
7977
+ };
7978
+ writeFileSync2(join21(options.out, "batch-report.json"), `${JSON.stringify(report, null, 2)}
7979
+ `);
7980
+ writeFileSync2(join21(options.out, "batch-report.md"), renderBatchReport(report));
7981
+ return report;
7982
+ }
7983
+ function requireOption2(value, name) {
7984
+ if (value === void 0) throw new Error(`replay-verify-batch: ${name} is required`);
7985
+ return value;
7986
+ }
7987
+ function pct(value) {
7988
+ return value === null ? "\u2014" : `${(value * 100).toFixed(1)}%`;
7989
+ }
7990
+ function renderBatchReport(report) {
7991
+ const lines = [];
7992
+ const { headline, totals } = report;
7993
+ lines.push("# Replay-verify batch report");
7994
+ lines.push("");
7995
+ lines.push(`Generated ${report.generatedAt}.`);
7996
+ lines.push("");
7997
+ lines.push("## Headline");
7998
+ lines.push("");
7999
+ lines.push(
8000
+ `- **Replayability rate: ${pct(headline.replayabilityRate.value)}** (${headline.replayabilityRate.numerator}/${headline.replayabilityRate.denominator} replayable cases where the prefix replayed with \u226410% returncode divergence AND arm A reproduced the recorded returncode at the gold step k).`
8001
+ );
8002
+ lines.push(
8003
+ `- Signature-strict rate: ${pct(headline.signatureStrictRate.value)} (${headline.signatureStrictRate.numerator}/${headline.signatureStrictRate.denominator}; additionally requires the recorded error substring in arm A output).`
8004
+ );
8005
+ if (headline.fixFlipRate) {
8006
+ lines.push(
8007
+ `- **Fix-flip rate: ${pct(headline.fixFlipRate.value)}** (${headline.fixFlipRate.numerator}/${headline.fixFlipRate.denominator} arm-B-executed cases where the generated fix made the failure vanish).`
8008
+ );
8009
+ }
8010
+ if (headline.fixFlipRateNonzeroRc) {
8011
+ lines.push(
8012
+ `- 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
+ );
8014
+ }
8015
+ lines.push("");
8016
+ lines.push("## Enumeration");
8017
+ lines.push("");
8018
+ lines.push(
8019
+ `${totals.labelEntries} label entries across ${report.corpora.length} corpora \u2192 ${totals.replayable} replayable (SWE-style docker image + \u22651 gold incorrect step), ${totals.executed} executed.`
8020
+ );
8021
+ lines.push("");
8022
+ lines.push("| exclusion reason | count |");
8023
+ lines.push("| --- | --- |");
8024
+ for (const [reason, count2] of Object.entries(totals.excludedByReason).sort((a, b) => b[1] - a[1])) {
8025
+ lines.push(`| ${reason} | ${count2} |`);
8026
+ }
8027
+ const submitEntries = Object.entries(totals.submitGoldsByCorpus);
8028
+ if (submitEntries.length > 0) {
8029
+ lines.push("");
8030
+ lines.push(
8031
+ "Submit-command golds are never counterfactual targets (a gold on the submit step marks a bad submit decision, not a failed command):"
8032
+ );
8033
+ lines.push("");
8034
+ lines.push("| corpus | cases excluded (all golds = submit) | golds skipped within replayable cases |");
8035
+ lines.push("| --- | --- | --- |");
8036
+ for (const [corpus, stats] of submitEntries.sort((a, b) => a[0].localeCompare(b[0]))) {
8037
+ lines.push(`| ${corpus} | ${stats.submitOnlyCases} | ${stats.goldsSkippedWithinReplayable} |`);
8038
+ }
8039
+ }
8040
+ if (report.pullFailures.length > 0) {
8041
+ lines.push("");
8042
+ lines.push("## Image pull/build failures");
8043
+ lines.push("");
8044
+ lines.push("| corpus | trajectory | image | error |");
8045
+ lines.push("| --- | --- | --- | --- |");
8046
+ for (const failure of report.pullFailures) {
8047
+ lines.push(
8048
+ `| ${failure.corpus} | ${failure.trajId} | \`${failure.image}\` | ${failure.error.replaceAll("|", "\\|")} |`
8049
+ );
8050
+ }
8051
+ }
8052
+ lines.push("");
8053
+ lines.push("## Per-case results");
8054
+ lines.push("");
8055
+ lines.push(
8056
+ "| corpus | trajectory | k | rc@k | prefix | div | div% | armA exit | rc match | sig match | replayed | fix | armB exit | vanished | wall s |"
8057
+ );
8058
+ lines.push(
8059
+ "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
8060
+ );
8061
+ for (const row of report.cases) {
8062
+ const fix = row.fix;
8063
+ const fixCell = !fix ? "\u2014" : fix.sampledOut ? "sampled-out" : fix.llmError ? "llm-failed" : fix.armBError ? "armB-error" : "generated";
8064
+ lines.push(
8065
+ [
8066
+ row.corpus,
8067
+ row.trajId.length > 48 ? `${row.trajId.slice(0, 45)}\u2026` : row.trajId,
8068
+ row.k,
8069
+ row.recordedReturncodeAtK ?? "null",
8070
+ row.prefixExecuted ?? "\u2014",
8071
+ row.prefixDivergences ?? "\u2014",
8072
+ row.prefixDivergencePct ?? "\u2014",
8073
+ row.status === "ok" ? row.armAExit : row.status,
8074
+ row.armAReturncodeMatch ? "yes" : "no",
8075
+ row.armASignatureMatch ? "yes" : "no",
8076
+ row.replayed ? "**yes**" : "no",
8077
+ fixCell,
8078
+ fix?.armBExit ?? "\u2014",
8079
+ fix?.failureVanished === null || fix === null ? "\u2014" : fix.failureVanished ? "**yes**" : "no",
8080
+ (row.wallMs / 1e3).toFixed(1)
8081
+ ].join(" | ")
8082
+ );
8083
+ }
8084
+ if (report.llm) {
8085
+ lines.push("");
8086
+ lines.push("## LLM fix generation");
8087
+ lines.push("");
8088
+ lines.push(
8089
+ `Model ${report.llm.model}: ${report.llm.calls} calls (${report.llm.failures} failed), ${report.llm.promptTokens} prompt + ${report.llm.completionTokens} completion tokens.`
8090
+ );
8091
+ }
8092
+ lines.push("");
8093
+ return lines.join("\n");
8094
+ }
8095
+ function parseReplayBatchArgs(argv) {
8096
+ const corpora = [];
8097
+ const values = /* @__PURE__ */ new Map();
8098
+ let enumerateOnly = false;
8099
+ for (let i = 0; i < argv.length; i++) {
8100
+ const arg = argv[i];
8101
+ if (arg === "--help" || arg === "-h") return "help";
8102
+ if (arg === "--enumerate-only") {
8103
+ enumerateOnly = true;
8104
+ continue;
8105
+ }
8106
+ if (!arg.startsWith("--")) throw new Error(`unexpected positional argument: ${arg}`);
8107
+ const value = argv[++i];
8108
+ if (value === void 0) throw new Error(`missing value for ${arg}`);
8109
+ if (arg === "--corpus") corpora.push(parseCorpusFlag(value));
8110
+ else values.set(arg, value);
8111
+ }
8112
+ if (corpora.length === 0) {
8113
+ throw new Error("replay-verify-batch: at least one --corpus name=<labels>::<prepared> is required");
8114
+ }
8115
+ const out = values.get("--out");
8116
+ if (!out && !enumerateOnly) throw new Error("replay-verify-batch: --out is required");
8117
+ const optionalNumber = (flag) => {
8118
+ const v = values.get(flag);
8119
+ if (v === void 0) return void 0;
8120
+ const n = Number(v);
8121
+ if (!Number.isFinite(n)) throw new Error(`replay-verify-batch: ${flag} must be a number, got ${v}`);
8122
+ return n;
8123
+ };
8124
+ const fix = values.get("--fix") ?? "none";
8125
+ if (fix !== "none" && fix !== "generate") {
8126
+ throw new Error(`replay-verify-batch: --fix must be none or generate, got ${fix}`);
8127
+ }
8128
+ return {
8129
+ corpora,
8130
+ out: out ?? ".",
8131
+ fix,
8132
+ fixModel: values.get("--fix-model") ?? "glm-5.2",
8133
+ fixBaseUrl: values.get("--fix-base-url") ?? "https://api.z.ai/api/coding/paas/v4",
8134
+ fixApiKeyEnv: values.get("--fix-api-key-env") ?? "ZAI_GLM_API_KEY",
8135
+ maxFixCases: optionalNumber("--max-fix-cases") ?? 30,
8136
+ seed: optionalNumber("--seed") ?? 17,
8137
+ stepTimeoutMs: optionalNumber("--step-timeout"),
8138
+ prefixLimit: optionalNumber("--prefix-limit"),
8139
+ caseFilter: values.get("--case-filter"),
8140
+ caseLimit: optionalNumber("--case-limit"),
8141
+ baseUrl: values.get("--base-url") ?? process.env.SANDBOX_API_URL ?? "http://127.0.0.1:4097",
8142
+ apiKeyEnv: values.get("--api-key-env") ?? "SANDBOX_API_KEY",
8143
+ maxLifetimeSeconds: optionalNumber("--max-lifetime"),
8144
+ enumerateOnly
8145
+ };
8146
+ }
8147
+ function replayBatchUsage() {
8148
+ return `traces replay-verify-batch \u2014 measure replayability and fix-flip rates across gold-labeled corpora
8149
+
8150
+ Usage:
8151
+ traces replay-verify-batch \\
8152
+ --corpus NAME=<labels.json>::<prepared-dir> [--corpus ...] \\
8153
+ --out DIR [--fix none|generate] [--enumerate-only] \\
8154
+ [--fix-model glm-5.2] [--fix-base-url URL] [--fix-api-key-env ZAI_GLM_API_KEY] \\
8155
+ [--max-fix-cases 30] [--seed 17] [--step-timeout MS] [--prefix-limit N] \\
8156
+ [--case-filter SUBSTRING] [--case-limit N] \\
8157
+ [--base-url URL] [--api-key-env SANDBOX_API_KEY] [--max-lifetime SECONDS]
8158
+
8159
+ Replayable = the raw trajectory carries info.docker_config.base_image AND the
8160
+ labels mark at least one gold incorrect step that is a real mid-trajectory
8161
+ action. k = the first such gold step; golds whose action is the agent's
8162
+ submit command (COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT) are skipped and
8163
+ counted, and cases whose golds are all submit steps are excluded as
8164
+ gold-only-submit-step. Excluded cases and image pull failures are report
8165
+ rows, never skipped silently. --enumerate-only prints the enumeration
8166
+ without touching a sandbox.
8167
+
8168
+ --fix generate runs ONE LLM call per arm-A-reproduced case (cap
8169
+ --max-fix-cases, seeded sample beyond it) and executes the corrected command
8170
+ as arm B in a fresh sandbox.
8171
+
8172
+ Outputs batch-report.json, batch-report.md, cases.jsonl (incremental), and one
8173
+ directory per case with the full replay-verify artifacts.
8174
+ See docs/replay-verify.md for orchestrator setup and honest limits.
8175
+ `;
8176
+ }
8177
+ async function cmdReplayVerifyBatch(argv) {
8178
+ const parsed = parseReplayBatchArgs(argv);
8179
+ if (parsed === "help") {
8180
+ process.stdout.write(replayBatchUsage());
8181
+ return;
8182
+ }
8183
+ if (parsed.enumerateOnly) {
8184
+ const enumeration = enumerateReplayableCases(parsed.corpora);
8185
+ process.stdout.write(
8186
+ `${JSON.stringify(
8187
+ {
8188
+ labelEntries: enumeration.labelEntryCount,
8189
+ replayable: enumeration.replayable.map((c) => ({
8190
+ corpus: c.corpus,
8191
+ trajId: c.trajId,
8192
+ image: c.image,
8193
+ cwd: c.cwd,
8194
+ cwdSource: c.cwdSource,
8195
+ k: c.k,
8196
+ submitGoldsSkipped: c.submitGoldsSkipped,
8197
+ recordedReturncodeAtK: c.recordedReturncodeAtK,
8198
+ goldIncorrectSteps: c.goldIncorrectSteps
8199
+ })),
8200
+ excluded: enumeration.excluded
8201
+ },
8202
+ null,
8203
+ 2
8204
+ )}
8205
+ `
8206
+ );
8207
+ return;
8208
+ }
8209
+ const apiKey = process.env[parsed.apiKeyEnv];
8210
+ if (!apiKey) {
8211
+ throw new Error(
8212
+ `replay-verify-batch: env var ${parsed.apiKeyEnv} is empty \u2014 pass --api-key-env or export it`
8213
+ );
8214
+ }
8215
+ let fixCaller;
8216
+ if (parsed.fix === "generate") {
8217
+ const fixApiKey = process.env[parsed.fixApiKeyEnv];
8218
+ if (!fixApiKey) {
8219
+ throw new Error(
8220
+ `replay-verify-batch: env var ${parsed.fixApiKeyEnv} is empty \u2014 pass --fix-api-key-env or export it`
8221
+ );
8222
+ }
8223
+ fixCaller = zaiChatCaller({
8224
+ baseUrl: parsed.fixBaseUrl,
8225
+ apiKey: fixApiKey,
8226
+ model: parsed.fixModel
8227
+ });
8228
+ }
8229
+ const report = await runReplayBatch({
8230
+ corpora: parsed.corpora,
8231
+ out: parsed.out,
8232
+ baseUrl: parsed.baseUrl,
8233
+ apiKey,
8234
+ fix: parsed.fix,
8235
+ fixCaller,
8236
+ fixModelLabel: parsed.fixModel,
8237
+ maxFixCases: parsed.maxFixCases,
8238
+ seed: parsed.seed,
8239
+ stepTimeoutMs: parsed.stepTimeoutMs,
8240
+ prefixLimit: parsed.prefixLimit,
8241
+ caseFilter: parsed.caseFilter,
8242
+ caseLimit: parsed.caseLimit,
8243
+ maxLifetimeSeconds: parsed.maxLifetimeSeconds,
8244
+ onProgress: (message) => process.stderr.write(`${message}
8245
+ `)
8246
+ });
8247
+ const { replayabilityRate, fixFlipRate } = report.headline;
8248
+ process.stderr.write(
8249
+ `replay-verify-batch: replayability ${replayabilityRate.numerator}/${replayabilityRate.denominator}` + (fixFlipRate ? `, fix-flip ${fixFlipRate.numerator}/${fixFlipRate.denominator}` : "") + ` \u2192 ${parsed.out}
8250
+ `
8251
+ );
8252
+ }
8253
+
7144
8254
  // src/external.ts
7145
8255
  import { spawn } from "child_process";
7146
8256
  import { readdir as readdir14, readFile as readFile7 } from "fs/promises";
@@ -7419,13 +8529,13 @@ function terminateWindowsProcessTree(child) {
7419
8529
  forceKill(child);
7420
8530
  return Promise.resolve();
7421
8531
  }
7422
- return new Promise((resolve7) => {
8532
+ return new Promise((resolve8) => {
7423
8533
  let finished = false;
7424
8534
  const finish = () => {
7425
8535
  if (finished) return;
7426
8536
  finished = true;
7427
8537
  forceKill(child);
7428
- resolve7();
8538
+ resolve8();
7429
8539
  };
7430
8540
  let killer;
7431
8541
  try {
@@ -7452,7 +8562,7 @@ function signalProcess(pid, signal) {
7452
8562
  }
7453
8563
  }
7454
8564
  function readProcessTableWithPs() {
7455
- return new Promise((resolve7, reject) => {
8565
+ return new Promise((resolve8, reject) => {
7456
8566
  let child;
7457
8567
  try {
7458
8568
  child = spawn("ps", ["-A", "-o", "pid=", "-o", "ppid="], {
@@ -7506,7 +8616,7 @@ function readProcessTableWithPs() {
7506
8616
  reject(new Error("ps: returned no process records"));
7507
8617
  return;
7508
8618
  }
7509
- resolve7(rows);
8619
+ resolve8(rows);
7510
8620
  });
7511
8621
  });
7512
8622
  });
@@ -7641,7 +8751,7 @@ function runCommand(command, args = [], opts = {}) {
7641
8751
  throw new RangeError("maxBuffer must be a positive safe integer");
7642
8752
  }
7643
8753
  if (opts.signal?.aborted) return Promise.reject(abortError(opts.signal));
7644
- return new Promise((resolve7, reject) => {
8754
+ return new Promise((resolve8, reject) => {
7645
8755
  let child;
7646
8756
  try {
7647
8757
  child = spawn(command, [...args], { detached: process.platform !== "win32" });
@@ -7673,7 +8783,7 @@ function runCommand(command, args = [], opts = {}) {
7673
8783
  } else if (spawnError) {
7674
8784
  reject(spawnError);
7675
8785
  } else {
7676
- resolve7({ stdout: out, stderr: err, code: closeCode });
8786
+ resolve8({ stdout: out, stderr: err, code: closeCode });
7677
8787
  }
7678
8788
  };
7679
8789
  function requestTermination(error) {
@@ -7902,21 +9012,21 @@ function commandRedactor(spec) {
7902
9012
  }
7903
9013
 
7904
9014
  // src/hodoscope.ts
7905
- import { createHash as createHash8 } from "crypto";
9015
+ import { createHash as createHash9 } from "crypto";
7906
9016
  import { mkdir as mkdir3, mkdtemp as mkdtemp3, readFile as readFile8, readdir as readdir15, writeFile as writeFile4 } from "fs/promises";
7907
- import { endianness, tmpdir as tmpdir3 } from "os";
7908
- import { join as join20, resolve as resolve5 } from "path";
9017
+ import { endianness, tmpdir as tmpdir4 } from "os";
9018
+ import { join as join22, resolve as resolve6 } from "path";
7909
9019
  var HODOSCOPE_VERSION = "0.2.4";
7910
9020
  async function writeHodoscopeInput(otlpPath, options = {}) {
7911
- const explicitDirectory = options.directory ? resolve5(options.directory) : void 0;
7912
- const directory = explicitDirectory ?? await mkdtemp3(join20(tmpdir3(), "traces-hodoscope-input-"));
9021
+ const explicitDirectory = options.directory ? resolve6(options.directory) : void 0;
9022
+ const directory = explicitDirectory ?? await mkdtemp3(join22(tmpdir4(), "traces-hodoscope-input-"));
7913
9023
  if (explicitDirectory) {
7914
9024
  await mkdir3(directory, { recursive: true });
7915
9025
  if ((await readdir15(directory)).length > 0) {
7916
9026
  throw new Error(`Hodoscope explicit directory must be empty: ${directory}`);
7917
9027
  }
7918
9028
  }
7919
- const samples = join20(directory, "samples");
9029
+ const samples = join22(directory, "samples");
7920
9030
  await mkdir3(samples);
7921
9031
  const rows = [];
7922
9032
  for await (const value of readJsonl(otlpPath)) rows.push(parseOpenInferenceRow(value));
@@ -7941,8 +9051,8 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
7941
9051
  throw new Error(`Hodoscope trace '${traceId}' contains no assistant actions`);
7942
9052
  }
7943
9053
  actionCount += actions;
7944
- const file = `${createHash8("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
7945
- await writeFile4(join20(samples, file), `${JSON.stringify(trajectory, null, 2)}
9054
+ const file = `${createHash9("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
9055
+ await writeFile4(join22(samples, file), `${JSON.stringify(trajectory, null, 2)}
7946
9056
  `, "utf8");
7947
9057
  }
7948
9058
  return {
@@ -7964,12 +9074,12 @@ function hodoscopeAnalyzer(options = {}) {
7964
9074
  return {
7965
9075
  name: "hodoscope",
7966
9076
  async analyze(otlpPath, runOptions2 = {}) {
7967
- const outputRoot = options.outputDir ? resolve5(options.outputDir) : tmpdir3();
9077
+ const outputRoot = options.outputDir ? resolve6(options.outputDir) : tmpdir4();
7968
9078
  await mkdir3(outputRoot, { recursive: true });
7969
- const directory = await mkdtemp3(join20(outputRoot, "traces-hodoscope-"));
7970
- const input = join20(directory, "input");
7971
- const analysisFile = join20(directory, "analysis.hodoscope.json");
7972
- const samplesFile = join20(directory, "samples.json");
9079
+ const directory = await mkdtemp3(join22(outputRoot, "traces-hodoscope-"));
9080
+ const input = join22(directory, "input");
9081
+ const analysisFile = join22(directory, "analysis.hodoscope.json");
9082
+ const samplesFile = join22(directory, "samples.json");
7973
9083
  try {
7974
9084
  const prepared = await writeHodoscopeInput(otlpPath, {
7975
9085
  directory: input,
@@ -8168,7 +9278,7 @@ async function readHodoscopeAnalysis(path, expected) {
8168
9278
  throw new Error("Hodoscope analysis.created_at must be a valid date-time");
8169
9279
  }
8170
9280
  const source = requiredString2(value.source, "analysis.source");
8171
- if (resolve5(source) !== resolve5(expected.source)) {
9281
+ if (resolve6(source) !== resolve6(expected.source)) {
8172
9282
  throw new Error(
8173
9283
  `Hodoscope analysis.source '${source}' does not match requested input '${expected.source}'`
8174
9284
  );
@@ -8600,9 +9710,19 @@ var CODING_REDACTION_RULES = [
8600
9710
  id: "private-key",
8601
9711
  pattern: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----[\s\S]*?-----END(?:[A-Z ]+)?PRIVATE KEY-----/g
8602
9712
  },
9713
+ // Match the whole identifier because underscores prevent a word boundary before a secret suffix.
8603
9714
  {
8604
9715
  id: "assigned-secret",
8605
- pattern: /\b(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)\b\s*[:=]\s*["']?[A-Za-z0-9._\-]{12,}["']?/gi
9716
+ pattern: /(?<![A-Za-z0-9_-])(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)(?![A-Za-z0-9_-])\+?=(?:\$\((?:\\.|[^\\\r\n])*\)|\$\{(?:\\.|[^}\\\r\n])*\}|`(?:\\.|[^`\\\r\n])*`|\$?'(?:\\.|[^'\\\r\n])*'|\$?"(?:\\.|[^"\\\r\n])*"|\\[^\r\n]|[^\s;&|<>()"'`\\\r\n])+/gi
9717
+ },
9718
+ // Config-file secrets (YAML/INI line keys, JSON quoted keys) — the coverage the shell
9719
+ // rule above deliberately gave up to spare prose ("Narrative token: x") and type
9720
+ // annotations ("(token: string)"). The line anchor is the discriminator: config keys
9721
+ // start their line, prose colons never do; JSON keys are safe anywhere because both
9722
+ // sides are quoted. Identifiers must END in a secret word, same as the shell rule.
9723
+ {
9724
+ id: "config-secret",
9725
+ pattern: /(?:^[ \t]*["']?(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)(?![A-Za-z0-9_-])["']?[ \t]*[:=][ \t]*|["'](?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)["']\s*:\s*)["']?[A-Za-z0-9._\-]{12,}["']?/gim
8606
9726
  },
8607
9727
  // Credentials embedded in URLs — common when a prompt pastes a curl/clone line.
8608
9728
  { id: "url-userinfo", pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@/gi },
@@ -8668,10 +9788,10 @@ async function applyRedactor(spans, redactor) {
8668
9788
  }
8669
9789
 
8670
9790
  // src/file-export.ts
8671
- import { createHash as createHash9 } from "crypto";
9791
+ import { createHash as createHash10 } from "crypto";
8672
9792
  import { mkdtemp as mkdtemp4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
8673
- import { tmpdir as tmpdir4 } from "os";
8674
- import { join as join21 } from "path";
9793
+ import { tmpdir as tmpdir5 } from "os";
9794
+ import { join as join23 } from "path";
8675
9795
  import {
8676
9796
  firstNumberAttr,
8677
9797
  LLM_CACHED_TOKEN_ATTR_KEYS,
@@ -8706,7 +9826,7 @@ function stableJson(value) {
8706
9826
  });
8707
9827
  }
8708
9828
  function hashId(value, chars = 16) {
8709
- return createHash9("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
9829
+ return createHash10("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
8710
9830
  }
8711
9831
  function isoTime2(value) {
8712
9832
  if (typeof value === "string") {
@@ -9267,15 +10387,15 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
9267
10387
  }
9268
10388
  async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
9269
10389
  const result = await exportTraceEvidenceFile(inputPath, opts);
9270
- const path = outPath ?? join21(await mkdtemp4(join21(tmpdir4(), "traces-export-")), "spans.openinference.jsonl");
10390
+ const path = outPath ?? join23(await mkdtemp4(join23(tmpdir5(), "traces-export-")), "spans.openinference.jsonl");
9271
10391
  await writeFile5(path, serializeSpans(result.spans), "utf8");
9272
10392
  return { ...result, path };
9273
10393
  }
9274
10394
 
9275
10395
  // src/inspect.ts
9276
10396
  import { mkdtemp as mkdtemp5, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
9277
- import { tmpdir as tmpdir5 } from "os";
9278
- import { basename as basename13, join as join22 } from "path";
10397
+ import { tmpdir as tmpdir6 } from "os";
10398
+ import { basename as basename13, join as join24 } from "path";
9279
10399
  var severityRank = {
9280
10400
  high: 0,
9281
10401
  medium: 1,
@@ -9291,7 +10411,7 @@ function sessionLabel(row) {
9291
10411
  const id = row.session.sessionId.length > 12 ? row.session.sessionId.slice(0, 12) : row.session.sessionId;
9292
10412
  return `${row.session.harness}:${id} (${repoLabel(row)})`;
9293
10413
  }
9294
- function pct(value) {
10414
+ function pct2(value) {
9295
10415
  return `${(value * 100).toFixed(value >= 0.1 ? 0 : 1)}%`;
9296
10416
  }
9297
10417
  function topToolErrors(row) {
@@ -9370,7 +10490,7 @@ function addSessionFindings(index, findings) {
9370
10490
  area: "session",
9371
10491
  title: `High tool-error count/rate in ${errorSessions.length}/${index.totals.sessions} session(s)`,
9372
10492
  impact: errors,
9373
- evidence: errorSessions.slice(0, 5).map((row) => `${sessionLabel(row)}: ${row.metrics.erroredToolCallCount}/${row.metrics.toolCallCount} tool errors (${pct(row.metrics.toolErrorRate)}); ${topToolErrors(row)}`),
10493
+ evidence: errorSessions.slice(0, 5).map((row) => `${sessionLabel(row)}: ${row.metrics.erroredToolCallCount}/${row.metrics.toolCallCount} tool errors (${pct2(row.metrics.toolErrorRate)}); ${topToolErrors(row)}`),
9374
10494
  next: "Fix the most common failing command/tool first, then rerun `traces index` to confirm the error count drops.",
9375
10495
  refs: errorSessions.slice(0, 5).map((row) => row.session.path)
9376
10496
  });
@@ -9564,14 +10684,14 @@ async function readSessionIndexFile(path) {
9564
10684
  return parsed;
9565
10685
  }
9566
10686
  async function writeInspectionReportFile(report, outPath) {
9567
- const path = outPath ?? join22(await mkdtemp5(join22(tmpdir5(), "traces-inspect-")), "inspection-report.md");
10687
+ const path = outPath ?? join24(await mkdtemp5(join24(tmpdir6(), "traces-inspect-")), "inspection-report.md");
9568
10688
  await writeFile6(path, renderInspectionReport(report), "utf8");
9569
10689
  return path;
9570
10690
  }
9571
10691
 
9572
10692
  // src/adoption.ts
9573
10693
  import { readFile as readFile11 } from "fs/promises";
9574
- import { join as join23 } from "path";
10694
+ import { join as join25 } from "path";
9575
10695
  function toolName2(s) {
9576
10696
  const n = s.attributes["tool.name"];
9577
10697
  if (typeof n === "string") return n;
@@ -9691,7 +10811,7 @@ async function readLoopRuns(cwds, sessionIds) {
9691
10811
  seenCwds.add(cwd);
9692
10812
  const firstSourceByRun = /* @__PURE__ */ new Map();
9693
10813
  for (const artifactDir of [".agent", ".evolve"]) {
9694
- const path = join23(cwd, artifactDir, "skill-runs.jsonl");
10814
+ const path = join25(cwd, artifactDir, "skill-runs.jsonl");
9695
10815
  let raw;
9696
10816
  try {
9697
10817
  raw = await readFile11(path, "utf8");
@@ -11102,8 +12222,8 @@ function renderAdoption(ar) {
11102
12222
 
11103
12223
  // src/improvement.ts
11104
12224
  import { access, mkdir as mkdir4, mkdtemp as mkdtemp6, writeFile as writeFile7 } from "fs/promises";
11105
- import { tmpdir as tmpdir6 } from "os";
11106
- import { dirname as dirname7, join as join24, resolve as resolve6 } from "path";
12225
+ import { tmpdir as tmpdir7 } from "os";
12226
+ import { dirname as dirname7, join as join26, resolve as resolve7 } from "path";
11107
12227
  import { pathToFileURL } from "url";
11108
12228
  import {
11109
12229
  AnalystRegistry,
@@ -11461,7 +12581,7 @@ function registryFromConfig(config) {
11461
12581
  async function existingConfigPath(path) {
11462
12582
  const candidates = path ? [path] : ["traces.config.mjs", "traces.config.js", "traces.config.cjs"];
11463
12583
  for (const candidate of candidates) {
11464
- const resolved = resolve6(candidate);
12584
+ const resolved = resolve7(candidate);
11465
12585
  try {
11466
12586
  await access(resolved);
11467
12587
  return resolved;
@@ -11610,13 +12730,13 @@ async function runTraceStoreInvestigation(opts) {
11610
12730
  };
11611
12731
  }
11612
12732
  async function writeTraceImprovementArtifacts(result, outDir) {
11613
- const directory = outDir ? resolve6(outDir) : await mkdtemp6(join24(tmpdir6(), "traces-improvement-"));
12733
+ const directory = outDir ? resolve7(outDir) : await mkdtemp6(join26(tmpdir7(), "traces-improvement-"));
11614
12734
  await mkdir4(directory, { recursive: true });
11615
12735
  const paths = {
11616
12736
  directory,
11617
- result: join24(directory, "result.json"),
11618
- evidence: join24(directory, "evidence.jsonl"),
11619
- report: join24(directory, "report.md"),
12737
+ result: join26(directory, "result.json"),
12738
+ evidence: join26(directory, "evidence.jsonl"),
12739
+ report: join26(directory, "report.md"),
11620
12740
  traces: result.otlpPath
11621
12741
  };
11622
12742
  const { report, ...machineResult } = result;
@@ -11628,11 +12748,11 @@ async function writeTraceImprovementArtifacts(result, outDir) {
11628
12748
  return paths;
11629
12749
  }
11630
12750
  async function runTraceImprovement(opts) {
11631
- const directory = opts.outDir ? resolve6(opts.outDir) : await mkdtemp6(join24(tmpdir6(), "traces-improvement-"));
12751
+ const directory = opts.outDir ? resolve7(opts.outDir) : await mkdtemp6(join26(tmpdir7(), "traces-improvement-"));
11632
12752
  await mkdir4(directory, { recursive: true });
11633
12753
  const investigation = await runTraceInvestigation({
11634
12754
  ...opts,
11635
- otlpOutPath: opts.otlpOutPath ?? join24(directory, "traces.otlp.jsonl")
12755
+ otlpOutPath: opts.otlpOutPath ?? join26(directory, "traces.otlp.jsonl")
11636
12756
  });
11637
12757
  const result = {
11638
12758
  ...investigation,
@@ -11644,32 +12764,32 @@ async function runTraceImprovement(opts) {
11644
12764
  };
11645
12765
  }
11646
12766
  async function saveReport(path, report) {
11647
- await mkdir4(dirname7(resolve6(path)), { recursive: true });
12767
+ await mkdir4(dirname7(resolve7(path)), { recursive: true });
11648
12768
  await writeFile7(path, report, "utf8");
11649
12769
  }
11650
12770
 
11651
12771
  // src/live.ts
11652
- import { createHash as createHash10 } from "crypto";
12772
+ import { createHash as createHash11 } from "crypto";
11653
12773
  var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
11654
12774
  var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
11655
12775
  var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
11656
- var sleep = (ms2, signal) => new Promise((resolve7) => {
12776
+ var sleep = (ms2, signal) => new Promise((resolve8) => {
11657
12777
  if (signal?.aborted) {
11658
- resolve7();
12778
+ resolve8();
11659
12779
  return;
11660
12780
  }
11661
12781
  const onAbort = () => {
11662
12782
  clearTimeout(t);
11663
- resolve7();
12783
+ resolve8();
11664
12784
  };
11665
12785
  const t = setTimeout(() => {
11666
12786
  signal?.removeEventListener("abort", onAbort);
11667
- resolve7();
12787
+ resolve8();
11668
12788
  }, ms2);
11669
12789
  signal?.addEventListener("abort", onAbort, { once: true });
11670
12790
  });
11671
12791
  function hash(parts) {
11672
- return createHash10("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
12792
+ return createHash11("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
11673
12793
  }
11674
12794
  function asString(value) {
11675
12795
  if (typeof value === "string" && value.trim()) return value;
@@ -12075,7 +13195,7 @@ async function streamSessions(opts) {
12075
13195
 
12076
13196
  // src/supervisor-run-context.ts
12077
13197
  import { readFile as readFile12, stat as stat14 } from "fs/promises";
12078
- import { basename as basename14, join as join25 } from "path";
13198
+ import { basename as basename14, join as join27 } from "path";
12079
13199
  import { materializeTreeView } from "@tangle-network/agent-runtime/kernel";
12080
13200
  import {
12081
13201
  NO_SOURCE_LIMITS
@@ -12236,10 +13356,10 @@ function parseInstant(v) {
12236
13356
  return Number.isFinite(t) ? t : null;
12237
13357
  }
12238
13358
  async function readFileRunContext(runDir, opts = {}) {
12239
- const journalText = await readMaybe(join25(runDir, SPAWN_JOURNAL_FILE));
13359
+ const journalText = await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE));
12240
13360
  const journal = readRunContextJournal(journalText);
12241
- const coordination = readCoordinationLog(await readMaybe(join25(runDir, COORDINATION_LOG_FILE)));
12242
- const result = await readMaybe(join25(runDir, RESULT_FILE));
13361
+ const coordination = readCoordinationLog(await readMaybe(join27(runDir, COORDINATION_LOG_FILE)));
13362
+ const result = await readMaybe(join27(runDir, RESULT_FILE));
12243
13363
  return {
12244
13364
  runRef: runDir,
12245
13365
  instanceId: opts.instanceId !== void 0 ? opts.instanceId : journal.root,
@@ -12262,7 +13382,7 @@ async function readFileRunContext(runDir, opts = {}) {
12262
13382
  harnessWorkerTokens: null,
12263
13383
  harnessMissingReason: "the file run context journals each child\u2019s own settled spend, so no external harness join is used",
12264
13384
  limits: sourceLimits(journal, coordination),
12265
- rootTranscriptRef: journal.present ? join25(runDir, SPAWN_JOURNAL_FILE) : null,
13385
+ rootTranscriptRef: journal.present ? join27(runDir, SPAWN_JOURNAL_FILE) : null,
12266
13386
  traceCommand: `traces watch ${runDir}`
12267
13387
  };
12268
13388
  }
@@ -12270,7 +13390,7 @@ function fileRunContextSupervisorRunReader(runDir, opts = {}) {
12270
13390
  return { runRef: runDir, read: () => readFileRunContext(runDir, opts) };
12271
13391
  }
12272
13392
  async function isFileRunContextDir(runDir) {
12273
- return stat14(join25(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
13393
+ return stat14(join27(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
12274
13394
  }
12275
13395
  function flattenJournal(journal) {
12276
13396
  return journal.events.map((event) => {
@@ -12416,9 +13536,9 @@ function mergeRolls(a, b) {
12416
13536
  };
12417
13537
  }
12418
13538
  async function readRunContextSnapshot(runDir) {
12419
- const journal = readRunContextJournal(await readMaybe(join25(runDir, SPAWN_JOURNAL_FILE)));
12420
- const coordination = readCoordinationLog(await readMaybe(join25(runDir, COORDINATION_LOG_FILE)));
12421
- const resultText = await readMaybe(join25(runDir, RESULT_FILE));
13539
+ const journal = readRunContextJournal(await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE)));
13540
+ const coordination = readCoordinationLog(await readMaybe(join27(runDir, COORDINATION_LOG_FILE)));
13541
+ const resultText = await readMaybe(join27(runDir, RESULT_FILE));
12422
13542
  const result = parseResult(resultText);
12423
13543
  const view = safeTreeView(journal.events);
12424
13544
  const nodes = view === null ? [] : orderNodes(view, journal);
@@ -12601,7 +13721,7 @@ function wrap(text2, indent) {
12601
13721
 
12602
13722
  // src/run-span-tree.ts
12603
13723
  import { readdir as readdir16 } from "fs/promises";
12604
- import { join as join26 } from "path";
13724
+ import { join as join28 } from "path";
12605
13725
  import {
12606
13726
  firstNumberAttr as firstNumberAttr3,
12607
13727
  LLM_COST_ATTR_KEYS as LLM_COST_ATTR_KEYS2,
@@ -12874,7 +13994,7 @@ async function findSpanSourceFiles(dir) {
12874
13994
  const found = [];
12875
13995
  const entries = await readdir16(dir, { withFileTypes: true }).catch(() => []);
12876
13996
  for (const entry of entries) {
12877
- const child = join26(dir, entry.name);
13997
+ const child = join28(dir, entry.name);
12878
13998
  if (entry.isFile()) {
12879
13999
  if (SPAN_FILE_PATTERN.test(entry.name)) found.push(child);
12880
14000
  continue;
@@ -12882,7 +14002,7 @@ async function findSpanSourceFiles(dir) {
12882
14002
  if (!entry.isDirectory()) continue;
12883
14003
  const nested = await readdir16(child, { withFileTypes: true }).catch(() => []);
12884
14004
  for (const inner of nested) {
12885
- if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join26(child, inner.name));
14005
+ if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join28(child, inner.name));
12886
14006
  }
12887
14007
  }
12888
14008
  return found.sort();
@@ -12890,7 +14010,7 @@ async function findSpanSourceFiles(dir) {
12890
14010
 
12891
14011
  // src/supervisor-run-watch.ts
12892
14012
  import { readdir as readdir17 } from "fs/promises";
12893
- import { join as join27 } from "path";
14013
+ import { join as join29 } from "path";
12894
14014
  function budget(node) {
12895
14015
  const parts = [`${int(node.budget.maxIterations)}it`, `${int(node.budget.maxTokens)}tok`];
12896
14016
  if (node.budget.maxUsd !== void 0) parts.push(`$${node.budget.maxUsd.toFixed(2)}`);
@@ -13034,7 +14154,7 @@ async function resolveRunContextDir(dir) {
13034
14154
  const candidates = [];
13035
14155
  for (const entry of entries) {
13036
14156
  if (!entry.isDirectory()) continue;
13037
- const child = join27(dir, entry.name);
14157
+ const child = join29(dir, entry.name);
13038
14158
  if (await isFileRunContextDir(child)) candidates.push(child);
13039
14159
  }
13040
14160
  if (candidates.length === 1) return candidates[0];
@@ -13139,13 +14259,13 @@ function aborted(signal) {
13139
14259
  return signal !== void 0 && signal.aborted;
13140
14260
  }
13141
14261
  function sleep2(msDelay, signal) {
13142
- return new Promise((resolve7) => {
13143
- const timer = setTimeout(resolve7, msDelay);
14262
+ return new Promise((resolve8) => {
14263
+ const timer = setTimeout(resolve8, msDelay);
13144
14264
  signal?.addEventListener(
13145
14265
  "abort",
13146
14266
  () => {
13147
14267
  clearTimeout(timer);
13148
- resolve7();
14268
+ resolve8();
13149
14269
  },
13150
14270
  { once: true }
13151
14271
  );
@@ -13485,11 +14605,11 @@ async function collectSessionWorkflow(adapter, seeds, options = {}) {
13485
14605
  }
13486
14606
 
13487
14607
  // src/session-selection.ts
13488
- import { createHash as createHash11 } from "crypto";
14608
+ import { createHash as createHash12 } from "crypto";
13489
14609
  import { createReadStream as createReadStream2 } from "fs";
13490
14610
  async function fileSha256(path, signal) {
13491
14611
  signal?.throwIfAborted();
13492
- const hash2 = createHash11("sha256");
14612
+ const hash2 = createHash12("sha256");
13493
14613
  try {
13494
14614
  for await (const chunk of createReadStream2(path, { signal })) {
13495
14615
  signal?.throwIfAborted();
@@ -13517,7 +14637,7 @@ async function sourceSnapshot(adapter, ref, signal) {
13517
14637
  signal?.throwIfAborted();
13518
14638
  files.push({ path, sha256: await fileSha256(path, signal) });
13519
14639
  }
13520
- const sha2562 = files.length === 1 ? files[0].sha256 : createHash11("sha256").update(JSON.stringify(files)).digest("hex");
14640
+ const sha2562 = files.length === 1 ? files[0].sha256 : createHash12("sha256").update(JSON.stringify(files)).digest("hex");
13521
14641
  return { sha256: sha2562, files };
13522
14642
  }
13523
14643
  function summarizeWorkflows(workflows) {
@@ -13613,8 +14733,8 @@ async function collectSessionSelection(groups, options = {}) {
13613
14733
 
13614
14734
  // src/session-index.ts
13615
14735
  import { mkdtemp as mkdtemp7, readdir as readdir18, readFile as readFile13, stat as stat16, writeFile as writeFile8 } from "fs/promises";
13616
- import { tmpdir as tmpdir7 } from "os";
13617
- import { dirname as dirname8, join as join28 } from "path";
14736
+ import { tmpdir as tmpdir8 } from "os";
14737
+ import { dirname as dirname8, join as join30 } from "path";
13618
14738
  function indexRow(record2) {
13619
14739
  return {
13620
14740
  session: record2.session,
@@ -13687,7 +14807,7 @@ async function findContextRoot(cwd) {
13687
14807
  if (!s) return null;
13688
14808
  if (!s.isDirectory()) current = dirname8(current);
13689
14809
  for (; ; ) {
13690
- if (await pathStat2(join28(current, ".git")) || await dirExists(join28(current, ".evolve")) || await fileExists(join28(current, "AGENTS.md")) || await fileExists(join28(current, "CLAUDE.md"))) {
14810
+ if (await pathStat2(join30(current, ".git")) || await dirExists(join30(current, ".evolve")) || await fileExists(join30(current, "AGENTS.md")) || await fileExists(join30(current, "CLAUDE.md"))) {
13691
14811
  return current;
13692
14812
  }
13693
14813
  const parent = dirname8(current);
@@ -13755,7 +14875,7 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
13755
14875
  }
13756
14876
  for (const entry of entries) {
13757
14877
  if (out.length >= maxFiles) return;
13758
- const path = join28(dir, entry.name);
14878
+ const path = join30(dir, entry.name);
13759
14879
  if (entry.isDirectory()) {
13760
14880
  await walk(path);
13761
14881
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -13770,20 +14890,20 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
13770
14890
  async function collectContextRoot(root) {
13771
14891
  const files = [];
13772
14892
  for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
13773
- const summary = await summarizeFile(join28(root, name), "instruction-doc");
14893
+ const summary = await summarizeFile(join30(root, name), "instruction-doc");
13774
14894
  if (summary) files.push(summary);
13775
14895
  }
13776
- const evolve = join28(root, ".evolve");
14896
+ const evolve = join30(root, ".evolve");
13777
14897
  for (const name of ["skill-runs.jsonl", "governor.jsonl", "experiments.jsonl"]) {
13778
- const summary = await summarizeFile(join28(evolve, name), "evolve-jsonl");
14898
+ const summary = await summarizeFile(join30(evolve, name), "evolve-jsonl");
13779
14899
  if (summary) files.push(summary);
13780
14900
  }
13781
14901
  for (const name of ["scorecard.json"]) {
13782
- const summary = await summarizeFile(join28(evolve, name), "evolve-json");
14902
+ const summary = await summarizeFile(join30(evolve, name), "evolve-json");
13783
14903
  if (summary) files.push(summary);
13784
14904
  }
13785
- files.push(...await walkMarkdown(join28(evolve, "reflections"), "reflection"));
13786
- files.push(...await walkMarkdown(join28(evolve, "handoffs"), "handoff"));
14905
+ files.push(...await walkMarkdown(join30(evolve, "reflections"), "reflection"));
14906
+ files.push(...await walkMarkdown(join30(evolve, "handoffs"), "handoff"));
13787
14907
  return { root, files: files.sort((a, b) => a.path.localeCompare(b.path)) };
13788
14908
  }
13789
14909
  async function collectContextIndex(records) {
@@ -13829,7 +14949,7 @@ function serializeSessionIndex(index) {
13829
14949
  `;
13830
14950
  }
13831
14951
  async function writeSessionIndexFile(index, outPath) {
13832
- const path = outPath ?? join28(await mkdtemp7(join28(tmpdir7(), "traces-index-")), "session-index.json");
14952
+ const path = outPath ?? join30(await mkdtemp7(join30(tmpdir8(), "traces-index-")), "session-index.json");
13833
14953
  await writeFile8(path, serializeSessionIndex(index), "utf8");
13834
14954
  return path;
13835
14955
  }
@@ -13837,11 +14957,11 @@ async function writeSessionIndexFile(index, outPath) {
13837
14957
  // src/upload-state.ts
13838
14958
  import { mkdir as mkdir5, readFile as readFile14, writeFile as writeFile9 } from "fs/promises";
13839
14959
  import { homedir as homedir12 } from "os";
13840
- import { dirname as dirname9, join as join29 } from "path";
14960
+ import { dirname as dirname9, join as join31 } from "path";
13841
14961
  import { contentHash } from "@tangle-network/agent-eval";
13842
14962
  function stateFile() {
13843
- const base = process.env.XDG_STATE_HOME ?? join29(homedir12(), ".local", "state");
13844
- return join29(base, "tangle-traces", "uploaded.json");
14963
+ const base = process.env.XDG_STATE_HOME ?? join31(homedir12(), ".local", "state");
14964
+ return join31(base, "tangle-traces", "uploaded.json");
13845
14965
  }
13846
14966
  function uploadKey(harness, sessionId) {
13847
14967
  return `${harness}:${sessionId}`;
@@ -13873,7 +14993,7 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
13873
14993
  import { readFile as readFile15 } from "fs/promises";
13874
14994
  import { createRequire as createRequire2 } from "module";
13875
14995
  import { hostname as hostname2 } from "os";
13876
- import { basename as basename15, join as join30 } from "path";
14996
+ import { basename as basename15, join as join32 } from "path";
13877
14997
  import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
13878
14998
  import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
13879
14999
  var require2 = createRequire2(import.meta.url);
@@ -13897,7 +15017,7 @@ async function planUpload(opts = {}) {
13897
15017
  async function gitBranch(cwd) {
13898
15018
  if (!cwd) return void 0;
13899
15019
  try {
13900
- const head = await readFile15(join30(cwd, ".git", "HEAD"), "utf8");
15020
+ const head = await readFile15(join32(cwd, ".git", "HEAD"), "utf8");
13901
15021
  const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
13902
15022
  return m?.[1]?.trim();
13903
15023
  } catch {
@@ -14153,6 +15273,25 @@ export {
14153
15273
  parseReplayVerifyArgs,
14154
15274
  replayVerifyUsage,
14155
15275
  cmdReplayVerify,
15276
+ parseCorpusFlag,
15277
+ SUBMIT_ACTION_SIGNATURE,
15278
+ isSubmitAction,
15279
+ readLabelEntries,
15280
+ goldIncorrectSteps,
15281
+ resolveCaseResources,
15282
+ enumerateReplayableCases,
15283
+ zaiChatCaller,
15284
+ buildFixPrompt,
15285
+ extractFixCommand,
15286
+ generateFixCommand,
15287
+ derivedImageTag,
15288
+ dockerImagePreparer,
15289
+ seededSample,
15290
+ runReplayBatch,
15291
+ renderBatchReport,
15292
+ parseReplayBatchArgs,
15293
+ replayBatchUsage,
15294
+ cmdReplayVerifyBatch,
14156
15295
  runCommand,
14157
15296
  commandAnalyzer,
14158
15297
  haloAnalyzer,