vibe-replay 0.2.0 → 0.2.2
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 +14 -1
- package/assets/viewer.html +40 -39
- package/dist/index.js +2465 -521
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -40,8 +40,15 @@ function normalizeTitle(value) {
|
|
|
40
40
|
return cleaned || void 0;
|
|
41
41
|
}
|
|
42
42
|
function extractToolFilePath(input) {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
return extractToolFilePaths(input)[0];
|
|
44
|
+
}
|
|
45
|
+
function extractToolFilePaths(input) {
|
|
46
|
+
if (!input) return [];
|
|
47
|
+
const plural2 = input.file_paths ?? input.filePaths ?? input.paths;
|
|
48
|
+
const paths = Array.isArray(plural2) ? plural2.filter((fp) => typeof fp === "string" && fp.trim().length > 0) : [];
|
|
49
|
+
const singular = input.file_path ?? input.filePath ?? input.path ?? input.relativeWorkspacePath;
|
|
50
|
+
if (typeof singular === "string" && singular.trim()) paths.unshift(singular);
|
|
51
|
+
return [...new Set(paths)];
|
|
45
52
|
}
|
|
46
53
|
function localDayKey(input) {
|
|
47
54
|
if (input == null || input === "") return void 0;
|
|
@@ -68,13 +75,13 @@ var init_utils = __esm({
|
|
|
68
75
|
});
|
|
69
76
|
|
|
70
77
|
// src/overlays.ts
|
|
71
|
-
import { readFile as
|
|
72
|
-
import { join as
|
|
78
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
79
|
+
import { join as join13, resolve } from "path";
|
|
73
80
|
async function loadOverlays(baseDir, slug) {
|
|
74
|
-
const dirs = [
|
|
81
|
+
const dirs = [join13(baseDir, slug), resolve("./vibe-replay", slug)];
|
|
75
82
|
for (const dir of dirs) {
|
|
76
83
|
try {
|
|
77
|
-
const raw = await
|
|
84
|
+
const raw = await readFile14(join13(dir, "overlays.json"), "utf-8");
|
|
78
85
|
const parsed = JSON.parse(raw);
|
|
79
86
|
if (parsed && typeof parsed === "object" && Array.isArray(parsed.overlays)) {
|
|
80
87
|
return parsed;
|
|
@@ -133,9 +140,9 @@ __export(cloud_exports, {
|
|
|
133
140
|
saveAuthTokenSync: () => saveAuthTokenSync
|
|
134
141
|
});
|
|
135
142
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
136
|
-
import { mkdir as mkdir3, readFile as
|
|
137
|
-
import { homedir as
|
|
138
|
-
import { basename as basename5, dirname as dirname3, join as
|
|
143
|
+
import { mkdir as mkdir3, readFile as readFile15, unlink, writeFile as writeFile3 } from "fs/promises";
|
|
144
|
+
import { homedir as homedir12 } from "os";
|
|
145
|
+
import { basename as basename5, dirname as dirname3, join as join14 } from "path";
|
|
139
146
|
function authOrigin(url) {
|
|
140
147
|
try {
|
|
141
148
|
return new URL(url).origin;
|
|
@@ -225,7 +232,7 @@ function getAuthFilePath() {
|
|
|
225
232
|
return AUTH_FILE;
|
|
226
233
|
}
|
|
227
234
|
function getApiUrl() {
|
|
228
|
-
return process.env.VIBE_REPLAY_API_URL || DEFAULT_API_URL;
|
|
235
|
+
return (process.env.VIBE_REPLAY_API_URL || DEFAULT_API_URL).replace(/\/$/, "");
|
|
229
236
|
}
|
|
230
237
|
function getSessionCookieName(apiUrl) {
|
|
231
238
|
const url = apiUrl || getApiUrl();
|
|
@@ -236,8 +243,8 @@ async function publishCloud(outputDir, opts) {
|
|
|
236
243
|
if (!auth) {
|
|
237
244
|
throw new Error("Not logged in. Run `vibe-replay auth login` first.");
|
|
238
245
|
}
|
|
239
|
-
const jsonPath =
|
|
240
|
-
const content = await
|
|
246
|
+
const jsonPath = join14(outputDir, "replay.json");
|
|
247
|
+
const content = await readFile15(jsonPath, "utf-8");
|
|
241
248
|
const replay = JSON.parse(content);
|
|
242
249
|
const sizeBytes = Buffer.byteLength(content, "utf-8");
|
|
243
250
|
const MAX_SIZE = 10 * 1024 * 1024;
|
|
@@ -284,8 +291,8 @@ async function publishCloudWithOverlays(outputDir, opts) {
|
|
|
284
291
|
if (overlays.overlays.length === 0) {
|
|
285
292
|
return publishCloud(outputDir, opts);
|
|
286
293
|
}
|
|
287
|
-
const replayPath =
|
|
288
|
-
const originalContent = await
|
|
294
|
+
const replayPath = join14(outputDir, "replay.json");
|
|
295
|
+
const originalContent = await readFile15(replayPath, "utf-8");
|
|
289
296
|
const session = JSON.parse(originalContent);
|
|
290
297
|
const merged = sessionWithEffectiveContent(session, overlays);
|
|
291
298
|
await writeFile3(replayPath, JSON.stringify(merged), "utf-8");
|
|
@@ -297,7 +304,7 @@ async function publishCloudWithOverlays(outputDir, opts) {
|
|
|
297
304
|
}
|
|
298
305
|
async function loadSavedCloudInfo(outputDir) {
|
|
299
306
|
try {
|
|
300
|
-
const raw = await
|
|
307
|
+
const raw = await readFile15(join14(outputDir, CLOUD_META_FILE), "utf-8");
|
|
301
308
|
const parsed = JSON.parse(raw);
|
|
302
309
|
if (!parsed?.id || !parsed?.url) return void 0;
|
|
303
310
|
return parsed;
|
|
@@ -306,7 +313,7 @@ async function loadSavedCloudInfo(outputDir) {
|
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
async function saveCloudInfo(outputDir, info) {
|
|
309
|
-
await writeFile3(
|
|
316
|
+
await writeFile3(join14(outputDir, CLOUD_META_FILE), JSON.stringify(info, null, 2), "utf-8");
|
|
310
317
|
}
|
|
311
318
|
var CLOUD_META_FILE, DEFAULT_API_URL, AUTH_DIR, AUTH_FILE;
|
|
312
319
|
var init_cloud = __esm({
|
|
@@ -315,8 +322,8 @@ var init_cloud = __esm({
|
|
|
315
322
|
init_overlays();
|
|
316
323
|
CLOUD_META_FILE = ".vibe-replay-cloud.json";
|
|
317
324
|
DEFAULT_API_URL = "https://vibe-replay.com";
|
|
318
|
-
AUTH_DIR =
|
|
319
|
-
AUTH_FILE =
|
|
325
|
+
AUTH_DIR = join14(homedir12(), ".config", "vibe-replay");
|
|
326
|
+
AUTH_FILE = join14(AUTH_DIR, "auth.json");
|
|
320
327
|
}
|
|
321
328
|
});
|
|
322
329
|
|
|
@@ -399,12 +406,12 @@ __export(insights_exports, {
|
|
|
399
406
|
scanResultToInsight: () => scanResultToInsight,
|
|
400
407
|
writeInsightsStore: () => writeInsightsStore
|
|
401
408
|
});
|
|
402
|
-
import { mkdir as mkdir4, readFile as
|
|
403
|
-
import { homedir as
|
|
404
|
-
import { dirname as dirname4, join as
|
|
409
|
+
import { mkdir as mkdir4, readFile as readFile17, rename, writeFile as writeFile5 } from "fs/promises";
|
|
410
|
+
import { homedir as homedir13 } from "os";
|
|
411
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
405
412
|
async function readInsightsStore() {
|
|
406
413
|
try {
|
|
407
|
-
const raw = await
|
|
414
|
+
const raw = await readFile17(STORE_PATH, "utf-8");
|
|
408
415
|
const parsed = JSON.parse(raw);
|
|
409
416
|
return migrateInsightsStore(parsed);
|
|
410
417
|
} catch {
|
|
@@ -600,8 +607,8 @@ var init_insights = __esm({
|
|
|
600
607
|
init_machine_id();
|
|
601
608
|
init_utils();
|
|
602
609
|
init_version();
|
|
603
|
-
INSIGHTS_DIR =
|
|
604
|
-
STORE_PATH =
|
|
610
|
+
INSIGHTS_DIR = join16(homedir13(), ".vibe-replay", "insights");
|
|
611
|
+
STORE_PATH = join16(INSIGHTS_DIR, "store.json");
|
|
605
612
|
}
|
|
606
613
|
});
|
|
607
614
|
|
|
@@ -834,7 +841,7 @@ function generateGitHubMarkdown(session, opts = {}) {
|
|
|
834
841
|
}
|
|
835
842
|
lines.push("");
|
|
836
843
|
}
|
|
837
|
-
const risks = detectRiskSignals(session
|
|
844
|
+
const risks = detectRiskSignals(session, filesChanged);
|
|
838
845
|
if (risks.length > 0) {
|
|
839
846
|
lines.push("**Signals:**");
|
|
840
847
|
for (const risk of risks) {
|
|
@@ -967,9 +974,9 @@ function parseToolCall(scene) {
|
|
|
967
974
|
case "View":
|
|
968
975
|
return { kind: "read", file: baseName(input.file_path || input.path || "") };
|
|
969
976
|
case "Edit":
|
|
970
|
-
return { kind: "edit", file: baseName(input
|
|
977
|
+
return { kind: "edit", file: baseName(firstFilePath(input) || "") };
|
|
971
978
|
case "Write":
|
|
972
|
-
return { kind: "create", file: baseName(input
|
|
979
|
+
return { kind: "create", file: baseName(firstFilePath(input) || "") };
|
|
973
980
|
case "Bash": {
|
|
974
981
|
const cmd = (input.command || "").split("\n")[0];
|
|
975
982
|
const lower = result.toLowerCase();
|
|
@@ -1040,8 +1047,9 @@ function groupActions(raw) {
|
|
|
1040
1047
|
flushReads();
|
|
1041
1048
|
return grouped;
|
|
1042
1049
|
}
|
|
1043
|
-
function detectRiskSignals(
|
|
1050
|
+
function detectRiskSignals(session, filesChanged) {
|
|
1044
1051
|
const risks = [];
|
|
1052
|
+
const { scenes } = session;
|
|
1045
1053
|
for (const [file, count] of filesChanged) {
|
|
1046
1054
|
if (count >= 4) {
|
|
1047
1055
|
risks.push(`\`${file}\` modified ${count}x`);
|
|
@@ -1066,7 +1074,7 @@ function detectRiskSignals(scenes, filesChanged) {
|
|
|
1066
1074
|
const resolved = testPasses > 0 ? " (resolved)" : "";
|
|
1067
1075
|
risks.push(`${testFailures} test failure(s)${resolved}`);
|
|
1068
1076
|
}
|
|
1069
|
-
if (scenes.some((s) => s.type === "compaction-summary")) {
|
|
1077
|
+
if (scenes.some((s) => s.type === "compaction-summary") || (session.meta.compactions?.length || 0) > 0) {
|
|
1070
1078
|
risks.push("Session was compacted (very long conversation)");
|
|
1071
1079
|
}
|
|
1072
1080
|
return risks;
|
|
@@ -1180,6 +1188,17 @@ function stripMarkdown(s) {
|
|
|
1180
1188
|
function selectKeyPhases(phases, max) {
|
|
1181
1189
|
return phases.slice(0, max);
|
|
1182
1190
|
}
|
|
1191
|
+
function buildSvgFooter(session, opts) {
|
|
1192
|
+
const duration = formatDuration(session.meta.stats.durationMs);
|
|
1193
|
+
const tStats = computeToolStats(session.scenes);
|
|
1194
|
+
const parts = [duration];
|
|
1195
|
+
if (tStats.responses > 0) parts.push(`${tStats.responses} responses`);
|
|
1196
|
+
if (tStats.totalTools > 0) parts.push(`${tStats.totalTools} tools`);
|
|
1197
|
+
return {
|
|
1198
|
+
footerLeft: parts.filter(Boolean).join(" \xB7 "),
|
|
1199
|
+
footerRight: opts.replayUrl ? "View full replay \u2192" : "vibe-replay.com"
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1183
1202
|
function renderSvg(frames, session, opts = {}) {
|
|
1184
1203
|
const N = frames.length;
|
|
1185
1204
|
const secPerFrame = 5;
|
|
@@ -1193,13 +1212,7 @@ function renderSvg(frames, session, opts = {}) {
|
|
|
1193
1212
|
const fill = i === 0 ? " animation-fill-mode: backwards;" : "";
|
|
1194
1213
|
return ` .frame-${i} { animation: fadeInOut ${cycleDuration}s ${delay}s infinite;${fill} }`;
|
|
1195
1214
|
}).join("\n");
|
|
1196
|
-
const
|
|
1197
|
-
const tStats = computeToolStats(session.scenes);
|
|
1198
|
-
const footerParts = [duration];
|
|
1199
|
-
if (tStats.responses > 0) footerParts.push(`${tStats.responses} responses`);
|
|
1200
|
-
if (tStats.totalTools > 0) footerParts.push(`${tStats.totalTools} tools`);
|
|
1201
|
-
const footerLeft = footerParts.filter(Boolean).join(" \xB7 ");
|
|
1202
|
-
const footerRight = opts.replayUrl ? "View full replay \u2192" : "vibe-replay.com";
|
|
1215
|
+
const { footerLeft, footerRight } = buildSvgFooter(session, opts);
|
|
1203
1216
|
const framesSvg = frames.map((f, i) => renderFrame(f, i)).join("\n\n");
|
|
1204
1217
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SVG_W}" height="${SVG_H}" viewBox="0 0 ${SVG_W} ${SVG_H}">
|
|
1205
1218
|
<style>
|
|
@@ -1383,13 +1396,7 @@ function renderTurnFrame(frame, _index, gAttr) {
|
|
|
1383
1396
|
return parts.join("\n");
|
|
1384
1397
|
}
|
|
1385
1398
|
function renderStaticFrameSvg(frame, session, opts = {}) {
|
|
1386
|
-
const
|
|
1387
|
-
const tStats = computeToolStats(session.scenes);
|
|
1388
|
-
const footerParts = [duration];
|
|
1389
|
-
if (tStats.responses > 0) footerParts.push(`${tStats.responses} responses`);
|
|
1390
|
-
if (tStats.totalTools > 0) footerParts.push(`${tStats.totalTools} tools`);
|
|
1391
|
-
const footerLeft = footerParts.filter(Boolean).join(" \xB7 ");
|
|
1392
|
-
const footerRight = opts.replayUrl ? "View full replay \u2192" : "vibe-replay.com";
|
|
1399
|
+
const { footerLeft, footerRight } = buildSvgFooter(session, opts);
|
|
1393
1400
|
let frameContent;
|
|
1394
1401
|
if (frame.userPrompt != null) {
|
|
1395
1402
|
frameContent = renderTurnFrame(frame, 0, "");
|
|
@@ -1426,8 +1433,7 @@ function collectFilesChanged(scenes) {
|
|
|
1426
1433
|
for (const scene of scenes) {
|
|
1427
1434
|
if (scene.type !== "tool-call") continue;
|
|
1428
1435
|
if (scene.toolName === "Edit" || scene.toolName === "Write") {
|
|
1429
|
-
const fp
|
|
1430
|
-
if (fp) {
|
|
1436
|
+
for (const fp of filePathsFromInput(scene.input)) {
|
|
1431
1437
|
const name = shortPath(fp);
|
|
1432
1438
|
files.set(name, (files.get(name) || 0) + 1);
|
|
1433
1439
|
}
|
|
@@ -1435,6 +1441,16 @@ function collectFilesChanged(scenes) {
|
|
|
1435
1441
|
}
|
|
1436
1442
|
return files;
|
|
1437
1443
|
}
|
|
1444
|
+
function firstFilePath(input) {
|
|
1445
|
+
return filePathsFromInput(input)[0];
|
|
1446
|
+
}
|
|
1447
|
+
function filePathsFromInput(input) {
|
|
1448
|
+
const plural2 = input.file_paths || input.filePaths || input.paths;
|
|
1449
|
+
const paths = Array.isArray(plural2) ? plural2.filter((fp) => typeof fp === "string" && fp.trim().length > 0) : [];
|
|
1450
|
+
const singular = input.file_path || input.filePath || input.path || input.relativeWorkspacePath;
|
|
1451
|
+
if (typeof singular === "string" && singular.trim()) paths.unshift(singular);
|
|
1452
|
+
return [...new Set(paths)];
|
|
1453
|
+
}
|
|
1438
1454
|
function shortPath(fullPath) {
|
|
1439
1455
|
const parts = fullPath.split("/");
|
|
1440
1456
|
if (parts.length <= 2) return parts.join("/");
|
|
@@ -1852,13 +1868,37 @@ function estimateActiveDuration(timestamps, maxGapMs = DEFAULT_MAX_GAP_MS) {
|
|
|
1852
1868
|
return active > 0 ? active : void 0;
|
|
1853
1869
|
}
|
|
1854
1870
|
|
|
1871
|
+
// src/providers/warnings.ts
|
|
1872
|
+
function addParseWarning(warnings, warning) {
|
|
1873
|
+
const existing = warnings.find(
|
|
1874
|
+
(w) => w.kind === warning.kind && w.source === warning.source && w.message === warning.message
|
|
1875
|
+
);
|
|
1876
|
+
if (existing) {
|
|
1877
|
+
existing.count += warning.count ?? 1;
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
warnings.push({
|
|
1881
|
+
kind: warning.kind,
|
|
1882
|
+
count: warning.count ?? 1,
|
|
1883
|
+
message: warning.message,
|
|
1884
|
+
source: warning.source,
|
|
1885
|
+
firstLine: warning.firstLine,
|
|
1886
|
+
sample: warning.kind === "malformed-json" ? void 0 : warning.sample
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
function compactWarningSample(value, max = 160) {
|
|
1890
|
+
const compacted = value.replace(/\s+/g, " ").trim();
|
|
1891
|
+
if (compacted.length <= max) return compacted;
|
|
1892
|
+
return `${compacted.slice(0, max)}...`;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1855
1895
|
// src/providers/claude-code/parser.ts
|
|
1856
1896
|
async function parseClaudeCodeSession(filePaths) {
|
|
1857
1897
|
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
|
1858
1898
|
const allLines = [];
|
|
1859
1899
|
for (const fp of paths) {
|
|
1860
1900
|
const content = await readFile4(fp, "utf-8");
|
|
1861
|
-
allLines.push(...content.split("\n")
|
|
1901
|
+
allLines.push(...content.split("\n"));
|
|
1862
1902
|
}
|
|
1863
1903
|
return parseClaudeCodeLines(allLines, { subagentsSourcePath: paths[0] });
|
|
1864
1904
|
}
|
|
@@ -1902,11 +1942,21 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
1902
1942
|
const toolImages = /* @__PURE__ */ new Map();
|
|
1903
1943
|
const userTurns = [];
|
|
1904
1944
|
const allTimestamps = [];
|
|
1905
|
-
|
|
1945
|
+
const parseWarnings = [];
|
|
1946
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
1947
|
+
const line = lines[lineIndex];
|
|
1948
|
+
if (!line.trim()) continue;
|
|
1906
1949
|
let obj;
|
|
1907
1950
|
try {
|
|
1908
1951
|
obj = JSON.parse(line);
|
|
1909
1952
|
} catch {
|
|
1953
|
+
addParseWarning(parseWarnings, {
|
|
1954
|
+
kind: "malformed-json",
|
|
1955
|
+
source: "claude-code JSONL",
|
|
1956
|
+
firstLine: lineIndex + 1,
|
|
1957
|
+
message: "Skipped malformed JSONL line",
|
|
1958
|
+
sample: line
|
|
1959
|
+
});
|
|
1910
1960
|
continue;
|
|
1911
1961
|
}
|
|
1912
1962
|
if (obj.timestamp) allTimestamps.push(obj.timestamp);
|
|
@@ -2013,6 +2063,14 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2013
2063
|
}
|
|
2014
2064
|
if (!obj.message) continue;
|
|
2015
2065
|
const { role, content: msgContent, id: msgId } = obj.message;
|
|
2066
|
+
if (obj.isApiErrorMessage && obj.timestamp) {
|
|
2067
|
+
const text = extractMessageText(msgContent);
|
|
2068
|
+
const errorType = inferApiErrorMessageType(text);
|
|
2069
|
+
apiErrors.push({
|
|
2070
|
+
timestamp: obj.timestamp,
|
|
2071
|
+
...errorType ? { errorType } : {}
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2016
2074
|
if (obj.isMeta && role === "user") {
|
|
2017
2075
|
const text = extractMessageText(msgContent);
|
|
2018
2076
|
if (text.trim()) {
|
|
@@ -2123,7 +2181,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2123
2181
|
}
|
|
2124
2182
|
}
|
|
2125
2183
|
}
|
|
2126
|
-
const subAgentData = options.subagentsSourcePath ? await readSubagents(options.subagentsSourcePath, usageByMsgId) : /* @__PURE__ */ new Map();
|
|
2184
|
+
const subAgentData = options.subagentsSourcePath ? await readSubagents(options.subagentsSourcePath, usageByMsgId, parseWarnings) : /* @__PURE__ */ new Map();
|
|
2127
2185
|
const assistantTurns = [];
|
|
2128
2186
|
for (const msgId of assistantOrder) {
|
|
2129
2187
|
const blocks = assistantBlocks.get(msgId);
|
|
@@ -2259,7 +2317,8 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2259
2317
|
mcpServersUsed: mcpServersUsed.size > 0 ? [...mcpServersUsed].sort() : void 0,
|
|
2260
2318
|
agentName,
|
|
2261
2319
|
worktree,
|
|
2262
|
-
queueOperationStats
|
|
2320
|
+
queueOperationStats,
|
|
2321
|
+
parseWarnings: parseWarnings.length > 0 ? parseWarnings : void 0
|
|
2263
2322
|
};
|
|
2264
2323
|
}
|
|
2265
2324
|
function extractMessageText(content) {
|
|
@@ -2269,6 +2328,13 @@ function extractMessageText(content) {
|
|
|
2269
2328
|
}
|
|
2270
2329
|
return "";
|
|
2271
2330
|
}
|
|
2331
|
+
function inferApiErrorMessageType(text) {
|
|
2332
|
+
const lower = text.toLowerCase();
|
|
2333
|
+
if (lower.includes("rate limit")) return "rate_limit_error";
|
|
2334
|
+
if (lower.includes("overloaded")) return "overloaded_error";
|
|
2335
|
+
if (lower.includes("api error")) return "api_error";
|
|
2336
|
+
return void 0;
|
|
2337
|
+
}
|
|
2272
2338
|
function extractImages(block) {
|
|
2273
2339
|
if (block.type !== "tool_result") return [];
|
|
2274
2340
|
const { content } = block;
|
|
@@ -2367,16 +2433,16 @@ function buildTurnStats(finalTurns, usageByMsgId, turnDurations) {
|
|
|
2367
2433
|
} else if (group.startTimestamp && group.endTimestamp) {
|
|
2368
2434
|
durationMs = estimateActiveDuration([group.startTimestamp, group.endTimestamp]);
|
|
2369
2435
|
}
|
|
2370
|
-
const
|
|
2371
|
-
if (turnModel)
|
|
2372
|
-
if (durationMs !== void 0)
|
|
2373
|
-
if (turnTokens)
|
|
2374
|
-
if (maxContextTokens > 0)
|
|
2375
|
-
stats.push(
|
|
2436
|
+
const stat11 = { turnIndex: i };
|
|
2437
|
+
if (turnModel) stat11.model = turnModel;
|
|
2438
|
+
if (durationMs !== void 0) stat11.durationMs = durationMs;
|
|
2439
|
+
if (turnTokens) stat11.tokenUsage = turnTokens;
|
|
2440
|
+
if (maxContextTokens > 0) stat11.contextTokens = maxContextTokens;
|
|
2441
|
+
stats.push(stat11);
|
|
2376
2442
|
}
|
|
2377
2443
|
return stats;
|
|
2378
2444
|
}
|
|
2379
|
-
async function readSubagents(mainFilePath, usageByMsgId) {
|
|
2445
|
+
async function readSubagents(mainFilePath, usageByMsgId, parseWarnings) {
|
|
2380
2446
|
const result = /* @__PURE__ */ new Map();
|
|
2381
2447
|
const sessionDir = mainFilePath.replace(/\.jsonl$/, "");
|
|
2382
2448
|
const subagentsDir = join5(sessionDir, "subagents");
|
|
@@ -2418,12 +2484,21 @@ async function readSubagents(mainFilePath, usageByMsgId) {
|
|
|
2418
2484
|
const saAssistantBlocks = /* @__PURE__ */ new Map();
|
|
2419
2485
|
const saAssistantOrder = [];
|
|
2420
2486
|
const saAssistantTimestamps = /* @__PURE__ */ new Map();
|
|
2487
|
+
let lineNumber = 0;
|
|
2421
2488
|
for (const line of content.split("\n")) {
|
|
2489
|
+
lineNumber++;
|
|
2422
2490
|
if (!line.trim()) continue;
|
|
2423
2491
|
let obj;
|
|
2424
2492
|
try {
|
|
2425
2493
|
obj = JSON.parse(line);
|
|
2426
2494
|
} catch {
|
|
2495
|
+
addParseWarning(parseWarnings, {
|
|
2496
|
+
kind: "malformed-json",
|
|
2497
|
+
source: "claude-code subagent JSONL",
|
|
2498
|
+
firstLine: lineNumber,
|
|
2499
|
+
message: "Skipped malformed subagent JSONL line",
|
|
2500
|
+
sample: line
|
|
2501
|
+
});
|
|
2427
2502
|
continue;
|
|
2428
2503
|
}
|
|
2429
2504
|
const msg = obj?.message;
|
|
@@ -2641,6 +2716,7 @@ async function extractCoworkSessionInfo(jsonPath) {
|
|
|
2641
2716
|
const timestamp = meta.lastActivityAt ? new Date(meta.lastActivityAt).toISOString() : meta.createdAt ? new Date(meta.createdAt).toISOString() : auditStat.mtime.toISOString();
|
|
2642
2717
|
const fileSize = auditStat.size;
|
|
2643
2718
|
const model = meta.model ? meta.model.replace(/\[[^\]]*\]$/, "") : void 0;
|
|
2719
|
+
const fsDetectedFiles = Array.isArray(meta.fsDetectedFiles) ? meta.fsDetectedFiles.filter((file) => typeof file === "string") : void 0;
|
|
2644
2720
|
const project = "Cowork";
|
|
2645
2721
|
return {
|
|
2646
2722
|
provider: "claude-cowork",
|
|
@@ -2659,7 +2735,13 @@ async function extractCoworkSessionInfo(jsonPath) {
|
|
|
2659
2735
|
prompts,
|
|
2660
2736
|
promptCount,
|
|
2661
2737
|
toolCallCount,
|
|
2662
|
-
model
|
|
2738
|
+
model,
|
|
2739
|
+
isStarred: meta.isStarred,
|
|
2740
|
+
spaceId: meta.spaceId,
|
|
2741
|
+
spaceIdSetBy: meta.spaceIdSetBy,
|
|
2742
|
+
pluginsEnabled: meta.pluginsEnabled,
|
|
2743
|
+
skillsEnabled: meta.skillsEnabled,
|
|
2744
|
+
fsDetectedFiles
|
|
2663
2745
|
};
|
|
2664
2746
|
}
|
|
2665
2747
|
function collectPromptsFromLines(lines, initialMessage) {
|
|
@@ -2694,11 +2776,12 @@ function collectPromptsFromLines(lines, initialMessage) {
|
|
|
2694
2776
|
|
|
2695
2777
|
// src/providers/claude-cowork/parser.ts
|
|
2696
2778
|
import { readFile as readFile6 } from "fs/promises";
|
|
2697
|
-
function normalizeCoworkLine(line) {
|
|
2779
|
+
function normalizeCoworkLine(line, onMalformedJson) {
|
|
2698
2780
|
let obj;
|
|
2699
2781
|
try {
|
|
2700
2782
|
obj = JSON.parse(line);
|
|
2701
2783
|
} catch {
|
|
2784
|
+
onMalformedJson?.();
|
|
2702
2785
|
return null;
|
|
2703
2786
|
}
|
|
2704
2787
|
if (!obj || typeof obj !== "object") return null;
|
|
@@ -2718,16 +2801,30 @@ function normalizeCoworkLine(line) {
|
|
|
2718
2801
|
async function parseClaudeCoworkSession(filePaths, sessionInfo) {
|
|
2719
2802
|
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
|
2720
2803
|
const normalized = [];
|
|
2804
|
+
const parseWarnings = [];
|
|
2721
2805
|
for (const fp of paths) {
|
|
2722
2806
|
const content = await readFile6(fp, "utf-8");
|
|
2723
|
-
|
|
2807
|
+
const lines = content.split("\n");
|
|
2808
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
2809
|
+
const raw = lines[lineIndex];
|
|
2724
2810
|
const t = raw.trim();
|
|
2725
2811
|
if (!t) continue;
|
|
2726
|
-
const line = normalizeCoworkLine(t)
|
|
2812
|
+
const line = normalizeCoworkLine(t, () => {
|
|
2813
|
+
addParseWarning(parseWarnings, {
|
|
2814
|
+
kind: "malformed-json",
|
|
2815
|
+
source: "claude-cowork audit JSONL",
|
|
2816
|
+
firstLine: lineIndex + 1,
|
|
2817
|
+
message: "Skipped malformed JSONL line",
|
|
2818
|
+
sample: t
|
|
2819
|
+
});
|
|
2820
|
+
});
|
|
2727
2821
|
if (line) normalized.push(line);
|
|
2728
2822
|
}
|
|
2729
2823
|
}
|
|
2730
2824
|
const result = await parseClaudeCodeLines(normalized);
|
|
2825
|
+
if (parseWarnings.length > 0) {
|
|
2826
|
+
result.parseWarnings = [...parseWarnings, ...result.parseWarnings || []];
|
|
2827
|
+
}
|
|
2731
2828
|
if (sessionInfo) {
|
|
2732
2829
|
if (!result.title && sessionInfo.title) result.title = sessionInfo.title;
|
|
2733
2830
|
if (!result.model && sessionInfo.model) result.model = sessionInfo.model;
|
|
@@ -2833,7 +2930,7 @@ var claudeDesktopProvider = {
|
|
|
2833
2930
|
|
|
2834
2931
|
// src/providers/codex/discover.ts
|
|
2835
2932
|
import { createReadStream as createReadStream3 } from "fs";
|
|
2836
|
-
import { readdir as readdir5, stat as stat4 } from "fs/promises";
|
|
2933
|
+
import { readFile as readFile8, readdir as readdir5, stat as stat4 } from "fs/promises";
|
|
2837
2934
|
import { homedir as homedir7 } from "os";
|
|
2838
2935
|
import { basename, join as join8 } from "path";
|
|
2839
2936
|
import { createInterface as createInterface3 } from "readline";
|
|
@@ -2841,6 +2938,47 @@ init_utils();
|
|
|
2841
2938
|
|
|
2842
2939
|
// src/providers/codex/constants.ts
|
|
2843
2940
|
var USER_MESSAGE_BEGIN = "## My request for Codex:";
|
|
2941
|
+
var CODEX_CONTEXT_TAGS = [
|
|
2942
|
+
"environment_context",
|
|
2943
|
+
"permissions instructions",
|
|
2944
|
+
"app-context",
|
|
2945
|
+
"collaboration_mode",
|
|
2946
|
+
"apps_instructions",
|
|
2947
|
+
"skills_instructions",
|
|
2948
|
+
"plugins_instructions"
|
|
2949
|
+
];
|
|
2950
|
+
function isCodexToolCallType(type) {
|
|
2951
|
+
return type === "function_call" || type === "local_shell_call" || type === "custom_tool_call" || type === "tool_search_call" || type === "web_search_call" || type === "image_generation_call";
|
|
2952
|
+
}
|
|
2953
|
+
function stripUserMessagePrefix(text) {
|
|
2954
|
+
const idx = text.indexOf(USER_MESSAGE_BEGIN);
|
|
2955
|
+
return idx === -1 ? text : text.slice(idx + USER_MESSAGE_BEGIN.length);
|
|
2956
|
+
}
|
|
2957
|
+
function stripLeadingCodexContextBlocks(text) {
|
|
2958
|
+
let remaining = text.trim();
|
|
2959
|
+
let stripped = true;
|
|
2960
|
+
while (stripped) {
|
|
2961
|
+
stripped = false;
|
|
2962
|
+
for (const tag of CODEX_CONTEXT_TAGS) {
|
|
2963
|
+
const open3 = `<${tag}>`;
|
|
2964
|
+
const close = `</${tag}>`;
|
|
2965
|
+
if (!remaining.startsWith(open3)) continue;
|
|
2966
|
+
const closeIndex = remaining.indexOf(close);
|
|
2967
|
+
if (closeIndex === -1) return "";
|
|
2968
|
+
remaining = remaining.slice(closeIndex + close.length).trim();
|
|
2969
|
+
stripped = true;
|
|
2970
|
+
break;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
return remaining;
|
|
2974
|
+
}
|
|
2975
|
+
function codexStripTwoPass(text) {
|
|
2976
|
+
let result = text;
|
|
2977
|
+
for (let i = 0; i < 2; i++) {
|
|
2978
|
+
result = stripUserMessagePrefix(stripLeadingCodexContextBlocks(result)).trim();
|
|
2979
|
+
}
|
|
2980
|
+
return result;
|
|
2981
|
+
}
|
|
2844
2982
|
|
|
2845
2983
|
// src/providers/codex/discover.ts
|
|
2846
2984
|
var STATE_DB_FILENAME = "state_5.sqlite";
|
|
@@ -2864,7 +3002,8 @@ async function sessionInfoFromThreadRow(row) {
|
|
|
2864
3002
|
const fileStat = await stat4(row.rollout_path).catch(() => null);
|
|
2865
3003
|
if (!fileStat?.isFile()) return null;
|
|
2866
3004
|
const extracted = await extractCodexSessionInfo(row.rollout_path, fileStat.size);
|
|
2867
|
-
const
|
|
3005
|
+
const rowFirstPrompt = row.first_user_message ? normalizeDiscoveredUserMessage(row.first_user_message) : "";
|
|
3006
|
+
const firstPrompt = rowFirstPrompt || extracted?.firstPrompt || "";
|
|
2868
3007
|
if (!firstPrompt) return extracted;
|
|
2869
3008
|
return {
|
|
2870
3009
|
provider: "codex",
|
|
@@ -2904,6 +3043,7 @@ async function extractCodexSessionInfo(filePath, fileSize) {
|
|
|
2904
3043
|
let editCountEst = 0;
|
|
2905
3044
|
let durationMsEst = 0;
|
|
2906
3045
|
const prompts = [];
|
|
3046
|
+
const promptSeen = /* @__PURE__ */ new Map();
|
|
2907
3047
|
let rl;
|
|
2908
3048
|
try {
|
|
2909
3049
|
const input = createReadStream3(filePath, { encoding: "utf-8" });
|
|
@@ -2937,13 +3077,11 @@ async function extractCodexSessionInfo(filePath, fileSize) {
|
|
|
2937
3077
|
const p = obj.payload || {};
|
|
2938
3078
|
if (p.type === "thread_name_updated" && p.thread_name) title = p.thread_name;
|
|
2939
3079
|
if (p.type === "user_message") {
|
|
2940
|
-
const
|
|
2941
|
-
const
|
|
2942
|
-
|
|
3080
|
+
const rawText = typeof p.message === "string" ? p.message : "";
|
|
3081
|
+
const cleaned = normalizeDiscoveredUserMessage(rawText);
|
|
3082
|
+
const imageKey = userImageDedupeKey(p);
|
|
3083
|
+
if (recordDiscoveredPrompt(promptSeen, prompts, obj.timestamp, cleaned, imageKey)) {
|
|
2943
3084
|
promptCount++;
|
|
2944
|
-
if (prompts.length < 2) {
|
|
2945
|
-
prompts.push((cleaned || "[Image]").slice(0, 200));
|
|
2946
|
-
}
|
|
2947
3085
|
}
|
|
2948
3086
|
}
|
|
2949
3087
|
if (p.type === "exec_command_end" && typeof p.duration?.secs === "number") {
|
|
@@ -2953,7 +3091,15 @@ async function extractCodexSessionInfo(filePath, fileSize) {
|
|
|
2953
3091
|
}
|
|
2954
3092
|
if (obj.type === "response_item") {
|
|
2955
3093
|
const p = obj.payload || {};
|
|
2956
|
-
if (p.type === "
|
|
3094
|
+
if (p.type === "message" && p.role === "user") {
|
|
3095
|
+
const rawText = contentText(p.content);
|
|
3096
|
+
const cleaned = normalizeDiscoveredUserMessage(rawText);
|
|
3097
|
+
const imageKey = contentImageDedupeKey(p.content);
|
|
3098
|
+
if (recordDiscoveredPrompt(promptSeen, prompts, obj.timestamp, cleaned, imageKey)) {
|
|
3099
|
+
promptCount++;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
if (isCodexToolCallType(p.type)) {
|
|
2957
3103
|
toolCallCount++;
|
|
2958
3104
|
if (isEditTool(p.name)) editCountEst++;
|
|
2959
3105
|
}
|
|
@@ -2999,7 +3145,7 @@ async function readThreadRowsFromStateDb() {
|
|
|
2999
3145
|
try {
|
|
3000
3146
|
const mod = await import("sql.js");
|
|
3001
3147
|
const SQL = await mod.default();
|
|
3002
|
-
const dbBuffer = await
|
|
3148
|
+
const dbBuffer = await readFile8(stateDbPath);
|
|
3003
3149
|
const db = new SQL.Database(dbBuffer);
|
|
3004
3150
|
try {
|
|
3005
3151
|
const result = db.exec(`
|
|
@@ -3062,28 +3208,74 @@ function toIsoFlexible(value) {
|
|
|
3062
3208
|
function isEditTool(name) {
|
|
3063
3209
|
return !!name && ["apply_patch", "edit", "write_file"].includes(name);
|
|
3064
3210
|
}
|
|
3065
|
-
function
|
|
3066
|
-
|
|
3067
|
-
|
|
3211
|
+
function normalizeDiscoveredUserMessage(text) {
|
|
3212
|
+
return cleanPromptText(codexStripTwoPass(text));
|
|
3213
|
+
}
|
|
3214
|
+
function recordDiscoveredPrompt(promptSeen, prompts, timestamp, text, imageKey) {
|
|
3215
|
+
const hasImages = imageKey.length > 0;
|
|
3216
|
+
if (isCodexContextMessage(text)) return false;
|
|
3217
|
+
if (text.length < 10 && !hasImages) return false;
|
|
3218
|
+
const key = `${text}:images:${imageKey}`;
|
|
3219
|
+
const time = timestamp ? Date.parse(timestamp) : Number.NaN;
|
|
3220
|
+
const previous = promptSeen.get(key) || [];
|
|
3221
|
+
const isDuplicate = Number.isNaN(time) ? previous.length > 0 : previous.some((prev) => Math.abs(time - prev) <= 2e3);
|
|
3222
|
+
if (isDuplicate) return false;
|
|
3223
|
+
promptSeen.set(key, [...previous, Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time]);
|
|
3224
|
+
if (prompts.length < 2) prompts.push((text || "[Image]").slice(0, 200));
|
|
3225
|
+
return true;
|
|
3068
3226
|
}
|
|
3069
|
-
function
|
|
3070
|
-
|
|
3227
|
+
function contentText(content) {
|
|
3228
|
+
if (typeof content === "string") return content;
|
|
3229
|
+
if (!Array.isArray(content)) return "";
|
|
3230
|
+
return content.map((part) => {
|
|
3231
|
+
if (typeof part === "string") return part;
|
|
3232
|
+
if (part?.type === "output_text" || part?.type === "input_text" || part?.type === "text") {
|
|
3233
|
+
return part.text || "";
|
|
3234
|
+
}
|
|
3235
|
+
return "";
|
|
3236
|
+
}).filter(Boolean).join("\n");
|
|
3237
|
+
}
|
|
3238
|
+
function isCodexContextMessage(text) {
|
|
3239
|
+
const trimmed = text.trim();
|
|
3240
|
+
return CODEX_CONTEXT_TAGS.some((tag) => trimmed.startsWith(`<${tag}>`));
|
|
3241
|
+
}
|
|
3242
|
+
function userImageDedupeKey(payload) {
|
|
3243
|
+
return [
|
|
3244
|
+
...Array.isArray(payload.images) ? payload.images : [],
|
|
3245
|
+
...Array.isArray(payload.local_images) ? payload.local_images : []
|
|
3246
|
+
].filter((image) => typeof image === "string" && image.length > 0).map(imageDedupeKey).join(",");
|
|
3247
|
+
}
|
|
3248
|
+
function contentImageDedupeKey(content) {
|
|
3249
|
+
if (!Array.isArray(content)) return "";
|
|
3250
|
+
return content.flatMap((part) => {
|
|
3251
|
+
if (part?.type !== "input_image" && part?.type !== "image" && part?.type !== "local_image") {
|
|
3252
|
+
return [];
|
|
3253
|
+
}
|
|
3254
|
+
const image = typeof part.image_url === "string" ? part.image_url : typeof part.image_url?.url === "string" ? part.image_url.url : typeof part.source?.data === "string" ? `data:${part.source.media_type || "image/png"};base64,${part.source.data}` : typeof part.path === "string" ? part.path : "";
|
|
3255
|
+
return image ? [image] : [];
|
|
3256
|
+
}).map(imageDedupeKey).join(",");
|
|
3257
|
+
}
|
|
3258
|
+
function imageDedupeKey(image) {
|
|
3259
|
+
return `${image.slice(0, 64)}:${image.length}:${image.slice(-32)}`;
|
|
3071
3260
|
}
|
|
3072
3261
|
|
|
3073
3262
|
// src/providers/codex/parser.ts
|
|
3074
3263
|
import { readFileSync } from "fs";
|
|
3075
|
-
import { readFile as
|
|
3264
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3076
3265
|
import { extname } from "path";
|
|
3077
3266
|
function asCodexTokenInfo(value) {
|
|
3078
3267
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3079
3268
|
return value;
|
|
3080
3269
|
}
|
|
3270
|
+
function asOptionalString(value) {
|
|
3271
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
3272
|
+
}
|
|
3081
3273
|
async function parseCodexSession(filePaths, sessionInfo) {
|
|
3082
3274
|
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
|
3083
3275
|
const lines = [];
|
|
3084
3276
|
for (const fp of paths) {
|
|
3085
|
-
const content = await
|
|
3086
|
-
lines.push(...content.split("\n")
|
|
3277
|
+
const content = await readFile9(fp, "utf-8");
|
|
3278
|
+
lines.push(...content.split("\n"));
|
|
3087
3279
|
}
|
|
3088
3280
|
return parseCodexLines(lines, sessionInfo, paths);
|
|
3089
3281
|
}
|
|
@@ -3099,20 +3291,34 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3099
3291
|
let entrypoint;
|
|
3100
3292
|
let permissionMode;
|
|
3101
3293
|
let approvalPolicy;
|
|
3294
|
+
let memoryMode;
|
|
3102
3295
|
const turns = [];
|
|
3103
3296
|
const allTimestamps = [];
|
|
3104
3297
|
const compactions = [];
|
|
3105
3298
|
const tools = /* @__PURE__ */ new Map();
|
|
3106
3299
|
const toolResults = /* @__PURE__ */ new Map();
|
|
3107
3300
|
const tokenSnapshots = [];
|
|
3301
|
+
const taskDurations = [];
|
|
3108
3302
|
const mcpServersUsed = /* @__PURE__ */ new Set();
|
|
3109
3303
|
const skillsUsed = /* @__PURE__ */ new Set();
|
|
3110
3304
|
const gitBranches = [];
|
|
3111
|
-
|
|
3305
|
+
const seenUserMessages = /* @__PURE__ */ new Map();
|
|
3306
|
+
const seenAssistantMessages = /* @__PURE__ */ new Map();
|
|
3307
|
+
const parseWarnings = [];
|
|
3308
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
3309
|
+
const line = lines[lineIndex];
|
|
3310
|
+
if (!line.trim()) continue;
|
|
3112
3311
|
let obj;
|
|
3113
3312
|
try {
|
|
3114
3313
|
obj = JSON.parse(line);
|
|
3115
3314
|
} catch {
|
|
3315
|
+
addParseWarning(parseWarnings, {
|
|
3316
|
+
kind: "malformed-json",
|
|
3317
|
+
source: "codex JSONL",
|
|
3318
|
+
firstLine: lineIndex + 1,
|
|
3319
|
+
message: "Skipped malformed JSONL line",
|
|
3320
|
+
sample: line
|
|
3321
|
+
});
|
|
3116
3322
|
continue;
|
|
3117
3323
|
}
|
|
3118
3324
|
if (obj.timestamp) {
|
|
@@ -3129,6 +3335,7 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3129
3335
|
gitBranch = gitBranch || p2.git?.branch || p2.git_branch;
|
|
3130
3336
|
if (gitBranch) pushUnique(gitBranches, gitBranch);
|
|
3131
3337
|
entrypoint = entrypoint || p2.originator || p2.source;
|
|
3338
|
+
memoryMode = memoryMode || asOptionalString(p2.memory_mode);
|
|
3132
3339
|
continue;
|
|
3133
3340
|
}
|
|
3134
3341
|
if (obj.type === "turn_context") {
|
|
@@ -3146,11 +3353,27 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3146
3353
|
}
|
|
3147
3354
|
if (p2.type === "user_message") {
|
|
3148
3355
|
const blocks = userMessageBlocks(p2);
|
|
3149
|
-
|
|
3150
|
-
|
|
3356
|
+
const text = textFromBlocks(blocks);
|
|
3357
|
+
if (blocks.length > 0 && shouldRecordMessage(seenUserMessages, obj.timestamp, blocks)) {
|
|
3358
|
+
turns.push({
|
|
3359
|
+
role: "user",
|
|
3360
|
+
...isCompactionSummaryText(text) ? { subtype: "compaction-summary" } : {},
|
|
3361
|
+
timestamp: obj.timestamp,
|
|
3362
|
+
blocks
|
|
3363
|
+
});
|
|
3151
3364
|
}
|
|
3152
3365
|
continue;
|
|
3153
3366
|
}
|
|
3367
|
+
if (p2.type === "agent_message" && typeof p2.message === "string" && p2.message.trim().length > 0) {
|
|
3368
|
+
const blocks = [{ type: "text", text: p2.message }];
|
|
3369
|
+
if (!shouldRecordMessage(seenAssistantMessages, obj.timestamp, blocks)) continue;
|
|
3370
|
+
turns.push({
|
|
3371
|
+
role: "assistant",
|
|
3372
|
+
timestamp: obj.timestamp,
|
|
3373
|
+
blocks
|
|
3374
|
+
});
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3154
3377
|
if (p2.type === "agent_reasoning" && typeof p2.text === "string" && p2.text.trim()) {
|
|
3155
3378
|
turns.push({
|
|
3156
3379
|
role: "assistant",
|
|
@@ -3160,7 +3383,7 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3160
3383
|
continue;
|
|
3161
3384
|
}
|
|
3162
3385
|
if (p2.type === "exec_command_end" && p2.call_id) {
|
|
3163
|
-
toolResults
|
|
3386
|
+
mergeToolResult(toolResults, p2.call_id, {
|
|
3164
3387
|
result: formatExecResult(p2),
|
|
3165
3388
|
isError: typeof p2.exit_code === "number" ? p2.exit_code !== 0 : void 0,
|
|
3166
3389
|
timestamp: obj.timestamp,
|
|
@@ -3172,12 +3395,34 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3172
3395
|
tokenSnapshots.push({
|
|
3173
3396
|
timestamp: obj.timestamp,
|
|
3174
3397
|
total: asCodexTokenInfo(p2.info?.total_token_usage),
|
|
3175
|
-
last: asCodexTokenInfo(p2.info?.last_token_usage)
|
|
3398
|
+
last: asCodexTokenInfo(p2.info?.last_token_usage),
|
|
3399
|
+
contextLimit: typeof p2.info?.model_context_window === "number" ? p2.info.model_context_window : void 0
|
|
3400
|
+
});
|
|
3401
|
+
continue;
|
|
3402
|
+
}
|
|
3403
|
+
if (p2.type === "task_complete" && typeof p2.duration_ms === "number") {
|
|
3404
|
+
if (p2.duration_ms > 0) taskDurations.push(p2.duration_ms);
|
|
3405
|
+
continue;
|
|
3406
|
+
}
|
|
3407
|
+
if (p2.type === "context_compacted") {
|
|
3408
|
+
recordCompaction(compactions, obj.timestamp || "", "codex-context");
|
|
3409
|
+
continue;
|
|
3410
|
+
}
|
|
3411
|
+
if (p2.type === "patch_apply_end" && p2.call_id) {
|
|
3412
|
+
const tool = tools.get(p2.call_id);
|
|
3413
|
+
const changedFiles = patchApplyChangedFiles(p2);
|
|
3414
|
+
if (tool && changedFiles.length > 0) {
|
|
3415
|
+
tool.input = mergeToolFilePaths(tool.input, changedFiles);
|
|
3416
|
+
}
|
|
3417
|
+
mergeToolResult(toolResults, p2.call_id, {
|
|
3418
|
+
result: formatPatchApplyResult(p2),
|
|
3419
|
+
isError: typeof p2.success === "boolean" ? !p2.success : void 0,
|
|
3420
|
+
timestamp: obj.timestamp
|
|
3176
3421
|
});
|
|
3177
3422
|
continue;
|
|
3178
3423
|
}
|
|
3179
|
-
if (p2.type === "web_search_end"
|
|
3180
|
-
const callId = p2.call_id || `web_search:${obj.timestamp}`;
|
|
3424
|
+
if (p2.type === "web_search_end") {
|
|
3425
|
+
const callId = (p2.call_id && tools.has(p2.call_id) ? p2.call_id : void 0) || findWebSearchToolId(tools, toolResults, p2, obj.timestamp) || p2.call_id || `web_search:${obj.timestamp}`;
|
|
3181
3426
|
if (!tools.has(callId)) {
|
|
3182
3427
|
tools.set(callId, {
|
|
3183
3428
|
id: callId,
|
|
@@ -3186,31 +3431,84 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3186
3431
|
timestamp: obj.timestamp
|
|
3187
3432
|
});
|
|
3188
3433
|
}
|
|
3189
|
-
toolResults
|
|
3190
|
-
result:
|
|
3434
|
+
mergeToolResult(toolResults, callId, {
|
|
3435
|
+
result: formatWebSearchResult(p2),
|
|
3191
3436
|
timestamp: obj.timestamp
|
|
3192
3437
|
});
|
|
3193
3438
|
}
|
|
3439
|
+
if (p2.type === "mcp_tool_call_end" && p2.call_id) {
|
|
3440
|
+
const invocation = p2.invocation || {};
|
|
3441
|
+
const server = typeof invocation.server === "string" ? invocation.server : "";
|
|
3442
|
+
const toolName = typeof invocation.tool === "string" ? invocation.tool : "";
|
|
3443
|
+
const tool = tools.get(p2.call_id);
|
|
3444
|
+
if (tool && server && toolName) {
|
|
3445
|
+
tool.name = `mcp__${server}__${toolName}`;
|
|
3446
|
+
mcpServersUsed.add(server);
|
|
3447
|
+
}
|
|
3448
|
+
mergeToolResult(toolResults, p2.call_id, {
|
|
3449
|
+
result: formatMcpToolResult(p2),
|
|
3450
|
+
isError: isMcpToolError(p2),
|
|
3451
|
+
timestamp: obj.timestamp,
|
|
3452
|
+
durationMs: durationToMs(p2.duration)
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3457
|
+
if (obj.type === "compacted") {
|
|
3458
|
+
recordCompaction(compactions, obj.timestamp || "", "codex");
|
|
3459
|
+
const text = compactedSummaryText(obj.payload);
|
|
3460
|
+
if (text) {
|
|
3461
|
+
turns.push({
|
|
3462
|
+
role: "user",
|
|
3463
|
+
subtype: "compaction-summary",
|
|
3464
|
+
timestamp: obj.timestamp,
|
|
3465
|
+
blocks: [{ type: "text", text }]
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3194
3468
|
continue;
|
|
3195
3469
|
}
|
|
3196
3470
|
if (obj.type !== "response_item") continue;
|
|
3197
3471
|
const p = obj.payload || {};
|
|
3472
|
+
if (p.type === "message" && p.role === "developer") {
|
|
3473
|
+
const text = contentText2(p.content);
|
|
3474
|
+
if (text.trim()) {
|
|
3475
|
+
turns.push({
|
|
3476
|
+
role: "user",
|
|
3477
|
+
subtype: "context-injection",
|
|
3478
|
+
timestamp: obj.timestamp,
|
|
3479
|
+
blocks: [{ type: "text", text }]
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3484
|
+
if (p.type === "message" && p.role === "user") {
|
|
3485
|
+
const blocks = userMessageBlocksFromContent(p.content);
|
|
3486
|
+
const text = textFromBlocks(blocks);
|
|
3487
|
+
if (blocks.length > 0 && shouldRecordMessage(seenUserMessages, obj.timestamp, blocks)) {
|
|
3488
|
+
turns.push({
|
|
3489
|
+
role: "user",
|
|
3490
|
+
...isCompactionSummaryText(text) ? { subtype: "compaction-summary" } : {},
|
|
3491
|
+
timestamp: obj.timestamp,
|
|
3492
|
+
blocks
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
continue;
|
|
3496
|
+
}
|
|
3198
3497
|
if (p.type === "message" && p.role === "assistant") {
|
|
3199
|
-
const text =
|
|
3498
|
+
const text = contentText2(p.content);
|
|
3200
3499
|
if (text.trim()) {
|
|
3500
|
+
const blocks = [{ type: "text", text }];
|
|
3501
|
+
if (!shouldRecordMessage(seenAssistantMessages, obj.timestamp, blocks)) continue;
|
|
3201
3502
|
turns.push({
|
|
3202
3503
|
role: "assistant",
|
|
3203
3504
|
timestamp: obj.timestamp,
|
|
3204
|
-
blocks
|
|
3505
|
+
blocks
|
|
3205
3506
|
});
|
|
3206
3507
|
}
|
|
3207
3508
|
continue;
|
|
3208
3509
|
}
|
|
3209
3510
|
if (p.type === "compaction") {
|
|
3210
|
-
compactions.
|
|
3211
|
-
timestamp: obj.timestamp || "",
|
|
3212
|
-
trigger: "codex"
|
|
3213
|
-
});
|
|
3511
|
+
recordCompaction(compactions, obj.timestamp || "", "codex");
|
|
3214
3512
|
continue;
|
|
3215
3513
|
}
|
|
3216
3514
|
if (p.type === "reasoning") {
|
|
@@ -3224,8 +3522,9 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3224
3522
|
}
|
|
3225
3523
|
continue;
|
|
3226
3524
|
}
|
|
3227
|
-
if (p.type
|
|
3228
|
-
const
|
|
3525
|
+
if (isCodexToolCallType(p.type)) {
|
|
3526
|
+
const existingWebSearchId = p.type === "web_search_call" && !p.call_id && !p.id ? findWebSearchToolId(tools, toolResults, p, obj.timestamp, { includeResolved: true }) : void 0;
|
|
3527
|
+
const callId = p.call_id || p.id || existingWebSearchId || `${p.type}:${obj.timestamp}:${tools.size}`;
|
|
3229
3528
|
const name = p.name || normalizeCodexToolName(p.type);
|
|
3230
3529
|
const input = inputForResponseItem(p);
|
|
3231
3530
|
tools.set(callId, { id: callId, name, input, timestamp: obj.timestamp });
|
|
@@ -3238,10 +3537,15 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3238
3537
|
}
|
|
3239
3538
|
if (p.type === "function_call_output" || p.type === "custom_tool_call_output" || p.type === "tool_search_output") {
|
|
3240
3539
|
if (p.call_id) {
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3540
|
+
mergeToolResult(
|
|
3541
|
+
toolResults,
|
|
3542
|
+
p.call_id,
|
|
3543
|
+
{
|
|
3544
|
+
result: formatOutputPayload(p),
|
|
3545
|
+
timestamp: obj.timestamp
|
|
3546
|
+
},
|
|
3547
|
+
{ preferExistingResult: true }
|
|
3548
|
+
);
|
|
3245
3549
|
}
|
|
3246
3550
|
continue;
|
|
3247
3551
|
}
|
|
@@ -3266,10 +3570,14 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3266
3570
|
}
|
|
3267
3571
|
turns.sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
|
|
3268
3572
|
const tokenUsage = tokenUsageFromSnapshots(tokenSnapshots);
|
|
3573
|
+
const contextLimit = [...tokenSnapshots].toReversed().find((s) => s.contextLimit)?.contextLimit;
|
|
3269
3574
|
const tokenUsageByModel = tokenUsage && model ? {
|
|
3270
3575
|
[model]: tokenUsage
|
|
3271
3576
|
} : void 0;
|
|
3272
|
-
const turnStats = buildCodexTurnStats(turns, tokenSnapshots, model);
|
|
3577
|
+
const turnStats = buildCodexTurnStats(turns, tokenSnapshots, taskDurations, model);
|
|
3578
|
+
const summedTaskDurationMs = taskDurations.reduce((sum, duration) => sum + duration, 0);
|
|
3579
|
+
const userPromptTurnCount = turns.filter((turn) => turn.role === "user" && !turn.subtype).length;
|
|
3580
|
+
const completeTaskDurationMs = userPromptTurnCount > 0 && taskDurations.length >= userPromptTurnCount ? summedTaskDurationMs : void 0;
|
|
3273
3581
|
return {
|
|
3274
3582
|
sessionId,
|
|
3275
3583
|
slug: slug || sessionId.slice(0, 8),
|
|
@@ -3278,16 +3586,18 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3278
3586
|
model,
|
|
3279
3587
|
startTime,
|
|
3280
3588
|
endTime,
|
|
3281
|
-
totalDurationMs: estimateActiveDuration(allTimestamps),
|
|
3589
|
+
totalDurationMs: completeTaskDurationMs || estimateActiveDuration(allTimestamps),
|
|
3282
3590
|
turns,
|
|
3283
3591
|
tokenUsage,
|
|
3284
3592
|
tokenUsageByModel,
|
|
3285
3593
|
compactions: compactions.length > 0 ? compactions : void 0,
|
|
3286
3594
|
turnStats: turnStats.length > 0 ? turnStats : void 0,
|
|
3595
|
+
contextLimit,
|
|
3287
3596
|
gitBranch,
|
|
3288
3597
|
gitBranches: gitBranches.length > 1 ? gitBranches : void 0,
|
|
3289
3598
|
entrypoint,
|
|
3290
3599
|
permissionMode: approvalPolicy || permissionMode,
|
|
3600
|
+
memoryMode,
|
|
3291
3601
|
skillsUsed: skillsUsed.size > 0 ? [...skillsUsed].sort() : void 0,
|
|
3292
3602
|
mcpServersUsed: mcpServersUsed.size > 0 ? [...mcpServersUsed].sort() : void 0,
|
|
3293
3603
|
dataSource: "jsonl",
|
|
@@ -3296,12 +3606,13 @@ function parseCodexLines(lines, sessionInfo, sourcePaths = []) {
|
|
|
3296
3606
|
sources: ["~/.codex/state_5.sqlite", "~/.codex/sessions"],
|
|
3297
3607
|
supplements: sourcePaths,
|
|
3298
3608
|
notes: ["Discovered from Codex state and parsed from rollout JSONL (local beta)."]
|
|
3299
|
-
}
|
|
3609
|
+
},
|
|
3610
|
+
parseWarnings: parseWarnings.length > 0 ? parseWarnings : void 0
|
|
3300
3611
|
};
|
|
3301
3612
|
}
|
|
3302
3613
|
function userMessageBlocks(payload) {
|
|
3303
3614
|
const blocks = [];
|
|
3304
|
-
const text = typeof payload.message === "string" ?
|
|
3615
|
+
const text = typeof payload.message === "string" ? normalizeUserMessageText(payload.message) : "";
|
|
3305
3616
|
if (text) blocks.push({ type: "text", text });
|
|
3306
3617
|
const imageUrls = [...Array.isArray(payload.images) ? payload.images : []].filter(
|
|
3307
3618
|
(v) => typeof v === "string" && v.trim()
|
|
@@ -3311,7 +3622,42 @@ function userMessageBlocks(payload) {
|
|
|
3311
3622
|
if (imageUrls.length > 0) blocks.push({ type: "_user_images", images: imageUrls });
|
|
3312
3623
|
return blocks;
|
|
3313
3624
|
}
|
|
3314
|
-
function
|
|
3625
|
+
function userMessageBlocksFromContent(content) {
|
|
3626
|
+
const blocks = [];
|
|
3627
|
+
const text = normalizeUserMessageText(contentText2(content));
|
|
3628
|
+
if (text) blocks.push({ type: "text", text });
|
|
3629
|
+
const images = contentImages(content);
|
|
3630
|
+
if (images.length > 0) blocks.push({ type: "_user_images", images });
|
|
3631
|
+
return blocks;
|
|
3632
|
+
}
|
|
3633
|
+
function textFromBlocks(blocks) {
|
|
3634
|
+
return blocks.filter((block) => block.type === "text").map((block) => block.text || "").join("\n").trim();
|
|
3635
|
+
}
|
|
3636
|
+
function shouldRecordMessage(seen, timestamp, blocks) {
|
|
3637
|
+
const key = messageDedupeKey(blocks);
|
|
3638
|
+
const time = timestamp ? Date.parse(timestamp) : Number.NaN;
|
|
3639
|
+
const previous = seen.get(key) || [];
|
|
3640
|
+
const isDuplicate = Number.isNaN(time) ? previous.length > 0 : previous.some((prev) => Math.abs(time - prev) <= 2e3);
|
|
3641
|
+
if (isDuplicate) return false;
|
|
3642
|
+
seen.set(key, [...previous, Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time]);
|
|
3643
|
+
return true;
|
|
3644
|
+
}
|
|
3645
|
+
function messageDedupeKey(blocks) {
|
|
3646
|
+
return blocks.map((block) => {
|
|
3647
|
+
if (block.type === "text") return `text:${block.text || ""}`;
|
|
3648
|
+
if (block.type === "_user_images") {
|
|
3649
|
+
return `images:${block.images.map(imageDedupeKey2).join(",")}`;
|
|
3650
|
+
}
|
|
3651
|
+
return `block:${block.type}`;
|
|
3652
|
+
}).join("|");
|
|
3653
|
+
}
|
|
3654
|
+
function imageDedupeKey2(image) {
|
|
3655
|
+
return `${image.slice(0, 64)}:${image.length}:${image.slice(-32)}`;
|
|
3656
|
+
}
|
|
3657
|
+
function isCompactionSummaryText(text) {
|
|
3658
|
+
return text.startsWith("This session is being continued from a previous conversation");
|
|
3659
|
+
}
|
|
3660
|
+
function contentText2(content) {
|
|
3315
3661
|
if (typeof content === "string") return content;
|
|
3316
3662
|
if (!Array.isArray(content)) return "";
|
|
3317
3663
|
return content.map((part) => {
|
|
@@ -3322,10 +3668,22 @@ function contentText(content) {
|
|
|
3322
3668
|
return "";
|
|
3323
3669
|
}).filter(Boolean).join("\n");
|
|
3324
3670
|
}
|
|
3671
|
+
function contentImages(content) {
|
|
3672
|
+
if (!Array.isArray(content)) return [];
|
|
3673
|
+
const images = [];
|
|
3674
|
+
for (const part of content) {
|
|
3675
|
+
if (part?.type !== "input_image" && part?.type !== "image" && part?.type !== "local_image") {
|
|
3676
|
+
continue;
|
|
3677
|
+
}
|
|
3678
|
+
const imageUrl = typeof part.image_url === "string" ? part.image_url : typeof part.image_url?.url === "string" ? part.image_url.url : typeof part.source?.data === "string" ? `data:${part.source.media_type || "image/png"};base64,${part.source.data}` : typeof part.path === "string" ? localImageToDataUrl(part.path) || "" : "";
|
|
3679
|
+
if (imageUrl) images.push(imageUrl);
|
|
3680
|
+
}
|
|
3681
|
+
return images;
|
|
3682
|
+
}
|
|
3325
3683
|
function reasoningText(payload) {
|
|
3326
3684
|
if (typeof payload.content === "string") return payload.content;
|
|
3327
|
-
if (Array.isArray(payload.content)) return
|
|
3328
|
-
if (Array.isArray(payload.summary)) return
|
|
3685
|
+
if (Array.isArray(payload.content)) return contentText2(payload.content);
|
|
3686
|
+
if (Array.isArray(payload.summary)) return contentText2(payload.summary);
|
|
3329
3687
|
return "";
|
|
3330
3688
|
}
|
|
3331
3689
|
function parseArguments(value) {
|
|
@@ -3356,6 +3714,20 @@ function inputForResponseItem(payload) {
|
|
|
3356
3714
|
}
|
|
3357
3715
|
return parseArguments(payload.arguments ?? payload.action ?? {});
|
|
3358
3716
|
}
|
|
3717
|
+
function patchApplyChangedFiles(payload) {
|
|
3718
|
+
const changes = payload?.changes;
|
|
3719
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return [];
|
|
3720
|
+
return Object.keys(changes).filter((file) => file.trim().length > 0);
|
|
3721
|
+
}
|
|
3722
|
+
function mergeToolFilePaths(input, filePaths) {
|
|
3723
|
+
const unique = [...new Set(filePaths)];
|
|
3724
|
+
if (unique.length === 0) return input;
|
|
3725
|
+
return {
|
|
3726
|
+
...input,
|
|
3727
|
+
file_paths: unique,
|
|
3728
|
+
...input.file_path || unique.length !== 1 ? {} : { file_path: unique[0] }
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3359
3731
|
function localShellActionInput(action) {
|
|
3360
3732
|
const command = Array.isArray(action?.command) ? action.command.map(String).join(" ") : typeof action?.command === "string" ? action.command : "";
|
|
3361
3733
|
return {
|
|
@@ -3393,6 +3765,27 @@ function formatExecResult(payload) {
|
|
|
3393
3765
|
const exit = typeof payload.exit_code === "number" ? `Exit code: ${payload.exit_code}` : "";
|
|
3394
3766
|
return [output, status, exit].filter(Boolean).join("\n");
|
|
3395
3767
|
}
|
|
3768
|
+
function formatPatchApplyResult(payload) {
|
|
3769
|
+
const status = payload.status ? `Status: ${payload.status}` : "";
|
|
3770
|
+
const stdout = payload.stdout || "";
|
|
3771
|
+
const stderr = payload.stderr || "";
|
|
3772
|
+
const files = patchApplyChangedFiles(payload);
|
|
3773
|
+
const changed = files.length > 0 ? `Changed files:
|
|
3774
|
+
${files.map((file) => `- ${file}`).join("\n")}` : "";
|
|
3775
|
+
return [stdout, stderr, status, changed].filter(Boolean).join("\n");
|
|
3776
|
+
}
|
|
3777
|
+
function formatWebSearchResult(payload) {
|
|
3778
|
+
if (typeof payload.query === "string" && payload.query.trim()) {
|
|
3779
|
+
return `[Search: ${payload.query}]`;
|
|
3780
|
+
}
|
|
3781
|
+
const action = payload.action || {};
|
|
3782
|
+
if (typeof action.url === "string" && action.url.trim()) return `[Open: ${action.url}]`;
|
|
3783
|
+
if (typeof action.pattern === "string" && action.pattern.trim())
|
|
3784
|
+
return `[Find: ${action.pattern}]`;
|
|
3785
|
+
if (typeof action.query === "string" && action.query.trim()) return `[Search: ${action.query}]`;
|
|
3786
|
+
if (typeof action.type === "string" && action.type.trim()) return `[Web search: ${action.type}]`;
|
|
3787
|
+
return "[Web search]";
|
|
3788
|
+
}
|
|
3396
3789
|
function formatOutputPayload(payload) {
|
|
3397
3790
|
if (typeof payload.output === "string") return payload.output;
|
|
3398
3791
|
if (Array.isArray(payload.output)) return formatContentItems(payload.output);
|
|
@@ -3400,6 +3793,81 @@ function formatOutputPayload(payload) {
|
|
|
3400
3793
|
if (payload.action) return JSON.stringify(payload.action, null, 2);
|
|
3401
3794
|
return JSON.stringify(payload);
|
|
3402
3795
|
}
|
|
3796
|
+
function mergeToolResult(toolResults, callId, next, options = {}) {
|
|
3797
|
+
const previous = toolResults.get(callId);
|
|
3798
|
+
if (!previous) {
|
|
3799
|
+
toolResults.set(callId, next);
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
toolResults.set(callId, {
|
|
3803
|
+
result: options.preferExistingResult && previous.result ? previous.result : next.result || previous.result,
|
|
3804
|
+
isError: next.isError ?? previous.isError,
|
|
3805
|
+
timestamp: next.timestamp || previous.timestamp,
|
|
3806
|
+
durationMs: next.durationMs ?? previous.durationMs
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
function formatMcpToolResult(payload) {
|
|
3810
|
+
const result = payload.result?.Ok ?? payload.result?.Err ?? payload.result;
|
|
3811
|
+
if (result?.content) return formatContentItems(result.content);
|
|
3812
|
+
if (typeof result === "string") return result;
|
|
3813
|
+
return result ? JSON.stringify(result, null, 2) : "";
|
|
3814
|
+
}
|
|
3815
|
+
function isMcpToolError(payload) {
|
|
3816
|
+
return Boolean(
|
|
3817
|
+
payload.isError || payload.is_error || payload.result?.Err || payload.result?.isError
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
function findWebSearchToolId(tools, toolResults, payload, timestamp, options = {}) {
|
|
3821
|
+
for (const tool of tools.values()) {
|
|
3822
|
+
if (tool.name !== "web_search") continue;
|
|
3823
|
+
if (!options.includeResolved && toolResults.has(tool.id)) continue;
|
|
3824
|
+
if (!sameWebSearchAction(tool.input, payload.action || { query: payload.query })) continue;
|
|
3825
|
+
if (!timestamp || !tool.timestamp) return tool.id;
|
|
3826
|
+
const diff = Math.abs(Date.parse(timestamp) - Date.parse(tool.timestamp));
|
|
3827
|
+
if (!Number.isFinite(diff) || diff <= 5e3) return tool.id;
|
|
3828
|
+
}
|
|
3829
|
+
return void 0;
|
|
3830
|
+
}
|
|
3831
|
+
function sameWebSearchAction(a, b) {
|
|
3832
|
+
try {
|
|
3833
|
+
return stableStringify(a || {}) === stableStringify(b || {});
|
|
3834
|
+
} catch {
|
|
3835
|
+
return false;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
function stableStringify(value) {
|
|
3839
|
+
return JSON.stringify(stableNormalize(value));
|
|
3840
|
+
}
|
|
3841
|
+
function stableNormalize(value) {
|
|
3842
|
+
if (Array.isArray(value)) return value.map(stableNormalize);
|
|
3843
|
+
if (!value || typeof value !== "object") return value;
|
|
3844
|
+
return Object.fromEntries(
|
|
3845
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, nested]) => [key, stableNormalize(nested)])
|
|
3846
|
+
);
|
|
3847
|
+
}
|
|
3848
|
+
function compactedSummaryText(payload) {
|
|
3849
|
+
if (typeof payload?.message === "string" && payload.message.trim()) return payload.message.trim();
|
|
3850
|
+
return "";
|
|
3851
|
+
}
|
|
3852
|
+
function recordCompaction(compactions, timestamp, trigger) {
|
|
3853
|
+
const time = Date.parse(timestamp);
|
|
3854
|
+
const duplicateIndex = compactions.findIndex((compaction) => {
|
|
3855
|
+
const previous = Date.parse(compaction.timestamp);
|
|
3856
|
+
return Number.isFinite(time) && Number.isFinite(previous) ? Math.abs(time - previous) <= 2e3 : compaction.timestamp === timestamp;
|
|
3857
|
+
});
|
|
3858
|
+
if (duplicateIndex >= 0) {
|
|
3859
|
+
if (compactionPriority(trigger) > compactionPriority(compactions[duplicateIndex].trigger)) {
|
|
3860
|
+
compactions[duplicateIndex] = { ...compactions[duplicateIndex], trigger };
|
|
3861
|
+
}
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
compactions.push({ timestamp, trigger });
|
|
3865
|
+
}
|
|
3866
|
+
function compactionPriority(trigger) {
|
|
3867
|
+
if (trigger === "codex-context") return 2;
|
|
3868
|
+
if (trigger === "codex") return 1;
|
|
3869
|
+
return 0;
|
|
3870
|
+
}
|
|
3403
3871
|
function formatContentItems(items) {
|
|
3404
3872
|
return items.map((item) => {
|
|
3405
3873
|
if (item?.type === "input_text" || item?.type === "output_text" || item?.type === "text") {
|
|
@@ -3428,41 +3896,43 @@ function tokenUsageFromSnapshots(snapshots) {
|
|
|
3428
3896
|
cacheReadTokens: cached
|
|
3429
3897
|
};
|
|
3430
3898
|
}
|
|
3431
|
-
function buildCodexTurnStats(turns, snapshots, model) {
|
|
3432
|
-
const userTurns = turns.filter((t) => t.role === "user");
|
|
3899
|
+
function buildCodexTurnStats(turns, snapshots, taskDurations, model) {
|
|
3900
|
+
const userTurns = turns.filter((t) => t.role === "user" && !t.subtype);
|
|
3433
3901
|
const usable = snapshots.filter((s) => s.last);
|
|
3434
3902
|
return userTurns.map((turn, i) => {
|
|
3435
3903
|
const usage = usable[i]?.last;
|
|
3436
|
-
const
|
|
3437
|
-
if (model)
|
|
3904
|
+
const stat11 = { turnIndex: i };
|
|
3905
|
+
if (model) stat11.model = model;
|
|
3438
3906
|
if (usage) {
|
|
3439
3907
|
const input = usage.input_tokens || 0;
|
|
3440
3908
|
const cached = usage.cached_input_tokens || 0;
|
|
3441
|
-
|
|
3909
|
+
stat11.tokenUsage = {
|
|
3442
3910
|
inputTokens: Math.max(0, input - cached),
|
|
3443
3911
|
outputTokens: usage.output_tokens || 0,
|
|
3444
3912
|
cacheCreationTokens: 0,
|
|
3445
3913
|
cacheReadTokens: cached
|
|
3446
3914
|
};
|
|
3447
|
-
|
|
3915
|
+
stat11.contextTokens = input;
|
|
3448
3916
|
}
|
|
3449
3917
|
const nextUser = userTurns[i + 1]?.timestamp;
|
|
3450
|
-
|
|
3918
|
+
const taskDuration = taskDurations[i];
|
|
3919
|
+
if (typeof taskDuration === "number" && taskDuration > 0) {
|
|
3920
|
+
stat11.durationMs = taskDuration;
|
|
3921
|
+
} else if (turn.timestamp) {
|
|
3451
3922
|
const end = nextUser || usable[i]?.timestamp;
|
|
3452
3923
|
if (end) {
|
|
3453
3924
|
const diff = Date.parse(end) - Date.parse(turn.timestamp);
|
|
3454
|
-
if (diff > 0 && diff < 12 * 60 * 60 * 1e3)
|
|
3925
|
+
if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat11.durationMs = diff;
|
|
3455
3926
|
}
|
|
3456
3927
|
}
|
|
3457
|
-
return
|
|
3928
|
+
return stat11;
|
|
3458
3929
|
});
|
|
3459
3930
|
}
|
|
3460
3931
|
function pushUnique(items, value) {
|
|
3461
3932
|
if (items.length === 0 || items[items.length - 1] !== value) items.push(value);
|
|
3462
3933
|
}
|
|
3463
|
-
function
|
|
3464
|
-
|
|
3465
|
-
return idx === -1 ? text : text.slice(idx + USER_MESSAGE_BEGIN.length);
|
|
3934
|
+
function normalizeUserMessageText(text) {
|
|
3935
|
+
return codexStripTwoPass(text);
|
|
3466
3936
|
}
|
|
3467
3937
|
function localImageToDataUrl(path) {
|
|
3468
3938
|
try {
|
|
@@ -3499,16 +3969,16 @@ var codexProvider = {
|
|
|
3499
3969
|
|
|
3500
3970
|
// src/providers/cursor/discover.ts
|
|
3501
3971
|
init_utils();
|
|
3502
|
-
import { readdir as
|
|
3503
|
-
import { homedir as
|
|
3504
|
-
import { basename as basename3, join as
|
|
3972
|
+
import { readdir as readdir8, readFile as readFile12, stat as stat7 } from "fs/promises";
|
|
3973
|
+
import { homedir as homedir10 } from "os";
|
|
3974
|
+
import { basename as basename3, join as join11 } from "path";
|
|
3505
3975
|
|
|
3506
3976
|
// src/providers/cursor/sqlite-reader.ts
|
|
3507
3977
|
import { createHash } from "crypto";
|
|
3508
3978
|
import { execFile } from "child_process";
|
|
3509
|
-
import { readdir as readdir6, readFile as
|
|
3979
|
+
import { readdir as readdir6, readFile as readFile10, stat as stat5 } from "fs/promises";
|
|
3510
3980
|
import { homedir as homedir8 } from "os";
|
|
3511
|
-
import { basename as basename2, dirname as dirname2, join as join9 } from "path";
|
|
3981
|
+
import { basename as basename2, dirname as dirname2, join as join9, posix } from "path";
|
|
3512
3982
|
import { promisify } from "util";
|
|
3513
3983
|
init_utils();
|
|
3514
3984
|
|
|
@@ -3589,6 +4059,7 @@ var MAX_CURSOR_REQUEST_CONTEXT_ROWS = 500;
|
|
|
3589
4059
|
var SQLITE_CLI_MAX_BUFFER = 256 * 1024 * 1024;
|
|
3590
4060
|
var SQLITE_CLI_QUERY_TIMEOUT_MS = 12e4;
|
|
3591
4061
|
var MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS = 1e4;
|
|
4062
|
+
var GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE = 200;
|
|
3592
4063
|
var execFileAsync = promisify(execFile);
|
|
3593
4064
|
function createRetryableInit(factory) {
|
|
3594
4065
|
let promise = null;
|
|
@@ -3616,7 +4087,7 @@ function closeCachedSqlJsDb() {
|
|
|
3616
4087
|
}
|
|
3617
4088
|
var cachedStoreDbIndex = null;
|
|
3618
4089
|
var resolvedProjectRootCache = /* @__PURE__ */ new Map();
|
|
3619
|
-
var GLOBAL_STATE_DISCOVERY_CACHE_PREFIX = "cursor-global-state-discovery-
|
|
4090
|
+
var GLOBAL_STATE_DISCOVERY_CACHE_PREFIX = "cursor-global-state-discovery-v4";
|
|
3620
4091
|
function workspaceHash(absolutePath) {
|
|
3621
4092
|
return createHash("md5").update(absolutePath).digest("hex");
|
|
3622
4093
|
}
|
|
@@ -3656,10 +4127,21 @@ async function globalStateWalFingerprint(dbPath) {
|
|
|
3656
4127
|
function sqlString(value) {
|
|
3657
4128
|
return `'${value.replaceAll("'", "''")}'`;
|
|
3658
4129
|
}
|
|
3659
|
-
function
|
|
3660
|
-
return
|
|
3661
|
-
|
|
3662
|
-
);
|
|
4130
|
+
function nextStringPrefix(prefix) {
|
|
4131
|
+
if (!prefix) return null;
|
|
4132
|
+
const chars = [...prefix];
|
|
4133
|
+
const last = chars.pop();
|
|
4134
|
+
if (!last) return null;
|
|
4135
|
+
const codePoint = last.codePointAt(0);
|
|
4136
|
+
if (codePoint === void 0 || codePoint >= 1114111) return null;
|
|
4137
|
+
const next = codePoint + 1;
|
|
4138
|
+
if (next >= 55296 && next <= 57343) return null;
|
|
4139
|
+
return `${chars.join("")}${String.fromCodePoint(next)}`;
|
|
4140
|
+
}
|
|
4141
|
+
function sqlKeyPrefixRange(prefix, column = "key") {
|
|
4142
|
+
const upperBound = nextStringPrefix(prefix);
|
|
4143
|
+
if (!upperBound) return `${column} >= ${sqlString(prefix)}`;
|
|
4144
|
+
return `${column} >= ${sqlString(prefix)} AND ${column} < ${sqlString(upperBound)}`;
|
|
3663
4145
|
}
|
|
3664
4146
|
function projectedCursorBubbleSelectSql() {
|
|
3665
4147
|
const value = "value";
|
|
@@ -3696,6 +4178,41 @@ function projectedCursorBubbleSelectSql() {
|
|
|
3696
4178
|
`json_extract(${value}, '$.recentlyViewedFiles') AS recentlyViewedFiles`
|
|
3697
4179
|
].join(", ");
|
|
3698
4180
|
}
|
|
4181
|
+
function replayableGlobalStateBubblePredicateSql(valueExpr) {
|
|
4182
|
+
const nonEmptyText = (path) => `(json_type(${valueExpr}, ${sqlString(path)}) = 'text' AND length(trim(COALESCE(json_extract(${valueExpr}, ${sqlString(
|
|
4183
|
+
path
|
|
4184
|
+
)}), ''))) > 0)`;
|
|
4185
|
+
return [
|
|
4186
|
+
nonEmptyText("$.text"),
|
|
4187
|
+
nonEmptyText("$.thinking"),
|
|
4188
|
+
nonEmptyText("$.thinking.text"),
|
|
4189
|
+
nonEmptyText("$.toolFormerData.name")
|
|
4190
|
+
].join(" OR ");
|
|
4191
|
+
}
|
|
4192
|
+
function replayableGlobalStateBubbleCountSql(composerValueExpr, composerKeyExpr) {
|
|
4193
|
+
const inlineBubblePredicate = replayableGlobalStateBubblePredicateSql("conversation.value");
|
|
4194
|
+
const referencedBubblePredicate = replayableGlobalStateBubblePredicateSql("bubble.value");
|
|
4195
|
+
return [
|
|
4196
|
+
"COALESCE((",
|
|
4197
|
+
"SELECT COUNT(*) FROM json_each(",
|
|
4198
|
+
composerValueExpr,
|
|
4199
|
+
", '$.conversation') AS conversation",
|
|
4200
|
+
" WHERE json_type(conversation.value) = 'object' AND (",
|
|
4201
|
+
inlineBubblePredicate,
|
|
4202
|
+
")",
|
|
4203
|
+
"), 0) + COALESCE((",
|
|
4204
|
+
"SELECT COUNT(*) FROM json_each(",
|
|
4205
|
+
composerValueExpr,
|
|
4206
|
+
", '$.fullConversationHeadersOnly') AS header",
|
|
4207
|
+
" JOIN cursorDiskKV AS bubble ON bubble.key = 'bubbleId:' || substr(",
|
|
4208
|
+
composerKeyExpr,
|
|
4209
|
+
", length('composerData:') + 1) || ':' || json_extract(header.value, '$.bubbleId')",
|
|
4210
|
+
" WHERE json_valid(bubble.value) AND (",
|
|
4211
|
+
referencedBubblePredicate,
|
|
4212
|
+
")",
|
|
4213
|
+
"), 0)"
|
|
4214
|
+
].join("");
|
|
4215
|
+
}
|
|
3699
4216
|
var SQLITE_CLI_NEGATIVE_CACHE_TTL_MS = 3e4;
|
|
3700
4217
|
var sqliteCliUsabilityCache = /* @__PURE__ */ new Map();
|
|
3701
4218
|
async function canUseSqliteCli(dbPath) {
|
|
@@ -3728,6 +4245,13 @@ async function querySqliteCli(dbPath, sql) {
|
|
|
3728
4245
|
if (!trimmed) return [];
|
|
3729
4246
|
return JSON.parse(trimmed);
|
|
3730
4247
|
}
|
|
4248
|
+
async function querySqliteCliText(dbPath, sql) {
|
|
4249
|
+
const { stdout } = await execFileAsync("sqlite3", ["-readonly", dbPath, sql], {
|
|
4250
|
+
maxBuffer: SQLITE_CLI_MAX_BUFFER,
|
|
4251
|
+
timeout: SQLITE_CLI_QUERY_TIMEOUT_MS
|
|
4252
|
+
});
|
|
4253
|
+
return stdout.replace(/\r?\n$/, "");
|
|
4254
|
+
}
|
|
3731
4255
|
function sqlJsRows(db, sql) {
|
|
3732
4256
|
const result = db.exec(sql);
|
|
3733
4257
|
if (!result.length) return [];
|
|
@@ -3746,6 +4270,21 @@ async function queryGlobalStateRows(globalStateDb, sql) {
|
|
|
3746
4270
|
}
|
|
3747
4271
|
return sqlJsRows(globalStateDb.db, sql);
|
|
3748
4272
|
}
|
|
4273
|
+
async function queryGlobalStateTextValue(globalStateDb, key) {
|
|
4274
|
+
if (globalStateDb.backend === "sqlite-cli") {
|
|
4275
|
+
const value = await querySqliteCliText(
|
|
4276
|
+
globalStateDb.dbPath,
|
|
4277
|
+
`SELECT CAST(value AS TEXT) FROM cursorDiskKV WHERE key = ${sqlString(key)} LIMIT 1`
|
|
4278
|
+
);
|
|
4279
|
+
return value || null;
|
|
4280
|
+
}
|
|
4281
|
+
const rows = sqlJsRows(
|
|
4282
|
+
globalStateDb.db,
|
|
4283
|
+
`SELECT value FROM cursorDiskKV WHERE key = ${sqlString(key)} LIMIT 1`
|
|
4284
|
+
);
|
|
4285
|
+
if (rows.length === 0) return null;
|
|
4286
|
+
return valueToString(rows[0].value) || null;
|
|
4287
|
+
}
|
|
3749
4288
|
async function openGlobalStateDb() {
|
|
3750
4289
|
const dbPath = await findGlobalStateDb();
|
|
3751
4290
|
if (!dbPath) return null;
|
|
@@ -3772,7 +4311,7 @@ async function openGlobalStateDb() {
|
|
|
3772
4311
|
} catch {
|
|
3773
4312
|
return null;
|
|
3774
4313
|
}
|
|
3775
|
-
const dbBuffer = await
|
|
4314
|
+
const dbBuffer = await readFile10(dbPath).catch(() => null);
|
|
3776
4315
|
if (!dbBuffer) return null;
|
|
3777
4316
|
const db = new SQL.Database(dbBuffer);
|
|
3778
4317
|
closeCachedSqlJsDb();
|
|
@@ -3867,6 +4406,110 @@ async function resolveCursorLiveWatchPaths(sessionId) {
|
|
|
3867
4406
|
}
|
|
3868
4407
|
return [...paths];
|
|
3869
4408
|
}
|
|
4409
|
+
async function readCursorLiveDiagnostics(sessionId) {
|
|
4410
|
+
const globalStateDb = await openGlobalStateDb();
|
|
4411
|
+
if (!globalStateDb || !await hasGlobalStateSession(globalStateDb, sessionId)) return null;
|
|
4412
|
+
const rawComposer = await queryGlobalStateTextValue(globalStateDb, `composerData:${sessionId}`);
|
|
4413
|
+
const composer = parseJson(rawComposer || "");
|
|
4414
|
+
if (!composer) return null;
|
|
4415
|
+
const headerBubbleIds = Array.isArray(composer.fullConversationHeadersOnly) ? composer.fullConversationHeadersOnly.map(
|
|
4416
|
+
(header) => header && typeof header === "object" && typeof header.bubbleId === "string" ? header.bubbleId : ""
|
|
4417
|
+
).filter(Boolean) : Array.isArray(composer.conversation) ? composer.conversation.map(
|
|
4418
|
+
(item) => item && typeof item === "object" && typeof item.bubbleId === "string" ? item.bubbleId : ""
|
|
4419
|
+
).filter(Boolean) : [];
|
|
4420
|
+
const latestBubbleId = headerBubbleIds[headerBubbleIds.length - 1];
|
|
4421
|
+
const bubbleRows = [];
|
|
4422
|
+
const bubbleKeys = headerBubbleIds.map((bubbleId) => `bubbleId:${sessionId}:${bubbleId}`);
|
|
4423
|
+
for (let i = 0; i < bubbleKeys.length; i += GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE) {
|
|
4424
|
+
const chunk = bubbleKeys.slice(i, i + GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE);
|
|
4425
|
+
bubbleRows.push(
|
|
4426
|
+
...await queryGlobalStateRows(
|
|
4427
|
+
globalStateDb,
|
|
4428
|
+
[
|
|
4429
|
+
"SELECT",
|
|
4430
|
+
"key,",
|
|
4431
|
+
"length(value) AS bytes,",
|
|
4432
|
+
"json_extract(value,'$.type') AS type,",
|
|
4433
|
+
"json_extract(value,'$.text') AS text,",
|
|
4434
|
+
"json_extract(value,'$.createdAt') AS createdAt,",
|
|
4435
|
+
"json_extract(value,'$.lastUpdatedAt') AS lastUpdatedAt,",
|
|
4436
|
+
"json_extract(value,'$.toolFormerData.name') AS toolName,",
|
|
4437
|
+
"json_extract(value,'$.toolFormerData.result') IS NOT NULL AS toolHasResult,",
|
|
4438
|
+
"length(json_extract(value,'$.toolFormerData.result')) AS toolResultLength",
|
|
4439
|
+
"FROM cursorDiskKV",
|
|
4440
|
+
`WHERE key IN (${chunk.map(sqlString).join(",")}) AND json_valid(value)`
|
|
4441
|
+
].join(" ")
|
|
4442
|
+
)
|
|
4443
|
+
);
|
|
4444
|
+
}
|
|
4445
|
+
const bubbleRowsByKey = new Map(bubbleRows.map((row) => [valueToString(row.key), row]));
|
|
4446
|
+
const latest = latestBubbleId ? bubbleRowsByKey.get(`bubbleId:${sessionId}:${latestBubbleId}`) || {} : {};
|
|
4447
|
+
const latestToolName = valueToString(latest.toolName).trim();
|
|
4448
|
+
let toolCallCount = 0;
|
|
4449
|
+
let toolResultCount = 0;
|
|
4450
|
+
let pendingToolCount = 0;
|
|
4451
|
+
let maxBubbleBytes = 0;
|
|
4452
|
+
let totalBubbleBytes = 0;
|
|
4453
|
+
for (const row of bubbleRows) {
|
|
4454
|
+
const bytes = toNonNegativeInt(row.bytes);
|
|
4455
|
+
maxBubbleBytes = Math.max(maxBubbleBytes, bytes);
|
|
4456
|
+
totalBubbleBytes += bytes;
|
|
4457
|
+
if (!valueToString(row.toolName)) continue;
|
|
4458
|
+
toolCallCount++;
|
|
4459
|
+
if (Number(row.toolHasResult)) {
|
|
4460
|
+
toolResultCount++;
|
|
4461
|
+
} else {
|
|
4462
|
+
pendingToolCount++;
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
const wal = await globalStateWalFingerprint(globalStateDb.dbPath);
|
|
4466
|
+
const diagnostics = {
|
|
4467
|
+
source: "global-state",
|
|
4468
|
+
probedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4469
|
+
dbPath: globalStateDb.dbPath,
|
|
4470
|
+
dbMtimeMs: Math.round(globalStateDb.mtimeMs),
|
|
4471
|
+
walMtimeMs: Math.round(wal.walMtimeMs),
|
|
4472
|
+
walSize: Math.round(wal.walSize),
|
|
4473
|
+
composerBytes: rawComposer ? Buffer.byteLength(rawComposer, "utf-8") : 0,
|
|
4474
|
+
headerCount: headerBubbleIds.length,
|
|
4475
|
+
...toIsoTimestamp(composer.lastUpdatedAt) ? { composerLastUpdatedAt: toIsoTimestamp(composer.lastUpdatedAt) } : {},
|
|
4476
|
+
...latestBubbleId ? { latestBubbleId } : {},
|
|
4477
|
+
...toIsoTimestamp(latest.createdAt) ? { latestBubbleCreatedAt: toIsoTimestamp(latest.createdAt) } : {},
|
|
4478
|
+
...toIsoTimestamp(latest.lastUpdatedAt) ? { latestBubbleUpdatedAt: toIsoTimestamp(latest.lastUpdatedAt) } : {},
|
|
4479
|
+
...typeof latest.type === "number" ? { latestBubbleType: latest.type } : {},
|
|
4480
|
+
...valueToString(latest.text).trim() ? { latestTextPreview: valueToString(latest.text).replace(/\s+/g, " ").trim().slice(0, 160) } : {},
|
|
4481
|
+
...latestToolName ? { latestToolName } : {},
|
|
4482
|
+
...latestToolName && latest.toolHasResult !== void 0 ? { latestToolHasResult: Number(latest.toolHasResult) !== 0 } : {},
|
|
4483
|
+
...latest.toolResultLength !== null && latest.toolResultLength !== void 0 ? { latestToolResultLength: toNonNegativeInt(latest.toolResultLength) } : {},
|
|
4484
|
+
bubbleCount: bubbleRows.length,
|
|
4485
|
+
toolCallCount,
|
|
4486
|
+
toolResultCount,
|
|
4487
|
+
pendingToolCount,
|
|
4488
|
+
maxBubbleBytes,
|
|
4489
|
+
totalBubbleBytes,
|
|
4490
|
+
signature: ""
|
|
4491
|
+
};
|
|
4492
|
+
diagnostics.signature = cursorLiveDiagnosticsSignature(diagnostics);
|
|
4493
|
+
return diagnostics;
|
|
4494
|
+
}
|
|
4495
|
+
function cursorLiveDiagnosticsSignature(diagnostics) {
|
|
4496
|
+
return [
|
|
4497
|
+
diagnostics.headerCount,
|
|
4498
|
+
diagnostics.bubbleCount,
|
|
4499
|
+
diagnostics.toolCallCount,
|
|
4500
|
+
diagnostics.toolResultCount,
|
|
4501
|
+
diagnostics.pendingToolCount,
|
|
4502
|
+
diagnostics.composerBytes,
|
|
4503
|
+
diagnostics.composerLastUpdatedAt || "",
|
|
4504
|
+
diagnostics.latestBubbleId || "",
|
|
4505
|
+
diagnostics.latestBubbleCreatedAt || "",
|
|
4506
|
+
diagnostics.latestBubbleUpdatedAt || "",
|
|
4507
|
+
diagnostics.latestToolName || "",
|
|
4508
|
+
diagnostics.latestToolHasResult ? "1" : "0",
|
|
4509
|
+
diagnostics.latestToolResultLength ?? "",
|
|
4510
|
+
diagnostics.totalBubbleBytes
|
|
4511
|
+
].join("|");
|
|
4512
|
+
}
|
|
3870
4513
|
async function listStoreDbSessionIds(forceRefresh = false) {
|
|
3871
4514
|
return new Set((await getStoreDbIndex(forceRefresh)).keys());
|
|
3872
4515
|
}
|
|
@@ -4193,7 +4836,7 @@ async function readStoreDbMeta(dbPath) {
|
|
|
4193
4836
|
} catch {
|
|
4194
4837
|
return null;
|
|
4195
4838
|
}
|
|
4196
|
-
const dbBuffer = await
|
|
4839
|
+
const dbBuffer = await readFile10(dbPath).catch(() => null);
|
|
4197
4840
|
if (!dbBuffer) return null;
|
|
4198
4841
|
const db = new SQL.Database(dbBuffer);
|
|
4199
4842
|
try {
|
|
@@ -4252,6 +4895,32 @@ function countComposerConversationHeaders(composer) {
|
|
|
4252
4895
|
const legacyConversation = Array.isArray(composer.conversation) ? composer.conversation.length : 0;
|
|
4253
4896
|
return Math.max(fullHeaders, legacyConversation);
|
|
4254
4897
|
}
|
|
4898
|
+
function composerHeaderBubbleIds(composer) {
|
|
4899
|
+
if (!Array.isArray(composer.fullConversationHeadersOnly)) return [];
|
|
4900
|
+
return composer.fullConversationHeadersOnly.map(
|
|
4901
|
+
(header) => header && typeof header === "object" && typeof header.bubbleId === "string" ? header.bubbleId : ""
|
|
4902
|
+
).filter((bubbleId) => Boolean(bubbleId));
|
|
4903
|
+
}
|
|
4904
|
+
function isReplayableGlobalStateBubble(bubble) {
|
|
4905
|
+
return bubbleToTurn(bubble) !== null;
|
|
4906
|
+
}
|
|
4907
|
+
async function hasReplayableGlobalStateComposer(globalStateDb, sessionId, composer) {
|
|
4908
|
+
if (Array.isArray(composer.conversation) && composer.conversation.some(
|
|
4909
|
+
(item) => item && typeof item === "object" && isReplayableGlobalStateBubble(item)
|
|
4910
|
+
)) {
|
|
4911
|
+
return true;
|
|
4912
|
+
}
|
|
4913
|
+
const bubbleIds = composerHeaderBubbleIds(composer);
|
|
4914
|
+
if (bubbleIds.length === 0) return false;
|
|
4915
|
+
const rows = await loadProjectedBubbleRowsByKeys(
|
|
4916
|
+
globalStateDb,
|
|
4917
|
+
bubbleIds.map((bubbleId) => `bubbleId:${sessionId}:${bubbleId}`)
|
|
4918
|
+
);
|
|
4919
|
+
return rows.some((row) => {
|
|
4920
|
+
const bubble = projectedCursorBubbleRowToBubble(row);
|
|
4921
|
+
return bubble ? isReplayableGlobalStateBubble(bubble) : false;
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4255
4924
|
function firstUserTextSnippet(turns) {
|
|
4256
4925
|
const firstUser = turns.find((t) => t.role === "user");
|
|
4257
4926
|
const firstText = firstUser?.blocks.find(
|
|
@@ -4284,15 +4953,19 @@ async function discoverGlobalStateOnlySessions(knownSessionIds, decodedWorkspace
|
|
|
4284
4953
|
const discoveredSessions = [];
|
|
4285
4954
|
try {
|
|
4286
4955
|
const composerDiscoverySql = globalStateDb.backend === "sqlite-cli" ? [
|
|
4287
|
-
"SELECT key,",
|
|
4288
|
-
"MAX(COALESCE(json_array_length(value,'$.fullConversationHeadersOnly'),0),",
|
|
4289
|
-
"COALESCE(json_array_length(value,'$.conversation'),0)) AS headerCount,",
|
|
4290
|
-
"json_extract(value,'$.lastUpdatedAt') AS lastUpdatedAt,",
|
|
4291
|
-
"json_extract(value,'$.createdAt') AS createdAt,",
|
|
4292
|
-
"json_extract(value,'$.name') AS title,",
|
|
4293
|
-
"length(value) AS fileSize",
|
|
4294
|
-
"
|
|
4295
|
-
|
|
4956
|
+
"SELECT kv.key,",
|
|
4957
|
+
"MAX(COALESCE(json_array_length(kv.value,'$.fullConversationHeadersOnly'),0),",
|
|
4958
|
+
"COALESCE(json_array_length(kv.value,'$.conversation'),0)) AS headerCount,",
|
|
4959
|
+
"json_extract(kv.value,'$.lastUpdatedAt') AS lastUpdatedAt,",
|
|
4960
|
+
"json_extract(kv.value,'$.createdAt') AS createdAt,",
|
|
4961
|
+
"json_extract(kv.value,'$.name') AS title,",
|
|
4962
|
+
"length(kv.value) AS fileSize,",
|
|
4963
|
+
`${replayableGlobalStateBubbleCountSql("kv.value", "kv.key")} AS replayableBubbleCount`,
|
|
4964
|
+
`FROM cursorDiskKV AS kv WHERE ${sqlKeyPrefixRange(
|
|
4965
|
+
"composerData:",
|
|
4966
|
+
"kv.key"
|
|
4967
|
+
)} AND json_valid(kv.value)`
|
|
4968
|
+
].join(" ") : `SELECT key, value FROM cursorDiskKV WHERE ${sqlKeyPrefixRange("composerData:")}`;
|
|
4296
4969
|
const rows = await queryGlobalStateRows(globalStateDb, composerDiscoverySql);
|
|
4297
4970
|
if (rows.length === 0) return { sessions: [], sessionIds };
|
|
4298
4971
|
for (const row of rows) {
|
|
@@ -4304,6 +4977,8 @@ async function discoverGlobalStateOnlySessions(knownSessionIds, decodedWorkspace
|
|
|
4304
4977
|
if (rawComposer && !composer) continue;
|
|
4305
4978
|
const headerCount = composer ? countComposerConversationHeaders(composer) : toNonNegativeInt(row.headerCount);
|
|
4306
4979
|
if (headerCount === 0) continue;
|
|
4980
|
+
const hasReplayableBubble = composer ? await hasReplayableGlobalStateComposer(globalStateDb, sessionId, composer) : toNonNegativeInt(row.replayableBubbleCount) > 0;
|
|
4981
|
+
if (!hasReplayableBubble) continue;
|
|
4307
4982
|
sessionIds.add(sessionId);
|
|
4308
4983
|
const timestamp = toIsoTimestamp(composer?.lastUpdatedAt ?? row.lastUpdatedAt) || toIsoTimestamp(composer?.createdAt ?? row.createdAt) || (/* @__PURE__ */ new Date()).toISOString();
|
|
4309
4984
|
const title = typeof (composer?.name ?? row.title) === "string" && valueToString(composer?.name ?? row.title).trim() ? valueToString(composer?.name ?? row.title).trim() : void 0;
|
|
@@ -4390,7 +5065,7 @@ async function parseCursorStoreDb(sessionId, workspacePath = "") {
|
|
|
4390
5065
|
} catch {
|
|
4391
5066
|
return null;
|
|
4392
5067
|
}
|
|
4393
|
-
const dbBuffer = await
|
|
5068
|
+
const dbBuffer = await readFile10(dbPath);
|
|
4394
5069
|
const db = new SQL.Database(dbBuffer);
|
|
4395
5070
|
try {
|
|
4396
5071
|
const metaRows = db.exec("SELECT value FROM meta WHERE key = '0'");
|
|
@@ -4472,7 +5147,7 @@ function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
|
|
|
4472
5147
|
const { turns, turnStats, totalDurationMs } = messagesToTurns(messages);
|
|
4473
5148
|
const slug = sessionId.slice(0, 8);
|
|
4474
5149
|
const title = metaJson.name || firstUserTextSnippet(turns);
|
|
4475
|
-
const hasDurationStats = turnStats.some((
|
|
5150
|
+
const hasDurationStats = turnStats.some((stat11) => (stat11.durationMs || 0) > 0);
|
|
4476
5151
|
const notes = [];
|
|
4477
5152
|
if (hasDurationStats) {
|
|
4478
5153
|
notes.push("Per-turn duration is estimated from Cursor tool execution metadata.");
|
|
@@ -4485,7 +5160,7 @@ function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
|
|
|
4485
5160
|
slug,
|
|
4486
5161
|
title,
|
|
4487
5162
|
cwd: workspacePath,
|
|
4488
|
-
model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((
|
|
5163
|
+
model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((stat11) => typeof stat11.model === "string" && stat11.model)?.model,
|
|
4489
5164
|
startTime: metaJson.createdAt ? new Date(metaJson.createdAt).toISOString() : void 0,
|
|
4490
5165
|
...totalDurationMs !== void 0 ? { totalDurationMs } : {},
|
|
4491
5166
|
...turnStats.length > 0 ? { turnStats } : {},
|
|
@@ -4754,7 +5429,7 @@ function addContextFilesFromAttachedFolderResult(files, seen, value) {
|
|
|
4754
5429
|
for (const file of parsed.files) {
|
|
4755
5430
|
const name = file && typeof file === "object" ? normalizeCursorContextFile(file.name) : normalizeCursorContextFile(file);
|
|
4756
5431
|
if (directory && name) {
|
|
4757
|
-
addUniqueContextFile(files, seen,
|
|
5432
|
+
addUniqueContextFile(files, seen, posix.join(directory, name));
|
|
4758
5433
|
continue;
|
|
4759
5434
|
}
|
|
4760
5435
|
const direct = file && typeof file === "object" ? normalizeCursorContextFile(file.filePath) || normalizeCursorContextFile(file.path) || normalizeCursorContextFile(file.fsPath) || normalizeCursorContextFile(file.uri) || normalizeCursorContextFile(file.relativeWorkspacePath) || normalizeCursorContextFile(file.relativePath) : void 0;
|
|
@@ -4825,9 +5500,9 @@ function buildBubbleProjectHintData(entries) {
|
|
|
4825
5500
|
async function loadCursorRequestContexts(globalStateDb, sessionId) {
|
|
4826
5501
|
const rows = await queryGlobalStateRows(
|
|
4827
5502
|
globalStateDb,
|
|
4828
|
-
`SELECT value FROM cursorDiskKV WHERE
|
|
5503
|
+
`SELECT value FROM cursorDiskKV WHERE ${sqlKeyPrefixRange(
|
|
4829
5504
|
`messageRequestContext:${sessionId}:`
|
|
4830
|
-
)}
|
|
5505
|
+
)} LIMIT ${MAX_CURSOR_REQUEST_CONTEXT_ROWS}`
|
|
4831
5506
|
);
|
|
4832
5507
|
const contexts = [];
|
|
4833
5508
|
for (const row of rows) {
|
|
@@ -4840,12 +5515,25 @@ async function loadCursorRequestContexts(globalStateDb, sessionId) {
|
|
|
4840
5515
|
async function countCursorCheckpointEntries(globalStateDb, sessionId) {
|
|
4841
5516
|
const rows = await queryGlobalStateRows(
|
|
4842
5517
|
globalStateDb,
|
|
4843
|
-
`SELECT COUNT(*) AS count FROM cursorDiskKV WHERE
|
|
5518
|
+
`SELECT COUNT(*) AS count FROM cursorDiskKV WHERE ${sqlKeyPrefixRange(
|
|
4844
5519
|
`checkpointId:${sessionId}:`
|
|
4845
|
-
)}
|
|
5520
|
+
)}`
|
|
4846
5521
|
);
|
|
4847
5522
|
return toNonNegativeInt(rows[0]?.count);
|
|
4848
5523
|
}
|
|
5524
|
+
async function loadProjectedBubbleRowsByKeys(globalStateDb, keys) {
|
|
5525
|
+
const rows = [];
|
|
5526
|
+
for (let i = 0; i < keys.length; i += GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE) {
|
|
5527
|
+
const chunk = keys.slice(i, i + GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE);
|
|
5528
|
+
rows.push(
|
|
5529
|
+
...await queryGlobalStateRows(
|
|
5530
|
+
globalStateDb,
|
|
5531
|
+
`SELECT ${projectedCursorBubbleSelectSql()} FROM cursorDiskKV WHERE key IN (${chunk.map(sqlString).join(",")}) AND json_valid(value)`
|
|
5532
|
+
)
|
|
5533
|
+
);
|
|
5534
|
+
}
|
|
5535
|
+
return rows;
|
|
5536
|
+
}
|
|
4849
5537
|
function mergeUniqueStrings(...groups) {
|
|
4850
5538
|
const merged = [];
|
|
4851
5539
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4861,13 +5549,13 @@ function mergeUniqueStrings(...groups) {
|
|
|
4861
5549
|
}
|
|
4862
5550
|
function mergeCursorParseResults(primary, enrichment) {
|
|
4863
5551
|
const mergedModel = chooseMergedCursorModel(primary.model, enrichment.model);
|
|
4864
|
-
const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((
|
|
5552
|
+
const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((stat11) => stat11.durationMs !== void 0) === true);
|
|
4865
5553
|
const mergedTurnStats = mergeTurnStats(primary.turnStats, enrichment.turnStats, {
|
|
4866
5554
|
preferEnrichmentDuration
|
|
4867
5555
|
});
|
|
4868
5556
|
const mergedTokenUsage = primary.tokenUsage || enrichment.tokenUsage;
|
|
4869
5557
|
const mergedTokenUsageByModel = primary.tokenUsageByModel || enrichment.tokenUsageByModel;
|
|
4870
|
-
const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum,
|
|
5558
|
+
const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0 : void 0);
|
|
4871
5559
|
const mergedDuration = mergedTotalDurationMs !== primary.totalDurationMs;
|
|
4872
5560
|
const mergedTokens = !primary.tokenUsage && !!enrichment.tokenUsage || !primary.tokenUsageByModel && !!enrichment.tokenUsageByModel;
|
|
4873
5561
|
const supplements = mergeUniqueStrings(
|
|
@@ -4937,11 +5625,11 @@ function mergeTurnStats(primary, enrichment, options) {
|
|
|
4937
5625
|
if (!primary?.length) return enrichment;
|
|
4938
5626
|
if (!enrichment?.length) return primary;
|
|
4939
5627
|
const maxTurnIndex = Math.max(
|
|
4940
|
-
...primary.map((
|
|
4941
|
-
...enrichment.map((
|
|
5628
|
+
...primary.map((stat11) => stat11.turnIndex),
|
|
5629
|
+
...enrichment.map((stat11) => stat11.turnIndex)
|
|
4942
5630
|
);
|
|
4943
|
-
const primaryByIndex = new Map(primary.map((
|
|
4944
|
-
const enrichmentByIndex = new Map(enrichment.map((
|
|
5631
|
+
const primaryByIndex = new Map(primary.map((stat11) => [stat11.turnIndex, stat11]));
|
|
5632
|
+
const enrichmentByIndex = new Map(enrichment.map((stat11) => [stat11.turnIndex, stat11]));
|
|
4945
5633
|
const merged = [];
|
|
4946
5634
|
for (let turnIndex = 0; turnIndex <= maxTurnIndex; turnIndex++) {
|
|
4947
5635
|
const current = primaryByIndex.get(turnIndex);
|
|
@@ -5035,17 +5723,17 @@ function applyGlobalStateWallClockDurations(entries, turnStats) {
|
|
|
5035
5723
|
if (durationsByTurn.size === 0) {
|
|
5036
5724
|
return {
|
|
5037
5725
|
turnStats,
|
|
5038
|
-
totalDurationMs: turnStats.reduce((sum,
|
|
5726
|
+
totalDurationMs: turnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0,
|
|
5039
5727
|
usedWallClock: false
|
|
5040
5728
|
};
|
|
5041
5729
|
}
|
|
5042
|
-
const mergedTurnStats = turnStats.map((
|
|
5043
|
-
const wallClockDuration = durationsByTurn.get(
|
|
5044
|
-
return wallClockDuration !== void 0 ? { ...
|
|
5730
|
+
const mergedTurnStats = turnStats.map((stat11) => {
|
|
5731
|
+
const wallClockDuration = durationsByTurn.get(stat11.turnIndex);
|
|
5732
|
+
return wallClockDuration !== void 0 ? { ...stat11, durationMs: wallClockDuration } : stat11;
|
|
5045
5733
|
});
|
|
5046
5734
|
return {
|
|
5047
5735
|
turnStats: mergedTurnStats,
|
|
5048
|
-
totalDurationMs: mergedTurnStats.reduce((sum,
|
|
5736
|
+
totalDurationMs: mergedTurnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0,
|
|
5049
5737
|
usedWallClock: true
|
|
5050
5738
|
};
|
|
5051
5739
|
}
|
|
@@ -5091,15 +5779,15 @@ function buildGlobalStateMetrics(entries, fallbackModel, sessionTokenUsage) {
|
|
|
5091
5779
|
current.durationMs = (current.durationMs || 0) + bubbleDurationMs;
|
|
5092
5780
|
}
|
|
5093
5781
|
}
|
|
5094
|
-
for (const
|
|
5095
|
-
if (
|
|
5096
|
-
delete
|
|
5782
|
+
for (const stat11 of turnStats) {
|
|
5783
|
+
if (stat11.tokenUsage && !hasAnyTokens(stat11.tokenUsage)) {
|
|
5784
|
+
delete stat11.tokenUsage;
|
|
5097
5785
|
}
|
|
5098
|
-
if ((
|
|
5099
|
-
delete
|
|
5786
|
+
if ((stat11.durationMs || 0) <= 0) {
|
|
5787
|
+
delete stat11.durationMs;
|
|
5100
5788
|
}
|
|
5101
|
-
if ((
|
|
5102
|
-
delete
|
|
5789
|
+
if ((stat11.contextTokens || 0) <= 0) {
|
|
5790
|
+
delete stat11.contextTokens;
|
|
5103
5791
|
}
|
|
5104
5792
|
}
|
|
5105
5793
|
const {
|
|
@@ -5146,12 +5834,11 @@ async function parseCursorGlobalStateDb(sessionId, globalStateDb, preferredWorks
|
|
|
5146
5834
|
const resolvedGlobalStateDb = globalStateDb ?? await openGlobalStateDb();
|
|
5147
5835
|
if (!resolvedGlobalStateDb) return null;
|
|
5148
5836
|
try {
|
|
5149
|
-
const
|
|
5837
|
+
const rawComposer = await queryGlobalStateTextValue(
|
|
5150
5838
|
resolvedGlobalStateDb,
|
|
5151
|
-
`
|
|
5839
|
+
`composerData:${sessionId}`
|
|
5152
5840
|
);
|
|
5153
|
-
if (
|
|
5154
|
-
const rawComposer = valueToString(composerRows[0].value);
|
|
5841
|
+
if (!rawComposer) return null;
|
|
5155
5842
|
const composer = parseJson(rawComposer);
|
|
5156
5843
|
if (!composer) return null;
|
|
5157
5844
|
const entries = [];
|
|
@@ -5173,10 +5860,9 @@ async function parseCursorGlobalStateDb(sessionId, globalStateDb, preferredWorks
|
|
|
5173
5860
|
bubbleIds.map((bubbleId) => `bubbleId:${sessionId}:${bubbleId}`)
|
|
5174
5861
|
);
|
|
5175
5862
|
const bubblesByKey = /* @__PURE__ */ new Map();
|
|
5176
|
-
const rows = await
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
);
|
|
5863
|
+
const rows = await loadProjectedBubbleRowsByKeys(resolvedGlobalStateDb, [
|
|
5864
|
+
...expectedBubbleKeys
|
|
5865
|
+
]);
|
|
5180
5866
|
for (const row of rows) {
|
|
5181
5867
|
const key = valueToString(row.key);
|
|
5182
5868
|
if (!expectedBubbleKeys.has(key)) continue;
|
|
@@ -5233,7 +5919,7 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
|
|
|
5233
5919
|
}
|
|
5234
5920
|
const hasDetailedTurnStats = Boolean(
|
|
5235
5921
|
metrics.turnStats?.some(
|
|
5236
|
-
(
|
|
5922
|
+
(stat11) => !!stat11.durationMs || !!stat11.contextTokens || !!stat11.tokenUsage
|
|
5237
5923
|
)
|
|
5238
5924
|
);
|
|
5239
5925
|
if (!hasDetailedTurnStats) {
|
|
@@ -5325,7 +6011,7 @@ function messagesToTurns(messages) {
|
|
|
5325
6011
|
}
|
|
5326
6012
|
}
|
|
5327
6013
|
const turnStats = buildStoreTurnStats(turns);
|
|
5328
|
-
const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum,
|
|
6014
|
+
const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0 : void 0;
|
|
5329
6015
|
return { turns, turnStats, totalDurationMs };
|
|
5330
6016
|
}
|
|
5331
6017
|
var CURSOR_SYSTEM_CONTEXT_RE = /^<(?:user_info|system_reminder|agent_transcripts|rules|git_status)>/;
|
|
@@ -5407,10 +6093,14 @@ function buildStoreTurnStats(turns) {
|
|
|
5407
6093
|
function mapCursorToolName(name) {
|
|
5408
6094
|
const mapping = {
|
|
5409
6095
|
Shell: "Bash",
|
|
6096
|
+
shell: "Bash",
|
|
6097
|
+
// Cursor SDK lowercase tool name
|
|
5410
6098
|
run_terminal_command_v2: "Bash",
|
|
5411
6099
|
run_terminal_cmd: "Bash",
|
|
5412
6100
|
Read: "Read",
|
|
5413
6101
|
ReadFile: "Read",
|
|
6102
|
+
read: "Read",
|
|
6103
|
+
// Cursor SDK lowercase tool name
|
|
5414
6104
|
read_file_v2: "Read",
|
|
5415
6105
|
read_file: "Read",
|
|
5416
6106
|
read_lints: "ReadLints",
|
|
@@ -5428,6 +6118,8 @@ function mapCursorToolName(name) {
|
|
|
5428
6118
|
LS: "Glob",
|
|
5429
6119
|
StrReplace: "Edit",
|
|
5430
6120
|
EditFile: "Edit",
|
|
6121
|
+
edit: "Edit",
|
|
6122
|
+
// Cursor SDK lowercase tool name
|
|
5431
6123
|
edit_file_v2: "Edit",
|
|
5432
6124
|
edit_file: "Edit",
|
|
5433
6125
|
search_replace: "Edit",
|
|
@@ -5690,42 +6382,426 @@ function extractModel(msg) {
|
|
|
5690
6382
|
return void 0;
|
|
5691
6383
|
}
|
|
5692
6384
|
|
|
6385
|
+
// src/providers/cursor/sdk-reader.ts
|
|
6386
|
+
import { execFile as execFile2 } from "child_process";
|
|
6387
|
+
import { readdir as readdir7, readFile as readFile11, stat as stat6 } from "fs/promises";
|
|
6388
|
+
import { homedir as homedir9 } from "os";
|
|
6389
|
+
import { join as join10 } from "path";
|
|
6390
|
+
import { promisify as promisify2 } from "util";
|
|
6391
|
+
var CURSOR_PROJECTS_ROOT = join10(homedir9(), ".cursor", "projects");
|
|
6392
|
+
var SDK_STORE_SUBDIR = "sdk-agent-store";
|
|
6393
|
+
var SDK_INDEX_DB_BASENAME = "index.db";
|
|
6394
|
+
var MIN_INDEX_DB_SIZE = 1024;
|
|
6395
|
+
var MAX_SQLJS_DB_BYTES = 128 * 1024 * 1024;
|
|
6396
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
6397
|
+
var getSqlJs2 = /* @__PURE__ */ (() => {
|
|
6398
|
+
let promise = null;
|
|
6399
|
+
return async () => {
|
|
6400
|
+
if (!promise) {
|
|
6401
|
+
promise = (async () => {
|
|
6402
|
+
const mod = await import("sql.js");
|
|
6403
|
+
return mod.default();
|
|
6404
|
+
})().catch((err) => {
|
|
6405
|
+
promise = null;
|
|
6406
|
+
throw err;
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6409
|
+
return promise;
|
|
6410
|
+
};
|
|
6411
|
+
})();
|
|
6412
|
+
var cachedAgentIndex = null;
|
|
6413
|
+
var cachedAgentIndexAt = 0;
|
|
6414
|
+
var AGENT_INDEX_TTL_MS = 5e3;
|
|
6415
|
+
async function discoverSdkAgents() {
|
|
6416
|
+
const index = await getSdkAgentIndex();
|
|
6417
|
+
return [...index.values()];
|
|
6418
|
+
}
|
|
6419
|
+
async function findSdkAgentById(agentId) {
|
|
6420
|
+
if (!agentId) return null;
|
|
6421
|
+
const index = await getSdkAgentIndex();
|
|
6422
|
+
return index.get(agentId) || null;
|
|
6423
|
+
}
|
|
6424
|
+
async function getSdkAgentIndex() {
|
|
6425
|
+
if (cachedAgentIndex && Date.now() - cachedAgentIndexAt < AGENT_INDEX_TTL_MS) {
|
|
6426
|
+
return cachedAgentIndex;
|
|
6427
|
+
}
|
|
6428
|
+
const dbPaths = await listSdkIndexDbPaths(CURSOR_PROJECTS_ROOT);
|
|
6429
|
+
const index = /* @__PURE__ */ new Map();
|
|
6430
|
+
for (const dbPath of dbPaths) {
|
|
6431
|
+
let handle = null;
|
|
6432
|
+
try {
|
|
6433
|
+
handle = await openIndexDb(dbPath);
|
|
6434
|
+
if (!handle) continue;
|
|
6435
|
+
const rows = await queryIndexDb(
|
|
6436
|
+
handle,
|
|
6437
|
+
"SELECT agent_id, workspace_ref, status, name, created_at, updated_at FROM agents"
|
|
6438
|
+
);
|
|
6439
|
+
for (const row of rows) {
|
|
6440
|
+
const agentId = stringOrEmpty(row.agent_id);
|
|
6441
|
+
if (!agentId) continue;
|
|
6442
|
+
index.set(agentId, {
|
|
6443
|
+
agentId,
|
|
6444
|
+
workspaceRef: stringOrEmpty(row.workspace_ref),
|
|
6445
|
+
status: stringOrEmpty(row.status) || "UNKNOWN",
|
|
6446
|
+
name: typeof row.name === "string" ? row.name : void 0,
|
|
6447
|
+
createdAt: stringOrEmpty(row.created_at),
|
|
6448
|
+
updatedAt: stringOrEmpty(row.updated_at),
|
|
6449
|
+
dbPath
|
|
6450
|
+
});
|
|
6451
|
+
}
|
|
6452
|
+
} catch {
|
|
6453
|
+
} finally {
|
|
6454
|
+
closeIndexDb(handle);
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
cachedAgentIndex = index;
|
|
6458
|
+
cachedAgentIndexAt = Date.now();
|
|
6459
|
+
return index;
|
|
6460
|
+
}
|
|
6461
|
+
async function loadSdkAgentEnrichment(agent) {
|
|
6462
|
+
let handle = null;
|
|
6463
|
+
try {
|
|
6464
|
+
handle = await openIndexDb(agent.dbPath);
|
|
6465
|
+
if (!handle) return null;
|
|
6466
|
+
const runRows = await queryIndexDb(
|
|
6467
|
+
handle,
|
|
6468
|
+
[
|
|
6469
|
+
"SELECT run_id, agent_id, turn_number, status, model,",
|
|
6470
|
+
"started_at, finished_at, result",
|
|
6471
|
+
`FROM runs WHERE agent_id = ${sqlString2(agent.agentId)}`,
|
|
6472
|
+
"ORDER BY turn_number ASC, created_at ASC"
|
|
6473
|
+
].join(" ")
|
|
6474
|
+
);
|
|
6475
|
+
if (runRows.length === 0) return null;
|
|
6476
|
+
const runs = runRows.map((row) => ({
|
|
6477
|
+
runId: stringOrEmpty(row.run_id),
|
|
6478
|
+
agentId: stringOrEmpty(row.agent_id),
|
|
6479
|
+
turnNumber: typeof row.turn_number === "number" ? row.turn_number : Number(row.turn_number),
|
|
6480
|
+
status: stringOrEmpty(row.status),
|
|
6481
|
+
model: row.model ? String(row.model) : void 0,
|
|
6482
|
+
startedAt: row.started_at ? String(row.started_at) : void 0,
|
|
6483
|
+
finishedAt: row.finished_at ? String(row.finished_at) : void 0,
|
|
6484
|
+
result: row.result ? String(row.result) : void 0
|
|
6485
|
+
}));
|
|
6486
|
+
const runIds = runs.map((r) => r.runId).filter(Boolean);
|
|
6487
|
+
if (runIds.length === 0) return { agent, runs, toolCallsByRun: /* @__PURE__ */ new Map() };
|
|
6488
|
+
const inList = runIds.map(sqlString2).join(", ");
|
|
6489
|
+
const eventRows = await queryIndexDb(
|
|
6490
|
+
handle,
|
|
6491
|
+
[
|
|
6492
|
+
"SELECT run_id, seq, payload_json FROM run_events",
|
|
6493
|
+
`WHERE run_id IN (${inList}) ORDER BY run_id, seq`
|
|
6494
|
+
].join(" ")
|
|
6495
|
+
);
|
|
6496
|
+
const toolCallsByRun = collectToolCalls(eventRows);
|
|
6497
|
+
return finalizeEnrichment(agent, runs, toolCallsByRun);
|
|
6498
|
+
} catch {
|
|
6499
|
+
return null;
|
|
6500
|
+
} finally {
|
|
6501
|
+
closeIndexDb(handle);
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
function finalizeEnrichment(agent, runs, toolCallsByRun) {
|
|
6505
|
+
let startedAt;
|
|
6506
|
+
let finishedAt;
|
|
6507
|
+
let totalDurationMs = 0;
|
|
6508
|
+
let totalDurationSeen = false;
|
|
6509
|
+
let latestModel;
|
|
6510
|
+
for (const run of runs) {
|
|
6511
|
+
if (run.startedAt && (!startedAt || run.startedAt < startedAt)) startedAt = run.startedAt;
|
|
6512
|
+
if (run.finishedAt && (!finishedAt || run.finishedAt > finishedAt)) finishedAt = run.finishedAt;
|
|
6513
|
+
if (run.startedAt && run.finishedAt) {
|
|
6514
|
+
const startMs = Date.parse(run.startedAt);
|
|
6515
|
+
const endMs = Date.parse(run.finishedAt);
|
|
6516
|
+
if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs) {
|
|
6517
|
+
totalDurationMs += endMs - startMs;
|
|
6518
|
+
totalDurationSeen = true;
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
if (run.model) latestModel = run.model;
|
|
6522
|
+
}
|
|
6523
|
+
return {
|
|
6524
|
+
agent,
|
|
6525
|
+
runs,
|
|
6526
|
+
toolCallsByRun,
|
|
6527
|
+
startedAt,
|
|
6528
|
+
finishedAt,
|
|
6529
|
+
totalDurationMs: totalDurationSeen ? totalDurationMs : void 0,
|
|
6530
|
+
latestModel
|
|
6531
|
+
};
|
|
6532
|
+
}
|
|
6533
|
+
function collectToolCalls(rows) {
|
|
6534
|
+
const out = /* @__PURE__ */ new Map();
|
|
6535
|
+
const inProgress = /* @__PURE__ */ new Map();
|
|
6536
|
+
for (const rawRow of rows) {
|
|
6537
|
+
const row = rawRow;
|
|
6538
|
+
const payload = row.payload_json;
|
|
6539
|
+
if (typeof payload !== "string" || !payload) continue;
|
|
6540
|
+
const parsed = safeJsonParse(payload);
|
|
6541
|
+
const message = parsed?.message;
|
|
6542
|
+
if (!message || message.type !== "tool_call") continue;
|
|
6543
|
+
const runId = stringOrEmpty(row.run_id);
|
|
6544
|
+
const callId = stringOrEmpty(message.call_id);
|
|
6545
|
+
if (!runId || !callId) continue;
|
|
6546
|
+
const seq = typeof row.seq === "number" ? row.seq : Number(row.seq);
|
|
6547
|
+
if (!Number.isFinite(seq)) continue;
|
|
6548
|
+
const key = `${runId}::${callId}`;
|
|
6549
|
+
const rawName = typeof message.name === "string" ? message.name : "Tool";
|
|
6550
|
+
const mappedName = mapCursorToolName(rawName);
|
|
6551
|
+
const argsObj = isPlainObject(message.args) ? message.args : {};
|
|
6552
|
+
const status = typeof message.status === "string" ? message.status : "unknown";
|
|
6553
|
+
let entry = inProgress.get(key);
|
|
6554
|
+
if (!entry) {
|
|
6555
|
+
entry = {
|
|
6556
|
+
callId,
|
|
6557
|
+
runId,
|
|
6558
|
+
firstSeq: seq,
|
|
6559
|
+
lastSeq: seq,
|
|
6560
|
+
name: mappedName,
|
|
6561
|
+
args: argsObj,
|
|
6562
|
+
status
|
|
6563
|
+
};
|
|
6564
|
+
inProgress.set(key, entry);
|
|
6565
|
+
const list = out.get(runId) || [];
|
|
6566
|
+
list.push(entry);
|
|
6567
|
+
out.set(runId, list);
|
|
6568
|
+
} else {
|
|
6569
|
+
entry.lastSeq = seq;
|
|
6570
|
+
entry.status = status;
|
|
6571
|
+
if (Object.keys(argsObj).length > 0) {
|
|
6572
|
+
entry.args = argsObj;
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
if (message.result !== void 0) {
|
|
6576
|
+
const formatted = formatSdkToolResult(rawName, message.result);
|
|
6577
|
+
entry.result = formatted.text;
|
|
6578
|
+
entry.isError = formatted.isError || status === "error";
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
for (const [runId, list] of out) {
|
|
6582
|
+
list.sort((a, b) => a.firstSeq - b.firstSeq);
|
|
6583
|
+
out.set(runId, list);
|
|
6584
|
+
}
|
|
6585
|
+
return out;
|
|
6586
|
+
}
|
|
6587
|
+
function formatSdkToolResult(rawToolName, result) {
|
|
6588
|
+
if (result === null || result === void 0) return { text: "", isError: false };
|
|
6589
|
+
if (typeof result === "string") return { text: result, isError: false };
|
|
6590
|
+
if (!isPlainObject(result)) {
|
|
6591
|
+
return { text: safeJsonStringify(result), isError: false };
|
|
6592
|
+
}
|
|
6593
|
+
const status = typeof result.status === "string" ? result.status : "";
|
|
6594
|
+
const isError = status === "error" || status === "failed" || result.error !== void 0;
|
|
6595
|
+
const value = result.value !== void 0 ? result.value : result;
|
|
6596
|
+
const mapped = mapCursorToolName(rawToolName);
|
|
6597
|
+
if (isError && typeof result.error === "string") {
|
|
6598
|
+
return { text: result.error, isError: true };
|
|
6599
|
+
}
|
|
6600
|
+
if (mapped === "Bash" && isPlainObject(value)) {
|
|
6601
|
+
const stdout = typeof value.stdout === "string" ? value.stdout : "";
|
|
6602
|
+
const stderr = typeof value.stderr === "string" ? value.stderr : "";
|
|
6603
|
+
const exitCode = typeof value.exitCode === "number" ? value.exitCode : void 0;
|
|
6604
|
+
const parts = [];
|
|
6605
|
+
if (stdout) parts.push(stdout.endsWith("\n") ? stdout.slice(0, -1) : stdout);
|
|
6606
|
+
if (stderr) parts.push(`[stderr]
|
|
6607
|
+
${stderr}`);
|
|
6608
|
+
if (parts.length === 0 && typeof exitCode === "number") parts.push(`exit ${exitCode}`);
|
|
6609
|
+
return {
|
|
6610
|
+
text: parts.join("\n"),
|
|
6611
|
+
isError: isError || exitCode !== void 0 && exitCode !== 0
|
|
6612
|
+
};
|
|
6613
|
+
}
|
|
6614
|
+
if (mapped === "Read" && isPlainObject(value) && typeof value.content === "string") {
|
|
6615
|
+
return { text: value.content, isError };
|
|
6616
|
+
}
|
|
6617
|
+
if ((mapped === "Edit" || mapped === "Write" || mapped === "MultiEdit") && isPlainObject(value)) {
|
|
6618
|
+
const diffString = typeof value.diffString === "string" ? value.diffString : "";
|
|
6619
|
+
if (diffString) {
|
|
6620
|
+
const wrapped = {
|
|
6621
|
+
...value,
|
|
6622
|
+
diff: { chunks: [{ diffString }] }
|
|
6623
|
+
};
|
|
6624
|
+
return { text: safeJsonStringify(wrapped), isError };
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6627
|
+
return { text: safeJsonStringify(value), isError };
|
|
6628
|
+
}
|
|
6629
|
+
function applySdkEnrichmentToTurns(turns, enrichment) {
|
|
6630
|
+
const sortedRuns = [...enrichment.runs].sort((a, b) => a.turnNumber - b.turnNumber);
|
|
6631
|
+
if (sortedRuns.length === 0) {
|
|
6632
|
+
return { turns, toolCallsEnriched: 0, assistantTurnsModelTagged: 0 };
|
|
6633
|
+
}
|
|
6634
|
+
let runIdx = -1;
|
|
6635
|
+
let toolUseIdx = 0;
|
|
6636
|
+
let toolCallsEnriched = 0;
|
|
6637
|
+
let assistantTurnsModelTagged = 0;
|
|
6638
|
+
for (const turn of turns) {
|
|
6639
|
+
if (turn.role === "user") {
|
|
6640
|
+
runIdx++;
|
|
6641
|
+
toolUseIdx = 0;
|
|
6642
|
+
continue;
|
|
6643
|
+
}
|
|
6644
|
+
if (turn.role !== "assistant") continue;
|
|
6645
|
+
if (runIdx < 0) {
|
|
6646
|
+
continue;
|
|
6647
|
+
}
|
|
6648
|
+
const run = sortedRuns[runIdx];
|
|
6649
|
+
if (!run) continue;
|
|
6650
|
+
if (run.model && !turn.model) {
|
|
6651
|
+
turn.model = run.model;
|
|
6652
|
+
assistantTurnsModelTagged++;
|
|
6653
|
+
}
|
|
6654
|
+
const sdkCalls = enrichment.toolCallsByRun.get(run.runId) || [];
|
|
6655
|
+
for (const block of turn.blocks) {
|
|
6656
|
+
if (block.type !== "tool_use") continue;
|
|
6657
|
+
if (typeof block._result === "string") continue;
|
|
6658
|
+
const sdkCall = sdkCalls[toolUseIdx];
|
|
6659
|
+
toolUseIdx++;
|
|
6660
|
+
if (!sdkCall) continue;
|
|
6661
|
+
if (sdkCall.result === void 0) continue;
|
|
6662
|
+
block._result = sdkCall.result;
|
|
6663
|
+
if (sdkCall.isError) block._isError = true;
|
|
6664
|
+
try {
|
|
6665
|
+
block.input = mapToolArgs(block.name, block.input, block._result);
|
|
6666
|
+
} catch {
|
|
6667
|
+
}
|
|
6668
|
+
toolCallsEnriched++;
|
|
6669
|
+
}
|
|
6670
|
+
}
|
|
6671
|
+
return { turns, toolCallsEnriched, assistantTurnsModelTagged };
|
|
6672
|
+
}
|
|
6673
|
+
async function openIndexDb(dbPath) {
|
|
6674
|
+
const st = await stat6(dbPath).catch(() => null);
|
|
6675
|
+
if (!st?.isFile() || st.size < MIN_INDEX_DB_SIZE) return null;
|
|
6676
|
+
if (await canUseSqliteCliFor(dbPath)) {
|
|
6677
|
+
return { dbPath, backend: "sqlite-cli" };
|
|
6678
|
+
}
|
|
6679
|
+
let SQL;
|
|
6680
|
+
try {
|
|
6681
|
+
SQL = await getSqlJs2();
|
|
6682
|
+
} catch {
|
|
6683
|
+
return null;
|
|
6684
|
+
}
|
|
6685
|
+
if (st.size > MAX_SQLJS_DB_BYTES) return null;
|
|
6686
|
+
const buffer = await readFile11(dbPath).catch(() => null);
|
|
6687
|
+
if (!buffer) return null;
|
|
6688
|
+
const db = new SQL.Database(buffer);
|
|
6689
|
+
return { dbPath, backend: "sqljs", db };
|
|
6690
|
+
}
|
|
6691
|
+
function closeIndexDb(handle) {
|
|
6692
|
+
if (!handle || handle.backend !== "sqljs" || !handle.db) return;
|
|
6693
|
+
try {
|
|
6694
|
+
handle.db.close();
|
|
6695
|
+
} catch {
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
async function queryIndexDb(handle, sql) {
|
|
6699
|
+
if (handle.backend === "sqlite-cli") {
|
|
6700
|
+
const { stdout } = await execFileAsync2("sqlite3", ["-readonly", "-json", handle.dbPath, sql], {
|
|
6701
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
6702
|
+
timeout: 6e4
|
|
6703
|
+
});
|
|
6704
|
+
const trimmed = stdout.trim();
|
|
6705
|
+
if (!trimmed) return [];
|
|
6706
|
+
const parsed = JSON.parse(trimmed);
|
|
6707
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
6708
|
+
}
|
|
6709
|
+
const result = handle.db.exec(sql);
|
|
6710
|
+
if (!Array.isArray(result) || result.length === 0) return [];
|
|
6711
|
+
const [{ columns, values }] = result;
|
|
6712
|
+
return values.map((row) => {
|
|
6713
|
+
const record = {};
|
|
6714
|
+
columns.forEach((column, index) => {
|
|
6715
|
+
record[column] = row[index];
|
|
6716
|
+
});
|
|
6717
|
+
return record;
|
|
6718
|
+
});
|
|
6719
|
+
}
|
|
6720
|
+
var sqliteCliCheckCache = /* @__PURE__ */ new Map();
|
|
6721
|
+
var SQLITE_CLI_CHECK_TTL_MS = 3e4;
|
|
6722
|
+
async function canUseSqliteCliFor(dbPath) {
|
|
6723
|
+
const cached = sqliteCliCheckCache.get(dbPath);
|
|
6724
|
+
if (cached && Date.now() - cached.checkedAt < SQLITE_CLI_CHECK_TTL_MS) return cached.canUse;
|
|
6725
|
+
let canUse = false;
|
|
6726
|
+
try {
|
|
6727
|
+
const { stdout } = await execFileAsync2(
|
|
6728
|
+
"sqlite3",
|
|
6729
|
+
["-readonly", "-json", dbPath, "SELECT json_valid('{}') AS ok;"],
|
|
6730
|
+
{ maxBuffer: 1024 * 1024 }
|
|
6731
|
+
);
|
|
6732
|
+
const rows = JSON.parse(stdout.trim());
|
|
6733
|
+
canUse = rows[0]?.ok === 1;
|
|
6734
|
+
} catch {
|
|
6735
|
+
canUse = false;
|
|
6736
|
+
}
|
|
6737
|
+
sqliteCliCheckCache.set(dbPath, { canUse, checkedAt: Date.now() });
|
|
6738
|
+
return canUse;
|
|
6739
|
+
}
|
|
6740
|
+
async function listSdkIndexDbPaths(projectsRoot) {
|
|
6741
|
+
const projects = await readdir7(projectsRoot).catch(() => []);
|
|
6742
|
+
const perProjectPaths = await Promise.all(
|
|
6743
|
+
projects.map(async (project) => {
|
|
6744
|
+
const out = [];
|
|
6745
|
+
const sdkRoot = join10(projectsRoot, project, SDK_STORE_SUBDIR);
|
|
6746
|
+
const sdkStat = await stat6(sdkRoot).catch(() => null);
|
|
6747
|
+
if (!sdkStat?.isDirectory()) return out;
|
|
6748
|
+
const projectHashes = await readdir7(sdkRoot).catch(() => []);
|
|
6749
|
+
for (const hash of projectHashes) {
|
|
6750
|
+
const dbPath = join10(sdkRoot, hash, SDK_INDEX_DB_BASENAME);
|
|
6751
|
+
const dbStat = await stat6(dbPath).catch(() => null);
|
|
6752
|
+
if (!dbStat?.isFile() || dbStat.size < MIN_INDEX_DB_SIZE) continue;
|
|
6753
|
+
out.push(dbPath);
|
|
6754
|
+
}
|
|
6755
|
+
return out;
|
|
6756
|
+
})
|
|
6757
|
+
);
|
|
6758
|
+
return perProjectPaths.flat();
|
|
6759
|
+
}
|
|
6760
|
+
function sqlString2(value) {
|
|
6761
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
6762
|
+
}
|
|
6763
|
+
function stringOrEmpty(value) {
|
|
6764
|
+
return typeof value === "string" ? value : "";
|
|
6765
|
+
}
|
|
6766
|
+
function isPlainObject(value) {
|
|
6767
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6768
|
+
}
|
|
6769
|
+
function safeJsonParse(text) {
|
|
6770
|
+
try {
|
|
6771
|
+
return JSON.parse(text);
|
|
6772
|
+
} catch {
|
|
6773
|
+
return null;
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
function safeJsonStringify(value) {
|
|
6777
|
+
try {
|
|
6778
|
+
return JSON.stringify(value);
|
|
6779
|
+
} catch {
|
|
6780
|
+
return String(value);
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
|
|
5693
6784
|
// src/providers/cursor/discover.ts
|
|
5694
|
-
var CURSOR_DIR =
|
|
6785
|
+
var CURSOR_DIR = join11(homedir10(), ".cursor", "projects");
|
|
6786
|
+
var PROJECT_DISCOVERY_CONCURRENCY = 6;
|
|
6787
|
+
var TRANSCRIPT_INFO_CONCURRENCY = 6;
|
|
6788
|
+
var ENTRY_STAT_CONCURRENCY = 32;
|
|
6789
|
+
var decodedProjectDirCache = /* @__PURE__ */ new Map();
|
|
5695
6790
|
async function discoverCursorSessions() {
|
|
5696
6791
|
const sessions = [];
|
|
5697
6792
|
let projectDirs;
|
|
5698
6793
|
try {
|
|
5699
|
-
projectDirs = await
|
|
6794
|
+
projectDirs = await readdir8(CURSOR_DIR);
|
|
5700
6795
|
} catch {
|
|
5701
6796
|
return sessions;
|
|
5702
6797
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
5711
|
-
const toolEntries = await collectToolEntries(join10(CURSOR_DIR, projDir, "agent-tools"));
|
|
5712
|
-
toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
5713
|
-
for (let i = 0; i < transcriptEntries.length; i++) {
|
|
5714
|
-
const transcript = transcriptEntries[i];
|
|
5715
|
-
const prevMtimeMs = i === 0 ? Number.NEGATIVE_INFINITY : transcriptEntries[i - 1].mtimeMs;
|
|
5716
|
-
const toolPaths = toolEntries.filter((t) => t.mtimeMs > prevMtimeMs && t.mtimeMs <= transcript.mtimeMs).map((t) => t.path);
|
|
5717
|
-
const info = await extractSessionInfo2(
|
|
5718
|
-
transcript.path,
|
|
5719
|
-
transcript.fileSize,
|
|
5720
|
-
transcript.mtimeMs,
|
|
5721
|
-
project,
|
|
5722
|
-
toolPaths
|
|
5723
|
-
);
|
|
5724
|
-
if (!info) continue;
|
|
5725
|
-
info.workspacePath = project;
|
|
5726
|
-
info.hasSqlite = false;
|
|
5727
|
-
sessions.push(info);
|
|
5728
|
-
}
|
|
6798
|
+
const projectSessions = await mapLimit(
|
|
6799
|
+
projectDirs,
|
|
6800
|
+
PROJECT_DISCOVERY_CONCURRENCY,
|
|
6801
|
+
(projDir) => discoverProjectSessions(projDir)
|
|
6802
|
+
);
|
|
6803
|
+
for (const projectSessionList of projectSessions) {
|
|
6804
|
+
sessions.push(...projectSessionList);
|
|
5729
6805
|
}
|
|
5730
6806
|
const transcriptSessions = sessions.slice();
|
|
5731
6807
|
const knownIds = new Set(transcriptSessions.map((s) => s.sessionId));
|
|
@@ -5740,54 +6816,159 @@ async function discoverCursorSessions() {
|
|
|
5740
6816
|
const hasStoreDb = storeDbSessionIds.has(session.sessionId);
|
|
5741
6817
|
session.hasSqlite = hasStoreDb || globalState.sessionIds.has(session.sessionId);
|
|
5742
6818
|
}
|
|
6819
|
+
await enrichWithSdkAgents(sessions);
|
|
5743
6820
|
sessions.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
5744
6821
|
return sessions;
|
|
5745
6822
|
}
|
|
6823
|
+
async function discoverProjectSessions(projDir) {
|
|
6824
|
+
const transcriptsDir = join11(CURSOR_DIR, projDir, "agent-transcripts");
|
|
6825
|
+
const dirStat = await stat7(transcriptsDir).catch(() => null);
|
|
6826
|
+
if (!dirStat?.isDirectory()) return [];
|
|
6827
|
+
const project = await decodeProjectDir2(projDir);
|
|
6828
|
+
const transcriptEntries = await collectTranscriptEntries(transcriptsDir);
|
|
6829
|
+
if (transcriptEntries.length === 0) return [];
|
|
6830
|
+
transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
6831
|
+
const toolEntries = await collectToolEntries(join11(CURSOR_DIR, projDir, "agent-tools"));
|
|
6832
|
+
toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
6833
|
+
const jobs = [];
|
|
6834
|
+
let toolStart = 0;
|
|
6835
|
+
let toolEnd = 0;
|
|
6836
|
+
for (let i = 0; i < transcriptEntries.length; i++) {
|
|
6837
|
+
const transcript = transcriptEntries[i];
|
|
6838
|
+
const prevMtimeMs = i === 0 ? Number.NEGATIVE_INFINITY : transcriptEntries[i - 1].mtimeMs;
|
|
6839
|
+
while (toolStart < toolEntries.length && toolEntries[toolStart].mtimeMs <= prevMtimeMs) {
|
|
6840
|
+
toolStart++;
|
|
6841
|
+
}
|
|
6842
|
+
while (toolEnd < toolEntries.length && toolEntries[toolEnd].mtimeMs <= transcript.mtimeMs) {
|
|
6843
|
+
toolEnd++;
|
|
6844
|
+
}
|
|
6845
|
+
jobs.push({
|
|
6846
|
+
...transcript,
|
|
6847
|
+
toolPaths: toolEntries.slice(toolStart, toolEnd).map((tool) => tool.path)
|
|
6848
|
+
});
|
|
6849
|
+
}
|
|
6850
|
+
const infos = await mapLimit(jobs, TRANSCRIPT_INFO_CONCURRENCY, async (job) => {
|
|
6851
|
+
const info = await extractSessionInfo2(
|
|
6852
|
+
job.path,
|
|
6853
|
+
job.fileSize,
|
|
6854
|
+
job.mtimeMs,
|
|
6855
|
+
project,
|
|
6856
|
+
job.toolPaths
|
|
6857
|
+
);
|
|
6858
|
+
if (!info) return null;
|
|
6859
|
+
info.workspacePath = project;
|
|
6860
|
+
info.hasSqlite = false;
|
|
6861
|
+
return info;
|
|
6862
|
+
});
|
|
6863
|
+
return infos.filter((info) => info !== null);
|
|
6864
|
+
}
|
|
6865
|
+
async function enrichWithSdkAgents(sessions) {
|
|
6866
|
+
let sdkAgents = [];
|
|
6867
|
+
try {
|
|
6868
|
+
sdkAgents = await discoverSdkAgents();
|
|
6869
|
+
} catch {
|
|
6870
|
+
return;
|
|
6871
|
+
}
|
|
6872
|
+
if (sdkAgents.length === 0) return;
|
|
6873
|
+
const sessionsByAgentId = /* @__PURE__ */ new Map();
|
|
6874
|
+
for (const session of sessions) {
|
|
6875
|
+
sessionsByAgentId.set(session.sessionId, session);
|
|
6876
|
+
}
|
|
6877
|
+
for (const agent of sdkAgents) {
|
|
6878
|
+
const existing = sessionsByAgentId.get(agent.agentId);
|
|
6879
|
+
if (!existing) continue;
|
|
6880
|
+
existing.hasSdk = true;
|
|
6881
|
+
if (agent.workspaceRef && (!existing.cwd || existing.cwd.startsWith("/-"))) {
|
|
6882
|
+
existing.cwd = agent.workspaceRef;
|
|
6883
|
+
existing.workspacePath = agent.workspaceRef;
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
5746
6887
|
async function decodeProjectDir2(encoded) {
|
|
6888
|
+
let cached = decodedProjectDirCache.get(encoded);
|
|
6889
|
+
if (!cached) {
|
|
6890
|
+
cached = decodeProjectDirUncached(encoded).catch((err) => {
|
|
6891
|
+
decodedProjectDirCache.delete(encoded);
|
|
6892
|
+
throw err;
|
|
6893
|
+
});
|
|
6894
|
+
decodedProjectDirCache.set(encoded, cached);
|
|
6895
|
+
}
|
|
6896
|
+
return cached;
|
|
6897
|
+
}
|
|
6898
|
+
async function decodeProjectDirUncached(encoded) {
|
|
6899
|
+
if (process.platform === "win32") return decodeWindowsProjectDir(encoded);
|
|
5747
6900
|
const parts = encoded.split("-");
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
6901
|
+
const startIdx = parts[0] ? 0 : 1;
|
|
6902
|
+
const resolved = await resolveEncodedProjectParts(parts, startIdx, "/");
|
|
6903
|
+
const fallbackEncoded = encoded.startsWith("-") ? encoded.slice(1) : encoded;
|
|
6904
|
+
return `/${resolved ? resolved.slice(1) : fallbackEncoded.replace(/-/g, "/")}`;
|
|
6905
|
+
}
|
|
6906
|
+
async function decodeWindowsProjectDir(encoded) {
|
|
6907
|
+
const parts = encoded.split("-").filter((part, idx) => part !== "" || idx !== 0);
|
|
6908
|
+
if (parts.length === 0) return encoded;
|
|
6909
|
+
const drive = parts[0];
|
|
6910
|
+
if (!/^[a-zA-Z]$/.test(drive)) {
|
|
6911
|
+
return parts.join("\\");
|
|
6912
|
+
}
|
|
6913
|
+
const root = `${drive.toUpperCase()}:\\`;
|
|
6914
|
+
const resolved = await resolveEncodedProjectParts(parts, 1, root);
|
|
6915
|
+
if (resolved) return resolved;
|
|
6916
|
+
return parts.length === 1 ? root : `${root}${parts.slice(1).join("\\")}`;
|
|
6917
|
+
}
|
|
6918
|
+
async function resolveEncodedProjectParts(parts, idx, current) {
|
|
6919
|
+
if (idx >= parts.length) {
|
|
6920
|
+
const currentStat = await stat7(current).catch(() => null);
|
|
6921
|
+
return currentStat?.isDirectory() ? current : null;
|
|
6922
|
+
}
|
|
6923
|
+
const entries = await readdir8(current, { withFileTypes: true }).catch(() => []);
|
|
6924
|
+
if (entries.length === 0) return null;
|
|
6925
|
+
const dirNames = new Set(
|
|
6926
|
+
entries.filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name)
|
|
6927
|
+
);
|
|
6928
|
+
for (let end = idx + 1; end <= parts.length; end++) {
|
|
6929
|
+
const candidate = parts.slice(idx, end).join("-");
|
|
6930
|
+
const candidatePath = join11(current, candidate);
|
|
6931
|
+
if (!dirNames.has(candidate)) {
|
|
6932
|
+
if (process.platform !== "win32") continue;
|
|
6933
|
+
const candidateStat = await stat7(candidatePath).catch(() => null);
|
|
6934
|
+
if (!candidateStat?.isDirectory()) continue;
|
|
5752
6935
|
}
|
|
5753
|
-
const
|
|
5754
|
-
|
|
5755
|
-
if (slashResult) return slashResult;
|
|
5756
|
-
const withHyphen = `${current}-${parts[idx]}`;
|
|
5757
|
-
return resolve3(idx + 1, withHyphen);
|
|
6936
|
+
const resolved = await resolveEncodedProjectParts(parts, end, candidatePath);
|
|
6937
|
+
if (resolved) return resolved;
|
|
5758
6938
|
}
|
|
5759
|
-
|
|
5760
|
-
return result || `/${encoded.replace(/-/g, "/")}`;
|
|
6939
|
+
return null;
|
|
5761
6940
|
}
|
|
5762
6941
|
async function extractSessionInfo2(filePath, fileSize, mtimeMs, project, toolPaths) {
|
|
5763
6942
|
try {
|
|
5764
|
-
const content = await
|
|
5765
|
-
const lines = content.split("\n").filter((l) => l.trim());
|
|
5766
|
-
if (lines.length < 2) return null;
|
|
6943
|
+
const content = await readFile12(filePath, "utf-8");
|
|
5767
6944
|
const sessionId = basename3(filePath, ".jsonl");
|
|
5768
6945
|
let firstPrompt = "";
|
|
5769
|
-
for (const line of lines.slice(0, 10)) {
|
|
5770
|
-
try {
|
|
5771
|
-
const obj = JSON.parse(line);
|
|
5772
|
-
if (obj.role === "user") {
|
|
5773
|
-
const textBlock = obj.message?.content?.find?.((b) => b.type === "text");
|
|
5774
|
-
if (textBlock?.text) {
|
|
5775
|
-
firstPrompt = sanitizeCursorUserText(textBlock.text).slice(0, 200);
|
|
5776
|
-
break;
|
|
5777
|
-
}
|
|
5778
|
-
}
|
|
5779
|
-
} catch {
|
|
5780
|
-
}
|
|
5781
|
-
}
|
|
5782
|
-
if (!firstPrompt) return null;
|
|
5783
6946
|
let promptCount = 0;
|
|
5784
6947
|
let toolCallCount = 0;
|
|
5785
6948
|
let editCountEst = 0;
|
|
5786
6949
|
let model;
|
|
6950
|
+
let lineCount = 0;
|
|
6951
|
+
let promptScanCount = 0;
|
|
5787
6952
|
const toolUseRe = /"type"\s*:\s*"tool_use"/g;
|
|
5788
6953
|
const editToolRe = /"name"\s*:\s*"(edit_file|file_edit|create_file)"/;
|
|
5789
6954
|
const modelRe = /"model(?:Id)?"\s*:\s*"([^"]+)"/;
|
|
5790
|
-
for (const
|
|
6955
|
+
for (const rawLine of content.split("\n")) {
|
|
6956
|
+
const line = rawLine.trim();
|
|
6957
|
+
if (!line) continue;
|
|
6958
|
+
lineCount++;
|
|
6959
|
+
if (!firstPrompt && promptScanCount < 10) {
|
|
6960
|
+
promptScanCount++;
|
|
6961
|
+
try {
|
|
6962
|
+
const obj = JSON.parse(line);
|
|
6963
|
+
if (obj.role === "user") {
|
|
6964
|
+
const textBlock = obj.message?.content?.find?.((b) => b.type === "text");
|
|
6965
|
+
if (textBlock?.text) {
|
|
6966
|
+
firstPrompt = sanitizeCursorUserText(textBlock.text).slice(0, 200);
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
} catch {
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
5791
6972
|
if ((line.includes('"role":"user"') || line.includes('"role": "user"')) && !line.includes('"tool_result"')) {
|
|
5792
6973
|
promptCount++;
|
|
5793
6974
|
}
|
|
@@ -5801,6 +6982,8 @@ async function extractSessionInfo2(filePath, fileSize, mtimeMs, project, toolPat
|
|
|
5801
6982
|
if (m) model = m[1];
|
|
5802
6983
|
}
|
|
5803
6984
|
}
|
|
6985
|
+
if (lineCount < 2) return null;
|
|
6986
|
+
if (!firstPrompt) return null;
|
|
5804
6987
|
toolCallCount = Math.max(toolCallCount, toolPaths.length);
|
|
5805
6988
|
const timestamp = new Date(mtimeMs).toISOString();
|
|
5806
6989
|
return {
|
|
@@ -5811,7 +6994,7 @@ async function extractSessionInfo2(filePath, fileSize, mtimeMs, project, toolPat
|
|
|
5811
6994
|
cwd: project,
|
|
5812
6995
|
version: "",
|
|
5813
6996
|
timestamp,
|
|
5814
|
-
lineCount
|
|
6997
|
+
lineCount,
|
|
5815
6998
|
fileSize,
|
|
5816
6999
|
filePath,
|
|
5817
7000
|
filePaths: [filePath],
|
|
@@ -5829,59 +7012,73 @@ async function extractSessionInfo2(filePath, fileSize, mtimeMs, project, toolPat
|
|
|
5829
7012
|
async function collectTranscriptEntries(transcriptsDir) {
|
|
5830
7013
|
let entries;
|
|
5831
7014
|
try {
|
|
5832
|
-
entries = await
|
|
7015
|
+
entries = await readdir8(transcriptsDir);
|
|
5833
7016
|
} catch {
|
|
5834
7017
|
return [];
|
|
5835
7018
|
}
|
|
5836
|
-
const transcripts =
|
|
5837
|
-
|
|
5838
|
-
const
|
|
5839
|
-
|
|
5840
|
-
if (!entryStat) continue;
|
|
7019
|
+
const transcripts = await mapLimit(entries, ENTRY_STAT_CONCURRENCY, async (entry) => {
|
|
7020
|
+
const entryPath = join11(transcriptsDir, entry);
|
|
7021
|
+
const entryStat = await stat7(entryPath).catch(() => null);
|
|
7022
|
+
if (!entryStat) return null;
|
|
5841
7023
|
if (entry.endsWith(".jsonl") && entryStat.isFile()) {
|
|
5842
|
-
|
|
7024
|
+
return {
|
|
5843
7025
|
path: entryPath,
|
|
5844
7026
|
fileSize: entryStat.size,
|
|
5845
7027
|
mtimeMs: entryStat.mtimeMs
|
|
5846
|
-
}
|
|
5847
|
-
continue;
|
|
7028
|
+
};
|
|
5848
7029
|
}
|
|
5849
|
-
if (!entryStat.isDirectory())
|
|
5850
|
-
const innerPath =
|
|
5851
|
-
const innerStat = await
|
|
5852
|
-
if (!innerStat?.isFile())
|
|
5853
|
-
|
|
7030
|
+
if (!entryStat.isDirectory()) return null;
|
|
7031
|
+
const innerPath = join11(entryPath, `${entry}.jsonl`);
|
|
7032
|
+
const innerStat = await stat7(innerPath).catch(() => null);
|
|
7033
|
+
if (!innerStat?.isFile()) return null;
|
|
7034
|
+
return {
|
|
5854
7035
|
path: innerPath,
|
|
5855
7036
|
fileSize: innerStat.size,
|
|
5856
7037
|
mtimeMs: innerStat.mtimeMs
|
|
5857
|
-
}
|
|
5858
|
-
}
|
|
5859
|
-
return transcripts;
|
|
7038
|
+
};
|
|
7039
|
+
});
|
|
7040
|
+
return transcripts.filter((entry) => entry !== null);
|
|
5860
7041
|
}
|
|
5861
7042
|
async function collectToolEntries(toolDir) {
|
|
5862
|
-
const dirStat = await
|
|
7043
|
+
const dirStat = await stat7(toolDir).catch(() => null);
|
|
5863
7044
|
if (!dirStat?.isDirectory()) return [];
|
|
5864
7045
|
let entries;
|
|
5865
7046
|
try {
|
|
5866
|
-
entries = await
|
|
7047
|
+
entries = await readdir8(toolDir);
|
|
5867
7048
|
} catch {
|
|
5868
7049
|
return [];
|
|
5869
7050
|
}
|
|
5870
|
-
const
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
const
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
7051
|
+
const toolFiles = entries.filter((entry) => entry.endsWith(".txt"));
|
|
7052
|
+
const tools = await mapLimit(toolFiles, ENTRY_STAT_CONCURRENCY, async (entry) => {
|
|
7053
|
+
const entryPath = join11(toolDir, entry);
|
|
7054
|
+
const entryStat = await stat7(entryPath).catch(() => null);
|
|
7055
|
+
if (!entryStat?.isFile()) return null;
|
|
7056
|
+
return { path: entryPath, mtimeMs: entryStat.mtimeMs };
|
|
7057
|
+
});
|
|
7058
|
+
return tools.filter((entry) => entry !== null);
|
|
7059
|
+
}
|
|
7060
|
+
async function mapLimit(items, concurrency, worker) {
|
|
7061
|
+
if (items.length === 0) return [];
|
|
7062
|
+
const results = [];
|
|
7063
|
+
results.length = items.length;
|
|
7064
|
+
let nextIndex = 0;
|
|
7065
|
+
const workerCount = Math.min(Math.max(1, concurrency), items.length);
|
|
7066
|
+
await Promise.all(
|
|
7067
|
+
Array.from({ length: workerCount }, async () => {
|
|
7068
|
+
while (nextIndex < items.length) {
|
|
7069
|
+
const index = nextIndex++;
|
|
7070
|
+
results[index] = await worker(items[index], index);
|
|
7071
|
+
}
|
|
7072
|
+
})
|
|
7073
|
+
);
|
|
7074
|
+
return results;
|
|
5879
7075
|
}
|
|
5880
7076
|
|
|
5881
7077
|
// src/providers/cursor/parser.ts
|
|
5882
|
-
import { readdir as
|
|
5883
|
-
import { homedir as
|
|
5884
|
-
import { basename as basename4, extname as extname2, join as
|
|
7078
|
+
import { readdir as readdir9, readFile as readFile13, stat as stat8 } from "fs/promises";
|
|
7079
|
+
import { homedir as homedir11 } from "os";
|
|
7080
|
+
import { basename as basename4, extname as extname2, join as join12 } from "path";
|
|
7081
|
+
var CURSOR_UUID_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5885
7082
|
function toErrorMessage(err) {
|
|
5886
7083
|
if (err instanceof Error && err.message) return err.message;
|
|
5887
7084
|
return String(err);
|
|
@@ -5895,15 +7092,17 @@ async function parseCursorSession(filePaths, sessionInfo) {
|
|
|
5895
7092
|
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
|
5896
7093
|
const transcriptPaths = paths.filter((p) => p.endsWith(".jsonl"));
|
|
5897
7094
|
const explicitToolPaths = paths.filter((p) => p.endsWith(".txt"));
|
|
7095
|
+
const inferredSqliteSession = inferCursorSqliteSession(paths);
|
|
5898
7096
|
let sqliteError;
|
|
5899
7097
|
let sqliteFallbackNote;
|
|
5900
7098
|
let sqliteAttempted = false;
|
|
5901
|
-
|
|
7099
|
+
const sqliteSessionId = sessionInfo?.sessionId || inferredSqliteSession?.sessionId;
|
|
7100
|
+
if (sqliteSessionId) {
|
|
5902
7101
|
sqliteAttempted = true;
|
|
5903
7102
|
let sqliteResult = null;
|
|
5904
7103
|
try {
|
|
5905
|
-
const preferredWorkspacePath2 = sessionInfo
|
|
5906
|
-
sqliteResult = await parseCursorSqlite(preferredWorkspacePath2,
|
|
7104
|
+
const preferredWorkspacePath2 = sessionInfo?.workspacePath || sessionInfo?.cwd || "";
|
|
7105
|
+
sqliteResult = await parseCursorSqlite(preferredWorkspacePath2, sqliteSessionId);
|
|
5907
7106
|
} catch (err) {
|
|
5908
7107
|
sqliteError = compactErrorMessage(err);
|
|
5909
7108
|
sqliteFallbackNote = `cursor SQLite parse failed (${sqliteError}); fell back to JSONL transcript`;
|
|
@@ -5916,6 +7115,10 @@ async function parseCursorSession(filePaths, sessionInfo) {
|
|
|
5916
7115
|
const jsonlThinking = await parseCursorJsonl(transcriptPaths, [], {
|
|
5917
7116
|
inferToolPaths: false
|
|
5918
7117
|
});
|
|
7118
|
+
sqliteResult.parseWarnings = mergeParseWarnings(
|
|
7119
|
+
sqliteResult.parseWarnings,
|
|
7120
|
+
jsonlThinking.parseWarnings
|
|
7121
|
+
);
|
|
5919
7122
|
sqliteResult.turns = mergeJsonlSupplementsIntoCursorTurns(
|
|
5920
7123
|
sqliteResult.turns,
|
|
5921
7124
|
jsonlThinking.turns
|
|
@@ -5958,8 +7161,83 @@ async function parseCursorSession(filePaths, sessionInfo) {
|
|
|
5958
7161
|
sqliteFallbackNote
|
|
5959
7162
|
);
|
|
5960
7163
|
}
|
|
7164
|
+
const sdkSessionId = sessionInfo?.sessionId || deriveSessionIdFromTranscript(transcriptPaths);
|
|
7165
|
+
if (sdkSessionId) {
|
|
7166
|
+
const enrichment = await tryLoadSdkEnrichment(sdkSessionId);
|
|
7167
|
+
if (enrichment) {
|
|
7168
|
+
const { toolCallsEnriched, assistantTurnsModelTagged } = applySdkEnrichmentToTurns(
|
|
7169
|
+
jsonlResult.turns,
|
|
7170
|
+
enrichment
|
|
7171
|
+
);
|
|
7172
|
+
if (toolCallsEnriched > 0 || assistantTurnsModelTagged > 0) {
|
|
7173
|
+
jsonlResult.dataSourceInfo = withSupplement(
|
|
7174
|
+
jsonlResult.dataSourceInfo || defaultDataSourceInfo(jsonlResult.dataSource),
|
|
7175
|
+
`cursor-sdk index.db (tool results +${toolCallsEnriched}, model tags +${assistantTurnsModelTagged})`
|
|
7176
|
+
);
|
|
7177
|
+
}
|
|
7178
|
+
if (enrichment.latestModel && !jsonlResult.model) {
|
|
7179
|
+
jsonlResult.model = enrichment.latestModel;
|
|
7180
|
+
}
|
|
7181
|
+
if (enrichment.startedAt && !jsonlResult.startTime) {
|
|
7182
|
+
jsonlResult.startTime = enrichment.startedAt;
|
|
7183
|
+
}
|
|
7184
|
+
if (enrichment.finishedAt && !jsonlResult.endTime) {
|
|
7185
|
+
jsonlResult.endTime = enrichment.finishedAt;
|
|
7186
|
+
}
|
|
7187
|
+
if (enrichment.totalDurationMs && !jsonlResult.totalDurationMs) {
|
|
7188
|
+
jsonlResult.totalDurationMs = enrichment.totalDurationMs;
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
5961
7192
|
return jsonlResult;
|
|
5962
7193
|
}
|
|
7194
|
+
function inferCursorSqliteSession(paths) {
|
|
7195
|
+
for (const path of paths) {
|
|
7196
|
+
const sessionId = sessionIdFromGlobalStatePath(path) || sessionIdFromStoreDbPath(path);
|
|
7197
|
+
if (sessionId) return { sessionId };
|
|
7198
|
+
}
|
|
7199
|
+
return void 0;
|
|
7200
|
+
}
|
|
7201
|
+
function sessionIdFromGlobalStatePath(path) {
|
|
7202
|
+
const marker = "#composerData:";
|
|
7203
|
+
const markerIndex = path.indexOf(marker);
|
|
7204
|
+
if (markerIndex < 0) return void 0;
|
|
7205
|
+
const rawSessionId = path.slice(markerIndex + marker.length).split(/[/?#]/, 1)[0]?.trim();
|
|
7206
|
+
return rawSessionId || void 0;
|
|
7207
|
+
}
|
|
7208
|
+
function sessionIdFromStoreDbPath(path) {
|
|
7209
|
+
const normalized = path.replaceAll("\\", "/");
|
|
7210
|
+
const parts = normalized.split("/");
|
|
7211
|
+
if (parts.at(-1) !== "store.db") return void 0;
|
|
7212
|
+
const rawSessionId = parts.at(-2)?.trim();
|
|
7213
|
+
if (!rawSessionId || !CURSOR_UUID_SESSION_ID_RE.test(rawSessionId)) return void 0;
|
|
7214
|
+
return rawSessionId;
|
|
7215
|
+
}
|
|
7216
|
+
async function tryLoadSdkEnrichment(sessionId) {
|
|
7217
|
+
if (!sessionId.startsWith("agent-")) return null;
|
|
7218
|
+
try {
|
|
7219
|
+
const agent = await findSdkAgentById(sessionId);
|
|
7220
|
+
if (!agent) return null;
|
|
7221
|
+
return await loadSdkAgentEnrichment(agent);
|
|
7222
|
+
} catch {
|
|
7223
|
+
return null;
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
function deriveSessionIdFromTranscript(transcriptPaths) {
|
|
7227
|
+
for (let i = transcriptPaths.length - 1; i >= 0; i--) {
|
|
7228
|
+
const p = transcriptPaths[i];
|
|
7229
|
+
const base = basename4(p, ".jsonl");
|
|
7230
|
+
if (base.startsWith("agent-")) return base;
|
|
7231
|
+
}
|
|
7232
|
+
return null;
|
|
7233
|
+
}
|
|
7234
|
+
function mergeParseWarnings(base, extra) {
|
|
7235
|
+
const merged = [];
|
|
7236
|
+
for (const warning of [...base || [], ...extra || []]) {
|
|
7237
|
+
addParseWarning(merged, warning);
|
|
7238
|
+
}
|
|
7239
|
+
return merged.length > 0 ? merged : void 0;
|
|
7240
|
+
}
|
|
5963
7241
|
function defaultDataSourceInfo(dataSource) {
|
|
5964
7242
|
if (!dataSource) return void 0;
|
|
5965
7243
|
return {
|
|
@@ -5985,16 +7263,26 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
|
|
|
5985
7263
|
const toolResults = /* @__PURE__ */ new Map();
|
|
5986
7264
|
const toolErrors = /* @__PURE__ */ new Map();
|
|
5987
7265
|
const toolImages = /* @__PURE__ */ new Map();
|
|
7266
|
+
const parseWarnings = [];
|
|
5988
7267
|
const sortedTranscriptPaths = await sortByMtime(transcriptPaths);
|
|
5989
7268
|
const sessionId = basename4(sortedTranscriptPaths[sortedTranscriptPaths.length - 1], ".jsonl");
|
|
5990
7269
|
for (const filePath of sortedTranscriptPaths) {
|
|
5991
|
-
const content = await
|
|
5992
|
-
const lines = content.split("\n")
|
|
5993
|
-
for (
|
|
7270
|
+
const content = await readFile13(filePath, "utf-8");
|
|
7271
|
+
const lines = content.split("\n");
|
|
7272
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
7273
|
+
const line = lines[lineIndex];
|
|
7274
|
+
if (!line.trim()) continue;
|
|
5994
7275
|
let obj;
|
|
5995
7276
|
try {
|
|
5996
7277
|
obj = JSON.parse(line);
|
|
5997
7278
|
} catch {
|
|
7279
|
+
addParseWarning(parseWarnings, {
|
|
7280
|
+
kind: "malformed-json",
|
|
7281
|
+
source: "cursor transcript JSONL",
|
|
7282
|
+
firstLine: lineIndex + 1,
|
|
7283
|
+
message: "Skipped malformed JSONL line",
|
|
7284
|
+
sample: line
|
|
7285
|
+
});
|
|
5998
7286
|
continue;
|
|
5999
7287
|
}
|
|
6000
7288
|
const role = obj.role;
|
|
@@ -6031,7 +7319,17 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
|
|
|
6031
7319
|
}
|
|
6032
7320
|
for (const imagePath of imageFilePaths) {
|
|
6033
7321
|
const dataUrl = await readImageFileAsDataUrl(imagePath);
|
|
6034
|
-
if (dataUrl)
|
|
7322
|
+
if (dataUrl) {
|
|
7323
|
+
userImages.push(dataUrl);
|
|
7324
|
+
} else {
|
|
7325
|
+
addParseWarning(parseWarnings, {
|
|
7326
|
+
kind: "missing-image",
|
|
7327
|
+
source: "cursor transcript image reference",
|
|
7328
|
+
firstLine: lineIndex + 1,
|
|
7329
|
+
message: "Skipped image reference because the file could not be read",
|
|
7330
|
+
sample: imagePath
|
|
7331
|
+
});
|
|
7332
|
+
}
|
|
6035
7333
|
}
|
|
6036
7334
|
if (role === "user" && textParts.length === 0 && userImages.length === 0) continue;
|
|
6037
7335
|
if (role === "user") {
|
|
@@ -6118,7 +7416,8 @@ async function parseCursorJsonl(transcriptPaths, explicitToolPaths, options) {
|
|
|
6118
7416
|
"cursor/projects/agent-transcripts/*.jsonl",
|
|
6119
7417
|
...hasToolData ? ["cursor/projects/agent-tools/*.txt"] : []
|
|
6120
7418
|
]
|
|
6121
|
-
}
|
|
7419
|
+
},
|
|
7420
|
+
parseWarnings: parseWarnings.length > 0 ? parseWarnings : void 0
|
|
6122
7421
|
};
|
|
6123
7422
|
}
|
|
6124
7423
|
function stripUserQueryWrapper(text) {
|
|
@@ -6133,7 +7432,9 @@ function normalizeImagePlaceholderLines(text) {
|
|
|
6133
7432
|
return filtered.join("\n").trim();
|
|
6134
7433
|
}
|
|
6135
7434
|
function resolveImagePath(pathValue) {
|
|
6136
|
-
if (pathValue.startsWith("~/")
|
|
7435
|
+
if (pathValue.startsWith("~/") || pathValue.startsWith("~\\")) {
|
|
7436
|
+
return join12(homedir11(), pathValue.slice(2));
|
|
7437
|
+
}
|
|
6137
7438
|
return pathValue;
|
|
6138
7439
|
}
|
|
6139
7440
|
function imageMediaType(pathValue) {
|
|
@@ -6149,7 +7450,7 @@ function extractImageFilePathsFromText(text) {
|
|
|
6149
7450
|
let match;
|
|
6150
7451
|
while ((match = blockPattern.exec(text)) !== null) {
|
|
6151
7452
|
const blockContent = match[1];
|
|
6152
|
-
const pathRegex = /(?:^|\n)\s*(?:\d+\.\s*)?((
|
|
7453
|
+
const pathRegex = /(?:^|\n)\s*(?:\d+\.\s*)?((?:~[\\/]|[\\/]|[A-Za-z]:[\\/])[^\n]+\.(?:png|jpe?g|gif|webp))/gi;
|
|
6153
7454
|
let pathMatch;
|
|
6154
7455
|
while ((pathMatch = pathRegex.exec(blockContent)) !== null) {
|
|
6155
7456
|
const pathValue = pathMatch[1].trim();
|
|
@@ -6161,7 +7462,7 @@ function extractImageFilePathsFromText(text) {
|
|
|
6161
7462
|
}
|
|
6162
7463
|
async function readImageFileAsDataUrl(pathValue) {
|
|
6163
7464
|
try {
|
|
6164
|
-
const data = await
|
|
7465
|
+
const data = await readFile13(pathValue);
|
|
6165
7466
|
return `data:${imageMediaType(pathValue)};base64,${data.toString("base64")}`;
|
|
6166
7467
|
} catch {
|
|
6167
7468
|
return null;
|
|
@@ -6298,7 +7599,7 @@ function mergeJsonlSupplementsIntoCursorTurns(primaryTurns, jsonlTurns) {
|
|
|
6298
7599
|
async function sortByMtime(paths) {
|
|
6299
7600
|
const entries = [];
|
|
6300
7601
|
for (const path of paths) {
|
|
6301
|
-
const st = await
|
|
7602
|
+
const st = await stat8(path).catch(() => null);
|
|
6302
7603
|
if (!st?.isFile()) continue;
|
|
6303
7604
|
entries.push({ path, mtimeMs: st.mtimeMs });
|
|
6304
7605
|
}
|
|
@@ -6316,8 +7617,8 @@ async function inferToolPaths(transcriptPaths) {
|
|
|
6316
7617
|
projects.get(projectRoot)?.add(transcriptPath);
|
|
6317
7618
|
}
|
|
6318
7619
|
for (const [projectRoot, selectedPaths] of projects.entries()) {
|
|
6319
|
-
const transcriptsDir =
|
|
6320
|
-
const toolDir =
|
|
7620
|
+
const transcriptsDir = join12(projectRoot, "agent-transcripts");
|
|
7621
|
+
const toolDir = join12(projectRoot, "agent-tools");
|
|
6321
7622
|
const transcriptEntries = await collectTranscriptEntries2(transcriptsDir);
|
|
6322
7623
|
const toolEntries = await collectToolEntries2(toolDir);
|
|
6323
7624
|
if (transcriptEntries.length === 0 || toolEntries.length === 0) continue;
|
|
@@ -6340,22 +7641,22 @@ async function inferToolPaths(transcriptPaths) {
|
|
|
6340
7641
|
async function collectTranscriptEntries2(transcriptsDir) {
|
|
6341
7642
|
let entries;
|
|
6342
7643
|
try {
|
|
6343
|
-
entries = await
|
|
7644
|
+
entries = await readdir9(transcriptsDir);
|
|
6344
7645
|
} catch {
|
|
6345
7646
|
return [];
|
|
6346
7647
|
}
|
|
6347
7648
|
const transcripts = [];
|
|
6348
7649
|
for (const entry of entries) {
|
|
6349
|
-
const entryPath =
|
|
6350
|
-
const st = await
|
|
7650
|
+
const entryPath = join12(transcriptsDir, entry);
|
|
7651
|
+
const st = await stat8(entryPath).catch(() => null);
|
|
6351
7652
|
if (!st) continue;
|
|
6352
7653
|
if (entry.endsWith(".jsonl") && st.isFile()) {
|
|
6353
7654
|
transcripts.push({ path: entryPath, mtimeMs: st.mtimeMs });
|
|
6354
7655
|
continue;
|
|
6355
7656
|
}
|
|
6356
7657
|
if (!st.isDirectory()) continue;
|
|
6357
|
-
const nested =
|
|
6358
|
-
const nestedStat = await
|
|
7658
|
+
const nested = join12(entryPath, `${entry}.jsonl`);
|
|
7659
|
+
const nestedStat = await stat8(nested).catch(() => null);
|
|
6359
7660
|
if (!nestedStat?.isFile()) continue;
|
|
6360
7661
|
transcripts.push({ path: nested, mtimeMs: nestedStat.mtimeMs });
|
|
6361
7662
|
}
|
|
@@ -6364,15 +7665,15 @@ async function collectTranscriptEntries2(transcriptsDir) {
|
|
|
6364
7665
|
async function collectToolEntries2(toolDir) {
|
|
6365
7666
|
let entries;
|
|
6366
7667
|
try {
|
|
6367
|
-
entries = await
|
|
7668
|
+
entries = await readdir9(toolDir);
|
|
6368
7669
|
} catch {
|
|
6369
7670
|
return [];
|
|
6370
7671
|
}
|
|
6371
7672
|
const tools = [];
|
|
6372
7673
|
for (const entry of entries) {
|
|
6373
7674
|
if (!entry.endsWith(".txt")) continue;
|
|
6374
|
-
const entryPath =
|
|
6375
|
-
const st = await
|
|
7675
|
+
const entryPath = join12(toolDir, entry);
|
|
7676
|
+
const st = await stat8(entryPath).catch(() => null);
|
|
6376
7677
|
if (!st?.isFile()) continue;
|
|
6377
7678
|
tools.push({ path: entryPath, mtimeMs: st.mtimeMs });
|
|
6378
7679
|
}
|
|
@@ -6386,10 +7687,10 @@ function getCursorProjectRoot(transcriptPath) {
|
|
|
6386
7687
|
async function loadToolEvents(toolPaths) {
|
|
6387
7688
|
const events = [];
|
|
6388
7689
|
for (const path of toolPaths) {
|
|
6389
|
-
const content = await
|
|
7690
|
+
const content = await readFile13(path, "utf-8").catch(() => "");
|
|
6390
7691
|
const result = content.trim();
|
|
6391
7692
|
if (!result) continue;
|
|
6392
|
-
const st = await
|
|
7693
|
+
const st = await stat8(path).catch(() => null);
|
|
6393
7694
|
const id = basename4(path, extname2(path));
|
|
6394
7695
|
events.push({
|
|
6395
7696
|
id,
|
|
@@ -6532,8 +7833,8 @@ init_cloud();
|
|
|
6532
7833
|
// src/publishers/gist.ts
|
|
6533
7834
|
init_cloud();
|
|
6534
7835
|
import { createHash as createHash2 } from "crypto";
|
|
6535
|
-
import { readFile as
|
|
6536
|
-
import { join as
|
|
7836
|
+
import { readFile as readFile16, writeFile as writeFile4 } from "fs/promises";
|
|
7837
|
+
import { join as join15 } from "path";
|
|
6537
7838
|
var GIST_META_FILE = ".vibe-replay-gist.json";
|
|
6538
7839
|
function checkPublishStatus() {
|
|
6539
7840
|
const auth = loadAuthToken();
|
|
@@ -6545,12 +7846,16 @@ async function publishGist(outputDir, title, opts) {
|
|
|
6545
7846
|
if (!auth) {
|
|
6546
7847
|
throw new Error("Not logged in. Run `vibe-replay auth login` first.");
|
|
6547
7848
|
}
|
|
6548
|
-
const jsonPath =
|
|
6549
|
-
const content = await
|
|
7849
|
+
const jsonPath = join15(outputDir, "replay.json");
|
|
7850
|
+
const content = await readFile16(jsonPath, "utf-8");
|
|
6550
7851
|
const overwrite = opts?.overwrite;
|
|
6551
7852
|
const filename = overwrite?.filename || `${sanitizeFilename(title)}.json`;
|
|
6552
7853
|
const description = `vibe-replay: ${title}`;
|
|
6553
7854
|
const apiUrl = getApiUrl();
|
|
7855
|
+
const headers = {
|
|
7856
|
+
"Content-Type": "application/json",
|
|
7857
|
+
Cookie: `${getSessionCookieName(apiUrl)}=${auth.token}`
|
|
7858
|
+
};
|
|
6554
7859
|
let gistId;
|
|
6555
7860
|
let gistUrl;
|
|
6556
7861
|
let viewerUrl;
|
|
@@ -6558,10 +7863,7 @@ async function publishGist(outputDir, title, opts) {
|
|
|
6558
7863
|
if (overwrite) {
|
|
6559
7864
|
const resp = await fetch(`${apiUrl}/api/gists/${overwrite.gistId}`, {
|
|
6560
7865
|
method: "PATCH",
|
|
6561
|
-
headers
|
|
6562
|
-
"Content-Type": "application/json",
|
|
6563
|
-
Cookie: `${getSessionCookieName(apiUrl)}=${auth.token}`
|
|
6564
|
-
},
|
|
7866
|
+
headers,
|
|
6565
7867
|
body: JSON.stringify({ filename, content, description })
|
|
6566
7868
|
});
|
|
6567
7869
|
if (!resp.ok) {
|
|
@@ -6578,10 +7880,7 @@ async function publishGist(outputDir, title, opts) {
|
|
|
6578
7880
|
} else {
|
|
6579
7881
|
const resp = await fetch(`${apiUrl}/api/gists`, {
|
|
6580
7882
|
method: "POST",
|
|
6581
|
-
headers
|
|
6582
|
-
"Content-Type": "application/json",
|
|
6583
|
-
Cookie: `${getSessionCookieName(apiUrl)}=${auth.token}`
|
|
6584
|
-
},
|
|
7883
|
+
headers,
|
|
6585
7884
|
body: JSON.stringify({ filename, content, description, public: true })
|
|
6586
7885
|
});
|
|
6587
7886
|
if (!resp.ok) {
|
|
@@ -6612,7 +7911,7 @@ async function publishGist(outputDir, title, opts) {
|
|
|
6612
7911
|
}
|
|
6613
7912
|
async function loadSavedGistInfo(outputDir) {
|
|
6614
7913
|
try {
|
|
6615
|
-
const raw = await
|
|
7914
|
+
const raw = await readFile16(join15(outputDir, GIST_META_FILE), "utf-8");
|
|
6616
7915
|
const parsed = JSON.parse(raw);
|
|
6617
7916
|
if (!parsed?.gistId || !parsed?.filename) return void 0;
|
|
6618
7917
|
return parsed;
|
|
@@ -6621,7 +7920,7 @@ async function loadSavedGistInfo(outputDir) {
|
|
|
6621
7920
|
}
|
|
6622
7921
|
}
|
|
6623
7922
|
async function saveGistInfo(outputDir, info) {
|
|
6624
|
-
await writeFile4(
|
|
7923
|
+
await writeFile4(join15(outputDir, GIST_META_FILE), JSON.stringify(info, null, 2), "utf-8");
|
|
6625
7924
|
}
|
|
6626
7925
|
function sanitizeFilename(s) {
|
|
6627
7926
|
return s.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").slice(0, 60);
|
|
@@ -6732,21 +8031,21 @@ function scanForSecrets(content) {
|
|
|
6732
8031
|
}
|
|
6733
8032
|
|
|
6734
8033
|
// src/server.ts
|
|
6735
|
-
import { execFile as
|
|
8034
|
+
import { execFile as execFile3 } from "child_process";
|
|
6736
8035
|
import { createHash as createHash4 } from "crypto";
|
|
6737
8036
|
import { watch as fsWatch } from "fs";
|
|
6738
8037
|
import {
|
|
6739
8038
|
mkdir as mkdir5,
|
|
6740
8039
|
open as fsOpen,
|
|
6741
|
-
readdir as
|
|
6742
|
-
readFile as
|
|
6743
|
-
stat as
|
|
8040
|
+
readdir as readdir11,
|
|
8041
|
+
readFile as readFile19,
|
|
8042
|
+
stat as stat10,
|
|
6744
8043
|
unlink as unlink2,
|
|
6745
8044
|
writeFile as writeFile6
|
|
6746
8045
|
} from "fs/promises";
|
|
6747
|
-
import { homedir as
|
|
6748
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
6749
|
-
import { promisify as
|
|
8046
|
+
import { homedir as homedir16 } from "os";
|
|
8047
|
+
import { basename as basename6, dirname as dirname5, join as join18, resolve as resolve2 } from "path";
|
|
8048
|
+
import { promisify as promisify3 } from "util";
|
|
6750
8049
|
import { serve } from "@hono/node-server";
|
|
6751
8050
|
import chalk from "chalk";
|
|
6752
8051
|
import { Hono } from "hono";
|
|
@@ -6756,6 +8055,14 @@ import open2 from "open";
|
|
|
6756
8055
|
// src/feedback.ts
|
|
6757
8056
|
import { spawn } from "child_process";
|
|
6758
8057
|
import { randomUUID } from "crypto";
|
|
8058
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
8059
|
+
function spawnTool(command, args, options) {
|
|
8060
|
+
if (IS_WINDOWS) {
|
|
8061
|
+
const quoted = /\s/.test(command) ? `"${command}"` : command;
|
|
8062
|
+
return spawn(quoted, args, { ...options, shell: true });
|
|
8063
|
+
}
|
|
8064
|
+
return spawn(command, args, options);
|
|
8065
|
+
}
|
|
6759
8066
|
var TOOL_PRIORITY = ["claude", "agent", "opencode"];
|
|
6760
8067
|
async function detectFeedbackTools() {
|
|
6761
8068
|
const insideClaude = !!process.env.CLAUDECODE;
|
|
@@ -6767,8 +8074,8 @@ async function detectFeedbackTools() {
|
|
|
6767
8074
|
const tools = [];
|
|
6768
8075
|
for (const tool of candidates) {
|
|
6769
8076
|
try {
|
|
6770
|
-
const
|
|
6771
|
-
if (
|
|
8077
|
+
const located = await locateCommand(tool.cmd);
|
|
8078
|
+
if (located) tools.push({ name: tool.name, command: located });
|
|
6772
8079
|
} catch {
|
|
6773
8080
|
}
|
|
6774
8081
|
}
|
|
@@ -6994,7 +8301,7 @@ async function executeFeedback(prompt, tool) {
|
|
|
6994
8301
|
}
|
|
6995
8302
|
function runClaude(prompt, cmd) {
|
|
6996
8303
|
return new Promise((resolve3, reject) => {
|
|
6997
|
-
const proc =
|
|
8304
|
+
const proc = spawnTool(cmd, ["-p", "--output-format", "json"], {
|
|
6998
8305
|
env: { ...process.env, NO_COLOR: "1" },
|
|
6999
8306
|
timeout: 6e5,
|
|
7000
8307
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7022,7 +8329,7 @@ function runClaude(prompt, cmd) {
|
|
|
7022
8329
|
}
|
|
7023
8330
|
function runOpencode(prompt, cmd) {
|
|
7024
8331
|
return new Promise((resolve3, reject) => {
|
|
7025
|
-
const proc =
|
|
8332
|
+
const proc = spawnTool(cmd, ["run"], {
|
|
7026
8333
|
env: { ...process.env, NO_COLOR: "1", TERM: "dumb" },
|
|
7027
8334
|
timeout: 6e5,
|
|
7028
8335
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7045,7 +8352,7 @@ function runOpencode(prompt, cmd) {
|
|
|
7045
8352
|
}
|
|
7046
8353
|
function runAgent(prompt, cmd) {
|
|
7047
8354
|
return new Promise((resolve3, reject) => {
|
|
7048
|
-
const proc =
|
|
8355
|
+
const proc = spawnTool(cmd, ["-p", "--output-format", "json", "--mode", "ask", "--trust"], {
|
|
7049
8356
|
env: { ...process.env, NO_COLOR: "1" },
|
|
7050
8357
|
timeout: 6e5,
|
|
7051
8358
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7398,10 +8705,13 @@ async function generateFeedback(session, tool, { debug = false } = {}) {
|
|
|
7398
8705
|
}
|
|
7399
8706
|
return { annotations: feedbackToAnnotations(result), result };
|
|
7400
8707
|
}
|
|
7401
|
-
function
|
|
7402
|
-
|
|
8708
|
+
function collectTranslatableScenes(scenes) {
|
|
8709
|
+
return scenes.map(
|
|
7403
8710
|
(s, i) => s.type === "user-prompt" || s.type === "text-response" ? { index: i, content: s.content } : null
|
|
7404
8711
|
).filter((s) => s !== null);
|
|
8712
|
+
}
|
|
8713
|
+
function buildTranslationPrompt(session, opts) {
|
|
8714
|
+
const translatableScenes = collectTranslatableScenes(session.scenes);
|
|
7405
8715
|
const scenesBlock = translatableScenes.map((s) => `--- SCENE ${s.index} ---
|
|
7406
8716
|
${s.content}`).join("\n\n");
|
|
7407
8717
|
const sourcePart = opts.sourceLang ? `from ${opts.sourceLang} ` : "";
|
|
@@ -7483,9 +8793,7 @@ function parseTranslationBatch(output, batchScenes, opts, now) {
|
|
|
7483
8793
|
}
|
|
7484
8794
|
async function generateTranslation(session, tool, opts) {
|
|
7485
8795
|
if (session.scenes.length === 0) return null;
|
|
7486
|
-
const allScenes = session.scenes
|
|
7487
|
-
(s, i) => s.type === "user-prompt" || s.type === "text-response" ? { index: i, content: s.content } : null
|
|
7488
|
-
).filter((s) => s !== null);
|
|
8796
|
+
const allScenes = collectTranslatableScenes(session.scenes);
|
|
7489
8797
|
if (allScenes.length === 0) return null;
|
|
7490
8798
|
const batches = [];
|
|
7491
8799
|
for (let i = 0; i < allScenes.length; i += TRANSLATE_BATCH_SIZE) {
|
|
@@ -7654,14 +8962,19 @@ async function generateToneAdjustment(session, tool, opts) {
|
|
|
7654
8962
|
function stripAnsi(str) {
|
|
7655
8963
|
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "");
|
|
7656
8964
|
}
|
|
7657
|
-
function
|
|
8965
|
+
function locateCommand(cmd) {
|
|
7658
8966
|
return new Promise((resolve3, reject) => {
|
|
7659
|
-
const
|
|
8967
|
+
const locator = IS_WINDOWS ? "where" : "which";
|
|
8968
|
+
const proc = spawn(locator, [cmd], { timeout: 5e3 });
|
|
7660
8969
|
let out = "";
|
|
7661
8970
|
proc.stdout.on("data", (d) => out += d.toString());
|
|
7662
8971
|
proc.on("close", (code) => {
|
|
7663
|
-
if (code
|
|
7664
|
-
|
|
8972
|
+
if (code !== 0) {
|
|
8973
|
+
resolve3(null);
|
|
8974
|
+
return;
|
|
8975
|
+
}
|
|
8976
|
+
const first = out.split(/\r?\n/).find((line) => line.trim());
|
|
8977
|
+
resolve3(first ? first.trim() : null);
|
|
7665
8978
|
});
|
|
7666
8979
|
proc.on("error", reject);
|
|
7667
8980
|
});
|
|
@@ -7673,12 +8986,30 @@ init_overlays();
|
|
|
7673
8986
|
init_cloud();
|
|
7674
8987
|
|
|
7675
8988
|
// src/scanner.ts
|
|
7676
|
-
import { readdir as
|
|
7677
|
-
import { homedir as
|
|
7678
|
-
import { join as
|
|
8989
|
+
import { readdir as readdir10, readFile as readFile18, stat as stat9 } from "fs/promises";
|
|
8990
|
+
import { homedir as homedir14 } from "os";
|
|
8991
|
+
import { join as join17 } from "path";
|
|
7679
8992
|
|
|
7680
8993
|
// src/pricing.ts
|
|
7681
8994
|
var MODEL_PRICING = {
|
|
8995
|
+
"gpt-5.5": {
|
|
8996
|
+
inputRate: 5,
|
|
8997
|
+
outputRate: 30,
|
|
8998
|
+
cacheCreateRate: 5,
|
|
8999
|
+
cacheReadRate: 0.5
|
|
9000
|
+
},
|
|
9001
|
+
"gpt-5.4": {
|
|
9002
|
+
inputRate: 2.5,
|
|
9003
|
+
outputRate: 15,
|
|
9004
|
+
cacheCreateRate: 2.5,
|
|
9005
|
+
cacheReadRate: 0.25
|
|
9006
|
+
},
|
|
9007
|
+
"gpt-5.4-mini": {
|
|
9008
|
+
inputRate: 0.75,
|
|
9009
|
+
outputRate: 4.5,
|
|
9010
|
+
cacheCreateRate: 0.75,
|
|
9011
|
+
cacheReadRate: 0.075
|
|
9012
|
+
},
|
|
7682
9013
|
// Opus 4.6/4.5
|
|
7683
9014
|
"opus-4-new": {
|
|
7684
9015
|
inputRate: 5,
|
|
@@ -7732,6 +9063,9 @@ var MODEL_PRICING = {
|
|
|
7732
9063
|
var DEFAULT_PRICING = MODEL_PRICING.sonnet;
|
|
7733
9064
|
function getModelPricing(model) {
|
|
7734
9065
|
const lower = model.toLowerCase();
|
|
9066
|
+
if (lower.includes("gpt-5.4-mini")) return MODEL_PRICING["gpt-5.4-mini"];
|
|
9067
|
+
if (lower.includes("gpt-5.5")) return MODEL_PRICING["gpt-5.5"];
|
|
9068
|
+
if (lower.includes("gpt-5.4")) return MODEL_PRICING["gpt-5.4"];
|
|
7735
9069
|
if (lower.includes("opus-4-6") || lower.includes("opus-4-5")) return MODEL_PRICING["opus-4-new"];
|
|
7736
9070
|
if (lower.includes("opus")) return MODEL_PRICING.opus;
|
|
7737
9071
|
if (lower.includes("sonnet-4-6") || lower.includes("sonnet-4-5"))
|
|
@@ -7743,6 +9077,9 @@ function getModelPricing(model) {
|
|
|
7743
9077
|
return DEFAULT_PRICING;
|
|
7744
9078
|
}
|
|
7745
9079
|
var MODEL_CONTEXT_LIMITS = {
|
|
9080
|
+
"gpt-5.5": 27e4,
|
|
9081
|
+
"gpt-5.4-mini": 27e4,
|
|
9082
|
+
"gpt-5.4": 27e4,
|
|
7746
9083
|
"gpt-4o": 128e3,
|
|
7747
9084
|
"gpt-4-turbo": 128e3,
|
|
7748
9085
|
"gpt-4": 8192,
|
|
@@ -7856,7 +9193,7 @@ async function scanSession(input) {
|
|
|
7856
9193
|
for (const filePath of input.filePaths) {
|
|
7857
9194
|
let content;
|
|
7858
9195
|
try {
|
|
7859
|
-
content = await
|
|
9196
|
+
content = await readFile18(filePath, "utf-8");
|
|
7860
9197
|
} catch {
|
|
7861
9198
|
continue;
|
|
7862
9199
|
}
|
|
@@ -7911,6 +9248,9 @@ async function scanSession(input) {
|
|
|
7911
9248
|
if (obj.subtype === "api_error") apiErrorCount++;
|
|
7912
9249
|
continue;
|
|
7913
9250
|
}
|
|
9251
|
+
if (obj.type === "assistant" && obj.isApiErrorMessage) {
|
|
9252
|
+
apiErrorCount++;
|
|
9253
|
+
}
|
|
7914
9254
|
if (obj.isMeta) {
|
|
7915
9255
|
const text = extractMetaText(obj.message?.content);
|
|
7916
9256
|
if (text.startsWith("Base directory for this skill:")) {
|
|
@@ -7975,15 +9315,15 @@ async function scanSession(input) {
|
|
|
7975
9315
|
if (input.filePaths.length > 0) {
|
|
7976
9316
|
const mainFile = input.filePaths[0];
|
|
7977
9317
|
const sessionDir = mainFile.replace(/\.jsonl$/, "");
|
|
7978
|
-
const subagentsDir =
|
|
9318
|
+
const subagentsDir = join17(sessionDir, "subagents");
|
|
7979
9319
|
try {
|
|
7980
|
-
const files = await
|
|
9320
|
+
const files = await readdir10(subagentsDir);
|
|
7981
9321
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
7982
9322
|
subAgentCount = jsonlFiles.length;
|
|
7983
9323
|
for (const saFile of jsonlFiles) {
|
|
7984
9324
|
let saContent;
|
|
7985
9325
|
try {
|
|
7986
|
-
saContent = await
|
|
9326
|
+
saContent = await readFile18(join17(subagentsDir, saFile), "utf-8");
|
|
7987
9327
|
} catch {
|
|
7988
9328
|
continue;
|
|
7989
9329
|
}
|
|
@@ -8170,7 +9510,7 @@ function buildScanResultFromParsed(input, parsed) {
|
|
|
8170
9510
|
let derivedSubAgentCount = 0;
|
|
8171
9511
|
const fileEditCounts = /* @__PURE__ */ new Map();
|
|
8172
9512
|
for (const turn of parsed.turns) {
|
|
8173
|
-
if (turn.role === "user" && turn.subtype
|
|
9513
|
+
if (turn.role === "user" && !turn.subtype) {
|
|
8174
9514
|
const hasText = turn.blocks.some(
|
|
8175
9515
|
(block) => block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0
|
|
8176
9516
|
);
|
|
@@ -8193,16 +9533,18 @@ function buildScanResultFromParsed(input, parsed) {
|
|
|
8193
9533
|
const saPath = extractToolFilePath(saScene.input);
|
|
8194
9534
|
if (!saPath) continue;
|
|
8195
9535
|
editCount++;
|
|
8196
|
-
const
|
|
8197
|
-
fileEditCounts.set(
|
|
9536
|
+
const short = shortenPath(saPath);
|
|
9537
|
+
fileEditCounts.set(short, (fileEditCounts.get(short) || 0) + 1);
|
|
8198
9538
|
}
|
|
8199
9539
|
}
|
|
8200
9540
|
if (!FILE_EDIT_TOOLS.has(block.name)) continue;
|
|
8201
|
-
const
|
|
8202
|
-
if (
|
|
9541
|
+
const rawPaths = extractToolFilePaths(block.input);
|
|
9542
|
+
if (rawPaths.length === 0) continue;
|
|
8203
9543
|
editCount++;
|
|
8204
|
-
const
|
|
8205
|
-
|
|
9544
|
+
for (const rawPath of rawPaths) {
|
|
9545
|
+
const short = shortenPath(rawPath);
|
|
9546
|
+
fileEditCounts.set(short, (fileEditCounts.get(short) || 0) + 1);
|
|
9547
|
+
}
|
|
8206
9548
|
}
|
|
8207
9549
|
}
|
|
8208
9550
|
const costEstimate = estimateParsedCost(parsed);
|
|
@@ -8244,7 +9586,7 @@ function buildScanResultFromParsed(input, parsed) {
|
|
|
8244
9586
|
}
|
|
8245
9587
|
function firstUserPrompt(turns) {
|
|
8246
9588
|
for (const turn of turns) {
|
|
8247
|
-
if (turn.role !== "user" || turn.subtype
|
|
9589
|
+
if (turn.role !== "user" || turn.subtype) continue;
|
|
8248
9590
|
for (const block of turn.blocks) {
|
|
8249
9591
|
if (block.type !== "text") continue;
|
|
8250
9592
|
const text = block.text.replace(/\s+/g, " ").trim();
|
|
@@ -8274,7 +9616,7 @@ async function getFileMeta(filePaths) {
|
|
|
8274
9616
|
let maxMtime = 0;
|
|
8275
9617
|
for (const fp of filePaths) {
|
|
8276
9618
|
try {
|
|
8277
|
-
const s = await
|
|
9619
|
+
const s = await stat9(fp);
|
|
8278
9620
|
totalSize += s.size;
|
|
8279
9621
|
if (s.mtimeMs > maxMtime) maxMtime = s.mtimeMs;
|
|
8280
9622
|
} catch {
|
|
@@ -8627,33 +9969,33 @@ function buildAggregateDataQuality(scans) {
|
|
|
8627
9969
|
}
|
|
8628
9970
|
return notes.length > 0 ? { notes } : void 0;
|
|
8629
9971
|
}
|
|
8630
|
-
var CLAUDE_DIR2 =
|
|
9972
|
+
var CLAUDE_DIR2 = join17(homedir14(), ".claude", "projects");
|
|
8631
9973
|
function encodeProjectDir(project) {
|
|
8632
9974
|
let resolved = project;
|
|
8633
9975
|
if (resolved.startsWith("~/")) {
|
|
8634
|
-
resolved =
|
|
9976
|
+
resolved = join17(homedir14(), resolved.slice(2));
|
|
8635
9977
|
} else if (resolved === "~") {
|
|
8636
|
-
resolved =
|
|
9978
|
+
resolved = homedir14();
|
|
8637
9979
|
}
|
|
8638
9980
|
return resolved.replace(/\//g, "-");
|
|
8639
9981
|
}
|
|
8640
9982
|
async function readProjectMemory(project) {
|
|
8641
9983
|
const encoded = encodeProjectDir(project);
|
|
8642
|
-
const projectDir =
|
|
8643
|
-
const memoryDir =
|
|
9984
|
+
const projectDir = join17(CLAUDE_DIR2, encoded);
|
|
9985
|
+
const memoryDir = join17(projectDir, "memory");
|
|
8644
9986
|
const memoryFiles = [];
|
|
8645
9987
|
let claudeMd;
|
|
8646
9988
|
try {
|
|
8647
|
-
const content = await
|
|
9989
|
+
const content = await readFile18(join17(projectDir, "CLAUDE.md"), "utf-8");
|
|
8648
9990
|
if (content.trim()) claudeMd = content.slice(0, 5e3);
|
|
8649
9991
|
} catch {
|
|
8650
9992
|
}
|
|
8651
9993
|
try {
|
|
8652
|
-
const files = await
|
|
9994
|
+
const files = await readdir10(memoryDir);
|
|
8653
9995
|
for (const file of files) {
|
|
8654
9996
|
if (!file.endsWith(".md") || file === "MEMORY.md") continue;
|
|
8655
9997
|
try {
|
|
8656
|
-
const content = await
|
|
9998
|
+
const content = await readFile18(join17(memoryDir, file), "utf-8");
|
|
8657
9999
|
const fm = parseFrontmatter(content);
|
|
8658
10000
|
memoryFiles.push({
|
|
8659
10001
|
name: fm.name || file.replace(/\.md$/, ""),
|
|
@@ -8694,7 +10036,7 @@ function extractMetaText(content) {
|
|
|
8694
10036
|
}
|
|
8695
10037
|
|
|
8696
10038
|
// src/transform.ts
|
|
8697
|
-
import { homedir as
|
|
10039
|
+
import { homedir as homedir15 } from "os";
|
|
8698
10040
|
|
|
8699
10041
|
// src/utils/tokenEstimate.ts
|
|
8700
10042
|
function estimateTokens(s) {
|
|
@@ -8704,11 +10046,15 @@ function estimateTokens(s) {
|
|
|
8704
10046
|
}
|
|
8705
10047
|
|
|
8706
10048
|
// src/transform.ts
|
|
8707
|
-
var HOME =
|
|
10049
|
+
var HOME = homedir15();
|
|
8708
10050
|
function redactPath(s) {
|
|
8709
10051
|
if (!HOME) return s;
|
|
8710
10052
|
return s.replaceAll(HOME, "~");
|
|
8711
10053
|
}
|
|
10054
|
+
function redactFilePath(s) {
|
|
10055
|
+
const redacted = redactPath(s);
|
|
10056
|
+
return process.platform === "win32" ? redacted.replaceAll("\\", "/") : redacted;
|
|
10057
|
+
}
|
|
8712
10058
|
function transformToReplay(parsed, provider, project, options) {
|
|
8713
10059
|
const scenes = [];
|
|
8714
10060
|
let userPrompts = 0;
|
|
@@ -8834,7 +10180,7 @@ function transformToReplay(parsed, provider, project, options) {
|
|
|
8834
10180
|
startTime: parsed.startTime || (/* @__PURE__ */ new Date()).toISOString(),
|
|
8835
10181
|
endTime: parsed.endTime,
|
|
8836
10182
|
model: parsed.model,
|
|
8837
|
-
cwd:
|
|
10183
|
+
cwd: redactFilePath(parsed.cwd),
|
|
8838
10184
|
project,
|
|
8839
10185
|
...options?.generator ? { generator: options.generator } : {},
|
|
8840
10186
|
stats: {
|
|
@@ -8847,7 +10193,7 @@ function transformToReplay(parsed, provider, project, options) {
|
|
|
8847
10193
|
costEstimate,
|
|
8848
10194
|
...parsed.turnStats ? { turnStats: parsed.turnStats } : {}
|
|
8849
10195
|
},
|
|
8850
|
-
...parsed.model ? { contextLimit: getModelContextLimit(parsed.model) } : {},
|
|
10196
|
+
...parsed.contextLimit || parsed.model ? { contextLimit: parsed.contextLimit || getModelContextLimit(parsed.model || "") } : {},
|
|
8851
10197
|
...parsed.tokenUsageByModel ? { tokenUsageByModel: parsed.tokenUsageByModel } : {},
|
|
8852
10198
|
...parsed.prLinks && parsed.prLinks.length > 0 ? { prLinks: parsed.prLinks } : {},
|
|
8853
10199
|
compactions: parsed.compactions,
|
|
@@ -8856,10 +10202,19 @@ function transformToReplay(parsed, provider, project, options) {
|
|
|
8856
10202
|
...parsed.gitBranches ? { gitBranches: parsed.gitBranches } : {},
|
|
8857
10203
|
...parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {},
|
|
8858
10204
|
...parsed.permissionMode ? { permissionMode: parsed.permissionMode } : {},
|
|
10205
|
+
...parsed.memoryMode ? { memoryMode: parsed.memoryMode } : {},
|
|
8859
10206
|
...parsed.apiErrors && parsed.apiErrors.length > 0 ? { apiErrors: parsed.apiErrors } : {},
|
|
8860
|
-
...parsed.trackedFiles && parsed.trackedFiles.length > 0 ? { trackedFiles: parsed.trackedFiles.map(
|
|
8861
|
-
...parsed.contextFiles && parsed.contextFiles.length > 0 ? { contextFiles: parsed.contextFiles.map(
|
|
10207
|
+
...parsed.trackedFiles && parsed.trackedFiles.length > 0 ? { trackedFiles: parsed.trackedFiles.map(redactFilePath) } : {},
|
|
10208
|
+
...parsed.contextFiles && parsed.contextFiles.length > 0 ? { contextFiles: parsed.contextFiles.map(redactFilePath) } : {},
|
|
8862
10209
|
...parsed.cursorSidecars ? { cursorSidecars: parsed.cursorSidecars } : {},
|
|
10210
|
+
...parsed.parseWarnings && parsed.parseWarnings.length > 0 ? {
|
|
10211
|
+
parseWarnings: parsed.parseWarnings.map((warning) => ({
|
|
10212
|
+
...warning,
|
|
10213
|
+
message: redactWarningText(warning.message),
|
|
10214
|
+
...warning.source ? { source: redactWarningText(warning.source) } : {},
|
|
10215
|
+
sample: warning.kind !== "malformed-json" && warning.sample ? compactWarningSample(redactWarningText(warning.sample)) : void 0
|
|
10216
|
+
}))
|
|
10217
|
+
} : {},
|
|
8863
10218
|
...parsed.serviceTier ? { serviceTier: parsed.serviceTier } : {},
|
|
8864
10219
|
...parsed.skillsUsed ? { skillsUsed: parsed.skillsUsed } : {},
|
|
8865
10220
|
...parsed.mcpServersUsed ? { mcpServersUsed: parsed.mcpServersUsed } : {},
|
|
@@ -8874,9 +10229,9 @@ function transformToReplay(parsed, provider, project, options) {
|
|
|
8874
10229
|
function buildFileDiff(toolName, input) {
|
|
8875
10230
|
if (toolName === "Edit" && input.file_path) {
|
|
8876
10231
|
return {
|
|
8877
|
-
filePath:
|
|
8878
|
-
oldContent: input.old_string
|
|
8879
|
-
newContent: input.new_string
|
|
10232
|
+
filePath: redactFilePath(input.file_path),
|
|
10233
|
+
oldContent: redactDiffContent(input.old_string),
|
|
10234
|
+
newContent: redactDiffContent(input.new_string)
|
|
8880
10235
|
};
|
|
8881
10236
|
}
|
|
8882
10237
|
if (toolName === "MultiEdit" && input.file_path && Array.isArray(input.edits)) {
|
|
@@ -8885,28 +10240,49 @@ function buildFileDiff(toolName, input) {
|
|
|
8885
10240
|
});
|
|
8886
10241
|
if (edits.length > 0) {
|
|
8887
10242
|
return {
|
|
8888
|
-
filePath:
|
|
8889
|
-
oldContent: edits.map((edit) => edit.old_string
|
|
8890
|
-
newContent: edits.map((edit) => edit.new_string
|
|
10243
|
+
filePath: redactFilePath(input.file_path),
|
|
10244
|
+
oldContent: edits.map((edit) => redactDiffContent(edit.old_string)).join("\n\n"),
|
|
10245
|
+
newContent: edits.map((edit) => redactDiffContent(edit.new_string)).join("\n\n")
|
|
8891
10246
|
};
|
|
8892
10247
|
}
|
|
8893
10248
|
}
|
|
8894
10249
|
if (toolName === "Write" && input.file_path) {
|
|
8895
10250
|
return {
|
|
8896
|
-
filePath:
|
|
10251
|
+
filePath: redactFilePath(input.file_path),
|
|
8897
10252
|
oldContent: "",
|
|
8898
|
-
newContent: truncate(input.content
|
|
10253
|
+
newContent: truncate(redactDiffContent(input.content), 3e3)
|
|
8899
10254
|
};
|
|
8900
10255
|
}
|
|
8901
10256
|
if (toolName === "Delete" && input.file_path) {
|
|
8902
10257
|
return {
|
|
8903
|
-
filePath:
|
|
8904
|
-
oldContent: input.old_string
|
|
10258
|
+
filePath: redactFilePath(input.file_path),
|
|
10259
|
+
oldContent: typeof input.old_string === "string" ? redactDiffContent(input.old_string) : "(file deleted)",
|
|
8905
10260
|
newContent: ""
|
|
8906
10261
|
};
|
|
8907
10262
|
}
|
|
8908
10263
|
return void 0;
|
|
8909
10264
|
}
|
|
10265
|
+
function redactDiffContent(value) {
|
|
10266
|
+
return typeof value === "string" ? redactSecrets(redactPath(value)) : "";
|
|
10267
|
+
}
|
|
10268
|
+
function redactWarningText(value) {
|
|
10269
|
+
return redactSecrets(redactWarningPaths(redactPath(value))).replace(
|
|
10270
|
+
/[A-Za-z0-9_-]{4,}\.\.\.\[REDACTED\]/g,
|
|
10271
|
+
"[REDACTED]"
|
|
10272
|
+
);
|
|
10273
|
+
}
|
|
10274
|
+
function redactWarningPaths(value) {
|
|
10275
|
+
let result = value;
|
|
10276
|
+
if (HOME) {
|
|
10277
|
+
const slashEscapedHome = HOME.replaceAll("/", "\\/");
|
|
10278
|
+
result = result.replaceAll(slashEscapedHome, "~");
|
|
10279
|
+
const backslashEscapedHome = HOME.replaceAll("\\", "\\\\");
|
|
10280
|
+
if (backslashEscapedHome !== HOME) {
|
|
10281
|
+
result = result.replaceAll(backslashEscapedHome, "~");
|
|
10282
|
+
}
|
|
10283
|
+
}
|
|
10284
|
+
return result.replace(/[A-Za-z]:(?:\\\\|\\)Users(?:\\\\|\\)[^\\/"\s]+/g, "~");
|
|
10285
|
+
}
|
|
8910
10286
|
function buildToolScene(toolName, input, result, images) {
|
|
8911
10287
|
const resultTokens = estimateTokens(result);
|
|
8912
10288
|
const scene = {
|
|
@@ -9123,7 +10499,7 @@ function getErrorMessage(err) {
|
|
|
9123
10499
|
return "Unknown error";
|
|
9124
10500
|
}
|
|
9125
10501
|
function normalizeProjectPath(project) {
|
|
9126
|
-
const home =
|
|
10502
|
+
const home = homedir16();
|
|
9127
10503
|
return project.startsWith(home) ? `~${project.slice(home.length)}` : project;
|
|
9128
10504
|
}
|
|
9129
10505
|
var MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST = 90;
|
|
@@ -9194,20 +10570,20 @@ function resolveGenerateInputs(body, discoveredSessions) {
|
|
|
9194
10570
|
var ARCHIVE_DIR = ".archive";
|
|
9195
10571
|
async function getArchivedSlugs(baseDir) {
|
|
9196
10572
|
try {
|
|
9197
|
-
const entries = await
|
|
10573
|
+
const entries = await readdir11(join18(baseDir, ARCHIVE_DIR));
|
|
9198
10574
|
return new Set(entries);
|
|
9199
10575
|
} catch {
|
|
9200
10576
|
return /* @__PURE__ */ new Set();
|
|
9201
10577
|
}
|
|
9202
10578
|
}
|
|
9203
10579
|
async function archiveSlug(baseDir, slug) {
|
|
9204
|
-
const dir =
|
|
10580
|
+
const dir = join18(baseDir, ARCHIVE_DIR);
|
|
9205
10581
|
await mkdir5(dir, { recursive: true });
|
|
9206
|
-
await writeFile6(
|
|
10582
|
+
await writeFile6(join18(dir, slug), "");
|
|
9207
10583
|
}
|
|
9208
10584
|
async function unarchiveSlug(baseDir, slug) {
|
|
9209
10585
|
try {
|
|
9210
|
-
await unlink2(
|
|
10586
|
+
await unlink2(join18(baseDir, ARCHIVE_DIR, slug));
|
|
9211
10587
|
} catch {
|
|
9212
10588
|
}
|
|
9213
10589
|
}
|
|
@@ -9215,29 +10591,29 @@ async function scanSessionsFromDir(baseDir) {
|
|
|
9215
10591
|
const results = [];
|
|
9216
10592
|
let entries;
|
|
9217
10593
|
try {
|
|
9218
|
-
entries = await
|
|
10594
|
+
entries = await readdir11(baseDir);
|
|
9219
10595
|
} catch {
|
|
9220
10596
|
return results;
|
|
9221
10597
|
}
|
|
9222
10598
|
for (const entry of entries) {
|
|
9223
|
-
const replayPath =
|
|
10599
|
+
const replayPath = join18(baseDir, entry, "replay.json");
|
|
9224
10600
|
try {
|
|
9225
|
-
const raw = await
|
|
10601
|
+
const raw = await readFile19(replayPath, "utf-8");
|
|
9226
10602
|
const session = JSON.parse(raw);
|
|
9227
|
-
const annotationsPath =
|
|
10603
|
+
const annotationsPath = join18(baseDir, entry, "annotations.json");
|
|
9228
10604
|
let annotationCount = 0;
|
|
9229
10605
|
try {
|
|
9230
|
-
const annRaw = await
|
|
10606
|
+
const annRaw = await readFile19(annotationsPath, "utf-8");
|
|
9231
10607
|
const anns = JSON.parse(annRaw);
|
|
9232
10608
|
annotationCount = Array.isArray(anns) ? anns.length : 0;
|
|
9233
10609
|
} catch {
|
|
9234
10610
|
}
|
|
9235
10611
|
let gist;
|
|
9236
10612
|
try {
|
|
9237
|
-
gist = await loadSavedGistInfo(
|
|
10613
|
+
gist = await loadSavedGistInfo(join18(baseDir, entry));
|
|
9238
10614
|
} catch {
|
|
9239
10615
|
}
|
|
9240
|
-
const cloudInfo = await loadSavedCloudInfo(
|
|
10616
|
+
const cloudInfo = await loadSavedCloudInfo(join18(baseDir, entry));
|
|
9241
10617
|
const userPrompts = (session.scenes || []).filter((sc) => sc.type === "user-prompt").map((sc) => previewPrompt(sc.content)).filter((m) => m.length >= 10);
|
|
9242
10618
|
const firstMessage = userPrompts[0] || void 0;
|
|
9243
10619
|
const messages = userPrompts.length > 0 ? userPrompts.slice(0, 2) : void 0;
|
|
@@ -9265,7 +10641,7 @@ async function scanSessionsFromDir(baseDir) {
|
|
|
9265
10641
|
let outdated = false;
|
|
9266
10642
|
if (gist?.contentHash) {
|
|
9267
10643
|
try {
|
|
9268
|
-
const content = await
|
|
10644
|
+
const content = await readFile19(replayPath, "utf-8");
|
|
9269
10645
|
const currentHash = createHash4("sha256").update(content).digest("hex").slice(0, 16);
|
|
9270
10646
|
outdated = currentHash !== gist?.contentHash;
|
|
9271
10647
|
} catch {
|
|
@@ -9311,20 +10687,20 @@ async function scanSessions(baseDir) {
|
|
|
9311
10687
|
return allResults;
|
|
9312
10688
|
}
|
|
9313
10689
|
async function loadSessionFromDisk(baseDir, slug) {
|
|
9314
|
-
let replayPath =
|
|
10690
|
+
let replayPath = join18(baseDir, slug, "replay.json");
|
|
9315
10691
|
try {
|
|
9316
|
-
await
|
|
10692
|
+
await stat10(replayPath);
|
|
9317
10693
|
} catch {
|
|
9318
10694
|
const fallback = resolve2("./vibe-replay", slug, "replay.json");
|
|
9319
|
-
await
|
|
10695
|
+
await stat10(fallback);
|
|
9320
10696
|
replayPath = fallback;
|
|
9321
10697
|
}
|
|
9322
|
-
const raw = await
|
|
10698
|
+
const raw = await readFile19(replayPath, "utf-8");
|
|
9323
10699
|
const session = JSON.parse(raw);
|
|
9324
10700
|
const sessionDir = dirname5(replayPath);
|
|
9325
|
-
const annotationsPath =
|
|
10701
|
+
const annotationsPath = join18(sessionDir, "annotations.json");
|
|
9326
10702
|
try {
|
|
9327
|
-
const annRaw = await
|
|
10703
|
+
const annRaw = await readFile19(annotationsPath, "utf-8");
|
|
9328
10704
|
const anns = JSON.parse(annRaw);
|
|
9329
10705
|
if (Array.isArray(anns) && anns.length > 0) {
|
|
9330
10706
|
session.annotations = anns;
|
|
@@ -9340,7 +10716,12 @@ function pickSourceRecordForSession(session, bySessionId, byKey) {
|
|
|
9340
10716
|
const byIdMatch = bySessionId.get(session.sessionId);
|
|
9341
10717
|
return (byIdMatch?.provider === session.provider ? byIdMatch : void 0) ?? byKey.get(sourceSessionKey(session.provider, session.project, session.slug));
|
|
9342
10718
|
}
|
|
9343
|
-
function selectCursorEnrichmentCandidates(merged, baseSources,
|
|
10719
|
+
function selectCursorEnrichmentCandidates(merged, baseSources, limitOrHints = 30) {
|
|
10720
|
+
const hints = typeof limitOrHints === "number" ? { limit: limitOrHints } : limitOrHints;
|
|
10721
|
+
const limit = Math.max(1, Math.min(hints.limit ?? 30, 100));
|
|
10722
|
+
const preferredSessionIds = new Set(hints.sessionIds || []);
|
|
10723
|
+
const preferredSlugs = new Set(hints.slugs || []);
|
|
10724
|
+
const preferredProjects = new Set(hints.projects || []);
|
|
9344
10725
|
const mergedBySessionId = /* @__PURE__ */ new Map();
|
|
9345
10726
|
const mergedByKey = /* @__PURE__ */ new Map();
|
|
9346
10727
|
for (const session of merged) {
|
|
@@ -9352,7 +10733,91 @@ function selectCursorEnrichmentCandidates(merged, baseSources, limit = 30) {
|
|
|
9352
10733
|
).map((s) => {
|
|
9353
10734
|
const byId = s.sessionId ? mergedBySessionId.get(s.sessionId) : void 0;
|
|
9354
10735
|
return byId || mergedByKey.get(sourceSessionKey(s.provider, s.project, s.slug));
|
|
9355
|
-
}).filter((s) => Boolean(s)).sort((a, b) =>
|
|
10736
|
+
}).filter((s) => Boolean(s)).sort((a, b) => {
|
|
10737
|
+
const scoreDelta = enrichmentPriorityScore(b, preferredSessionIds, preferredSlugs, preferredProjects) - enrichmentPriorityScore(a, preferredSessionIds, preferredSlugs, preferredProjects);
|
|
10738
|
+
return scoreDelta || b.timestamp.localeCompare(a.timestamp);
|
|
10739
|
+
}).slice(0, limit);
|
|
10740
|
+
}
|
|
10741
|
+
function enrichmentPriorityScore(session, preferredSessionIds, preferredSlugs, preferredProjects) {
|
|
10742
|
+
let score = 0;
|
|
10743
|
+
if (preferredSessionIds.has(session.sessionId)) score += 1e3;
|
|
10744
|
+
if (preferredSlugs.has(session.slug)) score += 800;
|
|
10745
|
+
if (preferredProjects.has(session.project)) score += 400;
|
|
10746
|
+
if (session.timestamp && Date.now() - Date.parse(session.timestamp) <= 24 * 60 * 60 * 1e3) {
|
|
10747
|
+
score += 100;
|
|
10748
|
+
}
|
|
10749
|
+
if (session.hasPR) score += 25;
|
|
10750
|
+
if (session.hasSqlite) score += 10;
|
|
10751
|
+
return score;
|
|
10752
|
+
}
|
|
10753
|
+
function prioritizeScanInputs(inputs, previousResults, hints = {}) {
|
|
10754
|
+
const previousSessionIds = new Set(previousResults.map((result) => result.sessionId));
|
|
10755
|
+
const preferredSessionIds = new Set(hints.sessionIds || []);
|
|
10756
|
+
const preferredSlugs = new Set(hints.slugs || []);
|
|
10757
|
+
const preferredProjects = new Set(hints.projects || []);
|
|
10758
|
+
return [...inputs].sort((a, b) => {
|
|
10759
|
+
const scoreDelta = scanInputPriorityScore(
|
|
10760
|
+
b,
|
|
10761
|
+
previousSessionIds,
|
|
10762
|
+
preferredSessionIds,
|
|
10763
|
+
preferredSlugs,
|
|
10764
|
+
preferredProjects
|
|
10765
|
+
) - scanInputPriorityScore(
|
|
10766
|
+
a,
|
|
10767
|
+
previousSessionIds,
|
|
10768
|
+
preferredSessionIds,
|
|
10769
|
+
preferredSlugs,
|
|
10770
|
+
preferredProjects
|
|
10771
|
+
);
|
|
10772
|
+
return scoreDelta || (b.timestamp || "").localeCompare(a.timestamp || "");
|
|
10773
|
+
});
|
|
10774
|
+
}
|
|
10775
|
+
function scanInputPriorityScore(input, previousSessionIds, preferredSessionIds, preferredSlugs, preferredProjects) {
|
|
10776
|
+
let score = 0;
|
|
10777
|
+
if (!previousSessionIds.has(input.sessionId)) score += 1e3;
|
|
10778
|
+
if (preferredSessionIds.has(input.sessionId)) score += 2e3;
|
|
10779
|
+
if (preferredSlugs.has(input.slug)) score += 1600;
|
|
10780
|
+
if (preferredProjects.has(input.project)) score += 400;
|
|
10781
|
+
if (input.timestamp && Date.now() - Date.parse(input.timestamp) <= 24 * 60 * 60 * 1e3) {
|
|
10782
|
+
score += 100;
|
|
10783
|
+
}
|
|
10784
|
+
return score;
|
|
10785
|
+
}
|
|
10786
|
+
function mergeEnrichmentHints(existing, next) {
|
|
10787
|
+
return {
|
|
10788
|
+
sessionIds: uniqueStrings([...existing?.sessionIds || [], ...next.sessionIds || []]),
|
|
10789
|
+
slugs: uniqueStrings([...existing?.slugs || [], ...next.slugs || []]),
|
|
10790
|
+
projects: uniqueStrings([...existing?.projects || [], ...next.projects || []]),
|
|
10791
|
+
limit: Math.max(existing?.limit || 0, next.limit || 0) || void 0
|
|
10792
|
+
};
|
|
10793
|
+
}
|
|
10794
|
+
function uniqueStrings(values) {
|
|
10795
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.trim()))].slice(
|
|
10796
|
+
0,
|
|
10797
|
+
200
|
|
10798
|
+
);
|
|
10799
|
+
}
|
|
10800
|
+
function enrichmentHintsFromBody(value) {
|
|
10801
|
+
if (!value || typeof value !== "object") return {};
|
|
10802
|
+
const body = value;
|
|
10803
|
+
return {
|
|
10804
|
+
sessionIds: stringArrayFromUnknown(body.sessionIds),
|
|
10805
|
+
slugs: stringArrayFromUnknown(body.slugs),
|
|
10806
|
+
projects: stringArrayFromUnknown(body.projects),
|
|
10807
|
+
limit: typeof body.limit === "number" ? body.limit : void 0
|
|
10808
|
+
};
|
|
10809
|
+
}
|
|
10810
|
+
function stringArrayFromUnknown(value) {
|
|
10811
|
+
if (!Array.isArray(value)) return void 0;
|
|
10812
|
+
const strings = value.filter(
|
|
10813
|
+
(item) => typeof item === "string" && item.trim().length > 0
|
|
10814
|
+
);
|
|
10815
|
+
return strings.length > 0 ? [...new Set(strings)] : void 0;
|
|
10816
|
+
}
|
|
10817
|
+
function normalizeSessionProjectsForHome(sessions, home) {
|
|
10818
|
+
return sessions.map(
|
|
10819
|
+
(session) => session.project.startsWith(home) ? { ...session, project: `~${session.project.slice(home.length)}` } : { ...session }
|
|
10820
|
+
);
|
|
9356
10821
|
}
|
|
9357
10822
|
function countSessionStats(turns) {
|
|
9358
10823
|
let promptCount = 0;
|
|
@@ -9408,13 +10873,13 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
|
|
|
9408
10873
|
const projectExistsMap = /* @__PURE__ */ new Map();
|
|
9409
10874
|
const projectIsGitMap = /* @__PURE__ */ new Map();
|
|
9410
10875
|
for (const p of uniqueProjects) {
|
|
9411
|
-
const resolved = p.startsWith("~/") ?
|
|
10876
|
+
const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join18(home, p.slice(2)) : p;
|
|
9412
10877
|
try {
|
|
9413
|
-
const s = await
|
|
10878
|
+
const s = await stat10(resolved);
|
|
9414
10879
|
projectExistsMap.set(p, s.isDirectory());
|
|
9415
10880
|
if (s.isDirectory()) {
|
|
9416
10881
|
try {
|
|
9417
|
-
await
|
|
10882
|
+
await stat10(join18(resolved, ".git"));
|
|
9418
10883
|
projectIsGitMap.set(p, true);
|
|
9419
10884
|
} catch {
|
|
9420
10885
|
projectIsGitMap.set(p, false);
|
|
@@ -9456,11 +10921,18 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
|
|
|
9456
10921
|
filePaths: s.filePaths,
|
|
9457
10922
|
toolPaths: s.toolPaths,
|
|
9458
10923
|
hasSqlite: s.hasSqlite,
|
|
10924
|
+
hasSdk: s.hasSdk,
|
|
9459
10925
|
gitBranch: s.gitBranch,
|
|
9460
10926
|
model: s.model,
|
|
9461
10927
|
durationMsEst: s.durationMsEst,
|
|
9462
10928
|
editCountEst: s.editCountEst,
|
|
9463
10929
|
hasPR: s.hasPR,
|
|
10930
|
+
isStarred: s.isStarred,
|
|
10931
|
+
spaceId: s.spaceId,
|
|
10932
|
+
spaceIdSetBy: s.spaceIdSetBy,
|
|
10933
|
+
pluginsEnabled: s.pluginsEnabled,
|
|
10934
|
+
skillsEnabled: s.skillsEnabled,
|
|
10935
|
+
fsDetectedFiles: s.fsDetectedFiles,
|
|
9464
10936
|
expiresInDays: s.provider === "claude-code" && cleanupPeriodDays > 0 ? computeDaysUntilCleanup(s.timestamp, cleanupPeriodDays) : void 0,
|
|
9465
10937
|
existingReplay: replay ? replay.slug : null,
|
|
9466
10938
|
projectExists: projectExistsMap.get(s.project) ?? false,
|
|
@@ -9487,10 +10959,10 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
|
|
|
9487
10959
|
});
|
|
9488
10960
|
}
|
|
9489
10961
|
async function readClaudeSessionState(sessionId) {
|
|
9490
|
-
const sessionsDir =
|
|
10962
|
+
const sessionsDir = join18(homedir16(), ".claude", "sessions");
|
|
9491
10963
|
let files;
|
|
9492
10964
|
try {
|
|
9493
|
-
files = await
|
|
10965
|
+
files = await readdir11(sessionsDir);
|
|
9494
10966
|
} catch {
|
|
9495
10967
|
return "stopped";
|
|
9496
10968
|
}
|
|
@@ -9498,7 +10970,7 @@ async function readClaudeSessionState(sessionId) {
|
|
|
9498
10970
|
if (!file.endsWith(".json")) continue;
|
|
9499
10971
|
let data;
|
|
9500
10972
|
try {
|
|
9501
|
-
const content = await
|
|
10973
|
+
const content = await readFile19(join18(sessionsDir, file), "utf-8");
|
|
9502
10974
|
data = JSON.parse(content);
|
|
9503
10975
|
} catch {
|
|
9504
10976
|
continue;
|
|
@@ -9548,10 +11020,10 @@ function mergeSameSessions(sessions) {
|
|
|
9548
11020
|
return result;
|
|
9549
11021
|
}
|
|
9550
11022
|
async function loadAnnotations(baseDir, slug) {
|
|
9551
|
-
const dirs = [
|
|
11023
|
+
const dirs = [join18(baseDir, slug), resolve2("./vibe-replay", slug)];
|
|
9552
11024
|
for (const dir of dirs) {
|
|
9553
11025
|
try {
|
|
9554
|
-
const raw = await
|
|
11026
|
+
const raw = await readFile19(join18(dir, "annotations.json"), "utf-8");
|
|
9555
11027
|
const anns = JSON.parse(raw);
|
|
9556
11028
|
if (Array.isArray(anns)) return anns;
|
|
9557
11029
|
} catch {
|
|
@@ -9560,11 +11032,11 @@ async function loadAnnotations(baseDir, slug) {
|
|
|
9560
11032
|
return [];
|
|
9561
11033
|
}
|
|
9562
11034
|
async function saveAnnotations(baseDir, slug, annotations) {
|
|
9563
|
-
const annPath =
|
|
11035
|
+
const annPath = join18(baseDir, slug, "annotations.json");
|
|
9564
11036
|
await writeFile6(annPath, JSON.stringify(annotations, null, 2), "utf-8");
|
|
9565
11037
|
}
|
|
9566
11038
|
async function saveOverlays(baseDir, slug, overlays) {
|
|
9567
|
-
const overlayPath =
|
|
11039
|
+
const overlayPath = join18(baseDir, slug, "overlays.json");
|
|
9568
11040
|
await writeFile6(overlayPath, JSON.stringify(overlays, null, 2), "utf-8");
|
|
9569
11041
|
}
|
|
9570
11042
|
async function startServer(baseDir, opts) {
|
|
@@ -9573,7 +11045,7 @@ async function startServer(baseDir, opts) {
|
|
|
9573
11045
|
const viewerHtml = isDevMode ? "" : await loadViewerHtml();
|
|
9574
11046
|
const cleanupPeriodDays = await getClaudeCodeCleanupPeriod();
|
|
9575
11047
|
const cacheKeySuffix = createHash4("sha1").update(baseDir).digest("hex").slice(0, 12);
|
|
9576
|
-
const sourcesCacheKey = `dashboard-sources-
|
|
11048
|
+
const sourcesCacheKey = `dashboard-sources-v4-${cacheKeySuffix}`;
|
|
9577
11049
|
const replaysCacheKey = `dashboard-replays-v1-${cacheKeySuffix}`;
|
|
9578
11050
|
const scanResultsCacheKey = `dashboard-scan-results-v1-${cacheKeySuffix}`;
|
|
9579
11051
|
const insightsCacheKey = `dashboard-insights-v1-${cacheKeySuffix}`;
|
|
@@ -9634,11 +11106,20 @@ async function startServer(baseDir, opts) {
|
|
|
9634
11106
|
total: 0,
|
|
9635
11107
|
updated: 0
|
|
9636
11108
|
};
|
|
9637
|
-
|
|
9638
|
-
|
|
11109
|
+
let pendingSourcesEnrichment = null;
|
|
11110
|
+
let lastDiscoveredMergedSessions = [];
|
|
11111
|
+
const enrichCursorStatsInBackground = (merged, baseSources, hints = {}) => {
|
|
11112
|
+
if (sourcesEnrichmentStatus.running) {
|
|
11113
|
+
pendingSourcesEnrichment = {
|
|
11114
|
+
merged,
|
|
11115
|
+
baseSources,
|
|
11116
|
+
hints: mergeEnrichmentHints(pendingSourcesEnrichment?.hints, hints)
|
|
11117
|
+
};
|
|
11118
|
+
return;
|
|
11119
|
+
}
|
|
9639
11120
|
const cursorProvider2 = getProvider("cursor");
|
|
9640
11121
|
if (!cursorProvider2) return;
|
|
9641
|
-
const candidates = selectCursorEnrichmentCandidates(merged, baseSources);
|
|
11122
|
+
const candidates = selectCursorEnrichmentCandidates(merged, baseSources, hints);
|
|
9642
11123
|
sourcesEnrichmentStatus = {
|
|
9643
11124
|
running: true,
|
|
9644
11125
|
processed: 0,
|
|
@@ -9655,9 +11136,9 @@ async function startServer(baseDir, opts) {
|
|
|
9655
11136
|
};
|
|
9656
11137
|
return;
|
|
9657
11138
|
}
|
|
11139
|
+
const enrichedSources = baseSources.map((s) => ({ ...s }));
|
|
9658
11140
|
void (async () => {
|
|
9659
11141
|
let changed = false;
|
|
9660
|
-
const enrichedSources = baseSources.map((s) => ({ ...s }));
|
|
9661
11142
|
const bySessionId = /* @__PURE__ */ new Map();
|
|
9662
11143
|
const byKey = /* @__PURE__ */ new Map();
|
|
9663
11144
|
for (const source of enrichedSources) {
|
|
@@ -9733,6 +11214,11 @@ async function startServer(baseDir, opts) {
|
|
|
9733
11214
|
running: false,
|
|
9734
11215
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9735
11216
|
};
|
|
11217
|
+
const pending = pendingSourcesEnrichment;
|
|
11218
|
+
pendingSourcesEnrichment = null;
|
|
11219
|
+
if (pending) {
|
|
11220
|
+
enrichCursorStatsInBackground(pending.merged, enrichedSources, pending.hints);
|
|
11221
|
+
}
|
|
9736
11222
|
})().catch(() => {
|
|
9737
11223
|
sourcesEnrichmentStatus = {
|
|
9738
11224
|
...sourcesEnrichmentStatus,
|
|
@@ -9740,6 +11226,11 @@ async function startServer(baseDir, opts) {
|
|
|
9740
11226
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9741
11227
|
message: "Cursor stat backfill failed"
|
|
9742
11228
|
};
|
|
11229
|
+
const pending = pendingSourcesEnrichment;
|
|
11230
|
+
pendingSourcesEnrichment = null;
|
|
11231
|
+
if (pending) {
|
|
11232
|
+
enrichCursorStatsInBackground(pending.merged, enrichedSources, pending.hints);
|
|
11233
|
+
}
|
|
9743
11234
|
});
|
|
9744
11235
|
};
|
|
9745
11236
|
const persistedScanResults = await readFileCache(scanResultsCacheKey);
|
|
@@ -9871,7 +11362,7 @@ async function startServer(baseDir, opts) {
|
|
|
9871
11362
|
computedAt: insightsCache.computedAt
|
|
9872
11363
|
});
|
|
9873
11364
|
};
|
|
9874
|
-
const startBackgroundScan = () => {
|
|
11365
|
+
const startBackgroundScan = (hints = {}) => {
|
|
9875
11366
|
if (scanState.running) return;
|
|
9876
11367
|
const previousResults = scanState.results;
|
|
9877
11368
|
const previousFinishedAt = scanState.finishedAt;
|
|
@@ -9893,29 +11384,34 @@ async function startServer(baseDir, opts) {
|
|
|
9893
11384
|
allSessions.push(...sessions);
|
|
9894
11385
|
}
|
|
9895
11386
|
const merged = mergeSameSessions(allSessions);
|
|
9896
|
-
const home =
|
|
11387
|
+
const home = homedir16();
|
|
9897
11388
|
for (const s of merged) {
|
|
9898
11389
|
if (s.project.startsWith(home)) {
|
|
9899
11390
|
s.project = `~${s.project.slice(home.length)}`;
|
|
9900
11391
|
}
|
|
9901
11392
|
}
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
11393
|
+
lastDiscoveredMergedSessions = merged.map((session) => ({ ...session }));
|
|
11394
|
+
const scanInputs = prioritizeScanInputs(
|
|
11395
|
+
merged.map((s) => ({
|
|
11396
|
+
sessionId: s.sessionId,
|
|
11397
|
+
provider: s.provider,
|
|
11398
|
+
project: s.project,
|
|
11399
|
+
slug: s.slug,
|
|
11400
|
+
filePaths: s.filePaths,
|
|
11401
|
+
toolPaths: s.toolPaths,
|
|
11402
|
+
sourceFilePath: s.filePath,
|
|
11403
|
+
sourceFileSize: s.fileSize,
|
|
11404
|
+
sourceLineCount: s.lineCount,
|
|
11405
|
+
workspacePath: s.workspacePath,
|
|
11406
|
+
hasSqlite: s.hasSqlite,
|
|
11407
|
+
deferRichCursorParse: s.provider === "cursor" && !!s.hasSqlite,
|
|
11408
|
+
timestamp: s.timestamp,
|
|
11409
|
+
title: s.title,
|
|
11410
|
+
firstPrompt: s.firstPrompt
|
|
11411
|
+
})),
|
|
11412
|
+
previousResults,
|
|
11413
|
+
hints
|
|
11414
|
+
);
|
|
9919
11415
|
scanState.total = scanInputs.length;
|
|
9920
11416
|
const results = await runBackgroundScan(scanInputs, (progress) => {
|
|
9921
11417
|
scanState = {
|
|
@@ -10002,7 +11498,7 @@ async function startServer(baseDir, opts) {
|
|
|
10002
11498
|
await sendError(`Session not found: ${sessionId}`);
|
|
10003
11499
|
return;
|
|
10004
11500
|
}
|
|
10005
|
-
const home =
|
|
11501
|
+
const home = homedir16();
|
|
10006
11502
|
const projectFor = (info) => info.project.startsWith(home) ? `~${info.project.slice(home.length)}` : info.project;
|
|
10007
11503
|
const watchers = [];
|
|
10008
11504
|
const watchedPaths = /* @__PURE__ */ new Set();
|
|
@@ -10011,6 +11507,7 @@ async function startServer(baseDir, opts) {
|
|
|
10011
11507
|
let inFlight = false;
|
|
10012
11508
|
let dirty = false;
|
|
10013
11509
|
let lastSignature = null;
|
|
11510
|
+
let lastCursorDiagnosticsSignature = null;
|
|
10014
11511
|
const isClaudeProvider = providerName === "claude-code";
|
|
10015
11512
|
const isCursorProvider = providerName === "cursor";
|
|
10016
11513
|
const isCodexProvider = providerName === "codex";
|
|
@@ -10068,13 +11565,13 @@ async function startServer(baseDir, opts) {
|
|
|
10068
11565
|
const cached = jsonlTail.get(filePath);
|
|
10069
11566
|
let size;
|
|
10070
11567
|
try {
|
|
10071
|
-
size = (await
|
|
11568
|
+
size = (await stat10(filePath)).size;
|
|
10072
11569
|
} catch {
|
|
10073
11570
|
jsonlTail.delete(filePath);
|
|
10074
11571
|
return cached?.lines ?? [];
|
|
10075
11572
|
}
|
|
10076
11573
|
if (!cached || size < cached.offset) {
|
|
10077
|
-
const content = await
|
|
11574
|
+
const content = await readFile19(filePath);
|
|
10078
11575
|
const { lines, partial } = splitDecodedLines(content);
|
|
10079
11576
|
jsonlTail.set(filePath, { offset: content.length, partial, lines });
|
|
10080
11577
|
return lines;
|
|
@@ -10114,6 +11611,17 @@ async function startServer(baseDir, opts) {
|
|
|
10114
11611
|
const paths = [...info.filePaths, ...info.toolPaths || []];
|
|
10115
11612
|
ensureWatchersFor(paths);
|
|
10116
11613
|
await ensureCursorDbWatchersFor(info);
|
|
11614
|
+
const cursorDiagnostics = isCursorProvider && info.hasSqlite ? await readCursorLiveDiagnostics(info.sessionId).catch(() => null) : null;
|
|
11615
|
+
if (cursorDiagnostics && lastCursorDiagnosticsSignature === cursorDiagnostics.signature && lastSignature !== null) {
|
|
11616
|
+
await stream.writeSSE({
|
|
11617
|
+
data: JSON.stringify({
|
|
11618
|
+
type: "diagnostics",
|
|
11619
|
+
cursorDiagnostics,
|
|
11620
|
+
cursorRowsChanged: false
|
|
11621
|
+
})
|
|
11622
|
+
});
|
|
11623
|
+
return;
|
|
11624
|
+
}
|
|
10117
11625
|
let parsed;
|
|
10118
11626
|
if (isJsonlLiveProvider) {
|
|
10119
11627
|
const allLines = [];
|
|
@@ -10136,13 +11644,25 @@ async function startServer(baseDir, opts) {
|
|
|
10136
11644
|
lastLiveState = await readClaudeSessionState(sessionId);
|
|
10137
11645
|
}
|
|
10138
11646
|
const signature = JSON.stringify(replay.scenes);
|
|
11647
|
+
if (cursorDiagnostics) {
|
|
11648
|
+
lastCursorDiagnosticsSignature = cursorDiagnostics.signature;
|
|
11649
|
+
}
|
|
10139
11650
|
if (signature !== lastSignature) {
|
|
10140
11651
|
lastSignature = signature;
|
|
10141
11652
|
await stream.writeSSE({
|
|
10142
11653
|
data: JSON.stringify({
|
|
10143
11654
|
type: "session",
|
|
10144
11655
|
session: replay,
|
|
10145
|
-
state: lastLiveState
|
|
11656
|
+
state: lastLiveState,
|
|
11657
|
+
...cursorDiagnostics ? { cursorDiagnostics, cursorRowsChanged: true } : {}
|
|
11658
|
+
})
|
|
11659
|
+
});
|
|
11660
|
+
} else if (cursorDiagnostics) {
|
|
11661
|
+
await stream.writeSSE({
|
|
11662
|
+
data: JSON.stringify({
|
|
11663
|
+
type: "diagnostics",
|
|
11664
|
+
cursorDiagnostics,
|
|
11665
|
+
cursorRowsChanged: true
|
|
10146
11666
|
})
|
|
10147
11667
|
});
|
|
10148
11668
|
}
|
|
@@ -10243,8 +11763,8 @@ async function startServer(baseDir, opts) {
|
|
|
10243
11763
|
try {
|
|
10244
11764
|
const target = await loadSessionFromDisk(baseDir, slug);
|
|
10245
11765
|
target.meta.title = normalizeTitle(body.title);
|
|
10246
|
-
const targetDir =
|
|
10247
|
-
await writeFile6(
|
|
11766
|
+
const targetDir = join18(baseDir, slug);
|
|
11767
|
+
await writeFile6(join18(targetDir, "replay.json"), JSON.stringify(target), "utf-8");
|
|
10248
11768
|
await generateOutput(target, targetDir);
|
|
10249
11769
|
const updatedReplays = await refreshReplaysCache();
|
|
10250
11770
|
if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
|
|
@@ -10258,7 +11778,7 @@ async function startServer(baseDir, opts) {
|
|
|
10258
11778
|
if (!slug) return c.json({ error: "invalid slug" }, 400);
|
|
10259
11779
|
try {
|
|
10260
11780
|
const { rm } = await import("fs/promises");
|
|
10261
|
-
await rm(
|
|
11781
|
+
await rm(join18(baseDir, slug), { recursive: true });
|
|
10262
11782
|
const updatedReplays = await refreshReplaysCache();
|
|
10263
11783
|
if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
|
|
10264
11784
|
return c.json({ ok: true });
|
|
@@ -10292,6 +11812,36 @@ async function startServer(baseDir, opts) {
|
|
|
10292
11812
|
app.get("/api/sources/enrichment-status", async (c) => {
|
|
10293
11813
|
return c.json(sourcesEnrichmentStatus);
|
|
10294
11814
|
});
|
|
11815
|
+
app.post("/api/sources/enrich", async (c) => {
|
|
11816
|
+
const hints = enrichmentHintsFromBody(await c.req.json().catch(() => void 0));
|
|
11817
|
+
const cached = await readFileCache(sourcesCacheKey);
|
|
11818
|
+
if (!cached?.data?.length) {
|
|
11819
|
+
return c.json({ ok: false, message: "No sources cache available" }, 404);
|
|
11820
|
+
}
|
|
11821
|
+
const cursorProvider2 = getProvider("cursor");
|
|
11822
|
+
if (!cursorProvider2) return c.json({ ok: false, message: "Cursor provider unavailable" }, 404);
|
|
11823
|
+
const home = homedir16();
|
|
11824
|
+
let cursorSessions = lastDiscoveredMergedSessions.filter(
|
|
11825
|
+
(session) => session.provider === "cursor"
|
|
11826
|
+
);
|
|
11827
|
+
if (cursorSessions.length === 0) {
|
|
11828
|
+
cursorSessions = normalizeSessionProjectsForHome(
|
|
11829
|
+
mergeSameSessions(await cursorProvider2.discover()),
|
|
11830
|
+
home
|
|
11831
|
+
);
|
|
11832
|
+
lastDiscoveredMergedSessions = [
|
|
11833
|
+
...lastDiscoveredMergedSessions.filter((session) => session.provider !== "cursor"),
|
|
11834
|
+
...cursorSessions
|
|
11835
|
+
];
|
|
11836
|
+
}
|
|
11837
|
+
const wasRunning = sourcesEnrichmentStatus.running;
|
|
11838
|
+
enrichCursorStatsInBackground(cursorSessions, cached.data, hints);
|
|
11839
|
+
return c.json({
|
|
11840
|
+
ok: true,
|
|
11841
|
+
running: sourcesEnrichmentStatus.running,
|
|
11842
|
+
queued: wasRunning
|
|
11843
|
+
});
|
|
11844
|
+
});
|
|
10295
11845
|
app.get("/api/sources", async (c) => {
|
|
10296
11846
|
try {
|
|
10297
11847
|
const providers2 = getAllProviders();
|
|
@@ -10301,11 +11851,12 @@ async function startServer(baseDir, opts) {
|
|
|
10301
11851
|
allSessions.push(...sessions);
|
|
10302
11852
|
}
|
|
10303
11853
|
const merged = mergeSameSessions(allSessions);
|
|
11854
|
+
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir16());
|
|
10304
11855
|
const previous = await readFileCache(sourcesCacheKey);
|
|
10305
11856
|
const result = await buildSourcesResult(
|
|
10306
11857
|
merged,
|
|
10307
11858
|
baseDir,
|
|
10308
|
-
|
|
11859
|
+
homedir16(),
|
|
10309
11860
|
previous?.data || [],
|
|
10310
11861
|
cleanupPeriodDays
|
|
10311
11862
|
);
|
|
@@ -10335,11 +11886,12 @@ async function startServer(baseDir, opts) {
|
|
|
10335
11886
|
}
|
|
10336
11887
|
}
|
|
10337
11888
|
const merged = mergeSameSessions(allSessions);
|
|
11889
|
+
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir16());
|
|
10338
11890
|
const previous = await readFileCache(sourcesCacheKey);
|
|
10339
11891
|
const result = await buildSourcesResult(
|
|
10340
11892
|
merged,
|
|
10341
11893
|
baseDir,
|
|
10342
|
-
|
|
11894
|
+
homedir16(),
|
|
10343
11895
|
previous?.data || [],
|
|
10344
11896
|
cleanupPeriodDays
|
|
10345
11897
|
);
|
|
@@ -10374,7 +11926,7 @@ async function startServer(baseDir, opts) {
|
|
|
10374
11926
|
return c.json({ error: "title must be a string" }, 400);
|
|
10375
11927
|
}
|
|
10376
11928
|
const parsed = await provider.parse(resolved.value.paths, resolved.value.sessionInfo);
|
|
10377
|
-
const home =
|
|
11929
|
+
const home = homedir16();
|
|
10378
11930
|
const rawProject = body.sessionProject || parsed.cwd;
|
|
10379
11931
|
const project = rawProject.startsWith(home) ? `~${rawProject.slice(home.length)}` : rawProject;
|
|
10380
11932
|
const replay = transformToReplay(parsed, body.provider, project, {
|
|
@@ -10392,7 +11944,7 @@ async function startServer(baseDir, opts) {
|
|
|
10392
11944
|
}
|
|
10393
11945
|
const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
|
|
10394
11946
|
const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
10395
|
-
const outputDir =
|
|
11947
|
+
const outputDir = join18(baseDir, slug);
|
|
10396
11948
|
await generateOutput(replay, outputDir);
|
|
10397
11949
|
const updatedReplays = await refreshReplaysCache();
|
|
10398
11950
|
if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
|
|
@@ -10415,7 +11967,7 @@ async function startServer(baseDir, opts) {
|
|
|
10415
11967
|
});
|
|
10416
11968
|
app.post("/api/regenerate-all", async (c) => {
|
|
10417
11969
|
const replaysDir = baseDir;
|
|
10418
|
-
const { readdir:
|
|
11970
|
+
const { readdir: readdir12, readFile: readF } = await import("fs/promises");
|
|
10419
11971
|
const results = [];
|
|
10420
11972
|
const allProviders = getAllProviders();
|
|
10421
11973
|
const allSessions = [];
|
|
@@ -10428,14 +11980,14 @@ async function startServer(baseDir, opts) {
|
|
|
10428
11980
|
}
|
|
10429
11981
|
let entries;
|
|
10430
11982
|
try {
|
|
10431
|
-
entries = await
|
|
11983
|
+
entries = await readdir12(replaysDir);
|
|
10432
11984
|
} catch {
|
|
10433
11985
|
return c.json({ error: "No replays directory" }, 404);
|
|
10434
11986
|
}
|
|
10435
11987
|
for (const slug of entries) {
|
|
10436
11988
|
if (slug.startsWith(".") || slug === "cache") continue;
|
|
10437
11989
|
try {
|
|
10438
|
-
const replayPath =
|
|
11990
|
+
const replayPath = join18(replaysDir, slug, "replay.json");
|
|
10439
11991
|
const raw = await readF(replayPath, "utf-8").catch(() => null);
|
|
10440
11992
|
if (!raw) continue;
|
|
10441
11993
|
const oldReplay = JSON.parse(raw);
|
|
@@ -10457,7 +12009,7 @@ async function startServer(baseDir, opts) {
|
|
|
10457
12009
|
}
|
|
10458
12010
|
const paths = [...sessionInfo.filePaths, ...sessionInfo.toolPaths || []];
|
|
10459
12011
|
const parsed = await provider.parse(paths, sessionInfo);
|
|
10460
|
-
const home =
|
|
12012
|
+
const home = homedir16();
|
|
10461
12013
|
const project = sessionInfo.project.startsWith(home) ? `~${sessionInfo.project.slice(home.length)}` : sessionInfo.project;
|
|
10462
12014
|
const replay = transformToReplay(parsed, providerName, project, {
|
|
10463
12015
|
generator: {
|
|
@@ -10467,7 +12019,7 @@ async function startServer(baseDir, opts) {
|
|
|
10467
12019
|
}
|
|
10468
12020
|
});
|
|
10469
12021
|
if (oldReplay.meta?.title) replay.meta.title = oldReplay.meta.title;
|
|
10470
|
-
const outputDir =
|
|
12022
|
+
const outputDir = join18(replaysDir, slug);
|
|
10471
12023
|
await generateOutput(replay, outputDir);
|
|
10472
12024
|
results.push({ slug, status: "regenerated", scenes: replay.scenes.length });
|
|
10473
12025
|
} catch (err) {
|
|
@@ -10483,7 +12035,8 @@ async function startServer(baseDir, opts) {
|
|
|
10483
12035
|
});
|
|
10484
12036
|
});
|
|
10485
12037
|
app.post("/api/scan/start", async (c) => {
|
|
10486
|
-
|
|
12038
|
+
const hints = enrichmentHintsFromBody(await c.req.json().catch(() => void 0));
|
|
12039
|
+
startBackgroundScan(hints);
|
|
10487
12040
|
return c.json({ ok: true, message: "Background scan started" });
|
|
10488
12041
|
});
|
|
10489
12042
|
app.get("/api/scan/status", async (c) => {
|
|
@@ -10596,7 +12149,7 @@ async function startServer(baseDir, opts) {
|
|
|
10596
12149
|
app.get("/api/gh-status", (c) => {
|
|
10597
12150
|
return c.json(checkPublishStatus());
|
|
10598
12151
|
});
|
|
10599
|
-
const cloudApiBaseUrl = getApiUrl()
|
|
12152
|
+
const cloudApiBaseUrl = getApiUrl();
|
|
10600
12153
|
function readLocalAuthSession() {
|
|
10601
12154
|
const exact = loadAuthToken(cloudApiBaseUrl);
|
|
10602
12155
|
if (exact) {
|
|
@@ -10611,7 +12164,7 @@ async function startServer(baseDir, opts) {
|
|
|
10611
12164
|
return {
|
|
10612
12165
|
token: fallback.token,
|
|
10613
12166
|
user: fallback.user,
|
|
10614
|
-
targetApi: fallback.origin
|
|
12167
|
+
targetApi: fallback.origin
|
|
10615
12168
|
};
|
|
10616
12169
|
}
|
|
10617
12170
|
return null;
|
|
@@ -10628,7 +12181,7 @@ async function startServer(baseDir, opts) {
|
|
|
10628
12181
|
if (exact) candidates.push({ token: exact.token, apiUrl: cloudApiBaseUrl });
|
|
10629
12182
|
const fallback = loadAnyAuthToken();
|
|
10630
12183
|
if (fallback) {
|
|
10631
|
-
const fallbackApi = fallback.origin
|
|
12184
|
+
const fallbackApi = fallback.origin;
|
|
10632
12185
|
if (fallbackApi !== cloudApiBaseUrl) {
|
|
10633
12186
|
candidates.push({ token: fallback.token, apiUrl: fallbackApi });
|
|
10634
12187
|
}
|
|
@@ -10880,7 +12433,7 @@ async function startServer(baseDir, opts) {
|
|
|
10880
12433
|
});
|
|
10881
12434
|
});
|
|
10882
12435
|
app.get("/api/system-checks", async (c) => {
|
|
10883
|
-
const exec =
|
|
12436
|
+
const exec = promisify3(execFile3);
|
|
10884
12437
|
const TOOL_CHECK_TIMEOUT_MS = 3e3;
|
|
10885
12438
|
const CHECK_TIMEOUT_MARKER = "__check_timeout__";
|
|
10886
12439
|
const CHECK_TIMEOUT_DETAIL = "check timeout";
|
|
@@ -10901,7 +12454,8 @@ async function startServer(baseDir, opts) {
|
|
|
10901
12454
|
}
|
|
10902
12455
|
};
|
|
10903
12456
|
async function checkCli(name, label, purpose, cmd, versionArgs = ["--version"], extraCheck) {
|
|
10904
|
-
const
|
|
12457
|
+
const locator = process.platform === "win32" ? "where" : "which";
|
|
12458
|
+
const whichResult = await runCommand(locator, [cmd]);
|
|
10905
12459
|
if (!whichResult.ok) {
|
|
10906
12460
|
if (whichResult.timedOut) {
|
|
10907
12461
|
return { name, label, purpose, installed: false, detail: CHECK_TIMEOUT_DETAIL };
|
|
@@ -10972,7 +12526,7 @@ async function startServer(baseDir, opts) {
|
|
|
10972
12526
|
app.get("/api/gist-info", async (c) => {
|
|
10973
12527
|
const result = requireSlug(c.req.query("slug"));
|
|
10974
12528
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
10975
|
-
const targetDir =
|
|
12529
|
+
const targetDir = join18(baseDir, result.slug);
|
|
10976
12530
|
const gist = await loadSavedGistInfo(targetDir);
|
|
10977
12531
|
if (!gist) return c.json({ gist: null });
|
|
10978
12532
|
return c.json({ gist });
|
|
@@ -10980,7 +12534,7 @@ async function startServer(baseDir, opts) {
|
|
|
10980
12534
|
app.delete("/api/gist-info", async (c) => {
|
|
10981
12535
|
const result = requireSlug(c.req.query("slug"));
|
|
10982
12536
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
10983
|
-
const metaPath =
|
|
12537
|
+
const metaPath = join18(baseDir, result.slug, ".vibe-replay-gist.json");
|
|
10984
12538
|
await unlink2(metaPath).catch(() => {
|
|
10985
12539
|
});
|
|
10986
12540
|
return c.json({ ok: true });
|
|
@@ -10988,7 +12542,7 @@ async function startServer(baseDir, opts) {
|
|
|
10988
12542
|
app.get("/api/cloud-info", async (c) => {
|
|
10989
12543
|
const result = requireSlug(c.req.query("slug"));
|
|
10990
12544
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
10991
|
-
const targetDir =
|
|
12545
|
+
const targetDir = join18(baseDir, result.slug);
|
|
10992
12546
|
const cloud = await loadSavedCloudInfo(targetDir);
|
|
10993
12547
|
if (!cloud) return c.json({ cloud: null });
|
|
10994
12548
|
return c.json({ cloud });
|
|
@@ -10996,10 +12550,10 @@ async function startServer(baseDir, opts) {
|
|
|
10996
12550
|
app.post("/api/cloud-info", async (c) => {
|
|
10997
12551
|
const result = requireSlug(c.req.query("slug"));
|
|
10998
12552
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
10999
|
-
const targetDir =
|
|
12553
|
+
const targetDir = join18(baseDir, result.slug);
|
|
11000
12554
|
const body = await c.req.json();
|
|
11001
12555
|
if (!body.id || !body.url) return c.json({ error: "Missing id/url" }, 400);
|
|
11002
|
-
const metaPath =
|
|
12556
|
+
const metaPath = join18(targetDir, ".vibe-replay-cloud.json");
|
|
11003
12557
|
await writeFile6(
|
|
11004
12558
|
metaPath,
|
|
11005
12559
|
JSON.stringify(
|
|
@@ -11019,7 +12573,7 @@ async function startServer(baseDir, opts) {
|
|
|
11019
12573
|
app.delete("/api/cloud-info", async (c) => {
|
|
11020
12574
|
const result = requireSlug(c.req.query("slug"));
|
|
11021
12575
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
11022
|
-
const metaPath =
|
|
12576
|
+
const metaPath = join18(baseDir, result.slug, ".vibe-replay-cloud.json");
|
|
11023
12577
|
await unlink2(metaPath).catch(() => {
|
|
11024
12578
|
});
|
|
11025
12579
|
return c.json({ ok: true });
|
|
@@ -11027,13 +12581,13 @@ async function startServer(baseDir, opts) {
|
|
|
11027
12581
|
app.post("/api/publish/gist", async (c) => {
|
|
11028
12582
|
const result = requireSlug(c.req.query("slug"));
|
|
11029
12583
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
11030
|
-
const targetDir =
|
|
12584
|
+
const targetDir = join18(baseDir, result.slug);
|
|
11031
12585
|
try {
|
|
11032
12586
|
const rawSession = await loadSessionFromDisk(baseDir, result.slug);
|
|
11033
12587
|
const overlaysData = await loadOverlays(baseDir, result.slug);
|
|
11034
12588
|
const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
|
|
11035
|
-
const replayPath =
|
|
11036
|
-
const originalContent = await
|
|
12589
|
+
const replayPath = join18(targetDir, "replay.json");
|
|
12590
|
+
const originalContent = await readFile19(replayPath, "utf-8");
|
|
11037
12591
|
await writeFile6(replayPath, JSON.stringify(targetSession), "utf-8");
|
|
11038
12592
|
try {
|
|
11039
12593
|
const title = targetSession.meta.title || targetSession.meta.slug;
|
|
@@ -11052,7 +12606,7 @@ async function startServer(baseDir, opts) {
|
|
|
11052
12606
|
app.post("/api/publish/cloud", async (c) => {
|
|
11053
12607
|
const result = requireSlug(c.req.query("slug"));
|
|
11054
12608
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
11055
|
-
const targetDir =
|
|
12609
|
+
const targetDir = join18(baseDir, result.slug);
|
|
11056
12610
|
try {
|
|
11057
12611
|
const body = await c.req.json().catch(() => ({}));
|
|
11058
12612
|
const cloudResult = await publishCloudWithOverlays(targetDir, {
|
|
@@ -11066,13 +12620,13 @@ async function startServer(baseDir, opts) {
|
|
|
11066
12620
|
app.post("/api/export/html", async (c) => {
|
|
11067
12621
|
const result = requireSlug(c.req.query("slug"));
|
|
11068
12622
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
11069
|
-
const targetDir =
|
|
12623
|
+
const targetDir = join18(baseDir, result.slug);
|
|
11070
12624
|
try {
|
|
11071
12625
|
const rawSession = await loadSessionFromDisk(baseDir, result.slug);
|
|
11072
12626
|
const overlaysData = await loadOverlays(baseDir, result.slug);
|
|
11073
12627
|
const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
|
|
11074
|
-
const replayPath =
|
|
11075
|
-
const originalContent = await
|
|
12628
|
+
const replayPath = join18(targetDir, "replay.json");
|
|
12629
|
+
const originalContent = await readFile19(replayPath, "utf-8");
|
|
11076
12630
|
try {
|
|
11077
12631
|
const outputPath = await generateOutput(targetSession, targetDir);
|
|
11078
12632
|
return c.json({ path: outputPath });
|
|
@@ -11086,23 +12640,23 @@ async function startServer(baseDir, opts) {
|
|
|
11086
12640
|
app.get("/api/export/github/status", async (c) => {
|
|
11087
12641
|
const result = requireSlug(c.req.query("slug"));
|
|
11088
12642
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
11089
|
-
const targetDir =
|
|
12643
|
+
const targetDir = join18(baseDir, result.slug);
|
|
11090
12644
|
try {
|
|
11091
|
-
const svgPath =
|
|
11092
|
-
const mdPath =
|
|
11093
|
-
const gifPath =
|
|
12645
|
+
const svgPath = join18(targetDir, "session-preview.svg");
|
|
12646
|
+
const mdPath = join18(targetDir, "github-summary.md");
|
|
12647
|
+
const gifPath = join18(targetDir, "session-preview.gif");
|
|
11094
12648
|
const [svgContent, markdown, gifBuf] = await Promise.all([
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
12649
|
+
readFile19(svgPath, "utf-8").catch(() => null),
|
|
12650
|
+
readFile19(mdPath, "utf-8").catch(() => null),
|
|
12651
|
+
readFile19(gifPath).catch(() => null)
|
|
11098
12652
|
]);
|
|
11099
12653
|
if (!svgContent && !markdown && !gifBuf) return c.json({ exists: false });
|
|
11100
12654
|
const gist = await loadSavedGistInfo(targetDir);
|
|
11101
12655
|
const gifContent = gifBuf ? gifBuf.toString("base64") : null;
|
|
11102
12656
|
const [gifMtime, svgMtime, mdMtime] = await Promise.all([
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
12657
|
+
stat10(gifPath).then((s) => s.mtime.toISOString()).catch(() => null),
|
|
12658
|
+
stat10(svgPath).then((s) => s.mtime.toISOString()).catch(() => null),
|
|
12659
|
+
stat10(mdPath).then((s) => s.mtime.toISOString()).catch(() => null)
|
|
11106
12660
|
]);
|
|
11107
12661
|
return c.json({
|
|
11108
12662
|
exists: true,
|
|
@@ -11124,7 +12678,7 @@ async function startServer(baseDir, opts) {
|
|
|
11124
12678
|
app.post("/api/export/github", async (c) => {
|
|
11125
12679
|
const result = requireSlug(c.req.query("slug"));
|
|
11126
12680
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
11127
|
-
const targetDir =
|
|
12681
|
+
const targetDir = join18(baseDir, result.slug);
|
|
11128
12682
|
try {
|
|
11129
12683
|
const rawSession = await loadSessionFromDisk(baseDir, result.slug);
|
|
11130
12684
|
const overlaysData = await loadOverlays(baseDir, result.slug);
|
|
@@ -11132,14 +12686,14 @@ async function startServer(baseDir, opts) {
|
|
|
11132
12686
|
const gist = await loadSavedGistInfo(targetDir);
|
|
11133
12687
|
const replayUrl = gist?.viewerUrl || void 0;
|
|
11134
12688
|
const svgContent = generateGitHubSvg(targetSession, { replayUrl });
|
|
11135
|
-
const svgFilePath =
|
|
12689
|
+
const svgFilePath = join18(targetDir, "session-preview.svg");
|
|
11136
12690
|
await writeFile6(svgFilePath, svgContent, "utf-8");
|
|
11137
12691
|
let gifContent = null;
|
|
11138
12692
|
let gifFilePath = null;
|
|
11139
12693
|
let gifWarning;
|
|
11140
12694
|
try {
|
|
11141
12695
|
const gifBuffer = await generateGitHubGif(targetSession, { replayUrl });
|
|
11142
|
-
gifFilePath =
|
|
12696
|
+
gifFilePath = join18(targetDir, "session-preview.gif");
|
|
11143
12697
|
await writeFile6(gifFilePath, gifBuffer);
|
|
11144
12698
|
gifContent = gifBuffer.toString("base64");
|
|
11145
12699
|
} catch (err) {
|
|
@@ -11150,7 +12704,7 @@ async function startServer(baseDir, opts) {
|
|
|
11150
12704
|
svgPath: "./session-preview.svg",
|
|
11151
12705
|
gifPath: gifContent ? "./session-preview.gif" : void 0
|
|
11152
12706
|
});
|
|
11153
|
-
const mdFilePath =
|
|
12707
|
+
const mdFilePath = join18(targetDir, "github-summary.md");
|
|
11154
12708
|
await writeFile6(mdFilePath, markdown, "utf-8");
|
|
11155
12709
|
const findings = scanForSecrets(JSON.stringify(targetSession));
|
|
11156
12710
|
const warnings = findings.map((f) => `[${f.rule}] ${f.match}`);
|
|
@@ -11395,24 +12949,370 @@ async function startDashboard(baseDir, opts) {
|
|
|
11395
12949
|
await startServer(baseDir, { openDashboard: true, externalViewerUrl: opts?.externalViewerUrl });
|
|
11396
12950
|
}
|
|
11397
12951
|
|
|
12952
|
+
// src/session-query.ts
|
|
12953
|
+
init_utils();
|
|
12954
|
+
var HIGH_TOOL_DENSITY_THRESHOLD = 20;
|
|
12955
|
+
async function queryLocalSessions(sessions, options = {}) {
|
|
12956
|
+
const limit = normalizeLimit(options.limit);
|
|
12957
|
+
const terms = splitTerms(options.query);
|
|
12958
|
+
const filtered = filterScoredSessionInfos(sessions, options).slice(0, limit);
|
|
12959
|
+
const matches = filtered.map(({ session, query }) => sessionInfoToMatch(session, terms, query));
|
|
12960
|
+
if (!options.scan && !options.brief) return matches;
|
|
12961
|
+
const scanned = await Promise.all(
|
|
12962
|
+
filtered.map(async ({ session }) => {
|
|
12963
|
+
try {
|
|
12964
|
+
return await scanSession(scanInputFromSession(session));
|
|
12965
|
+
} catch {
|
|
12966
|
+
return void 0;
|
|
12967
|
+
}
|
|
12968
|
+
})
|
|
12969
|
+
);
|
|
12970
|
+
return matches.map((match, index) => {
|
|
12971
|
+
const scan = scanned[index];
|
|
12972
|
+
const summary = scan ? scanSummary(scan) : void 0;
|
|
12973
|
+
return {
|
|
12974
|
+
...match,
|
|
12975
|
+
...summary ? { scan: summary } : {},
|
|
12976
|
+
...options.brief ? { brief: buildSessionBrief(match, summary) } : {}
|
|
12977
|
+
};
|
|
12978
|
+
});
|
|
12979
|
+
}
|
|
12980
|
+
function filterScoredSessionInfos(sessions, options = {}) {
|
|
12981
|
+
const terms = splitTerms(options.query);
|
|
12982
|
+
const projectTerm = normalizeSearch(options.project);
|
|
12983
|
+
const provider = normalizeSearch(options.provider);
|
|
12984
|
+
const wideMatch = Boolean(options.any);
|
|
12985
|
+
const filtered = [...sessions].map((session) => ({
|
|
12986
|
+
session,
|
|
12987
|
+
query: scoreSession(session, terms)
|
|
12988
|
+
})).filter(({ session, query }) => {
|
|
12989
|
+
if (provider && normalizeSearch(session.provider) !== provider) return false;
|
|
12990
|
+
if (projectTerm && !sessionProjectHaystack(session).includes(projectTerm)) return false;
|
|
12991
|
+
if (terms.length > 0) {
|
|
12992
|
+
if (wideMatch) return query.matchedTerms.length > 0;
|
|
12993
|
+
if (query.matchedTerms.length !== terms.length) return false;
|
|
12994
|
+
}
|
|
12995
|
+
return true;
|
|
12996
|
+
}).sort((a, b) => {
|
|
12997
|
+
if (wideMatch || terms.length > 0) {
|
|
12998
|
+
const scoreDelta = b.query.score - a.query.score;
|
|
12999
|
+
if (scoreDelta) return scoreDelta;
|
|
13000
|
+
}
|
|
13001
|
+
return b.session.timestamp.localeCompare(a.session.timestamp);
|
|
13002
|
+
});
|
|
13003
|
+
return options.dedupe ? dedupeSimilarScoredSessions(filtered) : filtered;
|
|
13004
|
+
}
|
|
13005
|
+
function scanInputFromSession(session) {
|
|
13006
|
+
return {
|
|
13007
|
+
sessionId: session.sessionId,
|
|
13008
|
+
provider: session.provider,
|
|
13009
|
+
project: shortenPath(session.project),
|
|
13010
|
+
slug: session.slug,
|
|
13011
|
+
filePaths: session.filePaths,
|
|
13012
|
+
toolPaths: session.toolPaths,
|
|
13013
|
+
sourceFilePath: session.filePath,
|
|
13014
|
+
sourceFileSize: session.fileSize,
|
|
13015
|
+
sourceLineCount: session.lineCount,
|
|
13016
|
+
workspacePath: session.workspacePath,
|
|
13017
|
+
hasSqlite: session.hasSqlite,
|
|
13018
|
+
timestamp: session.timestamp,
|
|
13019
|
+
title: session.title,
|
|
13020
|
+
firstPrompt: session.firstPrompt
|
|
13021
|
+
};
|
|
13022
|
+
}
|
|
13023
|
+
function formatSessionQueryText(matches) {
|
|
13024
|
+
if (matches.length === 0) return "No matching sessions found.";
|
|
13025
|
+
return matches.map((match, index) => {
|
|
13026
|
+
const title = match.title || previewPrompt(match.firstPrompt) || match.slug;
|
|
13027
|
+
const lines = [
|
|
13028
|
+
`${index + 1}. ${title}`,
|
|
13029
|
+
` ${match.provider} | ${match.timestamp} | ${match.project}`,
|
|
13030
|
+
` session: ${match.sessionId}`,
|
|
13031
|
+
` file: ${match.filePath}`
|
|
13032
|
+
];
|
|
13033
|
+
if (match.gitBranch) lines.push(` branch: ${match.gitBranch}`);
|
|
13034
|
+
if (match.model) lines.push(` model: ${match.model}`);
|
|
13035
|
+
if (match.matchedTerms?.length) {
|
|
13036
|
+
const quality = match.matchQuality ? `${match.matchQuality}, ` : "";
|
|
13037
|
+
lines.push(
|
|
13038
|
+
` matched: ${quality}${match.matchedTerms.length}/${(match.matchedTerms.length || 0) + (match.unmatchedTerms?.length || 0)} terms (${match.matchedTerms.join(", ")})`
|
|
13039
|
+
);
|
|
13040
|
+
}
|
|
13041
|
+
if (match.unmatchedTerms?.length) {
|
|
13042
|
+
lines.push(` unmatched: ${match.unmatchedTerms.join(", ")}`);
|
|
13043
|
+
}
|
|
13044
|
+
if (match.whyMatched?.length) lines.push(` why: ${match.whyMatched.join("; ")}`);
|
|
13045
|
+
if (match.firstPrompt) lines.push(` first prompt: ${previewPrompt(match.firstPrompt)}`);
|
|
13046
|
+
if (match.scan) lines.push(` efficiency: ${formatScanSummary(match.scan)}`);
|
|
13047
|
+
if (match.brief) {
|
|
13048
|
+
lines.push(` brief: ${match.brief.summary}`);
|
|
13049
|
+
if (match.brief.signals.length > 0) {
|
|
13050
|
+
lines.push(` signals: ${match.brief.signals.join("; ")}`);
|
|
13051
|
+
}
|
|
13052
|
+
lines.push(` next: ${match.brief.suggestedNextAction}`);
|
|
13053
|
+
}
|
|
13054
|
+
return lines.join("\n");
|
|
13055
|
+
}).join("\n\n");
|
|
13056
|
+
}
|
|
13057
|
+
function sessionInfoToMatch(session, terms = [], precomputedScore) {
|
|
13058
|
+
const query = precomputedScore || scoreSession(session, terms);
|
|
13059
|
+
return {
|
|
13060
|
+
provider: session.provider,
|
|
13061
|
+
sessionId: session.sessionId,
|
|
13062
|
+
slug: session.slug,
|
|
13063
|
+
title: cleanPromptText(session.title || "") || void 0,
|
|
13064
|
+
project: shortenPath(session.project),
|
|
13065
|
+
cwd: shortenPath(session.cwd),
|
|
13066
|
+
timestamp: session.timestamp,
|
|
13067
|
+
gitBranch: session.gitBranch,
|
|
13068
|
+
model: session.model,
|
|
13069
|
+
firstPrompt: cleanPromptText(session.firstPrompt),
|
|
13070
|
+
prompts: session.prompts?.map(cleanPromptText).filter(Boolean),
|
|
13071
|
+
filePath: session.filePath,
|
|
13072
|
+
filePaths: session.filePaths,
|
|
13073
|
+
toolPaths: session.toolPaths,
|
|
13074
|
+
promptCount: session.promptCount,
|
|
13075
|
+
toolCallCount: session.toolCallCount,
|
|
13076
|
+
editCount: session.editCountEst,
|
|
13077
|
+
durationMs: session.durationMsEst,
|
|
13078
|
+
hasPR: session.hasPR,
|
|
13079
|
+
score: query.score,
|
|
13080
|
+
matchedTerms: query.matchedTerms,
|
|
13081
|
+
unmatchedTerms: query.unmatchedTerms,
|
|
13082
|
+
matchQuality: query.matchQuality,
|
|
13083
|
+
whyMatched: query.whyMatched
|
|
13084
|
+
};
|
|
13085
|
+
}
|
|
13086
|
+
function scanSummary(scan) {
|
|
13087
|
+
const promptCount = scan.promptCount;
|
|
13088
|
+
return {
|
|
13089
|
+
promptCount,
|
|
13090
|
+
toolCallCount: scan.toolCallCount,
|
|
13091
|
+
editCount: scan.editCount,
|
|
13092
|
+
filesModified: scan.filesModified.slice(0, 20),
|
|
13093
|
+
durationMs: scan.durationMs,
|
|
13094
|
+
costEstimate: scan.costEstimate,
|
|
13095
|
+
apiErrorCount: scan.apiErrorCount,
|
|
13096
|
+
compactionCount: scan.compactionCount,
|
|
13097
|
+
subAgentCount: scan.subAgentCount,
|
|
13098
|
+
toolCallsPerPrompt: ratio(scan.toolCallCount, promptCount),
|
|
13099
|
+
editsPerPrompt: ratio(scan.editCount, promptCount),
|
|
13100
|
+
medianTurnDurationMs: median(scan.turnDurations),
|
|
13101
|
+
dataQualityNotes: scan.dataQualityNotes
|
|
13102
|
+
};
|
|
13103
|
+
}
|
|
13104
|
+
function formatScanSummary(scan) {
|
|
13105
|
+
const parts = [
|
|
13106
|
+
`${scan.promptCount} prompts`,
|
|
13107
|
+
`${scan.toolCallCount} tools`,
|
|
13108
|
+
`${scan.editCount} edits`
|
|
13109
|
+
];
|
|
13110
|
+
if (scan.durationMs) parts.push(formatDuration2(scan.durationMs));
|
|
13111
|
+
if (scan.costEstimate) parts.push(`$${scan.costEstimate.toFixed(2)}`);
|
|
13112
|
+
if (scan.toolCallsPerPrompt !== void 0) {
|
|
13113
|
+
parts.push(`${scan.toolCallsPerPrompt.toFixed(1)} tools/prompt`);
|
|
13114
|
+
}
|
|
13115
|
+
if (scan.apiErrorCount > 0) parts.push(`${scan.apiErrorCount} API errors`);
|
|
13116
|
+
if (scan.compactionCount > 0) parts.push(`${scan.compactionCount} compactions`);
|
|
13117
|
+
return parts.join(", ");
|
|
13118
|
+
}
|
|
13119
|
+
function scoreSession(session, terms) {
|
|
13120
|
+
if (terms.length === 0) {
|
|
13121
|
+
return { score: 0, matchedTerms: [], unmatchedTerms: [], whyMatched: [] };
|
|
13122
|
+
}
|
|
13123
|
+
const fields = [
|
|
13124
|
+
{ name: "title", value: session.title, weight: 8 },
|
|
13125
|
+
{ name: "first prompt", value: session.firstPrompt, weight: 7 },
|
|
13126
|
+
{ name: "prompts", value: (session.prompts || []).join(" "), weight: 5 },
|
|
13127
|
+
{ name: "project", value: session.project, weight: 4 },
|
|
13128
|
+
{ name: "branch", value: session.gitBranch, weight: 4 },
|
|
13129
|
+
{ name: "model", value: session.model, weight: 2 },
|
|
13130
|
+
{ name: "provider", value: session.provider, weight: 2 },
|
|
13131
|
+
{ name: "slug", value: session.slug, weight: 2 },
|
|
13132
|
+
{ name: "files", value: (session.fsDetectedFiles || []).join(" "), weight: 3 }
|
|
13133
|
+
].map((field) => ({ ...field, normalized: normalizeSearch(field.value) }));
|
|
13134
|
+
const matchedTerms = [];
|
|
13135
|
+
const unmatchedTerms = [];
|
|
13136
|
+
const whyMatched = [];
|
|
13137
|
+
let score = 0;
|
|
13138
|
+
for (const term of terms) {
|
|
13139
|
+
const hits = fields.filter((field) => textMatchesTerm(field.normalized, term));
|
|
13140
|
+
if (hits.length === 0) {
|
|
13141
|
+
unmatchedTerms.push(term);
|
|
13142
|
+
continue;
|
|
13143
|
+
}
|
|
13144
|
+
matchedTerms.push(term);
|
|
13145
|
+
const best = hits.sort((a, b) => b.weight - a.weight)[0];
|
|
13146
|
+
score += best.weight;
|
|
13147
|
+
whyMatched.push(`${term} in ${best.name}`);
|
|
13148
|
+
}
|
|
13149
|
+
if (matchedTerms.length === terms.length && terms.length > 1) score += 5;
|
|
13150
|
+
if (session.hasPR && terms.some((term) => ["pr", "review", "pull", "merge"].includes(term))) {
|
|
13151
|
+
score += 2;
|
|
13152
|
+
}
|
|
13153
|
+
if (session.promptCount && session.promptCount > 10) score += 1;
|
|
13154
|
+
return {
|
|
13155
|
+
score,
|
|
13156
|
+
matchedTerms,
|
|
13157
|
+
unmatchedTerms,
|
|
13158
|
+
matchQuality: matchQuality(matchedTerms.length, terms.length),
|
|
13159
|
+
whyMatched
|
|
13160
|
+
};
|
|
13161
|
+
}
|
|
13162
|
+
function matchQuality(matchedTermCount, queryTermCount) {
|
|
13163
|
+
if (queryTermCount === 0 || matchedTermCount === 0) return void 0;
|
|
13164
|
+
if (matchedTermCount === queryTermCount) return "all-terms";
|
|
13165
|
+
if (matchedTermCount >= 2) return "strong";
|
|
13166
|
+
return "weak";
|
|
13167
|
+
}
|
|
13168
|
+
function buildSessionBrief(match, scan) {
|
|
13169
|
+
const text = normalizeSearch(
|
|
13170
|
+
[match.title, match.firstPrompt, match.project, match.gitBranch].filter(Boolean).join(" ")
|
|
13171
|
+
);
|
|
13172
|
+
const taskType = classifyTaskType(text, match, scan);
|
|
13173
|
+
const signals = sessionSignals(match, scan);
|
|
13174
|
+
return {
|
|
13175
|
+
taskType,
|
|
13176
|
+
summary: briefSummary(taskType, match, scan),
|
|
13177
|
+
signals,
|
|
13178
|
+
suggestedNextAction: suggestedNextAction(taskType, signals, scan)
|
|
13179
|
+
};
|
|
13180
|
+
}
|
|
13181
|
+
function classifyTaskType(text, match, scan) {
|
|
13182
|
+
if (/(review|pr|pull request|merge|ai review)/i.test(text) || match.hasPR) return "PR/review";
|
|
13183
|
+
if (/(latest main|branch|current feature|what.*working)/i.test(text)) {
|
|
13184
|
+
return "context recovery";
|
|
13185
|
+
}
|
|
13186
|
+
if (/(skill|dev space|devspaces)/i.test(text)) return "skill workflow";
|
|
13187
|
+
if (/(auth|oauth|login)/i.test(text)) return "auth/debugging";
|
|
13188
|
+
if (/(ci|test|lint|build|e2e)/i.test(text)) return "CI/test";
|
|
13189
|
+
if ((scan?.editCount || 0) > 0 || (match.editCount || 0) > 0) return "implementation";
|
|
13190
|
+
return "research";
|
|
13191
|
+
}
|
|
13192
|
+
function sessionSignals(match, scan) {
|
|
13193
|
+
const signals = [];
|
|
13194
|
+
const promptCount = scan?.promptCount ?? match.promptCount ?? 0;
|
|
13195
|
+
const toolCount = scan?.toolCallCount ?? match.toolCallCount ?? 0;
|
|
13196
|
+
const toolsPerPrompt = scan?.toolCallsPerPrompt ?? ratio(toolCount, promptCount);
|
|
13197
|
+
if (toolsPerPrompt !== void 0 && toolsPerPrompt >= HIGH_TOOL_DENSITY_THRESHOLD) {
|
|
13198
|
+
signals.push(`high tool density (${toolsPerPrompt.toFixed(1)} tools/prompt)`);
|
|
13199
|
+
}
|
|
13200
|
+
if (promptCount >= 40) signals.push(`long conversation (${promptCount} prompts)`);
|
|
13201
|
+
if ((scan?.editCount ?? match.editCount ?? 0) >= 50) {
|
|
13202
|
+
signals.push(`heavy editing (${scan?.editCount ?? match.editCount} edits)`);
|
|
13203
|
+
}
|
|
13204
|
+
if ((scan?.filesModified?.length || 0) >= 10) {
|
|
13205
|
+
signals.push(`broad file surface (${scan?.filesModified.length} files in scan summary)`);
|
|
13206
|
+
}
|
|
13207
|
+
if ((scan?.apiErrorCount || 0) > 0) signals.push(`${scan?.apiErrorCount} API errors`);
|
|
13208
|
+
if ((scan?.compactionCount || 0) > 0) signals.push(`${scan?.compactionCount} compactions`);
|
|
13209
|
+
if ((scan?.subAgentCount || 0) > 0) signals.push(`${scan?.subAgentCount} sub-agents`);
|
|
13210
|
+
if (match.matchedTerms?.length) {
|
|
13211
|
+
const quality = match.matchQuality ? `${match.matchQuality} match` : "matched";
|
|
13212
|
+
signals.push(`${quality}: ${match.matchedTerms.join(", ")}`);
|
|
13213
|
+
}
|
|
13214
|
+
if (match.unmatchedTerms?.length) {
|
|
13215
|
+
signals.push(`unmatched: ${match.unmatchedTerms.join(", ")}`);
|
|
13216
|
+
}
|
|
13217
|
+
return signals;
|
|
13218
|
+
}
|
|
13219
|
+
function briefSummary(taskType, match, scan) {
|
|
13220
|
+
const title = previewPrompt(match.title || match.firstPrompt || match.slug);
|
|
13221
|
+
if (!scan) return `${taskType} session: ${title}`;
|
|
13222
|
+
return `${taskType} session with ${plural(scan.promptCount, "prompt")}, ${plural(scan.toolCallCount, "tool")}, and ${plural(scan.editCount, "edit")}: ${title}`;
|
|
13223
|
+
}
|
|
13224
|
+
function suggestedNextAction(taskType, signals, scan) {
|
|
13225
|
+
if (!scan) return "Run again with --scan or --brief before doing a retro.";
|
|
13226
|
+
if (signals.some((signal) => signal.includes("high tool density") || signal.includes("long"))) {
|
|
13227
|
+
return "Generate a replay or inspect chapters; raw metrics suggest a dense session.";
|
|
13228
|
+
}
|
|
13229
|
+
if (taskType === "PR/review")
|
|
13230
|
+
return "Inspect PR links/review comments or generate a PR-focused replay.";
|
|
13231
|
+
if (taskType === "context recovery")
|
|
13232
|
+
return "Use this as evidence for a session-start snapshot feature.";
|
|
13233
|
+
return "Use filePaths for deeper transcript/replay inspection if this is the right candidate.";
|
|
13234
|
+
}
|
|
13235
|
+
function splitTerms(query) {
|
|
13236
|
+
return normalizeSearch(query).split(/\s+/).map((term) => term.trim()).filter(Boolean);
|
|
13237
|
+
}
|
|
13238
|
+
function textMatchesTerm(text, term) {
|
|
13239
|
+
if (!term) return false;
|
|
13240
|
+
if (/^[a-z0-9]{1,3}$/.test(term)) {
|
|
13241
|
+
return text.split(/[^a-z0-9]+/).filter(Boolean).includes(term);
|
|
13242
|
+
}
|
|
13243
|
+
return text.includes(term);
|
|
13244
|
+
}
|
|
13245
|
+
function dedupeSimilarScoredSessions(scoredSessions) {
|
|
13246
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13247
|
+
const deduped = [];
|
|
13248
|
+
for (const scored of scoredSessions) {
|
|
13249
|
+
const { session } = scored;
|
|
13250
|
+
const key = duplicateSessionKey(session);
|
|
13251
|
+
if (key && seen.has(key)) continue;
|
|
13252
|
+
if (key) seen.add(key);
|
|
13253
|
+
deduped.push(scored);
|
|
13254
|
+
}
|
|
13255
|
+
return deduped;
|
|
13256
|
+
}
|
|
13257
|
+
function duplicateSessionKey(session) {
|
|
13258
|
+
const prompt = normalizeSearch(cleanPromptText(session.firstPrompt));
|
|
13259
|
+
if (prompt.length >= 60) return `prompt:${prompt.slice(0, 240)}`;
|
|
13260
|
+
const title = normalizeSearch(cleanPromptText(session.title || ""));
|
|
13261
|
+
if (title.length >= 60) return `title:${title.slice(0, 240)}`;
|
|
13262
|
+
return void 0;
|
|
13263
|
+
}
|
|
13264
|
+
function sessionProjectHaystack(session) {
|
|
13265
|
+
return normalizeSearch(
|
|
13266
|
+
[session.project, session.cwd, session.workspacePath].filter(Boolean).join(" ")
|
|
13267
|
+
);
|
|
13268
|
+
}
|
|
13269
|
+
function normalizeSearch(value) {
|
|
13270
|
+
return (value || "").toLowerCase().replace(/\s+/g, " ").trim();
|
|
13271
|
+
}
|
|
13272
|
+
function plural(count, label) {
|
|
13273
|
+
return `${count} ${label}${count === 1 ? "" : "s"}`;
|
|
13274
|
+
}
|
|
13275
|
+
function normalizeLimit(limit) {
|
|
13276
|
+
if (!Number.isFinite(limit) || !limit || limit <= 0) return 10;
|
|
13277
|
+
return Math.min(Math.floor(limit), 100);
|
|
13278
|
+
}
|
|
13279
|
+
function ratio(value, denominator) {
|
|
13280
|
+
if (denominator <= 0) return void 0;
|
|
13281
|
+
return Math.round(value / denominator * 10) / 10;
|
|
13282
|
+
}
|
|
13283
|
+
function median(values) {
|
|
13284
|
+
if (!values?.length) return void 0;
|
|
13285
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
13286
|
+
const mid = Math.floor(sorted.length / 2);
|
|
13287
|
+
if (sorted.length % 2 === 1) return sorted[mid];
|
|
13288
|
+
return Math.round((sorted[mid - 1] + sorted[mid]) / 2);
|
|
13289
|
+
}
|
|
13290
|
+
function formatDuration2(ms) {
|
|
13291
|
+
const minutes = Math.round(ms / 6e4);
|
|
13292
|
+
if (minutes < 60) return `${minutes}m`;
|
|
13293
|
+
const hours = Math.floor(minutes / 60);
|
|
13294
|
+
const remainder = minutes % 60;
|
|
13295
|
+
return remainder ? `${hours}h ${remainder}m` : `${hours}h`;
|
|
13296
|
+
}
|
|
13297
|
+
|
|
11398
13298
|
// src/index.ts
|
|
11399
13299
|
init_utils();
|
|
11400
13300
|
init_version();
|
|
11401
13301
|
async function runGitHubExport(replay, outputDir) {
|
|
11402
|
-
const { join:
|
|
13302
|
+
const { join: join19 } = await import("path");
|
|
11403
13303
|
const { writeFile: writeFile7 } = await import("fs/promises");
|
|
11404
13304
|
const savedGist = await loadSavedGistInfo(outputDir);
|
|
11405
13305
|
const replayUrl = savedGist?.viewerUrl;
|
|
11406
13306
|
const svgSpinner = ora("Generating animated SVG...").start();
|
|
11407
13307
|
const svgContent = generateGitHubSvg(replay, { replayUrl });
|
|
11408
|
-
const svgFilePath =
|
|
13308
|
+
const svgFilePath = join19(outputDir, "session-preview.svg");
|
|
11409
13309
|
await writeFile7(svgFilePath, svgContent, "utf-8");
|
|
11410
13310
|
svgSpinner.succeed(`SVG: ${svgFilePath}`);
|
|
11411
13311
|
let gifGenerated = false;
|
|
11412
13312
|
const gifSpinner = ora("Generating animated GIF...").start();
|
|
11413
13313
|
try {
|
|
11414
13314
|
const gifBuffer = await generateGitHubGif(replay, { replayUrl });
|
|
11415
|
-
const gifFilePath =
|
|
13315
|
+
const gifFilePath = join19(outputDir, "session-preview.gif");
|
|
11416
13316
|
await writeFile7(gifFilePath, gifBuffer);
|
|
11417
13317
|
const gifSizeKB = Math.round(gifBuffer.length / 1024);
|
|
11418
13318
|
gifSpinner.succeed(`GIF: ${gifFilePath} (${gifSizeKB} KB)`);
|
|
@@ -11426,7 +13326,7 @@ async function runGitHubExport(replay, outputDir) {
|
|
|
11426
13326
|
svgPath: "./session-preview.svg",
|
|
11427
13327
|
gifPath: gifGenerated ? "./session-preview.gif" : void 0
|
|
11428
13328
|
});
|
|
11429
|
-
const mdFilePath =
|
|
13329
|
+
const mdFilePath = join19(outputDir, "github-summary.md");
|
|
11430
13330
|
await writeFile7(mdFilePath, markdown, "utf-8");
|
|
11431
13331
|
mdSpinner.succeed(`Markdown: ${mdFilePath}`);
|
|
11432
13332
|
const redactionsSpinner = ora("Generating redaction report...").start();
|
|
@@ -11442,7 +13342,7 @@ async function runGitHubExport(replay, outputDir) {
|
|
|
11442
13342
|
"leftoverFindings: regex matches in the final markdown \u2014 review these before sharing."
|
|
11443
13343
|
]
|
|
11444
13344
|
};
|
|
11445
|
-
const redactionsPath =
|
|
13345
|
+
const redactionsPath = join19(outputDir, "redactions.json");
|
|
11446
13346
|
await writeFile7(redactionsPath, `${JSON.stringify(redactionsReport, null, 2)}
|
|
11447
13347
|
`, "utf-8");
|
|
11448
13348
|
const leftoverNote = leftoverFindings.length > 0 ? chalk2.yellow(`(${leftoverFindings.length} leftover finding(s) \u2014 review)`) : chalk2.dim("(no leftover findings)");
|
|
@@ -11451,12 +13351,16 @@ async function runGitHubExport(replay, outputDir) {
|
|
|
11451
13351
|
markdown,
|
|
11452
13352
|
outputDir,
|
|
11453
13353
|
svgPath: svgFilePath,
|
|
11454
|
-
gifPath: gifGenerated ?
|
|
13354
|
+
gifPath: gifGenerated ? join19(outputDir, "session-preview.gif") : void 0,
|
|
11455
13355
|
mdPath: mdFilePath,
|
|
11456
13356
|
redactionsPath
|
|
11457
13357
|
};
|
|
11458
13358
|
}
|
|
11459
13359
|
var DEV_MENU_ENABLED = process.env.VIBE_REPLAY_DEV_MENU === "1";
|
|
13360
|
+
function getDevViewerOpts() {
|
|
13361
|
+
if (!DEV_MENU_ENABLED) return void 0;
|
|
13362
|
+
return { externalViewerUrl: `http://localhost:${process.env.VIBE_VIEWER_PORT || "5173"}` };
|
|
13363
|
+
}
|
|
11460
13364
|
var SESSION_DISCOVERY_CACHE_KEY = "session-discovery-v3";
|
|
11461
13365
|
function normalizePromptTitle(value) {
|
|
11462
13366
|
return normalizeTitle(cleanPromptText(value || "")) || "";
|
|
@@ -11475,6 +13379,35 @@ function suggestedReplayTitle(replayTitle, replaySlug, sessionInfo) {
|
|
|
11475
13379
|
if (firstPromptTitle) return firstPromptTitle;
|
|
11476
13380
|
return replayCandidate || slug || replaySlug;
|
|
11477
13381
|
}
|
|
13382
|
+
function formatParseWarningSummary(warnings) {
|
|
13383
|
+
if (!warnings?.length) return [];
|
|
13384
|
+
return warnings.map((warning) => {
|
|
13385
|
+
const label = warning.kind === "malformed-json" ? "malformed JSONL line" : warning.kind === "missing-image" ? "image file" : warning.kind === "unreadable-source" ? "unreadable source" : "item";
|
|
13386
|
+
const plural2 = warning.count === 1 ? label : `${label}s`;
|
|
13387
|
+
const location = warning.firstLine ? ` first at line ${warning.firstLine}` : "";
|
|
13388
|
+
const source = warning.source ? ` in ${warning.source}` : "";
|
|
13389
|
+
const sample = warning.sample ? ` Sample: ${warning.sample}` : "";
|
|
13390
|
+
return `${warning.count} ${plural2} skipped${source}${location}.${sample}`;
|
|
13391
|
+
});
|
|
13392
|
+
}
|
|
13393
|
+
function printParseWarnings(warnings) {
|
|
13394
|
+
const lines = formatParseWarningSummary(warnings);
|
|
13395
|
+
if (lines.length === 0) return;
|
|
13396
|
+
const total = warnings?.reduce((sum, warning) => sum + warning.count, 0) ?? 0;
|
|
13397
|
+
process.stderr.write(
|
|
13398
|
+
`${chalk2.yellow(` \u26A0 ${total} parser warning${total === 1 ? "" : "s"} detected`)}
|
|
13399
|
+
`
|
|
13400
|
+
);
|
|
13401
|
+
for (const line of lines.slice(0, 4)) {
|
|
13402
|
+
process.stderr.write(`${chalk2.dim(" - ")}${chalk2.yellow(line)}
|
|
13403
|
+
`);
|
|
13404
|
+
}
|
|
13405
|
+
if (lines.length > 4) {
|
|
13406
|
+
process.stderr.write(chalk2.dim(` ... ${lines.length - 4} more warning group(s)
|
|
13407
|
+
`));
|
|
13408
|
+
}
|
|
13409
|
+
process.stderr.write("\n");
|
|
13410
|
+
}
|
|
11478
13411
|
async function discoverAllSessions() {
|
|
11479
13412
|
const providers2 = getAllProviders();
|
|
11480
13413
|
const allSessions = [];
|
|
@@ -11495,27 +13428,15 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
11495
13428
|
).action(async (opts) => {
|
|
11496
13429
|
console.log(chalk2.bold.cyan("\n vibe-replay") + chalk2.dim(` v${CLI_VERSION}
|
|
11497
13430
|
`));
|
|
11498
|
-
if (process.platform === "win32") {
|
|
11499
|
-
console.log(chalk2.yellow(" \u26A0 Windows is not supported yet.\n"));
|
|
11500
|
-
console.log(chalk2.dim(" We're working on it! Follow progress and updates:"));
|
|
11501
|
-
console.log(
|
|
11502
|
-
chalk2.dim(" \u2192 ") + chalk2.white("https://github.com/tuo-lei/vibe-replay/issues/26")
|
|
11503
|
-
);
|
|
11504
|
-
console.log(chalk2.dim(" \u2192 ") + chalk2.white("https://vibe-replay.com\n"));
|
|
11505
|
-
process.exit(1);
|
|
11506
|
-
}
|
|
11507
13431
|
const { join: pathJoin } = await import("path");
|
|
11508
|
-
const { homedir:
|
|
11509
|
-
const replayBaseDir = pathJoin(
|
|
13432
|
+
const { homedir: homedir17 } = await import("os");
|
|
13433
|
+
const replayBaseDir = pathJoin(homedir17(), ".vibe-replay");
|
|
11510
13434
|
void discoverAllSessions().then(async (sessions) => {
|
|
11511
13435
|
await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, sessions);
|
|
11512
13436
|
}).catch(() => {
|
|
11513
13437
|
});
|
|
11514
13438
|
if (opts.dashboard) {
|
|
11515
|
-
await startDashboard(
|
|
11516
|
-
replayBaseDir,
|
|
11517
|
-
DEV_MENU_ENABLED ? { externalViewerUrl: `http://localhost:${process.env.VIBE_VIEWER_PORT || "5173"}` } : void 0
|
|
11518
|
-
);
|
|
13439
|
+
await startDashboard(replayBaseDir, getDevViewerOpts());
|
|
11519
13440
|
return;
|
|
11520
13441
|
}
|
|
11521
13442
|
if ((opts.open || opts.github) && !opts.session) {
|
|
@@ -11547,21 +13468,18 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
11547
13468
|
]
|
|
11548
13469
|
});
|
|
11549
13470
|
if (topChoice === "dashboard") {
|
|
11550
|
-
await startDashboard(
|
|
11551
|
-
replayBaseDir,
|
|
11552
|
-
DEV_MENU_ENABLED ? { externalViewerUrl: `http://localhost:${process.env.VIBE_VIEWER_PORT || "5173"}` } : void 0
|
|
11553
|
-
);
|
|
13471
|
+
await startDashboard(replayBaseDir, getDevViewerOpts());
|
|
11554
13472
|
return;
|
|
11555
13473
|
}
|
|
11556
13474
|
if (topChoice === "replays") {
|
|
11557
|
-
const { readdir:
|
|
13475
|
+
const { readdir: readdir12, readFile: readFile20 } = await import("fs/promises");
|
|
11558
13476
|
const replayEntries = [];
|
|
11559
13477
|
try {
|
|
11560
|
-
const entries = await
|
|
13478
|
+
const entries = await readdir12(replayBaseDir);
|
|
11561
13479
|
for (const slug2 of entries) {
|
|
11562
13480
|
if (slug2.startsWith(".") || slug2 === "cache") continue;
|
|
11563
13481
|
try {
|
|
11564
|
-
const raw = await
|
|
13482
|
+
const raw = await readFile20(pathJoin(replayBaseDir, slug2, "replay.json"), "utf-8");
|
|
11565
13483
|
const replay2 = JSON.parse(raw);
|
|
11566
13484
|
const title = replay2.meta?.title || slug2;
|
|
11567
13485
|
const provider2 = replay2.meta?.provider || "";
|
|
@@ -11574,7 +13492,7 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
11574
13492
|
minute: "2-digit",
|
|
11575
13493
|
hour12: false
|
|
11576
13494
|
}) : "";
|
|
11577
|
-
const providerBadge = provider2 === "claude-code" ? chalk2.hex("#D97706")("claude") : provider2 === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : provider2 === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : provider2 === "cursor" ? chalk2.hex("#0096FF")("cursor") : chalk2.yellow(provider2);
|
|
13495
|
+
const providerBadge = provider2 === "claude-code" ? chalk2.hex("#D97706")("claude") : provider2 === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : provider2 === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : provider2 === "cursor" ? chalk2.hex("#0096FF")("cursor") : provider2 === "codex" ? chalk2.hex("#B392F0")("codex") : chalk2.yellow(provider2);
|
|
11578
13496
|
replayEntries.push({
|
|
11579
13497
|
name: `${providerBadge} ${chalk2.dim(`[${time}]`)} ${chalk2.white(title)} ${chalk2.dim(`(${scenes} scenes)`)}`,
|
|
11580
13498
|
value: slug2,
|
|
@@ -11695,7 +13613,8 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
11695
13613
|
}
|
|
11696
13614
|
const info = displayedSessions.find((s) => s.filePath === chosen);
|
|
11697
13615
|
sessionInfo = info;
|
|
11698
|
-
|
|
13616
|
+
const sourcePaths = info ? info.filePaths.length > 0 ? info.filePaths : [info.filePath] : [chosen];
|
|
13617
|
+
sessionPaths = [...sourcePaths, ...info?.toolPaths || []];
|
|
11699
13618
|
providerName = info?.provider || opts.provider;
|
|
11700
13619
|
}
|
|
11701
13620
|
const provider = getProvider(providerName);
|
|
@@ -11746,10 +13665,10 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
11746
13665
|
replay.meta.title = normalizedUserTitle;
|
|
11747
13666
|
}
|
|
11748
13667
|
}
|
|
11749
|
-
const { join:
|
|
13668
|
+
const { join: join19 } = await import("path");
|
|
11750
13669
|
const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
|
|
11751
13670
|
const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
11752
|
-
const outputDir =
|
|
13671
|
+
const outputDir = join19(home, ".vibe-replay", slug);
|
|
11753
13672
|
const genSpinner = ora("Generating replay...").start();
|
|
11754
13673
|
const outputPath = await generateOutput(replay, outputDir);
|
|
11755
13674
|
const { stat: fsStat } = await import("fs/promises");
|
|
@@ -11787,6 +13706,7 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
11787
13706
|
console.log(chalk2.dim(" Continuing \u2014 user confirmed findings are safe.\n"));
|
|
11788
13707
|
}
|
|
11789
13708
|
}
|
|
13709
|
+
printParseWarnings(replay.meta.parseWarnings);
|
|
11790
13710
|
if (opts.open) {
|
|
11791
13711
|
await publishLocal(outputPath);
|
|
11792
13712
|
console.log();
|
|
@@ -11840,9 +13760,9 @@ ${chalk2.bold.green(" Done!")}
|
|
|
11840
13760
|
if (target === "local") {
|
|
11841
13761
|
await publishLocal(outputPath);
|
|
11842
13762
|
} else if (target === "editor") {
|
|
11843
|
-
await startServer(
|
|
13763
|
+
await startServer(join19(home, ".vibe-replay"), {
|
|
11844
13764
|
openSlug: slug,
|
|
11845
|
-
|
|
13765
|
+
...getDevViewerOpts()
|
|
11846
13766
|
});
|
|
11847
13767
|
return;
|
|
11848
13768
|
} else if (target === "cloud") {
|
|
@@ -11944,8 +13864,36 @@ ${chalk2.bold.green(" Done!")}
|
|
|
11944
13864
|
console.log(chalk2.dim(" File: ") + chalk2.white(outputPath));
|
|
11945
13865
|
console.log();
|
|
11946
13866
|
});
|
|
13867
|
+
program.command("sessions").description("Search local AI coding sessions (agent-friendly)").option("-q, --query <text>", "Search title, prompt, project, branch, model, or slug").option("--project <text>", "Filter by project path substring").option("-p, --provider <name>", "Filter by provider (claude-code, cursor, codex, ...)").option("-l, --limit <number>", "Maximum sessions to return", (value) => Number(value), 10).option("--scan", "Run richer per-session scan for efficiency metrics").option("--any", "Match any query term instead of requiring all terms").option("--brief", "Include scan-backed session briefs and match evidence").option("--dedupe", "Collapse near-duplicate sessions with the same long prompt/title").option("--json", "Print machine-readable JSON").action(async (opts) => {
|
|
13868
|
+
const discoverSpinner = opts.json ? void 0 : ora("Discovering local sessions...").start();
|
|
13869
|
+
try {
|
|
13870
|
+
const sessions = mergeSameSessions2(await discoverAllSessions());
|
|
13871
|
+
discoverSpinner?.succeed(`Found ${sessions.length} sessions`);
|
|
13872
|
+
const scanSpinner = opts.scan && !opts.json ? ora("Scanning matching sessions...").start() : void 0;
|
|
13873
|
+
const matches = await queryLocalSessions(sessions, opts);
|
|
13874
|
+
scanSpinner?.succeed(`Prepared ${matches.length} session result(s)`);
|
|
13875
|
+
if (opts.json) {
|
|
13876
|
+
console.log(JSON.stringify({ sessions: matches }, null, 2));
|
|
13877
|
+
} else {
|
|
13878
|
+
console.log();
|
|
13879
|
+
console.log(formatSessionQueryText(matches));
|
|
13880
|
+
console.log();
|
|
13881
|
+
}
|
|
13882
|
+
} catch (err) {
|
|
13883
|
+
discoverSpinner?.fail("Session search failed");
|
|
13884
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13885
|
+
if (opts.json) {
|
|
13886
|
+
console.error(message);
|
|
13887
|
+
} else {
|
|
13888
|
+
console.error(chalk2.red(`
|
|
13889
|
+
\u2717 ${message}
|
|
13890
|
+
`));
|
|
13891
|
+
}
|
|
13892
|
+
process.exit(1);
|
|
13893
|
+
}
|
|
13894
|
+
});
|
|
11947
13895
|
var authCmd = program.command("auth").description("Manage authentication");
|
|
11948
|
-
authCmd.command("login").description("Log in to vibe-replay with GitHub").option("--api-url <url>", "API base URL",
|
|
13896
|
+
authCmd.command("login").description("Log in to vibe-replay with GitHub").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async (opts) => {
|
|
11949
13897
|
const crypto = await import("crypto");
|
|
11950
13898
|
const apiUrl = opts.apiUrl.replace(/\/$/, "");
|
|
11951
13899
|
const parsed = new URL(apiUrl);
|
|
@@ -12037,7 +13985,7 @@ authCmd.command("login").description("Log in to vibe-replay with GitHub").option
|
|
|
12037
13985
|
5 * 60 * 1e3
|
|
12038
13986
|
);
|
|
12039
13987
|
});
|
|
12040
|
-
authCmd.command("logout").description("Log out of vibe-replay").option("--api-url <url>", "API base URL",
|
|
13988
|
+
authCmd.command("logout").description("Log out of vibe-replay").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async (opts) => {
|
|
12041
13989
|
const apiUrl = opts.apiUrl.replace(/\/$/, "");
|
|
12042
13990
|
const auth = loadAuthToken(apiUrl);
|
|
12043
13991
|
if (!auth) {
|
|
@@ -12047,7 +13995,7 @@ authCmd.command("logout").description("Log out of vibe-replay").option("--api-ur
|
|
|
12047
13995
|
removeAuthTokenSync(apiUrl);
|
|
12048
13996
|
console.log(chalk2.bold.green("\n \u2713 Logged out successfully\n"));
|
|
12049
13997
|
});
|
|
12050
|
-
authCmd.command("status").description("Show current authentication status").option("--api-url <url>", "API base URL",
|
|
13998
|
+
authCmd.command("status").description("Show current authentication status").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async (opts) => {
|
|
12051
13999
|
const apiUrl = opts.apiUrl.replace(/\/$/, "");
|
|
12052
14000
|
const auth = loadAuthToken(apiUrl);
|
|
12053
14001
|
if (!auth) {
|
|
@@ -12071,9 +14019,9 @@ authCmd.command("status").description("Show current authentication status").opti
|
|
|
12071
14019
|
var VISIBILITIES = ["public", "unlisted", "private"];
|
|
12072
14020
|
program.command("share").description("Share an existing replay via cloud (unlisted link, expires in 7 days)").argument("[path]", "Path to replay directory or replay.json").option("--visibility <type>", `Visibility: ${VISIBILITIES.join(", ")}`, "unlisted").option("--api-url <url>", `API base URL (default: ${DEFAULT_API_URL})`).action(async (pathArg, opts) => {
|
|
12073
14021
|
const { existsSync: existsSync2, statSync } = await import("fs");
|
|
12074
|
-
const { readFile:
|
|
12075
|
-
const { join:
|
|
12076
|
-
const { homedir:
|
|
14022
|
+
const { readFile: readFile20, readdir: readdir12 } = await import("fs/promises");
|
|
14023
|
+
const { join: join19, dirname: dirname6, resolve: resolve3 } = await import("path");
|
|
14024
|
+
const { homedir: homedir17 } = await import("os");
|
|
12077
14025
|
if (opts.apiUrl) {
|
|
12078
14026
|
process.env.VIBE_REPLAY_API_URL = opts.apiUrl.replace(/\/$/, "");
|
|
12079
14027
|
}
|
|
@@ -12102,14 +14050,14 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
12102
14050
|
const s = statSync(abs);
|
|
12103
14051
|
outputDir = s.isDirectory() ? abs : dirname6(abs);
|
|
12104
14052
|
} else {
|
|
12105
|
-
const replayBaseDir =
|
|
12106
|
-
const entries = await
|
|
14053
|
+
const replayBaseDir = join19(homedir17(), ".vibe-replay");
|
|
14054
|
+
const entries = await readdir12(replayBaseDir).catch(() => []);
|
|
12107
14055
|
const replays = [];
|
|
12108
14056
|
for (const slug of entries) {
|
|
12109
14057
|
if (slug.startsWith(".") || slug === "cache") continue;
|
|
12110
|
-
const jsonPath2 =
|
|
14058
|
+
const jsonPath2 = join19(replayBaseDir, slug, "replay.json");
|
|
12111
14059
|
try {
|
|
12112
|
-
const raw = await
|
|
14060
|
+
const raw = await readFile20(jsonPath2, "utf-8");
|
|
12113
14061
|
const replay = JSON.parse(raw);
|
|
12114
14062
|
const title = replay.meta?.title || slug;
|
|
12115
14063
|
const startTime = replay.meta?.startTime || "";
|
|
@@ -12122,7 +14070,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
12122
14070
|
}) : "";
|
|
12123
14071
|
replays.push({
|
|
12124
14072
|
name: time ? `${chalk2.dim(`[${time}]`)} ${chalk2.white(title)}` : chalk2.white(title),
|
|
12125
|
-
value:
|
|
14073
|
+
value: join19(replayBaseDir, slug),
|
|
12126
14074
|
time: startTime
|
|
12127
14075
|
});
|
|
12128
14076
|
} catch {
|
|
@@ -12138,7 +14086,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
12138
14086
|
choices: replays
|
|
12139
14087
|
});
|
|
12140
14088
|
}
|
|
12141
|
-
const jsonPath =
|
|
14089
|
+
const jsonPath = join19(outputDir, "replay.json");
|
|
12142
14090
|
if (!existsSync2(jsonPath)) {
|
|
12143
14091
|
console.error(chalk2.red(`
|
|
12144
14092
|
\u2717 No replay.json found in ${outputDir}
|
|
@@ -12162,13 +14110,9 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
12162
14110
|
});
|
|
12163
14111
|
var LIVE_STALENESS_WARNING_MS = 5 * 6e4;
|
|
12164
14112
|
program.command("live").description("Watch a running AI coding session live in the browser").option("-p, --provider <name>", "Provider name (default: auto-detect)").option("-s, --session <sessionId>", "Specific session ID to watch").action(async (opts) => {
|
|
12165
|
-
if (process.platform === "win32") {
|
|
12166
|
-
console.log(chalk2.yellow("\n \u26A0 Windows is not supported yet.\n"));
|
|
12167
|
-
process.exit(1);
|
|
12168
|
-
}
|
|
12169
14113
|
const { join: pathJoin } = await import("path");
|
|
12170
|
-
const { homedir:
|
|
12171
|
-
const replayBaseDir = pathJoin(
|
|
14114
|
+
const { homedir: homedir17 } = await import("os");
|
|
14115
|
+
const replayBaseDir = pathJoin(homedir17(), ".vibe-replay");
|
|
12172
14116
|
console.log(chalk2.bold.cyan("\n vibe-replay live") + chalk2.dim(` v${CLI_VERSION}
|
|
12173
14117
|
`));
|
|
12174
14118
|
let target = null;
|
|
@@ -12227,7 +14171,7 @@ program.command("live").description("Watch a running AI coding session live in t
|
|
|
12227
14171
|
openLive: { provider: target.provider, sessionId: target.sessionId }
|
|
12228
14172
|
});
|
|
12229
14173
|
});
|
|
12230
|
-
program.command("login", { hidden: true }).description("Log in to vibe-replay (alias for auth login)").option("--api-url <url>", "API base URL",
|
|
14174
|
+
program.command("login", { hidden: true }).description("Log in to vibe-replay (alias for auth login)").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async () => {
|
|
12231
14175
|
await authCmd.commands.find((c) => c.name() === "login")?.parseAsync(["login", ...process.argv.slice(3)], { from: "user" });
|
|
12232
14176
|
});
|
|
12233
14177
|
program.parse();
|
|
@@ -12256,7 +14200,7 @@ function formatSessionChoices(sessions, cleanupPeriodDays) {
|
|
|
12256
14200
|
});
|
|
12257
14201
|
const sizeKB = Math.round(s.fileSize / 1024);
|
|
12258
14202
|
const prompt = s.firstPrompt.replace(/\n/g, " ").slice(0, 50);
|
|
12259
|
-
const providerBadge = s.provider === "claude-code" ? chalk2.hex("#D97706")("claude") : s.provider === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : s.provider === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : s.provider === "cursor" ? chalk2.hex("#0096FF")("cursor") : chalk2.yellow(s.provider);
|
|
14203
|
+
const providerBadge = s.provider === "claude-code" ? chalk2.hex("#D97706")("claude") : s.provider === "claude-desktop" ? chalk2.hex("#C084FC")("desktop") : s.provider === "claude-cowork" ? chalk2.hex("#F472B6")("cowork") : s.provider === "cursor" ? chalk2.hex("#0096FF")("cursor") : s.provider === "codex" ? chalk2.hex("#B392F0")("codex") : chalk2.yellow(s.provider);
|
|
12260
14204
|
const titleStr = s.title ? chalk2.white(` "${s.title}"`) : "";
|
|
12261
14205
|
const fileCount = s.filePaths.length > 1 ? chalk2.dim(` [${s.filePaths.length} parts]`) : "";
|
|
12262
14206
|
const sqliteBadge = s.hasSqlite ? chalk2.green(" db") : "";
|