@tangle-network/traces 0.11.2 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UY3M7XUF.js → chunk-CK5F5TWO.js} +1184 -77
- package/dist/cli.js +9 -1
- package/dist/index.d.ts +429 -1
- package/dist/index.js +115 -1
- package/docs/replay-batch-20260802.md +76 -0
- package/docs/replay-verify.md +50 -0
- package/package.json +3 -3
|
@@ -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
|
}
|
|
@@ -5997,9 +5997,9 @@ function normalizeRemote(url) {
|
|
|
5997
5997
|
}
|
|
5998
5998
|
async function readGit(cwd) {
|
|
5999
5999
|
await stat13(`${cwd}/.git`);
|
|
6000
|
-
const { execFile } = await import("child_process");
|
|
6001
|
-
const { promisify } = await import("util");
|
|
6002
|
-
const run =
|
|
6000
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
6001
|
+
const { promisify: promisify2 } = await import("util");
|
|
6002
|
+
const run = promisify2(execFile2);
|
|
6003
6003
|
const git = async (args) => {
|
|
6004
6004
|
try {
|
|
6005
6005
|
const { stdout } = await run("git", ["-C", cwd, ...args], { timeout: 5e3 });
|
|
@@ -6025,9 +6025,9 @@ async function readGit(cwd) {
|
|
|
6025
6025
|
};
|
|
6026
6026
|
}
|
|
6027
6027
|
async function gitOutput(cwd, args) {
|
|
6028
|
-
const { execFile } = await import("child_process");
|
|
6029
|
-
const { promisify } = await import("util");
|
|
6030
|
-
const run =
|
|
6028
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
6029
|
+
const { promisify: promisify2 } = await import("util");
|
|
6030
|
+
const run = promisify2(execFile2);
|
|
6031
6031
|
try {
|
|
6032
6032
|
const { stdout } = await run("git", ["-C", cwd, ...args], { timeout: 5e3 });
|
|
6033
6033
|
const v = stdout.trim();
|
|
@@ -7141,6 +7141,1084 @@ async function cmdReplayVerify(argv) {
|
|
|
7141
7141
|
);
|
|
7142
7142
|
}
|
|
7143
7143
|
|
|
7144
|
+
// src/replay-corpus.ts
|
|
7145
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
7146
|
+
import { join as join20 } from "path";
|
|
7147
|
+
function parseCorpusFlag(value) {
|
|
7148
|
+
const eq = value.indexOf("=");
|
|
7149
|
+
if (eq <= 0) throw new Error(`--corpus must be name=<labels>::<prepared>, got: ${value}`);
|
|
7150
|
+
const name = value.slice(0, eq);
|
|
7151
|
+
const rest = value.slice(eq + 1);
|
|
7152
|
+
const sep2 = rest.indexOf("::");
|
|
7153
|
+
if (sep2 <= 0 || sep2 === rest.length - 2) {
|
|
7154
|
+
throw new Error(`--corpus must be name=<labels>::<prepared>, got: ${value}`);
|
|
7155
|
+
}
|
|
7156
|
+
return { name, labelsPath: rest.slice(0, sep2), preparedDir: rest.slice(sep2 + 2) };
|
|
7157
|
+
}
|
|
7158
|
+
var SUBMIT_ACTION_SIGNATURE = "COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT";
|
|
7159
|
+
function isSubmitAction(action) {
|
|
7160
|
+
return action.includes(SUBMIT_ACTION_SIGNATURE);
|
|
7161
|
+
}
|
|
7162
|
+
function readLabelEntries(labelsPath) {
|
|
7163
|
+
const parsed = JSON.parse(readFileSync3(labelsPath, "utf8"));
|
|
7164
|
+
if (!Array.isArray(parsed)) throw new Error(`${labelsPath} is not a JSON array of label entries`);
|
|
7165
|
+
for (const entry of parsed) {
|
|
7166
|
+
if (typeof entry.traj_id !== "string") {
|
|
7167
|
+
throw new Error(`${labelsPath} has an entry without a string traj_id`);
|
|
7168
|
+
}
|
|
7169
|
+
}
|
|
7170
|
+
return parsed;
|
|
7171
|
+
}
|
|
7172
|
+
function goldIncorrectSteps(entry) {
|
|
7173
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7174
|
+
for (const stage of entry.incorrect_stages ?? []) {
|
|
7175
|
+
for (const id of stage.incorrect_step_ids ?? []) ids.add(id);
|
|
7176
|
+
}
|
|
7177
|
+
return [...ids].sort((a, b) => a - b);
|
|
7178
|
+
}
|
|
7179
|
+
function findRawTrajFiles(sweRawDir) {
|
|
7180
|
+
if (!existsSync2(sweRawDir)) return [];
|
|
7181
|
+
const entries = readdirSync2(sweRawDir, { recursive: true, encoding: "utf8" });
|
|
7182
|
+
return entries.filter((relative5) => relative5.endsWith(".traj.json")).map((relative5) => join20(sweRawDir, relative5)).sort();
|
|
7183
|
+
}
|
|
7184
|
+
function cwdFromPwdObservation(steps) {
|
|
7185
|
+
for (const step of steps) {
|
|
7186
|
+
if (step.action.trim() !== "pwd") continue;
|
|
7187
|
+
const line = parseObservationOutput(step.observation).split("\n").map((l) => l.trim()).find((l) => l.startsWith("/"));
|
|
7188
|
+
if (line) return line;
|
|
7189
|
+
}
|
|
7190
|
+
return null;
|
|
7191
|
+
}
|
|
7192
|
+
function taskStatementOf(preparedDir, trajId, raw) {
|
|
7193
|
+
const taskPath = join20(preparedDir, "normalized", trajId, "task.md");
|
|
7194
|
+
if (existsSync2(taskPath)) {
|
|
7195
|
+
const text2 = readFileSync3(taskPath, "utf8").trim();
|
|
7196
|
+
if (text2.length > 0) return text2;
|
|
7197
|
+
}
|
|
7198
|
+
const userMessage = raw.messages?.find((m) => m.role === "user");
|
|
7199
|
+
if (typeof userMessage?.content === "string" && userMessage.content.trim().length > 0) {
|
|
7200
|
+
return userMessage.content.trim();
|
|
7201
|
+
}
|
|
7202
|
+
return null;
|
|
7203
|
+
}
|
|
7204
|
+
function resolveCaseResources(corpus, trajId) {
|
|
7205
|
+
const sweRawDir = join20(corpus.preparedDir, "extracted", trajId, "swe_raw");
|
|
7206
|
+
const rawFiles = findRawTrajFiles(sweRawDir);
|
|
7207
|
+
if (rawFiles.length === 0) {
|
|
7208
|
+
return { resolved: false, reason: "no-swe-raw-trajectory", detail: sweRawDir };
|
|
7209
|
+
}
|
|
7210
|
+
if (rawFiles.length > 1) {
|
|
7211
|
+
return { resolved: false, reason: "ambiguous-swe-raw-trajectory", detail: rawFiles.join(", ") };
|
|
7212
|
+
}
|
|
7213
|
+
let raw;
|
|
7214
|
+
try {
|
|
7215
|
+
raw = JSON.parse(readFileSync3(rawFiles[0], "utf8"));
|
|
7216
|
+
} catch (err) {
|
|
7217
|
+
return {
|
|
7218
|
+
resolved: false,
|
|
7219
|
+
reason: "unreadable-raw-trajectory",
|
|
7220
|
+
detail: `${rawFiles[0]}: ${err instanceof Error ? err.message : String(err)}`
|
|
7221
|
+
};
|
|
7222
|
+
}
|
|
7223
|
+
const dockerConfig = raw.info?.docker_config;
|
|
7224
|
+
const image = dockerConfig?.base_image;
|
|
7225
|
+
if (typeof image !== "string" || image.length === 0) {
|
|
7226
|
+
return { resolved: false, reason: "no-docker-image", detail: rawFiles[0] };
|
|
7227
|
+
}
|
|
7228
|
+
const stepsPath = join20(corpus.preparedDir, "normalized", trajId, "steps.json");
|
|
7229
|
+
if (!existsSync2(stepsPath)) {
|
|
7230
|
+
return { resolved: false, reason: "missing-steps-json", detail: stepsPath };
|
|
7231
|
+
}
|
|
7232
|
+
const steps = JSON.parse(readFileSync3(stepsPath, "utf8"));
|
|
7233
|
+
const runConfigCwd = raw.info?.config?.environment?.cwd;
|
|
7234
|
+
const dockerCwd = dockerConfig?.cwd;
|
|
7235
|
+
let cwd;
|
|
7236
|
+
let cwdSource;
|
|
7237
|
+
if (typeof runConfigCwd === "string" && runConfigCwd.length > 0) {
|
|
7238
|
+
cwd = runConfigCwd;
|
|
7239
|
+
cwdSource = "run-config";
|
|
7240
|
+
} else if (typeof dockerCwd === "string" && dockerCwd.length > 0) {
|
|
7241
|
+
cwd = dockerCwd;
|
|
7242
|
+
cwdSource = "docker-config";
|
|
7243
|
+
} else {
|
|
7244
|
+
const observed = cwdFromPwdObservation(steps);
|
|
7245
|
+
if (!observed) return { resolved: false, reason: "cwd-underivable" };
|
|
7246
|
+
cwd = observed;
|
|
7247
|
+
cwdSource = "pwd-observation";
|
|
7248
|
+
}
|
|
7249
|
+
const recordedTimeout = raw.info?.config?.environment?.timeout;
|
|
7250
|
+
return {
|
|
7251
|
+
resolved: true,
|
|
7252
|
+
resources: {
|
|
7253
|
+
corpus: corpus.name,
|
|
7254
|
+
trajId,
|
|
7255
|
+
stepsPath,
|
|
7256
|
+
steps,
|
|
7257
|
+
taskStatement: taskStatementOf(corpus.preparedDir, trajId, raw),
|
|
7258
|
+
image,
|
|
7259
|
+
cwd,
|
|
7260
|
+
cwdSource,
|
|
7261
|
+
recordedStepTimeoutMs: typeof recordedTimeout === "number" && recordedTimeout > 0 ? recordedTimeout * 1e3 : null
|
|
7262
|
+
}
|
|
7263
|
+
};
|
|
7264
|
+
}
|
|
7265
|
+
function enumerateReplayableCases(corpora) {
|
|
7266
|
+
const replayable = [];
|
|
7267
|
+
const excluded = [];
|
|
7268
|
+
let labelEntryCount = 0;
|
|
7269
|
+
for (const corpus of corpora) {
|
|
7270
|
+
for (const entry of readLabelEntries(corpus.labelsPath)) {
|
|
7271
|
+
labelEntryCount += 1;
|
|
7272
|
+
const trajId = entry.traj_id;
|
|
7273
|
+
const resolution = resolveCaseResources(corpus, trajId);
|
|
7274
|
+
if (!resolution.resolved) {
|
|
7275
|
+
excluded.push({ corpus: corpus.name, trajId, reason: resolution.reason, detail: resolution.detail });
|
|
7276
|
+
continue;
|
|
7277
|
+
}
|
|
7278
|
+
const gold = goldIncorrectSteps(entry);
|
|
7279
|
+
if (gold.length === 0) {
|
|
7280
|
+
excluded.push({ corpus: corpus.name, trajId, reason: "no-gold-incorrect-step" });
|
|
7281
|
+
continue;
|
|
7282
|
+
}
|
|
7283
|
+
const resources = resolution.resources;
|
|
7284
|
+
let submitGoldsSkipped = 0;
|
|
7285
|
+
let target = null;
|
|
7286
|
+
let missingGoldId = null;
|
|
7287
|
+
for (const goldId of gold) {
|
|
7288
|
+
const step = resources.steps.find((s) => s.step_id === goldId);
|
|
7289
|
+
if (!step) {
|
|
7290
|
+
missingGoldId = goldId;
|
|
7291
|
+
break;
|
|
7292
|
+
}
|
|
7293
|
+
if (isSubmitAction(step.action)) {
|
|
7294
|
+
submitGoldsSkipped += 1;
|
|
7295
|
+
continue;
|
|
7296
|
+
}
|
|
7297
|
+
target = step;
|
|
7298
|
+
break;
|
|
7299
|
+
}
|
|
7300
|
+
if (missingGoldId !== null) {
|
|
7301
|
+
excluded.push({
|
|
7302
|
+
corpus: corpus.name,
|
|
7303
|
+
trajId,
|
|
7304
|
+
reason: "gold-step-outside-steps",
|
|
7305
|
+
detail: `k=${missingGoldId}, steps=${resources.steps.length}`
|
|
7306
|
+
});
|
|
7307
|
+
continue;
|
|
7308
|
+
}
|
|
7309
|
+
if (!target) {
|
|
7310
|
+
excluded.push({
|
|
7311
|
+
corpus: corpus.name,
|
|
7312
|
+
trajId,
|
|
7313
|
+
reason: "gold-only-submit-step",
|
|
7314
|
+
detail: `${submitGoldsSkipped} gold step(s), all submit commands`
|
|
7315
|
+
});
|
|
7316
|
+
continue;
|
|
7317
|
+
}
|
|
7318
|
+
replayable.push({
|
|
7319
|
+
...resources,
|
|
7320
|
+
goldIncorrectSteps: gold,
|
|
7321
|
+
k: target.step_id,
|
|
7322
|
+
submitGoldsSkipped,
|
|
7323
|
+
recordedReturncodeAtK: parseRecordedReturncode(target.observation)
|
|
7324
|
+
});
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
return { replayable, excluded, labelEntryCount };
|
|
7328
|
+
}
|
|
7329
|
+
|
|
7330
|
+
// src/replay-fix.ts
|
|
7331
|
+
function zaiChatCaller(options) {
|
|
7332
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
7333
|
+
return {
|
|
7334
|
+
async complete(system, user) {
|
|
7335
|
+
const baseCap = options.maxTokens ?? 16384;
|
|
7336
|
+
let lastError = "empty completion content";
|
|
7337
|
+
for (const cap of [baseCap, baseCap * 2]) {
|
|
7338
|
+
const outcome = await callOnce(cap);
|
|
7339
|
+
if (outcome.succeeded) return outcome;
|
|
7340
|
+
lastError = outcome.error;
|
|
7341
|
+
if (!/finish_reason":"length/.test(outcome.error)) return outcome;
|
|
7342
|
+
}
|
|
7343
|
+
return { succeeded: false, error: lastError };
|
|
7344
|
+
async function callOnce(maxTokens) {
|
|
7345
|
+
const controller = new AbortController();
|
|
7346
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 18e4);
|
|
7347
|
+
try {
|
|
7348
|
+
const response = await fetchImpl(`${options.baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
7349
|
+
method: "POST",
|
|
7350
|
+
headers: {
|
|
7351
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
7352
|
+
"content-type": "application/json"
|
|
7353
|
+
},
|
|
7354
|
+
body: JSON.stringify({
|
|
7355
|
+
model: options.model,
|
|
7356
|
+
messages: [
|
|
7357
|
+
{ role: "system", content: system },
|
|
7358
|
+
{ role: "user", content: user }
|
|
7359
|
+
],
|
|
7360
|
+
temperature: 0,
|
|
7361
|
+
max_tokens: maxTokens
|
|
7362
|
+
}),
|
|
7363
|
+
signal: controller.signal
|
|
7364
|
+
});
|
|
7365
|
+
const bodyText = await response.text();
|
|
7366
|
+
if (!response.ok) {
|
|
7367
|
+
return { succeeded: false, error: `HTTP ${response.status}: ${bodyText.slice(0, 400)}` };
|
|
7368
|
+
}
|
|
7369
|
+
const body = JSON.parse(bodyText);
|
|
7370
|
+
if (body.error) {
|
|
7371
|
+
return { succeeded: false, error: String(body.error.message ?? "provider error") };
|
|
7372
|
+
}
|
|
7373
|
+
const content = body.choices?.[0]?.message?.content;
|
|
7374
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
7375
|
+
return { succeeded: false, error: `empty completion content: ${bodyText.slice(0, 400)}` };
|
|
7376
|
+
}
|
|
7377
|
+
const promptTokens = body.usage?.prompt_tokens;
|
|
7378
|
+
const completionTokens = body.usage?.completion_tokens;
|
|
7379
|
+
const usage = typeof promptTokens === "number" && typeof completionTokens === "number" ? { promptTokens, completionTokens } : null;
|
|
7380
|
+
return { succeeded: true, value: { content, usage } };
|
|
7381
|
+
} catch (err) {
|
|
7382
|
+
return { succeeded: false, error: err instanceof Error ? err.message : String(err) };
|
|
7383
|
+
} finally {
|
|
7384
|
+
clearTimeout(timer);
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
}
|
|
7388
|
+
};
|
|
7389
|
+
}
|
|
7390
|
+
function clip(text2, limit) {
|
|
7391
|
+
if (text2.length <= limit) return text2;
|
|
7392
|
+
const half = Math.floor(limit / 2);
|
|
7393
|
+
return `${text2.slice(0, half)}
|
|
7394
|
+
\u2026 [${text2.length - limit} chars elided] \u2026
|
|
7395
|
+
${text2.slice(-half)}`;
|
|
7396
|
+
}
|
|
7397
|
+
function renderStep(step, marker) {
|
|
7398
|
+
const rc = parseRecordedReturncode(step.observation);
|
|
7399
|
+
const output = clip(parseObservationOutput(step.observation).trim(), 1600);
|
|
7400
|
+
return [
|
|
7401
|
+
`### step ${step.step_id}${marker}`,
|
|
7402
|
+
"```sh",
|
|
7403
|
+
clip(step.action, 2e3),
|
|
7404
|
+
"```",
|
|
7405
|
+
`returncode: ${rc ?? "none recorded"}`,
|
|
7406
|
+
output.length > 0 ? `output:
|
|
7407
|
+
\`\`\`
|
|
7408
|
+
${output}
|
|
7409
|
+
\`\`\`` : "output: (empty)"
|
|
7410
|
+
].join("\n");
|
|
7411
|
+
}
|
|
7412
|
+
function buildFixPrompt(input) {
|
|
7413
|
+
const radius = input.contextRadius ?? 3;
|
|
7414
|
+
const target = input.steps.find((s) => s.step_id === input.k);
|
|
7415
|
+
if (!target) throw new Error(`replay-fix: no step with step_id ${input.k}`);
|
|
7416
|
+
const context = input.steps.filter(
|
|
7417
|
+
(s) => s.step_id !== input.k && Math.abs(s.step_id - input.k) <= radius
|
|
7418
|
+
);
|
|
7419
|
+
const system = [
|
|
7420
|
+
"You repair one failed shell command from a recorded coding-agent trajectory.",
|
|
7421
|
+
"The trajectory replays inside the original docker image; every command runs as a fresh /bin/sh subshell from a fixed working directory.",
|
|
7422
|
+
"You are given the failing step, its recorded output, surrounding steps, and the task statement.",
|
|
7423
|
+
"Reply with exactly ONE corrected shell command (compound commands with && or pipes are fine) inside a single ```sh fenced block.",
|
|
7424
|
+
"The corrected command must accomplish the failing step's intent and exit 0. No prose outside the fenced block."
|
|
7425
|
+
].join("\n");
|
|
7426
|
+
const user = [
|
|
7427
|
+
"## Task the agent was solving",
|
|
7428
|
+
input.taskStatement ? clip(input.taskStatement, 4e3) : "(no task statement recorded)",
|
|
7429
|
+
"",
|
|
7430
|
+
"## Surrounding steps",
|
|
7431
|
+
...context.map((s) => renderStep(s, "")),
|
|
7432
|
+
"",
|
|
7433
|
+
"## Failing step to correct",
|
|
7434
|
+
renderStep(target, " (INCORRECT \u2014 correct this one)"),
|
|
7435
|
+
"",
|
|
7436
|
+
"Output the single corrected replacement for the failing step now."
|
|
7437
|
+
].join("\n");
|
|
7438
|
+
return { system, user };
|
|
7439
|
+
}
|
|
7440
|
+
function extractFixCommand(content) {
|
|
7441
|
+
const blocks = [...content.matchAll(/```(?:sh|bash|shell)?\n([\s\S]*?)```/g)];
|
|
7442
|
+
const candidate = blocks.length > 0 ? blocks[blocks.length - 1][1] : content;
|
|
7443
|
+
const command = candidate.trim();
|
|
7444
|
+
if (command.length === 0 || command.includes("```")) return null;
|
|
7445
|
+
return command;
|
|
7446
|
+
}
|
|
7447
|
+
async function generateFixCommand(caller, input) {
|
|
7448
|
+
const { system, user } = buildFixPrompt(input);
|
|
7449
|
+
const outcome = await caller.complete(system, user);
|
|
7450
|
+
if (!outcome.succeeded) return outcome;
|
|
7451
|
+
const command = extractFixCommand(outcome.value.content);
|
|
7452
|
+
if (command === null) {
|
|
7453
|
+
return {
|
|
7454
|
+
succeeded: false,
|
|
7455
|
+
error: `completion carried no usable command: ${clip(outcome.value.content, 300)}`
|
|
7456
|
+
};
|
|
7457
|
+
}
|
|
7458
|
+
return { succeeded: true, value: { command, usage: outcome.value.usage } };
|
|
7459
|
+
}
|
|
7460
|
+
|
|
7461
|
+
// src/replay-batch.ts
|
|
7462
|
+
import { execFile } from "child_process";
|
|
7463
|
+
import { createHash as createHash8 } from "crypto";
|
|
7464
|
+
import { appendFileSync, mkdirSync as mkdirSync2, mkdtempSync, writeFileSync as writeFileSync2 } from "fs";
|
|
7465
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
7466
|
+
import { join as join21 } from "path";
|
|
7467
|
+
import { promisify } from "util";
|
|
7468
|
+
import {
|
|
7469
|
+
InMemoryTraceStore as InMemoryTraceStore3,
|
|
7470
|
+
runCounterfactual as runCounterfactual2
|
|
7471
|
+
} from "@tangle-network/agent-eval";
|
|
7472
|
+
var execFileAsync = promisify(execFile);
|
|
7473
|
+
function derivedImageTag(image, cwd) {
|
|
7474
|
+
const digest = createHash8("sha256").update(`${image}
|
|
7475
|
+
${cwd}`).digest("hex").slice(0, 12);
|
|
7476
|
+
return `ctb-replay:${digest}-uid1000`;
|
|
7477
|
+
}
|
|
7478
|
+
function dockerImagePreparer(options = {}) {
|
|
7479
|
+
const pullTimeoutMs = options.pullTimeoutMs ?? 12e5;
|
|
7480
|
+
const buildTimeoutMs = options.buildTimeoutMs ?? 9e5;
|
|
7481
|
+
const imageExists = async (tag) => {
|
|
7482
|
+
try {
|
|
7483
|
+
await execFileAsync("docker", ["image", "inspect", tag], { maxBuffer: 8 * 1024 * 1024 });
|
|
7484
|
+
return true;
|
|
7485
|
+
} catch {
|
|
7486
|
+
return false;
|
|
7487
|
+
}
|
|
7488
|
+
};
|
|
7489
|
+
const errorTail = (err) => {
|
|
7490
|
+
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);
|
|
7491
|
+
return raw.trim().split("\n").slice(-3).join(" | ").slice(0, 400);
|
|
7492
|
+
};
|
|
7493
|
+
return {
|
|
7494
|
+
async ensure(image, cwd) {
|
|
7495
|
+
if (cwd === "/") {
|
|
7496
|
+
if (await imageExists(image)) {
|
|
7497
|
+
return { succeeded: true, value: { derivedImage: image, pulled: false, built: false } };
|
|
7498
|
+
}
|
|
7499
|
+
try {
|
|
7500
|
+
await execFileAsync("docker", ["pull", image], {
|
|
7501
|
+
timeout: pullTimeoutMs,
|
|
7502
|
+
maxBuffer: 32 * 1024 * 1024
|
|
7503
|
+
});
|
|
7504
|
+
} catch (err) {
|
|
7505
|
+
return { succeeded: false, error: `pull ${image}: ${errorTail(err)}` };
|
|
7506
|
+
}
|
|
7507
|
+
return { succeeded: true, value: { derivedImage: image, pulled: true, built: false } };
|
|
7508
|
+
}
|
|
7509
|
+
const derived = derivedImageTag(image, cwd);
|
|
7510
|
+
if (await imageExists(derived)) {
|
|
7511
|
+
return { succeeded: true, value: { derivedImage: derived, pulled: false, built: false } };
|
|
7512
|
+
}
|
|
7513
|
+
let pulled = false;
|
|
7514
|
+
if (!await imageExists(image)) {
|
|
7515
|
+
try {
|
|
7516
|
+
await execFileAsync("docker", ["pull", image], {
|
|
7517
|
+
timeout: pullTimeoutMs,
|
|
7518
|
+
maxBuffer: 32 * 1024 * 1024
|
|
7519
|
+
});
|
|
7520
|
+
pulled = true;
|
|
7521
|
+
} catch (err) {
|
|
7522
|
+
return { succeeded: false, error: `pull ${image}: ${errorTail(err)}` };
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7525
|
+
const contextDir = mkdtempSync(join21(tmpdir3(), "ctb-replay-image-"));
|
|
7526
|
+
const quotedCwd = `'${cwd.replaceAll("'", `'\\''`)}'`;
|
|
7527
|
+
writeFileSync2(
|
|
7528
|
+
join21(contextDir, "Dockerfile"),
|
|
7529
|
+
`FROM ${image}
|
|
7530
|
+
RUN chown -R 1000:1000 ${quotedCwd}
|
|
7531
|
+
`
|
|
7532
|
+
);
|
|
7533
|
+
try {
|
|
7534
|
+
await execFileAsync("docker", ["build", "-t", derived, contextDir], {
|
|
7535
|
+
timeout: buildTimeoutMs,
|
|
7536
|
+
maxBuffer: 32 * 1024 * 1024
|
|
7537
|
+
});
|
|
7538
|
+
} catch (err) {
|
|
7539
|
+
return { succeeded: false, error: `build ${derived} from ${image}: ${errorTail(err)}` };
|
|
7540
|
+
}
|
|
7541
|
+
return { succeeded: true, value: { derivedImage: derived, pulled, built: true } };
|
|
7542
|
+
}
|
|
7543
|
+
};
|
|
7544
|
+
}
|
|
7545
|
+
function mulberry32(seed) {
|
|
7546
|
+
let state = seed >>> 0;
|
|
7547
|
+
return () => {
|
|
7548
|
+
state = state + 1831565813 >>> 0;
|
|
7549
|
+
let t = state;
|
|
7550
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
7551
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
7552
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
7553
|
+
};
|
|
7554
|
+
}
|
|
7555
|
+
function seededSample(items, size, seed) {
|
|
7556
|
+
const pool = [...items];
|
|
7557
|
+
const random = mulberry32(seed);
|
|
7558
|
+
for (let i = pool.length - 1; i > 0; i--) {
|
|
7559
|
+
const j = Math.floor(random() * (i + 1));
|
|
7560
|
+
[pool[i], pool[j]] = [pool[j], pool[i]];
|
|
7561
|
+
}
|
|
7562
|
+
return new Set(pool.slice(0, size));
|
|
7563
|
+
}
|
|
7564
|
+
function caseOutDirName(row) {
|
|
7565
|
+
return `${row.corpus}--${row.trajId}`.replaceAll(/[^A-Za-z0-9._-]/g, "_").slice(0, 180);
|
|
7566
|
+
}
|
|
7567
|
+
async function executeArmB(replayCase, fixCommand, backend, signature, stepTimeoutMs, prefixLimit, onProgress) {
|
|
7568
|
+
const store = new InMemoryTraceStore3();
|
|
7569
|
+
const { runId } = await ingestCodeTraceBenchSteps(store, replayCase.steps, replayCase.trajId);
|
|
7570
|
+
const runner = new SandboxCounterfactualRunner(backend, {
|
|
7571
|
+
cwd: replayCase.cwd,
|
|
7572
|
+
stepTimeoutMs,
|
|
7573
|
+
prefixLimit,
|
|
7574
|
+
onProgress
|
|
7575
|
+
});
|
|
7576
|
+
await runCounterfactual2(
|
|
7577
|
+
store,
|
|
7578
|
+
runId,
|
|
7579
|
+
{
|
|
7580
|
+
kind: "custom",
|
|
7581
|
+
at: replayCase.k - 1,
|
|
7582
|
+
describe: "arm-B corrected step",
|
|
7583
|
+
apply: (step) => ({
|
|
7584
|
+
...step,
|
|
7585
|
+
span: { ...step.span, args: { command: fixCommand } }
|
|
7586
|
+
})
|
|
7587
|
+
},
|
|
7588
|
+
runner
|
|
7589
|
+
);
|
|
7590
|
+
const exec = runner.lastArm;
|
|
7591
|
+
if (!exec) throw new Error("arm B finished without executing the corrected step");
|
|
7592
|
+
const output = `${exec.stdout}
|
|
7593
|
+
${exec.stderr}`;
|
|
7594
|
+
return {
|
|
7595
|
+
exitCode: exec.exitCode,
|
|
7596
|
+
prefixExecuted: runner.lastPrefix?.prefixExecuted ?? 0,
|
|
7597
|
+
prefixDivergences: runner.lastPrefix?.prefixDivergences.length ?? 0,
|
|
7598
|
+
failureVanished: exec.exitCode === 0 && (signature ? !output.includes(signature) : true),
|
|
7599
|
+
stdout: exec.stdout,
|
|
7600
|
+
stderr: exec.stderr
|
|
7601
|
+
};
|
|
7602
|
+
}
|
|
7603
|
+
function rate(numerator, denominator) {
|
|
7604
|
+
return denominator > 0 ? numerator / denominator : null;
|
|
7605
|
+
}
|
|
7606
|
+
async function runReplayBatch(options) {
|
|
7607
|
+
const onProgress = options.onProgress ?? (() => {
|
|
7608
|
+
});
|
|
7609
|
+
const enumeration = enumerateReplayableCases(options.corpora);
|
|
7610
|
+
let selected = enumeration.replayable;
|
|
7611
|
+
if (options.caseFilter) {
|
|
7612
|
+
selected = selected.filter((c) => c.trajId.includes(options.caseFilter));
|
|
7613
|
+
}
|
|
7614
|
+
if (options.caseLimit !== void 0) selected = selected.slice(0, options.caseLimit);
|
|
7615
|
+
onProgress(
|
|
7616
|
+
`enumerated ${enumeration.labelEntryCount} label entries \u2192 ${enumeration.replayable.length} replayable, ${enumeration.excluded.length} excluded; executing ${selected.length}`
|
|
7617
|
+
);
|
|
7618
|
+
const preparer = options.preparer ?? dockerImagePreparer();
|
|
7619
|
+
const backendFactory = options.backendFactory ?? ((derivedImage) => sandboxReplayBackend({
|
|
7620
|
+
image: derivedImage,
|
|
7621
|
+
apiKey: requireOption2(options.apiKey, "apiKey"),
|
|
7622
|
+
baseUrl: requireOption2(options.baseUrl, "baseUrl"),
|
|
7623
|
+
maxLifetimeSeconds: options.maxLifetimeSeconds ?? 3600
|
|
7624
|
+
}));
|
|
7625
|
+
mkdirSync2(options.out, { recursive: true });
|
|
7626
|
+
const progressPath = join21(options.out, "cases.jsonl");
|
|
7627
|
+
const rows = [];
|
|
7628
|
+
const pullFailures = [];
|
|
7629
|
+
const verdictByTraj = /* @__PURE__ */ new Map();
|
|
7630
|
+
for (const [index, replayCase] of selected.entries()) {
|
|
7631
|
+
const caseStart = Date.now();
|
|
7632
|
+
const label = `[${index + 1}/${selected.length}] ${replayCase.corpus}/${replayCase.trajId}`;
|
|
7633
|
+
onProgress(`${label}: preparing image ${replayCase.image}`);
|
|
7634
|
+
const preparation = await preparer.ensure(replayCase.image, replayCase.cwd);
|
|
7635
|
+
const base = {
|
|
7636
|
+
corpus: replayCase.corpus,
|
|
7637
|
+
trajId: replayCase.trajId,
|
|
7638
|
+
image: replayCase.image,
|
|
7639
|
+
cwd: replayCase.cwd,
|
|
7640
|
+
cwdSource: replayCase.cwdSource,
|
|
7641
|
+
k: replayCase.k,
|
|
7642
|
+
stepCount: replayCase.steps.length,
|
|
7643
|
+
goldIncorrectSteps: replayCase.goldIncorrectSteps,
|
|
7644
|
+
submitGoldsSkipped: replayCase.submitGoldsSkipped,
|
|
7645
|
+
recordedReturncodeAtK: replayCase.recordedReturncodeAtK
|
|
7646
|
+
};
|
|
7647
|
+
if (!preparation.succeeded) {
|
|
7648
|
+
pullFailures.push({
|
|
7649
|
+
corpus: replayCase.corpus,
|
|
7650
|
+
trajId: replayCase.trajId,
|
|
7651
|
+
image: replayCase.image,
|
|
7652
|
+
error: preparation.error
|
|
7653
|
+
});
|
|
7654
|
+
const row = {
|
|
7655
|
+
...base,
|
|
7656
|
+
derivedImage: null,
|
|
7657
|
+
signature: null,
|
|
7658
|
+
status: "image-unavailable",
|
|
7659
|
+
error: preparation.error,
|
|
7660
|
+
imagePulled: false,
|
|
7661
|
+
imageBuilt: false,
|
|
7662
|
+
prefixExecuted: null,
|
|
7663
|
+
prefixDivergences: null,
|
|
7664
|
+
prefixDivergencePct: null,
|
|
7665
|
+
armAExit: null,
|
|
7666
|
+
armAReturncodeMatch: false,
|
|
7667
|
+
armASignatureMatch: false,
|
|
7668
|
+
replayed: false,
|
|
7669
|
+
fix: null,
|
|
7670
|
+
wallMs: Date.now() - caseStart
|
|
7671
|
+
};
|
|
7672
|
+
rows.push(row);
|
|
7673
|
+
appendFileSync(progressPath, `${JSON.stringify(row)}
|
|
7674
|
+
`);
|
|
7675
|
+
onProgress(`${label}: image unavailable \u2014 ${preparation.error}`);
|
|
7676
|
+
continue;
|
|
7677
|
+
}
|
|
7678
|
+
const { derivedImage, pulled, built } = preparation.value;
|
|
7679
|
+
const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
|
|
7680
|
+
const caseOut = join21(options.out, caseOutDirName(replayCase));
|
|
7681
|
+
onProgress(`${label}: arm A on ${derivedImage} (k=${replayCase.k}, timeout ${stepTimeoutMs}ms)`);
|
|
7682
|
+
try {
|
|
7683
|
+
const verdict = await replayVerify({
|
|
7684
|
+
stepsPath: replayCase.stepsPath,
|
|
7685
|
+
image: derivedImage,
|
|
7686
|
+
at: replayCase.k,
|
|
7687
|
+
cwd: replayCase.cwd,
|
|
7688
|
+
out: caseOut,
|
|
7689
|
+
caseId: replayCase.trajId,
|
|
7690
|
+
stepTimeoutMs,
|
|
7691
|
+
prefixLimit: options.prefixLimit,
|
|
7692
|
+
backend: backendFactory(derivedImage),
|
|
7693
|
+
onProgress: (message) => onProgress(`${label}: ${message}`)
|
|
7694
|
+
});
|
|
7695
|
+
verdictByTraj.set(replayCase.trajId, verdict);
|
|
7696
|
+
const divergencePct = verdict.prefixExecuted > 0 ? verdict.prefixDivergences.length / verdict.prefixExecuted * 100 : 0;
|
|
7697
|
+
const returncodeMatch = verdict.recordedReturncode !== null && verdict.armA.exitCode === verdict.recordedReturncode;
|
|
7698
|
+
const row = {
|
|
7699
|
+
...base,
|
|
7700
|
+
derivedImage,
|
|
7701
|
+
signature: verdict.signature,
|
|
7702
|
+
status: "ok",
|
|
7703
|
+
error: null,
|
|
7704
|
+
imagePulled: pulled,
|
|
7705
|
+
imageBuilt: built,
|
|
7706
|
+
prefixExecuted: verdict.prefixExecuted,
|
|
7707
|
+
prefixDivergences: verdict.prefixDivergences.length,
|
|
7708
|
+
prefixDivergencePct: Number(divergencePct.toFixed(1)),
|
|
7709
|
+
armAExit: verdict.armA.exitCode,
|
|
7710
|
+
armAReturncodeMatch: returncodeMatch,
|
|
7711
|
+
armASignatureMatch: verdict.armA.failureSignatureMatch,
|
|
7712
|
+
replayed: divergencePct <= 10 && returncodeMatch,
|
|
7713
|
+
fix: null,
|
|
7714
|
+
wallMs: Date.now() - caseStart
|
|
7715
|
+
};
|
|
7716
|
+
rows.push(row);
|
|
7717
|
+
appendFileSync(progressPath, `${JSON.stringify(row)}
|
|
7718
|
+
`);
|
|
7719
|
+
onProgress(
|
|
7720
|
+
`${label}: armA exit=${verdict.armA.exitCode} rcMatch=${returncodeMatch} divergences=${verdict.prefixDivergences.length}/${verdict.prefixExecuted}`
|
|
7721
|
+
);
|
|
7722
|
+
} catch (err) {
|
|
7723
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7724
|
+
const row = {
|
|
7725
|
+
...base,
|
|
7726
|
+
derivedImage,
|
|
7727
|
+
signature: null,
|
|
7728
|
+
status: "replay-error",
|
|
7729
|
+
error: message.slice(0, 500),
|
|
7730
|
+
imagePulled: pulled,
|
|
7731
|
+
imageBuilt: built,
|
|
7732
|
+
prefixExecuted: null,
|
|
7733
|
+
prefixDivergences: null,
|
|
7734
|
+
prefixDivergencePct: null,
|
|
7735
|
+
armAExit: null,
|
|
7736
|
+
armAReturncodeMatch: false,
|
|
7737
|
+
armASignatureMatch: false,
|
|
7738
|
+
replayed: false,
|
|
7739
|
+
fix: null,
|
|
7740
|
+
wallMs: Date.now() - caseStart
|
|
7741
|
+
};
|
|
7742
|
+
rows.push(row);
|
|
7743
|
+
appendFileSync(progressPath, `${JSON.stringify(row)}
|
|
7744
|
+
`);
|
|
7745
|
+
onProgress(`${label}: replay error \u2014 ${message.slice(0, 200)}`);
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
let llm = null;
|
|
7749
|
+
if (options.fix === "generate") {
|
|
7750
|
+
const caller = options.fixCaller;
|
|
7751
|
+
if (!caller) throw new Error("replay-verify-batch: fix=generate requires a fixCaller");
|
|
7752
|
+
const maxFixCases = options.maxFixCases ?? 30;
|
|
7753
|
+
const seed = options.seed ?? 17;
|
|
7754
|
+
const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
|
|
7755
|
+
const sampled = eligible.length > maxFixCases ? seededSample(eligible, maxFixCases, seed) : new Set(eligible);
|
|
7756
|
+
if (eligible.length > maxFixCases) {
|
|
7757
|
+
onProgress(
|
|
7758
|
+
`fix phase: ${eligible.length} eligible > cap ${maxFixCases}; seeded sample (seed=${seed})`
|
|
7759
|
+
);
|
|
7760
|
+
}
|
|
7761
|
+
let calls = 0;
|
|
7762
|
+
let failures = 0;
|
|
7763
|
+
let promptTokens = 0;
|
|
7764
|
+
let completionTokens = 0;
|
|
7765
|
+
for (const row of eligible) {
|
|
7766
|
+
const index = rows.indexOf(row);
|
|
7767
|
+
if (!sampled.has(row)) {
|
|
7768
|
+
rows[index] = {
|
|
7769
|
+
...row,
|
|
7770
|
+
fix: {
|
|
7771
|
+
attempted: false,
|
|
7772
|
+
sampledOut: true,
|
|
7773
|
+
command: null,
|
|
7774
|
+
llmError: null,
|
|
7775
|
+
usage: null,
|
|
7776
|
+
armBExit: null,
|
|
7777
|
+
armBPrefixExecuted: null,
|
|
7778
|
+
armBPrefixDivergences: null,
|
|
7779
|
+
failureVanished: null,
|
|
7780
|
+
armBError: null
|
|
7781
|
+
}
|
|
7782
|
+
};
|
|
7783
|
+
continue;
|
|
7784
|
+
}
|
|
7785
|
+
const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
|
|
7786
|
+
const label = `fix ${row.corpus}/${row.trajId}`;
|
|
7787
|
+
onProgress(`${label}: generating corrected command`);
|
|
7788
|
+
calls += 1;
|
|
7789
|
+
const generated = await generateFixCommand(caller, {
|
|
7790
|
+
taskStatement: replayCase.taskStatement,
|
|
7791
|
+
steps: replayCase.steps,
|
|
7792
|
+
k: replayCase.k
|
|
7793
|
+
});
|
|
7794
|
+
if (!generated.succeeded) {
|
|
7795
|
+
failures += 1;
|
|
7796
|
+
rows[index] = {
|
|
7797
|
+
...row,
|
|
7798
|
+
fix: {
|
|
7799
|
+
attempted: true,
|
|
7800
|
+
sampledOut: false,
|
|
7801
|
+
command: null,
|
|
7802
|
+
llmError: generated.error,
|
|
7803
|
+
usage: null,
|
|
7804
|
+
armBExit: null,
|
|
7805
|
+
armBPrefixExecuted: null,
|
|
7806
|
+
armBPrefixDivergences: null,
|
|
7807
|
+
failureVanished: null,
|
|
7808
|
+
armBError: null
|
|
7809
|
+
}
|
|
7810
|
+
};
|
|
7811
|
+
onProgress(`${label}: LLM failed \u2014 ${generated.error.slice(0, 200)}`);
|
|
7812
|
+
continue;
|
|
7813
|
+
}
|
|
7814
|
+
promptTokens += generated.value.usage?.promptTokens ?? 0;
|
|
7815
|
+
completionTokens += generated.value.usage?.completionTokens ?? 0;
|
|
7816
|
+
const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
|
|
7817
|
+
const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
|
|
7818
|
+
onProgress(`${label}: arm B \u2014 ${generated.value.command.split("\n")[0].slice(0, 120)}`);
|
|
7819
|
+
try {
|
|
7820
|
+
const armB = await executeArmB(
|
|
7821
|
+
replayCase,
|
|
7822
|
+
generated.value.command,
|
|
7823
|
+
backendFactory(row.derivedImage),
|
|
7824
|
+
signature,
|
|
7825
|
+
stepTimeoutMs,
|
|
7826
|
+
options.prefixLimit,
|
|
7827
|
+
(message) => onProgress(`${label}: ${message}`)
|
|
7828
|
+
);
|
|
7829
|
+
rows[index] = {
|
|
7830
|
+
...row,
|
|
7831
|
+
fix: {
|
|
7832
|
+
attempted: true,
|
|
7833
|
+
sampledOut: false,
|
|
7834
|
+
command: generated.value.command,
|
|
7835
|
+
llmError: null,
|
|
7836
|
+
usage: generated.value.usage,
|
|
7837
|
+
armBExit: armB.exitCode,
|
|
7838
|
+
armBPrefixExecuted: armB.prefixExecuted,
|
|
7839
|
+
armBPrefixDivergences: armB.prefixDivergences,
|
|
7840
|
+
failureVanished: armB.failureVanished,
|
|
7841
|
+
armBError: null
|
|
7842
|
+
}
|
|
7843
|
+
};
|
|
7844
|
+
const caseOut = join21(options.out, caseOutDirName(row));
|
|
7845
|
+
mkdirSync2(caseOut, { recursive: true });
|
|
7846
|
+
writeFileSync2(
|
|
7847
|
+
join21(caseOut, "armB-result.json"),
|
|
7848
|
+
`${JSON.stringify({ command: generated.value.command, ...armB }, null, 2)}
|
|
7849
|
+
`
|
|
7850
|
+
);
|
|
7851
|
+
onProgress(`${label}: armB exit=${armB.exitCode} failureVanished=${armB.failureVanished}`);
|
|
7852
|
+
} catch (err) {
|
|
7853
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7854
|
+
rows[index] = {
|
|
7855
|
+
...row,
|
|
7856
|
+
fix: {
|
|
7857
|
+
attempted: true,
|
|
7858
|
+
sampledOut: false,
|
|
7859
|
+
command: generated.value.command,
|
|
7860
|
+
llmError: null,
|
|
7861
|
+
usage: generated.value.usage,
|
|
7862
|
+
armBExit: null,
|
|
7863
|
+
armBPrefixExecuted: null,
|
|
7864
|
+
armBPrefixDivergences: null,
|
|
7865
|
+
failureVanished: null,
|
|
7866
|
+
armBError: message.slice(0, 500)
|
|
7867
|
+
}
|
|
7868
|
+
};
|
|
7869
|
+
onProgress(`${label}: arm B error \u2014 ${message.slice(0, 200)}`);
|
|
7870
|
+
}
|
|
7871
|
+
}
|
|
7872
|
+
llm = {
|
|
7873
|
+
model: options.fixModelLabel ?? "unknown",
|
|
7874
|
+
calls,
|
|
7875
|
+
failures,
|
|
7876
|
+
promptTokens,
|
|
7877
|
+
completionTokens
|
|
7878
|
+
};
|
|
7879
|
+
}
|
|
7880
|
+
const executedRows = rows.filter((r) => r.status === "ok");
|
|
7881
|
+
const replayed = executedRows.filter((r) => r.replayed);
|
|
7882
|
+
const signatureStrict = executedRows.filter(
|
|
7883
|
+
(r) => r.replayed && r.armASignatureMatch
|
|
7884
|
+
);
|
|
7885
|
+
const armBExecuted = rows.filter((r) => r.fix?.attempted && r.fix.failureVanished !== null);
|
|
7886
|
+
const flipped = armBExecuted.filter((r) => r.fix.failureVanished === true);
|
|
7887
|
+
const armBNonzeroRc = armBExecuted.filter(
|
|
7888
|
+
(r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0
|
|
7889
|
+
);
|
|
7890
|
+
const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
|
|
7891
|
+
const excludedByReason = {};
|
|
7892
|
+
for (const excluded of enumeration.excluded) {
|
|
7893
|
+
excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
|
|
7894
|
+
}
|
|
7895
|
+
const submitGoldsByCorpus = {};
|
|
7896
|
+
const submitEntry = (corpus) => submitGoldsByCorpus[corpus] ??= { submitOnlyCases: 0, goldsSkippedWithinReplayable: 0 };
|
|
7897
|
+
for (const excluded of enumeration.excluded) {
|
|
7898
|
+
if (excluded.reason === "gold-only-submit-step") submitEntry(excluded.corpus).submitOnlyCases += 1;
|
|
7899
|
+
}
|
|
7900
|
+
for (const replayCase of enumeration.replayable) {
|
|
7901
|
+
if (replayCase.submitGoldsSkipped > 0) {
|
|
7902
|
+
submitEntry(replayCase.corpus).goldsSkippedWithinReplayable += replayCase.submitGoldsSkipped;
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
const report = {
|
|
7906
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7907
|
+
corpora: options.corpora.map((c) => ({
|
|
7908
|
+
name: c.name,
|
|
7909
|
+
labelsPath: c.labelsPath,
|
|
7910
|
+
preparedDir: c.preparedDir
|
|
7911
|
+
})),
|
|
7912
|
+
totals: {
|
|
7913
|
+
labelEntries: enumeration.labelEntryCount,
|
|
7914
|
+
replayable: enumeration.replayable.length,
|
|
7915
|
+
executed: selected.length,
|
|
7916
|
+
excludedByReason,
|
|
7917
|
+
submitGoldsByCorpus
|
|
7918
|
+
},
|
|
7919
|
+
headline: {
|
|
7920
|
+
replayabilityRate: {
|
|
7921
|
+
numerator: replayed.length,
|
|
7922
|
+
denominator: selected.length,
|
|
7923
|
+
value: rate(replayed.length, selected.length)
|
|
7924
|
+
},
|
|
7925
|
+
signatureStrictRate: {
|
|
7926
|
+
numerator: signatureStrict.length,
|
|
7927
|
+
denominator: selected.length,
|
|
7928
|
+
value: rate(signatureStrict.length, selected.length)
|
|
7929
|
+
},
|
|
7930
|
+
fixFlipRate: options.fix === "generate" ? {
|
|
7931
|
+
numerator: flipped.length,
|
|
7932
|
+
denominator: armBExecuted.length,
|
|
7933
|
+
value: rate(flipped.length, armBExecuted.length)
|
|
7934
|
+
} : null,
|
|
7935
|
+
fixFlipRateNonzeroRc: options.fix === "generate" ? {
|
|
7936
|
+
numerator: flippedNonzeroRc.length,
|
|
7937
|
+
denominator: armBNonzeroRc.length,
|
|
7938
|
+
value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
|
|
7939
|
+
} : null
|
|
7940
|
+
},
|
|
7941
|
+
llm,
|
|
7942
|
+
excluded: enumeration.excluded,
|
|
7943
|
+
pullFailures,
|
|
7944
|
+
cases: rows
|
|
7945
|
+
};
|
|
7946
|
+
writeFileSync2(join21(options.out, "batch-report.json"), `${JSON.stringify(report, null, 2)}
|
|
7947
|
+
`);
|
|
7948
|
+
writeFileSync2(join21(options.out, "batch-report.md"), renderBatchReport(report));
|
|
7949
|
+
return report;
|
|
7950
|
+
}
|
|
7951
|
+
function requireOption2(value, name) {
|
|
7952
|
+
if (value === void 0) throw new Error(`replay-verify-batch: ${name} is required`);
|
|
7953
|
+
return value;
|
|
7954
|
+
}
|
|
7955
|
+
function pct(value) {
|
|
7956
|
+
return value === null ? "\u2014" : `${(value * 100).toFixed(1)}%`;
|
|
7957
|
+
}
|
|
7958
|
+
function renderBatchReport(report) {
|
|
7959
|
+
const lines = [];
|
|
7960
|
+
const { headline, totals } = report;
|
|
7961
|
+
lines.push("# Replay-verify batch report");
|
|
7962
|
+
lines.push("");
|
|
7963
|
+
lines.push(`Generated ${report.generatedAt}.`);
|
|
7964
|
+
lines.push("");
|
|
7965
|
+
lines.push("## Headline");
|
|
7966
|
+
lines.push("");
|
|
7967
|
+
lines.push(
|
|
7968
|
+
`- **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).`
|
|
7969
|
+
);
|
|
7970
|
+
lines.push(
|
|
7971
|
+
`- Signature-strict rate: ${pct(headline.signatureStrictRate.value)} (${headline.signatureStrictRate.numerator}/${headline.signatureStrictRate.denominator}; additionally requires the recorded error substring in arm A output).`
|
|
7972
|
+
);
|
|
7973
|
+
if (headline.fixFlipRate) {
|
|
7974
|
+
lines.push(
|
|
7975
|
+
`- **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).`
|
|
7976
|
+
);
|
|
7977
|
+
}
|
|
7978
|
+
if (headline.fixFlipRateNonzeroRc) {
|
|
7979
|
+
lines.push(
|
|
7980
|
+
`- 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).`
|
|
7981
|
+
);
|
|
7982
|
+
}
|
|
7983
|
+
lines.push("");
|
|
7984
|
+
lines.push("## Enumeration");
|
|
7985
|
+
lines.push("");
|
|
7986
|
+
lines.push(
|
|
7987
|
+
`${totals.labelEntries} label entries across ${report.corpora.length} corpora \u2192 ${totals.replayable} replayable (SWE-style docker image + \u22651 gold incorrect step), ${totals.executed} executed.`
|
|
7988
|
+
);
|
|
7989
|
+
lines.push("");
|
|
7990
|
+
lines.push("| exclusion reason | count |");
|
|
7991
|
+
lines.push("| --- | --- |");
|
|
7992
|
+
for (const [reason, count2] of Object.entries(totals.excludedByReason).sort((a, b) => b[1] - a[1])) {
|
|
7993
|
+
lines.push(`| ${reason} | ${count2} |`);
|
|
7994
|
+
}
|
|
7995
|
+
const submitEntries = Object.entries(totals.submitGoldsByCorpus);
|
|
7996
|
+
if (submitEntries.length > 0) {
|
|
7997
|
+
lines.push("");
|
|
7998
|
+
lines.push(
|
|
7999
|
+
"Submit-command golds are never counterfactual targets (a gold on the submit step marks a bad submit decision, not a failed command):"
|
|
8000
|
+
);
|
|
8001
|
+
lines.push("");
|
|
8002
|
+
lines.push("| corpus | cases excluded (all golds = submit) | golds skipped within replayable cases |");
|
|
8003
|
+
lines.push("| --- | --- | --- |");
|
|
8004
|
+
for (const [corpus, stats] of submitEntries.sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
8005
|
+
lines.push(`| ${corpus} | ${stats.submitOnlyCases} | ${stats.goldsSkippedWithinReplayable} |`);
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
if (report.pullFailures.length > 0) {
|
|
8009
|
+
lines.push("");
|
|
8010
|
+
lines.push("## Image pull/build failures");
|
|
8011
|
+
lines.push("");
|
|
8012
|
+
lines.push("| corpus | trajectory | image | error |");
|
|
8013
|
+
lines.push("| --- | --- | --- | --- |");
|
|
8014
|
+
for (const failure of report.pullFailures) {
|
|
8015
|
+
lines.push(
|
|
8016
|
+
`| ${failure.corpus} | ${failure.trajId} | \`${failure.image}\` | ${failure.error.replaceAll("|", "\\|")} |`
|
|
8017
|
+
);
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
lines.push("");
|
|
8021
|
+
lines.push("## Per-case results");
|
|
8022
|
+
lines.push("");
|
|
8023
|
+
lines.push(
|
|
8024
|
+
"| corpus | trajectory | k | rc@k | prefix | div | div% | armA exit | rc match | sig match | replayed | fix | armB exit | vanished | wall s |"
|
|
8025
|
+
);
|
|
8026
|
+
lines.push(
|
|
8027
|
+
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
|
|
8028
|
+
);
|
|
8029
|
+
for (const row of report.cases) {
|
|
8030
|
+
const fix = row.fix;
|
|
8031
|
+
const fixCell = !fix ? "\u2014" : fix.sampledOut ? "sampled-out" : fix.llmError ? "llm-failed" : fix.armBError ? "armB-error" : "generated";
|
|
8032
|
+
lines.push(
|
|
8033
|
+
[
|
|
8034
|
+
row.corpus,
|
|
8035
|
+
row.trajId.length > 48 ? `${row.trajId.slice(0, 45)}\u2026` : row.trajId,
|
|
8036
|
+
row.k,
|
|
8037
|
+
row.recordedReturncodeAtK ?? "null",
|
|
8038
|
+
row.prefixExecuted ?? "\u2014",
|
|
8039
|
+
row.prefixDivergences ?? "\u2014",
|
|
8040
|
+
row.prefixDivergencePct ?? "\u2014",
|
|
8041
|
+
row.status === "ok" ? row.armAExit : row.status,
|
|
8042
|
+
row.armAReturncodeMatch ? "yes" : "no",
|
|
8043
|
+
row.armASignatureMatch ? "yes" : "no",
|
|
8044
|
+
row.replayed ? "**yes**" : "no",
|
|
8045
|
+
fixCell,
|
|
8046
|
+
fix?.armBExit ?? "\u2014",
|
|
8047
|
+
fix?.failureVanished === null || fix === null ? "\u2014" : fix.failureVanished ? "**yes**" : "no",
|
|
8048
|
+
(row.wallMs / 1e3).toFixed(1)
|
|
8049
|
+
].join(" | ")
|
|
8050
|
+
);
|
|
8051
|
+
}
|
|
8052
|
+
if (report.llm) {
|
|
8053
|
+
lines.push("");
|
|
8054
|
+
lines.push("## LLM fix generation");
|
|
8055
|
+
lines.push("");
|
|
8056
|
+
lines.push(
|
|
8057
|
+
`Model ${report.llm.model}: ${report.llm.calls} calls (${report.llm.failures} failed), ${report.llm.promptTokens} prompt + ${report.llm.completionTokens} completion tokens.`
|
|
8058
|
+
);
|
|
8059
|
+
}
|
|
8060
|
+
lines.push("");
|
|
8061
|
+
return lines.join("\n");
|
|
8062
|
+
}
|
|
8063
|
+
function parseReplayBatchArgs(argv) {
|
|
8064
|
+
const corpora = [];
|
|
8065
|
+
const values = /* @__PURE__ */ new Map();
|
|
8066
|
+
let enumerateOnly = false;
|
|
8067
|
+
for (let i = 0; i < argv.length; i++) {
|
|
8068
|
+
const arg = argv[i];
|
|
8069
|
+
if (arg === "--help" || arg === "-h") return "help";
|
|
8070
|
+
if (arg === "--enumerate-only") {
|
|
8071
|
+
enumerateOnly = true;
|
|
8072
|
+
continue;
|
|
8073
|
+
}
|
|
8074
|
+
if (!arg.startsWith("--")) throw new Error(`unexpected positional argument: ${arg}`);
|
|
8075
|
+
const value = argv[++i];
|
|
8076
|
+
if (value === void 0) throw new Error(`missing value for ${arg}`);
|
|
8077
|
+
if (arg === "--corpus") corpora.push(parseCorpusFlag(value));
|
|
8078
|
+
else values.set(arg, value);
|
|
8079
|
+
}
|
|
8080
|
+
if (corpora.length === 0) {
|
|
8081
|
+
throw new Error("replay-verify-batch: at least one --corpus name=<labels>::<prepared> is required");
|
|
8082
|
+
}
|
|
8083
|
+
const out = values.get("--out");
|
|
8084
|
+
if (!out && !enumerateOnly) throw new Error("replay-verify-batch: --out is required");
|
|
8085
|
+
const optionalNumber = (flag) => {
|
|
8086
|
+
const v = values.get(flag);
|
|
8087
|
+
if (v === void 0) return void 0;
|
|
8088
|
+
const n = Number(v);
|
|
8089
|
+
if (!Number.isFinite(n)) throw new Error(`replay-verify-batch: ${flag} must be a number, got ${v}`);
|
|
8090
|
+
return n;
|
|
8091
|
+
};
|
|
8092
|
+
const fix = values.get("--fix") ?? "none";
|
|
8093
|
+
if (fix !== "none" && fix !== "generate") {
|
|
8094
|
+
throw new Error(`replay-verify-batch: --fix must be none or generate, got ${fix}`);
|
|
8095
|
+
}
|
|
8096
|
+
return {
|
|
8097
|
+
corpora,
|
|
8098
|
+
out: out ?? ".",
|
|
8099
|
+
fix,
|
|
8100
|
+
fixModel: values.get("--fix-model") ?? "glm-5.2",
|
|
8101
|
+
fixBaseUrl: values.get("--fix-base-url") ?? "https://api.z.ai/api/coding/paas/v4",
|
|
8102
|
+
fixApiKeyEnv: values.get("--fix-api-key-env") ?? "ZAI_GLM_API_KEY",
|
|
8103
|
+
maxFixCases: optionalNumber("--max-fix-cases") ?? 30,
|
|
8104
|
+
seed: optionalNumber("--seed") ?? 17,
|
|
8105
|
+
stepTimeoutMs: optionalNumber("--step-timeout"),
|
|
8106
|
+
prefixLimit: optionalNumber("--prefix-limit"),
|
|
8107
|
+
caseFilter: values.get("--case-filter"),
|
|
8108
|
+
caseLimit: optionalNumber("--case-limit"),
|
|
8109
|
+
baseUrl: values.get("--base-url") ?? process.env.SANDBOX_API_URL ?? "http://127.0.0.1:4097",
|
|
8110
|
+
apiKeyEnv: values.get("--api-key-env") ?? "SANDBOX_API_KEY",
|
|
8111
|
+
maxLifetimeSeconds: optionalNumber("--max-lifetime"),
|
|
8112
|
+
enumerateOnly
|
|
8113
|
+
};
|
|
8114
|
+
}
|
|
8115
|
+
function replayBatchUsage() {
|
|
8116
|
+
return `traces replay-verify-batch \u2014 measure replayability and fix-flip rates across gold-labeled corpora
|
|
8117
|
+
|
|
8118
|
+
Usage:
|
|
8119
|
+
traces replay-verify-batch \\
|
|
8120
|
+
--corpus NAME=<labels.json>::<prepared-dir> [--corpus ...] \\
|
|
8121
|
+
--out DIR [--fix none|generate] [--enumerate-only] \\
|
|
8122
|
+
[--fix-model glm-5.2] [--fix-base-url URL] [--fix-api-key-env ZAI_GLM_API_KEY] \\
|
|
8123
|
+
[--max-fix-cases 30] [--seed 17] [--step-timeout MS] [--prefix-limit N] \\
|
|
8124
|
+
[--case-filter SUBSTRING] [--case-limit N] \\
|
|
8125
|
+
[--base-url URL] [--api-key-env SANDBOX_API_KEY] [--max-lifetime SECONDS]
|
|
8126
|
+
|
|
8127
|
+
Replayable = the raw trajectory carries info.docker_config.base_image AND the
|
|
8128
|
+
labels mark at least one gold incorrect step that is a real mid-trajectory
|
|
8129
|
+
action. k = the first such gold step; golds whose action is the agent's
|
|
8130
|
+
submit command (COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT) are skipped and
|
|
8131
|
+
counted, and cases whose golds are all submit steps are excluded as
|
|
8132
|
+
gold-only-submit-step. Excluded cases and image pull failures are report
|
|
8133
|
+
rows, never skipped silently. --enumerate-only prints the enumeration
|
|
8134
|
+
without touching a sandbox.
|
|
8135
|
+
|
|
8136
|
+
--fix generate runs ONE LLM call per arm-A-reproduced case (cap
|
|
8137
|
+
--max-fix-cases, seeded sample beyond it) and executes the corrected command
|
|
8138
|
+
as arm B in a fresh sandbox.
|
|
8139
|
+
|
|
8140
|
+
Outputs batch-report.json, batch-report.md, cases.jsonl (incremental), and one
|
|
8141
|
+
directory per case with the full replay-verify artifacts.
|
|
8142
|
+
See docs/replay-verify.md for orchestrator setup and honest limits.
|
|
8143
|
+
`;
|
|
8144
|
+
}
|
|
8145
|
+
async function cmdReplayVerifyBatch(argv) {
|
|
8146
|
+
const parsed = parseReplayBatchArgs(argv);
|
|
8147
|
+
if (parsed === "help") {
|
|
8148
|
+
process.stdout.write(replayBatchUsage());
|
|
8149
|
+
return;
|
|
8150
|
+
}
|
|
8151
|
+
if (parsed.enumerateOnly) {
|
|
8152
|
+
const enumeration = enumerateReplayableCases(parsed.corpora);
|
|
8153
|
+
process.stdout.write(
|
|
8154
|
+
`${JSON.stringify(
|
|
8155
|
+
{
|
|
8156
|
+
labelEntries: enumeration.labelEntryCount,
|
|
8157
|
+
replayable: enumeration.replayable.map((c) => ({
|
|
8158
|
+
corpus: c.corpus,
|
|
8159
|
+
trajId: c.trajId,
|
|
8160
|
+
image: c.image,
|
|
8161
|
+
cwd: c.cwd,
|
|
8162
|
+
cwdSource: c.cwdSource,
|
|
8163
|
+
k: c.k,
|
|
8164
|
+
submitGoldsSkipped: c.submitGoldsSkipped,
|
|
8165
|
+
recordedReturncodeAtK: c.recordedReturncodeAtK,
|
|
8166
|
+
goldIncorrectSteps: c.goldIncorrectSteps
|
|
8167
|
+
})),
|
|
8168
|
+
excluded: enumeration.excluded
|
|
8169
|
+
},
|
|
8170
|
+
null,
|
|
8171
|
+
2
|
|
8172
|
+
)}
|
|
8173
|
+
`
|
|
8174
|
+
);
|
|
8175
|
+
return;
|
|
8176
|
+
}
|
|
8177
|
+
const apiKey = process.env[parsed.apiKeyEnv];
|
|
8178
|
+
if (!apiKey) {
|
|
8179
|
+
throw new Error(
|
|
8180
|
+
`replay-verify-batch: env var ${parsed.apiKeyEnv} is empty \u2014 pass --api-key-env or export it`
|
|
8181
|
+
);
|
|
8182
|
+
}
|
|
8183
|
+
let fixCaller;
|
|
8184
|
+
if (parsed.fix === "generate") {
|
|
8185
|
+
const fixApiKey = process.env[parsed.fixApiKeyEnv];
|
|
8186
|
+
if (!fixApiKey) {
|
|
8187
|
+
throw new Error(
|
|
8188
|
+
`replay-verify-batch: env var ${parsed.fixApiKeyEnv} is empty \u2014 pass --fix-api-key-env or export it`
|
|
8189
|
+
);
|
|
8190
|
+
}
|
|
8191
|
+
fixCaller = zaiChatCaller({
|
|
8192
|
+
baseUrl: parsed.fixBaseUrl,
|
|
8193
|
+
apiKey: fixApiKey,
|
|
8194
|
+
model: parsed.fixModel
|
|
8195
|
+
});
|
|
8196
|
+
}
|
|
8197
|
+
const report = await runReplayBatch({
|
|
8198
|
+
corpora: parsed.corpora,
|
|
8199
|
+
out: parsed.out,
|
|
8200
|
+
baseUrl: parsed.baseUrl,
|
|
8201
|
+
apiKey,
|
|
8202
|
+
fix: parsed.fix,
|
|
8203
|
+
fixCaller,
|
|
8204
|
+
fixModelLabel: parsed.fixModel,
|
|
8205
|
+
maxFixCases: parsed.maxFixCases,
|
|
8206
|
+
seed: parsed.seed,
|
|
8207
|
+
stepTimeoutMs: parsed.stepTimeoutMs,
|
|
8208
|
+
prefixLimit: parsed.prefixLimit,
|
|
8209
|
+
caseFilter: parsed.caseFilter,
|
|
8210
|
+
caseLimit: parsed.caseLimit,
|
|
8211
|
+
maxLifetimeSeconds: parsed.maxLifetimeSeconds,
|
|
8212
|
+
onProgress: (message) => process.stderr.write(`${message}
|
|
8213
|
+
`)
|
|
8214
|
+
});
|
|
8215
|
+
const { replayabilityRate, fixFlipRate } = report.headline;
|
|
8216
|
+
process.stderr.write(
|
|
8217
|
+
`replay-verify-batch: replayability ${replayabilityRate.numerator}/${replayabilityRate.denominator}` + (fixFlipRate ? `, fix-flip ${fixFlipRate.numerator}/${fixFlipRate.denominator}` : "") + ` \u2192 ${parsed.out}
|
|
8218
|
+
`
|
|
8219
|
+
);
|
|
8220
|
+
}
|
|
8221
|
+
|
|
7144
8222
|
// src/external.ts
|
|
7145
8223
|
import { spawn } from "child_process";
|
|
7146
8224
|
import { readdir as readdir14, readFile as readFile7 } from "fs/promises";
|
|
@@ -7902,21 +8980,21 @@ function commandRedactor(spec) {
|
|
|
7902
8980
|
}
|
|
7903
8981
|
|
|
7904
8982
|
// src/hodoscope.ts
|
|
7905
|
-
import { createHash as
|
|
8983
|
+
import { createHash as createHash9 } from "crypto";
|
|
7906
8984
|
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
|
|
8985
|
+
import { endianness, tmpdir as tmpdir4 } from "os";
|
|
8986
|
+
import { join as join22, resolve as resolve5 } from "path";
|
|
7909
8987
|
var HODOSCOPE_VERSION = "0.2.4";
|
|
7910
8988
|
async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
7911
8989
|
const explicitDirectory = options.directory ? resolve5(options.directory) : void 0;
|
|
7912
|
-
const directory = explicitDirectory ?? await mkdtemp3(
|
|
8990
|
+
const directory = explicitDirectory ?? await mkdtemp3(join22(tmpdir4(), "traces-hodoscope-input-"));
|
|
7913
8991
|
if (explicitDirectory) {
|
|
7914
8992
|
await mkdir3(directory, { recursive: true });
|
|
7915
8993
|
if ((await readdir15(directory)).length > 0) {
|
|
7916
8994
|
throw new Error(`Hodoscope explicit directory must be empty: ${directory}`);
|
|
7917
8995
|
}
|
|
7918
8996
|
}
|
|
7919
|
-
const samples =
|
|
8997
|
+
const samples = join22(directory, "samples");
|
|
7920
8998
|
await mkdir3(samples);
|
|
7921
8999
|
const rows = [];
|
|
7922
9000
|
for await (const value of readJsonl(otlpPath)) rows.push(parseOpenInferenceRow(value));
|
|
@@ -7941,8 +9019,8 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
|
|
|
7941
9019
|
throw new Error(`Hodoscope trace '${traceId}' contains no assistant actions`);
|
|
7942
9020
|
}
|
|
7943
9021
|
actionCount += actions;
|
|
7944
|
-
const file = `${
|
|
7945
|
-
await writeFile4(
|
|
9022
|
+
const file = `${createHash9("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
|
|
9023
|
+
await writeFile4(join22(samples, file), `${JSON.stringify(trajectory, null, 2)}
|
|
7946
9024
|
`, "utf8");
|
|
7947
9025
|
}
|
|
7948
9026
|
return {
|
|
@@ -7964,12 +9042,12 @@ function hodoscopeAnalyzer(options = {}) {
|
|
|
7964
9042
|
return {
|
|
7965
9043
|
name: "hodoscope",
|
|
7966
9044
|
async analyze(otlpPath, runOptions2 = {}) {
|
|
7967
|
-
const outputRoot = options.outputDir ? resolve5(options.outputDir) :
|
|
9045
|
+
const outputRoot = options.outputDir ? resolve5(options.outputDir) : tmpdir4();
|
|
7968
9046
|
await mkdir3(outputRoot, { recursive: true });
|
|
7969
|
-
const directory = await mkdtemp3(
|
|
7970
|
-
const input =
|
|
7971
|
-
const analysisFile =
|
|
7972
|
-
const samplesFile =
|
|
9047
|
+
const directory = await mkdtemp3(join22(outputRoot, "traces-hodoscope-"));
|
|
9048
|
+
const input = join22(directory, "input");
|
|
9049
|
+
const analysisFile = join22(directory, "analysis.hodoscope.json");
|
|
9050
|
+
const samplesFile = join22(directory, "samples.json");
|
|
7973
9051
|
try {
|
|
7974
9052
|
const prepared = await writeHodoscopeInput(otlpPath, {
|
|
7975
9053
|
directory: input,
|
|
@@ -8600,9 +9678,19 @@ var CODING_REDACTION_RULES = [
|
|
|
8600
9678
|
id: "private-key",
|
|
8601
9679
|
pattern: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----[\s\S]*?-----END(?:[A-Z ]+)?PRIVATE KEY-----/g
|
|
8602
9680
|
},
|
|
9681
|
+
// Match the whole identifier because underscores prevent a word boundary before a secret suffix.
|
|
8603
9682
|
{
|
|
8604
9683
|
id: "assigned-secret",
|
|
8605
|
-
pattern:
|
|
9684
|
+
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
|
|
9685
|
+
},
|
|
9686
|
+
// Config-file secrets (YAML/INI line keys, JSON quoted keys) — the coverage the shell
|
|
9687
|
+
// rule above deliberately gave up to spare prose ("Narrative token: x") and type
|
|
9688
|
+
// annotations ("(token: string)"). The line anchor is the discriminator: config keys
|
|
9689
|
+
// start their line, prose colons never do; JSON keys are safe anywhere because both
|
|
9690
|
+
// sides are quoted. Identifiers must END in a secret word, same as the shell rule.
|
|
9691
|
+
{
|
|
9692
|
+
id: "config-secret",
|
|
9693
|
+
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
9694
|
},
|
|
8607
9695
|
// Credentials embedded in URLs — common when a prompt pastes a curl/clone line.
|
|
8608
9696
|
{ id: "url-userinfo", pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@/gi },
|
|
@@ -8668,10 +9756,10 @@ async function applyRedactor(spans, redactor) {
|
|
|
8668
9756
|
}
|
|
8669
9757
|
|
|
8670
9758
|
// src/file-export.ts
|
|
8671
|
-
import { createHash as
|
|
9759
|
+
import { createHash as createHash10 } from "crypto";
|
|
8672
9760
|
import { mkdtemp as mkdtemp4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
8673
|
-
import { tmpdir as
|
|
8674
|
-
import { join as
|
|
9761
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
9762
|
+
import { join as join23 } from "path";
|
|
8675
9763
|
import {
|
|
8676
9764
|
firstNumberAttr,
|
|
8677
9765
|
LLM_CACHED_TOKEN_ATTR_KEYS,
|
|
@@ -8706,7 +9794,7 @@ function stableJson(value) {
|
|
|
8706
9794
|
});
|
|
8707
9795
|
}
|
|
8708
9796
|
function hashId(value, chars = 16) {
|
|
8709
|
-
return
|
|
9797
|
+
return createHash10("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
|
|
8710
9798
|
}
|
|
8711
9799
|
function isoTime2(value) {
|
|
8712
9800
|
if (typeof value === "string") {
|
|
@@ -9267,15 +10355,15 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
|
|
|
9267
10355
|
}
|
|
9268
10356
|
async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
|
|
9269
10357
|
const result = await exportTraceEvidenceFile(inputPath, opts);
|
|
9270
|
-
const path = outPath ??
|
|
10358
|
+
const path = outPath ?? join23(await mkdtemp4(join23(tmpdir5(), "traces-export-")), "spans.openinference.jsonl");
|
|
9271
10359
|
await writeFile5(path, serializeSpans(result.spans), "utf8");
|
|
9272
10360
|
return { ...result, path };
|
|
9273
10361
|
}
|
|
9274
10362
|
|
|
9275
10363
|
// src/inspect.ts
|
|
9276
10364
|
import { mkdtemp as mkdtemp5, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
|
|
9277
|
-
import { tmpdir as
|
|
9278
|
-
import { basename as basename13, join as
|
|
10365
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
10366
|
+
import { basename as basename13, join as join24 } from "path";
|
|
9279
10367
|
var severityRank = {
|
|
9280
10368
|
high: 0,
|
|
9281
10369
|
medium: 1,
|
|
@@ -9291,7 +10379,7 @@ function sessionLabel(row) {
|
|
|
9291
10379
|
const id = row.session.sessionId.length > 12 ? row.session.sessionId.slice(0, 12) : row.session.sessionId;
|
|
9292
10380
|
return `${row.session.harness}:${id} (${repoLabel(row)})`;
|
|
9293
10381
|
}
|
|
9294
|
-
function
|
|
10382
|
+
function pct2(value) {
|
|
9295
10383
|
return `${(value * 100).toFixed(value >= 0.1 ? 0 : 1)}%`;
|
|
9296
10384
|
}
|
|
9297
10385
|
function topToolErrors(row) {
|
|
@@ -9370,7 +10458,7 @@ function addSessionFindings(index, findings) {
|
|
|
9370
10458
|
area: "session",
|
|
9371
10459
|
title: `High tool-error count/rate in ${errorSessions.length}/${index.totals.sessions} session(s)`,
|
|
9372
10460
|
impact: errors,
|
|
9373
|
-
evidence: errorSessions.slice(0, 5).map((row) => `${sessionLabel(row)}: ${row.metrics.erroredToolCallCount}/${row.metrics.toolCallCount} tool errors (${
|
|
10461
|
+
evidence: errorSessions.slice(0, 5).map((row) => `${sessionLabel(row)}: ${row.metrics.erroredToolCallCount}/${row.metrics.toolCallCount} tool errors (${pct2(row.metrics.toolErrorRate)}); ${topToolErrors(row)}`),
|
|
9374
10462
|
next: "Fix the most common failing command/tool first, then rerun `traces index` to confirm the error count drops.",
|
|
9375
10463
|
refs: errorSessions.slice(0, 5).map((row) => row.session.path)
|
|
9376
10464
|
});
|
|
@@ -9564,14 +10652,14 @@ async function readSessionIndexFile(path) {
|
|
|
9564
10652
|
return parsed;
|
|
9565
10653
|
}
|
|
9566
10654
|
async function writeInspectionReportFile(report, outPath) {
|
|
9567
|
-
const path = outPath ??
|
|
10655
|
+
const path = outPath ?? join24(await mkdtemp5(join24(tmpdir6(), "traces-inspect-")), "inspection-report.md");
|
|
9568
10656
|
await writeFile6(path, renderInspectionReport(report), "utf8");
|
|
9569
10657
|
return path;
|
|
9570
10658
|
}
|
|
9571
10659
|
|
|
9572
10660
|
// src/adoption.ts
|
|
9573
10661
|
import { readFile as readFile11 } from "fs/promises";
|
|
9574
|
-
import { join as
|
|
10662
|
+
import { join as join25 } from "path";
|
|
9575
10663
|
function toolName2(s) {
|
|
9576
10664
|
const n = s.attributes["tool.name"];
|
|
9577
10665
|
if (typeof n === "string") return n;
|
|
@@ -9691,7 +10779,7 @@ async function readLoopRuns(cwds, sessionIds) {
|
|
|
9691
10779
|
seenCwds.add(cwd);
|
|
9692
10780
|
const firstSourceByRun = /* @__PURE__ */ new Map();
|
|
9693
10781
|
for (const artifactDir of [".agent", ".evolve"]) {
|
|
9694
|
-
const path =
|
|
10782
|
+
const path = join25(cwd, artifactDir, "skill-runs.jsonl");
|
|
9695
10783
|
let raw;
|
|
9696
10784
|
try {
|
|
9697
10785
|
raw = await readFile11(path, "utf8");
|
|
@@ -11102,8 +12190,8 @@ function renderAdoption(ar) {
|
|
|
11102
12190
|
|
|
11103
12191
|
// src/improvement.ts
|
|
11104
12192
|
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
|
|
12193
|
+
import { tmpdir as tmpdir7 } from "os";
|
|
12194
|
+
import { dirname as dirname7, join as join26, resolve as resolve6 } from "path";
|
|
11107
12195
|
import { pathToFileURL } from "url";
|
|
11108
12196
|
import {
|
|
11109
12197
|
AnalystRegistry,
|
|
@@ -11610,13 +12698,13 @@ async function runTraceStoreInvestigation(opts) {
|
|
|
11610
12698
|
};
|
|
11611
12699
|
}
|
|
11612
12700
|
async function writeTraceImprovementArtifacts(result, outDir) {
|
|
11613
|
-
const directory = outDir ? resolve6(outDir) : await mkdtemp6(
|
|
12701
|
+
const directory = outDir ? resolve6(outDir) : await mkdtemp6(join26(tmpdir7(), "traces-improvement-"));
|
|
11614
12702
|
await mkdir4(directory, { recursive: true });
|
|
11615
12703
|
const paths = {
|
|
11616
12704
|
directory,
|
|
11617
|
-
result:
|
|
11618
|
-
evidence:
|
|
11619
|
-
report:
|
|
12705
|
+
result: join26(directory, "result.json"),
|
|
12706
|
+
evidence: join26(directory, "evidence.jsonl"),
|
|
12707
|
+
report: join26(directory, "report.md"),
|
|
11620
12708
|
traces: result.otlpPath
|
|
11621
12709
|
};
|
|
11622
12710
|
const { report, ...machineResult } = result;
|
|
@@ -11628,11 +12716,11 @@ async function writeTraceImprovementArtifacts(result, outDir) {
|
|
|
11628
12716
|
return paths;
|
|
11629
12717
|
}
|
|
11630
12718
|
async function runTraceImprovement(opts) {
|
|
11631
|
-
const directory = opts.outDir ? resolve6(opts.outDir) : await mkdtemp6(
|
|
12719
|
+
const directory = opts.outDir ? resolve6(opts.outDir) : await mkdtemp6(join26(tmpdir7(), "traces-improvement-"));
|
|
11632
12720
|
await mkdir4(directory, { recursive: true });
|
|
11633
12721
|
const investigation = await runTraceInvestigation({
|
|
11634
12722
|
...opts,
|
|
11635
|
-
otlpOutPath: opts.otlpOutPath ??
|
|
12723
|
+
otlpOutPath: opts.otlpOutPath ?? join26(directory, "traces.otlp.jsonl")
|
|
11636
12724
|
});
|
|
11637
12725
|
const result = {
|
|
11638
12726
|
...investigation,
|
|
@@ -11649,7 +12737,7 @@ async function saveReport(path, report) {
|
|
|
11649
12737
|
}
|
|
11650
12738
|
|
|
11651
12739
|
// src/live.ts
|
|
11652
|
-
import { createHash as
|
|
12740
|
+
import { createHash as createHash11 } from "crypto";
|
|
11653
12741
|
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
12742
|
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
12743
|
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
@@ -11669,7 +12757,7 @@ var sleep = (ms2, signal) => new Promise((resolve7) => {
|
|
|
11669
12757
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
11670
12758
|
});
|
|
11671
12759
|
function hash(parts) {
|
|
11672
|
-
return
|
|
12760
|
+
return createHash11("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
11673
12761
|
}
|
|
11674
12762
|
function asString(value) {
|
|
11675
12763
|
if (typeof value === "string" && value.trim()) return value;
|
|
@@ -12075,7 +13163,7 @@ async function streamSessions(opts) {
|
|
|
12075
13163
|
|
|
12076
13164
|
// src/supervisor-run-context.ts
|
|
12077
13165
|
import { readFile as readFile12, stat as stat14 } from "fs/promises";
|
|
12078
|
-
import { basename as basename14, join as
|
|
13166
|
+
import { basename as basename14, join as join27 } from "path";
|
|
12079
13167
|
import { materializeTreeView } from "@tangle-network/agent-runtime/kernel";
|
|
12080
13168
|
import {
|
|
12081
13169
|
NO_SOURCE_LIMITS
|
|
@@ -12236,10 +13324,10 @@ function parseInstant(v) {
|
|
|
12236
13324
|
return Number.isFinite(t) ? t : null;
|
|
12237
13325
|
}
|
|
12238
13326
|
async function readFileRunContext(runDir, opts = {}) {
|
|
12239
|
-
const journalText = await readMaybe(
|
|
13327
|
+
const journalText = await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE));
|
|
12240
13328
|
const journal = readRunContextJournal(journalText);
|
|
12241
|
-
const coordination = readCoordinationLog(await readMaybe(
|
|
12242
|
-
const result = await readMaybe(
|
|
13329
|
+
const coordination = readCoordinationLog(await readMaybe(join27(runDir, COORDINATION_LOG_FILE)));
|
|
13330
|
+
const result = await readMaybe(join27(runDir, RESULT_FILE));
|
|
12243
13331
|
return {
|
|
12244
13332
|
runRef: runDir,
|
|
12245
13333
|
instanceId: opts.instanceId !== void 0 ? opts.instanceId : journal.root,
|
|
@@ -12262,7 +13350,7 @@ async function readFileRunContext(runDir, opts = {}) {
|
|
|
12262
13350
|
harnessWorkerTokens: null,
|
|
12263
13351
|
harnessMissingReason: "the file run context journals each child\u2019s own settled spend, so no external harness join is used",
|
|
12264
13352
|
limits: sourceLimits(journal, coordination),
|
|
12265
|
-
rootTranscriptRef: journal.present ?
|
|
13353
|
+
rootTranscriptRef: journal.present ? join27(runDir, SPAWN_JOURNAL_FILE) : null,
|
|
12266
13354
|
traceCommand: `traces watch ${runDir}`
|
|
12267
13355
|
};
|
|
12268
13356
|
}
|
|
@@ -12270,7 +13358,7 @@ function fileRunContextSupervisorRunReader(runDir, opts = {}) {
|
|
|
12270
13358
|
return { runRef: runDir, read: () => readFileRunContext(runDir, opts) };
|
|
12271
13359
|
}
|
|
12272
13360
|
async function isFileRunContextDir(runDir) {
|
|
12273
|
-
return stat14(
|
|
13361
|
+
return stat14(join27(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
|
|
12274
13362
|
}
|
|
12275
13363
|
function flattenJournal(journal) {
|
|
12276
13364
|
return journal.events.map((event) => {
|
|
@@ -12416,9 +13504,9 @@ function mergeRolls(a, b) {
|
|
|
12416
13504
|
};
|
|
12417
13505
|
}
|
|
12418
13506
|
async function readRunContextSnapshot(runDir) {
|
|
12419
|
-
const journal = readRunContextJournal(await readMaybe(
|
|
12420
|
-
const coordination = readCoordinationLog(await readMaybe(
|
|
12421
|
-
const resultText = await readMaybe(
|
|
13507
|
+
const journal = readRunContextJournal(await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE)));
|
|
13508
|
+
const coordination = readCoordinationLog(await readMaybe(join27(runDir, COORDINATION_LOG_FILE)));
|
|
13509
|
+
const resultText = await readMaybe(join27(runDir, RESULT_FILE));
|
|
12422
13510
|
const result = parseResult(resultText);
|
|
12423
13511
|
const view = safeTreeView(journal.events);
|
|
12424
13512
|
const nodes = view === null ? [] : orderNodes(view, journal);
|
|
@@ -12601,7 +13689,7 @@ function wrap(text2, indent) {
|
|
|
12601
13689
|
|
|
12602
13690
|
// src/run-span-tree.ts
|
|
12603
13691
|
import { readdir as readdir16 } from "fs/promises";
|
|
12604
|
-
import { join as
|
|
13692
|
+
import { join as join28 } from "path";
|
|
12605
13693
|
import {
|
|
12606
13694
|
firstNumberAttr as firstNumberAttr3,
|
|
12607
13695
|
LLM_COST_ATTR_KEYS as LLM_COST_ATTR_KEYS2,
|
|
@@ -12874,7 +13962,7 @@ async function findSpanSourceFiles(dir) {
|
|
|
12874
13962
|
const found = [];
|
|
12875
13963
|
const entries = await readdir16(dir, { withFileTypes: true }).catch(() => []);
|
|
12876
13964
|
for (const entry of entries) {
|
|
12877
|
-
const child =
|
|
13965
|
+
const child = join28(dir, entry.name);
|
|
12878
13966
|
if (entry.isFile()) {
|
|
12879
13967
|
if (SPAN_FILE_PATTERN.test(entry.name)) found.push(child);
|
|
12880
13968
|
continue;
|
|
@@ -12882,7 +13970,7 @@ async function findSpanSourceFiles(dir) {
|
|
|
12882
13970
|
if (!entry.isDirectory()) continue;
|
|
12883
13971
|
const nested = await readdir16(child, { withFileTypes: true }).catch(() => []);
|
|
12884
13972
|
for (const inner of nested) {
|
|
12885
|
-
if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(
|
|
13973
|
+
if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join28(child, inner.name));
|
|
12886
13974
|
}
|
|
12887
13975
|
}
|
|
12888
13976
|
return found.sort();
|
|
@@ -12890,7 +13978,7 @@ async function findSpanSourceFiles(dir) {
|
|
|
12890
13978
|
|
|
12891
13979
|
// src/supervisor-run-watch.ts
|
|
12892
13980
|
import { readdir as readdir17 } from "fs/promises";
|
|
12893
|
-
import { join as
|
|
13981
|
+
import { join as join29 } from "path";
|
|
12894
13982
|
function budget(node) {
|
|
12895
13983
|
const parts = [`${int(node.budget.maxIterations)}it`, `${int(node.budget.maxTokens)}tok`];
|
|
12896
13984
|
if (node.budget.maxUsd !== void 0) parts.push(`$${node.budget.maxUsd.toFixed(2)}`);
|
|
@@ -13034,7 +14122,7 @@ async function resolveRunContextDir(dir) {
|
|
|
13034
14122
|
const candidates = [];
|
|
13035
14123
|
for (const entry of entries) {
|
|
13036
14124
|
if (!entry.isDirectory()) continue;
|
|
13037
|
-
const child =
|
|
14125
|
+
const child = join29(dir, entry.name);
|
|
13038
14126
|
if (await isFileRunContextDir(child)) candidates.push(child);
|
|
13039
14127
|
}
|
|
13040
14128
|
if (candidates.length === 1) return candidates[0];
|
|
@@ -13485,11 +14573,11 @@ async function collectSessionWorkflow(adapter, seeds, options = {}) {
|
|
|
13485
14573
|
}
|
|
13486
14574
|
|
|
13487
14575
|
// src/session-selection.ts
|
|
13488
|
-
import { createHash as
|
|
14576
|
+
import { createHash as createHash12 } from "crypto";
|
|
13489
14577
|
import { createReadStream as createReadStream2 } from "fs";
|
|
13490
14578
|
async function fileSha256(path, signal) {
|
|
13491
14579
|
signal?.throwIfAborted();
|
|
13492
|
-
const hash2 =
|
|
14580
|
+
const hash2 = createHash12("sha256");
|
|
13493
14581
|
try {
|
|
13494
14582
|
for await (const chunk of createReadStream2(path, { signal })) {
|
|
13495
14583
|
signal?.throwIfAborted();
|
|
@@ -13517,7 +14605,7 @@ async function sourceSnapshot(adapter, ref, signal) {
|
|
|
13517
14605
|
signal?.throwIfAborted();
|
|
13518
14606
|
files.push({ path, sha256: await fileSha256(path, signal) });
|
|
13519
14607
|
}
|
|
13520
|
-
const sha2562 = files.length === 1 ? files[0].sha256 :
|
|
14608
|
+
const sha2562 = files.length === 1 ? files[0].sha256 : createHash12("sha256").update(JSON.stringify(files)).digest("hex");
|
|
13521
14609
|
return { sha256: sha2562, files };
|
|
13522
14610
|
}
|
|
13523
14611
|
function summarizeWorkflows(workflows) {
|
|
@@ -13613,8 +14701,8 @@ async function collectSessionSelection(groups, options = {}) {
|
|
|
13613
14701
|
|
|
13614
14702
|
// src/session-index.ts
|
|
13615
14703
|
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
|
|
14704
|
+
import { tmpdir as tmpdir8 } from "os";
|
|
14705
|
+
import { dirname as dirname8, join as join30 } from "path";
|
|
13618
14706
|
function indexRow(record2) {
|
|
13619
14707
|
return {
|
|
13620
14708
|
session: record2.session,
|
|
@@ -13687,7 +14775,7 @@ async function findContextRoot(cwd) {
|
|
|
13687
14775
|
if (!s) return null;
|
|
13688
14776
|
if (!s.isDirectory()) current = dirname8(current);
|
|
13689
14777
|
for (; ; ) {
|
|
13690
|
-
if (await pathStat2(
|
|
14778
|
+
if (await pathStat2(join30(current, ".git")) || await dirExists(join30(current, ".evolve")) || await fileExists(join30(current, "AGENTS.md")) || await fileExists(join30(current, "CLAUDE.md"))) {
|
|
13691
14779
|
return current;
|
|
13692
14780
|
}
|
|
13693
14781
|
const parent = dirname8(current);
|
|
@@ -13755,7 +14843,7 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
13755
14843
|
}
|
|
13756
14844
|
for (const entry of entries) {
|
|
13757
14845
|
if (out.length >= maxFiles) return;
|
|
13758
|
-
const path =
|
|
14846
|
+
const path = join30(dir, entry.name);
|
|
13759
14847
|
if (entry.isDirectory()) {
|
|
13760
14848
|
await walk(path);
|
|
13761
14849
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -13770,20 +14858,20 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
13770
14858
|
async function collectContextRoot(root) {
|
|
13771
14859
|
const files = [];
|
|
13772
14860
|
for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
|
|
13773
|
-
const summary = await summarizeFile(
|
|
14861
|
+
const summary = await summarizeFile(join30(root, name), "instruction-doc");
|
|
13774
14862
|
if (summary) files.push(summary);
|
|
13775
14863
|
}
|
|
13776
|
-
const evolve =
|
|
14864
|
+
const evolve = join30(root, ".evolve");
|
|
13777
14865
|
for (const name of ["skill-runs.jsonl", "governor.jsonl", "experiments.jsonl"]) {
|
|
13778
|
-
const summary = await summarizeFile(
|
|
14866
|
+
const summary = await summarizeFile(join30(evolve, name), "evolve-jsonl");
|
|
13779
14867
|
if (summary) files.push(summary);
|
|
13780
14868
|
}
|
|
13781
14869
|
for (const name of ["scorecard.json"]) {
|
|
13782
|
-
const summary = await summarizeFile(
|
|
14870
|
+
const summary = await summarizeFile(join30(evolve, name), "evolve-json");
|
|
13783
14871
|
if (summary) files.push(summary);
|
|
13784
14872
|
}
|
|
13785
|
-
files.push(...await walkMarkdown(
|
|
13786
|
-
files.push(...await walkMarkdown(
|
|
14873
|
+
files.push(...await walkMarkdown(join30(evolve, "reflections"), "reflection"));
|
|
14874
|
+
files.push(...await walkMarkdown(join30(evolve, "handoffs"), "handoff"));
|
|
13787
14875
|
return { root, files: files.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
13788
14876
|
}
|
|
13789
14877
|
async function collectContextIndex(records) {
|
|
@@ -13829,7 +14917,7 @@ function serializeSessionIndex(index) {
|
|
|
13829
14917
|
`;
|
|
13830
14918
|
}
|
|
13831
14919
|
async function writeSessionIndexFile(index, outPath) {
|
|
13832
|
-
const path = outPath ??
|
|
14920
|
+
const path = outPath ?? join30(await mkdtemp7(join30(tmpdir8(), "traces-index-")), "session-index.json");
|
|
13833
14921
|
await writeFile8(path, serializeSessionIndex(index), "utf8");
|
|
13834
14922
|
return path;
|
|
13835
14923
|
}
|
|
@@ -13837,11 +14925,11 @@ async function writeSessionIndexFile(index, outPath) {
|
|
|
13837
14925
|
// src/upload-state.ts
|
|
13838
14926
|
import { mkdir as mkdir5, readFile as readFile14, writeFile as writeFile9 } from "fs/promises";
|
|
13839
14927
|
import { homedir as homedir12 } from "os";
|
|
13840
|
-
import { dirname as dirname9, join as
|
|
14928
|
+
import { dirname as dirname9, join as join31 } from "path";
|
|
13841
14929
|
import { contentHash } from "@tangle-network/agent-eval";
|
|
13842
14930
|
function stateFile() {
|
|
13843
|
-
const base = process.env.XDG_STATE_HOME ??
|
|
13844
|
-
return
|
|
14931
|
+
const base = process.env.XDG_STATE_HOME ?? join31(homedir12(), ".local", "state");
|
|
14932
|
+
return join31(base, "tangle-traces", "uploaded.json");
|
|
13845
14933
|
}
|
|
13846
14934
|
function uploadKey(harness, sessionId) {
|
|
13847
14935
|
return `${harness}:${sessionId}`;
|
|
@@ -13873,7 +14961,7 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
|
|
|
13873
14961
|
import { readFile as readFile15 } from "fs/promises";
|
|
13874
14962
|
import { createRequire as createRequire2 } from "module";
|
|
13875
14963
|
import { hostname as hostname2 } from "os";
|
|
13876
|
-
import { basename as basename15, join as
|
|
14964
|
+
import { basename as basename15, join as join32 } from "path";
|
|
13877
14965
|
import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
13878
14966
|
import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
|
|
13879
14967
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -13897,7 +14985,7 @@ async function planUpload(opts = {}) {
|
|
|
13897
14985
|
async function gitBranch(cwd) {
|
|
13898
14986
|
if (!cwd) return void 0;
|
|
13899
14987
|
try {
|
|
13900
|
-
const head = await readFile15(
|
|
14988
|
+
const head = await readFile15(join32(cwd, ".git", "HEAD"), "utf8");
|
|
13901
14989
|
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
|
|
13902
14990
|
return m?.[1]?.trim();
|
|
13903
14991
|
} catch {
|
|
@@ -14153,6 +15241,25 @@ export {
|
|
|
14153
15241
|
parseReplayVerifyArgs,
|
|
14154
15242
|
replayVerifyUsage,
|
|
14155
15243
|
cmdReplayVerify,
|
|
15244
|
+
parseCorpusFlag,
|
|
15245
|
+
SUBMIT_ACTION_SIGNATURE,
|
|
15246
|
+
isSubmitAction,
|
|
15247
|
+
readLabelEntries,
|
|
15248
|
+
goldIncorrectSteps,
|
|
15249
|
+
resolveCaseResources,
|
|
15250
|
+
enumerateReplayableCases,
|
|
15251
|
+
zaiChatCaller,
|
|
15252
|
+
buildFixPrompt,
|
|
15253
|
+
extractFixCommand,
|
|
15254
|
+
generateFixCommand,
|
|
15255
|
+
derivedImageTag,
|
|
15256
|
+
dockerImagePreparer,
|
|
15257
|
+
seededSample,
|
|
15258
|
+
runReplayBatch,
|
|
15259
|
+
renderBatchReport,
|
|
15260
|
+
parseReplayBatchArgs,
|
|
15261
|
+
replayBatchUsage,
|
|
15262
|
+
cmdReplayVerifyBatch,
|
|
14156
15263
|
runCommand,
|
|
14157
15264
|
commandAnalyzer,
|
|
14158
15265
|
haloAnalyzer,
|