hillclimb 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +1085 -2111
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs21 from "fs";
|
|
5
|
+
import path23 from "path";
|
|
6
6
|
import * as p6 from "@clack/prompts";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
@@ -252,6 +252,95 @@ function appendLog(level, message) {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
// src/platform/put.ts
|
|
256
|
+
import fs4 from "fs";
|
|
257
|
+
import http from "http";
|
|
258
|
+
import https from "https";
|
|
259
|
+
var DEFAULT_SOCKET_IDLE_TIMEOUT_MS = 12e4;
|
|
260
|
+
var DEFAULT_DEADLINE_MS = 60 * 6e4;
|
|
261
|
+
var MAX_RESPONSE_BODY_BYTES = 64 * 1024;
|
|
262
|
+
async function putToPresignedUrl(url, headers, body, options) {
|
|
263
|
+
const socketIdleTimeoutMs = options?.socketIdleTimeoutMs ?? DEFAULT_SOCKET_IDLE_TIMEOUT_MS;
|
|
264
|
+
const deadlineMs = options?.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
265
|
+
const parsed = new URL(url);
|
|
266
|
+
const transport = parsed.protocol === "http:" ? http : https;
|
|
267
|
+
const contentLength = body.kind === "buffer" ? body.buffer.byteLength : body.sizeBytes;
|
|
268
|
+
const start = Date.now();
|
|
269
|
+
return new Promise((resolve, reject) => {
|
|
270
|
+
let settled = false;
|
|
271
|
+
const settleResolve = (result) => {
|
|
272
|
+
if (settled) return;
|
|
273
|
+
settled = true;
|
|
274
|
+
clearTimeout(deadlineTimer);
|
|
275
|
+
resolve(result);
|
|
276
|
+
};
|
|
277
|
+
const settleReject = (err) => {
|
|
278
|
+
if (settled) return;
|
|
279
|
+
settled = true;
|
|
280
|
+
clearTimeout(deadlineTimer);
|
|
281
|
+
reject(err);
|
|
282
|
+
};
|
|
283
|
+
const req = transport.request(
|
|
284
|
+
parsed,
|
|
285
|
+
{
|
|
286
|
+
method: "PUT",
|
|
287
|
+
// One-shot upload; skip the keep-alive agent pool so the idle
|
|
288
|
+
// timeout below dies with this socket instead of leaking onto a
|
|
289
|
+
// reused one.
|
|
290
|
+
agent: false,
|
|
291
|
+
headers: { ...headers, "content-length": String(contentLength) }
|
|
292
|
+
},
|
|
293
|
+
(res) => {
|
|
294
|
+
const chunks = [];
|
|
295
|
+
let buffered = 0;
|
|
296
|
+
res.on("data", (chunk) => {
|
|
297
|
+
if (buffered >= MAX_RESPONSE_BODY_BYTES) return;
|
|
298
|
+
const remaining = MAX_RESPONSE_BODY_BYTES - buffered;
|
|
299
|
+
chunks.push(
|
|
300
|
+
chunk.length > remaining ? chunk.subarray(0, remaining) : chunk
|
|
301
|
+
);
|
|
302
|
+
buffered += Math.min(chunk.length, remaining);
|
|
303
|
+
});
|
|
304
|
+
res.on("end", () => {
|
|
305
|
+
const status = res.statusCode ?? 0;
|
|
306
|
+
settleResolve({
|
|
307
|
+
status,
|
|
308
|
+
ok: status >= 200 && status < 300,
|
|
309
|
+
bodyText: Buffer.concat(chunks).toString("utf8")
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
res.on("error", settleReject);
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
const deadlineTimer = setTimeout(() => {
|
|
316
|
+
const err = new Error(
|
|
317
|
+
`PUT to presigned URL exceeded ${deadlineMs}ms deadline (elapsed ${Date.now() - start}ms)`
|
|
318
|
+
);
|
|
319
|
+
settleReject(err);
|
|
320
|
+
req.destroy(err);
|
|
321
|
+
}, deadlineMs);
|
|
322
|
+
deadlineTimer.unref();
|
|
323
|
+
req.setTimeout(socketIdleTimeoutMs, () => {
|
|
324
|
+
const err = new Error(
|
|
325
|
+
`PUT to presigned URL timed out: socket idle for ${socketIdleTimeoutMs}ms (elapsed ${Date.now() - start}ms)`
|
|
326
|
+
);
|
|
327
|
+
settleReject(err);
|
|
328
|
+
req.destroy(err);
|
|
329
|
+
});
|
|
330
|
+
req.on("error", settleReject);
|
|
331
|
+
if (body.kind === "buffer") {
|
|
332
|
+
req.end(body.buffer);
|
|
333
|
+
} else {
|
|
334
|
+
const stream = fs4.createReadStream(body.path);
|
|
335
|
+
stream.on("error", (err) => {
|
|
336
|
+
settleReject(err);
|
|
337
|
+
req.destroy(err);
|
|
338
|
+
});
|
|
339
|
+
stream.pipe(req);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
255
344
|
// src/platform/client.ts
|
|
256
345
|
var PlatformError = class extends Error {
|
|
257
346
|
constructor(message, status, code) {
|
|
@@ -457,8 +546,21 @@ var PlatformClient = class {
|
|
|
457
546
|
);
|
|
458
547
|
}
|
|
459
548
|
async uploadToPresignedUrl(presignedUrl, headers, body) {
|
|
460
|
-
|
|
461
|
-
|
|
549
|
+
await this.putToPresignedUrlLogged(presignedUrl, headers, {
|
|
550
|
+
kind: "buffer",
|
|
551
|
+
buffer: body
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
// Same logging and error shape as uploadToPresignedUrl, but streams the
|
|
555
|
+
// file from disk so large snapshots never have to fit in memory.
|
|
556
|
+
async uploadFileToPresignedUrl(presignedUrl, headers, filePath, sizeBytes) {
|
|
557
|
+
await this.putToPresignedUrlLogged(presignedUrl, headers, {
|
|
558
|
+
kind: "file",
|
|
559
|
+
path: filePath,
|
|
560
|
+
sizeBytes
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async putToPresignedUrlLogged(presignedUrl, headers, body) {
|
|
462
564
|
const host = (() => {
|
|
463
565
|
try {
|
|
464
566
|
return new URL(presignedUrl).host;
|
|
@@ -466,14 +568,11 @@ var PlatformClient = class {
|
|
|
466
568
|
return "presigned-url";
|
|
467
569
|
}
|
|
468
570
|
})();
|
|
571
|
+
const sizeBytes = body.kind === "buffer" ? body.buffer.byteLength : body.sizeBytes;
|
|
469
572
|
const start = Date.now();
|
|
470
573
|
let res;
|
|
471
574
|
try {
|
|
472
|
-
res = await
|
|
473
|
-
method: "PUT",
|
|
474
|
-
headers,
|
|
475
|
-
body: new Blob([ab])
|
|
476
|
-
});
|
|
575
|
+
res = await putToPresignedUrl(presignedUrl, headers, body);
|
|
477
576
|
} catch (err) {
|
|
478
577
|
appendLog(
|
|
479
578
|
"error",
|
|
@@ -484,11 +583,7 @@ var PlatformClient = class {
|
|
|
484
583
|
const elapsedMs = Date.now() - start;
|
|
485
584
|
if (!res.ok) {
|
|
486
585
|
appendLog("warn", `[PUT ${host}] ${res.status} ${elapsedMs}ms`);
|
|
487
|
-
|
|
488
|
-
try {
|
|
489
|
-
detail = await res.text();
|
|
490
|
-
} catch {
|
|
491
|
-
}
|
|
586
|
+
const detail = res.bodyText;
|
|
492
587
|
throw new PlatformError(
|
|
493
588
|
`PUT to presigned URL failed: HTTP ${res.status}${detail ? ` ${detail}` : ""}`,
|
|
494
589
|
res.status
|
|
@@ -496,7 +591,7 @@ var PlatformClient = class {
|
|
|
496
591
|
}
|
|
497
592
|
appendLog(
|
|
498
593
|
"info",
|
|
499
|
-
`[PUT ${host}] ${res.status} ${elapsedMs}ms (${
|
|
594
|
+
`[PUT ${host}] ${res.status} ${elapsedMs}ms (${sizeBytes} bytes)`
|
|
500
595
|
);
|
|
501
596
|
}
|
|
502
597
|
async submitContribution(contributionId) {
|
|
@@ -523,12 +618,12 @@ var PlatformClient = class {
|
|
|
523
618
|
|
|
524
619
|
// src/platform/hooks.ts
|
|
525
620
|
import { execFileSync } from "child_process";
|
|
526
|
-
import
|
|
621
|
+
import fs6 from "fs";
|
|
527
622
|
import os2 from "os";
|
|
528
623
|
import path6 from "path";
|
|
529
624
|
|
|
530
625
|
// src/gitignore.ts
|
|
531
|
-
import
|
|
626
|
+
import fs5 from "fs";
|
|
532
627
|
import path5 from "path";
|
|
533
628
|
function toGitignorePattern(repoRoot, file) {
|
|
534
629
|
const rel = path5.relative(repoRoot, file);
|
|
@@ -548,7 +643,7 @@ async function ensureGitignored(repoRoot, files) {
|
|
|
548
643
|
let content = "";
|
|
549
644
|
let created = false;
|
|
550
645
|
try {
|
|
551
|
-
content = await
|
|
646
|
+
content = await fs5.promises.readFile(gitignorePath, "utf-8");
|
|
552
647
|
} catch (err) {
|
|
553
648
|
if (err.code !== "ENOENT") throw err;
|
|
554
649
|
created = true;
|
|
@@ -567,7 +662,7 @@ async function ensureGitignored(repoRoot, files) {
|
|
|
567
662
|
if (next) next += "\n";
|
|
568
663
|
next += ["# Hillclimb hook files", ...added].join("\n");
|
|
569
664
|
next += "\n";
|
|
570
|
-
await
|
|
665
|
+
await fs5.promises.writeFile(gitignorePath, next);
|
|
571
666
|
return {
|
|
572
667
|
path: gitignorePath,
|
|
573
668
|
added,
|
|
@@ -684,7 +779,7 @@ var TOOLS = [
|
|
|
684
779
|
];
|
|
685
780
|
function isDir(p7) {
|
|
686
781
|
try {
|
|
687
|
-
return
|
|
782
|
+
return fs6.statSync(p7).isDirectory();
|
|
688
783
|
} catch {
|
|
689
784
|
return false;
|
|
690
785
|
}
|
|
@@ -710,7 +805,7 @@ function settingsPath(repoRoot, def) {
|
|
|
710
805
|
}
|
|
711
806
|
async function readJson(file, options = {}) {
|
|
712
807
|
try {
|
|
713
|
-
const raw = await
|
|
808
|
+
const raw = await fs6.promises.readFile(file, "utf-8");
|
|
714
809
|
if (!raw.trim()) return {};
|
|
715
810
|
const parsed = JSON.parse(raw);
|
|
716
811
|
if (parsed && typeof parsed === "object") return parsed;
|
|
@@ -720,8 +815,8 @@ async function readJson(file, options = {}) {
|
|
|
720
815
|
if (options.repairMalformed && err instanceof SyntaxError) {
|
|
721
816
|
const backup = `${file}.malformed-${Date.now()}`;
|
|
722
817
|
try {
|
|
723
|
-
const raw = await
|
|
724
|
-
await
|
|
818
|
+
const raw = await fs6.promises.readFile(file, "utf-8");
|
|
819
|
+
await fs6.promises.writeFile(backup, raw);
|
|
725
820
|
appendLog(
|
|
726
821
|
"warn",
|
|
727
822
|
`hooks: backed up malformed JSON hook file ${file} to ${backup}`
|
|
@@ -739,8 +834,8 @@ async function readJson(file, options = {}) {
|
|
|
739
834
|
}
|
|
740
835
|
}
|
|
741
836
|
async function writeJson(file, obj) {
|
|
742
|
-
await
|
|
743
|
-
await
|
|
837
|
+
await fs6.promises.mkdir(path6.dirname(file), { recursive: true });
|
|
838
|
+
await fs6.promises.writeFile(file, `${JSON.stringify(obj, null, 2)}
|
|
744
839
|
`);
|
|
745
840
|
}
|
|
746
841
|
function claudeHookPresent(matchers, command, options = {}) {
|
|
@@ -1084,19 +1179,19 @@ export default HillclimbPlugin;
|
|
|
1084
1179
|
`;
|
|
1085
1180
|
async function opencodeInstall(file, content = OPENCODE_PLUGIN_CONTENT) {
|
|
1086
1181
|
try {
|
|
1087
|
-
const existing = await
|
|
1182
|
+
const existing = await fs6.promises.readFile(file, "utf-8");
|
|
1088
1183
|
if (existing === content) {
|
|
1089
1184
|
return { installed: 0, alreadyPresent: 1 };
|
|
1090
1185
|
}
|
|
1091
1186
|
} catch {
|
|
1092
1187
|
}
|
|
1093
|
-
await
|
|
1094
|
-
await
|
|
1188
|
+
await fs6.promises.mkdir(path6.dirname(file), { recursive: true });
|
|
1189
|
+
await fs6.promises.writeFile(file, content);
|
|
1095
1190
|
return { installed: 1, alreadyPresent: 0 };
|
|
1096
1191
|
}
|
|
1097
1192
|
async function opencodeCheck(file) {
|
|
1098
1193
|
try {
|
|
1099
|
-
const content = await
|
|
1194
|
+
const content = await fs6.promises.readFile(file, "utf-8");
|
|
1100
1195
|
return content.includes(OPENCODE_PLUGIN_MARKER);
|
|
1101
1196
|
} catch {
|
|
1102
1197
|
return false;
|
|
@@ -1395,7 +1490,7 @@ function codexHooksEnabledInConfig(content) {
|
|
|
1395
1490
|
}
|
|
1396
1491
|
async function areCodexHooksEnabled() {
|
|
1397
1492
|
try {
|
|
1398
|
-
const content = await
|
|
1493
|
+
const content = await fs6.promises.readFile(codexConfigPath(), "utf-8");
|
|
1399
1494
|
return codexHooksEnabledInConfig(content);
|
|
1400
1495
|
} catch {
|
|
1401
1496
|
return false;
|
|
@@ -1405,13 +1500,13 @@ async function ensureCodexHooksEnabled() {
|
|
|
1405
1500
|
let content;
|
|
1406
1501
|
const configPath2 = codexConfigPath();
|
|
1407
1502
|
try {
|
|
1408
|
-
content = await
|
|
1503
|
+
content = await fs6.promises.readFile(configPath2, "utf-8");
|
|
1409
1504
|
} catch (err) {
|
|
1410
1505
|
if (err.code === "ENOENT") {
|
|
1411
|
-
await
|
|
1506
|
+
await fs6.promises.mkdir(path6.dirname(configPath2), {
|
|
1412
1507
|
recursive: true
|
|
1413
1508
|
});
|
|
1414
|
-
await
|
|
1509
|
+
await fs6.promises.writeFile(configPath2, "[features]\nhooks = true\n");
|
|
1415
1510
|
return true;
|
|
1416
1511
|
}
|
|
1417
1512
|
throw err;
|
|
@@ -1449,7 +1544,7 @@ hooks = true
|
|
|
1449
1544
|
`;
|
|
1450
1545
|
}
|
|
1451
1546
|
if (content === original) return false;
|
|
1452
|
-
await
|
|
1547
|
+
await fs6.promises.writeFile(configPath2, content);
|
|
1453
1548
|
return true;
|
|
1454
1549
|
}
|
|
1455
1550
|
var CLAUDE_DEF = TOOLS[0];
|
|
@@ -2187,15 +2282,15 @@ async function runStatus(args = []) {
|
|
|
2187
2282
|
|
|
2188
2283
|
// src/commands/upload.ts
|
|
2189
2284
|
import { spawn as spawn2 } from "child_process";
|
|
2190
|
-
import
|
|
2191
|
-
import
|
|
2285
|
+
import crypto4 from "crypto";
|
|
2286
|
+
import fs13 from "fs";
|
|
2192
2287
|
import os6 from "os";
|
|
2193
|
-
import
|
|
2288
|
+
import path14 from "path";
|
|
2194
2289
|
import readline from "readline";
|
|
2195
2290
|
|
|
2196
2291
|
// src/debug-logs.ts
|
|
2197
2292
|
import crypto from "crypto";
|
|
2198
|
-
import
|
|
2293
|
+
import fs10 from "fs";
|
|
2199
2294
|
import path12 from "path";
|
|
2200
2295
|
|
|
2201
2296
|
// src/hook-events.ts
|
|
@@ -2255,11 +2350,11 @@ import os3 from "os";
|
|
|
2255
2350
|
import { Worker } from "worker_threads";
|
|
2256
2351
|
|
|
2257
2352
|
// src/middleware/file-utils.ts
|
|
2258
|
-
import
|
|
2353
|
+
import fs7 from "fs";
|
|
2259
2354
|
async function checkBinary(filePath) {
|
|
2260
2355
|
let handle = null;
|
|
2261
2356
|
try {
|
|
2262
|
-
handle = await
|
|
2357
|
+
handle = await fs7.promises.open(filePath, "r");
|
|
2263
2358
|
const buf = Buffer.alloc(8192);
|
|
2264
2359
|
const { bytesRead } = await handle.read(buf, 0, 8192, 0);
|
|
2265
2360
|
for (let i = 0; i < bytesRead; i++) {
|
|
@@ -2284,7 +2379,7 @@ async function readFileContent(file) {
|
|
|
2284
2379
|
return { kind: "binary" };
|
|
2285
2380
|
}
|
|
2286
2381
|
try {
|
|
2287
|
-
const content = await
|
|
2382
|
+
const content = await fs7.promises.readFile(file.absolutePath, "utf-8");
|
|
2288
2383
|
return { kind: "text", content };
|
|
2289
2384
|
} catch {
|
|
2290
2385
|
return { kind: "error" };
|
|
@@ -10890,7 +10985,7 @@ var RedactMiddleware = class {
|
|
|
10890
10985
|
var middleware = [];
|
|
10891
10986
|
|
|
10892
10987
|
// src/middleware/secrets.ts
|
|
10893
|
-
import
|
|
10988
|
+
import fs8 from "fs";
|
|
10894
10989
|
import path9 from "path";
|
|
10895
10990
|
var KNOWN_NON_SECRETS = /* @__PURE__ */ new Set([
|
|
10896
10991
|
"true",
|
|
@@ -10971,7 +11066,7 @@ async function parseEnvFile(filePath) {
|
|
|
10971
11066
|
const values = [];
|
|
10972
11067
|
let content;
|
|
10973
11068
|
try {
|
|
10974
|
-
content = await
|
|
11069
|
+
content = await fs8.promises.readFile(filePath, "utf-8");
|
|
10975
11070
|
} catch {
|
|
10976
11071
|
return values;
|
|
10977
11072
|
}
|
|
@@ -11039,7 +11134,7 @@ function addWithVariants(set, value) {
|
|
|
11039
11134
|
async function discoverEnvFiles(repoRoot) {
|
|
11040
11135
|
let entries;
|
|
11041
11136
|
try {
|
|
11042
|
-
entries = await
|
|
11137
|
+
entries = await fs8.promises.readdir(repoRoot);
|
|
11043
11138
|
} catch {
|
|
11044
11139
|
return [];
|
|
11045
11140
|
}
|
|
@@ -11048,7 +11143,7 @@ async function discoverEnvFiles(repoRoot) {
|
|
|
11048
11143
|
if (!name.startsWith(".env")) continue;
|
|
11049
11144
|
const filePath = path9.join(repoRoot, name);
|
|
11050
11145
|
try {
|
|
11051
|
-
const stat = await
|
|
11146
|
+
const stat = await fs8.promises.stat(filePath);
|
|
11052
11147
|
if (stat.isFile()) envFiles.push(name);
|
|
11053
11148
|
} catch {
|
|
11054
11149
|
}
|
|
@@ -11115,12 +11210,15 @@ function getSourceBaseDir(sourceName) {
|
|
|
11115
11210
|
return home;
|
|
11116
11211
|
}
|
|
11117
11212
|
}
|
|
11213
|
+
function archivePathFor(file) {
|
|
11214
|
+
const baseDir = getSourceBaseDir(file.sourceName);
|
|
11215
|
+
const relativePath = file.absolutePath.startsWith(baseDir) ? path10.relative(baseDir, file.absolutePath) : path10.basename(file.absolutePath);
|
|
11216
|
+
return path10.join(file.sourceName, relativePath);
|
|
11217
|
+
}
|
|
11118
11218
|
function addGroupToArchive(archive, group, selectedSources) {
|
|
11119
11219
|
for (const file of group.files) {
|
|
11120
11220
|
if (!selectedSources.has(file.sourceName)) continue;
|
|
11121
|
-
const
|
|
11122
|
-
const relativePath = file.absolutePath.startsWith(baseDir) ? path10.relative(baseDir, file.absolutePath) : path10.basename(file.absolutePath);
|
|
11123
|
-
const archivePath = path10.join(file.sourceName, relativePath);
|
|
11221
|
+
const archivePath = archivePathFor(file);
|
|
11124
11222
|
if (file.content) {
|
|
11125
11223
|
archive.append(file.content, { name: archivePath });
|
|
11126
11224
|
} else {
|
|
@@ -11130,6 +11228,17 @@ function addGroupToArchive(archive, group, selectedSources) {
|
|
|
11130
11228
|
}
|
|
11131
11229
|
|
|
11132
11230
|
// src/outputs/platform.ts
|
|
11231
|
+
var UploadTooLargeError = class extends Error {
|
|
11232
|
+
constructor(filename, sizeBytes, maxBytes) {
|
|
11233
|
+
super(
|
|
11234
|
+
`upload ${filename} is ${sizeBytes} bytes, over the ${maxBytes}-byte limit`
|
|
11235
|
+
);
|
|
11236
|
+
this.filename = filename;
|
|
11237
|
+
this.sizeBytes = sizeBytes;
|
|
11238
|
+
this.maxBytes = maxBytes;
|
|
11239
|
+
this.name = "UploadTooLargeError";
|
|
11240
|
+
}
|
|
11241
|
+
};
|
|
11133
11242
|
async function buildZipBuffer(group, selectedSources) {
|
|
11134
11243
|
const archive = archiver("zip", { zlib: { level: 6 } });
|
|
11135
11244
|
const stream = new PassThrough();
|
|
@@ -11164,8 +11273,16 @@ var PlatformUploadOutput = class {
|
|
|
11164
11273
|
zipFilename,
|
|
11165
11274
|
autoSubmit,
|
|
11166
11275
|
existingContributionId,
|
|
11167
|
-
onContributionCreated
|
|
11276
|
+
onContributionCreated,
|
|
11277
|
+
maxUploadBytes
|
|
11168
11278
|
} = this.opts;
|
|
11279
|
+
if (maxUploadBytes !== void 0 && buffer.byteLength > maxUploadBytes) {
|
|
11280
|
+
throw new UploadTooLargeError(
|
|
11281
|
+
zipFilename,
|
|
11282
|
+
buffer.byteLength,
|
|
11283
|
+
maxUploadBytes
|
|
11284
|
+
);
|
|
11285
|
+
}
|
|
11169
11286
|
let contributionId;
|
|
11170
11287
|
if (existingContributionId) {
|
|
11171
11288
|
contributionId = existingContributionId;
|
|
@@ -11195,15 +11312,26 @@ var PlatformUploadOutput = class {
|
|
|
11195
11312
|
appendLog("info", `PUT to presigned URL succeeded for ${zipFilename}`);
|
|
11196
11313
|
if (autoSubmit) {
|
|
11197
11314
|
appendLog("info", `submitting contribution ${contributionId}`);
|
|
11198
|
-
|
|
11199
|
-
|
|
11315
|
+
try {
|
|
11316
|
+
await client.submitContribution(contributionId);
|
|
11317
|
+
appendLog("info", `contribution ${contributionId} submitted`);
|
|
11318
|
+
} catch (err) {
|
|
11319
|
+
if (err instanceof PlatformError && err.status === 409 && err.code === "CONTRIBUTION_WRONG_STATE") {
|
|
11320
|
+
appendLog(
|
|
11321
|
+
"info",
|
|
11322
|
+
`contribution ${contributionId} was already submitted (409); continuing`
|
|
11323
|
+
);
|
|
11324
|
+
} else {
|
|
11325
|
+
throw err;
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11200
11328
|
}
|
|
11201
11329
|
return contributionId;
|
|
11202
11330
|
}
|
|
11203
11331
|
};
|
|
11204
11332
|
|
|
11205
11333
|
// src/pipeline.ts
|
|
11206
|
-
import
|
|
11334
|
+
import fs9 from "fs";
|
|
11207
11335
|
import path11 from "path";
|
|
11208
11336
|
function canonicalizePath(p7) {
|
|
11209
11337
|
let resolved = path11.resolve(p7);
|
|
@@ -11239,7 +11367,7 @@ async function mergeByRepo(files) {
|
|
|
11239
11367
|
const groups = [];
|
|
11240
11368
|
for (const [repoPath, groupFiles] of grouped) {
|
|
11241
11369
|
const stats = await Promise.all(
|
|
11242
|
-
groupFiles.map((f) =>
|
|
11370
|
+
groupFiles.map((f) => fs9.promises.stat(f.absolutePath).catch(() => null))
|
|
11243
11371
|
);
|
|
11244
11372
|
let lastModified = /* @__PURE__ */ new Date(0);
|
|
11245
11373
|
for (const stat of stats) {
|
|
@@ -11262,7 +11390,7 @@ async function preloadFiles(group) {
|
|
|
11262
11390
|
group.files.map(async (file) => {
|
|
11263
11391
|
if (file.content) return file;
|
|
11264
11392
|
try {
|
|
11265
|
-
const buf = await
|
|
11393
|
+
const buf = await fs9.promises.readFile(file.absolutePath);
|
|
11266
11394
|
const checkLen = Math.min(buf.length, 8192);
|
|
11267
11395
|
for (let i = 0; i < checkLen; i++) {
|
|
11268
11396
|
if (buf[i] === 0) {
|
|
@@ -11353,7 +11481,7 @@ async function transcriptFingerprint(payload) {
|
|
|
11353
11481
|
if (!transcriptPath) return {};
|
|
11354
11482
|
const resolved = path12.resolve(transcriptPath);
|
|
11355
11483
|
try {
|
|
11356
|
-
const stat = await
|
|
11484
|
+
const stat = await fs10.promises.stat(resolved);
|
|
11357
11485
|
return {
|
|
11358
11486
|
transcriptPath: resolved,
|
|
11359
11487
|
transcriptMtimeMs: stat.mtimeMs,
|
|
@@ -11409,7 +11537,7 @@ async function eventContext(tool, payload) {
|
|
|
11409
11537
|
}
|
|
11410
11538
|
async function readState(eventId) {
|
|
11411
11539
|
try {
|
|
11412
|
-
const raw = await
|
|
11540
|
+
const raw = await fs10.promises.readFile(stateFile(eventId), "utf-8");
|
|
11413
11541
|
const parsed = JSON.parse(raw);
|
|
11414
11542
|
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION) return null;
|
|
11415
11543
|
return parsed;
|
|
@@ -11418,21 +11546,21 @@ async function readState(eventId) {
|
|
|
11418
11546
|
}
|
|
11419
11547
|
}
|
|
11420
11548
|
async function writeState(state) {
|
|
11421
|
-
await
|
|
11549
|
+
await fs10.promises.mkdir(stateDir(), { recursive: true, mode: 448 });
|
|
11422
11550
|
const file = stateFile(state.eventId);
|
|
11423
11551
|
const tmp = `${file}.tmp`;
|
|
11424
|
-
await
|
|
11552
|
+
await fs10.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
11425
11553
|
mode: 384
|
|
11426
11554
|
});
|
|
11427
|
-
await
|
|
11555
|
+
await fs10.promises.rename(tmp, file);
|
|
11428
11556
|
}
|
|
11429
11557
|
async function acquireLock(eventId) {
|
|
11430
|
-
await
|
|
11558
|
+
await fs10.promises.mkdir(stateDir(), { recursive: true, mode: 448 });
|
|
11431
11559
|
for (let i = 0; i < LOCK_RETRIES; i++) {
|
|
11432
11560
|
try {
|
|
11433
|
-
const fd = await
|
|
11561
|
+
const fd = await fs10.promises.open(
|
|
11434
11562
|
lockFile(eventId),
|
|
11435
|
-
|
|
11563
|
+
fs10.constants.O_CREAT | fs10.constants.O_EXCL | fs10.constants.O_WRONLY
|
|
11436
11564
|
);
|
|
11437
11565
|
await fd.write(String(process.pid));
|
|
11438
11566
|
await fd.close();
|
|
@@ -11448,7 +11576,7 @@ async function acquireLock(eventId) {
|
|
|
11448
11576
|
}
|
|
11449
11577
|
async function releaseLock(eventId) {
|
|
11450
11578
|
try {
|
|
11451
|
-
await
|
|
11579
|
+
await fs10.promises.unlink(lockFile(eventId));
|
|
11452
11580
|
} catch {
|
|
11453
11581
|
}
|
|
11454
11582
|
}
|
|
@@ -11539,7 +11667,7 @@ async function uploadDebugLog(ctx, state) {
|
|
|
11539
11667
|
const logPath = todayLogPath();
|
|
11540
11668
|
let content;
|
|
11541
11669
|
try {
|
|
11542
|
-
content = await
|
|
11670
|
+
content = await fs10.promises.readFile(logPath);
|
|
11543
11671
|
} catch (err) {
|
|
11544
11672
|
appendLog(
|
|
11545
11673
|
"warn",
|
|
@@ -11663,1832 +11791,281 @@ async function recordDebugLogCompletion(args) {
|
|
|
11663
11791
|
}
|
|
11664
11792
|
}
|
|
11665
11793
|
|
|
11666
|
-
// src/
|
|
11667
|
-
import
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
if (typeof value === "string") return value;
|
|
11672
|
-
try {
|
|
11673
|
-
return JSON.stringify(value);
|
|
11674
|
-
} catch {
|
|
11675
|
-
return String(value);
|
|
11676
|
-
}
|
|
11794
|
+
// src/transcript-artifacts.ts
|
|
11795
|
+
import { gzipSync } from "zlib";
|
|
11796
|
+
var TRANSCRIPT_PATCH_FORMAT = "jsonl-append-v1";
|
|
11797
|
+
function epochPrefix(epoch) {
|
|
11798
|
+
return `epoch-${String(epoch).padStart(3, "0")}`;
|
|
11677
11799
|
}
|
|
11678
|
-
function
|
|
11679
|
-
|
|
11680
|
-
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
}
|
|
11706
|
-
if (blockType === "code" && typeof b.code === "string") {
|
|
11707
|
-
textParts.push(b.code);
|
|
11708
|
-
continue;
|
|
11709
|
-
}
|
|
11710
|
-
const textValue = b.text;
|
|
11711
|
-
if (typeof textValue === "string") {
|
|
11712
|
-
textParts.push(textValue);
|
|
11713
|
-
} else {
|
|
11714
|
-
textParts.push(stringify(b));
|
|
11715
|
-
}
|
|
11716
|
-
}
|
|
11717
|
-
} else if (content !== void 0 && content !== null) {
|
|
11718
|
-
textParts.push(stringify(content));
|
|
11719
|
-
}
|
|
11720
|
-
const text2 = textParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
11721
|
-
const reasoning = reasoningParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
11722
|
-
return [text2, reasoning || void 0, toolBlocks];
|
|
11723
|
-
}
|
|
11724
|
-
function buildMetrics(usage) {
|
|
11725
|
-
if (typeof usage !== "object" || usage === null) return void 0;
|
|
11726
|
-
const u = usage;
|
|
11727
|
-
const cachedTokens = u.cache_read_input_tokens || 0;
|
|
11728
|
-
const creation = u.cache_creation_input_tokens || 0;
|
|
11729
|
-
const inputTokens = u.input_tokens || 0;
|
|
11730
|
-
const promptTokens = inputTokens + cachedTokens + creation;
|
|
11731
|
-
const completionTokens = u.output_tokens || 0;
|
|
11732
|
-
const extra = {};
|
|
11733
|
-
for (const [key, value] of Object.entries(u)) {
|
|
11734
|
-
if (key === "input_tokens" || key === "output_tokens") continue;
|
|
11735
|
-
extra[key] = value;
|
|
11736
|
-
}
|
|
11737
|
-
return {
|
|
11738
|
-
prompt_tokens: promptTokens,
|
|
11739
|
-
completion_tokens: completionTokens,
|
|
11740
|
-
cached_tokens: cachedTokens,
|
|
11741
|
-
extra: Object.keys(extra).length > 0 ? extra : void 0
|
|
11800
|
+
function turnSuffix(turn) {
|
|
11801
|
+
return `turn-${String(turn).padStart(3, "0")}`;
|
|
11802
|
+
}
|
|
11803
|
+
function snapshotFilename(epoch, recordedAt) {
|
|
11804
|
+
return `${epochPrefix(epoch)}-snapshot-${recordedAt}.zip`;
|
|
11805
|
+
}
|
|
11806
|
+
function metaFilename(epoch, recordedAt) {
|
|
11807
|
+
return `${epochPrefix(epoch)}-meta-${recordedAt}.json`;
|
|
11808
|
+
}
|
|
11809
|
+
function patchFilename(epoch, turn, recordedAt) {
|
|
11810
|
+
return `${epochPrefix(epoch)}-${turnSuffix(turn)}-${recordedAt}.patch.gz`;
|
|
11811
|
+
}
|
|
11812
|
+
function buildEpochMeta(params) {
|
|
11813
|
+
const meta = {
|
|
11814
|
+
formatVersion: 1,
|
|
11815
|
+
patchFormat: TRANSCRIPT_PATCH_FORMAT,
|
|
11816
|
+
sessionId: params.sessionId,
|
|
11817
|
+
tool: params.tool,
|
|
11818
|
+
cliVersion: params.cliVersion,
|
|
11819
|
+
epoch: params.epoch,
|
|
11820
|
+
transitionKind: params.transitionKind,
|
|
11821
|
+
baseline: params.baseline,
|
|
11822
|
+
snapshotFilename: params.snapshotFilename,
|
|
11823
|
+
transcriptArchivePath: params.transcriptArchivePath,
|
|
11824
|
+
rawByteOffset: params.cursor.rawByteOffset,
|
|
11825
|
+
rawPrefixSha256: params.cursor.rawPrefixSha256,
|
|
11826
|
+
recordedAt: new Date(params.recordedAt).toISOString()
|
|
11742
11827
|
};
|
|
11828
|
+
return Buffer.from(JSON.stringify(meta, null, 2));
|
|
11743
11829
|
}
|
|
11744
|
-
function
|
|
11745
|
-
|
|
11746
|
-
const content = block.content;
|
|
11747
|
-
if (typeof content === "string") {
|
|
11748
|
-
if (content.trim()) parts.push(content.trim());
|
|
11749
|
-
} else if (Array.isArray(content)) {
|
|
11750
|
-
for (const item of content) {
|
|
11751
|
-
const text2 = stringify(item);
|
|
11752
|
-
if (text2.trim()) parts.push(text2.trim());
|
|
11753
|
-
}
|
|
11754
|
-
} else if (content !== void 0 && content !== null && content !== "") {
|
|
11755
|
-
parts.push(stringify(content));
|
|
11756
|
-
}
|
|
11757
|
-
let metadata;
|
|
11758
|
-
if (toolUseResult && typeof toolUseResult === "object") {
|
|
11759
|
-
metadata = { tool_use_result: toolUseResult };
|
|
11760
|
-
const stdout = toolUseResult.stdout;
|
|
11761
|
-
const stderr = toolUseResult.stderr;
|
|
11762
|
-
const exitCode = toolUseResult.exitCode ?? toolUseResult.exit_code;
|
|
11763
|
-
const interrupted = toolUseResult.interrupted;
|
|
11764
|
-
const isImage = toolUseResult.isImage;
|
|
11765
|
-
const formatted = [];
|
|
11766
|
-
if (stdout) formatted.push(`[stdout]
|
|
11767
|
-
${stdout}`.trimEnd());
|
|
11768
|
-
if (stderr) formatted.push(`[stderr]
|
|
11769
|
-
${stderr}`.trimEnd());
|
|
11770
|
-
if (exitCode !== void 0 && exitCode !== null && exitCode !== 0)
|
|
11771
|
-
formatted.push(`[exit_code] ${exitCode}`);
|
|
11772
|
-
if (interrupted) formatted.push(`[interrupted] ${interrupted}`);
|
|
11773
|
-
if (isImage) formatted.push(`[is_image] ${isImage}`);
|
|
11774
|
-
const skipKeys = /* @__PURE__ */ new Set([
|
|
11775
|
-
"stdout",
|
|
11776
|
-
"stderr",
|
|
11777
|
-
"exitCode",
|
|
11778
|
-
"exit_code",
|
|
11779
|
-
"interrupted",
|
|
11780
|
-
"isImage"
|
|
11781
|
-
]);
|
|
11782
|
-
const remainingMeta = {};
|
|
11783
|
-
for (const [k, v] of Object.entries(toolUseResult)) {
|
|
11784
|
-
if (!skipKeys.has(k)) remainingMeta[k] = v;
|
|
11785
|
-
}
|
|
11786
|
-
if (Object.keys(remainingMeta).length > 0) {
|
|
11787
|
-
formatted.push(`[metadata] ${JSON.stringify(remainingMeta)}`);
|
|
11788
|
-
}
|
|
11789
|
-
if (formatted.length > 0) {
|
|
11790
|
-
parts.push(formatted.filter(Boolean).join("\n"));
|
|
11791
|
-
}
|
|
11792
|
-
}
|
|
11793
|
-
if (block.is_error === true) {
|
|
11794
|
-
parts.push("[error] tool reported failure");
|
|
11795
|
-
metadata = metadata || {};
|
|
11796
|
-
metadata.is_error = true;
|
|
11797
|
-
}
|
|
11798
|
-
if (metadata !== void 0) {
|
|
11799
|
-
if (!("raw_tool_result" in metadata)) {
|
|
11800
|
-
metadata.raw_tool_result = block;
|
|
11801
|
-
}
|
|
11802
|
-
}
|
|
11803
|
-
const resultText = parts.filter(Boolean).join("\n\n").trim();
|
|
11804
|
-
return [resultText || void 0, metadata];
|
|
11830
|
+
function gzipPatch(redactedTail) {
|
|
11831
|
+
return gzipSync(redactedTail);
|
|
11805
11832
|
}
|
|
11806
|
-
function
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
const trimmed = line.trim();
|
|
11810
|
-
if (!trimmed) continue;
|
|
11811
|
-
try {
|
|
11812
|
-
rawEvents.push(JSON.parse(trimmed));
|
|
11813
|
-
} catch {
|
|
11814
|
-
}
|
|
11833
|
+
async function uploadArtifact(client, contributionId, filename, mimeType, buffer, maxBytes) {
|
|
11834
|
+
if (maxBytes !== void 0 && buffer.byteLength > maxBytes) {
|
|
11835
|
+
throw new UploadTooLargeError(filename, buffer.byteLength, maxBytes);
|
|
11815
11836
|
}
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
|
|
11837
|
+
const presigned = await client.createUpload(contributionId, {
|
|
11838
|
+
originalFilename: filename,
|
|
11839
|
+
mimeType,
|
|
11840
|
+
sizeBytes: buffer.byteLength
|
|
11841
|
+
});
|
|
11842
|
+
appendLog(
|
|
11843
|
+
"info",
|
|
11844
|
+
`uploading ${filename} (${buffer.byteLength} bytes) to presigned URL`
|
|
11819
11845
|
);
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
const
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
const
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
const lastUsageByMsgId = /* @__PURE__ */ new Map();
|
|
11860
|
-
for (const ev of events) {
|
|
11861
|
-
if (ev.type !== "assistant") continue;
|
|
11862
|
-
const msg = ev.message;
|
|
11863
|
-
if (typeof msg !== "object" || msg === null) continue;
|
|
11864
|
-
const mid = msg.id;
|
|
11865
|
-
const usage = msg.usage;
|
|
11866
|
-
if (mid && usage !== void 0) {
|
|
11867
|
-
lastUsageByMsgId.set(mid, usage);
|
|
11868
|
-
}
|
|
11869
|
-
}
|
|
11870
|
-
const normalizedEvents = [];
|
|
11871
|
-
const pendingCalls = /* @__PURE__ */ new Map();
|
|
11872
|
-
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
11873
|
-
for (const event of events) {
|
|
11874
|
-
const message = event.message;
|
|
11875
|
-
if (typeof message !== "object" || message === null) continue;
|
|
11876
|
-
const msg = message;
|
|
11877
|
-
const eventType = event.type;
|
|
11878
|
-
const timestamp = event.timestamp;
|
|
11879
|
-
if (eventType === "assistant") {
|
|
11880
|
-
const [text2, reasoning, toolBlocks] = extractTextReasoningToolUses(
|
|
11881
|
-
msg.content
|
|
11846
|
+
await client.uploadToPresignedUrl(
|
|
11847
|
+
presigned.presignedUrl,
|
|
11848
|
+
presigned.headers,
|
|
11849
|
+
buffer
|
|
11850
|
+
);
|
|
11851
|
+
appendLog("info", `PUT to presigned URL succeeded for ${filename}`);
|
|
11852
|
+
}
|
|
11853
|
+
|
|
11854
|
+
// src/transcript-cursor.ts
|
|
11855
|
+
import crypto2 from "crypto";
|
|
11856
|
+
import fs11 from "fs";
|
|
11857
|
+
function sha256OfBuffer(buffer) {
|
|
11858
|
+
return crypto2.createHash("sha256").update(buffer).digest("hex");
|
|
11859
|
+
}
|
|
11860
|
+
async function hashPrefix(filePath, byteLength) {
|
|
11861
|
+
const hash = crypto2.createHash("sha256");
|
|
11862
|
+
if (byteLength === 0) return hash;
|
|
11863
|
+
await new Promise((resolve, reject) => {
|
|
11864
|
+
const stream = fs11.createReadStream(filePath, {
|
|
11865
|
+
start: 0,
|
|
11866
|
+
end: byteLength - 1
|
|
11867
|
+
});
|
|
11868
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
11869
|
+
stream.on("error", reject);
|
|
11870
|
+
stream.on("end", () => resolve());
|
|
11871
|
+
});
|
|
11872
|
+
return hash;
|
|
11873
|
+
}
|
|
11874
|
+
async function readRange(filePath, start, end) {
|
|
11875
|
+
const fd = await fs11.promises.open(filePath, "r");
|
|
11876
|
+
try {
|
|
11877
|
+
const buffer = Buffer.alloc(end - start);
|
|
11878
|
+
let filled = 0;
|
|
11879
|
+
while (filled < buffer.length) {
|
|
11880
|
+
const { bytesRead } = await fd.read(
|
|
11881
|
+
buffer,
|
|
11882
|
+
filled,
|
|
11883
|
+
buffer.length - filled,
|
|
11884
|
+
start + filled
|
|
11882
11885
|
);
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
if (msgId && seenMessageIds.has(msgId)) {
|
|
11886
|
-
metrics = void 0;
|
|
11887
|
-
} else {
|
|
11888
|
-
const usage = msgId ? lastUsageByMsgId.get(msgId) ?? msg.usage : msg.usage;
|
|
11889
|
-
metrics = buildMetrics(usage);
|
|
11890
|
-
if (msgId) seenMessageIds.add(msgId);
|
|
11891
|
-
}
|
|
11892
|
-
const extra = {};
|
|
11893
|
-
for (const key of ["stop_reason", "stop_sequence", "requestId"]) {
|
|
11894
|
-
const value = msg[key];
|
|
11895
|
-
if (value !== void 0 && value !== null) extra[key] = value;
|
|
11896
|
-
}
|
|
11897
|
-
if (event.id) extra.id = event.id;
|
|
11898
|
-
if (event.agentId) extra.agent_id = event.agentId;
|
|
11899
|
-
if (event.cwd) extra.cwd = extra.cwd ?? event.cwd;
|
|
11900
|
-
if (event.userType && event.userType !== "external")
|
|
11901
|
-
extra.user_type = event.userType;
|
|
11902
|
-
extra.is_sidechain = event.isSidechain ?? false;
|
|
11903
|
-
const modelName = msg.model || defaultModelName;
|
|
11904
|
-
if (text2 || reasoning || toolBlocks.length === 0) {
|
|
11905
|
-
normalizedEvents.push({
|
|
11906
|
-
kind: "message",
|
|
11907
|
-
timestamp,
|
|
11908
|
-
role: msg.role ?? "assistant",
|
|
11909
|
-
text: text2 || "",
|
|
11910
|
-
reasoning: msg.role === "assistant" ? reasoning : void 0,
|
|
11911
|
-
metrics,
|
|
11912
|
-
extra: Object.keys(extra).length > 0 ? extra : void 0,
|
|
11913
|
-
model_name: modelName
|
|
11914
|
-
});
|
|
11915
|
-
metrics = void 0;
|
|
11916
|
-
}
|
|
11917
|
-
for (let idx = 0; idx < toolBlocks.length; idx++) {
|
|
11918
|
-
const toolBlock = toolBlocks[idx];
|
|
11919
|
-
const callId = toolBlock.id ?? toolBlock.tool_use_id;
|
|
11920
|
-
if (!callId) continue;
|
|
11921
|
-
const rawArguments = toolBlock.input;
|
|
11922
|
-
const args = typeof rawArguments === "object" && rawArguments !== null && !Array.isArray(rawArguments) ? rawArguments : { input: rawArguments };
|
|
11923
|
-
const callExtra = { ...extra };
|
|
11924
|
-
if (toolBlock.is_error !== void 0)
|
|
11925
|
-
callExtra.tool_use_is_error = toolBlock.is_error;
|
|
11926
|
-
if (toolBlock.name)
|
|
11927
|
-
callExtra.tool_use_name = callExtra.tool_use_name ?? toolBlock.name;
|
|
11928
|
-
pendingCalls.set(callId, {
|
|
11929
|
-
kind: "tool_call",
|
|
11930
|
-
timestamp,
|
|
11931
|
-
call_id: callId,
|
|
11932
|
-
tool_name: toolBlock.name ?? "",
|
|
11933
|
-
arguments: args,
|
|
11934
|
-
raw_arguments: rawArguments,
|
|
11935
|
-
reasoning,
|
|
11936
|
-
status: toolBlock.status,
|
|
11937
|
-
message: void 0,
|
|
11938
|
-
extra: Object.keys(callExtra).length > 0 ? callExtra : void 0,
|
|
11939
|
-
metrics: idx === 0 && metrics !== void 0 ? metrics : void 0,
|
|
11940
|
-
model_name: modelName
|
|
11941
|
-
});
|
|
11942
|
-
if (idx === 0 && metrics !== void 0) metrics = void 0;
|
|
11943
|
-
}
|
|
11944
|
-
continue;
|
|
11945
|
-
}
|
|
11946
|
-
if (eventType === "user") {
|
|
11947
|
-
const content = msg.content;
|
|
11948
|
-
if (typeof content === "string") {
|
|
11949
|
-
const text2 = content.trim();
|
|
11950
|
-
if (text2) {
|
|
11951
|
-
normalizedEvents.push({
|
|
11952
|
-
kind: "message",
|
|
11953
|
-
timestamp,
|
|
11954
|
-
role: "user",
|
|
11955
|
-
text: text2,
|
|
11956
|
-
extra: { is_sidechain: event.isSidechain ?? false }
|
|
11957
|
-
});
|
|
11958
|
-
}
|
|
11959
|
-
continue;
|
|
11960
|
-
}
|
|
11961
|
-
if (Array.isArray(content)) {
|
|
11962
|
-
const textParts = [];
|
|
11963
|
-
for (const block of content) {
|
|
11964
|
-
if (typeof block === "object" && block !== null && block.type === "tool_result") {
|
|
11965
|
-
const b = block;
|
|
11966
|
-
const callId = b.tool_use_id;
|
|
11967
|
-
const [formattedOutput, resultMetadata] = formatToolResult(
|
|
11968
|
-
b,
|
|
11969
|
-
event.toolUseResult
|
|
11970
|
-
);
|
|
11971
|
-
let callInfo = callId ? pendingCalls.get(callId) : void 0;
|
|
11972
|
-
if (callId) pendingCalls.delete(callId);
|
|
11973
|
-
if (!callInfo) {
|
|
11974
|
-
callInfo = {
|
|
11975
|
-
kind: "tool_call",
|
|
11976
|
-
timestamp,
|
|
11977
|
-
call_id: callId ?? "",
|
|
11978
|
-
tool_name: b.name ?? b.tool_name ?? "",
|
|
11979
|
-
arguments: {},
|
|
11980
|
-
raw_arguments: void 0,
|
|
11981
|
-
reasoning: void 0,
|
|
11982
|
-
status: void 0,
|
|
11983
|
-
message: void 0,
|
|
11984
|
-
extra: void 0,
|
|
11985
|
-
metrics: void 0,
|
|
11986
|
-
model_name: defaultModelName
|
|
11987
|
-
};
|
|
11988
|
-
}
|
|
11989
|
-
const extraVal = callInfo.extra;
|
|
11990
|
-
const callExtra = typeof extraVal === "object" && extraVal !== null ? { ...extraVal } : {};
|
|
11991
|
-
if (resultMetadata)
|
|
11992
|
-
callExtra.tool_result_metadata = callExtra.tool_result_metadata ?? resultMetadata;
|
|
11993
|
-
if (b.is_error !== void 0)
|
|
11994
|
-
callExtra.tool_result_is_error = callExtra.tool_result_is_error ?? b.is_error;
|
|
11995
|
-
callInfo.extra = Object.keys(callExtra).length > 0 ? callExtra : void 0;
|
|
11996
|
-
callInfo.output = formattedOutput;
|
|
11997
|
-
callInfo.metadata = resultMetadata;
|
|
11998
|
-
callInfo.timestamp = callInfo.timestamp ?? timestamp;
|
|
11999
|
-
callInfo.model_name = callInfo.model_name ?? defaultModelName;
|
|
12000
|
-
normalizedEvents.push(callInfo);
|
|
12001
|
-
continue;
|
|
12002
|
-
}
|
|
12003
|
-
textParts.push(stringify(block));
|
|
12004
|
-
}
|
|
12005
|
-
const textMessage = textParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
12006
|
-
if (textMessage) {
|
|
12007
|
-
normalizedEvents.push({
|
|
12008
|
-
kind: "message",
|
|
12009
|
-
timestamp,
|
|
12010
|
-
role: "user",
|
|
12011
|
-
text: textMessage
|
|
12012
|
-
});
|
|
12013
|
-
}
|
|
12014
|
-
continue;
|
|
12015
|
-
}
|
|
12016
|
-
if (content !== void 0 && content !== null && content !== "") {
|
|
12017
|
-
const text2 = stringify(content).trim();
|
|
12018
|
-
if (text2) {
|
|
12019
|
-
normalizedEvents.push({
|
|
12020
|
-
kind: "message",
|
|
12021
|
-
timestamp,
|
|
12022
|
-
role: "user",
|
|
12023
|
-
text: text2
|
|
12024
|
-
});
|
|
12025
|
-
}
|
|
12026
|
-
}
|
|
12027
|
-
}
|
|
12028
|
-
}
|
|
12029
|
-
for (const leftover of pendingCalls.values()) {
|
|
12030
|
-
normalizedEvents.push(leftover);
|
|
12031
|
-
}
|
|
12032
|
-
const steps = [];
|
|
12033
|
-
let stepId = 1;
|
|
12034
|
-
for (const normEvent of normalizedEvents) {
|
|
12035
|
-
const step = convertEventToStep(normEvent, stepId, defaultModelName);
|
|
12036
|
-
if (!step) continue;
|
|
12037
|
-
if (step.source === "agent" && !step.model_name && defaultModelName) {
|
|
12038
|
-
step.model_name = defaultModelName;
|
|
12039
|
-
}
|
|
12040
|
-
steps.push(step);
|
|
12041
|
-
stepId++;
|
|
12042
|
-
}
|
|
12043
|
-
if (steps.length === 0) return null;
|
|
12044
|
-
const promptValues = steps.filter((s) => s.metrics?.prompt_tokens !== void 0).map((s) => s.metrics.prompt_tokens);
|
|
12045
|
-
const completionValues = steps.filter((s) => s.metrics?.completion_tokens !== void 0).map((s) => s.metrics.completion_tokens);
|
|
12046
|
-
const cachedValues = steps.filter((s) => s.metrics?.cached_tokens !== void 0).map((s) => s.metrics.cached_tokens);
|
|
12047
|
-
const serviceTiers = /* @__PURE__ */ new Set();
|
|
12048
|
-
let cacheCreationTotal = 0;
|
|
12049
|
-
let cacheReadTotal = 0;
|
|
12050
|
-
let cacheCreationSeen = false;
|
|
12051
|
-
let cacheReadSeen = false;
|
|
12052
|
-
for (const step of steps) {
|
|
12053
|
-
if (!step.metrics?.extra) continue;
|
|
12054
|
-
const tier = step.metrics.extra.service_tier;
|
|
12055
|
-
if (typeof tier === "string") serviceTiers.add(tier);
|
|
12056
|
-
const cacheCreation = step.metrics.extra.cache_creation_input_tokens;
|
|
12057
|
-
if (typeof cacheCreation === "number") {
|
|
12058
|
-
cacheCreationTotal += cacheCreation;
|
|
12059
|
-
cacheCreationSeen = true;
|
|
11886
|
+
if (bytesRead === 0) break;
|
|
11887
|
+
filled += bytesRead;
|
|
12060
11888
|
}
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
11889
|
+
return buffer.subarray(0, filled);
|
|
11890
|
+
} finally {
|
|
11891
|
+
await fd.close();
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
async function evaluateTranscript(filePath, cursor) {
|
|
11895
|
+
const stat = await fs11.promises.stat(filePath);
|
|
11896
|
+
if (stat.size < cursor.rawByteOffset) return { kind: "truncate" };
|
|
11897
|
+
const prefixHash = await hashPrefix(filePath, cursor.rawByteOffset);
|
|
11898
|
+
const continuation = prefixHash.copy();
|
|
11899
|
+
if (prefixHash.digest("hex") !== cursor.rawPrefixSha256) {
|
|
11900
|
+
return { kind: "rewrite" };
|
|
11901
|
+
}
|
|
11902
|
+
if (stat.size === cursor.rawByteOffset) return { kind: "unchanged" };
|
|
11903
|
+
const grown = await readRange(filePath, cursor.rawByteOffset, stat.size);
|
|
11904
|
+
const lastNewline = grown.lastIndexOf(10);
|
|
11905
|
+
if (lastNewline === -1) return { kind: "unchanged" };
|
|
11906
|
+
const tail = grown.subarray(0, lastNewline + 1);
|
|
11907
|
+
return {
|
|
11908
|
+
kind: "append",
|
|
11909
|
+
tail,
|
|
11910
|
+
nextCursor: {
|
|
11911
|
+
rawByteOffset: cursor.rawByteOffset + tail.length,
|
|
11912
|
+
rawPrefixSha256: continuation.update(tail).digest("hex")
|
|
12065
11913
|
}
|
|
12066
|
-
}
|
|
12067
|
-
const finalExtra = {};
|
|
12068
|
-
if (serviceTiers.size > 0)
|
|
12069
|
-
finalExtra.service_tiers = [...serviceTiers].sort();
|
|
12070
|
-
if (cacheCreationSeen)
|
|
12071
|
-
finalExtra.total_cache_creation_input_tokens = cacheCreationTotal;
|
|
12072
|
-
if (cacheReadSeen) finalExtra.total_cache_read_input_tokens = cacheReadTotal;
|
|
12073
|
-
const finalMetrics = {
|
|
12074
|
-
total_prompt_tokens: promptValues.length > 0 ? promptValues.reduce((a, b) => a + b, 0) : void 0,
|
|
12075
|
-
total_completion_tokens: completionValues.length > 0 ? completionValues.reduce((a, b) => a + b, 0) : void 0,
|
|
12076
|
-
total_cached_tokens: cachedValues.length > 0 ? cachedValues.reduce((a, b) => a + b, 0) : void 0,
|
|
12077
|
-
total_steps: steps.length,
|
|
12078
|
-
extra: Object.keys(finalExtra).length > 0 ? finalExtra : void 0
|
|
12079
11914
|
};
|
|
11915
|
+
}
|
|
11916
|
+
function truncateAtLastNewline(buffer) {
|
|
11917
|
+
const lastNewline = buffer.lastIndexOf(10);
|
|
11918
|
+
if (lastNewline === -1) return null;
|
|
11919
|
+
const covered = buffer.subarray(0, lastNewline + 1);
|
|
12080
11920
|
return {
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
model_name: defaultModelName,
|
|
12087
|
-
extra: Object.keys(agentExtra).length > 0 ? agentExtra : void 0
|
|
12088
|
-
},
|
|
12089
|
-
steps,
|
|
12090
|
-
final_metrics: finalMetrics
|
|
11921
|
+
covered,
|
|
11922
|
+
cursor: {
|
|
11923
|
+
rawByteOffset: covered.length,
|
|
11924
|
+
rawPrefixSha256: sha256OfBuffer(covered)
|
|
11925
|
+
}
|
|
12091
11926
|
};
|
|
12092
11927
|
}
|
|
12093
|
-
function
|
|
12094
|
-
|
|
12095
|
-
const
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
const
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
}
|
|
12106
|
-
|
|
12107
|
-
if (event.reasoning) step.reasoning_content = event.reasoning;
|
|
12108
|
-
if (event.model_name) step.model_name = event.model_name;
|
|
12109
|
-
else if (defaultModelName) step.model_name = defaultModelName;
|
|
11928
|
+
async function cursorMatchesFile(filePath, cursor) {
|
|
11929
|
+
try {
|
|
11930
|
+
const stat = await fs11.promises.stat(filePath);
|
|
11931
|
+
if (stat.size < cursor.rawByteOffset) return false;
|
|
11932
|
+
const prefixHash = await hashPrefix(filePath, cursor.rawByteOffset);
|
|
11933
|
+
if (prefixHash.digest("hex") !== cursor.rawPrefixSha256) return false;
|
|
11934
|
+
if (cursor.rawByteOffset === 0) return true;
|
|
11935
|
+
const fd = await fs11.promises.open(filePath, "r");
|
|
11936
|
+
try {
|
|
11937
|
+
const byte = Buffer.alloc(1);
|
|
11938
|
+
const { bytesRead } = await fd.read(byte, 0, 1, cursor.rawByteOffset - 1);
|
|
11939
|
+
return bytesRead === 1 && byte[0] === 10;
|
|
11940
|
+
} finally {
|
|
11941
|
+
await fd.close();
|
|
12110
11942
|
}
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
return step;
|
|
12114
|
-
}
|
|
12115
|
-
if (event.kind === "tool_call") {
|
|
12116
|
-
const callId = event.call_id;
|
|
12117
|
-
const toolName = event.tool_name;
|
|
12118
|
-
if (!callId || !toolName) return null;
|
|
12119
|
-
const toolCall = {
|
|
12120
|
-
tool_call_id: callId,
|
|
12121
|
-
function_name: toolName,
|
|
12122
|
-
arguments: event.arguments ?? {}
|
|
12123
|
-
};
|
|
12124
|
-
const observationResult = {
|
|
12125
|
-
source_call_id: callId,
|
|
12126
|
-
content: event.output
|
|
12127
|
-
};
|
|
12128
|
-
const observation = event.output !== void 0 ? { results: [observationResult] } : void 0;
|
|
12129
|
-
const extra = { ...event.extra ?? {} };
|
|
12130
|
-
if (event.metadata !== void 0)
|
|
12131
|
-
extra.metadata = extra.metadata ?? event.metadata;
|
|
12132
|
-
if (event.raw_arguments !== void 0)
|
|
12133
|
-
extra.raw_arguments = extra.raw_arguments ?? event.raw_arguments;
|
|
12134
|
-
if (event.status !== void 0) extra.status = extra.status ?? event.status;
|
|
12135
|
-
const summaryParts = [toolName, callId].filter(Boolean);
|
|
12136
|
-
const message = event.message || `Executed ${summaryParts.length > 0 ? summaryParts.join(" ") : "Tool call"}`;
|
|
12137
|
-
const step = {
|
|
12138
|
-
step_id: stepId,
|
|
12139
|
-
timestamp: event.timestamp,
|
|
12140
|
-
source: "agent",
|
|
12141
|
-
message,
|
|
12142
|
-
tool_calls: [toolCall],
|
|
12143
|
-
observation
|
|
12144
|
-
};
|
|
12145
|
-
if (event.model_name) step.model_name = event.model_name;
|
|
12146
|
-
if (event.reasoning) step.reasoning_content = event.reasoning;
|
|
12147
|
-
if (event.metrics) step.metrics = event.metrics;
|
|
12148
|
-
if (Object.keys(extra).length > 0) step.extra = extra;
|
|
12149
|
-
return step;
|
|
11943
|
+
} catch {
|
|
11944
|
+
return false;
|
|
12150
11945
|
}
|
|
12151
|
-
return null;
|
|
12152
11946
|
}
|
|
12153
11947
|
|
|
12154
|
-
// src/
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
11948
|
+
// src/upload-state.ts
|
|
11949
|
+
import crypto3 from "crypto";
|
|
11950
|
+
import fs12 from "fs";
|
|
11951
|
+
import os5 from "os";
|
|
11952
|
+
import path13 from "path";
|
|
11953
|
+
var CURRENT_SCHEMA_VERSION2 = 1;
|
|
11954
|
+
var DEFAULT_STATE_DIR = path13.join(
|
|
11955
|
+
os5.homedir(),
|
|
11956
|
+
".hillclimb",
|
|
11957
|
+
"agent-uploads"
|
|
11958
|
+
);
|
|
11959
|
+
var DEFAULT_LOCK_WAIT_MS = 5 * 60 * 1e3;
|
|
11960
|
+
var DEFAULT_LOCK_RETRY_DELAY_MS = 500;
|
|
11961
|
+
var DEFAULT_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11962
|
+
var STALE_LOCK_TTL_MS = 60 * 60 * 1e3;
|
|
11963
|
+
function stateDir2() {
|
|
11964
|
+
return process.env.HILLCLIMB_UPLOAD_STATE_DIR ?? DEFAULT_STATE_DIR;
|
|
12164
11965
|
}
|
|
12165
|
-
function
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
const obj = parsed;
|
|
12177
|
-
let output = obj.output;
|
|
12178
|
-
if (output === void 0 && Object.keys(obj).length > 0) {
|
|
12179
|
-
output = JSON.stringify(obj);
|
|
12180
|
-
}
|
|
12181
|
-
const metadata = obj.metadata;
|
|
12182
|
-
return [
|
|
12183
|
-
output ?? void 0,
|
|
12184
|
-
typeof metadata === "object" && metadata !== null ? metadata : void 0
|
|
12185
|
-
];
|
|
12186
|
-
}
|
|
12187
|
-
return [String(parsed), void 0];
|
|
11966
|
+
function readPositiveEnvMs(name, fallback) {
|
|
11967
|
+
const raw = process.env[name];
|
|
11968
|
+
if (raw === void 0) return fallback;
|
|
11969
|
+
const n = Number(raw);
|
|
11970
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
11971
|
+
}
|
|
11972
|
+
function stateTtlMs() {
|
|
11973
|
+
return readPositiveEnvMs(
|
|
11974
|
+
"HILLCLIMB_UPLOAD_STATE_TTL_MS",
|
|
11975
|
+
DEFAULT_STATE_TTL_MS
|
|
11976
|
+
);
|
|
12188
11977
|
}
|
|
12189
|
-
function
|
|
12190
|
-
return
|
|
11978
|
+
function lockRetryDelayMs() {
|
|
11979
|
+
return Math.max(
|
|
11980
|
+
1,
|
|
11981
|
+
readPositiveEnvMs(
|
|
11982
|
+
"HILLCLIMB_UPLOAD_LOCK_RETRY_DELAY_MS",
|
|
11983
|
+
DEFAULT_LOCK_RETRY_DELAY_MS
|
|
11984
|
+
)
|
|
11985
|
+
);
|
|
12191
11986
|
}
|
|
12192
|
-
function
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
11987
|
+
function lockRetries() {
|
|
11988
|
+
return Math.max(
|
|
11989
|
+
1,
|
|
11990
|
+
Math.ceil(
|
|
11991
|
+
readPositiveEnvMs("HILLCLIMB_UPLOAD_LOCK_WAIT_MS", DEFAULT_LOCK_WAIT_MS) / lockRetryDelayMs()
|
|
11992
|
+
)
|
|
11993
|
+
);
|
|
11994
|
+
}
|
|
11995
|
+
function stateFileFor(repoRoot, tool, sessionId) {
|
|
11996
|
+
const hash = crypto3.createHash("sha256").update(`${path13.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex").slice(0, 16);
|
|
11997
|
+
return path13.join(stateDir2(), `${hash}.json`);
|
|
11998
|
+
}
|
|
11999
|
+
function lockFileFor(repoRoot, tool, sessionId) {
|
|
12000
|
+
return `${stateFileFor(repoRoot, tool, sessionId)}.lock`;
|
|
12198
12001
|
}
|
|
12199
|
-
function
|
|
12200
|
-
const obj = asObject(raw);
|
|
12201
|
-
if (obj) return obj;
|
|
12202
|
-
if (typeof raw !== "string") return void 0;
|
|
12002
|
+
async function readUploadState(repoRoot, tool, sessionId) {
|
|
12203
12003
|
try {
|
|
12204
|
-
|
|
12004
|
+
const raw = await fs12.promises.readFile(
|
|
12005
|
+
stateFileFor(repoRoot, tool, sessionId),
|
|
12006
|
+
"utf-8"
|
|
12007
|
+
);
|
|
12008
|
+
const parsed = JSON.parse(raw);
|
|
12009
|
+
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION2) return null;
|
|
12010
|
+
return parsed;
|
|
12205
12011
|
} catch {
|
|
12206
|
-
return
|
|
12012
|
+
return null;
|
|
12207
12013
|
}
|
|
12208
12014
|
}
|
|
12209
|
-
function
|
|
12210
|
-
const
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
nickname: typeof output?.nickname === "string" && output.nickname || typeof output?.agent_nickname === "string" && output.agent_nickname || void 0
|
|
12216
|
-
});
|
|
12217
|
-
return {
|
|
12218
|
-
session_id: sessionId,
|
|
12219
|
-
extra
|
|
12220
|
-
};
|
|
12221
|
-
}
|
|
12222
|
-
function codexTrajectoryExtra(metaPayload) {
|
|
12223
|
-
const source = asObject(metaPayload.source);
|
|
12224
|
-
const subagent = asObject(source?.subagent);
|
|
12225
|
-
const threadSpawn = asObject(subagent?.thread_spawn);
|
|
12226
|
-
const parentThreadId = typeof threadSpawn?.parent_thread_id === "string" ? threadSpawn.parent_thread_id : void 0;
|
|
12227
|
-
const subagentExtra = threadSpawn ? compactExtra({
|
|
12228
|
-
depth: threadSpawn.depth,
|
|
12229
|
-
agent_path: threadSpawn.agent_path,
|
|
12230
|
-
agent_nickname: threadSpawn.agent_nickname,
|
|
12231
|
-
agent_role: threadSpawn.agent_role
|
|
12232
|
-
}) : void 0;
|
|
12233
|
-
return compactExtra({
|
|
12234
|
-
thread_source: metaPayload.thread_source,
|
|
12235
|
-
agent_nickname: metaPayload.agent_nickname,
|
|
12236
|
-
agent_role: metaPayload.agent_role,
|
|
12237
|
-
parent_session_id: parentThreadId,
|
|
12238
|
-
parent_thread_id: parentThreadId,
|
|
12239
|
-
subagent: subagentExtra
|
|
12015
|
+
async function writeUploadState(state) {
|
|
12016
|
+
const file = stateFileFor(state.repoRoot, state.tool, state.sessionId);
|
|
12017
|
+
await fs12.promises.mkdir(stateDir2(), { recursive: true, mode: 448 });
|
|
12018
|
+
const tmp = `${file}.tmp`;
|
|
12019
|
+
await fs12.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
12020
|
+
mode: 384
|
|
12240
12021
|
});
|
|
12022
|
+
await fs12.promises.rename(tmp, file);
|
|
12241
12023
|
}
|
|
12242
|
-
function
|
|
12243
|
-
const rawEvents = [];
|
|
12244
|
-
for (const line of jsonlContent.split("\n")) {
|
|
12245
|
-
const trimmed = line.trim();
|
|
12246
|
-
if (!trimmed) continue;
|
|
12247
|
-
try {
|
|
12248
|
-
rawEvents.push(JSON.parse(trimmed));
|
|
12249
|
-
} catch {
|
|
12250
|
-
}
|
|
12251
|
-
}
|
|
12252
|
-
if (rawEvents.length === 0) return null;
|
|
12253
|
-
const sessionMeta = rawEvents.find((e) => e.type === "session_meta");
|
|
12254
|
-
const metaPayload = sessionMeta?.payload ?? {};
|
|
12255
|
-
const sid = sessionId ?? (typeof metaPayload.id === "string" ? metaPayload.id : "");
|
|
12256
|
-
const agentVersion = metaPayload.cli_version ?? "unknown";
|
|
12257
|
-
const agentExtra = {};
|
|
12258
|
-
for (const key of ["originator", "cwd", "git", "instructions"]) {
|
|
12259
|
-
const value = metaPayload[key];
|
|
12260
|
-
if (value !== void 0 && value !== null) agentExtra[key] = value;
|
|
12261
|
-
}
|
|
12262
|
-
let defaultModelName;
|
|
12263
|
-
for (const event of rawEvents) {
|
|
12264
|
-
if (event.type === "turn_context") {
|
|
12265
|
-
const model = event.payload?.model;
|
|
12266
|
-
if (typeof model === "string") {
|
|
12267
|
-
defaultModelName = model;
|
|
12268
|
-
break;
|
|
12269
|
-
}
|
|
12270
|
-
}
|
|
12271
|
-
}
|
|
12272
|
-
const normalizedEvents = [];
|
|
12273
|
-
const pendingCalls = /* @__PURE__ */ new Map();
|
|
12274
|
-
let pendingReasoning;
|
|
12275
|
-
for (const event of rawEvents) {
|
|
12276
|
-
const etype = event.type;
|
|
12277
|
-
const payload = event.payload ?? {};
|
|
12278
|
-
const timestamp = event.timestamp;
|
|
12279
|
-
if (etype !== "response_item") continue;
|
|
12280
|
-
const payloadType = payload.type;
|
|
12281
|
-
if (payloadType === "reasoning") {
|
|
12282
|
-
const summary = payload.summary;
|
|
12283
|
-
if (Array.isArray(summary) && summary.length > 0) {
|
|
12284
|
-
pendingReasoning = summary.filter((item) => typeof item === "string").join("\n");
|
|
12285
|
-
} else {
|
|
12286
|
-
pendingReasoning = void 0;
|
|
12287
|
-
}
|
|
12288
|
-
continue;
|
|
12289
|
-
}
|
|
12290
|
-
if (payloadType === "message") {
|
|
12291
|
-
const content = payload.content;
|
|
12292
|
-
const text2 = Array.isArray(content) ? extractMessageText(content) : "";
|
|
12293
|
-
normalizedEvents.push({
|
|
12294
|
-
kind: "message",
|
|
12295
|
-
timestamp,
|
|
12296
|
-
role: payload.role ?? "user",
|
|
12297
|
-
text: text2,
|
|
12298
|
-
reasoning: payload.role === "assistant" ? pendingReasoning : void 0
|
|
12299
|
-
});
|
|
12300
|
-
pendingReasoning = void 0;
|
|
12301
|
-
continue;
|
|
12302
|
-
}
|
|
12303
|
-
if (payloadType === "web_search_call") {
|
|
12304
|
-
const action = payload.action ?? {};
|
|
12305
|
-
const actionType = action.type ?? "";
|
|
12306
|
-
const args = { action_type: actionType };
|
|
12307
|
-
if ("query" in action) args.query = action.query;
|
|
12308
|
-
if ("queries" in action) args.queries = action.queries;
|
|
12309
|
-
if ("url" in action) args.url = action.url;
|
|
12310
|
-
normalizedEvents.push({
|
|
12311
|
-
kind: "tool_call",
|
|
12312
|
-
timestamp,
|
|
12313
|
-
call_id: "",
|
|
12314
|
-
tool_name: "web_search_call",
|
|
12315
|
-
arguments: args,
|
|
12316
|
-
reasoning: pendingReasoning,
|
|
12317
|
-
status: payload.status
|
|
12318
|
-
});
|
|
12319
|
-
pendingReasoning = void 0;
|
|
12320
|
-
continue;
|
|
12321
|
-
}
|
|
12322
|
-
if (payloadType === "function_call" || payloadType === "custom_tool_call") {
|
|
12323
|
-
const callId = payload.call_id;
|
|
12324
|
-
if (!callId) continue;
|
|
12325
|
-
const rawArgsKey = payloadType === "function_call" ? "arguments" : "input";
|
|
12326
|
-
const rawArguments = payload[rawArgsKey];
|
|
12327
|
-
let parsedArgs;
|
|
12328
|
-
if (typeof rawArguments === "string") {
|
|
12329
|
-
try {
|
|
12330
|
-
parsedArgs = JSON.parse(rawArguments);
|
|
12331
|
-
} catch {
|
|
12332
|
-
parsedArgs = { input: rawArguments };
|
|
12333
|
-
}
|
|
12334
|
-
} else if (rawArguments === void 0 || rawArguments === null) {
|
|
12335
|
-
parsedArgs = {};
|
|
12336
|
-
} else if (typeof rawArguments === "object" && !Array.isArray(rawArguments)) {
|
|
12337
|
-
parsedArgs = rawArguments;
|
|
12338
|
-
} else {
|
|
12339
|
-
parsedArgs = { value: rawArguments };
|
|
12340
|
-
}
|
|
12341
|
-
pendingCalls.set(callId, {
|
|
12342
|
-
kind: "tool_call",
|
|
12343
|
-
timestamp,
|
|
12344
|
-
call_id: callId,
|
|
12345
|
-
tool_name: payload.name ?? "",
|
|
12346
|
-
arguments: parsedArgs,
|
|
12347
|
-
raw_arguments: rawArguments,
|
|
12348
|
-
reasoning: pendingReasoning,
|
|
12349
|
-
status: payload.status
|
|
12350
|
-
});
|
|
12351
|
-
pendingReasoning = void 0;
|
|
12352
|
-
continue;
|
|
12353
|
-
}
|
|
12354
|
-
if (payloadType === "function_call_output" || payloadType === "custom_tool_call_output") {
|
|
12355
|
-
const callId = payload.call_id;
|
|
12356
|
-
const [outputText, metadata] = parseOutputBlob(payload.output);
|
|
12357
|
-
let callInfo = callId ? pendingCalls.get(callId) : void 0;
|
|
12358
|
-
if (callId) pendingCalls.delete(callId);
|
|
12359
|
-
if (!callInfo) {
|
|
12360
|
-
callInfo = {
|
|
12361
|
-
kind: "tool_call",
|
|
12362
|
-
timestamp,
|
|
12363
|
-
call_id: callId ?? "",
|
|
12364
|
-
tool_name: payload.name ?? "",
|
|
12365
|
-
arguments: {},
|
|
12366
|
-
reasoning: pendingReasoning
|
|
12367
|
-
};
|
|
12368
|
-
}
|
|
12369
|
-
callInfo.output = outputText;
|
|
12370
|
-
callInfo.metadata = metadata;
|
|
12371
|
-
callInfo.timestamp = callInfo.timestamp ?? timestamp;
|
|
12372
|
-
if (callInfo.tool_name === "spawn_agent") {
|
|
12373
|
-
const subagentRef = subagentRefFromSpawnOutput(
|
|
12374
|
-
callInfo.arguments,
|
|
12375
|
-
payload.output
|
|
12376
|
-
);
|
|
12377
|
-
if (subagentRef) callInfo.subagentRefs = [subagentRef];
|
|
12378
|
-
}
|
|
12379
|
-
normalizedEvents.push(callInfo);
|
|
12380
|
-
pendingReasoning = void 0;
|
|
12381
|
-
}
|
|
12382
|
-
}
|
|
12383
|
-
const steps = [];
|
|
12384
|
-
let stepId = 1;
|
|
12385
|
-
for (const normEvent of normalizedEvents) {
|
|
12386
|
-
const step = convertEventToStep2(normEvent, stepId, defaultModelName);
|
|
12387
|
-
if (!step) continue;
|
|
12388
|
-
if (step.source === "agent" && !step.model_name && defaultModelName) {
|
|
12389
|
-
step.model_name = defaultModelName;
|
|
12390
|
-
}
|
|
12391
|
-
steps.push(step);
|
|
12392
|
-
stepId++;
|
|
12393
|
-
}
|
|
12394
|
-
if (steps.length === 0) return null;
|
|
12395
|
-
let finalMetrics;
|
|
12396
|
-
for (let i = rawEvents.length - 1; i >= 0; i--) {
|
|
12397
|
-
const event = rawEvents[i];
|
|
12398
|
-
if (event.type !== "event_msg") continue;
|
|
12399
|
-
const payload = event.payload;
|
|
12400
|
-
if (!payload || payload.type !== "token_count") continue;
|
|
12401
|
-
const info = payload.info;
|
|
12402
|
-
if (!info || typeof info !== "object") continue;
|
|
12403
|
-
const totalUsage = info.total_token_usage;
|
|
12404
|
-
if (!totalUsage || typeof totalUsage !== "object") continue;
|
|
12405
|
-
const promptTokens = totalUsage.input_tokens;
|
|
12406
|
-
const completionTokens = totalUsage.output_tokens;
|
|
12407
|
-
const reasoningTokens = totalUsage.reasoning_output_tokens;
|
|
12408
|
-
const cachedTokens = totalUsage.cached_input_tokens;
|
|
12409
|
-
const overallTokens = totalUsage.total_tokens;
|
|
12410
|
-
finalMetrics = {
|
|
12411
|
-
total_prompt_tokens: promptTokens || void 0,
|
|
12412
|
-
total_completion_tokens: completionTokens || void 0,
|
|
12413
|
-
total_cached_tokens: cachedTokens || void 0,
|
|
12414
|
-
total_cost_usd: info.total_cost ?? info.cost_usd ?? void 0,
|
|
12415
|
-
total_steps: steps.length,
|
|
12416
|
-
extra: {
|
|
12417
|
-
reasoning_output_tokens: reasoningTokens,
|
|
12418
|
-
total_tokens: overallTokens,
|
|
12419
|
-
last_token_usage: info.last_token_usage
|
|
12420
|
-
}
|
|
12421
|
-
};
|
|
12422
|
-
break;
|
|
12423
|
-
}
|
|
12424
|
-
return {
|
|
12425
|
-
schema_version: "ATIF-v1.5",
|
|
12426
|
-
session_id: sid,
|
|
12427
|
-
agent: {
|
|
12428
|
-
name: "codex",
|
|
12429
|
-
version: agentVersion,
|
|
12430
|
-
model_name: defaultModelName,
|
|
12431
|
-
extra: Object.keys(agentExtra).length > 0 ? agentExtra : void 0
|
|
12432
|
-
},
|
|
12433
|
-
steps,
|
|
12434
|
-
final_metrics: finalMetrics,
|
|
12435
|
-
extra: codexTrajectoryExtra(metaPayload)
|
|
12436
|
-
};
|
|
12437
|
-
}
|
|
12438
|
-
function convertEventToStep2(event, stepId, defaultModelName) {
|
|
12439
|
-
if (event.kind === "message") {
|
|
12440
|
-
const role = event.role ?? "user";
|
|
12441
|
-
let source;
|
|
12442
|
-
if (role === "assistant") source = "agent";
|
|
12443
|
-
else if (role === "user") source = "user";
|
|
12444
|
-
else source = "system";
|
|
12445
|
-
const step = {
|
|
12446
|
-
step_id: stepId,
|
|
12447
|
-
timestamp: event.timestamp,
|
|
12448
|
-
source,
|
|
12449
|
-
message: event.text ?? ""
|
|
12450
|
-
};
|
|
12451
|
-
if (source === "agent") {
|
|
12452
|
-
if (event.reasoning) step.reasoning_content = event.reasoning;
|
|
12453
|
-
if (defaultModelName) step.model_name = defaultModelName;
|
|
12454
|
-
}
|
|
12455
|
-
if (event.extra) step.extra = event.extra;
|
|
12456
|
-
return step;
|
|
12457
|
-
}
|
|
12458
|
-
if (event.kind === "tool_call") {
|
|
12459
|
-
const callId = event.call_id ?? "";
|
|
12460
|
-
const toolName = event.tool_name ?? "";
|
|
12461
|
-
const toolCall = {
|
|
12462
|
-
tool_call_id: callId,
|
|
12463
|
-
function_name: toolName,
|
|
12464
|
-
arguments: typeof event.arguments === "object" && event.arguments !== null ? event.arguments : { value: event.arguments }
|
|
12465
|
-
};
|
|
12466
|
-
let observation;
|
|
12467
|
-
if (event.output !== void 0) {
|
|
12468
|
-
const result = {
|
|
12469
|
-
source_call_id: callId || void 0,
|
|
12470
|
-
content: event.output,
|
|
12471
|
-
subagent_trajectory_ref: event.subagentRefs
|
|
12472
|
-
};
|
|
12473
|
-
observation = { results: [result] };
|
|
12474
|
-
}
|
|
12475
|
-
const extra = {};
|
|
12476
|
-
if (event.metadata) extra.tool_metadata = event.metadata;
|
|
12477
|
-
if (event.raw_arguments !== void 0)
|
|
12478
|
-
extra.raw_arguments = event.raw_arguments;
|
|
12479
|
-
if (event.status) extra.status = event.status;
|
|
12480
|
-
const summaryParts = [toolName, callId].filter(Boolean);
|
|
12481
|
-
const message = event.message || `Executed ${summaryParts.length > 0 ? summaryParts.join(" ") : "Tool call"}`;
|
|
12482
|
-
const step = {
|
|
12483
|
-
step_id: stepId,
|
|
12484
|
-
timestamp: event.timestamp,
|
|
12485
|
-
source: "agent",
|
|
12486
|
-
message,
|
|
12487
|
-
tool_calls: [toolCall],
|
|
12488
|
-
observation
|
|
12489
|
-
};
|
|
12490
|
-
if (defaultModelName) step.model_name = defaultModelName;
|
|
12491
|
-
if (event.reasoning) step.reasoning_content = event.reasoning;
|
|
12492
|
-
if (event.metrics) step.metrics = event.metrics;
|
|
12493
|
-
if (Object.keys(extra).length > 0) step.extra = extra;
|
|
12494
|
-
return step;
|
|
12495
|
-
}
|
|
12496
|
-
return null;
|
|
12497
|
-
}
|
|
12498
|
-
|
|
12499
|
-
// src/normalizer/types.ts
|
|
12500
|
-
var ATIF_VERSION = "ATIF-v1.6";
|
|
12501
|
-
function excludeNone(obj) {
|
|
12502
|
-
const result = {};
|
|
12503
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
12504
|
-
if (value === void 0 || value === null) continue;
|
|
12505
|
-
if (Array.isArray(value)) {
|
|
12506
|
-
result[key] = value.map(
|
|
12507
|
-
(item) => typeof item === "object" && item !== null && !Array.isArray(item) ? excludeNone(item) : item
|
|
12508
|
-
);
|
|
12509
|
-
} else if (typeof value === "object" && !Array.isArray(value)) {
|
|
12510
|
-
result[key] = excludeNone(value);
|
|
12511
|
-
} else {
|
|
12512
|
-
result[key] = value;
|
|
12513
|
-
}
|
|
12514
|
-
}
|
|
12515
|
-
return result;
|
|
12516
|
-
}
|
|
12517
|
-
|
|
12518
|
-
// src/normalizer/copilotChat.ts
|
|
12519
|
-
function asObject2(value) {
|
|
12520
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12521
|
-
}
|
|
12522
|
-
function asNumber(value) {
|
|
12523
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12524
|
-
}
|
|
12525
|
-
function compactExtra2(extra) {
|
|
12526
|
-
const result = {};
|
|
12527
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
12528
|
-
if (value !== void 0 && value !== null) result[key] = value;
|
|
12529
|
-
}
|
|
12530
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
12531
|
-
}
|
|
12532
|
-
function parseJsonLines(content) {
|
|
12533
|
-
const entries = [];
|
|
12534
|
-
for (const line of content.split("\n")) {
|
|
12535
|
-
const trimmed = line.trim();
|
|
12536
|
-
if (!trimmed) continue;
|
|
12537
|
-
try {
|
|
12538
|
-
const parsed = asObject2(JSON.parse(trimmed));
|
|
12539
|
-
if (!parsed) continue;
|
|
12540
|
-
entries.push({
|
|
12541
|
-
type: typeof parsed.type === "string" ? parsed.type : void 0,
|
|
12542
|
-
data: asObject2(parsed.data),
|
|
12543
|
-
id: typeof parsed.id === "string" ? parsed.id : void 0,
|
|
12544
|
-
timestamp: typeof parsed.timestamp === "string" ? parsed.timestamp : void 0
|
|
12545
|
-
});
|
|
12546
|
-
} catch {
|
|
12547
|
-
}
|
|
12548
|
-
}
|
|
12549
|
-
return entries;
|
|
12550
|
-
}
|
|
12551
|
-
function parseArguments(value) {
|
|
12552
|
-
const obj = asObject2(value);
|
|
12553
|
-
if (obj) return obj;
|
|
12554
|
-
if (typeof value === "string") {
|
|
12555
|
-
try {
|
|
12556
|
-
const parsed = asObject2(JSON.parse(value));
|
|
12557
|
-
if (parsed) return parsed;
|
|
12558
|
-
} catch {
|
|
12559
|
-
return value ? { input: value } : {};
|
|
12560
|
-
}
|
|
12561
|
-
}
|
|
12562
|
-
return value === void 0 || value === null ? {} : { value };
|
|
12563
|
-
}
|
|
12564
|
-
function buildMetrics2(data) {
|
|
12565
|
-
const inputTokens = asNumber(data.inputTokens) ?? 0;
|
|
12566
|
-
const outputTokens = asNumber(data.outputTokens) ?? 0;
|
|
12567
|
-
const cacheReadTokens = asNumber(data.cacheReadTokens) ?? 0;
|
|
12568
|
-
const cacheWriteTokens = asNumber(data.cacheWriteTokens) ?? 0;
|
|
12569
|
-
const cost = asNumber(data.cost);
|
|
12570
|
-
if (!inputTokens && !outputTokens && !cacheReadTokens && !cacheWriteTokens && !cost) {
|
|
12571
|
-
return void 0;
|
|
12572
|
-
}
|
|
12573
|
-
return {
|
|
12574
|
-
prompt_tokens: inputTokens + cacheReadTokens || void 0,
|
|
12575
|
-
completion_tokens: outputTokens || void 0,
|
|
12576
|
-
cached_tokens: cacheReadTokens || void 0,
|
|
12577
|
-
cost_usd: cost || void 0,
|
|
12578
|
-
extra: compactExtra2({
|
|
12579
|
-
cache_write_tokens: cacheWriteTokens || void 0,
|
|
12580
|
-
duration_ms: data.duration,
|
|
12581
|
-
initiator: data.initiator,
|
|
12582
|
-
api_call_id: data.apiCallId,
|
|
12583
|
-
provider_call_id: data.providerCallId,
|
|
12584
|
-
parent_tool_call_id: data.parentToolCallId,
|
|
12585
|
-
quota_snapshots: data.quotaSnapshots,
|
|
12586
|
-
copilot_usage: data.copilotUsage
|
|
12587
|
-
})
|
|
12588
|
-
};
|
|
12589
|
-
}
|
|
12590
|
-
function finalMetricsFromSteps(steps) {
|
|
12591
|
-
let prompt = 0;
|
|
12592
|
-
let completion = 0;
|
|
12593
|
-
let cached = 0;
|
|
12594
|
-
let cost = 0;
|
|
12595
|
-
for (const step of steps) {
|
|
12596
|
-
prompt += step.metrics?.prompt_tokens ?? 0;
|
|
12597
|
-
completion += step.metrics?.completion_tokens ?? 0;
|
|
12598
|
-
cached += step.metrics?.cached_tokens ?? 0;
|
|
12599
|
-
cost += step.metrics?.cost_usd ?? 0;
|
|
12600
|
-
}
|
|
12601
|
-
return {
|
|
12602
|
-
total_prompt_tokens: prompt || void 0,
|
|
12603
|
-
total_completion_tokens: completion || void 0,
|
|
12604
|
-
total_cached_tokens: cached || void 0,
|
|
12605
|
-
total_cost_usd: cost || void 0,
|
|
12606
|
-
total_steps: steps.length
|
|
12607
|
-
};
|
|
12608
|
-
}
|
|
12609
|
-
function makeToolCall(request) {
|
|
12610
|
-
const req = asObject2(request);
|
|
12611
|
-
if (!req) return null;
|
|
12612
|
-
const callId = typeof req.toolCallId === "string" && req.toolCallId || typeof req.id === "string" && req.id || "";
|
|
12613
|
-
const name = typeof req.name === "string" && req.name || typeof req.toolName === "string" && req.toolName || "tool";
|
|
12614
|
-
return {
|
|
12615
|
-
tool_call_id: callId,
|
|
12616
|
-
function_name: name,
|
|
12617
|
-
arguments: parseArguments(req.arguments)
|
|
12618
|
-
};
|
|
12619
|
-
}
|
|
12620
|
-
function toolCallFromExecutionStart(data) {
|
|
12621
|
-
const callId = typeof data.toolCallId === "string" && data.toolCallId || typeof data.id === "string" && data.id || "";
|
|
12622
|
-
const name = typeof data.toolName === "string" && data.toolName || typeof data.name === "string" && data.name || "tool";
|
|
12623
|
-
return {
|
|
12624
|
-
tool_call_id: callId,
|
|
12625
|
-
function_name: name,
|
|
12626
|
-
arguments: parseArguments(data.arguments)
|
|
12627
|
-
};
|
|
12628
|
-
}
|
|
12629
|
-
function contentFromToolResult(data) {
|
|
12630
|
-
const result = asObject2(data.result);
|
|
12631
|
-
if (typeof result?.content === "string") return result.content;
|
|
12632
|
-
if (typeof data.content === "string") return data.content;
|
|
12633
|
-
return void 0;
|
|
12634
|
-
}
|
|
12635
|
-
function convertCopilotChatToTrajectory(jsonlContent, sessionId) {
|
|
12636
|
-
const entries = parseJsonLines(jsonlContent);
|
|
12637
|
-
if (entries.length === 0) return null;
|
|
12638
|
-
let sid = sessionId ?? "unknown";
|
|
12639
|
-
let copilotVersion = "unknown";
|
|
12640
|
-
let vscodeVersion;
|
|
12641
|
-
let cwd;
|
|
12642
|
-
let defaultModelName;
|
|
12643
|
-
const steps = [];
|
|
12644
|
-
const pendingReasoning = [];
|
|
12645
|
-
const pendingToolSteps = /* @__PURE__ */ new Map();
|
|
12646
|
-
let lastAgentStep;
|
|
12647
|
-
for (const entry of entries) {
|
|
12648
|
-
const data = entry.data ?? {};
|
|
12649
|
-
if (entry.type === "session.start") {
|
|
12650
|
-
if (!sessionId && typeof data.sessionId === "string")
|
|
12651
|
-
sid = data.sessionId;
|
|
12652
|
-
if (typeof data.copilotVersion === "string")
|
|
12653
|
-
copilotVersion = data.copilotVersion;
|
|
12654
|
-
if (typeof data.vscodeVersion === "string")
|
|
12655
|
-
vscodeVersion = data.vscodeVersion;
|
|
12656
|
-
const context = asObject2(data.context);
|
|
12657
|
-
if (typeof context?.cwd === "string") cwd = context.cwd;
|
|
12658
|
-
continue;
|
|
12659
|
-
}
|
|
12660
|
-
if (entry.type === "user.message" || entry.type === "system.message") {
|
|
12661
|
-
const content = typeof data.content === "string" && data.content || typeof data.transformedContent === "string" && data.transformedContent || "";
|
|
12662
|
-
if (!content.trim()) continue;
|
|
12663
|
-
const source = entry.type === "system.message" ? "system" : "user";
|
|
12664
|
-
const step = {
|
|
12665
|
-
step_id: steps.length + 1,
|
|
12666
|
-
timestamp: entry.timestamp,
|
|
12667
|
-
source,
|
|
12668
|
-
message: content,
|
|
12669
|
-
extra: compactExtra2({
|
|
12670
|
-
attachments: data.attachments,
|
|
12671
|
-
source: data.source,
|
|
12672
|
-
agent_mode: data.agentMode,
|
|
12673
|
-
interaction_id: data.interactionId
|
|
12674
|
-
})
|
|
12675
|
-
};
|
|
12676
|
-
steps.push(step);
|
|
12677
|
-
continue;
|
|
12678
|
-
}
|
|
12679
|
-
if (entry.type === "assistant.reasoning") {
|
|
12680
|
-
if (typeof data.content === "string" && data.content.trim()) {
|
|
12681
|
-
pendingReasoning.push(data.content.trim());
|
|
12682
|
-
}
|
|
12683
|
-
continue;
|
|
12684
|
-
}
|
|
12685
|
-
if (entry.type === "assistant.message") {
|
|
12686
|
-
const toolCalls = (Array.isArray(data.toolRequests) ? data.toolRequests : []).flatMap((request) => {
|
|
12687
|
-
const call = makeToolCall(request);
|
|
12688
|
-
return call ? [call] : [];
|
|
12689
|
-
});
|
|
12690
|
-
const content = typeof data.content === "string" && data.content.trim() ? data.content.trim() : "";
|
|
12691
|
-
const reasoning = typeof data.reasoningText === "string" && data.reasoningText.trim() || pendingReasoning.join("\n\n") || void 0;
|
|
12692
|
-
pendingReasoning.length = 0;
|
|
12693
|
-
const step = {
|
|
12694
|
-
step_id: steps.length + 1,
|
|
12695
|
-
timestamp: entry.timestamp,
|
|
12696
|
-
source: "agent",
|
|
12697
|
-
message: content || (toolCalls.length > 0 ? "(tool use)" : ""),
|
|
12698
|
-
model_name: defaultModelName,
|
|
12699
|
-
extra: compactExtra2({
|
|
12700
|
-
message_id: data.messageId,
|
|
12701
|
-
phase: data.phase,
|
|
12702
|
-
output_tokens: data.outputTokens
|
|
12703
|
-
})
|
|
12704
|
-
};
|
|
12705
|
-
if (reasoning) step.reasoning_content = reasoning;
|
|
12706
|
-
if (toolCalls.length > 0) step.tool_calls = toolCalls;
|
|
12707
|
-
if (step.message || step.tool_calls?.length || step.reasoning_content) {
|
|
12708
|
-
steps.push(step);
|
|
12709
|
-
lastAgentStep = step;
|
|
12710
|
-
for (const call of toolCalls) {
|
|
12711
|
-
if (call.tool_call_id) pendingToolSteps.set(call.tool_call_id, step);
|
|
12712
|
-
}
|
|
12713
|
-
}
|
|
12714
|
-
continue;
|
|
12715
|
-
}
|
|
12716
|
-
if (entry.type === "tool.execution_start") {
|
|
12717
|
-
const toolCall = toolCallFromExecutionStart(data);
|
|
12718
|
-
const step = {
|
|
12719
|
-
step_id: steps.length + 1,
|
|
12720
|
-
timestamp: entry.timestamp,
|
|
12721
|
-
source: "agent",
|
|
12722
|
-
message: `Executed ${toolCall.function_name} ${toolCall.tool_call_id}`.trim(),
|
|
12723
|
-
model_name: defaultModelName,
|
|
12724
|
-
tool_calls: [toolCall]
|
|
12725
|
-
};
|
|
12726
|
-
steps.push(step);
|
|
12727
|
-
lastAgentStep = step;
|
|
12728
|
-
if (toolCall.tool_call_id)
|
|
12729
|
-
pendingToolSteps.set(toolCall.tool_call_id, step);
|
|
12730
|
-
continue;
|
|
12731
|
-
}
|
|
12732
|
-
if (entry.type === "tool.execution_complete") {
|
|
12733
|
-
const callId = typeof data.toolCallId === "string" && data.toolCallId || typeof data.id === "string" && data.id || "";
|
|
12734
|
-
const target = pendingToolSteps.get(callId);
|
|
12735
|
-
const content = contentFromToolResult(data);
|
|
12736
|
-
if (target && content !== void 0) {
|
|
12737
|
-
const observation = target.observation ?? { results: [] };
|
|
12738
|
-
observation.results.push({
|
|
12739
|
-
source_call_id: callId || void 0,
|
|
12740
|
-
content
|
|
12741
|
-
});
|
|
12742
|
-
target.observation = observation;
|
|
12743
|
-
const extra = { ...target.extra ?? {} };
|
|
12744
|
-
extra.tool_success = data.success;
|
|
12745
|
-
target.extra = compactExtra2(extra);
|
|
12746
|
-
}
|
|
12747
|
-
if (callId) pendingToolSteps.delete(callId);
|
|
12748
|
-
continue;
|
|
12749
|
-
}
|
|
12750
|
-
if (entry.type === "assistant.usage") {
|
|
12751
|
-
if (typeof data.model === "string" && !defaultModelName) {
|
|
12752
|
-
defaultModelName = data.model;
|
|
12753
|
-
if (lastAgentStep && !lastAgentStep.model_name) {
|
|
12754
|
-
lastAgentStep.model_name = defaultModelName;
|
|
12755
|
-
}
|
|
12756
|
-
}
|
|
12757
|
-
const metrics = buildMetrics2(data);
|
|
12758
|
-
if (metrics && lastAgentStep && !lastAgentStep.metrics) {
|
|
12759
|
-
lastAgentStep.metrics = metrics;
|
|
12760
|
-
}
|
|
12761
|
-
}
|
|
12762
|
-
}
|
|
12763
|
-
if (steps.length === 0) return null;
|
|
12764
|
-
return {
|
|
12765
|
-
schema_version: ATIF_VERSION,
|
|
12766
|
-
session_id: sid,
|
|
12767
|
-
agent: {
|
|
12768
|
-
name: "github-copilot-chat",
|
|
12769
|
-
version: copilotVersion,
|
|
12770
|
-
model_name: defaultModelName,
|
|
12771
|
-
extra: compactExtra2({
|
|
12772
|
-
vscode_version: vscodeVersion,
|
|
12773
|
-
cwd
|
|
12774
|
-
})
|
|
12775
|
-
},
|
|
12776
|
-
steps,
|
|
12777
|
-
final_metrics: finalMetricsFromSteps(steps)
|
|
12778
|
-
};
|
|
12779
|
-
}
|
|
12780
|
-
|
|
12781
|
-
// src/normalizer/cursor.ts
|
|
12782
|
-
function convertCursorToTrajectory(jsonlContent, sessionId) {
|
|
12783
|
-
const lines = [];
|
|
12784
|
-
for (const line of jsonlContent.split("\n")) {
|
|
12785
|
-
const trimmed = line.trim();
|
|
12786
|
-
if (!trimmed) continue;
|
|
12787
|
-
try {
|
|
12788
|
-
lines.push(JSON.parse(trimmed));
|
|
12789
|
-
} catch {
|
|
12790
|
-
}
|
|
12791
|
-
}
|
|
12792
|
-
if (lines.length === 0) return null;
|
|
12793
|
-
const steps = [];
|
|
12794
|
-
let stepId = 1;
|
|
12795
|
-
for (const entry of lines) {
|
|
12796
|
-
const role = entry.role;
|
|
12797
|
-
if (!role) continue;
|
|
12798
|
-
let source;
|
|
12799
|
-
if (role === "assistant") source = "agent";
|
|
12800
|
-
else if (role === "user") source = "user";
|
|
12801
|
-
else source = "system";
|
|
12802
|
-
const contentParts = entry.message?.content;
|
|
12803
|
-
let text2 = "";
|
|
12804
|
-
if (Array.isArray(contentParts)) {
|
|
12805
|
-
const textParts = [];
|
|
12806
|
-
for (const part of contentParts) {
|
|
12807
|
-
if (typeof part === "object" && part !== null && typeof part.text === "string") {
|
|
12808
|
-
textParts.push(part.text);
|
|
12809
|
-
}
|
|
12810
|
-
}
|
|
12811
|
-
text2 = textParts.join("\n\n").trim();
|
|
12812
|
-
}
|
|
12813
|
-
if (!text2) continue;
|
|
12814
|
-
const step = {
|
|
12815
|
-
step_id: stepId,
|
|
12816
|
-
source,
|
|
12817
|
-
message: text2
|
|
12818
|
-
};
|
|
12819
|
-
steps.push(step);
|
|
12820
|
-
stepId++;
|
|
12821
|
-
}
|
|
12822
|
-
if (steps.length === 0) return null;
|
|
12823
|
-
const finalMetrics = {
|
|
12824
|
-
total_steps: steps.length
|
|
12825
|
-
};
|
|
12826
|
-
return {
|
|
12827
|
-
schema_version: "ATIF-v1.6",
|
|
12828
|
-
session_id: sessionId ?? "unknown",
|
|
12829
|
-
agent: {
|
|
12830
|
-
name: "cursor",
|
|
12831
|
-
version: "unknown"
|
|
12832
|
-
},
|
|
12833
|
-
steps,
|
|
12834
|
-
final_metrics: finalMetrics
|
|
12835
|
-
};
|
|
12836
|
-
}
|
|
12837
|
-
|
|
12838
|
-
// src/normalizer/opencode.ts
|
|
12839
|
-
function asObject3(value) {
|
|
12840
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12841
|
-
}
|
|
12842
|
-
function asArray(value) {
|
|
12843
|
-
return Array.isArray(value) ? value : [];
|
|
12844
|
-
}
|
|
12845
|
-
function asNumber2(value) {
|
|
12846
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12847
|
-
}
|
|
12848
|
-
function compactExtra3(extra) {
|
|
12849
|
-
const result = {};
|
|
12850
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
12851
|
-
if (value !== void 0 && value !== null) result[key] = value;
|
|
12852
|
-
}
|
|
12853
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
12854
|
-
}
|
|
12855
|
-
function parseJsonLines2(content) {
|
|
12856
|
-
const events = [];
|
|
12857
|
-
for (const line of content.split("\n")) {
|
|
12858
|
-
const trimmed = line.trim();
|
|
12859
|
-
if (!trimmed) continue;
|
|
12860
|
-
try {
|
|
12861
|
-
const parsed = JSON.parse(trimmed);
|
|
12862
|
-
const obj = asObject3(parsed);
|
|
12863
|
-
if (obj) events.push(obj);
|
|
12864
|
-
} catch {
|
|
12865
|
-
}
|
|
12866
|
-
}
|
|
12867
|
-
return events;
|
|
12868
|
-
}
|
|
12869
|
-
function timestampToIso(value) {
|
|
12870
|
-
const numeric = asNumber2(value);
|
|
12871
|
-
if (numeric === void 0) return void 0;
|
|
12872
|
-
const millis = numeric < 1e12 ? numeric * 1e3 : numeric;
|
|
12873
|
-
const date = new Date(millis);
|
|
12874
|
-
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
12875
|
-
}
|
|
12876
|
-
function timeFromObject(value) {
|
|
12877
|
-
const obj = asObject3(value);
|
|
12878
|
-
if (!obj) return void 0;
|
|
12879
|
-
return timestampToIso(obj.start ?? obj.created ?? obj.completed ?? obj.end);
|
|
12880
|
-
}
|
|
12881
|
-
function stringify2(value) {
|
|
12882
|
-
if (typeof value === "string") return value;
|
|
12024
|
+
async function deleteUploadState(repoRoot, tool, sessionId) {
|
|
12883
12025
|
try {
|
|
12884
|
-
|
|
12026
|
+
await fs12.promises.unlink(stateFileFor(repoRoot, tool, sessionId));
|
|
12885
12027
|
} catch {
|
|
12886
|
-
return String(value);
|
|
12887
|
-
}
|
|
12888
|
-
}
|
|
12889
|
-
function argsFromUnknown(value) {
|
|
12890
|
-
const obj = asObject3(value);
|
|
12891
|
-
if (obj) return obj;
|
|
12892
|
-
if (typeof value === "string") {
|
|
12893
|
-
try {
|
|
12894
|
-
const parsed = JSON.parse(value);
|
|
12895
|
-
const parsedObj = asObject3(parsed);
|
|
12896
|
-
if (parsedObj) return parsedObj;
|
|
12897
|
-
} catch {
|
|
12898
|
-
return value ? { input: value } : {};
|
|
12899
|
-
}
|
|
12900
|
-
}
|
|
12901
|
-
return value === void 0 || value === null ? {} : { value };
|
|
12902
|
-
}
|
|
12903
|
-
function modelNameFromInfo(info) {
|
|
12904
|
-
const model = asObject3(info.model);
|
|
12905
|
-
const modelID = typeof info.modelID === "string" && info.modelID || typeof model?.modelID === "string" && model.modelID || void 0;
|
|
12906
|
-
const providerID = typeof info.providerID === "string" && info.providerID || typeof model?.providerID === "string" && model.providerID || void 0;
|
|
12907
|
-
if (providerID && modelID) return `${providerID}/${modelID}`;
|
|
12908
|
-
return modelID;
|
|
12909
|
-
}
|
|
12910
|
-
function metricsFromTokens(tokens, cost) {
|
|
12911
|
-
const t = asObject3(tokens);
|
|
12912
|
-
if (!t) return void 0;
|
|
12913
|
-
const cache = asObject3(t.cache);
|
|
12914
|
-
const input = asNumber2(t.input) ?? 0;
|
|
12915
|
-
const output = asNumber2(t.output) ?? 0;
|
|
12916
|
-
const reasoning = asNumber2(t.reasoning) ?? 0;
|
|
12917
|
-
const cacheRead = asNumber2(cache?.read) ?? 0;
|
|
12918
|
-
const cacheWrite = asNumber2(cache?.write) ?? 0;
|
|
12919
|
-
const costUsd = asNumber2(cost);
|
|
12920
|
-
if (!input && !output && !cacheRead && !cacheWrite && !costUsd) {
|
|
12921
|
-
return void 0;
|
|
12922
|
-
}
|
|
12923
|
-
const extra = compactExtra3({
|
|
12924
|
-
reasoning_tokens: reasoning || void 0,
|
|
12925
|
-
cache_write_tokens: cacheWrite || void 0
|
|
12926
|
-
});
|
|
12927
|
-
return {
|
|
12928
|
-
prompt_tokens: input + cacheRead || void 0,
|
|
12929
|
-
completion_tokens: output || void 0,
|
|
12930
|
-
cached_tokens: cacheRead || void 0,
|
|
12931
|
-
cost_usd: costUsd || void 0,
|
|
12932
|
-
extra
|
|
12933
|
-
};
|
|
12934
|
-
}
|
|
12935
|
-
function metricsFromInfo(info) {
|
|
12936
|
-
return metricsFromTokens(info.tokens, info.cost);
|
|
12937
|
-
}
|
|
12938
|
-
function addFinalMetricTotals(totals, metrics) {
|
|
12939
|
-
if (!metrics) return;
|
|
12940
|
-
totals.total_prompt_tokens += metrics.prompt_tokens ?? 0;
|
|
12941
|
-
totals.total_completion_tokens += metrics.completion_tokens ?? 0;
|
|
12942
|
-
totals.total_cached_tokens += metrics.cached_tokens ?? 0;
|
|
12943
|
-
totals.total_cost_usd += metrics.cost_usd ?? 0;
|
|
12944
|
-
}
|
|
12945
|
-
function finalMetricsFromSteps2(steps) {
|
|
12946
|
-
const totals = {
|
|
12947
|
-
total_prompt_tokens: 0,
|
|
12948
|
-
total_completion_tokens: 0,
|
|
12949
|
-
total_cached_tokens: 0,
|
|
12950
|
-
total_cost_usd: 0,
|
|
12951
|
-
total_steps: steps.length,
|
|
12952
|
-
extra: {}
|
|
12953
|
-
};
|
|
12954
|
-
for (const step of steps) addFinalMetricTotals(totals, step.metrics);
|
|
12955
|
-
return {
|
|
12956
|
-
total_prompt_tokens: totals.total_prompt_tokens || void 0,
|
|
12957
|
-
total_completion_tokens: totals.total_completion_tokens || void 0,
|
|
12958
|
-
total_cached_tokens: totals.total_cached_tokens || void 0,
|
|
12959
|
-
total_cost_usd: totals.total_cost_usd || void 0,
|
|
12960
|
-
total_steps: steps.length
|
|
12961
|
-
};
|
|
12962
|
-
}
|
|
12963
|
-
function entryFromLine(line) {
|
|
12964
|
-
const info = asObject3(line.info);
|
|
12965
|
-
if (info) {
|
|
12966
|
-
return {
|
|
12967
|
-
info,
|
|
12968
|
-
parts: asArray(line.parts).flatMap((part) => {
|
|
12969
|
-
const obj = asObject3(part);
|
|
12970
|
-
return obj ? [obj] : [];
|
|
12971
|
-
})
|
|
12972
|
-
};
|
|
12973
|
-
}
|
|
12974
|
-
if (typeof line.role === "string" || typeof line.sessionID === "string") {
|
|
12975
|
-
return {
|
|
12976
|
-
info: line,
|
|
12977
|
-
parts: asArray(line.parts).flatMap((part) => {
|
|
12978
|
-
const obj = asObject3(part);
|
|
12979
|
-
return obj ? [obj] : [];
|
|
12980
|
-
})
|
|
12981
|
-
};
|
|
12982
|
-
}
|
|
12983
|
-
return null;
|
|
12984
|
-
}
|
|
12985
|
-
function entriesFromEventWrappers(lines) {
|
|
12986
|
-
const byMessage = /* @__PURE__ */ new Map();
|
|
12987
|
-
function getEntry(messageID, sessionID) {
|
|
12988
|
-
let entry = byMessage.get(messageID);
|
|
12989
|
-
if (!entry) {
|
|
12990
|
-
entry = { info: { id: messageID, sessionID }, parts: [] };
|
|
12991
|
-
byMessage.set(messageID, entry);
|
|
12992
|
-
}
|
|
12993
|
-
return entry;
|
|
12994
|
-
}
|
|
12995
|
-
for (const line of lines) {
|
|
12996
|
-
const type = line.type;
|
|
12997
|
-
const props = asObject3(line.properties) ?? line;
|
|
12998
|
-
if (type === "message.updated") {
|
|
12999
|
-
const info = asObject3(props.info);
|
|
13000
|
-
const id = typeof info?.id === "string" ? info.id : void 0;
|
|
13001
|
-
if (!info || !id) continue;
|
|
13002
|
-
const entry = getEntry(id, info.sessionID);
|
|
13003
|
-
entry.info = info;
|
|
13004
|
-
continue;
|
|
13005
|
-
}
|
|
13006
|
-
if (type === "message.part.updated") {
|
|
13007
|
-
const part = asObject3(props.part);
|
|
13008
|
-
const messageID = typeof part?.messageID === "string" && part.messageID || void 0;
|
|
13009
|
-
if (!part || !messageID) continue;
|
|
13010
|
-
const entry = getEntry(messageID, part.sessionID);
|
|
13011
|
-
const partID = typeof part.id === "string" ? part.id : void 0;
|
|
13012
|
-
const existingIndex = partID ? entry.parts.findIndex((p7) => p7.id === partID) : -1;
|
|
13013
|
-
if (existingIndex >= 0) entry.parts[existingIndex] = part;
|
|
13014
|
-
else entry.parts.push(part);
|
|
13015
|
-
}
|
|
13016
|
-
}
|
|
13017
|
-
return [...byMessage.values()];
|
|
13018
|
-
}
|
|
13019
|
-
function getSessionId(sessionId, exportInfo, entries) {
|
|
13020
|
-
if (sessionId) return sessionId;
|
|
13021
|
-
if (typeof exportInfo?.id === "string") return exportInfo.id;
|
|
13022
|
-
for (const entry of entries) {
|
|
13023
|
-
if (typeof entry.info.sessionID === "string") return entry.info.sessionID;
|
|
13024
|
-
}
|
|
13025
|
-
return "unknown";
|
|
13026
|
-
}
|
|
13027
|
-
function getAgentVersion(exportInfo) {
|
|
13028
|
-
return typeof exportInfo?.version === "string" && exportInfo.version || "unknown";
|
|
13029
|
-
}
|
|
13030
|
-
function sortEntries(entries) {
|
|
13031
|
-
return [...entries].sort((a, b) => {
|
|
13032
|
-
const at = asNumber2(asObject3(a.info.time)?.created) ?? 0;
|
|
13033
|
-
const bt = asNumber2(asObject3(b.info.time)?.created) ?? 0;
|
|
13034
|
-
return at - bt;
|
|
13035
|
-
});
|
|
13036
|
-
}
|
|
13037
|
-
function splitAssistantParts(parts) {
|
|
13038
|
-
let sawBoundary = false;
|
|
13039
|
-
const groups = [];
|
|
13040
|
-
let current = [];
|
|
13041
|
-
for (const part of parts) {
|
|
13042
|
-
const type = part.type;
|
|
13043
|
-
if (type === "step-start") {
|
|
13044
|
-
sawBoundary = true;
|
|
13045
|
-
if (current.length > 0) groups.push(current);
|
|
13046
|
-
current = [part];
|
|
13047
|
-
continue;
|
|
13048
|
-
}
|
|
13049
|
-
current.push(part);
|
|
13050
|
-
if (type === "step-finish") {
|
|
13051
|
-
sawBoundary = true;
|
|
13052
|
-
groups.push(current);
|
|
13053
|
-
current = [];
|
|
13054
|
-
}
|
|
13055
|
-
}
|
|
13056
|
-
if (current.length > 0) groups.push(current);
|
|
13057
|
-
return sawBoundary ? groups : [parts];
|
|
13058
|
-
}
|
|
13059
|
-
function textFromFilePart(part) {
|
|
13060
|
-
const filename = typeof part.filename === "string" ? part.filename : void 0;
|
|
13061
|
-
const url = typeof part.url === "string" ? part.url : void 0;
|
|
13062
|
-
const mime = typeof part.mime === "string" ? part.mime : void 0;
|
|
13063
|
-
const label = filename ?? url;
|
|
13064
|
-
if (!label) return void 0;
|
|
13065
|
-
return mime ? `[file:${mime}] ${label}` : `[file] ${label}`;
|
|
13066
|
-
}
|
|
13067
|
-
function buildUserStep(entry, stepId, defaultModelName) {
|
|
13068
|
-
const textParts = [];
|
|
13069
|
-
const extra = {};
|
|
13070
|
-
for (const part of entry.parts) {
|
|
13071
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
13072
|
-
if (part.text.trim()) textParts.push(part.text.trim());
|
|
13073
|
-
continue;
|
|
13074
|
-
}
|
|
13075
|
-
if (part.type === "file") {
|
|
13076
|
-
const fileText = textFromFilePart(part);
|
|
13077
|
-
if (fileText) textParts.push(fileText);
|
|
13078
|
-
}
|
|
13079
|
-
}
|
|
13080
|
-
const message = textParts.join("\n\n").trim();
|
|
13081
|
-
if (!message) return null;
|
|
13082
|
-
if (entry.info.agent) extra.agent = entry.info.agent;
|
|
13083
|
-
if (entry.info.tools) extra.tools = entry.info.tools;
|
|
13084
|
-
return {
|
|
13085
|
-
step_id: stepId,
|
|
13086
|
-
timestamp: timeFromObject(entry.info.time),
|
|
13087
|
-
source: "user",
|
|
13088
|
-
message,
|
|
13089
|
-
model_name: defaultModelName,
|
|
13090
|
-
extra: compactExtra3(extra)
|
|
13091
|
-
};
|
|
13092
|
-
}
|
|
13093
|
-
function buildAgentStep(parts, info, stepId, defaultModelName, fallbackMetrics) {
|
|
13094
|
-
const textParts = [];
|
|
13095
|
-
const reasoningParts = [];
|
|
13096
|
-
const toolCalls = [];
|
|
13097
|
-
const observationResults = [];
|
|
13098
|
-
const extra = {};
|
|
13099
|
-
let metrics;
|
|
13100
|
-
let timestamp = timeFromObject(info.time);
|
|
13101
|
-
for (const part of parts) {
|
|
13102
|
-
if (!timestamp) timestamp = timeFromObject(part.time);
|
|
13103
|
-
switch (part.type) {
|
|
13104
|
-
case "text":
|
|
13105
|
-
if (typeof part.text === "string" && part.text.trim()) {
|
|
13106
|
-
textParts.push(part.text.trim());
|
|
13107
|
-
}
|
|
13108
|
-
break;
|
|
13109
|
-
case "reasoning":
|
|
13110
|
-
if (typeof part.text === "string" && part.text.trim()) {
|
|
13111
|
-
reasoningParts.push(part.text.trim());
|
|
13112
|
-
}
|
|
13113
|
-
break;
|
|
13114
|
-
case "file": {
|
|
13115
|
-
const fileText = textFromFilePart(part);
|
|
13116
|
-
if (fileText) textParts.push(fileText);
|
|
13117
|
-
break;
|
|
13118
|
-
}
|
|
13119
|
-
case "tool": {
|
|
13120
|
-
const state = asObject3(part.state) ?? {};
|
|
13121
|
-
const callID = typeof part.callID === "string" && part.callID || typeof part.id === "string" && part.id || "";
|
|
13122
|
-
const toolName = typeof part.tool === "string" && part.tool || "tool";
|
|
13123
|
-
const input = argsFromUnknown(state.input);
|
|
13124
|
-
toolCalls.push({
|
|
13125
|
-
tool_call_id: callID,
|
|
13126
|
-
function_name: toolName,
|
|
13127
|
-
arguments: input
|
|
13128
|
-
});
|
|
13129
|
-
const output = state.output ?? state.error;
|
|
13130
|
-
if (output !== void 0 && output !== null) {
|
|
13131
|
-
observationResults.push({
|
|
13132
|
-
source_call_id: callID || void 0,
|
|
13133
|
-
content: stringify2(output)
|
|
13134
|
-
});
|
|
13135
|
-
}
|
|
13136
|
-
if (state.status) extra.status = state.status;
|
|
13137
|
-
if (state.metadata) extra.tool_metadata = state.metadata;
|
|
13138
|
-
if (part.metadata) extra.part_metadata = part.metadata;
|
|
13139
|
-
break;
|
|
13140
|
-
}
|
|
13141
|
-
case "step-finish":
|
|
13142
|
-
metrics = metricsFromTokens(part.tokens, part.cost) ?? metrics;
|
|
13143
|
-
if (part.reason) extra.finish_reason = part.reason;
|
|
13144
|
-
if (part.snapshot) extra.snapshot = part.snapshot;
|
|
13145
|
-
break;
|
|
13146
|
-
case "patch":
|
|
13147
|
-
extra.patches = [...asArray(extra.patches), part];
|
|
13148
|
-
break;
|
|
13149
|
-
case "agent":
|
|
13150
|
-
if (part.name) extra.agent = part.name;
|
|
13151
|
-
break;
|
|
13152
|
-
case "retry":
|
|
13153
|
-
extra.retry = part;
|
|
13154
|
-
break;
|
|
13155
|
-
}
|
|
13156
|
-
}
|
|
13157
|
-
metrics = metrics ?? fallbackMetrics;
|
|
13158
|
-
if (textParts.length === 0 && reasoningParts.length === 0 && toolCalls.length === 0 && !metrics) {
|
|
13159
|
-
return null;
|
|
13160
|
-
}
|
|
13161
|
-
const observation = observationResults.length > 0 ? { results: observationResults } : void 0;
|
|
13162
|
-
const step = {
|
|
13163
|
-
step_id: stepId,
|
|
13164
|
-
timestamp,
|
|
13165
|
-
source: "agent",
|
|
13166
|
-
message: textParts.length > 0 ? textParts.join("\n\n") : "(tool use)",
|
|
13167
|
-
model_name: modelNameFromInfo(info) ?? defaultModelName
|
|
13168
|
-
};
|
|
13169
|
-
if (reasoningParts.length > 0)
|
|
13170
|
-
step.reasoning_content = reasoningParts.join("\n\n");
|
|
13171
|
-
if (toolCalls.length > 0) step.tool_calls = toolCalls;
|
|
13172
|
-
if (observation) step.observation = observation;
|
|
13173
|
-
if (metrics) step.metrics = metrics;
|
|
13174
|
-
const compactedExtra = compactExtra3(extra);
|
|
13175
|
-
if (compactedExtra) step.extra = compactedExtra;
|
|
13176
|
-
return step;
|
|
13177
|
-
}
|
|
13178
|
-
function buildUnavailableAgentStep(info, stepId, defaultModelName) {
|
|
13179
|
-
const metrics = metricsFromInfo(info);
|
|
13180
|
-
if (!metrics && !modelNameFromInfo(info) && !info.error && !info.finish) {
|
|
13181
|
-
return null;
|
|
13182
|
-
}
|
|
13183
|
-
return {
|
|
13184
|
-
step_id: stepId,
|
|
13185
|
-
timestamp: timeFromObject(info.time),
|
|
13186
|
-
source: "agent",
|
|
13187
|
-
message: "(message unavailable)",
|
|
13188
|
-
model_name: modelNameFromInfo(info) ?? defaultModelName,
|
|
13189
|
-
metrics,
|
|
13190
|
-
extra: compactExtra3({
|
|
13191
|
-
content_unavailable: true,
|
|
13192
|
-
finish_reason: info.finish,
|
|
13193
|
-
error: info.error
|
|
13194
|
-
})
|
|
13195
|
-
};
|
|
13196
|
-
}
|
|
13197
|
-
function convertMessageEntriesToTrajectory(entries, sessionId, exportInfo) {
|
|
13198
|
-
if (entries.length === 0) return null;
|
|
13199
|
-
const orderedEntries = sortEntries(entries);
|
|
13200
|
-
const defaultModelName = orderedEntries.map((entry) => modelNameFromInfo(entry.info)).find((model) => typeof model === "string");
|
|
13201
|
-
const steps = [];
|
|
13202
|
-
for (const entry of orderedEntries) {
|
|
13203
|
-
const role = entry.info.role;
|
|
13204
|
-
if (role === "user") {
|
|
13205
|
-
const step = buildUserStep(entry, steps.length + 1, defaultModelName);
|
|
13206
|
-
if (step) steps.push(step);
|
|
13207
|
-
continue;
|
|
13208
|
-
}
|
|
13209
|
-
if (role !== "assistant") continue;
|
|
13210
|
-
if (entry.parts.length === 0) {
|
|
13211
|
-
const step = buildUnavailableAgentStep(
|
|
13212
|
-
entry.info,
|
|
13213
|
-
steps.length + 1,
|
|
13214
|
-
defaultModelName
|
|
13215
|
-
);
|
|
13216
|
-
if (step) steps.push(step);
|
|
13217
|
-
continue;
|
|
13218
|
-
}
|
|
13219
|
-
const groups = splitAssistantParts(entry.parts);
|
|
13220
|
-
const hasPartMetrics = groups.some(
|
|
13221
|
-
(group) => group.some((part) => part.type === "step-finish")
|
|
13222
|
-
);
|
|
13223
|
-
const fallbackMetrics = hasPartMetrics ? void 0 : metricsFromInfo(entry.info);
|
|
13224
|
-
for (let i = 0; i < groups.length; i++) {
|
|
13225
|
-
const step = buildAgentStep(
|
|
13226
|
-
groups[i],
|
|
13227
|
-
entry.info,
|
|
13228
|
-
steps.length + 1,
|
|
13229
|
-
defaultModelName,
|
|
13230
|
-
i === 0 ? fallbackMetrics : void 0
|
|
13231
|
-
);
|
|
13232
|
-
if (step) steps.push(step);
|
|
13233
|
-
}
|
|
13234
12028
|
}
|
|
13235
|
-
if (steps.length === 0) return null;
|
|
13236
|
-
return {
|
|
13237
|
-
schema_version: ATIF_VERSION,
|
|
13238
|
-
session_id: getSessionId(sessionId, exportInfo, orderedEntries),
|
|
13239
|
-
agent: {
|
|
13240
|
-
name: "opencode",
|
|
13241
|
-
version: getAgentVersion(exportInfo),
|
|
13242
|
-
model_name: defaultModelName
|
|
13243
|
-
},
|
|
13244
|
-
steps,
|
|
13245
|
-
final_metrics: finalMetricsFromSteps2(steps)
|
|
13246
|
-
};
|
|
13247
|
-
}
|
|
13248
|
-
function convertRunEventsToTrajectory(events, sessionId) {
|
|
13249
|
-
const session = sessionId ?? (events.map((event) => event.sessionID).find((sid) => typeof sid === "string") || "unknown");
|
|
13250
|
-
const turns = [];
|
|
13251
|
-
let current = null;
|
|
13252
|
-
for (const event of events) {
|
|
13253
|
-
const type = event.type;
|
|
13254
|
-
if (type === "step_start") {
|
|
13255
|
-
current = { parts: [], timestamp: event.timestamp };
|
|
13256
|
-
continue;
|
|
13257
|
-
}
|
|
13258
|
-
if (type === "step_finish") {
|
|
13259
|
-
if (current) {
|
|
13260
|
-
current.finish = asObject3(event.part) ?? {};
|
|
13261
|
-
turns.push(current);
|
|
13262
|
-
current = null;
|
|
13263
|
-
}
|
|
13264
|
-
continue;
|
|
13265
|
-
}
|
|
13266
|
-
if (current && (type === "text" || type === "reasoning" || type === "tool_use")) {
|
|
13267
|
-
const part = asObject3(event.part);
|
|
13268
|
-
if (part) current.parts.push(part);
|
|
13269
|
-
}
|
|
13270
|
-
}
|
|
13271
|
-
const steps = [];
|
|
13272
|
-
for (const turn of turns) {
|
|
13273
|
-
const parts = [...turn.parts];
|
|
13274
|
-
if (turn.finish) {
|
|
13275
|
-
parts.push({ ...turn.finish, type: "step-finish" });
|
|
13276
|
-
}
|
|
13277
|
-
const step = buildAgentStep(
|
|
13278
|
-
parts,
|
|
13279
|
-
{ time: { created: turn.timestamp } },
|
|
13280
|
-
steps.length + 1,
|
|
13281
|
-
void 0
|
|
13282
|
-
);
|
|
13283
|
-
if (step) steps.push(step);
|
|
13284
|
-
}
|
|
13285
|
-
if (steps.length === 0) return null;
|
|
13286
|
-
return {
|
|
13287
|
-
schema_version: ATIF_VERSION,
|
|
13288
|
-
session_id: session,
|
|
13289
|
-
agent: {
|
|
13290
|
-
name: "opencode",
|
|
13291
|
-
version: "unknown"
|
|
13292
|
-
},
|
|
13293
|
-
steps,
|
|
13294
|
-
final_metrics: finalMetricsFromSteps2(steps)
|
|
13295
|
-
};
|
|
13296
|
-
}
|
|
13297
|
-
function isRunEvent(lines) {
|
|
13298
|
-
return lines.some(
|
|
13299
|
-
(line) => ["step_start", "step_finish", "text", "reasoning", "tool_use"].includes(
|
|
13300
|
-
String(line.type ?? "")
|
|
13301
|
-
)
|
|
13302
|
-
);
|
|
13303
|
-
}
|
|
13304
|
-
function convertOpenCodeToTrajectory(content, sessionId) {
|
|
13305
|
-
const trimmed = content.trim();
|
|
13306
|
-
if (!trimmed) return null;
|
|
13307
12029
|
try {
|
|
13308
|
-
|
|
13309
|
-
const parsedObj = asObject3(parsed);
|
|
13310
|
-
const messages = asArray(parsedObj?.messages);
|
|
13311
|
-
if (parsedObj && messages.length > 0) {
|
|
13312
|
-
const entries2 = messages.flatMap((message) => {
|
|
13313
|
-
const entry = entryFromLine(asObject3(message) ?? {});
|
|
13314
|
-
return entry ? [entry] : [];
|
|
13315
|
-
});
|
|
13316
|
-
return convertMessageEntriesToTrajectory(
|
|
13317
|
-
entries2,
|
|
13318
|
-
sessionId,
|
|
13319
|
-
asObject3(parsedObj.info)
|
|
13320
|
-
);
|
|
13321
|
-
}
|
|
12030
|
+
await fs12.promises.unlink(cursorFileFor(repoRoot, tool, sessionId));
|
|
13322
12031
|
} catch {
|
|
13323
12032
|
}
|
|
13324
|
-
const lines = parseJsonLines2(content);
|
|
13325
|
-
if (lines.length === 0) return null;
|
|
13326
|
-
if (isRunEvent(lines)) return convertRunEventsToTrajectory(lines, sessionId);
|
|
13327
|
-
const wrappedEntries = entriesFromEventWrappers(lines);
|
|
13328
|
-
const entries = wrappedEntries.length > 0 ? wrappedEntries : lines.flatMap((line) => {
|
|
13329
|
-
const entry = entryFromLine(line);
|
|
13330
|
-
return entry ? [entry] : [];
|
|
13331
|
-
});
|
|
13332
|
-
return convertMessageEntriesToTrajectory(entries, sessionId);
|
|
13333
|
-
}
|
|
13334
|
-
|
|
13335
|
-
// src/normalizer/index.ts
|
|
13336
|
-
function normalizeContent(sourceName, content, sessionId) {
|
|
13337
|
-
switch (sourceName) {
|
|
13338
|
-
case "claude":
|
|
13339
|
-
return convertClaudeToTrajectory(content, sessionId);
|
|
13340
|
-
case "codex":
|
|
13341
|
-
return convertCodexToTrajectory(content, sessionId);
|
|
13342
|
-
case "cursor":
|
|
13343
|
-
return convertCursorToTrajectory(content, sessionId);
|
|
13344
|
-
case "opencode":
|
|
13345
|
-
return convertOpenCodeToTrajectory(content, sessionId);
|
|
13346
|
-
case "copilot-chat":
|
|
13347
|
-
return convertCopilotChatToTrajectory(content, sessionId);
|
|
13348
|
-
default:
|
|
13349
|
-
return null;
|
|
13350
|
-
}
|
|
13351
12033
|
}
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
async process(group) {
|
|
13355
|
-
const newFiles = [];
|
|
13356
|
-
for (const file of group.files) {
|
|
13357
|
-
newFiles.push(file);
|
|
13358
|
-
if (!file.absolutePath.endsWith(".jsonl")) continue;
|
|
13359
|
-
if (!["claude", "codex", "cursor", "opencode", "copilot-chat"].includes(
|
|
13360
|
-
file.sourceName
|
|
13361
|
-
))
|
|
13362
|
-
continue;
|
|
13363
|
-
const sessionId = file.metadata?.sessionId ?? (file.sourceName === "codex" ? void 0 : path13.basename(file.absolutePath, ".jsonl"));
|
|
13364
|
-
try {
|
|
13365
|
-
const content = file.content ? file.content.toString("utf-8") : null;
|
|
13366
|
-
if (!content) continue;
|
|
13367
|
-
const trajectory = normalizeContent(
|
|
13368
|
-
file.sourceName,
|
|
13369
|
-
content,
|
|
13370
|
-
sessionId
|
|
13371
|
-
);
|
|
13372
|
-
if (!trajectory) {
|
|
13373
|
-
appendLog(
|
|
13374
|
-
"warn",
|
|
13375
|
-
`normalize: produced no ATIF for source=${file.sourceName} session=${sessionId ?? "<unknown>"} file=${file.absolutePath}`
|
|
13376
|
-
);
|
|
13377
|
-
continue;
|
|
13378
|
-
}
|
|
13379
|
-
const json = JSON.stringify(
|
|
13380
|
-
excludeNone(trajectory),
|
|
13381
|
-
null,
|
|
13382
|
-
2
|
|
13383
|
-
);
|
|
13384
|
-
const atifPath = file.absolutePath.replace(/\.jsonl$/, ".atif.json");
|
|
13385
|
-
newFiles.push({
|
|
13386
|
-
sourceName: file.sourceName,
|
|
13387
|
-
absolutePath: atifPath,
|
|
13388
|
-
repoPath: file.repoPath,
|
|
13389
|
-
metadata: { ...file.metadata, isAtif: true },
|
|
13390
|
-
content: Buffer.from(json, "utf-8")
|
|
13391
|
-
});
|
|
13392
|
-
} catch (err) {
|
|
13393
|
-
appendLog(
|
|
13394
|
-
"warn",
|
|
13395
|
-
`normalize: skipped ATIF for source=${file.sourceName} session=${sessionId ?? "<unknown>"} file=${file.absolutePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
13396
|
-
);
|
|
13397
|
-
}
|
|
13398
|
-
}
|
|
13399
|
-
return { ...group, files: newFiles };
|
|
13400
|
-
}
|
|
13401
|
-
};
|
|
13402
|
-
|
|
13403
|
-
// src/upload-state.ts
|
|
13404
|
-
import crypto2 from "crypto";
|
|
13405
|
-
import fs10 from "fs";
|
|
13406
|
-
import os5 from "os";
|
|
13407
|
-
import path14 from "path";
|
|
13408
|
-
var CURRENT_SCHEMA_VERSION2 = 1;
|
|
13409
|
-
var DEFAULT_STATE_DIR = path14.join(
|
|
13410
|
-
os5.homedir(),
|
|
13411
|
-
".hillclimb",
|
|
13412
|
-
"agent-uploads"
|
|
13413
|
-
);
|
|
13414
|
-
var DEFAULT_LOCK_WAIT_MS = 5 * 60 * 1e3;
|
|
13415
|
-
var DEFAULT_LOCK_RETRY_DELAY_MS = 500;
|
|
13416
|
-
var DEFAULT_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
13417
|
-
function stateDir2() {
|
|
13418
|
-
return process.env.HILLCLIMB_UPLOAD_STATE_DIR ?? DEFAULT_STATE_DIR;
|
|
13419
|
-
}
|
|
13420
|
-
function readPositiveEnvMs(name, fallback) {
|
|
13421
|
-
const raw = process.env[name];
|
|
13422
|
-
if (raw === void 0) return fallback;
|
|
13423
|
-
const n = Number(raw);
|
|
13424
|
-
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
13425
|
-
}
|
|
13426
|
-
function stateTtlMs() {
|
|
13427
|
-
return readPositiveEnvMs(
|
|
13428
|
-
"HILLCLIMB_UPLOAD_STATE_TTL_MS",
|
|
13429
|
-
DEFAULT_STATE_TTL_MS
|
|
13430
|
-
);
|
|
13431
|
-
}
|
|
13432
|
-
function lockRetryDelayMs() {
|
|
13433
|
-
return Math.max(
|
|
13434
|
-
1,
|
|
13435
|
-
readPositiveEnvMs(
|
|
13436
|
-
"HILLCLIMB_UPLOAD_LOCK_RETRY_DELAY_MS",
|
|
13437
|
-
DEFAULT_LOCK_RETRY_DELAY_MS
|
|
13438
|
-
)
|
|
13439
|
-
);
|
|
12034
|
+
function cursorFileFor(repoRoot, tool, sessionId) {
|
|
12035
|
+
return `${stateFileFor(repoRoot, tool, sessionId)}.cursor.json`;
|
|
13440
12036
|
}
|
|
13441
|
-
function
|
|
13442
|
-
return Math.max(
|
|
13443
|
-
1,
|
|
13444
|
-
Math.ceil(
|
|
13445
|
-
readPositiveEnvMs("HILLCLIMB_UPLOAD_LOCK_WAIT_MS", DEFAULT_LOCK_WAIT_MS) / lockRetryDelayMs()
|
|
13446
|
-
)
|
|
13447
|
-
);
|
|
13448
|
-
}
|
|
13449
|
-
function stateFileFor(repoRoot, tool, sessionId) {
|
|
13450
|
-
const hash = crypto2.createHash("sha256").update(`${path14.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex").slice(0, 16);
|
|
13451
|
-
return path14.join(stateDir2(), `${hash}.json`);
|
|
13452
|
-
}
|
|
13453
|
-
function lockFileFor(repoRoot, tool, sessionId) {
|
|
13454
|
-
return `${stateFileFor(repoRoot, tool, sessionId)}.lock`;
|
|
13455
|
-
}
|
|
13456
|
-
async function readUploadState(repoRoot, tool, sessionId) {
|
|
12037
|
+
async function readCursorState(repoRoot, tool, sessionId) {
|
|
13457
12038
|
try {
|
|
13458
|
-
const raw = await
|
|
13459
|
-
|
|
12039
|
+
const raw = await fs12.promises.readFile(
|
|
12040
|
+
cursorFileFor(repoRoot, tool, sessionId),
|
|
13460
12041
|
"utf-8"
|
|
13461
12042
|
);
|
|
13462
12043
|
const parsed = JSON.parse(raw);
|
|
13463
|
-
if (parsed.
|
|
12044
|
+
if (typeof parsed.contributionId !== "string" || typeof parsed.epoch !== "number" || typeof parsed.turnCount !== "number" || typeof parsed.rawByteOffset !== "number" || typeof parsed.rawPrefixSha256 !== "string" || typeof parsed.snapshotUploaded !== "boolean") {
|
|
12045
|
+
return null;
|
|
12046
|
+
}
|
|
13464
12047
|
return parsed;
|
|
13465
12048
|
} catch {
|
|
13466
12049
|
return null;
|
|
13467
12050
|
}
|
|
13468
12051
|
}
|
|
13469
|
-
async function
|
|
13470
|
-
const file =
|
|
13471
|
-
await
|
|
12052
|
+
async function writeCursorState(repoRoot, tool, sessionId, cursor) {
|
|
12053
|
+
const file = cursorFileFor(repoRoot, tool, sessionId);
|
|
12054
|
+
await fs12.promises.mkdir(stateDir2(), { recursive: true, mode: 448 });
|
|
13472
12055
|
const tmp = `${file}.tmp`;
|
|
13473
|
-
await
|
|
12056
|
+
await fs12.promises.writeFile(tmp, JSON.stringify(cursor, null, 2), {
|
|
13474
12057
|
mode: 384
|
|
13475
12058
|
});
|
|
13476
|
-
await
|
|
13477
|
-
}
|
|
13478
|
-
async function deleteUploadState(repoRoot, tool, sessionId) {
|
|
13479
|
-
try {
|
|
13480
|
-
await fs10.promises.unlink(stateFileFor(repoRoot, tool, sessionId));
|
|
13481
|
-
} catch {
|
|
13482
|
-
}
|
|
12059
|
+
await fs12.promises.rename(tmp, file);
|
|
13483
12060
|
}
|
|
13484
12061
|
async function acquireLock2(repoRoot, tool, sessionId, retries = lockRetries(), delayMs = lockRetryDelayMs()) {
|
|
13485
12062
|
const lockPath = lockFileFor(repoRoot, tool, sessionId);
|
|
13486
|
-
await
|
|
12063
|
+
await fs12.promises.mkdir(stateDir2(), { recursive: true, mode: 448 });
|
|
13487
12064
|
for (let i = 0; i < retries; i++) {
|
|
13488
12065
|
try {
|
|
13489
|
-
const fd = await
|
|
12066
|
+
const fd = await fs12.promises.open(
|
|
13490
12067
|
lockPath,
|
|
13491
|
-
|
|
12068
|
+
fs12.constants.O_CREAT | fs12.constants.O_EXCL | fs12.constants.O_WRONLY
|
|
13492
12069
|
);
|
|
13493
12070
|
try {
|
|
13494
12071
|
await fd.write(String(process.pid));
|
|
@@ -13511,7 +12088,7 @@ async function acquireLock2(repoRoot, tool, sessionId, retries = lockRetries(),
|
|
|
13511
12088
|
}
|
|
13512
12089
|
async function releaseLock2(repoRoot, tool, sessionId) {
|
|
13513
12090
|
try {
|
|
13514
|
-
await
|
|
12091
|
+
await fs12.promises.unlink(lockFileFor(repoRoot, tool, sessionId));
|
|
13515
12092
|
} catch {
|
|
13516
12093
|
}
|
|
13517
12094
|
}
|
|
@@ -13523,20 +12100,43 @@ async function withUploadLock(repoRoot, tool, sessionId, fn) {
|
|
|
13523
12100
|
await releaseLock2(repoRoot, tool, sessionId);
|
|
13524
12101
|
}
|
|
13525
12102
|
}
|
|
12103
|
+
function isProcessAlive(pid) {
|
|
12104
|
+
try {
|
|
12105
|
+
process.kill(pid, 0);
|
|
12106
|
+
return true;
|
|
12107
|
+
} catch (err) {
|
|
12108
|
+
return err.code === "EPERM";
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
13526
12111
|
async function sweepStaleUploadStates(ttlMs = stateTtlMs(), now = Date.now()) {
|
|
13527
12112
|
let entries;
|
|
13528
12113
|
try {
|
|
13529
|
-
entries = await
|
|
12114
|
+
entries = await fs12.promises.readdir(stateDir2(), { withFileTypes: true });
|
|
13530
12115
|
} catch {
|
|
13531
12116
|
return;
|
|
13532
12117
|
}
|
|
13533
12118
|
for (const entry of entries) {
|
|
13534
12119
|
if (!entry.isFile()) continue;
|
|
13535
|
-
const file =
|
|
12120
|
+
const file = path13.join(stateDir2(), entry.name);
|
|
13536
12121
|
try {
|
|
13537
|
-
|
|
12122
|
+
if (entry.name.endsWith(".lock")) {
|
|
12123
|
+
const raw = await fs12.promises.readFile(file, "utf-8").catch(() => "");
|
|
12124
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
12125
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
12126
|
+
if (!isProcessAlive(pid)) {
|
|
12127
|
+
await fs12.promises.unlink(file);
|
|
12128
|
+
}
|
|
12129
|
+
} else {
|
|
12130
|
+
const st2 = await fs12.promises.stat(file);
|
|
12131
|
+
if (now - st2.mtimeMs > STALE_LOCK_TTL_MS) {
|
|
12132
|
+
await fs12.promises.unlink(file);
|
|
12133
|
+
}
|
|
12134
|
+
}
|
|
12135
|
+
continue;
|
|
12136
|
+
}
|
|
12137
|
+
const st = await fs12.promises.stat(file);
|
|
13538
12138
|
if (now - st.mtimeMs > ttlMs) {
|
|
13539
|
-
await
|
|
12139
|
+
await fs12.promises.unlink(file);
|
|
13540
12140
|
}
|
|
13541
12141
|
} catch {
|
|
13542
12142
|
}
|
|
@@ -13552,20 +12152,17 @@ async function readStdin() {
|
|
|
13552
12152
|
}
|
|
13553
12153
|
return Buffer.concat(chunks).toString("utf-8");
|
|
13554
12154
|
}
|
|
13555
|
-
function sanitize2(value) {
|
|
13556
|
-
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
13557
|
-
}
|
|
13558
12155
|
function formatEpochSeconds2(date) {
|
|
13559
12156
|
return String(Math.floor(date.getTime() / 1e3));
|
|
13560
12157
|
}
|
|
13561
12158
|
function newFlowId() {
|
|
13562
|
-
return
|
|
12159
|
+
return crypto4.randomBytes(3).toString("hex");
|
|
13563
12160
|
}
|
|
13564
12161
|
function lineHasAssistant(line) {
|
|
13565
12162
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
13566
12163
|
}
|
|
13567
12164
|
async function hasAssistantMessage(transcriptPath) {
|
|
13568
|
-
const stream =
|
|
12165
|
+
const stream = fs13.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
13569
12166
|
let buffer = "";
|
|
13570
12167
|
try {
|
|
13571
12168
|
for await (const chunk of stream) {
|
|
@@ -13587,15 +12184,6 @@ async function hasAssistantMessage(transcriptPath) {
|
|
|
13587
12184
|
}
|
|
13588
12185
|
return false;
|
|
13589
12186
|
}
|
|
13590
|
-
function hashFileSha256(file) {
|
|
13591
|
-
return new Promise((resolve, reject) => {
|
|
13592
|
-
const hash = crypto3.createHash("sha256");
|
|
13593
|
-
const stream = fs11.createReadStream(file);
|
|
13594
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
13595
|
-
stream.on("error", reject);
|
|
13596
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
13597
|
-
});
|
|
13598
|
-
}
|
|
13599
12187
|
async function resolveSourceTool(payload, repoRoot) {
|
|
13600
12188
|
const inferred = inferSourceToolFromPayload(payload);
|
|
13601
12189
|
if (inferred) return inferred;
|
|
@@ -13636,7 +12224,7 @@ function summarizePayload(payload) {
|
|
|
13636
12224
|
});
|
|
13637
12225
|
}
|
|
13638
12226
|
async function codexSessionIdFromFile(filePath) {
|
|
13639
|
-
const stream =
|
|
12227
|
+
const stream = fs13.createReadStream(filePath, { encoding: "utf-8" });
|
|
13640
12228
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
13641
12229
|
try {
|
|
13642
12230
|
for await (const line of rl) {
|
|
@@ -13656,17 +12244,17 @@ async function codexSessionIdFromFile(filePath) {
|
|
|
13656
12244
|
return null;
|
|
13657
12245
|
}
|
|
13658
12246
|
async function findCodexTranscriptPath(sessionId) {
|
|
13659
|
-
const sessionsDir = process.env.HILLCLIMB_CODEX_SESSIONS_DIR ??
|
|
12247
|
+
const sessionsDir = process.env.HILLCLIMB_CODEX_SESSIONS_DIR ?? path14.join(os6.homedir(), ".codex", "sessions");
|
|
13660
12248
|
const candidates = [];
|
|
13661
12249
|
async function walk(dir) {
|
|
13662
12250
|
let entries;
|
|
13663
12251
|
try {
|
|
13664
|
-
entries = await
|
|
12252
|
+
entries = await fs13.promises.readdir(dir, { withFileTypes: true });
|
|
13665
12253
|
} catch {
|
|
13666
12254
|
return;
|
|
13667
12255
|
}
|
|
13668
12256
|
for (const entry of entries) {
|
|
13669
|
-
const full =
|
|
12257
|
+
const full = path14.join(dir, entry.name);
|
|
13670
12258
|
if (entry.isDirectory()) {
|
|
13671
12259
|
await walk(full);
|
|
13672
12260
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(sessionId)) {
|
|
@@ -13706,7 +12294,7 @@ function resolveCursorTranscriptPath(payload) {
|
|
|
13706
12294
|
const workspace = payload.workspace_roots?.[0];
|
|
13707
12295
|
if (!id || !workspace) return void 0;
|
|
13708
12296
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
13709
|
-
return
|
|
12297
|
+
return path14.join(
|
|
13710
12298
|
os6.homedir(),
|
|
13711
12299
|
".cursor",
|
|
13712
12300
|
"projects",
|
|
@@ -13735,6 +12323,7 @@ async function runUploadInner(payload) {
|
|
|
13735
12323
|
const sessionId = resolveHookSessionId(payload);
|
|
13736
12324
|
const cwd = resolveHookCwd(payload);
|
|
13737
12325
|
const eventKind = classifyHookEvent(payload.hook_event_name);
|
|
12326
|
+
const recordedAt = Date.now();
|
|
13738
12327
|
if (!sessionId || !cwd) {
|
|
13739
12328
|
appendLog(
|
|
13740
12329
|
"warn",
|
|
@@ -13756,101 +12345,221 @@ async function runUploadInner(payload) {
|
|
|
13756
12345
|
payload.tool = "unknown";
|
|
13757
12346
|
return false;
|
|
13758
12347
|
}
|
|
13759
|
-
payload.tool = sourceTool;
|
|
13760
|
-
appendLog(
|
|
13761
|
-
"info",
|
|
13762
|
-
`[${sessionId}] payload parsed (tool=${sourceTool}, cwd=${cwd}, event=${eventKind ?? "?"})`
|
|
13763
|
-
);
|
|
13764
|
-
await selfHealHook(repoRoot, sourceTool);
|
|
13765
|
-
const transcriptPath = await resolveTranscriptPath(
|
|
13766
|
-
payload,
|
|
13767
|
-
sourceTool,
|
|
13768
|
-
sessionId
|
|
13769
|
-
);
|
|
13770
|
-
if (!transcriptPath) {
|
|
12348
|
+
payload.tool = sourceTool;
|
|
12349
|
+
appendLog(
|
|
12350
|
+
"info",
|
|
12351
|
+
`[${sessionId}] payload parsed (tool=${sourceTool}, cwd=${cwd}, event=${eventKind ?? "?"})`
|
|
12352
|
+
);
|
|
12353
|
+
await selfHealHook(repoRoot, sourceTool);
|
|
12354
|
+
const transcriptPath = await resolveTranscriptPath(
|
|
12355
|
+
payload,
|
|
12356
|
+
sourceTool,
|
|
12357
|
+
sessionId
|
|
12358
|
+
);
|
|
12359
|
+
if (!transcriptPath) {
|
|
12360
|
+
appendLog(
|
|
12361
|
+
"warn",
|
|
12362
|
+
`Skipping upload: missing required hook fields (session_id=true, transcript_path=false, cwd=true)`
|
|
12363
|
+
);
|
|
12364
|
+
return false;
|
|
12365
|
+
}
|
|
12366
|
+
const transcriptResolved = path14.resolve(transcriptPath);
|
|
12367
|
+
try {
|
|
12368
|
+
const stat = await fs13.promises.stat(transcriptResolved);
|
|
12369
|
+
if (!stat.isFile()) {
|
|
12370
|
+
appendLog(
|
|
12371
|
+
"warn",
|
|
12372
|
+
`Skipping session ${sessionId}: transcript_path is not a file: ${transcriptResolved}`
|
|
12373
|
+
);
|
|
12374
|
+
return false;
|
|
12375
|
+
}
|
|
12376
|
+
} catch (err) {
|
|
12377
|
+
appendLog(
|
|
12378
|
+
"warn",
|
|
12379
|
+
`Skipping session ${sessionId}: transcript_path not readable (${transcriptResolved}): ${err instanceof Error ? err.message : String(err)}`
|
|
12380
|
+
);
|
|
12381
|
+
return false;
|
|
12382
|
+
}
|
|
12383
|
+
if (!await hasAssistantMessage(transcriptResolved)) {
|
|
12384
|
+
appendLog(
|
|
12385
|
+
"info",
|
|
12386
|
+
`Skipping session ${sessionId}: transcript contains no assistant messages (nothing to upload).`
|
|
12387
|
+
);
|
|
12388
|
+
return false;
|
|
12389
|
+
}
|
|
12390
|
+
return await uploadSession({
|
|
12391
|
+
sessionId,
|
|
12392
|
+
transcriptPath: transcriptResolved,
|
|
12393
|
+
repoRoot,
|
|
12394
|
+
config,
|
|
12395
|
+
sourceTool,
|
|
12396
|
+
eventKind,
|
|
12397
|
+
recordedAt
|
|
12398
|
+
});
|
|
12399
|
+
}
|
|
12400
|
+
var AGENT_MAX_UPLOAD_BYTES = 1024 * 1024 * 1024;
|
|
12401
|
+
var CLI_VERSION = "0.7.0";
|
|
12402
|
+
function makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, content) {
|
|
12403
|
+
const sourceFile = {
|
|
12404
|
+
sourceName: sourceTool,
|
|
12405
|
+
absolutePath: transcriptPath,
|
|
12406
|
+
repoPath: repoRoot,
|
|
12407
|
+
content
|
|
12408
|
+
};
|
|
12409
|
+
return {
|
|
12410
|
+
repoPath: repoRoot,
|
|
12411
|
+
label: path14.basename(repoRoot),
|
|
12412
|
+
files: [sourceFile],
|
|
12413
|
+
sourceNames: [sourceTool],
|
|
12414
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
12415
|
+
};
|
|
12416
|
+
}
|
|
12417
|
+
async function buildRedactChain(repoRoot) {
|
|
12418
|
+
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
12419
|
+
const envFilePaths = envFileNames.map((n) => path14.join(repoRoot, n));
|
|
12420
|
+
const secretResult = await collectSecrets(repoRoot, envFilePaths, []);
|
|
12421
|
+
const chain = [];
|
|
12422
|
+
if (secretResult.values.size > 0) {
|
|
12423
|
+
chain.push(new RedactMiddleware(secretResult.values));
|
|
12424
|
+
}
|
|
12425
|
+
chain.push(new PatternRedactMiddleware());
|
|
12426
|
+
return chain;
|
|
12427
|
+
}
|
|
12428
|
+
async function redactTail(tail, repoRoot, sourceTool, transcriptPath, redactChain) {
|
|
12429
|
+
let group = makeTranscriptGroup(repoRoot, sourceTool, transcriptPath, tail);
|
|
12430
|
+
for (const mw of redactChain) {
|
|
12431
|
+
group = await mw.process(group);
|
|
12432
|
+
}
|
|
12433
|
+
const file = group.files.find((f) => f.absolutePath === transcriptPath);
|
|
12434
|
+
if (!file?.content) {
|
|
12435
|
+
throw new Error("redacted tail missing from middleware output");
|
|
12436
|
+
}
|
|
12437
|
+
return file.content;
|
|
12438
|
+
}
|
|
12439
|
+
async function handleUploadFailure(err, sessionId, repoRoot, sourceTool) {
|
|
12440
|
+
if (err instanceof UploadTooLargeError) {
|
|
12441
|
+
appendLog(
|
|
12442
|
+
"error",
|
|
12443
|
+
`Session ${sessionId} upload skipped: ${err.message}. The transcript snapshot exceeds the platform limit; later turns will keep failing until the session is split or the limit raised.`
|
|
12444
|
+
);
|
|
12445
|
+
return false;
|
|
12446
|
+
}
|
|
12447
|
+
if (err instanceof PlatformError && err.status === 401) {
|
|
13771
12448
|
appendLog(
|
|
13772
|
-
"
|
|
13773
|
-
`
|
|
12449
|
+
"error",
|
|
12450
|
+
`Session ${sessionId} upload failed: authentication expired. Re-run \`npx hillclimb\` in ${repoRoot}.`
|
|
13774
12451
|
);
|
|
13775
12452
|
return false;
|
|
13776
12453
|
}
|
|
13777
|
-
|
|
13778
|
-
try {
|
|
13779
|
-
const stat = await fs11.promises.stat(transcriptResolved);
|
|
13780
|
-
if (!stat.isFile()) {
|
|
13781
|
-
appendLog(
|
|
13782
|
-
"warn",
|
|
13783
|
-
`Skipping session ${sessionId}: transcript_path is not a file: ${transcriptResolved}`
|
|
13784
|
-
);
|
|
13785
|
-
return false;
|
|
13786
|
-
}
|
|
13787
|
-
} catch (err) {
|
|
12454
|
+
if (err instanceof PlatformError && err.status === 400 && /too big|sizebytes/i.test(err.message)) {
|
|
13788
12455
|
appendLog(
|
|
13789
|
-
"
|
|
13790
|
-
`
|
|
12456
|
+
"error",
|
|
12457
|
+
`Session ${sessionId} upload skipped: the platform rejected the declared size (${err.message}). The server's upload limit is below this CLI's; the upload will keep failing until the platform limit is raised.`
|
|
13791
12458
|
);
|
|
13792
12459
|
return false;
|
|
13793
12460
|
}
|
|
13794
|
-
if (
|
|
12461
|
+
if (err instanceof PlatformError && err.status === 404 && err.code === "CONTRIBUTION_NOT_FOUND") {
|
|
12462
|
+
await deleteUploadState(repoRoot, sourceTool, sessionId);
|
|
13795
12463
|
appendLog(
|
|
13796
|
-
"
|
|
13797
|
-
`
|
|
12464
|
+
"warn",
|
|
12465
|
+
`Session ${sessionId} upload failed: contribution deleted server-side; local state cleared so the next turn re-creates it.`
|
|
13798
12466
|
);
|
|
13799
12467
|
return false;
|
|
13800
12468
|
}
|
|
13801
|
-
|
|
12469
|
+
appendLog(
|
|
12470
|
+
"error",
|
|
12471
|
+
`Session ${sessionId} upload failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12472
|
+
);
|
|
12473
|
+
return false;
|
|
12474
|
+
}
|
|
12475
|
+
async function uploadSession(args) {
|
|
12476
|
+
const {
|
|
13802
12477
|
sessionId,
|
|
13803
|
-
transcriptPath
|
|
12478
|
+
transcriptPath,
|
|
13804
12479
|
repoRoot,
|
|
13805
12480
|
config,
|
|
13806
12481
|
sourceTool,
|
|
13807
|
-
eventKind
|
|
13808
|
-
|
|
13809
|
-
}
|
|
13810
|
-
async function uploadSession(args) {
|
|
13811
|
-
const { sessionId, transcriptPath, repoRoot, config, sourceTool, eventKind } = args;
|
|
12482
|
+
eventKind,
|
|
12483
|
+
recordedAt
|
|
12484
|
+
} = args;
|
|
13812
12485
|
const isSessionEnd = eventKind === "sessionEnd";
|
|
12486
|
+
const eventLabel = isSessionEnd ? "SessionEnd" : "Stop";
|
|
13813
12487
|
return await withUploadLock(repoRoot, sourceTool, sessionId, async () => {
|
|
13814
12488
|
const prior = await readUploadState(repoRoot, sourceTool, sessionId);
|
|
13815
|
-
let
|
|
13816
|
-
let
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
12489
|
+
let cursor = null;
|
|
12490
|
+
let adoptedLegacy = false;
|
|
12491
|
+
let restored = null;
|
|
12492
|
+
if (prior?.contributionId && prior.snapshotUploaded && prior.rawByteOffset !== void 0 && prior.rawPrefixSha256 !== void 0) {
|
|
12493
|
+
cursor = {
|
|
12494
|
+
rawByteOffset: prior.rawByteOffset,
|
|
12495
|
+
rawPrefixSha256: prior.rawPrefixSha256
|
|
12496
|
+
};
|
|
12497
|
+
} else if (prior?.contributionId && prior.rawByteOffset === void 0) {
|
|
12498
|
+
const sidecar = await readCursorState(repoRoot, sourceTool, sessionId);
|
|
12499
|
+
if (sidecar && sidecar.contributionId !== prior.contributionId) {
|
|
12500
|
+
appendLog(
|
|
12501
|
+
"info",
|
|
12502
|
+
`[${sessionId}] ignoring sidecar cursor for stale contribution ${sidecar.contributionId} (state has ${prior.contributionId})`
|
|
12503
|
+
);
|
|
12504
|
+
}
|
|
12505
|
+
if (sidecar && sidecar.contributionId === prior.contributionId && sidecar.snapshotUploaded) {
|
|
12506
|
+
cursor = {
|
|
12507
|
+
rawByteOffset: sidecar.rawByteOffset,
|
|
12508
|
+
rawPrefixSha256: sidecar.rawPrefixSha256
|
|
12509
|
+
};
|
|
12510
|
+
restored = sidecar;
|
|
12511
|
+
appendLog(
|
|
12512
|
+
"info",
|
|
12513
|
+
`[${sessionId}] restored epoch cursor from sidecar after old-CLI state rewrite (epoch=${sidecar.epoch}, turn=${sidecar.turnCount}, offset=${sidecar.rawByteOffset})`
|
|
12514
|
+
);
|
|
12515
|
+
} else if (prior.lastTranscriptSize !== void 0 && prior.lastTranscriptSha256 !== void 0) {
|
|
12516
|
+
const candidate = {
|
|
12517
|
+
rawByteOffset: prior.lastTranscriptSize,
|
|
12518
|
+
rawPrefixSha256: prior.lastTranscriptSha256
|
|
12519
|
+
};
|
|
12520
|
+
if (await cursorMatchesFile(transcriptPath, candidate)) {
|
|
12521
|
+
cursor = candidate;
|
|
12522
|
+
adoptedLegacy = true;
|
|
12523
|
+
appendLog(
|
|
12524
|
+
"info",
|
|
12525
|
+
`[${sessionId}] adopting last uploaded full zip as epoch-001 baseline (legacy state bootstrap, offset=${candidate.rawByteOffset})`
|
|
12526
|
+
);
|
|
12527
|
+
} else {
|
|
12528
|
+
appendLog(
|
|
12529
|
+
"info",
|
|
12530
|
+
`[${sessionId}] legacy state present but prefix mismatch or mid-line offset (offset=${candidate.rawByteOffset}) \u2014 will re-baseline with a fresh snapshot`
|
|
12531
|
+
);
|
|
12532
|
+
}
|
|
13829
12533
|
}
|
|
13830
|
-
return true;
|
|
13831
12534
|
}
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
12535
|
+
let mode;
|
|
12536
|
+
let transition = "initial";
|
|
12537
|
+
let tail;
|
|
12538
|
+
let nextCursor;
|
|
12539
|
+
if (cursor) {
|
|
12540
|
+
const evaluation = await evaluateTranscript(transcriptPath, cursor);
|
|
12541
|
+
if (evaluation.kind === "unchanged") {
|
|
12542
|
+
appendLog(
|
|
12543
|
+
"info",
|
|
12544
|
+
`[${sessionId}] skipping ${sourceTool} ${eventLabel} upload (no new complete transcript lines past offset ${cursor.rawByteOffset}${isSessionEnd ? "; local state cleared" : ""})`
|
|
12545
|
+
);
|
|
12546
|
+
if (isSessionEnd) {
|
|
12547
|
+
await deleteUploadState(repoRoot, sourceTool, sessionId);
|
|
12548
|
+
}
|
|
12549
|
+
return true;
|
|
12550
|
+
}
|
|
12551
|
+
if (evaluation.kind === "append") {
|
|
12552
|
+
mode = "patch";
|
|
12553
|
+
tail = evaluation.tail;
|
|
12554
|
+
nextCursor = evaluation.nextCursor;
|
|
12555
|
+
} else {
|
|
12556
|
+
mode = "snapshot";
|
|
12557
|
+
transition = evaluation.kind;
|
|
12558
|
+
}
|
|
12559
|
+
} else {
|
|
12560
|
+
mode = "snapshot";
|
|
12561
|
+
transition = prior ? "state-lost" : "initial";
|
|
13851
12562
|
}
|
|
13852
|
-
mwChain.push(new PatternRedactMiddleware());
|
|
13853
|
-
mwChain.push(new NormalizeMiddleware());
|
|
13854
12563
|
const identity = await loadIdentity(config.apiBaseUrl);
|
|
13855
12564
|
if (!identity) {
|
|
13856
12565
|
appendLog(
|
|
@@ -13863,6 +12572,159 @@ async function uploadSession(args) {
|
|
|
13863
12572
|
config.apiBaseUrl,
|
|
13864
12573
|
identity.sessionCookie
|
|
13865
12574
|
);
|
|
12575
|
+
const now = /* @__PURE__ */ new Date();
|
|
12576
|
+
const redactChain = await buildRedactChain(repoRoot);
|
|
12577
|
+
const transcriptArchivePath = archivePathFor({
|
|
12578
|
+
sourceName: sourceTool,
|
|
12579
|
+
absolutePath: transcriptPath
|
|
12580
|
+
});
|
|
12581
|
+
const alreadySubmitted = prior?.submitted ?? false;
|
|
12582
|
+
const submitThisUpload = config.autoSubmit && !alreadySubmitted;
|
|
12583
|
+
if (mode === "patch" && cursor && tail && nextCursor && prior?.contributionId) {
|
|
12584
|
+
const baseCursor = cursor;
|
|
12585
|
+
const contributionId2 = prior.contributionId;
|
|
12586
|
+
const epoch2 = prior.epoch ?? restored?.epoch ?? 1;
|
|
12587
|
+
const turn = (prior.turnCount ?? restored?.turnCount ?? 0) + 1;
|
|
12588
|
+
appendLog(
|
|
12589
|
+
"info",
|
|
12590
|
+
`[${sessionId}] ${sourceTool} ${eventLabel} upload \u2192 patch epoch=${epoch2} turn=${turn} (${tail.length} raw tail bytes, contribution ${contributionId2})`
|
|
12591
|
+
);
|
|
12592
|
+
try {
|
|
12593
|
+
const redactedTail = await redactTail(
|
|
12594
|
+
tail,
|
|
12595
|
+
repoRoot,
|
|
12596
|
+
sourceTool,
|
|
12597
|
+
transcriptPath,
|
|
12598
|
+
redactChain
|
|
12599
|
+
);
|
|
12600
|
+
if (adoptedLegacy) {
|
|
12601
|
+
await uploadArtifact(
|
|
12602
|
+
client,
|
|
12603
|
+
contributionId2,
|
|
12604
|
+
metaFilename(epoch2, recordedAt),
|
|
12605
|
+
"application/json",
|
|
12606
|
+
buildEpochMeta({
|
|
12607
|
+
sessionId,
|
|
12608
|
+
tool: sourceTool,
|
|
12609
|
+
cliVersion: CLI_VERSION,
|
|
12610
|
+
epoch: epoch2,
|
|
12611
|
+
transitionKind: "legacy-adopted",
|
|
12612
|
+
baseline: "legacy-latest-zip",
|
|
12613
|
+
transcriptArchivePath,
|
|
12614
|
+
cursor: baseCursor,
|
|
12615
|
+
recordedAt
|
|
12616
|
+
})
|
|
12617
|
+
);
|
|
12618
|
+
}
|
|
12619
|
+
await uploadArtifact(
|
|
12620
|
+
client,
|
|
12621
|
+
contributionId2,
|
|
12622
|
+
patchFilename(epoch2, turn, recordedAt),
|
|
12623
|
+
"application/gzip",
|
|
12624
|
+
gzipPatch(redactedTail),
|
|
12625
|
+
AGENT_MAX_UPLOAD_BYTES
|
|
12626
|
+
);
|
|
12627
|
+
} catch (err) {
|
|
12628
|
+
return handleUploadFailure(err, sessionId, repoRoot, sourceTool);
|
|
12629
|
+
}
|
|
12630
|
+
const persistState = async (submitted2) => {
|
|
12631
|
+
await writeUploadState({
|
|
12632
|
+
schemaVersion: CURRENT_SCHEMA_VERSION2,
|
|
12633
|
+
sessionId,
|
|
12634
|
+
tool: sourceTool,
|
|
12635
|
+
repoRoot,
|
|
12636
|
+
projectId: config.projectId,
|
|
12637
|
+
contributionId: contributionId2,
|
|
12638
|
+
submitted: submitted2,
|
|
12639
|
+
uploadCount: (prior.uploadCount ?? 0) + (adoptedLegacy ? 2 : 1),
|
|
12640
|
+
firstUploadedAt: prior.firstUploadedAt ?? now.toISOString(),
|
|
12641
|
+
lastUploadedAt: now.toISOString(),
|
|
12642
|
+
// Mirrors the cursor from here on: describes the raw bytes covered
|
|
12643
|
+
// by uploads, not the whole file (which may end in a torn line). An
|
|
12644
|
+
// old CLI reading these sees a mismatch against the grown file and
|
|
12645
|
+
// safely falls back to a full-zip upload.
|
|
12646
|
+
lastTranscriptSize: nextCursor.rawByteOffset,
|
|
12647
|
+
lastTranscriptSha256: nextCursor.rawPrefixSha256,
|
|
12648
|
+
epoch: epoch2,
|
|
12649
|
+
turnCount: turn,
|
|
12650
|
+
rawByteOffset: nextCursor.rawByteOffset,
|
|
12651
|
+
rawPrefixSha256: nextCursor.rawPrefixSha256,
|
|
12652
|
+
snapshotUploaded: true
|
|
12653
|
+
});
|
|
12654
|
+
await writeCursorState(repoRoot, sourceTool, sessionId, {
|
|
12655
|
+
contributionId: contributionId2,
|
|
12656
|
+
epoch: epoch2,
|
|
12657
|
+
turnCount: turn,
|
|
12658
|
+
rawByteOffset: nextCursor.rawByteOffset,
|
|
12659
|
+
rawPrefixSha256: nextCursor.rawPrefixSha256,
|
|
12660
|
+
snapshotUploaded: true
|
|
12661
|
+
});
|
|
12662
|
+
};
|
|
12663
|
+
await persistState(alreadySubmitted);
|
|
12664
|
+
let submitted = alreadySubmitted;
|
|
12665
|
+
if (submitThisUpload) {
|
|
12666
|
+
try {
|
|
12667
|
+
appendLog("info", `submitting contribution ${contributionId2}`);
|
|
12668
|
+
await client.submitContribution(contributionId2);
|
|
12669
|
+
submitted = true;
|
|
12670
|
+
appendLog("info", `contribution ${contributionId2} submitted`);
|
|
12671
|
+
} catch (err) {
|
|
12672
|
+
if (err instanceof PlatformError && err.status === 409 && err.code === "CONTRIBUTION_WRONG_STATE") {
|
|
12673
|
+
submitted = true;
|
|
12674
|
+
appendLog(
|
|
12675
|
+
"info",
|
|
12676
|
+
`contribution ${contributionId2} was already submitted (409); recording locally`
|
|
12677
|
+
);
|
|
12678
|
+
} else {
|
|
12679
|
+
appendLog(
|
|
12680
|
+
"warn",
|
|
12681
|
+
`[${sessionId}] submit failed (patch is durable; will retry next turn): ${err instanceof Error ? err.message : String(err)}`
|
|
12682
|
+
);
|
|
12683
|
+
}
|
|
12684
|
+
}
|
|
12685
|
+
if (submitted) await persistState(true);
|
|
12686
|
+
}
|
|
12687
|
+
appendLog(
|
|
12688
|
+
"info",
|
|
12689
|
+
`Uploaded session ${sessionId} patch epoch=${epoch2} turn=${turn} to contribution ${contributionId2} (offset ${baseCursor.rawByteOffset} \u2192 ${nextCursor.rawByteOffset})`
|
|
12690
|
+
);
|
|
12691
|
+
if (isSessionEnd) {
|
|
12692
|
+
await deleteUploadState(repoRoot, sourceTool, sessionId);
|
|
12693
|
+
appendLog(
|
|
12694
|
+
"info",
|
|
12695
|
+
`[${sessionId}] session complete \u2014 contribution ${contributionId2}, epoch ${epoch2}, ${turn} patch(es); local state cleared`
|
|
12696
|
+
);
|
|
12697
|
+
}
|
|
12698
|
+
return true;
|
|
12699
|
+
}
|
|
12700
|
+
let raw;
|
|
12701
|
+
try {
|
|
12702
|
+
raw = await fs13.promises.readFile(transcriptPath);
|
|
12703
|
+
} catch (err) {
|
|
12704
|
+
if (err.code === "ERR_FS_FILE_TOO_LARGE") {
|
|
12705
|
+
appendLog(
|
|
12706
|
+
"error",
|
|
12707
|
+
`Session ${sessionId} snapshot impossible: transcript is over Node's 2 GiB read limit (${transcriptPath}). Established patch chains keep working, but a (re-)baseline of this session cannot upload until snapshots are built streaming.`
|
|
12708
|
+
);
|
|
12709
|
+
} else {
|
|
12710
|
+
appendLog(
|
|
12711
|
+
"warn",
|
|
12712
|
+
`Skipping session ${sessionId}: transcript unreadable (${transcriptPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
12713
|
+
);
|
|
12714
|
+
}
|
|
12715
|
+
return false;
|
|
12716
|
+
}
|
|
12717
|
+
const truncated = truncateAtLastNewline(raw);
|
|
12718
|
+
if (!truncated) {
|
|
12719
|
+
appendLog(
|
|
12720
|
+
"info",
|
|
12721
|
+
`[${sessionId}] skipping ${sourceTool} ${eventLabel} upload (no complete transcript line yet)`
|
|
12722
|
+
);
|
|
12723
|
+
if (isSessionEnd)
|
|
12724
|
+
await deleteUploadState(repoRoot, sourceTool, sessionId);
|
|
12725
|
+
return true;
|
|
12726
|
+
}
|
|
12727
|
+
const epoch = (prior?.epoch ?? restored?.epoch ?? 0) + 1;
|
|
13866
12728
|
const shortId = sessionId.slice(0, 12);
|
|
13867
12729
|
const toolLabels = {
|
|
13868
12730
|
cursor: "Cursor",
|
|
@@ -13872,16 +12734,19 @@ async function uploadSession(args) {
|
|
|
13872
12734
|
opencode: "opencode"
|
|
13873
12735
|
};
|
|
13874
12736
|
const toolLabel2 = toolLabels[sourceTool] ?? "Claude";
|
|
13875
|
-
const
|
|
13876
|
-
const seq = (prior?.uploadCount ?? 0) + 1;
|
|
13877
|
-
const title = `${toolLabel2} session ${shortId} \u2014 ${epochSeconds}`;
|
|
12737
|
+
const title = `${toolLabel2} session ${shortId} \u2014 ${formatEpochSeconds2(now)}`;
|
|
13878
12738
|
const body = `Session ID: ${sessionId}
|
|
13879
12739
|
Tool: ${toolLabel2}
|
|
13880
12740
|
Repo: ${repoRoot}
|
|
13881
12741
|
Uploaded: ${now.toISOString()}`;
|
|
13882
|
-
const zipFilename =
|
|
13883
|
-
const
|
|
13884
|
-
|
|
12742
|
+
const zipFilename = snapshotFilename(epoch, recordedAt);
|
|
12743
|
+
const group = makeTranscriptGroup(
|
|
12744
|
+
repoRoot,
|
|
12745
|
+
sourceTool,
|
|
12746
|
+
transcriptPath,
|
|
12747
|
+
truncated.covered
|
|
12748
|
+
);
|
|
12749
|
+
const mwChain = redactChain;
|
|
13885
12750
|
const output = new PlatformUploadOutput({
|
|
13886
12751
|
client,
|
|
13887
12752
|
projectId: config.projectId,
|
|
@@ -13891,6 +12756,7 @@ Uploaded: ${now.toISOString()}`;
|
|
|
13891
12756
|
zipFilename,
|
|
13892
12757
|
autoSubmit: submitThisUpload,
|
|
13893
12758
|
existingContributionId: prior?.contributionId ?? void 0,
|
|
12759
|
+
maxUploadBytes: AGENT_MAX_UPLOAD_BYTES,
|
|
13894
12760
|
// Persist the new contribution id before the file PUT so a failed
|
|
13895
12761
|
// upload can't make the next Stop create a second contribution.
|
|
13896
12762
|
onContributionCreated: (id) => writeUploadState({
|
|
@@ -13908,10 +12774,10 @@ Uploaded: ${now.toISOString()}`;
|
|
|
13908
12774
|
lastTranscriptSha256: prior?.lastTranscriptSha256
|
|
13909
12775
|
})
|
|
13910
12776
|
});
|
|
13911
|
-
const reuseDesc = prior?.contributionId ? `reusing contribution ${prior.contributionId}
|
|
12777
|
+
const reuseDesc = prior?.contributionId ? `reusing contribution ${prior.contributionId}` : prior ? "new contribution (prior state had no contribution \u2014 earlier create may have failed)" : "new contribution (first upload for session)";
|
|
13912
12778
|
appendLog(
|
|
13913
12779
|
"info",
|
|
13914
|
-
`[${sessionId}] ${sourceTool} ${
|
|
12780
|
+
`[${sessionId}] ${sourceTool} ${eventLabel} upload \u2192 snapshot epoch=${epoch} transition=${transition} (${truncated.covered.length} raw bytes, ${reuseDesc})`
|
|
13915
12781
|
);
|
|
13916
12782
|
let contributionId;
|
|
13917
12783
|
try {
|
|
@@ -13919,20 +12785,36 @@ Uploaded: ${now.toISOString()}`;
|
|
|
13919
12785
|
selectedSources: [sourceTool]
|
|
13920
12786
|
});
|
|
13921
12787
|
} catch (err) {
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
12788
|
+
return handleUploadFailure(err, sessionId, repoRoot, sourceTool);
|
|
12789
|
+
}
|
|
12790
|
+
let metaUploaded = 0;
|
|
12791
|
+
try {
|
|
12792
|
+
await uploadArtifact(
|
|
12793
|
+
client,
|
|
12794
|
+
contributionId,
|
|
12795
|
+
metaFilename(epoch, recordedAt),
|
|
12796
|
+
"application/json",
|
|
12797
|
+
buildEpochMeta({
|
|
12798
|
+
sessionId,
|
|
12799
|
+
tool: sourceTool,
|
|
12800
|
+
cliVersion: CLI_VERSION,
|
|
12801
|
+
epoch,
|
|
12802
|
+
transitionKind: transition,
|
|
12803
|
+
baseline: "snapshot",
|
|
12804
|
+
snapshotFilename: zipFilename,
|
|
12805
|
+
transcriptArchivePath,
|
|
12806
|
+
cursor: truncated.cursor,
|
|
12807
|
+
recordedAt
|
|
12808
|
+
})
|
|
12809
|
+
);
|
|
12810
|
+
metaUploaded = 1;
|
|
12811
|
+
} catch (err) {
|
|
13929
12812
|
appendLog(
|
|
13930
|
-
"
|
|
13931
|
-
`
|
|
12813
|
+
"warn",
|
|
12814
|
+
`[${sessionId}] epoch meta upload failed (snapshot is durable; continuing): ${err instanceof Error ? err.message : String(err)}`
|
|
13932
12815
|
);
|
|
13933
|
-
return false;
|
|
13934
12816
|
}
|
|
13935
|
-
|
|
12817
|
+
await writeUploadState({
|
|
13936
12818
|
schemaVersion: CURRENT_SCHEMA_VERSION2,
|
|
13937
12819
|
sessionId,
|
|
13938
12820
|
tool: sourceTool,
|
|
@@ -13940,22 +12822,35 @@ Uploaded: ${now.toISOString()}`;
|
|
|
13940
12822
|
projectId: config.projectId,
|
|
13941
12823
|
contributionId,
|
|
13942
12824
|
submitted: alreadySubmitted || submitThisUpload,
|
|
13943
|
-
uploadCount:
|
|
12825
|
+
uploadCount: (prior?.uploadCount ?? 0) + 1 + metaUploaded,
|
|
13944
12826
|
firstUploadedAt: prior?.firstUploadedAt ?? now.toISOString(),
|
|
13945
12827
|
lastUploadedAt: now.toISOString(),
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
12828
|
+
// Mirrors the cursor (see patch path).
|
|
12829
|
+
lastTranscriptSize: truncated.cursor.rawByteOffset,
|
|
12830
|
+
lastTranscriptSha256: truncated.cursor.rawPrefixSha256,
|
|
12831
|
+
epoch,
|
|
12832
|
+
turnCount: 0,
|
|
12833
|
+
rawByteOffset: truncated.cursor.rawByteOffset,
|
|
12834
|
+
rawPrefixSha256: truncated.cursor.rawPrefixSha256,
|
|
12835
|
+
snapshotUploaded: true
|
|
12836
|
+
});
|
|
12837
|
+
await writeCursorState(repoRoot, sourceTool, sessionId, {
|
|
12838
|
+
contributionId,
|
|
12839
|
+
epoch,
|
|
12840
|
+
turnCount: 0,
|
|
12841
|
+
rawByteOffset: truncated.cursor.rawByteOffset,
|
|
12842
|
+
rawPrefixSha256: truncated.cursor.rawPrefixSha256,
|
|
12843
|
+
snapshotUploaded: true
|
|
12844
|
+
});
|
|
13950
12845
|
appendLog(
|
|
13951
12846
|
"info",
|
|
13952
|
-
`Uploaded session ${sessionId} to project ${config.projectSlug} (${config.projectId}) as contribution ${contributionId}
|
|
12847
|
+
`Uploaded session ${sessionId} snapshot epoch=${epoch} to project ${config.projectSlug} (${config.projectId}) as contribution ${contributionId}`
|
|
13953
12848
|
);
|
|
13954
12849
|
if (isSessionEnd) {
|
|
13955
12850
|
await deleteUploadState(repoRoot, sourceTool, sessionId);
|
|
13956
12851
|
appendLog(
|
|
13957
12852
|
"info",
|
|
13958
|
-
`[${sessionId}] session complete \u2014 contribution ${contributionId}, ${
|
|
12853
|
+
`[${sessionId}] session complete \u2014 contribution ${contributionId}, epoch ${epoch} snapshot; local state cleared`
|
|
13959
12854
|
);
|
|
13960
12855
|
}
|
|
13961
12856
|
return true;
|
|
@@ -14087,18 +12982,18 @@ async function runUploadWorker() {
|
|
|
14087
12982
|
|
|
14088
12983
|
// src/git-traces/index.ts
|
|
14089
12984
|
import { spawn as spawn3 } from "child_process";
|
|
14090
|
-
import
|
|
12985
|
+
import crypto6 from "crypto";
|
|
14091
12986
|
|
|
14092
12987
|
// src/git-traces/handlers.ts
|
|
14093
12988
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
14094
|
-
import
|
|
12989
|
+
import path17 from "path";
|
|
14095
12990
|
|
|
14096
12991
|
// src/git-traces/git-ops.ts
|
|
14097
12992
|
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
14098
|
-
import
|
|
12993
|
+
import fs14 from "fs";
|
|
14099
12994
|
import os7 from "os";
|
|
14100
|
-
import
|
|
14101
|
-
import { gzipSync } from "zlib";
|
|
12995
|
+
import path15 from "path";
|
|
12996
|
+
import { gzipSync as gzipSync2 } from "zlib";
|
|
14102
12997
|
var GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
14103
12998
|
var EXEC_OPTS = {
|
|
14104
12999
|
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
@@ -14203,8 +13098,8 @@ var EXCLUDED_SNAPSHOT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
14203
13098
|
]);
|
|
14204
13099
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
14205
13100
|
function isExcludedSnapshotPath(filePath) {
|
|
14206
|
-
if (EXCLUDED_SNAPSHOT_BASENAMES.has(
|
|
14207
|
-
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(
|
|
13101
|
+
if (EXCLUDED_SNAPSHOT_BASENAMES.has(path15.basename(filePath))) return true;
|
|
13102
|
+
return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path15.extname(filePath).toLowerCase());
|
|
14208
13103
|
}
|
|
14209
13104
|
function isBinaryBuffer(buffer) {
|
|
14210
13105
|
return buffer.includes(0);
|
|
@@ -14224,16 +13119,16 @@ function readTreeBlobHead(repoRoot, sha) {
|
|
|
14224
13119
|
function readWorkingFileHead(absPath) {
|
|
14225
13120
|
let fd = null;
|
|
14226
13121
|
try {
|
|
14227
|
-
fd =
|
|
13122
|
+
fd = fs14.openSync(absPath, "r");
|
|
14228
13123
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
14229
|
-
const bytesRead =
|
|
13124
|
+
const bytesRead = fs14.readSync(fd, buffer, 0, BINARY_SNIFF_BYTES, 0);
|
|
14230
13125
|
return buffer.subarray(0, bytesRead);
|
|
14231
13126
|
} catch {
|
|
14232
13127
|
return null;
|
|
14233
13128
|
} finally {
|
|
14234
13129
|
if (fd !== null) {
|
|
14235
13130
|
try {
|
|
14236
|
-
|
|
13131
|
+
fs14.closeSync(fd);
|
|
14237
13132
|
} catch {
|
|
14238
13133
|
}
|
|
14239
13134
|
}
|
|
@@ -14411,7 +13306,7 @@ function removePathsFromIndex(repoRoot, env, paths) {
|
|
|
14411
13306
|
function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
14412
13307
|
const omittedFiles = listOmittedTreeFiles(repoRoot, treeSha, options);
|
|
14413
13308
|
if (omittedFiles.length === 0) return treeSha;
|
|
14414
|
-
const tmpIndex =
|
|
13309
|
+
const tmpIndex = path15.join(
|
|
14415
13310
|
os7.tmpdir(),
|
|
14416
13311
|
`hillclimb-filter-${Date.now()}-${process.pid}`
|
|
14417
13312
|
);
|
|
@@ -14426,7 +13321,7 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
|
|
|
14426
13321
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
14427
13322
|
} finally {
|
|
14428
13323
|
try {
|
|
14429
|
-
|
|
13324
|
+
fs14.unlinkSync(tmpIndex);
|
|
14430
13325
|
} catch {
|
|
14431
13326
|
}
|
|
14432
13327
|
}
|
|
@@ -14443,8 +13338,8 @@ function buildUntrackedTree(repoRoot, omittedFiles) {
|
|
|
14443
13338
|
for (const relPath of list.split("\0")) {
|
|
14444
13339
|
if (!relPath) continue;
|
|
14445
13340
|
try {
|
|
14446
|
-
const absPath =
|
|
14447
|
-
const stat =
|
|
13341
|
+
const absPath = path15.join(repoRoot, relPath);
|
|
13342
|
+
const stat = fs14.lstatSync(absPath);
|
|
14448
13343
|
const reason = classifyOmission(
|
|
14449
13344
|
relPath,
|
|
14450
13345
|
stat.size,
|
|
@@ -14464,7 +13359,7 @@ function buildUntrackedTree(repoRoot, omittedFiles) {
|
|
|
14464
13359
|
}
|
|
14465
13360
|
}
|
|
14466
13361
|
if (kept.length === 0) return null;
|
|
14467
|
-
const tmpIndex =
|
|
13362
|
+
const tmpIndex = path15.join(
|
|
14468
13363
|
os7.tmpdir(),
|
|
14469
13364
|
`hillclimb-untracked-${Date.now()}-${process.pid}`
|
|
14470
13365
|
);
|
|
@@ -14477,7 +13372,7 @@ function buildUntrackedTree(repoRoot, omittedFiles) {
|
|
|
14477
13372
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
14478
13373
|
} finally {
|
|
14479
13374
|
try {
|
|
14480
|
-
|
|
13375
|
+
fs14.unlinkSync(tmpIndex);
|
|
14481
13376
|
} catch {
|
|
14482
13377
|
}
|
|
14483
13378
|
}
|
|
@@ -14494,7 +13389,7 @@ function buildSnapshotTree(repoRoot, stashSha, options = {}) {
|
|
|
14494
13389
|
const untrackedTree = buildUntrackedTree(repoRoot, options.omittedFiles);
|
|
14495
13390
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)
|
|
14496
13391
|
return filteredTrackedTree;
|
|
14497
|
-
const tmpIndex =
|
|
13392
|
+
const tmpIndex = path15.join(
|
|
14498
13393
|
os7.tmpdir(),
|
|
14499
13394
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
14500
13395
|
);
|
|
@@ -14522,7 +13417,7 @@ function buildSnapshotTree(repoRoot, stashSha, options = {}) {
|
|
|
14522
13417
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
14523
13418
|
} finally {
|
|
14524
13419
|
try {
|
|
14525
|
-
|
|
13420
|
+
fs14.unlinkSync(tmpIndex);
|
|
14526
13421
|
} catch {
|
|
14527
13422
|
}
|
|
14528
13423
|
}
|
|
@@ -14536,7 +13431,7 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
14536
13431
|
]);
|
|
14537
13432
|
const orphanRef = `refs/hillclimb/bundle/${sessionId}`;
|
|
14538
13433
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
14539
|
-
const tmpFile =
|
|
13434
|
+
const tmpFile = path15.join(
|
|
14540
13435
|
os7.tmpdir(),
|
|
14541
13436
|
// Include the pid (like the other temp files in this module) so concurrent
|
|
14542
13437
|
// git-traces workers — e.g. two sessions, or a parent + subagent — don't
|
|
@@ -14545,10 +13440,10 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
14545
13440
|
);
|
|
14546
13441
|
try {
|
|
14547
13442
|
git(repoRoot, ["bundle", "create", tmpFile, orphanRef]);
|
|
14548
|
-
return
|
|
13443
|
+
return fs14.readFileSync(tmpFile);
|
|
14549
13444
|
} finally {
|
|
14550
13445
|
try {
|
|
14551
|
-
|
|
13446
|
+
fs14.unlinkSync(tmpFile);
|
|
14552
13447
|
} catch {
|
|
14553
13448
|
}
|
|
14554
13449
|
deleteRef(repoRoot, orphanRef);
|
|
@@ -14574,7 +13469,7 @@ function createTreeDiffPatchGz(repoRoot, fromTreeSha, toTreeSha) {
|
|
|
14574
13469
|
filteredToTreeSha
|
|
14575
13470
|
]);
|
|
14576
13471
|
if (diff.length === 0) return null;
|
|
14577
|
-
return Buffer.from(
|
|
13472
|
+
return Buffer.from(gzipSync2(diff));
|
|
14578
13473
|
}
|
|
14579
13474
|
function detectTransitionKind(repoRoot, prevHeadSha, nextHeadSha) {
|
|
14580
13475
|
if (!prevHeadSha) return "initial";
|
|
@@ -14735,9 +13630,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
14735
13630
|
oldPath
|
|
14736
13631
|
});
|
|
14737
13632
|
} else {
|
|
14738
|
-
const
|
|
14739
|
-
indexByPath.set(
|
|
14740
|
-
files.push({ path:
|
|
13633
|
+
const path24 = parts[parts.length - 1];
|
|
13634
|
+
indexByPath.set(path24, files.length);
|
|
13635
|
+
files.push({ path: path24, status, additions: 0, deletions: 0 });
|
|
14741
13636
|
}
|
|
14742
13637
|
}
|
|
14743
13638
|
for (const line of numstat.split("\n")) {
|
|
@@ -14803,29 +13698,30 @@ function cleanupSessionRefs(repoRoot, sessionId) {
|
|
|
14803
13698
|
}
|
|
14804
13699
|
|
|
14805
13700
|
// src/git-traces/session-state.ts
|
|
14806
|
-
import
|
|
14807
|
-
import
|
|
13701
|
+
import crypto5 from "crypto";
|
|
13702
|
+
import fs15 from "fs";
|
|
14808
13703
|
import os8 from "os";
|
|
14809
|
-
import
|
|
13704
|
+
import path16 from "path";
|
|
14810
13705
|
var CURRENT_SCHEMA_VERSION3 = 3;
|
|
14811
|
-
var DEFAULT_STATE_DIR2 =
|
|
13706
|
+
var DEFAULT_STATE_DIR2 = path16.join(os8.homedir(), ".hillclimb", "git-traces");
|
|
14812
13707
|
var LOCK_RETRIES2 = 120;
|
|
14813
13708
|
var LOCK_RETRY_DELAY_MS2 = 500;
|
|
13709
|
+
var STALE_LOCK_TTL_MS2 = 60 * 60 * 1e3;
|
|
14814
13710
|
function stateDir3() {
|
|
14815
13711
|
return process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? DEFAULT_STATE_DIR2;
|
|
14816
13712
|
}
|
|
14817
13713
|
function stateFileForRepo(repoRoot, tool, sessionId) {
|
|
14818
|
-
const hash =
|
|
14819
|
-
sessionId ? `${
|
|
13714
|
+
const hash = crypto5.createHash("sha256").update(
|
|
13715
|
+
sessionId ? `${path16.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path16.resolve(repoRoot)}\0${tool}`
|
|
14820
13716
|
).digest("hex").slice(0, 16);
|
|
14821
|
-
return
|
|
13717
|
+
return path16.join(stateDir3(), `${hash}.json`);
|
|
14822
13718
|
}
|
|
14823
|
-
function lockFileForRepo(repoRoot, tool) {
|
|
14824
|
-
return `${stateFileForRepo(repoRoot, tool)}.lock`;
|
|
13719
|
+
function lockFileForRepo(repoRoot, tool, sessionId) {
|
|
13720
|
+
return `${stateFileForRepo(repoRoot, tool, sessionId)}.lock`;
|
|
14825
13721
|
}
|
|
14826
13722
|
async function readStateFile(file) {
|
|
14827
13723
|
try {
|
|
14828
|
-
const raw = await
|
|
13724
|
+
const raw = await fs15.promises.readFile(file, "utf-8");
|
|
14829
13725
|
const parsed = JSON.parse(raw);
|
|
14830
13726
|
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION3) {
|
|
14831
13727
|
return null;
|
|
@@ -14838,26 +13734,26 @@ async function readStateFile(file) {
|
|
|
14838
13734
|
async function listScopedSessionStates(repoRoot, tool) {
|
|
14839
13735
|
let entries;
|
|
14840
13736
|
try {
|
|
14841
|
-
entries = await
|
|
13737
|
+
entries = await fs15.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
14842
13738
|
} catch {
|
|
14843
13739
|
return [];
|
|
14844
13740
|
}
|
|
14845
13741
|
const states = [];
|
|
14846
13742
|
for (const entry of entries) {
|
|
14847
13743
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
14848
|
-
const file =
|
|
13744
|
+
const file = path16.join(stateDir3(), entry.name);
|
|
14849
13745
|
const state = await readStateFile(file);
|
|
14850
13746
|
if (!state) continue;
|
|
14851
13747
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
14852
13748
|
continue;
|
|
14853
13749
|
}
|
|
14854
|
-
if (
|
|
14855
|
-
if (
|
|
13750
|
+
if (path16.resolve(state.repoRoot) !== path16.resolve(repoRoot)) continue;
|
|
13751
|
+
if (path16.resolve(file) !== path16.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
|
|
14856
13752
|
continue;
|
|
14857
13753
|
}
|
|
14858
13754
|
let mtimeMs = 0;
|
|
14859
13755
|
try {
|
|
14860
|
-
mtimeMs = (await
|
|
13756
|
+
mtimeMs = (await fs15.promises.stat(file)).mtimeMs;
|
|
14861
13757
|
} catch {
|
|
14862
13758
|
continue;
|
|
14863
13759
|
}
|
|
@@ -14868,26 +13764,26 @@ async function listScopedSessionStates(repoRoot, tool) {
|
|
|
14868
13764
|
async function listSessionStatesForSession(tool, sessionId) {
|
|
14869
13765
|
let entries;
|
|
14870
13766
|
try {
|
|
14871
|
-
entries = await
|
|
13767
|
+
entries = await fs15.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
14872
13768
|
} catch {
|
|
14873
13769
|
return [];
|
|
14874
13770
|
}
|
|
14875
13771
|
const states = [];
|
|
14876
13772
|
for (const entry of entries) {
|
|
14877
13773
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
14878
|
-
const file =
|
|
13774
|
+
const file = path16.join(stateDir3(), entry.name);
|
|
14879
13775
|
const state = await readStateFile(file);
|
|
14880
13776
|
if (!state) continue;
|
|
14881
13777
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
14882
13778
|
continue;
|
|
14883
13779
|
}
|
|
14884
13780
|
if (state.sessionId !== sessionId) continue;
|
|
14885
|
-
if (
|
|
13781
|
+
if (path16.resolve(file) !== path16.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
|
|
14886
13782
|
continue;
|
|
14887
13783
|
}
|
|
14888
13784
|
let mtimeMs = 0;
|
|
14889
13785
|
try {
|
|
14890
|
-
mtimeMs = (await
|
|
13786
|
+
mtimeMs = (await fs15.promises.stat(file)).mtimeMs;
|
|
14891
13787
|
} catch {
|
|
14892
13788
|
continue;
|
|
14893
13789
|
}
|
|
@@ -14908,12 +13804,12 @@ async function readSessionState(repoRoot, tool, sessionId) {
|
|
|
14908
13804
|
}
|
|
14909
13805
|
async function writeSessionState(state, tool) {
|
|
14910
13806
|
const file = stateFileForRepo(state.repoRoot, tool, state.sessionId);
|
|
14911
|
-
await
|
|
13807
|
+
await fs15.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
14912
13808
|
const tmp = `${file}.tmp`;
|
|
14913
|
-
await
|
|
13809
|
+
await fs15.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
14914
13810
|
mode: 384
|
|
14915
13811
|
});
|
|
14916
|
-
await
|
|
13812
|
+
await fs15.promises.rename(tmp, file);
|
|
14917
13813
|
const legacyFile = stateFileForRepo(state.repoRoot, tool);
|
|
14918
13814
|
const legacy = await readStateFile(legacyFile);
|
|
14919
13815
|
if (legacy?.sessionId === state.sessionId) {
|
|
@@ -14922,7 +13818,7 @@ async function writeSessionState(state, tool) {
|
|
|
14922
13818
|
}
|
|
14923
13819
|
async function deleteStateFile(file) {
|
|
14924
13820
|
try {
|
|
14925
|
-
await
|
|
13821
|
+
await fs15.promises.unlink(file);
|
|
14926
13822
|
} catch {
|
|
14927
13823
|
}
|
|
14928
13824
|
}
|
|
@@ -14938,14 +13834,14 @@ async function deleteSessionState(repoRoot, tool, sessionId) {
|
|
|
14938
13834
|
}
|
|
14939
13835
|
await deleteStateFile(stateFileForRepo(repoRoot, tool));
|
|
14940
13836
|
}
|
|
14941
|
-
async function acquireLock3(repoRoot, tool, retries = LOCK_RETRIES2, delayMs = LOCK_RETRY_DELAY_MS2) {
|
|
14942
|
-
const lockPath = lockFileForRepo(repoRoot, tool);
|
|
14943
|
-
await
|
|
13837
|
+
async function acquireLock3(repoRoot, tool, sessionId, retries = LOCK_RETRIES2, delayMs = LOCK_RETRY_DELAY_MS2) {
|
|
13838
|
+
const lockPath = lockFileForRepo(repoRoot, tool, sessionId);
|
|
13839
|
+
await fs15.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
|
|
14944
13840
|
for (let i = 0; i < retries; i++) {
|
|
14945
13841
|
try {
|
|
14946
|
-
const fd = await
|
|
13842
|
+
const fd = await fs15.promises.open(
|
|
14947
13843
|
lockPath,
|
|
14948
|
-
|
|
13844
|
+
fs15.constants.O_CREAT | fs15.constants.O_EXCL | fs15.constants.O_WRONLY
|
|
14949
13845
|
);
|
|
14950
13846
|
await fd.write(String(process.pid));
|
|
14951
13847
|
await fd.close();
|
|
@@ -14960,15 +13856,54 @@ async function acquireLock3(repoRoot, tool, retries = LOCK_RETRIES2, delayMs = L
|
|
|
14960
13856
|
}
|
|
14961
13857
|
throw new Error(`Failed to acquire lock after ${retries} retries`);
|
|
14962
13858
|
}
|
|
14963
|
-
async function releaseLock3(repoRoot, tool) {
|
|
13859
|
+
async function releaseLock3(repoRoot, tool, sessionId) {
|
|
13860
|
+
try {
|
|
13861
|
+
await fs15.promises.unlink(lockFileForRepo(repoRoot, tool, sessionId));
|
|
13862
|
+
} catch {
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
function isProcessAlive2(pid) {
|
|
13866
|
+
try {
|
|
13867
|
+
process.kill(pid, 0);
|
|
13868
|
+
return true;
|
|
13869
|
+
} catch (err) {
|
|
13870
|
+
return err.code === "EPERM";
|
|
13871
|
+
}
|
|
13872
|
+
}
|
|
13873
|
+
async function sweepStaleLockFiles(ttlMs = STALE_LOCK_TTL_MS2, now = Date.now()) {
|
|
13874
|
+
let entries;
|
|
14964
13875
|
try {
|
|
14965
|
-
await
|
|
13876
|
+
entries = await fs15.promises.readdir(stateDir3(), { withFileTypes: true });
|
|
14966
13877
|
} catch {
|
|
13878
|
+
return 0;
|
|
14967
13879
|
}
|
|
13880
|
+
let removed = 0;
|
|
13881
|
+
for (const entry of entries) {
|
|
13882
|
+
if (!entry.isFile() || !entry.name.endsWith(".lock")) continue;
|
|
13883
|
+
const file = path16.join(stateDir3(), entry.name);
|
|
13884
|
+
try {
|
|
13885
|
+
const raw = await fs15.promises.readFile(file, "utf-8").catch(() => "");
|
|
13886
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
13887
|
+
const havePid = Number.isInteger(pid) && pid > 0;
|
|
13888
|
+
let reap;
|
|
13889
|
+
if (havePid) {
|
|
13890
|
+
reap = !isProcessAlive2(pid);
|
|
13891
|
+
} else {
|
|
13892
|
+
const st = await fs15.promises.stat(file);
|
|
13893
|
+
reap = now - st.mtimeMs > ttlMs;
|
|
13894
|
+
}
|
|
13895
|
+
if (reap) {
|
|
13896
|
+
await fs15.promises.unlink(file);
|
|
13897
|
+
removed++;
|
|
13898
|
+
}
|
|
13899
|
+
} catch {
|
|
13900
|
+
}
|
|
13901
|
+
}
|
|
13902
|
+
return removed;
|
|
14968
13903
|
}
|
|
14969
13904
|
|
|
14970
13905
|
// src/git-traces/handlers.ts
|
|
14971
|
-
var
|
|
13906
|
+
var CLI_VERSION2 = "0.7.0";
|
|
14972
13907
|
var GIT_TRACES_SLUG = "git-traces";
|
|
14973
13908
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
14974
13909
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -14985,12 +13920,12 @@ var TOOL_LABELS = {
|
|
|
14985
13920
|
async function loadConfiguredRepos() {
|
|
14986
13921
|
const file = await loadProjects();
|
|
14987
13922
|
return Object.entries(file.projects).map(([repoRoot, config]) => ({
|
|
14988
|
-
repoRoot:
|
|
13923
|
+
repoRoot: path17.resolve(repoRoot),
|
|
14989
13924
|
config
|
|
14990
13925
|
})).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
14991
13926
|
}
|
|
14992
13927
|
function repoLabel(repoRoot) {
|
|
14993
|
-
return
|
|
13928
|
+
return path17.basename(repoRoot) || repoRoot;
|
|
14994
13929
|
}
|
|
14995
13930
|
function resolveCwd2(payload) {
|
|
14996
13931
|
return resolveHookCwd(payload);
|
|
@@ -14998,10 +13933,10 @@ function resolveCwd2(payload) {
|
|
|
14998
13933
|
function resolveSessionId2(payload) {
|
|
14999
13934
|
return resolveHookSessionId(payload);
|
|
15000
13935
|
}
|
|
15001
|
-
function
|
|
13936
|
+
function epochPrefix2(epoch) {
|
|
15002
13937
|
return `epoch-${String(epoch).padStart(3, "0")}`;
|
|
15003
13938
|
}
|
|
15004
|
-
function
|
|
13939
|
+
function turnSuffix2(turn) {
|
|
15005
13940
|
return `turn-${String(turn).padStart(3, "0")}`;
|
|
15006
13941
|
}
|
|
15007
13942
|
function captureHeadSha(cwd) {
|
|
@@ -15046,18 +13981,18 @@ async function uploadFile(client, contributionId, filename, mimeType, buffer) {
|
|
|
15046
13981
|
return true;
|
|
15047
13982
|
}
|
|
15048
13983
|
function canUploadEpochBaselineArtifacts(epoch, artifacts) {
|
|
15049
|
-
const prefix =
|
|
13984
|
+
const prefix = epochPrefix2(epoch);
|
|
15050
13985
|
return canUploadFile(`${prefix}-baseline.bundle`, artifacts.bundleBuffer) && canUploadFile(`${prefix}-baseline.json`, artifacts.metadataBuffer);
|
|
15051
13986
|
}
|
|
15052
13987
|
function pinEpochBaseline(repoRoot, sessionId, epoch) {
|
|
15053
13988
|
const baselineSha = captureBaselineSha(repoRoot);
|
|
15054
|
-
const baselineRefPrefix = `refs/hillclimb/baseline/${sessionId}/${
|
|
13989
|
+
const baselineRefPrefix = `refs/hillclimb/baseline/${sessionId}/${epochPrefix2(epoch)}`;
|
|
15055
13990
|
deleteRef(repoRoot, baselineRefPrefix);
|
|
15056
13991
|
pinRef(repoRoot, `${baselineRefPrefix}/tracked`, baselineSha);
|
|
15057
13992
|
return { baselineSha, headSha: captureHeadSha(repoRoot) };
|
|
15058
13993
|
}
|
|
15059
13994
|
function pinFrozenBaselineTree(repoRoot, sessionId, epoch, baselineTreeSha) {
|
|
15060
|
-
const prefix =
|
|
13995
|
+
const prefix = epochPrefix2(epoch);
|
|
15061
13996
|
const commit = execGit(repoRoot, [
|
|
15062
13997
|
"commit-tree",
|
|
15063
13998
|
baselineTreeSha,
|
|
@@ -15080,7 +14015,7 @@ function freezeEpochBaseline(params) {
|
|
|
15080
14015
|
transitionKind,
|
|
15081
14016
|
startedAt
|
|
15082
14017
|
} = params;
|
|
15083
|
-
const prefix =
|
|
14018
|
+
const prefix = epochPrefix2(epoch);
|
|
15084
14019
|
try {
|
|
15085
14020
|
const { baselineSha, headSha } = pinEpochBaseline(
|
|
15086
14021
|
repoRoot,
|
|
@@ -15096,7 +14031,7 @@ function freezeEpochBaseline(params) {
|
|
|
15096
14031
|
sessionId,
|
|
15097
14032
|
tool,
|
|
15098
14033
|
baselineSha,
|
|
15099
|
-
|
|
14034
|
+
CLI_VERSION2,
|
|
15100
14035
|
epoch,
|
|
15101
14036
|
prevHeadSha,
|
|
15102
14037
|
transitionKind,
|
|
@@ -15115,7 +14050,7 @@ function freezeEpochBaseline(params) {
|
|
|
15115
14050
|
}
|
|
15116
14051
|
function buildFrozenEpochBaselineArtifacts(params) {
|
|
15117
14052
|
const { repoRoot, sessionId, epoch, baselineTreeSha, baselineMetadata } = params;
|
|
15118
|
-
const prefix =
|
|
14053
|
+
const prefix = epochPrefix2(epoch);
|
|
15119
14054
|
try {
|
|
15120
14055
|
const bundleBuffer = createBundleFromTree(
|
|
15121
14056
|
repoRoot,
|
|
@@ -15146,7 +14081,7 @@ function buildEpochBaselineArtifacts(params) {
|
|
|
15146
14081
|
transitionKind,
|
|
15147
14082
|
startedAt
|
|
15148
14083
|
} = params;
|
|
15149
|
-
const prefix =
|
|
14084
|
+
const prefix = epochPrefix2(epoch);
|
|
15150
14085
|
try {
|
|
15151
14086
|
const omittedFiles = [];
|
|
15152
14087
|
const baselineTreeSha = buildSnapshotTree(repoRoot, baselineSha, {
|
|
@@ -15157,7 +14092,7 @@ function buildEpochBaselineArtifacts(params) {
|
|
|
15157
14092
|
sessionId,
|
|
15158
14093
|
tool,
|
|
15159
14094
|
baselineSha,
|
|
15160
|
-
|
|
14095
|
+
CLI_VERSION2,
|
|
15161
14096
|
epoch,
|
|
15162
14097
|
prevHeadSha,
|
|
15163
14098
|
transitionKind,
|
|
@@ -15181,7 +14116,7 @@ function buildEpochBaselineArtifacts(params) {
|
|
|
15181
14116
|
}
|
|
15182
14117
|
async function uploadEpochBaselineArtifacts(params) {
|
|
15183
14118
|
const { client, contributionId, epoch, artifacts } = params;
|
|
15184
|
-
const prefix =
|
|
14119
|
+
const prefix = epochPrefix2(epoch);
|
|
15185
14120
|
if (!canUploadEpochBaselineArtifacts(epoch, artifacts)) return false;
|
|
15186
14121
|
const bundleUploaded = await uploadFile(
|
|
15187
14122
|
client,
|
|
@@ -15302,13 +14237,26 @@ async function cleanupStaleScopedSessionStates(repoRoot, tool, currentSessionId,
|
|
|
15302
14237
|
const startedAtMs = Date.parse(state.startedAt);
|
|
15303
14238
|
const ageBaseMs = Number.isNaN(startedAtMs) ? mtimeMs : startedAtMs;
|
|
15304
14239
|
if (nowMs - ageBaseMs <= ttlMs) continue;
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
14240
|
+
try {
|
|
14241
|
+
await acquireLock3(repoRoot, tool, state.sessionId, 1, 0);
|
|
14242
|
+
} catch {
|
|
14243
|
+
appendLog(
|
|
14244
|
+
"info",
|
|
14245
|
+
`git-traces: leaving session ${state.sessionId} (lock held \u2014 still active)`
|
|
14246
|
+
);
|
|
14247
|
+
continue;
|
|
14248
|
+
}
|
|
14249
|
+
try {
|
|
14250
|
+
appendLog(
|
|
14251
|
+
"info",
|
|
14252
|
+
`git-traces: cleaning up stale scoped session ${state.sessionId}`
|
|
14253
|
+
);
|
|
14254
|
+
cleanupSessionRefs(repoRoot, state.sessionId);
|
|
14255
|
+
await deleteSessionState(repoRoot, tool, state.sessionId);
|
|
14256
|
+
removed++;
|
|
14257
|
+
} finally {
|
|
14258
|
+
await releaseLock3(repoRoot, tool, state.sessionId);
|
|
14259
|
+
}
|
|
15312
14260
|
}
|
|
15313
14261
|
return removed;
|
|
15314
14262
|
}
|
|
@@ -15321,16 +14269,34 @@ async function processSessionStartRepo(repo, tool, sessionId) {
|
|
|
15321
14269
|
);
|
|
15322
14270
|
return "skipped";
|
|
15323
14271
|
}
|
|
15324
|
-
|
|
14272
|
+
let acquired = false;
|
|
15325
14273
|
try {
|
|
14274
|
+
await acquireLock3(repoRoot, tool, sessionId);
|
|
14275
|
+
acquired = true;
|
|
15326
14276
|
const staleLegacy = await readSessionState(repoRoot, tool);
|
|
15327
14277
|
if (staleLegacy && staleLegacy.sessionId !== sessionId) {
|
|
15328
|
-
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
14278
|
+
let acquiredLegacy = false;
|
|
14279
|
+
try {
|
|
14280
|
+
await acquireLock3(repoRoot, tool, staleLegacy.sessionId, 1, 0);
|
|
14281
|
+
acquiredLegacy = true;
|
|
14282
|
+
} catch {
|
|
14283
|
+
appendLog(
|
|
14284
|
+
"info",
|
|
14285
|
+
`git-traces: leaving legacy session ${staleLegacy.sessionId} (lock held \u2014 still active)`
|
|
14286
|
+
);
|
|
14287
|
+
}
|
|
14288
|
+
if (acquiredLegacy) {
|
|
14289
|
+
try {
|
|
14290
|
+
appendLog(
|
|
14291
|
+
"info",
|
|
14292
|
+
`git-traces: cleaning up stale session ${staleLegacy.sessionId} (repo=${repoRoot})`
|
|
14293
|
+
);
|
|
14294
|
+
cleanupSessionRefs(repoRoot, staleLegacy.sessionId);
|
|
14295
|
+
await deleteSessionState(repoRoot, tool);
|
|
14296
|
+
} finally {
|
|
14297
|
+
await releaseLock3(repoRoot, tool, staleLegacy.sessionId);
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
15334
14300
|
}
|
|
15335
14301
|
const staleCount = await cleanupStaleScopedSessionStates(
|
|
15336
14302
|
repoRoot,
|
|
@@ -15352,7 +14318,7 @@ async function processSessionStartRepo(repo, tool, sessionId) {
|
|
|
15352
14318
|
);
|
|
15353
14319
|
return "failed";
|
|
15354
14320
|
} finally {
|
|
15355
|
-
await releaseLock3(repoRoot, tool);
|
|
14321
|
+
if (acquired) await releaseLock3(repoRoot, tool, sessionId);
|
|
15356
14322
|
}
|
|
15357
14323
|
}
|
|
15358
14324
|
async function handleSessionStart(payload, tool) {
|
|
@@ -15374,6 +14340,10 @@ async function handleSessionStart(payload, tool) {
|
|
|
15374
14340
|
appendLog("warn", "git-traces: no session_id in payload, skipping");
|
|
15375
14341
|
return;
|
|
15376
14342
|
}
|
|
14343
|
+
const reaped = await sweepStaleLockFiles();
|
|
14344
|
+
if (reaped > 0) {
|
|
14345
|
+
appendLog("info", `git-traces: reaped ${reaped} stale lock file(s)`);
|
|
14346
|
+
}
|
|
15377
14347
|
const repos = await loadConfiguredRepos();
|
|
15378
14348
|
let initialized = 0;
|
|
15379
14349
|
let skipped = 0;
|
|
@@ -15453,9 +14423,11 @@ async function processStopRepo(repo, tool, sessionId, recordedAt) {
|
|
|
15453
14423
|
);
|
|
15454
14424
|
return "skipped";
|
|
15455
14425
|
}
|
|
15456
|
-
await acquireLock3(repoRoot, tool);
|
|
15457
14426
|
let state = null;
|
|
14427
|
+
let acquired = false;
|
|
15458
14428
|
try {
|
|
14429
|
+
await acquireLock3(repoRoot, tool, sessionId);
|
|
14430
|
+
acquired = true;
|
|
15459
14431
|
state = await readSessionState(repoRoot, tool, sessionId);
|
|
15460
14432
|
if (!state) {
|
|
15461
14433
|
appendLog(
|
|
@@ -15555,9 +14527,9 @@ async function processStopRepo(repo, tool, sessionId, recordedAt) {
|
|
|
15555
14527
|
);
|
|
15556
14528
|
return "unchanged";
|
|
15557
14529
|
}
|
|
15558
|
-
const prefix =
|
|
14530
|
+
const prefix = epochPrefix2(state.epoch);
|
|
15559
14531
|
const nextTurnCount = state.turnCount + 1;
|
|
15560
|
-
const turnLabel =
|
|
14532
|
+
const turnLabel = turnSuffix2(nextTurnCount);
|
|
15561
14533
|
const filename = `${prefix}-${turnLabel}-${recordedAt}.patch.gz`;
|
|
15562
14534
|
if ((state.contributionId === null || !state.baselineUploaded) && !canUploadFile(filename, patchBuffer)) {
|
|
15563
14535
|
appendLog(
|
|
@@ -15655,7 +14627,7 @@ async function processStopRepo(repo, tool, sessionId, recordedAt) {
|
|
|
15655
14627
|
}
|
|
15656
14628
|
return "failed";
|
|
15657
14629
|
} finally {
|
|
15658
|
-
await releaseLock3(repoRoot, tool);
|
|
14630
|
+
if (acquired) await releaseLock3(repoRoot, tool, sessionId);
|
|
15659
14631
|
}
|
|
15660
14632
|
}
|
|
15661
14633
|
async function handleStop(payload, tool) {
|
|
@@ -15672,7 +14644,7 @@ async function handleStop(payload, tool) {
|
|
|
15672
14644
|
if (sessionId) {
|
|
15673
14645
|
const storedStates = await listSessionStatesForSession(tool, sessionId);
|
|
15674
14646
|
for (const { state } of storedStates) {
|
|
15675
|
-
const repo = repoByRoot.get(
|
|
14647
|
+
const repo = repoByRoot.get(path17.resolve(state.repoRoot));
|
|
15676
14648
|
if (!repo) {
|
|
15677
14649
|
missingConfig++;
|
|
15678
14650
|
appendLog(
|
|
@@ -15708,8 +14680,10 @@ async function handleStop(payload, tool) {
|
|
|
15708
14680
|
);
|
|
15709
14681
|
}
|
|
15710
14682
|
async function cleanupSessionStateForRepo(repoRoot, tool, sessionId) {
|
|
15711
|
-
|
|
14683
|
+
let acquired = false;
|
|
15712
14684
|
try {
|
|
14685
|
+
await acquireLock3(repoRoot, tool, sessionId);
|
|
14686
|
+
acquired = true;
|
|
15713
14687
|
const state = await readSessionState(repoRoot, tool, sessionId);
|
|
15714
14688
|
if (!state) return "no-state";
|
|
15715
14689
|
cleanupSessionRefs(repoRoot, state.sessionId);
|
|
@@ -15726,7 +14700,7 @@ async function cleanupSessionStateForRepo(repoRoot, tool, sessionId) {
|
|
|
15726
14700
|
);
|
|
15727
14701
|
return "failed";
|
|
15728
14702
|
} finally {
|
|
15729
|
-
await releaseLock3(repoRoot, tool);
|
|
14703
|
+
if (acquired) await releaseLock3(repoRoot, tool, sessionId);
|
|
15730
14704
|
}
|
|
15731
14705
|
}
|
|
15732
14706
|
async function handleSessionEnd(payload, tool) {
|
|
@@ -15739,7 +14713,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
15739
14713
|
if (sessionId) {
|
|
15740
14714
|
const states = await listSessionStatesForSession(tool, sessionId);
|
|
15741
14715
|
for (const { state } of states) {
|
|
15742
|
-
repoRoots.push(
|
|
14716
|
+
repoRoots.push(path17.resolve(state.repoRoot));
|
|
15743
14717
|
}
|
|
15744
14718
|
}
|
|
15745
14719
|
if (repoRoots.length === 0 && cwd) {
|
|
@@ -15755,7 +14729,7 @@ async function handleSessionEnd(payload, tool) {
|
|
|
15755
14729
|
let skipped = 0;
|
|
15756
14730
|
let failed = 0;
|
|
15757
14731
|
for (const repoRoot of repoRoots) {
|
|
15758
|
-
const repo = repoByRoot.get(
|
|
14732
|
+
const repo = repoByRoot.get(path17.resolve(repoRoot)) ?? (project && path17.resolve(project.repoRoot) === path17.resolve(repoRoot) ? { repoRoot: project.repoRoot, config: project.config } : null);
|
|
15759
14733
|
if (!repo) {
|
|
15760
14734
|
skipped++;
|
|
15761
14735
|
appendLog(
|
|
@@ -15806,7 +14780,7 @@ var WORKER_ENV_FLAG2 = "HILLCLIMB_GIT_TRACES_WORKER";
|
|
|
15806
14780
|
var TOOL_ENV_FLAG2 = "HILLCLIMB_GIT_TRACES_TOOL";
|
|
15807
14781
|
var FLOW_ID_ENV2 = "HILLCLIMB_GIT_TRACES_FLOW";
|
|
15808
14782
|
function newFlowId2() {
|
|
15809
|
-
return
|
|
14783
|
+
return crypto6.randomBytes(3).toString("hex");
|
|
15810
14784
|
}
|
|
15811
14785
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
15812
14786
|
"claude",
|
|
@@ -16092,15 +15066,15 @@ ${stack}` : ""}`
|
|
|
16092
15066
|
}
|
|
16093
15067
|
|
|
16094
15068
|
// src/outputs/zip.ts
|
|
16095
|
-
import
|
|
16096
|
-
import
|
|
15069
|
+
import fs17 from "fs";
|
|
15070
|
+
import path19 from "path";
|
|
16097
15071
|
import archiver2 from "archiver";
|
|
16098
15072
|
|
|
16099
15073
|
// src/outputs/downloads.ts
|
|
16100
15074
|
import { execSync as execSync2 } from "child_process";
|
|
16101
|
-
import
|
|
15075
|
+
import fs16 from "fs";
|
|
16102
15076
|
import os9 from "os";
|
|
16103
|
-
import
|
|
15077
|
+
import path18 from "path";
|
|
16104
15078
|
function getDownloadsFolder() {
|
|
16105
15079
|
const home = os9.homedir();
|
|
16106
15080
|
if (process.platform === "linux") {
|
|
@@ -16109,12 +15083,12 @@ function getDownloadsFolder() {
|
|
|
16109
15083
|
encoding: "utf-8",
|
|
16110
15084
|
timeout: 3e3
|
|
16111
15085
|
}).trim();
|
|
16112
|
-
if (xdgDir &&
|
|
15086
|
+
if (xdgDir && fs16.existsSync(xdgDir)) return xdgDir;
|
|
16113
15087
|
} catch {
|
|
16114
15088
|
}
|
|
16115
15089
|
}
|
|
16116
|
-
const downloads =
|
|
16117
|
-
if (
|
|
15090
|
+
const downloads = path18.join(home, "Downloads");
|
|
15091
|
+
if (fs16.existsSync(downloads)) return downloads;
|
|
16118
15092
|
return home;
|
|
16119
15093
|
}
|
|
16120
15094
|
|
|
@@ -16123,11 +15097,11 @@ function sanitizeFilename(name) {
|
|
|
16123
15097
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
16124
15098
|
}
|
|
16125
15099
|
function getUniqueFilename(dir, base, ext) {
|
|
16126
|
-
let candidate =
|
|
16127
|
-
if (!
|
|
15100
|
+
let candidate = path19.join(dir, `${base}${ext}`);
|
|
15101
|
+
if (!fs17.existsSync(candidate)) return candidate;
|
|
16128
15102
|
let i = 1;
|
|
16129
|
-
while (
|
|
16130
|
-
candidate =
|
|
15103
|
+
while (fs17.existsSync(candidate)) {
|
|
15104
|
+
candidate = path19.join(dir, `${base}-${i}${ext}`);
|
|
16131
15105
|
i++;
|
|
16132
15106
|
}
|
|
16133
15107
|
return candidate;
|
|
@@ -16137,13 +15111,13 @@ var ZipOutput = class {
|
|
|
16137
15111
|
label = "Save as .zip to Downloads";
|
|
16138
15112
|
async emit(group, options) {
|
|
16139
15113
|
const downloadsDir = getDownloadsFolder();
|
|
16140
|
-
const repoName = sanitizeFilename(
|
|
15114
|
+
const repoName = sanitizeFilename(path19.basename(group.repoPath));
|
|
16141
15115
|
const timeRange = options.timeRange;
|
|
16142
15116
|
const rangePart = timeRange?.label ?? "all";
|
|
16143
15117
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
16144
15118
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
16145
15119
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
16146
|
-
const output =
|
|
15120
|
+
const output = fs17.createWriteStream(outputPath);
|
|
16147
15121
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
16148
15122
|
const done = new Promise((resolve, reject) => {
|
|
16149
15123
|
output.on("close", resolve);
|
|
@@ -16337,15 +15311,15 @@ async function confirmExport(group, output) {
|
|
|
16337
15311
|
}
|
|
16338
15312
|
|
|
16339
15313
|
// src/sources/claude.ts
|
|
16340
|
-
import
|
|
15314
|
+
import fs18 from "fs";
|
|
16341
15315
|
import os10 from "os";
|
|
16342
|
-
import
|
|
15316
|
+
import path20 from "path";
|
|
16343
15317
|
import readline2 from "readline";
|
|
16344
15318
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
16345
15319
|
async function resolveRepoPath(projectDir) {
|
|
16346
|
-
const indexPath =
|
|
15320
|
+
const indexPath = path20.join(projectDir, "sessions-index.json");
|
|
16347
15321
|
try {
|
|
16348
|
-
const raw = await
|
|
15322
|
+
const raw = await fs18.promises.readFile(indexPath, "utf-8");
|
|
16349
15323
|
const data = JSON.parse(raw);
|
|
16350
15324
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
16351
15325
|
return data.originalPath;
|
|
@@ -16353,12 +15327,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
16353
15327
|
} catch {
|
|
16354
15328
|
}
|
|
16355
15329
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
16356
|
-
const entries = await
|
|
15330
|
+
const entries = await fs18.promises.readdir(projectDir, {
|
|
16357
15331
|
withFileTypes: true
|
|
16358
15332
|
});
|
|
16359
15333
|
for (const entry of entries) {
|
|
16360
15334
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
16361
|
-
const cwd = await extractCwdFromJsonl(
|
|
15335
|
+
const cwd = await extractCwdFromJsonl(path20.join(projectDir, entry.name));
|
|
16362
15336
|
if (cwd) {
|
|
16363
15337
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
16364
15338
|
}
|
|
@@ -16377,7 +15351,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
16377
15351
|
return null;
|
|
16378
15352
|
}
|
|
16379
15353
|
async function extractCwdFromJsonl(filePath) {
|
|
16380
|
-
const stream =
|
|
15354
|
+
const stream = fs18.createReadStream(filePath, { encoding: "utf-8" });
|
|
16381
15355
|
const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
|
|
16382
15356
|
try {
|
|
16383
15357
|
for await (const line of rl) {
|
|
@@ -16399,12 +15373,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
16399
15373
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
16400
15374
|
let entries;
|
|
16401
15375
|
try {
|
|
16402
|
-
entries = await
|
|
15376
|
+
entries = await fs18.promises.readdir(dir, { withFileTypes: true });
|
|
16403
15377
|
} catch {
|
|
16404
15378
|
return;
|
|
16405
15379
|
}
|
|
16406
15380
|
for (const entry of entries) {
|
|
16407
|
-
const fullPath =
|
|
15381
|
+
const fullPath = path20.join(dir, entry.name);
|
|
16408
15382
|
if (entry.isDirectory()) {
|
|
16409
15383
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
16410
15384
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -16426,19 +15400,19 @@ function fallbackDecode(encodedName) {
|
|
|
16426
15400
|
var ClaudeSource = class {
|
|
16427
15401
|
name = "claude";
|
|
16428
15402
|
async scan() {
|
|
16429
|
-
const baseDir =
|
|
15403
|
+
const baseDir = path20.join(os10.homedir(), ".claude", "projects");
|
|
16430
15404
|
try {
|
|
16431
|
-
await
|
|
15405
|
+
await fs18.promises.access(baseDir);
|
|
16432
15406
|
} catch {
|
|
16433
15407
|
return [];
|
|
16434
15408
|
}
|
|
16435
|
-
const projectDirs = await
|
|
15409
|
+
const projectDirs = await fs18.promises.readdir(baseDir, {
|
|
16436
15410
|
withFileTypes: true
|
|
16437
15411
|
});
|
|
16438
15412
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
16439
15413
|
const resultArrays = await Promise.all(
|
|
16440
15414
|
dirEntries.map(async (dir) => {
|
|
16441
|
-
const projectPath =
|
|
15415
|
+
const projectPath = path20.join(baseDir, dir.name);
|
|
16442
15416
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
16443
15417
|
const files = [];
|
|
16444
15418
|
await collectFiles(
|
|
@@ -16456,12 +15430,12 @@ var ClaudeSource = class {
|
|
|
16456
15430
|
};
|
|
16457
15431
|
|
|
16458
15432
|
// src/sources/codex.ts
|
|
16459
|
-
import
|
|
15433
|
+
import fs19 from "fs";
|
|
16460
15434
|
import os11 from "os";
|
|
16461
|
-
import
|
|
15435
|
+
import path21 from "path";
|
|
16462
15436
|
import readline3 from "readline";
|
|
16463
15437
|
async function parseSessionMeta(filePath) {
|
|
16464
|
-
const stream =
|
|
15438
|
+
const stream = fs19.createReadStream(filePath, { encoding: "utf-8" });
|
|
16465
15439
|
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
16466
15440
|
try {
|
|
16467
15441
|
for await (const line of rl) {
|
|
@@ -16486,12 +15460,12 @@ async function findJsonlFiles(dir) {
|
|
|
16486
15460
|
async function walk(d) {
|
|
16487
15461
|
let entries;
|
|
16488
15462
|
try {
|
|
16489
|
-
entries = await
|
|
15463
|
+
entries = await fs19.promises.readdir(d, { withFileTypes: true });
|
|
16490
15464
|
} catch {
|
|
16491
15465
|
return;
|
|
16492
15466
|
}
|
|
16493
15467
|
for (const entry of entries) {
|
|
16494
|
-
const full =
|
|
15468
|
+
const full = path21.join(d, entry.name);
|
|
16495
15469
|
if (entry.isDirectory()) {
|
|
16496
15470
|
await walk(full);
|
|
16497
15471
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -16505,11 +15479,11 @@ async function findJsonlFiles(dir) {
|
|
|
16505
15479
|
async function loadHistory(historyPath) {
|
|
16506
15480
|
const map = /* @__PURE__ */ new Map();
|
|
16507
15481
|
try {
|
|
16508
|
-
await
|
|
15482
|
+
await fs19.promises.access(historyPath);
|
|
16509
15483
|
} catch {
|
|
16510
15484
|
return map;
|
|
16511
15485
|
}
|
|
16512
|
-
const stream =
|
|
15486
|
+
const stream = fs19.createReadStream(historyPath, { encoding: "utf-8" });
|
|
16513
15487
|
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
16514
15488
|
try {
|
|
16515
15489
|
for await (const line of rl) {
|
|
@@ -16536,14 +15510,14 @@ async function loadHistory(historyPath) {
|
|
|
16536
15510
|
var CodexSource = class {
|
|
16537
15511
|
name = "codex";
|
|
16538
15512
|
async scan() {
|
|
16539
|
-
const codexDir =
|
|
16540
|
-
const sessionsDir =
|
|
15513
|
+
const codexDir = path21.join(os11.homedir(), ".codex");
|
|
15514
|
+
const sessionsDir = path21.join(codexDir, "sessions");
|
|
16541
15515
|
try {
|
|
16542
|
-
await
|
|
15516
|
+
await fs19.promises.access(sessionsDir);
|
|
16543
15517
|
} catch {
|
|
16544
15518
|
return [];
|
|
16545
15519
|
}
|
|
16546
|
-
const historyPath =
|
|
15520
|
+
const historyPath = path21.join(codexDir, "history.jsonl");
|
|
16547
15521
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
16548
15522
|
findJsonlFiles(sessionsDir),
|
|
16549
15523
|
loadHistory(historyPath)
|
|
@@ -16566,8 +15540,8 @@ var CodexSource = class {
|
|
|
16566
15540
|
});
|
|
16567
15541
|
const historyLines = historyMap.get(meta.sessionId);
|
|
16568
15542
|
if (historyLines) {
|
|
16569
|
-
const sessionDir =
|
|
16570
|
-
const historyAbsPath =
|
|
15543
|
+
const sessionDir = path21.relative(sessionsDir, path21.dirname(filePath));
|
|
15544
|
+
const historyAbsPath = path21.join(
|
|
16571
15545
|
sessionsDir,
|
|
16572
15546
|
sessionDir,
|
|
16573
15547
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -16587,18 +15561,18 @@ var CodexSource = class {
|
|
|
16587
15561
|
};
|
|
16588
15562
|
|
|
16589
15563
|
// src/sources/copilotChat.ts
|
|
16590
|
-
import
|
|
15564
|
+
import fs20 from "fs";
|
|
16591
15565
|
import os12 from "os";
|
|
16592
|
-
import
|
|
15566
|
+
import path22 from "path";
|
|
16593
15567
|
import { fileURLToPath } from "url";
|
|
16594
15568
|
function vsCodeUserDirs() {
|
|
16595
15569
|
const home = os12.homedir();
|
|
16596
15570
|
const dirs = [
|
|
16597
|
-
|
|
16598
|
-
|
|
15571
|
+
path22.join(home, "Library", "Application Support", "Code", "User"),
|
|
15572
|
+
path22.join(home, ".config", "Code", "User")
|
|
16599
15573
|
];
|
|
16600
15574
|
if (process.env.APPDATA) {
|
|
16601
|
-
dirs.push(
|
|
15575
|
+
dirs.push(path22.join(process.env.APPDATA, "Code", "User"));
|
|
16602
15576
|
}
|
|
16603
15577
|
return dirs;
|
|
16604
15578
|
}
|
|
@@ -16613,7 +15587,7 @@ function uriToFsPath(uri) {
|
|
|
16613
15587
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
16614
15588
|
let raw;
|
|
16615
15589
|
try {
|
|
16616
|
-
raw = await
|
|
15590
|
+
raw = await fs20.promises.readFile(workspaceJsonPath, "utf-8");
|
|
16617
15591
|
} catch {
|
|
16618
15592
|
return null;
|
|
16619
15593
|
}
|
|
@@ -16635,10 +15609,10 @@ var CopilotChatSource = class {
|
|
|
16635
15609
|
async scan() {
|
|
16636
15610
|
const results = [];
|
|
16637
15611
|
for (const userDir of vsCodeUserDirs()) {
|
|
16638
|
-
const workspaceStorage =
|
|
15612
|
+
const workspaceStorage = path22.join(userDir, "workspaceStorage");
|
|
16639
15613
|
let hashDirs;
|
|
16640
15614
|
try {
|
|
16641
|
-
hashDirs = await
|
|
15615
|
+
hashDirs = await fs20.promises.readdir(workspaceStorage, {
|
|
16642
15616
|
withFileTypes: true
|
|
16643
15617
|
});
|
|
16644
15618
|
} catch {
|
|
@@ -16646,22 +15620,22 @@ var CopilotChatSource = class {
|
|
|
16646
15620
|
}
|
|
16647
15621
|
for (const hash of hashDirs) {
|
|
16648
15622
|
if (!hash.isDirectory()) continue;
|
|
16649
|
-
const wsRoot =
|
|
16650
|
-
const transcriptsDir =
|
|
15623
|
+
const wsRoot = path22.join(workspaceStorage, hash.name);
|
|
15624
|
+
const transcriptsDir = path22.join(
|
|
16651
15625
|
wsRoot,
|
|
16652
15626
|
"GitHub.copilot-chat",
|
|
16653
15627
|
"transcripts"
|
|
16654
15628
|
);
|
|
16655
15629
|
let transcriptEntries;
|
|
16656
15630
|
try {
|
|
16657
|
-
transcriptEntries = await
|
|
15631
|
+
transcriptEntries = await fs20.promises.readdir(transcriptsDir, {
|
|
16658
15632
|
withFileTypes: true
|
|
16659
15633
|
});
|
|
16660
15634
|
} catch {
|
|
16661
15635
|
continue;
|
|
16662
15636
|
}
|
|
16663
15637
|
const repoPath = await readWorkspaceFolder(
|
|
16664
|
-
|
|
15638
|
+
path22.join(wsRoot, "workspace.json")
|
|
16665
15639
|
);
|
|
16666
15640
|
if (!repoPath) continue;
|
|
16667
15641
|
for (const entry of transcriptEntries) {
|
|
@@ -16669,7 +15643,7 @@ var CopilotChatSource = class {
|
|
|
16669
15643
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
16670
15644
|
results.push({
|
|
16671
15645
|
sourceName: this.name,
|
|
16672
|
-
absolutePath:
|
|
15646
|
+
absolutePath: path22.join(transcriptsDir, entry.name),
|
|
16673
15647
|
repoPath,
|
|
16674
15648
|
metadata: { sessionId }
|
|
16675
15649
|
});
|
|
@@ -16709,7 +15683,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
16709
15683
|
async function filterByTimeRange(group, range) {
|
|
16710
15684
|
const results = await Promise.all(
|
|
16711
15685
|
group.files.map(
|
|
16712
|
-
(f) =>
|
|
15686
|
+
(f) => fs21.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
16713
15687
|
)
|
|
16714
15688
|
);
|
|
16715
15689
|
const filtered = [];
|
|
@@ -16736,10 +15710,10 @@ async function runInteractive() {
|
|
|
16736
15710
|
s.start(`Scanning ${source.name} logs...`);
|
|
16737
15711
|
const allFiles = await source.scan();
|
|
16738
15712
|
const allGroups = await mergeByRepo(allFiles);
|
|
16739
|
-
const repoRoot =
|
|
15713
|
+
const repoRoot = path23.resolve(repo.root);
|
|
16740
15714
|
const matching = allGroups.filter((g) => {
|
|
16741
|
-
const resolved =
|
|
16742
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
15715
|
+
const resolved = path23.resolve(g.repoPath);
|
|
15716
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path23.sep);
|
|
16743
15717
|
});
|
|
16744
15718
|
if (matching.length === 0) {
|
|
16745
15719
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -16770,7 +15744,7 @@ async function runInteractive() {
|
|
|
16770
15744
|
}
|
|
16771
15745
|
}
|
|
16772
15746
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
16773
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
15747
|
+
const envFilePaths = envFileNames.map((n) => path23.join(repoRoot, n));
|
|
16774
15748
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
16775
15749
|
const secretResult = await collectSecrets(
|
|
16776
15750
|
repoRoot,
|