sessiongraph 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/gif-worker-CghHEBpp.js +1 -0
- package/dist/assets/index-BbP8lcrF.css +1 -0
- package/dist/assets/index-BowMe1f4.js +1 -0
- package/dist/assets/index-CRLUUuVS.js +1 -0
- package/dist/assets/index-Ci31shU-.js +165 -0
- package/dist/assets/{index-Bn0u7W0k.js → index-G_Vk_zeX.js} +1 -1
- package/dist/assets/mp4-worker-HFPra5JP.js +1 -0
- package/dist/assets/scene-Cg3ezvud.js +139 -0
- package/dist/assets/share-Cyn7-rP1.js +8 -0
- package/dist/assets/{three-B4kj4iLH.js → three-D_1Qt16u.js} +70 -70
- package/dist/index.html +2 -2
- package/dist-server/server.mjs +259 -103
- package/docs/guide.md +12 -6
- package/package.json +11 -7
- package/dist/assets/index-C9rv5Uuu.js +0 -163
- package/dist/assets/index-To7WPdy6.css +0 -1
- package/dist/assets/scene-DHluV79M.js +0 -49
package/dist-server/server.mjs
CHANGED
|
@@ -2635,6 +2635,14 @@ function databasePath(env = process.env, platform = process.platform, home = hom
|
|
|
2635
2635
|
return paths.find((path) => existsSync(path)) || paths[0];
|
|
2636
2636
|
}
|
|
2637
2637
|
|
|
2638
|
+
// src/playback-tokens.ts
|
|
2639
|
+
var validTokenCount = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
2640
|
+
function sumRecordedTokens(values) {
|
|
2641
|
+
if (!values.length || !values.every(validTokenCount)) return null;
|
|
2642
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
2643
|
+
return validTokenCount(total) ? total : null;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2638
2646
|
// server/opencode.ts
|
|
2639
2647
|
async function api(path, signal2) {
|
|
2640
2648
|
const endpoint = await Service.discover({ file: serviceFile() });
|
|
@@ -2698,7 +2706,7 @@ async function loadSessionGraph() {
|
|
|
2698
2706
|
agent: raw.agent || "build",
|
|
2699
2707
|
cost: raw.cost || 0,
|
|
2700
2708
|
// Input + output + cache tokens; reasoning is generally part of output.
|
|
2701
|
-
tokens: (t?.input
|
|
2709
|
+
tokens: sumRecordedTokens([t?.input, t?.output, t?.cache?.read, t?.cache?.write]),
|
|
2702
2710
|
outcome: raw.outcome
|
|
2703
2711
|
});
|
|
2704
2712
|
}
|
|
@@ -3028,11 +3036,11 @@ function opencodeEvents(raw, message) {
|
|
|
3028
3036
|
if (part.type === "text") events.push(eventFor(message, id2, "message", "Assistant message", part.text || "", { ...native, ...timing }));
|
|
3029
3037
|
else if (part.type === "reasoning") events.push(eventFor(message, id2, "reasoning", "Recorded reasoning", part.text || "", { ...native, ...timing }));
|
|
3030
3038
|
else if (part.type === "tool") {
|
|
3031
|
-
const
|
|
3032
|
-
const status =
|
|
3039
|
+
const state2 = part.state || {}, endTime = messageTime(part.time?.completed);
|
|
3040
|
+
const status = state2.status === "error" ? "failed" : state2.status === "streaming" ? "pending" : ["running", "completed"].includes(state2.status) ? state2.status : "unknown";
|
|
3033
3041
|
const common = { ...native, callID: part.id, status, endTime };
|
|
3034
|
-
events.push(eventFor(message, `${id2}:call`, "tool_call", part.name || "Tool call", "", { ...common, ...timing, input: eventText(
|
|
3035
|
-
if (["completed", "error"].includes(
|
|
3042
|
+
events.push(eventFor(message, `${id2}:call`, "tool_call", part.name || "Tool call", "", { ...common, ...timing, input: eventText(state2.input) }));
|
|
3043
|
+
if (["completed", "error"].includes(state2.status)) events.push(eventFor(message, `${id2}:result`, "tool_result", part.name || "Tool result", "", { ...common, role: "tool", ...endTime ? { time: endTime, timeSource: "recorded" } : {}, output: eventText(state2.error ?? state2.content, 64e3) }));
|
|
3036
3044
|
} else events.push(eventFor(message, id2, "event", `${part.type || "Unknown"} content`, "Unsupported content.", { ...native, ...timing, partial: true }));
|
|
3037
3045
|
}
|
|
3038
3046
|
if (raw.error) events.push(eventFor(message, "error", "error", "Assistant error", eventText(raw.error), { status: "failed" }));
|
|
@@ -30380,16 +30388,121 @@ async function f3e(e10, t, n, r) {
|
|
|
30380
30388
|
import { readdir, stat } from "node:fs/promises";
|
|
30381
30389
|
import { homedir as homedir3 } from "node:os";
|
|
30382
30390
|
import { join as join4 } from "node:path";
|
|
30391
|
+
|
|
30392
|
+
// server/adapters/claude-transcripts.ts
|
|
30393
|
+
import { constants as constants2 } from "node:fs";
|
|
30394
|
+
import { lstat, open } from "node:fs/promises";
|
|
30395
|
+
import { basename as basename3 } from "node:path";
|
|
30396
|
+
var claudeSessionID = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
30397
|
+
var MAX_BYTES = 128e6;
|
|
30398
|
+
var sidecar = (path) => basename3(path).startsWith("agent-") ? path.replace(/\.jsonl$/, ".meta.json") : void 0;
|
|
30399
|
+
async function state(path) {
|
|
30400
|
+
const s = await lstat(path);
|
|
30401
|
+
if (!s.isFile()) throw new Error("Claude transcript must be a regular file.");
|
|
30402
|
+
return { size: s.size, modified: s.mtimeMs, signature: [s.dev, s.ino, s.size, s.mtimeMs] };
|
|
30403
|
+
}
|
|
30404
|
+
async function metadataState(path) {
|
|
30405
|
+
const file = sidecar(path);
|
|
30406
|
+
try {
|
|
30407
|
+
return file ? await state(file) : void 0;
|
|
30408
|
+
} catch (error) {
|
|
30409
|
+
if (error.code === "ENOENT") return;
|
|
30410
|
+
throw error;
|
|
30411
|
+
}
|
|
30412
|
+
}
|
|
30413
|
+
async function claudeRevision(path) {
|
|
30414
|
+
return digest([6, (await state(path)).signature, (await metadataState(path))?.signature]);
|
|
30415
|
+
}
|
|
30416
|
+
async function bytes(path, max) {
|
|
30417
|
+
const before = await state(path);
|
|
30418
|
+
if (before.size > max) throw new Error(`Claude transcript exceeds the ${max === MAX_BYTES ? "128 MB import" : "metadata size"} limit.`);
|
|
30419
|
+
const file = await open(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW || 0) | (constants2.O_NONBLOCK || 0));
|
|
30420
|
+
try {
|
|
30421
|
+
const opened = await file.stat();
|
|
30422
|
+
if (!opened.isFile() || opened.dev !== before.signature[0] || opened.ino !== before.signature[1]) throw new Error("Claude transcript changed during import; import again.");
|
|
30423
|
+
const buffer = Buffer.alloc(before.size + 1);
|
|
30424
|
+
let length = 0;
|
|
30425
|
+
while (length < buffer.length) {
|
|
30426
|
+
const part = await file.read(buffer, length, buffer.length - length, length);
|
|
30427
|
+
if (!part.bytesRead) break;
|
|
30428
|
+
length += part.bytesRead;
|
|
30429
|
+
}
|
|
30430
|
+
if (length !== before.size || digest((await state(path)).signature) !== digest(before.signature)) throw new Error("Claude transcript changed during import; import again.");
|
|
30431
|
+
return buffer.toString("utf8", 0, length);
|
|
30432
|
+
} finally {
|
|
30433
|
+
await file.close();
|
|
30434
|
+
}
|
|
30435
|
+
}
|
|
30436
|
+
var cached;
|
|
30437
|
+
async function claudeTranscript(path, revision2) {
|
|
30438
|
+
if (await claudeRevision(path) !== revision2) throw new Error("Claude transcript changed during import; import again.");
|
|
30439
|
+
if (cached?.path === path && cached.revision === revision2) return cached.transcript;
|
|
30440
|
+
const source = await bytes(path, MAX_BYTES), entries2 = [], times = /* @__PURE__ */ new Map();
|
|
30441
|
+
let partial = false;
|
|
30442
|
+
for (const line of source.split("\n")) {
|
|
30443
|
+
if (!line.trim()) continue;
|
|
30444
|
+
try {
|
|
30445
|
+
const row = JSON.parse(line);
|
|
30446
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
30447
|
+
partial = true;
|
|
30448
|
+
continue;
|
|
30449
|
+
}
|
|
30450
|
+
entries2.push(row);
|
|
30451
|
+
if (typeof row.uuid === "string") {
|
|
30452
|
+
const time2 = typeof row.timestamp === "string" ? Date.parse(row.timestamp) : NaN;
|
|
30453
|
+
times.set(row.uuid, Number.isFinite(time2) && time2 > 0 ? time2 : 0);
|
|
30454
|
+
}
|
|
30455
|
+
} catch {
|
|
30456
|
+
partial = true;
|
|
30457
|
+
}
|
|
30458
|
+
if (entries2.length > 2e5) throw new Error("Claude transcript exceeds the 200,000-record import limit.");
|
|
30459
|
+
}
|
|
30460
|
+
const metadata2 = sidecar(path);
|
|
30461
|
+
if (metadata2 && await metadataState(path)) {
|
|
30462
|
+
try {
|
|
30463
|
+
entries2.push({ ...JSON.parse(await bytes(metadata2, 64e3)), type: "agent_metadata" });
|
|
30464
|
+
} catch {
|
|
30465
|
+
partial = true;
|
|
30466
|
+
}
|
|
30467
|
+
}
|
|
30468
|
+
if (await claudeRevision(path) !== revision2) throw new Error("Claude transcript changed during import; import again.");
|
|
30469
|
+
const { size, modified } = await state(path), transcript = { entries: entries2, times, partial, size, modified };
|
|
30470
|
+
cached = { path, revision: revision2, transcript };
|
|
30471
|
+
return transcript;
|
|
30472
|
+
}
|
|
30473
|
+
function claudeTranscriptInfo(transcript, sessionId) {
|
|
30474
|
+
const rows = transcript.entries;
|
|
30475
|
+
if (rows[0]?.isSidechain === true) return;
|
|
30476
|
+
const messages = rows.filter((row) => (row.type === "user" || row.type === "assistant") && row.sessionId === sessionId && row.isSidechain !== true && typeof row.uuid === "string" && row.message && typeof row.message === "object");
|
|
30477
|
+
if (!messages.length) return;
|
|
30478
|
+
const firstPrompt = messages.filter((row) => row.type === "user" && !row.isMeta).map((row) => {
|
|
30479
|
+
const content = row.message.content;
|
|
30480
|
+
const text2 = typeof content === "string" ? content : Array.isArray(content) ? content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n") : "";
|
|
30481
|
+
return text2.replace(/<(ide_opened_file|ide_selection|ide_diagnostics)>[\s\S]*?<\/\1>/g, "").trim().replace(/\s+/g, " ").slice(0, 180);
|
|
30482
|
+
}).find(Boolean);
|
|
30483
|
+
const title = rows.findLast((row) => ["custom-title", "ai-title", "summary"].includes(String(row.type)) && typeof (row.customTitle || row.aiTitle || row.summary) === "string");
|
|
30484
|
+
const cwd2 = rows.findLast((row) => row.type === "relocated" && typeof row.relocatedCwd === "string")?.relocatedCwd || messages.find((row) => typeof row.cwd === "string")?.cwd;
|
|
30485
|
+
const createdAt = messages.map((row) => transcript.times.get(String(row.uuid))).find((time2) => time2 && time2 > 0);
|
|
30486
|
+
return { sessionId, summary: String(title?.customTitle || title?.aiTitle || title?.summary || firstPrompt || "Claude Code session"), firstPrompt, cwd: typeof cwd2 === "string" ? cwd2 : void 0, createdAt, lastModified: transcript.modified, fileSize: transcript.size };
|
|
30487
|
+
}
|
|
30488
|
+
function claudeTranscriptStore(transcript, sessionId, agent) {
|
|
30489
|
+
return {
|
|
30490
|
+
async load(key) {
|
|
30491
|
+
return key.sessionId === sessionId && key.subpath === (agent ? `subagents/agent-${agent}` : void 0) ? transcript.entries : null;
|
|
30492
|
+
},
|
|
30493
|
+
async append() {
|
|
30494
|
+
throw new Error("Claude history is read-only.");
|
|
30495
|
+
}
|
|
30496
|
+
};
|
|
30497
|
+
}
|
|
30498
|
+
|
|
30499
|
+
// server/adapters/claude.ts
|
|
30383
30500
|
var root = () => join4(process.env.CLAUDE_CONFIG_DIR || join4(homedir3(), ".claude"), "projects");
|
|
30384
|
-
|
|
30385
|
-
const s = await stat(path);
|
|
30386
|
-
return digest([5, s.mtimeMs, s.size]);
|
|
30387
|
-
};
|
|
30388
|
-
function claudeMessages(rows, candidate) {
|
|
30501
|
+
function claudeMessages(rows, candidate, times) {
|
|
30389
30502
|
const calls = /* @__PURE__ */ new Map();
|
|
30390
30503
|
return rows.map((row) => {
|
|
30391
30504
|
const raw = row.message;
|
|
30392
|
-
const msg = blankMessage(row.uuid, Number.isFinite(Date.parse(raw?.timestamp)) ? Date.parse(raw.timestamp) : 0, row.type);
|
|
30505
|
+
const msg = blankMessage(row.uuid, times?.get(row.uuid) ?? (Number.isFinite(Date.parse(raw?.timestamp)) ? Date.parse(raw.timestamp) : 0), row.type);
|
|
30393
30506
|
if (raw?.model) candidate.session.model = raw.model;
|
|
30394
30507
|
if (row.parent_agent_id && candidate.locator.parent) candidate.session.parentID = identity("claude-code", `${candidate.locator.parent}/agent/${row.parent_agent_id}`);
|
|
30395
30508
|
if (typeof raw?.content === "string") addText(msg, raw.content, row.type === "user" ? "You" : "Assistant");
|
|
@@ -30418,7 +30531,7 @@ function claudeMessages(rows, candidate) {
|
|
|
30418
30531
|
var claudeAdapter = {
|
|
30419
30532
|
id: "claude-code",
|
|
30420
30533
|
name: "Claude Code",
|
|
30421
|
-
description: "Local
|
|
30534
|
+
description: "Local CLI and VS Code history \xB7 all projects and subagent transcripts",
|
|
30422
30535
|
capabilities: { history: true, forks: false, children: true, files: true },
|
|
30423
30536
|
async detect() {
|
|
30424
30537
|
try {
|
|
@@ -30432,49 +30545,89 @@ var claudeAdapter = {
|
|
|
30432
30545
|
const infos = await I1t({ limit: 2e4, includeProgrammatic: true });
|
|
30433
30546
|
if (infos.length === 2e4) warnings.push("Claude Code listing is limited to 20,000 sessions.");
|
|
30434
30547
|
const files = /* @__PURE__ */ new Map();
|
|
30548
|
+
let unreadable = 0;
|
|
30435
30549
|
for (const dir of await readdir(root(), { withFileTypes: true })) {
|
|
30436
30550
|
if (!dir.isDirectory()) continue;
|
|
30437
30551
|
const parent2 = join4(root(), dir.name);
|
|
30438
|
-
|
|
30552
|
+
let entries2;
|
|
30553
|
+
try {
|
|
30554
|
+
entries2 = await readdir(parent2, { withFileTypes: true });
|
|
30555
|
+
} catch {
|
|
30556
|
+
unreadable++;
|
|
30557
|
+
continue;
|
|
30558
|
+
}
|
|
30559
|
+
for (const file of entries2) if (file.isFile() && file.name.endsWith(".jsonl") && claudeSessionID(file.name.slice(0, -6))) {
|
|
30439
30560
|
const id2 = file.name.slice(0, -6), path = join4(parent2, file.name);
|
|
30440
|
-
|
|
30441
|
-
|
|
30561
|
+
try {
|
|
30562
|
+
const old = files.get(id2);
|
|
30563
|
+
if (!old || (await stat(path)).mtimeMs > (await stat(old.path)).mtimeMs) files.set(id2, { path, children: join4(parent2, id2, "subagents") });
|
|
30564
|
+
} catch {
|
|
30565
|
+
unreadable++;
|
|
30566
|
+
}
|
|
30567
|
+
}
|
|
30568
|
+
}
|
|
30569
|
+
const listed = new Set(infos.map((info) => info.sessionId));
|
|
30570
|
+
for (const [id2, file] of files) {
|
|
30571
|
+
if (listed.has(id2)) continue;
|
|
30572
|
+
if (infos.length >= 2e4) {
|
|
30573
|
+
warnings.push("Claude Code discovery is limited to 20,000 sessions.");
|
|
30574
|
+
break;
|
|
30575
|
+
}
|
|
30576
|
+
try {
|
|
30577
|
+
const transcript = await claudeTranscript(file.path, await claudeRevision(file.path));
|
|
30578
|
+
const info = claudeTranscriptInfo(transcript, id2);
|
|
30579
|
+
if (info) infos.push(info);
|
|
30580
|
+
else if (transcript.partial) unreadable++;
|
|
30581
|
+
} catch {
|
|
30582
|
+
unreadable++;
|
|
30442
30583
|
}
|
|
30443
30584
|
}
|
|
30444
30585
|
for (const info of infos) {
|
|
30445
30586
|
const file = files.get(info.sessionId);
|
|
30446
30587
|
if (!file) continue;
|
|
30447
30588
|
const s = { ...blankSession("claude-code", info.sessionId, info.cwd), title: info.customTitle || info.summary || info.firstPrompt || "Untitled session", created: info.createdAt || info.lastModified, updated: info.lastModified, provider: "anthropic", agent: "Claude Code" };
|
|
30448
|
-
|
|
30589
|
+
try {
|
|
30590
|
+
sessions.push({ session: s, revision: await claudeRevision(file.path), locator: { path: file.path, dir: info.cwd || "" } });
|
|
30591
|
+
} catch {
|
|
30592
|
+
unreadable++;
|
|
30593
|
+
continue;
|
|
30594
|
+
}
|
|
30449
30595
|
let children;
|
|
30450
30596
|
try {
|
|
30451
30597
|
children = await readdir(file.children, { withFileTypes: true });
|
|
30452
|
-
} catch {
|
|
30598
|
+
} catch (error) {
|
|
30599
|
+
if (error.code !== "ENOENT") unreadable++;
|
|
30453
30600
|
continue;
|
|
30454
30601
|
}
|
|
30455
30602
|
for (const child of children) {
|
|
30456
30603
|
if (!child.isFile() || !/^agent-[\w-]+\.jsonl$/.test(child.name)) continue;
|
|
30457
|
-
|
|
30458
|
-
|
|
30604
|
+
try {
|
|
30605
|
+
const agent = child.name.slice(6, -6), path = join4(file.children, child.name), stats = await stat(path);
|
|
30606
|
+
sessions.push({ session: { ...s, id: identity("claude-code", `${info.sessionId}/agent/${agent}`), nativeID: `${info.sessionId}/agent/${agent}`, title: `Subagent ${agent}`, created: stats.birthtimeMs || s.created, updated: stats.mtimeMs, parentID: s.id, agent }, revision: await claudeRevision(path), locator: { path, dir: info.cwd || "", parent: info.sessionId, agent } });
|
|
30607
|
+
} catch {
|
|
30608
|
+
unreadable++;
|
|
30609
|
+
}
|
|
30459
30610
|
}
|
|
30460
30611
|
}
|
|
30612
|
+
if (unreadable) warnings.push(`${unreadable} Claude Code histories unavailable or too large. Saved imports are kept.`);
|
|
30461
30613
|
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
30462
30614
|
const p = project("claude-code", c.session.directory);
|
|
30463
30615
|
return [p.id, p];
|
|
30464
30616
|
})).values()], warnings };
|
|
30465
30617
|
},
|
|
30466
30618
|
async read(candidate, cursor) {
|
|
30467
|
-
const size = (await stat(candidate.locator.path)).size;
|
|
30468
|
-
if (size > 128e6) throw new Error("Transcript exceeds the 128 MB import limit.");
|
|
30469
30619
|
const offset = Number(cursor || 0);
|
|
30620
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error("Invalid history cursor.");
|
|
30621
|
+
const transcript = await claudeTranscript(candidate.locator.path, candidate.revision);
|
|
30470
30622
|
const start = Math.max(0, offset - 1), count = 120 + offset - start;
|
|
30471
|
-
const
|
|
30623
|
+
const sessionId = candidate.locator.parent || candidate.session.nativeID;
|
|
30624
|
+
const options = { dir: candidate.locator.dir || void 0, offset: start, limit: count + 1, sessionStore: claudeTranscriptStore(transcript, sessionId, candidate.locator.agent) };
|
|
30472
30625
|
const rows = candidate.locator.parent ? await U1t(candidate.locator.parent, candidate.locator.agent, options) : await P1t(candidate.session.nativeID, options);
|
|
30473
30626
|
const more = rows.length > count;
|
|
30474
|
-
const messages = claudeMessages(rows.slice(0, count), candidate).slice(offset - start);
|
|
30475
|
-
if (!offset && !messages.length && size > 100) throw new Error("SDK returned no readable conversation for this transcript.");
|
|
30476
|
-
if (!more && await
|
|
30477
|
-
return { messages, cursor: more ? String(offset + 120) : void 0, note:
|
|
30627
|
+
const messages = claudeMessages(rows.slice(0, count), candidate, transcript.times).slice(offset - start);
|
|
30628
|
+
if (!offset && !messages.length && transcript.size > 100) throw new Error("SDK returned no readable conversation for this transcript.");
|
|
30629
|
+
if (!more && await claudeRevision(candidate.locator.path) !== candidate.revision) throw new Error("Session changed during import; import again to capture the latest messages.");
|
|
30630
|
+
return { messages, cursor: more ? String(offset + 120) : void 0, partial: transcript.partial, note: `SDK-visible conversation chain from the local CLI/VS Code transcript; pre-compaction text and unsupported message types may be absent. Fork ancestry is unavailable.${transcript.partial ? " Some transcript records or subagent metadata were unreadable." : ""}` };
|
|
30478
30631
|
}
|
|
30479
30632
|
};
|
|
30480
30633
|
|
|
@@ -30484,10 +30637,10 @@ import { homedir as homedir4 } from "node:os";
|
|
|
30484
30637
|
import { join as join6, sep as sep2 } from "node:path";
|
|
30485
30638
|
|
|
30486
30639
|
// server/adapters/jsonl.ts
|
|
30487
|
-
import { open, readdir as readdir2 } from "node:fs/promises";
|
|
30640
|
+
import { open as open2, readdir as readdir2 } from "node:fs/promises";
|
|
30488
30641
|
import { join as join5 } from "node:path";
|
|
30489
30642
|
async function readJSONL(path, offset = 0, budget = 2e6, limit = 200) {
|
|
30490
|
-
const file = await
|
|
30643
|
+
const file = await open2(path, "r");
|
|
30491
30644
|
const rows = [];
|
|
30492
30645
|
let position = offset, carry = Buffer.alloc(0), lineStart = offset, partial = false, skipping = false, eof = false;
|
|
30493
30646
|
try {
|
|
@@ -30556,7 +30709,7 @@ async function transcriptFiles(root3, max = 2e4) {
|
|
|
30556
30709
|
|
|
30557
30710
|
// server/adapters/codex.ts
|
|
30558
30711
|
var root2 = () => process.env.CODEX_HOME || join6(homedir4(), ".codex");
|
|
30559
|
-
var
|
|
30712
|
+
var revision = async (path) => {
|
|
30560
30713
|
const s = await stat2(path);
|
|
30561
30714
|
return digest([7, s.mtimeMs, s.size]);
|
|
30562
30715
|
};
|
|
@@ -30671,7 +30824,7 @@ var codexAdapter = {
|
|
|
30671
30824
|
const parent2 = meta.parent_thread_id || source?.parent_thread_id;
|
|
30672
30825
|
const fork = meta.forked_from_id || meta.forkedFromId;
|
|
30673
30826
|
Object.assign(session, { created: Date.parse(meta.timestamp) || stats.birthtimeMs || stats.mtimeMs, updated: stats.mtimeMs, archived: path.includes(`${sep2}archived_sessions${sep2}`), provider: meta.model_provider || "", agent: "Codex", parentID: parent2 ? identity("codex", parent2) : void 0, forkID: fork ? identity("codex", fork) : void 0 });
|
|
30674
|
-
const candidate = { session, locator: { path }, revision: await
|
|
30827
|
+
const candidate = { session, locator: { path }, revision: await revision(path) };
|
|
30675
30828
|
for (const row of first.rows) {
|
|
30676
30829
|
const msg = codexRecord(row.value, row.offset, candidate);
|
|
30677
30830
|
if (session.title === "Untitled session" && msg?.role === "user") {
|
|
@@ -30693,7 +30846,7 @@ var codexAdapter = {
|
|
|
30693
30846
|
},
|
|
30694
30847
|
async read(candidate, cursor) {
|
|
30695
30848
|
const page = await readJSONL(candidate.locator.path, Number(cursor || 0));
|
|
30696
|
-
if (page.cursor === void 0 && await
|
|
30849
|
+
if (page.cursor === void 0 && await revision(candidate.locator.path) !== candidate.revision) throw new Error("Session changed during import; import again to capture the latest messages.");
|
|
30697
30850
|
return { messages: page.rows.flatMap((r) => {
|
|
30698
30851
|
const msg = codexRecord(r.value, r.offset, candidate);
|
|
30699
30852
|
return msg ? [msg] : [];
|
|
@@ -30703,7 +30856,7 @@ var codexAdapter = {
|
|
|
30703
30856
|
|
|
30704
30857
|
// server/adapters/cursor.ts
|
|
30705
30858
|
import { createHash as createHash3 } from "node:crypto";
|
|
30706
|
-
import { lstat, opendir, readFile as readFile3 } from "node:fs/promises";
|
|
30859
|
+
import { lstat as lstat2, opendir, readFile as readFile3 } from "node:fs/promises";
|
|
30707
30860
|
import { homedir as homedir5 } from "node:os";
|
|
30708
30861
|
import { isAbsolute as isAbsolute2, join as join8 } from "node:path";
|
|
30709
30862
|
import { fileURLToPath } from "node:url";
|
|
@@ -30785,7 +30938,7 @@ import { chmodSync, copyFileSync, lstatSync as lstatSync2, mkdtempSync, rmSync a
|
|
|
30785
30938
|
import { tmpdir } from "node:os";
|
|
30786
30939
|
import { join as join7 } from "node:path";
|
|
30787
30940
|
var MAX_VALUE = 2e6;
|
|
30788
|
-
var
|
|
30941
|
+
var MAX_BYTES2 = 64e6;
|
|
30789
30942
|
var MAX_NODES = 5e4;
|
|
30790
30943
|
var MAX_TEXT = 16e6;
|
|
30791
30944
|
function withStore(path, read3) {
|
|
@@ -30872,10 +31025,10 @@ function readCursorStore(path) {
|
|
|
30872
31025
|
if (typeof root3 !== "string" || !/^[a-f\d]{64}$/i.test(root3)) throw new Error("Unsupported Cursor conversation root.");
|
|
30873
31026
|
const get = db2.prepare("SELECT data FROM blobs WHERE id=? AND length(data)<=?");
|
|
30874
31027
|
const messages = [];
|
|
30875
|
-
let
|
|
31028
|
+
let bytes2 = 0, nodes = 0, text2 = 0, partial = false, ordinal = 0;
|
|
30876
31029
|
const stack = [{ id: root3, leaf: false, ancestors: [] }];
|
|
30877
31030
|
while (stack.length) {
|
|
30878
|
-
if (++nodes > MAX_NODES ||
|
|
31031
|
+
if (++nodes > MAX_NODES || bytes2 >= MAX_BYTES2 || text2 >= MAX_TEXT) {
|
|
30879
31032
|
partial = true;
|
|
30880
31033
|
break;
|
|
30881
31034
|
}
|
|
@@ -30884,13 +31037,13 @@ function readCursorStore(path) {
|
|
|
30884
31037
|
partial = true;
|
|
30885
31038
|
continue;
|
|
30886
31039
|
}
|
|
30887
|
-
const row = get.get(item.id, Math.min(MAX_VALUE,
|
|
31040
|
+
const row = get.get(item.id, Math.min(MAX_VALUE, MAX_BYTES2 - bytes2));
|
|
30888
31041
|
if (!(row?.data instanceof Uint8Array)) {
|
|
30889
31042
|
partial = true;
|
|
30890
31043
|
continue;
|
|
30891
31044
|
}
|
|
30892
31045
|
const data = Buffer.from(row.data);
|
|
30893
|
-
|
|
31046
|
+
bytes2 += data.length;
|
|
30894
31047
|
try {
|
|
30895
31048
|
if (item.leaf) {
|
|
30896
31049
|
const json4 = data[0] === 123 ? data : fields(data).find((v3) => v3.field === 4)?.data;
|
|
@@ -30949,7 +31102,7 @@ var hashCwd = (path) => createHash3("md5").update(path).digest("hex");
|
|
|
30949
31102
|
var userData = () => process.platform === "darwin" ? join8(homedir5(), "Library/Application Support/Cursor") : process.platform === "win32" ? join8(process.env.APPDATA || join8(homedir5(), "AppData/Roaming"), "Cursor") : join8(process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config"), "Cursor");
|
|
30950
31103
|
async function fileStats(path) {
|
|
30951
31104
|
try {
|
|
30952
|
-
const s = await
|
|
31105
|
+
const s = await lstat2(path);
|
|
30953
31106
|
if (s.isSymbolicLink()) throw new Error("Cursor history symlinks are not followed.");
|
|
30954
31107
|
return s.isFile() ? s : void 0;
|
|
30955
31108
|
} catch (error) {
|
|
@@ -30974,7 +31127,7 @@ async function json2(path) {
|
|
|
30974
31127
|
async function entries(path, warnings) {
|
|
30975
31128
|
const result = [];
|
|
30976
31129
|
try {
|
|
30977
|
-
if (!(await
|
|
31130
|
+
if (!(await lstat2(path)).isDirectory()) return result;
|
|
30978
31131
|
for await (const entry of await opendir(path)) {
|
|
30979
31132
|
if (!entry.isSymbolicLink()) result.push(entry);
|
|
30980
31133
|
if (result.length === LIMIT) {
|
|
@@ -31013,7 +31166,7 @@ function createCursorAdapter(options = {}) {
|
|
|
31013
31166
|
async detect() {
|
|
31014
31167
|
for (const name of ["projects", "chats", "acp-sessions"]) {
|
|
31015
31168
|
try {
|
|
31016
|
-
if ((await
|
|
31169
|
+
if ((await lstat2(join8(root3(), name))).isDirectory()) return true;
|
|
31017
31170
|
} catch {
|
|
31018
31171
|
}
|
|
31019
31172
|
}
|
|
@@ -31166,11 +31319,11 @@ var cursorAdapter = createCursorAdapter();
|
|
|
31166
31319
|
|
|
31167
31320
|
// server/adapters/gemini.ts
|
|
31168
31321
|
import { homedir as homedir6 } from "node:os";
|
|
31169
|
-
import { basename as
|
|
31322
|
+
import { basename as basename4, dirname as dirname2, join as join10, sep as sep3 } from "node:path";
|
|
31170
31323
|
|
|
31171
31324
|
// server/adapters/local-files.ts
|
|
31172
|
-
import { constants as
|
|
31173
|
-
import { lstat as
|
|
31325
|
+
import { constants as constants3 } from "node:fs";
|
|
31326
|
+
import { lstat as lstat3, open as open3, readdir as readdir3 } from "node:fs/promises";
|
|
31174
31327
|
import { join as join9, relative } from "node:path";
|
|
31175
31328
|
var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
31176
31329
|
var text = (value) => typeof value === "string" ? value : "";
|
|
@@ -31178,14 +31331,14 @@ var time = (value) => {
|
|
|
31178
31331
|
const n = typeof value === "number" ? value : typeof value === "string" ? /^\d{13}$/.test(value) ? Number(value) : Date.parse(value) : 0;
|
|
31179
31332
|
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
31180
31333
|
};
|
|
31181
|
-
var
|
|
31334
|
+
var MAX_BYTES3 = 32e6;
|
|
31182
31335
|
var MAX_RECORDS = 5e4;
|
|
31183
31336
|
var MAX_FILES = 2e4;
|
|
31184
31337
|
async function fileState(path) {
|
|
31185
31338
|
try {
|
|
31186
|
-
const s = await
|
|
31339
|
+
const s = await lstat3(path);
|
|
31187
31340
|
if (!s.isFile()) throw new Error("History must be a regular file, not a symlink.");
|
|
31188
|
-
if (s.size >
|
|
31341
|
+
if (s.size > MAX_BYTES3) throw new Error("History exceeds the 32 MB per-file limit.");
|
|
31189
31342
|
return { path, signature: digest([s.size, s.mtimeMs, s.ctimeMs, s.ino]), created: s.birthtimeMs || s.mtimeMs, updated: s.mtimeMs, size: s.size };
|
|
31190
31343
|
} catch (error) {
|
|
31191
31344
|
if (error.code === "ENOENT") return { path, signature: "missing", created: 0, updated: 0, size: 0 };
|
|
@@ -31197,7 +31350,7 @@ async function historyFiles(roots, match) {
|
|
|
31197
31350
|
const warnings = [];
|
|
31198
31351
|
for (const root3 of new Set(roots)) {
|
|
31199
31352
|
try {
|
|
31200
|
-
if ((await
|
|
31353
|
+
if ((await lstat3(root3)).isDirectory()) queue.push({ root: root3, path: root3, depth: 0 });
|
|
31201
31354
|
} catch (error) {
|
|
31202
31355
|
if (error.code !== "ENOENT") warnings.push("A history directory could not be read.");
|
|
31203
31356
|
}
|
|
@@ -31250,27 +31403,27 @@ function jsonLines(value) {
|
|
|
31250
31403
|
}
|
|
31251
31404
|
function localFileAdapter(source) {
|
|
31252
31405
|
const metadata2 = /* @__PURE__ */ new Map();
|
|
31253
|
-
let
|
|
31406
|
+
let cached2;
|
|
31254
31407
|
const states = (path) => Promise.all([path, ...source.extras?.(path) || []].map(fileState));
|
|
31255
|
-
const
|
|
31408
|
+
const revision2 = (values) => digest([1, ...values.filter((_, i) => i === 0 || source.revisionExtras !== false).map((s) => [s.path, s.signature])]);
|
|
31256
31409
|
async function load(path, expected, before) {
|
|
31257
|
-
if (
|
|
31258
|
-
if (
|
|
31410
|
+
if (revision2(before) !== expected) throw new Error("Session changed during import; import again.");
|
|
31411
|
+
if (cached2?.path === path && cached2.revision === expected) return cached2.history;
|
|
31259
31412
|
if (before[0].signature === "missing") throw new Error("History file is unavailable.");
|
|
31260
|
-
if (before.reduce((n, s) => n + s.size, 0) >
|
|
31413
|
+
if (before.reduce((n, s) => n + s.size, 0) > MAX_BYTES3) throw new Error("Combined history exceeds the 32 MB snapshot limit.");
|
|
31261
31414
|
const files = /* @__PURE__ */ new Map();
|
|
31262
31415
|
for (const s of before.filter((s2) => s2.signature !== "missing")) {
|
|
31263
|
-
const handle = await
|
|
31416
|
+
const handle = await open3(s.path, constants3.O_RDONLY | (constants3.O_NOFOLLOW || 0));
|
|
31264
31417
|
try {
|
|
31265
31418
|
const buffer = Buffer.alloc(s.size + 1);
|
|
31266
|
-
let
|
|
31267
|
-
while (
|
|
31268
|
-
const part = await handle.read(buffer,
|
|
31419
|
+
let bytes2 = 0;
|
|
31420
|
+
while (bytes2 < buffer.length) {
|
|
31421
|
+
const part = await handle.read(buffer, bytes2, buffer.length - bytes2, bytes2);
|
|
31269
31422
|
if (!part.bytesRead) break;
|
|
31270
|
-
|
|
31423
|
+
bytes2 += part.bytesRead;
|
|
31271
31424
|
}
|
|
31272
|
-
if (
|
|
31273
|
-
files.set(s.path, buffer.toString("utf8", 0,
|
|
31425
|
+
if (bytes2 !== s.size) throw new Error("History changed while reading; import again.");
|
|
31426
|
+
files.set(s.path, buffer.toString("utf8", 0, bytes2));
|
|
31274
31427
|
} finally {
|
|
31275
31428
|
await handle.close();
|
|
31276
31429
|
}
|
|
@@ -31279,7 +31432,7 @@ function localFileAdapter(source) {
|
|
|
31279
31432
|
const history = source.parse(path, files);
|
|
31280
31433
|
if (!history.nativeID || history.nativeID.length > 512) throw new Error("History has no supported native session identity.");
|
|
31281
31434
|
if (history.messages.length > MAX_RECORDS) throw new Error("History exceeds the 50,000-message limit.");
|
|
31282
|
-
|
|
31435
|
+
cached2 = { path, revision: expected, history };
|
|
31283
31436
|
return history;
|
|
31284
31437
|
}
|
|
31285
31438
|
return {
|
|
@@ -31288,7 +31441,7 @@ function localFileAdapter(source) {
|
|
|
31288
31441
|
description: source.description,
|
|
31289
31442
|
capabilities: source.capabilities,
|
|
31290
31443
|
async detect() {
|
|
31291
|
-
for (const root3 of source.roots()) if ((await
|
|
31444
|
+
for (const root3 of source.roots()) if ((await lstat3(root3).catch(() => void 0))?.isDirectory()) return true;
|
|
31292
31445
|
return false;
|
|
31293
31446
|
},
|
|
31294
31447
|
async discover() {
|
|
@@ -31296,7 +31449,7 @@ function localFileAdapter(source) {
|
|
|
31296
31449
|
let failed = 0;
|
|
31297
31450
|
for (const path of listing.files) {
|
|
31298
31451
|
try {
|
|
31299
|
-
const before = await states(path), rev =
|
|
31452
|
+
const before = await states(path), rev = revision2(before);
|
|
31300
31453
|
let candidate = metadata2.get(path);
|
|
31301
31454
|
if (!candidate || candidate.revision !== rev) {
|
|
31302
31455
|
const history = await load(path, rev, before);
|
|
@@ -31497,7 +31650,7 @@ var geminiAdapter = localFileAdapter({
|
|
|
31497
31650
|
return message;
|
|
31498
31651
|
});
|
|
31499
31652
|
successfulEdits(messages, ["write_file", "replace"]);
|
|
31500
|
-
const parent2 = meta.kind === "subagent" &&
|
|
31653
|
+
const parent2 = meta.kind === "subagent" && basename4(dirname2(dirname2(path))) === "chats" ? basename4(dirname2(path)) : void 0;
|
|
31501
31654
|
return {
|
|
31502
31655
|
nativeID: meta.sessionId,
|
|
31503
31656
|
messages,
|
|
@@ -31646,7 +31799,7 @@ var copilotAdapter = localFileAdapter({
|
|
|
31646
31799
|
|
|
31647
31800
|
// server/adapters/extensions.ts
|
|
31648
31801
|
import { homedir as homedir10 } from "node:os";
|
|
31649
|
-
import { basename as
|
|
31802
|
+
import { basename as basename5, dirname as dirname3, join as join14 } from "node:path";
|
|
31650
31803
|
function editorTaskRoots(extension) {
|
|
31651
31804
|
const home = homedir10();
|
|
31652
31805
|
const base = process.platform === "darwin" ? join14(home, "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA || join14(home, "AppData", "Roaming") : process.env.XDG_CONFIG_HOME || join14(home, ".config");
|
|
@@ -31668,7 +31821,7 @@ function taskAdapter(id2, name, extension, env) {
|
|
|
31668
31821
|
if (!Array.isArray(records) || sdk && !text(value.sessionId)) throw new Error("Unsupported task history.");
|
|
31669
31822
|
const read3 = (name2) => object(files.has(join14(dirname3(path), name2)) ? json3(files.get(join14(dirname3(path), name2))) : void 0);
|
|
31670
31823
|
const history = read3("history_item.json"), info = read3("session.json"), task = read3("task_metadata.json");
|
|
31671
|
-
const nativeID = sdk ? value.sessionId :
|
|
31824
|
+
const nativeID = sdk ? value.sessionId : basename5(dirname3(path));
|
|
31672
31825
|
if (history.id && history.id !== nativeID || info.session_id && info.session_id !== nativeID) throw new Error("Task metadata identity mismatch.");
|
|
31673
31826
|
const messages = records.map((raw, index) => {
|
|
31674
31827
|
const row = object(raw), message = conversationMessage(row, `record:${index}`);
|
|
@@ -31721,7 +31874,7 @@ var continueAdapter = localFileAdapter({
|
|
|
31721
31874
|
return {
|
|
31722
31875
|
nativeID: value.sessionId,
|
|
31723
31876
|
messages,
|
|
31724
|
-
session: { directory: text(value.workspaceDirectory), title: text(value.title), created: time(meta.dateCreated), model: text(value.chatModelTitle) || "Unknown model", cost: typeof usage.totalCost === "number" ? usage.totalCost : null, tokens:
|
|
31877
|
+
session: { directory: text(value.workspaceDirectory), title: text(value.title), created: time(meta.dateCreated), model: text(value.chatModelTitle) || "Unknown model", cost: typeof usage.totalCost === "number" ? usage.totalCost : null, tokens: sumRecordedTokens([usage.promptTokens, usage.completionTokens]) },
|
|
31725
31878
|
note: "Local Continue conversation snapshots. Missing message times remain unknown. Context attachments and transient tool UI state are not imported; tool outputs are retained when recorded as messages."
|
|
31726
31879
|
};
|
|
31727
31880
|
}
|
|
@@ -31771,14 +31924,14 @@ var EVENT_SCHEMA = `CREATE TABLE IF NOT EXISTS timeline_items (
|
|
|
31771
31924
|
function writeEvents(db2, id2, rows) {
|
|
31772
31925
|
db2.prepare("DELETE FROM timeline_items WHERE session_id=?").run(id2);
|
|
31773
31926
|
const insert = db2.prepare("INSERT OR IGNORE INTO timeline_items VALUES (?,?,?,?,?,?,?,?,?,?,?)");
|
|
31774
|
-
let ordinal = 0,
|
|
31927
|
+
let ordinal = 0, bytes2 = 0, partial = false, legacy = false, limited = false;
|
|
31775
31928
|
for (const row of rows) {
|
|
31776
31929
|
const message = JSON.parse(String(row.data));
|
|
31777
31930
|
legacy ||= !message.events;
|
|
31778
31931
|
for (const event of normalizedEvents(message, id2)) {
|
|
31779
31932
|
const item = { ...event, ordinal: ordinal++ }, data = JSON.stringify(item);
|
|
31780
|
-
|
|
31781
|
-
if (ordinal > 2e5 ||
|
|
31933
|
+
bytes2 += Buffer.byteLength(data);
|
|
31934
|
+
if (ordinal > 2e5 || bytes2 > 32e6) {
|
|
31782
31935
|
limited = true;
|
|
31783
31936
|
break;
|
|
31784
31937
|
}
|
|
@@ -31810,21 +31963,21 @@ function coverage(db2, session) {
|
|
|
31810
31963
|
function readEventMarkers(db2, session) {
|
|
31811
31964
|
const end = Math.max(session.created, session.activity?.lastMessageAt || session.created);
|
|
31812
31965
|
const totals = db2.prepare(`SELECT count(*) AS total, count(*) FILTER (WHERE time IS NULL) AS untimed, count(*) FILTER (WHERE time<? OR time>?) AS outside FROM timeline_items WHERE session_id=? AND (${filters.messages})`).get(session.created, end, session.id);
|
|
31813
|
-
const rows = db2.prepare(`SELECT ${summaryColumns} FROM timeline_items WHERE session_id=? AND (${filters.messages}) AND time>=? AND time<=? ORDER BY time DESC,ordinal DESC
|
|
31966
|
+
const rows = db2.prepare(`SELECT ${summaryColumns} FROM timeline_items WHERE session_id=? AND (${filters.messages}) AND time>=? AND time<=? ORDER BY time DESC,ordinal DESC`).all(session.id, session.created, end);
|
|
31814
31967
|
return { sessionID: session.id, ...coverage(db2, session), items: rows.reverse().map(eventSummary), total: Number(totals.total), untimed: Number(totals.untimed), outsideLifetime: Number(totals.outside), limited: Number(totals.total) - Number(totals.untimed) - Number(totals.outside) > rows.length };
|
|
31815
31968
|
}
|
|
31816
31969
|
function readEvents(db2, session, options = {}) {
|
|
31817
|
-
const
|
|
31970
|
+
const state2 = coverage(db2, session), filter = options.filter || "all", size = 60;
|
|
31818
31971
|
if (!Object.hasOwn(filters, filter)) throw new EventReadError("Invalid event filter.", 400);
|
|
31819
31972
|
const where = filters[filter];
|
|
31820
|
-
if (options.revision && options.revision !==
|
|
31973
|
+
if (options.revision && options.revision !== state2.revision) throw new EventReadError("Saved history changed. Reload the event explorer.", 409);
|
|
31821
31974
|
let offset = 0;
|
|
31822
31975
|
if (options.cursor) {
|
|
31823
31976
|
try {
|
|
31824
31977
|
if (options.cursor.length > 512) throw new Error();
|
|
31825
31978
|
const cursor2 = JSON.parse(Buffer.from(options.cursor, "base64url").toString());
|
|
31826
31979
|
if (!Number.isSafeInteger(cursor2.offset) || cursor2.offset < 0 || cursor2.offset > 2e5 || cursor2.filter !== filter || typeof cursor2.revision !== "string") throw new Error();
|
|
31827
|
-
if (cursor2.revision !==
|
|
31980
|
+
if (cursor2.revision !== state2.revision) throw new EventReadError("Saved history changed. Reload the event explorer.", 409);
|
|
31828
31981
|
offset = cursor2.offset;
|
|
31829
31982
|
} catch (error) {
|
|
31830
31983
|
if (error instanceof EventReadError) throw error;
|
|
@@ -31845,10 +31998,10 @@ function readEvents(db2, session, options = {}) {
|
|
|
31845
31998
|
const related = selected?.callID ? db2.prepare(`SELECT ${summaryColumns} FROM timeline_items WHERE session_id=? AND call_id=? AND id<>? ORDER BY ordinal LIMIT 21`).all(session.id, selected.callID, selected.id).map(eventSummary) : [];
|
|
31846
31999
|
const previousItem = selected ? db2.prepare(`SELECT id FROM timeline_items WHERE session_id=? AND ordinal<? AND (${where}) ORDER BY ordinal DESC LIMIT 1`).get(session.id, selected.ordinal)?.id : void 0;
|
|
31847
32000
|
const nextItem = selected ? db2.prepare(`SELECT id FROM timeline_items WHERE session_id=? AND ordinal>? AND (${where}) ORDER BY ordinal LIMIT 1`).get(session.id, selected.ordinal)?.id : void 0;
|
|
31848
|
-
const cursor = (position) => Buffer.from(JSON.stringify({ revision:
|
|
32001
|
+
const cursor = (position) => Buffer.from(JSON.stringify({ revision: state2.revision, filter, offset: position })).toString("base64url");
|
|
31849
32002
|
return {
|
|
31850
32003
|
sessionID: session.id,
|
|
31851
|
-
...
|
|
32004
|
+
...state2,
|
|
31852
32005
|
items,
|
|
31853
32006
|
total,
|
|
31854
32007
|
offset,
|
|
@@ -32147,12 +32300,12 @@ var SessionGraphStore = class {
|
|
|
32147
32300
|
}
|
|
32148
32301
|
}
|
|
32149
32302
|
events(id2, options = {}) {
|
|
32150
|
-
return this.
|
|
32303
|
+
return this.readSnapshot(id2, (session) => readEvents(this.db, session, options));
|
|
32151
32304
|
}
|
|
32152
32305
|
eventMarkers(id2) {
|
|
32153
|
-
return this.
|
|
32306
|
+
return this.readSnapshot(id2, (session) => readEventMarkers(this.db, session));
|
|
32154
32307
|
}
|
|
32155
|
-
|
|
32308
|
+
readSnapshot(id2, read3) {
|
|
32156
32309
|
this.db.exec("BEGIN");
|
|
32157
32310
|
try {
|
|
32158
32311
|
const session = this.getSession(id2);
|
|
@@ -32181,27 +32334,30 @@ var SessionGraphStore = class {
|
|
|
32181
32334
|
return { id: id2, fragments, messages: page.messages.length, nextCursor: page.next, truncated };
|
|
32182
32335
|
}
|
|
32183
32336
|
detail(id2, cursor) {
|
|
32184
|
-
|
|
32185
|
-
|
|
32186
|
-
|
|
32187
|
-
for (const
|
|
32188
|
-
const
|
|
32189
|
-
|
|
32337
|
+
return this.readSnapshot(id2, (session) => {
|
|
32338
|
+
const page = this.messagePage(id2, Number(cursor || 0), 120);
|
|
32339
|
+
const files = /* @__PURE__ */ new Map(), tools = /* @__PURE__ */ new Map();
|
|
32340
|
+
for (const message of page.messages) {
|
|
32341
|
+
for (const file of message.files) {
|
|
32342
|
+
const previous = files.get(file.path);
|
|
32343
|
+
files.set(file.path, { ...file, source: previous?.source === "snapshot" ? "snapshot" : file.source, steps: (previous?.steps || 0) + file.steps });
|
|
32344
|
+
}
|
|
32345
|
+
message.tools.forEach((tool) => tools.set(tool, (tools.get(tool) || 0) + 1));
|
|
32190
32346
|
}
|
|
32191
|
-
|
|
32192
|
-
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
|
|
32196
|
-
|
|
32197
|
-
|
|
32198
|
-
|
|
32199
|
-
|
|
32200
|
-
|
|
32201
|
-
|
|
32202
|
-
|
|
32203
|
-
|
|
32204
|
-
};
|
|
32347
|
+
return {
|
|
32348
|
+
id: id2,
|
|
32349
|
+
revision: session.revision,
|
|
32350
|
+
files: [...files.values()].sort((a, b) => b.steps - a.steps),
|
|
32351
|
+
tools: [...tools].map(([name, count]) => ({ name, count })),
|
|
32352
|
+
locations: [],
|
|
32353
|
+
messagesScanned: page.messages.length,
|
|
32354
|
+
snapshotSteps: page.messages.reduce((n, m) => n + (m.snapshots || 0), 0),
|
|
32355
|
+
nextCursor: page.next,
|
|
32356
|
+
partial: Boolean(page.next) || session.coverage?.history !== "complete",
|
|
32357
|
+
loadedAt: Date.now(),
|
|
32358
|
+
note: session.coverage?.note || (session.coverage?.history === "pending" ? "Waiting for import." : void 0)
|
|
32359
|
+
};
|
|
32360
|
+
});
|
|
32205
32361
|
}
|
|
32206
32362
|
checkpoints(after = "") {
|
|
32207
32363
|
const rows = this.db.prepare("SELECT id,data FROM search_cache WHERE id>? ORDER BY id LIMIT 51").all(after);
|
|
@@ -32378,7 +32534,7 @@ var Importer = class {
|
|
|
32378
32534
|
import { execFile, spawn as spawn2 } from "node:child_process";
|
|
32379
32535
|
import { promisify } from "node:util";
|
|
32380
32536
|
import { stat as stat4 } from "node:fs/promises";
|
|
32381
|
-
import { basename as
|
|
32537
|
+
import { basename as basename6, dirname as dirname5, isAbsolute as isAbsolute4 } from "node:path";
|
|
32382
32538
|
|
|
32383
32539
|
// src/launch-links.ts
|
|
32384
32540
|
var HARNESSES = [
|
|
@@ -32411,7 +32567,7 @@ function resumeArgs(value) {
|
|
|
32411
32567
|
|
|
32412
32568
|
// server/harnesses.ts
|
|
32413
32569
|
import { access, stat as stat3 } from "node:fs/promises";
|
|
32414
|
-
import { constants as
|
|
32570
|
+
import { constants as constants4 } from "node:fs";
|
|
32415
32571
|
import { homedir as homedir11 } from "node:os";
|
|
32416
32572
|
import { delimiter, isAbsolute as isAbsolute3, join as join15 } from "node:path";
|
|
32417
32573
|
async function executable(name) {
|
|
@@ -32423,7 +32579,7 @@ async function executable(name) {
|
|
|
32423
32579
|
for (const path of candidates) {
|
|
32424
32580
|
try {
|
|
32425
32581
|
if ((await stat3(path)).isFile()) {
|
|
32426
|
-
await access(path, process.platform === "win32" ?
|
|
32582
|
+
await access(path, process.platform === "win32" ? constants4.F_OK : constants4.X_OK);
|
|
32427
32583
|
return path;
|
|
32428
32584
|
}
|
|
32429
32585
|
} catch {
|
|
@@ -32586,7 +32742,7 @@ function commandInvocation(command, terminal, platform = process.platform) {
|
|
|
32586
32742
|
const encoded = Buffer.from(command, "utf16le").toString("base64");
|
|
32587
32743
|
return { file: terminal, args: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process powershell.exe -ArgumentList '-NoExit','-NoProfile','-EncodedCommand','${encoded}'`] };
|
|
32588
32744
|
}
|
|
32589
|
-
return { file: terminal, args: [
|
|
32745
|
+
return { file: terminal, args: [basename6(terminal) === "gnome-terminal" ? "--" : "-e", "sh", "-lc", command] };
|
|
32590
32746
|
}
|
|
32591
32747
|
async function launchTerminal(session, prepared) {
|
|
32592
32748
|
if (!prepared.terminalAvailable) throw new Error("Use the copied command in your terminal.");
|
|
@@ -32762,7 +32918,7 @@ var server = createServer(async (req, res) => {
|
|
|
32762
32918
|
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
32763
32919
|
res.setHeader("Referrer-Policy", "no-referrer");
|
|
32764
32920
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
32765
|
-
res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'${ipc}; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'`);
|
|
32921
|
+
res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self'; connect-src 'self'${ipc}; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'`);
|
|
32766
32922
|
if (url.pathname === "/__sessiongraph" && equals(url.searchParams.get("token") || "")) {
|
|
32767
32923
|
res.setHeader("Set-Cookie", `sessiongraph=${token}; HttpOnly; SameSite=Strict; Path=/`);
|
|
32768
32924
|
res.setHeader("Cache-Control", "no-store");
|