codesesh 0.5.0 → 0.6.1
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 +6 -3
- package/dist/{chunk-FZNZAMTZ.js → chunk-SQYHWMQV.js} +2533 -458
- package/dist/chunk-SQYHWMQV.js.map +1 -0
- package/dist/{dist-DMEDEJ2D.js → dist-NT4CH6KD.js} +46 -2
- package/dist/index.js +680 -83
- package/dist/index.js.map +1 -1
- package/dist/search-index-worker.js +40 -0
- package/dist/search-index-worker.js.map +1 -0
- package/dist/web/assets/index-D9lwrpQG.css +2 -0
- package/dist/web/assets/index-DZaQy6OQ.js +106 -0
- package/dist/web/assets/vendor-Bs5B_LvM.js +43 -0
- package/dist/web/index.html +3 -3
- package/package.json +2 -3
- package/dist/chunk-FZNZAMTZ.js.map +0 -1
- package/dist/web/assets/index-BRW_TBMw.js +0 -106
- package/dist/web/assets/index-CCgk7cPa.css +0 -2
- package/dist/web/assets/vendor-CWmLg_mG.js +0 -43
- /package/dist/{dist-DMEDEJ2D.js.map → dist-NT4CH6KD.js.map} +0 -0
|
@@ -11,38 +11,38 @@ import { basename } from "path";
|
|
|
11
11
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
12
12
|
import { homedir as homedir2 } from "os";
|
|
13
13
|
import { join as join2 } from "path";
|
|
14
|
-
import { existsSync as
|
|
15
|
-
import { join as
|
|
16
|
-
import { mkdirSync as mkdirSync2 } from "fs";
|
|
17
|
-
import { dirname as dirname2 } from "path";
|
|
14
|
+
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
15
|
+
import { join as join5 } from "path";
|
|
16
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
17
|
+
import { basename as basename3, dirname as dirname2, join as join4 } from "path";
|
|
18
18
|
import { createRequire } from "module";
|
|
19
19
|
import { createHash } from "crypto";
|
|
20
|
-
import { existsSync as
|
|
21
|
-
import { join as
|
|
20
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
21
|
+
import { join as join6, basename as basename4, dirname as dirname3 } from "path";
|
|
22
22
|
import {
|
|
23
23
|
closeSync,
|
|
24
|
-
existsSync as
|
|
24
|
+
existsSync as existsSync7,
|
|
25
25
|
openSync,
|
|
26
26
|
readFileSync as readFileSync5,
|
|
27
27
|
readSync,
|
|
28
28
|
readdirSync as readdirSync3,
|
|
29
29
|
statSync as statSync4
|
|
30
30
|
} from "fs";
|
|
31
|
-
import { join as
|
|
32
|
-
import { existsSync as
|
|
33
|
-
import { join as
|
|
31
|
+
import { join as join7, basename as basename5 } from "path";
|
|
32
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
|
|
33
|
+
import { join as join8, normalize } from "path";
|
|
34
34
|
import { resolve, sep } from "path";
|
|
35
35
|
import { availableParallelism } from "os";
|
|
36
36
|
import { Worker } from "worker_threads";
|
|
37
|
-
import { existsSync as
|
|
37
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
38
38
|
import { spawnSync } from "child_process";
|
|
39
39
|
import { homedir as homedir3 } from "os";
|
|
40
40
|
import * as path from "path";
|
|
41
|
-
import { existsSync as
|
|
42
|
-
import { join as
|
|
41
|
+
import { existsSync as existsSync10, rmSync, unlinkSync } from "fs";
|
|
42
|
+
import { join as join9 } from "path";
|
|
43
43
|
import { homedir as homedir4 } from "os";
|
|
44
44
|
import { homedir as homedir5, platform as platform2 } from "os";
|
|
45
|
-
import { join as
|
|
45
|
+
import { join as join10 } from "path";
|
|
46
46
|
var registrations = [];
|
|
47
47
|
function registerAgent(reg) {
|
|
48
48
|
registrations.push(reg);
|
|
@@ -64,6 +64,18 @@ function getAgentInfoMap(sessionsByAgent) {
|
|
|
64
64
|
function getAgentByName(name) {
|
|
65
65
|
return registrations.find((r) => r.name === name);
|
|
66
66
|
}
|
|
67
|
+
function parsedSession(session) {
|
|
68
|
+
return { status: "parsed", data: session };
|
|
69
|
+
}
|
|
70
|
+
function skippedSession(reason) {
|
|
71
|
+
return { status: "skipped", reason };
|
|
72
|
+
}
|
|
73
|
+
function filteredSession(reason) {
|
|
74
|
+
return { status: "filtered", reason };
|
|
75
|
+
}
|
|
76
|
+
function getParsedSession(result) {
|
|
77
|
+
return result.status === "parsed" ? result.data : null;
|
|
78
|
+
}
|
|
67
79
|
function matchesScanWindow(activityTime, options) {
|
|
68
80
|
if (options?.from != null && activityTime < options.from) return false;
|
|
69
81
|
if (options?.to != null && activityTime > options.to) return false;
|
|
@@ -134,10 +146,68 @@ function readJsonlFile(filePath) {
|
|
|
134
146
|
const content = readFileSync(filePath, "utf-8");
|
|
135
147
|
return parseJsonlLines(content);
|
|
136
148
|
}
|
|
149
|
+
var INTERNAL_TAGS = [
|
|
150
|
+
"command-message",
|
|
151
|
+
"command-name",
|
|
152
|
+
"local-command-stdout",
|
|
153
|
+
"system-reminder"
|
|
154
|
+
];
|
|
155
|
+
var INTERNAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
156
|
+
"progress",
|
|
157
|
+
"file history snapshot",
|
|
158
|
+
"file-history snapshot",
|
|
159
|
+
"file_history snapshot",
|
|
160
|
+
"queue operation",
|
|
161
|
+
"queue-operation",
|
|
162
|
+
"queue_operation",
|
|
163
|
+
"last prompt",
|
|
164
|
+
"last-prompt",
|
|
165
|
+
"last_prompt"
|
|
166
|
+
]);
|
|
167
|
+
function blockTagPattern(tag) {
|
|
168
|
+
return new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi");
|
|
169
|
+
}
|
|
170
|
+
function blockTagLinePattern(tag) {
|
|
171
|
+
return new RegExp(
|
|
172
|
+
`(^|\\r?\\n)[ \\t]*<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>[ \\t]*(?:\\r?\\n|$)`,
|
|
173
|
+
"gi"
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function openBlockTagPattern(tag) {
|
|
177
|
+
return new RegExp(`\\n*<${tag}\\b[^>]*>[\\s\\S]*$`, "gi");
|
|
178
|
+
}
|
|
179
|
+
function looseTagPattern(tag) {
|
|
180
|
+
return new RegExp(`<\\/?${tag}\\b[^>]*>`, "gi");
|
|
181
|
+
}
|
|
182
|
+
function isInternalEventType(value) {
|
|
183
|
+
if (typeof value !== "string") return false;
|
|
184
|
+
const normalized = value.trim().toLowerCase().replace(/[_-]+/g, " ");
|
|
185
|
+
return INTERNAL_EVENT_TYPES.has(normalized);
|
|
186
|
+
}
|
|
187
|
+
function cleanDisplayText(text) {
|
|
188
|
+
let cleaned = text;
|
|
189
|
+
for (const tag of INTERNAL_TAGS) {
|
|
190
|
+
cleaned = cleaned.replace(blockTagLinePattern(tag), "$1");
|
|
191
|
+
cleaned = cleaned.replace(blockTagPattern(tag), "");
|
|
192
|
+
cleaned = cleaned.replace(openBlockTagPattern(tag), "");
|
|
193
|
+
}
|
|
194
|
+
for (const tag of INTERNAL_TAGS) {
|
|
195
|
+
cleaned = cleaned.replace(looseTagPattern(tag), "");
|
|
196
|
+
}
|
|
197
|
+
cleaned = cleaned.replace(/[ \t]+(?=\r?\n|$)/g, "").replace(/(?:\r?\n)+$/g, "");
|
|
198
|
+
return cleaned.trim() ? cleaned : null;
|
|
199
|
+
}
|
|
200
|
+
function firstVisibleLine(text) {
|
|
201
|
+
const cleaned = cleanDisplayText(text);
|
|
202
|
+
if (!cleaned) return null;
|
|
203
|
+
return cleaned.split("\n").find((line) => line.trim())?.trim() ?? null;
|
|
204
|
+
}
|
|
137
205
|
var TITLE_MAX_LENGTH = 100;
|
|
138
206
|
var UNTITLED_SESSION = "Untitled Session";
|
|
139
207
|
function normalizeTitleText(text) {
|
|
140
|
-
const
|
|
208
|
+
const visible = cleanDisplayText(text)?.split("\n").find((line) => line.trim())?.trim() ?? null;
|
|
209
|
+
if (!visible) return null;
|
|
210
|
+
const cleaned = visible.replace(/\s+/g, " ").trim();
|
|
141
211
|
if (!cleaned) return null;
|
|
142
212
|
return cleaned.slice(0, TITLE_MAX_LENGTH);
|
|
143
213
|
}
|
|
@@ -157,6 +227,83 @@ function resolveSessionTitle(explicit, message, directory) {
|
|
|
157
227
|
}
|
|
158
228
|
return UNTITLED_SESSION;
|
|
159
229
|
}
|
|
230
|
+
function parsed(data) {
|
|
231
|
+
return { status: "parsed", data };
|
|
232
|
+
}
|
|
233
|
+
function skipped(reason) {
|
|
234
|
+
return reason ? { status: "skipped", reason } : { status: "skipped" };
|
|
235
|
+
}
|
|
236
|
+
function filtered(reason) {
|
|
237
|
+
return reason ? { status: "filtered", reason } : { status: "filtered" };
|
|
238
|
+
}
|
|
239
|
+
function isInternalEventType2(value) {
|
|
240
|
+
return isInternalEventType(value);
|
|
241
|
+
}
|
|
242
|
+
function cleanInternalText(text) {
|
|
243
|
+
return cleanDisplayText(text) ?? "";
|
|
244
|
+
}
|
|
245
|
+
function cleanUnknown(value) {
|
|
246
|
+
if (typeof value === "string") return cleanInternalText(value);
|
|
247
|
+
if (Array.isArray(value)) return value.map(cleanUnknown);
|
|
248
|
+
if (!value || typeof value !== "object") return value;
|
|
249
|
+
const cleaned = {};
|
|
250
|
+
for (const [key, child] of Object.entries(value)) {
|
|
251
|
+
cleaned[key] = cleanUnknown(child);
|
|
252
|
+
}
|
|
253
|
+
return cleaned;
|
|
254
|
+
}
|
|
255
|
+
function cleanMessagePart(part) {
|
|
256
|
+
const next = { ...part };
|
|
257
|
+
if (typeof next.text === "string") {
|
|
258
|
+
next.text = cleanInternalText(next.text);
|
|
259
|
+
if (!next.text && (next.type === "text" || next.type === "reasoning" || next.type === "plan")) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (typeof next.title === "string") {
|
|
264
|
+
const title = cleanInternalText(next.title);
|
|
265
|
+
if (title) next.title = title;
|
|
266
|
+
else delete next.title;
|
|
267
|
+
}
|
|
268
|
+
if (next.input !== void 0) {
|
|
269
|
+
next.input = cleanUnknown(next.input);
|
|
270
|
+
}
|
|
271
|
+
if (next.output !== void 0) {
|
|
272
|
+
next.output = cleanUnknown(next.output);
|
|
273
|
+
}
|
|
274
|
+
if (next.state !== void 0) {
|
|
275
|
+
next.state = cleanUnknown(next.state);
|
|
276
|
+
}
|
|
277
|
+
return next;
|
|
278
|
+
}
|
|
279
|
+
function cleanMessageParts(parts) {
|
|
280
|
+
return parts.flatMap((part) => {
|
|
281
|
+
const cleaned = cleanMessagePart(part);
|
|
282
|
+
return cleaned ? [cleaned] : [];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function cleanParsedMessage(message) {
|
|
286
|
+
const parts = cleanMessageParts(message.parts);
|
|
287
|
+
if (parts.length === 0) return null;
|
|
288
|
+
return { ...message, parts };
|
|
289
|
+
}
|
|
290
|
+
function cleanParsedMessages(messages) {
|
|
291
|
+
return messages.flatMap((message) => {
|
|
292
|
+
const cleaned = cleanParsedMessage(message);
|
|
293
|
+
return cleaned ? [cleaned] : [];
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function firstUserMessageTitle(messages) {
|
|
297
|
+
for (const message of messages) {
|
|
298
|
+
if (message.role !== "user") continue;
|
|
299
|
+
for (const part of message.parts) {
|
|
300
|
+
if (part.type !== "text" || typeof part.text !== "string") continue;
|
|
301
|
+
const title = normalizeTitleText(cleanInternalText(part.text));
|
|
302
|
+
if (title) return title;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
160
307
|
var PerfTracer = class {
|
|
161
308
|
rootMarkers = [];
|
|
162
309
|
activeStack = [];
|
|
@@ -564,10 +711,6 @@ function parseTimestampMs(data) {
|
|
|
564
711
|
return 0;
|
|
565
712
|
}
|
|
566
713
|
}
|
|
567
|
-
function normalizeTitleText2(text) {
|
|
568
|
-
const line = text.split("\n").find((l) => l.trim());
|
|
569
|
-
return line?.trim().slice(0, 80) || "";
|
|
570
|
-
}
|
|
571
714
|
var ClaudeCodeAgent = class extends BaseAgent {
|
|
572
715
|
name = "claudecode";
|
|
573
716
|
displayName = "Claude Code";
|
|
@@ -608,7 +751,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
608
751
|
try {
|
|
609
752
|
if (!matchesScanWindow(statSync(file).mtimeMs, options)) continue;
|
|
610
753
|
const parseMarker = perf.start(`parseSessionHead:${basename2(file)}`);
|
|
611
|
-
const head = this.
|
|
754
|
+
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
612
755
|
perf.end(parseMarker);
|
|
613
756
|
if (head) {
|
|
614
757
|
heads.push(head);
|
|
@@ -671,7 +814,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
671
814
|
} catch {
|
|
672
815
|
}
|
|
673
816
|
}
|
|
674
|
-
|
|
817
|
+
const cleanedMessages = cleanParsedMessages(messages);
|
|
818
|
+
for (const msg of cleanedMessages) {
|
|
675
819
|
totalCost += msg.cost ?? 0;
|
|
676
820
|
totalInputTokens += msg.tokens?.input ?? 0;
|
|
677
821
|
totalOutputTokens += msg.tokens?.output ?? 0;
|
|
@@ -687,7 +831,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
687
831
|
time_created: meta.createdAt,
|
|
688
832
|
time_updated: meta.updatedAt,
|
|
689
833
|
stats: {
|
|
690
|
-
message_count:
|
|
834
|
+
message_count: cleanedMessages.length,
|
|
691
835
|
total_input_tokens: totalInputTokens,
|
|
692
836
|
total_output_tokens: totalOutputTokens,
|
|
693
837
|
total_cost: totalCost,
|
|
@@ -695,7 +839,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
695
839
|
total_cache_read_tokens: totalCacheRead,
|
|
696
840
|
total_cache_create_tokens: totalCacheCreate
|
|
697
841
|
},
|
|
698
|
-
messages
|
|
842
|
+
messages: cleanedMessages
|
|
699
843
|
};
|
|
700
844
|
}
|
|
701
845
|
/**
|
|
@@ -762,7 +906,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
762
906
|
try {
|
|
763
907
|
const sessionId = basename2(file, ".jsonl");
|
|
764
908
|
if (changedIds.includes(sessionId)) {
|
|
765
|
-
const head = this.
|
|
909
|
+
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
766
910
|
if (head) {
|
|
767
911
|
sessionMap.set(head.id, head);
|
|
768
912
|
this.sessionMetaMap.set(head.id, {
|
|
@@ -786,7 +930,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
786
930
|
try {
|
|
787
931
|
const sessionId = basename2(file, ".jsonl");
|
|
788
932
|
if (!sessionMap.has(sessionId)) {
|
|
789
|
-
const head = this.
|
|
933
|
+
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
790
934
|
if (head) {
|
|
791
935
|
sessionMap.set(head.id, head);
|
|
792
936
|
this.sessionMetaMap.set(head.id, {
|
|
@@ -847,15 +991,18 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
847
991
|
return map;
|
|
848
992
|
}
|
|
849
993
|
parseSessionHead(filePath, projectDir) {
|
|
994
|
+
return getParsedSession(this.parseSessionHeadResult(filePath, projectDir));
|
|
995
|
+
}
|
|
996
|
+
parseSessionHeadResult(filePath, projectDir) {
|
|
850
997
|
const content = readFileSync3(filePath, "utf-8");
|
|
851
998
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
852
|
-
if (lines.length === 0) return
|
|
999
|
+
if (lines.length === 0) return skippedSession("empty file");
|
|
853
1000
|
const sessionId = basename2(filePath, ".jsonl");
|
|
854
1001
|
let firstRecord;
|
|
855
1002
|
try {
|
|
856
1003
|
firstRecord = JSON.parse(lines[0]);
|
|
857
1004
|
} catch {
|
|
858
|
-
return
|
|
1005
|
+
return skippedSession("malformed first record");
|
|
859
1006
|
}
|
|
860
1007
|
const createdAt = parseTimestampMs(firstRecord) || statSync(filePath).mtimeMs;
|
|
861
1008
|
const index = this.loadSessionsIndex(projectDir);
|
|
@@ -874,6 +1021,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
874
1021
|
for (const line of lines) {
|
|
875
1022
|
try {
|
|
876
1023
|
const data = JSON.parse(line);
|
|
1024
|
+
if (isInternalEventType(data["type"])) continue;
|
|
877
1025
|
const ts = parseTimestampMs(data);
|
|
878
1026
|
if (ts > updatedAt) updatedAt = ts;
|
|
879
1027
|
if (!cwd && data["cwd"] && typeof data["cwd"] === "string") {
|
|
@@ -924,7 +1072,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
924
1072
|
const directoryTitle = basenameTitle(directory) || basenameTitle(projectDir);
|
|
925
1073
|
const title = resolveSessionTitle(explicitTitle, messageTitle, directoryTitle);
|
|
926
1074
|
const hasModelUsage = Object.keys(modelUsageMap).length > 0;
|
|
927
|
-
return
|
|
1075
|
+
if (messageCount === 0) return filteredSession("no visible messages");
|
|
1076
|
+
return parsedSession({
|
|
928
1077
|
id: sessionId,
|
|
929
1078
|
slug: `claudecode/${sessionId}`,
|
|
930
1079
|
title,
|
|
@@ -941,23 +1090,26 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
941
1090
|
total_cache_create_tokens: totalCacheCreateTokens
|
|
942
1091
|
},
|
|
943
1092
|
model_usage: hasModelUsage ? modelUsageMap : void 0
|
|
944
|
-
};
|
|
1093
|
+
});
|
|
945
1094
|
}
|
|
946
1095
|
extractTitle(lines) {
|
|
947
1096
|
for (const line of lines.slice(0, 20)) {
|
|
948
1097
|
try {
|
|
949
1098
|
const data = JSON.parse(line);
|
|
1099
|
+
if (isInternalEventType(data["type"])) continue;
|
|
950
1100
|
const msg = data["message"];
|
|
951
1101
|
if (!msg || typeof msg !== "object") continue;
|
|
952
1102
|
if (msg["role"] !== "user") continue;
|
|
953
1103
|
const content = msg["content"];
|
|
954
1104
|
if (!content) continue;
|
|
955
1105
|
if (typeof content === "string") {
|
|
956
|
-
|
|
1106
|
+
const title = normalizeTitleText(content);
|
|
1107
|
+
if (title) return title;
|
|
957
1108
|
}
|
|
958
1109
|
if (Array.isArray(content)) {
|
|
959
1110
|
const texts = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
|
|
960
|
-
|
|
1111
|
+
const title = normalizeTitleText(texts);
|
|
1112
|
+
if (title) return title;
|
|
961
1113
|
}
|
|
962
1114
|
} catch {
|
|
963
1115
|
}
|
|
@@ -968,6 +1120,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
968
1120
|
convertRecord(data, messages, pendingToolCalls, ignoredToolCallIds, assistantUuidToToolCalls, assistantState) {
|
|
969
1121
|
if (data["isMeta"] === true) return;
|
|
970
1122
|
const msgType = String(data["type"] ?? "");
|
|
1123
|
+
if (isInternalEventType(msgType)) return;
|
|
971
1124
|
if (msgType === "assistant") {
|
|
972
1125
|
this.convertAssistantRecord(
|
|
973
1126
|
data,
|
|
@@ -1004,8 +1157,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1004
1157
|
const part = item;
|
|
1005
1158
|
const partType = String(part["type"] ?? "");
|
|
1006
1159
|
if (partType === "thinking") {
|
|
1007
|
-
const text = String(part["thinking"] ?? "");
|
|
1008
|
-
if (text
|
|
1160
|
+
const text = cleanInternalText(String(part["thinking"] ?? ""));
|
|
1161
|
+
if (text) {
|
|
1009
1162
|
currentAssistantIndex = this.appendAssistantReasoning(
|
|
1010
1163
|
messages,
|
|
1011
1164
|
{ messageId: uuid, msg, timestampMs, text },
|
|
@@ -1015,8 +1168,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1015
1168
|
continue;
|
|
1016
1169
|
}
|
|
1017
1170
|
if (partType === "text") {
|
|
1018
|
-
const text = String(part["text"] ?? "");
|
|
1019
|
-
if (text
|
|
1171
|
+
const text = cleanInternalText(String(part["text"] ?? ""));
|
|
1172
|
+
if (text) {
|
|
1020
1173
|
currentAssistantIndex = this.appendAssistantText(
|
|
1021
1174
|
messages,
|
|
1022
1175
|
{ messageId: uuid, msg, timestampMs, text },
|
|
@@ -1252,7 +1405,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1252
1405
|
// --- User content normalization ---
|
|
1253
1406
|
normalizeUserTextParts(content, timestampMs) {
|
|
1254
1407
|
if (typeof content === "string") {
|
|
1255
|
-
|
|
1408
|
+
const text = cleanInternalText(content);
|
|
1409
|
+
return text ? [this.buildTextPart(text, timestampMs)] : [];
|
|
1256
1410
|
}
|
|
1257
1411
|
if (!Array.isArray(content)) return [];
|
|
1258
1412
|
const parts = [];
|
|
@@ -1260,17 +1414,19 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1260
1414
|
if (typeof item === "object" && item !== null) {
|
|
1261
1415
|
const ci = item;
|
|
1262
1416
|
if (ci["type"] === "tool_result") continue;
|
|
1263
|
-
const text = String(ci["text"] ?? "");
|
|
1264
|
-
if (text
|
|
1265
|
-
} else if (typeof item === "string"
|
|
1266
|
-
|
|
1417
|
+
const text = cleanInternalText(String(ci["text"] ?? ""));
|
|
1418
|
+
if (text) parts.push(this.buildTextPart(text, timestampMs));
|
|
1419
|
+
} else if (typeof item === "string") {
|
|
1420
|
+
const text = cleanInternalText(item);
|
|
1421
|
+
if (text) parts.push(this.buildTextPart(text, timestampMs));
|
|
1267
1422
|
}
|
|
1268
1423
|
}
|
|
1269
1424
|
return parts;
|
|
1270
1425
|
}
|
|
1271
1426
|
normalizeClaudeToolOutput(content, timestampMs) {
|
|
1272
1427
|
if (typeof content === "string") {
|
|
1273
|
-
|
|
1428
|
+
const text2 = cleanInternalText(content);
|
|
1429
|
+
return text2 ? [this.buildTextPart(text2, timestampMs)] : [];
|
|
1274
1430
|
}
|
|
1275
1431
|
if (content === null || content === void 0) return [];
|
|
1276
1432
|
if (Array.isArray(content)) {
|
|
@@ -1280,15 +1436,17 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1280
1436
|
const text2 = String(
|
|
1281
1437
|
item["text"] ?? item["content"] ?? ""
|
|
1282
1438
|
);
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1439
|
+
const cleaned = cleanInternalText(text2);
|
|
1440
|
+
if (cleaned) parts.push(this.buildTextPart(cleaned, timestampMs));
|
|
1441
|
+
} else if (typeof item === "string") {
|
|
1442
|
+
const text2 = cleanInternalText(item);
|
|
1443
|
+
if (text2) parts.push(this.buildTextPart(text2, timestampMs));
|
|
1286
1444
|
}
|
|
1287
1445
|
}
|
|
1288
1446
|
return parts;
|
|
1289
1447
|
}
|
|
1290
|
-
const text = String(content);
|
|
1291
|
-
return text
|
|
1448
|
+
const text = cleanInternalText(String(content));
|
|
1449
|
+
return text ? [this.buildTextPart(text, timestampMs)] : [];
|
|
1292
1450
|
}
|
|
1293
1451
|
// --- Tool backfill ---
|
|
1294
1452
|
backfillToolOutput(messages, pendingToolCalls, callId, outputParts, stateUpdates) {
|
|
@@ -1368,6 +1526,98 @@ try {
|
|
|
1368
1526
|
DatabaseConstructor = typeof mod === "function" ? mod : mod.default;
|
|
1369
1527
|
} catch {
|
|
1370
1528
|
}
|
|
1529
|
+
function quoteIdentifier(value) {
|
|
1530
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
1531
|
+
}
|
|
1532
|
+
function quoteSqlString(value) {
|
|
1533
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
1534
|
+
}
|
|
1535
|
+
function isInMemoryPath(dbPath) {
|
|
1536
|
+
return dbPath === ":memory:" || dbPath.startsWith("file::memory:") || dbPath.includes("mode=memory");
|
|
1537
|
+
}
|
|
1538
|
+
function getUserVersion(db) {
|
|
1539
|
+
const row = db.prepare("PRAGMA user_version").get();
|
|
1540
|
+
return Number(row?.user_version ?? 0);
|
|
1541
|
+
}
|
|
1542
|
+
function setUserVersion(db, version) {
|
|
1543
|
+
db.exec(`PRAGMA user_version = ${Math.trunc(version)}`);
|
|
1544
|
+
}
|
|
1545
|
+
function tableExists(db, tableName) {
|
|
1546
|
+
const row = db.prepare(
|
|
1547
|
+
`
|
|
1548
|
+
SELECT 1 AS value
|
|
1549
|
+
FROM sqlite_master
|
|
1550
|
+
WHERE name = ? AND type IN ('table', 'view')
|
|
1551
|
+
LIMIT 1
|
|
1552
|
+
`
|
|
1553
|
+
).get(tableName);
|
|
1554
|
+
return row !== void 0;
|
|
1555
|
+
}
|
|
1556
|
+
function columnExists(db, tableName, columnName) {
|
|
1557
|
+
if (!tableExists(db, tableName)) {
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
|
|
1561
|
+
return rows.some((row) => String(row.name) === columnName);
|
|
1562
|
+
}
|
|
1563
|
+
function tableHasRows(db, tableName) {
|
|
1564
|
+
if (!tableExists(db, tableName)) {
|
|
1565
|
+
return false;
|
|
1566
|
+
}
|
|
1567
|
+
const row = db.prepare(`SELECT 1 AS value FROM ${quoteIdentifier(tableName)} LIMIT 1`).get();
|
|
1568
|
+
return row !== void 0;
|
|
1569
|
+
}
|
|
1570
|
+
function backupDatabase(db, dbPath, label) {
|
|
1571
|
+
if (isInMemoryPath(dbPath)) {
|
|
1572
|
+
return null;
|
|
1573
|
+
}
|
|
1574
|
+
const timestamp = new Date(Date.now()).toISOString().replaceAll(":", "").replaceAll(".", "-");
|
|
1575
|
+
let backupPath = join4(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.bak`);
|
|
1576
|
+
for (let counter = 1; existsSync4(backupPath); counter += 1) {
|
|
1577
|
+
backupPath = join4(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.${counter}.bak`);
|
|
1578
|
+
}
|
|
1579
|
+
db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
|
|
1580
|
+
return backupPath;
|
|
1581
|
+
}
|
|
1582
|
+
function backupDatabaseIfPopulated(db, dbPath, label, tables) {
|
|
1583
|
+
if (!tables.some((table) => tableHasRows(db, table))) {
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
return backupDatabase(db, dbPath, label);
|
|
1587
|
+
}
|
|
1588
|
+
function runSchemaMigrations(db, options) {
|
|
1589
|
+
const backups = [];
|
|
1590
|
+
let currentVersion = options.currentVersion;
|
|
1591
|
+
for (const migration of options.migrations) {
|
|
1592
|
+
if (migration.version <= currentVersion) {
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (migration.version > options.targetVersion) {
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
if (migration.destructive) {
|
|
1599
|
+
const backupPath = backupDatabaseIfPopulated(
|
|
1600
|
+
db,
|
|
1601
|
+
options.dbPath,
|
|
1602
|
+
options.backupLabel,
|
|
1603
|
+
options.backupTables
|
|
1604
|
+
);
|
|
1605
|
+
if (backupPath) {
|
|
1606
|
+
backups.push(backupPath);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
const apply = db.transaction(() => {
|
|
1610
|
+
migration.migrate(db);
|
|
1611
|
+
setUserVersion(db, migration.version);
|
|
1612
|
+
});
|
|
1613
|
+
apply();
|
|
1614
|
+
currentVersion = migration.version;
|
|
1615
|
+
}
|
|
1616
|
+
if (currentVersion < options.targetVersion) {
|
|
1617
|
+
setUserVersion(db, options.targetVersion);
|
|
1618
|
+
}
|
|
1619
|
+
return backups;
|
|
1620
|
+
}
|
|
1371
1621
|
function openDbReadOnly(dbPath) {
|
|
1372
1622
|
if (!DatabaseConstructor) return null;
|
|
1373
1623
|
try {
|
|
@@ -1382,9 +1632,12 @@ function openDb(dbPath) {
|
|
|
1382
1632
|
try {
|
|
1383
1633
|
mkdirSync2(dirname2(dbPath), { recursive: true });
|
|
1384
1634
|
const db = DatabaseConstructor(dbPath);
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1635
|
+
try {
|
|
1636
|
+
db.pragma("journal_mode = WAL");
|
|
1637
|
+
db.pragma("synchronous = NORMAL");
|
|
1638
|
+
db.pragma("foreign_keys = ON");
|
|
1639
|
+
} catch {
|
|
1640
|
+
}
|
|
1388
1641
|
return db;
|
|
1389
1642
|
} catch {
|
|
1390
1643
|
return null;
|
|
@@ -1402,7 +1655,7 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1402
1655
|
findDbPath() {
|
|
1403
1656
|
if (!isSqliteAvailable()) return null;
|
|
1404
1657
|
const roots = resolveProviderRoots();
|
|
1405
|
-
return firstExisting(
|
|
1658
|
+
return firstExisting(join5(roots.opencodeRoot, "opencode.db"), "data/opencode/opencode.db");
|
|
1406
1659
|
}
|
|
1407
1660
|
isAvailable() {
|
|
1408
1661
|
this.dbPath = this.findDbPath();
|
|
@@ -1414,7 +1667,9 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1414
1667
|
if (!db) return [];
|
|
1415
1668
|
try {
|
|
1416
1669
|
const cutoffTime = options?.from ?? Date.now() - 3650 * 24 * 60 * 60 * 1e3;
|
|
1417
|
-
const hasMessageTable =
|
|
1670
|
+
const hasMessageTable = Boolean(
|
|
1671
|
+
db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get()
|
|
1672
|
+
);
|
|
1418
1673
|
let rows;
|
|
1419
1674
|
if (hasMessageTable) {
|
|
1420
1675
|
rows = db.prepare(`
|
|
@@ -1440,31 +1695,12 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1440
1695
|
}
|
|
1441
1696
|
const heads = [];
|
|
1442
1697
|
for (const row of rows) {
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
const timeUpdated = Number(row.time_updated ?? timeCreated);
|
|
1447
|
-
const slug = `opencode/${id}`;
|
|
1448
|
-
const directory = String(row.directory ?? "");
|
|
1449
|
-
const stats = hasMessageTable ? this.readSessionStats(db, id) : null;
|
|
1450
|
-
heads.push({
|
|
1451
|
-
id,
|
|
1452
|
-
slug,
|
|
1453
|
-
title,
|
|
1454
|
-
directory,
|
|
1455
|
-
time_created: timeCreated,
|
|
1456
|
-
time_updated: timeUpdated,
|
|
1457
|
-
stats: {
|
|
1458
|
-
message_count: stats?.message_count ?? Number(row.message_count ?? 0),
|
|
1459
|
-
total_input_tokens: stats?.total_input_tokens ?? 0,
|
|
1460
|
-
total_output_tokens: stats?.total_output_tokens ?? 0,
|
|
1461
|
-
total_cost: stats?.total_cost ?? 0,
|
|
1462
|
-
cost_source: stats?.cost_source
|
|
1463
|
-
}
|
|
1464
|
-
});
|
|
1698
|
+
const head = getParsedSession(this.parseSessionHeadRow(db, row, hasMessageTable));
|
|
1699
|
+
if (!head) continue;
|
|
1700
|
+
heads.push(head);
|
|
1465
1701
|
if (this.dbPath) {
|
|
1466
|
-
this.sessionMetaMap.set(id, {
|
|
1467
|
-
id,
|
|
1702
|
+
this.sessionMetaMap.set(head.id, {
|
|
1703
|
+
id: head.id,
|
|
1468
1704
|
sourcePath: this.dbPath
|
|
1469
1705
|
});
|
|
1470
1706
|
}
|
|
@@ -1476,6 +1712,31 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1476
1712
|
db.close();
|
|
1477
1713
|
}
|
|
1478
1714
|
}
|
|
1715
|
+
parseSessionHeadRow(db, row, hasMessageTable) {
|
|
1716
|
+
const id = String(row.id ?? "");
|
|
1717
|
+
if (!id) return skippedSession("missing session id");
|
|
1718
|
+
const timeCreated = Number(row.time_created ?? 0);
|
|
1719
|
+
const timeUpdated = Number(row.time_updated ?? timeCreated);
|
|
1720
|
+
const stats = hasMessageTable ? this.readSessionStats(db, id) : null;
|
|
1721
|
+
const messageCount = stats?.message_count ?? Number(row.message_count ?? 0);
|
|
1722
|
+
if (hasMessageTable && messageCount === 0) return filteredSession("no visible messages");
|
|
1723
|
+
const messageTitle = hasMessageTable ? this.readFirstUserTitle(db, id) : null;
|
|
1724
|
+
return parsedSession({
|
|
1725
|
+
id,
|
|
1726
|
+
slug: `opencode/${id}`,
|
|
1727
|
+
title: resolveSessionTitle(String(row.title ?? ""), messageTitle, null),
|
|
1728
|
+
directory: String(row.directory ?? ""),
|
|
1729
|
+
time_created: timeCreated,
|
|
1730
|
+
time_updated: timeUpdated,
|
|
1731
|
+
stats: {
|
|
1732
|
+
message_count: messageCount,
|
|
1733
|
+
total_input_tokens: stats?.total_input_tokens ?? 0,
|
|
1734
|
+
total_output_tokens: stats?.total_output_tokens ?? 0,
|
|
1735
|
+
total_cost: stats?.total_cost ?? 0,
|
|
1736
|
+
cost_source: stats?.cost_source
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1479
1740
|
getSessionMetaMap() {
|
|
1480
1741
|
return this.sessionMetaMap;
|
|
1481
1742
|
}
|
|
@@ -1490,7 +1751,7 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1490
1751
|
if (!this.dbPath) {
|
|
1491
1752
|
this.dbPath = this.findDbPath();
|
|
1492
1753
|
}
|
|
1493
|
-
if (!this.dbPath || !
|
|
1754
|
+
if (!this.dbPath || !existsSync5(this.dbPath)) {
|
|
1494
1755
|
return { hasChanges: false, timestamp: Date.now() };
|
|
1495
1756
|
}
|
|
1496
1757
|
try {
|
|
@@ -1512,15 +1773,70 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1512
1773
|
incrementalScan(_cachedSessions, _changedIds) {
|
|
1513
1774
|
return this.scan();
|
|
1514
1775
|
}
|
|
1776
|
+
readMessageParts(db, messageId) {
|
|
1777
|
+
const partRows = db.prepare("SELECT * FROM part WHERE message_id = ? ORDER BY time_created ASC").all(messageId);
|
|
1778
|
+
const parts = [];
|
|
1779
|
+
for (const partRow of partRows) {
|
|
1780
|
+
const partData = JSON.parse(String(partRow.data ?? "{}"));
|
|
1781
|
+
const partType = String(partData.type ?? "");
|
|
1782
|
+
if (isInternalEventType(partType)) continue;
|
|
1783
|
+
if (partType === "text" || partType === "reasoning") {
|
|
1784
|
+
const text = cleanInternalText(String(partData.text ?? ""));
|
|
1785
|
+
if (text) {
|
|
1786
|
+
parts.push({
|
|
1787
|
+
type: partType,
|
|
1788
|
+
text,
|
|
1789
|
+
time_created: Number(partRow.time_created ?? 0)
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
} else if (partType === "tool") {
|
|
1793
|
+
parts.push({
|
|
1794
|
+
type: "tool",
|
|
1795
|
+
tool: String(partData.tool ?? ""),
|
|
1796
|
+
callID: String(partData.callID ?? ""),
|
|
1797
|
+
title: cleanInternalText(String(partData.title ?? "")),
|
|
1798
|
+
state: partData.state ?? {},
|
|
1799
|
+
time_created: Number(partRow.time_created ?? 0)
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return parts;
|
|
1804
|
+
}
|
|
1805
|
+
readFirstUserTitle(db, sessionId) {
|
|
1806
|
+
const rows = db.prepare(
|
|
1807
|
+
"SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC"
|
|
1808
|
+
).all(sessionId);
|
|
1809
|
+
for (const row of rows) {
|
|
1810
|
+
const msgData = JSON.parse(String(row.data ?? "{}"));
|
|
1811
|
+
if (isInternalEventType(msgData.type)) continue;
|
|
1812
|
+
if (String(msgData.role ?? "") !== "user") continue;
|
|
1813
|
+
const parts = this.readMessageParts(db, row.id);
|
|
1814
|
+
const title = firstUserMessageTitle([
|
|
1815
|
+
{
|
|
1816
|
+
id: String(row.id ?? ""),
|
|
1817
|
+
role: "user",
|
|
1818
|
+
agent: null,
|
|
1819
|
+
time_created: Number(row.time_created ?? 0),
|
|
1820
|
+
parts
|
|
1821
|
+
}
|
|
1822
|
+
]);
|
|
1823
|
+
if (title) return title;
|
|
1824
|
+
}
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1515
1827
|
readSessionStats(db, sessionId) {
|
|
1516
1828
|
try {
|
|
1517
|
-
const rows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
|
|
1829
|
+
const rows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
|
|
1518
1830
|
let totalCost = 0;
|
|
1519
1831
|
let totalInputTokens = 0;
|
|
1520
1832
|
let totalOutputTokens = 0;
|
|
1521
1833
|
let hasEstimatedCost = false;
|
|
1834
|
+
let messageCount = 0;
|
|
1522
1835
|
for (const row of rows) {
|
|
1523
1836
|
const msgData = JSON.parse(String(row.data ?? "{}"));
|
|
1837
|
+
if (isInternalEventType(msgData.type)) continue;
|
|
1838
|
+
const parts = this.readMessageParts(db, row.id);
|
|
1839
|
+
if (parts.length === 0) continue;
|
|
1524
1840
|
const cost = Number(msgData.cost ?? 0);
|
|
1525
1841
|
const tokens = msgData.tokens;
|
|
1526
1842
|
const inputTokens = Number(tokens?.input ?? 0);
|
|
@@ -1531,9 +1847,10 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1531
1847
|
totalCost += cost || estimatedCost || 0;
|
|
1532
1848
|
totalInputTokens += inputTokens;
|
|
1533
1849
|
totalOutputTokens += outputTokens;
|
|
1850
|
+
messageCount++;
|
|
1534
1851
|
}
|
|
1535
1852
|
return {
|
|
1536
|
-
message_count:
|
|
1853
|
+
message_count: messageCount,
|
|
1537
1854
|
total_input_tokens: totalInputTokens,
|
|
1538
1855
|
total_output_tokens: totalOutputTokens,
|
|
1539
1856
|
total_cost: totalCost,
|
|
@@ -1560,7 +1877,6 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1560
1877
|
throw new Error(`Session not found: ${sessionId}`);
|
|
1561
1878
|
}
|
|
1562
1879
|
const id = String(sessionRow.id ?? sessionId);
|
|
1563
|
-
const title = String(sessionRow.title ?? "Untitled");
|
|
1564
1880
|
const slug = `opencode/${id}`;
|
|
1565
1881
|
const directory = String(sessionRow.directory ?? "");
|
|
1566
1882
|
const timeCreated = Number(sessionRow.time_created ?? 0);
|
|
@@ -1573,7 +1889,7 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1573
1889
|
const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
|
|
1574
1890
|
for (const msgRow of msgRows) {
|
|
1575
1891
|
const msgData = JSON.parse(String(msgRow.data ?? "{}"));
|
|
1576
|
-
|
|
1892
|
+
if (isInternalEventType(msgData.type)) continue;
|
|
1577
1893
|
const cost = Number(msgData.cost ?? 0);
|
|
1578
1894
|
const tokens = msgData.tokens;
|
|
1579
1895
|
const inputTokens = Number(tokens?.input ?? 0);
|
|
@@ -1581,31 +1897,8 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1581
1897
|
const model = msgData.modelID ?? null;
|
|
1582
1898
|
const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
|
|
1583
1899
|
const resolvedCost = cost || estimatedCost || 0;
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
totalInputTokens += inputTokens;
|
|
1587
|
-
totalOutputTokens += outputTokens;
|
|
1588
|
-
const partRows = db.prepare("SELECT * FROM part WHERE message_id = ? ORDER BY time_created ASC").all(msgRow.id);
|
|
1589
|
-
for (const partRow of partRows) {
|
|
1590
|
-
const partData = JSON.parse(String(partRow.data ?? "{}"));
|
|
1591
|
-
const partType = String(partData.type ?? "");
|
|
1592
|
-
if (partType === "text" || partType === "reasoning") {
|
|
1593
|
-
parts.push({
|
|
1594
|
-
type: partType,
|
|
1595
|
-
text: partData.text ?? "",
|
|
1596
|
-
time_created: Number(partRow.time_created ?? 0)
|
|
1597
|
-
});
|
|
1598
|
-
} else if (partType === "tool") {
|
|
1599
|
-
parts.push({
|
|
1600
|
-
type: "tool",
|
|
1601
|
-
tool: String(partData.tool ?? ""),
|
|
1602
|
-
callID: String(partData.callID ?? ""),
|
|
1603
|
-
title: String(partData.title ?? ""),
|
|
1604
|
-
state: partData.state ?? {},
|
|
1605
|
-
time_created: Number(partRow.time_created ?? 0)
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1900
|
+
const parts = this.readMessageParts(db, msgRow.id);
|
|
1901
|
+
if (parts.length === 0) continue;
|
|
1609
1902
|
messages.push({
|
|
1610
1903
|
id: String(msgRow.id ?? ""),
|
|
1611
1904
|
role: String(msgData.role ?? "assistant"),
|
|
@@ -1620,6 +1913,18 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1620
1913
|
parts
|
|
1621
1914
|
});
|
|
1622
1915
|
}
|
|
1916
|
+
const cleanedMessages = cleanParsedMessages(messages);
|
|
1917
|
+
const title = resolveSessionTitle(
|
|
1918
|
+
String(sessionRow.title ?? ""),
|
|
1919
|
+
firstUserMessageTitle(cleanedMessages),
|
|
1920
|
+
null
|
|
1921
|
+
);
|
|
1922
|
+
for (const message of cleanedMessages) {
|
|
1923
|
+
totalCost += message.cost ?? 0;
|
|
1924
|
+
totalInputTokens += message.tokens?.input ?? 0;
|
|
1925
|
+
totalOutputTokens += message.tokens?.output ?? 0;
|
|
1926
|
+
if (message.cost_source === "estimated") hasEstimatedCost = true;
|
|
1927
|
+
}
|
|
1623
1928
|
return {
|
|
1624
1929
|
id,
|
|
1625
1930
|
title,
|
|
@@ -1630,13 +1935,13 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1630
1935
|
time_updated: timeUpdated,
|
|
1631
1936
|
summary_files: sessionRow.summary_files ?? void 0,
|
|
1632
1937
|
stats: {
|
|
1633
|
-
message_count:
|
|
1938
|
+
message_count: cleanedMessages.length,
|
|
1634
1939
|
total_input_tokens: totalInputTokens,
|
|
1635
1940
|
total_output_tokens: totalOutputTokens,
|
|
1636
1941
|
total_cost: totalCost,
|
|
1637
1942
|
cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
|
|
1638
1943
|
},
|
|
1639
|
-
messages
|
|
1944
|
+
messages: cleanedMessages
|
|
1640
1945
|
};
|
|
1641
1946
|
} finally {
|
|
1642
1947
|
db.close();
|
|
@@ -1667,36 +1972,74 @@ function normalizeToolArguments(raw) {
|
|
|
1667
1972
|
}
|
|
1668
1973
|
function normalizeToolOutputParts(content, timestampMs) {
|
|
1669
1974
|
if (typeof content === "string") {
|
|
1670
|
-
|
|
1975
|
+
const text2 = cleanInternalText(content);
|
|
1976
|
+
return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
|
|
1671
1977
|
}
|
|
1672
1978
|
if (Array.isArray(content)) {
|
|
1673
1979
|
const parts = [];
|
|
1674
1980
|
for (const item of content) {
|
|
1675
1981
|
if (typeof item === "object" && item !== null && "text" in item) {
|
|
1676
1982
|
const text2 = String(item.text ?? "");
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1983
|
+
const cleaned = cleanInternalText(text2);
|
|
1984
|
+
if (cleaned) parts.push({ type: "text", text: cleaned, time_created: timestampMs });
|
|
1985
|
+
} else if (typeof item === "string") {
|
|
1986
|
+
const text2 = cleanInternalText(item);
|
|
1987
|
+
if (text2) parts.push({ type: "text", text: text2, time_created: timestampMs });
|
|
1680
1988
|
}
|
|
1681
1989
|
}
|
|
1682
1990
|
return parts;
|
|
1683
1991
|
}
|
|
1684
1992
|
if (content == null) return [];
|
|
1685
|
-
const text = String(content);
|
|
1686
|
-
return text
|
|
1993
|
+
const text = cleanInternalText(String(content));
|
|
1994
|
+
return text ? [{ type: "text", text, time_created: timestampMs }] : [];
|
|
1687
1995
|
}
|
|
1688
1996
|
function normalizeWireToolOutputParts(returnValue, timestampMs) {
|
|
1689
1997
|
if (returnValue == null) return [];
|
|
1690
1998
|
if (typeof returnValue === "string") {
|
|
1691
|
-
|
|
1999
|
+
const text2 = cleanInternalText(returnValue);
|
|
2000
|
+
return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
|
|
1692
2001
|
}
|
|
1693
2002
|
if (typeof returnValue === "object") {
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2003
|
+
const text2 = cleanInternalText(JSON.stringify(returnValue, null, 2));
|
|
2004
|
+
return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
|
|
2005
|
+
}
|
|
2006
|
+
const text = cleanInternalText(String(returnValue));
|
|
2007
|
+
return text ? [{ type: "text", text, time_created: timestampMs }] : [];
|
|
2008
|
+
}
|
|
2009
|
+
function kimiContentText(content) {
|
|
2010
|
+
if (typeof content === "string") return content;
|
|
2011
|
+
if (!Array.isArray(content)) return "";
|
|
2012
|
+
return content.map((item) => {
|
|
2013
|
+
if (typeof item === "string") return item;
|
|
2014
|
+
if (typeof item === "object" && item !== null) {
|
|
2015
|
+
const record = item;
|
|
2016
|
+
return String(record.text ?? record.content ?? "");
|
|
2017
|
+
}
|
|
2018
|
+
return "";
|
|
2019
|
+
}).join(" ");
|
|
2020
|
+
}
|
|
2021
|
+
function extractFirstUserTitle(contextFile, wireFile) {
|
|
2022
|
+
if (contextFile && existsSync6(contextFile)) {
|
|
2023
|
+
const content = readFileSync4(contextFile, "utf-8");
|
|
2024
|
+
for (const record of parseJsonlLines(content)) {
|
|
2025
|
+
if (record.role !== "user") continue;
|
|
2026
|
+
const title = normalizeTitleText(kimiContentText(record.content));
|
|
2027
|
+
if (title) return title;
|
|
2028
|
+
}
|
|
1697
2029
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
2030
|
+
if (wireFile && existsSync6(wireFile)) {
|
|
2031
|
+
const content = readFileSync4(wireFile, "utf-8");
|
|
2032
|
+
for (const record of parseJsonlLines(content)) {
|
|
2033
|
+
const message = record.message ?? {};
|
|
2034
|
+
if (message.type !== "TurnBegin") continue;
|
|
2035
|
+
const payload = message.payload ?? {};
|
|
2036
|
+
const userInput = payload.user_input;
|
|
2037
|
+
if (!Array.isArray(userInput)) continue;
|
|
2038
|
+
const title = normalizeTitleText(kimiContentText(userInput));
|
|
2039
|
+
if (title) return title;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return null;
|
|
1700
2043
|
}
|
|
1701
2044
|
var KimiAgent = class extends BaseAgent {
|
|
1702
2045
|
name = "kimi";
|
|
@@ -1707,18 +2050,18 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1707
2050
|
defaultModel = null;
|
|
1708
2051
|
findBasePath() {
|
|
1709
2052
|
const roots = resolveProviderRoots();
|
|
1710
|
-
return firstExisting(
|
|
2053
|
+
return firstExisting(join6(roots.kimiRoot, "sessions"), "data/kimi");
|
|
1711
2054
|
}
|
|
1712
2055
|
/** Parse kimi.json and build md5(project_path) → cwd mapping */
|
|
1713
2056
|
loadKimiConfig() {
|
|
1714
2057
|
const roots = resolveProviderRoots();
|
|
1715
|
-
const configPath =
|
|
1716
|
-
const tomlPath =
|
|
1717
|
-
if (
|
|
2058
|
+
const configPath = join6(roots.kimiRoot, "kimi.json");
|
|
2059
|
+
const tomlPath = join6(roots.kimiRoot, "config.toml");
|
|
2060
|
+
if (existsSync6(tomlPath)) {
|
|
1718
2061
|
const configText = readFileSync4(tomlPath, "utf-8");
|
|
1719
2062
|
this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
|
|
1720
2063
|
}
|
|
1721
|
-
if (!
|
|
2064
|
+
if (!existsSync6(configPath)) return;
|
|
1722
2065
|
try {
|
|
1723
2066
|
const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1724
2067
|
const workDirs = raw?.work_dirs;
|
|
@@ -1749,12 +2092,12 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1749
2092
|
try {
|
|
1750
2093
|
for (const hashEntry of readdirSync2(this.basePath, { withFileTypes: true })) {
|
|
1751
2094
|
if (!hashEntry.isDirectory()) continue;
|
|
1752
|
-
const hashPath =
|
|
2095
|
+
const hashPath = join6(this.basePath, hashEntry.name);
|
|
1753
2096
|
try {
|
|
1754
2097
|
for (const sessionEntry of readdirSync2(hashPath, { withFileTypes: true })) {
|
|
1755
2098
|
if (!sessionEntry.isDirectory()) continue;
|
|
1756
|
-
const sessionPath =
|
|
1757
|
-
if (
|
|
2099
|
+
const sessionPath = join6(hashPath, sessionEntry.name);
|
|
2100
|
+
if (existsSync6(join6(sessionPath, "metadata.json")) || existsSync6(join6(sessionPath, "state.json"))) {
|
|
1758
2101
|
dirs.push(sessionPath);
|
|
1759
2102
|
}
|
|
1760
2103
|
}
|
|
@@ -1767,42 +2110,50 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1767
2110
|
}
|
|
1768
2111
|
/** Parse session directory, preferring state.json over metadata.json */
|
|
1769
2112
|
parseSessionDir(sessionDir) {
|
|
2113
|
+
return getParsedSession(this.parseSessionDirResult(sessionDir));
|
|
2114
|
+
}
|
|
2115
|
+
parseSessionDirResult(sessionDir) {
|
|
1770
2116
|
try {
|
|
1771
|
-
const sessionId =
|
|
1772
|
-
const projectHash =
|
|
1773
|
-
const contextFile =
|
|
1774
|
-
const wireFile =
|
|
1775
|
-
if (!
|
|
1776
|
-
|
|
1777
|
-
|
|
2117
|
+
const sessionId = basename4(sessionDir);
|
|
2118
|
+
const projectHash = basename4(dirname3(sessionDir));
|
|
2119
|
+
const contextFile = join6(sessionDir, "context.jsonl");
|
|
2120
|
+
const wireFile = join6(sessionDir, "wire.jsonl");
|
|
2121
|
+
if (!existsSync6(contextFile) && !existsSync6(wireFile)) {
|
|
2122
|
+
return skippedSession("missing transcript");
|
|
2123
|
+
}
|
|
2124
|
+
const statePath = join6(sessionDir, "state.json");
|
|
2125
|
+
const metaPath = join6(sessionDir, "metadata.json");
|
|
1778
2126
|
let title = "";
|
|
1779
2127
|
let wireMtime = null;
|
|
1780
2128
|
let metaFile = "";
|
|
1781
|
-
if (
|
|
2129
|
+
if (existsSync6(statePath)) {
|
|
1782
2130
|
const state = JSON.parse(readFileSync4(statePath, "utf-8"));
|
|
1783
2131
|
title = String(state.custom_title ?? "");
|
|
1784
2132
|
wireMtime = typeof state.wire_mtime === "number" ? state.wire_mtime : null;
|
|
1785
2133
|
metaFile = statePath;
|
|
1786
|
-
} else if (
|
|
2134
|
+
} else if (existsSync6(metaPath)) {
|
|
1787
2135
|
const meta = JSON.parse(readFileSync4(metaPath, "utf-8"));
|
|
1788
2136
|
title = String(meta.title ?? "");
|
|
1789
2137
|
wireMtime = typeof meta.wire_mtime === "number" ? meta.wire_mtime : null;
|
|
1790
2138
|
metaFile = metaPath;
|
|
1791
2139
|
}
|
|
1792
2140
|
const cwd = this.projectMap.get(projectHash) || "";
|
|
2141
|
+
const existingContextFile = existsSync6(contextFile) ? contextFile : null;
|
|
2142
|
+
const existingWireFile = existsSync6(wireFile) ? wireFile : null;
|
|
2143
|
+
const messageTitle = extractFirstUserTitle(existingContextFile, existingWireFile);
|
|
1793
2144
|
const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync3(metaFile).mtimeMs : statSync3(sessionDir).mtimeMs;
|
|
1794
|
-
return {
|
|
2145
|
+
return parsedSession({
|
|
1795
2146
|
id: sessionId,
|
|
1796
|
-
title: title
|
|
2147
|
+
title: resolveSessionTitle(title, messageTitle, null),
|
|
1797
2148
|
sourcePath: sessionDir,
|
|
1798
2149
|
cwd,
|
|
1799
|
-
contextFile:
|
|
1800
|
-
wireFile:
|
|
2150
|
+
contextFile: existingContextFile,
|
|
2151
|
+
wireFile: existingWireFile,
|
|
1801
2152
|
createdAt,
|
|
1802
2153
|
metaFile
|
|
1803
|
-
};
|
|
2154
|
+
});
|
|
1804
2155
|
} catch {
|
|
1805
|
-
return
|
|
2156
|
+
return skippedSession("malformed metadata");
|
|
1806
2157
|
}
|
|
1807
2158
|
}
|
|
1808
2159
|
scan(options) {
|
|
@@ -1814,8 +2165,8 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1814
2165
|
const heads = [];
|
|
1815
2166
|
for (const dir of sessionDirs) {
|
|
1816
2167
|
try {
|
|
1817
|
-
const parseMarker = perf.start(`parseSessionDir:${
|
|
1818
|
-
const meta = this.
|
|
2168
|
+
const parseMarker = perf.start(`parseSessionDir:${basename4(dir)}`);
|
|
2169
|
+
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
1819
2170
|
perf.end(parseMarker);
|
|
1820
2171
|
if (!meta) continue;
|
|
1821
2172
|
if (!matchesScanWindow(meta.createdAt, options)) continue;
|
|
@@ -1850,7 +2201,7 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1850
2201
|
const cachedIds = new Set(cachedSessions.map((session) => session.id));
|
|
1851
2202
|
const currentIds = /* @__PURE__ */ new Set();
|
|
1852
2203
|
for (const dir of this.listSessionDirs()) {
|
|
1853
|
-
const meta = this.
|
|
2204
|
+
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
1854
2205
|
if (!meta) continue;
|
|
1855
2206
|
currentIds.add(meta.id);
|
|
1856
2207
|
this.sessionMetaMap.set(meta.id, meta);
|
|
@@ -1897,7 +2248,7 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1897
2248
|
}
|
|
1898
2249
|
for (const dir of this.listSessionDirs()) {
|
|
1899
2250
|
try {
|
|
1900
|
-
const meta = this.
|
|
2251
|
+
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
1901
2252
|
if (!meta) continue;
|
|
1902
2253
|
if (changedIdSet.has(meta.id)) {
|
|
1903
2254
|
this.sessionMetaMap.set(meta.id, meta);
|
|
@@ -1937,10 +2288,10 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1937
2288
|
seq++;
|
|
1938
2289
|
try {
|
|
1939
2290
|
const role = String(record.role ?? "");
|
|
1940
|
-
if (role === "_checkpoint" || role === "_usage") continue;
|
|
2291
|
+
if (role === "_checkpoint" || role === "_usage" || isInternalEventType(role)) continue;
|
|
1941
2292
|
if (role === "user") {
|
|
1942
|
-
const text =
|
|
1943
|
-
if (text
|
|
2293
|
+
const text = cleanInternalText(kimiContentText(record.content));
|
|
2294
|
+
if (text) {
|
|
1944
2295
|
messages.push(
|
|
1945
2296
|
this.buildMessage({
|
|
1946
2297
|
messageId: `context-${seq}`,
|
|
@@ -1992,8 +2343,8 @@ var KimiAgent = class extends BaseAgent {
|
|
|
1992
2343
|
return this.buildSessionData(meta, messages, stats);
|
|
1993
2344
|
}
|
|
1994
2345
|
getSessionDataFromWire(meta) {
|
|
1995
|
-
const wirePath = meta.wireFile ??
|
|
1996
|
-
if (!
|
|
2346
|
+
const wirePath = meta.wireFile ?? join6(meta.sourcePath, "wire.jsonl");
|
|
2347
|
+
if (!existsSync6(wirePath)) throw new Error("wire.jsonl is missing");
|
|
1997
2348
|
const content = readFileSync4(wirePath, "utf-8");
|
|
1998
2349
|
const messages = [];
|
|
1999
2350
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
@@ -2007,6 +2358,7 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2007
2358
|
try {
|
|
2008
2359
|
const message = record.message ?? {};
|
|
2009
2360
|
const msgType = String(message.type ?? "");
|
|
2361
|
+
if (isInternalEventType(msgType)) continue;
|
|
2010
2362
|
const payload = message.payload ?? {};
|
|
2011
2363
|
const timestamp = Number(record.timestamp ?? 0);
|
|
2012
2364
|
const timestampMs = Number.isFinite(timestamp) ? Math.floor(timestamp * 1e3) : 0;
|
|
@@ -2033,8 +2385,8 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2033
2385
|
if (msgType === "TurnBegin") {
|
|
2034
2386
|
const userInput = payload.user_input;
|
|
2035
2387
|
if (Array.isArray(userInput) && userInput.length > 0) {
|
|
2036
|
-
const text =
|
|
2037
|
-
if (text
|
|
2388
|
+
const text = cleanInternalText(kimiContentText(userInput));
|
|
2389
|
+
if (text) {
|
|
2038
2390
|
messages.push(
|
|
2039
2391
|
this.buildMessage({
|
|
2040
2392
|
messageId: `wire-${seq}`,
|
|
@@ -2058,13 +2410,13 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2058
2410
|
const assistant = messages[currentAssistantIndex];
|
|
2059
2411
|
const partType = String(payload.type ?? "");
|
|
2060
2412
|
if (partType === "think") {
|
|
2061
|
-
const text = String(payload.think ?? "");
|
|
2062
|
-
if (text
|
|
2413
|
+
const text = cleanInternalText(String(payload.think ?? ""));
|
|
2414
|
+
if (text) {
|
|
2063
2415
|
assistant.parts.push({ type: "reasoning", text, time_created: timestampMs });
|
|
2064
2416
|
}
|
|
2065
2417
|
} else if (partType === "text") {
|
|
2066
|
-
const text = String(payload.text ?? "");
|
|
2067
|
-
if (text
|
|
2418
|
+
const text = cleanInternalText(String(payload.text ?? ""));
|
|
2419
|
+
if (text) {
|
|
2068
2420
|
assistant.parts.push({ type: "text", text, time_created: timestampMs });
|
|
2069
2421
|
}
|
|
2070
2422
|
}
|
|
@@ -2170,11 +2522,11 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2170
2522
|
const ci = item;
|
|
2171
2523
|
const partType = String(ci.type ?? "");
|
|
2172
2524
|
if (partType === "think") {
|
|
2173
|
-
const text = String(ci.think ?? "");
|
|
2174
|
-
if (text
|
|
2525
|
+
const text = cleanInternalText(String(ci.think ?? ""));
|
|
2526
|
+
if (text) parts.push({ type: "reasoning", text, time_created: fallbackTs });
|
|
2175
2527
|
} else if (partType === "text") {
|
|
2176
|
-
const text = String(ci.text ?? "");
|
|
2177
|
-
if (text
|
|
2528
|
+
const text = cleanInternalText(String(ci.text ?? ""));
|
|
2529
|
+
if (text) parts.push({ type: "text", text, time_created: fallbackTs });
|
|
2178
2530
|
}
|
|
2179
2531
|
}
|
|
2180
2532
|
}
|
|
@@ -2244,12 +2596,12 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2244
2596
|
const existing = buffer.get(openCallId) ?? "";
|
|
2245
2597
|
const combined = existing + argumentsPart;
|
|
2246
2598
|
try {
|
|
2247
|
-
const
|
|
2599
|
+
const parsed2 = JSON.parse(combined);
|
|
2248
2600
|
const location = pendingToolCalls.get(openCallId);
|
|
2249
2601
|
if (!location) return;
|
|
2250
2602
|
const msgPart = messages[location[0]]?.parts[location[1]];
|
|
2251
2603
|
if (msgPart?.state) {
|
|
2252
|
-
msgPart.state.arguments =
|
|
2604
|
+
msgPart.state.arguments = parsed2;
|
|
2253
2605
|
}
|
|
2254
2606
|
buffer.delete(openCallId);
|
|
2255
2607
|
} catch {
|
|
@@ -2275,8 +2627,8 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2275
2627
|
total_tokens: 0,
|
|
2276
2628
|
message_count: 0
|
|
2277
2629
|
};
|
|
2278
|
-
const wirePath =
|
|
2279
|
-
if (!
|
|
2630
|
+
const wirePath = join6(sessionDir, "wire.jsonl");
|
|
2631
|
+
if (!existsSync6(wirePath)) return stats;
|
|
2280
2632
|
try {
|
|
2281
2633
|
const content = readFileSync4(wirePath, "utf-8");
|
|
2282
2634
|
for (const line of content.split("\n").filter((l) => l.trim())) {
|
|
@@ -2298,9 +2650,9 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2298
2650
|
}
|
|
2299
2651
|
} catch {
|
|
2300
2652
|
}
|
|
2301
|
-
const contextPath =
|
|
2302
|
-
const rawPath =
|
|
2303
|
-
if (!
|
|
2653
|
+
const contextPath = join6(sessionDir, "context.jsonl");
|
|
2654
|
+
const rawPath = existsSync6(contextPath) ? contextPath : wirePath;
|
|
2655
|
+
if (!existsSync6(rawPath)) return stats;
|
|
2304
2656
|
try {
|
|
2305
2657
|
const rawContent = readFileSync4(rawPath, "utf-8");
|
|
2306
2658
|
for (const line of rawContent.split("\n").filter((l) => l.trim())) {
|
|
@@ -2321,8 +2673,9 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2321
2673
|
return stats;
|
|
2322
2674
|
}
|
|
2323
2675
|
buildSessionData(meta, messages, stats) {
|
|
2324
|
-
|
|
2325
|
-
|
|
2676
|
+
const cleanedMessages = cleanParsedMessages(messages);
|
|
2677
|
+
stats.message_count = cleanedMessages.length;
|
|
2678
|
+
const totalCost = cleanedMessages.reduce((sum, message) => sum + (message.cost ?? 0), 0);
|
|
2326
2679
|
if (totalCost > 0) {
|
|
2327
2680
|
stats.total_cost = Number(totalCost.toFixed(8));
|
|
2328
2681
|
stats.cost_source = "estimated";
|
|
@@ -2335,7 +2688,7 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2335
2688
|
time_created: meta.createdAt,
|
|
2336
2689
|
time_updated: meta.createdAt,
|
|
2337
2690
|
stats,
|
|
2338
|
-
messages
|
|
2691
|
+
messages: cleanedMessages
|
|
2339
2692
|
};
|
|
2340
2693
|
}
|
|
2341
2694
|
};
|
|
@@ -2362,7 +2715,7 @@ var CODEX_TOOL_TITLE_MAP = {
|
|
|
2362
2715
|
};
|
|
2363
2716
|
var RECENT_SESSION_REVALIDATION_WINDOW_MS2 = 24 * 60 * 60 * 1e3;
|
|
2364
2717
|
function extractSessionId(filename) {
|
|
2365
|
-
const stem =
|
|
2718
|
+
const stem = basename5(filename, ".jsonl");
|
|
2366
2719
|
const parts = stem.split("-");
|
|
2367
2720
|
if (parts.length >= 5) {
|
|
2368
2721
|
return parts.slice(-5).join("-");
|
|
@@ -2385,10 +2738,6 @@ function extractCachedInputTokens(usage) {
|
|
|
2385
2738
|
if (!usage) return 0;
|
|
2386
2739
|
return Number(usage["cached_input_tokens"] ?? usage["cache_read_input_tokens"] ?? 0);
|
|
2387
2740
|
}
|
|
2388
|
-
function normalizeTitleText3(text) {
|
|
2389
|
-
const line = text.split("\n").find((l) => l.trim());
|
|
2390
|
-
return line?.trim().slice(0, 80) || "";
|
|
2391
|
-
}
|
|
2392
2741
|
function mapToolTitle2(name) {
|
|
2393
2742
|
return CODEX_TOOL_TITLE_MAP[name] ?? name;
|
|
2394
2743
|
}
|
|
@@ -2499,7 +2848,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2499
2848
|
// ---- BaseAgent implementation ----
|
|
2500
2849
|
findBasePath() {
|
|
2501
2850
|
const roots = resolveProviderRoots();
|
|
2502
|
-
return firstExisting(
|
|
2851
|
+
return firstExisting(join7(roots.codexRoot, "sessions"));
|
|
2503
2852
|
}
|
|
2504
2853
|
isAvailable() {
|
|
2505
2854
|
this.basePath = this.findBasePath();
|
|
@@ -2523,8 +2872,8 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2523
2872
|
perf.end(listMarker);
|
|
2524
2873
|
for (const file of files) {
|
|
2525
2874
|
try {
|
|
2526
|
-
const parseMarker = perf.start(`parseSessionHead:${
|
|
2527
|
-
const head = this.
|
|
2875
|
+
const parseMarker = perf.start(`parseSessionHead:${basename5(file)}`);
|
|
2876
|
+
const head = getParsedSession(this.parseSessionHeadResult(file, options));
|
|
2528
2877
|
perf.end(parseMarker);
|
|
2529
2878
|
if (head) {
|
|
2530
2879
|
heads.push(head);
|
|
@@ -2660,7 +3009,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2660
3009
|
getSessionData(sessionId) {
|
|
2661
3010
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
2662
3011
|
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
2663
|
-
if (!
|
|
3012
|
+
if (!existsSync7(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
|
|
2664
3013
|
const content = readFileSync5(meta.sourcePath, "utf-8");
|
|
2665
3014
|
const messages = [];
|
|
2666
3015
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
@@ -2765,6 +3114,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2765
3114
|
if (pendingPlan && currentAssistantIndex !== null) {
|
|
2766
3115
|
messages[currentAssistantIndex].parts.push(pendingPlan);
|
|
2767
3116
|
}
|
|
3117
|
+
const cleanedMessages = cleanParsedMessages(messages);
|
|
2768
3118
|
return {
|
|
2769
3119
|
id: meta.id,
|
|
2770
3120
|
title: meta.title,
|
|
@@ -2773,14 +3123,14 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2773
3123
|
time_created: meta.createdAt,
|
|
2774
3124
|
time_updated: meta.updatedAt,
|
|
2775
3125
|
stats: {
|
|
2776
|
-
message_count:
|
|
3126
|
+
message_count: cleanedMessages.length,
|
|
2777
3127
|
total_input_tokens: totalInputTokens,
|
|
2778
3128
|
total_output_tokens: totalOutputTokens,
|
|
2779
3129
|
total_cache_read_tokens: totalCacheReadTokens || void 0,
|
|
2780
3130
|
total_cost: totalCost,
|
|
2781
3131
|
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
2782
3132
|
},
|
|
2783
|
-
messages
|
|
3133
|
+
messages: cleanedMessages
|
|
2784
3134
|
};
|
|
2785
3135
|
}
|
|
2786
3136
|
// ---- File listing ----
|
|
@@ -2796,7 +3146,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2796
3146
|
const files = [];
|
|
2797
3147
|
try {
|
|
2798
3148
|
for (const entry of readdirSync3(dir)) {
|
|
2799
|
-
const fullPath =
|
|
3149
|
+
const fullPath = join7(dir, entry);
|
|
2800
3150
|
const stat = statSync4(fullPath);
|
|
2801
3151
|
if (stat.isDirectory()) {
|
|
2802
3152
|
files.push(...this.walkDirForRolloutFiles(fullPath, options));
|
|
@@ -2813,8 +3163,8 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2813
3163
|
loadSessionIndex() {
|
|
2814
3164
|
if (this.sessionIndexCache.size > 0) return;
|
|
2815
3165
|
const roots = resolveProviderRoots();
|
|
2816
|
-
const indexPath =
|
|
2817
|
-
if (!
|
|
3166
|
+
const indexPath = join7(roots.codexRoot, "session_index.jsonl");
|
|
3167
|
+
if (!existsSync7(indexPath)) return;
|
|
2818
3168
|
try {
|
|
2819
3169
|
const content = readFileSync5(indexPath, "utf-8");
|
|
2820
3170
|
for (const record of parseJsonlLines(content)) {
|
|
@@ -2843,18 +3193,21 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2843
3193
|
}
|
|
2844
3194
|
}
|
|
2845
3195
|
parseSessionHead(filePath, options) {
|
|
3196
|
+
return getParsedSession(this.parseSessionHeadResult(filePath, options));
|
|
3197
|
+
}
|
|
3198
|
+
parseSessionHeadResult(filePath, options) {
|
|
2846
3199
|
if (options?.fast) {
|
|
2847
|
-
return this.
|
|
3200
|
+
return this.parseFastSessionHeadResult(filePath);
|
|
2848
3201
|
}
|
|
2849
3202
|
const content = readFileSync5(filePath, "utf-8");
|
|
2850
3203
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
2851
|
-
if (lines.length === 0) return
|
|
3204
|
+
if (lines.length === 0) return skippedSession("empty file");
|
|
2852
3205
|
const sessionId = extractSessionId(filePath);
|
|
2853
3206
|
let firstRecord;
|
|
2854
3207
|
try {
|
|
2855
3208
|
firstRecord = JSON.parse(lines[0]);
|
|
2856
3209
|
} catch {
|
|
2857
|
-
return
|
|
3210
|
+
return skippedSession("malformed first record");
|
|
2858
3211
|
}
|
|
2859
3212
|
const payload = firstRecord["payload"] ?? {};
|
|
2860
3213
|
const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
|
|
@@ -2877,14 +3230,18 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2877
3230
|
let scanPrevReasoning = 0;
|
|
2878
3231
|
let scanPrevCachedInput = 0;
|
|
2879
3232
|
const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
|
|
3233
|
+
let hasNonInternalRecord = false;
|
|
2880
3234
|
for (const line of lines) {
|
|
2881
3235
|
try {
|
|
2882
3236
|
const data = JSON.parse(line);
|
|
2883
3237
|
const recordType = String(data["type"] ?? "");
|
|
3238
|
+
const payload2 = data["payload"] ?? {};
|
|
3239
|
+
const payloadType = String(payload2["type"] ?? "");
|
|
3240
|
+
if (isInternalEventType2(recordType) || isInternalEventType2(payloadType)) continue;
|
|
3241
|
+
hasNonInternalRecord = true;
|
|
2884
3242
|
const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
|
|
2885
3243
|
if (recordTs > updatedAt) updatedAt = recordTs;
|
|
2886
3244
|
if (recordType === "session_meta" || recordType === "turn_context") {
|
|
2887
|
-
const payload2 = data["payload"] ?? {};
|
|
2888
3245
|
const nextModel = extractModelName(payload2["model"]);
|
|
2889
3246
|
if (nextModel) {
|
|
2890
3247
|
activeModel = nextModel;
|
|
@@ -2893,7 +3250,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2893
3250
|
continue;
|
|
2894
3251
|
}
|
|
2895
3252
|
if (recordType === "response_item") {
|
|
2896
|
-
const p =
|
|
3253
|
+
const p = payload2;
|
|
2897
3254
|
const pType = String(p["type"] ?? "");
|
|
2898
3255
|
if (COUNTED_TYPES.has(pType)) {
|
|
2899
3256
|
messageCount++;
|
|
@@ -2955,8 +3312,9 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2955
3312
|
} catch {
|
|
2956
3313
|
}
|
|
2957
3314
|
}
|
|
3315
|
+
if (!hasNonInternalRecord) return filteredSession("internal events only");
|
|
2958
3316
|
const directory = payload["cwd"] ? String(payload["cwd"]) : "";
|
|
2959
|
-
return {
|
|
3317
|
+
return parsedSession({
|
|
2960
3318
|
id: sessionId,
|
|
2961
3319
|
slug: `codex/${sessionId}`,
|
|
2962
3320
|
title,
|
|
@@ -2972,18 +3330,21 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2972
3330
|
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
2973
3331
|
},
|
|
2974
3332
|
model_usage: Object.keys(modelUsageMap).length > 0 ? modelUsageMap : void 0
|
|
2975
|
-
};
|
|
3333
|
+
});
|
|
2976
3334
|
}
|
|
2977
3335
|
parseFastSessionHead(filePath) {
|
|
3336
|
+
return getParsedSession(this.parseFastSessionHeadResult(filePath));
|
|
3337
|
+
}
|
|
3338
|
+
parseFastSessionHeadResult(filePath) {
|
|
2978
3339
|
const prefix = this.readFilePrefix(filePath);
|
|
2979
3340
|
const lines = prefix.split("\n").filter((l) => l.trim());
|
|
2980
|
-
if (lines.length === 0) return
|
|
3341
|
+
if (lines.length === 0) return skippedSession("empty file");
|
|
2981
3342
|
const sessionId = extractSessionId(filePath);
|
|
2982
3343
|
let firstRecord;
|
|
2983
3344
|
try {
|
|
2984
3345
|
firstRecord = JSON.parse(lines[0]);
|
|
2985
3346
|
} catch {
|
|
2986
|
-
return
|
|
3347
|
+
return skippedSession("malformed first record");
|
|
2987
3348
|
}
|
|
2988
3349
|
const payload = firstRecord["payload"] ?? {};
|
|
2989
3350
|
const stat = statSync4(filePath);
|
|
@@ -2993,7 +3354,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2993
3354
|
const directory = payload["cwd"] ? String(payload["cwd"]) : "";
|
|
2994
3355
|
const directoryTitle = basenameTitle(directory || null);
|
|
2995
3356
|
const title = resolveSessionTitle(indexTitle, messageTitle, directoryTitle);
|
|
2996
|
-
return {
|
|
3357
|
+
return parsedSession({
|
|
2997
3358
|
id: sessionId,
|
|
2998
3359
|
slug: `codex/${sessionId}`,
|
|
2999
3360
|
title,
|
|
@@ -3006,29 +3367,28 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3006
3367
|
total_output_tokens: 0,
|
|
3007
3368
|
total_cost: 0
|
|
3008
3369
|
}
|
|
3009
|
-
};
|
|
3370
|
+
});
|
|
3010
3371
|
}
|
|
3011
3372
|
extractTitleFromLines(lines) {
|
|
3012
|
-
let userMessageCount = 0;
|
|
3013
3373
|
for (const line of lines.slice(0, 20)) {
|
|
3014
3374
|
try {
|
|
3015
3375
|
const data = JSON.parse(line);
|
|
3016
3376
|
const recordType = String(data["type"] ?? "");
|
|
3017
|
-
if (recordType !== "response_item") continue;
|
|
3377
|
+
if (recordType !== "response_item" || isInternalEventType2(recordType)) continue;
|
|
3018
3378
|
const payload = data["payload"] ?? {};
|
|
3019
3379
|
const pType = String(payload["type"] ?? "");
|
|
3020
|
-
if (pType !== "message") continue;
|
|
3380
|
+
if (pType !== "message" || isInternalEventType2(pType)) continue;
|
|
3021
3381
|
if (String(payload["role"] ?? "") !== "user") continue;
|
|
3022
|
-
userMessageCount++;
|
|
3023
|
-
if (userMessageCount < 2) continue;
|
|
3024
3382
|
const content = payload["content"];
|
|
3383
|
+
let text = null;
|
|
3025
3384
|
if (Array.isArray(content)) {
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
if (typeof content === "string") {
|
|
3030
|
-
return normalizeTitleText3(content);
|
|
3385
|
+
text = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
|
|
3386
|
+
} else if (typeof content === "string") {
|
|
3387
|
+
text = content;
|
|
3031
3388
|
}
|
|
3389
|
+
if (!text || isDeveloperLikeUserMessage(text)) continue;
|
|
3390
|
+
const title = normalizeTitleText(text);
|
|
3391
|
+
if (title) return title;
|
|
3032
3392
|
} catch {
|
|
3033
3393
|
}
|
|
3034
3394
|
}
|
|
@@ -3037,6 +3397,9 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3037
3397
|
// ---- Record conversion ----
|
|
3038
3398
|
convertRecord(data, messages, pendingToolCalls, sessionId, currentAssistantIndex, latestAssistantTextIndex, pendingPlan) {
|
|
3039
3399
|
const recordType = String(data["type"] ?? "");
|
|
3400
|
+
if (isInternalEventType2(recordType)) {
|
|
3401
|
+
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3402
|
+
}
|
|
3040
3403
|
if (recordType === "session_meta" || recordType === "event_msg") {
|
|
3041
3404
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3042
3405
|
}
|
|
@@ -3045,6 +3408,9 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3045
3408
|
}
|
|
3046
3409
|
const payload = data["payload"] ?? {};
|
|
3047
3410
|
const payloadType = String(payload["type"] ?? "");
|
|
3411
|
+
if (isInternalEventType2(payloadType)) {
|
|
3412
|
+
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3413
|
+
}
|
|
3048
3414
|
const timestampMs = parseTimestampMs2(data) || parseTimestampMs2(payload);
|
|
3049
3415
|
switch (payloadType) {
|
|
3050
3416
|
case "message": {
|
|
@@ -3130,7 +3496,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3130
3496
|
};
|
|
3131
3497
|
pendingPlan = planPart;
|
|
3132
3498
|
}
|
|
3133
|
-
const displayText = fullText.replace(PROPOSED_PLAN_PATTERN, "")
|
|
3499
|
+
const displayText = cleanInternalText(fullText.replace(PROPOSED_PLAN_PATTERN, ""));
|
|
3134
3500
|
if (!displayText) {
|
|
3135
3501
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3136
3502
|
}
|
|
@@ -3163,13 +3529,14 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3163
3529
|
const text = Array.isArray(content) ? content.map(
|
|
3164
3530
|
(c) => typeof c === "object" && c !== null ? String(c["text"] ?? "") : String(c ?? "")
|
|
3165
3531
|
).join(" ") : String(content ?? "");
|
|
3166
|
-
|
|
3532
|
+
const visibleText = cleanInternalText(text);
|
|
3533
|
+
if (!visibleText) {
|
|
3167
3534
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3168
3535
|
}
|
|
3169
|
-
if (isDeveloperLikeUserMessage(
|
|
3536
|
+
if (isDeveloperLikeUserMessage(visibleText)) {
|
|
3170
3537
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3171
3538
|
}
|
|
3172
|
-
if (
|
|
3539
|
+
if (visibleText.trimStart().startsWith(PLAN_APPROVAL_PREFIX)) {
|
|
3173
3540
|
if (pendingPlan && currentAssistantIndex !== null) {
|
|
3174
3541
|
messages[currentAssistantIndex].parts.push(pendingPlan);
|
|
3175
3542
|
}
|
|
@@ -3179,14 +3546,14 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3179
3546
|
messageId: "",
|
|
3180
3547
|
role: "user",
|
|
3181
3548
|
timestampMs,
|
|
3182
|
-
parts: [{ type: "text", text:
|
|
3549
|
+
parts: [{ type: "text", text: visibleText, time_created: timestampMs }]
|
|
3183
3550
|
})
|
|
3184
3551
|
);
|
|
3185
3552
|
currentAssistantIndex = null;
|
|
3186
3553
|
latestAssistantTextIndex = null;
|
|
3187
3554
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
|
|
3188
3555
|
}
|
|
3189
|
-
const subagentMatch =
|
|
3556
|
+
const subagentMatch = visibleText.match(SUBAGENT_NOTIFICATION_PATTERN);
|
|
3190
3557
|
if (subagentMatch) {
|
|
3191
3558
|
try {
|
|
3192
3559
|
const notifPayload = JSON.parse(subagentMatch[1]);
|
|
@@ -3220,7 +3587,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3220
3587
|
messageId: "",
|
|
3221
3588
|
role: "user",
|
|
3222
3589
|
timestampMs,
|
|
3223
|
-
parts: [{ type: "text", text:
|
|
3590
|
+
parts: [{ type: "text", text: visibleText, time_created: timestampMs }]
|
|
3224
3591
|
})
|
|
3225
3592
|
);
|
|
3226
3593
|
currentAssistantIndex = null;
|
|
@@ -3329,8 +3696,8 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3329
3696
|
if (!callId) return;
|
|
3330
3697
|
const location = pendingToolCalls.get(callId);
|
|
3331
3698
|
if (!location) return;
|
|
3332
|
-
const outputText = String(payload["output"] ?? "");
|
|
3333
|
-
const outputParts = outputText
|
|
3699
|
+
const outputText = cleanInternalText(String(payload["output"] ?? ""));
|
|
3700
|
+
const outputParts = outputText ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
|
|
3334
3701
|
const [msgIndex, partIndex] = location;
|
|
3335
3702
|
const state = messages[msgIndex].parts[partIndex].state ?? (messages[msgIndex].parts[partIndex].state = {});
|
|
3336
3703
|
if (outputParts.length > 0) {
|
|
@@ -3396,8 +3763,8 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3396
3763
|
if (!callId) return;
|
|
3397
3764
|
const location = pendingToolCalls.get(callId);
|
|
3398
3765
|
if (!location) return;
|
|
3399
|
-
const outputText = String(payload["output"] ?? "");
|
|
3400
|
-
const outputParts = outputText
|
|
3766
|
+
const outputText = cleanInternalText(String(payload["output"] ?? ""));
|
|
3767
|
+
const outputParts = outputText ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
|
|
3401
3768
|
const [msgIndex, partIndex] = location;
|
|
3402
3769
|
const state = messages[msgIndex].parts[partIndex].state ?? (messages[msgIndex].parts[partIndex].state = {});
|
|
3403
3770
|
if (outputParts.length > 0) {
|
|
@@ -3436,7 +3803,8 @@ function mapToolTitle3(toolName) {
|
|
|
3436
3803
|
function normalizeToolOutputParts2(output, timestampMs) {
|
|
3437
3804
|
if (output == null) return [];
|
|
3438
3805
|
if (typeof output === "string") {
|
|
3439
|
-
|
|
3806
|
+
const text2 = cleanInternalText(output);
|
|
3807
|
+
return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
|
|
3440
3808
|
}
|
|
3441
3809
|
if (Array.isArray(output)) {
|
|
3442
3810
|
const parts = [];
|
|
@@ -3445,15 +3813,17 @@ function normalizeToolOutputParts2(output, timestampMs) {
|
|
|
3445
3813
|
const text2 = String(
|
|
3446
3814
|
item.text ?? item.content ?? ""
|
|
3447
3815
|
);
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3816
|
+
const cleaned = cleanInternalText(text2);
|
|
3817
|
+
if (cleaned) parts.push({ type: "text", text: cleaned, time_created: timestampMs });
|
|
3818
|
+
} else if (typeof item === "string") {
|
|
3819
|
+
const text2 = cleanInternalText(item);
|
|
3820
|
+
if (text2) parts.push({ type: "text", text: text2, time_created: timestampMs });
|
|
3451
3821
|
}
|
|
3452
3822
|
}
|
|
3453
3823
|
return parts;
|
|
3454
3824
|
}
|
|
3455
|
-
const text = String(output);
|
|
3456
|
-
return text
|
|
3825
|
+
const text = cleanInternalText(String(output));
|
|
3826
|
+
return text ? [{ type: "text", text, time_created: timestampMs }] : [];
|
|
3457
3827
|
}
|
|
3458
3828
|
function extractTimestamp(msg) {
|
|
3459
3829
|
if (msg.createdAt && typeof msg.createdAt === "number" && msg.createdAt > 0) {
|
|
@@ -3464,6 +3834,9 @@ function extractTimestamp(msg) {
|
|
|
3464
3834
|
}
|
|
3465
3835
|
return 0;
|
|
3466
3836
|
}
|
|
3837
|
+
function isInternalBubble(bubble) {
|
|
3838
|
+
return ["eventType", "kind", "subtype", "name"].some((key) => isInternalEventType(bubble[key]));
|
|
3839
|
+
}
|
|
3467
3840
|
function buildToolState(action) {
|
|
3468
3841
|
const state = {};
|
|
3469
3842
|
if (action.input) {
|
|
@@ -3502,7 +3875,7 @@ function buildToolPart(action, timestampMs) {
|
|
|
3502
3875
|
}
|
|
3503
3876
|
function buildTerminalToolPart(action, timestampMs) {
|
|
3504
3877
|
const command = String(action.input?.command ?? "");
|
|
3505
|
-
const description = String(action.input?.commandDescription ?? "");
|
|
3878
|
+
const description = cleanInternalText(String(action.input?.commandDescription ?? ""));
|
|
3506
3879
|
return {
|
|
3507
3880
|
type: "tool",
|
|
3508
3881
|
tool: "bash",
|
|
@@ -3537,7 +3910,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3537
3910
|
if (!isSqliteAvailable()) return null;
|
|
3538
3911
|
const dataPath = getCursorDataPath();
|
|
3539
3912
|
if (!dataPath) return null;
|
|
3540
|
-
return
|
|
3913
|
+
return join8(dataPath, "globalStorage", "state.vscdb");
|
|
3541
3914
|
}
|
|
3542
3915
|
/**
|
|
3543
3916
|
* Build a map of composerId → workspace folder path by reading
|
|
@@ -3547,8 +3920,8 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3547
3920
|
const map = /* @__PURE__ */ new Map();
|
|
3548
3921
|
const dataPath = getCursorDataPath();
|
|
3549
3922
|
if (!dataPath) return map;
|
|
3550
|
-
const wsStoragePath =
|
|
3551
|
-
if (!
|
|
3923
|
+
const wsStoragePath = join8(dataPath, "workspaceStorage");
|
|
3924
|
+
if (!existsSync8(wsStoragePath)) return map;
|
|
3552
3925
|
let entryNames;
|
|
3553
3926
|
try {
|
|
3554
3927
|
entryNames = readdirSync4(wsStoragePath);
|
|
@@ -3556,14 +3929,14 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3556
3929
|
return map;
|
|
3557
3930
|
}
|
|
3558
3931
|
for (const name of entryNames) {
|
|
3559
|
-
const wsDir =
|
|
3932
|
+
const wsDir = join8(wsStoragePath, name);
|
|
3560
3933
|
try {
|
|
3561
3934
|
if (!statSync5(wsDir).isDirectory()) continue;
|
|
3562
3935
|
} catch {
|
|
3563
3936
|
continue;
|
|
3564
3937
|
}
|
|
3565
|
-
const wsJsonPath =
|
|
3566
|
-
if (!
|
|
3938
|
+
const wsJsonPath = join8(wsDir, "workspace.json");
|
|
3939
|
+
if (!existsSync8(wsJsonPath)) continue;
|
|
3567
3940
|
let workspacePath;
|
|
3568
3941
|
try {
|
|
3569
3942
|
const data = JSON.parse(readFileSync6(wsJsonPath, "utf-8"));
|
|
@@ -3573,19 +3946,19 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3573
3946
|
} catch {
|
|
3574
3947
|
continue;
|
|
3575
3948
|
}
|
|
3576
|
-
const wsDbPath =
|
|
3577
|
-
if (!
|
|
3949
|
+
const wsDbPath = join8(wsDir, "state.vscdb");
|
|
3950
|
+
if (!existsSync8(wsDbPath)) continue;
|
|
3578
3951
|
const wsDb = openDbReadOnly(wsDbPath);
|
|
3579
3952
|
if (!wsDb) continue;
|
|
3580
3953
|
try {
|
|
3581
3954
|
const row = wsDb.prepare("SELECT value FROM ItemTable WHERE key = 'composer.composerData'").get();
|
|
3582
3955
|
if (!row?.value) continue;
|
|
3583
|
-
const
|
|
3956
|
+
const parsed2 = JSON.parse(row.value);
|
|
3584
3957
|
let composers;
|
|
3585
|
-
if (
|
|
3586
|
-
composers =
|
|
3587
|
-
} else if (Array.isArray(
|
|
3588
|
-
composers =
|
|
3958
|
+
if (parsed2 !== null && typeof parsed2 === "object" && "allComposers" in parsed2 && Array.isArray(parsed2["allComposers"])) {
|
|
3959
|
+
composers = parsed2.allComposers;
|
|
3960
|
+
} else if (Array.isArray(parsed2)) {
|
|
3961
|
+
composers = parsed2;
|
|
3589
3962
|
} else {
|
|
3590
3963
|
continue;
|
|
3591
3964
|
}
|
|
@@ -3602,7 +3975,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3602
3975
|
}
|
|
3603
3976
|
isAvailable() {
|
|
3604
3977
|
this.dbPath = this.findDbPath();
|
|
3605
|
-
return this.dbPath !== null &&
|
|
3978
|
+
return this.dbPath !== null && existsSync8(this.dbPath);
|
|
3606
3979
|
}
|
|
3607
3980
|
scan(options) {
|
|
3608
3981
|
if (!this.dbPath) return [];
|
|
@@ -3625,7 +3998,8 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3625
3998
|
const createdAt = composer.createdAt ?? 0;
|
|
3626
3999
|
const updatedAt = composer.updatedAt ?? composer.lastUpdatedAt ?? composer.lastSendTime ?? createdAt;
|
|
3627
4000
|
if (!matchesScanWindow(updatedAt, options)) continue;
|
|
3628
|
-
const
|
|
4001
|
+
const fastTitle = this.extractTitle(composer);
|
|
4002
|
+
const hasFastMessages = Array.isArray(composer.chatMessages);
|
|
3629
4003
|
const fastMessageCount = composer.chatMessages?.length ?? 0;
|
|
3630
4004
|
const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
|
|
3631
4005
|
if (options?.fast) {
|
|
@@ -3634,21 +4008,25 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3634
4008
|
input: composer.inputTokenCount ?? 0,
|
|
3635
4009
|
output: composer.outputTokenCount ?? 0
|
|
3636
4010
|
}) ?? 0;
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
4011
|
+
const head = getParsedSession(
|
|
4012
|
+
hasFastMessages && fastMessageCount === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession({
|
|
4013
|
+
id: composerId,
|
|
4014
|
+
slug: `cursor/${composerId}`,
|
|
4015
|
+
title: fastTitle,
|
|
4016
|
+
directory: directory2,
|
|
4017
|
+
time_created: createdAt,
|
|
4018
|
+
time_updated: updatedAt || void 0,
|
|
4019
|
+
stats: {
|
|
4020
|
+
message_count: fastMessageCount,
|
|
4021
|
+
total_input_tokens: composer.inputTokenCount ?? 0,
|
|
4022
|
+
total_output_tokens: composer.outputTokenCount ?? 0,
|
|
4023
|
+
total_cost: totalCost2,
|
|
4024
|
+
cost_source: totalCost2 > 0 ? "estimated" : void 0
|
|
4025
|
+
}
|
|
4026
|
+
})
|
|
4027
|
+
);
|
|
4028
|
+
if (!head) continue;
|
|
4029
|
+
heads.push(head);
|
|
3652
4030
|
this.composerCache.set(composerId, composer);
|
|
3653
4031
|
this.sessionMetaMap.set(composerId, {
|
|
3654
4032
|
id: composerId,
|
|
@@ -3658,16 +4036,20 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3658
4036
|
}
|
|
3659
4037
|
const requestId = this.extractRequestIdFromBubbles(db, composerId);
|
|
3660
4038
|
const sessionId = requestId || composerId;
|
|
3661
|
-
const
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
4039
|
+
const parsedMessages = cleanParsedMessages(
|
|
4040
|
+
this.loadMessagesFromBubbles(
|
|
4041
|
+
db,
|
|
4042
|
+
composerId,
|
|
4043
|
+
sessionId,
|
|
4044
|
+
composer.modelConfig?.modelName ?? composer.model ?? null
|
|
4045
|
+
)
|
|
3666
4046
|
);
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
4047
|
+
const messages = getParsedSession(
|
|
4048
|
+
parsedMessages.length === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession(parsedMessages)
|
|
4049
|
+
);
|
|
4050
|
+
if (!messages) continue;
|
|
3670
4051
|
const messageCount = messages.length;
|
|
4052
|
+
const title = this.extractTitle(composer, messages);
|
|
3671
4053
|
const directory = workspacePathMap.get(composerId) ?? "";
|
|
3672
4054
|
const modelUsageMap = {};
|
|
3673
4055
|
let totalCost = 0;
|
|
@@ -3735,7 +4117,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3735
4117
|
if (!this.dbPath) {
|
|
3736
4118
|
this.dbPath = this.findDbPath();
|
|
3737
4119
|
}
|
|
3738
|
-
if (!this.dbPath || !
|
|
4120
|
+
if (!this.dbPath || !existsSync8(this.dbPath)) {
|
|
3739
4121
|
return { hasChanges: false, timestamp: Date.now() };
|
|
3740
4122
|
}
|
|
3741
4123
|
try {
|
|
@@ -3785,7 +4167,6 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3785
4167
|
throw new Error(`Session not found: ${sessionId}`);
|
|
3786
4168
|
}
|
|
3787
4169
|
const composerId = composer.id || composer.composerId || "";
|
|
3788
|
-
const title = this.extractTitle(composer);
|
|
3789
4170
|
const createdAt = composer.createdAt ?? 0;
|
|
3790
4171
|
const updatedAt = composer.updatedAt ?? createdAt;
|
|
3791
4172
|
const messages = this.loadMessagesFromBubbles(
|
|
@@ -3794,10 +4175,13 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3794
4175
|
resolvedSessionId,
|
|
3795
4176
|
composer.modelConfig?.modelName ?? composer.model ?? null
|
|
3796
4177
|
);
|
|
4178
|
+
this.appendSubagentMessages(db, composer, messages);
|
|
4179
|
+
const cleanedMessages = cleanParsedMessages(messages);
|
|
4180
|
+
const title = this.extractTitle(composer, cleanedMessages);
|
|
3797
4181
|
let totalInputTokens = 0;
|
|
3798
4182
|
let totalOutputTokens = 0;
|
|
3799
4183
|
let totalCost = 0;
|
|
3800
|
-
for (const msg of
|
|
4184
|
+
for (const msg of cleanedMessages) {
|
|
3801
4185
|
totalInputTokens += msg.tokens?.input ?? 0;
|
|
3802
4186
|
totalOutputTokens += msg.tokens?.output ?? 0;
|
|
3803
4187
|
totalCost += msg.cost ?? 0;
|
|
@@ -3810,7 +4194,6 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3810
4194
|
output: totalOutputTokens
|
|
3811
4195
|
}) ?? 0;
|
|
3812
4196
|
}
|
|
3813
|
-
this.appendSubagentMessages(db, composer, messages);
|
|
3814
4197
|
const cachedDir = this.composerCache.get(`__dir__${composerId}`);
|
|
3815
4198
|
const directory = cachedDir?.directory ?? this.buildWorkspacePathMap().get(composerId) ?? "";
|
|
3816
4199
|
return {
|
|
@@ -3821,13 +4204,13 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3821
4204
|
time_created: createdAt,
|
|
3822
4205
|
time_updated: updatedAt || void 0,
|
|
3823
4206
|
stats: {
|
|
3824
|
-
message_count:
|
|
4207
|
+
message_count: cleanedMessages.length,
|
|
3825
4208
|
total_input_tokens: totalInputTokens,
|
|
3826
4209
|
total_output_tokens: totalOutputTokens,
|
|
3827
4210
|
total_cost: totalCost,
|
|
3828
4211
|
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
3829
4212
|
},
|
|
3830
|
-
messages
|
|
4213
|
+
messages: cleanedMessages
|
|
3831
4214
|
};
|
|
3832
4215
|
} finally {
|
|
3833
4216
|
db.close();
|
|
@@ -3876,19 +4259,10 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3876
4259
|
return null;
|
|
3877
4260
|
}
|
|
3878
4261
|
/** Extract title from composer (like agent-dump) */
|
|
3879
|
-
extractTitle(composer) {
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (composer.title && typeof composer.title === "string" && composer.title.trim()) {
|
|
3884
|
-
return composer.title.trim();
|
|
3885
|
-
}
|
|
3886
|
-
if (composer.text && typeof composer.text === "string" && composer.text.trim()) {
|
|
3887
|
-
const firstLine = composer.text.split("\n").find((l) => l.trim())?.trim().slice(0, 80);
|
|
3888
|
-
if (firstLine) return firstLine;
|
|
3889
|
-
}
|
|
3890
|
-
const composerId = composer.composerId || composer.id || "";
|
|
3891
|
-
return `Cursor Session ${composerId.slice(0, 8)}`;
|
|
4262
|
+
extractTitle(composer, messages = []) {
|
|
4263
|
+
const explicit = composer.name || composer.title;
|
|
4264
|
+
const messageTitle = firstUserMessageTitle(messages) ?? composer.text;
|
|
4265
|
+
return resolveSessionTitle(explicit, messageTitle, null);
|
|
3892
4266
|
}
|
|
3893
4267
|
/** Count messages from bubbles */
|
|
3894
4268
|
countMessagesFromBubbles(db, composerId) {
|
|
@@ -3919,6 +4293,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3919
4293
|
for (const row of rows) {
|
|
3920
4294
|
try {
|
|
3921
4295
|
const bubble = JSON.parse(row.value);
|
|
4296
|
+
if (isInternalBubble(bubble)) continue;
|
|
3922
4297
|
const bubbleId = row.key.split(":").pop() || String(messageIndex);
|
|
3923
4298
|
const role = bubble.type === 2 ? "assistant" : "user";
|
|
3924
4299
|
let timestampMs = 0;
|
|
@@ -3935,7 +4310,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3935
4310
|
const inputTokens = bubble.tokenCount?.inputTokens ?? 0;
|
|
3936
4311
|
const outputTokens = bubble.tokenCount?.outputTokens ?? 0;
|
|
3937
4312
|
const parts = [];
|
|
3938
|
-
const text = bubble.text
|
|
4313
|
+
const text = cleanInternalText(bubble.text ?? "");
|
|
3939
4314
|
if (text) {
|
|
3940
4315
|
parts.push({ type: "text", text, time_created: timestampMs });
|
|
3941
4316
|
}
|
|
@@ -3993,10 +4368,10 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3993
4368
|
if (toolData.result !== void 0) {
|
|
3994
4369
|
if (typeof toolData.result === "string") {
|
|
3995
4370
|
try {
|
|
3996
|
-
const
|
|
3997
|
-
state.output =
|
|
3998
|
-
if (
|
|
3999
|
-
state.error =
|
|
4371
|
+
const parsed2 = JSON.parse(toolData.result);
|
|
4372
|
+
state.output = parsed2;
|
|
4373
|
+
if (parsed2.error || parsed2.message || parsed2.stderr) {
|
|
4374
|
+
state.error = parsed2.error || parsed2.message || parsed2.stderr;
|
|
4000
4375
|
state.status = "error";
|
|
4001
4376
|
}
|
|
4002
4377
|
} catch {
|
|
@@ -4021,7 +4396,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
4021
4396
|
type: "tool",
|
|
4022
4397
|
tool: normalizedName,
|
|
4023
4398
|
callID: toolData.toolCallId || "",
|
|
4024
|
-
title: `Tool: ${
|
|
4399
|
+
title: `Tool: ${normalizedName}`,
|
|
4025
4400
|
state,
|
|
4026
4401
|
time_created: timestampMs
|
|
4027
4402
|
};
|
|
@@ -4056,12 +4431,13 @@ var CursorAgent = class extends BaseAgent {
|
|
|
4056
4431
|
if (role !== "user" && role !== "assistant") continue;
|
|
4057
4432
|
const timestampMs = extractTimestamp(chatMsg);
|
|
4058
4433
|
const parts = [];
|
|
4059
|
-
const text = chatMsg.text ?? "";
|
|
4060
|
-
if (text
|
|
4434
|
+
const text = cleanInternalText(chatMsg.text ?? "");
|
|
4435
|
+
if (text) {
|
|
4061
4436
|
parts.push({ type: "text", text, time_created: timestampMs });
|
|
4062
4437
|
}
|
|
4063
4438
|
if (role === "assistant" && Array.isArray(chatMsg.actions)) {
|
|
4064
4439
|
for (const action of chatMsg.actions) {
|
|
4440
|
+
if (isInternalEventType(action.type) || isInternalEventType(action.tool)) continue;
|
|
4065
4441
|
const part = convertActionToPart(action, timestampMs);
|
|
4066
4442
|
if (part) parts.push(part);
|
|
4067
4443
|
}
|
|
@@ -4124,7 +4500,7 @@ function fallbackDisplayName(input) {
|
|
|
4124
4500
|
}
|
|
4125
4501
|
var realFs = {
|
|
4126
4502
|
exists(path2) {
|
|
4127
|
-
return
|
|
4503
|
+
return existsSync9(path2);
|
|
4128
4504
|
},
|
|
4129
4505
|
readText(path2) {
|
|
4130
4506
|
try {
|
|
@@ -4401,27 +4777,206 @@ function hasEditedDocPath(value) {
|
|
|
4401
4777
|
}
|
|
4402
4778
|
return Object.values(record).some(hasEditedDocPath);
|
|
4403
4779
|
}
|
|
4404
|
-
|
|
4780
|
+
function toRecord(value) {
|
|
4781
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4782
|
+
return value;
|
|
4783
|
+
}
|
|
4784
|
+
return {};
|
|
4785
|
+
}
|
|
4786
|
+
function toStringValue(value) {
|
|
4787
|
+
return typeof value === "string" ? value : "";
|
|
4788
|
+
}
|
|
4789
|
+
function normalizeToolLabel(part) {
|
|
4790
|
+
if (typeof part.title === "string" && part.title.trim()) {
|
|
4791
|
+
return part.title.trim().replace(/^tool:\s*/i, "");
|
|
4792
|
+
}
|
|
4793
|
+
if (typeof part.tool === "string" && part.tool.trim()) return part.tool.trim();
|
|
4794
|
+
return "tool";
|
|
4795
|
+
}
|
|
4796
|
+
function normalizeToolName(part) {
|
|
4797
|
+
return normalizeToolLabel(part).trim().toLowerCase();
|
|
4798
|
+
}
|
|
4799
|
+
function looksLikeFilePath(value) {
|
|
4800
|
+
const text = value.trim();
|
|
4801
|
+
if (!text || text.length > 300) return false;
|
|
4802
|
+
if (text.includes("\n")) return false;
|
|
4803
|
+
if (/^[a-z]+:\/\//i.test(text)) return false;
|
|
4804
|
+
if (/[<>{}]/.test(text)) return false;
|
|
4805
|
+
if (text.startsWith("/")) return true;
|
|
4806
|
+
if (text.startsWith("./") || text.startsWith("../") || text.startsWith("~/")) return true;
|
|
4807
|
+
if (text.includes("/") || text.includes("\\")) return true;
|
|
4808
|
+
return /^[A-Za-z0-9_.@-]+\.[A-Za-z0-9_-]+$/.test(text);
|
|
4809
|
+
}
|
|
4810
|
+
function shouldTreatAsPathKey(key) {
|
|
4811
|
+
const normalized = key.trim().toLowerCase();
|
|
4812
|
+
if (!normalized) return false;
|
|
4813
|
+
if (normalized.includes("command") || normalized.includes("content") || normalized.includes("text") || normalized.includes("prompt") || normalized.includes("url") || normalized.includes("body") || normalized.includes("title") || normalized.includes("description") || normalized === "cwd" || normalized === "workdir" || normalized === "directory") {
|
|
4814
|
+
return false;
|
|
4815
|
+
}
|
|
4816
|
+
return normalized === "path" || normalized === "paths" || normalized.includes("file") || normalized.includes("path");
|
|
4817
|
+
}
|
|
4818
|
+
function collectPathsFromValue(value, keyHint, paths, depth = 0) {
|
|
4819
|
+
if (value == null || depth > 4) return;
|
|
4820
|
+
if (typeof value === "string") {
|
|
4821
|
+
if (shouldTreatAsPathKey(keyHint) && looksLikeFilePath(value)) {
|
|
4822
|
+
paths.add(value.trim());
|
|
4823
|
+
}
|
|
4824
|
+
return;
|
|
4825
|
+
}
|
|
4826
|
+
if (Array.isArray(value)) {
|
|
4827
|
+
for (const item of value) {
|
|
4828
|
+
collectPathsFromValue(item, keyHint, paths, depth + 1);
|
|
4829
|
+
}
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
if (typeof value === "object") {
|
|
4833
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
4834
|
+
collectPathsFromValue(nested, key, paths, depth + 1);
|
|
4835
|
+
}
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
function extractPathsFromToolInput(inputValue) {
|
|
4839
|
+
const paths = /* @__PURE__ */ new Set();
|
|
4840
|
+
collectPathsFromValue(inputValue, "", paths);
|
|
4841
|
+
return [...paths];
|
|
4842
|
+
}
|
|
4843
|
+
function getToolInputValue(part) {
|
|
4844
|
+
return part.state?.arguments ?? part.state?.input ?? part.input ?? null;
|
|
4845
|
+
}
|
|
4846
|
+
function classifyToolKind(part) {
|
|
4847
|
+
const toolName = normalizeToolName(part);
|
|
4848
|
+
if (toolName === "read" || toolName === "readfile" || toolName === "read_file") return "read";
|
|
4849
|
+
if (toolName === "edit" || toolName === "multiedit" || toolName === "apply_patch" || toolName === "notebookedit") {
|
|
4850
|
+
return "edit";
|
|
4851
|
+
}
|
|
4852
|
+
if (toolName === "write" || toolName === "writefile" || toolName === "write_file" || toolName === "create_file") {
|
|
4853
|
+
return "write";
|
|
4854
|
+
}
|
|
4855
|
+
if (toolName === "delete" || toolName === "delete_file") {
|
|
4856
|
+
return "delete";
|
|
4857
|
+
}
|
|
4858
|
+
return null;
|
|
4859
|
+
}
|
|
4860
|
+
function normalizeCodexPatchEntry(entry) {
|
|
4861
|
+
const record = toRecord(entry);
|
|
4862
|
+
const type = toStringValue(record.type);
|
|
4863
|
+
if (!type) return null;
|
|
4864
|
+
return {
|
|
4865
|
+
type,
|
|
4866
|
+
path: toStringValue(record.path),
|
|
4867
|
+
oldPath: toStringValue(record.old_path)
|
|
4868
|
+
};
|
|
4869
|
+
}
|
|
4870
|
+
function getCodexPatchEntries(inputValue) {
|
|
4871
|
+
const input = toRecord(inputValue);
|
|
4872
|
+
const rawContent = Array.isArray(inputValue) ? inputValue : Array.isArray(input.content) ? input.content : [];
|
|
4873
|
+
return rawContent.map((entry) => normalizeCodexPatchEntry(entry)).filter((entry) => entry != null);
|
|
4874
|
+
}
|
|
4875
|
+
function patchEntryKind(type) {
|
|
4876
|
+
if (type === "write_file") return "write";
|
|
4877
|
+
if (type === "delete_file") return "delete";
|
|
4878
|
+
return "edit";
|
|
4879
|
+
}
|
|
4880
|
+
function extractFileActivityOccurrences(messages) {
|
|
4881
|
+
const occurrences = [];
|
|
4882
|
+
messages.forEach((message, messageIndex) => {
|
|
4883
|
+
let toolIndex = 0;
|
|
4884
|
+
for (const part of message.parts) {
|
|
4885
|
+
if (part.type !== "tool") continue;
|
|
4886
|
+
const inputValue = getToolInputValue(part);
|
|
4887
|
+
const toolLabel = normalizeToolLabel(part);
|
|
4888
|
+
const time = part.time_created ?? message.time_created;
|
|
4889
|
+
const currentToolIndex = toolIndex;
|
|
4890
|
+
toolIndex += 1;
|
|
4891
|
+
const patchEntries = getCodexPatchEntries(inputValue);
|
|
4892
|
+
if (patchEntries.length > 0) {
|
|
4893
|
+
for (const entry of patchEntries) {
|
|
4894
|
+
const path2 = (entry.path || entry.oldPath).trim();
|
|
4895
|
+
if (!path2) continue;
|
|
4896
|
+
occurrences.push({
|
|
4897
|
+
path: path2,
|
|
4898
|
+
kind: patchEntryKind(entry.type),
|
|
4899
|
+
time,
|
|
4900
|
+
tool_label: toolLabel,
|
|
4901
|
+
message_index: messageIndex,
|
|
4902
|
+
tool_index: currentToolIndex
|
|
4903
|
+
});
|
|
4904
|
+
}
|
|
4905
|
+
continue;
|
|
4906
|
+
}
|
|
4907
|
+
const kind = classifyToolKind(part);
|
|
4908
|
+
if (!kind) continue;
|
|
4909
|
+
for (const path2 of extractPathsFromToolInput(inputValue)) {
|
|
4910
|
+
occurrences.push({
|
|
4911
|
+
path: path2,
|
|
4912
|
+
kind,
|
|
4913
|
+
time,
|
|
4914
|
+
tool_label: toolLabel,
|
|
4915
|
+
message_index: messageIndex,
|
|
4916
|
+
tool_index: currentToolIndex
|
|
4917
|
+
});
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
});
|
|
4921
|
+
return occurrences;
|
|
4922
|
+
}
|
|
4923
|
+
function summarizeFileActivity(agentName, sessionId, projectIdentityKey, occurrences) {
|
|
4924
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4925
|
+
for (const occurrence of occurrences) {
|
|
4926
|
+
const key = `${occurrence.kind}\0${occurrence.path}`;
|
|
4927
|
+
const current = grouped.get(key);
|
|
4928
|
+
if (current) {
|
|
4929
|
+
current.count += 1;
|
|
4930
|
+
current.latest_time = Math.max(current.latest_time, occurrence.time);
|
|
4931
|
+
continue;
|
|
4932
|
+
}
|
|
4933
|
+
grouped.set(key, {
|
|
4934
|
+
agent_name: agentName,
|
|
4935
|
+
session_id: sessionId,
|
|
4936
|
+
project_identity_key: projectIdentityKey,
|
|
4937
|
+
path: occurrence.path,
|
|
4938
|
+
kind: occurrence.kind,
|
|
4939
|
+
count: 1,
|
|
4940
|
+
latest_time: occurrence.time
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
return [...grouped.values()].sort((a, b) => {
|
|
4944
|
+
if (b.latest_time !== a.latest_time) return b.latest_time - a.latest_time;
|
|
4945
|
+
return a.path.localeCompare(b.path);
|
|
4946
|
+
});
|
|
4947
|
+
}
|
|
4948
|
+
function extractSessionFileActivity(agentName, sessionId, projectIdentityKey, messages) {
|
|
4949
|
+
return summarizeFileActivity(
|
|
4950
|
+
agentName,
|
|
4951
|
+
sessionId,
|
|
4952
|
+
projectIdentityKey,
|
|
4953
|
+
extractFileActivityOccurrences(messages)
|
|
4954
|
+
);
|
|
4955
|
+
}
|
|
4956
|
+
var CACHE_SCHEMA_VERSION = 8;
|
|
4405
4957
|
var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
|
|
4406
4958
|
var CACHE_FILENAME = "codesesh.db";
|
|
4407
4959
|
var LEGACY_CACHE_FILENAME = "scan-cache.json";
|
|
4960
|
+
var SEARCH_INDEX_BULK_SYNC_THRESHOLD = 100;
|
|
4961
|
+
var ftsIntegrityCheckedPath = null;
|
|
4408
4962
|
function getCacheDir2() {
|
|
4409
|
-
return
|
|
4963
|
+
return join9(homedir4(), ".cache", "codesesh");
|
|
4410
4964
|
}
|
|
4411
4965
|
function getCachePath2() {
|
|
4412
|
-
return
|
|
4966
|
+
return join9(getCacheDir2(), CACHE_FILENAME);
|
|
4413
4967
|
}
|
|
4414
4968
|
function getLegacyCachePath() {
|
|
4415
|
-
return
|
|
4969
|
+
return join9(getCacheDir2(), LEGACY_CACHE_FILENAME);
|
|
4416
4970
|
}
|
|
4417
4971
|
function hasCacheStorage() {
|
|
4418
|
-
return
|
|
4972
|
+
return existsSync10(getCachePath2());
|
|
4419
4973
|
}
|
|
4420
4974
|
function withCacheDb(fn) {
|
|
4421
|
-
const
|
|
4975
|
+
const cachePath = getCachePath2();
|
|
4976
|
+
const db = openDb(cachePath);
|
|
4422
4977
|
if (!db) return null;
|
|
4423
4978
|
try {
|
|
4424
|
-
ensureSchema(db);
|
|
4979
|
+
ensureSchema(db, cachePath);
|
|
4425
4980
|
return fn(db);
|
|
4426
4981
|
} catch {
|
|
4427
4982
|
return null;
|
|
@@ -4429,7 +4984,7 @@ function withCacheDb(fn) {
|
|
|
4429
4984
|
db.close();
|
|
4430
4985
|
}
|
|
4431
4986
|
}
|
|
4432
|
-
function
|
|
4987
|
+
function createCacheTables(db) {
|
|
4433
4988
|
db.exec(`
|
|
4434
4989
|
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
4435
4990
|
key TEXT PRIMARY KEY,
|
|
@@ -4448,50 +5003,118 @@ function ensureSchema(db) {
|
|
|
4448
5003
|
meta_json TEXT,
|
|
4449
5004
|
PRIMARY KEY (agent_name, session_id)
|
|
4450
5005
|
);
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
5006
|
+
`);
|
|
5007
|
+
}
|
|
5008
|
+
function createSessionTables(db) {
|
|
5009
|
+
db.exec(`
|
|
5010
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
4454
5011
|
agent_name TEXT NOT NULL,
|
|
4455
5012
|
session_id TEXT NOT NULL,
|
|
5013
|
+
sort_index INTEGER NOT NULL DEFAULT 0,
|
|
4456
5014
|
slug TEXT NOT NULL,
|
|
4457
5015
|
title TEXT NOT NULL,
|
|
5016
|
+
source_path TEXT,
|
|
4458
5017
|
directory TEXT NOT NULL,
|
|
4459
|
-
project_identity_kind TEXT NOT NULL
|
|
4460
|
-
project_identity_key TEXT NOT NULL
|
|
4461
|
-
project_display_name TEXT NOT NULL
|
|
5018
|
+
project_identity_kind TEXT NOT NULL,
|
|
5019
|
+
project_identity_key TEXT NOT NULL,
|
|
5020
|
+
project_display_name TEXT NOT NULL,
|
|
4462
5021
|
time_created INTEGER NOT NULL,
|
|
4463
5022
|
time_updated INTEGER,
|
|
4464
5023
|
activity_time INTEGER NOT NULL,
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
5024
|
+
message_count INTEGER NOT NULL,
|
|
5025
|
+
total_input_tokens INTEGER NOT NULL,
|
|
5026
|
+
total_output_tokens INTEGER NOT NULL,
|
|
5027
|
+
total_cache_read_tokens INTEGER,
|
|
5028
|
+
total_cache_create_tokens INTEGER,
|
|
5029
|
+
total_cost REAL NOT NULL,
|
|
5030
|
+
cost_source TEXT,
|
|
5031
|
+
total_tokens INTEGER,
|
|
5032
|
+
model_usage_json TEXT,
|
|
5033
|
+
smart_tags_json TEXT,
|
|
5034
|
+
smart_tags_source_updated_at INTEGER,
|
|
5035
|
+
meta_json TEXT,
|
|
5036
|
+
PRIMARY KEY (agent_name, session_id)
|
|
4469
5037
|
);
|
|
4470
5038
|
|
|
4471
|
-
CREATE
|
|
5039
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_agent_activity
|
|
5040
|
+
ON sessions(agent_name, activity_time);
|
|
5041
|
+
|
|
5042
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
5043
|
+
ON sessions(project_identity_kind, project_identity_key, activity_time);
|
|
5044
|
+
|
|
5045
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
4472
5046
|
agent_name TEXT NOT NULL,
|
|
4473
5047
|
session_id TEXT NOT NULL,
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
5048
|
+
message_index INTEGER NOT NULL,
|
|
5049
|
+
message_id TEXT NOT NULL,
|
|
5050
|
+
role TEXT NOT NULL,
|
|
5051
|
+
time_created INTEGER NOT NULL,
|
|
5052
|
+
time_completed INTEGER,
|
|
5053
|
+
agent TEXT,
|
|
5054
|
+
mode TEXT,
|
|
5055
|
+
model TEXT,
|
|
5056
|
+
provider TEXT,
|
|
5057
|
+
tokens_json TEXT,
|
|
5058
|
+
cost REAL,
|
|
5059
|
+
cost_source TEXT,
|
|
5060
|
+
parts_json TEXT NOT NULL,
|
|
5061
|
+
subagent_id TEXT,
|
|
5062
|
+
nickname TEXT,
|
|
5063
|
+
content_text TEXT NOT NULL,
|
|
5064
|
+
tool_metadata_json TEXT,
|
|
5065
|
+
PRIMARY KEY (agent_name, session_id, message_index),
|
|
5066
|
+
FOREIGN KEY (agent_name, session_id)
|
|
5067
|
+
REFERENCES sessions(agent_name, session_id)
|
|
5068
|
+
ON DELETE CASCADE
|
|
4480
5069
|
);
|
|
4481
5070
|
|
|
4482
|
-
CREATE INDEX IF NOT EXISTS
|
|
4483
|
-
ON
|
|
5071
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
5072
|
+
ON messages(agent_name, session_id, message_index);
|
|
5073
|
+
`);
|
|
5074
|
+
}
|
|
5075
|
+
function createFileActivityTables(db) {
|
|
5076
|
+
db.exec(`
|
|
5077
|
+
CREATE TABLE IF NOT EXISTS session_file_activity (
|
|
5078
|
+
agent_name TEXT NOT NULL,
|
|
5079
|
+
session_id TEXT NOT NULL,
|
|
5080
|
+
project_identity_key TEXT NOT NULL,
|
|
5081
|
+
path TEXT NOT NULL,
|
|
5082
|
+
kind TEXT NOT NULL,
|
|
5083
|
+
count INTEGER NOT NULL,
|
|
5084
|
+
latest_time INTEGER NOT NULL,
|
|
5085
|
+
PRIMARY KEY (agent_name, session_id, project_identity_key, path, kind),
|
|
5086
|
+
FOREIGN KEY (agent_name, session_id)
|
|
5087
|
+
REFERENCES sessions(agent_name, session_id)
|
|
5088
|
+
ON DELETE CASCADE
|
|
5089
|
+
);
|
|
4484
5090
|
|
|
4485
|
-
CREATE
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
5091
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest
|
|
5092
|
+
ON session_file_activity(project_identity_key, latest_time);
|
|
5093
|
+
|
|
5094
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_path
|
|
5095
|
+
ON session_file_activity(path);
|
|
5096
|
+
|
|
5097
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_kind
|
|
5098
|
+
ON session_file_activity(kind);
|
|
5099
|
+
`);
|
|
5100
|
+
}
|
|
5101
|
+
function createSearchTables(db) {
|
|
5102
|
+
db.exec(`
|
|
5103
|
+
CREATE TABLE IF NOT EXISTS session_documents (
|
|
5104
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5105
|
+
agent_name TEXT NOT NULL,
|
|
5106
|
+
session_id TEXT NOT NULL,
|
|
5107
|
+
slug TEXT NOT NULL,
|
|
5108
|
+
title TEXT NOT NULL,
|
|
5109
|
+
directory TEXT NOT NULL,
|
|
5110
|
+
time_created INTEGER NOT NULL,
|
|
5111
|
+
time_updated INTEGER,
|
|
5112
|
+
activity_time INTEGER NOT NULL,
|
|
5113
|
+
content_text TEXT NOT NULL,
|
|
5114
|
+
content_hash TEXT NOT NULL,
|
|
5115
|
+
indexed_at INTEGER NOT NULL,
|
|
5116
|
+
UNIQUE(agent_name, session_id)
|
|
5117
|
+
);
|
|
4495
5118
|
|
|
4496
5119
|
CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
|
|
4497
5120
|
title,
|
|
@@ -4499,7 +5122,11 @@ function ensureSchema(db) {
|
|
|
4499
5122
|
content='session_documents',
|
|
4500
5123
|
content_rowid='id'
|
|
4501
5124
|
);
|
|
4502
|
-
|
|
5125
|
+
`);
|
|
5126
|
+
createSearchTriggers(db);
|
|
5127
|
+
}
|
|
5128
|
+
function createSearchTriggers(db) {
|
|
5129
|
+
db.exec(`
|
|
4503
5130
|
CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
|
|
4504
5131
|
INSERT INTO session_documents_fts(rowid, title, content_text)
|
|
4505
5132
|
VALUES (new.id, new.title, new.content_text);
|
|
@@ -4517,33 +5144,686 @@ function ensureSchema(db) {
|
|
|
4517
5144
|
VALUES (new.id, new.title, new.content_text);
|
|
4518
5145
|
END;
|
|
4519
5146
|
`);
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
5147
|
+
}
|
|
5148
|
+
function dropSearchTriggers(db) {
|
|
5149
|
+
db.exec(`
|
|
5150
|
+
DROP TRIGGER IF EXISTS session_documents_ai;
|
|
5151
|
+
DROP TRIGGER IF EXISTS session_documents_ad;
|
|
5152
|
+
DROP TRIGGER IF EXISTS session_documents_au;
|
|
5153
|
+
`);
|
|
5154
|
+
}
|
|
5155
|
+
function ensureProjectColumns(db) {
|
|
5156
|
+
if (!tableExists(db, "session_documents")) {
|
|
5157
|
+
return;
|
|
5158
|
+
}
|
|
5159
|
+
if (!columnExists(db, "session_documents", "project_identity_kind")) {
|
|
5160
|
+
db.exec(
|
|
5161
|
+
"ALTER TABLE session_documents ADD COLUMN project_identity_kind TEXT NOT NULL DEFAULT 'path'"
|
|
5162
|
+
);
|
|
5163
|
+
}
|
|
5164
|
+
if (!columnExists(db, "session_documents", "project_identity_key")) {
|
|
5165
|
+
db.exec(
|
|
5166
|
+
"ALTER TABLE session_documents ADD COLUMN project_identity_key TEXT NOT NULL DEFAULT ''"
|
|
5167
|
+
);
|
|
5168
|
+
}
|
|
5169
|
+
if (!columnExists(db, "session_documents", "project_display_name")) {
|
|
5170
|
+
db.exec(
|
|
5171
|
+
"ALTER TABLE session_documents ADD COLUMN project_display_name TEXT NOT NULL DEFAULT ''"
|
|
5172
|
+
);
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
function createProjectTables(db) {
|
|
5176
|
+
ensureProjectColumns(db);
|
|
5177
|
+
db.exec(`
|
|
5178
|
+
CREATE TABLE IF NOT EXISTS project_sessions (
|
|
5179
|
+
agent_name TEXT NOT NULL,
|
|
5180
|
+
session_id TEXT NOT NULL,
|
|
5181
|
+
identity_kind TEXT NOT NULL,
|
|
5182
|
+
identity_key TEXT NOT NULL,
|
|
5183
|
+
display_name TEXT NOT NULL,
|
|
5184
|
+
directory TEXT NOT NULL,
|
|
5185
|
+
activity_time INTEGER NOT NULL,
|
|
5186
|
+
PRIMARY KEY (agent_name, session_id)
|
|
5187
|
+
);
|
|
5188
|
+
|
|
5189
|
+
CREATE INDEX IF NOT EXISTS idx_project_sessions_identity
|
|
5190
|
+
ON project_sessions(identity_kind, identity_key);
|
|
5191
|
+
`);
|
|
5192
|
+
createProjectGroupsView(db);
|
|
5193
|
+
}
|
|
5194
|
+
function createProjectGroupsView(db) {
|
|
5195
|
+
if (!tableExists(db, "sessions")) {
|
|
4526
5196
|
db.exec(`
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
5197
|
+
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
5198
|
+
SELECT
|
|
5199
|
+
identity_kind,
|
|
5200
|
+
identity_key,
|
|
5201
|
+
MIN(display_name) AS display_name,
|
|
5202
|
+
GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
|
|
5203
|
+
COUNT(*) AS session_count,
|
|
5204
|
+
MAX(activity_time) AS last_activity
|
|
5205
|
+
FROM project_sessions
|
|
5206
|
+
GROUP BY identity_kind, identity_key;
|
|
4530
5207
|
`);
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
db.exec(`
|
|
5211
|
+
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
5212
|
+
SELECT
|
|
5213
|
+
project_identity_kind AS identity_kind,
|
|
5214
|
+
project_identity_key AS identity_key,
|
|
5215
|
+
MIN(project_display_name) AS display_name,
|
|
5216
|
+
GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
|
|
5217
|
+
COUNT(*) AS session_count,
|
|
5218
|
+
MAX(activity_time) AS last_activity
|
|
5219
|
+
FROM sessions
|
|
5220
|
+
GROUP BY project_identity_kind, project_identity_key;
|
|
5221
|
+
`);
|
|
5222
|
+
}
|
|
5223
|
+
function recreateProjectGroupsView(db) {
|
|
5224
|
+
db.exec("DROP VIEW IF EXISTS project_groups_v");
|
|
5225
|
+
createProjectGroupsView(db);
|
|
5226
|
+
}
|
|
5227
|
+
function createLatestCacheSchema(db) {
|
|
5228
|
+
createCacheTables(db);
|
|
5229
|
+
createSessionTables(db);
|
|
5230
|
+
createFileActivityTables(db);
|
|
5231
|
+
createSearchTables(db);
|
|
5232
|
+
createProjectTables(db);
|
|
5233
|
+
}
|
|
5234
|
+
function stringifyOptionalJson(value) {
|
|
5235
|
+
return value == null ? null : JSON.stringify(value);
|
|
5236
|
+
}
|
|
5237
|
+
function parseOptionalJson(value) {
|
|
5238
|
+
return value == null ? void 0 : JSON.parse(String(value));
|
|
5239
|
+
}
|
|
5240
|
+
function sourcePathFromMeta(meta) {
|
|
5241
|
+
return typeof meta?.sourcePath === "string" ? meta.sourcePath : null;
|
|
5242
|
+
}
|
|
5243
|
+
function sourcePathFromMetaJson(metaJson) {
|
|
5244
|
+
if (!metaJson) return null;
|
|
5245
|
+
const meta = JSON.parse(metaJson);
|
|
5246
|
+
return sourcePathFromMeta(meta);
|
|
5247
|
+
}
|
|
5248
|
+
function prepareUpsertSession(db) {
|
|
5249
|
+
return db.prepare(`
|
|
5250
|
+
INSERT INTO sessions(
|
|
5251
|
+
agent_name,
|
|
5252
|
+
session_id,
|
|
5253
|
+
sort_index,
|
|
5254
|
+
slug,
|
|
5255
|
+
title,
|
|
5256
|
+
source_path,
|
|
5257
|
+
directory,
|
|
5258
|
+
project_identity_kind,
|
|
5259
|
+
project_identity_key,
|
|
5260
|
+
project_display_name,
|
|
5261
|
+
time_created,
|
|
5262
|
+
time_updated,
|
|
5263
|
+
activity_time,
|
|
5264
|
+
message_count,
|
|
5265
|
+
total_input_tokens,
|
|
5266
|
+
total_output_tokens,
|
|
5267
|
+
total_cache_read_tokens,
|
|
5268
|
+
total_cache_create_tokens,
|
|
5269
|
+
total_cost,
|
|
5270
|
+
cost_source,
|
|
5271
|
+
total_tokens,
|
|
5272
|
+
model_usage_json,
|
|
5273
|
+
smart_tags_json,
|
|
5274
|
+
smart_tags_source_updated_at,
|
|
5275
|
+
meta_json
|
|
5276
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5277
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
5278
|
+
sort_index = excluded.sort_index,
|
|
5279
|
+
slug = excluded.slug,
|
|
5280
|
+
title = excluded.title,
|
|
5281
|
+
source_path = excluded.source_path,
|
|
5282
|
+
directory = excluded.directory,
|
|
5283
|
+
project_identity_kind = excluded.project_identity_kind,
|
|
5284
|
+
project_identity_key = excluded.project_identity_key,
|
|
5285
|
+
project_display_name = excluded.project_display_name,
|
|
5286
|
+
time_created = excluded.time_created,
|
|
5287
|
+
time_updated = excluded.time_updated,
|
|
5288
|
+
activity_time = excluded.activity_time,
|
|
5289
|
+
message_count = excluded.message_count,
|
|
5290
|
+
total_input_tokens = excluded.total_input_tokens,
|
|
5291
|
+
total_output_tokens = excluded.total_output_tokens,
|
|
5292
|
+
total_cache_read_tokens = excluded.total_cache_read_tokens,
|
|
5293
|
+
total_cache_create_tokens = excluded.total_cache_create_tokens,
|
|
5294
|
+
total_cost = excluded.total_cost,
|
|
5295
|
+
cost_source = excluded.cost_source,
|
|
5296
|
+
total_tokens = excluded.total_tokens,
|
|
5297
|
+
model_usage_json = excluded.model_usage_json,
|
|
5298
|
+
smart_tags_json = excluded.smart_tags_json,
|
|
5299
|
+
smart_tags_source_updated_at = excluded.smart_tags_source_updated_at,
|
|
5300
|
+
meta_json = excluded.meta_json
|
|
5301
|
+
`);
|
|
5302
|
+
}
|
|
5303
|
+
function prepareUpsertIndexedSession(db) {
|
|
5304
|
+
return db.prepare(`
|
|
5305
|
+
INSERT INTO sessions(
|
|
5306
|
+
agent_name,
|
|
5307
|
+
session_id,
|
|
5308
|
+
sort_index,
|
|
5309
|
+
slug,
|
|
5310
|
+
title,
|
|
5311
|
+
source_path,
|
|
5312
|
+
directory,
|
|
5313
|
+
project_identity_kind,
|
|
5314
|
+
project_identity_key,
|
|
5315
|
+
project_display_name,
|
|
5316
|
+
time_created,
|
|
5317
|
+
time_updated,
|
|
5318
|
+
activity_time,
|
|
5319
|
+
message_count,
|
|
5320
|
+
total_input_tokens,
|
|
5321
|
+
total_output_tokens,
|
|
5322
|
+
total_cache_read_tokens,
|
|
5323
|
+
total_cache_create_tokens,
|
|
5324
|
+
total_cost,
|
|
5325
|
+
cost_source,
|
|
5326
|
+
total_tokens,
|
|
5327
|
+
model_usage_json,
|
|
5328
|
+
smart_tags_json,
|
|
5329
|
+
smart_tags_source_updated_at,
|
|
5330
|
+
meta_json
|
|
5331
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5332
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
5333
|
+
slug = excluded.slug,
|
|
5334
|
+
title = excluded.title,
|
|
5335
|
+
directory = excluded.directory,
|
|
5336
|
+
project_identity_kind = excluded.project_identity_kind,
|
|
5337
|
+
project_identity_key = excluded.project_identity_key,
|
|
5338
|
+
project_display_name = excluded.project_display_name,
|
|
5339
|
+
time_created = excluded.time_created,
|
|
5340
|
+
time_updated = excluded.time_updated,
|
|
5341
|
+
activity_time = excluded.activity_time,
|
|
5342
|
+
message_count = excluded.message_count,
|
|
5343
|
+
total_input_tokens = excluded.total_input_tokens,
|
|
5344
|
+
total_output_tokens = excluded.total_output_tokens,
|
|
5345
|
+
total_cache_read_tokens = excluded.total_cache_read_tokens,
|
|
5346
|
+
total_cache_create_tokens = excluded.total_cache_create_tokens,
|
|
5347
|
+
total_cost = excluded.total_cost,
|
|
5348
|
+
cost_source = excluded.cost_source,
|
|
5349
|
+
total_tokens = excluded.total_tokens,
|
|
5350
|
+
model_usage_json = excluded.model_usage_json,
|
|
5351
|
+
smart_tags_json = excluded.smart_tags_json,
|
|
5352
|
+
smart_tags_source_updated_at = excluded.smart_tags_source_updated_at
|
|
5353
|
+
`);
|
|
5354
|
+
}
|
|
5355
|
+
function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, sourcePath) {
|
|
5356
|
+
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
5357
|
+
const activityTime = session.time_updated ?? session.time_created;
|
|
5358
|
+
statement.run(
|
|
5359
|
+
agentName,
|
|
5360
|
+
session.id,
|
|
5361
|
+
sortIndex,
|
|
5362
|
+
session.slug,
|
|
5363
|
+
session.title,
|
|
5364
|
+
sourcePath,
|
|
5365
|
+
session.directory,
|
|
5366
|
+
identity.kind,
|
|
5367
|
+
identity.key,
|
|
5368
|
+
identity.displayName,
|
|
5369
|
+
session.time_created,
|
|
5370
|
+
session.time_updated ?? null,
|
|
5371
|
+
activityTime,
|
|
5372
|
+
session.stats.message_count,
|
|
5373
|
+
session.stats.total_input_tokens,
|
|
5374
|
+
session.stats.total_output_tokens,
|
|
5375
|
+
session.stats.total_cache_read_tokens ?? null,
|
|
5376
|
+
session.stats.total_cache_create_tokens ?? null,
|
|
5377
|
+
session.stats.total_cost,
|
|
5378
|
+
session.stats.cost_source ?? null,
|
|
5379
|
+
session.stats.total_tokens ?? null,
|
|
5380
|
+
stringifyOptionalJson(session.model_usage),
|
|
5381
|
+
stringifyOptionalJson(session.smart_tags),
|
|
5382
|
+
session.smart_tags_source_updated_at ?? null,
|
|
5383
|
+
metaJson
|
|
5384
|
+
);
|
|
5385
|
+
}
|
|
5386
|
+
function prepareInsertFileActivity(db) {
|
|
5387
|
+
return db.prepare(`
|
|
5388
|
+
INSERT INTO session_file_activity(
|
|
5389
|
+
agent_name,
|
|
5390
|
+
session_id,
|
|
5391
|
+
project_identity_key,
|
|
5392
|
+
path,
|
|
5393
|
+
kind,
|
|
5394
|
+
count,
|
|
5395
|
+
latest_time
|
|
5396
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
5397
|
+
`);
|
|
5398
|
+
}
|
|
5399
|
+
function writeFileActivityRows(statement, activities) {
|
|
5400
|
+
for (const activity of activities) {
|
|
5401
|
+
statement.run(
|
|
5402
|
+
activity.agent_name,
|
|
5403
|
+
activity.session_id,
|
|
5404
|
+
activity.project_identity_key,
|
|
5405
|
+
activity.path,
|
|
5406
|
+
activity.kind,
|
|
5407
|
+
activity.count,
|
|
5408
|
+
activity.latest_time
|
|
5409
|
+
);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
function sessionFromRow(row) {
|
|
5413
|
+
const session = {
|
|
5414
|
+
id: String(row.session_id),
|
|
5415
|
+
slug: String(row.slug),
|
|
5416
|
+
title: String(row.title),
|
|
5417
|
+
directory: String(row.directory),
|
|
5418
|
+
time_created: Number(row.time_created),
|
|
5419
|
+
stats: {
|
|
5420
|
+
message_count: Number(row.message_count ?? 0),
|
|
5421
|
+
total_input_tokens: Number(row.total_input_tokens ?? 0),
|
|
5422
|
+
total_output_tokens: Number(row.total_output_tokens ?? 0),
|
|
5423
|
+
total_cost: Number(row.total_cost ?? 0)
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5426
|
+
if (row.project_identity_key) {
|
|
5427
|
+
session.project_identity = {
|
|
5428
|
+
kind: row.project_identity_kind ?? "path",
|
|
5429
|
+
key: String(row.project_identity_key),
|
|
5430
|
+
displayName: String(row.project_display_name ?? "")
|
|
5431
|
+
};
|
|
5432
|
+
}
|
|
5433
|
+
if (row.time_updated != null) {
|
|
5434
|
+
session.time_updated = Number(row.time_updated);
|
|
5435
|
+
}
|
|
5436
|
+
if (row.total_cache_read_tokens != null) {
|
|
5437
|
+
session.stats.total_cache_read_tokens = Number(row.total_cache_read_tokens);
|
|
5438
|
+
}
|
|
5439
|
+
if (row.total_cache_create_tokens != null) {
|
|
5440
|
+
session.stats.total_cache_create_tokens = Number(row.total_cache_create_tokens);
|
|
5441
|
+
}
|
|
5442
|
+
if (row.cost_source) {
|
|
5443
|
+
session.stats.cost_source = row.cost_source;
|
|
5444
|
+
}
|
|
5445
|
+
if (row.total_tokens != null) {
|
|
5446
|
+
session.stats.total_tokens = Number(row.total_tokens);
|
|
5447
|
+
}
|
|
5448
|
+
const modelUsage = parseOptionalJson(row.model_usage_json);
|
|
5449
|
+
if (modelUsage) {
|
|
5450
|
+
session.model_usage = modelUsage;
|
|
5451
|
+
}
|
|
5452
|
+
const smartTags = parseOptionalJson(row.smart_tags_json);
|
|
5453
|
+
if (smartTags) {
|
|
5454
|
+
session.smart_tags = smartTags;
|
|
5455
|
+
}
|
|
5456
|
+
if (row.smart_tags_source_updated_at != null) {
|
|
5457
|
+
session.smart_tags_source_updated_at = Number(row.smart_tags_source_updated_at);
|
|
5458
|
+
}
|
|
5459
|
+
return session;
|
|
5460
|
+
}
|
|
5461
|
+
function recreateSearchIndexSchema(db) {
|
|
5462
|
+
db.exec(`
|
|
5463
|
+
DROP TRIGGER IF EXISTS session_documents_ai;
|
|
5464
|
+
DROP TRIGGER IF EXISTS session_documents_ad;
|
|
5465
|
+
DROP TRIGGER IF EXISTS session_documents_au;
|
|
5466
|
+
DROP TABLE IF EXISTS session_documents_fts;
|
|
5467
|
+
`);
|
|
5468
|
+
createSearchTables(db);
|
|
5469
|
+
rebuildSearchIndex(db);
|
|
5470
|
+
}
|
|
5471
|
+
function readLegacyCacheVersion(db) {
|
|
5472
|
+
if (!tableExists(db, "cache_meta") || !columnExists(db, "cache_meta", "key") || !columnExists(db, "cache_meta", "value")) {
|
|
5473
|
+
return 0;
|
|
4531
5474
|
}
|
|
4532
5475
|
const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
|
|
4533
|
-
|
|
4534
|
-
|
|
5476
|
+
return Number(versionRow?.value ?? 0);
|
|
5477
|
+
}
|
|
5478
|
+
function inferCacheSchemaVersion(db) {
|
|
5479
|
+
if (tableExists(db, "session_file_activity")) {
|
|
5480
|
+
return 8;
|
|
5481
|
+
}
|
|
5482
|
+
if (tableExists(db, "sessions") || tableExists(db, "messages")) {
|
|
5483
|
+
return 7;
|
|
5484
|
+
}
|
|
5485
|
+
if (tableExists(db, "project_sessions") || columnExists(db, "session_documents", "project_identity_key")) {
|
|
5486
|
+
return 5;
|
|
5487
|
+
}
|
|
5488
|
+
if (tableExists(db, "session_documents")) {
|
|
5489
|
+
return 4;
|
|
5490
|
+
}
|
|
5491
|
+
if (tableExists(db, "cached_sessions") || tableExists(db, "agent_cache")) {
|
|
5492
|
+
return 3;
|
|
5493
|
+
}
|
|
5494
|
+
return 0;
|
|
5495
|
+
}
|
|
5496
|
+
function getCurrentCacheSchemaVersion(db) {
|
|
5497
|
+
const userVersion = getUserVersion(db);
|
|
5498
|
+
if (userVersion > 0) {
|
|
5499
|
+
return userVersion;
|
|
5500
|
+
}
|
|
5501
|
+
const legacyVersion = readLegacyCacheVersion(db);
|
|
5502
|
+
return Math.max(legacyVersion, inferCacheSchemaVersion(db));
|
|
5503
|
+
}
|
|
5504
|
+
function hasAnyCacheSchema(db) {
|
|
5505
|
+
return [
|
|
5506
|
+
"cache_meta",
|
|
5507
|
+
"agent_cache",
|
|
5508
|
+
"cached_sessions",
|
|
5509
|
+
"sessions",
|
|
5510
|
+
"messages",
|
|
5511
|
+
"session_file_activity",
|
|
5512
|
+
"session_documents",
|
|
5513
|
+
"session_documents_fts",
|
|
5514
|
+
"project_sessions"
|
|
5515
|
+
].some((table) => tableExists(db, table));
|
|
5516
|
+
}
|
|
5517
|
+
function backfillProjectSessions(db) {
|
|
5518
|
+
if (!tableExists(db, "cached_sessions") || !tableExists(db, "project_sessions")) {
|
|
4535
5519
|
return;
|
|
4536
5520
|
}
|
|
4537
|
-
db.
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
5521
|
+
const rows = db.prepare("SELECT agent_name, session_id, session_json FROM cached_sessions").all();
|
|
5522
|
+
const upsert = db.prepare(`
|
|
5523
|
+
INSERT INTO project_sessions(
|
|
5524
|
+
agent_name,
|
|
5525
|
+
session_id,
|
|
5526
|
+
identity_kind,
|
|
5527
|
+
identity_key,
|
|
5528
|
+
display_name,
|
|
5529
|
+
directory,
|
|
5530
|
+
activity_time
|
|
5531
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
5532
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
5533
|
+
identity_kind = excluded.identity_kind,
|
|
5534
|
+
identity_key = excluded.identity_key,
|
|
5535
|
+
display_name = excluded.display_name,
|
|
5536
|
+
directory = excluded.directory,
|
|
5537
|
+
activity_time = excluded.activity_time
|
|
4546
5538
|
`);
|
|
5539
|
+
for (const row of rows) {
|
|
5540
|
+
if (!row.session_json || !row.agent_name || !row.session_id) {
|
|
5541
|
+
continue;
|
|
5542
|
+
}
|
|
5543
|
+
try {
|
|
5544
|
+
const session = JSON.parse(row.session_json);
|
|
5545
|
+
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
5546
|
+
upsert.run(
|
|
5547
|
+
row.agent_name,
|
|
5548
|
+
row.session_id,
|
|
5549
|
+
identity.kind,
|
|
5550
|
+
identity.key,
|
|
5551
|
+
identity.displayName,
|
|
5552
|
+
session.directory,
|
|
5553
|
+
session.time_updated ?? session.time_created
|
|
5554
|
+
);
|
|
5555
|
+
} catch {
|
|
5556
|
+
continue;
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
function backfillSessionDocumentProjects(db) {
|
|
5561
|
+
if (!tableExists(db, "session_documents") || !columnExists(db, "session_documents", "project_identity_key")) {
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5564
|
+
const rows = db.prepare("SELECT id, directory FROM session_documents").all();
|
|
5565
|
+
const update = db.prepare(`
|
|
5566
|
+
UPDATE session_documents
|
|
5567
|
+
SET
|
|
5568
|
+
project_identity_kind = ?,
|
|
5569
|
+
project_identity_key = ?,
|
|
5570
|
+
project_display_name = ?
|
|
5571
|
+
WHERE id = ?
|
|
5572
|
+
`);
|
|
5573
|
+
for (const row of rows) {
|
|
5574
|
+
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
5575
|
+
update.run(identity.kind, identity.key, identity.displayName, Number(row.id));
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
function migrateProjectIdentity(db) {
|
|
5579
|
+
createProjectTables(db);
|
|
5580
|
+
backfillProjectSessions(db);
|
|
5581
|
+
backfillSessionDocumentProjects(db);
|
|
5582
|
+
}
|
|
5583
|
+
function backfillStructuredSessions(db) {
|
|
5584
|
+
createSessionTables(db);
|
|
5585
|
+
recreateProjectGroupsView(db);
|
|
5586
|
+
const upsertSession = prepareUpsertSession(db);
|
|
5587
|
+
if (tableExists(db, "cached_sessions")) {
|
|
5588
|
+
const rows = db.prepare(
|
|
5589
|
+
"SELECT agent_name, session_id, session_json, meta_json, rowid AS sort_index FROM cached_sessions ORDER BY agent_name, rowid"
|
|
5590
|
+
).all();
|
|
5591
|
+
for (const row of rows) {
|
|
5592
|
+
if (!row.agent_name || !row.session_json) {
|
|
5593
|
+
continue;
|
|
5594
|
+
}
|
|
5595
|
+
try {
|
|
5596
|
+
const session = JSON.parse(row.session_json);
|
|
5597
|
+
upsertSessionRow(
|
|
5598
|
+
upsertSession,
|
|
5599
|
+
String(row.agent_name),
|
|
5600
|
+
session,
|
|
5601
|
+
row.meta_json ?? null,
|
|
5602
|
+
Number(row.sort_index ?? 0),
|
|
5603
|
+
sourcePathFromMetaJson(row.meta_json)
|
|
5604
|
+
);
|
|
5605
|
+
} catch {
|
|
5606
|
+
continue;
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
if (!tableExists(db, "session_documents")) {
|
|
5611
|
+
return;
|
|
5612
|
+
}
|
|
5613
|
+
const documentRows = db.prepare(
|
|
5614
|
+
`
|
|
5615
|
+
SELECT
|
|
5616
|
+
d.agent_name,
|
|
5617
|
+
d.session_id,
|
|
5618
|
+
d.slug,
|
|
5619
|
+
d.title,
|
|
5620
|
+
d.directory,
|
|
5621
|
+
d.project_identity_kind,
|
|
5622
|
+
d.project_identity_key,
|
|
5623
|
+
d.project_display_name,
|
|
5624
|
+
d.time_created,
|
|
5625
|
+
d.time_updated,
|
|
5626
|
+
d.activity_time,
|
|
5627
|
+
d.id
|
|
5628
|
+
FROM session_documents d
|
|
5629
|
+
LEFT JOIN sessions s ON s.agent_name = d.agent_name AND s.session_id = d.session_id
|
|
5630
|
+
WHERE s.session_id IS NULL
|
|
5631
|
+
ORDER BY d.id
|
|
5632
|
+
`
|
|
5633
|
+
).all();
|
|
5634
|
+
for (const row of documentRows) {
|
|
5635
|
+
const directory = String(row.directory ?? "");
|
|
5636
|
+
const identity = row.project_identity_key && row.project_identity_kind && row.project_display_name ? {
|
|
5637
|
+
kind: row.project_identity_kind,
|
|
5638
|
+
key: String(row.project_identity_key),
|
|
5639
|
+
displayName: String(row.project_display_name)
|
|
5640
|
+
} : computeIdentity(directory, realFs);
|
|
5641
|
+
upsertSessionRow(
|
|
5642
|
+
upsertSession,
|
|
5643
|
+
String(row.agent_name),
|
|
5644
|
+
{
|
|
5645
|
+
id: String(row.session_id),
|
|
5646
|
+
slug: String(row.slug),
|
|
5647
|
+
title: String(row.title),
|
|
5648
|
+
directory,
|
|
5649
|
+
project_identity: identity,
|
|
5650
|
+
time_created: Number(row.time_created ?? row.activity_time ?? 0),
|
|
5651
|
+
time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
|
|
5652
|
+
stats: {
|
|
5653
|
+
message_count: 0,
|
|
5654
|
+
total_input_tokens: 0,
|
|
5655
|
+
total_output_tokens: 0,
|
|
5656
|
+
total_cost: 0
|
|
5657
|
+
}
|
|
5658
|
+
},
|
|
5659
|
+
null,
|
|
5660
|
+
Number(row.id ?? 0),
|
|
5661
|
+
null
|
|
5662
|
+
);
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
function messageFromBackfillRow(row) {
|
|
5666
|
+
const role = row.role === "assistant" || row.role === "tool" ? row.role : "user";
|
|
5667
|
+
return {
|
|
5668
|
+
id: String(row.message_id ?? ""),
|
|
5669
|
+
role,
|
|
5670
|
+
agent: row.agent ?? null,
|
|
5671
|
+
time_created: Number(row.time_created ?? 0),
|
|
5672
|
+
time_completed: row.time_completed == null ? null : Number(row.time_completed),
|
|
5673
|
+
mode: row.mode ?? null,
|
|
5674
|
+
model: row.model ?? null,
|
|
5675
|
+
provider: row.provider ?? null,
|
|
5676
|
+
parts: JSON.parse(String(row.parts_json ?? "[]")),
|
|
5677
|
+
subagent_id: row.subagent_id ?? void 0,
|
|
5678
|
+
nickname: row.nickname ?? void 0
|
|
5679
|
+
};
|
|
5680
|
+
}
|
|
5681
|
+
function backfillFileActivity(db) {
|
|
5682
|
+
createFileActivityTables(db);
|
|
5683
|
+
if (!tableExists(db, "sessions") || !tableExists(db, "messages")) {
|
|
5684
|
+
return;
|
|
5685
|
+
}
|
|
5686
|
+
const sessions = db.prepare(
|
|
5687
|
+
`
|
|
5688
|
+
SELECT agent_name, session_id, project_identity_key
|
|
5689
|
+
FROM sessions
|
|
5690
|
+
ORDER BY agent_name, session_id
|
|
5691
|
+
`
|
|
5692
|
+
).all();
|
|
5693
|
+
const loadMessages = db.prepare(`
|
|
5694
|
+
SELECT
|
|
5695
|
+
message_id,
|
|
5696
|
+
role,
|
|
5697
|
+
time_created,
|
|
5698
|
+
time_completed,
|
|
5699
|
+
agent,
|
|
5700
|
+
mode,
|
|
5701
|
+
model,
|
|
5702
|
+
provider,
|
|
5703
|
+
parts_json,
|
|
5704
|
+
subagent_id,
|
|
5705
|
+
nickname
|
|
5706
|
+
FROM messages
|
|
5707
|
+
WHERE agent_name = ? AND session_id = ?
|
|
5708
|
+
ORDER BY message_index
|
|
5709
|
+
`);
|
|
5710
|
+
const deleteActivity = db.prepare(
|
|
5711
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
5712
|
+
);
|
|
5713
|
+
const insertActivity = prepareInsertFileActivity(db);
|
|
5714
|
+
for (const session of sessions) {
|
|
5715
|
+
if (!session.agent_name || !session.session_id || !session.project_identity_key) {
|
|
5716
|
+
continue;
|
|
5717
|
+
}
|
|
5718
|
+
try {
|
|
5719
|
+
const rows = loadMessages.all(session.agent_name, session.session_id);
|
|
5720
|
+
const messages = rows.map((row) => messageFromBackfillRow(row));
|
|
5721
|
+
const activities = extractSessionFileActivity(
|
|
5722
|
+
String(session.agent_name),
|
|
5723
|
+
String(session.session_id),
|
|
5724
|
+
String(session.project_identity_key),
|
|
5725
|
+
messages
|
|
5726
|
+
);
|
|
5727
|
+
deleteActivity.run(session.agent_name, session.session_id);
|
|
5728
|
+
writeFileActivityRows(insertActivity, activities);
|
|
5729
|
+
} catch {
|
|
5730
|
+
continue;
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
function invalidateSearchContentHashes(db) {
|
|
5735
|
+
if (tableExists(db, "session_documents") && columnExists(db, "session_documents", "content_hash")) {
|
|
5736
|
+
db.exec("UPDATE session_documents SET content_hash = ''");
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
function rebuildSearchIndex(db) {
|
|
5740
|
+
if (!tableExists(db, "session_documents_fts")) {
|
|
5741
|
+
return;
|
|
5742
|
+
}
|
|
5743
|
+
db.exec("INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild')");
|
|
5744
|
+
}
|
|
5745
|
+
function shouldBulkSyncSearchIndex(options, changedCount) {
|
|
5746
|
+
if (options.isBulk != null) {
|
|
5747
|
+
return options.isBulk;
|
|
5748
|
+
}
|
|
5749
|
+
const threshold = options.bulkThreshold ?? SEARCH_INDEX_BULK_SYNC_THRESHOLD;
|
|
5750
|
+
return threshold > 0 && changedCount >= threshold;
|
|
5751
|
+
}
|
|
5752
|
+
function ensureFtsReady(db) {
|
|
5753
|
+
if (!tableExists(db, "session_documents_fts")) {
|
|
5754
|
+
createSearchTables(db);
|
|
5755
|
+
}
|
|
5756
|
+
createSearchTriggers(db);
|
|
5757
|
+
}
|
|
5758
|
+
function ensureFtsConsistency(db) {
|
|
5759
|
+
ensureFtsReady(db);
|
|
5760
|
+
const cachePath = getCachePath2();
|
|
5761
|
+
if (ftsIntegrityCheckedPath === cachePath) {
|
|
5762
|
+
return;
|
|
5763
|
+
}
|
|
5764
|
+
try {
|
|
5765
|
+
db.exec(
|
|
5766
|
+
"INSERT INTO session_documents_fts(session_documents_fts, rank) VALUES ('integrity-check', 1)"
|
|
5767
|
+
);
|
|
5768
|
+
ftsIntegrityCheckedPath = cachePath;
|
|
5769
|
+
} catch {
|
|
5770
|
+
rebuildSearchIndex(db);
|
|
5771
|
+
ftsIntegrityCheckedPath = cachePath;
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
function setCacheSchemaVersion(db) {
|
|
5775
|
+
createCacheTables(db);
|
|
5776
|
+
setUserVersion(db, CACHE_SCHEMA_VERSION);
|
|
5777
|
+
db.prepare(
|
|
5778
|
+
`
|
|
5779
|
+
INSERT INTO cache_meta(key, value)
|
|
5780
|
+
VALUES ('version', ?)
|
|
5781
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
5782
|
+
`
|
|
5783
|
+
).run(String(CACHE_SCHEMA_VERSION));
|
|
5784
|
+
}
|
|
5785
|
+
function ensureSchema(db, dbPath) {
|
|
5786
|
+
const currentVersion = getCurrentCacheSchemaVersion(db);
|
|
5787
|
+
if (currentVersion === 0 && !hasAnyCacheSchema(db)) {
|
|
5788
|
+
createLatestCacheSchema(db);
|
|
5789
|
+
setCacheSchemaVersion(db);
|
|
5790
|
+
return;
|
|
5791
|
+
}
|
|
5792
|
+
runSchemaMigrations(db, {
|
|
5793
|
+
dbPath,
|
|
5794
|
+
currentVersion,
|
|
5795
|
+
targetVersion: CACHE_SCHEMA_VERSION,
|
|
5796
|
+
backupLabel: "cache-migration",
|
|
5797
|
+
backupTables: [
|
|
5798
|
+
"agent_cache",
|
|
5799
|
+
"cached_sessions",
|
|
5800
|
+
"sessions",
|
|
5801
|
+
"messages",
|
|
5802
|
+
"session_file_activity",
|
|
5803
|
+
"session_documents",
|
|
5804
|
+
"project_sessions"
|
|
5805
|
+
],
|
|
5806
|
+
migrations: [
|
|
5807
|
+
{ version: 3, migrate: createCacheTables },
|
|
5808
|
+
{ version: 4, migrate: createSearchTables },
|
|
5809
|
+
{ version: 5, migrate: migrateProjectIdentity },
|
|
5810
|
+
{
|
|
5811
|
+
version: 6,
|
|
5812
|
+
destructive: true,
|
|
5813
|
+
migrate(db2) {
|
|
5814
|
+
createLatestCacheSchema(db2);
|
|
5815
|
+
recreateSearchIndexSchema(db2);
|
|
5816
|
+
invalidateSearchContentHashes(db2);
|
|
5817
|
+
}
|
|
5818
|
+
},
|
|
5819
|
+
{ version: 7, migrate: backfillStructuredSessions },
|
|
5820
|
+
{ version: 8, migrate: backfillFileActivity }
|
|
5821
|
+
]
|
|
5822
|
+
});
|
|
5823
|
+
createLatestCacheSchema(db);
|
|
5824
|
+
if (getUserVersion(db) <= CACHE_SCHEMA_VERSION) {
|
|
5825
|
+
setCacheSchemaVersion(db);
|
|
5826
|
+
}
|
|
4547
5827
|
}
|
|
4548
5828
|
function sessionContentHash(session) {
|
|
4549
5829
|
return JSON.stringify([
|
|
@@ -4565,9 +5845,123 @@ function sessionContentHash(session) {
|
|
|
4565
5845
|
function escapeFtsTerm(value) {
|
|
4566
5846
|
return value.replaceAll('"', '""');
|
|
4567
5847
|
}
|
|
5848
|
+
function splitSearchTokens(input) {
|
|
5849
|
+
const tokens = [];
|
|
5850
|
+
let token = "";
|
|
5851
|
+
let inQuote = false;
|
|
5852
|
+
for (const char of input) {
|
|
5853
|
+
if (char === '"') {
|
|
5854
|
+
inQuote = !inQuote;
|
|
5855
|
+
token += char;
|
|
5856
|
+
continue;
|
|
5857
|
+
}
|
|
5858
|
+
if (/\s/.test(char) && !inQuote) {
|
|
5859
|
+
if (token) {
|
|
5860
|
+
tokens.push(token);
|
|
5861
|
+
token = "";
|
|
5862
|
+
}
|
|
5863
|
+
continue;
|
|
5864
|
+
}
|
|
5865
|
+
token += char;
|
|
5866
|
+
}
|
|
5867
|
+
if (token) {
|
|
5868
|
+
tokens.push(token);
|
|
5869
|
+
}
|
|
5870
|
+
return tokens;
|
|
5871
|
+
}
|
|
5872
|
+
function unwrapSearchValue(value) {
|
|
5873
|
+
const trimmed = value.trim();
|
|
5874
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
5875
|
+
return trimmed.slice(1, -1).trim();
|
|
5876
|
+
}
|
|
5877
|
+
return trimmed;
|
|
5878
|
+
}
|
|
5879
|
+
function parseCostQualifier(value, filters) {
|
|
5880
|
+
const raw = unwrapSearchValue(value);
|
|
5881
|
+
const range = raw.match(/^(\d+(?:\.\d+)?)\.\.(\d+(?:\.\d+)?)$/);
|
|
5882
|
+
if (range) {
|
|
5883
|
+
filters.costMin = Number(range[1]);
|
|
5884
|
+
filters.costMax = Number(range[2]);
|
|
5885
|
+
return;
|
|
5886
|
+
}
|
|
5887
|
+
const comparison = raw.match(/^(>=|>|<=|<)(\d+(?:\.\d+)?)$/);
|
|
5888
|
+
if (comparison) {
|
|
5889
|
+
const amount2 = Number(comparison[2]);
|
|
5890
|
+
if (comparison[1]?.includes(">")) {
|
|
5891
|
+
filters.costMin = amount2;
|
|
5892
|
+
filters.costMinExclusive = comparison[1] === ">";
|
|
5893
|
+
} else {
|
|
5894
|
+
filters.costMax = amount2;
|
|
5895
|
+
filters.costMaxExclusive = comparison[1] === "<";
|
|
5896
|
+
}
|
|
5897
|
+
return;
|
|
5898
|
+
}
|
|
5899
|
+
const amount = Number(raw);
|
|
5900
|
+
if (!Number.isNaN(amount)) {
|
|
5901
|
+
filters.costMin = amount;
|
|
5902
|
+
filters.costMax = amount;
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
function appendUnique(values, value) {
|
|
5906
|
+
if (values?.includes(value)) return values;
|
|
5907
|
+
return [...values ?? [], value];
|
|
5908
|
+
}
|
|
5909
|
+
function isSmartTag(value) {
|
|
5910
|
+
return value === "bugfix" || value === "refactoring" || value === "feature-dev" || value === "testing" || value === "docs" || value === "git-ops" || value === "build-deploy" || value === "exploration" || value === "planning";
|
|
5911
|
+
}
|
|
5912
|
+
function parseSearchQuery(input) {
|
|
5913
|
+
const filters = {};
|
|
5914
|
+
const textTokens = [];
|
|
5915
|
+
let hasQualifiers = false;
|
|
5916
|
+
for (const token of splitSearchTokens(input)) {
|
|
5917
|
+
const match = token.match(/^([a-zA-Z][a-zA-Z_-]*):(.+)$/);
|
|
5918
|
+
if (!match) {
|
|
5919
|
+
textTokens.push(token);
|
|
5920
|
+
continue;
|
|
5921
|
+
}
|
|
5922
|
+
const key = match[1].toLowerCase();
|
|
5923
|
+
const value = unwrapSearchValue(match[2]);
|
|
5924
|
+
if (!value) continue;
|
|
5925
|
+
let consumed = true;
|
|
5926
|
+
if (key === "agent") filters.agent = value.toLowerCase();
|
|
5927
|
+
else if (key === "project") filters.project = value;
|
|
5928
|
+
else if (key === "projectkey" || key === "project-key") filters.projectKey = value;
|
|
5929
|
+
else if (key === "cwd") filters.cwd = value;
|
|
5930
|
+
else if (key === "tool") filters.tools = appendUnique(filters.tools, value.toLowerCase());
|
|
5931
|
+
else if (key === "file" || key === "path") filters.file = value;
|
|
5932
|
+
else if (key === "kind" || key === "filekind" || key === "file-kind") {
|
|
5933
|
+
if (value === "read" || value === "edit" || value === "write" || value === "delete") {
|
|
5934
|
+
filters.fileKind = value;
|
|
5935
|
+
} else {
|
|
5936
|
+
consumed = false;
|
|
5937
|
+
}
|
|
5938
|
+
} else if (key === "tag" || key === "signal") {
|
|
5939
|
+
const tag = value.toLowerCase();
|
|
5940
|
+
if (isSmartTag(tag)) {
|
|
5941
|
+
filters.tags = appendUnique(filters.tags, tag);
|
|
5942
|
+
} else {
|
|
5943
|
+
consumed = false;
|
|
5944
|
+
}
|
|
5945
|
+
} else if (key === "cost") {
|
|
5946
|
+
parseCostQualifier(value, filters);
|
|
5947
|
+
} else {
|
|
5948
|
+
consumed = false;
|
|
5949
|
+
}
|
|
5950
|
+
if (consumed) {
|
|
5951
|
+
hasQualifiers = true;
|
|
5952
|
+
} else {
|
|
5953
|
+
textTokens.push(token);
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5956
|
+
return {
|
|
5957
|
+
text: textTokens.join(" ").trim(),
|
|
5958
|
+
filters,
|
|
5959
|
+
hasQualifiers
|
|
5960
|
+
};
|
|
5961
|
+
}
|
|
4568
5962
|
function toFtsQuery(input) {
|
|
4569
|
-
const tokens = input
|
|
4570
|
-
|
|
5963
|
+
const tokens = splitSearchTokens(input);
|
|
5964
|
+
const mapped = tokens.map((token) => {
|
|
4571
5965
|
if (/^OR$/i.test(token)) {
|
|
4572
5966
|
return "OR";
|
|
4573
5967
|
}
|
|
@@ -4575,7 +5969,10 @@ function toFtsQuery(input) {
|
|
|
4575
5969
|
return `"${escapeFtsTerm(token.slice(1, -1))}"`;
|
|
4576
5970
|
}
|
|
4577
5971
|
return `"${escapeFtsTerm(token)}"`;
|
|
4578
|
-
}).
|
|
5972
|
+
}).filter(
|
|
5973
|
+
(token, index, values) => token !== "OR" || index > 0 && index < values.length - 1 && values[index - 1] !== "OR" && values[index + 1] !== "OR"
|
|
5974
|
+
);
|
|
5975
|
+
return mapped.join(" ");
|
|
4579
5976
|
}
|
|
4580
5977
|
function appendPlainText(value, chunks) {
|
|
4581
5978
|
if (value == null) return;
|
|
@@ -4602,29 +5999,77 @@ function appendPlainText(value, chunks) {
|
|
|
4602
5999
|
}
|
|
4603
6000
|
}
|
|
4604
6001
|
}
|
|
4605
|
-
function
|
|
6002
|
+
function compactRecord(record) {
|
|
6003
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value != null));
|
|
6004
|
+
}
|
|
6005
|
+
function summarizeToolPart(part) {
|
|
6006
|
+
const state = part.state == null ? void 0 : compactRecord({
|
|
6007
|
+
status: part.state.status,
|
|
6008
|
+
error: part.state.error,
|
|
6009
|
+
metadata: part.state.metadata
|
|
6010
|
+
});
|
|
6011
|
+
return compactRecord({
|
|
6012
|
+
type: part.type,
|
|
6013
|
+
tool: part.tool,
|
|
6014
|
+
title: part.title,
|
|
6015
|
+
nickname: part.nickname,
|
|
6016
|
+
callID: part.callID,
|
|
6017
|
+
approval_status: part.approval_status,
|
|
6018
|
+
state
|
|
6019
|
+
});
|
|
6020
|
+
}
|
|
6021
|
+
function buildMessageText(message) {
|
|
4606
6022
|
const chunks = [];
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
appendPlainText(
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
6023
|
+
chunks.push(message.role);
|
|
6024
|
+
appendPlainText(message.agent, chunks);
|
|
6025
|
+
appendPlainText(message.model, chunks);
|
|
6026
|
+
for (const part of message.parts) {
|
|
6027
|
+
appendPlainText(part.type, chunks);
|
|
6028
|
+
appendPlainText(part.title, chunks);
|
|
6029
|
+
appendPlainText(part.nickname, chunks);
|
|
6030
|
+
appendPlainText(part.tool, chunks);
|
|
6031
|
+
appendPlainText(part.text, chunks);
|
|
6032
|
+
appendPlainText(part.input, chunks);
|
|
6033
|
+
appendPlainText(part.output, chunks);
|
|
6034
|
+
appendPlainText(part.state, chunks);
|
|
6035
|
+
}
|
|
6036
|
+
return chunks.join("\n");
|
|
6037
|
+
}
|
|
6038
|
+
function normalizeMessages(session) {
|
|
6039
|
+
return session.messages.map((message, index) => {
|
|
6040
|
+
const toolMetadata = message.parts.filter((part) => part.type === "tool").map((part) => summarizeToolPart(part));
|
|
6041
|
+
return {
|
|
6042
|
+
index,
|
|
6043
|
+
id: message.id || `${session.id}:${index}`,
|
|
6044
|
+
role: message.role,
|
|
6045
|
+
timeCreated: message.time_created,
|
|
6046
|
+
timeCompleted: message.time_completed ?? null,
|
|
6047
|
+
agent: message.agent ?? null,
|
|
6048
|
+
mode: message.mode ?? null,
|
|
6049
|
+
model: message.model ?? null,
|
|
6050
|
+
provider: message.provider ?? null,
|
|
6051
|
+
tokensJson: stringifyOptionalJson(message.tokens),
|
|
6052
|
+
cost: message.cost ?? null,
|
|
6053
|
+
costSource: message.cost_source ?? null,
|
|
6054
|
+
partsJson: JSON.stringify(message.parts),
|
|
6055
|
+
subagentId: message.subagent_id ?? null,
|
|
6056
|
+
nickname: message.nickname ?? null,
|
|
6057
|
+
contentText: buildMessageText(message),
|
|
6058
|
+
toolMetadataJson: toolMetadata.length > 0 ? JSON.stringify(toolMetadata) : null
|
|
6059
|
+
};
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
6062
|
+
function buildSessionContentFromMessages(title, messages) {
|
|
6063
|
+
const chunks = [];
|
|
6064
|
+
appendPlainText(title, chunks);
|
|
6065
|
+
for (const message of messages) {
|
|
6066
|
+
appendPlainText(message.contentText, chunks);
|
|
4622
6067
|
}
|
|
4623
6068
|
return chunks.join("\n");
|
|
4624
6069
|
}
|
|
4625
6070
|
function deleteLegacyCacheFile() {
|
|
4626
6071
|
const legacyPath = getLegacyCachePath();
|
|
4627
|
-
if (!
|
|
6072
|
+
if (!existsSync10(legacyPath)) {
|
|
4628
6073
|
return;
|
|
4629
6074
|
}
|
|
4630
6075
|
try {
|
|
@@ -4644,19 +6089,39 @@ function loadCachedSessions(agentName) {
|
|
|
4644
6089
|
}
|
|
4645
6090
|
const rows = db.prepare(
|
|
4646
6091
|
`
|
|
4647
|
-
SELECT
|
|
4648
|
-
|
|
6092
|
+
SELECT
|
|
6093
|
+
session_id,
|
|
6094
|
+
sort_index,
|
|
6095
|
+
slug,
|
|
6096
|
+
title,
|
|
6097
|
+
source_path,
|
|
6098
|
+
directory,
|
|
6099
|
+
project_identity_kind,
|
|
6100
|
+
project_identity_key,
|
|
6101
|
+
project_display_name,
|
|
6102
|
+
time_created,
|
|
6103
|
+
time_updated,
|
|
6104
|
+
message_count,
|
|
6105
|
+
total_input_tokens,
|
|
6106
|
+
total_output_tokens,
|
|
6107
|
+
total_cache_read_tokens,
|
|
6108
|
+
total_cache_create_tokens,
|
|
6109
|
+
total_cost,
|
|
6110
|
+
cost_source,
|
|
6111
|
+
total_tokens,
|
|
6112
|
+
model_usage_json,
|
|
6113
|
+
smart_tags_json,
|
|
6114
|
+
smart_tags_source_updated_at,
|
|
6115
|
+
meta_json
|
|
6116
|
+
FROM sessions
|
|
4649
6117
|
WHERE agent_name = ?
|
|
4650
|
-
ORDER BY
|
|
6118
|
+
ORDER BY sort_index, activity_time DESC
|
|
4651
6119
|
`
|
|
4652
6120
|
).all(agentName);
|
|
4653
6121
|
const sessions = [];
|
|
4654
6122
|
const meta = {};
|
|
4655
6123
|
for (const row of rows) {
|
|
4656
|
-
|
|
4657
|
-
continue;
|
|
4658
|
-
}
|
|
4659
|
-
const session = JSON.parse(row.session_json);
|
|
6124
|
+
const session = sessionFromRow(row);
|
|
4660
6125
|
sessions.push(session);
|
|
4661
6126
|
if (row.meta_json) {
|
|
4662
6127
|
meta[session.id] = JSON.parse(row.meta_json);
|
|
@@ -4668,17 +6133,27 @@ function loadCachedSessions(agentName) {
|
|
|
4668
6133
|
function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
4669
6134
|
withCacheDb((db) => {
|
|
4670
6135
|
const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
|
|
4671
|
-
const
|
|
6136
|
+
const deleteLegacySessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
|
|
6137
|
+
const deleteSession = db.prepare(
|
|
6138
|
+
"DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
|
|
6139
|
+
);
|
|
6140
|
+
const deleteSearchDocument = db.prepare(
|
|
6141
|
+
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
6142
|
+
);
|
|
6143
|
+
const deleteFileActivity = db.prepare(
|
|
6144
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6145
|
+
);
|
|
4672
6146
|
const deleteProjectSessions = db.prepare("DELETE FROM project_sessions WHERE agent_name = ?");
|
|
4673
6147
|
const upsertAgent = db.prepare(`
|
|
4674
6148
|
INSERT INTO agent_cache(agent_name, timestamp)
|
|
4675
6149
|
VALUES (?, ?)
|
|
4676
6150
|
ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
|
|
4677
6151
|
`);
|
|
4678
|
-
const
|
|
6152
|
+
const insertCachedSession = db.prepare(`
|
|
4679
6153
|
INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
|
|
4680
6154
|
VALUES (?, ?, ?, ?)
|
|
4681
6155
|
`);
|
|
6156
|
+
const upsertSession = prepareUpsertSession(db);
|
|
4682
6157
|
const insertProjectSession = db.prepare(`
|
|
4683
6158
|
INSERT INTO project_sessions(
|
|
4684
6159
|
agent_name,
|
|
@@ -4692,17 +6167,32 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
4692
6167
|
`);
|
|
4693
6168
|
const write = db.transaction(() => {
|
|
4694
6169
|
const timestamp = Date.now();
|
|
6170
|
+
const sessionIds = new Set(sessions.map((session) => session.id));
|
|
6171
|
+
const existingSessionIds = db.prepare("SELECT session_id FROM sessions WHERE agent_name = ?").all(agentName);
|
|
4695
6172
|
deleteAgent.run(agentName);
|
|
4696
|
-
|
|
6173
|
+
deleteLegacySessions.run(agentName);
|
|
4697
6174
|
deleteProjectSessions.run(agentName);
|
|
4698
6175
|
upsertAgent.run(agentName, timestamp);
|
|
4699
|
-
for (const
|
|
6176
|
+
for (const row of existingSessionIds) {
|
|
6177
|
+
const sessionId = String(row.session_id);
|
|
6178
|
+
if (!sessionIds.has(sessionId)) {
|
|
6179
|
+
deleteSearchDocument.run(agentName, sessionId);
|
|
6180
|
+
deleteFileActivity.run(agentName, sessionId);
|
|
6181
|
+
deleteSession.run(agentName, sessionId);
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
sessions.forEach((session, index) => {
|
|
4700
6185
|
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
4701
|
-
|
|
6186
|
+
const sessionMeta = meta[session.id];
|
|
6187
|
+
const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
|
|
6188
|
+
insertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
|
|
6189
|
+
upsertSessionRow(
|
|
6190
|
+
upsertSession,
|
|
4702
6191
|
agentName,
|
|
4703
|
-
session
|
|
4704
|
-
|
|
4705
|
-
|
|
6192
|
+
session,
|
|
6193
|
+
metaJson,
|
|
6194
|
+
index,
|
|
6195
|
+
sourcePathFromMeta(sessionMeta)
|
|
4706
6196
|
);
|
|
4707
6197
|
insertProjectSession.run(
|
|
4708
6198
|
agentName,
|
|
@@ -4713,13 +6203,14 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
4713
6203
|
session.directory,
|
|
4714
6204
|
session.time_updated ?? session.time_created
|
|
4715
6205
|
);
|
|
4716
|
-
}
|
|
6206
|
+
});
|
|
4717
6207
|
});
|
|
4718
6208
|
write();
|
|
4719
6209
|
deleteLegacyCacheFile();
|
|
4720
6210
|
});
|
|
4721
6211
|
}
|
|
4722
6212
|
function clearCache() {
|
|
6213
|
+
ftsIntegrityCheckedPath = null;
|
|
4723
6214
|
if (!hasCacheStorage()) {
|
|
4724
6215
|
deleteLegacyCacheFile();
|
|
4725
6216
|
return;
|
|
@@ -4728,6 +6219,10 @@ function clearCache() {
|
|
|
4728
6219
|
db.exec(`
|
|
4729
6220
|
DELETE FROM agent_cache;
|
|
4730
6221
|
DELETE FROM cached_sessions;
|
|
6222
|
+
DELETE FROM session_documents;
|
|
6223
|
+
DELETE FROM session_file_activity;
|
|
6224
|
+
DELETE FROM messages;
|
|
6225
|
+
DELETE FROM sessions;
|
|
4731
6226
|
DELETE FROM project_sessions;
|
|
4732
6227
|
`);
|
|
4733
6228
|
});
|
|
@@ -4736,7 +6231,7 @@ function clearCache() {
|
|
|
4736
6231
|
const walPath = `${cachePath}-wal`;
|
|
4737
6232
|
const shmPath = `${cachePath}-shm`;
|
|
4738
6233
|
for (const filePath of [walPath, shmPath]) {
|
|
4739
|
-
if (!
|
|
6234
|
+
if (!existsSync10(filePath)) {
|
|
4740
6235
|
continue;
|
|
4741
6236
|
}
|
|
4742
6237
|
try {
|
|
@@ -4751,36 +6246,54 @@ function getCacheInfo() {
|
|
|
4751
6246
|
}
|
|
4752
6247
|
const info = withCacheDb((db) => {
|
|
4753
6248
|
const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
|
|
4754
|
-
const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM
|
|
6249
|
+
const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM sessions").get();
|
|
4755
6250
|
const lastScanTime = Number(timestampRow?.value ?? 0) || null;
|
|
4756
6251
|
const size = Number(sizeRow?.value ?? 0);
|
|
4757
6252
|
return { lastScanTime, size };
|
|
4758
6253
|
});
|
|
4759
6254
|
return info ?? { lastScanTime: null, size: 0 };
|
|
4760
6255
|
}
|
|
4761
|
-
function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
withCacheDb((db) => {
|
|
6256
|
+
function syncSessionSearchIndex(agentName, sessions, loadSessionData, options = {}) {
|
|
6257
|
+
return withCacheDb((db) => {
|
|
6258
|
+
ensureFtsConsistency(db);
|
|
6259
|
+
const startedAt = performance.now();
|
|
4766
6260
|
const existingRows = db.prepare(
|
|
4767
6261
|
"SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
|
|
4768
6262
|
).all(agentName);
|
|
4769
6263
|
const existingMap = new Map(
|
|
4770
6264
|
existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
|
|
4771
6265
|
);
|
|
6266
|
+
const sessionSortIndexMap = new Map(sessions.map((session, index) => [session.id, index]));
|
|
6267
|
+
const messageCountRows = db.prepare(
|
|
6268
|
+
"SELECT session_id, COUNT(*) AS value FROM messages WHERE agent_name = ? GROUP BY session_id"
|
|
6269
|
+
).all(agentName);
|
|
6270
|
+
const messageCountMap = new Map(
|
|
6271
|
+
messageCountRows.map((row) => [String(row.session_id), Number(row.value ?? 0)])
|
|
6272
|
+
);
|
|
4772
6273
|
const sessionMap = new Map(sessions.map((session) => [session.id, session]));
|
|
4773
6274
|
const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
|
|
4774
6275
|
const toUpsert = sessions.filter(
|
|
4775
|
-
(session) => existingMap.get(session.id) !== sessionContentHash(session)
|
|
6276
|
+
(session) => existingMap.get(session.id) !== sessionContentHash(session) || messageCountMap.get(session.id) !== session.stats.message_count
|
|
4776
6277
|
);
|
|
6278
|
+
const changedCount = toDelete.length + toUpsert.length;
|
|
6279
|
+
const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
|
|
4777
6280
|
const loaded = toUpsert.map((session) => {
|
|
4778
6281
|
try {
|
|
4779
6282
|
const data = loadSessionData(session.id);
|
|
6283
|
+
const messages = normalizeMessages(data);
|
|
6284
|
+
const identity = session.project_identity ?? data.project_identity ?? computeIdentity(session.directory, realFs);
|
|
4780
6285
|
return {
|
|
4781
6286
|
session,
|
|
4782
|
-
|
|
4783
|
-
|
|
6287
|
+
identity,
|
|
6288
|
+
messages,
|
|
6289
|
+
contentText: buildSessionContentFromMessages(data.title ?? session.title, messages),
|
|
6290
|
+
contentHash: sessionContentHash(session),
|
|
6291
|
+
fileActivity: extractSessionFileActivity(
|
|
6292
|
+
agentName,
|
|
6293
|
+
session.id,
|
|
6294
|
+
identity.key,
|
|
6295
|
+
data.messages
|
|
6296
|
+
)
|
|
4784
6297
|
};
|
|
4785
6298
|
} catch {
|
|
4786
6299
|
return null;
|
|
@@ -4789,6 +6302,54 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
|
|
|
4789
6302
|
const deleteRow = db.prepare(
|
|
4790
6303
|
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
4791
6304
|
);
|
|
6305
|
+
const deleteMessages = db.prepare(
|
|
6306
|
+
"DELETE FROM messages WHERE agent_name = ? AND session_id = ? AND message_index >= ?"
|
|
6307
|
+
);
|
|
6308
|
+
const deleteFileActivity = db.prepare(
|
|
6309
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6310
|
+
);
|
|
6311
|
+
const upsertIndexedSession = prepareUpsertIndexedSession(db);
|
|
6312
|
+
const insertFileActivity = prepareInsertFileActivity(db);
|
|
6313
|
+
const upsertMessage = db.prepare(`
|
|
6314
|
+
INSERT INTO messages(
|
|
6315
|
+
agent_name,
|
|
6316
|
+
session_id,
|
|
6317
|
+
message_index,
|
|
6318
|
+
message_id,
|
|
6319
|
+
role,
|
|
6320
|
+
time_created,
|
|
6321
|
+
time_completed,
|
|
6322
|
+
agent,
|
|
6323
|
+
mode,
|
|
6324
|
+
model,
|
|
6325
|
+
provider,
|
|
6326
|
+
tokens_json,
|
|
6327
|
+
cost,
|
|
6328
|
+
cost_source,
|
|
6329
|
+
parts_json,
|
|
6330
|
+
subagent_id,
|
|
6331
|
+
nickname,
|
|
6332
|
+
content_text,
|
|
6333
|
+
tool_metadata_json
|
|
6334
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6335
|
+
ON CONFLICT(agent_name, session_id, message_index) DO UPDATE SET
|
|
6336
|
+
message_id = excluded.message_id,
|
|
6337
|
+
role = excluded.role,
|
|
6338
|
+
time_created = excluded.time_created,
|
|
6339
|
+
time_completed = excluded.time_completed,
|
|
6340
|
+
agent = excluded.agent,
|
|
6341
|
+
mode = excluded.mode,
|
|
6342
|
+
model = excluded.model,
|
|
6343
|
+
provider = excluded.provider,
|
|
6344
|
+
tokens_json = excluded.tokens_json,
|
|
6345
|
+
cost = excluded.cost,
|
|
6346
|
+
cost_source = excluded.cost_source,
|
|
6347
|
+
parts_json = excluded.parts_json,
|
|
6348
|
+
subagent_id = excluded.subagent_id,
|
|
6349
|
+
nickname = excluded.nickname,
|
|
6350
|
+
content_text = excluded.content_text,
|
|
6351
|
+
tool_metadata_json = excluded.tool_metadata_json
|
|
6352
|
+
`);
|
|
4792
6353
|
const upsertRow = db.prepare(`
|
|
4793
6354
|
INSERT INTO session_documents(
|
|
4794
6355
|
agent_name,
|
|
@@ -4820,22 +6381,57 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
|
|
|
4820
6381
|
content_hash = excluded.content_hash,
|
|
4821
6382
|
indexed_at = excluded.indexed_at
|
|
4822
6383
|
`);
|
|
4823
|
-
const
|
|
6384
|
+
const writeRows = () => {
|
|
4824
6385
|
for (const sessionId of toDelete) {
|
|
4825
6386
|
deleteRow.run(agentName, sessionId);
|
|
6387
|
+
deleteFileActivity.run(agentName, sessionId);
|
|
6388
|
+
deleteMessages.run(agentName, sessionId, 0);
|
|
4826
6389
|
}
|
|
4827
6390
|
for (const entry of loaded) {
|
|
4828
6391
|
const activityTime = entry.session.time_updated ?? entry.session.time_created;
|
|
4829
|
-
|
|
6392
|
+
upsertSessionRow(
|
|
6393
|
+
upsertIndexedSession,
|
|
6394
|
+
agentName,
|
|
6395
|
+
entry.session,
|
|
6396
|
+
null,
|
|
6397
|
+
sessionSortIndexMap.get(entry.session.id) ?? 0,
|
|
6398
|
+
null
|
|
6399
|
+
);
|
|
6400
|
+
deleteFileActivity.run(agentName, entry.session.id);
|
|
6401
|
+
writeFileActivityRows(insertFileActivity, entry.fileActivity);
|
|
6402
|
+
for (const message of entry.messages) {
|
|
6403
|
+
upsertMessage.run(
|
|
6404
|
+
agentName,
|
|
6405
|
+
entry.session.id,
|
|
6406
|
+
message.index,
|
|
6407
|
+
message.id,
|
|
6408
|
+
message.role,
|
|
6409
|
+
message.timeCreated,
|
|
6410
|
+
message.timeCompleted ?? null,
|
|
6411
|
+
message.agent ?? null,
|
|
6412
|
+
message.mode ?? null,
|
|
6413
|
+
message.model ?? null,
|
|
6414
|
+
message.provider ?? null,
|
|
6415
|
+
message.tokensJson ?? null,
|
|
6416
|
+
message.cost ?? null,
|
|
6417
|
+
message.costSource ?? null,
|
|
6418
|
+
message.partsJson,
|
|
6419
|
+
message.subagentId ?? null,
|
|
6420
|
+
message.nickname ?? null,
|
|
6421
|
+
message.contentText,
|
|
6422
|
+
message.toolMetadataJson ?? null
|
|
6423
|
+
);
|
|
6424
|
+
}
|
|
6425
|
+
deleteMessages.run(agentName, entry.session.id, entry.messages.length);
|
|
4830
6426
|
upsertRow.run(
|
|
4831
6427
|
agentName,
|
|
4832
6428
|
entry.session.id,
|
|
4833
6429
|
entry.session.slug,
|
|
4834
6430
|
entry.session.title,
|
|
4835
6431
|
entry.session.directory,
|
|
4836
|
-
identity.kind,
|
|
4837
|
-
identity.key,
|
|
4838
|
-
identity.displayName,
|
|
6432
|
+
entry.identity.kind,
|
|
6433
|
+
entry.identity.key,
|
|
6434
|
+
entry.identity.displayName,
|
|
4839
6435
|
entry.session.time_created,
|
|
4840
6436
|
entry.session.time_updated ?? null,
|
|
4841
6437
|
activityTime,
|
|
@@ -4844,74 +6440,441 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
|
|
|
4844
6440
|
Date.now()
|
|
4845
6441
|
);
|
|
4846
6442
|
}
|
|
4847
|
-
}
|
|
4848
|
-
|
|
6443
|
+
};
|
|
6444
|
+
let rebuildDurationMs;
|
|
6445
|
+
const needsRebuild = isBulk && (toDelete.length > 0 || loaded.length > 0);
|
|
6446
|
+
if (needsRebuild) {
|
|
6447
|
+
db.transaction(() => {
|
|
6448
|
+
dropSearchTriggers(db);
|
|
6449
|
+
writeRows();
|
|
6450
|
+
const rebuildStartedAt = performance.now();
|
|
6451
|
+
rebuildSearchIndex(db);
|
|
6452
|
+
rebuildDurationMs = performance.now() - rebuildStartedAt;
|
|
6453
|
+
createSearchTriggers(db);
|
|
6454
|
+
})();
|
|
6455
|
+
} else {
|
|
6456
|
+
db.transaction(writeRows)();
|
|
6457
|
+
}
|
|
6458
|
+
return {
|
|
6459
|
+
agentName,
|
|
6460
|
+
mode: isBulk ? "bulk" : "incremental",
|
|
6461
|
+
sessions: sessions.length,
|
|
6462
|
+
changed: toUpsert.length,
|
|
6463
|
+
deleted: toDelete.length,
|
|
6464
|
+
indexed: loaded.length,
|
|
6465
|
+
skipped: toUpsert.length - loaded.length,
|
|
6466
|
+
durationMs: performance.now() - startedAt,
|
|
6467
|
+
rebuildDurationMs
|
|
6468
|
+
};
|
|
6469
|
+
});
|
|
6470
|
+
}
|
|
6471
|
+
function sessionHeadFromSearchRow(row) {
|
|
6472
|
+
return sessionFromRow(row);
|
|
6473
|
+
}
|
|
6474
|
+
function mergeSearchLists(left, right) {
|
|
6475
|
+
const values = [...left ?? [], ...right ?? []];
|
|
6476
|
+
return values.length > 0 ? [...new Set(values)] : void 0;
|
|
6477
|
+
}
|
|
6478
|
+
function mergeSearchQueryOptions(query, options) {
|
|
6479
|
+
const parsed2 = parseSearchQuery(query);
|
|
6480
|
+
return {
|
|
6481
|
+
text: parsed2.text || (parsed2.hasQualifiers ? "" : query.trim()),
|
|
6482
|
+
options: {
|
|
6483
|
+
...options,
|
|
6484
|
+
agent: options.agent ?? parsed2.filters.agent,
|
|
6485
|
+
project: options.project ?? parsed2.filters.project,
|
|
6486
|
+
projectKey: options.projectKey ?? parsed2.filters.projectKey,
|
|
6487
|
+
cwd: options.cwd ?? parsed2.filters.cwd,
|
|
6488
|
+
tags: mergeSearchLists(options.tags, parsed2.filters.tags),
|
|
6489
|
+
tools: mergeSearchLists(options.tools, parsed2.filters.tools),
|
|
6490
|
+
file: options.file ?? parsed2.filters.file,
|
|
6491
|
+
fileKind: options.fileKind ?? parsed2.filters.fileKind,
|
|
6492
|
+
costMin: options.costMin ?? parsed2.filters.costMin,
|
|
6493
|
+
costMax: options.costMax ?? parsed2.filters.costMax,
|
|
6494
|
+
costMinExclusive: options.costMinExclusive ?? parsed2.filters.costMinExclusive,
|
|
6495
|
+
costMaxExclusive: options.costMaxExclusive ?? parsed2.filters.costMaxExclusive
|
|
6496
|
+
},
|
|
6497
|
+
parsed: parsed2
|
|
6498
|
+
};
|
|
6499
|
+
}
|
|
6500
|
+
function sessionMatchesSearchCost(session, options) {
|
|
6501
|
+
const cost = session.stats.total_cost;
|
|
6502
|
+
if (options.costMin != null) {
|
|
6503
|
+
if (options.costMinExclusive ? cost <= options.costMin : cost < options.costMin) {
|
|
6504
|
+
return false;
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
if (options.costMax != null) {
|
|
6508
|
+
if (options.costMaxExclusive ? cost >= options.costMax : cost > options.costMax) {
|
|
6509
|
+
return false;
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
return true;
|
|
6513
|
+
}
|
|
6514
|
+
function likePattern(value) {
|
|
6515
|
+
return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
|
|
6516
|
+
}
|
|
6517
|
+
function escapeRegExp(value) {
|
|
6518
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6519
|
+
}
|
|
6520
|
+
function buildSessionSearchFilters(options) {
|
|
6521
|
+
const clauses = [];
|
|
6522
|
+
const params = [];
|
|
6523
|
+
if (options.agent) {
|
|
6524
|
+
clauses.push("s.agent_name = ?");
|
|
6525
|
+
params.push(options.agent);
|
|
6526
|
+
}
|
|
6527
|
+
if (options.projectKey) {
|
|
6528
|
+
clauses.push("s.project_identity_key = ?");
|
|
6529
|
+
params.push(options.projectKey);
|
|
6530
|
+
}
|
|
6531
|
+
if (options.cwd) {
|
|
6532
|
+
clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
|
|
6533
|
+
params.push(computeIdentity(options.cwd, realFs).key, likePattern(options.cwd));
|
|
6534
|
+
}
|
|
6535
|
+
if (options.project) {
|
|
6536
|
+
clauses.push(
|
|
6537
|
+
"(LOWER(s.project_identity_key) LIKE ? ESCAPE '\\' OR LOWER(s.project_display_name) LIKE ? ESCAPE '\\' OR LOWER(s.directory) LIKE ? ESCAPE '\\')"
|
|
6538
|
+
);
|
|
6539
|
+
const pattern = likePattern(options.project);
|
|
6540
|
+
params.push(pattern, pattern, pattern);
|
|
6541
|
+
}
|
|
6542
|
+
for (const tag of options.tags ?? []) {
|
|
6543
|
+
clauses.push("s.smart_tags_json LIKE ?");
|
|
6544
|
+
params.push(`%"${tag}"%`);
|
|
6545
|
+
}
|
|
6546
|
+
for (const tool of options.tools ?? []) {
|
|
6547
|
+
clauses.push(
|
|
6548
|
+
"EXISTS (SELECT 1 FROM messages m WHERE m.agent_name = s.agent_name AND m.session_id = s.session_id AND LOWER(m.tool_metadata_json) LIKE ? ESCAPE '\\')"
|
|
6549
|
+
);
|
|
6550
|
+
params.push(likePattern(tool));
|
|
6551
|
+
}
|
|
6552
|
+
if (options.file || options.fileKind) {
|
|
6553
|
+
const fileClauses = ["fa.agent_name = s.agent_name", "fa.session_id = s.session_id"];
|
|
6554
|
+
if (options.file) {
|
|
6555
|
+
fileClauses.push("LOWER(fa.path) LIKE ? ESCAPE '\\'");
|
|
6556
|
+
params.push(likePattern(options.file));
|
|
6557
|
+
}
|
|
6558
|
+
if (options.fileKind) {
|
|
6559
|
+
fileClauses.push("fa.kind = ?");
|
|
6560
|
+
params.push(options.fileKind);
|
|
6561
|
+
}
|
|
6562
|
+
clauses.push(
|
|
6563
|
+
`EXISTS (SELECT 1 FROM session_file_activity fa WHERE ${fileClauses.join(" AND ")})`
|
|
6564
|
+
);
|
|
6565
|
+
}
|
|
6566
|
+
if (options.from != null) {
|
|
6567
|
+
clauses.push("s.activity_time >= ?");
|
|
6568
|
+
params.push(options.from);
|
|
6569
|
+
}
|
|
6570
|
+
if (options.to != null) {
|
|
6571
|
+
clauses.push("s.activity_time <= ?");
|
|
6572
|
+
params.push(options.to);
|
|
6573
|
+
}
|
|
6574
|
+
if (options.costMin != null) {
|
|
6575
|
+
clauses.push(options.costMinExclusive ? "s.total_cost > ?" : "s.total_cost >= ?");
|
|
6576
|
+
params.push(options.costMin);
|
|
6577
|
+
}
|
|
6578
|
+
if (options.costMax != null) {
|
|
6579
|
+
clauses.push(options.costMaxExclusive ? "s.total_cost < ?" : "s.total_cost <= ?");
|
|
6580
|
+
params.push(options.costMax);
|
|
6581
|
+
}
|
|
6582
|
+
return {
|
|
6583
|
+
where: clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "",
|
|
6584
|
+
params
|
|
6585
|
+
};
|
|
6586
|
+
}
|
|
6587
|
+
function searchSessionColumns() {
|
|
6588
|
+
return `
|
|
6589
|
+
s.agent_name,
|
|
6590
|
+
s.session_id,
|
|
6591
|
+
s.slug,
|
|
6592
|
+
s.title,
|
|
6593
|
+
s.directory,
|
|
6594
|
+
s.project_identity_kind,
|
|
6595
|
+
s.project_identity_key,
|
|
6596
|
+
s.project_display_name,
|
|
6597
|
+
s.time_created,
|
|
6598
|
+
s.time_updated,
|
|
6599
|
+
s.message_count,
|
|
6600
|
+
s.total_input_tokens,
|
|
6601
|
+
s.total_output_tokens,
|
|
6602
|
+
s.total_cache_read_tokens,
|
|
6603
|
+
s.total_cache_create_tokens,
|
|
6604
|
+
s.total_cost,
|
|
6605
|
+
s.cost_source,
|
|
6606
|
+
s.total_tokens,
|
|
6607
|
+
s.model_usage_json,
|
|
6608
|
+
s.smart_tags_json,
|
|
6609
|
+
s.smart_tags_source_updated_at
|
|
6610
|
+
`;
|
|
6611
|
+
}
|
|
6612
|
+
function parseTextTerms(input) {
|
|
6613
|
+
const tokens = splitSearchTokens(input);
|
|
6614
|
+
return {
|
|
6615
|
+
terms: tokens.filter((token) => !/^OR$/i.test(token)).map((token) => unwrapSearchValue(token).toLowerCase()).filter(Boolean),
|
|
6616
|
+
mode: tokens.some((token) => /^OR$/i.test(token)) ? "any" : "all"
|
|
6617
|
+
};
|
|
6618
|
+
}
|
|
6619
|
+
function textMatchesTerms(text, terms) {
|
|
6620
|
+
const lower = text.toLowerCase();
|
|
6621
|
+
if (terms.terms.length === 0) return true;
|
|
6622
|
+
if (terms.mode === "any") return terms.terms.some((term) => lower.includes(term));
|
|
6623
|
+
return terms.terms.every((term) => lower.includes(term));
|
|
6624
|
+
}
|
|
6625
|
+
function highlightTerm(text, term) {
|
|
6626
|
+
return text.replace(new RegExp(escapeRegExp(term), "gi"), (match) => `<mark>${match}</mark>`);
|
|
6627
|
+
}
|
|
6628
|
+
function buildTermSnippet(text, terms) {
|
|
6629
|
+
const lower = text.toLowerCase();
|
|
6630
|
+
const term = terms.terms.find((item) => lower.includes(item)) ?? terms.terms[0] ?? "";
|
|
6631
|
+
if (!term) return text.slice(0, 180);
|
|
6632
|
+
const index = lower.indexOf(term);
|
|
6633
|
+
const start = Math.max(0, index - 80);
|
|
6634
|
+
const end = Math.min(text.length, index + term.length + 80);
|
|
6635
|
+
return `${start > 0 ? "\u2026 " : ""}${highlightTerm(text.slice(start, end), term)}${end < text.length ? " \u2026" : ""}`;
|
|
6636
|
+
}
|
|
6637
|
+
function messageMatchType(row) {
|
|
6638
|
+
if (row.role === "user") return "user_message";
|
|
6639
|
+
if (row.role === "tool" || row.mode === "tool" || row.tool_metadata_json) return "tool_output";
|
|
6640
|
+
return "assistant_reply";
|
|
6641
|
+
}
|
|
6642
|
+
function resolveSearchMatch(db, row, textQuery) {
|
|
6643
|
+
const terms = parseTextTerms(textQuery);
|
|
6644
|
+
const title = String(row.title ?? "");
|
|
6645
|
+
if (terms.terms.length === 0) {
|
|
6646
|
+
return {
|
|
6647
|
+
snippet: `Recent session \xB7 ${String(row.directory ?? "")}`,
|
|
6648
|
+
matchType: "recent"
|
|
6649
|
+
};
|
|
6650
|
+
}
|
|
6651
|
+
if (textMatchesTerms(title, terms)) {
|
|
6652
|
+
return { snippet: buildTermSnippet(title, terms), matchType: "title" };
|
|
6653
|
+
}
|
|
6654
|
+
const messages = db.prepare(
|
|
6655
|
+
`
|
|
6656
|
+
SELECT role, mode, content_text, tool_metadata_json
|
|
6657
|
+
FROM messages
|
|
6658
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6659
|
+
ORDER BY message_index
|
|
6660
|
+
`
|
|
6661
|
+
).all(row.agent_name, row.session_id);
|
|
6662
|
+
for (const message of messages) {
|
|
6663
|
+
const text = String(message.content_text ?? "");
|
|
6664
|
+
if (!textMatchesTerms(text, terms)) continue;
|
|
6665
|
+
return {
|
|
6666
|
+
snippet: buildTermSnippet(text, terms),
|
|
6667
|
+
matchType: messageMatchType(message)
|
|
6668
|
+
};
|
|
6669
|
+
}
|
|
6670
|
+
return {
|
|
6671
|
+
snippet: String(row.snippet ?? ""),
|
|
6672
|
+
matchType: "assistant_reply"
|
|
6673
|
+
};
|
|
6674
|
+
}
|
|
6675
|
+
function rowsToSearchResults(db, rows, textQuery) {
|
|
6676
|
+
return rows.map((row) => {
|
|
6677
|
+
const match = resolveSearchMatch(db, row, textQuery);
|
|
6678
|
+
return {
|
|
6679
|
+
agentName: String(row.agent_name),
|
|
6680
|
+
session: sessionHeadFromSearchRow(row),
|
|
6681
|
+
snippet: match.snippet,
|
|
6682
|
+
matchType: match.matchType
|
|
6683
|
+
};
|
|
4849
6684
|
});
|
|
4850
6685
|
}
|
|
4851
6686
|
function searchSessions(query, options = {}) {
|
|
4852
|
-
const
|
|
4853
|
-
|
|
6687
|
+
const search = mergeSearchQueryOptions(query, options);
|
|
6688
|
+
const normalizedQuery = search.text.trim();
|
|
6689
|
+
if (!hasCacheStorage()) {
|
|
4854
6690
|
return [];
|
|
4855
6691
|
}
|
|
4856
|
-
const ftsQuery = toFtsQuery(normalizedQuery);
|
|
4857
6692
|
const results = withCacheDb((db) => {
|
|
6693
|
+
ensureFtsReady(db);
|
|
6694
|
+
const filters = buildSessionSearchFilters(search.options);
|
|
6695
|
+
if (!normalizedQuery) {
|
|
6696
|
+
const rows2 = db.prepare(
|
|
6697
|
+
`
|
|
6698
|
+
SELECT
|
|
6699
|
+
${searchSessionColumns()},
|
|
6700
|
+
'' AS snippet
|
|
6701
|
+
FROM sessions s
|
|
6702
|
+
WHERE 1 = 1
|
|
6703
|
+
${filters.where}
|
|
6704
|
+
ORDER BY s.activity_time DESC
|
|
6705
|
+
LIMIT ?
|
|
6706
|
+
`
|
|
6707
|
+
).all(...filters.params, search.options.limit ?? 50);
|
|
6708
|
+
return rowsToSearchResults(db, rows2, "");
|
|
6709
|
+
}
|
|
6710
|
+
const ftsQuery = toFtsQuery(normalizedQuery);
|
|
6711
|
+
if (!ftsQuery) return [];
|
|
4858
6712
|
const rows = db.prepare(
|
|
4859
6713
|
`
|
|
4860
6714
|
SELECT
|
|
4861
|
-
|
|
4862
|
-
d.session_id,
|
|
4863
|
-
d.slug,
|
|
4864
|
-
d.title,
|
|
4865
|
-
d.directory,
|
|
4866
|
-
d.time_created,
|
|
4867
|
-
d.time_updated,
|
|
6715
|
+
${searchSessionColumns()},
|
|
4868
6716
|
COALESCE(
|
|
4869
6717
|
NULLIF(snippet(session_documents_fts, 1, '<mark>', '</mark>', ' \u2026 ', 18), ''),
|
|
4870
6718
|
highlight(session_documents_fts, 0, '<mark>', '</mark>')
|
|
4871
6719
|
) AS snippet
|
|
4872
6720
|
FROM session_documents_fts
|
|
4873
6721
|
JOIN session_documents d ON d.id = session_documents_fts.rowid
|
|
6722
|
+
JOIN sessions s ON s.agent_name = d.agent_name AND s.session_id = d.session_id
|
|
4874
6723
|
WHERE session_documents_fts MATCH ?
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
6724
|
+
${filters.where}
|
|
6725
|
+
ORDER BY bm25(session_documents_fts, 8.0, 1.0), s.activity_time DESC
|
|
6726
|
+
LIMIT ?
|
|
6727
|
+
`
|
|
6728
|
+
).all(ftsQuery, ...filters.params, search.options.limit ?? 50);
|
|
6729
|
+
return rowsToSearchResults(db, rows, normalizedQuery);
|
|
6730
|
+
});
|
|
6731
|
+
return results ?? [];
|
|
6732
|
+
}
|
|
6733
|
+
function normalizeFilePathSearch(value) {
|
|
6734
|
+
return value.trim().replace(/^"|"$/g, "");
|
|
6735
|
+
}
|
|
6736
|
+
function fileActivityFilters(options) {
|
|
6737
|
+
const path2 = options.path ? normalizeFilePathSearch(options.path) : "";
|
|
6738
|
+
return {
|
|
6739
|
+
projectKey: options.projectKey ?? null,
|
|
6740
|
+
projectLike: options.project ? likePattern(options.project) : null,
|
|
6741
|
+
cwdKey: options.cwd ? computeIdentity(options.cwd, realFs).key : null,
|
|
6742
|
+
cwdLike: options.cwd ? likePattern(options.cwd) : null,
|
|
6743
|
+
pathLike: path2 ? likePattern(path2) : null
|
|
6744
|
+
};
|
|
6745
|
+
}
|
|
6746
|
+
function fileActivityFromRow(row) {
|
|
6747
|
+
return {
|
|
6748
|
+
agent_name: String(row.agent_name),
|
|
6749
|
+
session_id: String(row.session_id),
|
|
6750
|
+
project_identity_key: String(row.project_identity_key ?? ""),
|
|
6751
|
+
path: String(row.path ?? ""),
|
|
6752
|
+
kind: row.kind ?? "read",
|
|
6753
|
+
count: Number(row.count ?? 0),
|
|
6754
|
+
latest_time: Number(row.latest_time ?? 0)
|
|
6755
|
+
};
|
|
6756
|
+
}
|
|
6757
|
+
function listFileActivity(options = {}) {
|
|
6758
|
+
if (!hasCacheStorage()) {
|
|
6759
|
+
return [];
|
|
6760
|
+
}
|
|
6761
|
+
const filters = fileActivityFilters(options);
|
|
6762
|
+
const rows = withCacheDb(
|
|
6763
|
+
(db) => db.prepare(
|
|
6764
|
+
`
|
|
6765
|
+
SELECT
|
|
6766
|
+
fa.agent_name,
|
|
6767
|
+
fa.session_id,
|
|
6768
|
+
fa.project_identity_key,
|
|
6769
|
+
fa.path,
|
|
6770
|
+
fa.kind,
|
|
6771
|
+
fa.count,
|
|
6772
|
+
fa.latest_time,
|
|
6773
|
+
s.slug,
|
|
6774
|
+
s.title,
|
|
6775
|
+
s.directory,
|
|
6776
|
+
s.project_identity_kind,
|
|
6777
|
+
s.project_display_name,
|
|
6778
|
+
s.time_created,
|
|
6779
|
+
s.time_updated,
|
|
6780
|
+
s.message_count,
|
|
6781
|
+
s.total_input_tokens,
|
|
6782
|
+
s.total_output_tokens,
|
|
6783
|
+
s.total_cache_read_tokens,
|
|
6784
|
+
s.total_cache_create_tokens,
|
|
6785
|
+
s.total_cost,
|
|
6786
|
+
s.cost_source,
|
|
6787
|
+
s.total_tokens
|
|
6788
|
+
FROM session_file_activity fa
|
|
6789
|
+
JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
|
|
6790
|
+
WHERE (? IS NULL OR fa.agent_name = ?)
|
|
6791
|
+
AND (? IS NULL OR fa.session_id = ?)
|
|
6792
|
+
AND (? IS NULL OR fa.project_identity_key = ?)
|
|
6793
|
+
AND (? IS NULL OR LOWER(fa.project_identity_key) LIKE ? ESCAPE '\\' OR LOWER(s.project_display_name) LIKE ? ESCAPE '\\' OR LOWER(s.directory) LIKE ? ESCAPE '\\')
|
|
6794
|
+
AND (? IS NULL OR s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')
|
|
6795
|
+
AND (? IS NULL OR LOWER(fa.path) LIKE ? ESCAPE '\\')
|
|
6796
|
+
AND (? IS NULL OR fa.kind = ?)
|
|
6797
|
+
AND (? IS NULL OR fa.latest_time >= ?)
|
|
6798
|
+
AND (? IS NULL OR fa.latest_time <= ?)
|
|
6799
|
+
ORDER BY fa.latest_time DESC, fa.count DESC, fa.path
|
|
4880
6800
|
LIMIT ?
|
|
4881
6801
|
`
|
|
4882
6802
|
).all(
|
|
4883
|
-
ftsQuery,
|
|
4884
6803
|
options.agent ?? null,
|
|
4885
6804
|
options.agent ?? null,
|
|
4886
|
-
options.
|
|
4887
|
-
options.
|
|
4888
|
-
|
|
6805
|
+
options.sessionId ?? null,
|
|
6806
|
+
options.sessionId ?? null,
|
|
6807
|
+
filters.projectKey,
|
|
6808
|
+
filters.projectKey,
|
|
6809
|
+
filters.projectLike,
|
|
6810
|
+
filters.projectLike,
|
|
6811
|
+
filters.projectLike,
|
|
6812
|
+
filters.projectLike,
|
|
6813
|
+
filters.cwdKey,
|
|
6814
|
+
filters.cwdKey,
|
|
6815
|
+
filters.cwdLike,
|
|
6816
|
+
filters.pathLike,
|
|
6817
|
+
filters.pathLike,
|
|
6818
|
+
options.kind ?? null,
|
|
6819
|
+
options.kind ?? null,
|
|
4889
6820
|
options.from ?? null,
|
|
4890
6821
|
options.from ?? null,
|
|
4891
6822
|
options.to ?? null,
|
|
4892
6823
|
options.to ?? null,
|
|
4893
6824
|
options.limit ?? 50
|
|
4894
|
-
)
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
6825
|
+
)
|
|
6826
|
+
);
|
|
6827
|
+
return (rows ?? []).map((row) => ({
|
|
6828
|
+
...fileActivityFromRow(row),
|
|
6829
|
+
session: sessionHeadFromSearchRow(row)
|
|
6830
|
+
}));
|
|
6831
|
+
}
|
|
6832
|
+
function listSessionFileActivity(agentName, sessionId) {
|
|
6833
|
+
return listFileActivity({ agent: agentName, sessionId, limit: 500 }).map(
|
|
6834
|
+
({ session: _session, ...activity }) => activity
|
|
6835
|
+
);
|
|
6836
|
+
}
|
|
6837
|
+
function highlightFilePath(path2, query) {
|
|
6838
|
+
const needle = normalizeFilePathSearch(query);
|
|
6839
|
+
if (!needle) return path2;
|
|
6840
|
+
const lower = path2.toLowerCase();
|
|
6841
|
+
const index = lower.indexOf(needle.toLowerCase());
|
|
6842
|
+
if (index < 0) return path2;
|
|
6843
|
+
return `${path2.slice(0, index)}<mark>${path2.slice(index, index + needle.length)}</mark>${path2.slice(
|
|
6844
|
+
index + needle.length
|
|
6845
|
+
)}`;
|
|
6846
|
+
}
|
|
6847
|
+
function searchFileActivitySessions(query, options = {}) {
|
|
6848
|
+
const search = mergeSearchQueryOptions(query, options);
|
|
6849
|
+
const path2 = normalizeFilePathSearch(search.options.file ?? search.text);
|
|
6850
|
+
if (!path2) return [];
|
|
6851
|
+
const rows = listFileActivity({
|
|
6852
|
+
agent: search.options.agent,
|
|
6853
|
+
projectKey: search.options.projectKey,
|
|
6854
|
+
project: search.options.project,
|
|
6855
|
+
cwd: search.options.cwd,
|
|
6856
|
+
path: path2,
|
|
6857
|
+
kind: search.options.fileKind,
|
|
6858
|
+
from: search.options.from,
|
|
6859
|
+
to: search.options.to,
|
|
6860
|
+
limit: (search.options.limit ?? 50) * 3
|
|
4913
6861
|
});
|
|
4914
|
-
|
|
6862
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6863
|
+
const results = [];
|
|
6864
|
+
for (const row of rows) {
|
|
6865
|
+
const key = `${row.agent_name}/${row.session_id}`;
|
|
6866
|
+
if (seen.has(key)) continue;
|
|
6867
|
+
if (!sessionMatchesSearchCost(row.session, search.options)) continue;
|
|
6868
|
+
seen.add(key);
|
|
6869
|
+
results.push({
|
|
6870
|
+
agentName: row.agent_name,
|
|
6871
|
+
session: row.session,
|
|
6872
|
+
snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
|
|
6873
|
+
matchType: "file_path"
|
|
6874
|
+
});
|
|
6875
|
+
if (results.length >= (search.options.limit ?? 50)) break;
|
|
6876
|
+
}
|
|
6877
|
+
return results;
|
|
4915
6878
|
}
|
|
4916
6879
|
function listCachedProjectGroups(sessions) {
|
|
4917
6880
|
if (sessions) {
|
|
@@ -5178,8 +7141,8 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
5178
7141
|
phase: "complete",
|
|
5179
7142
|
newCount: tagged2.sessions.length
|
|
5180
7143
|
});
|
|
5181
|
-
const
|
|
5182
|
-
return { agent, heads:
|
|
7144
|
+
const filtered3 = filterSessions(tagged2.sessions, options);
|
|
7145
|
+
return { agent, heads: filtered3, fromCache: true, refreshed: true };
|
|
5183
7146
|
}
|
|
5184
7147
|
onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
|
|
5185
7148
|
}
|
|
@@ -5188,8 +7151,8 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
5188
7151
|
if (tagged.changed && options.writeCache !== false && options.from == null && options.to == null) {
|
|
5189
7152
|
saveCachedSessions(agent.name, tagged.sessions, buildAgentCacheMeta(agent));
|
|
5190
7153
|
}
|
|
5191
|
-
const
|
|
5192
|
-
return { agent, heads:
|
|
7154
|
+
const filtered2 = filterSessions(tagged.sessions, options);
|
|
7155
|
+
return { agent, heads: filtered2, fromCache: true };
|
|
5193
7156
|
}
|
|
5194
7157
|
}
|
|
5195
7158
|
return scanAgentFull(agent, options, onProgress);
|
|
@@ -5212,8 +7175,8 @@ async function scanAgentFull(agent, options, onProgress) {
|
|
|
5212
7175
|
saveCachedSessions(agent.name, tagged.sessions, meta);
|
|
5213
7176
|
}
|
|
5214
7177
|
onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
|
|
5215
|
-
const
|
|
5216
|
-
return { agent, heads:
|
|
7178
|
+
const filtered2 = filterSessions(tagged.sessions, options);
|
|
7179
|
+
return { agent, heads: filtered2, fromCache: false };
|
|
5217
7180
|
} catch (err) {
|
|
5218
7181
|
console.error(`Error scanning ${agent.name}:`, err);
|
|
5219
7182
|
return { agent, heads: [], fromCache: false };
|
|
@@ -5248,7 +7211,9 @@ async function scanSessionsAsync(options = {}, onProgress) {
|
|
|
5248
7211
|
return scanSessions(options, onProgress);
|
|
5249
7212
|
}
|
|
5250
7213
|
var BOOKMARK_DB_FILENAME = "state.db";
|
|
5251
|
-
var
|
|
7214
|
+
var BOOKMARK_SCHEMA_VERSION = 1;
|
|
7215
|
+
var MEMORY_STATE_STORE = "memory";
|
|
7216
|
+
var memoryBookmarks = /* @__PURE__ */ new Map();
|
|
5252
7217
|
var BookmarkStorageUnavailableError = class extends Error {
|
|
5253
7218
|
constructor() {
|
|
5254
7219
|
super("SQLite state database is unavailable");
|
|
@@ -5256,20 +7221,50 @@ var BookmarkStorageUnavailableError = class extends Error {
|
|
|
5256
7221
|
}
|
|
5257
7222
|
};
|
|
5258
7223
|
function getStateDir() {
|
|
7224
|
+
if (process.env.CODESESH_STATE_DIR) {
|
|
7225
|
+
return process.env.CODESESH_STATE_DIR;
|
|
7226
|
+
}
|
|
5259
7227
|
const p = platform2();
|
|
5260
7228
|
if (p === "darwin") {
|
|
5261
|
-
return
|
|
7229
|
+
return join10(homedir5(), "Library", "Application Support", "codesesh");
|
|
5262
7230
|
}
|
|
5263
7231
|
if (p === "win32") {
|
|
5264
7232
|
const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
|
|
5265
|
-
return
|
|
7233
|
+
return join10(appData ?? join10(homedir5(), "AppData", "Roaming"), "codesesh");
|
|
5266
7234
|
}
|
|
5267
|
-
return
|
|
7235
|
+
return join10(process.env.XDG_DATA_HOME ?? join10(homedir5(), ".local", "share"), "codesesh");
|
|
5268
7236
|
}
|
|
5269
7237
|
function getStateDbPath() {
|
|
5270
|
-
return
|
|
7238
|
+
return join10(getStateDir(), BOOKMARK_DB_FILENAME);
|
|
7239
|
+
}
|
|
7240
|
+
function useMemoryStateStore() {
|
|
7241
|
+
return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
|
|
7242
|
+
}
|
|
7243
|
+
function getBookmarkKey(agentKey, sessionId) {
|
|
7244
|
+
return JSON.stringify([agentKey, sessionId]);
|
|
7245
|
+
}
|
|
7246
|
+
function getActivityTime(bookmark) {
|
|
7247
|
+
return bookmark.time_updated ?? bookmark.time_created;
|
|
7248
|
+
}
|
|
7249
|
+
function sortBookmarks(bookmarks) {
|
|
7250
|
+
return bookmarks.sort((a, b) => {
|
|
7251
|
+
const activityDelta = getActivityTime(b) - getActivityTime(a);
|
|
7252
|
+
return activityDelta || b.bookmarked_at - a.bookmarked_at;
|
|
7253
|
+
});
|
|
5271
7254
|
}
|
|
5272
|
-
function
|
|
7255
|
+
function listMemoryBookmarks() {
|
|
7256
|
+
return sortBookmarks(Array.from(memoryBookmarks.values()));
|
|
7257
|
+
}
|
|
7258
|
+
function upsertMemoryBookmark(bookmark) {
|
|
7259
|
+
const key = getBookmarkKey(bookmark.agentKey, bookmark.sessionId);
|
|
7260
|
+
const saved = {
|
|
7261
|
+
...bookmark,
|
|
7262
|
+
bookmarked_at: memoryBookmarks.get(key)?.bookmarked_at ?? Date.now()
|
|
7263
|
+
};
|
|
7264
|
+
memoryBookmarks.set(key, saved);
|
|
7265
|
+
return saved;
|
|
7266
|
+
}
|
|
7267
|
+
function createStateSchema(db) {
|
|
5273
7268
|
db.exec(`
|
|
5274
7269
|
CREATE TABLE IF NOT EXISTS state_meta (
|
|
5275
7270
|
key TEXT PRIMARY KEY,
|
|
@@ -5289,24 +7284,66 @@ function ensureSchema2(db) {
|
|
|
5289
7284
|
PRIMARY KEY (agent_name, session_id)
|
|
5290
7285
|
);
|
|
5291
7286
|
`);
|
|
7287
|
+
}
|
|
7288
|
+
function readLegacyStateVersion(db) {
|
|
7289
|
+
if (!tableExists(db, "state_meta") || !columnExists(db, "state_meta", "key") || !columnExists(db, "state_meta", "value")) {
|
|
7290
|
+
return 0;
|
|
7291
|
+
}
|
|
5292
7292
|
const row = db.prepare("SELECT value FROM state_meta WHERE key = 'version'").get();
|
|
5293
|
-
|
|
5294
|
-
|
|
7293
|
+
return Number(row?.value ?? 0);
|
|
7294
|
+
}
|
|
7295
|
+
function getCurrentStateSchemaVersion(db) {
|
|
7296
|
+
const userVersion = getUserVersion(db);
|
|
7297
|
+
if (userVersion > 0) {
|
|
7298
|
+
return userVersion;
|
|
7299
|
+
}
|
|
7300
|
+
const legacyVersion = readLegacyStateVersion(db);
|
|
7301
|
+
if (legacyVersion > 0) {
|
|
7302
|
+
return legacyVersion;
|
|
7303
|
+
}
|
|
7304
|
+
return tableExists(db, "bookmarks") ? 1 : 0;
|
|
7305
|
+
}
|
|
7306
|
+
function hasAnyStateSchema(db) {
|
|
7307
|
+
return tableExists(db, "state_meta") || tableExists(db, "bookmarks");
|
|
7308
|
+
}
|
|
7309
|
+
function setStateSchemaVersion(db) {
|
|
7310
|
+
createStateSchema(db);
|
|
7311
|
+
setUserVersion(db, BOOKMARK_SCHEMA_VERSION);
|
|
5295
7312
|
db.prepare(
|
|
5296
7313
|
`
|
|
5297
7314
|
INSERT INTO state_meta(key, value)
|
|
5298
7315
|
VALUES ('version', ?)
|
|
5299
7316
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
5300
7317
|
`
|
|
5301
|
-
).run(String(
|
|
7318
|
+
).run(String(BOOKMARK_SCHEMA_VERSION));
|
|
7319
|
+
}
|
|
7320
|
+
function ensureSchema2(db, dbPath) {
|
|
7321
|
+
const currentVersion = getCurrentStateSchemaVersion(db);
|
|
7322
|
+
if (currentVersion === 0 && !hasAnyStateSchema(db)) {
|
|
7323
|
+
setStateSchemaVersion(db);
|
|
7324
|
+
return;
|
|
7325
|
+
}
|
|
7326
|
+
runSchemaMigrations(db, {
|
|
7327
|
+
dbPath,
|
|
7328
|
+
currentVersion,
|
|
7329
|
+
targetVersion: BOOKMARK_SCHEMA_VERSION,
|
|
7330
|
+
backupLabel: "state-migration",
|
|
7331
|
+
backupTables: ["bookmarks"],
|
|
7332
|
+
migrations: [{ version: 1, migrate: createStateSchema }]
|
|
7333
|
+
});
|
|
7334
|
+
createStateSchema(db);
|
|
7335
|
+
if (getUserVersion(db) <= BOOKMARK_SCHEMA_VERSION) {
|
|
7336
|
+
setStateSchemaVersion(db);
|
|
7337
|
+
}
|
|
5302
7338
|
}
|
|
5303
7339
|
function withStateDb(fn) {
|
|
5304
|
-
const
|
|
7340
|
+
const statePath = getStateDbPath();
|
|
7341
|
+
const db = openDb(statePath);
|
|
5305
7342
|
if (!db) {
|
|
5306
7343
|
throw new BookmarkStorageUnavailableError();
|
|
5307
7344
|
}
|
|
5308
7345
|
try {
|
|
5309
|
-
ensureSchema2(db);
|
|
7346
|
+
ensureSchema2(db, statePath);
|
|
5310
7347
|
return fn(db);
|
|
5311
7348
|
} finally {
|
|
5312
7349
|
db.close();
|
|
@@ -5326,6 +7363,9 @@ function toBookmarkRecord(row) {
|
|
|
5326
7363
|
};
|
|
5327
7364
|
}
|
|
5328
7365
|
function listBookmarks() {
|
|
7366
|
+
if (useMemoryStateStore()) {
|
|
7367
|
+
return listMemoryBookmarks();
|
|
7368
|
+
}
|
|
5329
7369
|
return withStateDb((db) => {
|
|
5330
7370
|
const rows = db.prepare(
|
|
5331
7371
|
`
|
|
@@ -5347,6 +7387,9 @@ function listBookmarks() {
|
|
|
5347
7387
|
});
|
|
5348
7388
|
}
|
|
5349
7389
|
function upsertBookmark(bookmark) {
|
|
7390
|
+
if (useMemoryStateStore()) {
|
|
7391
|
+
return upsertMemoryBookmark(bookmark);
|
|
7392
|
+
}
|
|
5350
7393
|
return withStateDb((db) => {
|
|
5351
7394
|
const existing = db.prepare(
|
|
5352
7395
|
`
|
|
@@ -5392,6 +7435,12 @@ function upsertBookmark(bookmark) {
|
|
|
5392
7435
|
});
|
|
5393
7436
|
}
|
|
5394
7437
|
function importBookmarks(bookmarks) {
|
|
7438
|
+
if (useMemoryStateStore()) {
|
|
7439
|
+
for (const bookmark of bookmarks) {
|
|
7440
|
+
upsertMemoryBookmark(bookmark);
|
|
7441
|
+
}
|
|
7442
|
+
return listMemoryBookmarks();
|
|
7443
|
+
}
|
|
5395
7444
|
return withStateDb((db) => {
|
|
5396
7445
|
const existingRows = db.prepare("SELECT agent_name, session_id, bookmarked_at FROM bookmarks").all();
|
|
5397
7446
|
const existingTimes = new Map(
|
|
@@ -5459,6 +7508,10 @@ function importBookmarks(bookmarks) {
|
|
|
5459
7508
|
});
|
|
5460
7509
|
}
|
|
5461
7510
|
function deleteBookmark(agentKey, sessionId) {
|
|
7511
|
+
if (useMemoryStateStore()) {
|
|
7512
|
+
memoryBookmarks.delete(getBookmarkKey(agentKey, sessionId));
|
|
7513
|
+
return;
|
|
7514
|
+
}
|
|
5462
7515
|
withStateDb((db) => {
|
|
5463
7516
|
db.prepare(
|
|
5464
7517
|
`
|
|
@@ -5475,15 +7528,30 @@ export {
|
|
|
5475
7528
|
getRegisteredAgents,
|
|
5476
7529
|
getAgentInfoMap,
|
|
5477
7530
|
getAgentByName,
|
|
7531
|
+
parsedSession,
|
|
7532
|
+
skippedSession,
|
|
7533
|
+
filteredSession,
|
|
7534
|
+
getParsedSession,
|
|
5478
7535
|
BaseAgent,
|
|
5479
7536
|
firstExisting,
|
|
5480
7537
|
resolveProviderRoots,
|
|
5481
7538
|
getCursorDataPath,
|
|
5482
7539
|
parseJsonlLines,
|
|
5483
7540
|
readJsonlFile,
|
|
7541
|
+
cleanDisplayText,
|
|
7542
|
+
firstVisibleLine,
|
|
5484
7543
|
normalizeTitleText,
|
|
5485
7544
|
basenameTitle,
|
|
5486
7545
|
resolveSessionTitle,
|
|
7546
|
+
parsed,
|
|
7547
|
+
skipped,
|
|
7548
|
+
filtered,
|
|
7549
|
+
cleanInternalText,
|
|
7550
|
+
cleanMessagePart,
|
|
7551
|
+
cleanMessageParts,
|
|
7552
|
+
cleanParsedMessage,
|
|
7553
|
+
cleanParsedMessages,
|
|
7554
|
+
firstUserMessageTitle,
|
|
5487
7555
|
perf,
|
|
5488
7556
|
getPricingRegistry,
|
|
5489
7557
|
hasBillablePricing,
|
|
@@ -5504,12 +7572,19 @@ export {
|
|
|
5504
7572
|
computeIdentity,
|
|
5505
7573
|
getSmartTagSourceTimestamp,
|
|
5506
7574
|
classifySessionTags,
|
|
7575
|
+
extractFileActivityOccurrences,
|
|
7576
|
+
summarizeFileActivity,
|
|
7577
|
+
extractSessionFileActivity,
|
|
7578
|
+
parseSearchQuery,
|
|
5507
7579
|
loadCachedSessions,
|
|
5508
7580
|
saveCachedSessions,
|
|
5509
7581
|
clearCache,
|
|
5510
7582
|
getCacheInfo,
|
|
5511
7583
|
syncSessionSearchIndex,
|
|
5512
7584
|
searchSessions,
|
|
7585
|
+
listFileActivity,
|
|
7586
|
+
listSessionFileActivity,
|
|
7587
|
+
searchFileActivitySessions,
|
|
5513
7588
|
listCachedProjectGroups,
|
|
5514
7589
|
filterSessions,
|
|
5515
7590
|
scanSessions,
|
|
@@ -5520,4 +7595,4 @@ export {
|
|
|
5520
7595
|
importBookmarks,
|
|
5521
7596
|
deleteBookmark
|
|
5522
7597
|
};
|
|
5523
|
-
//# sourceMappingURL=chunk-
|
|
7598
|
+
//# sourceMappingURL=chunk-SQYHWMQV.js.map
|