@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.
- package/README.md +1 -0
- package/dist/{chunk-UY3M7XUF.js → chunk-AGPOIWPQ.js} +1258 -119
- package/dist/cli.js +9 -1
- package/dist/index.d.ts +437 -2
- package/dist/index.js +115 -1
- package/docs/replay-batch-20260802.md +76 -0
- package/docs/replay-verify.md +50 -0
- package/package.json +4 -4
|
@@ -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:
|
|
769
|
-
const { join:
|
|
770
|
-
const path = outPath ??
|
|
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
|
|
5556
|
+
return this.sessionsRoot;
|
|
5553
5557
|
}
|
|
5554
5558
|
async locate(opts = {}) {
|
|
5555
5559
|
const root = this.root();
|
|
5556
|
-
let
|
|
5560
|
+
let entries;
|
|
5557
5561
|
try {
|
|
5558
|
-
|
|
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
|
|
5564
|
-
for (const
|
|
5565
|
-
|
|
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(
|
|
5577
|
+
files = await readdir12(dir);
|
|
5569
5578
|
} catch (error) {
|
|
5570
5579
|
if (isMissingPathError(error)) continue;
|
|
5571
5580
|
throw error;
|
|
5572
5581
|
}
|
|
5573
|
-
const
|
|
5574
|
-
|
|
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 =
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
7908
|
-
import { join as
|
|
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 ?
|
|
7912
|
-
const directory = explicitDirectory ?? await mkdtemp3(
|
|
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 =
|
|
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 = `${
|
|
7945
|
-
await writeFile4(
|
|
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 ?
|
|
9077
|
+
const outputRoot = options.outputDir ? resolve6(options.outputDir) : tmpdir4();
|
|
7968
9078
|
await mkdir3(outputRoot, { recursive: true });
|
|
7969
|
-
const directory = await mkdtemp3(
|
|
7970
|
-
const input =
|
|
7971
|
-
const analysisFile =
|
|
7972
|
-
const samplesFile =
|
|
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 (
|
|
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:
|
|
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
|
|
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
|
|
8674
|
-
import { join as
|
|
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
|
|
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 ??
|
|
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
|
|
9278
|
-
import { basename as basename13, join as
|
|
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
|
|
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 (${
|
|
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 ??
|
|
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
|
|
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 =
|
|
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
|
|
11106
|
-
import { dirname as dirname7, join as
|
|
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 =
|
|
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 ?
|
|
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:
|
|
11618
|
-
evidence:
|
|
11619
|
-
report:
|
|
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 ?
|
|
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 ??
|
|
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(
|
|
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
|
|
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((
|
|
12776
|
+
var sleep = (ms2, signal) => new Promise((resolve8) => {
|
|
11657
12777
|
if (signal?.aborted) {
|
|
11658
|
-
|
|
12778
|
+
resolve8();
|
|
11659
12779
|
return;
|
|
11660
12780
|
}
|
|
11661
12781
|
const onAbort = () => {
|
|
11662
12782
|
clearTimeout(t);
|
|
11663
|
-
|
|
12783
|
+
resolve8();
|
|
11664
12784
|
};
|
|
11665
12785
|
const t = setTimeout(() => {
|
|
11666
12786
|
signal?.removeEventListener("abort", onAbort);
|
|
11667
|
-
|
|
12787
|
+
resolve8();
|
|
11668
12788
|
}, ms2);
|
|
11669
12789
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11670
12790
|
});
|
|
11671
12791
|
function hash(parts) {
|
|
11672
|
-
return
|
|
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
|
|
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(
|
|
13359
|
+
const journalText = await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE));
|
|
12240
13360
|
const journal = readRunContextJournal(journalText);
|
|
12241
|
-
const coordination = readCoordinationLog(await readMaybe(
|
|
12242
|
-
const result = await readMaybe(
|
|
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 ?
|
|
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(
|
|
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(
|
|
12420
|
-
const coordination = readCoordinationLog(await readMaybe(
|
|
12421
|
-
const resultText = await readMaybe(
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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((
|
|
13143
|
-
const timer = setTimeout(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 :
|
|
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
|
|
13617
|
-
import { dirname as dirname8, join as
|
|
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(
|
|
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 =
|
|
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(
|
|
14893
|
+
const summary = await summarizeFile(join30(root, name), "instruction-doc");
|
|
13774
14894
|
if (summary) files.push(summary);
|
|
13775
14895
|
}
|
|
13776
|
-
const 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(
|
|
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(
|
|
14902
|
+
const summary = await summarizeFile(join30(evolve, name), "evolve-json");
|
|
13783
14903
|
if (summary) files.push(summary);
|
|
13784
14904
|
}
|
|
13785
|
-
files.push(...await walkMarkdown(
|
|
13786
|
-
files.push(...await walkMarkdown(
|
|
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 ??
|
|
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
|
|
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 ??
|
|
13844
|
-
return
|
|
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
|
|
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(
|
|
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,
|