vibe-replay 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/assets/viewer.html +37 -37
- package/dist/index.js +2085 -1154
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -75,13 +75,13 @@ var init_utils = __esm({
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
// src/overlays.ts
|
|
78
|
-
import { readFile as
|
|
79
|
-
import { join as
|
|
78
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
79
|
+
import { join as join16, resolve } from "path";
|
|
80
80
|
async function loadOverlays(baseDir, slug) {
|
|
81
|
-
const dirs = [
|
|
81
|
+
const dirs = [join16(baseDir, slug), resolve("./vibe-replay", slug)];
|
|
82
82
|
for (const dir of dirs) {
|
|
83
83
|
try {
|
|
84
|
-
const raw = await
|
|
84
|
+
const raw = await readFile16(join16(dir, "overlays.json"), "utf-8");
|
|
85
85
|
const parsed = JSON.parse(raw);
|
|
86
86
|
if (parsed && typeof parsed === "object" && Array.isArray(parsed.overlays)) {
|
|
87
87
|
return parsed;
|
|
@@ -140,9 +140,9 @@ __export(cloud_exports, {
|
|
|
140
140
|
saveAuthTokenSync: () => saveAuthTokenSync
|
|
141
141
|
});
|
|
142
142
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
143
|
-
import { mkdir as mkdir3, readFile as
|
|
144
|
-
import { homedir as
|
|
145
|
-
import { basename as
|
|
143
|
+
import { mkdir as mkdir3, readFile as readFile17, unlink, writeFile as writeFile3 } from "fs/promises";
|
|
144
|
+
import { homedir as homedir14 } from "os";
|
|
145
|
+
import { basename as basename7, dirname as dirname3, join as join17 } from "path";
|
|
146
146
|
function authOrigin(url) {
|
|
147
147
|
try {
|
|
148
148
|
return new URL(url).origin;
|
|
@@ -243,8 +243,8 @@ async function publishCloud(outputDir, opts) {
|
|
|
243
243
|
if (!auth) {
|
|
244
244
|
throw new Error("Not logged in. Run `vibe-replay auth login` first.");
|
|
245
245
|
}
|
|
246
|
-
const jsonPath =
|
|
247
|
-
const content = await
|
|
246
|
+
const jsonPath = join17(outputDir, "replay.json");
|
|
247
|
+
const content = await readFile17(jsonPath, "utf-8");
|
|
248
248
|
const replay = JSON.parse(content);
|
|
249
249
|
const sizeBytes = Buffer.byteLength(content, "utf-8");
|
|
250
250
|
const MAX_SIZE = 10 * 1024 * 1024;
|
|
@@ -285,14 +285,14 @@ async function publishCloud(outputDir, opts) {
|
|
|
285
285
|
return data;
|
|
286
286
|
}
|
|
287
287
|
async function publishCloudWithOverlays(outputDir, opts) {
|
|
288
|
-
const slug =
|
|
288
|
+
const slug = basename7(outputDir);
|
|
289
289
|
const baseDir = dirname3(outputDir);
|
|
290
290
|
const overlays = await loadOverlays(baseDir, slug);
|
|
291
291
|
if (overlays.overlays.length === 0) {
|
|
292
292
|
return publishCloud(outputDir, opts);
|
|
293
293
|
}
|
|
294
|
-
const replayPath =
|
|
295
|
-
const originalContent = await
|
|
294
|
+
const replayPath = join17(outputDir, "replay.json");
|
|
295
|
+
const originalContent = await readFile17(replayPath, "utf-8");
|
|
296
296
|
const session = JSON.parse(originalContent);
|
|
297
297
|
const merged = sessionWithEffectiveContent(session, overlays);
|
|
298
298
|
await writeFile3(replayPath, JSON.stringify(merged), "utf-8");
|
|
@@ -304,7 +304,7 @@ async function publishCloudWithOverlays(outputDir, opts) {
|
|
|
304
304
|
}
|
|
305
305
|
async function loadSavedCloudInfo(outputDir) {
|
|
306
306
|
try {
|
|
307
|
-
const raw = await
|
|
307
|
+
const raw = await readFile17(join17(outputDir, CLOUD_META_FILE), "utf-8");
|
|
308
308
|
const parsed = JSON.parse(raw);
|
|
309
309
|
if (!parsed?.id || !parsed?.url) return void 0;
|
|
310
310
|
return parsed;
|
|
@@ -313,7 +313,7 @@ async function loadSavedCloudInfo(outputDir) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
async function saveCloudInfo(outputDir, info) {
|
|
316
|
-
await writeFile3(
|
|
316
|
+
await writeFile3(join17(outputDir, CLOUD_META_FILE), JSON.stringify(info, null, 2), "utf-8");
|
|
317
317
|
}
|
|
318
318
|
var CLOUD_META_FILE, DEFAULT_API_URL, AUTH_DIR, AUTH_FILE;
|
|
319
319
|
var init_cloud = __esm({
|
|
@@ -322,8 +322,8 @@ var init_cloud = __esm({
|
|
|
322
322
|
init_overlays();
|
|
323
323
|
CLOUD_META_FILE = ".vibe-replay-cloud.json";
|
|
324
324
|
DEFAULT_API_URL = "https://vibe-replay.com";
|
|
325
|
-
AUTH_DIR =
|
|
326
|
-
AUTH_FILE =
|
|
325
|
+
AUTH_DIR = join17(homedir14(), ".config", "vibe-replay");
|
|
326
|
+
AUTH_FILE = join17(AUTH_DIR, "auth.json");
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
329
|
|
|
@@ -338,7 +338,7 @@ var init_src = __esm({
|
|
|
338
338
|
|
|
339
339
|
// src/machine-id.ts
|
|
340
340
|
import { execSync } from "child_process";
|
|
341
|
-
import { createHash as
|
|
341
|
+
import { createHash as createHash4 } from "crypto";
|
|
342
342
|
import { readFileSync as readFileSync3 } from "fs";
|
|
343
343
|
import { hostname, platform } from "os";
|
|
344
344
|
function getRawMachineId() {
|
|
@@ -379,7 +379,7 @@ function getRawMachineId() {
|
|
|
379
379
|
function getMachineId() {
|
|
380
380
|
if (cachedId) return cachedId;
|
|
381
381
|
const raw = getRawMachineId();
|
|
382
|
-
cachedId =
|
|
382
|
+
cachedId = createHash4("sha256").update(raw).digest("hex").slice(0, 32);
|
|
383
383
|
return cachedId;
|
|
384
384
|
}
|
|
385
385
|
function getMachineName() {
|
|
@@ -406,12 +406,12 @@ __export(insights_exports, {
|
|
|
406
406
|
scanResultToInsight: () => scanResultToInsight,
|
|
407
407
|
writeInsightsStore: () => writeInsightsStore
|
|
408
408
|
});
|
|
409
|
-
import { mkdir as mkdir4, readFile as
|
|
410
|
-
import { homedir as
|
|
411
|
-
import { dirname as dirname4, join as
|
|
409
|
+
import { mkdir as mkdir4, readFile as readFile19, rename, writeFile as writeFile5 } from "fs/promises";
|
|
410
|
+
import { homedir as homedir15 } from "os";
|
|
411
|
+
import { dirname as dirname4, join as join19 } from "path";
|
|
412
412
|
async function readInsightsStore() {
|
|
413
413
|
try {
|
|
414
|
-
const raw = await
|
|
414
|
+
const raw = await readFile19(STORE_PATH, "utf-8");
|
|
415
415
|
const parsed = JSON.parse(raw);
|
|
416
416
|
return migrateInsightsStore(parsed);
|
|
417
417
|
} catch {
|
|
@@ -607,8 +607,8 @@ var init_insights = __esm({
|
|
|
607
607
|
init_machine_id();
|
|
608
608
|
init_utils();
|
|
609
609
|
init_version();
|
|
610
|
-
INSIGHTS_DIR =
|
|
611
|
-
STORE_PATH =
|
|
610
|
+
INSIGHTS_DIR = join19(homedir15(), ".vibe-replay", "insights");
|
|
611
|
+
STORE_PATH = join19(INSIGHTS_DIR, "store.json");
|
|
612
612
|
}
|
|
613
613
|
});
|
|
614
614
|
|
|
@@ -2063,11 +2063,13 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2063
2063
|
}
|
|
2064
2064
|
if (!obj.message) continue;
|
|
2065
2065
|
const { role, content: msgContent, id: msgId } = obj.message;
|
|
2066
|
-
if (obj.isApiErrorMessage && obj.timestamp) {
|
|
2066
|
+
if ((obj.isApiErrorMessage || obj.apiErrorStatus !== void 0) && obj.timestamp) {
|
|
2067
2067
|
const text = extractMessageText(msgContent);
|
|
2068
|
-
const
|
|
2068
|
+
const statusCode = apiErrorStatusCode(obj.apiErrorStatus);
|
|
2069
|
+
const errorType = errorTypeFromStatus(obj.apiErrorStatus) || inferApiErrorMessageType(text);
|
|
2069
2070
|
apiErrors.push({
|
|
2070
2071
|
timestamp: obj.timestamp,
|
|
2072
|
+
...statusCode ? { statusCode } : {},
|
|
2071
2073
|
...errorType ? { errorType } : {}
|
|
2072
2074
|
});
|
|
2073
2075
|
}
|
|
@@ -2107,7 +2109,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2107
2109
|
continue;
|
|
2108
2110
|
}
|
|
2109
2111
|
if (role === "user" && Array.isArray(msgContent)) {
|
|
2110
|
-
const isToolSearchResponse = !!obj.sourceToolAssistantUUID;
|
|
2112
|
+
const isToolSearchResponse = !!obj.sourceToolAssistantUUID || !!obj.sourceToolUseID || obj.origin === "tool_result";
|
|
2111
2113
|
const textParts = [];
|
|
2112
2114
|
const userImages = [];
|
|
2113
2115
|
for (const block of msgContent) {
|
|
@@ -2146,6 +2148,8 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2146
2148
|
}
|
|
2147
2149
|
if (role === "assistant" && msgId && Array.isArray(msgContent)) {
|
|
2148
2150
|
if (!model && obj.message.model) model = obj.message.model;
|
|
2151
|
+
if (obj.attributionMcpServer) mcpServersUsed.add(obj.attributionMcpServer);
|
|
2152
|
+
if (obj.attributionSkill) skillsUsed.add(obj.attributionSkill);
|
|
2149
2153
|
const usage = obj.message.usage;
|
|
2150
2154
|
if (usage && msgId) {
|
|
2151
2155
|
usageByMsgId.set(msgId, { ...usage, model: obj.message.model });
|
|
@@ -2194,10 +2198,10 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2194
2198
|
const agentId = agentMapping.get(block.id);
|
|
2195
2199
|
const subAgent = agentId ? subAgentData.get(agentId) : void 0;
|
|
2196
2200
|
const resultTs = toolResultTimestamps.get(block.id);
|
|
2197
|
-
let
|
|
2201
|
+
let toolDurationMs2;
|
|
2198
2202
|
if (resultTs && prevToolEndTs) {
|
|
2199
2203
|
const diff = Date.parse(resultTs) - Date.parse(prevToolEndTs);
|
|
2200
|
-
if (diff > 0 && diff < 36e5)
|
|
2204
|
+
if (diff > 0 && diff < 36e5) toolDurationMs2 = diff;
|
|
2201
2205
|
}
|
|
2202
2206
|
if (resultTs) prevToolEndTs = resultTs;
|
|
2203
2207
|
return {
|
|
@@ -2208,7 +2212,7 @@ async function parseClaudeCodeLines(lines, options = {}) {
|
|
|
2208
2212
|
// SubAgentParsed.scenes widens Scene.type to string during parsing;
|
|
2209
2213
|
// the final attached shape matches SubAgent after scanSubAgent post-processing.
|
|
2210
2214
|
...subAgent ? { _subAgent: subAgent } : {},
|
|
2211
|
-
...
|
|
2215
|
+
...toolDurationMs2 ? { _durationMs: toolDurationMs2 } : {}
|
|
2212
2216
|
};
|
|
2213
2217
|
}
|
|
2214
2218
|
return block;
|
|
@@ -2335,6 +2339,22 @@ function inferApiErrorMessageType(text) {
|
|
|
2335
2339
|
if (lower.includes("api error")) return "api_error";
|
|
2336
2340
|
return void 0;
|
|
2337
2341
|
}
|
|
2342
|
+
function apiErrorStatusCode(status) {
|
|
2343
|
+
if (typeof status === "number" && Number.isFinite(status)) return status;
|
|
2344
|
+
if (typeof status !== "string") return void 0;
|
|
2345
|
+
const match = status.match(/\b(\d{3})\b/);
|
|
2346
|
+
return match ? Number(match[1]) : void 0;
|
|
2347
|
+
}
|
|
2348
|
+
function errorTypeFromStatus(status) {
|
|
2349
|
+
if (typeof status !== "string") return void 0;
|
|
2350
|
+
const lower = status.toLowerCase();
|
|
2351
|
+
if (lower.includes("rate")) return "rate_limit_error";
|
|
2352
|
+
if (lower.includes("overload")) return "overloaded_error";
|
|
2353
|
+
if (lower.includes("error")) {
|
|
2354
|
+
return lower.replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").replace(/_\d+$/, "");
|
|
2355
|
+
}
|
|
2356
|
+
return void 0;
|
|
2357
|
+
}
|
|
2338
2358
|
function extractImages(block) {
|
|
2339
2359
|
if (block.type !== "tool_result") return [];
|
|
2340
2360
|
const { content } = block;
|
|
@@ -2433,12 +2453,12 @@ function buildTurnStats(finalTurns, usageByMsgId, turnDurations) {
|
|
|
2433
2453
|
} else if (group.startTimestamp && group.endTimestamp) {
|
|
2434
2454
|
durationMs = estimateActiveDuration([group.startTimestamp, group.endTimestamp]);
|
|
2435
2455
|
}
|
|
2436
|
-
const
|
|
2437
|
-
if (turnModel)
|
|
2438
|
-
if (durationMs !== void 0)
|
|
2439
|
-
if (turnTokens)
|
|
2440
|
-
if (maxContextTokens > 0)
|
|
2441
|
-
stats.push(
|
|
2456
|
+
const stat12 = { turnIndex: i };
|
|
2457
|
+
if (turnModel) stat12.model = turnModel;
|
|
2458
|
+
if (durationMs !== void 0) stat12.durationMs = durationMs;
|
|
2459
|
+
if (turnTokens) stat12.tokenUsage = turnTokens;
|
|
2460
|
+
if (maxContextTokens > 0) stat12.contextTokens = maxContextTokens;
|
|
2461
|
+
stats.push(stat12);
|
|
2442
2462
|
}
|
|
2443
2463
|
return stats;
|
|
2444
2464
|
}
|
|
@@ -3901,31 +3921,31 @@ function buildCodexTurnStats(turns, snapshots, taskDurations, model) {
|
|
|
3901
3921
|
const usable = snapshots.filter((s) => s.last);
|
|
3902
3922
|
return userTurns.map((turn, i) => {
|
|
3903
3923
|
const usage = usable[i]?.last;
|
|
3904
|
-
const
|
|
3905
|
-
if (model)
|
|
3924
|
+
const stat12 = { turnIndex: i };
|
|
3925
|
+
if (model) stat12.model = model;
|
|
3906
3926
|
if (usage) {
|
|
3907
3927
|
const input = usage.input_tokens || 0;
|
|
3908
3928
|
const cached = usage.cached_input_tokens || 0;
|
|
3909
|
-
|
|
3929
|
+
stat12.tokenUsage = {
|
|
3910
3930
|
inputTokens: Math.max(0, input - cached),
|
|
3911
3931
|
outputTokens: usage.output_tokens || 0,
|
|
3912
3932
|
cacheCreationTokens: 0,
|
|
3913
3933
|
cacheReadTokens: cached
|
|
3914
3934
|
};
|
|
3915
|
-
|
|
3935
|
+
stat12.contextTokens = input;
|
|
3916
3936
|
}
|
|
3917
3937
|
const nextUser = userTurns[i + 1]?.timestamp;
|
|
3918
3938
|
const taskDuration = taskDurations[i];
|
|
3919
3939
|
if (typeof taskDuration === "number" && taskDuration > 0) {
|
|
3920
|
-
|
|
3940
|
+
stat12.durationMs = taskDuration;
|
|
3921
3941
|
} else if (turn.timestamp) {
|
|
3922
3942
|
const end = nextUser || usable[i]?.timestamp;
|
|
3923
3943
|
if (end) {
|
|
3924
3944
|
const diff = Date.parse(end) - Date.parse(turn.timestamp);
|
|
3925
|
-
if (diff > 0 && diff < 12 * 60 * 60 * 1e3)
|
|
3945
|
+
if (diff > 0 && diff < 12 * 60 * 60 * 1e3) stat12.durationMs = diff;
|
|
3926
3946
|
}
|
|
3927
3947
|
}
|
|
3928
|
-
return
|
|
3948
|
+
return stat12;
|
|
3929
3949
|
});
|
|
3930
3950
|
}
|
|
3931
3951
|
function pushUnique(items, value) {
|
|
@@ -3970,15 +3990,15 @@ var codexProvider = {
|
|
|
3970
3990
|
// src/providers/cursor/discover.ts
|
|
3971
3991
|
init_utils();
|
|
3972
3992
|
import { readdir as readdir8, readFile as readFile12, stat as stat7 } from "fs/promises";
|
|
3973
|
-
import { homedir as
|
|
3974
|
-
import { basename as basename3, join as
|
|
3993
|
+
import { homedir as homedir11 } from "os";
|
|
3994
|
+
import { basename as basename3, join as join12 } from "path";
|
|
3975
3995
|
|
|
3976
3996
|
// src/providers/cursor/sqlite-reader.ts
|
|
3977
|
-
import { createHash } from "crypto";
|
|
3997
|
+
import { createHash as createHash2 } from "crypto";
|
|
3978
3998
|
import { execFile } from "child_process";
|
|
3979
3999
|
import { readdir as readdir6, readFile as readFile10, stat as stat5 } from "fs/promises";
|
|
3980
|
-
import { homedir as
|
|
3981
|
-
import { basename as basename2, dirname as dirname2, join as
|
|
4000
|
+
import { homedir as homedir9 } from "os";
|
|
4001
|
+
import { basename as basename2, dirname as dirname2, join as join10, posix } from "path";
|
|
3982
4002
|
import { promisify } from "util";
|
|
3983
4003
|
init_utils();
|
|
3984
4004
|
|
|
@@ -4051,16 +4071,11 @@ function looksLikeInternalPlanningBody(text) {
|
|
|
4051
4071
|
);
|
|
4052
4072
|
}
|
|
4053
4073
|
|
|
4054
|
-
// src/providers/cursor/sqlite-
|
|
4074
|
+
// src/providers/cursor/sqlite-io.ts
|
|
4075
|
+
import { createHash } from "crypto";
|
|
4076
|
+
import { homedir as homedir8 } from "os";
|
|
4077
|
+
import { join as join9 } from "path";
|
|
4055
4078
|
var CURSOR_CHATS_DIR = join9(homedir8(), ".cursor", "chats");
|
|
4056
|
-
var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4057
|
-
var MIN_STORE_DB_SIZE = 8192;
|
|
4058
|
-
var MAX_CURSOR_REQUEST_CONTEXT_ROWS = 500;
|
|
4059
|
-
var SQLITE_CLI_MAX_BUFFER = 256 * 1024 * 1024;
|
|
4060
|
-
var SQLITE_CLI_QUERY_TIMEOUT_MS = 12e4;
|
|
4061
|
-
var MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS = 1e4;
|
|
4062
|
-
var GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE = 200;
|
|
4063
|
-
var execFileAsync = promisify(execFile);
|
|
4064
4079
|
function createRetryableInit(factory) {
|
|
4065
4080
|
let promise = null;
|
|
4066
4081
|
return async () => {
|
|
@@ -4073,115 +4088,419 @@ function createRetryableInit(factory) {
|
|
|
4073
4088
|
return promise;
|
|
4074
4089
|
};
|
|
4075
4090
|
}
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
function
|
|
4082
|
-
if (
|
|
4091
|
+
function workspaceHash(absolutePath) {
|
|
4092
|
+
return createHash("md5").update(absolutePath).digest("hex");
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
// src/providers/cursor/tool-mapping.ts
|
|
4096
|
+
function parseJson(raw) {
|
|
4097
|
+
if (typeof raw !== "string") return null;
|
|
4083
4098
|
try {
|
|
4084
|
-
|
|
4099
|
+
return JSON.parse(raw);
|
|
4085
4100
|
} catch {
|
|
4101
|
+
return null;
|
|
4086
4102
|
}
|
|
4087
4103
|
}
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4104
|
+
function mapCursorToolName(name) {
|
|
4105
|
+
const mapping = {
|
|
4106
|
+
Shell: "Bash",
|
|
4107
|
+
shell: "Bash",
|
|
4108
|
+
// Cursor SDK lowercase tool name
|
|
4109
|
+
run_terminal_command_v2: "Bash",
|
|
4110
|
+
run_terminal_cmd: "Bash",
|
|
4111
|
+
Read: "Read",
|
|
4112
|
+
ReadFile: "Read",
|
|
4113
|
+
read: "Read",
|
|
4114
|
+
// Cursor SDK lowercase tool name
|
|
4115
|
+
read_file_v2: "Read",
|
|
4116
|
+
read_file: "Read",
|
|
4117
|
+
read_lints: "ReadLints",
|
|
4118
|
+
Grep: "Grep",
|
|
4119
|
+
ripgrep_raw_search: "Grep",
|
|
4120
|
+
ripgrep: "Grep",
|
|
4121
|
+
rg: "Grep",
|
|
4122
|
+
grep: "Grep",
|
|
4123
|
+
grep_search: "Grep",
|
|
4124
|
+
Glob: "Glob",
|
|
4125
|
+
glob_file_search: "Glob",
|
|
4126
|
+
file_search: "Glob",
|
|
4127
|
+
list_dir: "Glob",
|
|
4128
|
+
list_dir_v2: "Glob",
|
|
4129
|
+
LS: "Glob",
|
|
4130
|
+
StrReplace: "Edit",
|
|
4131
|
+
EditFile: "Edit",
|
|
4132
|
+
edit: "Edit",
|
|
4133
|
+
// Cursor SDK lowercase tool name
|
|
4134
|
+
edit_file_v2: "Edit",
|
|
4135
|
+
edit_file: "Edit",
|
|
4136
|
+
search_replace: "Edit",
|
|
4137
|
+
ApplyPatch: "Edit",
|
|
4138
|
+
apply_patch: "Edit",
|
|
4139
|
+
MultiEdit: "MultiEdit",
|
|
4140
|
+
multi_edit: "MultiEdit",
|
|
4141
|
+
Write: "Write",
|
|
4142
|
+
WriteFile: "Write",
|
|
4143
|
+
write: "Write",
|
|
4144
|
+
Delete: "Delete",
|
|
4145
|
+
delete_file: "Delete",
|
|
4146
|
+
Task: "Agent",
|
|
4147
|
+
task_v2: "Agent",
|
|
4148
|
+
todo_write: "TodoWrite",
|
|
4149
|
+
ask_question: "AskQuestion",
|
|
4150
|
+
semantic_search_full: "SemanticSearch",
|
|
4151
|
+
codebase_search: "SemanticSearch",
|
|
4152
|
+
web_search: "WebSearch",
|
|
4153
|
+
WebFetch: "WebFetch",
|
|
4154
|
+
web_fetch: "WebFetch",
|
|
4155
|
+
create_plan: "Plan"
|
|
4156
|
+
};
|
|
4157
|
+
if (mapping[name]) return mapping[name];
|
|
4158
|
+
if (name.startsWith("mcp-cursor-ide-browser-cursor-ide-browser-browser_")) return "Browser";
|
|
4159
|
+
if (name.startsWith("chrome-devtools-")) return "Browser";
|
|
4160
|
+
return name;
|
|
4093
4161
|
}
|
|
4094
|
-
function
|
|
4095
|
-
const
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4162
|
+
function parseDiffStringSnippet(diffString) {
|
|
4163
|
+
const oldLines = [];
|
|
4164
|
+
const newLines = [];
|
|
4165
|
+
for (const line of diffString.split("\n")) {
|
|
4166
|
+
if (line.startsWith("@@") || line.startsWith("*** ") || line.startsWith("---") || line.startsWith("+++")) {
|
|
4167
|
+
continue;
|
|
4168
|
+
}
|
|
4169
|
+
if (line.startsWith("-")) {
|
|
4170
|
+
oldLines.push(line.slice(1));
|
|
4171
|
+
} else if (line.startsWith("+")) {
|
|
4172
|
+
newLines.push(line.slice(1));
|
|
4173
|
+
} else if (line.startsWith(" ")) {
|
|
4174
|
+
const shared = line.slice(1);
|
|
4175
|
+
oldLines.push(shared);
|
|
4176
|
+
newLines.push(shared);
|
|
4177
|
+
}
|
|
4110
4178
|
}
|
|
4111
|
-
return
|
|
4179
|
+
return { oldText: oldLines.join("\n"), newText: newLines.join("\n") };
|
|
4112
4180
|
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4181
|
+
function inferEditStringsFromResult(resultText) {
|
|
4182
|
+
if (!resultText.trim()) return {};
|
|
4183
|
+
const parsed = parseJson(resultText);
|
|
4184
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
4185
|
+
const out = {};
|
|
4186
|
+
if (typeof parsed.contentsAfterEdit === "string" && parsed.contentsAfterEdit.trim()) {
|
|
4187
|
+
out.new_string = parsed.contentsAfterEdit;
|
|
4117
4188
|
}
|
|
4118
|
-
|
|
4189
|
+
const chunks = parsed.diff && typeof parsed.diff === "object" && Array.isArray(parsed.diff.chunks) ? parsed.diff.chunks : [];
|
|
4190
|
+
const oldParts = [];
|
|
4191
|
+
const newParts = [];
|
|
4192
|
+
for (const chunk of chunks) {
|
|
4193
|
+
if (!chunk || typeof chunk !== "object") continue;
|
|
4194
|
+
if (typeof chunk.diffString !== "string" || !chunk.diffString.trim()) continue;
|
|
4195
|
+
const parsedSnippet = parseDiffStringSnippet(chunk.diffString);
|
|
4196
|
+
if (parsedSnippet.oldText) oldParts.push(parsedSnippet.oldText);
|
|
4197
|
+
if (parsedSnippet.newText) newParts.push(parsedSnippet.newText);
|
|
4198
|
+
}
|
|
4199
|
+
if (oldParts.length > 0) out.old_string = oldParts.join("\n");
|
|
4200
|
+
if (!out.new_string && newParts.length > 0) out.new_string = newParts.join("\n");
|
|
4201
|
+
return out;
|
|
4119
4202
|
}
|
|
4120
|
-
|
|
4121
|
-
const
|
|
4203
|
+
function parseApplyPatchArgs(rawPatch) {
|
|
4204
|
+
const fileMatch = rawPatch.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/m);
|
|
4205
|
+
const oldLines = [];
|
|
4206
|
+
const newLines = [];
|
|
4207
|
+
for (const line of rawPatch.split("\n")) {
|
|
4208
|
+
if (line.startsWith("*** ") || line.startsWith("@@") || line.startsWith("---") || line.startsWith("+++")) {
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
4211
|
+
if (line.startsWith("-")) {
|
|
4212
|
+
oldLines.push(line.slice(1));
|
|
4213
|
+
} else if (line.startsWith("+")) {
|
|
4214
|
+
newLines.push(line.slice(1));
|
|
4215
|
+
} else if (line.startsWith(" ")) {
|
|
4216
|
+
const shared = line.slice(1);
|
|
4217
|
+
oldLines.push(shared);
|
|
4218
|
+
newLines.push(shared);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4122
4221
|
return {
|
|
4123
|
-
|
|
4124
|
-
|
|
4222
|
+
...fileMatch?.[1] ? { file_path: fileMatch[1].trim() } : {},
|
|
4223
|
+
old_string: oldLines.join("\n"),
|
|
4224
|
+
new_string: newLines.join("\n"),
|
|
4225
|
+
patch: rawPatch
|
|
4125
4226
|
};
|
|
4126
4227
|
}
|
|
4127
|
-
function
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
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)}`;
|
|
4145
|
-
}
|
|
4146
|
-
function projectedCursorBubbleSelectSql() {
|
|
4147
|
-
const value = "value";
|
|
4148
|
-
const toolResult = `json_extract(${value}, '$.toolFormerData.result')`;
|
|
4149
|
-
const truncatedToolResult = [
|
|
4150
|
-
"CASE",
|
|
4151
|
-
`WHEN json_type(${value}, '$.toolFormerData.result') = 'text'`,
|
|
4152
|
-
`THEN substr(${toolResult}, 1, ${MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS})`,
|
|
4153
|
-
`ELSE ${toolResult}`,
|
|
4154
|
-
"END"
|
|
4155
|
-
].join(" ");
|
|
4156
|
-
return [
|
|
4157
|
-
"key",
|
|
4158
|
-
`json_extract(${value}, '$.type') AS type`,
|
|
4159
|
-
`json_extract(${value}, '$.text') AS text`,
|
|
4160
|
-
`json_extract(${value}, '$.thinking') AS thinking`,
|
|
4161
|
-
`json_extract(${value}, '$.createdAt') AS createdAt`,
|
|
4162
|
-
`json_extract(${value}, '$.lastUpdatedAt') AS lastUpdatedAt`,
|
|
4163
|
-
`json_extract(${value}, '$.timingInfo') AS timingInfo`,
|
|
4164
|
-
`json_extract(${value}, '$.thinkingDurationMs') AS thinkingDurationMs`,
|
|
4165
|
-
`json_extract(${value}, '$.tokenCount') AS tokenCount`,
|
|
4166
|
-
`json_extract(${value}, '$.modelInfo') AS modelInfo`,
|
|
4167
|
-
`json_extract(${value}, '$.modelName') AS modelName`,
|
|
4168
|
-
`json_extract(${value}, '$.modelConfig') AS modelConfig`,
|
|
4169
|
-
`json_extract(${value}, '$.toolFormerData.name') AS toolName`,
|
|
4170
|
-
`json_extract(${value}, '$.toolFormerData.params') AS toolParams`,
|
|
4171
|
-
`${truncatedToolResult} AS toolResult`,
|
|
4172
|
-
`length(${toolResult}) AS toolResultLength`,
|
|
4173
|
-
`json_extract(${value}, '$.toolFormerData.toolCallId') AS toolCallId`,
|
|
4174
|
-
`json_extract(${value}, '$.pullRequests') AS pullRequests`,
|
|
4175
|
-
`json_extract(${value}, '$.errorDetails') AS errorDetails`,
|
|
4176
|
-
`json_extract(${value}, '$.retryAttempt') AS retryAttempt`,
|
|
4177
|
-
`json_extract(${value}, '$.relevantFiles') AS relevantFiles`,
|
|
4178
|
-
`json_extract(${value}, '$.recentlyViewedFiles') AS recentlyViewedFiles`
|
|
4179
|
-
].join(", ");
|
|
4228
|
+
function mapEditLikeArgs(argsObj, resultText) {
|
|
4229
|
+
const mapped = {
|
|
4230
|
+
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? "",
|
|
4231
|
+
old_string: argsObj.old_string ?? argsObj.oldStr ?? "",
|
|
4232
|
+
new_string: argsObj.new_string ?? argsObj.newStr ?? argsObj.streamingContent ?? argsObj.content ?? ""
|
|
4233
|
+
};
|
|
4234
|
+
if (!mapped.old_string || !mapped.new_string) {
|
|
4235
|
+
const inferred = inferEditStringsFromResult(resultText);
|
|
4236
|
+
if (!mapped.old_string && inferred.old_string) mapped.old_string = inferred.old_string;
|
|
4237
|
+
if (!mapped.new_string && inferred.new_string) mapped.new_string = inferred.new_string;
|
|
4238
|
+
}
|
|
4239
|
+
return mapped;
|
|
4180
4240
|
}
|
|
4181
|
-
function
|
|
4182
|
-
const
|
|
4183
|
-
|
|
4184
|
-
|
|
4241
|
+
function mapToolArgs(toolName, args, resultText = "") {
|
|
4242
|
+
const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : null;
|
|
4243
|
+
if (toolName === "ApplyPatch" || toolName === "apply_patch") {
|
|
4244
|
+
const rawPatch = typeof args === "string" ? args : typeof argsObj?.patch === "string" ? argsObj.patch : typeof argsObj?.input === "string" ? argsObj.input : typeof argsObj?.content === "string" ? argsObj.content : typeof argsObj?.contents === "string" ? argsObj.contents : "";
|
|
4245
|
+
if (rawPatch) return parseApplyPatchArgs(rawPatch);
|
|
4246
|
+
if (argsObj && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
4247
|
+
return mapEditLikeArgs(argsObj, resultText);
|
|
4248
|
+
}
|
|
4249
|
+
return argsObj || {};
|
|
4250
|
+
}
|
|
4251
|
+
if (typeof args === "string") {
|
|
4252
|
+
return { raw: args };
|
|
4253
|
+
}
|
|
4254
|
+
if (!argsObj) return {};
|
|
4255
|
+
if (toolName === "Shell" && argsObj.command) {
|
|
4256
|
+
return {
|
|
4257
|
+
command: argsObj.command,
|
|
4258
|
+
...argsObj.description ? { description: argsObj.description } : {}
|
|
4259
|
+
};
|
|
4260
|
+
}
|
|
4261
|
+
if (toolName === "StrReplace" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
4262
|
+
return mapEditLikeArgs(argsObj, resultText);
|
|
4263
|
+
}
|
|
4264
|
+
if (toolName === "EditFile" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
4265
|
+
return mapEditLikeArgs(argsObj, resultText);
|
|
4266
|
+
}
|
|
4267
|
+
if (toolName === "Edit" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
4268
|
+
return mapEditLikeArgs(argsObj, resultText);
|
|
4269
|
+
}
|
|
4270
|
+
if ((toolName === "MultiEdit" || toolName === "multi_edit") && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
4271
|
+
return {
|
|
4272
|
+
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath,
|
|
4273
|
+
edits: Array.isArray(argsObj.edits) ? argsObj.edits : []
|
|
4274
|
+
};
|
|
4275
|
+
}
|
|
4276
|
+
if ((toolName === "Write" || toolName === "WriteFile") && (argsObj.path || argsObj.relativeWorkspacePath || argsObj.targetFile || argsObj.file_path)) {
|
|
4277
|
+
const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
|
|
4278
|
+
return {
|
|
4279
|
+
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile,
|
|
4280
|
+
content: argsObj.contents ?? argsObj.content ?? codeValue
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
if ((toolName === "Read" || toolName === "ReadFile") && argsObj.path) {
|
|
4284
|
+
return { file_path: argsObj.path };
|
|
4285
|
+
}
|
|
4286
|
+
if (toolName === "run_terminal_command_v2" && argsObj.command) {
|
|
4287
|
+
return {
|
|
4288
|
+
command: argsObj.command,
|
|
4289
|
+
...argsObj.commandDescription ? { description: argsObj.commandDescription } : {},
|
|
4290
|
+
...argsObj.cwd ? { cwd: argsObj.cwd } : {}
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
if (toolName === "run_terminal_cmd" && argsObj.command) {
|
|
4294
|
+
return {
|
|
4295
|
+
command: argsObj.command,
|
|
4296
|
+
...argsObj.cwd ? { cwd: argsObj.cwd } : {},
|
|
4297
|
+
...argsObj.requireUserApproval !== void 0 ? { requireUserApproval: Boolean(argsObj.requireUserApproval) } : {}
|
|
4298
|
+
};
|
|
4299
|
+
}
|
|
4300
|
+
if (toolName === "read_file_v2" && argsObj.targetFile) {
|
|
4301
|
+
return { file_path: argsObj.targetFile };
|
|
4302
|
+
}
|
|
4303
|
+
if (toolName === "read_file" && argsObj.targetFile) {
|
|
4304
|
+
return { file_path: argsObj.targetFile };
|
|
4305
|
+
}
|
|
4306
|
+
if (toolName === "edit_file_v2" && argsObj.relativeWorkspacePath) {
|
|
4307
|
+
return {
|
|
4308
|
+
file_path: argsObj.relativeWorkspacePath,
|
|
4309
|
+
new_string: argsObj.streamingContent ?? ""
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
if (toolName === "edit_file" || toolName === "search_replace") {
|
|
4313
|
+
return mapEditLikeArgs(argsObj, resultText);
|
|
4314
|
+
}
|
|
4315
|
+
if (toolName === "write" && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.targetFile)) {
|
|
4316
|
+
const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
|
|
4317
|
+
return {
|
|
4318
|
+
file_path: argsObj.relativeWorkspacePath ?? argsObj.path ?? argsObj.targetFile,
|
|
4319
|
+
content: argsObj.content ?? argsObj.contents ?? codeValue
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
if ((toolName === "Delete" || toolName === "delete_file") && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.file_path || argsObj.targetFile)) {
|
|
4323
|
+
return {
|
|
4324
|
+
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
if (toolName === "list_dir" || toolName === "list_dir_v2" || toolName === "LS") {
|
|
4328
|
+
return {
|
|
4329
|
+
path: argsObj.targetDirectory ?? argsObj.target_directory ?? ""
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
4332
|
+
if (toolName === "glob_file_search") {
|
|
4333
|
+
return {
|
|
4334
|
+
pattern: argsObj.globPattern ?? "",
|
|
4335
|
+
path: argsObj.targetDirectory ?? ""
|
|
4336
|
+
};
|
|
4337
|
+
}
|
|
4338
|
+
if (toolName === "file_search") {
|
|
4339
|
+
return {
|
|
4340
|
+
pattern: argsObj.pattern ?? argsObj.query ?? argsObj.searchTerm ?? "",
|
|
4341
|
+
path: argsObj.path ?? argsObj.targetDirectory ?? ""
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
if (toolName === "ripgrep_raw_search") {
|
|
4345
|
+
return {
|
|
4346
|
+
pattern: argsObj.pattern ?? "",
|
|
4347
|
+
path: argsObj.path ?? "",
|
|
4348
|
+
...argsObj.glob ? { glob: argsObj.glob } : {},
|
|
4349
|
+
...argsObj.caseInsensitive !== void 0 ? { case_insensitive: Boolean(argsObj.caseInsensitive) } : {}
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
if (toolName === "ripgrep") {
|
|
4353
|
+
return {
|
|
4354
|
+
pattern: argsObj.pattern ?? argsObj.query ?? "",
|
|
4355
|
+
path: argsObj.path ?? argsObj.targetDirectory ?? "",
|
|
4356
|
+
...argsObj.glob ? { glob: argsObj.glob } : {}
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
if (toolName === "web_search" && argsObj.searchTerm) {
|
|
4360
|
+
return { search_term: argsObj.searchTerm };
|
|
4361
|
+
}
|
|
4362
|
+
if (toolName === "codebase_search") {
|
|
4363
|
+
return {
|
|
4364
|
+
query: argsObj.query ?? "",
|
|
4365
|
+
path: argsObj.repositoryInfo && typeof argsObj.repositoryInfo === "object" && typeof argsObj.repositoryInfo.relativeWorkspacePath === "string" ? argsObj.repositoryInfo.relativeWorkspacePath : "",
|
|
4366
|
+
...argsObj.includePattern ? { includePattern: argsObj.includePattern } : {}
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
if (toolName === "task_v2" || toolName === "Task") {
|
|
4370
|
+
return {
|
|
4371
|
+
...argsObj.description ? { description: argsObj.description } : {},
|
|
4372
|
+
...argsObj.prompt ? { prompt: argsObj.prompt } : {},
|
|
4373
|
+
...argsObj.subagentType ? { subagent_type: argsObj.subagentType } : {}
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
if (toolName.startsWith("mcp-") && Array.isArray(argsObj.tools) && argsObj.tools.length > 0) {
|
|
4377
|
+
const first = argsObj.tools[0] || {};
|
|
4378
|
+
const parsedParameters = typeof first.parameters === "string" ? parseJson(first.parameters) ?? first.parameters : first.parameters;
|
|
4379
|
+
return {
|
|
4380
|
+
...first.serverName ? { server: first.serverName } : {},
|
|
4381
|
+
...first.name ? { tool_name: first.name } : {},
|
|
4382
|
+
...parsedParameters !== void 0 ? { arguments: parsedParameters } : {}
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
return argsObj;
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
// src/providers/cursor/sqlite-reader.ts
|
|
4389
|
+
var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4390
|
+
var MIN_STORE_DB_SIZE = 8192;
|
|
4391
|
+
var MAX_CURSOR_REQUEST_CONTEXT_ROWS = 500;
|
|
4392
|
+
var SQLITE_CLI_MAX_BUFFER = 256 * 1024 * 1024;
|
|
4393
|
+
var SQLITE_CLI_QUERY_TIMEOUT_MS = 12e4;
|
|
4394
|
+
var MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS = 1e4;
|
|
4395
|
+
var GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE = 200;
|
|
4396
|
+
var CURSOR_AGENTKV_BLOB_PREFIX = "agentKv:blob:";
|
|
4397
|
+
var execFileAsync = promisify(execFile);
|
|
4398
|
+
var getSqlJs = createRetryableInit(async () => {
|
|
4399
|
+
const mod = await import("sql.js");
|
|
4400
|
+
return mod.default();
|
|
4401
|
+
});
|
|
4402
|
+
var cachedGlobalStateDb = null;
|
|
4403
|
+
function closeCachedSqlJsDb() {
|
|
4404
|
+
if (cachedGlobalStateDb?.backend !== "sqljs") return;
|
|
4405
|
+
try {
|
|
4406
|
+
cachedGlobalStateDb.db.close();
|
|
4407
|
+
} catch {
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
var cachedStoreDbIndex = null;
|
|
4411
|
+
var resolvedProjectRootCache = /* @__PURE__ */ new Map();
|
|
4412
|
+
var GLOBAL_STATE_DISCOVERY_CACHE_PREFIX = "cursor-global-state-discovery-v4";
|
|
4413
|
+
function globalStateDbCandidates() {
|
|
4414
|
+
const candidates = [
|
|
4415
|
+
join10(
|
|
4416
|
+
homedir9(),
|
|
4417
|
+
"Library",
|
|
4418
|
+
"Application Support",
|
|
4419
|
+
"Cursor",
|
|
4420
|
+
"User",
|
|
4421
|
+
"globalStorage",
|
|
4422
|
+
"state.vscdb"
|
|
4423
|
+
),
|
|
4424
|
+
join10(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb")
|
|
4425
|
+
];
|
|
4426
|
+
const appData = process.env.APPDATA;
|
|
4427
|
+
if (appData) {
|
|
4428
|
+
candidates.push(join10(appData, "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
4429
|
+
}
|
|
4430
|
+
return [...new Set(candidates)];
|
|
4431
|
+
}
|
|
4432
|
+
async function findGlobalStateDb() {
|
|
4433
|
+
for (const candidate of globalStateDbCandidates()) {
|
|
4434
|
+
const s = await stat5(candidate).catch(() => null);
|
|
4435
|
+
if (s?.isFile() && s.size >= MIN_STORE_DB_SIZE) return candidate;
|
|
4436
|
+
}
|
|
4437
|
+
return null;
|
|
4438
|
+
}
|
|
4439
|
+
async function globalStateWalFingerprint(dbPath) {
|
|
4440
|
+
const walStat = await stat5(`${dbPath}-wal`).catch(() => null);
|
|
4441
|
+
return {
|
|
4442
|
+
walSize: walStat?.isFile() ? walStat.size : 0,
|
|
4443
|
+
walMtimeMs: walStat?.isFile() ? walStat.mtimeMs : 0
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
function sqlString(value) {
|
|
4447
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
4448
|
+
}
|
|
4449
|
+
function nextStringPrefix(prefix) {
|
|
4450
|
+
if (!prefix) return null;
|
|
4451
|
+
const chars = [...prefix];
|
|
4452
|
+
const last = chars.pop();
|
|
4453
|
+
if (!last) return null;
|
|
4454
|
+
const codePoint = last.codePointAt(0);
|
|
4455
|
+
if (codePoint === void 0 || codePoint >= 1114111) return null;
|
|
4456
|
+
const next = codePoint + 1;
|
|
4457
|
+
if (next >= 55296 && next <= 57343) return null;
|
|
4458
|
+
return `${chars.join("")}${String.fromCodePoint(next)}`;
|
|
4459
|
+
}
|
|
4460
|
+
function sqlKeyPrefixRange(prefix, column = "key") {
|
|
4461
|
+
const upperBound = nextStringPrefix(prefix);
|
|
4462
|
+
if (!upperBound) return `${column} >= ${sqlString(prefix)}`;
|
|
4463
|
+
return `${column} >= ${sqlString(prefix)} AND ${column} < ${sqlString(upperBound)}`;
|
|
4464
|
+
}
|
|
4465
|
+
function projectedCursorBubbleSelectSql() {
|
|
4466
|
+
const value = "value";
|
|
4467
|
+
const toolResult = `json_extract(${value}, '$.toolFormerData.result')`;
|
|
4468
|
+
const truncatedToolResult = [
|
|
4469
|
+
"CASE",
|
|
4470
|
+
`WHEN json_type(${value}, '$.toolFormerData.result') = 'text'`,
|
|
4471
|
+
`THEN substr(${toolResult}, 1, ${MAX_CURSOR_GLOBAL_STATE_TOOL_RESULT_CHARS})`,
|
|
4472
|
+
`ELSE ${toolResult}`,
|
|
4473
|
+
"END"
|
|
4474
|
+
].join(" ");
|
|
4475
|
+
return [
|
|
4476
|
+
"key",
|
|
4477
|
+
`json_extract(${value}, '$.type') AS type`,
|
|
4478
|
+
`json_extract(${value}, '$.text') AS text`,
|
|
4479
|
+
`json_extract(${value}, '$.thinking') AS thinking`,
|
|
4480
|
+
`json_extract(${value}, '$.createdAt') AS createdAt`,
|
|
4481
|
+
`json_extract(${value}, '$.lastUpdatedAt') AS lastUpdatedAt`,
|
|
4482
|
+
`json_extract(${value}, '$.timingInfo') AS timingInfo`,
|
|
4483
|
+
`json_extract(${value}, '$.thinkingDurationMs') AS thinkingDurationMs`,
|
|
4484
|
+
`json_extract(${value}, '$.tokenCount') AS tokenCount`,
|
|
4485
|
+
`json_extract(${value}, '$.modelInfo') AS modelInfo`,
|
|
4486
|
+
`json_extract(${value}, '$.modelName') AS modelName`,
|
|
4487
|
+
`json_extract(${value}, '$.modelConfig') AS modelConfig`,
|
|
4488
|
+
`json_extract(${value}, '$.toolFormerData.name') AS toolName`,
|
|
4489
|
+
`json_extract(${value}, '$.toolFormerData.params') AS toolParams`,
|
|
4490
|
+
`${truncatedToolResult} AS toolResult`,
|
|
4491
|
+
`length(${toolResult}) AS toolResultLength`,
|
|
4492
|
+
`json_extract(${value}, '$.toolFormerData.toolCallId') AS toolCallId`,
|
|
4493
|
+
`json_extract(${value}, '$.pullRequests') AS pullRequests`,
|
|
4494
|
+
`json_extract(${value}, '$.errorDetails') AS errorDetails`,
|
|
4495
|
+
`json_extract(${value}, '$.retryAttempt') AS retryAttempt`,
|
|
4496
|
+
`json_extract(${value}, '$.relevantFiles') AS relevantFiles`,
|
|
4497
|
+
`json_extract(${value}, '$.recentlyViewedFiles') AS recentlyViewedFiles`
|
|
4498
|
+
].join(", ");
|
|
4499
|
+
}
|
|
4500
|
+
function replayableGlobalStateBubblePredicateSql(valueExpr) {
|
|
4501
|
+
const nonEmptyText = (path) => `(json_type(${valueExpr}, ${sqlString(path)}) = 'text' AND length(trim(COALESCE(json_extract(${valueExpr}, ${sqlString(
|
|
4502
|
+
path
|
|
4503
|
+
)}), ''))) > 0)`;
|
|
4185
4504
|
return [
|
|
4186
4505
|
nonEmptyText("$.text"),
|
|
4187
4506
|
nonEmptyText("$.thinking"),
|
|
@@ -4270,6 +4589,22 @@ async function queryGlobalStateRows(globalStateDb, sql) {
|
|
|
4270
4589
|
}
|
|
4271
4590
|
return sqlJsRows(globalStateDb.db, sql);
|
|
4272
4591
|
}
|
|
4592
|
+
async function queryCursorDiskKvRowsByKeys(db, keys) {
|
|
4593
|
+
if (keys.length === 0) return [];
|
|
4594
|
+
const uniqueKeys = [...new Set(keys)];
|
|
4595
|
+
const rows = [];
|
|
4596
|
+
for (let i = 0; i < uniqueKeys.length; i += GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE) {
|
|
4597
|
+
const chunk = uniqueKeys.slice(i, i + GLOBAL_STATE_BUBBLE_KEY_CHUNK_SIZE);
|
|
4598
|
+
const chunkRows = await queryGlobalStateRows(
|
|
4599
|
+
db,
|
|
4600
|
+
`SELECT key, value FROM cursorDiskKV WHERE key IN (${chunk.map(sqlString).join(",")})`
|
|
4601
|
+
);
|
|
4602
|
+
for (const row of chunkRows) {
|
|
4603
|
+
if ("key" in row && "value" in row) rows.push({ key: row.key, value: row.value });
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
return rows;
|
|
4607
|
+
}
|
|
4273
4608
|
async function queryGlobalStateTextValue(globalStateDb, key) {
|
|
4274
4609
|
if (globalStateDb.backend === "sqlite-cli") {
|
|
4275
4610
|
const value = await querySqliteCliText(
|
|
@@ -4343,7 +4678,7 @@ async function buildStoreDbIndex() {
|
|
|
4343
4678
|
return index;
|
|
4344
4679
|
}
|
|
4345
4680
|
for (const wsHash of workspaceDirs) {
|
|
4346
|
-
const wsDir =
|
|
4681
|
+
const wsDir = join10(CURSOR_CHATS_DIR, wsHash);
|
|
4347
4682
|
const wsStat = await stat5(wsDir).catch(() => null);
|
|
4348
4683
|
if (!wsStat?.isDirectory()) continue;
|
|
4349
4684
|
let sessions;
|
|
@@ -4354,7 +4689,7 @@ async function buildStoreDbIndex() {
|
|
|
4354
4689
|
}
|
|
4355
4690
|
for (const sessionId of sessions) {
|
|
4356
4691
|
if (!SESSION_ID_RE.test(sessionId)) continue;
|
|
4357
|
-
const dbPath =
|
|
4692
|
+
const dbPath = join10(wsDir, sessionId, "store.db");
|
|
4358
4693
|
const dbStat = await stat5(dbPath).catch(() => null);
|
|
4359
4694
|
if (!dbStat?.isFile() || dbStat.size < MIN_STORE_DB_SIZE) continue;
|
|
4360
4695
|
index.set(sessionId, {
|
|
@@ -4410,7 +4745,7 @@ async function readCursorLiveDiagnostics(sessionId) {
|
|
|
4410
4745
|
const globalStateDb = await openGlobalStateDb();
|
|
4411
4746
|
if (!globalStateDb || !await hasGlobalStateSession(globalStateDb, sessionId)) return null;
|
|
4412
4747
|
const rawComposer = await queryGlobalStateTextValue(globalStateDb, `composerData:${sessionId}`);
|
|
4413
|
-
const composer =
|
|
4748
|
+
const composer = parseJson2(rawComposer || "");
|
|
4414
4749
|
if (!composer) return null;
|
|
4415
4750
|
const headerBubbleIds = Array.isArray(composer.fullConversationHeadersOnly) ? composer.fullConversationHeadersOnly.map(
|
|
4416
4751
|
(header) => header && typeof header === "object" && typeof header.bubbleId === "string" ? header.bubbleId : ""
|
|
@@ -4519,7 +4854,7 @@ function valueToString(value) {
|
|
|
4519
4854
|
if (value == null) return "";
|
|
4520
4855
|
return String(value);
|
|
4521
4856
|
}
|
|
4522
|
-
function
|
|
4857
|
+
function parseJson2(raw) {
|
|
4523
4858
|
if (typeof raw !== "string") return null;
|
|
4524
4859
|
try {
|
|
4525
4860
|
return JSON.parse(raw);
|
|
@@ -4529,7 +4864,7 @@ function parseJson(raw) {
|
|
|
4529
4864
|
}
|
|
4530
4865
|
function parseJsonColumn(value) {
|
|
4531
4866
|
if (typeof value !== "string") return value;
|
|
4532
|
-
return
|
|
4867
|
+
return parseJson2(value) ?? value;
|
|
4533
4868
|
}
|
|
4534
4869
|
function optionalJsonColumn(value) {
|
|
4535
4870
|
if (value === null || value === void 0 || value === "") return void 0;
|
|
@@ -4592,6 +4927,19 @@ function toIsoTimestamp(value) {
|
|
|
4592
4927
|
}
|
|
4593
4928
|
return void 0;
|
|
4594
4929
|
}
|
|
4930
|
+
function cursorComposerSidecarMetadata(composer) {
|
|
4931
|
+
const conversationCheckpointLastUpdatedAt = toIsoTimestamp(
|
|
4932
|
+
composer.conversationCheckpointLastUpdatedAt
|
|
4933
|
+
);
|
|
4934
|
+
const restrictAgentModeSwitching = typeof composer.restrictAgentModeSwitching === "boolean" ? composer.restrictAgentModeSwitching : void 0;
|
|
4935
|
+
const glassMetaParentAgent = valueToString(composer.glassMetaParentAgent);
|
|
4936
|
+
const metadata = {
|
|
4937
|
+
...conversationCheckpointLastUpdatedAt ? { conversationCheckpointLastUpdatedAt } : {},
|
|
4938
|
+
...restrictAgentModeSwitching !== void 0 ? { restrictAgentModeSwitching } : {},
|
|
4939
|
+
...glassMetaParentAgent ? { glassMetaParentAgent } : {}
|
|
4940
|
+
};
|
|
4941
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
4942
|
+
}
|
|
4595
4943
|
function maxIsoTimestamp(values) {
|
|
4596
4944
|
let maxMs;
|
|
4597
4945
|
for (const value of values) {
|
|
@@ -4697,7 +5045,7 @@ function tokenUsageFromCursorTokenCount(value) {
|
|
|
4697
5045
|
}
|
|
4698
5046
|
function hashWorkspacePaths(paths) {
|
|
4699
5047
|
const normalized = [...new Set(paths.filter(Boolean))].sort().join("\n");
|
|
4700
|
-
return
|
|
5048
|
+
return createHash2("sha1").update(normalized).digest("hex").slice(0, 16);
|
|
4701
5049
|
}
|
|
4702
5050
|
async function resolveProjectRootFromPath(rawPath) {
|
|
4703
5051
|
const candidate = rawPath.replaceAll("\\\\", "/").replace(/\\n.*$/, "").replace(/["']+$/g, "").replace(/[),]+$/g, "");
|
|
@@ -4713,7 +5061,7 @@ async function resolveProjectRootFromPath(rawPath) {
|
|
|
4713
5061
|
const dirStat = await stat5(current).catch(() => null);
|
|
4714
5062
|
if (dirStat?.isDirectory()) {
|
|
4715
5063
|
if (!deepestExisting) deepestExisting = current;
|
|
4716
|
-
const gitStat = await stat5(
|
|
5064
|
+
const gitStat = await stat5(join10(current, ".git")).catch(() => null);
|
|
4717
5065
|
if (gitStat) return current;
|
|
4718
5066
|
}
|
|
4719
5067
|
const parent = dirname2(current);
|
|
@@ -4818,7 +5166,7 @@ function canUseComposerProjectHintDirectly(pathValue) {
|
|
|
4818
5166
|
}
|
|
4819
5167
|
function hasReplayableRootBlob(data) {
|
|
4820
5168
|
if (!(data instanceof Uint8Array) || data.length === 0) return false;
|
|
4821
|
-
return extractChildBlobIds(data).length > 0;
|
|
5169
|
+
return extractChildBlobIds(data).length > 0 || extractAgentKvBlobIds(valueToString(data)).length > 0;
|
|
4822
5170
|
}
|
|
4823
5171
|
function buildHashToProjectMap(decodedWorkspacePaths) {
|
|
4824
5172
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4973,7 +5321,7 @@ async function discoverGlobalStateOnlySessions(knownSessionIds, decodedWorkspace
|
|
|
4973
5321
|
const sessionId = key.startsWith("composerData:") ? key.slice("composerData:".length) : "";
|
|
4974
5322
|
if (!SESSION_ID_RE.test(sessionId)) continue;
|
|
4975
5323
|
const rawComposer = row.value === void 0 ? "" : valueToString(row.value);
|
|
4976
|
-
const composer = rawComposer ?
|
|
5324
|
+
const composer = rawComposer ? parseJson2(rawComposer) : null;
|
|
4977
5325
|
if (rawComposer && !composer) continue;
|
|
4978
5326
|
const headerCount = composer ? countComposerConversationHeaders(composer) : toNonNegativeInt(row.headerCount);
|
|
4979
5327
|
if (headerCount === 0) continue;
|
|
@@ -5003,22 +5351,99 @@ async function discoverGlobalStateOnlySessions(knownSessionIds, decodedWorkspace
|
|
|
5003
5351
|
};
|
|
5004
5352
|
discoveredSessions.push(sessionInfo);
|
|
5005
5353
|
}
|
|
5006
|
-
} catch {
|
|
5354
|
+
} catch {
|
|
5355
|
+
}
|
|
5356
|
+
await writeFileCache(cacheKey, {
|
|
5357
|
+
dbPath,
|
|
5358
|
+
size: globalStateDb.size,
|
|
5359
|
+
mtimeMs: globalStateDb.mtimeMs,
|
|
5360
|
+
walSize: globalStateDb.walSize,
|
|
5361
|
+
walMtimeMs: globalStateDb.walMtimeMs,
|
|
5362
|
+
decodedPathsHash,
|
|
5363
|
+
sessions: discoveredSessions,
|
|
5364
|
+
sessionIds: [...sessionIds]
|
|
5365
|
+
});
|
|
5366
|
+
return {
|
|
5367
|
+
sessions: finalizeGlobalStateDiscovery(discoveredSessions, knownSessionIds),
|
|
5368
|
+
sessionIds
|
|
5369
|
+
};
|
|
5370
|
+
}
|
|
5371
|
+
function extractAgentKvBlobIds(value) {
|
|
5372
|
+
if (typeof value !== "string" || !value.includes(CURSOR_AGENTKV_BLOB_PREFIX)) return [];
|
|
5373
|
+
const ids = [];
|
|
5374
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5375
|
+
const re = /agentKv:blob:([a-f0-9]{64})/gi;
|
|
5376
|
+
let match;
|
|
5377
|
+
while (match = re.exec(value)) {
|
|
5378
|
+
const id = match[1].toLowerCase();
|
|
5379
|
+
if (seen.has(id)) continue;
|
|
5380
|
+
seen.add(id);
|
|
5381
|
+
ids.push(id);
|
|
5382
|
+
}
|
|
5383
|
+
return ids;
|
|
5384
|
+
}
|
|
5385
|
+
function agentKvBlobMessageToCursorMessage(message) {
|
|
5386
|
+
if (!message || typeof message !== "object") return void 0;
|
|
5387
|
+
if (!["system", "user", "assistant", "tool"].includes(message.role)) return void 0;
|
|
5388
|
+
const content = typeof message.content === "string" ? message.content : "";
|
|
5389
|
+
if (message.role === "assistant") {
|
|
5390
|
+
const toolMessage = message;
|
|
5391
|
+
if (toolMessage.toolCallId && toolMessage.toolName) {
|
|
5392
|
+
return {
|
|
5393
|
+
role: "assistant",
|
|
5394
|
+
content: [
|
|
5395
|
+
{
|
|
5396
|
+
type: "tool-call",
|
|
5397
|
+
toolCallId: toolMessage.toolCallId,
|
|
5398
|
+
toolName: toolMessage.toolName,
|
|
5399
|
+
args: toolMessage.args || {}
|
|
5400
|
+
}
|
|
5401
|
+
]
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
if (message.role === "tool") {
|
|
5406
|
+
const toolMessage = message;
|
|
5407
|
+
return {
|
|
5408
|
+
role: "tool",
|
|
5409
|
+
content: [
|
|
5410
|
+
{
|
|
5411
|
+
type: "tool-result",
|
|
5412
|
+
toolCallId: toolMessage.toolCallId || "agentkv-tool-result",
|
|
5413
|
+
toolName: toolMessage.toolName || "CursorAgentKv",
|
|
5414
|
+
result: content
|
|
5415
|
+
}
|
|
5416
|
+
]
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
return { role: message.role, content };
|
|
5420
|
+
}
|
|
5421
|
+
function appendAgentKvBlobMessages(messages, blobRows) {
|
|
5422
|
+
let appended = 0;
|
|
5423
|
+
let pendingToolCallId = "";
|
|
5424
|
+
let pendingToolName = "CursorAgentKv";
|
|
5425
|
+
for (const row of blobRows) {
|
|
5426
|
+
const raw = valueToString(row.value);
|
|
5427
|
+
const parsed = parseJson2(raw);
|
|
5428
|
+
if (!parsed) continue;
|
|
5429
|
+
const toolCallId = parsed.toolCallId || parsed.tool_call_id || (parsed.role === "assistant" ? raw.match(/(?:toolCallId|tool_call_id)["'\s:=]+([A-Za-z0-9_-]+)/i)?.[1] : void 0);
|
|
5430
|
+
if (toolCallId) pendingToolCallId = toolCallId;
|
|
5431
|
+
const toolName = parsed.toolName || parsed.tool_name || (parsed.role === "assistant" ? raw.match(/(?:toolName|tool_name)["'\s:=]+([A-Za-z0-9_-]+)/i)?.[1] : void 0);
|
|
5432
|
+
if (toolName) pendingToolName = toolName;
|
|
5433
|
+
const message = agentKvBlobMessageToCursorMessage({
|
|
5434
|
+
...parsed,
|
|
5435
|
+
...pendingToolCallId && parsed.role === "assistant" && toolCallId ? { toolCallId: pendingToolCallId, toolName: pendingToolName } : {},
|
|
5436
|
+
...parsed.role === "tool" ? { toolCallId: pendingToolCallId, toolName: pendingToolName } : {}
|
|
5437
|
+
});
|
|
5438
|
+
if (!message) continue;
|
|
5439
|
+
messages.push(message);
|
|
5440
|
+
appended++;
|
|
5441
|
+
if (parsed.role === "tool") {
|
|
5442
|
+
pendingToolCallId = "";
|
|
5443
|
+
pendingToolName = "CursorAgentKv";
|
|
5444
|
+
}
|
|
5007
5445
|
}
|
|
5008
|
-
|
|
5009
|
-
dbPath,
|
|
5010
|
-
size: globalStateDb.size,
|
|
5011
|
-
mtimeMs: globalStateDb.mtimeMs,
|
|
5012
|
-
walSize: globalStateDb.walSize,
|
|
5013
|
-
walMtimeMs: globalStateDb.walMtimeMs,
|
|
5014
|
-
decodedPathsHash,
|
|
5015
|
-
sessions: discoveredSessions,
|
|
5016
|
-
sessionIds: [...sessionIds]
|
|
5017
|
-
});
|
|
5018
|
-
return {
|
|
5019
|
-
sessions: finalizeGlobalStateDiscovery(discoveredSessions, knownSessionIds),
|
|
5020
|
-
sessionIds
|
|
5021
|
-
};
|
|
5446
|
+
return appended;
|
|
5022
5447
|
}
|
|
5023
5448
|
function extractChildBlobIds(data) {
|
|
5024
5449
|
const ids = [];
|
|
@@ -5078,7 +5503,8 @@ async function parseCursorStoreDb(sessionId, workspacePath = "") {
|
|
|
5078
5503
|
if (!rootRows.length || !rootRows[0].values.length) return null;
|
|
5079
5504
|
const rootData = rootRows[0].values[0][0];
|
|
5080
5505
|
const childIds = extractChildBlobIds(rootData);
|
|
5081
|
-
|
|
5506
|
+
const agentKvBlobIds = extractAgentKvBlobIds(new TextDecoder().decode(rootData));
|
|
5507
|
+
if (childIds.length === 0 && agentKvBlobIds.length === 0) return null;
|
|
5082
5508
|
const messages = [];
|
|
5083
5509
|
const stmt = db.prepare("SELECT data FROM blobs WHERE id = ?");
|
|
5084
5510
|
for (const cid of childIds) {
|
|
@@ -5097,6 +5523,24 @@ async function parseCursorStoreDb(sessionId, workspacePath = "") {
|
|
|
5097
5523
|
}
|
|
5098
5524
|
}
|
|
5099
5525
|
stmt.free();
|
|
5526
|
+
if (agentKvBlobIds.length > 0) {
|
|
5527
|
+
try {
|
|
5528
|
+
const agentKvStmt = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key = ?");
|
|
5529
|
+
try {
|
|
5530
|
+
for (const id of agentKvBlobIds) {
|
|
5531
|
+
agentKvStmt.bind([`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`]);
|
|
5532
|
+
if (agentKvStmt.step()) {
|
|
5533
|
+
const row = agentKvStmt.getAsObject();
|
|
5534
|
+
appendAgentKvBlobMessages(messages, [{ key: row.key, value: row.value }]);
|
|
5535
|
+
}
|
|
5536
|
+
agentKvStmt.reset();
|
|
5537
|
+
}
|
|
5538
|
+
} finally {
|
|
5539
|
+
agentKvStmt.free();
|
|
5540
|
+
}
|
|
5541
|
+
} catch {
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5100
5544
|
return buildCursorStoreResult(sessionId, workspacePath, metaJson, messages);
|
|
5101
5545
|
} finally {
|
|
5102
5546
|
db.close();
|
|
@@ -5115,8 +5559,10 @@ async function parseCursorStoreDbWithSqliteCli(dbPath, sessionId, workspacePath)
|
|
|
5115
5559
|
);
|
|
5116
5560
|
const rootDataHex = typeof rootRows[0]?.dataHex === "string" ? rootRows[0].dataHex : "";
|
|
5117
5561
|
if (!rootDataHex) return null;
|
|
5118
|
-
const
|
|
5119
|
-
|
|
5562
|
+
const rootData = Buffer.from(rootDataHex, "hex");
|
|
5563
|
+
const childIds = extractChildBlobIds(rootData);
|
|
5564
|
+
const agentKvBlobIds = extractAgentKvBlobIds(rootData.toString("utf-8"));
|
|
5565
|
+
if (childIds.length === 0 && agentKvBlobIds.length === 0) return null;
|
|
5120
5566
|
const blobHexById = /* @__PURE__ */ new Map();
|
|
5121
5567
|
const chunkSize = 200;
|
|
5122
5568
|
for (let i = 0; i < childIds.length; i += chunkSize) {
|
|
@@ -5141,13 +5587,27 @@ async function parseCursorStoreDbWithSqliteCli(dbPath, sessionId, workspacePath)
|
|
|
5141
5587
|
} catch {
|
|
5142
5588
|
}
|
|
5143
5589
|
}
|
|
5590
|
+
if (agentKvBlobIds.length > 0) {
|
|
5591
|
+
try {
|
|
5592
|
+
const rows = await querySqliteCli(
|
|
5593
|
+
dbPath,
|
|
5594
|
+
`SELECT key, value FROM cursorDiskKV WHERE key IN (${agentKvBlobIds.map((id) => sqlString(`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)).join(",")})`
|
|
5595
|
+
);
|
|
5596
|
+
const rowsByKey = new Map(rows.map((row) => [valueToString(row.key), row]));
|
|
5597
|
+
appendAgentKvBlobMessages(
|
|
5598
|
+
messages,
|
|
5599
|
+
agentKvBlobIds.map((id) => rowsByKey.get(`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)).filter((row) => Boolean(row))
|
|
5600
|
+
);
|
|
5601
|
+
} catch {
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5144
5604
|
return buildCursorStoreResult(sessionId, workspacePath, metaJson, messages);
|
|
5145
5605
|
}
|
|
5146
5606
|
function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
|
|
5147
5607
|
const { turns, turnStats, totalDurationMs } = messagesToTurns(messages);
|
|
5148
5608
|
const slug = sessionId.slice(0, 8);
|
|
5149
5609
|
const title = metaJson.name || firstUserTextSnippet(turns);
|
|
5150
|
-
const hasDurationStats = turnStats.some((
|
|
5610
|
+
const hasDurationStats = turnStats.some((stat12) => (stat12.durationMs || 0) > 0);
|
|
5151
5611
|
const notes = [];
|
|
5152
5612
|
if (hasDurationStats) {
|
|
5153
5613
|
notes.push("Per-turn duration is estimated from Cursor tool execution metadata.");
|
|
@@ -5160,7 +5620,7 @@ function buildCursorStoreResult(sessionId, workspacePath, metaJson, messages) {
|
|
|
5160
5620
|
slug,
|
|
5161
5621
|
title,
|
|
5162
5622
|
cwd: workspacePath,
|
|
5163
|
-
model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((
|
|
5623
|
+
model: normalizeCursorModelName(metaJson.lastUsedModel) || turnStats.find((stat12) => typeof stat12.model === "string" && stat12.model)?.model,
|
|
5164
5624
|
startTime: metaJson.createdAt ? new Date(metaJson.createdAt).toISOString() : void 0,
|
|
5165
5625
|
...totalDurationMs !== void 0 ? { totalDurationMs } : {},
|
|
5166
5626
|
...turnStats.length > 0 ? { turnStats } : {},
|
|
@@ -5196,7 +5656,7 @@ function normalizeAssistantTurnText(raw, hasToolContext) {
|
|
|
5196
5656
|
}
|
|
5197
5657
|
function extractToolResultText2(value) {
|
|
5198
5658
|
if (typeof value === "string") {
|
|
5199
|
-
const parsed =
|
|
5659
|
+
const parsed = parseJson2(value);
|
|
5200
5660
|
if (!parsed) return value;
|
|
5201
5661
|
return extractToolResultText2(parsed);
|
|
5202
5662
|
}
|
|
@@ -5214,7 +5674,7 @@ function extractToolResultText2(value) {
|
|
|
5214
5674
|
if (typeof obj.contents === "string" && obj.contents.trim()) return obj.contents;
|
|
5215
5675
|
if (typeof obj.markdown === "string" && obj.markdown.trim()) return obj.markdown;
|
|
5216
5676
|
if (typeof obj.result === "string" && obj.result.trim()) {
|
|
5217
|
-
const nested =
|
|
5677
|
+
const nested = parseJson2(obj.result);
|
|
5218
5678
|
if (nested) {
|
|
5219
5679
|
const nestedText = extractToolResultText2(nested);
|
|
5220
5680
|
if (nestedText.trim()) return nestedText;
|
|
@@ -5230,7 +5690,7 @@ function extractToolResultText2(value) {
|
|
|
5230
5690
|
function hasToolError(value) {
|
|
5231
5691
|
if (!value) return false;
|
|
5232
5692
|
if (typeof value === "string") {
|
|
5233
|
-
const parsed =
|
|
5693
|
+
const parsed = parseJson2(value);
|
|
5234
5694
|
if (!parsed) return false;
|
|
5235
5695
|
return hasToolError(parsed);
|
|
5236
5696
|
}
|
|
@@ -5252,7 +5712,7 @@ function hasToolError(value) {
|
|
|
5252
5712
|
function extractToolExecutionTimeMs(value) {
|
|
5253
5713
|
if (!value) return void 0;
|
|
5254
5714
|
if (typeof value === "string") {
|
|
5255
|
-
const parsed =
|
|
5715
|
+
const parsed = parseJson2(value);
|
|
5256
5716
|
return parsed ? extractToolExecutionTimeMs(parsed) : toPositiveMs(value);
|
|
5257
5717
|
}
|
|
5258
5718
|
if (Array.isArray(value)) {
|
|
@@ -5277,7 +5737,7 @@ function extractToolExecutionTimeMs(value) {
|
|
|
5277
5737
|
function parseToolFormerBlock(bubbleId, toolFormerData) {
|
|
5278
5738
|
const name = typeof toolFormerData.name === "string" ? toolFormerData.name : "";
|
|
5279
5739
|
if (!name) return null;
|
|
5280
|
-
const parsedParams =
|
|
5740
|
+
const parsedParams = parseJson2(toolFormerData.params);
|
|
5281
5741
|
const paramsRaw = parsedParams ?? toolFormerData.params ?? {};
|
|
5282
5742
|
const result = extractToolResultText2(toolFormerData.result);
|
|
5283
5743
|
return {
|
|
@@ -5370,9 +5830,9 @@ function extractCursorApiErrors(entries) {
|
|
|
5370
5830
|
for (const entry of entries) {
|
|
5371
5831
|
const rawDetails = entry.bubble.errorDetails;
|
|
5372
5832
|
if (!rawDetails) continue;
|
|
5373
|
-
const details = typeof rawDetails === "string" ?
|
|
5833
|
+
const details = typeof rawDetails === "string" ? parseJson2(rawDetails) ?? { message: rawDetails } : typeof rawDetails === "object" ? rawDetails : null;
|
|
5374
5834
|
if (!details) continue;
|
|
5375
|
-
const nestedError = typeof details.error === "string" ?
|
|
5835
|
+
const nestedError = typeof details.error === "string" ? parseJson2(details.error) : typeof details.error === "object" ? details.error : void 0;
|
|
5376
5836
|
const statusCode = toNonNegativeInt(details.statusCode) || toNonNegativeInt(details.status) || toNonNegativeInt(nestedError?.statusCode) || toNonNegativeInt(nestedError?.status) || void 0;
|
|
5377
5837
|
const errorType = typeof nestedError?.error === "string" && nestedError.error || typeof nestedError?.type === "string" && nestedError.type || typeof details.type === "string" && details.type || void 0;
|
|
5378
5838
|
const retryAttempt = toNonNegativeInt(details.retryAttempt) || toNonNegativeInt(entry.bubble.retryAttempt) || void 0;
|
|
@@ -5422,7 +5882,7 @@ function addUniqueContextFile(files, seen, value) {
|
|
|
5422
5882
|
files.push(file);
|
|
5423
5883
|
}
|
|
5424
5884
|
function addContextFilesFromAttachedFolderResult(files, seen, value) {
|
|
5425
|
-
const parsed = typeof value === "string" ?
|
|
5885
|
+
const parsed = typeof value === "string" ? parseJson2(value) : value && typeof value === "object" ? value : void 0;
|
|
5426
5886
|
if (!parsed) return;
|
|
5427
5887
|
const directory = normalizeCursorContextFile(parsed.directoryRelativeWorkspacePath) || normalizeCursorContextFile(parsed.directoryPath);
|
|
5428
5888
|
if (Array.isArray(parsed.files)) {
|
|
@@ -5486,6 +5946,19 @@ function extractCursorContextSummary(entries, requestContexts) {
|
|
|
5486
5946
|
hasCursorRules
|
|
5487
5947
|
};
|
|
5488
5948
|
}
|
|
5949
|
+
async function loadAgentKvBlobMessages(globalStateDb, agentKvBlobIds) {
|
|
5950
|
+
const messages = [];
|
|
5951
|
+
const rows = await queryCursorDiskKvRowsByKeys(
|
|
5952
|
+
globalStateDb,
|
|
5953
|
+
agentKvBlobIds.map((id) => `${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)
|
|
5954
|
+
);
|
|
5955
|
+
const rowsByKey = new Map(rows.map((row) => [valueToString(row.key), row]));
|
|
5956
|
+
appendAgentKvBlobMessages(
|
|
5957
|
+
messages,
|
|
5958
|
+
agentKvBlobIds.map((id) => rowsByKey.get(`${CURSOR_AGENTKV_BLOB_PREFIX}${id}`)).filter((row) => Boolean(row))
|
|
5959
|
+
);
|
|
5960
|
+
return messages;
|
|
5961
|
+
}
|
|
5489
5962
|
function buildBubbleProjectHintData(entries) {
|
|
5490
5963
|
return entries.map((entry) => {
|
|
5491
5964
|
const bubble = entry.bubble;
|
|
@@ -5507,7 +5980,7 @@ async function loadCursorRequestContexts(globalStateDb, sessionId) {
|
|
|
5507
5980
|
const contexts = [];
|
|
5508
5981
|
for (const row of rows) {
|
|
5509
5982
|
const raw = valueToString(row.value);
|
|
5510
|
-
const parsed =
|
|
5983
|
+
const parsed = parseJson2(raw);
|
|
5511
5984
|
if (parsed) contexts.push(parsed);
|
|
5512
5985
|
}
|
|
5513
5986
|
return contexts;
|
|
@@ -5549,13 +6022,13 @@ function mergeUniqueStrings(...groups) {
|
|
|
5549
6022
|
}
|
|
5550
6023
|
function mergeCursorParseResults(primary, enrichment) {
|
|
5551
6024
|
const mergedModel = chooseMergedCursorModel(primary.model, enrichment.model);
|
|
5552
|
-
const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((
|
|
6025
|
+
const preferEnrichmentDuration = enrichment.dataSource === "global-state" && (enrichment.totalDurationMs !== void 0 || enrichment.turnStats?.some((stat12) => stat12.durationMs !== void 0) === true);
|
|
5553
6026
|
const mergedTurnStats = mergeTurnStats(primary.turnStats, enrichment.turnStats, {
|
|
5554
6027
|
preferEnrichmentDuration
|
|
5555
6028
|
});
|
|
5556
6029
|
const mergedTokenUsage = primary.tokenUsage || enrichment.tokenUsage;
|
|
5557
6030
|
const mergedTokenUsageByModel = primary.tokenUsageByModel || enrichment.tokenUsageByModel;
|
|
5558
|
-
const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum,
|
|
6031
|
+
const mergedTotalDurationMs = (preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || primary.totalDurationMs || (!preferEnrichmentDuration ? enrichment.totalDurationMs : void 0) || (mergedTurnStats && mergedTurnStats.length > 0 ? mergedTurnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0 : void 0);
|
|
5559
6032
|
const mergedDuration = mergedTotalDurationMs !== primary.totalDurationMs;
|
|
5560
6033
|
const mergedTokens = !primary.tokenUsage && !!enrichment.tokenUsage || !primary.tokenUsageByModel && !!enrichment.tokenUsageByModel;
|
|
5561
6034
|
const supplements = mergeUniqueStrings(
|
|
@@ -5599,11 +6072,25 @@ function mergeCursorParseResults(primary, enrichment) {
|
|
|
5599
6072
|
}
|
|
5600
6073
|
function mergeCursorSidecars(primary, enrichment) {
|
|
5601
6074
|
if (!primary && !enrichment) return void 0;
|
|
5602
|
-
const merged = {
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
6075
|
+
const merged = {};
|
|
6076
|
+
if (primary?.requestContextCount ?? enrichment?.requestContextCount) {
|
|
6077
|
+
merged.requestContextCount = primary?.requestContextCount ?? enrichment?.requestContextCount;
|
|
6078
|
+
}
|
|
6079
|
+
if (primary?.checkpointCount ?? enrichment?.checkpointCount) {
|
|
6080
|
+
merged.checkpointCount = primary?.checkpointCount ?? enrichment?.checkpointCount;
|
|
6081
|
+
}
|
|
6082
|
+
if ((primary?.hasWorkspaceRules ?? enrichment?.hasWorkspaceRules) !== void 0) {
|
|
6083
|
+
merged.hasWorkspaceRules = primary?.hasWorkspaceRules ?? enrichment?.hasWorkspaceRules;
|
|
6084
|
+
}
|
|
6085
|
+
if (primary?.conversationCheckpointLastUpdatedAt || enrichment?.conversationCheckpointLastUpdatedAt) {
|
|
6086
|
+
merged.conversationCheckpointLastUpdatedAt = primary?.conversationCheckpointLastUpdatedAt ?? enrichment?.conversationCheckpointLastUpdatedAt;
|
|
6087
|
+
}
|
|
6088
|
+
if (primary?.restrictAgentModeSwitching !== void 0 || enrichment?.restrictAgentModeSwitching !== void 0) {
|
|
6089
|
+
merged.restrictAgentModeSwitching = primary?.restrictAgentModeSwitching ?? enrichment?.restrictAgentModeSwitching;
|
|
6090
|
+
}
|
|
6091
|
+
if (primary?.glassMetaParentAgent || enrichment?.glassMetaParentAgent) {
|
|
6092
|
+
merged.glassMetaParentAgent = primary?.glassMetaParentAgent ?? enrichment?.glassMetaParentAgent;
|
|
6093
|
+
}
|
|
5607
6094
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
5608
6095
|
}
|
|
5609
6096
|
function normalizeCursorModelName(model) {
|
|
@@ -5625,11 +6112,11 @@ function mergeTurnStats(primary, enrichment, options) {
|
|
|
5625
6112
|
if (!primary?.length) return enrichment;
|
|
5626
6113
|
if (!enrichment?.length) return primary;
|
|
5627
6114
|
const maxTurnIndex = Math.max(
|
|
5628
|
-
...primary.map((
|
|
5629
|
-
...enrichment.map((
|
|
6115
|
+
...primary.map((stat12) => stat12.turnIndex),
|
|
6116
|
+
...enrichment.map((stat12) => stat12.turnIndex)
|
|
5630
6117
|
);
|
|
5631
|
-
const primaryByIndex = new Map(primary.map((
|
|
5632
|
-
const enrichmentByIndex = new Map(enrichment.map((
|
|
6118
|
+
const primaryByIndex = new Map(primary.map((stat12) => [stat12.turnIndex, stat12]));
|
|
6119
|
+
const enrichmentByIndex = new Map(enrichment.map((stat12) => [stat12.turnIndex, stat12]));
|
|
5633
6120
|
const merged = [];
|
|
5634
6121
|
for (let turnIndex = 0; turnIndex <= maxTurnIndex; turnIndex++) {
|
|
5635
6122
|
const current = primaryByIndex.get(turnIndex);
|
|
@@ -5723,17 +6210,17 @@ function applyGlobalStateWallClockDurations(entries, turnStats) {
|
|
|
5723
6210
|
if (durationsByTurn.size === 0) {
|
|
5724
6211
|
return {
|
|
5725
6212
|
turnStats,
|
|
5726
|
-
totalDurationMs: turnStats.reduce((sum,
|
|
6213
|
+
totalDurationMs: turnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0,
|
|
5727
6214
|
usedWallClock: false
|
|
5728
6215
|
};
|
|
5729
6216
|
}
|
|
5730
|
-
const mergedTurnStats = turnStats.map((
|
|
5731
|
-
const wallClockDuration = durationsByTurn.get(
|
|
5732
|
-
return wallClockDuration !== void 0 ? { ...
|
|
6217
|
+
const mergedTurnStats = turnStats.map((stat12) => {
|
|
6218
|
+
const wallClockDuration = durationsByTurn.get(stat12.turnIndex);
|
|
6219
|
+
return wallClockDuration !== void 0 ? { ...stat12, durationMs: wallClockDuration } : stat12;
|
|
5733
6220
|
});
|
|
5734
6221
|
return {
|
|
5735
6222
|
turnStats: mergedTurnStats,
|
|
5736
|
-
totalDurationMs: mergedTurnStats.reduce((sum,
|
|
6223
|
+
totalDurationMs: mergedTurnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0,
|
|
5737
6224
|
usedWallClock: true
|
|
5738
6225
|
};
|
|
5739
6226
|
}
|
|
@@ -5779,15 +6266,15 @@ function buildGlobalStateMetrics(entries, fallbackModel, sessionTokenUsage) {
|
|
|
5779
6266
|
current.durationMs = (current.durationMs || 0) + bubbleDurationMs;
|
|
5780
6267
|
}
|
|
5781
6268
|
}
|
|
5782
|
-
for (const
|
|
5783
|
-
if (
|
|
5784
|
-
delete
|
|
6269
|
+
for (const stat12 of turnStats) {
|
|
6270
|
+
if (stat12.tokenUsage && !hasAnyTokens(stat12.tokenUsage)) {
|
|
6271
|
+
delete stat12.tokenUsage;
|
|
5785
6272
|
}
|
|
5786
|
-
if ((
|
|
5787
|
-
delete
|
|
6273
|
+
if ((stat12.durationMs || 0) <= 0) {
|
|
6274
|
+
delete stat12.durationMs;
|
|
5788
6275
|
}
|
|
5789
|
-
if ((
|
|
5790
|
-
delete
|
|
6276
|
+
if ((stat12.contextTokens || 0) <= 0) {
|
|
6277
|
+
delete stat12.contextTokens;
|
|
5791
6278
|
}
|
|
5792
6279
|
}
|
|
5793
6280
|
const {
|
|
@@ -5839,10 +6326,11 @@ async function parseCursorGlobalStateDb(sessionId, globalStateDb, preferredWorks
|
|
|
5839
6326
|
`composerData:${sessionId}`
|
|
5840
6327
|
);
|
|
5841
6328
|
if (!rawComposer) return null;
|
|
5842
|
-
const composer =
|
|
6329
|
+
const composer = parseJson2(rawComposer);
|
|
5843
6330
|
if (!composer) return null;
|
|
5844
6331
|
const entries = [];
|
|
5845
6332
|
const bubbleEntries = [];
|
|
6333
|
+
const agentKvBlobIds = extractAgentKvBlobIds(rawComposer);
|
|
5846
6334
|
const addBubble = (bubble) => {
|
|
5847
6335
|
bubbleEntries.push({
|
|
5848
6336
|
bubble,
|
|
@@ -5878,6 +6366,20 @@ async function parseCursorGlobalStateDb(sessionId, globalStateDb, preferredWorks
|
|
|
5878
6366
|
if (item && typeof item === "object") addBubble(item);
|
|
5879
6367
|
}
|
|
5880
6368
|
}
|
|
6369
|
+
if (agentKvBlobIds.length > 0) {
|
|
6370
|
+
const { turns: agentKvTurns } = messagesToTurns(
|
|
6371
|
+
await loadAgentKvBlobMessages(resolvedGlobalStateDb, agentKvBlobIds)
|
|
6372
|
+
);
|
|
6373
|
+
for (const turn of agentKvTurns) {
|
|
6374
|
+
const bubble = {
|
|
6375
|
+
type: turn.role === "user" ? 1 : 2,
|
|
6376
|
+
text: turn.blocks.filter(
|
|
6377
|
+
(block) => block.type === "text"
|
|
6378
|
+
).map((block) => block.text).join("\n")
|
|
6379
|
+
};
|
|
6380
|
+
entries.push({ turn, bubble });
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
5881
6383
|
const turns = entries.map((entry) => entry.turn);
|
|
5882
6384
|
if (turns.length === 0) return null;
|
|
5883
6385
|
const inferredProject = preferredWorkspacePath || await inferProjectFromComposerData(
|
|
@@ -5892,6 +6394,7 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
|
|
|
5892
6394
|
const startTime = toIsoTimestamp(composer.createdAt);
|
|
5893
6395
|
const endTime = maxIsoTimestamp([
|
|
5894
6396
|
composer.lastUpdatedAt,
|
|
6397
|
+
composer.conversationCheckpointLastUpdatedAt,
|
|
5895
6398
|
...turns.map((turn) => turn.timestamp).filter(Boolean)
|
|
5896
6399
|
]);
|
|
5897
6400
|
const sessionTokenUsage = tokenUsageFromCursorTokenCount(composer.tokenCount);
|
|
@@ -5919,7 +6422,7 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
|
|
|
5919
6422
|
}
|
|
5920
6423
|
const hasDetailedTurnStats = Boolean(
|
|
5921
6424
|
metrics.turnStats?.some(
|
|
5922
|
-
(
|
|
6425
|
+
(stat12) => !!stat12.durationMs || !!stat12.contextTokens || !!stat12.tokenUsage
|
|
5923
6426
|
)
|
|
5924
6427
|
);
|
|
5925
6428
|
if (!hasDetailedTurnStats) {
|
|
@@ -5933,10 +6436,21 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
|
|
|
5933
6436
|
"Context files are inferred from Cursor relevantFiles and request-context sidecars."
|
|
5934
6437
|
);
|
|
5935
6438
|
}
|
|
5936
|
-
const
|
|
6439
|
+
const composerSidecarMetadata = cursorComposerSidecarMetadata(composer);
|
|
6440
|
+
if (composerSidecarMetadata?.conversationCheckpointLastUpdatedAt) {
|
|
6441
|
+
notes.push("Cursor composerData reports a conversation checkpoint timestamp.");
|
|
6442
|
+
}
|
|
6443
|
+
if (composerSidecarMetadata?.restrictAgentModeSwitching !== void 0) {
|
|
6444
|
+
notes.push("Cursor composerData reports agent mode switching restrictions.");
|
|
6445
|
+
}
|
|
6446
|
+
if (composerSidecarMetadata?.glassMetaParentAgent) {
|
|
6447
|
+
notes.push("Cursor composerData links this session to a Glass parent agent.");
|
|
6448
|
+
}
|
|
6449
|
+
const cursorSidecars = contextSummary.requestContextCount || checkpointCount > 0 || contextSummary.hasCursorRules || composerSidecarMetadata ? {
|
|
5937
6450
|
...contextSummary.requestContextCount ? { requestContextCount: contextSummary.requestContextCount } : {},
|
|
5938
6451
|
...checkpointCount > 0 ? { checkpointCount } : {},
|
|
5939
|
-
...contextSummary.hasCursorRules ? { hasWorkspaceRules: true } : {}
|
|
6452
|
+
...contextSummary.hasCursorRules ? { hasWorkspaceRules: true } : {},
|
|
6453
|
+
...composerSidecarMetadata
|
|
5940
6454
|
} : void 0;
|
|
5941
6455
|
return {
|
|
5942
6456
|
sessionId,
|
|
@@ -5959,419 +6473,136 @@ ${buildBubbleProjectHintData(bubbleEntries)}`,
|
|
|
5959
6473
|
turns,
|
|
5960
6474
|
dataSource: "global-state",
|
|
5961
6475
|
dataSourceInfo: {
|
|
5962
|
-
primary: "global-state",
|
|
5963
|
-
sources: ["cursor/user/globalStorage/state.vscdb"],
|
|
5964
|
-
notes
|
|
5965
|
-
}
|
|
5966
|
-
};
|
|
5967
|
-
} catch {
|
|
5968
|
-
return null;
|
|
5969
|
-
}
|
|
5970
|
-
}
|
|
5971
|
-
function buildToolResultMap(messages) {
|
|
5972
|
-
const map = /* @__PURE__ */ new Map();
|
|
5973
|
-
for (const msg of messages) {
|
|
5974
|
-
if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
|
|
5975
|
-
for (const block of msg.content) {
|
|
5976
|
-
if (block.type === "tool-result" && block.toolCallId) {
|
|
5977
|
-
const resultText = extractToolResultText2(block.result);
|
|
5978
|
-
const topLevelResult = msg.providerOptions?.cursor?.highLevelToolCallResult;
|
|
5979
|
-
const combinedSource = topLevelResult || block.result;
|
|
5980
|
-
map.set(block.toolCallId, {
|
|
5981
|
-
result: resultText,
|
|
5982
|
-
...hasToolError(combinedSource) ? { isError: true } : {},
|
|
5983
|
-
...extractToolExecutionTimeMs(combinedSource) !== void 0 ? { executionTimeMs: extractToolExecutionTimeMs(combinedSource) } : {}
|
|
5984
|
-
});
|
|
5985
|
-
}
|
|
5986
|
-
}
|
|
5987
|
-
}
|
|
5988
|
-
return map;
|
|
5989
|
-
}
|
|
5990
|
-
function messagesToTurns(messages) {
|
|
5991
|
-
const toolResults = buildToolResultMap(messages);
|
|
5992
|
-
const turns = [];
|
|
5993
|
-
for (const msg of messages) {
|
|
5994
|
-
if (msg.role === "system" || msg.role === "tool") continue;
|
|
5995
|
-
if (msg.role === "user") {
|
|
5996
|
-
const blocks = parseUserContent(msg.content);
|
|
5997
|
-
if (blocks.length > 0) {
|
|
5998
|
-
turns.push({ role: "user", blocks });
|
|
5999
|
-
}
|
|
6000
|
-
continue;
|
|
6001
|
-
}
|
|
6002
|
-
if (msg.role === "assistant") {
|
|
6003
|
-
const blocks = parseAssistantContent(msg.content, toolResults);
|
|
6004
|
-
if (blocks.length > 0) {
|
|
6005
|
-
turns.push({
|
|
6006
|
-
role: "assistant",
|
|
6007
|
-
blocks,
|
|
6008
|
-
model: extractModel(msg)
|
|
6009
|
-
});
|
|
6010
|
-
}
|
|
6011
|
-
}
|
|
6012
|
-
}
|
|
6013
|
-
const turnStats = buildStoreTurnStats(turns);
|
|
6014
|
-
const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat11) => sum + (stat11.durationMs || 0), 0) || void 0 : void 0;
|
|
6015
|
-
return { turns, turnStats, totalDurationMs };
|
|
6016
|
-
}
|
|
6017
|
-
var CURSOR_SYSTEM_CONTEXT_RE = /^<(?:user_info|system_reminder|agent_transcripts|rules|git_status)>/;
|
|
6018
|
-
function isSystemContextText(text) {
|
|
6019
|
-
return CURSOR_SYSTEM_CONTEXT_RE.test(text.trim());
|
|
6020
|
-
}
|
|
6021
|
-
function parseUserContent(content) {
|
|
6022
|
-
if (!content) return [];
|
|
6023
|
-
if (typeof content === "string") {
|
|
6024
|
-
if (isSystemContextText(content)) return [];
|
|
6025
|
-
const cleaned = sanitizeCursorUserText(content);
|
|
6026
|
-
if (isSystemContextText(cleaned)) return [];
|
|
6027
|
-
return cleaned ? [{ type: "text", text: cleaned }] : [];
|
|
6028
|
-
}
|
|
6029
|
-
const blocks = [];
|
|
6030
|
-
for (const b of content) {
|
|
6031
|
-
if (b.type === "text" && b.text) {
|
|
6032
|
-
if (isSystemContextText(b.text)) continue;
|
|
6033
|
-
const cleaned = sanitizeCursorUserText(b.text);
|
|
6034
|
-
if (isSystemContextText(cleaned)) continue;
|
|
6035
|
-
if (cleaned) blocks.push({ type: "text", text: cleaned });
|
|
6036
|
-
}
|
|
6037
|
-
}
|
|
6038
|
-
return blocks;
|
|
6039
|
-
}
|
|
6040
|
-
function parseAssistantContent(content, toolResults) {
|
|
6041
|
-
if (!content) return [];
|
|
6042
|
-
if (typeof content === "string") {
|
|
6043
|
-
const cleaned = sanitizeCursorAssistantText(content, false);
|
|
6044
|
-
return cleaned ? [{ type: "text", text: cleaned }] : [];
|
|
6045
|
-
}
|
|
6046
|
-
const blocks = [];
|
|
6047
|
-
const hasToolCalls = content.some((block) => block.type === "tool-call");
|
|
6048
|
-
for (const b of content) {
|
|
6049
|
-
if (b.type === "reasoning" && b.text?.trim()) {
|
|
6050
|
-
const thinking = sanitizeCursorReasoningText(b.text);
|
|
6051
|
-
if (thinking) blocks.push({ type: "thinking", thinking });
|
|
6052
|
-
} else if (b.type === "text" && b.text?.trim()) {
|
|
6053
|
-
const text = sanitizeCursorAssistantText(b.text, hasToolCalls);
|
|
6054
|
-
if (text) blocks.push({ type: "text", text });
|
|
6055
|
-
} else if (b.type === "tool-call" && b.toolCallId && b.toolName) {
|
|
6056
|
-
const result = toolResults.get(b.toolCallId);
|
|
6057
|
-
const toolBlock = {
|
|
6058
|
-
type: "tool_use",
|
|
6059
|
-
id: b.toolCallId,
|
|
6060
|
-
name: mapCursorToolName(b.toolName),
|
|
6061
|
-
input: mapToolArgs(b.toolName, b.args || {}, result?.result || ""),
|
|
6062
|
-
_result: result?.result || "",
|
|
6063
|
-
...result?.isError ? { _isError: true } : {},
|
|
6064
|
-
...result?.executionTimeMs ? { _durationMs: result.executionTimeMs } : {}
|
|
6065
|
-
};
|
|
6066
|
-
blocks.push(toolBlock);
|
|
6067
|
-
}
|
|
6068
|
-
}
|
|
6069
|
-
return blocks;
|
|
6070
|
-
}
|
|
6071
|
-
function buildStoreTurnStats(turns) {
|
|
6072
|
-
const turnStats = [];
|
|
6073
|
-
let currentTurnIndex = -1;
|
|
6074
|
-
for (const turn of turns) {
|
|
6075
|
-
if (turn.role === "user") {
|
|
6076
|
-
currentTurnIndex++;
|
|
6077
|
-
turnStats.push({ turnIndex: currentTurnIndex });
|
|
6078
|
-
continue;
|
|
6079
|
-
}
|
|
6080
|
-
if (currentTurnIndex < 0) continue;
|
|
6081
|
-
const current = turnStats[currentTurnIndex];
|
|
6082
|
-
if (!current.model && turn.model) current.model = turn.model;
|
|
6083
|
-
for (const block of turn.blocks) {
|
|
6084
|
-
if (block.type !== "tool_use") continue;
|
|
6085
|
-
const ms = toPositiveMs(block._durationMs);
|
|
6086
|
-
if (ms !== void 0) {
|
|
6087
|
-
current.durationMs = (current.durationMs || 0) + ms;
|
|
6088
|
-
}
|
|
6089
|
-
}
|
|
6090
|
-
}
|
|
6091
|
-
return turnStats;
|
|
6092
|
-
}
|
|
6093
|
-
function mapCursorToolName(name) {
|
|
6094
|
-
const mapping = {
|
|
6095
|
-
Shell: "Bash",
|
|
6096
|
-
shell: "Bash",
|
|
6097
|
-
// Cursor SDK lowercase tool name
|
|
6098
|
-
run_terminal_command_v2: "Bash",
|
|
6099
|
-
run_terminal_cmd: "Bash",
|
|
6100
|
-
Read: "Read",
|
|
6101
|
-
ReadFile: "Read",
|
|
6102
|
-
read: "Read",
|
|
6103
|
-
// Cursor SDK lowercase tool name
|
|
6104
|
-
read_file_v2: "Read",
|
|
6105
|
-
read_file: "Read",
|
|
6106
|
-
read_lints: "ReadLints",
|
|
6107
|
-
Grep: "Grep",
|
|
6108
|
-
ripgrep_raw_search: "Grep",
|
|
6109
|
-
ripgrep: "Grep",
|
|
6110
|
-
rg: "Grep",
|
|
6111
|
-
grep: "Grep",
|
|
6112
|
-
grep_search: "Grep",
|
|
6113
|
-
Glob: "Glob",
|
|
6114
|
-
glob_file_search: "Glob",
|
|
6115
|
-
file_search: "Glob",
|
|
6116
|
-
list_dir: "Glob",
|
|
6117
|
-
list_dir_v2: "Glob",
|
|
6118
|
-
LS: "Glob",
|
|
6119
|
-
StrReplace: "Edit",
|
|
6120
|
-
EditFile: "Edit",
|
|
6121
|
-
edit: "Edit",
|
|
6122
|
-
// Cursor SDK lowercase tool name
|
|
6123
|
-
edit_file_v2: "Edit",
|
|
6124
|
-
edit_file: "Edit",
|
|
6125
|
-
search_replace: "Edit",
|
|
6126
|
-
ApplyPatch: "Edit",
|
|
6127
|
-
apply_patch: "Edit",
|
|
6128
|
-
MultiEdit: "MultiEdit",
|
|
6129
|
-
multi_edit: "MultiEdit",
|
|
6130
|
-
Write: "Write",
|
|
6131
|
-
WriteFile: "Write",
|
|
6132
|
-
write: "Write",
|
|
6133
|
-
Delete: "Delete",
|
|
6134
|
-
delete_file: "Delete",
|
|
6135
|
-
Task: "Agent",
|
|
6136
|
-
task_v2: "Agent",
|
|
6137
|
-
todo_write: "TodoWrite",
|
|
6138
|
-
ask_question: "AskQuestion",
|
|
6139
|
-
semantic_search_full: "SemanticSearch",
|
|
6140
|
-
codebase_search: "SemanticSearch",
|
|
6141
|
-
web_search: "WebSearch",
|
|
6142
|
-
WebFetch: "WebFetch",
|
|
6143
|
-
web_fetch: "WebFetch",
|
|
6144
|
-
create_plan: "Plan"
|
|
6145
|
-
};
|
|
6146
|
-
if (mapping[name]) return mapping[name];
|
|
6147
|
-
if (name.startsWith("mcp-cursor-ide-browser-cursor-ide-browser-browser_")) return "Browser";
|
|
6148
|
-
if (name.startsWith("chrome-devtools-")) return "Browser";
|
|
6149
|
-
return name;
|
|
6150
|
-
}
|
|
6151
|
-
function parseDiffStringSnippet(diffString) {
|
|
6152
|
-
const oldLines = [];
|
|
6153
|
-
const newLines = [];
|
|
6154
|
-
for (const line of diffString.split("\n")) {
|
|
6155
|
-
if (line.startsWith("@@") || line.startsWith("*** ") || line.startsWith("---") || line.startsWith("+++")) {
|
|
6156
|
-
continue;
|
|
6157
|
-
}
|
|
6158
|
-
if (line.startsWith("-")) {
|
|
6159
|
-
oldLines.push(line.slice(1));
|
|
6160
|
-
} else if (line.startsWith("+")) {
|
|
6161
|
-
newLines.push(line.slice(1));
|
|
6162
|
-
} else if (line.startsWith(" ")) {
|
|
6163
|
-
const shared = line.slice(1);
|
|
6164
|
-
oldLines.push(shared);
|
|
6165
|
-
newLines.push(shared);
|
|
6166
|
-
}
|
|
6476
|
+
primary: "global-state",
|
|
6477
|
+
sources: ["cursor/user/globalStorage/state.vscdb"],
|
|
6478
|
+
notes
|
|
6479
|
+
}
|
|
6480
|
+
};
|
|
6481
|
+
} catch {
|
|
6482
|
+
return null;
|
|
6167
6483
|
}
|
|
6168
|
-
return { oldText: oldLines.join("\n"), newText: newLines.join("\n") };
|
|
6169
6484
|
}
|
|
6170
|
-
function
|
|
6171
|
-
|
|
6172
|
-
const
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
if (parsedSnippet.newText) newParts.push(parsedSnippet.newText);
|
|
6485
|
+
function buildToolResultMap(messages) {
|
|
6486
|
+
const map = /* @__PURE__ */ new Map();
|
|
6487
|
+
for (const msg of messages) {
|
|
6488
|
+
if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
|
|
6489
|
+
for (const block of msg.content) {
|
|
6490
|
+
if (block.type === "tool-result" && block.toolCallId) {
|
|
6491
|
+
const resultText = extractToolResultText2(block.result);
|
|
6492
|
+
const topLevelResult = msg.providerOptions?.cursor?.highLevelToolCallResult;
|
|
6493
|
+
const combinedSource = topLevelResult || block.result;
|
|
6494
|
+
map.set(block.toolCallId, {
|
|
6495
|
+
result: resultText,
|
|
6496
|
+
...hasToolError(combinedSource) ? { isError: true } : {},
|
|
6497
|
+
...extractToolExecutionTimeMs(combinedSource) !== void 0 ? { executionTimeMs: extractToolExecutionTimeMs(combinedSource) } : {}
|
|
6498
|
+
});
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6187
6501
|
}
|
|
6188
|
-
|
|
6189
|
-
if (!out.new_string && newParts.length > 0) out.new_string = newParts.join("\n");
|
|
6190
|
-
return out;
|
|
6502
|
+
return map;
|
|
6191
6503
|
}
|
|
6192
|
-
function
|
|
6193
|
-
const
|
|
6194
|
-
const
|
|
6195
|
-
const
|
|
6196
|
-
|
|
6197
|
-
if (
|
|
6504
|
+
function messagesToTurns(messages) {
|
|
6505
|
+
const toolResults = buildToolResultMap(messages);
|
|
6506
|
+
const turns = [];
|
|
6507
|
+
for (const msg of messages) {
|
|
6508
|
+
if (msg.role === "system" || msg.role === "tool") continue;
|
|
6509
|
+
if (msg.role === "user") {
|
|
6510
|
+
const blocks = parseUserContent(msg.content);
|
|
6511
|
+
if (blocks.length > 0) {
|
|
6512
|
+
turns.push({ role: "user", blocks });
|
|
6513
|
+
}
|
|
6198
6514
|
continue;
|
|
6199
6515
|
}
|
|
6200
|
-
if (
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6516
|
+
if (msg.role === "assistant") {
|
|
6517
|
+
const blocks = parseAssistantContent(msg.content, toolResults);
|
|
6518
|
+
if (blocks.length > 0) {
|
|
6519
|
+
turns.push({
|
|
6520
|
+
role: "assistant",
|
|
6521
|
+
blocks,
|
|
6522
|
+
model: extractModel(msg)
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6208
6525
|
}
|
|
6209
6526
|
}
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
new_string: newLines.join("\n"),
|
|
6214
|
-
patch: rawPatch
|
|
6215
|
-
};
|
|
6527
|
+
const turnStats = buildStoreTurnStats(turns);
|
|
6528
|
+
const totalDurationMs = turnStats.length > 0 ? turnStats.reduce((sum, stat12) => sum + (stat12.durationMs || 0), 0) || void 0 : void 0;
|
|
6529
|
+
return { turns, turnStats, totalDurationMs };
|
|
6216
6530
|
}
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
old_string: argsObj.old_string ?? argsObj.oldStr ?? "",
|
|
6221
|
-
new_string: argsObj.new_string ?? argsObj.newStr ?? argsObj.streamingContent ?? argsObj.content ?? ""
|
|
6222
|
-
};
|
|
6223
|
-
if (!mapped.old_string || !mapped.new_string) {
|
|
6224
|
-
const inferred = inferEditStringsFromResult(resultText);
|
|
6225
|
-
if (!mapped.old_string && inferred.old_string) mapped.old_string = inferred.old_string;
|
|
6226
|
-
if (!mapped.new_string && inferred.new_string) mapped.new_string = inferred.new_string;
|
|
6227
|
-
}
|
|
6228
|
-
return mapped;
|
|
6531
|
+
var CURSOR_SYSTEM_CONTEXT_RE = /^<(?:user_info|system_reminder|agent_transcripts|rules|git_status)>/;
|
|
6532
|
+
function isSystemContextText(text) {
|
|
6533
|
+
return CURSOR_SYSTEM_CONTEXT_RE.test(text.trim());
|
|
6229
6534
|
}
|
|
6230
|
-
function
|
|
6231
|
-
|
|
6232
|
-
if (
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
if (
|
|
6236
|
-
|
|
6237
|
-
}
|
|
6238
|
-
return argsObj || {};
|
|
6239
|
-
}
|
|
6240
|
-
if (typeof args === "string") {
|
|
6241
|
-
return { raw: args };
|
|
6242
|
-
}
|
|
6243
|
-
if (!argsObj) return {};
|
|
6244
|
-
if (toolName === "Shell" && argsObj.command) {
|
|
6245
|
-
return {
|
|
6246
|
-
command: argsObj.command,
|
|
6247
|
-
...argsObj.description ? { description: argsObj.description } : {}
|
|
6248
|
-
};
|
|
6249
|
-
}
|
|
6250
|
-
if (toolName === "StrReplace" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
6251
|
-
return mapEditLikeArgs(argsObj, resultText);
|
|
6252
|
-
}
|
|
6253
|
-
if (toolName === "EditFile" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
6254
|
-
return mapEditLikeArgs(argsObj, resultText);
|
|
6255
|
-
}
|
|
6256
|
-
if (toolName === "Edit" && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
6257
|
-
return mapEditLikeArgs(argsObj, resultText);
|
|
6258
|
-
}
|
|
6259
|
-
if ((toolName === "MultiEdit" || toolName === "multi_edit") && (argsObj.path || argsObj.file_path || argsObj.relativeWorkspacePath)) {
|
|
6260
|
-
return {
|
|
6261
|
-
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath,
|
|
6262
|
-
edits: Array.isArray(argsObj.edits) ? argsObj.edits : []
|
|
6263
|
-
};
|
|
6264
|
-
}
|
|
6265
|
-
if ((toolName === "Write" || toolName === "WriteFile") && (argsObj.path || argsObj.relativeWorkspacePath || argsObj.targetFile || argsObj.file_path)) {
|
|
6266
|
-
const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
|
|
6267
|
-
return {
|
|
6268
|
-
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile,
|
|
6269
|
-
content: argsObj.contents ?? argsObj.content ?? codeValue
|
|
6270
|
-
};
|
|
6271
|
-
}
|
|
6272
|
-
if ((toolName === "Read" || toolName === "ReadFile") && argsObj.path) {
|
|
6273
|
-
return { file_path: argsObj.path };
|
|
6274
|
-
}
|
|
6275
|
-
if (toolName === "run_terminal_command_v2" && argsObj.command) {
|
|
6276
|
-
return {
|
|
6277
|
-
command: argsObj.command,
|
|
6278
|
-
...argsObj.commandDescription ? { description: argsObj.commandDescription } : {},
|
|
6279
|
-
...argsObj.cwd ? { cwd: argsObj.cwd } : {}
|
|
6280
|
-
};
|
|
6281
|
-
}
|
|
6282
|
-
if (toolName === "run_terminal_cmd" && argsObj.command) {
|
|
6283
|
-
return {
|
|
6284
|
-
command: argsObj.command,
|
|
6285
|
-
...argsObj.cwd ? { cwd: argsObj.cwd } : {},
|
|
6286
|
-
...argsObj.requireUserApproval !== void 0 ? { requireUserApproval: Boolean(argsObj.requireUserApproval) } : {}
|
|
6287
|
-
};
|
|
6288
|
-
}
|
|
6289
|
-
if (toolName === "read_file_v2" && argsObj.targetFile) {
|
|
6290
|
-
return { file_path: argsObj.targetFile };
|
|
6291
|
-
}
|
|
6292
|
-
if (toolName === "read_file" && argsObj.targetFile) {
|
|
6293
|
-
return { file_path: argsObj.targetFile };
|
|
6294
|
-
}
|
|
6295
|
-
if (toolName === "edit_file_v2" && argsObj.relativeWorkspacePath) {
|
|
6296
|
-
return {
|
|
6297
|
-
file_path: argsObj.relativeWorkspacePath,
|
|
6298
|
-
new_string: argsObj.streamingContent ?? ""
|
|
6299
|
-
};
|
|
6300
|
-
}
|
|
6301
|
-
if (toolName === "edit_file" || toolName === "search_replace") {
|
|
6302
|
-
return mapEditLikeArgs(argsObj, resultText);
|
|
6303
|
-
}
|
|
6304
|
-
if (toolName === "write" && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.targetFile)) {
|
|
6305
|
-
const codeValue = typeof argsObj.code === "string" ? argsObj.code : argsObj.code && typeof argsObj.code === "object" && typeof argsObj.code.code === "string" ? argsObj.code.code : "";
|
|
6306
|
-
return {
|
|
6307
|
-
file_path: argsObj.relativeWorkspacePath ?? argsObj.path ?? argsObj.targetFile,
|
|
6308
|
-
content: argsObj.content ?? argsObj.contents ?? codeValue
|
|
6309
|
-
};
|
|
6310
|
-
}
|
|
6311
|
-
if ((toolName === "Delete" || toolName === "delete_file") && (argsObj.relativeWorkspacePath || argsObj.path || argsObj.file_path || argsObj.targetFile)) {
|
|
6312
|
-
return {
|
|
6313
|
-
file_path: argsObj.file_path ?? argsObj.path ?? argsObj.relativeWorkspacePath ?? argsObj.targetFile
|
|
6314
|
-
};
|
|
6315
|
-
}
|
|
6316
|
-
if (toolName === "list_dir" || toolName === "list_dir_v2" || toolName === "LS") {
|
|
6317
|
-
return {
|
|
6318
|
-
path: argsObj.targetDirectory ?? argsObj.target_directory ?? ""
|
|
6319
|
-
};
|
|
6320
|
-
}
|
|
6321
|
-
if (toolName === "glob_file_search") {
|
|
6322
|
-
return {
|
|
6323
|
-
pattern: argsObj.globPattern ?? "",
|
|
6324
|
-
path: argsObj.targetDirectory ?? ""
|
|
6325
|
-
};
|
|
6326
|
-
}
|
|
6327
|
-
if (toolName === "file_search") {
|
|
6328
|
-
return {
|
|
6329
|
-
pattern: argsObj.pattern ?? argsObj.query ?? argsObj.searchTerm ?? "",
|
|
6330
|
-
path: argsObj.path ?? argsObj.targetDirectory ?? ""
|
|
6331
|
-
};
|
|
6332
|
-
}
|
|
6333
|
-
if (toolName === "ripgrep_raw_search") {
|
|
6334
|
-
return {
|
|
6335
|
-
pattern: argsObj.pattern ?? "",
|
|
6336
|
-
path: argsObj.path ?? "",
|
|
6337
|
-
...argsObj.glob ? { glob: argsObj.glob } : {},
|
|
6338
|
-
...argsObj.caseInsensitive !== void 0 ? { case_insensitive: Boolean(argsObj.caseInsensitive) } : {}
|
|
6339
|
-
};
|
|
6340
|
-
}
|
|
6341
|
-
if (toolName === "ripgrep") {
|
|
6342
|
-
return {
|
|
6343
|
-
pattern: argsObj.pattern ?? argsObj.query ?? "",
|
|
6344
|
-
path: argsObj.path ?? argsObj.targetDirectory ?? "",
|
|
6345
|
-
...argsObj.glob ? { glob: argsObj.glob } : {}
|
|
6346
|
-
};
|
|
6535
|
+
function parseUserContent(content) {
|
|
6536
|
+
if (!content) return [];
|
|
6537
|
+
if (typeof content === "string") {
|
|
6538
|
+
if (isSystemContextText(content)) return [];
|
|
6539
|
+
const cleaned = sanitizeCursorUserText(content);
|
|
6540
|
+
if (isSystemContextText(cleaned)) return [];
|
|
6541
|
+
return cleaned ? [{ type: "text", text: cleaned }] : [];
|
|
6347
6542
|
}
|
|
6348
|
-
|
|
6349
|
-
|
|
6543
|
+
const blocks = [];
|
|
6544
|
+
for (const b of content) {
|
|
6545
|
+
if (b.type === "text" && b.text) {
|
|
6546
|
+
if (isSystemContextText(b.text)) continue;
|
|
6547
|
+
const cleaned = sanitizeCursorUserText(b.text);
|
|
6548
|
+
if (isSystemContextText(cleaned)) continue;
|
|
6549
|
+
if (cleaned) blocks.push({ type: "text", text: cleaned });
|
|
6550
|
+
}
|
|
6350
6551
|
}
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6552
|
+
return blocks;
|
|
6553
|
+
}
|
|
6554
|
+
function parseAssistantContent(content, toolResults) {
|
|
6555
|
+
if (!content) return [];
|
|
6556
|
+
if (typeof content === "string") {
|
|
6557
|
+
const cleaned = sanitizeCursorAssistantText(content, false);
|
|
6558
|
+
return cleaned ? [{ type: "text", text: cleaned }] : [];
|
|
6357
6559
|
}
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6560
|
+
const blocks = [];
|
|
6561
|
+
const hasToolCalls = content.some((block) => block.type === "tool-call");
|
|
6562
|
+
for (const b of content) {
|
|
6563
|
+
if (b.type === "reasoning" && b.text?.trim()) {
|
|
6564
|
+
const thinking = sanitizeCursorReasoningText(b.text);
|
|
6565
|
+
if (thinking) blocks.push({ type: "thinking", thinking });
|
|
6566
|
+
} else if (b.type === "text" && b.text?.trim()) {
|
|
6567
|
+
const text = sanitizeCursorAssistantText(b.text, hasToolCalls);
|
|
6568
|
+
if (text) blocks.push({ type: "text", text });
|
|
6569
|
+
} else if (b.type === "tool-call" && b.toolCallId && b.toolName) {
|
|
6570
|
+
const result = toolResults.get(b.toolCallId);
|
|
6571
|
+
const toolBlock = {
|
|
6572
|
+
type: "tool_use",
|
|
6573
|
+
id: b.toolCallId,
|
|
6574
|
+
name: mapCursorToolName(b.toolName),
|
|
6575
|
+
input: mapToolArgs(b.toolName, b.args || {}, result?.result || ""),
|
|
6576
|
+
_result: result?.result || "",
|
|
6577
|
+
...result?.isError ? { _isError: true } : {},
|
|
6578
|
+
...result?.executionTimeMs ? { _durationMs: result.executionTimeMs } : {}
|
|
6579
|
+
};
|
|
6580
|
+
blocks.push(toolBlock);
|
|
6581
|
+
}
|
|
6364
6582
|
}
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6583
|
+
return blocks;
|
|
6584
|
+
}
|
|
6585
|
+
function buildStoreTurnStats(turns) {
|
|
6586
|
+
const turnStats = [];
|
|
6587
|
+
let currentTurnIndex = -1;
|
|
6588
|
+
for (const turn of turns) {
|
|
6589
|
+
if (turn.role === "user") {
|
|
6590
|
+
currentTurnIndex++;
|
|
6591
|
+
turnStats.push({ turnIndex: currentTurnIndex });
|
|
6592
|
+
continue;
|
|
6593
|
+
}
|
|
6594
|
+
if (currentTurnIndex < 0) continue;
|
|
6595
|
+
const current = turnStats[currentTurnIndex];
|
|
6596
|
+
if (!current.model && turn.model) current.model = turn.model;
|
|
6597
|
+
for (const block of turn.blocks) {
|
|
6598
|
+
if (block.type !== "tool_use") continue;
|
|
6599
|
+
const ms = toPositiveMs(block._durationMs);
|
|
6600
|
+
if (ms !== void 0) {
|
|
6601
|
+
current.durationMs = (current.durationMs || 0) + ms;
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6373
6604
|
}
|
|
6374
|
-
return
|
|
6605
|
+
return turnStats;
|
|
6375
6606
|
}
|
|
6376
6607
|
function extractModel(msg) {
|
|
6377
6608
|
if (!Array.isArray(msg.content)) return void 0;
|
|
@@ -6385,10 +6616,10 @@ function extractModel(msg) {
|
|
|
6385
6616
|
// src/providers/cursor/sdk-reader.ts
|
|
6386
6617
|
import { execFile as execFile2 } from "child_process";
|
|
6387
6618
|
import { readdir as readdir7, readFile as readFile11, stat as stat6 } from "fs/promises";
|
|
6388
|
-
import { homedir as
|
|
6389
|
-
import { join as
|
|
6619
|
+
import { homedir as homedir10 } from "os";
|
|
6620
|
+
import { join as join11 } from "path";
|
|
6390
6621
|
import { promisify as promisify2 } from "util";
|
|
6391
|
-
var CURSOR_PROJECTS_ROOT =
|
|
6622
|
+
var CURSOR_PROJECTS_ROOT = join11(homedir10(), ".cursor", "projects");
|
|
6392
6623
|
var SDK_STORE_SUBDIR = "sdk-agent-store";
|
|
6393
6624
|
var SDK_INDEX_DB_BASENAME = "index.db";
|
|
6394
6625
|
var MIN_INDEX_DB_SIZE = 1024;
|
|
@@ -6742,12 +6973,12 @@ async function listSdkIndexDbPaths(projectsRoot) {
|
|
|
6742
6973
|
const perProjectPaths = await Promise.all(
|
|
6743
6974
|
projects.map(async (project) => {
|
|
6744
6975
|
const out = [];
|
|
6745
|
-
const sdkRoot =
|
|
6976
|
+
const sdkRoot = join11(projectsRoot, project, SDK_STORE_SUBDIR);
|
|
6746
6977
|
const sdkStat = await stat6(sdkRoot).catch(() => null);
|
|
6747
6978
|
if (!sdkStat?.isDirectory()) return out;
|
|
6748
6979
|
const projectHashes = await readdir7(sdkRoot).catch(() => []);
|
|
6749
6980
|
for (const hash of projectHashes) {
|
|
6750
|
-
const dbPath =
|
|
6981
|
+
const dbPath = join11(sdkRoot, hash, SDK_INDEX_DB_BASENAME);
|
|
6751
6982
|
const dbStat = await stat6(dbPath).catch(() => null);
|
|
6752
6983
|
if (!dbStat?.isFile() || dbStat.size < MIN_INDEX_DB_SIZE) continue;
|
|
6753
6984
|
out.push(dbPath);
|
|
@@ -6782,7 +7013,7 @@ function safeJsonStringify(value) {
|
|
|
6782
7013
|
}
|
|
6783
7014
|
|
|
6784
7015
|
// src/providers/cursor/discover.ts
|
|
6785
|
-
var CURSOR_DIR =
|
|
7016
|
+
var CURSOR_DIR = join12(homedir11(), ".cursor", "projects");
|
|
6786
7017
|
var PROJECT_DISCOVERY_CONCURRENCY = 6;
|
|
6787
7018
|
var TRANSCRIPT_INFO_CONCURRENCY = 6;
|
|
6788
7019
|
var ENTRY_STAT_CONCURRENCY = 32;
|
|
@@ -6821,14 +7052,14 @@ async function discoverCursorSessions() {
|
|
|
6821
7052
|
return sessions;
|
|
6822
7053
|
}
|
|
6823
7054
|
async function discoverProjectSessions(projDir) {
|
|
6824
|
-
const transcriptsDir =
|
|
7055
|
+
const transcriptsDir = join12(CURSOR_DIR, projDir, "agent-transcripts");
|
|
6825
7056
|
const dirStat = await stat7(transcriptsDir).catch(() => null);
|
|
6826
7057
|
if (!dirStat?.isDirectory()) return [];
|
|
6827
7058
|
const project = await decodeProjectDir2(projDir);
|
|
6828
7059
|
const transcriptEntries = await collectTranscriptEntries(transcriptsDir);
|
|
6829
7060
|
if (transcriptEntries.length === 0) return [];
|
|
6830
7061
|
transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
6831
|
-
const toolEntries = await collectToolEntries(
|
|
7062
|
+
const toolEntries = await collectToolEntries(join12(CURSOR_DIR, projDir, "agent-tools"));
|
|
6832
7063
|
toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
6833
7064
|
const jobs = [];
|
|
6834
7065
|
let toolStart = 0;
|
|
@@ -6927,7 +7158,7 @@ async function resolveEncodedProjectParts(parts, idx, current) {
|
|
|
6927
7158
|
);
|
|
6928
7159
|
for (let end = idx + 1; end <= parts.length; end++) {
|
|
6929
7160
|
const candidate = parts.slice(idx, end).join("-");
|
|
6930
|
-
const candidatePath =
|
|
7161
|
+
const candidatePath = join12(current, candidate);
|
|
6931
7162
|
if (!dirNames.has(candidate)) {
|
|
6932
7163
|
if (process.platform !== "win32") continue;
|
|
6933
7164
|
const candidateStat = await stat7(candidatePath).catch(() => null);
|
|
@@ -7017,7 +7248,7 @@ async function collectTranscriptEntries(transcriptsDir) {
|
|
|
7017
7248
|
return [];
|
|
7018
7249
|
}
|
|
7019
7250
|
const transcripts = await mapLimit(entries, ENTRY_STAT_CONCURRENCY, async (entry) => {
|
|
7020
|
-
const entryPath =
|
|
7251
|
+
const entryPath = join12(transcriptsDir, entry);
|
|
7021
7252
|
const entryStat = await stat7(entryPath).catch(() => null);
|
|
7022
7253
|
if (!entryStat) return null;
|
|
7023
7254
|
if (entry.endsWith(".jsonl") && entryStat.isFile()) {
|
|
@@ -7028,7 +7259,7 @@ async function collectTranscriptEntries(transcriptsDir) {
|
|
|
7028
7259
|
};
|
|
7029
7260
|
}
|
|
7030
7261
|
if (!entryStat.isDirectory()) return null;
|
|
7031
|
-
const innerPath =
|
|
7262
|
+
const innerPath = join12(entryPath, `${entry}.jsonl`);
|
|
7032
7263
|
const innerStat = await stat7(innerPath).catch(() => null);
|
|
7033
7264
|
if (!innerStat?.isFile()) return null;
|
|
7034
7265
|
return {
|
|
@@ -7050,7 +7281,7 @@ async function collectToolEntries(toolDir) {
|
|
|
7050
7281
|
}
|
|
7051
7282
|
const toolFiles = entries.filter((entry) => entry.endsWith(".txt"));
|
|
7052
7283
|
const tools = await mapLimit(toolFiles, ENTRY_STAT_CONCURRENCY, async (entry) => {
|
|
7053
|
-
const entryPath =
|
|
7284
|
+
const entryPath = join12(toolDir, entry);
|
|
7054
7285
|
const entryStat = await stat7(entryPath).catch(() => null);
|
|
7055
7286
|
if (!entryStat?.isFile()) return null;
|
|
7056
7287
|
return { path: entryPath, mtimeMs: entryStat.mtimeMs };
|
|
@@ -7076,8 +7307,8 @@ async function mapLimit(items, concurrency, worker) {
|
|
|
7076
7307
|
|
|
7077
7308
|
// src/providers/cursor/parser.ts
|
|
7078
7309
|
import { readdir as readdir9, readFile as readFile13, stat as stat8 } from "fs/promises";
|
|
7079
|
-
import { homedir as
|
|
7080
|
-
import { basename as basename4, extname as extname2, join as
|
|
7310
|
+
import { homedir as homedir12 } from "os";
|
|
7311
|
+
import { basename as basename4, extname as extname2, join as join13 } from "path";
|
|
7081
7312
|
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;
|
|
7082
7313
|
function toErrorMessage(err) {
|
|
7083
7314
|
if (err instanceof Error && err.message) return err.message;
|
|
@@ -7433,7 +7664,7 @@ function normalizeImagePlaceholderLines(text) {
|
|
|
7433
7664
|
}
|
|
7434
7665
|
function resolveImagePath(pathValue) {
|
|
7435
7666
|
if (pathValue.startsWith("~/") || pathValue.startsWith("~\\")) {
|
|
7436
|
-
return
|
|
7667
|
+
return join13(homedir12(), pathValue.slice(2));
|
|
7437
7668
|
}
|
|
7438
7669
|
return pathValue;
|
|
7439
7670
|
}
|
|
@@ -7596,203 +7827,840 @@ function mergeJsonlSupplementsIntoCursorTurns(primaryTurns, jsonlTurns) {
|
|
|
7596
7827
|
const withThinking = mergeJsonlThinkingIntoCursorTurns(primaryTurns, jsonlTurns);
|
|
7597
7828
|
return mergeJsonlUserImagesIntoCursorTurns(withThinking, jsonlTurns);
|
|
7598
7829
|
}
|
|
7599
|
-
async function sortByMtime(paths) {
|
|
7600
|
-
const entries = [];
|
|
7601
|
-
for (const path of paths) {
|
|
7602
|
-
const st = await stat8(path).catch(() => null);
|
|
7603
|
-
if (!st?.isFile()) continue;
|
|
7604
|
-
entries.push({ path, mtimeMs: st.mtimeMs });
|
|
7830
|
+
async function sortByMtime(paths) {
|
|
7831
|
+
const entries = [];
|
|
7832
|
+
for (const path of paths) {
|
|
7833
|
+
const st = await stat8(path).catch(() => null);
|
|
7834
|
+
if (!st?.isFile()) continue;
|
|
7835
|
+
entries.push({ path, mtimeMs: st.mtimeMs });
|
|
7836
|
+
}
|
|
7837
|
+
entries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
7838
|
+
return entries.map((e) => e.path);
|
|
7839
|
+
}
|
|
7840
|
+
async function inferToolPaths(transcriptPaths) {
|
|
7841
|
+
const toolPaths = [];
|
|
7842
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7843
|
+
const projects = /* @__PURE__ */ new Map();
|
|
7844
|
+
for (const transcriptPath of transcriptPaths) {
|
|
7845
|
+
const projectRoot = getCursorProjectRoot(transcriptPath);
|
|
7846
|
+
if (!projectRoot) continue;
|
|
7847
|
+
if (!projects.has(projectRoot)) projects.set(projectRoot, /* @__PURE__ */ new Set());
|
|
7848
|
+
projects.get(projectRoot)?.add(transcriptPath);
|
|
7849
|
+
}
|
|
7850
|
+
for (const [projectRoot, selectedPaths] of projects.entries()) {
|
|
7851
|
+
const transcriptsDir = join13(projectRoot, "agent-transcripts");
|
|
7852
|
+
const toolDir = join13(projectRoot, "agent-tools");
|
|
7853
|
+
const transcriptEntries = await collectTranscriptEntries2(transcriptsDir);
|
|
7854
|
+
const toolEntries = await collectToolEntries2(toolDir);
|
|
7855
|
+
if (transcriptEntries.length === 0 || toolEntries.length === 0) continue;
|
|
7856
|
+
transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
7857
|
+
toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
7858
|
+
for (let i = 0; i < transcriptEntries.length; i++) {
|
|
7859
|
+
const transcript = transcriptEntries[i];
|
|
7860
|
+
if (!selectedPaths.has(transcript.path)) continue;
|
|
7861
|
+
const prevMtimeMs = i === 0 ? Number.NEGATIVE_INFINITY : transcriptEntries[i - 1].mtimeMs;
|
|
7862
|
+
for (const tool of toolEntries) {
|
|
7863
|
+
if (tool.mtimeMs <= prevMtimeMs || tool.mtimeMs > transcript.mtimeMs) continue;
|
|
7864
|
+
if (seen.has(tool.path)) continue;
|
|
7865
|
+
seen.add(tool.path);
|
|
7866
|
+
toolPaths.push(tool.path);
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
return sortByMtime(toolPaths);
|
|
7871
|
+
}
|
|
7872
|
+
async function collectTranscriptEntries2(transcriptsDir) {
|
|
7873
|
+
let entries;
|
|
7874
|
+
try {
|
|
7875
|
+
entries = await readdir9(transcriptsDir);
|
|
7876
|
+
} catch {
|
|
7877
|
+
return [];
|
|
7878
|
+
}
|
|
7879
|
+
const transcripts = [];
|
|
7880
|
+
for (const entry of entries) {
|
|
7881
|
+
const entryPath = join13(transcriptsDir, entry);
|
|
7882
|
+
const st = await stat8(entryPath).catch(() => null);
|
|
7883
|
+
if (!st) continue;
|
|
7884
|
+
if (entry.endsWith(".jsonl") && st.isFile()) {
|
|
7885
|
+
transcripts.push({ path: entryPath, mtimeMs: st.mtimeMs });
|
|
7886
|
+
continue;
|
|
7887
|
+
}
|
|
7888
|
+
if (!st.isDirectory()) continue;
|
|
7889
|
+
const nested = join13(entryPath, `${entry}.jsonl`);
|
|
7890
|
+
const nestedStat = await stat8(nested).catch(() => null);
|
|
7891
|
+
if (!nestedStat?.isFile()) continue;
|
|
7892
|
+
transcripts.push({ path: nested, mtimeMs: nestedStat.mtimeMs });
|
|
7893
|
+
}
|
|
7894
|
+
return transcripts;
|
|
7895
|
+
}
|
|
7896
|
+
async function collectToolEntries2(toolDir) {
|
|
7897
|
+
let entries;
|
|
7898
|
+
try {
|
|
7899
|
+
entries = await readdir9(toolDir);
|
|
7900
|
+
} catch {
|
|
7901
|
+
return [];
|
|
7902
|
+
}
|
|
7903
|
+
const tools = [];
|
|
7904
|
+
for (const entry of entries) {
|
|
7905
|
+
if (!entry.endsWith(".txt")) continue;
|
|
7906
|
+
const entryPath = join13(toolDir, entry);
|
|
7907
|
+
const st = await stat8(entryPath).catch(() => null);
|
|
7908
|
+
if (!st?.isFile()) continue;
|
|
7909
|
+
tools.push({ path: entryPath, mtimeMs: st.mtimeMs });
|
|
7910
|
+
}
|
|
7911
|
+
return tools;
|
|
7912
|
+
}
|
|
7913
|
+
function getCursorProjectRoot(transcriptPath) {
|
|
7914
|
+
const parts = transcriptPath.split(/[/\\]agent-transcripts[/\\]/);
|
|
7915
|
+
if (parts.length < 2) return null;
|
|
7916
|
+
return parts[0];
|
|
7917
|
+
}
|
|
7918
|
+
async function loadToolEvents(toolPaths) {
|
|
7919
|
+
const events = [];
|
|
7920
|
+
for (const path of toolPaths) {
|
|
7921
|
+
const content = await readFile13(path, "utf-8").catch(() => "");
|
|
7922
|
+
const result = content.trim();
|
|
7923
|
+
if (!result) continue;
|
|
7924
|
+
const st = await stat8(path).catch(() => null);
|
|
7925
|
+
const id = basename4(path, extname2(path));
|
|
7926
|
+
events.push({
|
|
7927
|
+
id,
|
|
7928
|
+
name: inferToolName(result),
|
|
7929
|
+
input: { source: basename4(path) },
|
|
7930
|
+
result,
|
|
7931
|
+
timestamp: st ? new Date(st.mtimeMs).toISOString() : void 0
|
|
7932
|
+
});
|
|
7933
|
+
}
|
|
7934
|
+
return events;
|
|
7935
|
+
}
|
|
7936
|
+
function inferToolName(result) {
|
|
7937
|
+
const firstLine = result.split("\n", 1)[0] || "";
|
|
7938
|
+
if (result.startsWith("diff --git")) return "Diff";
|
|
7939
|
+
if (firstLine.startsWith("http://") || firstLine.startsWith("https://")) return "WebFetch";
|
|
7940
|
+
if (firstLine.startsWith("{") || firstLine.startsWith("[")) return "API";
|
|
7941
|
+
if (/\$ |\bCommand\b|^\w+(\s+\w+){0,3}\s+-/.test(firstLine)) return "Bash";
|
|
7942
|
+
return "ToolOutput";
|
|
7943
|
+
}
|
|
7944
|
+
function attachToolEvents(turns, tools) {
|
|
7945
|
+
const markerBlocks = [];
|
|
7946
|
+
for (const turn of turns) {
|
|
7947
|
+
if (turn.role !== "assistant") continue;
|
|
7948
|
+
for (let bi = 0; bi < turn.blocks.length; bi++) {
|
|
7949
|
+
const block = turn.blocks[bi];
|
|
7950
|
+
if (block.type === "tool_use" && block._isPendingMarker) {
|
|
7951
|
+
markerBlocks.push({ block, turn, blockIndex: bi });
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
const paired = Math.min(markerBlocks.length, tools.length);
|
|
7956
|
+
for (let i = 0; i < paired; i++) {
|
|
7957
|
+
const marker = markerBlocks[i];
|
|
7958
|
+
const tool = tools[i];
|
|
7959
|
+
marker.block.name = tool.name;
|
|
7960
|
+
marker.block.input = {
|
|
7961
|
+
...marker.block.input,
|
|
7962
|
+
...tool.input
|
|
7963
|
+
};
|
|
7964
|
+
marker.block._result = tool.result;
|
|
7965
|
+
const durationMs = durationBetween(marker.turn.timestamp, tool.timestamp);
|
|
7966
|
+
if (durationMs !== void 0) marker.block._durationMs = durationMs;
|
|
7967
|
+
marker.block._isPendingMarker = void 0;
|
|
7968
|
+
if (!marker.turn.timestamp && tool.timestamp) {
|
|
7969
|
+
marker.turn.timestamp = tool.timestamp;
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
for (let i = paired; i < markerBlocks.length; i++) {
|
|
7973
|
+
const { turn, blockIndex, block } = markerBlocks[i];
|
|
7974
|
+
const markerText = typeof block.input?.marker === "string" ? block.input.marker : "";
|
|
7975
|
+
const thinkingBlock = {
|
|
7976
|
+
type: "thinking",
|
|
7977
|
+
thinking: block.name || markerText
|
|
7978
|
+
};
|
|
7979
|
+
turn.blocks[blockIndex] = thinkingBlock;
|
|
7980
|
+
}
|
|
7981
|
+
for (let i = paired; i < tools.length; i++) {
|
|
7982
|
+
const tool = tools[i];
|
|
7983
|
+
turns.push({
|
|
7984
|
+
role: "assistant",
|
|
7985
|
+
timestamp: tool.timestamp,
|
|
7986
|
+
blocks: [toToolUseBlock(tool)]
|
|
7987
|
+
});
|
|
7988
|
+
}
|
|
7989
|
+
}
|
|
7990
|
+
function toToolUseBlock(tool) {
|
|
7991
|
+
return {
|
|
7992
|
+
type: "tool_use",
|
|
7993
|
+
id: tool.id,
|
|
7994
|
+
name: tool.name,
|
|
7995
|
+
input: tool.input,
|
|
7996
|
+
_result: tool.result
|
|
7997
|
+
};
|
|
7998
|
+
}
|
|
7999
|
+
function durationBetween(start, end) {
|
|
8000
|
+
if (!start || !end) return void 0;
|
|
8001
|
+
const startMs = Date.parse(start);
|
|
8002
|
+
const endMs = Date.parse(end);
|
|
8003
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs) return void 0;
|
|
8004
|
+
return Math.round(endMs - startMs);
|
|
8005
|
+
}
|
|
8006
|
+
function splitToolMarker(text) {
|
|
8007
|
+
const trimmed = text.trim();
|
|
8008
|
+
const single = trimmed.match(/^\*\*([^*\n]{2,120})\*\*$/);
|
|
8009
|
+
if (single) return { markerName: single[1].trim() };
|
|
8010
|
+
const trailing = trimmed.match(/^([\s\S]*?)\n+\*\*([^*\n]{2,120})\*\*$/);
|
|
8011
|
+
if (trailing) {
|
|
8012
|
+
const textBody = trailing[1].trim();
|
|
8013
|
+
return {
|
|
8014
|
+
markerName: trailing[2].trim(),
|
|
8015
|
+
...textBody ? { textBody } : {}
|
|
8016
|
+
};
|
|
8017
|
+
}
|
|
8018
|
+
return void 0;
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
// src/providers/cursor/index.ts
|
|
8022
|
+
var cursorProvider = {
|
|
8023
|
+
name: "cursor",
|
|
8024
|
+
displayName: "Cursor",
|
|
8025
|
+
discover: discoverCursorSessions,
|
|
8026
|
+
parse: (filePaths, sessionInfo) => parseCursorSession(filePaths, sessionInfo)
|
|
8027
|
+
};
|
|
8028
|
+
|
|
8029
|
+
// src/providers/pi/discover.ts
|
|
8030
|
+
import { createReadStream as createReadStream4 } from "fs";
|
|
8031
|
+
import { readdir as readdir10, stat as stat9 } from "fs/promises";
|
|
8032
|
+
import { basename as basename5, join as join15 } from "path";
|
|
8033
|
+
import { createInterface as createInterface4 } from "readline";
|
|
8034
|
+
|
|
8035
|
+
// src/providers/pi/config.ts
|
|
8036
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
8037
|
+
import { homedir as homedir13 } from "os";
|
|
8038
|
+
import { join as join14 } from "path";
|
|
8039
|
+
function getPiAgentDir() {
|
|
8040
|
+
return process.env.PI_CODING_AGENT_DIR || join14(homedir13(), ".pi", "agent");
|
|
8041
|
+
}
|
|
8042
|
+
function getPiSessionsDir() {
|
|
8043
|
+
return process.env.PI_CODING_AGENT_SESSION_DIR || join14(getPiAgentDir(), "sessions");
|
|
8044
|
+
}
|
|
8045
|
+
async function readPiModelContextWindows() {
|
|
8046
|
+
const result = /* @__PURE__ */ new Map();
|
|
8047
|
+
let raw;
|
|
8048
|
+
try {
|
|
8049
|
+
raw = await readFile14(join14(getPiAgentDir(), "models.json"), "utf-8");
|
|
8050
|
+
} catch {
|
|
8051
|
+
return result;
|
|
7605
8052
|
}
|
|
7606
|
-
|
|
7607
|
-
|
|
8053
|
+
let parsed;
|
|
8054
|
+
try {
|
|
8055
|
+
parsed = JSON.parse(raw);
|
|
8056
|
+
} catch {
|
|
8057
|
+
return result;
|
|
8058
|
+
}
|
|
8059
|
+
collectModelContextWindows(parsed, result);
|
|
8060
|
+
return result;
|
|
7608
8061
|
}
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
const projectRoot = getCursorProjectRoot(transcriptPath);
|
|
7615
|
-
if (!projectRoot) continue;
|
|
7616
|
-
if (!projects.has(projectRoot)) projects.set(projectRoot, /* @__PURE__ */ new Set());
|
|
7617
|
-
projects.get(projectRoot)?.add(transcriptPath);
|
|
8062
|
+
function collectModelContextWindows(value, result) {
|
|
8063
|
+
if (!value || typeof value !== "object") return;
|
|
8064
|
+
if (Array.isArray(value)) {
|
|
8065
|
+
for (const item of value) collectModelContextWindows(item, result);
|
|
8066
|
+
return;
|
|
7618
8067
|
}
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
const transcriptEntries = await collectTranscriptEntries2(transcriptsDir);
|
|
7623
|
-
const toolEntries = await collectToolEntries2(toolDir);
|
|
7624
|
-
if (transcriptEntries.length === 0 || toolEntries.length === 0) continue;
|
|
7625
|
-
transcriptEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
7626
|
-
toolEntries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
7627
|
-
for (let i = 0; i < transcriptEntries.length; i++) {
|
|
7628
|
-
const transcript = transcriptEntries[i];
|
|
7629
|
-
if (!selectedPaths.has(transcript.path)) continue;
|
|
7630
|
-
const prevMtimeMs = i === 0 ? Number.NEGATIVE_INFINITY : transcriptEntries[i - 1].mtimeMs;
|
|
7631
|
-
for (const tool of toolEntries) {
|
|
7632
|
-
if (tool.mtimeMs <= prevMtimeMs || tool.mtimeMs > transcript.mtimeMs) continue;
|
|
7633
|
-
if (seen.has(tool.path)) continue;
|
|
7634
|
-
seen.add(tool.path);
|
|
7635
|
-
toolPaths.push(tool.path);
|
|
7636
|
-
}
|
|
7637
|
-
}
|
|
8068
|
+
const obj = value;
|
|
8069
|
+
if (typeof obj.id === "string" && typeof obj.contextWindow === "number") {
|
|
8070
|
+
result.set(obj.id, obj.contextWindow);
|
|
7638
8071
|
}
|
|
7639
|
-
|
|
8072
|
+
for (const nested of Object.values(obj)) collectModelContextWindows(nested, result);
|
|
7640
8073
|
}
|
|
7641
|
-
|
|
7642
|
-
|
|
8074
|
+
|
|
8075
|
+
// src/providers/pi/discover.ts
|
|
8076
|
+
var PROMPT_SCAN_LIMIT = 2;
|
|
8077
|
+
async function discoverPiSessions() {
|
|
8078
|
+
const sessionsRoot = getPiSessionsDir();
|
|
8079
|
+
const sessions = [];
|
|
8080
|
+
let projectDirs;
|
|
7643
8081
|
try {
|
|
7644
|
-
|
|
8082
|
+
projectDirs = await readdir10(sessionsRoot);
|
|
7645
8083
|
} catch {
|
|
7646
|
-
return
|
|
8084
|
+
return sessions;
|
|
7647
8085
|
}
|
|
7648
|
-
const
|
|
7649
|
-
|
|
7650
|
-
const
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
8086
|
+
for (const projectDir of projectDirs) {
|
|
8087
|
+
const projectPath = join15(sessionsRoot, projectDir);
|
|
8088
|
+
const projectStat = await stat9(projectPath).catch(() => null);
|
|
8089
|
+
if (!projectStat?.isDirectory()) continue;
|
|
8090
|
+
let files;
|
|
8091
|
+
try {
|
|
8092
|
+
files = await readdir10(projectPath);
|
|
8093
|
+
} catch {
|
|
7655
8094
|
continue;
|
|
7656
8095
|
}
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
8096
|
+
for (const file of files) {
|
|
8097
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
8098
|
+
const filePath = join15(projectPath, file);
|
|
8099
|
+
const fileStat = await stat9(filePath).catch(() => null);
|
|
8100
|
+
if (!fileStat) continue;
|
|
8101
|
+
const info = await extractPiSessionInfo(filePath, fileStat.size, projectDir);
|
|
8102
|
+
if (info) sessions.push(info);
|
|
8103
|
+
}
|
|
7662
8104
|
}
|
|
7663
|
-
|
|
8105
|
+
sessions.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
8106
|
+
return sessions;
|
|
7664
8107
|
}
|
|
7665
|
-
async function
|
|
7666
|
-
let
|
|
8108
|
+
async function extractPiSessionInfo(filePath, fileSize, encodedProjectDir) {
|
|
8109
|
+
let header;
|
|
8110
|
+
let timestamp = "";
|
|
8111
|
+
let lineCount = 0;
|
|
8112
|
+
let promptCount = 0;
|
|
8113
|
+
let toolCallCount = 0;
|
|
8114
|
+
let editCountEst = 0;
|
|
8115
|
+
let model;
|
|
8116
|
+
let title;
|
|
8117
|
+
const prompts = [];
|
|
8118
|
+
const input = createReadStream4(filePath, { encoding: "utf-8" });
|
|
8119
|
+
const rl = createInterface4({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
7667
8120
|
try {
|
|
7668
|
-
|
|
8121
|
+
for await (const rawLine of rl) {
|
|
8122
|
+
const line = rawLine.trim();
|
|
8123
|
+
if (!line) continue;
|
|
8124
|
+
lineCount++;
|
|
8125
|
+
let entry;
|
|
8126
|
+
try {
|
|
8127
|
+
entry = JSON.parse(line);
|
|
8128
|
+
} catch {
|
|
8129
|
+
continue;
|
|
8130
|
+
}
|
|
8131
|
+
if (typeof entry.timestamp === "string" && entry.timestamp) {
|
|
8132
|
+
timestamp = entry.timestamp;
|
|
8133
|
+
}
|
|
8134
|
+
if (!header) {
|
|
8135
|
+
if (entry.type !== "session" || typeof entry.id !== "string") return null;
|
|
8136
|
+
header = entry;
|
|
8137
|
+
if (typeof entry.timestamp === "string" && entry.timestamp) timestamp = entry.timestamp;
|
|
8138
|
+
continue;
|
|
8139
|
+
}
|
|
8140
|
+
if (entry.type === "session_info") {
|
|
8141
|
+
const name = typeof entry.name === "string" ? entry.name.trim() : "";
|
|
8142
|
+
title = name || void 0;
|
|
8143
|
+
continue;
|
|
8144
|
+
}
|
|
8145
|
+
if (entry.type === "model_change" && typeof entry.modelId === "string") {
|
|
8146
|
+
model = entry.modelId;
|
|
8147
|
+
continue;
|
|
8148
|
+
}
|
|
8149
|
+
if (entry.type !== "message") continue;
|
|
8150
|
+
const message = entry.message;
|
|
8151
|
+
if (!message || typeof message !== "object") continue;
|
|
8152
|
+
if (message.role === "user") {
|
|
8153
|
+
const text = extractText(message.content).trim();
|
|
8154
|
+
if (text) {
|
|
8155
|
+
promptCount++;
|
|
8156
|
+
if (prompts.length < PROMPT_SCAN_LIMIT) {
|
|
8157
|
+
prompts.push(cleanPromptText(text).slice(0, 200));
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
} else if (message.role === "assistant") {
|
|
8161
|
+
if (!model && typeof message.model === "string") model = message.model;
|
|
8162
|
+
if (Array.isArray(message.content)) {
|
|
8163
|
+
for (const block of message.content) {
|
|
8164
|
+
if (block?.type !== "toolCall") continue;
|
|
8165
|
+
toolCallCount++;
|
|
8166
|
+
if (isEditTool2(block.name)) editCountEst++;
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
}
|
|
7669
8171
|
} catch {
|
|
7670
|
-
return
|
|
8172
|
+
return null;
|
|
8173
|
+
} finally {
|
|
8174
|
+
rl.close();
|
|
7671
8175
|
}
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
8176
|
+
if (!header || prompts.length === 0) return null;
|
|
8177
|
+
const cwd = header.cwd || decodeProjectDir3(encodedProjectDir);
|
|
8178
|
+
const slug = basename5(filePath, ".jsonl").replace(/^\d{4}-\d{2}-\d{2}T[^_]+_/, "");
|
|
8179
|
+
const fallbackTimestamp = timestamp || header.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
8180
|
+
return {
|
|
8181
|
+
provider: "pi",
|
|
8182
|
+
sessionId: header.id,
|
|
8183
|
+
slug,
|
|
8184
|
+
title,
|
|
8185
|
+
project: cwd,
|
|
8186
|
+
cwd,
|
|
8187
|
+
version: String(header.version || 1),
|
|
8188
|
+
timestamp: fallbackTimestamp,
|
|
8189
|
+
lineCount,
|
|
8190
|
+
fileSize,
|
|
8191
|
+
filePath,
|
|
8192
|
+
filePaths: [filePath],
|
|
8193
|
+
firstPrompt: prompts[0] || "(pi session)",
|
|
8194
|
+
prompts,
|
|
8195
|
+
promptCount,
|
|
8196
|
+
toolCallCount,
|
|
8197
|
+
model,
|
|
8198
|
+
editCountEst
|
|
8199
|
+
};
|
|
8200
|
+
}
|
|
8201
|
+
function decodeProjectDir3(encoded) {
|
|
8202
|
+
if (encoded.startsWith("--") && encoded.endsWith("--")) {
|
|
8203
|
+
return `/${encoded.slice(2, -2).replace(/-/g, "/")}`;
|
|
7679
8204
|
}
|
|
7680
|
-
return
|
|
8205
|
+
return encoded;
|
|
7681
8206
|
}
|
|
7682
|
-
function
|
|
7683
|
-
|
|
7684
|
-
if (
|
|
7685
|
-
return
|
|
8207
|
+
function extractText(content) {
|
|
8208
|
+
if (typeof content === "string") return content;
|
|
8209
|
+
if (!Array.isArray(content)) return "";
|
|
8210
|
+
return content.filter((block) => block?.type === "text").map((block) => block.text || "").join("\n");
|
|
7686
8211
|
}
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
8212
|
+
function isEditTool2(name) {
|
|
8213
|
+
return name === "edit" || name === "write" || name === "Edit" || name === "Write";
|
|
8214
|
+
}
|
|
8215
|
+
|
|
8216
|
+
// src/providers/pi/parser.ts
|
|
8217
|
+
init_utils();
|
|
8218
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
8219
|
+
import { basename as basename6 } from "path";
|
|
8220
|
+
async function parsePiSession(filePaths, sessionInfo) {
|
|
8221
|
+
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
|
8222
|
+
const allLines = [];
|
|
8223
|
+
for (const fp of paths) {
|
|
8224
|
+
const content = await readFile15(fp, "utf-8");
|
|
8225
|
+
allLines.push(...content.split("\n"));
|
|
8226
|
+
}
|
|
8227
|
+
return parsePiLines(allLines, {
|
|
8228
|
+
sourcePath: paths[0],
|
|
8229
|
+
sessionInfo,
|
|
8230
|
+
sessionsDir: getPiSessionsDir(),
|
|
8231
|
+
modelContextWindows: await readPiModelContextWindows()
|
|
8232
|
+
});
|
|
8233
|
+
}
|
|
8234
|
+
function parsePiLines(lines, options = {}) {
|
|
8235
|
+
let header;
|
|
8236
|
+
const entries = [];
|
|
8237
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8238
|
+
const parseWarnings = [];
|
|
8239
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
8240
|
+
const line = lines[lineIndex];
|
|
8241
|
+
if (!line.trim()) continue;
|
|
8242
|
+
let entry;
|
|
8243
|
+
try {
|
|
8244
|
+
entry = JSON.parse(line);
|
|
8245
|
+
} catch {
|
|
8246
|
+
addParseWarning(parseWarnings, {
|
|
8247
|
+
kind: "malformed-json",
|
|
8248
|
+
source: "pi JSONL",
|
|
8249
|
+
firstLine: lineIndex + 1,
|
|
8250
|
+
message: "Skipped malformed JSONL line",
|
|
8251
|
+
sample: line
|
|
8252
|
+
});
|
|
8253
|
+
continue;
|
|
8254
|
+
}
|
|
8255
|
+
if (entry.type === "session") {
|
|
8256
|
+
if (!header && typeof entry.id === "string") {
|
|
8257
|
+
header = entry;
|
|
8258
|
+
}
|
|
8259
|
+
continue;
|
|
8260
|
+
}
|
|
8261
|
+
entries.push(entry);
|
|
8262
|
+
if (entry.id) byId.set(entry.id, entry);
|
|
8263
|
+
}
|
|
8264
|
+
const branchSelection = selectActiveBranch(entries, byId);
|
|
8265
|
+
const branchEntries = branchSelection.entries;
|
|
8266
|
+
const toolResults = collectToolResults(branchEntries);
|
|
8267
|
+
const turns = [];
|
|
8268
|
+
const usageByMessageId = /* @__PURE__ */ new Map();
|
|
8269
|
+
const allTimestamps = [];
|
|
8270
|
+
const compactions = [];
|
|
8271
|
+
const apiErrors = [];
|
|
8272
|
+
let title = options.sessionInfo?.title;
|
|
8273
|
+
let model = options.sessionInfo?.model;
|
|
8274
|
+
let currentModel = model;
|
|
8275
|
+
let firstTimestamp;
|
|
8276
|
+
let lastTimestamp;
|
|
8277
|
+
for (const entry of branchEntries) {
|
|
8278
|
+
if (entry.timestamp) {
|
|
8279
|
+
allTimestamps.push(entry.timestamp);
|
|
8280
|
+
if (!firstTimestamp || entry.timestamp < firstTimestamp) firstTimestamp = entry.timestamp;
|
|
8281
|
+
if (!lastTimestamp || entry.timestamp > lastTimestamp) lastTimestamp = entry.timestamp;
|
|
8282
|
+
}
|
|
8283
|
+
if (entry.type === "session_info") {
|
|
8284
|
+
const sessionInfoEntry = entry;
|
|
8285
|
+
const name = typeof sessionInfoEntry.name === "string" ? sessionInfoEntry.name.trim() : "";
|
|
8286
|
+
title = name || title;
|
|
8287
|
+
continue;
|
|
8288
|
+
}
|
|
8289
|
+
if (entry.type === "model_change") {
|
|
8290
|
+
const nextModel = entry.modelId;
|
|
8291
|
+
if (typeof nextModel === "string" && nextModel) {
|
|
8292
|
+
currentModel = nextModel;
|
|
8293
|
+
model = model || nextModel;
|
|
8294
|
+
}
|
|
8295
|
+
continue;
|
|
8296
|
+
}
|
|
8297
|
+
if (entry.type === "compaction") {
|
|
8298
|
+
const compaction = entry;
|
|
8299
|
+
const summary = typeof compaction.summary === "string" ? compaction.summary : "";
|
|
8300
|
+
if (summary) {
|
|
8301
|
+
turns.push({
|
|
8302
|
+
role: "user",
|
|
8303
|
+
subtype: "compaction-summary",
|
|
8304
|
+
timestamp: entry.timestamp,
|
|
8305
|
+
blocks: [{ type: "text", text: summary }]
|
|
8306
|
+
});
|
|
8307
|
+
}
|
|
8308
|
+
compactions.push({
|
|
8309
|
+
timestamp: entry.timestamp || lastTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
8310
|
+
trigger: "pi",
|
|
8311
|
+
...typeof compaction.tokensBefore === "number" ? { preTokens: compaction.tokensBefore } : {}
|
|
8312
|
+
});
|
|
8313
|
+
continue;
|
|
8314
|
+
}
|
|
8315
|
+
if (entry.type === "branch_summary") {
|
|
8316
|
+
const summary = entry.summary;
|
|
8317
|
+
if (typeof summary === "string" && summary.trim()) {
|
|
8318
|
+
turns.push({
|
|
8319
|
+
role: "user",
|
|
8320
|
+
subtype: "context-injection",
|
|
8321
|
+
timestamp: entry.timestamp,
|
|
8322
|
+
blocks: [{ type: "text", text: `Branch summary:
|
|
8323
|
+
${summary}` }]
|
|
8324
|
+
});
|
|
8325
|
+
}
|
|
8326
|
+
continue;
|
|
8327
|
+
}
|
|
8328
|
+
if (entry.type === "custom_message") {
|
|
8329
|
+
const custom = entry;
|
|
8330
|
+
const text = extractText2(custom.content).trim();
|
|
8331
|
+
if (text && custom.display !== false) {
|
|
8332
|
+
const customType = typeof custom.customType === "string" ? custom.customType : "custom";
|
|
8333
|
+
turns.push({
|
|
8334
|
+
role: "user",
|
|
8335
|
+
subtype: "context-injection",
|
|
8336
|
+
timestamp: entry.timestamp,
|
|
8337
|
+
blocks: [{ type: "text", text: `[${customType}]
|
|
8338
|
+
${text}` }]
|
|
8339
|
+
});
|
|
8340
|
+
}
|
|
8341
|
+
continue;
|
|
8342
|
+
}
|
|
8343
|
+
if (entry.type !== "message") continue;
|
|
8344
|
+
const messageEntry = entry;
|
|
8345
|
+
const message = messageEntry.message;
|
|
8346
|
+
if (!message) continue;
|
|
8347
|
+
if (message.role === "user") {
|
|
8348
|
+
const blocks = buildUserBlocks(message.content);
|
|
8349
|
+
if (blocks.length > 0) {
|
|
8350
|
+
turns.push({ role: "user", timestamp: entry.timestamp, blocks });
|
|
8351
|
+
}
|
|
8352
|
+
continue;
|
|
8353
|
+
}
|
|
8354
|
+
if (message.role === "assistant") {
|
|
8355
|
+
const blocks = buildAssistantBlocks(message.content, toolResults, entry.timestamp);
|
|
8356
|
+
if (message.errorMessage) {
|
|
8357
|
+
blocks.push({ type: "text", text: message.errorMessage });
|
|
8358
|
+
apiErrors.push({
|
|
8359
|
+
timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
8360
|
+
...parseApiErrorMessage(message.errorMessage)
|
|
8361
|
+
});
|
|
8362
|
+
}
|
|
8363
|
+
if (blocks.length > 0) {
|
|
8364
|
+
const msgModel = message.model || currentModel;
|
|
8365
|
+
if (msgModel) model = model || msgModel;
|
|
8366
|
+
if (message.usage && entry.id) {
|
|
8367
|
+
const usage = normalizeUsage(message.usage);
|
|
8368
|
+
usageByMessageId.set(entry.id, {
|
|
8369
|
+
usage,
|
|
8370
|
+
model: msgModel,
|
|
8371
|
+
contextTokens: usage.inputTokens + usage.cacheCreationTokens + usage.cacheReadTokens
|
|
8372
|
+
});
|
|
8373
|
+
}
|
|
8374
|
+
turns.push({
|
|
8375
|
+
role: "assistant",
|
|
8376
|
+
messageId: entry.id,
|
|
8377
|
+
model: msgModel,
|
|
8378
|
+
timestamp: entry.timestamp,
|
|
8379
|
+
blocks,
|
|
8380
|
+
...message.stopReason === "length" ? { stopReason: "max_tokens" } : {}
|
|
8381
|
+
});
|
|
8382
|
+
}
|
|
8383
|
+
continue;
|
|
8384
|
+
}
|
|
8385
|
+
if (message.role === "bashExecution") {
|
|
8386
|
+
const block = buildBashExecutionBlock(message, entry.id || `bash-${turns.length}`);
|
|
8387
|
+
turns.push({ role: "assistant", timestamp: entry.timestamp, blocks: [block] });
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8390
|
+
const tokenUsage = aggregateUsage([...usageByMessageId.values()].map((value) => value.usage));
|
|
8391
|
+
const tokenUsageByModel = aggregateUsageByModel(usageByMessageId);
|
|
8392
|
+
const turnStats = buildTurnStats2(turns, usageByMessageId);
|
|
8393
|
+
const sessionId = header?.id || options.sessionInfo?.sessionId || "pi-session";
|
|
8394
|
+
const sourcePath = options.sourcePath || options.sessionInfo?.filePath || "";
|
|
8395
|
+
const slug = options.sessionInfo?.slug || basename6(sourcePath || sessionId, ".jsonl");
|
|
8396
|
+
const cwd = header?.cwd || options.sessionInfo?.cwd || options.sessionInfo?.project || "";
|
|
8397
|
+
return {
|
|
8398
|
+
sessionId,
|
|
8399
|
+
slug,
|
|
8400
|
+
title,
|
|
8401
|
+
cwd,
|
|
8402
|
+
model,
|
|
8403
|
+
startTime: firstTimestamp || header?.timestamp,
|
|
8404
|
+
endTime: lastTimestamp,
|
|
8405
|
+
totalDurationMs: estimateActiveDuration2(allTimestamps),
|
|
8406
|
+
turns,
|
|
8407
|
+
tokenUsage,
|
|
8408
|
+
...tokenUsageByModel ? { tokenUsageByModel } : {},
|
|
8409
|
+
...turnStats.length > 0 ? { turnStats } : {},
|
|
8410
|
+
...compactions.length > 0 ? { compactions } : {},
|
|
8411
|
+
...apiErrors.length > 0 ? { apiErrors } : {},
|
|
8412
|
+
dataSource: "jsonl",
|
|
8413
|
+
dataSourceInfo: {
|
|
8414
|
+
primary: "jsonl",
|
|
8415
|
+
sources: [shortenPath(options.sessionsDir || getPiSessionsDir())],
|
|
8416
|
+
...branchSelection.abandonedEntries > 0 ? { notes: [`${branchSelection.abandonedEntries} off-branch Pi entries were omitted.`] } : {}
|
|
8417
|
+
},
|
|
8418
|
+
...model ? contextLimitForModel(model, options.modelContextWindows) : {},
|
|
8419
|
+
...parseWarnings.length > 0 ? { parseWarnings } : {}
|
|
8420
|
+
};
|
|
8421
|
+
}
|
|
8422
|
+
function parseApiErrorMessage(message) {
|
|
8423
|
+
const status = message.match(/\b(\d{3})\b/);
|
|
8424
|
+
const errorType = message.match(/"([a-z][a-z0-9_]*error[a-z0-9_]*)"/i);
|
|
8425
|
+
return {
|
|
8426
|
+
...status ? { statusCode: Number(status[1]) } : {},
|
|
8427
|
+
...errorType ? { errorType: errorType[1] } : {}
|
|
8428
|
+
};
|
|
8429
|
+
}
|
|
8430
|
+
function contextLimitForModel(model, modelContextWindows) {
|
|
8431
|
+
const contextLimit = modelContextWindows?.get(model);
|
|
8432
|
+
return contextLimit ? { contextLimit } : {};
|
|
8433
|
+
}
|
|
8434
|
+
function selectActiveBranch(entries, byId) {
|
|
8435
|
+
if (entries.length === 0) return { entries: [], abandonedEntries: 0 };
|
|
8436
|
+
if (byId.size === 0) return { entries, abandonedEntries: 0 };
|
|
8437
|
+
const leaf = entries.toReversed().find((entry) => entry.id && byId.has(entry.id));
|
|
8438
|
+
if (!leaf?.id) return { entries, abandonedEntries: 0 };
|
|
8439
|
+
const branchIds = /* @__PURE__ */ new Set();
|
|
8440
|
+
const branch = [];
|
|
8441
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8442
|
+
let current = leaf;
|
|
8443
|
+
while (current) {
|
|
8444
|
+
if (!current.id || seen.has(current.id)) break;
|
|
8445
|
+
seen.add(current.id);
|
|
8446
|
+
branchIds.add(current.id);
|
|
8447
|
+
branch.unshift(current);
|
|
8448
|
+
const parentId = current.parentId;
|
|
8449
|
+
current = typeof parentId === "string" ? byId.get(parentId) : void 0;
|
|
8450
|
+
}
|
|
8451
|
+
if (branch.length === 0) return { entries, abandonedEntries: 0 };
|
|
8452
|
+
const idlessEntries = entries.filter((entry) => !entry.id);
|
|
8453
|
+
if (idlessEntries.length === 0) {
|
|
8454
|
+
return { entries: branch, abandonedEntries: Math.max(0, entries.length - branch.length) };
|
|
8455
|
+
}
|
|
8456
|
+
const selected = new Set(branch);
|
|
8457
|
+
const branchWithIdless = entries.filter((entry) => selected.has(entry) || !entry.id);
|
|
8458
|
+
return {
|
|
8459
|
+
entries: branchWithIdless,
|
|
8460
|
+
abandonedEntries: entries.filter((entry) => entry.id && !branchIds.has(entry.id)).length
|
|
8461
|
+
};
|
|
8462
|
+
}
|
|
8463
|
+
function collectToolResults(entries) {
|
|
8464
|
+
const results = /* @__PURE__ */ new Map();
|
|
8465
|
+
for (const entry of entries) {
|
|
8466
|
+
if (entry.type !== "message") continue;
|
|
8467
|
+
const message = entry.message;
|
|
8468
|
+
if (message?.role !== "toolResult" || typeof message.toolCallId !== "string") continue;
|
|
8469
|
+
const { text, images } = extractTextAndImages(message.content);
|
|
8470
|
+
results.set(message.toolCallId, {
|
|
8471
|
+
text,
|
|
8472
|
+
images,
|
|
8473
|
+
isError: message.isError,
|
|
8474
|
+
timestamp: entry.timestamp
|
|
7701
8475
|
});
|
|
7702
8476
|
}
|
|
7703
|
-
return
|
|
8477
|
+
return results;
|
|
7704
8478
|
}
|
|
7705
|
-
function
|
|
7706
|
-
const
|
|
7707
|
-
|
|
7708
|
-
if (
|
|
7709
|
-
if (
|
|
7710
|
-
|
|
7711
|
-
return "ToolOutput";
|
|
8479
|
+
function buildUserBlocks(content) {
|
|
8480
|
+
const { text, images } = extractTextAndImages(content);
|
|
8481
|
+
const blocks = [];
|
|
8482
|
+
if (text.trim()) blocks.push({ type: "text", text });
|
|
8483
|
+
if (images.length > 0) blocks.push({ type: "_user_images", images });
|
|
8484
|
+
return blocks;
|
|
7712
8485
|
}
|
|
7713
|
-
function
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
8486
|
+
function buildAssistantBlocks(content, toolResults, assistantTimestamp) {
|
|
8487
|
+
if (!Array.isArray(content)) return [];
|
|
8488
|
+
const blocks = [];
|
|
8489
|
+
for (const block of content) {
|
|
8490
|
+
if (block.type === "thinking" && block.thinking?.trim()) {
|
|
8491
|
+
blocks.push({ type: "thinking", thinking: block.thinking });
|
|
8492
|
+
} else if (block.type === "text" && block.text?.trim()) {
|
|
8493
|
+
blocks.push({ type: "text", text: block.text });
|
|
8494
|
+
} else if (block.type === "toolCall") {
|
|
8495
|
+
const result = toolResults.get(block.id);
|
|
8496
|
+
blocks.push({
|
|
8497
|
+
type: "tool_use",
|
|
8498
|
+
id: block.id,
|
|
8499
|
+
name: mapToolName(block.name),
|
|
8500
|
+
input: normalizeToolInput2(block.name, block.arguments || {}),
|
|
8501
|
+
_result: result?.text || "",
|
|
8502
|
+
...result?.images.length ? { _images: result.images } : {},
|
|
8503
|
+
...result?.isError ? { _isError: true } : {},
|
|
8504
|
+
...assistantTimestamp && result?.timestamp ? { _durationMs: toolDurationMs(assistantTimestamp, result.timestamp) } : {}
|
|
8505
|
+
});
|
|
7722
8506
|
}
|
|
7723
8507
|
}
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
marker.block._isPendingMarker = void 0;
|
|
7737
|
-
if (!marker.turn.timestamp && tool.timestamp) {
|
|
7738
|
-
marker.turn.timestamp = tool.timestamp;
|
|
7739
|
-
}
|
|
8508
|
+
return blocks;
|
|
8509
|
+
}
|
|
8510
|
+
function toolDurationMs(start, end) {
|
|
8511
|
+
const duration = Date.parse(end) - Date.parse(start);
|
|
8512
|
+
return duration > 0 && duration < 60 * 6e4 ? duration : void 0;
|
|
8513
|
+
}
|
|
8514
|
+
function buildBashExecutionBlock(message, fallbackId) {
|
|
8515
|
+
const outputParts = [message.output || ""];
|
|
8516
|
+
if (message.cancelled) outputParts.push("\n(command cancelled)");
|
|
8517
|
+
if (message.truncated && message.fullOutputPath) {
|
|
8518
|
+
outputParts.push(`
|
|
8519
|
+
[Output truncated. Full output: ${message.fullOutputPath}]`);
|
|
7740
8520
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
8521
|
+
return {
|
|
8522
|
+
type: "tool_use",
|
|
8523
|
+
id: fallbackId,
|
|
8524
|
+
name: "Bash",
|
|
8525
|
+
input: { command: message.command || "" },
|
|
8526
|
+
_result: outputParts.join(""),
|
|
8527
|
+
...message.exitCode !== void 0 && message.exitCode !== 0 ? { _isError: true } : {}
|
|
8528
|
+
};
|
|
8529
|
+
}
|
|
8530
|
+
function extractTextAndImages(content) {
|
|
8531
|
+
if (typeof content === "string") return { text: content, images: [] };
|
|
8532
|
+
if (!Array.isArray(content)) return { text: "", images: [] };
|
|
8533
|
+
const text = [];
|
|
8534
|
+
const images = [];
|
|
8535
|
+
for (const block of content) {
|
|
8536
|
+
if (block.type === "text") {
|
|
8537
|
+
text.push(block.text || "");
|
|
8538
|
+
} else if (block.type === "image" && block.data) {
|
|
8539
|
+
images.push(`data:${block.mimeType || "image/png"};base64,${block.data}`);
|
|
8540
|
+
}
|
|
8541
|
+
}
|
|
8542
|
+
return { text: text.join("\n"), images };
|
|
8543
|
+
}
|
|
8544
|
+
function extractText2(content) {
|
|
8545
|
+
return extractTextAndImages(content).text;
|
|
8546
|
+
}
|
|
8547
|
+
function mapToolName(name) {
|
|
8548
|
+
const normalized = name.toLowerCase();
|
|
8549
|
+
if (normalized === "bash") return "Bash";
|
|
8550
|
+
if (normalized === "read") return "Read";
|
|
8551
|
+
if (normalized === "write") return "Write";
|
|
8552
|
+
if (normalized === "edit") return "Edit";
|
|
8553
|
+
if (normalized === "grep") return "Grep";
|
|
8554
|
+
if (normalized === "find") return "Find";
|
|
8555
|
+
if (normalized === "ls") return "LS";
|
|
8556
|
+
return name;
|
|
8557
|
+
}
|
|
8558
|
+
function normalizeToolInput2(name, input) {
|
|
8559
|
+
const normalized = name.toLowerCase();
|
|
8560
|
+
if (normalized === "write") {
|
|
8561
|
+
return {
|
|
8562
|
+
...input,
|
|
8563
|
+
...typeof input.path === "string" ? { file_path: input.path } : {}
|
|
7747
8564
|
};
|
|
7748
|
-
turn.blocks[blockIndex] = thinkingBlock;
|
|
7749
8565
|
}
|
|
7750
|
-
|
|
7751
|
-
const
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
8566
|
+
if (normalized === "edit") {
|
|
8567
|
+
const firstEdit = Array.isArray(input.edits) ? input.edits[0] : void 0;
|
|
8568
|
+
const edit = firstEdit && typeof firstEdit === "object" ? firstEdit : input;
|
|
8569
|
+
return {
|
|
8570
|
+
...input,
|
|
8571
|
+
...typeof input.path === "string" ? { file_path: input.path } : {},
|
|
8572
|
+
...typeof edit.oldText === "string" ? { old_string: edit.oldText } : {},
|
|
8573
|
+
...typeof edit.newText === "string" ? { new_string: edit.newText } : {}
|
|
8574
|
+
};
|
|
7757
8575
|
}
|
|
8576
|
+
return input;
|
|
7758
8577
|
}
|
|
7759
|
-
function
|
|
8578
|
+
function normalizeUsage(usage) {
|
|
7760
8579
|
return {
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
_result: tool.result
|
|
8580
|
+
inputTokens: usage.input || 0,
|
|
8581
|
+
outputTokens: usage.output || 0,
|
|
8582
|
+
cacheCreationTokens: usage.cacheWrite || 0,
|
|
8583
|
+
cacheReadTokens: usage.cacheRead || 0
|
|
7766
8584
|
};
|
|
7767
8585
|
}
|
|
7768
|
-
function
|
|
7769
|
-
if (
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
8586
|
+
function aggregateUsage(usages) {
|
|
8587
|
+
if (usages.length === 0) return void 0;
|
|
8588
|
+
return usages.reduce(
|
|
8589
|
+
(total, usage) => ({
|
|
8590
|
+
inputTokens: total.inputTokens + usage.inputTokens,
|
|
8591
|
+
outputTokens: total.outputTokens + usage.outputTokens,
|
|
8592
|
+
cacheCreationTokens: total.cacheCreationTokens + usage.cacheCreationTokens,
|
|
8593
|
+
cacheReadTokens: total.cacheReadTokens + usage.cacheReadTokens
|
|
8594
|
+
}),
|
|
8595
|
+
{ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 }
|
|
8596
|
+
);
|
|
7774
8597
|
}
|
|
7775
|
-
function
|
|
7776
|
-
|
|
7777
|
-
const
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
markerName: trailing[2].trim(),
|
|
7784
|
-
...textBody ? { textBody } : {}
|
|
7785
|
-
};
|
|
8598
|
+
function aggregateUsageByModel(usageByMessageId) {
|
|
8599
|
+
if (usageByMessageId.size === 0) return void 0;
|
|
8600
|
+
const result = {};
|
|
8601
|
+
for (const { usage, model } of usageByMessageId.values()) {
|
|
8602
|
+
const key = model || "unknown";
|
|
8603
|
+
result[key] = aggregateUsage(
|
|
8604
|
+
[result[key], usage].filter((value) => !!value)
|
|
8605
|
+
);
|
|
7786
8606
|
}
|
|
7787
|
-
return
|
|
8607
|
+
return result;
|
|
8608
|
+
}
|
|
8609
|
+
function buildTurnStats2(turns, usageByMessageId) {
|
|
8610
|
+
const stats = [];
|
|
8611
|
+
let current;
|
|
8612
|
+
let turnIndex = -1;
|
|
8613
|
+
for (const turn of turns) {
|
|
8614
|
+
if (turn.role === "user" && !turn.subtype) {
|
|
8615
|
+
if (current && current.messageIds.length > 0)
|
|
8616
|
+
stats.push(buildTurnStat(current, usageByMessageId));
|
|
8617
|
+
turnIndex++;
|
|
8618
|
+
current = { turnIndex, messageIds: [] };
|
|
8619
|
+
} else if (turn.role === "assistant" && turn.messageId && current) {
|
|
8620
|
+
current.messageIds.push(turn.messageId);
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
if (current && current.messageIds.length > 0)
|
|
8624
|
+
stats.push(buildTurnStat(current, usageByMessageId));
|
|
8625
|
+
return stats;
|
|
8626
|
+
}
|
|
8627
|
+
function buildTurnStat(turn, usageByMessageId) {
|
|
8628
|
+
const usages = [];
|
|
8629
|
+
let model;
|
|
8630
|
+
let contextTokens = 0;
|
|
8631
|
+
for (const messageId of turn.messageIds) {
|
|
8632
|
+
const item = usageByMessageId.get(messageId);
|
|
8633
|
+
if (!item) continue;
|
|
8634
|
+
usages.push(item.usage);
|
|
8635
|
+
model = model || item.model;
|
|
8636
|
+
contextTokens = Math.max(contextTokens, item.contextTokens || 0);
|
|
8637
|
+
}
|
|
8638
|
+
const tokenUsage = aggregateUsage(usages);
|
|
8639
|
+
return {
|
|
8640
|
+
turnIndex: turn.turnIndex,
|
|
8641
|
+
...model ? { model } : {},
|
|
8642
|
+
...tokenUsage ? { tokenUsage } : {},
|
|
8643
|
+
...contextTokens ? { contextTokens } : {}
|
|
8644
|
+
};
|
|
8645
|
+
}
|
|
8646
|
+
function estimateActiveDuration2(timestamps) {
|
|
8647
|
+
if (timestamps.length < 2) return void 0;
|
|
8648
|
+
const sorted = timestamps.map((timestamp) => Date.parse(timestamp)).filter((timestamp) => !Number.isNaN(timestamp)).sort((a, b) => a - b);
|
|
8649
|
+
if (sorted.length < 2) return void 0;
|
|
8650
|
+
let total = 0;
|
|
8651
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
8652
|
+
const gap = sorted[i] - sorted[i - 1];
|
|
8653
|
+
if (gap > 0 && gap < 30 * 6e4) total += gap;
|
|
8654
|
+
}
|
|
8655
|
+
return total || void 0;
|
|
7788
8656
|
}
|
|
7789
8657
|
|
|
7790
|
-
// src/providers/
|
|
7791
|
-
var
|
|
7792
|
-
name: "
|
|
7793
|
-
displayName: "
|
|
7794
|
-
discover:
|
|
7795
|
-
parse:
|
|
8658
|
+
// src/providers/pi/index.ts
|
|
8659
|
+
var piProvider = {
|
|
8660
|
+
name: "pi",
|
|
8661
|
+
displayName: "Pi",
|
|
8662
|
+
discover: discoverPiSessions,
|
|
8663
|
+
parse: parsePiSession
|
|
7796
8664
|
};
|
|
7797
8665
|
|
|
7798
8666
|
// src/providers/index.ts
|
|
@@ -7801,9 +8669,17 @@ var providers = [
|
|
|
7801
8669
|
claudeDesktopProvider,
|
|
7802
8670
|
claudeCodeProvider,
|
|
7803
8671
|
codexProvider,
|
|
7804
|
-
cursorProvider
|
|
8672
|
+
cursorProvider,
|
|
8673
|
+
piProvider
|
|
8674
|
+
];
|
|
8675
|
+
var PROVIDER_PRIORITY = [
|
|
8676
|
+
"claude-cowork",
|
|
8677
|
+
"claude-desktop",
|
|
8678
|
+
"claude-code",
|
|
8679
|
+
"codex",
|
|
8680
|
+
"cursor",
|
|
8681
|
+
"pi"
|
|
7805
8682
|
];
|
|
7806
|
-
var PROVIDER_PRIORITY = ["claude-cowork", "claude-desktop", "claude-code", "codex", "cursor"];
|
|
7807
8683
|
function getAllProviders() {
|
|
7808
8684
|
return providers;
|
|
7809
8685
|
}
|
|
@@ -7832,9 +8708,9 @@ init_cloud();
|
|
|
7832
8708
|
|
|
7833
8709
|
// src/publishers/gist.ts
|
|
7834
8710
|
init_cloud();
|
|
7835
|
-
import { createHash as
|
|
7836
|
-
import { readFile as
|
|
7837
|
-
import { join as
|
|
8711
|
+
import { createHash as createHash3 } from "crypto";
|
|
8712
|
+
import { readFile as readFile18, writeFile as writeFile4 } from "fs/promises";
|
|
8713
|
+
import { join as join18 } from "path";
|
|
7838
8714
|
var GIST_META_FILE = ".vibe-replay-gist.json";
|
|
7839
8715
|
function checkPublishStatus() {
|
|
7840
8716
|
const auth = loadAuthToken();
|
|
@@ -7846,8 +8722,8 @@ async function publishGist(outputDir, title, opts) {
|
|
|
7846
8722
|
if (!auth) {
|
|
7847
8723
|
throw new Error("Not logged in. Run `vibe-replay auth login` first.");
|
|
7848
8724
|
}
|
|
7849
|
-
const jsonPath =
|
|
7850
|
-
const content = await
|
|
8725
|
+
const jsonPath = join18(outputDir, "replay.json");
|
|
8726
|
+
const content = await readFile18(jsonPath, "utf-8");
|
|
7851
8727
|
const overwrite = opts?.overwrite;
|
|
7852
8728
|
const filename = overwrite?.filename || `${sanitizeFilename(title)}.json`;
|
|
7853
8729
|
const description = `vibe-replay: ${title}`;
|
|
@@ -7898,7 +8774,7 @@ async function publishGist(outputDir, title, opts) {
|
|
|
7898
8774
|
viewerUrl = data.viewerUrl;
|
|
7899
8775
|
mode = "created";
|
|
7900
8776
|
}
|
|
7901
|
-
const contentHash =
|
|
8777
|
+
const contentHash = createHash3("sha256").update(content).digest("hex").slice(0, 16);
|
|
7902
8778
|
await saveGistInfo(outputDir, {
|
|
7903
8779
|
gistId,
|
|
7904
8780
|
filename,
|
|
@@ -7911,7 +8787,7 @@ async function publishGist(outputDir, title, opts) {
|
|
|
7911
8787
|
}
|
|
7912
8788
|
async function loadSavedGistInfo(outputDir) {
|
|
7913
8789
|
try {
|
|
7914
|
-
const raw = await
|
|
8790
|
+
const raw = await readFile18(join18(outputDir, GIST_META_FILE), "utf-8");
|
|
7915
8791
|
const parsed = JSON.parse(raw);
|
|
7916
8792
|
if (!parsed?.gistId || !parsed?.filename) return void 0;
|
|
7917
8793
|
return parsed;
|
|
@@ -7920,7 +8796,7 @@ async function loadSavedGistInfo(outputDir) {
|
|
|
7920
8796
|
}
|
|
7921
8797
|
}
|
|
7922
8798
|
async function saveGistInfo(outputDir, info) {
|
|
7923
|
-
await writeFile4(
|
|
8799
|
+
await writeFile4(join18(outputDir, GIST_META_FILE), JSON.stringify(info, null, 2), "utf-8");
|
|
7924
8800
|
}
|
|
7925
8801
|
function sanitizeFilename(s) {
|
|
7926
8802
|
return s.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").slice(0, 60);
|
|
@@ -8032,19 +8908,19 @@ function scanForSecrets(content) {
|
|
|
8032
8908
|
|
|
8033
8909
|
// src/server.ts
|
|
8034
8910
|
import { execFile as execFile3 } from "child_process";
|
|
8035
|
-
import { createHash as
|
|
8911
|
+
import { createHash as createHash5 } from "crypto";
|
|
8036
8912
|
import { watch as fsWatch } from "fs";
|
|
8037
8913
|
import {
|
|
8038
|
-
mkdir as
|
|
8914
|
+
mkdir as mkdir6,
|
|
8039
8915
|
open as fsOpen,
|
|
8040
|
-
readdir as
|
|
8041
|
-
readFile as
|
|
8042
|
-
stat as
|
|
8916
|
+
readdir as readdir12,
|
|
8917
|
+
readFile as readFile22,
|
|
8918
|
+
stat as stat11,
|
|
8043
8919
|
unlink as unlink2,
|
|
8044
|
-
writeFile as
|
|
8920
|
+
writeFile as writeFile7
|
|
8045
8921
|
} from "fs/promises";
|
|
8046
|
-
import { homedir as
|
|
8047
|
-
import {
|
|
8922
|
+
import { homedir as homedir19 } from "os";
|
|
8923
|
+
import { join as join22, resolve as resolve3 } from "path";
|
|
8048
8924
|
import { promisify as promisify3 } from "util";
|
|
8049
8925
|
import { serve } from "@hono/node-server";
|
|
8050
8926
|
import chalk from "chalk";
|
|
@@ -8300,7 +9176,7 @@ async function executeFeedback(prompt, tool) {
|
|
|
8300
9176
|
return runOpencode(prompt, tool.command);
|
|
8301
9177
|
}
|
|
8302
9178
|
function runClaude(prompt, cmd) {
|
|
8303
|
-
return new Promise((
|
|
9179
|
+
return new Promise((resolve4, reject) => {
|
|
8304
9180
|
const proc = spawnTool(cmd, ["-p", "--output-format", "json"], {
|
|
8305
9181
|
env: { ...process.env, NO_COLOR: "1" },
|
|
8306
9182
|
timeout: 6e5,
|
|
@@ -8314,9 +9190,9 @@ function runClaude(prompt, cmd) {
|
|
|
8314
9190
|
if (code === 0) {
|
|
8315
9191
|
try {
|
|
8316
9192
|
const parsed = JSON.parse(stdout);
|
|
8317
|
-
|
|
9193
|
+
resolve4(parsed.result || stdout);
|
|
8318
9194
|
} catch {
|
|
8319
|
-
|
|
9195
|
+
resolve4(stdout);
|
|
8320
9196
|
}
|
|
8321
9197
|
} else {
|
|
8322
9198
|
reject(new Error(`claude exited ${code}: ${stderr.slice(0, 500)}`));
|
|
@@ -8328,7 +9204,7 @@ function runClaude(prompt, cmd) {
|
|
|
8328
9204
|
});
|
|
8329
9205
|
}
|
|
8330
9206
|
function runOpencode(prompt, cmd) {
|
|
8331
|
-
return new Promise((
|
|
9207
|
+
return new Promise((resolve4, reject) => {
|
|
8332
9208
|
const proc = spawnTool(cmd, ["run"], {
|
|
8333
9209
|
env: { ...process.env, NO_COLOR: "1", TERM: "dumb" },
|
|
8334
9210
|
timeout: 6e5,
|
|
@@ -8340,7 +9216,7 @@ function runOpencode(prompt, cmd) {
|
|
|
8340
9216
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
8341
9217
|
proc.on("close", (code) => {
|
|
8342
9218
|
if (code === 0) {
|
|
8343
|
-
|
|
9219
|
+
resolve4(stripAnsi(stdout));
|
|
8344
9220
|
} else {
|
|
8345
9221
|
reject(new Error(`opencode exited ${code}: ${stripAnsi(stderr).slice(0, 500)}`));
|
|
8346
9222
|
}
|
|
@@ -8351,7 +9227,7 @@ function runOpencode(prompt, cmd) {
|
|
|
8351
9227
|
});
|
|
8352
9228
|
}
|
|
8353
9229
|
function runAgent(prompt, cmd) {
|
|
8354
|
-
return new Promise((
|
|
9230
|
+
return new Promise((resolve4, reject) => {
|
|
8355
9231
|
const proc = spawnTool(cmd, ["-p", "--output-format", "json", "--mode", "ask", "--trust"], {
|
|
8356
9232
|
env: { ...process.env, NO_COLOR: "1" },
|
|
8357
9233
|
timeout: 6e5,
|
|
@@ -8365,9 +9241,9 @@ function runAgent(prompt, cmd) {
|
|
|
8365
9241
|
if (code === 0) {
|
|
8366
9242
|
try {
|
|
8367
9243
|
const parsed = JSON.parse(stdout);
|
|
8368
|
-
|
|
9244
|
+
resolve4(typeof parsed.result === "string" ? parsed.result : stdout);
|
|
8369
9245
|
} catch {
|
|
8370
|
-
|
|
9246
|
+
resolve4(stdout);
|
|
8371
9247
|
}
|
|
8372
9248
|
} else {
|
|
8373
9249
|
reject(new Error(`agent exited ${code}: ${stderr.slice(0, 500)}`));
|
|
@@ -8955,40 +9831,210 @@ async function generateToneAdjustment(session, tool, opts) {
|
|
|
8955
9831
|
}
|
|
8956
9832
|
if (allOverlays.length === 0 && totalSkipped === 0) return null;
|
|
8957
9833
|
return {
|
|
8958
|
-
overlays: allOverlays,
|
|
8959
|
-
stats: { adjusted: allOverlays.length, skipped: totalSkipped }
|
|
9834
|
+
overlays: allOverlays,
|
|
9835
|
+
stats: { adjusted: allOverlays.length, skipped: totalSkipped }
|
|
9836
|
+
};
|
|
9837
|
+
}
|
|
9838
|
+
function stripAnsi(str) {
|
|
9839
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "");
|
|
9840
|
+
}
|
|
9841
|
+
function locateCommand(cmd) {
|
|
9842
|
+
return new Promise((resolve4, reject) => {
|
|
9843
|
+
const locator = IS_WINDOWS ? "where" : "which";
|
|
9844
|
+
const proc = spawn(locator, [cmd], { timeout: 5e3 });
|
|
9845
|
+
let out = "";
|
|
9846
|
+
proc.stdout.on("data", (d) => out += d.toString());
|
|
9847
|
+
proc.on("close", (code) => {
|
|
9848
|
+
if (code !== 0) {
|
|
9849
|
+
resolve4(null);
|
|
9850
|
+
return;
|
|
9851
|
+
}
|
|
9852
|
+
const first = out.split(/\r?\n/).find((line) => line.trim());
|
|
9853
|
+
resolve4(first ? first.trim() : null);
|
|
9854
|
+
});
|
|
9855
|
+
proc.on("error", reject);
|
|
9856
|
+
});
|
|
9857
|
+
}
|
|
9858
|
+
|
|
9859
|
+
// src/server.ts
|
|
9860
|
+
init_insights();
|
|
9861
|
+
init_overlays();
|
|
9862
|
+
init_cloud();
|
|
9863
|
+
|
|
9864
|
+
// src/server-persistence.ts
|
|
9865
|
+
import { mkdir as mkdir5, readFile as readFile20, writeFile as writeFile6 } from "fs/promises";
|
|
9866
|
+
import { join as join20, resolve as resolve2 } from "path";
|
|
9867
|
+
async function loadAnnotations(baseDir, slug) {
|
|
9868
|
+
const dirs = [join20(baseDir, slug), resolve2("./vibe-replay", slug)];
|
|
9869
|
+
for (const dir of dirs) {
|
|
9870
|
+
try {
|
|
9871
|
+
const raw = await readFile20(join20(dir, "annotations.json"), "utf-8");
|
|
9872
|
+
const anns = JSON.parse(raw);
|
|
9873
|
+
if (Array.isArray(anns)) return anns;
|
|
9874
|
+
} catch {
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
return [];
|
|
9878
|
+
}
|
|
9879
|
+
async function saveAnnotations(baseDir, slug, annotations) {
|
|
9880
|
+
await mkdir5(join20(baseDir, slug), { recursive: true });
|
|
9881
|
+
const annPath = join20(baseDir, slug, "annotations.json");
|
|
9882
|
+
await writeFile6(annPath, JSON.stringify(annotations, null, 2), "utf-8");
|
|
9883
|
+
}
|
|
9884
|
+
async function saveOverlays(baseDir, slug, overlays) {
|
|
9885
|
+
await mkdir5(join20(baseDir, slug), { recursive: true });
|
|
9886
|
+
const overlayPath = join20(baseDir, slug, "overlays.json");
|
|
9887
|
+
await writeFile6(overlayPath, JSON.stringify(overlays, null, 2), "utf-8");
|
|
9888
|
+
}
|
|
9889
|
+
|
|
9890
|
+
// src/server-routes/session-assets.ts
|
|
9891
|
+
init_overlays();
|
|
9892
|
+
|
|
9893
|
+
// src/server-core.ts
|
|
9894
|
+
import { homedir as homedir16 } from "os";
|
|
9895
|
+
import { basename as basename8 } from "path";
|
|
9896
|
+
function safeSlug(raw) {
|
|
9897
|
+
if (!raw) return null;
|
|
9898
|
+
const clean = basename8(raw);
|
|
9899
|
+
if (!clean || clean !== raw || clean === "." || clean === "..") return null;
|
|
9900
|
+
return clean;
|
|
9901
|
+
}
|
|
9902
|
+
function requireSlug(raw) {
|
|
9903
|
+
const slug = safeSlug(raw);
|
|
9904
|
+
if (!slug) return { error: "slug parameter is required" };
|
|
9905
|
+
return { slug };
|
|
9906
|
+
}
|
|
9907
|
+
function getErrorMessage(err) {
|
|
9908
|
+
if (err instanceof Error) return err.message;
|
|
9909
|
+
if (typeof err === "string") return err;
|
|
9910
|
+
return "Unknown error";
|
|
9911
|
+
}
|
|
9912
|
+
function normalizeProjectPath(project) {
|
|
9913
|
+
const home = homedir16();
|
|
9914
|
+
return project.startsWith(home) ? `~${project.slice(home.length)}` : project;
|
|
9915
|
+
}
|
|
9916
|
+
var MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST = 90;
|
|
9917
|
+
function chunkItems(items, maxItems) {
|
|
9918
|
+
if (maxItems <= 0) return [items.slice()];
|
|
9919
|
+
const chunks = [];
|
|
9920
|
+
for (let i = 0; i < items.length; i += maxItems) {
|
|
9921
|
+
chunks.push(items.slice(i, i + maxItems));
|
|
9922
|
+
}
|
|
9923
|
+
return chunks;
|
|
9924
|
+
}
|
|
9925
|
+
function buildInsightsSyncBatches(days, existingDates, today, maxDaysPerBatch = MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST) {
|
|
9926
|
+
const existing = new Set(existingDates);
|
|
9927
|
+
const pending = days.filter((day) => !existing.has(day.date) || day.date === today);
|
|
9928
|
+
return chunkItems(pending, maxDaysPerBatch);
|
|
9929
|
+
}
|
|
9930
|
+
function toStringArray(value) {
|
|
9931
|
+
if (value === void 0) return [];
|
|
9932
|
+
if (!Array.isArray(value)) return null;
|
|
9933
|
+
if (value.some((item) => typeof item !== "string")) return null;
|
|
9934
|
+
return value;
|
|
9935
|
+
}
|
|
9936
|
+
function resolveGenerateInputs(body, discoveredSessions) {
|
|
9937
|
+
const filePaths = toStringArray(body.filePaths);
|
|
9938
|
+
if (!filePaths) {
|
|
9939
|
+
return { ok: false, error: "filePaths must be an array of strings" };
|
|
9940
|
+
}
|
|
9941
|
+
const toolPaths = toStringArray(body.toolPaths);
|
|
9942
|
+
if (!toolPaths) {
|
|
9943
|
+
return { ok: false, error: "toolPaths must be an array of strings" };
|
|
9944
|
+
}
|
|
9945
|
+
const requestedSessionSlug = typeof body.sessionSlug === "string" ? safeSlug(body.sessionSlug) : null;
|
|
9946
|
+
const requestedSessionProject = typeof body.sessionProject === "string" ? normalizeProjectPath(body.sessionProject) : void 0;
|
|
9947
|
+
let sessionInfo;
|
|
9948
|
+
if (requestedSessionSlug) {
|
|
9949
|
+
const slugMatches = discoveredSessions.filter((s) => s.slug === requestedSessionSlug);
|
|
9950
|
+
if (requestedSessionProject) {
|
|
9951
|
+
sessionInfo = slugMatches.find(
|
|
9952
|
+
(s) => normalizeProjectPath(s.project) === requestedSessionProject
|
|
9953
|
+
);
|
|
9954
|
+
}
|
|
9955
|
+
sessionInfo = sessionInfo || slugMatches[0];
|
|
9956
|
+
}
|
|
9957
|
+
if (!sessionInfo && typeof body.sessionId === "string" && body.sessionId) {
|
|
9958
|
+
sessionInfo = discoveredSessions.find((s) => s.sessionId === body.sessionId);
|
|
9959
|
+
}
|
|
9960
|
+
const fallbackFilePaths = sessionInfo?.filePaths || [];
|
|
9961
|
+
const fallbackToolPaths = sessionInfo?.toolPaths || [];
|
|
9962
|
+
const paths = [
|
|
9963
|
+
...filePaths.length > 0 ? filePaths : fallbackFilePaths,
|
|
9964
|
+
...toolPaths.length > 0 ? toolPaths : fallbackToolPaths
|
|
9965
|
+
];
|
|
9966
|
+
const hasCursorSessionFallback = body.provider === "cursor" && Boolean(sessionInfo?.sessionId);
|
|
9967
|
+
if (paths.length === 0 && !hasCursorSessionFallback) {
|
|
9968
|
+
return {
|
|
9969
|
+
ok: false,
|
|
9970
|
+
error: "filePaths is required (or provide a resolvable Cursor sessionSlug for SQLite/global-state sessions)"
|
|
9971
|
+
};
|
|
9972
|
+
}
|
|
9973
|
+
return {
|
|
9974
|
+
ok: true,
|
|
9975
|
+
value: {
|
|
9976
|
+
paths,
|
|
9977
|
+
sessionInfo
|
|
9978
|
+
}
|
|
8960
9979
|
};
|
|
8961
9980
|
}
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
const
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
}
|
|
8979
|
-
|
|
9981
|
+
|
|
9982
|
+
// src/server-routes/session-assets.ts
|
|
9983
|
+
function registerSessionAssetRoutes(app, deps) {
|
|
9984
|
+
const { baseDir } = deps;
|
|
9985
|
+
app.get("/api/annotations", async (c) => {
|
|
9986
|
+
const result = requireSlug(c.req.query("slug"));
|
|
9987
|
+
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
9988
|
+
const anns = await loadAnnotations(baseDir, result.slug);
|
|
9989
|
+
return c.json(anns);
|
|
9990
|
+
});
|
|
9991
|
+
app.post("/api/annotations", async (c) => {
|
|
9992
|
+
const result = requireSlug(c.req.query("slug"));
|
|
9993
|
+
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
9994
|
+
let body;
|
|
9995
|
+
try {
|
|
9996
|
+
body = await c.req.json();
|
|
9997
|
+
} catch {
|
|
9998
|
+
return c.json({ error: "invalid JSON body" }, 400);
|
|
9999
|
+
}
|
|
10000
|
+
try {
|
|
10001
|
+
await saveAnnotations(baseDir, result.slug, body);
|
|
10002
|
+
} catch (err) {
|
|
10003
|
+
return c.json({ error: `Failed to save annotations: ${getErrorMessage(err)}` }, 500);
|
|
10004
|
+
}
|
|
10005
|
+
return c.json({ ok: true });
|
|
10006
|
+
});
|
|
10007
|
+
app.get("/api/overlays", async (c) => {
|
|
10008
|
+
const result = requireSlug(c.req.query("slug"));
|
|
10009
|
+
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
10010
|
+
const overlays = await loadOverlays(baseDir, result.slug);
|
|
10011
|
+
return c.json(overlays);
|
|
10012
|
+
});
|
|
10013
|
+
app.post("/api/overlays", async (c) => {
|
|
10014
|
+
const result = requireSlug(c.req.query("slug"));
|
|
10015
|
+
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
10016
|
+
let body;
|
|
10017
|
+
try {
|
|
10018
|
+
body = await c.req.json();
|
|
10019
|
+
} catch {
|
|
10020
|
+
return c.json({ error: "invalid JSON body" }, 400);
|
|
10021
|
+
}
|
|
10022
|
+
if (!body || !Array.isArray(body.overlays)) {
|
|
10023
|
+
return c.json({ error: "invalid overlays shape" }, 400);
|
|
10024
|
+
}
|
|
10025
|
+
try {
|
|
10026
|
+
await saveOverlays(baseDir, result.slug, body);
|
|
10027
|
+
} catch (err) {
|
|
10028
|
+
return c.json({ error: `Failed to save overlays: ${getErrorMessage(err)}` }, 500);
|
|
10029
|
+
}
|
|
10030
|
+
return c.json({ ok: true });
|
|
8980
10031
|
});
|
|
8981
10032
|
}
|
|
8982
10033
|
|
|
8983
|
-
// src/server.ts
|
|
8984
|
-
init_insights();
|
|
8985
|
-
init_overlays();
|
|
8986
|
-
init_cloud();
|
|
8987
|
-
|
|
8988
10034
|
// src/scanner.ts
|
|
8989
|
-
import { readdir as
|
|
8990
|
-
import { homedir as
|
|
8991
|
-
import { join as
|
|
10035
|
+
import { readdir as readdir11, readFile as readFile21, stat as stat10 } from "fs/promises";
|
|
10036
|
+
import { homedir as homedir17 } from "os";
|
|
10037
|
+
import { join as join21 } from "path";
|
|
8992
10038
|
|
|
8993
10039
|
// src/pricing.ts
|
|
8994
10040
|
var MODEL_PRICING = {
|
|
@@ -9168,6 +10214,12 @@ async function scanSession(input) {
|
|
|
9168
10214
|
} catch {
|
|
9169
10215
|
}
|
|
9170
10216
|
}
|
|
10217
|
+
if (input.provider === "pi") {
|
|
10218
|
+
try {
|
|
10219
|
+
return await scanPiSession(input);
|
|
10220
|
+
} catch {
|
|
10221
|
+
}
|
|
10222
|
+
}
|
|
9171
10223
|
let startTime;
|
|
9172
10224
|
let endTime;
|
|
9173
10225
|
let model;
|
|
@@ -9193,7 +10245,7 @@ async function scanSession(input) {
|
|
|
9193
10245
|
for (const filePath of input.filePaths) {
|
|
9194
10246
|
let content;
|
|
9195
10247
|
try {
|
|
9196
|
-
content = await
|
|
10248
|
+
content = await readFile21(filePath, "utf-8");
|
|
9197
10249
|
} catch {
|
|
9198
10250
|
continue;
|
|
9199
10251
|
}
|
|
@@ -9315,15 +10367,15 @@ async function scanSession(input) {
|
|
|
9315
10367
|
if (input.filePaths.length > 0) {
|
|
9316
10368
|
const mainFile = input.filePaths[0];
|
|
9317
10369
|
const sessionDir = mainFile.replace(/\.jsonl$/, "");
|
|
9318
|
-
const subagentsDir =
|
|
10370
|
+
const subagentsDir = join21(sessionDir, "subagents");
|
|
9319
10371
|
try {
|
|
9320
|
-
const files = await
|
|
10372
|
+
const files = await readdir11(subagentsDir);
|
|
9321
10373
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
9322
10374
|
subAgentCount = jsonlFiles.length;
|
|
9323
10375
|
for (const saFile of jsonlFiles) {
|
|
9324
10376
|
let saContent;
|
|
9325
10377
|
try {
|
|
9326
|
-
saContent = await
|
|
10378
|
+
saContent = await readFile21(join21(subagentsDir, saFile), "utf-8");
|
|
9327
10379
|
} catch {
|
|
9328
10380
|
continue;
|
|
9329
10381
|
}
|
|
@@ -9444,6 +10496,25 @@ async function scanCodexSession(input) {
|
|
|
9444
10496
|
const parsed = await parseCodexSession(input.filePaths, sessionInfo);
|
|
9445
10497
|
return buildScanResultFromParsed(input, parsed);
|
|
9446
10498
|
}
|
|
10499
|
+
async function scanPiSession(input) {
|
|
10500
|
+
const sessionInfo = {
|
|
10501
|
+
provider: "pi",
|
|
10502
|
+
sessionId: input.sessionId,
|
|
10503
|
+
slug: input.slug,
|
|
10504
|
+
title: input.title,
|
|
10505
|
+
project: input.project,
|
|
10506
|
+
cwd: input.workspacePath || input.project,
|
|
10507
|
+
version: "",
|
|
10508
|
+
timestamp: input.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
10509
|
+
lineCount: 0,
|
|
10510
|
+
fileSize: 0,
|
|
10511
|
+
filePath: input.filePaths[0] || "",
|
|
10512
|
+
filePaths: input.filePaths,
|
|
10513
|
+
firstPrompt: input.firstPrompt || input.title || "(pi session)"
|
|
10514
|
+
};
|
|
10515
|
+
const parsed = await parsePiSession(input.filePaths, sessionInfo);
|
|
10516
|
+
return buildScanResultFromParsed(input, parsed);
|
|
10517
|
+
}
|
|
9447
10518
|
async function scanCursorSession(input) {
|
|
9448
10519
|
if (input.deferRichCursorParse && input.hasSqlite) {
|
|
9449
10520
|
return buildLightweightCursorScanResult(input);
|
|
@@ -9616,7 +10687,7 @@ async function getFileMeta(filePaths) {
|
|
|
9616
10687
|
let maxMtime = 0;
|
|
9617
10688
|
for (const fp of filePaths) {
|
|
9618
10689
|
try {
|
|
9619
|
-
const s = await
|
|
10690
|
+
const s = await stat10(fp);
|
|
9620
10691
|
totalSize += s.size;
|
|
9621
10692
|
if (s.mtimeMs > maxMtime) maxMtime = s.mtimeMs;
|
|
9622
10693
|
} catch {
|
|
@@ -9969,33 +11040,33 @@ function buildAggregateDataQuality(scans) {
|
|
|
9969
11040
|
}
|
|
9970
11041
|
return notes.length > 0 ? { notes } : void 0;
|
|
9971
11042
|
}
|
|
9972
|
-
var CLAUDE_DIR2 =
|
|
11043
|
+
var CLAUDE_DIR2 = join21(homedir17(), ".claude", "projects");
|
|
9973
11044
|
function encodeProjectDir(project) {
|
|
9974
11045
|
let resolved = project;
|
|
9975
11046
|
if (resolved.startsWith("~/")) {
|
|
9976
|
-
resolved =
|
|
11047
|
+
resolved = join21(homedir17(), resolved.slice(2));
|
|
9977
11048
|
} else if (resolved === "~") {
|
|
9978
|
-
resolved =
|
|
11049
|
+
resolved = homedir17();
|
|
9979
11050
|
}
|
|
9980
11051
|
return resolved.replace(/\//g, "-");
|
|
9981
11052
|
}
|
|
9982
11053
|
async function readProjectMemory(project) {
|
|
9983
11054
|
const encoded = encodeProjectDir(project);
|
|
9984
|
-
const projectDir =
|
|
9985
|
-
const memoryDir =
|
|
11055
|
+
const projectDir = join21(CLAUDE_DIR2, encoded);
|
|
11056
|
+
const memoryDir = join21(projectDir, "memory");
|
|
9986
11057
|
const memoryFiles = [];
|
|
9987
11058
|
let claudeMd;
|
|
9988
11059
|
try {
|
|
9989
|
-
const content = await
|
|
11060
|
+
const content = await readFile21(join21(projectDir, "CLAUDE.md"), "utf-8");
|
|
9990
11061
|
if (content.trim()) claudeMd = content.slice(0, 5e3);
|
|
9991
11062
|
} catch {
|
|
9992
11063
|
}
|
|
9993
11064
|
try {
|
|
9994
|
-
const files = await
|
|
11065
|
+
const files = await readdir11(memoryDir);
|
|
9995
11066
|
for (const file of files) {
|
|
9996
11067
|
if (!file.endsWith(".md") || file === "MEMORY.md") continue;
|
|
9997
11068
|
try {
|
|
9998
|
-
const content = await
|
|
11069
|
+
const content = await readFile21(join21(memoryDir, file), "utf-8");
|
|
9999
11070
|
const fm = parseFrontmatter(content);
|
|
10000
11071
|
memoryFiles.push({
|
|
10001
11072
|
name: fm.name || file.replace(/\.md$/, ""),
|
|
@@ -10036,7 +11107,7 @@ function extractMetaText(content) {
|
|
|
10036
11107
|
}
|
|
10037
11108
|
|
|
10038
11109
|
// src/transform.ts
|
|
10039
|
-
import { homedir as
|
|
11110
|
+
import { homedir as homedir18 } from "os";
|
|
10040
11111
|
|
|
10041
11112
|
// src/utils/tokenEstimate.ts
|
|
10042
11113
|
function estimateTokens(s) {
|
|
@@ -10046,7 +11117,7 @@ function estimateTokens(s) {
|
|
|
10046
11117
|
}
|
|
10047
11118
|
|
|
10048
11119
|
// src/transform.ts
|
|
10049
|
-
var HOME =
|
|
11120
|
+
var HOME = homedir18();
|
|
10050
11121
|
function redactPath(s) {
|
|
10051
11122
|
if (!HOME) return s;
|
|
10052
11123
|
return s.replaceAll(HOME, "~");
|
|
@@ -10482,108 +11553,23 @@ function redactSecrets(s) {
|
|
|
10482
11553
|
// src/server.ts
|
|
10483
11554
|
init_utils();
|
|
10484
11555
|
init_version();
|
|
10485
|
-
function safeSlug(raw) {
|
|
10486
|
-
if (!raw) return null;
|
|
10487
|
-
const clean = basename6(raw);
|
|
10488
|
-
if (!clean || clean !== raw || clean === "." || clean === "..") return null;
|
|
10489
|
-
return clean;
|
|
10490
|
-
}
|
|
10491
|
-
function requireSlug(raw) {
|
|
10492
|
-
const slug = safeSlug(raw);
|
|
10493
|
-
if (!slug) return { error: "slug parameter is required" };
|
|
10494
|
-
return { slug };
|
|
10495
|
-
}
|
|
10496
|
-
function getErrorMessage(err) {
|
|
10497
|
-
if (err instanceof Error) return err.message;
|
|
10498
|
-
if (typeof err === "string") return err;
|
|
10499
|
-
return "Unknown error";
|
|
10500
|
-
}
|
|
10501
|
-
function normalizeProjectPath(project) {
|
|
10502
|
-
const home = homedir16();
|
|
10503
|
-
return project.startsWith(home) ? `~${project.slice(home.length)}` : project;
|
|
10504
|
-
}
|
|
10505
|
-
var MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST = 90;
|
|
10506
|
-
function chunkItems(items, maxItems) {
|
|
10507
|
-
if (maxItems <= 0) return [items.slice()];
|
|
10508
|
-
const chunks = [];
|
|
10509
|
-
for (let i = 0; i < items.length; i += maxItems) {
|
|
10510
|
-
chunks.push(items.slice(i, i + maxItems));
|
|
10511
|
-
}
|
|
10512
|
-
return chunks;
|
|
10513
|
-
}
|
|
10514
|
-
function buildInsightsSyncBatches(days, existingDates, today, maxDaysPerBatch = MAX_INSIGHTS_SYNC_DAYS_PER_REQUEST) {
|
|
10515
|
-
const existing = new Set(existingDates);
|
|
10516
|
-
const pending = days.filter((day) => !existing.has(day.date) || day.date === today);
|
|
10517
|
-
return chunkItems(pending, maxDaysPerBatch);
|
|
10518
|
-
}
|
|
10519
|
-
function toStringArray(value) {
|
|
10520
|
-
if (value === void 0) return [];
|
|
10521
|
-
if (!Array.isArray(value)) return null;
|
|
10522
|
-
if (value.some((item) => typeof item !== "string")) return null;
|
|
10523
|
-
return value;
|
|
10524
|
-
}
|
|
10525
|
-
function resolveGenerateInputs(body, discoveredSessions) {
|
|
10526
|
-
const filePaths = toStringArray(body.filePaths);
|
|
10527
|
-
if (!filePaths) {
|
|
10528
|
-
return { ok: false, error: "filePaths must be an array of strings" };
|
|
10529
|
-
}
|
|
10530
|
-
const toolPaths = toStringArray(body.toolPaths);
|
|
10531
|
-
if (!toolPaths) {
|
|
10532
|
-
return { ok: false, error: "toolPaths must be an array of strings" };
|
|
10533
|
-
}
|
|
10534
|
-
const requestedSessionSlug = typeof body.sessionSlug === "string" ? safeSlug(body.sessionSlug) : null;
|
|
10535
|
-
const requestedSessionProject = typeof body.sessionProject === "string" ? normalizeProjectPath(body.sessionProject) : void 0;
|
|
10536
|
-
let sessionInfo;
|
|
10537
|
-
if (requestedSessionSlug) {
|
|
10538
|
-
const slugMatches = discoveredSessions.filter((s) => s.slug === requestedSessionSlug);
|
|
10539
|
-
if (requestedSessionProject) {
|
|
10540
|
-
sessionInfo = slugMatches.find(
|
|
10541
|
-
(s) => normalizeProjectPath(s.project) === requestedSessionProject
|
|
10542
|
-
);
|
|
10543
|
-
}
|
|
10544
|
-
sessionInfo = sessionInfo || slugMatches[0];
|
|
10545
|
-
}
|
|
10546
|
-
if (!sessionInfo && typeof body.sessionId === "string" && body.sessionId) {
|
|
10547
|
-
sessionInfo = discoveredSessions.find((s) => s.sessionId === body.sessionId);
|
|
10548
|
-
}
|
|
10549
|
-
const fallbackFilePaths = sessionInfo?.filePaths || [];
|
|
10550
|
-
const fallbackToolPaths = sessionInfo?.toolPaths || [];
|
|
10551
|
-
const paths = [
|
|
10552
|
-
...filePaths.length > 0 ? filePaths : fallbackFilePaths,
|
|
10553
|
-
...toolPaths.length > 0 ? toolPaths : fallbackToolPaths
|
|
10554
|
-
];
|
|
10555
|
-
const hasCursorSessionFallback = body.provider === "cursor" && Boolean(sessionInfo?.sessionId);
|
|
10556
|
-
if (paths.length === 0 && !hasCursorSessionFallback) {
|
|
10557
|
-
return {
|
|
10558
|
-
ok: false,
|
|
10559
|
-
error: "filePaths is required (or provide a resolvable Cursor sessionSlug for SQLite/global-state sessions)"
|
|
10560
|
-
};
|
|
10561
|
-
}
|
|
10562
|
-
return {
|
|
10563
|
-
ok: true,
|
|
10564
|
-
value: {
|
|
10565
|
-
paths,
|
|
10566
|
-
sessionInfo
|
|
10567
|
-
}
|
|
10568
|
-
};
|
|
10569
|
-
}
|
|
10570
11556
|
var ARCHIVE_DIR = ".archive";
|
|
10571
11557
|
async function getArchivedSlugs(baseDir) {
|
|
10572
11558
|
try {
|
|
10573
|
-
const entries = await
|
|
11559
|
+
const entries = await readdir12(join22(baseDir, ARCHIVE_DIR));
|
|
10574
11560
|
return new Set(entries);
|
|
10575
11561
|
} catch {
|
|
10576
11562
|
return /* @__PURE__ */ new Set();
|
|
10577
11563
|
}
|
|
10578
11564
|
}
|
|
10579
11565
|
async function archiveSlug(baseDir, slug) {
|
|
10580
|
-
const dir =
|
|
10581
|
-
await
|
|
10582
|
-
await
|
|
11566
|
+
const dir = join22(baseDir, ARCHIVE_DIR);
|
|
11567
|
+
await mkdir6(dir, { recursive: true });
|
|
11568
|
+
await writeFile7(join22(dir, slug), "");
|
|
10583
11569
|
}
|
|
10584
11570
|
async function unarchiveSlug(baseDir, slug) {
|
|
10585
11571
|
try {
|
|
10586
|
-
await unlink2(
|
|
11572
|
+
await unlink2(join22(baseDir, ARCHIVE_DIR, slug));
|
|
10587
11573
|
} catch {
|
|
10588
11574
|
}
|
|
10589
11575
|
}
|
|
@@ -10591,29 +11577,22 @@ async function scanSessionsFromDir(baseDir) {
|
|
|
10591
11577
|
const results = [];
|
|
10592
11578
|
let entries;
|
|
10593
11579
|
try {
|
|
10594
|
-
entries = await
|
|
11580
|
+
entries = await readdir12(baseDir);
|
|
10595
11581
|
} catch {
|
|
10596
11582
|
return results;
|
|
10597
11583
|
}
|
|
10598
11584
|
for (const entry of entries) {
|
|
10599
|
-
const replayPath =
|
|
11585
|
+
const replayPath = join22(baseDir, entry, "replay.json");
|
|
10600
11586
|
try {
|
|
10601
|
-
const raw = await
|
|
11587
|
+
const raw = await readFile22(replayPath, "utf-8");
|
|
10602
11588
|
const session = JSON.parse(raw);
|
|
10603
|
-
const
|
|
10604
|
-
let annotationCount = 0;
|
|
10605
|
-
try {
|
|
10606
|
-
const annRaw = await readFile19(annotationsPath, "utf-8");
|
|
10607
|
-
const anns = JSON.parse(annRaw);
|
|
10608
|
-
annotationCount = Array.isArray(anns) ? anns.length : 0;
|
|
10609
|
-
} catch {
|
|
10610
|
-
}
|
|
11589
|
+
const annotationCount = (await loadAnnotations(baseDir, entry)).length;
|
|
10611
11590
|
let gist;
|
|
10612
11591
|
try {
|
|
10613
|
-
gist = await loadSavedGistInfo(
|
|
11592
|
+
gist = await loadSavedGistInfo(join22(baseDir, entry));
|
|
10614
11593
|
} catch {
|
|
10615
11594
|
}
|
|
10616
|
-
const cloudInfo = await loadSavedCloudInfo(
|
|
11595
|
+
const cloudInfo = await loadSavedCloudInfo(join22(baseDir, entry));
|
|
10617
11596
|
const userPrompts = (session.scenes || []).filter((sc) => sc.type === "user-prompt").map((sc) => previewPrompt(sc.content)).filter((m) => m.length >= 10);
|
|
10618
11597
|
const firstMessage = userPrompts[0] || void 0;
|
|
10619
11598
|
const messages = userPrompts.length > 0 ? userPrompts.slice(0, 2) : void 0;
|
|
@@ -10641,8 +11620,8 @@ async function scanSessionsFromDir(baseDir) {
|
|
|
10641
11620
|
let outdated = false;
|
|
10642
11621
|
if (gist?.contentHash) {
|
|
10643
11622
|
try {
|
|
10644
|
-
const content = await
|
|
10645
|
-
const currentHash =
|
|
11623
|
+
const content = await readFile22(replayPath, "utf-8");
|
|
11624
|
+
const currentHash = createHash5("sha256").update(content).digest("hex").slice(0, 16);
|
|
10646
11625
|
outdated = currentHash !== gist?.contentHash;
|
|
10647
11626
|
} catch {
|
|
10648
11627
|
}
|
|
@@ -10668,7 +11647,7 @@ async function scanSessionsFromDir(baseDir) {
|
|
|
10668
11647
|
}
|
|
10669
11648
|
async function scanSessions(baseDir) {
|
|
10670
11649
|
const dirs = [baseDir];
|
|
10671
|
-
const cwdLocal =
|
|
11650
|
+
const cwdLocal = resolve3("./vibe-replay");
|
|
10672
11651
|
if (cwdLocal !== baseDir) {
|
|
10673
11652
|
dirs.push(cwdLocal);
|
|
10674
11653
|
}
|
|
@@ -10687,25 +11666,19 @@ async function scanSessions(baseDir) {
|
|
|
10687
11666
|
return allResults;
|
|
10688
11667
|
}
|
|
10689
11668
|
async function loadSessionFromDisk(baseDir, slug) {
|
|
10690
|
-
let replayPath =
|
|
11669
|
+
let replayPath = join22(baseDir, slug, "replay.json");
|
|
10691
11670
|
try {
|
|
10692
|
-
await
|
|
11671
|
+
await stat11(replayPath);
|
|
10693
11672
|
} catch {
|
|
10694
|
-
const fallback =
|
|
10695
|
-
await
|
|
11673
|
+
const fallback = resolve3("./vibe-replay", slug, "replay.json");
|
|
11674
|
+
await stat11(fallback);
|
|
10696
11675
|
replayPath = fallback;
|
|
10697
11676
|
}
|
|
10698
|
-
const raw = await
|
|
11677
|
+
const raw = await readFile22(replayPath, "utf-8");
|
|
10699
11678
|
const session = JSON.parse(raw);
|
|
10700
|
-
const
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
const annRaw = await readFile19(annotationsPath, "utf-8");
|
|
10704
|
-
const anns = JSON.parse(annRaw);
|
|
10705
|
-
if (Array.isArray(anns) && anns.length > 0) {
|
|
10706
|
-
session.annotations = anns;
|
|
10707
|
-
}
|
|
10708
|
-
} catch {
|
|
11679
|
+
const annotations = await loadAnnotations(baseDir, slug);
|
|
11680
|
+
if (annotations.length > 0) {
|
|
11681
|
+
session.annotations = annotations;
|
|
10709
11682
|
}
|
|
10710
11683
|
return session;
|
|
10711
11684
|
}
|
|
@@ -10873,13 +11846,13 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
|
|
|
10873
11846
|
const projectExistsMap = /* @__PURE__ */ new Map();
|
|
10874
11847
|
const projectIsGitMap = /* @__PURE__ */ new Map();
|
|
10875
11848
|
for (const p of uniqueProjects) {
|
|
10876
|
-
const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ?
|
|
11849
|
+
const resolved = p === "~" ? home : p.startsWith("~/") || p.startsWith("~\\") ? join22(home, p.slice(2)) : p;
|
|
10877
11850
|
try {
|
|
10878
|
-
const s = await
|
|
11851
|
+
const s = await stat11(resolved);
|
|
10879
11852
|
projectExistsMap.set(p, s.isDirectory());
|
|
10880
11853
|
if (s.isDirectory()) {
|
|
10881
11854
|
try {
|
|
10882
|
-
await
|
|
11855
|
+
await stat11(join22(resolved, ".git"));
|
|
10883
11856
|
projectIsGitMap.set(p, true);
|
|
10884
11857
|
} catch {
|
|
10885
11858
|
projectIsGitMap.set(p, false);
|
|
@@ -10959,10 +11932,10 @@ async function buildSourcesResult(merged, baseDir, home, previousSources = [], c
|
|
|
10959
11932
|
});
|
|
10960
11933
|
}
|
|
10961
11934
|
async function readClaudeSessionState(sessionId) {
|
|
10962
|
-
const sessionsDir =
|
|
11935
|
+
const sessionsDir = join22(homedir19(), ".claude", "sessions");
|
|
10963
11936
|
let files;
|
|
10964
11937
|
try {
|
|
10965
|
-
files = await
|
|
11938
|
+
files = await readdir12(sessionsDir);
|
|
10966
11939
|
} catch {
|
|
10967
11940
|
return "stopped";
|
|
10968
11941
|
}
|
|
@@ -10970,7 +11943,7 @@ async function readClaudeSessionState(sessionId) {
|
|
|
10970
11943
|
if (!file.endsWith(".json")) continue;
|
|
10971
11944
|
let data;
|
|
10972
11945
|
try {
|
|
10973
|
-
const content = await
|
|
11946
|
+
const content = await readFile22(join22(sessionsDir, file), "utf-8");
|
|
10974
11947
|
data = JSON.parse(content);
|
|
10975
11948
|
} catch {
|
|
10976
11949
|
continue;
|
|
@@ -11019,32 +11992,12 @@ function mergeSameSessions(sessions) {
|
|
|
11019
11992
|
result.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
11020
11993
|
return result;
|
|
11021
11994
|
}
|
|
11022
|
-
async function loadAnnotations(baseDir, slug) {
|
|
11023
|
-
const dirs = [join18(baseDir, slug), resolve2("./vibe-replay", slug)];
|
|
11024
|
-
for (const dir of dirs) {
|
|
11025
|
-
try {
|
|
11026
|
-
const raw = await readFile19(join18(dir, "annotations.json"), "utf-8");
|
|
11027
|
-
const anns = JSON.parse(raw);
|
|
11028
|
-
if (Array.isArray(anns)) return anns;
|
|
11029
|
-
} catch {
|
|
11030
|
-
}
|
|
11031
|
-
}
|
|
11032
|
-
return [];
|
|
11033
|
-
}
|
|
11034
|
-
async function saveAnnotations(baseDir, slug, annotations) {
|
|
11035
|
-
const annPath = join18(baseDir, slug, "annotations.json");
|
|
11036
|
-
await writeFile6(annPath, JSON.stringify(annotations, null, 2), "utf-8");
|
|
11037
|
-
}
|
|
11038
|
-
async function saveOverlays(baseDir, slug, overlays) {
|
|
11039
|
-
const overlayPath = join18(baseDir, slug, "overlays.json");
|
|
11040
|
-
await writeFile6(overlayPath, JSON.stringify(overlays, null, 2), "utf-8");
|
|
11041
|
-
}
|
|
11042
11995
|
async function startServer(baseDir, opts) {
|
|
11043
|
-
await
|
|
11996
|
+
await mkdir6(baseDir, { recursive: true });
|
|
11044
11997
|
const isDevMode = !!opts?.externalViewerUrl;
|
|
11045
11998
|
const viewerHtml = isDevMode ? "" : await loadViewerHtml();
|
|
11046
11999
|
const cleanupPeriodDays = await getClaudeCodeCleanupPeriod();
|
|
11047
|
-
const cacheKeySuffix =
|
|
12000
|
+
const cacheKeySuffix = createHash5("sha1").update(baseDir).digest("hex").slice(0, 12);
|
|
11048
12001
|
const sourcesCacheKey = `dashboard-sources-v4-${cacheKeySuffix}`;
|
|
11049
12002
|
const replaysCacheKey = `dashboard-replays-v1-${cacheKeySuffix}`;
|
|
11050
12003
|
const scanResultsCacheKey = `dashboard-scan-results-v1-${cacheKeySuffix}`;
|
|
@@ -11384,7 +12337,7 @@ async function startServer(baseDir, opts) {
|
|
|
11384
12337
|
allSessions.push(...sessions);
|
|
11385
12338
|
}
|
|
11386
12339
|
const merged = mergeSameSessions(allSessions);
|
|
11387
|
-
const home =
|
|
12340
|
+
const home = homedir19();
|
|
11388
12341
|
for (const s of merged) {
|
|
11389
12342
|
if (s.project.startsWith(home)) {
|
|
11390
12343
|
s.project = `~${s.project.slice(home.length)}`;
|
|
@@ -11498,7 +12451,7 @@ async function startServer(baseDir, opts) {
|
|
|
11498
12451
|
await sendError(`Session not found: ${sessionId}`);
|
|
11499
12452
|
return;
|
|
11500
12453
|
}
|
|
11501
|
-
const home =
|
|
12454
|
+
const home = homedir19();
|
|
11502
12455
|
const projectFor = (info) => info.project.startsWith(home) ? `~${info.project.slice(home.length)}` : info.project;
|
|
11503
12456
|
const watchers = [];
|
|
11504
12457
|
const watchedPaths = /* @__PURE__ */ new Set();
|
|
@@ -11511,7 +12464,8 @@ async function startServer(baseDir, opts) {
|
|
|
11511
12464
|
const isClaudeProvider = providerName === "claude-code";
|
|
11512
12465
|
const isCursorProvider = providerName === "cursor";
|
|
11513
12466
|
const isCodexProvider = providerName === "codex";
|
|
11514
|
-
const
|
|
12467
|
+
const isPiProvider = providerName === "pi";
|
|
12468
|
+
const isJsonlLiveProvider = isClaudeProvider || isCodexProvider || isPiProvider;
|
|
11515
12469
|
let lastLiveState = isClaudeProvider ? "busy" : "unknown";
|
|
11516
12470
|
let cursorDbWatchAttached = false;
|
|
11517
12471
|
const cursorDbWatchedSessionIds = /* @__PURE__ */ new Set();
|
|
@@ -11565,13 +12519,13 @@ async function startServer(baseDir, opts) {
|
|
|
11565
12519
|
const cached = jsonlTail.get(filePath);
|
|
11566
12520
|
let size;
|
|
11567
12521
|
try {
|
|
11568
|
-
size = (await
|
|
12522
|
+
size = (await stat11(filePath)).size;
|
|
11569
12523
|
} catch {
|
|
11570
12524
|
jsonlTail.delete(filePath);
|
|
11571
12525
|
return cached?.lines ?? [];
|
|
11572
12526
|
}
|
|
11573
12527
|
if (!cached || size < cached.offset) {
|
|
11574
|
-
const content = await
|
|
12528
|
+
const content = await readFile22(filePath);
|
|
11575
12529
|
const { lines, partial } = splitDecodedLines(content);
|
|
11576
12530
|
jsonlTail.set(filePath, { offset: content.length, partial, lines });
|
|
11577
12531
|
return lines;
|
|
@@ -11629,7 +12583,7 @@ async function startServer(baseDir, opts) {
|
|
|
11629
12583
|
const lines = await tailReadJsonl(fp);
|
|
11630
12584
|
allLines.push(...lines);
|
|
11631
12585
|
}
|
|
11632
|
-
parsed = isClaudeProvider ? await parseClaudeCodeLines(allLines, { subagentsSourcePath: paths[0] }) : parseCodexLines(allLines, info, paths);
|
|
12586
|
+
parsed = isClaudeProvider ? await parseClaudeCodeLines(allLines, { subagentsSourcePath: paths[0] }) : isCodexProvider ? parseCodexLines(allLines, info, paths) : parsePiLines(allLines, { sourcePath: paths[0], sessionInfo: info });
|
|
11633
12587
|
} else {
|
|
11634
12588
|
parsed = await provider.parse(paths, info);
|
|
11635
12589
|
}
|
|
@@ -11717,7 +12671,7 @@ async function startServer(baseDir, opts) {
|
|
|
11717
12671
|
if (aborted) return;
|
|
11718
12672
|
scheduleRebuild(0);
|
|
11719
12673
|
}, RESOLVE_REFRESH_INTERVAL_MS);
|
|
11720
|
-
await new Promise((
|
|
12674
|
+
await new Promise((resolve4) => {
|
|
11721
12675
|
stream.onAbort(() => {
|
|
11722
12676
|
aborted = true;
|
|
11723
12677
|
if (pendingTimer) clearTimeout(pendingTimer);
|
|
@@ -11731,7 +12685,7 @@ async function startServer(baseDir, opts) {
|
|
|
11731
12685
|
} catch {
|
|
11732
12686
|
}
|
|
11733
12687
|
}
|
|
11734
|
-
|
|
12688
|
+
resolve4();
|
|
11735
12689
|
});
|
|
11736
12690
|
});
|
|
11737
12691
|
});
|
|
@@ -11763,8 +12717,8 @@ async function startServer(baseDir, opts) {
|
|
|
11763
12717
|
try {
|
|
11764
12718
|
const target = await loadSessionFromDisk(baseDir, slug);
|
|
11765
12719
|
target.meta.title = normalizeTitle(body.title);
|
|
11766
|
-
const targetDir =
|
|
11767
|
-
await
|
|
12720
|
+
const targetDir = join22(baseDir, slug);
|
|
12721
|
+
await writeFile7(join22(targetDir, "replay.json"), JSON.stringify(target), "utf-8");
|
|
11768
12722
|
await generateOutput(target, targetDir);
|
|
11769
12723
|
const updatedReplays = await refreshReplaysCache();
|
|
11770
12724
|
if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
|
|
@@ -11778,7 +12732,7 @@ async function startServer(baseDir, opts) {
|
|
|
11778
12732
|
if (!slug) return c.json({ error: "invalid slug" }, 400);
|
|
11779
12733
|
try {
|
|
11780
12734
|
const { rm } = await import("fs/promises");
|
|
11781
|
-
await rm(
|
|
12735
|
+
await rm(join22(baseDir, slug), { recursive: true });
|
|
11782
12736
|
const updatedReplays = await refreshReplaysCache();
|
|
11783
12737
|
if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
|
|
11784
12738
|
return c.json({ ok: true });
|
|
@@ -11820,7 +12774,7 @@ async function startServer(baseDir, opts) {
|
|
|
11820
12774
|
}
|
|
11821
12775
|
const cursorProvider2 = getProvider("cursor");
|
|
11822
12776
|
if (!cursorProvider2) return c.json({ ok: false, message: "Cursor provider unavailable" }, 404);
|
|
11823
|
-
const home =
|
|
12777
|
+
const home = homedir19();
|
|
11824
12778
|
let cursorSessions = lastDiscoveredMergedSessions.filter(
|
|
11825
12779
|
(session) => session.provider === "cursor"
|
|
11826
12780
|
);
|
|
@@ -11851,12 +12805,12 @@ async function startServer(baseDir, opts) {
|
|
|
11851
12805
|
allSessions.push(...sessions);
|
|
11852
12806
|
}
|
|
11853
12807
|
const merged = mergeSameSessions(allSessions);
|
|
11854
|
-
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged,
|
|
12808
|
+
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir19());
|
|
11855
12809
|
const previous = await readFileCache(sourcesCacheKey);
|
|
11856
12810
|
const result = await buildSourcesResult(
|
|
11857
12811
|
merged,
|
|
11858
12812
|
baseDir,
|
|
11859
|
-
|
|
12813
|
+
homedir19(),
|
|
11860
12814
|
previous?.data || [],
|
|
11861
12815
|
cleanupPeriodDays
|
|
11862
12816
|
);
|
|
@@ -11886,12 +12840,12 @@ async function startServer(baseDir, opts) {
|
|
|
11886
12840
|
}
|
|
11887
12841
|
}
|
|
11888
12842
|
const merged = mergeSameSessions(allSessions);
|
|
11889
|
-
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged,
|
|
12843
|
+
lastDiscoveredMergedSessions = normalizeSessionProjectsForHome(merged, homedir19());
|
|
11890
12844
|
const previous = await readFileCache(sourcesCacheKey);
|
|
11891
12845
|
const result = await buildSourcesResult(
|
|
11892
12846
|
merged,
|
|
11893
12847
|
baseDir,
|
|
11894
|
-
|
|
12848
|
+
homedir19(),
|
|
11895
12849
|
previous?.data || [],
|
|
11896
12850
|
cleanupPeriodDays
|
|
11897
12851
|
);
|
|
@@ -11926,7 +12880,7 @@ async function startServer(baseDir, opts) {
|
|
|
11926
12880
|
return c.json({ error: "title must be a string" }, 400);
|
|
11927
12881
|
}
|
|
11928
12882
|
const parsed = await provider.parse(resolved.value.paths, resolved.value.sessionInfo);
|
|
11929
|
-
const home =
|
|
12883
|
+
const home = homedir19();
|
|
11930
12884
|
const rawProject = body.sessionProject || parsed.cwd;
|
|
11931
12885
|
const project = rawProject.startsWith(home) ? `~${rawProject.slice(home.length)}` : rawProject;
|
|
11932
12886
|
const replay = transformToReplay(parsed, body.provider, project, {
|
|
@@ -11944,7 +12898,7 @@ async function startServer(baseDir, opts) {
|
|
|
11944
12898
|
}
|
|
11945
12899
|
const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
|
|
11946
12900
|
const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
11947
|
-
const outputDir =
|
|
12901
|
+
const outputDir = join22(baseDir, slug);
|
|
11948
12902
|
await generateOutput(replay, outputDir);
|
|
11949
12903
|
const updatedReplays = await refreshReplaysCache();
|
|
11950
12904
|
if (updatedReplays) await syncSourcesCacheWithReplays(updatedReplays);
|
|
@@ -11967,7 +12921,7 @@ async function startServer(baseDir, opts) {
|
|
|
11967
12921
|
});
|
|
11968
12922
|
app.post("/api/regenerate-all", async (c) => {
|
|
11969
12923
|
const replaysDir = baseDir;
|
|
11970
|
-
const { readdir:
|
|
12924
|
+
const { readdir: readdir13, readFile: readF } = await import("fs/promises");
|
|
11971
12925
|
const results = [];
|
|
11972
12926
|
const allProviders = getAllProviders();
|
|
11973
12927
|
const allSessions = [];
|
|
@@ -11980,14 +12934,14 @@ async function startServer(baseDir, opts) {
|
|
|
11980
12934
|
}
|
|
11981
12935
|
let entries;
|
|
11982
12936
|
try {
|
|
11983
|
-
entries = await
|
|
12937
|
+
entries = await readdir13(replaysDir);
|
|
11984
12938
|
} catch {
|
|
11985
12939
|
return c.json({ error: "No replays directory" }, 404);
|
|
11986
12940
|
}
|
|
11987
12941
|
for (const slug of entries) {
|
|
11988
12942
|
if (slug.startsWith(".") || slug === "cache") continue;
|
|
11989
12943
|
try {
|
|
11990
|
-
const replayPath =
|
|
12944
|
+
const replayPath = join22(replaysDir, slug, "replay.json");
|
|
11991
12945
|
const raw = await readF(replayPath, "utf-8").catch(() => null);
|
|
11992
12946
|
if (!raw) continue;
|
|
11993
12947
|
const oldReplay = JSON.parse(raw);
|
|
@@ -12009,7 +12963,7 @@ async function startServer(baseDir, opts) {
|
|
|
12009
12963
|
}
|
|
12010
12964
|
const paths = [...sessionInfo.filePaths, ...sessionInfo.toolPaths || []];
|
|
12011
12965
|
const parsed = await provider.parse(paths, sessionInfo);
|
|
12012
|
-
const home =
|
|
12966
|
+
const home = homedir19();
|
|
12013
12967
|
const project = sessionInfo.project.startsWith(home) ? `~${sessionInfo.project.slice(home.length)}` : sessionInfo.project;
|
|
12014
12968
|
const replay = transformToReplay(parsed, providerName, project, {
|
|
12015
12969
|
generator: {
|
|
@@ -12019,7 +12973,7 @@ async function startServer(baseDir, opts) {
|
|
|
12019
12973
|
}
|
|
12020
12974
|
});
|
|
12021
12975
|
if (oldReplay.meta?.title) replay.meta.title = oldReplay.meta.title;
|
|
12022
|
-
const outputDir =
|
|
12976
|
+
const outputDir = join22(replaysDir, slug);
|
|
12023
12977
|
await generateOutput(replay, outputDir);
|
|
12024
12978
|
results.push({ slug, status: "regenerated", scenes: replay.scenes.length });
|
|
12025
12979
|
} catch (err) {
|
|
@@ -12124,28 +13078,7 @@ async function startServer(baseDir, opts) {
|
|
|
12124
13078
|
if (!memory) return c.json({ memoryFiles: [], claudeMd: null });
|
|
12125
13079
|
return c.json(memory);
|
|
12126
13080
|
});
|
|
12127
|
-
app
|
|
12128
|
-
const result = requireSlug(c.req.query("slug"));
|
|
12129
|
-
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12130
|
-
const anns = await loadAnnotations(baseDir, result.slug);
|
|
12131
|
-
return c.json(anns);
|
|
12132
|
-
});
|
|
12133
|
-
app.post("/api/annotations", async (c) => {
|
|
12134
|
-
const result = requireSlug(c.req.query("slug"));
|
|
12135
|
-
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12136
|
-
let body;
|
|
12137
|
-
try {
|
|
12138
|
-
body = await c.req.json();
|
|
12139
|
-
} catch {
|
|
12140
|
-
return c.json({ error: "invalid JSON body" }, 400);
|
|
12141
|
-
}
|
|
12142
|
-
try {
|
|
12143
|
-
await saveAnnotations(baseDir, result.slug, body);
|
|
12144
|
-
} catch (err) {
|
|
12145
|
-
return c.json({ error: `Failed to save annotations: ${getErrorMessage(err)}` }, 500);
|
|
12146
|
-
}
|
|
12147
|
-
return c.json({ ok: true });
|
|
12148
|
-
});
|
|
13081
|
+
registerSessionAssetRoutes(app, { baseDir });
|
|
12149
13082
|
app.get("/api/gh-status", (c) => {
|
|
12150
13083
|
return c.json(checkPublishStatus());
|
|
12151
13084
|
});
|
|
@@ -12526,7 +13459,7 @@ async function startServer(baseDir, opts) {
|
|
|
12526
13459
|
app.get("/api/gist-info", async (c) => {
|
|
12527
13460
|
const result = requireSlug(c.req.query("slug"));
|
|
12528
13461
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12529
|
-
const targetDir =
|
|
13462
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12530
13463
|
const gist = await loadSavedGistInfo(targetDir);
|
|
12531
13464
|
if (!gist) return c.json({ gist: null });
|
|
12532
13465
|
return c.json({ gist });
|
|
@@ -12534,7 +13467,7 @@ async function startServer(baseDir, opts) {
|
|
|
12534
13467
|
app.delete("/api/gist-info", async (c) => {
|
|
12535
13468
|
const result = requireSlug(c.req.query("slug"));
|
|
12536
13469
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12537
|
-
const metaPath =
|
|
13470
|
+
const metaPath = join22(baseDir, result.slug, ".vibe-replay-gist.json");
|
|
12538
13471
|
await unlink2(metaPath).catch(() => {
|
|
12539
13472
|
});
|
|
12540
13473
|
return c.json({ ok: true });
|
|
@@ -12542,7 +13475,7 @@ async function startServer(baseDir, opts) {
|
|
|
12542
13475
|
app.get("/api/cloud-info", async (c) => {
|
|
12543
13476
|
const result = requireSlug(c.req.query("slug"));
|
|
12544
13477
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12545
|
-
const targetDir =
|
|
13478
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12546
13479
|
const cloud = await loadSavedCloudInfo(targetDir);
|
|
12547
13480
|
if (!cloud) return c.json({ cloud: null });
|
|
12548
13481
|
return c.json({ cloud });
|
|
@@ -12550,11 +13483,11 @@ async function startServer(baseDir, opts) {
|
|
|
12550
13483
|
app.post("/api/cloud-info", async (c) => {
|
|
12551
13484
|
const result = requireSlug(c.req.query("slug"));
|
|
12552
13485
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12553
|
-
const targetDir =
|
|
13486
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12554
13487
|
const body = await c.req.json();
|
|
12555
13488
|
if (!body.id || !body.url) return c.json({ error: "Missing id/url" }, 400);
|
|
12556
|
-
const metaPath =
|
|
12557
|
-
await
|
|
13489
|
+
const metaPath = join22(targetDir, ".vibe-replay-cloud.json");
|
|
13490
|
+
await writeFile7(
|
|
12558
13491
|
metaPath,
|
|
12559
13492
|
JSON.stringify(
|
|
12560
13493
|
{
|
|
@@ -12573,7 +13506,7 @@ async function startServer(baseDir, opts) {
|
|
|
12573
13506
|
app.delete("/api/cloud-info", async (c) => {
|
|
12574
13507
|
const result = requireSlug(c.req.query("slug"));
|
|
12575
13508
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12576
|
-
const metaPath =
|
|
13509
|
+
const metaPath = join22(baseDir, result.slug, ".vibe-replay-cloud.json");
|
|
12577
13510
|
await unlink2(metaPath).catch(() => {
|
|
12578
13511
|
});
|
|
12579
13512
|
return c.json({ ok: true });
|
|
@@ -12581,14 +13514,14 @@ async function startServer(baseDir, opts) {
|
|
|
12581
13514
|
app.post("/api/publish/gist", async (c) => {
|
|
12582
13515
|
const result = requireSlug(c.req.query("slug"));
|
|
12583
13516
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12584
|
-
const targetDir =
|
|
13517
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12585
13518
|
try {
|
|
12586
13519
|
const rawSession = await loadSessionFromDisk(baseDir, result.slug);
|
|
12587
13520
|
const overlaysData = await loadOverlays(baseDir, result.slug);
|
|
12588
13521
|
const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
|
|
12589
|
-
const replayPath =
|
|
12590
|
-
const originalContent = await
|
|
12591
|
-
await
|
|
13522
|
+
const replayPath = join22(targetDir, "replay.json");
|
|
13523
|
+
const originalContent = await readFile22(replayPath, "utf-8");
|
|
13524
|
+
await writeFile7(replayPath, JSON.stringify(targetSession), "utf-8");
|
|
12592
13525
|
try {
|
|
12593
13526
|
const title = targetSession.meta.title || targetSession.meta.slug;
|
|
12594
13527
|
const savedGist = await loadSavedGistInfo(targetDir);
|
|
@@ -12597,7 +13530,7 @@ async function startServer(baseDir, opts) {
|
|
|
12597
13530
|
});
|
|
12598
13531
|
return c.json(gistResult);
|
|
12599
13532
|
} finally {
|
|
12600
|
-
await
|
|
13533
|
+
await writeFile7(replayPath, originalContent, "utf-8");
|
|
12601
13534
|
}
|
|
12602
13535
|
} catch (err) {
|
|
12603
13536
|
return c.json({ error: getErrorMessage(err) }, 500);
|
|
@@ -12606,7 +13539,7 @@ async function startServer(baseDir, opts) {
|
|
|
12606
13539
|
app.post("/api/publish/cloud", async (c) => {
|
|
12607
13540
|
const result = requireSlug(c.req.query("slug"));
|
|
12608
13541
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12609
|
-
const targetDir =
|
|
13542
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12610
13543
|
try {
|
|
12611
13544
|
const body = await c.req.json().catch(() => ({}));
|
|
12612
13545
|
const cloudResult = await publishCloudWithOverlays(targetDir, {
|
|
@@ -12620,18 +13553,18 @@ async function startServer(baseDir, opts) {
|
|
|
12620
13553
|
app.post("/api/export/html", async (c) => {
|
|
12621
13554
|
const result = requireSlug(c.req.query("slug"));
|
|
12622
13555
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12623
|
-
const targetDir =
|
|
13556
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12624
13557
|
try {
|
|
12625
13558
|
const rawSession = await loadSessionFromDisk(baseDir, result.slug);
|
|
12626
13559
|
const overlaysData = await loadOverlays(baseDir, result.slug);
|
|
12627
13560
|
const targetSession = sessionWithEffectiveContent(rawSession, overlaysData);
|
|
12628
|
-
const replayPath =
|
|
12629
|
-
const originalContent = await
|
|
13561
|
+
const replayPath = join22(targetDir, "replay.json");
|
|
13562
|
+
const originalContent = await readFile22(replayPath, "utf-8");
|
|
12630
13563
|
try {
|
|
12631
13564
|
const outputPath = await generateOutput(targetSession, targetDir);
|
|
12632
13565
|
return c.json({ path: outputPath });
|
|
12633
13566
|
} finally {
|
|
12634
|
-
await
|
|
13567
|
+
await writeFile7(replayPath, originalContent, "utf-8");
|
|
12635
13568
|
}
|
|
12636
13569
|
} catch (err) {
|
|
12637
13570
|
return c.json({ error: getErrorMessage(err) }, 500);
|
|
@@ -12640,23 +13573,23 @@ async function startServer(baseDir, opts) {
|
|
|
12640
13573
|
app.get("/api/export/github/status", async (c) => {
|
|
12641
13574
|
const result = requireSlug(c.req.query("slug"));
|
|
12642
13575
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12643
|
-
const targetDir =
|
|
13576
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12644
13577
|
try {
|
|
12645
|
-
const svgPath =
|
|
12646
|
-
const mdPath =
|
|
12647
|
-
const gifPath =
|
|
13578
|
+
const svgPath = join22(targetDir, "session-preview.svg");
|
|
13579
|
+
const mdPath = join22(targetDir, "github-summary.md");
|
|
13580
|
+
const gifPath = join22(targetDir, "session-preview.gif");
|
|
12648
13581
|
const [svgContent, markdown, gifBuf] = await Promise.all([
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
13582
|
+
readFile22(svgPath, "utf-8").catch(() => null),
|
|
13583
|
+
readFile22(mdPath, "utf-8").catch(() => null),
|
|
13584
|
+
readFile22(gifPath).catch(() => null)
|
|
12652
13585
|
]);
|
|
12653
13586
|
if (!svgContent && !markdown && !gifBuf) return c.json({ exists: false });
|
|
12654
13587
|
const gist = await loadSavedGistInfo(targetDir);
|
|
12655
13588
|
const gifContent = gifBuf ? gifBuf.toString("base64") : null;
|
|
12656
13589
|
const [gifMtime, svgMtime, mdMtime] = await Promise.all([
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
13590
|
+
stat11(gifPath).then((s) => s.mtime.toISOString()).catch(() => null),
|
|
13591
|
+
stat11(svgPath).then((s) => s.mtime.toISOString()).catch(() => null),
|
|
13592
|
+
stat11(mdPath).then((s) => s.mtime.toISOString()).catch(() => null)
|
|
12660
13593
|
]);
|
|
12661
13594
|
return c.json({
|
|
12662
13595
|
exists: true,
|
|
@@ -12678,7 +13611,7 @@ async function startServer(baseDir, opts) {
|
|
|
12678
13611
|
app.post("/api/export/github", async (c) => {
|
|
12679
13612
|
const result = requireSlug(c.req.query("slug"));
|
|
12680
13613
|
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12681
|
-
const targetDir =
|
|
13614
|
+
const targetDir = join22(baseDir, result.slug);
|
|
12682
13615
|
try {
|
|
12683
13616
|
const rawSession = await loadSessionFromDisk(baseDir, result.slug);
|
|
12684
13617
|
const overlaysData = await loadOverlays(baseDir, result.slug);
|
|
@@ -12686,15 +13619,15 @@ async function startServer(baseDir, opts) {
|
|
|
12686
13619
|
const gist = await loadSavedGistInfo(targetDir);
|
|
12687
13620
|
const replayUrl = gist?.viewerUrl || void 0;
|
|
12688
13621
|
const svgContent = generateGitHubSvg(targetSession, { replayUrl });
|
|
12689
|
-
const svgFilePath =
|
|
12690
|
-
await
|
|
13622
|
+
const svgFilePath = join22(targetDir, "session-preview.svg");
|
|
13623
|
+
await writeFile7(svgFilePath, svgContent, "utf-8");
|
|
12691
13624
|
let gifContent = null;
|
|
12692
13625
|
let gifFilePath = null;
|
|
12693
13626
|
let gifWarning;
|
|
12694
13627
|
try {
|
|
12695
13628
|
const gifBuffer = await generateGitHubGif(targetSession, { replayUrl });
|
|
12696
|
-
gifFilePath =
|
|
12697
|
-
await
|
|
13629
|
+
gifFilePath = join22(targetDir, "session-preview.gif");
|
|
13630
|
+
await writeFile7(gifFilePath, gifBuffer);
|
|
12698
13631
|
gifContent = gifBuffer.toString("base64");
|
|
12699
13632
|
} catch (err) {
|
|
12700
13633
|
gifWarning = `GIF generation failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -12704,8 +13637,8 @@ async function startServer(baseDir, opts) {
|
|
|
12704
13637
|
svgPath: "./session-preview.svg",
|
|
12705
13638
|
gifPath: gifContent ? "./session-preview.gif" : void 0
|
|
12706
13639
|
});
|
|
12707
|
-
const mdFilePath =
|
|
12708
|
-
await
|
|
13640
|
+
const mdFilePath = join22(targetDir, "github-summary.md");
|
|
13641
|
+
await writeFile7(mdFilePath, markdown, "utf-8");
|
|
12709
13642
|
const findings = scanForSecrets(JSON.stringify(targetSession));
|
|
12710
13643
|
const warnings = findings.map((f) => `[${f.rule}] ${f.match}`);
|
|
12711
13644
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12785,31 +13718,6 @@ async function startServer(baseDir, opts) {
|
|
|
12785
13718
|
return c.json({ error: getErrorMessage(err) }, 500);
|
|
12786
13719
|
}
|
|
12787
13720
|
});
|
|
12788
|
-
app.get("/api/overlays", async (c) => {
|
|
12789
|
-
const result = requireSlug(c.req.query("slug"));
|
|
12790
|
-
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12791
|
-
const overlays = await loadOverlays(baseDir, result.slug);
|
|
12792
|
-
return c.json(overlays);
|
|
12793
|
-
});
|
|
12794
|
-
app.post("/api/overlays", async (c) => {
|
|
12795
|
-
const result = requireSlug(c.req.query("slug"));
|
|
12796
|
-
if ("error" in result) return c.json({ error: result.error }, 400);
|
|
12797
|
-
let body;
|
|
12798
|
-
try {
|
|
12799
|
-
body = await c.req.json();
|
|
12800
|
-
} catch {
|
|
12801
|
-
return c.json({ error: "invalid JSON body" }, 400);
|
|
12802
|
-
}
|
|
12803
|
-
if (!body || !Array.isArray(body.overlays)) {
|
|
12804
|
-
return c.json({ error: "invalid overlays shape" }, 400);
|
|
12805
|
-
}
|
|
12806
|
-
try {
|
|
12807
|
-
await saveOverlays(baseDir, result.slug, body);
|
|
12808
|
-
} catch (err) {
|
|
12809
|
-
return c.json({ error: `Failed to save overlays: ${getErrorMessage(err)}` }, 500);
|
|
12810
|
-
}
|
|
12811
|
-
return c.json({ ok: true });
|
|
12812
|
-
});
|
|
12813
13721
|
function fixOriginalValues(overlays, originalSession) {
|
|
12814
13722
|
for (const overlay of overlays) {
|
|
12815
13723
|
const scene = originalSession.scenes[overlay.sceneIndex];
|
|
@@ -12937,10 +13845,10 @@ async function startServer(baseDir, opts) {
|
|
|
12937
13845
|
}
|
|
12938
13846
|
}
|
|
12939
13847
|
);
|
|
12940
|
-
await new Promise((
|
|
13848
|
+
await new Promise((resolve4) => {
|
|
12941
13849
|
process.on("SIGINT", () => {
|
|
12942
13850
|
console.log(chalk.dim("\n Server stopped.\n"));
|
|
12943
|
-
|
|
13851
|
+
resolve4();
|
|
12944
13852
|
process.exit(0);
|
|
12945
13853
|
});
|
|
12946
13854
|
});
|
|
@@ -13299,21 +14207,21 @@ function formatDuration2(ms) {
|
|
|
13299
14207
|
init_utils();
|
|
13300
14208
|
init_version();
|
|
13301
14209
|
async function runGitHubExport(replay, outputDir) {
|
|
13302
|
-
const { join:
|
|
13303
|
-
const { writeFile:
|
|
14210
|
+
const { join: join23 } = await import("path");
|
|
14211
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
13304
14212
|
const savedGist = await loadSavedGistInfo(outputDir);
|
|
13305
14213
|
const replayUrl = savedGist?.viewerUrl;
|
|
13306
14214
|
const svgSpinner = ora("Generating animated SVG...").start();
|
|
13307
14215
|
const svgContent = generateGitHubSvg(replay, { replayUrl });
|
|
13308
|
-
const svgFilePath =
|
|
13309
|
-
await
|
|
14216
|
+
const svgFilePath = join23(outputDir, "session-preview.svg");
|
|
14217
|
+
await writeFile8(svgFilePath, svgContent, "utf-8");
|
|
13310
14218
|
svgSpinner.succeed(`SVG: ${svgFilePath}`);
|
|
13311
14219
|
let gifGenerated = false;
|
|
13312
14220
|
const gifSpinner = ora("Generating animated GIF...").start();
|
|
13313
14221
|
try {
|
|
13314
14222
|
const gifBuffer = await generateGitHubGif(replay, { replayUrl });
|
|
13315
|
-
const gifFilePath =
|
|
13316
|
-
await
|
|
14223
|
+
const gifFilePath = join23(outputDir, "session-preview.gif");
|
|
14224
|
+
await writeFile8(gifFilePath, gifBuffer);
|
|
13317
14225
|
const gifSizeKB = Math.round(gifBuffer.length / 1024);
|
|
13318
14226
|
gifSpinner.succeed(`GIF: ${gifFilePath} (${gifSizeKB} KB)`);
|
|
13319
14227
|
gifGenerated = true;
|
|
@@ -13326,8 +14234,8 @@ async function runGitHubExport(replay, outputDir) {
|
|
|
13326
14234
|
svgPath: "./session-preview.svg",
|
|
13327
14235
|
gifPath: gifGenerated ? "./session-preview.gif" : void 0
|
|
13328
14236
|
});
|
|
13329
|
-
const mdFilePath =
|
|
13330
|
-
await
|
|
14237
|
+
const mdFilePath = join23(outputDir, "github-summary.md");
|
|
14238
|
+
await writeFile8(mdFilePath, markdown, "utf-8");
|
|
13331
14239
|
mdSpinner.succeed(`Markdown: ${mdFilePath}`);
|
|
13332
14240
|
const redactionsSpinner = ora("Generating redaction report...").start();
|
|
13333
14241
|
const alreadyRedactedCount = (markdown.match(/\[REDACTED\]/g) || []).length;
|
|
@@ -13342,8 +14250,8 @@ async function runGitHubExport(replay, outputDir) {
|
|
|
13342
14250
|
"leftoverFindings: regex matches in the final markdown \u2014 review these before sharing."
|
|
13343
14251
|
]
|
|
13344
14252
|
};
|
|
13345
|
-
const redactionsPath =
|
|
13346
|
-
await
|
|
14253
|
+
const redactionsPath = join23(outputDir, "redactions.json");
|
|
14254
|
+
await writeFile8(redactionsPath, `${JSON.stringify(redactionsReport, null, 2)}
|
|
13347
14255
|
`, "utf-8");
|
|
13348
14256
|
const leftoverNote = leftoverFindings.length > 0 ? chalk2.yellow(`(${leftoverFindings.length} leftover finding(s) \u2014 review)`) : chalk2.dim("(no leftover findings)");
|
|
13349
14257
|
redactionsSpinner.succeed(`Redactions: ${redactionsPath} ${leftoverNote}`);
|
|
@@ -13351,7 +14259,7 @@ async function runGitHubExport(replay, outputDir) {
|
|
|
13351
14259
|
markdown,
|
|
13352
14260
|
outputDir,
|
|
13353
14261
|
svgPath: svgFilePath,
|
|
13354
|
-
gifPath: gifGenerated ?
|
|
14262
|
+
gifPath: gifGenerated ? join23(outputDir, "session-preview.gif") : void 0,
|
|
13355
14263
|
mdPath: mdFilePath,
|
|
13356
14264
|
redactionsPath
|
|
13357
14265
|
};
|
|
@@ -13429,8 +14337,8 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
13429
14337
|
console.log(chalk2.bold.cyan("\n vibe-replay") + chalk2.dim(` v${CLI_VERSION}
|
|
13430
14338
|
`));
|
|
13431
14339
|
const { join: pathJoin } = await import("path");
|
|
13432
|
-
const { homedir:
|
|
13433
|
-
const replayBaseDir = pathJoin(
|
|
14340
|
+
const { homedir: homedir20 } = await import("os");
|
|
14341
|
+
const replayBaseDir = pathJoin(homedir20(), ".vibe-replay");
|
|
13434
14342
|
void discoverAllSessions().then(async (sessions) => {
|
|
13435
14343
|
await writeFileCache(SESSION_DISCOVERY_CACHE_KEY, sessions);
|
|
13436
14344
|
}).catch(() => {
|
|
@@ -13472,14 +14380,14 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
13472
14380
|
return;
|
|
13473
14381
|
}
|
|
13474
14382
|
if (topChoice === "replays") {
|
|
13475
|
-
const { readdir:
|
|
14383
|
+
const { readdir: readdir13, readFile: readFile23 } = await import("fs/promises");
|
|
13476
14384
|
const replayEntries = [];
|
|
13477
14385
|
try {
|
|
13478
|
-
const entries = await
|
|
14386
|
+
const entries = await readdir13(replayBaseDir);
|
|
13479
14387
|
for (const slug2 of entries) {
|
|
13480
14388
|
if (slug2.startsWith(".") || slug2 === "cache") continue;
|
|
13481
14389
|
try {
|
|
13482
|
-
const raw = await
|
|
14390
|
+
const raw = await readFile23(pathJoin(replayBaseDir, slug2, "replay.json"), "utf-8");
|
|
13483
14391
|
const replay2 = JSON.parse(raw);
|
|
13484
14392
|
const title = replay2.meta?.title || slug2;
|
|
13485
14393
|
const provider2 = replay2.meta?.provider || "";
|
|
@@ -13492,7 +14400,7 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
13492
14400
|
minute: "2-digit",
|
|
13493
14401
|
hour12: false
|
|
13494
14402
|
}) : "";
|
|
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);
|
|
14403
|
+
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") : provider2 === "pi" ? chalk2.hex("#14B8A6")("pi") : chalk2.yellow(provider2);
|
|
13496
14404
|
replayEntries.push({
|
|
13497
14405
|
name: `${providerBadge} ${chalk2.dim(`[${time}]`)} ${chalk2.white(title)} ${chalk2.dim(`(${scenes} scenes)`)}`,
|
|
13498
14406
|
value: slug2,
|
|
@@ -13665,10 +14573,10 @@ program.name("vibe-replay").description("AI Coding Session Replay & Sharing Tool
|
|
|
13665
14573
|
replay.meta.title = normalizedUserTitle;
|
|
13666
14574
|
}
|
|
13667
14575
|
}
|
|
13668
|
-
const { join:
|
|
14576
|
+
const { join: join23 } = await import("path");
|
|
13669
14577
|
const rawSlug = replay.meta.slug || replay.meta.sessionId.slice(0, 8);
|
|
13670
14578
|
const slug = rawSlug.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
13671
|
-
const outputDir =
|
|
14579
|
+
const outputDir = join23(home, ".vibe-replay", slug);
|
|
13672
14580
|
const genSpinner = ora("Generating replay...").start();
|
|
13673
14581
|
const outputPath = await generateOutput(replay, outputDir);
|
|
13674
14582
|
const { stat: fsStat } = await import("fs/promises");
|
|
@@ -13760,7 +14668,7 @@ ${chalk2.bold.green(" Done!")}
|
|
|
13760
14668
|
if (target === "local") {
|
|
13761
14669
|
await publishLocal(outputPath);
|
|
13762
14670
|
} else if (target === "editor") {
|
|
13763
|
-
await startServer(
|
|
14671
|
+
await startServer(join23(home, ".vibe-replay"), {
|
|
13764
14672
|
openSlug: slug,
|
|
13765
14673
|
...getDevViewerOpts()
|
|
13766
14674
|
});
|
|
@@ -13864,13 +14772,17 @@ ${chalk2.bold.green(" Done!")}
|
|
|
13864
14772
|
console.log(chalk2.dim(" File: ") + chalk2.white(outputPath));
|
|
13865
14773
|
console.log();
|
|
13866
14774
|
});
|
|
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(
|
|
14775
|
+
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(
|
|
14776
|
+
"-P, --provider-filter <name>",
|
|
14777
|
+
"Filter by provider (claude-code, cursor, codex, pi, ...)"
|
|
14778
|
+
).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, command) => {
|
|
13868
14779
|
const discoverSpinner = opts.json ? void 0 : ora("Discovering local sessions...").start();
|
|
13869
14780
|
try {
|
|
14781
|
+
const queryOptions = normalizeSessionsCommandOptions(opts, command);
|
|
13870
14782
|
const sessions = mergeSameSessions2(await discoverAllSessions());
|
|
13871
14783
|
discoverSpinner?.succeed(`Found ${sessions.length} sessions`);
|
|
13872
14784
|
const scanSpinner = opts.scan && !opts.json ? ora("Scanning matching sessions...").start() : void 0;
|
|
13873
|
-
const matches = await queryLocalSessions(sessions,
|
|
14785
|
+
const matches = await queryLocalSessions(sessions, queryOptions);
|
|
13874
14786
|
scanSpinner?.succeed(`Prepared ${matches.length} session result(s)`);
|
|
13875
14787
|
if (opts.json) {
|
|
13876
14788
|
console.log(JSON.stringify({ sessions: matches }, null, 2));
|
|
@@ -14019,9 +14931,9 @@ authCmd.command("status").description("Show current authentication status").opti
|
|
|
14019
14931
|
var VISIBILITIES = ["public", "unlisted", "private"];
|
|
14020
14932
|
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) => {
|
|
14021
14933
|
const { existsSync: existsSync2, statSync } = await import("fs");
|
|
14022
|
-
const { readFile:
|
|
14023
|
-
const { join:
|
|
14024
|
-
const { homedir:
|
|
14934
|
+
const { readFile: readFile23, readdir: readdir13 } = await import("fs/promises");
|
|
14935
|
+
const { join: join23, dirname: dirname5, resolve: resolve4 } = await import("path");
|
|
14936
|
+
const { homedir: homedir20 } = await import("os");
|
|
14025
14937
|
if (opts.apiUrl) {
|
|
14026
14938
|
process.env.VIBE_REPLAY_API_URL = opts.apiUrl.replace(/\/$/, "");
|
|
14027
14939
|
}
|
|
@@ -14040,7 +14952,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
14040
14952
|
}
|
|
14041
14953
|
let outputDir;
|
|
14042
14954
|
if (pathArg) {
|
|
14043
|
-
const abs =
|
|
14955
|
+
const abs = resolve4(pathArg);
|
|
14044
14956
|
if (!existsSync2(abs)) {
|
|
14045
14957
|
console.error(chalk2.red(`
|
|
14046
14958
|
\u2717 Path not found: ${abs}
|
|
@@ -14048,16 +14960,16 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
14048
14960
|
process.exit(1);
|
|
14049
14961
|
}
|
|
14050
14962
|
const s = statSync(abs);
|
|
14051
|
-
outputDir = s.isDirectory() ? abs :
|
|
14963
|
+
outputDir = s.isDirectory() ? abs : dirname5(abs);
|
|
14052
14964
|
} else {
|
|
14053
|
-
const replayBaseDir =
|
|
14054
|
-
const entries = await
|
|
14965
|
+
const replayBaseDir = join23(homedir20(), ".vibe-replay");
|
|
14966
|
+
const entries = await readdir13(replayBaseDir).catch(() => []);
|
|
14055
14967
|
const replays = [];
|
|
14056
14968
|
for (const slug of entries) {
|
|
14057
14969
|
if (slug.startsWith(".") || slug === "cache") continue;
|
|
14058
|
-
const jsonPath2 =
|
|
14970
|
+
const jsonPath2 = join23(replayBaseDir, slug, "replay.json");
|
|
14059
14971
|
try {
|
|
14060
|
-
const raw = await
|
|
14972
|
+
const raw = await readFile23(jsonPath2, "utf-8");
|
|
14061
14973
|
const replay = JSON.parse(raw);
|
|
14062
14974
|
const title = replay.meta?.title || slug;
|
|
14063
14975
|
const startTime = replay.meta?.startTime || "";
|
|
@@ -14070,7 +14982,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
14070
14982
|
}) : "";
|
|
14071
14983
|
replays.push({
|
|
14072
14984
|
name: time ? `${chalk2.dim(`[${time}]`)} ${chalk2.white(title)}` : chalk2.white(title),
|
|
14073
|
-
value:
|
|
14985
|
+
value: join23(replayBaseDir, slug),
|
|
14074
14986
|
time: startTime
|
|
14075
14987
|
});
|
|
14076
14988
|
} catch {
|
|
@@ -14086,7 +14998,7 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
14086
14998
|
choices: replays
|
|
14087
14999
|
});
|
|
14088
15000
|
}
|
|
14089
|
-
const jsonPath =
|
|
15001
|
+
const jsonPath = join23(outputDir, "replay.json");
|
|
14090
15002
|
if (!existsSync2(jsonPath)) {
|
|
14091
15003
|
console.error(chalk2.red(`
|
|
14092
15004
|
\u2717 No replay.json found in ${outputDir}
|
|
@@ -14109,15 +15021,15 @@ program.command("share").description("Share an existing replay via cloud (unlist
|
|
|
14109
15021
|
}
|
|
14110
15022
|
});
|
|
14111
15023
|
var LIVE_STALENESS_WARNING_MS = 5 * 6e4;
|
|
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) => {
|
|
15024
|
+
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, command) => {
|
|
14113
15025
|
const { join: pathJoin } = await import("path");
|
|
14114
|
-
const { homedir:
|
|
14115
|
-
const replayBaseDir = pathJoin(
|
|
15026
|
+
const { homedir: homedir20 } = await import("os");
|
|
15027
|
+
const replayBaseDir = pathJoin(homedir20(), ".vibe-replay");
|
|
14116
15028
|
console.log(chalk2.bold.cyan("\n vibe-replay live") + chalk2.dim(` v${CLI_VERSION}
|
|
14117
15029
|
`));
|
|
14118
15030
|
let target = null;
|
|
14119
15031
|
if (opts.session) {
|
|
14120
|
-
const providerHint = opts.provider;
|
|
15032
|
+
const providerHint = normalizeCommandProviderOption(opts.provider, command);
|
|
14121
15033
|
const providers2 = providerHint ? [getProvider(providerHint)].filter((p) => !!p) : getAllProviders();
|
|
14122
15034
|
for (const provider of providers2) {
|
|
14123
15035
|
try {
|
|
@@ -14139,7 +15051,8 @@ program.command("live").description("Watch a running AI coding session live in t
|
|
|
14139
15051
|
const ora2 = (await import("ora")).default;
|
|
14140
15052
|
const spinner = ora2("Finding the most recent session...").start();
|
|
14141
15053
|
const all = [];
|
|
14142
|
-
const
|
|
15054
|
+
const providerHint = normalizeCommandProviderOption(opts.provider, command);
|
|
15055
|
+
const providers2 = providerHint ? [getProvider(providerHint)].filter((p) => !!p) : getAllProviders();
|
|
14143
15056
|
for (const provider of providers2) {
|
|
14144
15057
|
try {
|
|
14145
15058
|
const sessions = await provider.discover();
|
|
@@ -14175,6 +15088,24 @@ program.command("login", { hidden: true }).description("Log in to vibe-replay (a
|
|
|
14175
15088
|
await authCmd.commands.find((c) => c.name() === "login")?.parseAsync(["login", ...process.argv.slice(3)], { from: "user" });
|
|
14176
15089
|
});
|
|
14177
15090
|
program.parse();
|
|
15091
|
+
function normalizeSessionsCommandOptions(opts, command) {
|
|
15092
|
+
const provider = opts.providerFilter || normalizeCommandProviderOption(opts.provider, command);
|
|
15093
|
+
return {
|
|
15094
|
+
...opts,
|
|
15095
|
+
...provider ? { provider } : {}
|
|
15096
|
+
};
|
|
15097
|
+
}
|
|
15098
|
+
function normalizeCommandProviderOption(provider, command) {
|
|
15099
|
+
if (command.getOptionValueSource("provider") === "cli") {
|
|
15100
|
+
return provider;
|
|
15101
|
+
}
|
|
15102
|
+
const parent = command.parent;
|
|
15103
|
+
if (parent?.getOptionValueSource("provider") === "cli") {
|
|
15104
|
+
const parentProvider = parent.opts().provider;
|
|
15105
|
+
return parentProvider;
|
|
15106
|
+
}
|
|
15107
|
+
return void 0;
|
|
15108
|
+
}
|
|
14178
15109
|
function formatSessionChoices(sessions, cleanupPeriodDays) {
|
|
14179
15110
|
const merged = mergeSameSessions2(sessions);
|
|
14180
15111
|
const byProject = /* @__PURE__ */ new Map();
|
|
@@ -14200,7 +15131,7 @@ function formatSessionChoices(sessions, cleanupPeriodDays) {
|
|
|
14200
15131
|
});
|
|
14201
15132
|
const sizeKB = Math.round(s.fileSize / 1024);
|
|
14202
15133
|
const prompt = s.firstPrompt.replace(/\n/g, " ").slice(0, 50);
|
|
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);
|
|
15134
|
+
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") : s.provider === "pi" ? chalk2.hex("#14B8A6")("pi") : chalk2.yellow(s.provider);
|
|
14204
15135
|
const titleStr = s.title ? chalk2.white(` "${s.title}"`) : "";
|
|
14205
15136
|
const fileCount = s.filePaths.length > 1 ? chalk2.dim(` [${s.filePaths.length} parts]`) : "";
|
|
14206
15137
|
const sqliteBadge = s.hasSqlite ? chalk2.green(" db") : "";
|