sessiongraph 1.0.1 → 1.0.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 +5 -3
- package/dist/assets/gif-worker-CghHEBpp.js +1 -0
- package/dist/assets/index-Ca1o8F2F.js +167 -0
- package/dist/assets/index-DZ3QjOks.js +1 -0
- package/dist/assets/index-DmhroAg3.js +1 -0
- package/dist/assets/index-Oz3Ov7Mv.css +1 -0
- package/dist/assets/{index-Bn0u7W0k.js → index-ngj67_1P.js} +1 -1
- package/dist/assets/mp4-worker-HFPra5JP.js +1 -0
- package/dist/assets/scene-DpPyjrii.js +139 -0
- package/dist/assets/share-BDMRLovI.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 +2298 -935
- package/docs/guide.md +19 -11
- package/docs/import-coverage.md +74 -0
- 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
|
@@ -2138,7 +2138,7 @@ var init_promise = __esm({
|
|
|
2138
2138
|
import { createServer } from "node:http";
|
|
2139
2139
|
import { createReadStream } from "node:fs";
|
|
2140
2140
|
import { stat as stat5 } from "node:fs/promises";
|
|
2141
|
-
import { fileURLToPath as
|
|
2141
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2142
2142
|
import { resolve as resolve2, extname, sep as sep4 } from "node:path";
|
|
2143
2143
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2144
2144
|
|
|
@@ -2239,9 +2239,9 @@ function contenderFailure(contender) {
|
|
|
2239
2239
|
function contenderFinished2(contender) {
|
|
2240
2240
|
return contender.error() !== void 0 || contender.closed();
|
|
2241
2241
|
}
|
|
2242
|
-
function startupError(
|
|
2243
|
-
return new Error(stderr ? `${
|
|
2244
|
-
${stderr}` :
|
|
2242
|
+
function startupError(message2, stderr) {
|
|
2243
|
+
return new Error(stderr ? `${message2}
|
|
2244
|
+
${stderr}` : message2);
|
|
2245
2245
|
}
|
|
2246
2246
|
|
|
2247
2247
|
// node_modules/.pnpm/@opencode+client@2.2.1_effect@4.0.1/node_modules/@opencode/client/dist/chunks/mcp-xgnj2w4v.js
|
|
@@ -2284,8 +2284,8 @@ function decide2(service, options) {
|
|
|
2284
2284
|
return fail("Background service failed to start");
|
|
2285
2285
|
return { _tag: "wait" };
|
|
2286
2286
|
}
|
|
2287
|
-
function fail(
|
|
2288
|
-
return { _tag: "fail", error: new Error(
|
|
2287
|
+
function fail(message2) {
|
|
2288
|
+
return { _tag: "fail", error: new Error(message2) };
|
|
2289
2289
|
}
|
|
2290
2290
|
function fallback2() {
|
|
2291
2291
|
return join(process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state"), "opencode", "service.json");
|
|
@@ -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
|
}
|
|
@@ -2735,15 +2743,15 @@ function summarizeMessages(id2, messages, nextCursor) {
|
|
|
2735
2743
|
const tools = /* @__PURE__ */ new Map();
|
|
2736
2744
|
const locations = [];
|
|
2737
2745
|
let snapshotSteps = 0;
|
|
2738
|
-
for (const
|
|
2746
|
+
for (const message2 of messages) {
|
|
2739
2747
|
const stepFiles = /* @__PURE__ */ new Map();
|
|
2740
|
-
if (
|
|
2741
|
-
for (const path of
|
|
2742
|
-
for (const part of
|
|
2748
|
+
if (message2.snapshot?.start && message2.snapshot?.end) snapshotSteps++;
|
|
2749
|
+
for (const path of message2.snapshot?.files || []) stepFiles.set(path, "snapshot");
|
|
2750
|
+
for (const part of message2.content || []) {
|
|
2743
2751
|
if (part.type !== "tool") continue;
|
|
2744
2752
|
const name = part.name || "unknown";
|
|
2745
2753
|
tools.set(name, (tools.get(name) || 0) + 1);
|
|
2746
|
-
if (!
|
|
2754
|
+
if (!message2.snapshot?.start || !message2.snapshot?.end) {
|
|
2747
2755
|
for (const path of editedPaths(part)) stepFiles.set(path, "edit tool");
|
|
2748
2756
|
}
|
|
2749
2757
|
}
|
|
@@ -2751,8 +2759,8 @@ function summarizeMessages(id2, messages, nextCursor) {
|
|
|
2751
2759
|
const previous = files.get(path);
|
|
2752
2760
|
files.set(path, { path, source: previous?.source === "snapshot" ? "snapshot" : source, steps: (previous?.steps || 0) + 1 });
|
|
2753
2761
|
}
|
|
2754
|
-
if (
|
|
2755
|
-
locations.push({ directory:
|
|
2762
|
+
if (message2.type === "location-switched" && message2.location) {
|
|
2763
|
+
locations.push({ directory: message2.location.directory, time: message2.time.created });
|
|
2756
2764
|
}
|
|
2757
2765
|
}
|
|
2758
2766
|
return {
|
|
@@ -2775,8 +2783,8 @@ function extractContent(messages) {
|
|
|
2775
2783
|
let truncated = false;
|
|
2776
2784
|
for (const raw of messages) {
|
|
2777
2785
|
if (!raw || typeof raw !== "object") continue;
|
|
2778
|
-
const
|
|
2779
|
-
const time2 = Number(
|
|
2786
|
+
const message2 = raw;
|
|
2787
|
+
const time2 = Number(message2.time?.created) || 0;
|
|
2780
2788
|
const seen = /* @__PURE__ */ new Set();
|
|
2781
2789
|
const add = (value, kind) => {
|
|
2782
2790
|
if (typeof value !== "string" || !value.trim() || value.startsWith("data:") || seen.has(value)) return;
|
|
@@ -2809,21 +2817,21 @@ function extractContent(messages) {
|
|
|
2809
2817
|
else strings(field, depth + 1);
|
|
2810
2818
|
}
|
|
2811
2819
|
};
|
|
2812
|
-
if (["user", "synthetic", "system"].includes(
|
|
2813
|
-
for (const file of
|
|
2820
|
+
if (["user", "synthetic", "system"].includes(message2.type)) add(message2.text, message2.type === "user" ? "You" : "Assistant");
|
|
2821
|
+
for (const file of message2.files || []) {
|
|
2814
2822
|
add(file.name, "File");
|
|
2815
2823
|
add(file.uri, "File");
|
|
2816
2824
|
}
|
|
2817
|
-
if (
|
|
2818
|
-
add(
|
|
2819
|
-
add(
|
|
2825
|
+
if (message2.type === "shell") {
|
|
2826
|
+
add(message2.command, "Tool");
|
|
2827
|
+
add(message2.output, "Tool");
|
|
2820
2828
|
}
|
|
2821
|
-
if (
|
|
2822
|
-
add(
|
|
2823
|
-
add(
|
|
2829
|
+
if (message2.type === "compaction") {
|
|
2830
|
+
add(message2.summary, "Assistant");
|
|
2831
|
+
add(message2.recent, "Assistant");
|
|
2824
2832
|
}
|
|
2825
|
-
for (const file of
|
|
2826
|
-
for (const part of
|
|
2833
|
+
for (const file of message2.snapshot?.files || []) add(file, "File");
|
|
2834
|
+
for (const part of message2.content || []) {
|
|
2827
2835
|
if (part.type === "text") add(part.text, "Assistant");
|
|
2828
2836
|
if (part.type !== "tool") continue;
|
|
2829
2837
|
add(part.name, "Tool");
|
|
@@ -2848,13 +2856,13 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2848
2856
|
import { basename as basename2 } from "node:path";
|
|
2849
2857
|
var digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2850
2858
|
var identity = (source, native) => `${source}:${native}`;
|
|
2851
|
-
var project = (source,
|
|
2852
|
-
var blankSession = (source, nativeID,
|
|
2859
|
+
var project = (source, directory2) => ({ id: identity(source, digest(directory2).slice(0, 24)), name: basename2(directory2) || "Global", directory: directory2, worktrees: [] });
|
|
2860
|
+
var blankSession = (source, nativeID, directory2 = "") => ({
|
|
2853
2861
|
source,
|
|
2854
2862
|
nativeID,
|
|
2855
2863
|
id: identity(source, nativeID),
|
|
2856
|
-
projectID: project(source,
|
|
2857
|
-
directory,
|
|
2864
|
+
projectID: project(source, directory2).id,
|
|
2865
|
+
directory: directory2,
|
|
2858
2866
|
title: "Untitled session",
|
|
2859
2867
|
created: 0,
|
|
2860
2868
|
updated: 0,
|
|
@@ -2866,31 +2874,31 @@ var blankSession = (source, nativeID, directory = "") => ({
|
|
|
2866
2874
|
tokens: null
|
|
2867
2875
|
});
|
|
2868
2876
|
var blankMessage = (id2, time2, role) => ({ id: id2, time: time2, role, fragments: [], files: [], tools: [] });
|
|
2869
|
-
function addText(
|
|
2877
|
+
function addText(message2, value, kind) {
|
|
2870
2878
|
if (typeof value !== "string" || !value.trim() || value.startsWith("data:")) return;
|
|
2871
|
-
const remaining = Math.max(0, 128e3 -
|
|
2872
|
-
if (value.length > remaining)
|
|
2873
|
-
if (remaining)
|
|
2879
|
+
const remaining = Math.max(0, 128e3 - message2.fragments.reduce((n, f) => n + f.text.length, 0));
|
|
2880
|
+
if (value.length > remaining) message2.partial = true;
|
|
2881
|
+
if (remaining) message2.fragments.push({ text: value.slice(0, remaining), kind, time: message2.time });
|
|
2874
2882
|
}
|
|
2875
|
-
function toolText(
|
|
2883
|
+
function toolText(message2, value, depth = 0) {
|
|
2876
2884
|
if (depth > 5) {
|
|
2877
|
-
|
|
2885
|
+
message2.partial = true;
|
|
2878
2886
|
return;
|
|
2879
2887
|
}
|
|
2880
|
-
if (typeof value === "string") return addText(
|
|
2888
|
+
if (typeof value === "string") return addText(message2, value, "Tool");
|
|
2881
2889
|
if (Array.isArray(value)) {
|
|
2882
|
-
if (value.length > 100)
|
|
2883
|
-
value.slice(0, 100).forEach((v3) => toolText(
|
|
2890
|
+
if (value.length > 100) message2.partial = true;
|
|
2891
|
+
value.slice(0, 100).forEach((v3) => toolText(message2, v3, depth + 1));
|
|
2884
2892
|
} else if (value && typeof value === "object") for (const [key, val] of Object.entries(value)) {
|
|
2885
2893
|
if (/base64|image|binary|credential|token|secret|password/i.test(key)) continue;
|
|
2886
|
-
if (/^(filePath|file_path|path|filename)$/i.test(key)) addText(
|
|
2887
|
-
else toolText(
|
|
2894
|
+
if (/^(filePath|file_path|path|filename)$/i.test(key)) addText(message2, val, "File");
|
|
2895
|
+
else toolText(message2, val, depth + 1);
|
|
2888
2896
|
}
|
|
2889
2897
|
}
|
|
2890
2898
|
|
|
2891
2899
|
// server/activity.ts
|
|
2892
2900
|
var messageTime = (time2) => Number.isFinite(time2) && time2 > 0 && time2 <= 864e13 ? time2 : null;
|
|
2893
|
-
var isUserMessage = (
|
|
2901
|
+
var isUserMessage = (message2) => message2.role === "user" && message2.fragments.some((f) => f.kind === "You" && f.text.trim());
|
|
2894
2902
|
var activitySQL = `SELECT min(time) AS firstMessageAt, max(time) AS lastMessageAt,
|
|
2895
2903
|
coalesce(sum(is_user), 0) AS userMessages,
|
|
2896
2904
|
coalesce(sum(CASE WHEN is_user=1 AND time IS NULL THEN 1 ELSE 0 END), 0) AS untimedUserMessages
|
|
@@ -2919,30 +2927,30 @@ function eventText(value, limit = 32e3) {
|
|
|
2919
2927
|
return "[unavailable]";
|
|
2920
2928
|
}
|
|
2921
2929
|
}
|
|
2922
|
-
function eventFor(
|
|
2923
|
-
const time2 = messageTime(
|
|
2924
|
-
const event = { id: id2, kind, name, role:
|
|
2930
|
+
function eventFor(message2, id2, kind, name, text2 = "", fields2 = {}) {
|
|
2931
|
+
const time2 = messageTime(message2.time);
|
|
2932
|
+
const event = { id: id2, kind, name, role: message2.role, time: time2, timeSource: time2 ? "message" : "unknown", text: eventText(text2, 64e3), ...fields2 };
|
|
2925
2933
|
event.partial ||= [event.text, event.input, event.output].some((value) => /\[(shortened|omitted|unavailable)\]/.test(value || "")) || text2.length > 64e3;
|
|
2926
2934
|
return event;
|
|
2927
2935
|
}
|
|
2928
|
-
function messageEvent(
|
|
2936
|
+
function messageEvent(message2, text2, id2 = "message") {
|
|
2929
2937
|
return eventFor(
|
|
2930
|
-
|
|
2938
|
+
message2,
|
|
2931
2939
|
id2,
|
|
2932
2940
|
"message",
|
|
2933
|
-
|
|
2941
|
+
message2.role === "user" ? "User message" : message2.role === "assistant" ? "Assistant message" : `${message2.role} message`,
|
|
2934
2942
|
text2,
|
|
2935
|
-
{ nativeID:
|
|
2943
|
+
{ nativeID: message2.id, timeSource: messageTime(message2.time) ? "recorded" : "unknown", partial: message2.partial }
|
|
2936
2944
|
);
|
|
2937
2945
|
}
|
|
2938
|
-
function boundEventPayloads(
|
|
2939
|
-
if (!
|
|
2940
|
-
let remaining = Math.min(
|
|
2946
|
+
function boundEventPayloads(message2, budget2) {
|
|
2947
|
+
if (!message2.events) return 0;
|
|
2948
|
+
let remaining = Math.min(budget2, 128e3);
|
|
2941
2949
|
const start = remaining;
|
|
2942
|
-
if (
|
|
2943
|
-
|
|
2950
|
+
if (message2.events.length > 200) message2.partial = true;
|
|
2951
|
+
message2.events = message2.events.slice(0, 200).flatMap((event) => {
|
|
2944
2952
|
if (remaining <= 0) {
|
|
2945
|
-
|
|
2953
|
+
message2.partial = true;
|
|
2946
2954
|
return [];
|
|
2947
2955
|
}
|
|
2948
2956
|
const bounded = { ...event };
|
|
@@ -2953,48 +2961,48 @@ function boundEventPayloads(message, budget) {
|
|
|
2953
2961
|
remaining -= bounded[key].length;
|
|
2954
2962
|
if (bounded[key].length < value.length) {
|
|
2955
2963
|
bounded.partial = true;
|
|
2956
|
-
|
|
2964
|
+
message2.partial = true;
|
|
2957
2965
|
}
|
|
2958
2966
|
}
|
|
2959
2967
|
return [bounded];
|
|
2960
2968
|
});
|
|
2961
2969
|
return start - remaining;
|
|
2962
2970
|
}
|
|
2963
|
-
function blockEvents(
|
|
2964
|
-
if (typeof content === "string") return [messageEvent(
|
|
2971
|
+
function blockEvents(message2, content, transform = (v3) => v3) {
|
|
2972
|
+
if (typeof content === "string") return [messageEvent(message2, transform(content))];
|
|
2965
2973
|
if (!Array.isArray(content)) return [];
|
|
2966
2974
|
return content.slice(0, 200).flatMap((part, i) => {
|
|
2967
|
-
if (!part || typeof part !== "object") return [eventFor(
|
|
2975
|
+
if (!part || typeof part !== "object") return [eventFor(message2, `unknown:${i}`, "event", "Unsupported content", "", { partial: true })];
|
|
2968
2976
|
const nativeID = typeof part.id === "string" ? part.id : void 0, id2 = `${part.type || "part"}:${nativeID || i}`;
|
|
2969
2977
|
const fields2 = { nativeID, nativeType: String(part.type || "unknown"), partial: content.length > 200 };
|
|
2970
2978
|
if (["text", "input_text", "output_text"].includes(part.type)) {
|
|
2971
|
-
const event = messageEvent(
|
|
2979
|
+
const event = messageEvent(message2, transform(typeof part.text === "string" ? part.text : ""), id2);
|
|
2972
2980
|
return [{ ...event, ...fields2, partial: Boolean(event.partial || fields2.partial) }];
|
|
2973
2981
|
}
|
|
2974
|
-
if (["tool_use", "tool-call", "tool_call"].includes(part.type)) return [eventFor(
|
|
2975
|
-
if (["tool_result", "tool-result"].includes(part.type)) return [eventFor(
|
|
2976
|
-
if (["thinking", "reasoning"].includes(part.type)) return [eventFor(
|
|
2977
|
-
return [eventFor(
|
|
2982
|
+
if (["tool_use", "tool-call", "tool_call"].includes(part.type)) return [eventFor(message2, id2, "tool_call", part.name || part.toolName || "Tool call", "", { ...fields2, callID: part.id || part.toolCallId, input: eventText(part.input ?? part.args ?? part.arguments), status: "unknown" })];
|
|
2983
|
+
if (["tool_result", "tool-result"].includes(part.type)) return [eventFor(message2, id2, "tool_result", part.name || part.toolName || "Tool result", "", { ...fields2, role: "tool", callID: part.tool_use_id || part.toolCallId || part.tool_call_id, output: eventText(part.content ?? part.result ?? part.output), status: part.is_error === true || part.isError === true || part.error != null ? "failed" : "completed" })];
|
|
2984
|
+
if (["thinking", "reasoning"].includes(part.type)) return [eventFor(message2, id2, "reasoning", "Recorded reasoning", eventText(part.thinking ?? part.text), fields2)];
|
|
2985
|
+
return [eventFor(message2, id2, "event", `${part.type || "Unknown"} content`, "This content type is not represented as text.", { ...fields2, partial: true })];
|
|
2978
2986
|
});
|
|
2979
2987
|
}
|
|
2980
|
-
function normalizedEvents(
|
|
2981
|
-
let events =
|
|
2988
|
+
function normalizedEvents(message2, sessionID) {
|
|
2989
|
+
let events = message2.events;
|
|
2982
2990
|
if (!events) {
|
|
2983
2991
|
events = [];
|
|
2984
|
-
const speech =
|
|
2985
|
-
if (speech) events.push(messageEvent(
|
|
2986
|
-
if (
|
|
2987
|
-
if (!events.length && !
|
|
2992
|
+
const speech = message2.fragments.filter((f) => f.kind === "You" || f.kind === "Assistant").map((f) => f.text).join("\n\n");
|
|
2993
|
+
if (speech) events.push(messageEvent(message2, speech));
|
|
2994
|
+
if (message2.tools.length || message2.fragments.some((f) => f.kind === "Tool")) events.push(eventFor(message2, "legacy-tools", "event", message2.tools.length ? `Tool activity \xB7 ${message2.tools.join(", ")}` : "Recorded tool activity", message2.fragments.filter((f) => f.kind === "Tool").map((f) => f.text).join("\n\n"), { role: "tool", partial: true, nativeType: "legacy-tool-activity" }));
|
|
2995
|
+
if (!events.length && !message2.files.length) events.push(eventFor(message2, "empty", message2.role === "user" || message2.role === "assistant" ? "message" : "event", `${message2.role} record`, "", { partial: true }));
|
|
2988
2996
|
}
|
|
2989
|
-
const files =
|
|
2997
|
+
const files = message2.files.map((file, i) => eventFor(message2, `file:${i}`, "file_change", file.path, `${file.source} \xB7 ${file.steps} recorded step(s)`, { nativeType: file.source }));
|
|
2990
2998
|
return [...events, ...files].slice(0, 200).map((event) => {
|
|
2991
2999
|
const text2 = String(event.text || "").slice(0, 64e3), input = event.input?.slice(0, 32e3), output = event.output?.slice(0, 64e3);
|
|
2992
3000
|
const time2 = messageTime(event.time || 0);
|
|
2993
3001
|
return {
|
|
2994
3002
|
...event,
|
|
2995
|
-
id: digest([
|
|
3003
|
+
id: digest([message2.id, event.id]),
|
|
2996
3004
|
sessionID,
|
|
2997
|
-
messageID:
|
|
3005
|
+
messageID: message2.id,
|
|
2998
3006
|
name: String(event.name || event.kind).slice(0, 300),
|
|
2999
3007
|
nativeID: event.nativeID ? String(event.nativeID).slice(0, 512) : void 0,
|
|
3000
3008
|
nativeType: event.nativeType?.slice(0, 160),
|
|
@@ -3006,37 +3014,37 @@ function normalizedEvents(message, sessionID) {
|
|
|
3006
3014
|
text: text2,
|
|
3007
3015
|
input,
|
|
3008
3016
|
output,
|
|
3009
|
-
partial: Boolean(event.partial ||
|
|
3017
|
+
partial: Boolean(event.partial || message2.partial || text2.length < (event.text?.length || 0) || input?.length !== event.input?.length || output?.length !== event.output?.length || events.length + files.length > 200)
|
|
3010
3018
|
};
|
|
3011
3019
|
});
|
|
3012
3020
|
}
|
|
3013
3021
|
|
|
3014
3022
|
// server/adapters/opencode-events.ts
|
|
3015
|
-
function opencodeEvents(raw,
|
|
3016
|
-
const fields2 = { nativeID:
|
|
3017
|
-
if (raw.type === "user") return [messageEvent(
|
|
3023
|
+
function opencodeEvents(raw, message2) {
|
|
3024
|
+
const fields2 = { nativeID: message2.id, nativeType: String(raw.type), timeSource: message2.time ? "recorded" : "unknown" };
|
|
3025
|
+
if (raw.type === "user") return [messageEvent(message2, typeof raw.text === "string" ? raw.text : ""), ...raw.files?.length ? [eventFor(message2, "attachments", "event", "Attachments", "Non-text attachments are not included.", { partial: true })] : []];
|
|
3018
3026
|
if (raw.type !== "assistant") {
|
|
3019
3027
|
const status = ["pending", "running", "completed", "failed"].includes(raw.status) ? raw.status : "unknown";
|
|
3020
3028
|
const known = ["compaction", "system", "synthetic", "shell", "model-switched", "agent-switched", "location-switched", "skill", "idle"].includes(raw.type);
|
|
3021
|
-
return [eventFor(
|
|
3029
|
+
return [eventFor(message2, "event", raw.type === "compaction" ? "compaction" : "event", raw.description || raw.type, eventText(raw.summary ?? raw.text ?? raw.error ?? (raw.type === "shell" ? raw.command : raw)), { ...fields2, role: "system", status, output: raw.type === "shell" ? eventText(raw.output?.output) : void 0, partial: !known || Boolean(raw.output?.truncated) })];
|
|
3022
3030
|
}
|
|
3023
3031
|
const events = [];
|
|
3024
3032
|
for (const [i, part] of (Array.isArray(raw.content) ? raw.content.slice(0, 200) : []).entries()) {
|
|
3025
3033
|
const id2 = `${part.type}:${part.id || i}`, native = { nativeID: part.id, nativeType: String(part.type) };
|
|
3026
3034
|
const time2 = messageTime(part.time?.created);
|
|
3027
3035
|
const timing = time2 ? { time: time2, timeSource: "recorded" } : {};
|
|
3028
|
-
if (part.type === "text") events.push(eventFor(
|
|
3029
|
-
else if (part.type === "reasoning") events.push(eventFor(
|
|
3036
|
+
if (part.type === "text") events.push(eventFor(message2, id2, "message", "Assistant message", part.text || "", { ...native, ...timing }));
|
|
3037
|
+
else if (part.type === "reasoning") events.push(eventFor(message2, 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(
|
|
3035
|
-
if (["completed", "error"].includes(
|
|
3036
|
-
} else events.push(eventFor(
|
|
3042
|
+
events.push(eventFor(message2, `${id2}:call`, "tool_call", part.name || "Tool call", "", { ...common, ...timing, input: eventText(state2.input) }));
|
|
3043
|
+
if (["completed", "error"].includes(state2.status)) events.push(eventFor(message2, `${id2}:result`, "tool_result", part.name || "Tool result", "", { ...common, role: "tool", ...endTime ? { time: endTime, timeSource: "recorded" } : {}, output: eventText(state2.error ?? state2.content, 64e3) }));
|
|
3044
|
+
} else events.push(eventFor(message2, id2, "event", `${part.type || "Unknown"} content`, "Unsupported content.", { ...native, ...timing, partial: true }));
|
|
3037
3045
|
}
|
|
3038
|
-
if (raw.error) events.push(eventFor(
|
|
3039
|
-
if (!events.length) events.push(eventFor(
|
|
3046
|
+
if (raw.error) events.push(eventFor(message2, "error", "error", "Assistant error", eventText(raw.error), { status: "failed" }));
|
|
3047
|
+
if (!events.length) events.push(eventFor(message2, "empty", "message", "Assistant message", "", { ...fields2, partial: true }));
|
|
3040
3048
|
if (raw.content?.length > 200) events[0].partial = true;
|
|
3041
3049
|
return events;
|
|
3042
3050
|
}
|
|
@@ -3084,9 +3092,9 @@ var opencodeAdapter = {
|
|
|
3084
3092
|
return { cursor: page.cursor?.next || void 0, messages: page.data.map((raw) => {
|
|
3085
3093
|
const content = extractContent([raw]);
|
|
3086
3094
|
const detail = summarizeMessages(candidate.session.id, [raw], null);
|
|
3087
|
-
const
|
|
3088
|
-
|
|
3089
|
-
return
|
|
3095
|
+
const message2 = { id: raw.id, time: raw.time?.created || 0, role: raw.type === "user" ? "user" : raw.type === "assistant" ? "assistant" : "system", fragments: content.fragments, files: detail.files, tools: detail.tools.map((t) => t.name), snapshots: detail.snapshotSteps, partial: content.truncated };
|
|
3096
|
+
message2.events = opencodeEvents(raw, message2);
|
|
3097
|
+
return message2;
|
|
3090
3098
|
}) };
|
|
3091
3099
|
}
|
|
3092
3100
|
};
|
|
@@ -30377,260 +30385,1165 @@ async function f3e(e10, t, n, r) {
|
|
|
30377
30385
|
}
|
|
30378
30386
|
|
|
30379
30387
|
// server/adapters/claude.ts
|
|
30380
|
-
import { readdir, stat } from "node:fs/promises";
|
|
30381
|
-
import { homedir as homedir3 } from "node:os";
|
|
30382
|
-
import { join as join4 } from "node:path";
|
|
30383
|
-
var root = () => join4(process.env.CLAUDE_CONFIG_DIR || join4(homedir3(), ".claude"), "projects");
|
|
30384
|
-
var revision = async (path) => {
|
|
30385
|
-
const s = await stat(path);
|
|
30386
|
-
return digest([5, s.mtimeMs, s.size]);
|
|
30387
|
-
};
|
|
30388
|
-
function claudeMessages(rows, candidate) {
|
|
30389
|
-
const calls = /* @__PURE__ */ new Map();
|
|
30390
|
-
return rows.map((row) => {
|
|
30391
|
-
const raw = row.message;
|
|
30392
|
-
const msg = blankMessage(row.uuid, Number.isFinite(Date.parse(raw?.timestamp)) ? Date.parse(raw.timestamp) : 0, row.type);
|
|
30393
|
-
if (raw?.model) candidate.session.model = raw.model;
|
|
30394
|
-
if (row.parent_agent_id && candidate.locator.parent) candidate.session.parentID = identity("claude-code", `${candidate.locator.parent}/agent/${row.parent_agent_id}`);
|
|
30395
|
-
if (typeof raw?.content === "string") addText(msg, raw.content, row.type === "user" ? "You" : "Assistant");
|
|
30396
|
-
for (const part of Array.isArray(raw?.content) ? raw.content : []) {
|
|
30397
|
-
if (part.type === "text") addText(msg, part.text, row.type === "user" ? "You" : "Assistant");
|
|
30398
|
-
if (part.type === "tool_use") {
|
|
30399
|
-
calls.set(part.id, { name: part.name, input: part.input });
|
|
30400
|
-
msg.tools.push(part.name);
|
|
30401
|
-
addText(msg, part.name, "Tool");
|
|
30402
|
-
toolText(msg, part.input);
|
|
30403
|
-
}
|
|
30404
|
-
if (part.type === "tool_result") {
|
|
30405
|
-
toolText(msg, part.content);
|
|
30406
|
-
const call = calls.get(part.tool_use_id);
|
|
30407
|
-
if (!part.is_error && call && ["Edit", "Write", "MultiEdit", "NotebookEdit"].includes(call.name)) {
|
|
30408
|
-
const path = call.input?.file_path || call.input?.notebook_path;
|
|
30409
|
-
if (typeof path === "string") msg.files.push({ path, source: "edit tool", steps: 1 });
|
|
30410
|
-
}
|
|
30411
|
-
}
|
|
30412
|
-
}
|
|
30413
|
-
msg.events = blockEvents(msg, raw?.content).map((event) => ({ ...event, parentID: row.parent_tool_use_id || void 0 }));
|
|
30414
|
-
if (!msg.events.length) msg.events = [eventFor(msg, "event", raw?.subtype === "compact_boundary" ? "compaction" : "event", raw?.subtype || `${row.type} event`, eventText(raw), { nativeType: raw?.subtype || row.type, partial: true })];
|
|
30415
|
-
return msg;
|
|
30416
|
-
});
|
|
30417
|
-
}
|
|
30418
|
-
var claudeAdapter = {
|
|
30419
|
-
id: "claude-code",
|
|
30420
|
-
name: "Claude Code",
|
|
30421
|
-
description: "Local SDK history \xB7 all projects and subagent transcripts",
|
|
30422
|
-
capabilities: { history: true, forks: false, children: true, files: true },
|
|
30423
|
-
async detect() {
|
|
30424
|
-
try {
|
|
30425
|
-
return (await stat(root())).isDirectory();
|
|
30426
|
-
} catch {
|
|
30427
|
-
return false;
|
|
30428
|
-
}
|
|
30429
|
-
},
|
|
30430
|
-
async discover() {
|
|
30431
|
-
const sessions = [], warnings = [];
|
|
30432
|
-
const infos = await I1t({ limit: 2e4, includeProgrammatic: true });
|
|
30433
|
-
if (infos.length === 2e4) warnings.push("Claude Code listing is limited to 20,000 sessions.");
|
|
30434
|
-
const files = /* @__PURE__ */ new Map();
|
|
30435
|
-
for (const dir of await readdir(root(), { withFileTypes: true })) {
|
|
30436
|
-
if (!dir.isDirectory()) continue;
|
|
30437
|
-
const parent2 = join4(root(), dir.name);
|
|
30438
|
-
for (const file of await readdir(parent2, { withFileTypes: true })) if (file.isFile() && file.name.endsWith(".jsonl")) {
|
|
30439
|
-
const id2 = file.name.slice(0, -6), path = join4(parent2, file.name);
|
|
30440
|
-
const old = files.get(id2);
|
|
30441
|
-
if (!old || (await stat(path)).mtimeMs > (await stat(old.path)).mtimeMs) files.set(id2, { path, children: join4(parent2, id2, "subagents") });
|
|
30442
|
-
}
|
|
30443
|
-
}
|
|
30444
|
-
for (const info of infos) {
|
|
30445
|
-
const file = files.get(info.sessionId);
|
|
30446
|
-
if (!file) continue;
|
|
30447
|
-
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
|
-
sessions.push({ session: s, revision: await revision(file.path), locator: { path: file.path, dir: info.cwd || "" } });
|
|
30449
|
-
let children;
|
|
30450
|
-
try {
|
|
30451
|
-
children = await readdir(file.children, { withFileTypes: true });
|
|
30452
|
-
} catch {
|
|
30453
|
-
continue;
|
|
30454
|
-
}
|
|
30455
|
-
for (const child of children) {
|
|
30456
|
-
if (!child.isFile() || !/^agent-[\w-]+\.jsonl$/.test(child.name)) continue;
|
|
30457
|
-
const agent = child.name.slice(6, -6), path = join4(file.children, child.name), stats = await stat(path);
|
|
30458
|
-
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 revision(path), locator: { path, dir: info.cwd || "", parent: info.sessionId, agent } });
|
|
30459
|
-
}
|
|
30460
|
-
}
|
|
30461
|
-
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
30462
|
-
const p = project("claude-code", c.session.directory);
|
|
30463
|
-
return [p.id, p];
|
|
30464
|
-
})).values()], warnings };
|
|
30465
|
-
},
|
|
30466
|
-
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
|
-
const offset = Number(cursor || 0);
|
|
30470
|
-
const start = Math.max(0, offset - 1), count = 120 + offset - start;
|
|
30471
|
-
const options = { dir: candidate.locator.dir || void 0, offset: start, limit: count + 1 };
|
|
30472
|
-
const rows = candidate.locator.parent ? await U1t(candidate.locator.parent, candidate.locator.agent, options) : await P1t(candidate.session.nativeID, options);
|
|
30473
|
-
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 revision(candidate.locator.path) !== candidate.revision) throw new Error("Session changed during import; import again to capture the latest messages.");
|
|
30477
|
-
return { messages, cursor: more ? String(offset + 120) : void 0, note: "SDK-visible conversation chain; pre-compaction text and unsupported message types may be absent. Fork ancestry is unavailable." };
|
|
30478
|
-
}
|
|
30479
|
-
};
|
|
30480
|
-
|
|
30481
|
-
// server/adapters/codex.ts
|
|
30482
|
-
import { stat as stat2 } from "node:fs/promises";
|
|
30388
|
+
import { readdir as readdir2, stat } from "node:fs/promises";
|
|
30483
30389
|
import { homedir as homedir4 } from "node:os";
|
|
30484
|
-
import { join as
|
|
30390
|
+
import { join as join8 } from "node:path";
|
|
30485
30391
|
|
|
30486
|
-
// server/adapters/
|
|
30487
|
-
import {
|
|
30488
|
-
import {
|
|
30489
|
-
|
|
30490
|
-
|
|
30491
|
-
|
|
30492
|
-
|
|
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);
|
|
30493
30406
|
try {
|
|
30494
|
-
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
|
|
30498
|
-
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
|
|
30502
|
-
|
|
30503
|
-
|
|
30504
|
-
|
|
30505
|
-
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
|
|
30509
|
-
|
|
30510
|
-
|
|
30511
|
-
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
if (carry.length > 2e6) {
|
|
30520
|
-
partial = true;
|
|
30521
|
-
skipping = true;
|
|
30522
|
-
lineStart += carry.length;
|
|
30523
|
-
carry = Buffer.alloc(0);
|
|
30524
|
-
}
|
|
30525
|
-
}
|
|
30526
|
-
if (eof && carry.length && !skipping) {
|
|
30527
|
-
try {
|
|
30528
|
-
rows.push({ offset: lineStart, value: JSON.parse(carry.toString("utf8")) });
|
|
30529
|
-
} catch {
|
|
30530
|
-
partial = true;
|
|
30531
|
-
}
|
|
30532
|
-
}
|
|
30533
|
-
return { rows, cursor: eof ? void 0 : lineStart, partial };
|
|
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);
|
|
30534
30432
|
} finally {
|
|
30535
30433
|
await file.close();
|
|
30536
30434
|
}
|
|
30537
30435
|
}
|
|
30538
|
-
|
|
30539
|
-
|
|
30540
|
-
|
|
30541
|
-
|
|
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;
|
|
30542
30444
|
try {
|
|
30543
|
-
|
|
30544
|
-
|
|
30545
|
-
|
|
30546
|
-
|
|
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;
|
|
30547
30457
|
}
|
|
30548
|
-
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
|
|
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;
|
|
30552
30466
|
}
|
|
30553
30467
|
}
|
|
30554
|
-
|
|
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 rows2 = transcript.entries;
|
|
30475
|
+
if (rows2[0]?.isSidechain === true) return;
|
|
30476
|
+
const messages = rows2.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 = rows2.findLast((row) => ["custom-title", "ai-title", "summary"].includes(String(row.type)) && typeof (row.customTitle || row.aiTitle || row.summary) === "string");
|
|
30484
|
+
const cwd2 = rows2.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
|
+
};
|
|
30555
30497
|
}
|
|
30556
30498
|
|
|
30557
|
-
// server/adapters/
|
|
30558
|
-
|
|
30559
|
-
|
|
30560
|
-
|
|
30561
|
-
|
|
30562
|
-
|
|
30563
|
-
|
|
30564
|
-
|
|
30565
|
-
|
|
30566
|
-
|
|
30567
|
-
|
|
30568
|
-
|
|
30569
|
-
if (typeof value !== "string") return "";
|
|
30570
|
-
let text2 = value.trim();
|
|
30571
|
-
if (/^# (?:AGENTS\.md|Instructions for)/i.test(text2)) return "";
|
|
30572
|
-
text2 = text2.replace(/<(environment_context|permissions|user_instructions|turn_aborted|external_codex_[\w-]+)>[\s\S]*?<\/\1>/g, "").trim();
|
|
30573
|
-
if (/^<(?:environment_context|permissions|user_instructions|turn_aborted|external_codex_[\w-]+)(?:>|\s)/.test(text2)) return "";
|
|
30574
|
-
return text2;
|
|
30499
|
+
// server/adapters/local-files.ts
|
|
30500
|
+
import { constants as constants3 } from "node:fs";
|
|
30501
|
+
import { lstat as lstat2, open as open2, readdir } from "node:fs/promises";
|
|
30502
|
+
import { join as join4, relative } from "node:path";
|
|
30503
|
+
|
|
30504
|
+
// server/adapters/compressed.ts
|
|
30505
|
+
import { gunzipSync, zstdDecompressSync } from "node:zlib";
|
|
30506
|
+
function expandHistory(buffer, maxOutputLength) {
|
|
30507
|
+
if (buffer.length >= 4 && buffer.readUInt32LE(0) === 4247762216) return zstdDecompressSync(buffer, { maxOutputLength });
|
|
30508
|
+
if (buffer[0] === 31 && buffer[1] === 139) return gunzipSync(buffer, { maxOutputLength });
|
|
30509
|
+
if (buffer.length > maxOutputLength) throw new Error("Expanded history exceeds the snapshot limit.");
|
|
30510
|
+
return buffer;
|
|
30575
30511
|
}
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
|
|
30512
|
+
|
|
30513
|
+
// server/adapters/local-files.ts
|
|
30514
|
+
var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
30515
|
+
var text = (value) => typeof value === "string" ? value : "";
|
|
30516
|
+
var time = (value) => {
|
|
30517
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? /^\d{13}$/.test(value) ? Number(value) : Date.parse(value) : 0;
|
|
30518
|
+
return Number.isFinite(n) && n > 0 && n <= 864e13 ? n : 0;
|
|
30519
|
+
};
|
|
30520
|
+
var MAX_BYTES2 = 32e6;
|
|
30521
|
+
var MAX_RECORDS = 5e4;
|
|
30522
|
+
var MAX_FILES = 2e4;
|
|
30523
|
+
async function fileState(path) {
|
|
30579
30524
|
try {
|
|
30580
|
-
|
|
30581
|
-
|
|
30582
|
-
|
|
30583
|
-
|
|
30584
|
-
count += page.rows.length;
|
|
30585
|
-
} while (cursor !== void 0 && count < 1e5);
|
|
30525
|
+
const s = await lstat2(path);
|
|
30526
|
+
if (!s.isFile()) throw new Error("History must be a regular file, not a symlink.");
|
|
30527
|
+
if (s.size > MAX_BYTES2) throw new Error("History exceeds the 32 MB per-file limit.");
|
|
30528
|
+
return { path, signature: digest([s.size, s.mtimeMs, s.ctimeMs, s.ino]), created: s.birthtimeMs || s.mtimeMs, updated: s.mtimeMs, size: s.size };
|
|
30586
30529
|
} catch (error) {
|
|
30587
|
-
if (error.code
|
|
30530
|
+
if (error.code === "ENOENT") return { path, signature: "missing", created: 0, updated: 0, size: 0 };
|
|
30531
|
+
throw error;
|
|
30588
30532
|
}
|
|
30589
|
-
return names;
|
|
30590
30533
|
}
|
|
30591
|
-
function
|
|
30592
|
-
const
|
|
30593
|
-
|
|
30594
|
-
|
|
30595
|
-
|
|
30596
|
-
|
|
30597
|
-
|
|
30598
|
-
|
|
30599
|
-
msg2.files.push({ path, source: "file change", steps: 1 });
|
|
30600
|
-
addText(msg2, path, "File");
|
|
30534
|
+
async function historyFiles(roots, match) {
|
|
30535
|
+
const files = [], queue = [];
|
|
30536
|
+
const warnings = [];
|
|
30537
|
+
for (const root3 of new Set(roots)) {
|
|
30538
|
+
try {
|
|
30539
|
+
if ((await lstat2(root3)).isDirectory()) queue.push({ root: root3, path: root3, depth: 0 });
|
|
30540
|
+
} catch (error) {
|
|
30541
|
+
if (error.code !== "ENOENT") warnings.push("A history directory could not be read.");
|
|
30601
30542
|
}
|
|
30602
|
-
return msg2;
|
|
30603
30543
|
}
|
|
30604
|
-
|
|
30605
|
-
|
|
30606
|
-
|
|
30607
|
-
|
|
30608
|
-
|
|
30609
|
-
|
|
30610
|
-
|
|
30611
|
-
|
|
30612
|
-
|
|
30544
|
+
let entriesSeen = 0, limited = false;
|
|
30545
|
+
for (let i = 0; i < queue.length && files.length < MAX_FILES && entriesSeen < 1e5; i++) {
|
|
30546
|
+
const { root: root3, path, depth } = queue[i];
|
|
30547
|
+
let entries2;
|
|
30548
|
+
try {
|
|
30549
|
+
entries2 = await readdir(path, { withFileTypes: true });
|
|
30550
|
+
} catch {
|
|
30551
|
+
warnings.push("A history directory could not be read.");
|
|
30552
|
+
continue;
|
|
30553
|
+
}
|
|
30554
|
+
for (const entry of entries2) {
|
|
30555
|
+
if (++entriesSeen > 1e5 || files.length >= MAX_FILES) {
|
|
30556
|
+
limited = true;
|
|
30557
|
+
break;
|
|
30558
|
+
}
|
|
30559
|
+
const file = join4(path, entry.name);
|
|
30560
|
+
if (entry.isDirectory()) {
|
|
30561
|
+
if (depth < 6 && queue.length < MAX_FILES) queue.push({ root: root3, path: file, depth: depth + 1 });
|
|
30562
|
+
else limited = true;
|
|
30613
30563
|
}
|
|
30564
|
+
if (entry.isFile() && match(relative(root3, file).split("\\").join("/"))) files.push(file);
|
|
30614
30565
|
}
|
|
30615
|
-
return msg2;
|
|
30616
|
-
}
|
|
30617
|
-
if (raw.type !== "response_item") {
|
|
30618
|
-
if (raw.type === "session_meta" || raw.type === "event_msg" && ["user_message", "agent_message", "agent_reasoning", "agent_reasoning_raw_content"].includes(p.type)) return;
|
|
30619
|
-
const msg2 = blankMessage(`event:${raw.ordinal ?? offset}`, Date.parse(raw.timestamp) || 0, "system");
|
|
30620
|
-
msg2.events = [eventFor(msg2, "event", /compact/.test(p.type || raw.type) ? "compaction" : p.type === "error" ? "error" : "event", p.type || raw.type, eventText(p), { nativeType: `${raw.type}:${p.type || ""}` })];
|
|
30621
|
-
return msg2;
|
|
30622
30566
|
}
|
|
30623
|
-
|
|
30624
|
-
|
|
30625
|
-
|
|
30626
|
-
|
|
30627
|
-
|
|
30628
|
-
|
|
30629
|
-
|
|
30630
|
-
|
|
30631
|
-
|
|
30632
|
-
|
|
30633
|
-
|
|
30567
|
+
if (limited) warnings.push("History discovery reached its file, directory, or depth limit.");
|
|
30568
|
+
return { files: [...new Set(files)].sort(), warnings };
|
|
30569
|
+
}
|
|
30570
|
+
function json2(value) {
|
|
30571
|
+
return JSON.parse(value);
|
|
30572
|
+
}
|
|
30573
|
+
function jsonLines(value) {
|
|
30574
|
+
const rows2 = [];
|
|
30575
|
+
let partial = false;
|
|
30576
|
+
for (const line of value.split("\n")) {
|
|
30577
|
+
if (!line.trim()) continue;
|
|
30578
|
+
if (rows2.length >= MAX_RECORDS) throw new Error("History exceeds the 50,000-record limit.");
|
|
30579
|
+
if (line.length > 2e6) throw new Error("History contains a record larger than 2 MB.");
|
|
30580
|
+
try {
|
|
30581
|
+
const row = json2(line);
|
|
30582
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) partial = true;
|
|
30583
|
+
else rows2.push(row);
|
|
30584
|
+
} catch {
|
|
30585
|
+
partial = true;
|
|
30586
|
+
}
|
|
30587
|
+
}
|
|
30588
|
+
return { rows: rows2, partial };
|
|
30589
|
+
}
|
|
30590
|
+
function localFileAdapter(source) {
|
|
30591
|
+
const metadata2 = /* @__PURE__ */ new Map();
|
|
30592
|
+
let cached2;
|
|
30593
|
+
const states = (path) => Promise.all([path, ...source.extras?.(path) || []].map(fileState));
|
|
30594
|
+
const revision2 = (values) => digest([1, ...values.filter((_, i) => i === 0 || source.revisionExtras !== false).map((s) => [s.path, s.signature])]);
|
|
30595
|
+
async function load(path, expected, before) {
|
|
30596
|
+
if (revision2(before) !== expected) throw new Error("Session changed during import; import again.");
|
|
30597
|
+
if (cached2?.path === path && cached2.revision === expected) return cached2.history;
|
|
30598
|
+
if (before[0].signature === "missing") throw new Error("History file is unavailable.");
|
|
30599
|
+
if (before.reduce((n, s) => n + s.size, 0) > MAX_BYTES2) throw new Error("Combined history exceeds the 32 MB snapshot limit.");
|
|
30600
|
+
const files = /* @__PURE__ */ new Map();
|
|
30601
|
+
let expanded = 0;
|
|
30602
|
+
for (const s of before.filter((s2) => s2.signature !== "missing")) {
|
|
30603
|
+
const handle = await open2(s.path, constants3.O_RDONLY | (constants3.O_NOFOLLOW || 0));
|
|
30604
|
+
try {
|
|
30605
|
+
const buffer = Buffer.alloc(s.size + 1);
|
|
30606
|
+
let bytes2 = 0;
|
|
30607
|
+
while (bytes2 < buffer.length) {
|
|
30608
|
+
const part = await handle.read(buffer, bytes2, buffer.length - bytes2, bytes2);
|
|
30609
|
+
if (!part.bytesRead) break;
|
|
30610
|
+
bytes2 += part.bytesRead;
|
|
30611
|
+
}
|
|
30612
|
+
if (bytes2 !== s.size) throw new Error("History changed while reading; import again.");
|
|
30613
|
+
const data = expandHistory(buffer.subarray(0, bytes2), MAX_BYTES2);
|
|
30614
|
+
expanded += data.length;
|
|
30615
|
+
if (expanded > MAX_BYTES2) throw new Error("Expanded history exceeds the 32 MB snapshot limit.");
|
|
30616
|
+
files.set(s.path, data.toString("utf8"));
|
|
30617
|
+
} finally {
|
|
30618
|
+
await handle.close();
|
|
30619
|
+
}
|
|
30620
|
+
}
|
|
30621
|
+
if (digest(await states(path)) !== digest(before)) throw new Error("History changed while reading; import again.");
|
|
30622
|
+
const history = source.parse(path, files);
|
|
30623
|
+
if (!history) return;
|
|
30624
|
+
if (source.capabilities.history && !history.messages.length) throw new Error("No readable conversation.");
|
|
30625
|
+
if (!history.nativeID || history.nativeID.length > 512) throw new Error("History has no supported native session identity.");
|
|
30626
|
+
if (history.messages.length > MAX_RECORDS) throw new Error("History exceeds the 50,000-message limit.");
|
|
30627
|
+
cached2 = { path, revision: expected, history };
|
|
30628
|
+
return history;
|
|
30629
|
+
}
|
|
30630
|
+
return {
|
|
30631
|
+
id: source.id,
|
|
30632
|
+
name: source.name,
|
|
30633
|
+
description: source.description,
|
|
30634
|
+
capabilities: source.capabilities,
|
|
30635
|
+
async detect() {
|
|
30636
|
+
for (const root3 of source.roots()) if ((await lstat2(root3).catch(() => void 0))?.isDirectory()) return true;
|
|
30637
|
+
return false;
|
|
30638
|
+
},
|
|
30639
|
+
async discover() {
|
|
30640
|
+
const listing = await historyFiles(source.roots(), source.match), found = /* @__PURE__ */ new Map();
|
|
30641
|
+
let failed = 0;
|
|
30642
|
+
for (const path of listing.files) {
|
|
30643
|
+
try {
|
|
30644
|
+
const before = await states(path), rev = revision2(before);
|
|
30645
|
+
let candidate = metadata2.get(path);
|
|
30646
|
+
if (!candidate || candidate.revision !== rev) {
|
|
30647
|
+
const history = await load(path, rev, before);
|
|
30648
|
+
if (!history) continue;
|
|
30649
|
+
const session = { ...blankSession(source.id, history.nativeID), ...history.session };
|
|
30650
|
+
session.id = identity(source.id, history.nativeID);
|
|
30651
|
+
session.nativeID = history.nativeID;
|
|
30652
|
+
session.source = source.id;
|
|
30653
|
+
session.directory = text(session.directory);
|
|
30654
|
+
session.projectID = project(source.id, session.directory).id;
|
|
30655
|
+
session.created = time(session.created) || history.messages.map((m) => time(m.time)).find(Boolean) || before[0].created;
|
|
30656
|
+
session.updated = before[0].updated;
|
|
30657
|
+
session.agent ||= source.name;
|
|
30658
|
+
session.parentID = history.parent ? identity(source.id, history.parent) : void 0;
|
|
30659
|
+
session.forkID = history.fork ? identity(source.id, history.fork) : void 0;
|
|
30660
|
+
if (session.title === "Untitled session" || !session.title) session.title = history.messages.flatMap((m) => m.role === "user" ? m.fragments.filter((f) => f.kind === "You").map((f) => f.text) : []).find(Boolean)?.replace(/\s+/g, " ").slice(0, 180) || `${source.name} session`;
|
|
30661
|
+
candidate = { session, revision: rev, locator: { path } };
|
|
30662
|
+
metadata2.set(path, candidate);
|
|
30663
|
+
}
|
|
30664
|
+
const previous = found.get(candidate.session.id);
|
|
30665
|
+
const preferred = candidate.locator.path.endsWith(".jsonl"), previousPreferred = previous?.locator.path.endsWith(".jsonl");
|
|
30666
|
+
if (!previous || preferred && !previousPreferred || preferred === previousPreferred && candidate.session.updated > previous.session.updated) found.set(candidate.session.id, candidate);
|
|
30667
|
+
} catch {
|
|
30668
|
+
failed++;
|
|
30669
|
+
}
|
|
30670
|
+
}
|
|
30671
|
+
const listed = new Set(listing.files);
|
|
30672
|
+
for (const path of metadata2.keys()) if (!listed.has(path)) metadata2.delete(path);
|
|
30673
|
+
if (failed) listing.warnings.push(`${failed} ${source.name} histories were unreadable, changing, unsupported, or over the size limit. Saved imports are kept.`);
|
|
30674
|
+
const sessions = [...found.values()];
|
|
30675
|
+
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
30676
|
+
const p = project(source.id, c.session.directory);
|
|
30677
|
+
return [p.id, p];
|
|
30678
|
+
})).values()], warnings: listing.warnings };
|
|
30679
|
+
},
|
|
30680
|
+
async read(candidate, cursor) {
|
|
30681
|
+
const offset = cursor === void 0 ? 0 : Number(cursor);
|
|
30682
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error("Invalid history cursor.");
|
|
30683
|
+
const history = await load(candidate.locator.path, candidate.revision, await states(candidate.locator.path));
|
|
30684
|
+
if (!history) throw new Error("History representation is no longer available.");
|
|
30685
|
+
const messages = history.messages.slice(offset, offset + 100);
|
|
30686
|
+
const untimed = history.messages.some((m) => !time(m.time));
|
|
30687
|
+
return {
|
|
30688
|
+
messages,
|
|
30689
|
+
cursor: offset + messages.length < history.messages.length ? String(offset + messages.length) : void 0,
|
|
30690
|
+
partial: Boolean(history.partial || messages.some((m) => m.partial)),
|
|
30691
|
+
note: `${history.note}${untimed ? " Missing message timestamps remain unknown; session placement falls back to recorded metadata or file time." : ""} File snapshots are unavailable.`
|
|
30692
|
+
};
|
|
30693
|
+
}
|
|
30694
|
+
};
|
|
30695
|
+
}
|
|
30696
|
+
|
|
30697
|
+
// server/adapters/harness-formats.ts
|
|
30698
|
+
import { basename as basename4, dirname as dirname2, join as join6 } from "node:path";
|
|
30699
|
+
|
|
30700
|
+
// server/adapters/local-messages.ts
|
|
30701
|
+
function conversationMessage(raw, fallbackID) {
|
|
30702
|
+
const role = raw.role === "user" ? "user" : ["assistant", "model", "thinking"].includes(raw.role) ? "assistant" : ["tool", "toolResult"].includes(raw.role) ? "tool" : "system";
|
|
30703
|
+
const message2 = blankMessage(text(raw.id) || fallbackID, time(raw.timestamp ?? raw.ts), role);
|
|
30704
|
+
let content = raw.content;
|
|
30705
|
+
if (Array.isArray(content)) content = content.map((part) => {
|
|
30706
|
+
const p = object(part);
|
|
30707
|
+
return p.type === "toolCall" ? { ...p, type: "tool_use", input: p.arguments } : p.type === "reasoning" ? { ...p, type: "thinking", thinking: p.text } : p;
|
|
30708
|
+
});
|
|
30709
|
+
if (raw.role === "thinking") content = [{ type: "thinking", thinking: raw.content }];
|
|
30710
|
+
message2.events = blockEvents(message2, content);
|
|
30711
|
+
if (role === "tool" && !(Array.isArray(content) && content.some((p) => p.type === "tool_result"))) {
|
|
30712
|
+
message2.events = [eventFor(message2, "result", "tool_result", text(raw.toolName) || "Tool result", "", {
|
|
30713
|
+
callID: text(raw.toolCallId) || text(raw.tool_call_id) || void 0,
|
|
30714
|
+
output: eventText(content),
|
|
30715
|
+
status: raw.isError === true ? "failed" : raw.isError === false ? "completed" : "unknown"
|
|
30716
|
+
})];
|
|
30717
|
+
}
|
|
30718
|
+
for (const event of message2.events) {
|
|
30719
|
+
if (event.kind === "message") addText(message2, event.text, role === "user" ? "You" : role === "assistant" ? "Assistant" : "Tool");
|
|
30720
|
+
else if (event.kind === "tool_call") {
|
|
30721
|
+
message2.tools.push(event.name);
|
|
30722
|
+
toolText(message2, event.input);
|
|
30723
|
+
} else if (event.kind === "tool_result") toolText(message2, event.output);
|
|
30724
|
+
else if (event.kind === "reasoning") addText(message2, event.text, "Assistant");
|
|
30725
|
+
}
|
|
30726
|
+
for (const call of Array.isArray(raw.toolCalls) ? raw.toolCalls : []) {
|
|
30727
|
+
const f = object(call.function);
|
|
30728
|
+
if (!text(f.name)) continue;
|
|
30729
|
+
message2.tools.push(f.name);
|
|
30730
|
+
message2.events.push(eventFor(message2, `call:${text(call.id) || message2.events.length}`, "tool_call", f.name, "", { callID: text(call.id) || void 0, input: eventText(f.arguments), status: "unknown" }));
|
|
30731
|
+
toolText(message2, f.arguments);
|
|
30732
|
+
}
|
|
30733
|
+
if (message2.events.length && message2.events.every((e10) => e10.kind === "tool_result")) message2.role = "tool";
|
|
30734
|
+
message2.partial ||= message2.events.some((e10) => e10.partial);
|
|
30735
|
+
return message2;
|
|
30736
|
+
}
|
|
30737
|
+
function googleMessage(raw, fallbackID) {
|
|
30738
|
+
const parts = Array.isArray(raw.content) ? raw.content : raw.content != null ? [raw.content] : [];
|
|
30739
|
+
const content = parts.map((part) => {
|
|
30740
|
+
if (typeof part === "string") return { type: "text", text: part };
|
|
30741
|
+
const p = object(part);
|
|
30742
|
+
if (typeof p.text === "string") return p.thought ? { type: "thinking", thinking: p.text } : { type: "text", text: p.text };
|
|
30743
|
+
if (p.functionCall) return { type: "tool_use", ...p.functionCall, input: p.functionCall.args };
|
|
30744
|
+
if (p.functionResponse) return { type: "tool_result", tool_use_id: p.functionResponse.id || raw.toolCallId, name: p.functionResponse.name, content: p.functionResponse.response, is_error: p.functionResponse.response?.error != null };
|
|
30745
|
+
return { type: "unsupported" };
|
|
30746
|
+
});
|
|
30747
|
+
return conversationMessage({ ...raw, content }, fallbackID);
|
|
30748
|
+
}
|
|
30749
|
+
function successfulEdits(messages, editNames) {
|
|
30750
|
+
const calls = /* @__PURE__ */ new Map();
|
|
30751
|
+
for (const message2 of messages) for (const event of message2.events || []) {
|
|
30752
|
+
if (event.kind === "tool_call" && event.callID) {
|
|
30753
|
+
let input = {};
|
|
30754
|
+
try {
|
|
30755
|
+
input = object(JSON.parse(event.input || "{}"));
|
|
30756
|
+
} catch {
|
|
30757
|
+
}
|
|
30758
|
+
calls.set(event.callID, { name: event.name, input });
|
|
30759
|
+
}
|
|
30760
|
+
if (event.kind !== "tool_result" || event.status !== "completed" || !event.callID) continue;
|
|
30761
|
+
const call = calls.get(event.callID);
|
|
30762
|
+
if (!call || !editNames.includes(call.name)) continue;
|
|
30763
|
+
if (call.name === "editor" && (typeof call.input.new_text !== "string" || typeof call.input.old_text !== "string")) continue;
|
|
30764
|
+
const path = text(call.input.file_path) || text(call.input.filePath) || text(call.input.path);
|
|
30765
|
+
if (!path || path.length > 4096) continue;
|
|
30766
|
+
try {
|
|
30767
|
+
const result = JSON.parse(event.output || "{}");
|
|
30768
|
+
if (result?.success === false || result?.error) continue;
|
|
30769
|
+
} catch {
|
|
30770
|
+
}
|
|
30771
|
+
message2.files.push({ path, source: "edit tool", steps: 1 });
|
|
30772
|
+
addText(message2, path, "File");
|
|
30773
|
+
}
|
|
30774
|
+
}
|
|
30775
|
+
|
|
30776
|
+
// server/adapters/pi.ts
|
|
30777
|
+
import { homedir as homedir3 } from "node:os";
|
|
30778
|
+
import { join as join5 } from "node:path";
|
|
30779
|
+
function piHistory(value) {
|
|
30780
|
+
const { rows: rows2, partial } = jsonLines(value);
|
|
30781
|
+
const header = rows2[0];
|
|
30782
|
+
if (header?.type !== "session" || !text(header.id)) throw new Error("Unsupported Pi session.");
|
|
30783
|
+
const messages = rows2.slice(1).map((raw, index) => {
|
|
30784
|
+
const id2 = text(raw.id) || `record:${index}`;
|
|
30785
|
+
if (raw.type === "message" && raw.message) {
|
|
30786
|
+
const message3 = conversationMessage({ ...raw.message, id: id2, timestamp: raw.message.timestamp ?? raw.timestamp }, id2);
|
|
30787
|
+
for (const event of message3.events || []) event.parentID = text(raw.parentId) || void 0;
|
|
30788
|
+
return message3;
|
|
30789
|
+
}
|
|
30790
|
+
const message2 = blankMessage(id2, time(raw.timestamp), "system");
|
|
30791
|
+
message2.events = [eventFor(message2, "entry", ["compaction", "branch_summary"].includes(raw.type) ? "compaction" : "event", text(raw.type) || "Pi entry", text(raw.summary) || eventText(raw), { nativeType: text(raw.type), parentID: text(raw.parentId) || void 0 })];
|
|
30792
|
+
return message2;
|
|
30793
|
+
});
|
|
30794
|
+
successfulEdits(messages, ["write", "edit"]);
|
|
30795
|
+
const lastModel = [...rows2].reverse().find((r) => r.type === "model_change" || r.message?.model);
|
|
30796
|
+
return {
|
|
30797
|
+
nativeID: header.id,
|
|
30798
|
+
messages,
|
|
30799
|
+
partial,
|
|
30800
|
+
session: { directory: text(header.cwd), created: time(header.timestamp), title: text([...rows2].reverse().find((r) => r.type === "session_info")?.name), model: text(lastModel?.modelId || lastModel?.message?.model) || "Unknown model", provider: text(lastModel?.provider || lastModel?.message?.provider) },
|
|
30801
|
+
note: "Raw Pi session history includes all recorded in-file branches, not just the active model context. Entry parent IDs are preserved as event links, not session ancestry. Parent-session file paths do not establish whether a session is a fork or a child. Binary content is omitted."
|
|
30802
|
+
};
|
|
30803
|
+
}
|
|
30804
|
+
var piAdapter = localFileAdapter({
|
|
30805
|
+
id: "pi",
|
|
30806
|
+
name: "Pi",
|
|
30807
|
+
description: "Local JSONL sessions \xB7 messages, tools, compaction, and branch entries",
|
|
30808
|
+
capabilities: { history: true, forks: false, children: false, files: true },
|
|
30809
|
+
roots: () => [process.env.SESSIONGRAPH_PI_SESSIONS_DIR || join5(process.env.PI_CODING_AGENT_DIR || join5(homedir3(), ".pi", "agent"), "sessions")],
|
|
30810
|
+
match: (path) => path.endsWith(".jsonl"),
|
|
30811
|
+
parse(path, files) {
|
|
30812
|
+
const history = piHistory(files.get(path));
|
|
30813
|
+
history.session.nativeResume = Boolean(history.session.directory) && !process.env.SESSIONGRAPH_PI_SESSIONS_DIR;
|
|
30814
|
+
return history;
|
|
30815
|
+
}
|
|
30816
|
+
});
|
|
30817
|
+
|
|
30818
|
+
// server/adapters/harness-formats.ts
|
|
30819
|
+
var nativeText = (v3) => typeof v3 === "number" && Number.isSafeInteger(v3) ? String(v3) : text(v3);
|
|
30820
|
+
var seconds = (v3) => typeof v3 === "number" && Number.isFinite(v3) && v3 > 0 ? v3 * 1e3 : time(v3);
|
|
30821
|
+
var fileID = (path) => basename4(path).replace(/\.jsonl(?:\..*)?$|\.json$/, "");
|
|
30822
|
+
var sidecar2 = (files, path) => object(files.has(path) ? json2(files.get(path)) : void 0);
|
|
30823
|
+
var roles = ["user", "assistant", "system", "tool", "toolResult", "model"];
|
|
30824
|
+
function message(raw, id2, timestamp) {
|
|
30825
|
+
if (!roles.includes(raw.role)) return;
|
|
30826
|
+
if (raw.content == null && !Array.isArray(raw.toolCalls ?? raw.tool_calls)) return;
|
|
30827
|
+
return conversationMessage({ ...raw, id: nativeText(raw.id) || id2, timestamp: timestamp ?? raw.timestamp ?? raw.createdAt, toolCalls: raw.toolCalls ?? raw.tool_calls }, id2);
|
|
30828
|
+
}
|
|
30829
|
+
function recordEvent(raw, id2, kind, timestamp, content) {
|
|
30830
|
+
const m = blankMessage(id2, time(timestamp), "system");
|
|
30831
|
+
m.events = [eventFor(m, "record", /compact|summary/i.test(kind) ? "compaction" : /error/i.test(kind) ? "error" : "event", kind || "Source event", eventText(content), { nativeType: kind, partial: true })];
|
|
30832
|
+
m.partial = true;
|
|
30833
|
+
return m;
|
|
30834
|
+
}
|
|
30835
|
+
function envelopeHistory(path, value) {
|
|
30836
|
+
const { rows: rows2, partial } = jsonLines(value);
|
|
30837
|
+
if (rows2[0]?.type === "session" && rows2.some((r) => r.type === "message" && r.message?.role)) return piHistory(value);
|
|
30838
|
+
const nativeIDs = new Set(rows2.map((r) => text(r.sessionId || r.session_id)).filter(Boolean));
|
|
30839
|
+
if (nativeIDs.size > 1) throw new Error("Multiple native sessions in one transcript are not supported.");
|
|
30840
|
+
const header = rows2.find((r) => r.type === "session" || r.type === "session_start" || r.type === "session_startup") || {};
|
|
30841
|
+
const nativeID = [...nativeIDs][0] || text(header.id || header.sessionId) || fileID(path);
|
|
30842
|
+
const messages = rows2.flatMap((r, i) => {
|
|
30843
|
+
const body2 = object(r.message), role = body2.role || r.role || (["user", "assistant", "system"].includes(r.type) ? r.type : void 0);
|
|
30844
|
+
const m = message({ ...body2, id: text(r.uuid || r.id) || body2.id, role, content: body2.content ?? r.content ?? r.text }, `record:${i}`, r.timestamp ?? body2.timestamp);
|
|
30845
|
+
return m ? [m] : [];
|
|
30846
|
+
});
|
|
30847
|
+
if (!messages.length) throw new Error("No readable conversation records.");
|
|
30848
|
+
const meta = rows2.find((r) => r.cwd) || header;
|
|
30849
|
+
const last = [...rows2].reverse().find((r) => r.message?.model || typeof r.model === "string");
|
|
30850
|
+
return {
|
|
30851
|
+
nativeID,
|
|
30852
|
+
messages,
|
|
30853
|
+
partial: true,
|
|
30854
|
+
session: { directory: text(meta.cwd), created: time(header.timestamp), title: text(header.title), model: text(last?.message?.model || last?.model) || "Unknown model" },
|
|
30855
|
+
note: `Local conversation envelopes; source-native message IDs are retained. Context-only records, opaque attachments, and unsupported branches may be absent.${partial ? " Malformed records were skipped." : ""}`
|
|
30856
|
+
};
|
|
30857
|
+
}
|
|
30858
|
+
function ampHistory(path, files) {
|
|
30859
|
+
const raw = object(json2(files.get(path)));
|
|
30860
|
+
if (!text(raw.id) || !Array.isArray(raw.messages)) throw new Error("Unsupported Amp thread.");
|
|
30861
|
+
const messages = raw.messages.flatMap((m, i) => {
|
|
30862
|
+
const result = message(m, nativeText(m.messageId) || `message:${i}`, m.timestamp ?? m.usage?.timestamp);
|
|
30863
|
+
return result ? [result] : [];
|
|
30864
|
+
});
|
|
30865
|
+
return { nativeID: raw.id, messages, partial: true, session: { created: time(raw.created), title: text(raw.title), directory: text(raw.cwd) }, note: "Amp local/exported thread messages. Usage-ledger ranges are not message timestamps. Export-only and missing content remain partial." };
|
|
30866
|
+
}
|
|
30867
|
+
function jcodeHistory(path, files) {
|
|
30868
|
+
const raw = object(json2(files.get(path)));
|
|
30869
|
+
if (!text(raw.id) || !Array.isArray(raw.messages)) throw new Error("Unsupported Jcode session.");
|
|
30870
|
+
const journal = path.replace(/\.json$/, ".journal.jsonl");
|
|
30871
|
+
const appended = files.has(journal) ? jsonLines(files.get(journal)) : { rows: [], partial: false };
|
|
30872
|
+
const records = [...raw.messages];
|
|
30873
|
+
for (const row of appended.rows) {
|
|
30874
|
+
if (Array.isArray(row.append_messages)) records.push(...row.append_messages);
|
|
30875
|
+
if (row.meta) Object.assign(raw, object(row.meta));
|
|
30876
|
+
}
|
|
30877
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30878
|
+
const messages = records.flatMap((r, i) => {
|
|
30879
|
+
const id2 = text(r.id) || `message:${i}`;
|
|
30880
|
+
if (seen.has(id2)) return [];
|
|
30881
|
+
seen.add(id2);
|
|
30882
|
+
const m = message(r, id2);
|
|
30883
|
+
return m ? [m] : [];
|
|
30884
|
+
});
|
|
30885
|
+
return { nativeID: raw.id, messages, partial: appended.partial, session: { title: text(raw.title), directory: text(raw.working_dir), created: time(raw.created_at), model: text(raw.model) || "Unknown model", provider: text(raw.provider_key) }, note: "Jcode session snapshot and append journal; replayed native message IDs count once. Absent message times remain unknown." };
|
|
30886
|
+
}
|
|
30887
|
+
function codebuffHistory(path, files, free) {
|
|
30888
|
+
const records = json2(files.get(path));
|
|
30889
|
+
if (!Array.isArray(records)) throw new Error("Unsupported Codebuff chat.");
|
|
30890
|
+
const isFree = records.some((m) => text(m.metadata?.runState?.sessionState?.mainAgentState?.agentType).startsWith("base2-free"));
|
|
30891
|
+
if (isFree !== free) return;
|
|
30892
|
+
const messages = records.flatMap((r, i) => {
|
|
30893
|
+
const content = r.content ?? (Array.isArray(r.blocks) ? r.blocks.filter((b) => typeof b.content === "string").map((b) => ({ type: "text", text: b.content })) : void 0);
|
|
30894
|
+
const m = message({ ...r, role: r.role || r.type, content }, text(r.id) || `message:${i}`);
|
|
30895
|
+
return m ? [m] : [];
|
|
30896
|
+
});
|
|
30897
|
+
const chat = basename4(dirname2(path)), project2 = basename4(dirname2(dirname2(dirname2(path))));
|
|
30898
|
+
return { nativeID: `${project2}/${chat}`, messages, partial: true, note: "Codebuff/Freebuff persisted chat messages. Freebuff requires a recorded base2-free root-agent marker. Shared storage is attributed once; token counts are not estimated from text." };
|
|
30899
|
+
}
|
|
30900
|
+
function vibeHistory(path, files) {
|
|
30901
|
+
const raw = object(json2(files.get(path))), legacy = basename4(path) !== "meta.json";
|
|
30902
|
+
const meta = legacy ? object(raw.metadata) : raw;
|
|
30903
|
+
const transcript = join6(dirname2(path), "messages.jsonl");
|
|
30904
|
+
const parsed = files.has(transcript) ? jsonLines(files.get(transcript)) : { rows: [], partial: false };
|
|
30905
|
+
const records = legacy ? raw.messages : parsed.rows;
|
|
30906
|
+
if (!text(meta.session_id) || !Array.isArray(records)) throw new Error("Unsupported Vibe session.");
|
|
30907
|
+
const messages = records.flatMap((r, i) => {
|
|
30908
|
+
const m = message(r, `message:${i}`);
|
|
30909
|
+
return m ? [m] : [];
|
|
30910
|
+
});
|
|
30911
|
+
return {
|
|
30912
|
+
nativeID: meta.session_id,
|
|
30913
|
+
messages,
|
|
30914
|
+
partial: parsed.partial || !messages.length,
|
|
30915
|
+
session: { directory: text(meta.environment?.working_directory), created: time(meta.start_time), model: text(meta.config?.active_model) || "Unknown model", title: text(meta.title) },
|
|
30916
|
+
note: "Vibe session metadata and conversation snapshot. Untimed transcript messages remain untimed; cumulative session usage is not spread across turns."
|
|
30917
|
+
};
|
|
30918
|
+
}
|
|
30919
|
+
function acpHistory(path, value) {
|
|
30920
|
+
const { rows: rows2 } = jsonLines(value), ids = /* @__PURE__ */ new Set(), messages = [];
|
|
30921
|
+
let current;
|
|
30922
|
+
const flush = () => {
|
|
30923
|
+
if (current) {
|
|
30924
|
+
messages.push(conversationMessage({ role: current.role, content: current.content, timestamp: current.time }, current.id));
|
|
30925
|
+
current = void 0;
|
|
30926
|
+
}
|
|
30927
|
+
};
|
|
30928
|
+
for (const [i, row] of rows2.entries()) {
|
|
30929
|
+
const p = object(row.params), u2 = object(p.update), kind = text(u2.sessionUpdate);
|
|
30930
|
+
if (text(p.sessionId)) ids.add(p.sessionId);
|
|
30931
|
+
const timestamp = time(p._meta?.agentTimestampMs) || time(row.timestamp);
|
|
30932
|
+
const role = kind === "user_message_chunk" ? "user" : kind === "agent_message_chunk" ? "assistant" : "";
|
|
30933
|
+
if (role && u2.content?.type === "text") {
|
|
30934
|
+
if (current?.role !== role) {
|
|
30935
|
+
flush();
|
|
30936
|
+
current = { role, content: "", id: `chunk:${i}`, time: timestamp };
|
|
30937
|
+
}
|
|
30938
|
+
current.content += text(u2.content.text);
|
|
30939
|
+
} else {
|
|
30940
|
+
flush();
|
|
30941
|
+
if (kind === "agent_thought_chunk") messages.push(recordEvent(row, `record:${i}`, "Recorded reasoning", timestamp, u2.content?.text));
|
|
30942
|
+
else if (kind) messages.push(recordEvent(row, `record:${i}`, kind, timestamp, u2.title));
|
|
30943
|
+
}
|
|
30944
|
+
}
|
|
30945
|
+
flush();
|
|
30946
|
+
if (ids.size > 1) throw new Error("Multiple sessions in one ACP event log.");
|
|
30947
|
+
if (!ids.size || !messages.length) throw new Error("No supported ACP session updates.");
|
|
30948
|
+
return { nativeID: [...ids][0], messages, partial: true, note: "Saved ACP updates. Adjacent text chunks are grouped; missing turn boundaries, tool payloads, and metadata remain partial." };
|
|
30949
|
+
}
|
|
30950
|
+
function dshHistory(value) {
|
|
30951
|
+
const { rows: rows2, partial } = jsonLines(value), header = rows2.find((r) => r.type === "session");
|
|
30952
|
+
if (!text(header?.id)) throw new Error("Unsupported DeepSeek Harness session.");
|
|
30953
|
+
const messages = rows2.flatMap((r, i) => {
|
|
30954
|
+
if (!["user/message", "assistant/message", "tool/message"].includes(r.type)) return [];
|
|
30955
|
+
const body2 = object(r.data?.message);
|
|
30956
|
+
const m = message({ ...body2, role: body2.role || r.type.split("/")[0] }, text(body2.id) || `record:${i}`, r.time);
|
|
30957
|
+
return m ? [m] : [];
|
|
30958
|
+
});
|
|
30959
|
+
return { nativeID: header.id, messages, partial: true, session: { directory: text(header.cwd), created: time(header.createdAt ?? header.time) }, note: `DeepSeek Harness final message records; streaming/retry bookkeeping is excluded. Context seeds and unsupported event payloads may be absent.${partial ? " Malformed records were skipped." : ""}` };
|
|
30960
|
+
}
|
|
30961
|
+
|
|
30962
|
+
// server/adapters/sqlite-histories.ts
|
|
30963
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
30964
|
+
|
|
30965
|
+
// server/adapters/sqlite-snapshot.ts
|
|
30966
|
+
import { DatabaseSync } from "node:sqlite";
|
|
30967
|
+
import { chmodSync, constants as constants4, copyFileSync, lstatSync as lstatSync2, mkdtempSync, rmSync as rmSync2 } from "node:fs";
|
|
30968
|
+
import { tmpdir } from "node:os";
|
|
30969
|
+
import { join as join7 } from "node:path";
|
|
30970
|
+
var copies = /* @__PURE__ */ new Map();
|
|
30971
|
+
var close = (path) => {
|
|
30972
|
+
const copy = copies.get(path);
|
|
30973
|
+
if (!copy) return;
|
|
30974
|
+
copies.delete(path);
|
|
30975
|
+
try {
|
|
30976
|
+
copy.db.close();
|
|
30977
|
+
} finally {
|
|
30978
|
+
rmSync2(copy.directory, { recursive: true, force: true });
|
|
30979
|
+
}
|
|
30980
|
+
};
|
|
30981
|
+
process.once("exit", () => {
|
|
30982
|
+
for (const path of copies.keys()) close(path);
|
|
30983
|
+
});
|
|
30984
|
+
function sqliteRevision(path) {
|
|
30985
|
+
return digest([path, ...[path, `${path}-wal`].map((file, i) => {
|
|
30986
|
+
try {
|
|
30987
|
+
const s = lstatSync2(file);
|
|
30988
|
+
if (!s.isFile() || s.size > 8e9) throw new Error("History database must be a regular file below 8 GB.");
|
|
30989
|
+
return [s.dev, s.ino, s.size, s.mtimeMs, s.ctimeMs];
|
|
30990
|
+
} catch (error) {
|
|
30991
|
+
if (i && error.code === "ENOENT") return null;
|
|
30992
|
+
throw error;
|
|
30993
|
+
}
|
|
30994
|
+
})]);
|
|
30995
|
+
}
|
|
30996
|
+
function withSqliteSnapshot(path, read3, expected) {
|
|
30997
|
+
const revision2 = sqliteRevision(path);
|
|
30998
|
+
if (expected && revision2 !== expected) throw new Error("History database changed during import; import again.");
|
|
30999
|
+
let copy = copies.get(path);
|
|
31000
|
+
if (copy?.revision !== revision2) {
|
|
31001
|
+
close(path);
|
|
31002
|
+
while (copies.size >= 2) close(copies.keys().next().value);
|
|
31003
|
+
const directory2 = mkdtempSync(join7(tmpdir(), "sessiongraph-history-"));
|
|
31004
|
+
let db2;
|
|
31005
|
+
try {
|
|
31006
|
+
chmodSync(directory2, 448);
|
|
31007
|
+
const destination = join7(directory2, "history.db");
|
|
31008
|
+
for (const suffix of ["", "-wal"]) {
|
|
31009
|
+
try {
|
|
31010
|
+
copyFileSync(path + suffix, destination + suffix, constants4.COPYFILE_FICLONE);
|
|
31011
|
+
chmodSync(destination + suffix, 384);
|
|
31012
|
+
} catch (error) {
|
|
31013
|
+
if (suffix && error.code === "ENOENT") continue;
|
|
31014
|
+
throw error;
|
|
31015
|
+
}
|
|
31016
|
+
}
|
|
31017
|
+
if (sqliteRevision(path) !== revision2) throw new Error("History database changed while copying; import again.");
|
|
31018
|
+
db2 = new DatabaseSync(destination, { readOnly: true, enableDoubleQuotedStringLiterals: false, allowExtension: false });
|
|
31019
|
+
db2.exec("PRAGMA query_only=ON; PRAGMA trusted_schema=OFF; PRAGMA busy_timeout=1000; BEGIN;");
|
|
31020
|
+
copy = { revision: revision2, directory: directory2, db: db2 };
|
|
31021
|
+
copies.set(path, copy);
|
|
31022
|
+
} catch (error) {
|
|
31023
|
+
try {
|
|
31024
|
+
db2?.close();
|
|
31025
|
+
} finally {
|
|
31026
|
+
rmSync2(directory2, { recursive: true, force: true });
|
|
31027
|
+
}
|
|
31028
|
+
throw error;
|
|
31029
|
+
}
|
|
31030
|
+
}
|
|
31031
|
+
const result = read3(copy.db);
|
|
31032
|
+
if (sqliteRevision(path) !== revision2) throw new Error("History database changed during reading; import again.");
|
|
31033
|
+
return result;
|
|
31034
|
+
}
|
|
31035
|
+
function hasTable(db2, name) {
|
|
31036
|
+
const row = db2.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?").get(name);
|
|
31037
|
+
return typeof row?.sql === "string" && !/CREATE\s+VIRTUAL/i.test(row.sql);
|
|
31038
|
+
}
|
|
31039
|
+
|
|
31040
|
+
// server/adapters/sqlite-histories.ts
|
|
31041
|
+
var budget;
|
|
31042
|
+
var rowBytes = (row) => Object.values(row).reduce((n, v3) => n + (typeof v3 === "string" ? Buffer.byteLength(v3) : v3 instanceof Uint8Array ? v3.byteLength : 8), 0);
|
|
31043
|
+
function consume(bytes2, records = 0) {
|
|
31044
|
+
if (!budget) return;
|
|
31045
|
+
budget.bytes += bytes2;
|
|
31046
|
+
budget.records += records;
|
|
31047
|
+
if (budget.bytes > 64e6 || budget.records > 5e4) throw new Error("Session exceeds the 64 MB / 50,000-record snapshot limit.");
|
|
31048
|
+
}
|
|
31049
|
+
function parseHistory(source, db2, row, path) {
|
|
31050
|
+
const previous = budget;
|
|
31051
|
+
budget = { bytes: 0, records: 0 };
|
|
31052
|
+
try {
|
|
31053
|
+
consume(rowBytes(row), 1);
|
|
31054
|
+
const history = source.parse(db2, row, path);
|
|
31055
|
+
if (history && history.messages.length > 5e4) throw new Error("Session exceeds the 50,000-message limit.");
|
|
31056
|
+
return history;
|
|
31057
|
+
} finally {
|
|
31058
|
+
budget = previous;
|
|
31059
|
+
}
|
|
31060
|
+
}
|
|
31061
|
+
function columns(db2, table) {
|
|
31062
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(table) || !hasTable(db2, table)) throw new Error(`Unsupported history table: ${table}.`);
|
|
31063
|
+
return new Set(db2.prepare(`PRAGMA table_info("${table}")`).all().map((r) => String(r.name)));
|
|
31064
|
+
}
|
|
31065
|
+
function rows(db2, table, fields2, filter, order, limit = 5e4) {
|
|
31066
|
+
const available = columns(db2, table);
|
|
31067
|
+
for (const name of [...fields2, ...filter ? [filter[0]] : [], ...order ? [order] : []]) if (!/^[a-zA-Z_][\w]*$/.test(name)) throw new Error("Invalid history column.");
|
|
31068
|
+
if (filter && !available.has(filter[0])) throw new Error("Unsupported history relation.");
|
|
31069
|
+
const size = (f) => `length(CAST("${f}" AS BLOB))`;
|
|
31070
|
+
const select = fields2.map((f) => available.has(f) ? `CASE WHEN ${size(f)}<=32000000 THEN "${f}" END AS "${f}"` : `NULL AS "${f}"`).join(",");
|
|
31071
|
+
const oversized = fields2.filter((f) => available.has(f)).map((f) => `COALESCE(${size(f)},0)>32000000`).join(" OR ") || "0";
|
|
31072
|
+
const query = `SELECT ${select}, (${oversized}) AS __sessiongraph_oversized FROM "${table}"${filter ? ` WHERE "${filter[0]}"=?` : ""}${order && available.has(order) ? ` ORDER BY "${order}"` : ""} LIMIT ${limit + 1}`;
|
|
31073
|
+
const result = [];
|
|
31074
|
+
let bytes2 = 0;
|
|
31075
|
+
for (const row of db2.prepare(query).iterate(...filter ? [filter[1]] : [])) {
|
|
31076
|
+
if (row.__sessiongraph_oversized) throw new Error("History field exceeds the 32 MB snapshot limit.");
|
|
31077
|
+
delete row.__sessiongraph_oversized;
|
|
31078
|
+
const size2 = rowBytes(row);
|
|
31079
|
+
bytes2 += size2;
|
|
31080
|
+
consume(size2, 1);
|
|
31081
|
+
if (bytes2 > 64e6 || result.length >= limit) throw new Error("History exceeds the 64 MB / record-count query limit.");
|
|
31082
|
+
result.push(row);
|
|
31083
|
+
}
|
|
31084
|
+
return result;
|
|
31085
|
+
}
|
|
31086
|
+
function sqliteJSON(value) {
|
|
31087
|
+
if (value instanceof Uint8Array) {
|
|
31088
|
+
const expanded = expandHistory(Buffer.from(value), Math.max(1, Math.min(32e6, 64e6 - (budget?.bytes || 0))));
|
|
31089
|
+
consume(expanded.byteLength);
|
|
31090
|
+
return JSON.parse(expanded.toString("utf8"));
|
|
31091
|
+
}
|
|
31092
|
+
if (typeof value === "string") return JSON.parse(value);
|
|
31093
|
+
throw new Error("Missing or oversized history payload.");
|
|
31094
|
+
}
|
|
31095
|
+
function optionalJSON(value) {
|
|
31096
|
+
return value == null || value === "" ? {} : sqliteJSON(value);
|
|
31097
|
+
}
|
|
31098
|
+
function sqliteHistoryAdapter(source) {
|
|
31099
|
+
const metadata2 = /* @__PURE__ */ new Map();
|
|
31100
|
+
return {
|
|
31101
|
+
id: source.id,
|
|
31102
|
+
name: source.name,
|
|
31103
|
+
description: source.description || "Local SQLite session history \xB7 isolated read-only snapshots",
|
|
31104
|
+
capabilities: { history: source.history !== false, forks: false, children: false, files: false },
|
|
31105
|
+
async detect() {
|
|
31106
|
+
return (await source.paths()).length > 0;
|
|
31107
|
+
},
|
|
31108
|
+
async discover() {
|
|
31109
|
+
const sessions = /* @__PURE__ */ new Map(), warnings = [], blocked = [];
|
|
31110
|
+
let unavailable = false;
|
|
31111
|
+
for (const path of await source.paths()) {
|
|
31112
|
+
try {
|
|
31113
|
+
const snapshot2 = sqliteRevision(path), rev = digest([2, source.id, snapshot2]);
|
|
31114
|
+
let cached2 = metadata2.get(path);
|
|
31115
|
+
if (!cached2 || cached2.revision !== rev) {
|
|
31116
|
+
cached2 = withSqliteSnapshot(path, (db2) => {
|
|
31117
|
+
const table = typeof source.table === "string" ? source.table : source.table(db2);
|
|
31118
|
+
const values2 = rows(db2, table, source.fields, void 0, source.key, 2e4), found = [];
|
|
31119
|
+
const blocked2 = [], warnings2 = [];
|
|
31120
|
+
for (const row of values2) {
|
|
31121
|
+
try {
|
|
31122
|
+
const h3 = parseHistory(source, db2, row, path);
|
|
31123
|
+
if (!h3) continue;
|
|
31124
|
+
if (source.history !== false && !h3.messages.length) throw new Error("No readable conversation.");
|
|
31125
|
+
if (!h3.nativeID || h3.nativeID.length > 512) throw new Error("Missing native session identity.");
|
|
31126
|
+
const created = time(h3.session?.created) || h3.messages.map((m) => time(m.time)).find(Boolean);
|
|
31127
|
+
if (!created) throw new Error("Session has no recorded creation or message date.");
|
|
31128
|
+
const session = { ...blankSession(source.id, h3.nativeID), ...h3.session, created };
|
|
31129
|
+
session.id = identity(source.id, h3.nativeID);
|
|
31130
|
+
session.nativeID = h3.nativeID;
|
|
31131
|
+
session.source = source.id;
|
|
31132
|
+
session.directory = text(session.directory);
|
|
31133
|
+
session.projectID = project(source.id, session.directory).id;
|
|
31134
|
+
session.agent ||= source.name;
|
|
31135
|
+
session.updated ||= created;
|
|
31136
|
+
session.parentID = h3.parent ? identity(source.id, h3.parent) : void 0;
|
|
31137
|
+
session.forkID = h3.fork ? identity(source.id, h3.fork) : void 0;
|
|
31138
|
+
if (!session.title || session.title === "Untitled session") session.title = h3.messages.flatMap((m) => m.role === "user" ? m.fragments.filter((f) => f.kind === "You").map((f) => f.text) : []).find(Boolean)?.replace(/\s+/g, " ").slice(0, 180) || `${source.name} session`;
|
|
31139
|
+
found.push({ session, revision: rev, locator: { path, snapshot: snapshot2, table, key: String(row[source.key]) } });
|
|
31140
|
+
} catch {
|
|
31141
|
+
blocked2.push(identity(source.id, String(row[source.key])));
|
|
31142
|
+
}
|
|
31143
|
+
}
|
|
31144
|
+
if (blocked2.length) warnings2.push(`${blocked2.length} ${source.name} sessions had unsupported, undated, missing, or oversized history; saved imports are kept.`);
|
|
31145
|
+
return { revision: rev, candidates: found, blocked: blocked2, warnings: warnings2 };
|
|
31146
|
+
}, snapshot2);
|
|
31147
|
+
metadata2.set(path, cached2);
|
|
31148
|
+
}
|
|
31149
|
+
warnings.push(...cached2.warnings);
|
|
31150
|
+
blocked.push(...cached2.blocked);
|
|
31151
|
+
for (const c of cached2.candidates) {
|
|
31152
|
+
const old = sessions.get(c.session.id);
|
|
31153
|
+
if (!old || c.session.updated > old.session.updated) sessions.set(c.session.id, c);
|
|
31154
|
+
}
|
|
31155
|
+
} catch {
|
|
31156
|
+
unavailable = true;
|
|
31157
|
+
warnings.push(`A ${source.name} database could not be read consistently; saved imports are kept.`);
|
|
31158
|
+
}
|
|
31159
|
+
}
|
|
31160
|
+
const values = [...sessions.values()];
|
|
31161
|
+
return { sessions: values, projects: [...new Map(values.map((c) => {
|
|
31162
|
+
const p = project(source.id, c.session.directory);
|
|
31163
|
+
return [p.id, p];
|
|
31164
|
+
})).values()], warnings, blocked, unavailable };
|
|
31165
|
+
},
|
|
31166
|
+
async read(candidate, cursor) {
|
|
31167
|
+
if (cursor) throw new Error("Invalid SQLite history cursor.");
|
|
31168
|
+
const { path, snapshot: snapshot2, key } = candidate.locator;
|
|
31169
|
+
return withSqliteSnapshot(path, (db2) => {
|
|
31170
|
+
const table = typeof source.table === "string" ? source.table : source.table(db2);
|
|
31171
|
+
const row = rows(db2, table, source.fields, [source.key, key], void 0, 1)[0];
|
|
31172
|
+
if (!row) throw new Error("Session metadata is unavailable.");
|
|
31173
|
+
const history = parseHistory(source, db2, row, path);
|
|
31174
|
+
if (!history || history.nativeID !== candidate.session.nativeID) throw new Error("Session identity changed.");
|
|
31175
|
+
if (source.history !== false && !history.messages.length) throw new Error("No readable conversation.");
|
|
31176
|
+
return { messages: history.messages, partial: Boolean(history.partial || history.messages.some((m) => m.partial)), note: `${history.note} File snapshots are unavailable. Missing message timestamps remain unknown.` };
|
|
31177
|
+
}, snapshot2);
|
|
31178
|
+
}
|
|
31179
|
+
};
|
|
31180
|
+
}
|
|
31181
|
+
async function existingDatabases(paths) {
|
|
31182
|
+
const found = [];
|
|
31183
|
+
for (const path of new Set(paths)) {
|
|
31184
|
+
try {
|
|
31185
|
+
if ((await lstat3(path)).isFile()) found.push(path);
|
|
31186
|
+
} catch (e10) {
|
|
31187
|
+
if (e10.code !== "ENOENT") throw e10;
|
|
31188
|
+
}
|
|
31189
|
+
}
|
|
31190
|
+
return found;
|
|
31191
|
+
}
|
|
31192
|
+
function combineAdapters(parts) {
|
|
31193
|
+
const first = parts[0];
|
|
31194
|
+
return {
|
|
31195
|
+
...first,
|
|
31196
|
+
async detect() {
|
|
31197
|
+
for (const p of parts) if (await p.detect().catch(() => false)) return true;
|
|
31198
|
+
return false;
|
|
31199
|
+
},
|
|
31200
|
+
async discover() {
|
|
31201
|
+
const sessions = /* @__PURE__ */ new Map(), projects = /* @__PURE__ */ new Map(), warnings = [], blocked = /* @__PURE__ */ new Set();
|
|
31202
|
+
for (const [i, part] of parts.entries()) {
|
|
31203
|
+
let result;
|
|
31204
|
+
try {
|
|
31205
|
+
if (!await part.detect()) continue;
|
|
31206
|
+
result = await part.discover();
|
|
31207
|
+
} catch {
|
|
31208
|
+
warnings.push(`A ${part.name} representation could not be read; saved imports are retained.`);
|
|
31209
|
+
break;
|
|
31210
|
+
}
|
|
31211
|
+
warnings.push(...result.warnings);
|
|
31212
|
+
for (const id2 of result.blocked || []) blocked.add(id2);
|
|
31213
|
+
for (const p of result.projects) projects.set(p.id, p);
|
|
31214
|
+
for (const c of result.sessions) if (!blocked.has(c.session.id) && !sessions.has(c.session.id)) sessions.set(c.session.id, { ...c, revision: digest([i, c.revision]), locator: { ...c.locator, representation: String(i), originalRevision: c.revision } });
|
|
31215
|
+
if (result.unavailable) break;
|
|
31216
|
+
}
|
|
31217
|
+
return { sessions: [...sessions.values()], projects: [...projects.values()], warnings };
|
|
31218
|
+
},
|
|
31219
|
+
async read(candidate, cursor) {
|
|
31220
|
+
const index = Number(candidate.locator.representation);
|
|
31221
|
+
if (!Number.isInteger(index) || !parts[index]) throw new Error("Invalid history representation.");
|
|
31222
|
+
return parts[index].read({ ...candidate, revision: candidate.locator.originalRevision }, cursor);
|
|
31223
|
+
}
|
|
31224
|
+
};
|
|
31225
|
+
}
|
|
31226
|
+
|
|
31227
|
+
// server/adapters/claude.ts
|
|
31228
|
+
var root = () => join8(process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude"), "projects");
|
|
31229
|
+
function claudeMessages(rows2, candidate, times) {
|
|
31230
|
+
const calls = /* @__PURE__ */ new Map();
|
|
31231
|
+
return rows2.map((row) => {
|
|
31232
|
+
const raw = row.message;
|
|
31233
|
+
const msg = blankMessage(row.uuid, times?.get(row.uuid) ?? (Number.isFinite(Date.parse(raw?.timestamp)) ? Date.parse(raw.timestamp) : 0), row.type);
|
|
31234
|
+
if (raw?.model) candidate.session.model = raw.model;
|
|
31235
|
+
if (row.parent_agent_id && candidate.locator.parent) candidate.session.parentID = identity("claude-code", `${candidate.locator.parent}/agent/${row.parent_agent_id}`);
|
|
31236
|
+
if (typeof raw?.content === "string") addText(msg, raw.content, row.type === "user" ? "You" : "Assistant");
|
|
31237
|
+
for (const part of Array.isArray(raw?.content) ? raw.content : []) {
|
|
31238
|
+
if (part.type === "text") addText(msg, part.text, row.type === "user" ? "You" : "Assistant");
|
|
31239
|
+
if (part.type === "tool_use") {
|
|
31240
|
+
calls.set(part.id, { name: part.name, input: part.input });
|
|
31241
|
+
msg.tools.push(part.name);
|
|
31242
|
+
addText(msg, part.name, "Tool");
|
|
31243
|
+
toolText(msg, part.input);
|
|
31244
|
+
}
|
|
31245
|
+
if (part.type === "tool_result") {
|
|
31246
|
+
toolText(msg, part.content);
|
|
31247
|
+
const call = calls.get(part.tool_use_id);
|
|
31248
|
+
if (!part.is_error && call && ["Edit", "Write", "MultiEdit", "NotebookEdit"].includes(call.name)) {
|
|
31249
|
+
const path = call.input?.file_path || call.input?.notebook_path;
|
|
31250
|
+
if (typeof path === "string") msg.files.push({ path, source: "edit tool", steps: 1 });
|
|
31251
|
+
}
|
|
31252
|
+
}
|
|
31253
|
+
}
|
|
31254
|
+
msg.events = blockEvents(msg, raw?.content).map((event) => ({ ...event, parentID: row.parent_tool_use_id || void 0 }));
|
|
31255
|
+
if (!msg.events.length) msg.events = [eventFor(msg, "event", raw?.subtype === "compact_boundary" ? "compaction" : "event", raw?.subtype || `${row.type} event`, eventText(raw), { nativeType: raw?.subtype || row.type, partial: true })];
|
|
31256
|
+
return msg;
|
|
31257
|
+
});
|
|
31258
|
+
}
|
|
31259
|
+
var sdkClaudeAdapter = {
|
|
31260
|
+
id: "claude-code",
|
|
31261
|
+
name: "Claude Code",
|
|
31262
|
+
description: "Local CLI and VS Code history \xB7 all projects and subagent transcripts",
|
|
31263
|
+
capabilities: { history: true, forks: false, children: true, files: true },
|
|
31264
|
+
async detect() {
|
|
31265
|
+
try {
|
|
31266
|
+
return (await stat(root())).isDirectory();
|
|
31267
|
+
} catch {
|
|
31268
|
+
return false;
|
|
31269
|
+
}
|
|
31270
|
+
},
|
|
31271
|
+
async discover() {
|
|
31272
|
+
const sessions = [], warnings = [];
|
|
31273
|
+
const infos = await I1t({ limit: 2e4, includeProgrammatic: true });
|
|
31274
|
+
if (infos.length === 2e4) warnings.push("Claude Code listing is limited to 20,000 sessions.");
|
|
31275
|
+
const files = /* @__PURE__ */ new Map();
|
|
31276
|
+
let unreadable = 0;
|
|
31277
|
+
for (const dir of await readdir2(root(), { withFileTypes: true })) {
|
|
31278
|
+
if (!dir.isDirectory()) continue;
|
|
31279
|
+
const parent2 = join8(root(), dir.name);
|
|
31280
|
+
let entries2;
|
|
31281
|
+
try {
|
|
31282
|
+
entries2 = await readdir2(parent2, { withFileTypes: true });
|
|
31283
|
+
} catch {
|
|
31284
|
+
unreadable++;
|
|
31285
|
+
continue;
|
|
31286
|
+
}
|
|
31287
|
+
for (const file of entries2) if (file.isFile() && file.name.endsWith(".jsonl") && claudeSessionID(file.name.slice(0, -6))) {
|
|
31288
|
+
const id2 = file.name.slice(0, -6), path = join8(parent2, file.name);
|
|
31289
|
+
try {
|
|
31290
|
+
const old = files.get(id2);
|
|
31291
|
+
if (!old || (await stat(path)).mtimeMs > (await stat(old.path)).mtimeMs) files.set(id2, { path, children: join8(parent2, id2, "subagents") });
|
|
31292
|
+
} catch {
|
|
31293
|
+
unreadable++;
|
|
31294
|
+
}
|
|
31295
|
+
}
|
|
31296
|
+
}
|
|
31297
|
+
const listed = new Set(infos.map((info) => info.sessionId));
|
|
31298
|
+
for (const [id2, file] of files) {
|
|
31299
|
+
if (listed.has(id2)) continue;
|
|
31300
|
+
if (infos.length >= 2e4) {
|
|
31301
|
+
warnings.push("Claude Code discovery is limited to 20,000 sessions.");
|
|
31302
|
+
break;
|
|
31303
|
+
}
|
|
31304
|
+
try {
|
|
31305
|
+
const transcript = await claudeTranscript(file.path, await claudeRevision(file.path));
|
|
31306
|
+
const info = claudeTranscriptInfo(transcript, id2);
|
|
31307
|
+
if (info) infos.push(info);
|
|
31308
|
+
else if (transcript.partial) unreadable++;
|
|
31309
|
+
} catch {
|
|
31310
|
+
unreadable++;
|
|
31311
|
+
}
|
|
31312
|
+
}
|
|
31313
|
+
for (const info of infos) {
|
|
31314
|
+
const file = files.get(info.sessionId);
|
|
31315
|
+
if (!file) continue;
|
|
31316
|
+
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" };
|
|
31317
|
+
try {
|
|
31318
|
+
sessions.push({ session: s, revision: await claudeRevision(file.path), locator: { path: file.path, dir: info.cwd || "" } });
|
|
31319
|
+
} catch {
|
|
31320
|
+
unreadable++;
|
|
31321
|
+
continue;
|
|
31322
|
+
}
|
|
31323
|
+
let children;
|
|
31324
|
+
try {
|
|
31325
|
+
children = await readdir2(file.children, { withFileTypes: true });
|
|
31326
|
+
} catch (error) {
|
|
31327
|
+
if (error.code !== "ENOENT") unreadable++;
|
|
31328
|
+
continue;
|
|
31329
|
+
}
|
|
31330
|
+
for (const child of children) {
|
|
31331
|
+
if (!child.isFile() || !/^agent-[\w-]+\.jsonl$/.test(child.name)) continue;
|
|
31332
|
+
try {
|
|
31333
|
+
const agent = child.name.slice(6, -6), path = join8(file.children, child.name), stats = await stat(path);
|
|
31334
|
+
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 } });
|
|
31335
|
+
} catch {
|
|
31336
|
+
unreadable++;
|
|
31337
|
+
}
|
|
31338
|
+
}
|
|
31339
|
+
}
|
|
31340
|
+
if (unreadable) warnings.push(`${unreadable} Claude Code histories unavailable or too large. Saved imports are kept.`);
|
|
31341
|
+
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
31342
|
+
const p = project("claude-code", c.session.directory);
|
|
31343
|
+
return [p.id, p];
|
|
31344
|
+
})).values()], warnings };
|
|
31345
|
+
},
|
|
31346
|
+
async read(candidate, cursor) {
|
|
31347
|
+
const offset = Number(cursor || 0);
|
|
31348
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error("Invalid history cursor.");
|
|
31349
|
+
const transcript = await claudeTranscript(candidate.locator.path, candidate.revision);
|
|
31350
|
+
const start = Math.max(0, offset - 1), count = 120 + offset - start;
|
|
31351
|
+
const sessionId = candidate.locator.parent || candidate.session.nativeID;
|
|
31352
|
+
const options = { dir: candidate.locator.dir || void 0, offset: start, limit: count + 1, sessionStore: claudeTranscriptStore(transcript, sessionId, candidate.locator.agent) };
|
|
31353
|
+
const rows2 = candidate.locator.parent ? await U1t(candidate.locator.parent, candidate.locator.agent, options) : await P1t(candidate.session.nativeID, options);
|
|
31354
|
+
const more = rows2.length > count;
|
|
31355
|
+
const messages = claudeMessages(rows2.slice(0, count), candidate, transcript.times).slice(offset - start);
|
|
31356
|
+
if (!offset && !messages.length && transcript.size > 100) throw new Error("SDK returned no readable conversation for this transcript.");
|
|
31357
|
+
if (!more && await claudeRevision(candidate.locator.path) !== candidate.revision) throw new Error("Session changed during import; import again to capture the latest messages.");
|
|
31358
|
+
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." : ""}` };
|
|
31359
|
+
}
|
|
31360
|
+
};
|
|
31361
|
+
var exportedClaudeAdapter = localFileAdapter({
|
|
31362
|
+
id: "claude-code",
|
|
31363
|
+
name: "Claude Code",
|
|
31364
|
+
description: "Legacy local transcript exports",
|
|
31365
|
+
capabilities: { history: true, forks: false, children: false, files: false },
|
|
31366
|
+
roots: () => [join8(process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude"), "transcripts")],
|
|
31367
|
+
match: (p) => p.endsWith(".jsonl"),
|
|
31368
|
+
parse: (path, files) => envelopeHistory(path, files.get(path))
|
|
31369
|
+
});
|
|
31370
|
+
var claudeAdapter = combineAdapters([sdkClaudeAdapter, exportedClaudeAdapter]);
|
|
31371
|
+
|
|
31372
|
+
// server/adapters/codex.ts
|
|
31373
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
31374
|
+
import { homedir as homedir5 } from "node:os";
|
|
31375
|
+
import { join as join10, sep as sep2 } from "node:path";
|
|
31376
|
+
|
|
31377
|
+
// server/adapters/jsonl.ts
|
|
31378
|
+
import { open as open3, readdir as readdir3 } from "node:fs/promises";
|
|
31379
|
+
import { join as join9 } from "node:path";
|
|
31380
|
+
import { constants as constants5 } from "node:fs";
|
|
31381
|
+
var compressed;
|
|
31382
|
+
async function readJSONL(path, offset = 0, budget2 = 2e6, limit = 200) {
|
|
31383
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error("Invalid transcript cursor.");
|
|
31384
|
+
const file = await open3(path, constants5.O_RDONLY | (constants5.O_NOFOLLOW || 0));
|
|
31385
|
+
const rows2 = [];
|
|
31386
|
+
let position = offset, carry = Buffer.alloc(0), lineStart = offset, partial = false, skipping = false, eof = false;
|
|
31387
|
+
try {
|
|
31388
|
+
let data;
|
|
31389
|
+
if (/\.jsonl\..+/.test(path)) {
|
|
31390
|
+
const s = await file.stat(), stamp2 = JSON.stringify([s.ino, s.size, s.mtimeMs, s.ctimeMs]);
|
|
31391
|
+
if (!s.isFile() || s.size > 128e6) throw new Error("Compressed history exceeds the 128 MB limit.");
|
|
31392
|
+
if (compressed?.path === path && compressed.stamp === stamp2) data = compressed.data;
|
|
31393
|
+
else {
|
|
31394
|
+
const input = Buffer.alloc(s.size + 1);
|
|
31395
|
+
let bytes2 = 0;
|
|
31396
|
+
while (bytes2 < input.length) {
|
|
31397
|
+
const r = await file.read(input, bytes2, input.length - bytes2, bytes2);
|
|
31398
|
+
if (!r.bytesRead) break;
|
|
31399
|
+
bytes2 += r.bytesRead;
|
|
31400
|
+
}
|
|
31401
|
+
const after = await file.stat();
|
|
31402
|
+
if (bytes2 !== s.size || stamp2 !== JSON.stringify([after.ino, after.size, after.mtimeMs, after.ctimeMs])) throw new Error("Compressed history changed during reading.");
|
|
31403
|
+
data = expandHistory(input.subarray(0, bytes2), 128e6);
|
|
31404
|
+
compressed = { path, stamp: stamp2, data };
|
|
31405
|
+
}
|
|
31406
|
+
}
|
|
31407
|
+
while (rows2.length < limit && (position - offset < budget2 || lineStart === offset || skipping)) {
|
|
31408
|
+
if (position - offset > 64e6) throw new Error("Transcript contains a line larger than the 64 MB scan limit.");
|
|
31409
|
+
const buffer = Buffer.alloc(64e3);
|
|
31410
|
+
const bytesRead = data ? data.copy(buffer, 0, Math.min(position, data.length), Math.min(position + buffer.length, data.length)) : (await file.read(buffer, 0, buffer.length, position)).bytesRead;
|
|
31411
|
+
if (!bytesRead) {
|
|
31412
|
+
eof = true;
|
|
31413
|
+
break;
|
|
31414
|
+
}
|
|
31415
|
+
position += bytesRead;
|
|
31416
|
+
carry = Buffer.concat([carry, buffer.subarray(0, bytesRead)]);
|
|
31417
|
+
let at2;
|
|
31418
|
+
while ((at2 = carry.indexOf(10)) !== -1) {
|
|
31419
|
+
const line = carry.subarray(0, at2);
|
|
31420
|
+
carry = carry.subarray(at2 + 1);
|
|
31421
|
+
if (!skipping && line.length) {
|
|
31422
|
+
try {
|
|
31423
|
+
rows2.push({ offset: lineStart, value: JSON.parse(line.toString("utf8")) });
|
|
31424
|
+
} catch {
|
|
31425
|
+
partial = true;
|
|
31426
|
+
}
|
|
31427
|
+
}
|
|
31428
|
+
skipping = false;
|
|
31429
|
+
lineStart += at2 + 1;
|
|
31430
|
+
if (rows2.length >= limit) return { rows: rows2, cursor: lineStart, partial };
|
|
31431
|
+
}
|
|
31432
|
+
if (carry.length > 2e6) {
|
|
31433
|
+
partial = true;
|
|
31434
|
+
skipping = true;
|
|
31435
|
+
lineStart += carry.length;
|
|
31436
|
+
carry = Buffer.alloc(0);
|
|
31437
|
+
}
|
|
31438
|
+
}
|
|
31439
|
+
if (eof && carry.length && !skipping) {
|
|
31440
|
+
try {
|
|
31441
|
+
rows2.push({ offset: lineStart, value: JSON.parse(carry.toString("utf8")) });
|
|
31442
|
+
} catch {
|
|
31443
|
+
partial = true;
|
|
31444
|
+
}
|
|
31445
|
+
}
|
|
31446
|
+
return { rows: rows2, cursor: eof ? void 0 : lineStart, partial };
|
|
31447
|
+
} finally {
|
|
31448
|
+
await file.close();
|
|
31449
|
+
}
|
|
31450
|
+
}
|
|
31451
|
+
async function transcriptFiles(root3, max = 2e4, match = (name) => name.endsWith(".jsonl")) {
|
|
31452
|
+
const files = [], queue = [root3];
|
|
31453
|
+
for (let i = 0; i < queue.length && files.length < max; i++) {
|
|
31454
|
+
let entries2;
|
|
31455
|
+
try {
|
|
31456
|
+
entries2 = await readdir3(queue[i], { withFileTypes: true });
|
|
31457
|
+
} catch (error) {
|
|
31458
|
+
if (error.code === "ENOENT") continue;
|
|
31459
|
+
throw error;
|
|
31460
|
+
}
|
|
31461
|
+
for (const entry of entries2) {
|
|
31462
|
+
if (entry.isDirectory() && queue.length < 2e4) queue.push(join9(queue[i], entry.name));
|
|
31463
|
+
if (entry.isFile() && match(entry.name)) files.push(join9(queue[i], entry.name));
|
|
31464
|
+
if (files.length >= max) break;
|
|
31465
|
+
}
|
|
31466
|
+
}
|
|
31467
|
+
return files;
|
|
31468
|
+
}
|
|
31469
|
+
|
|
31470
|
+
// server/adapters/codex.ts
|
|
31471
|
+
var root2 = () => process.env.CODEX_HOME || join10(homedir5(), ".codex");
|
|
31472
|
+
var revision = async (path) => {
|
|
31473
|
+
const s = await stat2(path);
|
|
31474
|
+
return digest([8, s.mtimeMs, s.size]);
|
|
31475
|
+
};
|
|
31476
|
+
function changedPaths(value) {
|
|
31477
|
+
if (!value || typeof value !== "object") return [];
|
|
31478
|
+
const changes = Array.isArray(value) ? value : Object.entries(value).map(([path, change]) => ({ ...change && typeof change === "object" ? change : {}, path }));
|
|
31479
|
+
return changes.flatMap((change) => [change.path, change.move_path, change.kind?.move_path].filter((path) => typeof path === "string"));
|
|
31480
|
+
}
|
|
31481
|
+
function codexUserText(value) {
|
|
31482
|
+
if (typeof value !== "string") return "";
|
|
31483
|
+
let text2 = value.trim();
|
|
31484
|
+
if (/^# (?:AGENTS\.md|Instructions for)/i.test(text2)) return "";
|
|
31485
|
+
text2 = text2.replace(/<(environment_context|permissions|user_instructions|turn_aborted|external_codex_[\w-]+)>[\s\S]*?<\/\1>/g, "").trim();
|
|
31486
|
+
if (/^<(?:environment_context|permissions|user_instructions|turn_aborted|external_codex_[\w-]+)(?:>|\s)/.test(text2)) return "";
|
|
31487
|
+
return text2;
|
|
31488
|
+
}
|
|
31489
|
+
async function sessionNames() {
|
|
31490
|
+
const names = /* @__PURE__ */ new Map();
|
|
31491
|
+
let cursor, count = 0;
|
|
31492
|
+
try {
|
|
31493
|
+
do {
|
|
31494
|
+
const page = await readJSONL(join10(root2(), "session_index.jsonl"), cursor, 2e6, 1e3);
|
|
31495
|
+
for (const { value } of page.rows) if (typeof value.id === "string" && typeof value.thread_name === "string" && value.thread_name.trim()) names.set(value.id, value.thread_name.trim().slice(0, 300));
|
|
31496
|
+
cursor = page.cursor;
|
|
31497
|
+
count += page.rows.length;
|
|
31498
|
+
} while (cursor !== void 0 && count < 1e5);
|
|
31499
|
+
} catch (error) {
|
|
31500
|
+
if (error.code !== "ENOENT") throw error;
|
|
31501
|
+
}
|
|
31502
|
+
return names;
|
|
31503
|
+
}
|
|
31504
|
+
function codexRecord(raw, offset, candidate) {
|
|
31505
|
+
const p = raw.payload;
|
|
31506
|
+
if (!p || typeof p !== "object") return;
|
|
31507
|
+
if (raw.type === "turn_context" && typeof p.model === "string") candidate.session.model = p.model;
|
|
31508
|
+
if (raw.type === "event_msg" && p.type === "item_completed" && ["FileChange", "fileChange"].includes(p.item?.type)) {
|
|
31509
|
+
const msg2 = blankMessage(`event:${p.item.id || raw.ordinal || offset}`, p.completed_at_ms || Date.parse(raw.timestamp) || 0, "tool");
|
|
31510
|
+
msg2.events = [eventFor(msg2, "change", "event", "File-change result", eventText(p.item), { nativeType: p.type, status: ["completed", "Completed"].includes(p.item.status) ? "completed" : "unknown" })];
|
|
31511
|
+
if (["completed", "Completed"].includes(p.item.status)) for (const path of changedPaths(p.item.changes)) {
|
|
31512
|
+
msg2.files.push({ path, source: "file change", steps: 1 });
|
|
31513
|
+
addText(msg2, path, "File");
|
|
31514
|
+
}
|
|
31515
|
+
return msg2;
|
|
31516
|
+
}
|
|
31517
|
+
if (raw.type === "event_msg" && p.type === "patch_apply_end") {
|
|
31518
|
+
const msg2 = blankMessage(`event:${p.call_id || raw.ordinal || offset}`, Date.parse(raw.timestamp) || 0, "tool");
|
|
31519
|
+
msg2.events = [eventFor(msg2, "patch", "tool_result", "apply_patch", "", { callID: p.call_id, output: eventText(p), nativeType: p.type, status: p.success === true ? "completed" : p.success === false ? "failed" : "unknown" })];
|
|
31520
|
+
if (p.success === true) for (const [path, change] of Object.entries(p.changes || {})) {
|
|
31521
|
+
msg2.files.push({ path, source: "file change", steps: 1 });
|
|
31522
|
+
addText(msg2, path, "File");
|
|
31523
|
+
if (change?.move_path) {
|
|
31524
|
+
msg2.files.push({ path: change.move_path, source: "file change", steps: 1 });
|
|
31525
|
+
addText(msg2, change.move_path, "File");
|
|
31526
|
+
}
|
|
31527
|
+
}
|
|
31528
|
+
return msg2;
|
|
31529
|
+
}
|
|
31530
|
+
if (raw.type !== "response_item") {
|
|
31531
|
+
if (raw.type === "session_meta" || raw.type === "event_msg" && ["user_message", "agent_message", "agent_reasoning", "agent_reasoning_raw_content"].includes(p.type)) return;
|
|
31532
|
+
const msg2 = blankMessage(`event:${raw.ordinal ?? offset}`, Date.parse(raw.timestamp) || 0, "system");
|
|
31533
|
+
msg2.events = [eventFor(msg2, "event", /compact/.test(p.type || raw.type) ? "compaction" : p.type === "error" ? "error" : "event", p.type || raw.type, eventText(p), { nativeType: `${raw.type}:${p.type || ""}` })];
|
|
31534
|
+
return msg2;
|
|
31535
|
+
}
|
|
31536
|
+
const role = p.role === "user" ? "user" : p.role === "assistant" ? "assistant" : p.type === "message" ? "system" : "tool";
|
|
31537
|
+
const msg = blankMessage(`item:${raw.ordinal ?? offset}`, Date.parse(raw.timestamp) || 0, role);
|
|
31538
|
+
const fields2 = { nativeID: p.id, nativeType: p.type, callID: p.call_id };
|
|
31539
|
+
msg.events = p.type === "message" ? blockEvents(msg, p.content) : ["function_call", "custom_tool_call"].includes(p.type) ? [eventFor(msg, "call", "tool_call", p.name || "Tool call", "", { ...fields2, role: "assistant", input: eventText(p.arguments ?? p.input), status: "unknown" })] : ["function_call_output", "custom_tool_call_output"].includes(p.type) ? [eventFor(msg, "result", "tool_result", "Tool result", "", { ...fields2, output: eventText(p.output, 64e3), status: "unknown" })] : [eventFor(msg, "event", p.type === "reasoning" ? "reasoning" : "event", p.type === "reasoning" ? "Recorded reasoning summary" : p.type || "Unknown event", eventText(p.type === "reasoning" ? p.summary : p), { ...fields2, partial: p.type === "reasoning" })];
|
|
31540
|
+
if (role === "user") msg.events = msg.events.map((event) => event.kind === "message" && event.text && !codexUserText(event.text) ? { ...event, kind: "event", role: "system", name: "Session context", nativeType: "user-context" } : event);
|
|
31541
|
+
if (p.type === "message") {
|
|
31542
|
+
for (const part of Array.isArray(p.content) ? p.content : []) if (["input_text", "output_text", "text"].includes(part.type)) addText(msg, role === "user" ? codexUserText(part.text) : part.text, role === "user" ? "You" : "Assistant");
|
|
31543
|
+
} else if (["function_call", "custom_tool_call"].includes(p.type)) {
|
|
31544
|
+
msg.tools.push(p.name || "tool");
|
|
31545
|
+
addText(msg, p.name, "Tool");
|
|
31546
|
+
toolText(msg, p.arguments || p.input);
|
|
30634
31547
|
} else if (["function_call_output", "custom_tool_call_output"].includes(p.type)) toolText(msg, p.output);
|
|
30635
31548
|
else if (p.type === "file_change" || p.type === "fileChange") {
|
|
30636
31549
|
if (p.status === "completed") for (const path of changedPaths(p.changes)) {
|
|
@@ -30655,36 +31568,42 @@ var codexAdapter = {
|
|
|
30655
31568
|
async discover() {
|
|
30656
31569
|
const found = /* @__PURE__ */ new Map(), warnings = [];
|
|
30657
31570
|
const names = await sessionNames();
|
|
30658
|
-
const
|
|
31571
|
+
const match = (name) => /\.jsonl(?:\.[\w.-]+)?$/.test(name);
|
|
31572
|
+
const paths = [...await transcriptFiles(join10(root2(), "sessions"), 2e4, match), ...await transcriptFiles(join10(root2(), "archived_sessions"), 2e4, match)];
|
|
30659
31573
|
if (paths.length >= 2e4) warnings.push("Codex transcript listing may be partial (20,000 files per history folder).");
|
|
30660
31574
|
let unsupported = 0;
|
|
30661
31575
|
for (const path of paths) {
|
|
30662
|
-
|
|
30663
|
-
|
|
30664
|
-
|
|
30665
|
-
|
|
30666
|
-
|
|
30667
|
-
|
|
30668
|
-
|
|
30669
|
-
|
|
30670
|
-
|
|
30671
|
-
|
|
30672
|
-
|
|
30673
|
-
|
|
30674
|
-
|
|
30675
|
-
|
|
30676
|
-
const
|
|
30677
|
-
|
|
30678
|
-
|
|
30679
|
-
|
|
31576
|
+
try {
|
|
31577
|
+
const first = await readJSONL(path, 0, 2e6, 100);
|
|
31578
|
+
const meta = first.rows.find((r) => r.value.type === "session_meta")?.value.payload;
|
|
31579
|
+
if (!meta?.id || typeof meta.id !== "string") {
|
|
31580
|
+
unsupported++;
|
|
31581
|
+
continue;
|
|
31582
|
+
}
|
|
31583
|
+
const stats = await stat2(path), session = blankSession("codex", meta.id, meta.cwd);
|
|
31584
|
+
session.title = names.get(meta.id) || (typeof meta.name === "string" ? meta.name : "Untitled session");
|
|
31585
|
+
const source = meta.source?.subagent?.thread_spawn || meta.source?.sub_agent?.thread_spawn;
|
|
31586
|
+
const parent2 = meta.parent_thread_id || source?.parent_thread_id;
|
|
31587
|
+
const fork = meta.forked_from_id || meta.forkedFromId;
|
|
31588
|
+
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 });
|
|
31589
|
+
const candidate = { session, locator: { path }, revision: await revision(path) };
|
|
31590
|
+
for (const row of first.rows) {
|
|
31591
|
+
const msg = codexRecord(row.value, row.offset, candidate);
|
|
31592
|
+
if (session.title === "Untitled session" && msg?.role === "user") {
|
|
31593
|
+
const text2 = msg.fragments.find((f) => f.kind === "You")?.text;
|
|
31594
|
+
if (text2) session.title = text2.replace(/\s+/g, " ").slice(0, 180);
|
|
31595
|
+
}
|
|
30680
31596
|
}
|
|
31597
|
+
if (session.title === "Untitled session") session.title = "Codex session";
|
|
31598
|
+
const old = found.get(session.id);
|
|
31599
|
+
const rank = (c) => (c.session.archived ? 0 : 2) + Number(c.locator.path.endsWith(".jsonl"));
|
|
31600
|
+
if (!old || rank(candidate) > rank(old) || rank(candidate) === rank(old) && session.updated > old.session.updated) found.set(session.id, candidate);
|
|
31601
|
+
} catch {
|
|
31602
|
+
unsupported++;
|
|
30681
31603
|
}
|
|
30682
|
-
if (session.title === "Untitled session") session.title = "Codex session";
|
|
30683
|
-
const old = found.get(session.id);
|
|
30684
|
-
if (!old || session.updated > old.session.updated) found.set(session.id, candidate);
|
|
30685
31604
|
}
|
|
30686
|
-
if (unsupported) warnings.push(`${unsupported} Codex files
|
|
30687
|
-
warnings.push("Codex imports local JSONL rollouts. Cloud-only and store-only history are not imported.");
|
|
31605
|
+
if (unsupported) warnings.push(`${unsupported} Codex files were unreadable, oversized, or had no supported rollout metadata; saved history is retained.`);
|
|
31606
|
+
warnings.push("Codex imports local JSONL rollouts and bounded zstd/gzip archive copies. Cloud-only and store-only history are not imported.");
|
|
30688
31607
|
const sessions = [...found.values()];
|
|
30689
31608
|
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
30690
31609
|
const p = project("codex", c.session.directory);
|
|
@@ -30693,7 +31612,7 @@ var codexAdapter = {
|
|
|
30693
31612
|
},
|
|
30694
31613
|
async read(candidate, cursor) {
|
|
30695
31614
|
const page = await readJSONL(candidate.locator.path, Number(cursor || 0));
|
|
30696
|
-
if (page.cursor === void 0 && await
|
|
31615
|
+
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
31616
|
return { messages: page.rows.flatMap((r) => {
|
|
30698
31617
|
const msg = codexRecord(r.value, r.offset, candidate);
|
|
30699
31618
|
return msg ? [msg] : [];
|
|
@@ -30703,10 +31622,10 @@ var codexAdapter = {
|
|
|
30703
31622
|
|
|
30704
31623
|
// server/adapters/cursor.ts
|
|
30705
31624
|
import { createHash as createHash3 } from "node:crypto";
|
|
30706
|
-
import { lstat, opendir, readFile as readFile3 } from "node:fs/promises";
|
|
30707
|
-
import { homedir as
|
|
30708
|
-
import { isAbsolute as
|
|
30709
|
-
import { fileURLToPath } from "node:url";
|
|
31625
|
+
import { lstat as lstat4, opendir, readFile as readFile3 } from "node:fs/promises";
|
|
31626
|
+
import { homedir as homedir6 } from "node:os";
|
|
31627
|
+
import { isAbsolute as isAbsolute3, join as join12 } from "node:path";
|
|
31628
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
30710
31629
|
|
|
30711
31630
|
// server/adapters/cursor-message.ts
|
|
30712
31631
|
function cursorTime(value) {
|
|
@@ -30780,19 +31699,19 @@ function cursorMessage(raw, fallbackID) {
|
|
|
30780
31699
|
}
|
|
30781
31700
|
|
|
30782
31701
|
// server/adapters/cursor-store.ts
|
|
30783
|
-
import { DatabaseSync } from "node:sqlite";
|
|
30784
|
-
import { chmodSync, copyFileSync, lstatSync as
|
|
30785
|
-
import { tmpdir } from "node:os";
|
|
30786
|
-
import { join as
|
|
31702
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31703
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, lstatSync as lstatSync3, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "node:fs";
|
|
31704
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
31705
|
+
import { join as join11 } from "node:path";
|
|
30787
31706
|
var MAX_VALUE = 2e6;
|
|
30788
|
-
var
|
|
31707
|
+
var MAX_BYTES3 = 64e6;
|
|
30789
31708
|
var MAX_NODES = 5e4;
|
|
30790
31709
|
var MAX_TEXT = 16e6;
|
|
30791
31710
|
function withStore(path, read3) {
|
|
30792
31711
|
const files = [path, `${path}-wal`];
|
|
30793
31712
|
const stats = () => files.map((file) => {
|
|
30794
31713
|
try {
|
|
30795
|
-
const s =
|
|
31714
|
+
const s = lstatSync3(file);
|
|
30796
31715
|
if (!s.isFile() || s.size > 256e6) throw new Error("Cursor store must be a regular file below 256 MB (including each WAL).");
|
|
30797
31716
|
return [s.ino, s.size, s.mtimeMs, s.ctimeMs];
|
|
30798
31717
|
} catch (error) {
|
|
@@ -30800,27 +31719,27 @@ function withStore(path, read3) {
|
|
|
30800
31719
|
throw error;
|
|
30801
31720
|
}
|
|
30802
31721
|
});
|
|
30803
|
-
const before = stats(),
|
|
31722
|
+
const before = stats(), directory2 = mkdtempSync2(join11(tmpdir2(), "sessiongraph-cursor-"));
|
|
30804
31723
|
let db2;
|
|
30805
31724
|
try {
|
|
30806
|
-
|
|
30807
|
-
const copy =
|
|
31725
|
+
chmodSync2(directory2, 448);
|
|
31726
|
+
const copy = join11(directory2, "store.db");
|
|
30808
31727
|
files.forEach((file, index) => {
|
|
30809
31728
|
if (before[index]) {
|
|
30810
31729
|
const target = index ? `${copy}-wal` : copy;
|
|
30811
|
-
|
|
30812
|
-
|
|
31730
|
+
copyFileSync2(file, target);
|
|
31731
|
+
chmodSync2(target, 384);
|
|
30813
31732
|
}
|
|
30814
31733
|
});
|
|
30815
31734
|
if (JSON.stringify(stats()) !== JSON.stringify(before)) throw new Error("Cursor store changed while copying; re-import to retry.");
|
|
30816
|
-
db2 = new
|
|
31735
|
+
db2 = new DatabaseSync2(copy, { readOnly: true, enableDoubleQuotedStringLiterals: false, allowExtension: false });
|
|
30817
31736
|
db2.exec("PRAGMA query_only=ON; PRAGMA trusted_schema=OFF; PRAGMA busy_timeout=1000; BEGIN;");
|
|
30818
31737
|
return read3(db2);
|
|
30819
31738
|
} finally {
|
|
30820
31739
|
try {
|
|
30821
31740
|
db2?.close();
|
|
30822
31741
|
} finally {
|
|
30823
|
-
|
|
31742
|
+
rmSync3(directory2, { recursive: true, force: true });
|
|
30824
31743
|
}
|
|
30825
31744
|
}
|
|
30826
31745
|
}
|
|
@@ -30872,10 +31791,10 @@ function readCursorStore(path) {
|
|
|
30872
31791
|
if (typeof root3 !== "string" || !/^[a-f\d]{64}$/i.test(root3)) throw new Error("Unsupported Cursor conversation root.");
|
|
30873
31792
|
const get = db2.prepare("SELECT data FROM blobs WHERE id=? AND length(data)<=?");
|
|
30874
31793
|
const messages = [];
|
|
30875
|
-
let
|
|
31794
|
+
let bytes2 = 0, nodes = 0, text2 = 0, partial = false, ordinal = 0;
|
|
30876
31795
|
const stack = [{ id: root3, leaf: false, ancestors: [] }];
|
|
30877
31796
|
while (stack.length) {
|
|
30878
|
-
if (++nodes > MAX_NODES ||
|
|
31797
|
+
if (++nodes > MAX_NODES || bytes2 >= MAX_BYTES3 || text2 >= MAX_TEXT) {
|
|
30879
31798
|
partial = true;
|
|
30880
31799
|
break;
|
|
30881
31800
|
}
|
|
@@ -30884,13 +31803,13 @@ function readCursorStore(path) {
|
|
|
30884
31803
|
partial = true;
|
|
30885
31804
|
continue;
|
|
30886
31805
|
}
|
|
30887
|
-
const row = get.get(item.id, Math.min(MAX_VALUE,
|
|
31806
|
+
const row = get.get(item.id, Math.min(MAX_VALUE, MAX_BYTES3 - bytes2));
|
|
30888
31807
|
if (!(row?.data instanceof Uint8Array)) {
|
|
30889
31808
|
partial = true;
|
|
30890
31809
|
continue;
|
|
30891
31810
|
}
|
|
30892
31811
|
const data = Buffer.from(row.data);
|
|
30893
|
-
|
|
31812
|
+
bytes2 += data.length;
|
|
30894
31813
|
try {
|
|
30895
31814
|
if (item.leaf) {
|
|
30896
31815
|
const json4 = data[0] === 123 ? data : fields(data).find((v3) => v3.field === 4)?.data;
|
|
@@ -30940,16 +31859,126 @@ function readCursorStore(path) {
|
|
|
30940
31859
|
});
|
|
30941
31860
|
}
|
|
30942
31861
|
|
|
31862
|
+
// server/adapters/cursor-composer.ts
|
|
31863
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
31864
|
+
import { fileURLToPath } from "node:url";
|
|
31865
|
+
var MAX_VALUE2 = 32e6;
|
|
31866
|
+
var MAX_BUBBLE = 8e6;
|
|
31867
|
+
var MAX_MESSAGES = 5e4;
|
|
31868
|
+
var MAX_BYTES4 = 64e6;
|
|
31869
|
+
function decode(value) {
|
|
31870
|
+
return object(JSON.parse(typeof value === "string" ? value : value instanceof Uint8Array ? Buffer.from(value).toString("utf8") : "{}"));
|
|
31871
|
+
}
|
|
31872
|
+
function directory(raw) {
|
|
31873
|
+
const value = text(raw.workspaceIdentifier?.uri?.path) || text(raw.workspaceIdentifier?.uri) || text(raw.cwd);
|
|
31874
|
+
try {
|
|
31875
|
+
return value.startsWith("file:") ? fileURLToPath(value) : isAbsolute2(value) ? value : "";
|
|
31876
|
+
} catch {
|
|
31877
|
+
return "";
|
|
31878
|
+
}
|
|
31879
|
+
}
|
|
31880
|
+
function discoverComposers(path, workspace = "") {
|
|
31881
|
+
const snapshot2 = sqliteRevision(path);
|
|
31882
|
+
return withSqliteSnapshot(path, (db2) => {
|
|
31883
|
+
const sessions = [], warnings = [];
|
|
31884
|
+
for (const table of ["cursorDiskKV", "ItemTable"]) {
|
|
31885
|
+
if (!hasTable(db2, table)) continue;
|
|
31886
|
+
let failed = 0, count = 0;
|
|
31887
|
+
const rows2 = db2.prepare(`SELECT key, CASE WHEN length(value)<=? THEN value END AS value FROM ${table} WHERE key LIKE 'composerData:%' ORDER BY key LIMIT 20001`).iterate(MAX_VALUE2);
|
|
31888
|
+
for (const row of rows2) {
|
|
31889
|
+
if (++count > 2e4) {
|
|
31890
|
+
warnings.push("Cursor Composer listing reached its 20,000-session limit.");
|
|
31891
|
+
break;
|
|
31892
|
+
}
|
|
31893
|
+
try {
|
|
31894
|
+
if (row.value == null) throw new Error("Oversized Composer.");
|
|
31895
|
+
const data = decode(row.value), nativeID = text(data.composerId);
|
|
31896
|
+
if (!nativeID || row.key !== `composerData:${nativeID}`) throw new Error("Composer identity mismatch.");
|
|
31897
|
+
const headers = Array.isArray(data.fullConversationHeadersOnly) ? data.fullConversationHeadersOnly : Array.isArray(data.conversation) ? data.conversation : [];
|
|
31898
|
+
if (!headers.length) continue;
|
|
31899
|
+
const session = blankSession("cursor", nativeID, directory(data) || workspace);
|
|
31900
|
+
session.created = cursorTime(data.createdAt);
|
|
31901
|
+
if (!session.created) continue;
|
|
31902
|
+
session.updated = cursorTime(data.lastUpdatedAt) || session.created;
|
|
31903
|
+
session.title = text(data.name).slice(0, 300) || "Cursor Composer session";
|
|
31904
|
+
session.agent = "Cursor Composer";
|
|
31905
|
+
session.model = text(data.modelConfig?.modelName) || "Unknown model";
|
|
31906
|
+
session.archived = data.isArchived === true;
|
|
31907
|
+
const children = Array.isArray(data.subagentComposerIds) ? data.subagentComposerIds.filter((id2) => typeof id2 === "string") : [];
|
|
31908
|
+
sessions.push({ session, revision: digest([1, snapshot2, table, nativeID]), locator: { path, kind: "composer", table, snapshot: snapshot2, nativeID, children: JSON.stringify(children) } });
|
|
31909
|
+
} catch {
|
|
31910
|
+
failed++;
|
|
31911
|
+
}
|
|
31912
|
+
}
|
|
31913
|
+
if (failed) warnings.push(`${failed} legacy Cursor Composer records were unreadable or oversized; saved imports are retained.`);
|
|
31914
|
+
}
|
|
31915
|
+
const parents = /* @__PURE__ */ new Map();
|
|
31916
|
+
for (const c of sessions) for (const id2 of JSON.parse(c.locator.children)) {
|
|
31917
|
+
const found = parents.get(id2) || /* @__PURE__ */ new Set();
|
|
31918
|
+
found.add(c.session.nativeID);
|
|
31919
|
+
parents.set(id2, found);
|
|
31920
|
+
}
|
|
31921
|
+
for (const c of sessions) {
|
|
31922
|
+
const found = parents.get(c.session.nativeID);
|
|
31923
|
+
if (found?.size === 1 && !found.has(c.session.nativeID)) c.session.parentID = identity("cursor", [...found][0]);
|
|
31924
|
+
c.session.projectID = project("cursor", c.session.directory).id;
|
|
31925
|
+
c.revision = digest([c.revision, c.session.directory, c.session.parentID]);
|
|
31926
|
+
}
|
|
31927
|
+
return { sessions, warnings };
|
|
31928
|
+
}, snapshot2);
|
|
31929
|
+
}
|
|
31930
|
+
function readComposer(candidate) {
|
|
31931
|
+
const { path, table, nativeID, snapshot: snapshot2 } = candidate.locator;
|
|
31932
|
+
if (!["cursorDiskKV", "ItemTable"].includes(table)) throw new Error("Unsupported Composer table.");
|
|
31933
|
+
return withSqliteSnapshot(path, (db2) => {
|
|
31934
|
+
const get = db2.prepare(`SELECT value FROM ${table} WHERE key=? AND length(value)<=?`);
|
|
31935
|
+
const row = get.get(`composerData:${nativeID}`, MAX_VALUE2);
|
|
31936
|
+
if (!row) throw new Error("Composer history is unavailable.");
|
|
31937
|
+
const data = decode(row.value), messages = [];
|
|
31938
|
+
const records = Array.isArray(data.fullConversationHeadersOnly) ? data.fullConversationHeadersOnly : data.conversation;
|
|
31939
|
+
if (!Array.isArray(records)) throw new Error("Unsupported Composer history.");
|
|
31940
|
+
let bytes2 = 0, partial = false;
|
|
31941
|
+
for (const [index, header] of records.entries()) {
|
|
31942
|
+
if (index >= MAX_MESSAGES || bytes2 >= MAX_BYTES4) {
|
|
31943
|
+
partial = true;
|
|
31944
|
+
break;
|
|
31945
|
+
}
|
|
31946
|
+
try {
|
|
31947
|
+
const id2 = text(header.bubbleId);
|
|
31948
|
+
const stored = id2 ? get.get(`bubbleId:${nativeID}:${id2}`, MAX_BUBBLE) : void 0;
|
|
31949
|
+
if (stored) bytes2 += typeof stored.value === "string" ? Buffer.byteLength(stored.value) : stored.value.byteLength;
|
|
31950
|
+
const raw = stored ? { ...header, ...decode(stored.value) } : { ...header, ...object(data.conversationMap?.[id2]) };
|
|
31951
|
+
if (![1, 2].includes(raw.type)) {
|
|
31952
|
+
partial = true;
|
|
31953
|
+
continue;
|
|
31954
|
+
}
|
|
31955
|
+
if (typeof raw.text !== "string") {
|
|
31956
|
+
partial = true;
|
|
31957
|
+
continue;
|
|
31958
|
+
}
|
|
31959
|
+
const content = [{ type: "text", text: raw.text }];
|
|
31960
|
+
if (typeof raw.thinking?.text === "string") content.push({ type: "thinking", thinking: raw.thinking.text });
|
|
31961
|
+
const message2 = cursorMessage({ id: id2 || `bubble:${index}`, role: raw.type === 1 ? "user" : "assistant", timestamp: cursorTime(raw.createdAt) || cursorTime(raw.timingInfo?.clientStartTime), content }, `bubble:${index}`);
|
|
31962
|
+
if (message2) messages.push(message2);
|
|
31963
|
+
} catch {
|
|
31964
|
+
partial = true;
|
|
31965
|
+
}
|
|
31966
|
+
}
|
|
31967
|
+
if (!messages.length) throw new Error("Composer has no readable message bodies; saved imports are retained.");
|
|
31968
|
+
return { messages, partial: true, note: `Legacy Cursor Composer conversation order and native bubble IDs. Original creation dates are preserved; absent message timestamps remain unknown. Tool internals, attachments, encrypted bodies, and file snapshots are unavailable.${partial ? " Some missing, unsupported, or bounded bubbles were omitted." : ""}` };
|
|
31969
|
+
}, snapshot2);
|
|
31970
|
+
}
|
|
31971
|
+
|
|
30943
31972
|
// server/adapters/cursor.ts
|
|
30944
31973
|
var LIMIT = 2e4;
|
|
30945
31974
|
var validID = (id2) => /^[a-z\d][\w-]{0,159}$/i.test(id2);
|
|
30946
|
-
var cwd = (value) => typeof value === "string" &&
|
|
31975
|
+
var cwd = (value) => typeof value === "string" && isAbsolute3(value) && !/[\x00-\x1f\x7f]/.test(value) ? value : "";
|
|
30947
31976
|
var slug = (path) => path.replace(/\\/g, "/").replace(/^([A-Z]):/i, (_, drive) => drive.toLowerCase()).replace(/^\/+|\/+$/g, "").replace(/[/:]+/g, "-");
|
|
30948
31977
|
var hashCwd = (path) => createHash3("md5").update(path).digest("hex");
|
|
30949
|
-
var userData = () => process.platform === "darwin" ?
|
|
31978
|
+
var userData = () => process.platform === "darwin" ? join12(homedir6(), "Library/Application Support/Cursor") : process.platform === "win32" ? join12(process.env.APPDATA || join12(homedir6(), "AppData/Roaming"), "Cursor") : join12(process.env.XDG_CONFIG_HOME || join12(homedir6(), ".config"), "Cursor");
|
|
30950
31979
|
async function fileStats(path) {
|
|
30951
31980
|
try {
|
|
30952
|
-
const s = await
|
|
31981
|
+
const s = await lstat4(path);
|
|
30953
31982
|
if (s.isSymbolicLink()) throw new Error("Cursor history symlinks are not followed.");
|
|
30954
31983
|
return s.isFile() ? s : void 0;
|
|
30955
31984
|
} catch (error) {
|
|
@@ -30964,7 +31993,7 @@ async function stamp(path) {
|
|
|
30964
31993
|
async function snapshot(locator) {
|
|
30965
31994
|
return digest(await Promise.all([stamp(locator.path), locator.kind === "transcript" ? null : stamp(`${locator.path}-wal`), locator.meta ? stamp(locator.meta) : null]));
|
|
30966
31995
|
}
|
|
30967
|
-
async function
|
|
31996
|
+
async function json3(path) {
|
|
30968
31997
|
const s = await fileStats(path);
|
|
30969
31998
|
if (!s) return {};
|
|
30970
31999
|
if (s.size > 128e3) throw new Error("Cursor metadata exceeds the 128 KB limit.");
|
|
@@ -30974,7 +32003,7 @@ async function json2(path) {
|
|
|
30974
32003
|
async function entries(path, warnings) {
|
|
30975
32004
|
const result = [];
|
|
30976
32005
|
try {
|
|
30977
|
-
if (!(await
|
|
32006
|
+
if (!(await lstat4(path)).isDirectory()) return result;
|
|
30978
32007
|
for await (const entry of await opendir(path)) {
|
|
30979
32008
|
if (!entry.isSymbolicLink()) result.push(entry);
|
|
30980
32009
|
if (result.length === LIMIT) {
|
|
@@ -30988,32 +32017,39 @@ async function entries(path, warnings) {
|
|
|
30988
32017
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
30989
32018
|
}
|
|
30990
32019
|
async function workspacePaths(root3, warnings) {
|
|
30991
|
-
const
|
|
30992
|
-
const storage =
|
|
32020
|
+
const stores = /* @__PURE__ */ new Map();
|
|
32021
|
+
const storage = join12(root3, "User/workspaceStorage");
|
|
30993
32022
|
for (const entry of await entries(storage, warnings)) if (entry.isDirectory()) {
|
|
32023
|
+
const database = join12(storage, entry.name, "state.vscdb");
|
|
32024
|
+
stores.set(database, "");
|
|
30994
32025
|
try {
|
|
30995
|
-
const info = await
|
|
32026
|
+
const info = await json3(join12(storage, entry.name, "workspace.json"));
|
|
30996
32027
|
if (typeof info.folder === "string" && info.folder.startsWith("file:")) {
|
|
30997
|
-
const path = cwd(
|
|
30998
|
-
if (path)
|
|
32028
|
+
const path = cwd(fileURLToPath2(info.folder));
|
|
32029
|
+
if (path) stores.set(database, path);
|
|
30999
32030
|
}
|
|
31000
32031
|
} catch {
|
|
31001
32032
|
warnings.push("A Cursor workspace mapping could not be read; its directory may be unknown.");
|
|
31002
32033
|
}
|
|
31003
32034
|
}
|
|
31004
|
-
return
|
|
32035
|
+
return stores;
|
|
31005
32036
|
}
|
|
31006
32037
|
function createCursorAdapter(options = {}) {
|
|
31007
|
-
const root3 = () => options.home || process.env.SESSIONGRAPH_CURSOR_HOME || process.env.SESSION_ATLAS_CURSOR_HOME ||
|
|
32038
|
+
const root3 = () => options.home || process.env.SESSIONGRAPH_CURSOR_HOME || process.env.SESSION_ATLAS_CURSOR_HOME || join12(homedir6(), ".cursor");
|
|
32039
|
+
const editor = () => options.userData || process.env.SESSIONGRAPH_CURSOR_USER_DATA_DIR || process.env.SESSION_ATLAS_CURSOR_USER_DATA_DIR || userData();
|
|
31008
32040
|
return {
|
|
31009
32041
|
id: "cursor",
|
|
31010
32042
|
name: "Cursor",
|
|
31011
|
-
description: "Local IDE
|
|
32043
|
+
description: "Local IDE transcripts, legacy Composer, subagents, and CLI / ACP stores \xB7 read-only",
|
|
31012
32044
|
capabilities: { history: true, forks: false, children: true, files: false },
|
|
31013
32045
|
async detect() {
|
|
32046
|
+
if (await fileStats(join12(editor(), "User/globalStorage/state.vscdb"))) return true;
|
|
32047
|
+
for (const entry of await entries(join12(editor(), "User/workspaceStorage"), [])) {
|
|
32048
|
+
if (entry.isDirectory() && await fileStats(join12(editor(), "User/workspaceStorage", entry.name, "state.vscdb"))) return true;
|
|
32049
|
+
}
|
|
31014
32050
|
for (const name of ["projects", "chats", "acp-sessions"]) {
|
|
31015
32051
|
try {
|
|
31016
|
-
if ((await
|
|
32052
|
+
if ((await lstat4(join12(root3(), name))).isDirectory()) return true;
|
|
31017
32053
|
} catch {
|
|
31018
32054
|
}
|
|
31019
32055
|
}
|
|
@@ -31021,40 +32057,60 @@ function createCursorAdapter(options = {}) {
|
|
|
31021
32057
|
},
|
|
31022
32058
|
async discover() {
|
|
31023
32059
|
const warnings = [], found = /* @__PURE__ */ new Map(), blocked = /* @__PURE__ */ new Set();
|
|
31024
|
-
const
|
|
32060
|
+
const workspaceStores = await workspacePaths(editor(), warnings);
|
|
32061
|
+
const paths = [...new Set([...workspaceStores.values()].filter(Boolean))];
|
|
32062
|
+
const legacyStores = new Map([[join12(editor(), "User/globalStorage/state.vscdb"), ""], ...workspaceStores]);
|
|
32063
|
+
for (const [path, workspace] of legacyStores) {
|
|
32064
|
+
try {
|
|
32065
|
+
if (!await fileStats(path)) continue;
|
|
32066
|
+
const result = discoverComposers(path, workspace);
|
|
32067
|
+
warnings.push(...result.warnings);
|
|
32068
|
+
for (const candidate of result.sessions) {
|
|
32069
|
+
const previous = found.get(candidate.session.id);
|
|
32070
|
+
const selected = !previous || candidate.session.updated > previous.session.updated ? candidate : previous;
|
|
32071
|
+
selected.session.directory ||= candidate.session.directory || previous?.session.directory || "";
|
|
32072
|
+
selected.session.projectID = project("cursor", selected.session.directory).id;
|
|
32073
|
+
selected.revision = digest([selected.revision, selected.session.directory]);
|
|
32074
|
+
found.set(candidate.session.id, selected);
|
|
32075
|
+
}
|
|
32076
|
+
} catch {
|
|
32077
|
+
warnings.push("A legacy Cursor database could not be read consistently; saved imports are retained.");
|
|
32078
|
+
}
|
|
32079
|
+
}
|
|
32080
|
+
const composers = new Map(found);
|
|
31025
32081
|
const match = (key, encode) => {
|
|
31026
32082
|
const matches = paths.filter((path) => encode(path) === key);
|
|
31027
32083
|
return matches.length === 1 ? matches[0] : "";
|
|
31028
32084
|
};
|
|
31029
32085
|
let unreadable = 0, legacy = 0, scanned = 0;
|
|
31030
|
-
const add = async (path, id2, kind,
|
|
32086
|
+
const add = async (path, id2, kind, directory2, parent2 = "", workspaceHash = "") => {
|
|
31031
32087
|
if (!validID(id2) || ++scanned > LIMIT) return;
|
|
31032
32088
|
try {
|
|
31033
32089
|
const stats = await fileStats(path);
|
|
31034
32090
|
if (!stats) return;
|
|
31035
32091
|
const native = parent2 ? `${parent2}/agent/${id2}` : id2;
|
|
31036
32092
|
const locator = { path, kind };
|
|
31037
|
-
if (kind !== "transcript") locator.meta =
|
|
32093
|
+
if (kind !== "transcript") locator.meta = join12(path, "..", "meta.json");
|
|
31038
32094
|
const before = await snapshot(locator);
|
|
31039
|
-
const
|
|
32095
|
+
const extra2 = locator.meta ? await json3(locator.meta) : {};
|
|
31040
32096
|
const meta = kind === "transcript" ? {} : cursorStoreMetadata(path);
|
|
31041
32097
|
if (meta.agentId && meta.agentId !== id2) throw new Error("Cursor native identity mismatch.");
|
|
31042
32098
|
if (kind !== "transcript" && !meta.latestRootBlobId) return;
|
|
31043
|
-
const session = blankSession("cursor", native, cwd(meta.cwd) || cwd(
|
|
32099
|
+
const session = blankSession("cursor", native, cwd(meta.cwd) || cwd(extra2.cwd) || directory2);
|
|
31044
32100
|
session.agent = kind === "cli" ? "Cursor Agent" : kind === "acp" ? "Cursor ACP" : parent2 ? "Cursor subagent" : "Cursor IDE";
|
|
31045
32101
|
if (parent2) session.parentID = identity("cursor", parent2);
|
|
31046
|
-
session.title = typeof meta.name === "string" && meta.name.trim() ? meta.name : typeof
|
|
32102
|
+
session.title = typeof meta.name === "string" && meta.name.trim() ? meta.name : typeof extra2.title === "string" && extra2.title.trim() ? extra2.title : "Cursor session";
|
|
31047
32103
|
session.title = session.title.slice(0, 300);
|
|
31048
|
-
session.created = cursorTime(meta.createdAt) || cursorTime(
|
|
31049
|
-
session.updated = cursorTime(
|
|
32104
|
+
session.created = cursorTime(meta.createdAt) || cursorTime(extra2.createdAtMs);
|
|
32105
|
+
session.updated = cursorTime(extra2.updatedAtMs) || stats.mtimeMs;
|
|
31050
32106
|
if (kind === "transcript") {
|
|
31051
32107
|
const page = await readJSONL(path, 0, 2e6, 100);
|
|
31052
32108
|
let first = 0;
|
|
31053
32109
|
for (const row of page.rows) {
|
|
31054
|
-
const
|
|
31055
|
-
if (
|
|
31056
|
-
if (session.title === "Cursor session" &&
|
|
31057
|
-
const text2 =
|
|
32110
|
+
const message2 = cursorMessage(row.value, `offset:${row.offset}`);
|
|
32111
|
+
if (message2?.time && (!first || message2.time < first)) first = message2.time;
|
|
32112
|
+
if (session.title === "Cursor session" && message2?.role === "user") {
|
|
32113
|
+
const text2 = message2.fragments.find((f) => f.kind === "You")?.text;
|
|
31058
32114
|
if (text2) session.title = text2.replace(/\s+/g, " ").slice(0, 180);
|
|
31059
32115
|
}
|
|
31060
32116
|
if (!session.directory) session.directory = cwd(row.value?.cwd);
|
|
@@ -31073,363 +32129,119 @@ function createCursorAdapter(options = {}) {
|
|
|
31073
32129
|
}
|
|
31074
32130
|
if (!session.directory) locator.directoryNote = "Working directory is unknown; choose one when continuing.";
|
|
31075
32131
|
session.projectID = project("cursor", session.directory).id;
|
|
31076
|
-
session.nativeResume = kind === "cli" && Boolean(session.directory) && hashCwd(session.directory) === workspaceHash && root3() ===
|
|
32132
|
+
session.nativeResume = kind === "cli" && Boolean(session.directory) && hashCwd(session.directory) === workspaceHash && root3() === join12(homedir6(), ".cursor");
|
|
31077
32133
|
locator.snapshot = before;
|
|
31078
32134
|
if (await snapshot(locator) !== before) throw new Error("Cursor session changed during discovery.");
|
|
31079
|
-
const candidate = { session, locator, revision: digest([2, kind, path, before, session.directory, session.parentID]) };
|
|
31080
32135
|
const previous = found.get(session.id);
|
|
31081
|
-
const rank = (c) => c.locator.kind === "
|
|
32136
|
+
const rank = (c) => c.locator.kind === "composer" ? 0 : c.locator.kind === "transcript" ? 1 : 2;
|
|
32137
|
+
const composer = composers.get(session.id);
|
|
32138
|
+
if (composer) {
|
|
32139
|
+
session.created = composer.session.created;
|
|
32140
|
+
delete locator.timeNote;
|
|
32141
|
+
session.parentID ||= composer.session.parentID;
|
|
32142
|
+
session.directory ||= composer.session.directory;
|
|
32143
|
+
session.projectID = project("cursor", session.directory).id;
|
|
32144
|
+
if (session.directory) delete locator.directoryNote;
|
|
32145
|
+
}
|
|
32146
|
+
const candidate = { session, locator, revision: digest([4, kind, path, before, session.created, session.directory, session.parentID]) };
|
|
31082
32147
|
if (!previous || rank(candidate) > rank(previous) || rank(candidate) === rank(previous) && session.updated > previous.session.updated) found.set(session.id, candidate);
|
|
31083
32148
|
} catch {
|
|
31084
32149
|
unreadable++;
|
|
31085
32150
|
if (kind !== "transcript") blocked.add(identity("cursor", id2));
|
|
31086
32151
|
}
|
|
31087
32152
|
};
|
|
31088
|
-
const projects =
|
|
32153
|
+
const projects = join12(root3(), "projects");
|
|
31089
32154
|
for (const dir of await entries(projects, warnings)) {
|
|
31090
32155
|
if (!dir.isDirectory() || scanned >= LIMIT) continue;
|
|
31091
|
-
const transcripts =
|
|
32156
|
+
const transcripts = join12(projects, dir.name, "agent-transcripts"), directory2 = match(dir.name, slug);
|
|
31092
32157
|
for (const entry of await entries(transcripts, warnings)) {
|
|
31093
32158
|
if (scanned >= LIMIT) break;
|
|
31094
|
-
if (entry.isFile() && entry.name.endsWith(".jsonl")) await add(
|
|
32159
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) await add(join12(transcripts, entry.name), entry.name.slice(0, -6), "transcript", directory2);
|
|
31095
32160
|
else if (entry.isFile() && entry.name.endsWith(".txt")) legacy++;
|
|
31096
32161
|
else if (entry.isDirectory() && validID(entry.name)) {
|
|
31097
|
-
await add(
|
|
31098
|
-
for (const child of await entries(
|
|
31099
|
-
if (child.isFile() && child.name.endsWith(".jsonl")) await add(
|
|
31100
|
-
if (scanned >= LIMIT) break;
|
|
31101
|
-
}
|
|
31102
|
-
}
|
|
31103
|
-
}
|
|
31104
|
-
}
|
|
31105
|
-
const chats = join8(root3(), "chats");
|
|
31106
|
-
for (const bucket of await entries(chats, warnings)) {
|
|
31107
|
-
if (!bucket.isDirectory() || scanned >= LIMIT) continue;
|
|
31108
|
-
const folder = join8(chats, bucket.name), directory = match(bucket.name, hashCwd);
|
|
31109
|
-
if (/^[a-f\d]{32}$/i.test(bucket.name) && await fileStats(join8(folder, "store.db"))) {
|
|
31110
|
-
const id2 = bucket.name.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
|
31111
|
-
await add(join8(folder, "store.db"), id2, "cli", "");
|
|
31112
|
-
}
|
|
31113
|
-
for (const entry of await entries(folder, warnings)) {
|
|
31114
|
-
if (entry.isDirectory()) await add(join8(folder, entry.name, "store.db"), entry.name, "cli", directory, "", bucket.name);
|
|
31115
|
-
if (scanned >= LIMIT) break;
|
|
31116
|
-
}
|
|
31117
|
-
}
|
|
31118
|
-
const acp = join8(root3(), "acp-sessions");
|
|
31119
|
-
for (const entry of await entries(acp, warnings)) {
|
|
31120
|
-
if (entry.isDirectory()) await add(join8(acp, entry.name, "store.db"), entry.name, "acp", "");
|
|
31121
|
-
if (scanned >= LIMIT) break;
|
|
31122
|
-
}
|
|
31123
|
-
if (scanned >= LIMIT) warnings.push("Cursor discovery limited to 20,000 history files.");
|
|
31124
|
-
if (unreadable) warnings.push(`${unreadable} Cursor histories were unreadable or changed during discovery; saved imports are retained. Re-import to retry.`);
|
|
31125
|
-
if (legacy) warnings.push(`${legacy} older plain-text Cursor transcripts are not supported.`);
|
|
31126
|
-
warnings.push("Cursor imports local IDE JSONL and CLI/ACP stores. Legacy Composer databases, plain-text exports, cloud-only and SDK-only history are not imported. Formats are not a stable public API.");
|
|
31127
|
-
const sessions = [...found.values()].filter((c) => !blocked.has(c.session.id));
|
|
31128
|
-
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
31129
|
-
const p = project("cursor", c.session.directory);
|
|
31130
|
-
return [p.id, p];
|
|
31131
|
-
})).values()], warnings: [...new Set(warnings)] };
|
|
31132
|
-
},
|
|
31133
|
-
async read(candidate, cursor) {
|
|
31134
|
-
const { locator } = candidate;
|
|
31135
|
-
const verify = async () => {
|
|
31136
|
-
if (await snapshot(locator) !== locator.snapshot) throw new Error("Cursor session changed during import; re-import to capture the latest history.");
|
|
31137
|
-
};
|
|
31138
|
-
await verify();
|
|
31139
|
-
const note = [
|
|
31140
|
-
locator.kind === "transcript" ? "Cursor IDE JSONL export; tool results and pre-compaction history may be absent." : "Cursor committed conversation root; abandoned branches and system prompts are excluded.",
|
|
31141
|
-
"No file snapshots or inferred edit evidence. Missing message timestamps stay unknown.",
|
|
31142
|
-
locator.timeNote,
|
|
31143
|
-
locator.directoryNote
|
|
31144
|
-
].filter(Boolean).join(" ");
|
|
31145
|
-
if (locator.kind !== "transcript") {
|
|
31146
|
-
if (cursor) throw new Error("Invalid Cursor store cursor.");
|
|
31147
|
-
const result = readCursorStore(locator.path);
|
|
31148
|
-
await verify();
|
|
31149
|
-
return { messages: result.messages, partial: result.partial, note: `${note}${result.partial ? " Unsupported, missing, or bounded store records were omitted (2 MB per blob, 64 MB / 50,000 nodes / 16 million text characters per session)." : ""}` };
|
|
31150
|
-
}
|
|
31151
|
-
const offset = Number(cursor || 0);
|
|
31152
|
-
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error("Invalid Cursor transcript cursor.");
|
|
31153
|
-
const page = await readJSONL(locator.path, offset);
|
|
31154
|
-
const messages = page.rows.flatMap((row) => {
|
|
31155
|
-
const message = cursorMessage(row.value, `offset:${row.offset}`);
|
|
31156
|
-
if (!message) page.partial = true;
|
|
31157
|
-
return message && message.role !== "system" ? [message] : [];
|
|
31158
|
-
});
|
|
31159
|
-
await verify();
|
|
31160
|
-
if (!offset && page.cursor === void 0 && !messages.length) throw new Error("Cursor transcript has no readable conversation. Previous imports are retained.");
|
|
31161
|
-
return { messages, cursor: page.cursor === void 0 ? void 0 : String(page.cursor), partial: true, note: `${note}${page.partial ? " Malformed or unsupported transcript records were skipped." : ""}` };
|
|
31162
|
-
}
|
|
31163
|
-
};
|
|
31164
|
-
}
|
|
31165
|
-
var cursorAdapter = createCursorAdapter();
|
|
31166
|
-
|
|
31167
|
-
// server/adapters/gemini.ts
|
|
31168
|
-
import { homedir as homedir6 } from "node:os";
|
|
31169
|
-
import { basename as basename3, dirname as dirname2, join as join10, sep as sep3 } from "node:path";
|
|
31170
|
-
|
|
31171
|
-
// server/adapters/local-files.ts
|
|
31172
|
-
import { constants as constants2 } from "node:fs";
|
|
31173
|
-
import { lstat as lstat2, open as open2, readdir as readdir3 } from "node:fs/promises";
|
|
31174
|
-
import { join as join9, relative } from "node:path";
|
|
31175
|
-
var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
31176
|
-
var text = (value) => typeof value === "string" ? value : "";
|
|
31177
|
-
var time = (value) => {
|
|
31178
|
-
const n = typeof value === "number" ? value : typeof value === "string" ? /^\d{13}$/.test(value) ? Number(value) : Date.parse(value) : 0;
|
|
31179
|
-
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
31180
|
-
};
|
|
31181
|
-
var MAX_BYTES2 = 32e6;
|
|
31182
|
-
var MAX_RECORDS = 5e4;
|
|
31183
|
-
var MAX_FILES = 2e4;
|
|
31184
|
-
async function fileState(path) {
|
|
31185
|
-
try {
|
|
31186
|
-
const s = await lstat2(path);
|
|
31187
|
-
if (!s.isFile()) throw new Error("History must be a regular file, not a symlink.");
|
|
31188
|
-
if (s.size > MAX_BYTES2) throw new Error("History exceeds the 32 MB per-file limit.");
|
|
31189
|
-
return { path, signature: digest([s.size, s.mtimeMs, s.ctimeMs, s.ino]), created: s.birthtimeMs || s.mtimeMs, updated: s.mtimeMs, size: s.size };
|
|
31190
|
-
} catch (error) {
|
|
31191
|
-
if (error.code === "ENOENT") return { path, signature: "missing", created: 0, updated: 0, size: 0 };
|
|
31192
|
-
throw error;
|
|
31193
|
-
}
|
|
31194
|
-
}
|
|
31195
|
-
async function historyFiles(roots, match) {
|
|
31196
|
-
const files = [], queue = [];
|
|
31197
|
-
const warnings = [];
|
|
31198
|
-
for (const root3 of new Set(roots)) {
|
|
31199
|
-
try {
|
|
31200
|
-
if ((await lstat2(root3)).isDirectory()) queue.push({ root: root3, path: root3, depth: 0 });
|
|
31201
|
-
} catch (error) {
|
|
31202
|
-
if (error.code !== "ENOENT") warnings.push("A history directory could not be read.");
|
|
31203
|
-
}
|
|
31204
|
-
}
|
|
31205
|
-
let entriesSeen = 0, limited = false;
|
|
31206
|
-
for (let i = 0; i < queue.length && files.length < MAX_FILES && entriesSeen < 1e5; i++) {
|
|
31207
|
-
const { root: root3, path, depth } = queue[i];
|
|
31208
|
-
let entries2;
|
|
31209
|
-
try {
|
|
31210
|
-
entries2 = await readdir3(path, { withFileTypes: true });
|
|
31211
|
-
} catch {
|
|
31212
|
-
warnings.push("A history directory could not be read.");
|
|
31213
|
-
continue;
|
|
31214
|
-
}
|
|
31215
|
-
for (const entry of entries2) {
|
|
31216
|
-
if (++entriesSeen > 1e5 || files.length >= MAX_FILES) {
|
|
31217
|
-
limited = true;
|
|
31218
|
-
break;
|
|
31219
|
-
}
|
|
31220
|
-
const file = join9(path, entry.name);
|
|
31221
|
-
if (entry.isDirectory()) {
|
|
31222
|
-
if (depth < 6 && queue.length < MAX_FILES) queue.push({ root: root3, path: file, depth: depth + 1 });
|
|
31223
|
-
else limited = true;
|
|
31224
|
-
}
|
|
31225
|
-
if (entry.isFile() && match(relative(root3, file).split("\\").join("/"))) files.push(file);
|
|
31226
|
-
}
|
|
31227
|
-
}
|
|
31228
|
-
if (limited) warnings.push("History discovery reached its file, directory, or depth limit.");
|
|
31229
|
-
return { files: [...new Set(files)].sort(), warnings };
|
|
31230
|
-
}
|
|
31231
|
-
function json3(value) {
|
|
31232
|
-
return JSON.parse(value);
|
|
31233
|
-
}
|
|
31234
|
-
function jsonLines(value) {
|
|
31235
|
-
const rows = [];
|
|
31236
|
-
let partial = false;
|
|
31237
|
-
for (const line of value.split("\n")) {
|
|
31238
|
-
if (!line.trim()) continue;
|
|
31239
|
-
if (rows.length >= MAX_RECORDS) throw new Error("History exceeds the 50,000-record limit.");
|
|
31240
|
-
if (line.length > 2e6) throw new Error("History contains a record larger than 2 MB.");
|
|
31241
|
-
try {
|
|
31242
|
-
const row = json3(line);
|
|
31243
|
-
if (!row || typeof row !== "object" || Array.isArray(row)) partial = true;
|
|
31244
|
-
else rows.push(row);
|
|
31245
|
-
} catch {
|
|
31246
|
-
partial = true;
|
|
31247
|
-
}
|
|
31248
|
-
}
|
|
31249
|
-
return { rows, partial };
|
|
31250
|
-
}
|
|
31251
|
-
function localFileAdapter(source) {
|
|
31252
|
-
const metadata2 = /* @__PURE__ */ new Map();
|
|
31253
|
-
let cached;
|
|
31254
|
-
const states = (path) => Promise.all([path, ...source.extras?.(path) || []].map(fileState));
|
|
31255
|
-
const revision3 = (values) => digest([1, ...values.filter((_, i) => i === 0 || source.revisionExtras !== false).map((s) => [s.path, s.signature])]);
|
|
31256
|
-
async function load(path, expected, before) {
|
|
31257
|
-
if (revision3(before) !== expected) throw new Error("Session changed during import; import again.");
|
|
31258
|
-
if (cached?.path === path && cached.revision === expected) return cached.history;
|
|
31259
|
-
if (before[0].signature === "missing") throw new Error("History file is unavailable.");
|
|
31260
|
-
if (before.reduce((n, s) => n + s.size, 0) > MAX_BYTES2) throw new Error("Combined history exceeds the 32 MB snapshot limit.");
|
|
31261
|
-
const files = /* @__PURE__ */ new Map();
|
|
31262
|
-
for (const s of before.filter((s2) => s2.signature !== "missing")) {
|
|
31263
|
-
const handle = await open2(s.path, constants2.O_RDONLY | (constants2.O_NOFOLLOW || 0));
|
|
31264
|
-
try {
|
|
31265
|
-
const buffer = Buffer.alloc(s.size + 1);
|
|
31266
|
-
let bytes = 0;
|
|
31267
|
-
while (bytes < buffer.length) {
|
|
31268
|
-
const part = await handle.read(buffer, bytes, buffer.length - bytes, bytes);
|
|
31269
|
-
if (!part.bytesRead) break;
|
|
31270
|
-
bytes += part.bytesRead;
|
|
31271
|
-
}
|
|
31272
|
-
if (bytes !== s.size) throw new Error("History changed while reading; import again.");
|
|
31273
|
-
files.set(s.path, buffer.toString("utf8", 0, bytes));
|
|
31274
|
-
} finally {
|
|
31275
|
-
await handle.close();
|
|
31276
|
-
}
|
|
31277
|
-
}
|
|
31278
|
-
if (digest(await states(path)) !== digest(before)) throw new Error("History changed while reading; import again.");
|
|
31279
|
-
const history = source.parse(path, files);
|
|
31280
|
-
if (!history.nativeID || history.nativeID.length > 512) throw new Error("History has no supported native session identity.");
|
|
31281
|
-
if (history.messages.length > MAX_RECORDS) throw new Error("History exceeds the 50,000-message limit.");
|
|
31282
|
-
cached = { path, revision: expected, history };
|
|
31283
|
-
return history;
|
|
31284
|
-
}
|
|
31285
|
-
return {
|
|
31286
|
-
id: source.id,
|
|
31287
|
-
name: source.name,
|
|
31288
|
-
description: source.description,
|
|
31289
|
-
capabilities: source.capabilities,
|
|
31290
|
-
async detect() {
|
|
31291
|
-
for (const root3 of source.roots()) if ((await lstat2(root3).catch(() => void 0))?.isDirectory()) return true;
|
|
31292
|
-
return false;
|
|
31293
|
-
},
|
|
31294
|
-
async discover() {
|
|
31295
|
-
const listing = await historyFiles(source.roots(), source.match), found = /* @__PURE__ */ new Map();
|
|
31296
|
-
let failed = 0;
|
|
31297
|
-
for (const path of listing.files) {
|
|
31298
|
-
try {
|
|
31299
|
-
const before = await states(path), rev = revision3(before);
|
|
31300
|
-
let candidate = metadata2.get(path);
|
|
31301
|
-
if (!candidate || candidate.revision !== rev) {
|
|
31302
|
-
const history = await load(path, rev, before);
|
|
31303
|
-
const session = { ...blankSession(source.id, history.nativeID), ...history.session };
|
|
31304
|
-
session.id = identity(source.id, history.nativeID);
|
|
31305
|
-
session.nativeID = history.nativeID;
|
|
31306
|
-
session.source = source.id;
|
|
31307
|
-
session.directory = text(session.directory);
|
|
31308
|
-
session.projectID = project(source.id, session.directory).id;
|
|
31309
|
-
session.created = time(session.created) || history.messages.map((m) => time(m.time)).find(Boolean) || before[0].created;
|
|
31310
|
-
session.updated = before[0].updated;
|
|
31311
|
-
session.agent ||= source.name;
|
|
31312
|
-
session.parentID = history.parent ? identity(source.id, history.parent) : void 0;
|
|
31313
|
-
session.forkID = history.fork ? identity(source.id, history.fork) : void 0;
|
|
31314
|
-
if (session.title === "Untitled session" || !session.title) session.title = history.messages.flatMap((m) => m.role === "user" ? m.fragments.filter((f) => f.kind === "You").map((f) => f.text) : []).find(Boolean)?.replace(/\s+/g, " ").slice(0, 180) || `${source.name} session`;
|
|
31315
|
-
candidate = { session, revision: rev, locator: { path } };
|
|
31316
|
-
metadata2.set(path, candidate);
|
|
31317
|
-
}
|
|
31318
|
-
const previous = found.get(candidate.session.id);
|
|
31319
|
-
const preferred = candidate.locator.path.endsWith(".jsonl"), previousPreferred = previous?.locator.path.endsWith(".jsonl");
|
|
31320
|
-
if (!previous || preferred && !previousPreferred || preferred === previousPreferred && candidate.session.updated > previous.session.updated) found.set(candidate.session.id, candidate);
|
|
31321
|
-
} catch {
|
|
31322
|
-
failed++;
|
|
32162
|
+
await add(join12(transcripts, entry.name, `${entry.name}.jsonl`), entry.name, "transcript", directory2);
|
|
32163
|
+
for (const child of await entries(join12(transcripts, entry.name, "subagents"), warnings)) {
|
|
32164
|
+
if (child.isFile() && child.name.endsWith(".jsonl")) await add(join12(transcripts, entry.name, "subagents", child.name), child.name.slice(0, -6), "transcript", directory2, entry.name);
|
|
32165
|
+
if (scanned >= LIMIT) break;
|
|
32166
|
+
}
|
|
32167
|
+
}
|
|
31323
32168
|
}
|
|
31324
32169
|
}
|
|
31325
|
-
const
|
|
31326
|
-
for (const
|
|
31327
|
-
|
|
31328
|
-
|
|
32170
|
+
const chats = join12(root3(), "chats");
|
|
32171
|
+
for (const bucket of await entries(chats, warnings)) {
|
|
32172
|
+
if (!bucket.isDirectory() || scanned >= LIMIT) continue;
|
|
32173
|
+
const folder = join12(chats, bucket.name), directory2 = match(bucket.name, hashCwd);
|
|
32174
|
+
if (/^[a-f\d]{32}$/i.test(bucket.name) && await fileStats(join12(folder, "store.db"))) {
|
|
32175
|
+
const id2 = bucket.name.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
|
32176
|
+
await add(join12(folder, "store.db"), id2, "cli", "");
|
|
32177
|
+
}
|
|
32178
|
+
for (const entry of await entries(folder, warnings)) {
|
|
32179
|
+
if (entry.isDirectory()) await add(join12(folder, entry.name, "store.db"), entry.name, "cli", directory2, "", bucket.name);
|
|
32180
|
+
if (scanned >= LIMIT) break;
|
|
32181
|
+
}
|
|
32182
|
+
}
|
|
32183
|
+
const acp = join12(root3(), "acp-sessions");
|
|
32184
|
+
for (const entry of await entries(acp, warnings)) {
|
|
32185
|
+
if (entry.isDirectory()) await add(join12(acp, entry.name, "store.db"), entry.name, "acp", "");
|
|
32186
|
+
if (scanned >= LIMIT) break;
|
|
32187
|
+
}
|
|
32188
|
+
if (scanned >= LIMIT) warnings.push("Cursor discovery limited to 20,000 history files.");
|
|
32189
|
+
if (unreadable) warnings.push(`${unreadable} Cursor histories were unreadable or changed during discovery; saved imports are retained. Re-import to retry.`);
|
|
32190
|
+
if (legacy) warnings.push(`${legacy} older plain-text Cursor transcripts are not supported.`);
|
|
32191
|
+
warnings.push("Cursor imports local IDE JSONL, legacy Composer databases, and CLI/ACP stores. Plain-text exports, cloud-only and SDK-only history are not imported. Formats are not a stable public API.");
|
|
32192
|
+
const sessions = [...found.values()].filter((c) => !blocked.has(c.session.id));
|
|
31329
32193
|
return { sessions, projects: [...new Map(sessions.map((c) => {
|
|
31330
|
-
const p = project(
|
|
32194
|
+
const p = project("cursor", c.session.directory);
|
|
31331
32195
|
return [p.id, p];
|
|
31332
|
-
})).values()], warnings:
|
|
32196
|
+
})).values()], warnings: [...new Set(warnings)] };
|
|
31333
32197
|
},
|
|
31334
32198
|
async read(candidate, cursor) {
|
|
31335
|
-
const
|
|
31336
|
-
if (
|
|
31337
|
-
|
|
31338
|
-
|
|
31339
|
-
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
cursor: offset + messages.length < history.messages.length ? String(offset + messages.length) : void 0,
|
|
31343
|
-
partial: Boolean(history.partial || messages.some((m) => m.partial)),
|
|
31344
|
-
note: `${history.note}${untimed ? " Missing message timestamps remain unknown; session placement falls back to recorded metadata or file time." : ""} File snapshots are unavailable.`
|
|
32199
|
+
const { locator } = candidate;
|
|
32200
|
+
if (locator.kind === "composer") {
|
|
32201
|
+
if (cursor) throw new Error("Invalid Composer cursor.");
|
|
32202
|
+
return readComposer(candidate);
|
|
32203
|
+
}
|
|
32204
|
+
const verify = async () => {
|
|
32205
|
+
if (await snapshot(locator) !== locator.snapshot) throw new Error("Cursor session changed during import; re-import to capture the latest history.");
|
|
31345
32206
|
};
|
|
31346
|
-
|
|
31347
|
-
|
|
31348
|
-
|
|
31349
|
-
|
|
31350
|
-
|
|
31351
|
-
|
|
31352
|
-
|
|
31353
|
-
|
|
31354
|
-
|
|
31355
|
-
|
|
31356
|
-
|
|
31357
|
-
|
|
31358
|
-
});
|
|
31359
|
-
if (raw.role === "thinking") content = [{ type: "thinking", thinking: raw.content }];
|
|
31360
|
-
message.events = blockEvents(message, content);
|
|
31361
|
-
if (role === "tool" && !(Array.isArray(content) && content.some((p) => p.type === "tool_result"))) {
|
|
31362
|
-
message.events = [eventFor(message, "result", "tool_result", text(raw.toolName) || "Tool result", "", {
|
|
31363
|
-
callID: text(raw.toolCallId) || text(raw.tool_call_id) || void 0,
|
|
31364
|
-
output: eventText(content),
|
|
31365
|
-
status: raw.isError === true ? "failed" : raw.isError === false ? "completed" : "unknown"
|
|
31366
|
-
})];
|
|
31367
|
-
}
|
|
31368
|
-
for (const event of message.events) {
|
|
31369
|
-
if (event.kind === "message") addText(message, event.text, role === "user" ? "You" : role === "assistant" ? "Assistant" : "Tool");
|
|
31370
|
-
else if (event.kind === "tool_call") {
|
|
31371
|
-
message.tools.push(event.name);
|
|
31372
|
-
toolText(message, event.input);
|
|
31373
|
-
} else if (event.kind === "tool_result") toolText(message, event.output);
|
|
31374
|
-
else if (event.kind === "reasoning") addText(message, event.text, "Assistant");
|
|
31375
|
-
}
|
|
31376
|
-
for (const call of Array.isArray(raw.toolCalls) ? raw.toolCalls : []) {
|
|
31377
|
-
const f = object(call.function);
|
|
31378
|
-
if (!text(f.name)) continue;
|
|
31379
|
-
message.tools.push(f.name);
|
|
31380
|
-
message.events.push(eventFor(message, `call:${text(call.id) || message.events.length}`, "tool_call", f.name, "", { callID: text(call.id) || void 0, input: eventText(f.arguments), status: "unknown" }));
|
|
31381
|
-
toolText(message, f.arguments);
|
|
31382
|
-
}
|
|
31383
|
-
if (message.events.length && message.events.every((e10) => e10.kind === "tool_result")) message.role = "tool";
|
|
31384
|
-
message.partial ||= message.events.some((e10) => e10.partial);
|
|
31385
|
-
return message;
|
|
31386
|
-
}
|
|
31387
|
-
function googleMessage(raw, fallbackID) {
|
|
31388
|
-
const parts = Array.isArray(raw.content) ? raw.content : raw.content != null ? [raw.content] : [];
|
|
31389
|
-
const content = parts.map((part) => {
|
|
31390
|
-
if (typeof part === "string") return { type: "text", text: part };
|
|
31391
|
-
const p = object(part);
|
|
31392
|
-
if (typeof p.text === "string") return p.thought ? { type: "thinking", thinking: p.text } : { type: "text", text: p.text };
|
|
31393
|
-
if (p.functionCall) return { type: "tool_use", ...p.functionCall, input: p.functionCall.args };
|
|
31394
|
-
if (p.functionResponse) return { type: "tool_result", tool_use_id: p.functionResponse.id || raw.toolCallId, name: p.functionResponse.name, content: p.functionResponse.response, is_error: p.functionResponse.response?.error != null };
|
|
31395
|
-
return { type: "unsupported" };
|
|
31396
|
-
});
|
|
31397
|
-
return conversationMessage({ ...raw, content }, fallbackID);
|
|
31398
|
-
}
|
|
31399
|
-
function successfulEdits(messages, editNames) {
|
|
31400
|
-
const calls = /* @__PURE__ */ new Map();
|
|
31401
|
-
for (const message of messages) for (const event of message.events || []) {
|
|
31402
|
-
if (event.kind === "tool_call" && event.callID) {
|
|
31403
|
-
let input = {};
|
|
31404
|
-
try {
|
|
31405
|
-
input = object(JSON.parse(event.input || "{}"));
|
|
31406
|
-
} catch {
|
|
32207
|
+
await verify();
|
|
32208
|
+
const note = [
|
|
32209
|
+
locator.kind === "transcript" ? "Cursor IDE JSONL export; tool results and pre-compaction history may be absent." : "Cursor committed conversation root; abandoned branches and system prompts are excluded.",
|
|
32210
|
+
"No file snapshots or inferred edit evidence. Missing message timestamps stay unknown.",
|
|
32211
|
+
locator.timeNote,
|
|
32212
|
+
locator.directoryNote
|
|
32213
|
+
].filter(Boolean).join(" ");
|
|
32214
|
+
if (locator.kind !== "transcript") {
|
|
32215
|
+
if (cursor) throw new Error("Invalid Cursor store cursor.");
|
|
32216
|
+
const result = readCursorStore(locator.path);
|
|
32217
|
+
await verify();
|
|
32218
|
+
return { messages: result.messages, partial: result.partial, note: `${note}${result.partial ? " Unsupported, missing, or bounded store records were omitted (2 MB per blob, 64 MB / 50,000 nodes / 16 million text characters per session)." : ""}` };
|
|
31407
32219
|
}
|
|
31408
|
-
|
|
31409
|
-
|
|
31410
|
-
|
|
31411
|
-
|
|
31412
|
-
|
|
31413
|
-
|
|
31414
|
-
|
|
31415
|
-
|
|
31416
|
-
|
|
31417
|
-
|
|
31418
|
-
|
|
31419
|
-
} catch {
|
|
32220
|
+
const offset = Number(cursor || 0);
|
|
32221
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error("Invalid Cursor transcript cursor.");
|
|
32222
|
+
const page = await readJSONL(locator.path, offset);
|
|
32223
|
+
const messages = page.rows.flatMap((row) => {
|
|
32224
|
+
const message2 = cursorMessage(row.value, `offset:${row.offset}`);
|
|
32225
|
+
if (!message2) page.partial = true;
|
|
32226
|
+
return message2 && message2.role !== "system" ? [message2] : [];
|
|
32227
|
+
});
|
|
32228
|
+
await verify();
|
|
32229
|
+
if (!offset && page.cursor === void 0 && !messages.length) throw new Error("Cursor transcript has no readable conversation. Previous imports are retained.");
|
|
32230
|
+
return { messages, cursor: page.cursor === void 0 ? void 0 : String(page.cursor), partial: true, note: `${note}${page.partial ? " Malformed or unsupported transcript records were skipped." : ""}` };
|
|
31420
32231
|
}
|
|
31421
|
-
|
|
31422
|
-
addText(message, path, "File");
|
|
31423
|
-
}
|
|
32232
|
+
};
|
|
31424
32233
|
}
|
|
32234
|
+
var cursorAdapter = createCursorAdapter();
|
|
31425
32235
|
|
|
31426
32236
|
// server/adapters/gemini.ts
|
|
32237
|
+
import { homedir as homedir7 } from "node:os";
|
|
32238
|
+
import { basename as basename5, dirname as dirname3, join as join13, sep as sep3 } from "node:path";
|
|
31427
32239
|
function projectRootFile(path) {
|
|
31428
32240
|
const parts = path.split(sep3);
|
|
31429
|
-
return
|
|
32241
|
+
return join13(parts.slice(0, parts.lastIndexOf("chats")).join(sep3), ".project_root");
|
|
31430
32242
|
}
|
|
31431
32243
|
function geminiConversation(contents, lines) {
|
|
31432
|
-
const parsed = lines ? jsonLines(contents) : { rows: [object(
|
|
32244
|
+
const parsed = lines ? jsonLines(contents) : { rows: [object(json2(contents))], partial: false };
|
|
31433
32245
|
let metadata2 = {};
|
|
31434
32246
|
const messages = /* @__PURE__ */ new Map();
|
|
31435
32247
|
const patch = (p) => {
|
|
@@ -31462,7 +32274,7 @@ function geminiConversation(contents, lines) {
|
|
|
31462
32274
|
metadata2 = { ...metadata2, ...update };
|
|
31463
32275
|
if (Array.isArray(update.messages)) {
|
|
31464
32276
|
if (row.$set) messages.clear();
|
|
31465
|
-
for (const
|
|
32277
|
+
for (const message2 of update.messages) if (typeof message2.id === "string") messages.set(message2.id, message2);
|
|
31466
32278
|
}
|
|
31467
32279
|
} else if (typeof row.id === "string" && typeof row.type === "string") messages.set(row.id, row);
|
|
31468
32280
|
else parsed.partial = true;
|
|
@@ -31474,165 +32286,128 @@ var geminiAdapter = localFileAdapter({
|
|
|
31474
32286
|
name: "Gemini CLI",
|
|
31475
32287
|
description: "Local JSON and JSONL chats, including recorded subagent histories",
|
|
31476
32288
|
capabilities: { history: true, forks: false, children: true, files: true },
|
|
31477
|
-
roots: () => process.env.SESSIONGRAPH_GEMINI_HOME ? [
|
|
32289
|
+
roots: () => process.env.SESSIONGRAPH_GEMINI_HOME ? [join13(process.env.SESSIONGRAPH_GEMINI_HOME, "tmp")] : [join13(process.env.GEMINI_CLI_HOME || homedir7(), ".gemini", "tmp"), join13(process.env.GEMINI_CLI_HOME || homedir7(), ".cache", ".gemini", "tmp")],
|
|
31478
32290
|
match: (path) => /^[^/]+\/chats\/(?:[^/]+\/)?[^/]+\.jsonl?$/.test(path),
|
|
31479
32291
|
extras: (path) => [projectRootFile(path)],
|
|
31480
32292
|
parse(path, files) {
|
|
31481
32293
|
const { metadata: meta, records, partial } = geminiConversation(files.get(path), path.endsWith(".jsonl"));
|
|
31482
32294
|
if (!text(meta.sessionId)) throw new Error("Unsupported Gemini history.");
|
|
31483
|
-
const
|
|
32295
|
+
const directory2 = (files.get(projectRootFile(path)) || "").trim().slice(0, 4096);
|
|
31484
32296
|
const messages = records.map((raw, index) => {
|
|
31485
|
-
const
|
|
31486
|
-
for (const [i, thought] of (Array.isArray(raw.thoughts) ? raw.thoughts : []).entries())
|
|
32297
|
+
const message2 = googleMessage({ id: raw.id, timestamp: raw.timestamp, role: raw.type === "user" ? "user" : raw.type === "gemini" ? "assistant" : "system", content: raw.content }, `record:${index}`);
|
|
32298
|
+
for (const [i, thought] of (Array.isArray(raw.thoughts) ? raw.thoughts : []).entries()) message2.events.push(eventFor(message2, `thought:${i}`, "reasoning", text(thought.subject) || "Recorded reasoning", text(thought.description), { time: time(thought.timestamp) || null, timeSource: time(thought.timestamp) ? "recorded" : "unknown" }));
|
|
31487
32299
|
for (const [i, call] of (Array.isArray(raw.toolCalls) ? raw.toolCalls : []).entries()) {
|
|
31488
32300
|
const id2 = text(call.id) || `tool:${i}`, name = text(call.name) || "Tool";
|
|
31489
|
-
|
|
31490
|
-
|
|
32301
|
+
message2.tools.push(name);
|
|
32302
|
+
message2.events.push(eventFor(message2, `call:${id2}`, "tool_call", name, "", { callID: id2, input: eventText(call.args), status: "unknown" }));
|
|
31491
32303
|
if (call.result != null || ["success", "error", "cancelled"].includes(call.status)) {
|
|
31492
32304
|
const output = eventText(call.result);
|
|
31493
|
-
|
|
31494
|
-
addText(
|
|
32305
|
+
message2.events.push(eventFor(message2, `result:${id2}`, "tool_result", name, "", { role: "tool", callID: id2, output, nativeType: text(call.status), time: time(call.timestamp) || null, timeSource: time(call.timestamp) ? "recorded" : "unknown", status: call.status === "success" ? "completed" : call.status === "error" ? "failed" : "unknown" }));
|
|
32306
|
+
addText(message2, output, "Tool");
|
|
31495
32307
|
}
|
|
31496
32308
|
}
|
|
31497
|
-
return
|
|
32309
|
+
return message2;
|
|
31498
32310
|
});
|
|
31499
32311
|
successfulEdits(messages, ["write_file", "replace"]);
|
|
31500
|
-
const parent2 = meta.kind === "subagent" &&
|
|
32312
|
+
const parent2 = meta.kind === "subagent" && basename5(dirname3(dirname3(path))) === "chats" ? basename5(dirname3(path)) : void 0;
|
|
31501
32313
|
return {
|
|
31502
32314
|
nativeID: meta.sessionId,
|
|
31503
32315
|
messages,
|
|
31504
32316
|
parent: parent2,
|
|
31505
32317
|
partial,
|
|
31506
|
-
session: { directory, created: time(meta.startTime), title: text(meta.summary), model: text([...records].reverse().find((r) => r.model)?.model) || "Unknown model", provider: "Google", nativeResume: !parent2 && Boolean(
|
|
32318
|
+
session: { directory: directory2, created: time(meta.startTime), title: text(meta.summary), model: text([...records].reverse().find((r) => r.model)?.model) || "Unknown model", provider: "Google", nativeResume: !parent2 && Boolean(directory2) && !process.env.SESSIONGRAPH_GEMINI_HOME && !path.includes(`${sep3}.cache${sep3}`) },
|
|
31507
32319
|
note: "Local Gemini chat records with rewind and patch reconstruction. Only explicit subagent directories form parent links; workspace paths come from the project-root marker. Binary content is omitted."
|
|
31508
32320
|
};
|
|
31509
32321
|
}
|
|
31510
32322
|
});
|
|
31511
32323
|
|
|
31512
32324
|
// server/adapters/qwen.ts
|
|
31513
|
-
import { homedir as
|
|
31514
|
-
import { join as
|
|
32325
|
+
import { homedir as homedir8 } from "node:os";
|
|
32326
|
+
import { join as join14 } from "node:path";
|
|
31515
32327
|
var qwenAdapter = localFileAdapter({
|
|
31516
32328
|
id: "qwen-code",
|
|
31517
32329
|
name: "Qwen Code",
|
|
31518
32330
|
description: "Local project chat records \xB7 tool results, forks, and child sessions",
|
|
31519
32331
|
capabilities: { history: true, forks: true, children: true, files: true },
|
|
31520
|
-
roots: () => [
|
|
32332
|
+
roots: () => [join14(process.env.SESSIONGRAPH_QWEN_HOME || process.env.QWEN_RUNTIME_DIR || process.env.QWEN_HOME || join14(homedir8(), ".qwen"), "projects")],
|
|
31521
32333
|
match: (path) => /^[^/]+\/chats\/[^/]+\.jsonl$/.test(path),
|
|
31522
32334
|
parse(path, files) {
|
|
31523
|
-
const { rows, partial } = jsonLines(files.get(path));
|
|
31524
|
-
const first =
|
|
32335
|
+
const { rows: rows2, partial } = jsonLines(files.get(path));
|
|
32336
|
+
const first = rows2.find((r) => text(r.sessionId));
|
|
31525
32337
|
if (!first) throw new Error("Unsupported Qwen history.");
|
|
31526
|
-
if (
|
|
31527
|
-
const messages =
|
|
32338
|
+
if (rows2.some((r) => r.sessionId && r.sessionId !== first.sessionId)) throw new Error("Mixed session identities.");
|
|
32339
|
+
const messages = rows2.map((raw, index) => {
|
|
31528
32340
|
const id2 = text(raw.uuid) || `record:${index}`;
|
|
31529
32341
|
if (raw.message && !["agent_message", "agent_mention", "notification"].includes(raw.subtype)) {
|
|
31530
|
-
const
|
|
31531
|
-
for (const event of
|
|
32342
|
+
const message3 = googleMessage({ id: id2, timestamp: raw.timestamp, role: raw.type === "assistant" ? "assistant" : raw.type === "user" ? "user" : "tool", toolCallId: raw.toolCallResult?.callId, content: object(raw.message).parts }, id2);
|
|
32343
|
+
for (const event of message3.events || []) if (event.kind === "tool_result") {
|
|
31532
32344
|
event.status = raw.toolCallResult?.status === "success" ? "completed" : raw.toolCallResult?.status === "error" ? "failed" : "unknown";
|
|
31533
32345
|
event.nativeType = text(raw.toolCallResult?.status);
|
|
31534
32346
|
}
|
|
31535
|
-
return
|
|
32347
|
+
return message3;
|
|
31536
32348
|
}
|
|
31537
|
-
const
|
|
31538
|
-
|
|
31539
|
-
return
|
|
32349
|
+
const message2 = blankMessage(id2, time(raw.timestamp), "system");
|
|
32350
|
+
message2.events = [eventFor(message2, "event", /compression/.test(raw.subtype) ? "compaction" : "event", text(raw.subtype) || text(raw.type) || "Qwen event", eventText(raw.systemPayload ?? raw.message), { nativeType: text(raw.subtype) || text(raw.type), parentID: text(raw.parentUuid) || void 0 })];
|
|
32351
|
+
return message2;
|
|
31540
32352
|
});
|
|
31541
32353
|
successfulEdits(messages, ["write_file", "edit", "replace"]);
|
|
31542
|
-
const latest = [...
|
|
32354
|
+
const latest = [...rows2].reverse(), parent2 = rows2.find((r) => r.subtype === "parent_session")?.systemPayload?.parentSessionId;
|
|
31543
32355
|
const title = latest.find((r) => r.subtype === "custom_title")?.systemPayload?.customTitle;
|
|
31544
32356
|
return {
|
|
31545
32357
|
nativeID: first.sessionId,
|
|
31546
32358
|
messages,
|
|
31547
32359
|
partial,
|
|
31548
32360
|
parent: text(parent2) || void 0,
|
|
31549
|
-
fork: text(
|
|
32361
|
+
fork: text(rows2.find((r) => r.forkedFrom?.sessionId)?.forkedFrom?.sessionId) || void 0,
|
|
31550
32362
|
session: { directory: text(first.cwd), created: time(first.timestamp), title: text(title), model: text(latest.find((r) => r.model)?.model) || text(latest.find((r) => r.subtype === "session_model")?.systemPayload?.modelId) || "Unknown model", nativeResume: Boolean(first.cwd) && !process.env.SESSIONGRAPH_QWEN_HOME },
|
|
31551
32363
|
note: "Local Qwen chat records, including compaction and branch events in source order. Parent and fork links use recorded session IDs. Managed-daemon-only and binary history may be unavailable."
|
|
31552
32364
|
};
|
|
31553
32365
|
}
|
|
31554
32366
|
});
|
|
31555
32367
|
|
|
31556
|
-
// server/adapters/pi.ts
|
|
31557
|
-
import { homedir as homedir8 } from "node:os";
|
|
31558
|
-
import { join as join12 } from "node:path";
|
|
31559
|
-
var piAdapter = localFileAdapter({
|
|
31560
|
-
id: "pi",
|
|
31561
|
-
name: "Pi",
|
|
31562
|
-
description: "Local JSONL sessions \xB7 messages, tools, compaction, and branch entries",
|
|
31563
|
-
capabilities: { history: true, forks: false, children: false, files: true },
|
|
31564
|
-
roots: () => [process.env.SESSIONGRAPH_PI_SESSIONS_DIR || join12(process.env.PI_CODING_AGENT_DIR || join12(homedir8(), ".pi", "agent"), "sessions")],
|
|
31565
|
-
match: (path) => path.endsWith(".jsonl"),
|
|
31566
|
-
parse(path, files) {
|
|
31567
|
-
const { rows, partial } = jsonLines(files.get(path));
|
|
31568
|
-
const header = rows[0];
|
|
31569
|
-
if (header?.type !== "session" || !text(header.id)) throw new Error("Unsupported Pi session.");
|
|
31570
|
-
const messages = rows.slice(1).map((raw, index) => {
|
|
31571
|
-
const id2 = text(raw.id) || `record:${index}`;
|
|
31572
|
-
if (raw.type === "message" && raw.message) {
|
|
31573
|
-
const message2 = conversationMessage({ ...raw.message, id: id2, timestamp: raw.message.timestamp ?? raw.timestamp }, id2);
|
|
31574
|
-
for (const event of message2.events || []) event.parentID = text(raw.parentId) || void 0;
|
|
31575
|
-
return message2;
|
|
31576
|
-
}
|
|
31577
|
-
const message = blankMessage(id2, time(raw.timestamp), "system");
|
|
31578
|
-
message.events = [eventFor(message, "entry", ["compaction", "branch_summary"].includes(raw.type) ? "compaction" : "event", text(raw.type) || "Pi entry", text(raw.summary) || eventText(raw), { nativeType: text(raw.type), parentID: text(raw.parentId) || void 0 })];
|
|
31579
|
-
return message;
|
|
31580
|
-
});
|
|
31581
|
-
successfulEdits(messages, ["write", "edit"]);
|
|
31582
|
-
const lastModel = [...rows].reverse().find((r) => r.type === "model_change" || r.message?.model);
|
|
31583
|
-
return {
|
|
31584
|
-
nativeID: header.id,
|
|
31585
|
-
messages,
|
|
31586
|
-
partial,
|
|
31587
|
-
session: { directory: text(header.cwd), created: time(header.timestamp), title: text([...rows].reverse().find((r) => r.type === "session_info")?.name), model: text(lastModel?.modelId || lastModel?.message?.model) || "Unknown model", provider: text(lastModel?.provider || lastModel?.message?.provider), nativeResume: Boolean(header.cwd) && !process.env.SESSIONGRAPH_PI_SESSIONS_DIR },
|
|
31588
|
-
note: "Raw Pi session history includes all recorded in-file branches, not just the active model context. Entry parent IDs are preserved as event links, not session ancestry. Parent-session file paths do not establish whether a session is a fork or a child. Binary content is omitted."
|
|
31589
|
-
};
|
|
31590
|
-
}
|
|
31591
|
-
});
|
|
31592
|
-
|
|
31593
32368
|
// server/adapters/copilot.ts
|
|
31594
32369
|
import { homedir as homedir9 } from "node:os";
|
|
31595
|
-
import { join as
|
|
32370
|
+
import { join as join15 } from "node:path";
|
|
31596
32371
|
var copilotAdapter = localFileAdapter({
|
|
31597
32372
|
id: "copilot",
|
|
31598
32373
|
name: "GitHub Copilot CLI",
|
|
31599
32374
|
description: "Local session event logs \xB7 conversations and completed file edits",
|
|
31600
32375
|
capabilities: { history: true, forks: false, children: true, files: true },
|
|
31601
|
-
roots: () => [
|
|
32376
|
+
roots: () => [join15(process.env.SESSIONGRAPH_COPILOT_HOME || join15(homedir9(), ".copilot"), "session-state")],
|
|
31602
32377
|
match: (path) => /(?:^|\/)events\.jsonl$/.test(path),
|
|
31603
32378
|
parse(path, files) {
|
|
31604
|
-
const { rows, partial } = jsonLines(files.get(path));
|
|
31605
|
-
const start =
|
|
32379
|
+
const { rows: rows2, partial } = jsonLines(files.get(path));
|
|
32380
|
+
const start = rows2.find((r) => r.type === "session.start"), meta = object(start?.data);
|
|
31606
32381
|
if (!text(meta.sessionId)) throw new Error("Unsupported Copilot event log.");
|
|
31607
|
-
const toolNames = new Map(
|
|
31608
|
-
const messages =
|
|
32382
|
+
const toolNames = new Map(rows2.filter((r) => r.type === "tool.execution_start").map((r) => [r.data?.toolCallId, text(r.data?.toolName)]));
|
|
32383
|
+
const messages = rows2.filter((r) => !r.ephemeral && !["assistant.message_delta", "assistant.reasoning_delta"].includes(r.type)).map((raw, index) => {
|
|
31609
32384
|
const data = object(raw.data), kind = text(raw.type), human = kind === "user.message" && !data.isAutopilotContinuation && !data.source;
|
|
31610
|
-
const
|
|
32385
|
+
const message2 = blankMessage(text(raw.id) || `record:${index}`, time(raw.timestamp), human ? "user" : kind === "assistant.message" ? "assistant" : kind.startsWith("tool.") ? "tool" : "system");
|
|
31611
32386
|
if (["user.message", "assistant.message"].includes(kind)) {
|
|
31612
32387
|
const content = text(data.content);
|
|
31613
|
-
|
|
31614
|
-
addText(
|
|
31615
|
-
if (data.attachments?.length || data.encryptedContent || data.reasoningOpaque)
|
|
32388
|
+
message2.events = [eventFor(message2, "message", human || kind === "assistant.message" ? "message" : "event", human ? "User message" : kind === "assistant.message" ? "Assistant message" : "Injected message", content, { nativeID: text(data.messageId) || void 0, nativeType: kind })];
|
|
32389
|
+
addText(message2, content, human ? "You" : kind === "assistant.message" ? "Assistant" : "Tool");
|
|
32390
|
+
if (data.attachments?.length || data.encryptedContent || data.reasoningOpaque) message2.partial = true;
|
|
31616
32391
|
} else if (kind === "tool.execution_start" || kind === "tool.execution_complete") {
|
|
31617
32392
|
const call = kind === "tool.execution_start", name = text(data.toolName) || toolNames.get(data.toolCallId) || "Tool";
|
|
31618
|
-
|
|
32393
|
+
message2.events = [eventFor(message2, call ? "call" : "result", call ? "tool_call" : "tool_result", name, "", {
|
|
31619
32394
|
callID: text(data.toolCallId) || void 0,
|
|
31620
32395
|
nativeType: kind,
|
|
31621
32396
|
...call ? { input: eventText(data.arguments) } : { output: eventText(data.error || data.result?.detailedContent || data.result?.content) },
|
|
31622
32397
|
status: call ? "unknown" : data.success === true ? "completed" : data.success === false ? "failed" : "unknown"
|
|
31623
32398
|
})];
|
|
31624
|
-
if (call)
|
|
31625
|
-
addText(
|
|
32399
|
+
if (call) message2.tools.push(name);
|
|
32400
|
+
addText(message2, message2.events[0].input || message2.events[0].output, "Tool");
|
|
31626
32401
|
if (!call) {
|
|
31627
32402
|
for (const edit of Array.isArray(data.fileEdits) ? data.fileEdits : []) if (text(edit.path)) {
|
|
31628
|
-
|
|
31629
|
-
addText(
|
|
32403
|
+
message2.files.push({ path: edit.path, source: "file change", steps: 1 });
|
|
32404
|
+
addText(message2, edit.path, "File");
|
|
31630
32405
|
}
|
|
31631
32406
|
}
|
|
31632
|
-
} else
|
|
31633
|
-
return
|
|
32407
|
+
} else message2.events = [eventFor(message2, "event", kind.includes("compaction") ? "compaction" : kind.includes("reasoning") ? "reasoning" : kind === "session.error" ? "error" : "event", kind || "Copilot event", eventText(data), { nativeType: kind, parentID: text(raw.parentId) || void 0 })];
|
|
32408
|
+
return message2;
|
|
31634
32409
|
});
|
|
31635
|
-
const latest = [...
|
|
32410
|
+
const latest = [...rows2].reverse();
|
|
31636
32411
|
return {
|
|
31637
32412
|
nativeID: meta.sessionId,
|
|
31638
32413
|
messages,
|
|
@@ -31646,39 +32421,39 @@ var copilotAdapter = localFileAdapter({
|
|
|
31646
32421
|
|
|
31647
32422
|
// server/adapters/extensions.ts
|
|
31648
32423
|
import { homedir as homedir10 } from "node:os";
|
|
31649
|
-
import { basename as
|
|
32424
|
+
import { basename as basename6, dirname as dirname4, join as join16 } from "node:path";
|
|
31650
32425
|
function editorTaskRoots(extension) {
|
|
31651
32426
|
const home = homedir10();
|
|
31652
|
-
const base = process.platform === "darwin" ?
|
|
31653
|
-
return ["Code", "Code - Insiders", "VSCodium", "Cursor", "Windsurf"].map((app) =>
|
|
32427
|
+
const base = process.platform === "darwin" ? join16(home, "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA || join16(home, "AppData", "Roaming") : process.env.XDG_CONFIG_HOME || join16(home, ".config");
|
|
32428
|
+
return ["Code", "Code - Insiders", "VSCodium", "Cursor", "Windsurf"].map((app) => join16(base, app, "User", "globalStorage", extension, "tasks")).concat([".vscode-server", ".vscode-server-insiders"].map((app) => join16(home, app, "data", "User", "globalStorage", extension, "tasks")));
|
|
31654
32429
|
}
|
|
31655
32430
|
function taskAdapter(id2, name, extension, env) {
|
|
31656
|
-
const roots = () => process.env[env] ? [
|
|
32431
|
+
const roots = () => process.env[env] ? [join16(process.env[env], "tasks"), ...id2 === "cline" ? [join16(process.env[env], "data", "sessions")] : []] : [...editorTaskRoots(extension), ...id2 === "cline" ? [process.env.CLINE_SESSION_DATA_DIR || join16(process.env.CLINE_DATA_DIR || join16(process.env.CLINE_DIR || join16(homedir10(), ".cline"), "data"), "sessions")] : []];
|
|
31657
32432
|
return localFileAdapter({
|
|
31658
32433
|
id: id2,
|
|
31659
32434
|
name,
|
|
31660
|
-
description: id2 === "kilo-code" ? "
|
|
32435
|
+
description: id2 === "kilo-code" ? "VS Code extension task histories \xB7 choose Kilo CLI separately" : id2 === "cline" ? "Local extension tasks and CLI/SDK message snapshots" : "Local VS Code extension task histories",
|
|
31661
32436
|
capabilities: { history: true, forks: id2 === "cline", children: true, files: true },
|
|
31662
32437
|
roots,
|
|
31663
32438
|
match: (path) => /^[^/]+\/api_conversation_history\.json$/.test(path) || id2 === "cline" && /^[^/]+\/[^/]+\.messages\.json$/.test(path),
|
|
31664
|
-
extras: (path) => ["history_item.json", "task_metadata.json", "session.json"].map((file) =>
|
|
32439
|
+
extras: (path) => ["history_item.json", "task_metadata.json", "session.json"].map((file) => join16(dirname4(path), file)),
|
|
31665
32440
|
parse(path, files) {
|
|
31666
|
-
const sdk = path.endsWith(".messages.json"), value =
|
|
32441
|
+
const sdk = path.endsWith(".messages.json"), value = json2(files.get(path));
|
|
31667
32442
|
const records = sdk ? object(value).messages : value;
|
|
31668
32443
|
if (!Array.isArray(records) || sdk && !text(value.sessionId)) throw new Error("Unsupported task history.");
|
|
31669
|
-
const read3 = (name2) => object(files.has(
|
|
32444
|
+
const read3 = (name2) => object(files.has(join16(dirname4(path), name2)) ? json2(files.get(join16(dirname4(path), name2))) : void 0);
|
|
31670
32445
|
const history = read3("history_item.json"), info = read3("session.json"), task = read3("task_metadata.json");
|
|
31671
|
-
const nativeID = sdk ? value.sessionId :
|
|
32446
|
+
const nativeID = sdk ? value.sessionId : basename6(dirname4(path));
|
|
31672
32447
|
if (history.id && history.id !== nativeID || info.session_id && info.session_id !== nativeID) throw new Error("Task metadata identity mismatch.");
|
|
31673
32448
|
const messages = records.map((raw, index) => {
|
|
31674
|
-
const row = object(raw),
|
|
32449
|
+
const row = object(raw), message2 = conversationMessage(row, `record:${index}`);
|
|
31675
32450
|
if (row.isSummary || row.isTruncationMarker || row.type === "reasoning") {
|
|
31676
|
-
|
|
31677
|
-
|
|
32451
|
+
message2.role = "system";
|
|
32452
|
+
message2.events = [eventFor(message2, "summary", row.type === "reasoning" ? "reasoning" : "compaction", row.type === "reasoning" ? "Recorded reasoning" : "Context checkpoint", eventText(row.summary ?? row.content ?? row.text), { partial: Boolean(row.encrypted_content), nativeType: row.type || "summary" })];
|
|
31678
32453
|
}
|
|
31679
|
-
if (row.reasoning_content)
|
|
31680
|
-
if (row.encrypted_content || row.reasoning_details)
|
|
31681
|
-
return
|
|
32454
|
+
if (row.reasoning_content) message2.events.push(eventFor(message2, "reasoning", "reasoning", "Recorded reasoning", text(row.reasoning_content)));
|
|
32455
|
+
if (row.encrypted_content || row.reasoning_details) message2.partial = true;
|
|
32456
|
+
return message2;
|
|
31682
32457
|
});
|
|
31683
32458
|
successfulEdits(messages, ["write_to_file", "apply_diff", "edit_file", "write_file", "replace_in_file", "editor"]);
|
|
31684
32459
|
const lastModel = [...records].reverse().find((r) => r.modelInfo?.id || r.model);
|
|
@@ -31689,7 +32464,7 @@ function taskAdapter(id2, name, extension, env) {
|
|
|
31689
32464
|
parent: text(history.parentTaskId || info.parent_session_id) || void 0,
|
|
31690
32465
|
fork: text(info.metadata?.fork?.forkedFromSessionId) || void 0,
|
|
31691
32466
|
session: { directory: text(history.workspace || info.cwd || info.workspace_root || task.workspace), created: time(info.started_at), title: text(history.task || info.metadata?.title), model: text(lastModel?.modelInfo?.id || lastModel?.model || info.model) || "Unknown model", provider: text(lastModel?.modelInfo?.provider || info.provider) },
|
|
31692
|
-
note: sdk ? "Cline CLI/SDK message snapshots. Metadata is used when a matching session.json exists; database-only workspace, title, and lineage fields remain unknown. Binary attachments are omitted." : `${name} API-conversation snapshots; UI-only messages, older compacted content, and binary attachments can be absent. XML tool requests alone are not successful edit evidence.${id2 === "kilo-code" ? "
|
|
32467
|
+
note: sdk ? "Cline CLI/SDK message snapshots. Metadata is used when a matching session.json exists; database-only workspace, title, and lineage fields remain unknown. Binary attachments are omitted." : `${name} API-conversation snapshots; UI-only messages, older compacted content, and binary attachments can be absent. XML tool requests alone are not successful edit evidence.${id2 === "kilo-code" ? " Select the separate Kilo CLI source for its SQLite history." : ""}`
|
|
31693
32468
|
};
|
|
31694
32469
|
}
|
|
31695
32470
|
});
|
|
@@ -31702,38 +32477,623 @@ var continueAdapter = localFileAdapter({
|
|
|
31702
32477
|
name: "Continue",
|
|
31703
32478
|
description: "Local IDE and CLI session JSON \xB7 recorded conversation and tool history",
|
|
31704
32479
|
capabilities: { history: true, forks: false, children: false, files: false },
|
|
31705
|
-
roots: () => [
|
|
32480
|
+
roots: () => [join16(process.env.SESSIONGRAPH_CONTINUE_HOME || process.env.CONTINUE_GLOBAL_DIR || join16(homedir10(), ".continue"), "sessions")],
|
|
31706
32481
|
match: (path) => /^[^/]+\.json$/.test(path) && path !== "sessions.json",
|
|
31707
|
-
extras: (path) => [
|
|
32482
|
+
extras: (path) => [join16(dirname4(path), "sessions.json")],
|
|
31708
32483
|
revisionExtras: false,
|
|
31709
32484
|
parse(path, files) {
|
|
31710
|
-
const value = object(
|
|
32485
|
+
const value = object(json2(files.get(path)));
|
|
31711
32486
|
if (!text(value.sessionId) || !Array.isArray(value.history)) throw new Error("Unsupported Continue session.");
|
|
31712
|
-
const indexPath =
|
|
32487
|
+
const indexPath = join16(dirname4(path), "sessions.json"), index = files.has(indexPath) ? json2(files.get(indexPath)) : [];
|
|
31713
32488
|
const meta = Array.isArray(index) ? object(index.find((r) => r.sessionId === value.sessionId)) : {};
|
|
31714
32489
|
const messages = value.history.map((item, i) => {
|
|
31715
|
-
const row = object(item), raw = object(row.message),
|
|
31716
|
-
if (row.reasoning?.text)
|
|
31717
|
-
if (row.conversationSummary)
|
|
31718
|
-
return
|
|
32490
|
+
const row = object(item), raw = object(row.message), message2 = conversationMessage(raw, `record:${i}`);
|
|
32491
|
+
if (row.reasoning?.text) message2.events.push(eventFor(message2, "reasoning", "reasoning", "Recorded reasoning", text(row.reasoning.text), { time: time(row.reasoning.startAt) || null, timeSource: time(row.reasoning.startAt) ? "recorded" : "unknown" }));
|
|
32492
|
+
if (row.conversationSummary) message2.events.push(eventFor(message2, "summary", "compaction", "Conversation summary", text(row.conversationSummary)));
|
|
32493
|
+
return message2;
|
|
31719
32494
|
});
|
|
31720
32495
|
const usage = object(value.usage);
|
|
31721
32496
|
return {
|
|
31722
32497
|
nativeID: value.sessionId,
|
|
31723
32498
|
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:
|
|
32499
|
+
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
32500
|
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
32501
|
};
|
|
31727
32502
|
}
|
|
31728
32503
|
});
|
|
31729
32504
|
|
|
32505
|
+
// server/adapters/harness-files.ts
|
|
32506
|
+
import { basename as basename8, dirname as dirname6, join as join19 } from "node:path";
|
|
32507
|
+
|
|
32508
|
+
// server/adapters/harness-paths.ts
|
|
32509
|
+
import { homedir as homedir11 } from "node:os";
|
|
32510
|
+
import { join as join17 } from "node:path";
|
|
32511
|
+
var homePath = (...parts) => join17(homedir11(), ...parts);
|
|
32512
|
+
var dataPath = (...parts) => join17(process.env.XDG_DATA_HOME || homePath(".local/share"), ...parts);
|
|
32513
|
+
var configPath = (...parts) => join17(process.env.XDG_CONFIG_HOME || homePath(".config"), ...parts);
|
|
32514
|
+
var appPath = (...parts) => join17(process.platform === "darwin" ? homePath("Library/Application Support") : process.platform === "win32" ? process.env.APPDATA || homePath("AppData/Roaming") : process.env.XDG_CONFIG_HOME || homePath(".config"), ...parts);
|
|
32515
|
+
var tokscalePath = (...parts) => join17(process.env.TOKSCALE_CONFIG_DIR || (process.platform === "win32" ? appPath("tokscale") : configPath("tokscale")), ...parts);
|
|
32516
|
+
var envHome = (env, fallback, ...parts) => join17(process.env[env]?.trim() || homePath(fallback), ...parts);
|
|
32517
|
+
var harnessRoots = (id2, defaults) => () => {
|
|
32518
|
+
const override = process.env[`SESSIONGRAPH_${id2.replace(/-/g, "_").toUpperCase()}_HISTORY_DIR`]?.trim();
|
|
32519
|
+
return override ? [override] : defaults();
|
|
32520
|
+
};
|
|
32521
|
+
|
|
32522
|
+
// server/adapters/harness-extra-formats.ts
|
|
32523
|
+
import { basename as basename7, dirname as dirname5, join as join18 } from "node:path";
|
|
32524
|
+
function kiroHistory(path, files) {
|
|
32525
|
+
const header = object(json2(files.get(path)));
|
|
32526
|
+
const ide2 = basename7(path) === "session.json";
|
|
32527
|
+
const nativeID = text(header.session_id || header.sessionId || header.id) || (ide2 ? basename7(dirname5(path)) : "");
|
|
32528
|
+
if (!nativeID) throw new Error("Unsupported Kiro session header.");
|
|
32529
|
+
const side = ide2 ? join18(dirname5(path), "messages.jsonl") : path.replace(/\.json$/, ".jsonl");
|
|
32530
|
+
if (!files.has(side)) throw new Error("Kiro conversation sidecar is unavailable.");
|
|
32531
|
+
const { rows: rows2 } = jsonLines(files.get(side));
|
|
32532
|
+
const messages = rows2.flatMap((r, i) => {
|
|
32533
|
+
if (ide2) {
|
|
32534
|
+
const p = object(r.payload || r);
|
|
32535
|
+
const role2 = p.type === "human" ? "user" : p.type === "ai" ? "assistant" : p.role || p.type;
|
|
32536
|
+
const m3 = message({ ...p, role: role2 }, text(p.id) || `message:${i}`, r.timestamp);
|
|
32537
|
+
return m3 ? [m3] : [recordEvent(r, `event:${i}`, text(p.type) || "Kiro event", r.timestamp, p.text)];
|
|
32538
|
+
}
|
|
32539
|
+
const data = object(r.data), role = r.kind === "Prompt" ? "user" : r.kind === "AssistantMessage" ? "assistant" : r.kind === "ToolResults" ? "tool" : "";
|
|
32540
|
+
if (!role) return [];
|
|
32541
|
+
const content = Array.isArray(data.content) ? data.content.map((p) => p.kind === "text" ? { type: "text", text: p.data } : { type: "unsupported" }) : void 0;
|
|
32542
|
+
const m = message({ role, content }, text(data.message_id) || `message:${i}`, seconds(data.meta?.timestamp));
|
|
32543
|
+
return m ? [m] : [];
|
|
32544
|
+
});
|
|
32545
|
+
if (!messages.length) throw new Error("No readable Kiro messages.");
|
|
32546
|
+
return {
|
|
32547
|
+
nativeID,
|
|
32548
|
+
messages,
|
|
32549
|
+
partial: true,
|
|
32550
|
+
session: { directory: text(header.cwd) || (header.workspacePaths?.length === 1 ? text(header.workspacePaths[0]) : ""), created: time(header.createdAt), title: text(header.title), model: text(header.modelId || header.session_state?.rts_model_state?.model_info?.model_id) || "Unknown model" },
|
|
32551
|
+
note: "Kiro CLI header/JSONL and IDE session snapshots. Unsupported payload variants and compacted context may be absent. Token estimates and credit conversions are not imported."
|
|
32552
|
+
};
|
|
32553
|
+
}
|
|
32554
|
+
function muxHistory(path, files) {
|
|
32555
|
+
const archive = files.get(join18(dirname5(path), "chat-archive.jsonl"));
|
|
32556
|
+
const records = [...archive ? jsonLines(archive).rows : [], ...jsonLines(files.get(path)).rows];
|
|
32557
|
+
const unique = /* @__PURE__ */ new Map();
|
|
32558
|
+
for (const r of records) {
|
|
32559
|
+
if (!text(r.id)) throw new Error("Mux message has no native identity.");
|
|
32560
|
+
unique.set(r.id, r);
|
|
32561
|
+
}
|
|
32562
|
+
const messages = [...unique.values()].flatMap((r) => {
|
|
32563
|
+
const parts = Array.isArray(r.parts) ? r.parts.flatMap((p) => {
|
|
32564
|
+
if (p.type !== "dynamic-tool") return [p];
|
|
32565
|
+
const call = { type: "tool_use", id: p.toolCallId, name: p.toolName, input: p.input };
|
|
32566
|
+
return p.state === "output-available" || p.state === "output-error" ? [call, { type: "tool_result", tool_use_id: p.toolCallId, content: p.output ?? p.errorText, is_error: p.state === "output-error" }] : [call];
|
|
32567
|
+
}) : r.content;
|
|
32568
|
+
if (r.metadata?.synthetic) return [recordEvent(r, r.id, "Synthetic context", r.metadata?.timestamp, parts)];
|
|
32569
|
+
const m = message({ ...r, content: parts }, r.id, r.metadata?.timestamp ?? r.createdAt);
|
|
32570
|
+
return m ? [m] : [];
|
|
32571
|
+
});
|
|
32572
|
+
if (!messages.length) throw new Error("No readable Mux conversation.");
|
|
32573
|
+
return {
|
|
32574
|
+
nativeID: basename7(dirname5(path)),
|
|
32575
|
+
messages,
|
|
32576
|
+
partial: true,
|
|
32577
|
+
note: "Mux durable chat archive followed by active chat, deduplicated by native message ID. In-flight partial.json and workspace ancestry are unavailable. Cumulative usage snapshots are not turns."
|
|
32578
|
+
};
|
|
32579
|
+
}
|
|
32580
|
+
function devinDesktopHistory(path, files) {
|
|
32581
|
+
const { rows: rows2 } = jsonLines(files.get(path));
|
|
32582
|
+
const normalized = rows2.flatMap((r) => {
|
|
32583
|
+
const n = object(r.notification);
|
|
32584
|
+
return n.sessionUpdate ? [{ params: { sessionId: text(r.sessionId || n.sessionId) || fileID(path).replace(/\.ndjson$/, ""), update: n }, timestamp: n.content?.metadata?.created_at ?? n.metadata?.created_at ?? n.timestamp ?? r.timestamp }] : [];
|
|
32585
|
+
});
|
|
32586
|
+
const history = acpHistory(path, normalized.map((r) => JSON.stringify(r)).join("\n"));
|
|
32587
|
+
history.note = "Devin Desktop saved ACP notification chunks. File/native identity is preserved; titles are never used to merge desktop and CLI sessions. Tool internals and turn boundaries may be incomplete.";
|
|
32588
|
+
return history;
|
|
32589
|
+
}
|
|
32590
|
+
|
|
32591
|
+
// server/adapters/harness-files.ts
|
|
32592
|
+
var piParse = (path, files) => piHistory(files.get(path));
|
|
32593
|
+
var jsonl = (path) => path.endsWith(".jsonl");
|
|
32594
|
+
var piSources = [
|
|
32595
|
+
{ id: "prime-agent", name: "Prime Agent", roots: () => [process.env.PRIME_AGENT_SESSION_DIR || process.env.PRIME_AGENT_CODING_AGENT_SESSION_DIR || envHome("PRIME_AGENT_CODING_AGENT_DIR", ".prime/agent", "sessions"), envHome("PRIME_AGENT_CODING_AGENT_DIR", ".prime/agent", "session-artifacts")] },
|
|
32596
|
+
{ id: "omp", name: "Oh My Pi", roots: () => [envHome("PI_CODING_AGENT_DIR_OMP", ".omp/agent", "sessions")] },
|
|
32597
|
+
{ id: "senpi", name: "Senpi / OmO Native", roots: () => [envHome("SENPI_CODING_AGENT_DIR", ".senpi/agent", "sessions"), homePath(".omo/agent/sessions")] },
|
|
32598
|
+
{ id: "kimchi", name: "Kimchi", roots: () => [join19(process.env.KIMCHI_CODING_AGENT_DIR || configPath("kimchi/harness"), "sessions")] },
|
|
32599
|
+
{ id: "gajae", name: "Gajae-Code", roots: () => [join19(process.env.GJC_CODING_AGENT_DIR || (process.env.GJC_CONFIG_DIR || process.env.PI_CONFIG_DIR ? join19(process.env.GJC_CONFIG_DIR || process.env.PI_CONFIG_DIR, "agent") : homePath(".gjc/agent")), "sessions"), dataPath("gjc/sessions")] }
|
|
32600
|
+
].map((s) => ({ ...s, parse: piParse }));
|
|
32601
|
+
var openclawRoots = () => [envHome("OPENCLAW_STATE_DIR", ".openclaw", "agents"), ...[".clawdbot", ".moltbot", ".moldbot"].map((p) => homePath(p, "agents"))];
|
|
32602
|
+
var promaRoots = () => [".proma", ".proma-dev"].flatMap((p) => ["agent-sessions", "sdk-config/sessions", "sdk-config/projects"].map((s) => homePath(p, s)));
|
|
32603
|
+
function kimiHistory(path, files) {
|
|
32604
|
+
const { rows: rows2 } = jsonLines(files.get(path));
|
|
32605
|
+
const folder = dirname6(path), id2 = basename8(folder), agent = basename8(dirname6(folder)) === "agents";
|
|
32606
|
+
const nativeID = agent ? `${basename8(dirname6(dirname6(folder)))}/agent/${id2}` : id2;
|
|
32607
|
+
const wire = join19(folder, "wire.jsonl"), wireRows = files.has(wire) ? jsonLines(files.get(wire)).rows : [];
|
|
32608
|
+
const messages = rows2.flatMap((r, i) => {
|
|
32609
|
+
const m = message(r, nativeText(r.id) || `context:${i}`);
|
|
32610
|
+
return m ? [m] : [];
|
|
32611
|
+
});
|
|
32612
|
+
if (!messages.length) throw new Error("No readable Kimi context messages.");
|
|
32613
|
+
return {
|
|
32614
|
+
nativeID,
|
|
32615
|
+
messages,
|
|
32616
|
+
partial: true,
|
|
32617
|
+
session: { created: seconds(wireRows.find((r) => r.timestamp)?.timestamp) },
|
|
32618
|
+
note: "Kimi persisted context, including tool messages. Earlier compacted context and wire-only activity may be absent. Context messages carry no reliable turn times; wire times are used only for session placement."
|
|
32619
|
+
};
|
|
32620
|
+
}
|
|
32621
|
+
function museHistory(path, files) {
|
|
32622
|
+
const { rows: rows2 } = jsonLines(files.get(path));
|
|
32623
|
+
const meta = rows2.find((r) => r.payload_type === "runtime.session.metadata");
|
|
32624
|
+
const ids = new Set(rows2.map((r) => text(r.stream?.id)).filter(Boolean));
|
|
32625
|
+
if (!meta || ids.size > 1) throw new Error("Unsupported Muse stream.");
|
|
32626
|
+
const nativeID = [...ids][0] || basename8(dirname6(path));
|
|
32627
|
+
const messages = rows2.flatMap((r, i) => {
|
|
32628
|
+
const event = object(r.payload?.event), body2 = object(event.message);
|
|
32629
|
+
const at2 = typeof r.recorded_at === "number" && r.recorded_at >= 1e15 ? r.recorded_at / 1e3 : typeof r.recorded_at === "number" && r.recorded_at < 1e12 ? seconds(r.recorded_at) : time(r.recorded_at);
|
|
32630
|
+
const m = message(body2, nativeText(r.sequence) || `record:${i}`, at2);
|
|
32631
|
+
if (m) return [m];
|
|
32632
|
+
return text(event.kind) ? [recordEvent(r, nativeText(r.sequence) || `record:${i}`, event.kind, at2, event.text)] : [];
|
|
32633
|
+
});
|
|
32634
|
+
return {
|
|
32635
|
+
nativeID,
|
|
32636
|
+
messages,
|
|
32637
|
+
partial: true,
|
|
32638
|
+
session: { directory: text(meta.payload?.record?.workspace_root), model: text(meta.payload?.record?.model_id) || "Unknown model", provider: text(meta.payload?.record?.provider_id) },
|
|
32639
|
+
note: "Muse runtime session events and recognized message payloads. Unsupported event content remains partial; no session ancestry is inferred from timestamps."
|
|
32640
|
+
};
|
|
32641
|
+
}
|
|
32642
|
+
function junieHistory(path, files) {
|
|
32643
|
+
const { rows: rows2 } = jsonLines(files.get(path));
|
|
32644
|
+
const messages = rows2.flatMap((r, i) => {
|
|
32645
|
+
const e10 = object(r.event?.agentEvent || r.event), kind = text(e10.kind);
|
|
32646
|
+
if (!kind || /State|Snapshot/.test(kind)) return [];
|
|
32647
|
+
return [recordEvent(r, text(r.id) || `event:${i}`, kind, r.timestampMs, e10.text ?? e10.prompt ?? e10.message)];
|
|
32648
|
+
});
|
|
32649
|
+
if (!messages.length) throw new Error("No supported Junie events.");
|
|
32650
|
+
return { nativeID: basename8(dirname6(path)), messages, partial: true, note: "Junie recorded agent events. State snapshots and opaque event bodies are omitted; events without an authoritative message role are not counted as user turns." };
|
|
32651
|
+
}
|
|
32652
|
+
function augmentHistory(path, files) {
|
|
32653
|
+
const r = object(json2(files.get(path)));
|
|
32654
|
+
if (!text(r.sessionId) || !Array.isArray(r.chatHistory)) throw new Error("Unsupported Augment session.");
|
|
32655
|
+
const messages = r.chatHistory.flatMap((turn, i) => {
|
|
32656
|
+
const exchange = object(turn.exchange), id2 = nativeText(turn.sequenceId) || `turn:${i}`;
|
|
32657
|
+
return [recordEvent(turn, id2, turn.completed === true ? "Completed exchange" : "Incomplete exchange", turn.finishedAt, exchange.response_text)];
|
|
32658
|
+
});
|
|
32659
|
+
return { nativeID: r.sessionId, messages, partial: true, session: { title: text(r.title), created: time(r.created), model: text(r.agentState?.modelId) || "Unknown model" }, note: "Augment saved exchange records. Typed response-node payloads and absent prompts are not reconstructed into conversations." };
|
|
32660
|
+
}
|
|
32661
|
+
function fxHistory(path, files) {
|
|
32662
|
+
const usage = object(json2(files.get(path))), meta = sidecar2(files, join19(dirname6(path), "session.json"));
|
|
32663
|
+
const id2 = text(usage.session_id) || text(meta.id);
|
|
32664
|
+
if (!id2 || !usage.snapshot || meta.id && meta.id !== id2) throw new Error("Unsupported Fx session metadata.");
|
|
32665
|
+
return {
|
|
32666
|
+
nativeID: id2,
|
|
32667
|
+
messages: [],
|
|
32668
|
+
partial: true,
|
|
32669
|
+
session: { created: time(meta.created_at_ms), directory: text(meta.workspace_root), title: text(meta.title) },
|
|
32670
|
+
note: "Fx session metadata only. The usage snapshot is cumulative; it contains no conversation or message timestamps."
|
|
32671
|
+
};
|
|
32672
|
+
}
|
|
32673
|
+
var definitions = [
|
|
32674
|
+
...piSources,
|
|
32675
|
+
{ id: "kiro", name: "Kiro", roots: () => [homePath(".kiro/sessions"), appPath("Kiro/User/globalStorage/kiro.kiroagent")], match: (p) => p.endsWith("/session.json") || /^cli\/[^/]+\.json$/.test(p), extras: (p) => [basename8(p) === "session.json" ? join19(dirname6(p), "messages.jsonl") : p.replace(/\.json$/, ".jsonl")], parse: kiroHistory },
|
|
32676
|
+
{ id: "mux", name: "Mux", roots: () => [envHome("MUX_ROOT", ".mux", "sessions")], match: (p) => p.endsWith("/chat.jsonl"), extras: (p) => [join19(dirname6(p), "chat-archive.jsonl")], parse: muxHistory },
|
|
32677
|
+
{ id: "devin-desktop", name: "Devin Desktop", roots: () => [appPath("Devin/User/acp-events")], match: (p) => /\.(ndjson|jsonl)$/.test(p), parse: devinDesktopHistory },
|
|
32678
|
+
{ id: "openclaw", name: "OpenClaw", roots: openclawRoots, match: (p) => /(?:^|\/)sessions\/.*\.jsonl(?:\.[\w.-]+)?$/.test(p) || /session-sqlite-import-archive\/.*\.jsonl\./.test(p), parse: piParse },
|
|
32679
|
+
{ id: "amp", name: "Amp", roots: () => [dataPath("amp/threads"), tokscalePath("amp-cache/threads")], match: (p) => p.endsWith(".json"), parse: ampHistory },
|
|
32680
|
+
{ id: "droid", name: "Factory Droid", roots: () => [envHome("FACTORY_HOME", ".factory", "sessions")] },
|
|
32681
|
+
{ id: "codebuff", name: "Codebuff", roots: () => process.env.CODEBUFF_DATA_DIR ? [process.env.CODEBUFF_DATA_DIR] : ["manicode", "manicode-dev", "manicode-staging"].map((p) => configPath(p)), match: (p) => p.endsWith("/chat-messages.json"), parse: (p, f) => codebuffHistory(p, f, false) },
|
|
32682
|
+
{ id: "freebuff", name: "Freebuff", roots: () => process.env.FREEBUFF_DATA_DIR ? [process.env.FREEBUFF_DATA_DIR] : process.env.CODEBUFF_DATA_DIR ? [process.env.CODEBUFF_DATA_DIR] : ["manicode", "manicode-dev", "manicode-staging"].map((p) => configPath(p)), match: (p) => p.endsWith("/chat-messages.json"), parse: (p, f) => codebuffHistory(p, f, true) },
|
|
32683
|
+
{ id: "kimi", name: "Kimi CLI / Code", roots: () => [envHome("KIMI_HOME", ".kimi", "sessions"), envHome("KIMI_CODE_HOME", ".kimi-code", "sessions")], match: (p) => p.endsWith("/context.jsonl"), extras: (p) => [join19(dirname6(p), "wire.jsonl")], parse: kimiHistory },
|
|
32684
|
+
{ id: "grok", name: "Grok Build", roots: () => [envHome("GROK_HOME", ".grok", "sessions")], match: (p) => p.endsWith("/updates.jsonl"), parse: (p, f) => acpHistory(p, f.get(p)) },
|
|
32685
|
+
{ id: "jcode", name: "Jcode", roots: () => [envHome("JCODE_HOME", ".jcode", "sessions")], match: (p) => /(?:^|\/)session_[^/]+\.json$/.test(p), extras: (p) => [p.replace(/\.json$/, ".journal.jsonl")], parse: jcodeHistory },
|
|
32686
|
+
{ id: "cherry-studio", name: "Cherry Studio", roots: () => [appPath("CherryStudio/Data/Agents/.claude/projects"), appPath("CherryStudio/.claude/projects")] },
|
|
32687
|
+
{ id: "command-code", name: "Command Code", roots: () => [homePath(".commandcode/projects")] },
|
|
32688
|
+
{ id: "zcode", name: "ZCode", roots: () => [homePath(".zcode/projects")] },
|
|
32689
|
+
{ id: "codebuddy", name: "CodeBuddy", roots: () => [homePath(".codebuddy/projects")] },
|
|
32690
|
+
{ id: "workbuddy", name: "WorkBuddy", roots: () => [homePath(".workbuddy-ai/projects"), homePath(".workbuddy/projects")] },
|
|
32691
|
+
{ id: "proma", name: "Proma", roots: promaRoots },
|
|
32692
|
+
{ id: "deepseek-harness", name: "DeepSeek Harness", roots: () => [envHome("DSH_HOME", ".dsh", "sessions")], match: (p) => /(?:^|\/)session(?:\.v\d+)?\.jsonl(?:\.zstd)?$/.test(p), parse: (p, f) => dshHistory(f.get(p)) },
|
|
32693
|
+
{ id: "aside", name: "Aside", roots: () => [homePath(".aside/u")], match: (p) => /^[^/]+\/sessions\/[^/]+\/messages\.jsonl$/.test(p), parse(p, f) {
|
|
32694
|
+
const h3 = envelopeHistory(p, f.get(p));
|
|
32695
|
+
h3.nativeID = `${basename8(dirname6(dirname6(dirname6(p))))}/${basename8(dirname6(p))}`;
|
|
32696
|
+
return h3;
|
|
32697
|
+
} },
|
|
32698
|
+
{ id: "vibe", name: "Mistral Vibe", roots: () => [envHome("VIBE_HOME", ".vibe", "logs/session")], match: (p) => p.endsWith("/meta.json") || /^[^/]+\.json$/.test(p), extras: (p) => basename8(p) === "meta.json" ? [join19(dirname6(p), "messages.jsonl")] : [], parse: vibeHistory },
|
|
32699
|
+
{ id: "muse", name: "Muse Code", roots: () => [dataPath("muse/sessions")], match: (p) => p.endsWith("/session.jsonl"), parse: museHistory },
|
|
32700
|
+
{ id: "junie", name: "Junie", roots: () => [homePath(".junie/sessions")], match: (p) => p.endsWith("/events.jsonl"), parse: junieHistory, description: "Local agent event history \xB7 partial conversation coverage" },
|
|
32701
|
+
{ id: "augment", name: "Augment Code", roots: () => [homePath(".augment/sessions")], match: (p) => p.endsWith(".json"), parse: augmentHistory, description: "Local exchange records \xB7 partial conversation coverage" },
|
|
32702
|
+
{ id: "fx", name: "Fx", roots: () => [homePath(".fx/sessions")], match: (p) => p.endsWith("/usage-v2.json"), extras: (p) => [join19(dirname6(p), "session.json")], parse: fxHistory, history: false, description: "Local native session metadata \xB7 conversation unavailable in usage snapshots" },
|
|
32703
|
+
{ id: "minimax-code", name: "MiniMax Code", roots: () => [join19(process.env.TOKSCALE_HEADLESS_DIR || tokscalePath("headless"), "mcode")], parse(p, f) {
|
|
32704
|
+
const value = f.get(p), { rows: rows2 } = jsonLines(value), ids = new Set(rows2.map((r) => text(r.sessionId)).filter(Boolean));
|
|
32705
|
+
if (ids.size !== 1) throw new Error("Headless capture needs one explicit session identity.");
|
|
32706
|
+
const messages = rows2.flatMap((r, i) => {
|
|
32707
|
+
const m = message(object(r.message), text(r.message?.id) || `record:${i}`);
|
|
32708
|
+
return m ? [m] : [];
|
|
32709
|
+
});
|
|
32710
|
+
return { nativeID: [...ids][0], messages, partial: true, note: "Previously captured MiniMax stream JSON only; no command is executed. User input and transient streaming records may be absent." };
|
|
32711
|
+
} },
|
|
32712
|
+
{ id: "opencodereview", name: "OpenCodeReview", roots: () => [homePath(".opencodereview/sessions")], parse(p, f) {
|
|
32713
|
+
const { rows: rows2 } = jsonLines(f.get(p)), header = rows2.find((r) => r.type === "session_start");
|
|
32714
|
+
if (!header) throw new Error("Unsupported review session.");
|
|
32715
|
+
return { nativeID: text(header.session_id || header.sessionId) || fileID(p), messages: rows2.filter((r) => r.type === "llm_response").map((r, i) => recordEvent(r, text(r.id) || `response:${i}`, "Review response", r.timestamp, r.content ?? r.response)), partial: true, session: { directory: text(header.cwd), created: time(header.timestamp) }, note: "OpenCodeReview saved response events. Usage-only records do not establish conversation text or user prompts." };
|
|
32716
|
+
} }
|
|
32717
|
+
];
|
|
32718
|
+
var fileHarnessAdapters = definitions.map((source) => localFileAdapter({
|
|
32719
|
+
...source,
|
|
32720
|
+
roots: harnessRoots(source.id, source.roots),
|
|
32721
|
+
match: source.match || jsonl,
|
|
32722
|
+
description: source.description || "Local transcript snapshots \xB7 bounded, read-only import",
|
|
32723
|
+
capabilities: { history: source.history !== false, forks: false, children: false, files: false },
|
|
32724
|
+
parse: source.parse || ((p, f) => envelopeHistory(p, f.get(p)))
|
|
32725
|
+
}));
|
|
32726
|
+
|
|
32727
|
+
// server/adapters/harness-databases.ts
|
|
32728
|
+
import { lstat as lstat5, readFile as readFile4 } from "node:fs/promises";
|
|
32729
|
+
import { isAbsolute as isAbsolute4, join as join20 } from "node:path";
|
|
32730
|
+
var parsedContent = (v3) => typeof v3 === "string" && /^[\[{]/.test(v3.trim()) ? optionalJSON(v3) : v3;
|
|
32731
|
+
var millisOrSeconds = (v3) => typeof v3 === "number" && v3 > 0 && v3 < 1e10 ? seconds(v3) : time(v3);
|
|
32732
|
+
function dbPaths(id2, defaults, match) {
|
|
32733
|
+
return async () => {
|
|
32734
|
+
const override = process.env[`SESSIONGRAPH_${id2.replace(/-/g, "_").toUpperCase()}_HISTORY_DIR`];
|
|
32735
|
+
return override ? (await historyFiles([override], match)).files : existingDatabases(defaults());
|
|
32736
|
+
};
|
|
32737
|
+
}
|
|
32738
|
+
function sessionHistory(r, messages, note) {
|
|
32739
|
+
return {
|
|
32740
|
+
nativeID: nativeText(r.id),
|
|
32741
|
+
messages,
|
|
32742
|
+
partial: true,
|
|
32743
|
+
session: { title: text(r.title || r.name), directory: text(r.directory || r.cwd || r.working_directory || r.working_dir), created: time(r.created_at), updated: time(r.updated_at), model: text(r.model) || "Unknown model", provider: text(r.provider || r.provider_name) },
|
|
32744
|
+
note
|
|
32745
|
+
};
|
|
32746
|
+
}
|
|
32747
|
+
var hermes = {
|
|
32748
|
+
id: "hermes",
|
|
32749
|
+
name: "Hermes Agent",
|
|
32750
|
+
paths: async () => (await historyFiles(harnessRoots("hermes", () => [envHome("HERMES_HOME", ".hermes")])(), (p) => p === "state.db" || /^profiles\/[^/]+\/state\.db$/.test(p))).files,
|
|
32751
|
+
table: "sessions",
|
|
32752
|
+
key: "id",
|
|
32753
|
+
fields: ["id", "title", "cwd", "model", "billing_provider", "started_at", "last_activity_at", "parent_session_id", "source", "model_config", "archived"],
|
|
32754
|
+
parse(db2, r) {
|
|
32755
|
+
const messages = rows(db2, "messages", ["id", "message_uid", "role", "content", "timestamp", "tool_call_id", "tool_calls", "tool_name", "_compressed_summary"], ["session_id", r.id], "id").map((m) => {
|
|
32756
|
+
if (m._compressed_summary) return recordEvent(m, text(m.message_uid) || nativeText(m.id), "Context summary", seconds(m.timestamp), m.content);
|
|
32757
|
+
return conversationMessage({ role: m.role, content: parsedContent(m.content), timestamp: seconds(m.timestamp), tool_call_id: m.tool_call_id, toolCalls: optionalJSON(m.tool_calls), toolName: m.tool_name }, text(m.message_uid) || nativeText(m.id));
|
|
32758
|
+
});
|
|
32759
|
+
const h3 = sessionHistory({ ...r, created_at: seconds(r.started_at), updated_at: seconds(r.last_activity_at), provider: r.billing_provider }, messages, "Hermes saved message rows, including retained summaries. Compression/reset parent links are not subagent ancestry. Session usage aggregates are not used as message times.");
|
|
32760
|
+
if (r.source === "subagent") h3.parent = text(r.parent_session_id) || void 0;
|
|
32761
|
+
h3.fork = text(optionalJSON(r.model_config)._branched_from) || void 0;
|
|
32762
|
+
h3.session.archived = r.archived === 1;
|
|
32763
|
+
return h3;
|
|
32764
|
+
}
|
|
32765
|
+
};
|
|
32766
|
+
var goose = {
|
|
32767
|
+
id: "goose",
|
|
32768
|
+
name: "Goose",
|
|
32769
|
+
paths: dbPaths("goose", () => process.env.GOOSE_PATH_ROOT && isAbsolute4(process.env.GOOSE_PATH_ROOT) ? [join20(process.env.GOOSE_PATH_ROOT, "data/sessions/sessions.db")] : [dataPath("goose/sessions/sessions.db"), dataPath("Block/goose/sessions/sessions.db"), appPath("goose/sessions/sessions.db"), appPath("Block/goose/sessions/sessions.db"), appPath("Block/goose/data/sessions/sessions.db")], (p) => p.endsWith("sessions.db")),
|
|
32770
|
+
table: "sessions",
|
|
32771
|
+
key: "id",
|
|
32772
|
+
fields: ["id", "name", "description", "working_dir", "created_at", "updated_at", "model_config_json", "provider_name", "session_type", "parent_session_id", "archived_at"],
|
|
32773
|
+
parse(db2, r) {
|
|
32774
|
+
const messages = rows(db2, "messages", ["id", "message_id", "role", "content_json", "created_timestamp"], ["session_id", r.id], "id").map((m) => {
|
|
32775
|
+
const content = sqliteJSON(m.content_json);
|
|
32776
|
+
return conversationMessage({ role: m.role, content, timestamp: millisOrSeconds(m.created_timestamp) }, text(m.message_id) || nativeText(m.id));
|
|
32777
|
+
});
|
|
32778
|
+
const h3 = sessionHistory({ ...r, name: r.name || r.description, model: optionalJSON(r.model_config_json).model_name }, messages, "Goose saved conversation rows. Tool/content variants not represented as text are marked partial.");
|
|
32779
|
+
if (r.session_type === "sub_agent") h3.parent = text(r.parent_session_id) || void 0;
|
|
32780
|
+
h3.session.archived = Boolean(r.archived_at);
|
|
32781
|
+
return h3;
|
|
32782
|
+
}
|
|
32783
|
+
};
|
|
32784
|
+
var openclaw = {
|
|
32785
|
+
id: "openclaw",
|
|
32786
|
+
name: "OpenClaw",
|
|
32787
|
+
paths: async () => (await historyFiles(harnessRoots("openclaw", openclawRoots)(), (p) => /^[^/]+\/agent\/openclaw-agent\.sqlite$/.test(p))).files,
|
|
32788
|
+
table: (db2) => hasTable(db2, "session_windows") ? "session_windows" : "sessions",
|
|
32789
|
+
key: "session_id",
|
|
32790
|
+
fields: ["session_id", "created_at", "updated_at", "model", "model_provider"],
|
|
32791
|
+
parse(db2, r) {
|
|
32792
|
+
const events = rows(db2, "transcript_events", ["seq", "event_json", "event_zstd"], ["session_id", r.session_id], "seq");
|
|
32793
|
+
const messages = events.flatMap((e10) => {
|
|
32794
|
+
const raw = object(sqliteJSON(e10.event_json ?? e10.event_zstd));
|
|
32795
|
+
if (raw.type !== "message") return [];
|
|
32796
|
+
const m = message(object(raw.message), text(raw.id) || `event:${e10.seq}`, raw.timestamp ?? raw.message?.timestamp);
|
|
32797
|
+
return m ? [m] : [];
|
|
32798
|
+
});
|
|
32799
|
+
return sessionHistory({ ...r, id: r.session_id, provider: r.model_provider }, messages, "OpenClaw authoritative transcript events, including zstd payloads. Database sessions take precedence over migrated JSONL copies. Session rollover and event-parent links are not session ancestry.");
|
|
32800
|
+
}
|
|
32801
|
+
};
|
|
32802
|
+
function forkStore(id2, name, defaultPath, desktop) {
|
|
32803
|
+
return {
|
|
32804
|
+
id: id2,
|
|
32805
|
+
name,
|
|
32806
|
+
paths: dbPaths(id2, () => [defaultPath()], (p) => /\.(db|sqlite)$/.test(p)),
|
|
32807
|
+
table: "session",
|
|
32808
|
+
key: "id",
|
|
32809
|
+
fields: ["id", "title", "directory", "time_created", "time_updated", "version"],
|
|
32810
|
+
parse(db2, r) {
|
|
32811
|
+
if (desktop !== void 0 && text(r.version).startsWith("desktop-") !== desktop) return;
|
|
32812
|
+
const messages = rows(db2, "message", ["id", "data", "time_created"], ["session_id", r.id], "time_created").map((m) => {
|
|
32813
|
+
const body2 = object(sqliteJSON(m.data));
|
|
32814
|
+
let content = body2.parts ?? body2.content;
|
|
32815
|
+
if (hasTable(db2, "part")) content = rows(db2, "part", ["id", "data"], ["message_id", m.id], "id").map((p) => sqliteJSON(p.data));
|
|
32816
|
+
if (Array.isArray(content)) content = content.flatMap((p) => {
|
|
32817
|
+
if (p.type === "tool") {
|
|
32818
|
+
const call = { type: "tool_use", id: p.callID, name: p.tool, input: p.state?.input };
|
|
32819
|
+
return p.state?.status === "completed" || p.state?.status === "error" ? [call, { type: "tool_result", tool_use_id: p.callID, content: p.state.output ?? p.state.error, is_error: p.state.status === "error" }] : [call];
|
|
32820
|
+
}
|
|
32821
|
+
return [p.type === "reasoning" ? { type: "thinking", thinking: p.text } : p];
|
|
32822
|
+
});
|
|
32823
|
+
return conversationMessage({ ...body2, content, timestamp: body2.time?.created ?? m.time_created }, text(m.id));
|
|
32824
|
+
});
|
|
32825
|
+
return sessionHistory({ ...r, created_at: r.time_created, updated_at: r.time_updated }, messages, `${name} native session/message/part tables. Only this harness's database is read; ancestry semantics not explicitly exposed by this format remain unknown.`);
|
|
32826
|
+
}
|
|
32827
|
+
};
|
|
32828
|
+
}
|
|
32829
|
+
var zed = {
|
|
32830
|
+
id: "zed",
|
|
32831
|
+
name: "Zed Agent",
|
|
32832
|
+
paths: dbPaths("zed", () => [process.platform === "darwin" ? appPath("Zed/threads/threads.db") : process.platform === "win32" ? join20(process.env.LOCALAPPDATA || homePath("AppData/Local"), "Zed/threads/threads.db") : dataPath("zed/threads/threads.db")], (p) => p.endsWith("threads.db")),
|
|
32833
|
+
table: "threads",
|
|
32834
|
+
key: "id",
|
|
32835
|
+
fields: ["id", "summary", "created_at", "updated_at", "data_type", "data", "folder_paths"],
|
|
32836
|
+
parse(db2, r) {
|
|
32837
|
+
if (!["json", "zstd"].includes(r.data_type)) throw new Error("Unsupported Zed encoding.");
|
|
32838
|
+
const body2 = object(sqliteJSON(r.data));
|
|
32839
|
+
if (body2.imported === true) return;
|
|
32840
|
+
if (!Array.isArray(body2.messages)) throw new Error("Unsupported Zed thread.");
|
|
32841
|
+
const messages = body2.messages.map((raw, i) => {
|
|
32842
|
+
const user = object(raw.User), agent = object(raw.Agent), b = raw.User ? user : agent;
|
|
32843
|
+
if (!raw.User && !raw.Agent) return recordEvent(raw, `message:${i}`, "Context checkpoint", 0, raw.Compaction?.Summary);
|
|
32844
|
+
const content = (Array.isArray(b.content) ? b.content : []).map((p) => typeof p.Text === "string" ? { type: "text", text: p.Text } : p.Thinking ? { type: "thinking", thinking: p.Thinking.text } : p.ToolUse ? { type: "tool_use", ...p.ToolUse } : { type: "unsupported" });
|
|
32845
|
+
return conversationMessage({ role: raw.User ? "user" : "assistant", content }, text(b.id) || `message:${i}`);
|
|
32846
|
+
});
|
|
32847
|
+
const folders = optionalJSON(r.folder_paths);
|
|
32848
|
+
return sessionHistory({ ...r, title: r.summary || body2.title, directory: Array.isArray(folders) && folders.length === 1 ? folders[0] : "", model: body2.model?.model, provider: body2.model?.provider }, messages, "Zed native thread messages, JSON or zstd. Native message times are absent in this format; no timing is invented. Imported/shared copies and external ACP histories are excluded from this native-thread store.");
|
|
32849
|
+
}
|
|
32850
|
+
};
|
|
32851
|
+
var devin = {
|
|
32852
|
+
id: "devin",
|
|
32853
|
+
name: "Devin CLI",
|
|
32854
|
+
paths: dbPaths("devin", () => [dataPath("devin/cli/sessions.db")], (p) => p.endsWith("sessions.db")),
|
|
32855
|
+
table: "sessions",
|
|
32856
|
+
key: "id",
|
|
32857
|
+
fields: ["id", "working_directory", "model", "title", "created_at", "last_activity_at"],
|
|
32858
|
+
parse(db2, r) {
|
|
32859
|
+
const messages = rows(db2, "message_nodes", ["node_id", "chat_message", "created_at"], ["session_id", r.id], "row_id").flatMap((m) => {
|
|
32860
|
+
const raw = object(sqliteJSON(m.chat_message)), result = message(raw, nativeText(m.node_id), millisOrSeconds(m.created_at));
|
|
32861
|
+
return result ? [result] : [recordEvent(raw, nativeText(m.node_id), "Devin message node", millisOrSeconds(m.created_at))];
|
|
32862
|
+
});
|
|
32863
|
+
return sessionHistory({ ...r, created_at: millisOrSeconds(r.created_at), updated_at: millisOrSeconds(r.last_activity_at) }, messages, "Devin saved message nodes. All retained in-session branches are included; node-parent edges are not session ancestry.");
|
|
32864
|
+
}
|
|
32865
|
+
};
|
|
32866
|
+
var crushDirectories = /* @__PURE__ */ new Map();
|
|
32867
|
+
var crush = {
|
|
32868
|
+
id: "crush",
|
|
32869
|
+
name: "Crush",
|
|
32870
|
+
table: "sessions",
|
|
32871
|
+
key: "id",
|
|
32872
|
+
fields: ["id", "title", "created_at", "updated_at", "cost"],
|
|
32873
|
+
paths: async () => {
|
|
32874
|
+
const override = process.env.SESSIONGRAPH_CRUSH_HISTORY_DIR;
|
|
32875
|
+
if (override) return (await historyFiles([override], (p) => p.endsWith("crush.db"))).files;
|
|
32876
|
+
const registry = join20(process.env.CRUSH_GLOBAL_DATA || dataPath("crush"), "projects.json");
|
|
32877
|
+
try {
|
|
32878
|
+
const stat6 = await lstat5(registry);
|
|
32879
|
+
if (!stat6.isFile() || stat6.size > 2e6) throw new Error("Unsupported Crush registry.");
|
|
32880
|
+
const raw = JSON.parse(await readFile4(registry, "utf8"));
|
|
32881
|
+
const paths = [];
|
|
32882
|
+
for (const p of Array.isArray(raw.projects) ? raw.projects.slice(0, 2e4) : []) {
|
|
32883
|
+
if (!isAbsolute4(text(p.path)) || !text(p.data_dir)) continue;
|
|
32884
|
+
const path = join20(isAbsolute4(p.data_dir) ? p.data_dir : join20(p.path, p.data_dir), "crush.db");
|
|
32885
|
+
paths.push(path);
|
|
32886
|
+
crushDirectories.set(path, p.path);
|
|
32887
|
+
}
|
|
32888
|
+
return existingDatabases(paths);
|
|
32889
|
+
} catch (e10) {
|
|
32890
|
+
if (e10.code === "ENOENT") return [];
|
|
32891
|
+
throw e10;
|
|
32892
|
+
}
|
|
32893
|
+
},
|
|
32894
|
+
parse(db2, r, path) {
|
|
32895
|
+
const messages = rows(db2, "messages", ["id", "role", "parts", "created_at", "is_summary_message"], ["session_id", r.id], "created_at").map((m) => {
|
|
32896
|
+
const parts = sqliteJSON(m.parts);
|
|
32897
|
+
const content = Array.isArray(parts) ? parts.map((p) => p.data && typeof p.data === "object" ? { type: p.type, ...p.data } : p) : [];
|
|
32898
|
+
if (m.is_summary_message) return recordEvent(m, text(m.id), "Context summary", millisOrSeconds(m.created_at), content);
|
|
32899
|
+
return conversationMessage({ role: m.role, content, timestamp: millisOrSeconds(m.created_at) }, text(m.id));
|
|
32900
|
+
});
|
|
32901
|
+
const h3 = sessionHistory({ ...r, directory: crushDirectories.get(path) || "", created_at: millisOrSeconds(r.created_at), updated_at: millisOrSeconds(r.updated_at) }, messages, "Crush per-project saved message parts. Session cost is retained when recorded; unreliable token counters remain unknown.");
|
|
32902
|
+
h3.session.cost = typeof r.cost === "number" && r.cost >= 0 ? r.cost : null;
|
|
32903
|
+
return h3;
|
|
32904
|
+
}
|
|
32905
|
+
};
|
|
32906
|
+
var octofriend = {
|
|
32907
|
+
id: "octofriend",
|
|
32908
|
+
name: "Octofriend",
|
|
32909
|
+
paths: dbPaths("octofriend", () => [homePath(".local/share/octofriend/sqlite.db")], (p) => p.endsWith("sqlite.db")),
|
|
32910
|
+
table: "trees",
|
|
32911
|
+
key: "id",
|
|
32912
|
+
fields: ["id", "name", "cwd", "updated_at"],
|
|
32913
|
+
parse(db2, r) {
|
|
32914
|
+
const nodes = rows(db2, "tree_nodes", ["id", "history_item_id", "parent_id", "created_at"], ["tree_id", r.id], "id");
|
|
32915
|
+
const messages = [];
|
|
32916
|
+
let bytes2 = 0;
|
|
32917
|
+
for (const node of nodes) {
|
|
32918
|
+
const item = rows(db2, "history_items", ["llm_ir_id"], ["id", node.history_item_id], void 0, 1)[0];
|
|
32919
|
+
if (!item?.llm_ir_id) continue;
|
|
32920
|
+
const row = rows(db2, "llm_irs", ["json"], ["id", item.llm_ir_id], void 0, 1)[0];
|
|
32921
|
+
bytes2 += text(row?.json).length;
|
|
32922
|
+
if (bytes2 > 32e6) throw new Error("Octofriend session exceeds the snapshot limit.");
|
|
32923
|
+
const payload = object(sqliteJSON(row?.json));
|
|
32924
|
+
if (payload.version !== "octo-llm-ir/v1") throw new Error("Unsupported Octofriend IR version.");
|
|
32925
|
+
const ir = object(payload.ir), content = Array.isArray(ir.content) ? ir.content.map((p) => p.type === "text" ? { type: "text", text: p.content } : p) : ir.content;
|
|
32926
|
+
const m = message({ ...ir, content, role: ir.role === "tool-output" ? "tool" : ir.role }, nativeText(node.id), node.created_at);
|
|
32927
|
+
messages.push(m || recordEvent(ir, nativeText(node.id), text(ir.role), node.created_at));
|
|
32928
|
+
}
|
|
32929
|
+
const h3 = sessionHistory({ ...r, created_at: nodes[0]?.created_at }, messages, "Octofriend native history trees and versioned IR. Retained branches are preserved; nested subagent trajectories and unsupported IR variants are partial. Provider attribution remains with the harness.");
|
|
32930
|
+
return h3;
|
|
32931
|
+
}
|
|
32932
|
+
};
|
|
32933
|
+
var unsloth = {
|
|
32934
|
+
id: "unsloth",
|
|
32935
|
+
name: "Unsloth Studio",
|
|
32936
|
+
paths: dbPaths("unsloth", () => [envHome("UNSLOTH_STUDIO_HOME", ".unsloth/studio", "studio.db")], (p) => p.endsWith("studio.db")),
|
|
32937
|
+
table: "chat_threads",
|
|
32938
|
+
key: "id",
|
|
32939
|
+
fields: ["id", "title", "model_id", "created_at", "updated_at", "archived", "forked_from_thread_id"],
|
|
32940
|
+
parse(db2, r) {
|
|
32941
|
+
const messages = rows(db2, "chat_messages", ["id", "role", "content_json", "created_at"], ["thread_id", r.id], "created_at").flatMap((row) => {
|
|
32942
|
+
const m = message({ role: row.role, content: sqliteJSON(row.content_json) }, text(row.id), seconds(row.created_at));
|
|
32943
|
+
return m ? [m] : [];
|
|
32944
|
+
});
|
|
32945
|
+
const h3 = sessionHistory({ ...r, model: r.model_id, created_at: seconds(r.created_at), updated_at: seconds(r.updated_at) }, messages, "Unsloth Studio native chat threads and message content. Content-free API usage, training runs, attachments, and unsupported tool blocks are excluded.");
|
|
32946
|
+
h3.fork = text(r.forked_from_thread_id) || void 0;
|
|
32947
|
+
h3.session.archived = r.archived === 1;
|
|
32948
|
+
return h3;
|
|
32949
|
+
}
|
|
32950
|
+
};
|
|
32951
|
+
var kiro = {
|
|
32952
|
+
id: "kiro",
|
|
32953
|
+
name: "Kiro",
|
|
32954
|
+
paths: dbPaths("kiro", () => [appPath("kiro-cli/data.sqlite3"), dataPath("kiro-cli/data.sqlite3")], (p) => p.endsWith("data.sqlite3")),
|
|
32955
|
+
table: "conversations_v2",
|
|
32956
|
+
key: "conversation_id",
|
|
32957
|
+
fields: ["key", "conversation_id", "value"],
|
|
32958
|
+
parse(db2, r) {
|
|
32959
|
+
const body2 = object(sqliteJSON(r.value));
|
|
32960
|
+
if (!Array.isArray(body2.history)) throw new Error("Unsupported Kiro conversation.");
|
|
32961
|
+
const messages = body2.history.flatMap((turn, i) => {
|
|
32962
|
+
const result = [], input = object(turn.user?.content), output = object(turn.assistant?.Response || turn.assistant?.ToolUse);
|
|
32963
|
+
if (typeof input.Prompt?.prompt === "string") result.push(conversationMessage({ role: "user", content: input.Prompt.prompt }, `turn:${i}:user`));
|
|
32964
|
+
if (input.ToolUseResults) result.push(conversationMessage({ role: "tool", content: input.ToolUseResults }, `turn:${i}:tool`));
|
|
32965
|
+
if (typeof output.content === "string") result.push(conversationMessage({ role: "assistant", content: output.content, timestamp: turn.request_metadata?.stream_end_timestamp_ms }, `turn:${i}:assistant`));
|
|
32966
|
+
return result;
|
|
32967
|
+
});
|
|
32968
|
+
const created = body2.history.map((t) => time(t.request_metadata?.request_start_timestamp_ms)).find(Boolean);
|
|
32969
|
+
return { nativeID: text(r.conversation_id), messages, partial: true, session: { directory: text(r.key), created, model: text(body2.model_info?.model_id) || "Unknown model" }, note: "Kiro retained SQLite conversation turns. Request-start metadata places the session; untimed user prompts remain untimed. Tool-use variants and compacted context are partial." };
|
|
32970
|
+
}
|
|
32971
|
+
};
|
|
32972
|
+
var databaseHarnessAdapters = [
|
|
32973
|
+
hermes,
|
|
32974
|
+
goose,
|
|
32975
|
+
openclaw,
|
|
32976
|
+
zed,
|
|
32977
|
+
devin,
|
|
32978
|
+
crush,
|
|
32979
|
+
octofriend,
|
|
32980
|
+
unsloth,
|
|
32981
|
+
kiro,
|
|
32982
|
+
forkStore("kilo-cli", "Kilo CLI", () => dataPath("kilo/kilo.db")),
|
|
32983
|
+
forkStore("mimo-code", "MiMo Code", () => dataPath("mimocode/mimocode.db"), false),
|
|
32984
|
+
forkStore("mimo-desktop", "Xiaomi MiMo AI", () => dataPath("mimocode/mimocode.db"), true)
|
|
32985
|
+
].map(sqliteHistoryAdapter);
|
|
32986
|
+
|
|
32987
|
+
// server/adapters/harness-metadata.ts
|
|
32988
|
+
import { basename as basename9 } from "node:path";
|
|
32989
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
32990
|
+
import { lstat as lstat6 } from "node:fs/promises";
|
|
32991
|
+
function protoFields(value) {
|
|
32992
|
+
if (value.length > 2e6) throw new Error("Trajectory metadata exceeds 2 MB.");
|
|
32993
|
+
const fields2 = /* @__PURE__ */ new Map();
|
|
32994
|
+
let at2 = 0, count = 0;
|
|
32995
|
+
const varint = () => {
|
|
32996
|
+
let n = 0n;
|
|
32997
|
+
for (let i = 0; i < 10 && at2 < value.length; i++) {
|
|
32998
|
+
const b = value[at2++];
|
|
32999
|
+
n |= BigInt(b & 127) << BigInt(7 * i);
|
|
33000
|
+
if (!(b & 128)) return n;
|
|
33001
|
+
}
|
|
33002
|
+
throw new Error("Invalid protobuf varint.");
|
|
33003
|
+
};
|
|
33004
|
+
while (at2 < value.length) {
|
|
33005
|
+
if (++count > 1e4) throw new Error("Too many protobuf fields.");
|
|
33006
|
+
const key = Number(varint()), field = key >>> 3, wire = key & 7;
|
|
33007
|
+
if (!field) throw new Error("Invalid protobuf field.");
|
|
33008
|
+
if (wire === 0) fields2.set(field, varint());
|
|
33009
|
+
else if (wire === 2) {
|
|
33010
|
+
const length = Number(varint());
|
|
33011
|
+
if (!Number.isSafeInteger(length) || length > value.length - at2) throw new Error("Invalid protobuf length.");
|
|
33012
|
+
fields2.set(field, value.subarray(at2, at2 + length));
|
|
33013
|
+
at2 += length;
|
|
33014
|
+
} else if (wire === 1 || wire === 5) {
|
|
33015
|
+
at2 += wire === 1 ? 8 : 4;
|
|
33016
|
+
if (at2 > value.length) throw new Error("Truncated protobuf field.");
|
|
33017
|
+
} else throw new Error("Unsupported protobuf wire type.");
|
|
33018
|
+
}
|
|
33019
|
+
return fields2;
|
|
33020
|
+
}
|
|
33021
|
+
var nested = (v3) => v3 instanceof Uint8Array ? protoFields(v3) : /* @__PURE__ */ new Map();
|
|
33022
|
+
var antigravityDatabaseAdapters = ["antigravity-cli", "antigravity-extension"].map((id2) => sqliteHistoryAdapter({
|
|
33023
|
+
id: id2,
|
|
33024
|
+
name: id2 === "antigravity-cli" ? "Antigravity CLI" : "Antigravity IDE Extension",
|
|
33025
|
+
history: false,
|
|
33026
|
+
description: "Native session metadata only \xB7 opaque protobuf conversations",
|
|
33027
|
+
paths: async () => (await historyFiles(harnessRoots(id2, () => [envHome("GEMINI_CLI_HOME", ".gemini", id2 === "antigravity-cli" ? "antigravity-cli/conversations" : "antigravity/conversations")])(), (p) => p.endsWith(".db"))).files,
|
|
33028
|
+
table: "trajectory_metadata_blob",
|
|
33029
|
+
key: "id",
|
|
33030
|
+
fields: ["id", "data"],
|
|
33031
|
+
parse(db2, row, path) {
|
|
33032
|
+
if (!(row.data instanceof Uint8Array)) throw new Error("Unsupported trajectory metadata.");
|
|
33033
|
+
const fields2 = protoFields(row.data), timestamp = nested(fields2.get(2));
|
|
33034
|
+
const sec = timestamp.get(1), nano = timestamp.get(2);
|
|
33035
|
+
const created = typeof sec === "bigint" && sec > 0n && sec < 8640000000000n ? time(Number(sec) * 1e3 + (typeof nano === "bigint" && nano < 1000000000n ? Number(nano) / 1e6 : 0)) : 0;
|
|
33036
|
+
const uri = nested(fields2.get(1)).get(1);
|
|
33037
|
+
let directory2 = "";
|
|
33038
|
+
if (uri instanceof Uint8Array) {
|
|
33039
|
+
try {
|
|
33040
|
+
directory2 = fileURLToPath3(Buffer.from(uri).toString("utf8"));
|
|
33041
|
+
} catch {
|
|
33042
|
+
}
|
|
33043
|
+
}
|
|
33044
|
+
if (!created) throw new Error("Trajectory creation time is unavailable.");
|
|
33045
|
+
return { nativeID: basename9(path, ".db"), messages: [], partial: true, session: { created, directory: directory2 }, note: "Native Antigravity trajectory identity and creation metadata only. Protobuf conversation/step bodies are opaque; no message text, generation-time guesses, or usage-derived turns are imported." };
|
|
33046
|
+
}
|
|
33047
|
+
}));
|
|
33048
|
+
var antigravityCacheAdapter = localFileAdapter({
|
|
33049
|
+
id: "antigravity",
|
|
33050
|
+
name: "Antigravity",
|
|
33051
|
+
description: "Previously synced session metadata only \xB7 cache contains no conversations",
|
|
33052
|
+
capabilities: { history: false, forks: false, children: false, files: false },
|
|
33053
|
+
roots: harnessRoots("antigravity", () => [tokscalePath("antigravity-cache/sessions")]),
|
|
33054
|
+
match: (p) => p.endsWith(".jsonl"),
|
|
33055
|
+
parse(path, files) {
|
|
33056
|
+
const { rows: rows2 } = jsonLines(files.get(path));
|
|
33057
|
+
const ids = new Set(rows2.map((r) => text(r.sessionId)).filter(Boolean));
|
|
33058
|
+
if (ids.size !== 1) throw new Error("Unsupported Antigravity cache identity.");
|
|
33059
|
+
const created = rows2.map((r) => time(r.timestamp)).filter(Boolean).sort((a, b) => a - b)[0];
|
|
33060
|
+
if (!created) throw new Error("Antigravity cache has no recorded date.");
|
|
33061
|
+
return { nativeID: [...ids][0], messages: [], partial: true, session: { created, model: text(rows2.find((r) => r.modelId)?.modelId) || "Unknown model" }, note: "Previously synced Antigravity session identity; placement uses earliest recorded usage date. This cache has no conversation bodies. SessionGraph never runs sync or contacts the language server." };
|
|
33062
|
+
}
|
|
33063
|
+
});
|
|
33064
|
+
var usageOnlyAdapters = [
|
|
33065
|
+
{ id: "reasonix", name: "Reasonix", roots: () => [envHome("REASONIX_STATE_HOME", ".reasonix", "stats"), envHome("REASONIX_HOME", ".reasonix", "stats")], note: "Request statistics lack conversation identities and text. Reasonix transcript import is not yet supported." },
|
|
33066
|
+
{ id: "trae", name: "Trae / Trae Solo", roots: () => [tokscalePath("trae-cache/sessions")], note: "The Tokscale cache contains account usage, not local conversations." },
|
|
33067
|
+
{ id: "warp", name: "Warp / Oz", roots: () => [tokscalePath("warp-cache")], note: "The Tokscale cache contains aggregate requests and spend, not local conversations." },
|
|
33068
|
+
{ id: "lm-studio", name: "LM Studio", roots: () => [envHome("LM_STUDIO_HOME", ".lmstudio", "server-logs")], note: "Server logs contain final-response usage, not retained prompts or responses." },
|
|
33069
|
+
{ id: "hindsight", name: "Hindsight", roots: () => [envHome("HINDSIGHT_HOME", ".hindsight", "usage")], note: "Synced memory-operation usage is not agent conversation history." }
|
|
33070
|
+
].map((source) => ({
|
|
33071
|
+
id: source.id,
|
|
33072
|
+
name: source.name,
|
|
33073
|
+
description: `Detection only \xB7 ${source.note}`,
|
|
33074
|
+
capabilities: { history: false, forks: false, children: false, files: false },
|
|
33075
|
+
async detect() {
|
|
33076
|
+
for (const p of harnessRoots(source.id, source.roots)()) if ((await lstat6(p).catch(() => void 0))?.isDirectory()) return true;
|
|
33077
|
+
return false;
|
|
33078
|
+
},
|
|
33079
|
+
async discover() {
|
|
33080
|
+
return { sessions: [], projects: [], warnings: [source.note] };
|
|
33081
|
+
},
|
|
33082
|
+
async read() {
|
|
33083
|
+
throw new Error(source.note);
|
|
33084
|
+
}
|
|
33085
|
+
}));
|
|
33086
|
+
|
|
31730
33087
|
// server/adapters/index.ts
|
|
31731
|
-
var
|
|
33088
|
+
var extra = [...databaseHarnessAdapters, ...fileHarnessAdapters, antigravityCacheAdapter, ...antigravityDatabaseAdapters, ...usageOnlyAdapters];
|
|
33089
|
+
var grouped = /* @__PURE__ */ new Map();
|
|
33090
|
+
for (const adapter of extra) grouped.set(adapter.id, [...grouped.get(adapter.id) || [], adapter]);
|
|
33091
|
+
var adapters = [opencodeAdapter, claudeAdapter, codexAdapter, cursorAdapter, geminiAdapter, qwenAdapter, piAdapter, copilotAdapter, clineAdapter, rooAdapter, kiloAdapter, continueAdapter, ...[...grouped.values()].map((parts) => parts.length === 1 ? parts[0] : combineAdapters(parts))];
|
|
31732
33092
|
|
|
31733
33093
|
// server/store.ts
|
|
31734
|
-
import { DatabaseSync as
|
|
31735
|
-
import { mkdirSync as mkdirSync2, chmodSync as
|
|
31736
|
-
import { dirname as
|
|
33094
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
33095
|
+
import { mkdirSync as mkdirSync2, chmodSync as chmodSync3 } from "node:fs";
|
|
33096
|
+
import { dirname as dirname7 } from "node:path";
|
|
31737
33097
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
31738
33098
|
|
|
31739
33099
|
// src/search/persistence.ts
|
|
@@ -31768,17 +33128,17 @@ var EVENT_SCHEMA = `CREATE TABLE IF NOT EXISTS timeline_items (
|
|
|
31768
33128
|
CREATE INDEX IF NOT EXISTS timeline_time ON timeline_items(session_id,time);
|
|
31769
33129
|
CREATE INDEX IF NOT EXISTS timeline_calls ON timeline_items(session_id,call_id);
|
|
31770
33130
|
CREATE TABLE IF NOT EXISTS timeline_coverage (session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE, partial INTEGER NOT NULL, note TEXT, revision TEXT NOT NULL);`;
|
|
31771
|
-
function writeEvents(db2, id2,
|
|
33131
|
+
function writeEvents(db2, id2, rows2) {
|
|
31772
33132
|
db2.prepare("DELETE FROM timeline_items WHERE session_id=?").run(id2);
|
|
31773
33133
|
const insert = db2.prepare("INSERT OR IGNORE INTO timeline_items VALUES (?,?,?,?,?,?,?,?,?,?,?)");
|
|
31774
|
-
let ordinal = 0,
|
|
31775
|
-
for (const row of
|
|
31776
|
-
const
|
|
31777
|
-
legacy ||= !
|
|
31778
|
-
for (const event of normalizedEvents(
|
|
33134
|
+
let ordinal = 0, bytes2 = 0, partial = false, legacy = false, limited = false;
|
|
33135
|
+
for (const row of rows2) {
|
|
33136
|
+
const message2 = JSON.parse(String(row.data));
|
|
33137
|
+
legacy ||= !message2.events;
|
|
33138
|
+
for (const event of normalizedEvents(message2, id2)) {
|
|
31779
33139
|
const item = { ...event, ordinal: ordinal++ }, data = JSON.stringify(item);
|
|
31780
|
-
|
|
31781
|
-
if (ordinal > 2e5 ||
|
|
33140
|
+
bytes2 += Buffer.byteLength(data);
|
|
33141
|
+
if (ordinal > 2e5 || bytes2 > 32e6) {
|
|
31782
33142
|
limited = true;
|
|
31783
33143
|
break;
|
|
31784
33144
|
}
|
|
@@ -31796,8 +33156,8 @@ function eventSummary(row) {
|
|
|
31796
33156
|
return { id: String(row.id), kind: row.kind, role: row.role, name: String(row.name), time: row.time === null ? null : Number(row.time), timeSource: row.timeSource, ordinal: Number(row.ordinal), preview: String(row.preview), status: row.status, partial: Boolean(row.partial) };
|
|
31797
33157
|
}
|
|
31798
33158
|
var EventReadError = class extends Error {
|
|
31799
|
-
constructor(
|
|
31800
|
-
super(
|
|
33159
|
+
constructor(message2, status) {
|
|
33160
|
+
super(message2);
|
|
31801
33161
|
this.status = status;
|
|
31802
33162
|
}
|
|
31803
33163
|
status;
|
|
@@ -31810,21 +33170,21 @@ function coverage(db2, session) {
|
|
|
31810
33170
|
function readEventMarkers(db2, session) {
|
|
31811
33171
|
const end = Math.max(session.created, session.activity?.lastMessageAt || session.created);
|
|
31812
33172
|
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
|
|
31814
|
-
return { sessionID: session.id, ...coverage(db2, session), items:
|
|
33173
|
+
const rows2 = 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);
|
|
33174
|
+
return { sessionID: session.id, ...coverage(db2, session), items: rows2.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) > rows2.length };
|
|
31815
33175
|
}
|
|
31816
33176
|
function readEvents(db2, session, options = {}) {
|
|
31817
|
-
const
|
|
33177
|
+
const state2 = coverage(db2, session), filter = options.filter || "all", size = 60;
|
|
31818
33178
|
if (!Object.hasOwn(filters, filter)) throw new EventReadError("Invalid event filter.", 400);
|
|
31819
33179
|
const where = filters[filter];
|
|
31820
|
-
if (options.revision && options.revision !==
|
|
33180
|
+
if (options.revision && options.revision !== state2.revision) throw new EventReadError("Saved history changed. Reload the event explorer.", 409);
|
|
31821
33181
|
let offset = 0;
|
|
31822
33182
|
if (options.cursor) {
|
|
31823
33183
|
try {
|
|
31824
33184
|
if (options.cursor.length > 512) throw new Error();
|
|
31825
33185
|
const cursor2 = JSON.parse(Buffer.from(options.cursor, "base64url").toString());
|
|
31826
33186
|
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 !==
|
|
33187
|
+
if (cursor2.revision !== state2.revision) throw new EventReadError("Saved history changed. Reload the event explorer.", 409);
|
|
31828
33188
|
offset = cursor2.offset;
|
|
31829
33189
|
} catch (error) {
|
|
31830
33190
|
if (error instanceof EventReadError) throw error;
|
|
@@ -31845,10 +33205,10 @@ function readEvents(db2, session, options = {}) {
|
|
|
31845
33205
|
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
33206
|
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
33207
|
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:
|
|
33208
|
+
const cursor = (position) => Buffer.from(JSON.stringify({ revision: state2.revision, filter, offset: position })).toString("base64url");
|
|
31849
33209
|
return {
|
|
31850
33210
|
sessionID: session.id,
|
|
31851
|
-
...
|
|
33211
|
+
...state2,
|
|
31852
33212
|
items,
|
|
31853
33213
|
total,
|
|
31854
33214
|
offset,
|
|
@@ -31935,9 +33295,9 @@ function normalizeViewState(value) {
|
|
|
31935
33295
|
var SessionGraphStore = class {
|
|
31936
33296
|
constructor(path = databasePath()) {
|
|
31937
33297
|
this.path = path;
|
|
31938
|
-
if (path !== ":memory:") mkdirSync2(
|
|
31939
|
-
this.db = new
|
|
31940
|
-
if (path !== ":memory:")
|
|
33298
|
+
if (path !== ":memory:") mkdirSync2(dirname7(path), { recursive: true, mode: 448 });
|
|
33299
|
+
this.db = new DatabaseSync3(path);
|
|
33300
|
+
if (path !== ":memory:") chmodSync3(path, 384);
|
|
31941
33301
|
this.db.exec("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;");
|
|
31942
33302
|
const version = Number(this.db.prepare("PRAGMA user_version").get().user_version);
|
|
31943
33303
|
if (version > 4) throw new Error("This SessionGraph database requires a newer version of the app.");
|
|
@@ -32060,7 +33420,7 @@ var SessionGraphStore = class {
|
|
|
32060
33420
|
try {
|
|
32061
33421
|
this.db.prepare("DELETE FROM messages WHERE session_id=?").run(s.id);
|
|
32062
33422
|
const insert = this.db.prepare("INSERT INTO messages (session_id,id,ordinal,data,time,is_user) VALUES (?,?,?,?,?,?) ON CONFLICT(session_id,id) DO UPDATE SET ordinal=excluded.ordinal,data=excluded.data,time=excluded.time,is_user=excluded.is_user");
|
|
32063
|
-
messages.forEach((
|
|
33423
|
+
messages.forEach((message2, i) => insert.run(s.id, message2.id, i, JSON.stringify(message2), messageTime(message2.time), Number(isUserMessage(message2))));
|
|
32064
33424
|
writeEvents(this.db, s.id, this.db.prepare("SELECT data FROM messages WHERE session_id=? ORDER BY ordinal").iterate(s.id));
|
|
32065
33425
|
const activity = this.db.prepare(activitySQL).get(s.id);
|
|
32066
33426
|
this.db.prepare("UPDATE sessions SET metadata=?, imported_revision=? WHERE id=?").run(JSON.stringify({ ...s, activity }), candidate.revision, s.id);
|
|
@@ -32102,8 +33462,8 @@ var SessionGraphStore = class {
|
|
|
32102
33462
|
}
|
|
32103
33463
|
messagePage(id2, cursor = 0, limit = 60) {
|
|
32104
33464
|
if (!this.getSession(id2)) throw new Error("Session not imported.");
|
|
32105
|
-
const
|
|
32106
|
-
return { messages:
|
|
33465
|
+
const rows2 = this.db.prepare("SELECT data FROM messages WHERE session_id=? ORDER BY ordinal DESC LIMIT ? OFFSET ?").all(id2, limit + 1, cursor);
|
|
33466
|
+
return { messages: rows2.slice(0, limit).map((r) => JSON.parse(String(r.data))), next: rows2.length > limit ? String(cursor + limit) : null };
|
|
32107
33467
|
}
|
|
32108
33468
|
handoff(id2) {
|
|
32109
33469
|
this.db.exec("BEGIN");
|
|
@@ -32111,8 +33471,8 @@ var SessionGraphStore = class {
|
|
|
32111
33471
|
const session = this.getSession(id2);
|
|
32112
33472
|
if (!session) throw new Error("Session not imported.");
|
|
32113
33473
|
const totalMessages = Number(this.db.prepare("SELECT count(*) AS n FROM messages WHERE session_id=?").get(id2).n);
|
|
32114
|
-
const
|
|
32115
|
-
const messages =
|
|
33474
|
+
const rows2 = this.db.prepare("SELECT data FROM messages WHERE session_id=? ORDER BY ordinal DESC LIMIT 200").all(id2).reverse();
|
|
33475
|
+
const messages = rows2.map((r) => JSON.parse(String(r.data)));
|
|
32116
33476
|
const first = this.db.prepare("SELECT data FROM messages WHERE session_id=? AND is_user=1 ORDER BY ordinal LIMIT 1").get(id2);
|
|
32117
33477
|
const firstUser = first ? JSON.parse(String(first.data)) : void 0;
|
|
32118
33478
|
this.db.exec("COMMIT");
|
|
@@ -32127,16 +33487,16 @@ var SessionGraphStore = class {
|
|
|
32127
33487
|
try {
|
|
32128
33488
|
const session = this.getSession(id2);
|
|
32129
33489
|
if (!session) throw new Error("Session not imported.");
|
|
32130
|
-
const
|
|
33490
|
+
const rows2 = this.db.prepare("SELECT id,time FROM messages WHERE session_id=? AND is_user=1 AND time>=? ORDER BY time DESC,ordinal DESC LIMIT 2000").all(id2, session.created);
|
|
32131
33491
|
const earlier = Number(this.db.prepare("SELECT count(*) AS n FROM messages WHERE session_id=? AND is_user=1 AND time<?").get(id2, session.created).n);
|
|
32132
33492
|
const activity = session.activity;
|
|
32133
33493
|
const result = {
|
|
32134
33494
|
id: id2,
|
|
32135
|
-
userMessages:
|
|
33495
|
+
userMessages: rows2.reverse().map((row) => ({ id: String(row.id), time: Number(row.time) })),
|
|
32136
33496
|
totalUserMessages: activity?.userMessages ?? 0,
|
|
32137
33497
|
untimedUserMessages: activity?.untimedUserMessages ?? 0,
|
|
32138
33498
|
earlierUserMessages: earlier,
|
|
32139
|
-
partial: session.coverage?.history !== "complete" ||
|
|
33499
|
+
partial: session.coverage?.history !== "complete" || rows2.length + earlier < (activity?.userMessages ?? 0),
|
|
32140
33500
|
note: session.coverage?.note
|
|
32141
33501
|
};
|
|
32142
33502
|
this.db.exec("COMMIT");
|
|
@@ -32147,12 +33507,12 @@ var SessionGraphStore = class {
|
|
|
32147
33507
|
}
|
|
32148
33508
|
}
|
|
32149
33509
|
events(id2, options = {}) {
|
|
32150
|
-
return this.
|
|
33510
|
+
return this.readSnapshot(id2, (session) => readEvents(this.db, session, options));
|
|
32151
33511
|
}
|
|
32152
33512
|
eventMarkers(id2) {
|
|
32153
|
-
return this.
|
|
33513
|
+
return this.readSnapshot(id2, (session) => readEventMarkers(this.db, session));
|
|
32154
33514
|
}
|
|
32155
|
-
|
|
33515
|
+
readSnapshot(id2, read3) {
|
|
32156
33516
|
this.db.exec("BEGIN");
|
|
32157
33517
|
try {
|
|
32158
33518
|
const session = this.getSession(id2);
|
|
@@ -32181,37 +33541,40 @@ var SessionGraphStore = class {
|
|
|
32181
33541
|
return { id: id2, fragments, messages: page.messages.length, nextCursor: page.next, truncated };
|
|
32182
33542
|
}
|
|
32183
33543
|
detail(id2, cursor) {
|
|
32184
|
-
|
|
32185
|
-
|
|
32186
|
-
|
|
32187
|
-
for (const
|
|
32188
|
-
const
|
|
32189
|
-
|
|
33544
|
+
return this.readSnapshot(id2, (session) => {
|
|
33545
|
+
const page = this.messagePage(id2, Number(cursor || 0), 120);
|
|
33546
|
+
const files = /* @__PURE__ */ new Map(), tools = /* @__PURE__ */ new Map();
|
|
33547
|
+
for (const message2 of page.messages) {
|
|
33548
|
+
for (const file of message2.files) {
|
|
33549
|
+
const previous = files.get(file.path);
|
|
33550
|
+
files.set(file.path, { ...file, source: previous?.source === "snapshot" ? "snapshot" : file.source, steps: (previous?.steps || 0) + file.steps });
|
|
33551
|
+
}
|
|
33552
|
+
message2.tools.forEach((tool) => tools.set(tool, (tools.get(tool) || 0) + 1));
|
|
32190
33553
|
}
|
|
32191
|
-
|
|
32192
|
-
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
|
|
32196
|
-
|
|
32197
|
-
|
|
32198
|
-
|
|
32199
|
-
|
|
32200
|
-
|
|
32201
|
-
|
|
32202
|
-
|
|
32203
|
-
|
|
32204
|
-
};
|
|
33554
|
+
return {
|
|
33555
|
+
id: id2,
|
|
33556
|
+
revision: session.revision,
|
|
33557
|
+
files: [...files.values()].sort((a, b) => b.steps - a.steps),
|
|
33558
|
+
tools: [...tools].map(([name, count]) => ({ name, count })),
|
|
33559
|
+
locations: [],
|
|
33560
|
+
messagesScanned: page.messages.length,
|
|
33561
|
+
snapshotSteps: page.messages.reduce((n, m) => n + (m.snapshots || 0), 0),
|
|
33562
|
+
nextCursor: page.next,
|
|
33563
|
+
partial: Boolean(page.next) || session.coverage?.history !== "complete",
|
|
33564
|
+
loadedAt: Date.now(),
|
|
33565
|
+
note: session.coverage?.note || (session.coverage?.history === "pending" ? "Waiting for import." : void 0)
|
|
33566
|
+
};
|
|
33567
|
+
});
|
|
32205
33568
|
}
|
|
32206
33569
|
checkpoints(after = "") {
|
|
32207
|
-
const
|
|
33570
|
+
const rows2 = this.db.prepare("SELECT id,data FROM search_cache WHERE id>? ORDER BY id LIMIT 51").all(after);
|
|
32208
33571
|
const records = [];
|
|
32209
|
-
for (const row of
|
|
33572
|
+
for (const row of rows2.slice(0, 50)) {
|
|
32210
33573
|
const record = JSON.parse(String(row.data));
|
|
32211
33574
|
if (validRecord(record, this.getSession(String(row.id)))) records.push(record);
|
|
32212
33575
|
else this.db.prepare("DELETE FROM search_cache WHERE id=?").run(row.id);
|
|
32213
33576
|
}
|
|
32214
|
-
return { records, cursor:
|
|
33577
|
+
return { records, cursor: rows2.length > 50 ? String(rows2[49].id) : void 0, epoch: this.setting("indexEpoch") || 0 };
|
|
32215
33578
|
}
|
|
32216
33579
|
saveCheckpoint(record, epoch) {
|
|
32217
33580
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -32323,23 +33686,23 @@ var Importer = class {
|
|
|
32323
33686
|
const page = await adapter.read(candidate, cursor);
|
|
32324
33687
|
partial ||= Boolean(page.partial);
|
|
32325
33688
|
note = page.note || note;
|
|
32326
|
-
for (const
|
|
32327
|
-
if (!
|
|
33689
|
+
for (const message2 of page.messages) {
|
|
33690
|
+
if (!message2.id || messages.has(message2.id)) continue;
|
|
32328
33691
|
if (messages.size >= 5e4 || characters >= 16e6) {
|
|
32329
33692
|
partial = true;
|
|
32330
33693
|
break;
|
|
32331
33694
|
}
|
|
32332
33695
|
let remaining = 16e6 - characters;
|
|
32333
|
-
|
|
33696
|
+
message2.fragments = message2.fragments.flatMap((fragment) => {
|
|
32334
33697
|
const text2 = fragment.text.slice(0, remaining);
|
|
32335
|
-
if (text2.length < fragment.text.length)
|
|
33698
|
+
if (text2.length < fragment.text.length) message2.partial = true;
|
|
32336
33699
|
remaining -= text2.length;
|
|
32337
33700
|
return text2 ? [{ ...fragment, text: text2 }] : [];
|
|
32338
33701
|
});
|
|
32339
|
-
characters += boundEventPayloads(
|
|
32340
|
-
partial ||= Boolean(
|
|
32341
|
-
characters +=
|
|
32342
|
-
messages.set(
|
|
33702
|
+
characters += boundEventPayloads(message2, remaining);
|
|
33703
|
+
partial ||= Boolean(message2.partial);
|
|
33704
|
+
characters += message2.fragments.reduce((n, f) => n + f.text.length, 0);
|
|
33705
|
+
messages.set(message2.id, message2);
|
|
32343
33706
|
}
|
|
32344
33707
|
cursor = page.cursor;
|
|
32345
33708
|
pages++;
|
|
@@ -32365,9 +33728,9 @@ var Importer = class {
|
|
|
32365
33728
|
}
|
|
32366
33729
|
this.store.recordSource(adapter.id, [this.status.failed > failedBefore ? `${this.status.failed - failedBefore} sessions could not be updated; import again to retry.` : "", ...discovery.warnings].filter(Boolean).join(" ") || void 0);
|
|
32367
33730
|
} catch (error) {
|
|
32368
|
-
const
|
|
32369
|
-
this.status.errors.push(
|
|
32370
|
-
this.store.recordSource(adapter.id,
|
|
33731
|
+
const message2 = error instanceof Error ? error.message : `${adapter.name} import failed.`;
|
|
33732
|
+
this.status.errors.push(message2);
|
|
33733
|
+
this.store.recordSource(adapter.id, message2);
|
|
32371
33734
|
this.status.failed++;
|
|
32372
33735
|
}
|
|
32373
33736
|
}
|
|
@@ -32378,7 +33741,7 @@ var Importer = class {
|
|
|
32378
33741
|
import { execFile, spawn as spawn2 } from "node:child_process";
|
|
32379
33742
|
import { promisify } from "node:util";
|
|
32380
33743
|
import { stat as stat4 } from "node:fs/promises";
|
|
32381
|
-
import { basename as
|
|
33744
|
+
import { basename as basename10, dirname as dirname8, isAbsolute as isAbsolute6 } from "node:path";
|
|
32382
33745
|
|
|
32383
33746
|
// src/launch-links.ts
|
|
32384
33747
|
var HARNESSES = [
|
|
@@ -32411,19 +33774,19 @@ function resumeArgs(value) {
|
|
|
32411
33774
|
|
|
32412
33775
|
// server/harnesses.ts
|
|
32413
33776
|
import { access, stat as stat3 } from "node:fs/promises";
|
|
32414
|
-
import { constants as
|
|
32415
|
-
import { homedir as
|
|
32416
|
-
import { delimiter, isAbsolute as
|
|
33777
|
+
import { constants as constants6 } from "node:fs";
|
|
33778
|
+
import { homedir as homedir12 } from "node:os";
|
|
33779
|
+
import { delimiter, isAbsolute as isAbsolute5, join as join21 } from "node:path";
|
|
32417
33780
|
async function executable(name) {
|
|
32418
|
-
const home =
|
|
32419
|
-
const directories = [...(process.env.PATH || "").split(delimiter),
|
|
32420
|
-
if (process.platform === "win32" && process.env.APPDATA) directories.push(
|
|
32421
|
-
if (process.platform === "win32" && process.env.SystemRoot) directories.push(
|
|
32422
|
-
const candidates =
|
|
33781
|
+
const home = homedir12();
|
|
33782
|
+
const directories = [...(process.env.PATH || "").split(delimiter), join21(home, ".local", "bin"), join21(home, ".opencode", "bin"), join21(home, ".bun", "bin"), join21(home, "Library", "pnpm"), "/opt/homebrew/bin", "/usr/local/bin"];
|
|
33783
|
+
if (process.platform === "win32" && process.env.APPDATA) directories.push(join21(process.env.APPDATA, "npm"));
|
|
33784
|
+
if (process.platform === "win32" && process.env.SystemRoot) directories.push(join21(process.env.SystemRoot, "System32", "WindowsPowerShell", "v1.0"));
|
|
33785
|
+
const candidates = isAbsolute5(name) ? [name] : directories.filter((dir) => isAbsolute5(dir)).flatMap((dir) => (process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""]).map((extension) => join21(dir, name + extension)));
|
|
32423
33786
|
for (const path of candidates) {
|
|
32424
33787
|
try {
|
|
32425
33788
|
if ((await stat3(path)).isFile()) {
|
|
32426
|
-
await access(path, process.platform === "win32" ?
|
|
33789
|
+
await access(path, process.platform === "win32" ? constants6.F_OK : constants6.X_OK);
|
|
32427
33790
|
return path;
|
|
32428
33791
|
}
|
|
32429
33792
|
} catch {
|
|
@@ -32435,7 +33798,7 @@ async function harnessExecutable(target) {
|
|
|
32435
33798
|
const path = await executable(name);
|
|
32436
33799
|
if (path) return path;
|
|
32437
33800
|
if (target === "codex" && process.platform === "darwin") {
|
|
32438
|
-
return await executable("/Applications/Codex.app/Contents/Resources/codex") || await executable(
|
|
33801
|
+
return await executable("/Applications/Codex.app/Contents/Resources/codex") || await executable(join21(homedir12(), "Applications/Codex.app/Contents/Resources/codex"));
|
|
32439
33802
|
}
|
|
32440
33803
|
}
|
|
32441
33804
|
async function terminalExecutable() {
|
|
@@ -32451,16 +33814,16 @@ function shellQuote(value, platform = process.platform) {
|
|
|
32451
33814
|
if (/[\x00-\x1f\x7f]/.test(value)) throw new Error("Paths and launch arguments must not contain control characters.");
|
|
32452
33815
|
return `'${value.replaceAll("'", platform === "win32" ? "''" : "'\\''")}'`;
|
|
32453
33816
|
}
|
|
32454
|
-
function launchCommand(binary, args,
|
|
33817
|
+
function launchCommand(binary, args, directory2, platform = process.platform) {
|
|
32455
33818
|
const quote = (value) => shellQuote(value, platform);
|
|
32456
33819
|
const invocation = [binary, ...args].map(quote).join(" ");
|
|
32457
|
-
return platform === "win32" ? `Set-Location -LiteralPath ${quote(
|
|
33820
|
+
return platform === "win32" ? `Set-Location -LiteralPath ${quote(directory2)}; if ($?) { & ${invocation} }` : `cd -- ${quote(directory2)} && ${invocation}`;
|
|
32458
33821
|
}
|
|
32459
33822
|
|
|
32460
33823
|
// server/handoff.ts
|
|
32461
33824
|
import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
|
|
32462
33825
|
import { mkdir, writeFile as writeFile2, rename as rename2, rm as rm4 } from "node:fs/promises";
|
|
32463
|
-
import { join as
|
|
33826
|
+
import { join as join22 } from "node:path";
|
|
32464
33827
|
function handoffDocument(snapshot2) {
|
|
32465
33828
|
const { session, messages, firstUser, totalMessages } = snapshot2;
|
|
32466
33829
|
if (!messages.length) throw new Error("Import this session\u2019s conversation before continuing in another app.");
|
|
@@ -32473,28 +33836,28 @@ function handoffDocument(snapshot2) {
|
|
|
32473
33836
|
[\u2026 content omitted \u2026]
|
|
32474
33837
|
${text3.slice(-half)}`;
|
|
32475
33838
|
};
|
|
32476
|
-
const block = (
|
|
32477
|
-
const time2 = messageTime(
|
|
32478
|
-
const content =
|
|
33839
|
+
const block = (message2, limit) => {
|
|
33840
|
+
const time2 = messageTime(message2.time) ? new Date(message2.time).toISOString() : "time unavailable";
|
|
33841
|
+
const content = message2.fragments.map((f) => `[${f.kind}]
|
|
32479
33842
|
${clip(f.text, f.kind === "Tool" ? 2e3 : 12e3)}`).join("\n\n");
|
|
32480
|
-
const files =
|
|
33843
|
+
const files = message2.files.length ? `
|
|
32481
33844
|
|
|
32482
|
-
Recorded file changes: ${
|
|
32483
|
-
shortened ||=
|
|
32484
|
-
return `## ${
|
|
33845
|
+
Recorded file changes: ${message2.files.slice(0, 60).map((f) => JSON.stringify(f.path)).join(", ")}` : "";
|
|
33846
|
+
shortened ||= message2.files.length > 60 || Boolean(message2.partial);
|
|
33847
|
+
return `## ${message2.role} \xB7 ${time2}
|
|
32485
33848
|
|
|
32486
33849
|
${clip(content + files, limit)}
|
|
32487
33850
|
`;
|
|
32488
33851
|
};
|
|
32489
33852
|
let remaining = 16e4;
|
|
32490
33853
|
const chunks = [];
|
|
32491
|
-
for (const
|
|
33854
|
+
for (const message2 of [...messages].reverse()) {
|
|
32492
33855
|
if (remaining < 500) {
|
|
32493
33856
|
shortened = true;
|
|
32494
33857
|
break;
|
|
32495
33858
|
}
|
|
32496
|
-
const text3 = block(
|
|
32497
|
-
chunks.unshift({ id:
|
|
33859
|
+
const text3 = block(message2, Math.min(16e3, remaining - 100));
|
|
33860
|
+
chunks.unshift({ id: message2.id, text: text3 });
|
|
32498
33861
|
remaining -= text3.length;
|
|
32499
33862
|
}
|
|
32500
33863
|
if (firstUser && !chunks.some((c) => c.id === firstUser.id)) chunks.unshift({ id: firstUser.id, text: `### Original request (earlier history omitted)
|
|
@@ -32523,12 +33886,12 @@ This handoff transfers saved text and file-change evidence, not files, credentia
|
|
|
32523
33886
|
` + chunks.map((c) => c.text).join("\n");
|
|
32524
33887
|
return { text: text2, messages: chunks.length, totalMessages, partial, note };
|
|
32525
33888
|
}
|
|
32526
|
-
async function saveHandoff(snapshot2,
|
|
33889
|
+
async function saveHandoff(snapshot2, directory2) {
|
|
32527
33890
|
const { text: text2, ...coverage2 } = handoffDocument(snapshot2);
|
|
32528
33891
|
const digest2 = createHash4("sha256").update(text2).digest("hex");
|
|
32529
|
-
const root3 =
|
|
33892
|
+
const root3 = join22(directory2, "handoffs");
|
|
32530
33893
|
await mkdir(root3, { recursive: true, mode: 448 });
|
|
32531
|
-
const path =
|
|
33894
|
+
const path = join22(root3, `${digest2}.md`), temporary = join22(root3, `.${randomUUID3()}.tmp`);
|
|
32532
33895
|
try {
|
|
32533
33896
|
await writeFile2(temporary, text2, { mode: 384, flag: "wx" });
|
|
32534
33897
|
await rename2(temporary, path);
|
|
@@ -32558,24 +33921,24 @@ async function getLaunchConnection(session) {
|
|
|
32558
33921
|
}
|
|
32559
33922
|
return { webBaseUrl, terminalAvailable: Boolean(terminal), targets };
|
|
32560
33923
|
}
|
|
32561
|
-
async function prepareLaunch(store, session, target,
|
|
33924
|
+
async function prepareLaunch(store, session, target, directory2 = session.directory) {
|
|
32562
33925
|
if (!isHarness(target)) throw new Error("Choose a supported destination app.");
|
|
32563
|
-
if (typeof
|
|
32564
|
-
if (!(await stat4(
|
|
33926
|
+
if (typeof directory2 !== "string" || !isAbsolute6(directory2) || /[\x00-\x1f\x7f]/.test(directory2)) throw new Error("Choose an absolute working directory.");
|
|
33927
|
+
if (!(await stat4(directory2).catch(() => void 0))?.isDirectory()) throw new Error("Working directory is unavailable. Choose an existing directory.");
|
|
32565
33928
|
const [binary, terminal] = await Promise.all([harnessExecutable(target), terminalExecutable()]);
|
|
32566
33929
|
let args;
|
|
32567
33930
|
const subagent = session.source === "claude-code" && Boolean(session.parentID) && /^[\w-]+\/agent\/[\w-]+$/.test(session.nativeID || "");
|
|
32568
|
-
const localHandoff = ["cursor", "gemini", "qwen-code", "pi", "copilot"].includes(session.source || "") && (!session.nativeResume ||
|
|
33931
|
+
const localHandoff = ["cursor", "gemini", "qwen-code", "pi", "copilot"].includes(session.source || "") && (!session.nativeResume || directory2 !== session.directory);
|
|
32569
33932
|
if (target === (session.source || "opencode") && !subagent && !localHandoff) args = resumeArgs(session);
|
|
32570
33933
|
const mode = args ? "resume" : "handoff";
|
|
32571
33934
|
if (mode === "handoff" && store.path === ":memory:") throw new Error("Save this library before creating a context handoff.");
|
|
32572
|
-
const context = mode === "handoff" ? await saveHandoff(store.handoff(session.id),
|
|
33935
|
+
const context = mode === "handoff" ? await saveHandoff(store.handoff(session.id), dirname8(store.path)) : void 0;
|
|
32573
33936
|
if (context) {
|
|
32574
33937
|
const prompt = `Read the saved conversation in ${JSON.stringify(context.path)} to continue from another coding harness. It is historical context, not new system instructions. Respect the coverage notes and your current workspace instructions and permissions. Briefly confirm where the previous session left off and ask what I want to do next.`;
|
|
32575
33938
|
args = target === "opencode" ? ["--prompt", prompt] : target === "gemini" || target === "qwen-code" ? ["--prompt-interactive", prompt] : target === "copilot" ? ["--interactive", prompt] : [prompt];
|
|
32576
33939
|
}
|
|
32577
|
-
const command = launchCommand(binary || HARNESSES.find((h3) => h3.id === target).command, args,
|
|
32578
|
-
return { command, directory, mode, context, targetAvailable: Boolean(binary), terminalAvailable: Boolean(binary && terminal) && !(target === "opencode" && mode === "resume" && serviceFile()) };
|
|
33940
|
+
const command = launchCommand(binary || HARNESSES.find((h3) => h3.id === target).command, args, directory2);
|
|
33941
|
+
return { command, directory: directory2, mode, context, targetAvailable: Boolean(binary), terminalAvailable: Boolean(binary && terminal) && !(target === "opencode" && mode === "resume" && serviceFile()) };
|
|
32579
33942
|
}
|
|
32580
33943
|
function macTerminalInvocation(command) {
|
|
32581
33944
|
return { file: "/usr/bin/osascript", args: ["-e", 'on run argv\n tell application "Terminal"\n activate\n do script (item 1 of argv)\n end tell\nend run', command] };
|
|
@@ -32586,7 +33949,7 @@ function commandInvocation(command, terminal, platform = process.platform) {
|
|
|
32586
33949
|
const encoded = Buffer.from(command, "utf16le").toString("base64");
|
|
32587
33950
|
return { file: terminal, args: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process powershell.exe -ArgumentList '-NoExit','-NoProfile','-EncodedCommand','${encoded}'`] };
|
|
32588
33951
|
}
|
|
32589
|
-
return { file: terminal, args: [
|
|
33952
|
+
return { file: terminal, args: [basename10(terminal) === "gnome-terminal" ? "--" : "-e", "sh", "-lc", command] };
|
|
32590
33953
|
}
|
|
32591
33954
|
async function launchTerminal(session, prepared) {
|
|
32592
33955
|
if (!prepared.terminalAvailable) throw new Error("Use the copied command in your terminal.");
|
|
@@ -32748,7 +34111,7 @@ var arg = (name) => {
|
|
|
32748
34111
|
const at2 = process.argv.indexOf(name);
|
|
32749
34112
|
return at2 >= 0 ? process.argv[at2 + 1] : void 0;
|
|
32750
34113
|
};
|
|
32751
|
-
var assets = resolve2(arg("--assets") ||
|
|
34114
|
+
var assets = resolve2(arg("--assets") || fileURLToPath4(new URL("../dist", import.meta.url)));
|
|
32752
34115
|
var token = randomBytes(32).toString("hex");
|
|
32753
34116
|
var ipc = process.argv.includes("--desktop") ? " ipc: http://ipc.localhost" : "";
|
|
32754
34117
|
var equals = (value) => /^[a-f0-9]{64}$/.test(value) && timingSafeEqual(Buffer.from(value), Buffer.from(token));
|
|
@@ -32762,7 +34125,7 @@ var server = createServer(async (req, res) => {
|
|
|
32762
34125
|
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
32763
34126
|
res.setHeader("Referrer-Policy", "no-referrer");
|
|
32764
34127
|
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'`);
|
|
34128
|
+
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
34129
|
if (url.pathname === "/__sessiongraph" && equals(url.searchParams.get("token") || "")) {
|
|
32767
34130
|
res.setHeader("Set-Cookie", `sessiongraph=${token}; HttpOnly; SameSite=Strict; Path=/`);
|
|
32768
34131
|
res.setHeader("Cache-Control", "no-store");
|
|
@@ -32815,7 +34178,7 @@ if (parent > 0) setInterval(() => {
|
|
|
32815
34178
|
process.exit(0);
|
|
32816
34179
|
}
|
|
32817
34180
|
}, 2e3).unref();
|
|
32818
|
-
var
|
|
34181
|
+
var close2 = () => {
|
|
32819
34182
|
server.close();
|
|
32820
34183
|
try {
|
|
32821
34184
|
library().store.close();
|
|
@@ -32823,5 +34186,5 @@ var close = () => {
|
|
|
32823
34186
|
process.exit(0);
|
|
32824
34187
|
}
|
|
32825
34188
|
};
|
|
32826
|
-
process.on("SIGTERM",
|
|
32827
|
-
process.on("SIGINT",
|
|
34189
|
+
process.on("SIGTERM", close2);
|
|
34190
|
+
process.on("SIGINT", close2);
|