hillclimb 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +321 -37
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2349,7 +2349,11 @@ var VOLATILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
2349
2349
|
"wall_time_ms"
|
|
2350
2350
|
]);
|
|
2351
2351
|
function sessionsDir() {
|
|
2352
|
-
|
|
2352
|
+
if (process.env.HILLCLIMB_CODEX_SESSIONS_DIR) {
|
|
2353
|
+
return process.env.HILLCLIMB_CODEX_SESSIONS_DIR;
|
|
2354
|
+
}
|
|
2355
|
+
const codexHome = process.env.CODEX_HOME || path9.join(os3.homedir(), ".codex");
|
|
2356
|
+
return path9.join(codexHome, "sessions");
|
|
2353
2357
|
}
|
|
2354
2358
|
function asRecord(value) {
|
|
2355
2359
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -2519,6 +2523,10 @@ async function findCodexRolloutPath(sessionId) {
|
|
|
2519
2523
|
}
|
|
2520
2524
|
return void 0;
|
|
2521
2525
|
}
|
|
2526
|
+
async function isCodexInternalThread(payload, sessionId) {
|
|
2527
|
+
if (!sessionId || payload.transcript_path) return false;
|
|
2528
|
+
return await findCodexRolloutPath(sessionId) === void 0;
|
|
2529
|
+
}
|
|
2522
2530
|
function commonPrefixLength(a, b) {
|
|
2523
2531
|
const limit = Math.min(a.length, b.length);
|
|
2524
2532
|
let count = 0;
|
|
@@ -11762,14 +11770,20 @@ function getSourceBaseDir(sourceName) {
|
|
|
11762
11770
|
}
|
|
11763
11771
|
}
|
|
11764
11772
|
function archivePathFor(file) {
|
|
11773
|
+
return path11.posix.join(file.sourceName, path11.basename(file.absolutePath));
|
|
11774
|
+
}
|
|
11775
|
+
function legacyArchivePathFor(file) {
|
|
11765
11776
|
const baseDir = getSourceBaseDir(file.sourceName);
|
|
11766
11777
|
const relativePath = file.absolutePath.startsWith(baseDir) ? path11.relative(baseDir, file.absolutePath) : path11.basename(file.absolutePath);
|
|
11767
11778
|
return path11.join(file.sourceName, relativePath);
|
|
11768
11779
|
}
|
|
11769
11780
|
function addGroupToArchive(archive, group, selectedSources) {
|
|
11781
|
+
const used = /* @__PURE__ */ new Set();
|
|
11770
11782
|
for (const file of group.files) {
|
|
11771
11783
|
if (!selectedSources.has(file.sourceName)) continue;
|
|
11772
|
-
|
|
11784
|
+
let archivePath = archivePathFor(file);
|
|
11785
|
+
if (used.has(archivePath)) archivePath = legacyArchivePathFor(file);
|
|
11786
|
+
used.add(archivePath);
|
|
11773
11787
|
if (file.content) {
|
|
11774
11788
|
archive.append(file.content, { name: archivePath });
|
|
11775
11789
|
} else {
|
|
@@ -13065,7 +13079,7 @@ async function runUploadInner(payload) {
|
|
|
13065
13079
|
"warn",
|
|
13066
13080
|
`Skipping upload: missing required hook fields (session_id=${!!sessionId}, transcript_path=${!!payload.transcript_path}, cwd=${!!cwd})`
|
|
13067
13081
|
);
|
|
13068
|
-
return
|
|
13082
|
+
return "skipped";
|
|
13069
13083
|
}
|
|
13070
13084
|
const match = await findProjectForCwd(cwd);
|
|
13071
13085
|
if (!match) {
|
|
@@ -13073,13 +13087,13 @@ async function runUploadInner(payload) {
|
|
|
13073
13087
|
"warn",
|
|
13074
13088
|
`Skipping session ${sessionId}: no hillclimb config for cwd ${cwd}. Run \`npx hillclimb\` in the repo.`
|
|
13075
13089
|
);
|
|
13076
|
-
return
|
|
13090
|
+
return "skipped";
|
|
13077
13091
|
}
|
|
13078
13092
|
const { repoRoot, config } = match;
|
|
13079
13093
|
const sourceTool = await resolveSourceTool(payload, repoRoot);
|
|
13080
13094
|
if (!sourceTool) {
|
|
13081
13095
|
payload.tool = "unknown";
|
|
13082
|
-
return
|
|
13096
|
+
return "skipped";
|
|
13083
13097
|
}
|
|
13084
13098
|
payload.tool = sourceTool;
|
|
13085
13099
|
appendLog(
|
|
@@ -13093,11 +13107,18 @@ async function runUploadInner(payload) {
|
|
|
13093
13107
|
sessionId
|
|
13094
13108
|
);
|
|
13095
13109
|
if (!transcriptPath) {
|
|
13110
|
+
if (sourceTool === "codex") {
|
|
13111
|
+
appendLog(
|
|
13112
|
+
"info",
|
|
13113
|
+
`Skipping session ${sessionId}: Codex internal thread (no transcript_path and no rollout under ~/.codex/sessions); not a user session`
|
|
13114
|
+
);
|
|
13115
|
+
return "internal-thread";
|
|
13116
|
+
}
|
|
13096
13117
|
appendLog(
|
|
13097
13118
|
"warn",
|
|
13098
13119
|
`Skipping upload: missing required hook fields (session_id=true, transcript_path=false, cwd=true)`
|
|
13099
13120
|
);
|
|
13100
|
-
return
|
|
13121
|
+
return "skipped";
|
|
13101
13122
|
}
|
|
13102
13123
|
const transcriptResolved = path15.resolve(transcriptPath);
|
|
13103
13124
|
try {
|
|
@@ -13107,23 +13128,32 @@ async function runUploadInner(payload) {
|
|
|
13107
13128
|
"warn",
|
|
13108
13129
|
`Skipping session ${sessionId}: transcript_path is not a file: ${transcriptResolved}`
|
|
13109
13130
|
);
|
|
13110
|
-
return
|
|
13131
|
+
return "skipped";
|
|
13111
13132
|
}
|
|
13112
13133
|
} catch (err) {
|
|
13134
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
13135
|
+
const missing = err?.code === "ENOENT";
|
|
13136
|
+
if (missing && eventKind2 === "sessionEnd") {
|
|
13137
|
+
appendLog(
|
|
13138
|
+
"info",
|
|
13139
|
+
`Skipping session ${sessionId}: session ended without any turns; transcript was never written (${transcriptResolved})`
|
|
13140
|
+
);
|
|
13141
|
+
return "skipped";
|
|
13142
|
+
}
|
|
13113
13143
|
appendLog(
|
|
13114
13144
|
"warn",
|
|
13115
|
-
`Skipping session ${sessionId}: transcript_path not readable (${transcriptResolved}): ${
|
|
13145
|
+
`Skipping session ${sessionId}: transcript_path not readable (${transcriptResolved}): ${detail}`
|
|
13116
13146
|
);
|
|
13117
|
-
return
|
|
13147
|
+
return "skipped";
|
|
13118
13148
|
}
|
|
13119
13149
|
if (!await hasAssistantMessage(transcriptResolved)) {
|
|
13120
13150
|
appendLog(
|
|
13121
13151
|
"info",
|
|
13122
13152
|
`Skipping session ${sessionId}: transcript contains no assistant messages (nothing to upload).`
|
|
13123
13153
|
);
|
|
13124
|
-
return
|
|
13154
|
+
return "skipped";
|
|
13125
13155
|
}
|
|
13126
|
-
|
|
13156
|
+
const uploaded = await uploadSession({
|
|
13127
13157
|
sessionId,
|
|
13128
13158
|
transcriptPath: transcriptResolved,
|
|
13129
13159
|
repoRoot,
|
|
@@ -13132,6 +13162,7 @@ async function runUploadInner(payload) {
|
|
|
13132
13162
|
eventKind: eventKind2,
|
|
13133
13163
|
recordedAt
|
|
13134
13164
|
});
|
|
13165
|
+
return uploaded ? "uploaded" : "skipped";
|
|
13135
13166
|
}
|
|
13136
13167
|
var AGENT_MAX_UPLOAD_BYTES = 1024 * 1024 * 1024;
|
|
13137
13168
|
var CLI_VERSION = "0.8.0";
|
|
@@ -13351,6 +13382,10 @@ async function uploadSession(args) {
|
|
|
13351
13382
|
sourceName: sourceTool,
|
|
13352
13383
|
absolutePath: transcriptPath
|
|
13353
13384
|
});
|
|
13385
|
+
const legacyTranscriptArchivePath = legacyArchivePathFor({
|
|
13386
|
+
sourceName: sourceTool,
|
|
13387
|
+
absolutePath: transcriptPath
|
|
13388
|
+
});
|
|
13354
13389
|
const alreadySubmitted = prior?.submitted ?? false;
|
|
13355
13390
|
const submitThisUpload = config.autoSubmit && !alreadySubmitted;
|
|
13356
13391
|
if (mode === "meta-refresh" && prior?.contributionId && prior.epochMeta && cursor) {
|
|
@@ -13428,7 +13463,7 @@ async function uploadSession(args) {
|
|
|
13428
13463
|
epochMeta2 = {
|
|
13429
13464
|
transitionKind: "legacy-adopted",
|
|
13430
13465
|
baseline: "legacy-latest-zip",
|
|
13431
|
-
transcriptArchivePath,
|
|
13466
|
+
transcriptArchivePath: legacyTranscriptArchivePath,
|
|
13432
13467
|
rawByteOffset: baseCursor.rawByteOffset,
|
|
13433
13468
|
rawPrefixSha256: baseCursor.rawPrefixSha256
|
|
13434
13469
|
};
|
|
@@ -13444,7 +13479,7 @@ async function uploadSession(args) {
|
|
|
13444
13479
|
epoch: epoch2,
|
|
13445
13480
|
transitionKind: "legacy-adopted",
|
|
13446
13481
|
baseline: "legacy-latest-zip",
|
|
13447
|
-
transcriptArchivePath,
|
|
13482
|
+
transcriptArchivePath: legacyTranscriptArchivePath,
|
|
13448
13483
|
cursor: baseCursor,
|
|
13449
13484
|
recordedAt,
|
|
13450
13485
|
lineage: codexLineage
|
|
@@ -13885,16 +13920,16 @@ async function runUploadWorker() {
|
|
|
13885
13920
|
const toolOverride = process.env[TOOL_ENV_FLAG];
|
|
13886
13921
|
if (toolOverride) payload.tool = toolOverride;
|
|
13887
13922
|
appendLog("info", `worker: payload summary: ${summarizePayload(payload)}`);
|
|
13888
|
-
let
|
|
13923
|
+
let outcome = "skipped";
|
|
13889
13924
|
try {
|
|
13890
|
-
|
|
13925
|
+
outcome = await runUploadInner(payload);
|
|
13891
13926
|
} catch (err) {
|
|
13892
13927
|
appendLog(
|
|
13893
13928
|
"error",
|
|
13894
13929
|
`worker: unexpected error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
|
|
13895
13930
|
);
|
|
13896
13931
|
} finally {
|
|
13897
|
-
if (
|
|
13932
|
+
if (outcome !== "internal-thread") {
|
|
13898
13933
|
await recordDebugLogCompletion({
|
|
13899
13934
|
kind: "agent",
|
|
13900
13935
|
tool: fallbackSourceTool(payload),
|
|
@@ -13910,7 +13945,7 @@ async function runUploadWorker() {
|
|
|
13910
13945
|
}
|
|
13911
13946
|
|
|
13912
13947
|
// src/git-traces/index.ts
|
|
13913
|
-
import { spawn as
|
|
13948
|
+
import { spawn as spawn4 } from "child_process";
|
|
13914
13949
|
import crypto7 from "crypto";
|
|
13915
13950
|
|
|
13916
13951
|
// src/git-traces/handlers.ts
|
|
@@ -13918,11 +13953,11 @@ import { execFileSync as execFileSync3 } from "child_process";
|
|
|
13918
13953
|
import path18 from "path";
|
|
13919
13954
|
|
|
13920
13955
|
// src/git-traces/git-ops.ts
|
|
13921
|
-
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
13956
|
+
import { execFileSync as execFileSync2, spawn as spawn3, spawnSync } from "child_process";
|
|
13922
13957
|
import fs16 from "fs";
|
|
13923
13958
|
import os8 from "os";
|
|
13924
13959
|
import path16 from "path";
|
|
13925
|
-
import {
|
|
13960
|
+
import { createGzip } from "zlib";
|
|
13926
13961
|
var GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
13927
13962
|
var EXEC_OPTS = {
|
|
13928
13963
|
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
@@ -14150,7 +14185,7 @@ function createGitError(args, err) {
|
|
|
14150
14185
|
const failure = err;
|
|
14151
14186
|
const command = formatGitCommand(args);
|
|
14152
14187
|
const isTimeout = failure.code === "ETIMEDOUT";
|
|
14153
|
-
const message = isTimeout ? `${command} timed out after ${GIT_COMMAND_TIMEOUT_MS}ms` : formatGitFailure(command, failure, err);
|
|
14188
|
+
const message = isTimeout ? `${command} timed out after ${GIT_COMMAND_TIMEOUT_MS}ms` : failure.code === "ENOBUFS" ? `${command} output exceeded the ${formatSize(failure.maxBufferBytes ?? EXEC_OPTS.maxBuffer)} limit` : formatGitFailure(command, failure, err);
|
|
14154
14189
|
const wrapped = new Error(message);
|
|
14155
14190
|
wrapped.isGitTimeout = isTimeout;
|
|
14156
14191
|
return wrapped;
|
|
@@ -14442,7 +14477,111 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
14442
14477
|
deleteRef(repoRoot, orphanRef);
|
|
14443
14478
|
}
|
|
14444
14479
|
}
|
|
14445
|
-
|
|
14480
|
+
var MAX_PATCH_GZ_BYTES = 500 * 1024 * 1024;
|
|
14481
|
+
function gitGzipStream(repoRoot, args, maxGzBytes) {
|
|
14482
|
+
return new Promise((resolve, reject) => {
|
|
14483
|
+
const child = spawn3("git", args, {
|
|
14484
|
+
cwd: repoRoot,
|
|
14485
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14486
|
+
windowsHide: true
|
|
14487
|
+
});
|
|
14488
|
+
const gzip = createGzip();
|
|
14489
|
+
const gzChunks = [];
|
|
14490
|
+
const stderrChunks = [];
|
|
14491
|
+
let gzBytes = 0;
|
|
14492
|
+
let rawBytes = 0;
|
|
14493
|
+
let stderrBytes = 0;
|
|
14494
|
+
let settled = false;
|
|
14495
|
+
let timedOut = false;
|
|
14496
|
+
let overflowed = false;
|
|
14497
|
+
let streamError = null;
|
|
14498
|
+
let exitCode = null;
|
|
14499
|
+
let exitSignal = null;
|
|
14500
|
+
let childClosed = false;
|
|
14501
|
+
let gzipDone = false;
|
|
14502
|
+
const timer = setTimeout(() => {
|
|
14503
|
+
timedOut = true;
|
|
14504
|
+
child.kill("SIGTERM");
|
|
14505
|
+
}, GIT_COMMAND_TIMEOUT_MS);
|
|
14506
|
+
const finish = () => {
|
|
14507
|
+
if (settled || !childClosed || !gzipDone) return;
|
|
14508
|
+
settled = true;
|
|
14509
|
+
clearTimeout(timer);
|
|
14510
|
+
if (timedOut) {
|
|
14511
|
+
reject(createGitError(args, { code: "ETIMEDOUT" }));
|
|
14512
|
+
} else if (overflowed) {
|
|
14513
|
+
reject(
|
|
14514
|
+
createGitError(args, { code: "ENOBUFS", maxBufferBytes: maxGzBytes })
|
|
14515
|
+
);
|
|
14516
|
+
} else if (streamError) {
|
|
14517
|
+
reject(createGitError(args, streamError));
|
|
14518
|
+
} else if (exitCode !== 0 || exitSignal) {
|
|
14519
|
+
reject(
|
|
14520
|
+
createGitError(args, {
|
|
14521
|
+
status: exitCode,
|
|
14522
|
+
signal: exitSignal,
|
|
14523
|
+
stderr: Buffer.concat(stderrChunks)
|
|
14524
|
+
})
|
|
14525
|
+
);
|
|
14526
|
+
} else {
|
|
14527
|
+
resolve({ gz: Buffer.concat(gzChunks), rawBytes });
|
|
14528
|
+
}
|
|
14529
|
+
};
|
|
14530
|
+
child.stdout.on("data", (chunk) => {
|
|
14531
|
+
rawBytes += chunk.length;
|
|
14532
|
+
});
|
|
14533
|
+
child.stdout.on("error", (err) => {
|
|
14534
|
+
streamError ??= err;
|
|
14535
|
+
gzipDone = true;
|
|
14536
|
+
finish();
|
|
14537
|
+
});
|
|
14538
|
+
child.stdout.pipe(gzip);
|
|
14539
|
+
gzip.on("data", (chunk) => {
|
|
14540
|
+
gzBytes += chunk.length;
|
|
14541
|
+
if (gzBytes > maxGzBytes) {
|
|
14542
|
+
if (!overflowed) {
|
|
14543
|
+
overflowed = true;
|
|
14544
|
+
child.stdout.unpipe(gzip);
|
|
14545
|
+
child.stdout.resume();
|
|
14546
|
+
child.kill("SIGTERM");
|
|
14547
|
+
gzip.destroy();
|
|
14548
|
+
gzipDone = true;
|
|
14549
|
+
finish();
|
|
14550
|
+
}
|
|
14551
|
+
return;
|
|
14552
|
+
}
|
|
14553
|
+
gzChunks.push(chunk);
|
|
14554
|
+
});
|
|
14555
|
+
gzip.on("end", () => {
|
|
14556
|
+
gzipDone = true;
|
|
14557
|
+
finish();
|
|
14558
|
+
});
|
|
14559
|
+
gzip.on("error", (err) => {
|
|
14560
|
+
if (!overflowed) streamError ??= err;
|
|
14561
|
+
gzipDone = true;
|
|
14562
|
+
finish();
|
|
14563
|
+
});
|
|
14564
|
+
child.stderr.on("data", (chunk) => {
|
|
14565
|
+
if (stderrBytes < 16 * 1024) {
|
|
14566
|
+
stderrChunks.push(chunk);
|
|
14567
|
+
stderrBytes += chunk.length;
|
|
14568
|
+
}
|
|
14569
|
+
});
|
|
14570
|
+
child.on("error", (err) => {
|
|
14571
|
+
streamError ??= err;
|
|
14572
|
+
childClosed = true;
|
|
14573
|
+
gzipDone = true;
|
|
14574
|
+
finish();
|
|
14575
|
+
});
|
|
14576
|
+
child.on("close", (code, signal) => {
|
|
14577
|
+
exitCode = code;
|
|
14578
|
+
exitSignal = signal;
|
|
14579
|
+
childClosed = true;
|
|
14580
|
+
finish();
|
|
14581
|
+
});
|
|
14582
|
+
});
|
|
14583
|
+
}
|
|
14584
|
+
async function createTreeDiffPatchGz(repoRoot, fromTreeSha, toTreeSha, options = {}) {
|
|
14446
14585
|
if (fromTreeSha === toTreeSha) return null;
|
|
14447
14586
|
const filteredFromTreeSha = filterOmittedFilesFromTree(
|
|
14448
14587
|
repoRoot,
|
|
@@ -14453,14 +14592,13 @@ function createTreeDiffPatchGz(repoRoot, fromTreeSha, toTreeSha, options = {}) {
|
|
|
14453
14592
|
limits: options.limits
|
|
14454
14593
|
});
|
|
14455
14594
|
if (filteredFromTreeSha === filteredToTreeSha) return null;
|
|
14456
|
-
const
|
|
14457
|
-
|
|
14458
|
-
"--binary",
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
return Buffer.from(gzipSync2(diff));
|
|
14595
|
+
const { gz, rawBytes } = await gitGzipStream(
|
|
14596
|
+
repoRoot,
|
|
14597
|
+
["diff", "--binary", filteredFromTreeSha, filteredToTreeSha],
|
|
14598
|
+
options.maxPatchGzBytes ?? MAX_PATCH_GZ_BYTES
|
|
14599
|
+
);
|
|
14600
|
+
if (rawBytes === 0) return null;
|
|
14601
|
+
return gz;
|
|
14464
14602
|
}
|
|
14465
14603
|
function detectTransitionKind(repoRoot, prevHeadSha, nextHeadSha) {
|
|
14466
14604
|
if (!prevHeadSha) return "initial";
|
|
@@ -14961,6 +15099,7 @@ var CLI_VERSION2 = "0.8.0";
|
|
|
14961
15099
|
var GIT_TRACES_SLUG = "git-traces";
|
|
14962
15100
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
14963
15101
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
15102
|
+
var REBASELINE_AFTER_DIFF_FAILURES = 2;
|
|
14964
15103
|
function formatEpochSeconds3(date) {
|
|
14965
15104
|
return String(Math.floor(date.getTime() / 1e3));
|
|
14966
15105
|
}
|
|
@@ -15844,6 +15983,107 @@ async function refreshLineageBaseline(params) {
|
|
|
15844
15983
|
return false;
|
|
15845
15984
|
}
|
|
15846
15985
|
}
|
|
15986
|
+
async function recoverFromWedgedDiff(params) {
|
|
15987
|
+
const {
|
|
15988
|
+
repo,
|
|
15989
|
+
state,
|
|
15990
|
+
tool,
|
|
15991
|
+
persistState,
|
|
15992
|
+
limits,
|
|
15993
|
+
currentSha,
|
|
15994
|
+
currentTreeSha,
|
|
15995
|
+
turnOmittedFiles
|
|
15996
|
+
} = params;
|
|
15997
|
+
const { repoRoot, config } = repo;
|
|
15998
|
+
if (state.contributionId === null || !state.baselineUploaded) {
|
|
15999
|
+
const prefix = epochPrefix2(state.epoch);
|
|
16000
|
+
pinRef(
|
|
16001
|
+
repoRoot,
|
|
16002
|
+
`refs/hillclimb/baseline/${state.sessionId}/${prefix}/tracked`,
|
|
16003
|
+
currentSha
|
|
16004
|
+
);
|
|
16005
|
+
pinRef(
|
|
16006
|
+
repoRoot,
|
|
16007
|
+
`refs/hillclimb/scoped/${state.sessionId}/baseline/${prefix}/tracked`,
|
|
16008
|
+
currentSha
|
|
16009
|
+
);
|
|
16010
|
+
pinFrozenBaselineTree(
|
|
16011
|
+
repoRoot,
|
|
16012
|
+
state.sessionId,
|
|
16013
|
+
state.epoch,
|
|
16014
|
+
currentTreeSha
|
|
16015
|
+
);
|
|
16016
|
+
const metadata = buildBaselineMetadata(
|
|
16017
|
+
repoRoot,
|
|
16018
|
+
state.sessionId,
|
|
16019
|
+
tool,
|
|
16020
|
+
currentSha,
|
|
16021
|
+
CLI_VERSION2,
|
|
16022
|
+
state.epoch,
|
|
16023
|
+
null,
|
|
16024
|
+
"initial",
|
|
16025
|
+
state.startedAt,
|
|
16026
|
+
turnOmittedFiles,
|
|
16027
|
+
state.codexLineage
|
|
16028
|
+
);
|
|
16029
|
+
Object.assign(state, {
|
|
16030
|
+
baselineSha: currentSha,
|
|
16031
|
+
baselineTreeSha: currentTreeSha,
|
|
16032
|
+
baselineMetadata: metadata,
|
|
16033
|
+
lastSnapshotSha: currentSha,
|
|
16034
|
+
lastSnapshotTreeSha: currentTreeSha,
|
|
16035
|
+
headSha: captureHeadSha(repoRoot),
|
|
16036
|
+
turnCount: 0,
|
|
16037
|
+
omittedKeys: omissionKeys(turnOmittedFiles)
|
|
16038
|
+
});
|
|
16039
|
+
delete state.diffFailureCount;
|
|
16040
|
+
await persistState(state);
|
|
16041
|
+
appendLog(
|
|
16042
|
+
"warn",
|
|
16043
|
+
`git-traces: wedge-recovery replaced the unuploaded ${prefix} baseline at the current state; the wedged interval is not recoverable (repo=${repoRoot}, project=${config.projectId}, session=${state.sessionId})`
|
|
16044
|
+
);
|
|
16045
|
+
return "skipped";
|
|
16046
|
+
}
|
|
16047
|
+
const client = await loadRepoClient(repo);
|
|
16048
|
+
if (!client) return "skipped";
|
|
16049
|
+
const nextEpoch = await reserveEpoch(state, persistState);
|
|
16050
|
+
appendLog(
|
|
16051
|
+
"warn",
|
|
16052
|
+
`git-traces: wedge-recovery abandoning the incremental chain after ${state.diffFailureCount} consecutive diff failures (repo=${repoRoot}, project=${config.projectId}, contribution=${state.contributionId}, abandonedEpoch=${state.epoch}, lastUploadedTurn=${state.turnCount}, openingEpoch=${nextEpoch})`
|
|
16053
|
+
);
|
|
16054
|
+
const artifacts = await openEpoch({
|
|
16055
|
+
repoRoot,
|
|
16056
|
+
client,
|
|
16057
|
+
contributionId: state.contributionId,
|
|
16058
|
+
sessionId: state.sessionId,
|
|
16059
|
+
tool,
|
|
16060
|
+
epoch: nextEpoch,
|
|
16061
|
+
prevHeadSha: state.headSha || null,
|
|
16062
|
+
transitionKind: "initial",
|
|
16063
|
+
startedAt: state.startedAt,
|
|
16064
|
+
limits,
|
|
16065
|
+
lineage: state.codexLineage
|
|
16066
|
+
});
|
|
16067
|
+
if (!artifacts) return "failed";
|
|
16068
|
+
Object.assign(state, {
|
|
16069
|
+
baselineSha: artifacts.baselineSha,
|
|
16070
|
+
baselineTreeSha: artifacts.baselineTreeSha,
|
|
16071
|
+
lastSnapshotSha: artifacts.baselineSha,
|
|
16072
|
+
lastSnapshotTreeSha: artifacts.baselineTreeSha,
|
|
16073
|
+
epoch: nextEpoch,
|
|
16074
|
+
turnCount: 0,
|
|
16075
|
+
headSha: artifacts.headSha,
|
|
16076
|
+
omittedKeys: artifacts.omittedKeys,
|
|
16077
|
+
codexLineageStamped: state.codexLineage
|
|
16078
|
+
});
|
|
16079
|
+
delete state.diffFailureCount;
|
|
16080
|
+
await persistState(state);
|
|
16081
|
+
appendLog(
|
|
16082
|
+
"info",
|
|
16083
|
+
`git-traces: wedge-recovery baseline uploaded (repo=${repoRoot}, project=${config.projectId}, contribution=${state.contributionId}, epoch=${nextEpoch}, baseline=${artifacts.baselineSha.slice(0, 8)})`
|
|
16084
|
+
);
|
|
16085
|
+
return "uploaded";
|
|
16086
|
+
}
|
|
15847
16087
|
async function registerInitialContribution(params) {
|
|
15848
16088
|
const { repo, state, tool, persistState, client, artifacts } = params;
|
|
15849
16089
|
const contributionId = await createGitTracesContribution({
|
|
@@ -16076,6 +16316,7 @@ async function processStopRepo(repo, tool, sessionId, recordedAt, lineageCheck,
|
|
|
16076
16316
|
omittedKeys: artifacts.omittedKeys,
|
|
16077
16317
|
codexLineageStamped: state.codexLineage
|
|
16078
16318
|
};
|
|
16319
|
+
delete next.diffFailureCount;
|
|
16079
16320
|
await persistState(next);
|
|
16080
16321
|
appendLog(
|
|
16081
16322
|
"info",
|
|
@@ -16090,19 +16331,55 @@ async function processStopRepo(repo, tool, sessionId, recordedAt, lineageCheck,
|
|
|
16090
16331
|
log: false,
|
|
16091
16332
|
limits
|
|
16092
16333
|
});
|
|
16334
|
+
const previousOmittedKeys = state.omittedKeys;
|
|
16093
16335
|
const omissionsChanged = reportNewlyOmittedFiles(
|
|
16094
16336
|
state,
|
|
16095
16337
|
turnOmittedFiles,
|
|
16096
16338
|
repoRoot
|
|
16097
16339
|
);
|
|
16098
|
-
const
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16340
|
+
const attemptWedgeRecovery = async () => {
|
|
16341
|
+
const outcome = await recoverFromWedgedDiff({
|
|
16342
|
+
repo,
|
|
16343
|
+
state: cleanupState,
|
|
16344
|
+
tool,
|
|
16345
|
+
persistState,
|
|
16346
|
+
limits,
|
|
16347
|
+
currentSha,
|
|
16348
|
+
currentTreeSha,
|
|
16349
|
+
turnOmittedFiles
|
|
16350
|
+
});
|
|
16351
|
+
return outcome === "uploaded" ? await finishSuccessfulStop("uploaded") : outcome;
|
|
16352
|
+
};
|
|
16353
|
+
if ((state.diffFailureCount ?? 0) >= REBASELINE_AFTER_DIFF_FAILURES) {
|
|
16354
|
+
appendLog(
|
|
16355
|
+
"warn",
|
|
16356
|
+
`git-traces: skipping turn diff after ${state.diffFailureCount} consecutive failures; retrying wedge recovery (repo=${repoRoot}, project=${config.projectId})`
|
|
16357
|
+
);
|
|
16358
|
+
return await attemptWedgeRecovery();
|
|
16359
|
+
}
|
|
16360
|
+
let patchBuffer;
|
|
16361
|
+
try {
|
|
16362
|
+
patchBuffer = await createTreeDiffPatchGz(
|
|
16363
|
+
repoRoot,
|
|
16364
|
+
lastSnapshotTreeSha,
|
|
16365
|
+
currentTreeSha,
|
|
16366
|
+
{ limits }
|
|
16367
|
+
);
|
|
16368
|
+
} catch (err) {
|
|
16369
|
+
const failures = (state.diffFailureCount ?? 0) + 1;
|
|
16370
|
+
state.diffFailureCount = failures;
|
|
16371
|
+
await persistState({ ...state, omittedKeys: previousOmittedKeys });
|
|
16372
|
+
appendLog(
|
|
16373
|
+
"error",
|
|
16374
|
+
`git-traces: turn diff failed (repo=${repoRoot}, project=${config.projectId}, consecutiveFailures=${failures}): ${formatError(err)}`
|
|
16375
|
+
);
|
|
16376
|
+
if (failures < REBASELINE_AFTER_DIFF_FAILURES) return "failed";
|
|
16377
|
+
return await attemptWedgeRecovery();
|
|
16378
|
+
}
|
|
16379
|
+
const diffFailuresCleared = state.diffFailureCount !== void 0;
|
|
16380
|
+
delete state.diffFailureCount;
|
|
16104
16381
|
if (!patchBuffer) {
|
|
16105
|
-
if (omissionsChanged) {
|
|
16382
|
+
if (omissionsChanged || diffFailuresCleared) {
|
|
16106
16383
|
await persistState(state);
|
|
16107
16384
|
} else {
|
|
16108
16385
|
await touchSessionState(state.repoRoot, tool, sessionId);
|
|
@@ -16618,7 +16895,7 @@ async function runGitTraces() {
|
|
|
16618
16895
|
};
|
|
16619
16896
|
await applyDebugLogParentEnv(workerEnv, raw);
|
|
16620
16897
|
try {
|
|
16621
|
-
const child =
|
|
16898
|
+
const child = spawn4(
|
|
16622
16899
|
process.execPath,
|
|
16623
16900
|
[entrypoint, "git-traces", `--tool=${tool}`],
|
|
16624
16901
|
{
|
|
@@ -16693,6 +16970,13 @@ async function runGitTracesWorker() {
|
|
|
16693
16970
|
);
|
|
16694
16971
|
return;
|
|
16695
16972
|
}
|
|
16973
|
+
if (tool === "codex" && await isCodexInternalThread(payload, resolveHookSessionId(payload))) {
|
|
16974
|
+
appendLog(
|
|
16975
|
+
"info",
|
|
16976
|
+
`git-traces: skipping Codex internal thread ${resolveHookSessionId(payload)} on ${event} (no transcript_path and no rollout on disk; not a user session)`
|
|
16977
|
+
);
|
|
16978
|
+
return;
|
|
16979
|
+
}
|
|
16696
16980
|
try {
|
|
16697
16981
|
await selfHealHook2(payload, tool);
|
|
16698
16982
|
switch (eventKind2) {
|