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.
@@ -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 existsSync4, statSync as statSync2 } from "fs";
15
- import { join as join4 } from "path";
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 existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
21
- import { join as join5, basename as basename3, dirname as dirname3 } from "path";
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 existsSync6,
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 join6, basename as basename4 } from "path";
32
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
33
- import { join as join7, normalize } from "path";
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 existsSync8, readFileSync as readFileSync7 } from "fs";
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 existsSync9, rmSync, unlinkSync } from "fs";
42
- import { join as join8 } from "path";
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 join9 } from "path";
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 cleaned = text.replace(/\s+/g, " ").trim();
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.parseSessionHead(file, projectDir);
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
- for (const msg of messages) {
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: messages.length,
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.parseSessionHead(file, projectDir);
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.parseSessionHead(file, projectDir);
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 null;
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 null;
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
- return normalizeTitleText2(content);
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
- return normalizeTitleText2(texts);
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.trim()) {
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.trim()) {
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
- return content.trim() ? [this.buildTextPart(content, timestampMs)] : [];
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.trim()) parts.push(this.buildTextPart(text, timestampMs));
1265
- } else if (typeof item === "string" && item.trim()) {
1266
- parts.push(this.buildTextPart(item, timestampMs));
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
- return content.trim() ? [this.buildTextPart(content, timestampMs)] : [];
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
- if (text2.trim()) parts.push(this.buildTextPart(text2, timestampMs));
1284
- } else if (typeof item === "string" && item.trim()) {
1285
- parts.push(this.buildTextPart(item, timestampMs));
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.trim() ? [this.buildTextPart(text, timestampMs)] : [];
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
- db.pragma("journal_mode = WAL");
1386
- db.pragma("synchronous = NORMAL");
1387
- db.pragma("foreign_keys = ON");
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(join4(roots.opencodeRoot, "opencode.db"), "data/opencode/opencode.db");
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 = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get();
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 id = String(row.id ?? "");
1444
- const title = String(row.title ?? "").trim() || "Untitled";
1445
- const timeCreated = Number(row.time_created ?? 0);
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 || !existsSync4(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: rows.length,
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
- const parts = [];
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
- if (estimatedCost !== null) hasEstimatedCost = true;
1585
- totalCost += resolvedCost;
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: messages.length,
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
- return content.trim() ? [{ type: "text", text: content, time_created: timestampMs }] : [];
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
- if (text2.trim()) parts.push({ type: "text", text: text2, time_created: timestampMs });
1678
- } else if (typeof item === "string" && item.trim()) {
1679
- parts.push({ type: "text", text: item, time_created: timestampMs });
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.trim() ? [{ type: "text", text, time_created: timestampMs }] : [];
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
- return returnValue.trim() ? [{ type: "text", text: returnValue, time_created: timestampMs }] : [];
1999
+ const text2 = cleanInternalText(returnValue);
2000
+ return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
1692
2001
  }
1693
2002
  if (typeof returnValue === "object") {
1694
- return [
1695
- { type: "text", text: JSON.stringify(returnValue, null, 2), time_created: timestampMs }
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
- const text = String(returnValue);
1699
- return text.trim() ? [{ type: "text", text, time_created: timestampMs }] : [];
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(join5(roots.kimiRoot, "sessions"), "data/kimi");
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 = join5(roots.kimiRoot, "kimi.json");
1716
- const tomlPath = join5(roots.kimiRoot, "config.toml");
1717
- if (existsSync5(tomlPath)) {
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 (!existsSync5(configPath)) return;
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 = join5(this.basePath, hashEntry.name);
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 = join5(hashPath, sessionEntry.name);
1757
- if (existsSync5(join5(sessionPath, "metadata.json")) || existsSync5(join5(sessionPath, "state.json"))) {
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 = basename3(sessionDir);
1772
- const projectHash = basename3(dirname3(sessionDir));
1773
- const contextFile = join5(sessionDir, "context.jsonl");
1774
- const wireFile = join5(sessionDir, "wire.jsonl");
1775
- if (!existsSync5(contextFile) && !existsSync5(wireFile)) return null;
1776
- const statePath = join5(sessionDir, "state.json");
1777
- const metaPath = join5(sessionDir, "metadata.json");
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 (existsSync5(statePath)) {
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 (existsSync5(metaPath)) {
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 || "Untitled Session",
2147
+ title: resolveSessionTitle(title, messageTitle, null),
1797
2148
  sourcePath: sessionDir,
1798
2149
  cwd,
1799
- contextFile: existsSync5(contextFile) ? contextFile : null,
1800
- wireFile: existsSync5(wireFile) ? wireFile : null,
2150
+ contextFile: existingContextFile,
2151
+ wireFile: existingWireFile,
1801
2152
  createdAt,
1802
2153
  metaFile
1803
- };
2154
+ });
1804
2155
  } catch {
1805
- return null;
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:${basename3(dir)}`);
1818
- const meta = this.parseSessionDir(dir);
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.parseSessionDir(dir);
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.parseSessionDir(dir);
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 = String(record.content ?? "");
1943
- if (text.trim()) {
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 ?? join5(meta.sourcePath, "wire.jsonl");
1996
- if (!existsSync5(wirePath)) throw new Error("wire.jsonl is missing");
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 = String(userInput[0]?.text ?? "");
2037
- if (text.trim()) {
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.trim()) {
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.trim()) {
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.trim()) parts.push({ type: "reasoning", text, time_created: fallbackTs });
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.trim()) parts.push({ type: "text", text, time_created: fallbackTs });
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 parsed = JSON.parse(combined);
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 = parsed;
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 = join5(sessionDir, "wire.jsonl");
2279
- if (!existsSync5(wirePath)) return stats;
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 = join5(sessionDir, "context.jsonl");
2302
- const rawPath = existsSync5(contextPath) ? contextPath : wirePath;
2303
- if (!existsSync5(rawPath)) return stats;
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
- stats.message_count = messages.length;
2325
- const totalCost = messages.reduce((sum, message) => sum + (message.cost ?? 0), 0);
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 = basename4(filename, ".jsonl");
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(join6(roots.codexRoot, "sessions"));
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:${basename4(file)}`);
2527
- const head = this.parseSessionHead(file, options);
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 (!existsSync6(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
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: messages.length,
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 = join6(dir, entry);
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 = join6(roots.codexRoot, "session_index.jsonl");
2817
- if (!existsSync6(indexPath)) return;
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.parseFastSessionHead(filePath);
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 null;
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 null;
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 = data["payload"] ?? {};
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 null;
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 null;
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
- const texts = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
3027
- return normalizeTitleText3(texts);
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, "").trim();
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
- if (!text.trim()) {
3532
+ const visibleText = cleanInternalText(text);
3533
+ if (!visibleText) {
3167
3534
  return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
3168
3535
  }
3169
- if (isDeveloperLikeUserMessage(text)) {
3536
+ if (isDeveloperLikeUserMessage(visibleText)) {
3170
3537
  return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
3171
3538
  }
3172
- if (text.trimStart().startsWith(PLAN_APPROVAL_PREFIX)) {
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: text.trim(), time_created: timestampMs }]
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 = text.match(SUBAGENT_NOTIFICATION_PATTERN);
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: text.trim(), time_created: timestampMs }]
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.trim() ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
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.trim() ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
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
- return output.trim() ? [{ type: "text", text: output, time_created: timestampMs }] : [];
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
- if (text2.trim()) parts.push({ type: "text", text: text2, time_created: timestampMs });
3449
- } else if (typeof item === "string" && item.trim()) {
3450
- parts.push({ type: "text", text: item, time_created: timestampMs });
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.trim() ? [{ type: "text", text, time_created: timestampMs }] : [];
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 join7(dataPath, "globalStorage", "state.vscdb");
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 = join7(dataPath, "workspaceStorage");
3551
- if (!existsSync7(wsStoragePath)) return map;
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 = join7(wsStoragePath, name);
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 = join7(wsDir, "workspace.json");
3566
- if (!existsSync7(wsJsonPath)) continue;
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 = join7(wsDir, "state.vscdb");
3577
- if (!existsSync7(wsDbPath)) continue;
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 parsed = JSON.parse(row.value);
3956
+ const parsed2 = JSON.parse(row.value);
3584
3957
  let composers;
3585
- if (parsed !== null && typeof parsed === "object" && "allComposers" in parsed && Array.isArray(parsed["allComposers"])) {
3586
- composers = parsed.allComposers;
3587
- } else if (Array.isArray(parsed)) {
3588
- composers = parsed;
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 && existsSync7(this.dbPath);
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 title = this.extractTitle(composer);
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
- heads.push({
3638
- id: composerId,
3639
- slug: `cursor/${composerId}`,
3640
- title,
3641
- directory: directory2,
3642
- time_created: createdAt,
3643
- time_updated: updatedAt || void 0,
3644
- stats: {
3645
- message_count: fastMessageCount,
3646
- total_input_tokens: composer.inputTokenCount ?? 0,
3647
- total_output_tokens: composer.outputTokenCount ?? 0,
3648
- total_cost: totalCost2,
3649
- cost_source: totalCost2 > 0 ? "estimated" : void 0
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 messages = this.loadMessagesFromBubbles(
3662
- db,
3663
- composerId,
3664
- sessionId,
3665
- composer.modelConfig?.modelName ?? composer.model ?? null
4039
+ const parsedMessages = cleanParsedMessages(
4040
+ this.loadMessagesFromBubbles(
4041
+ db,
4042
+ composerId,
4043
+ sessionId,
4044
+ composer.modelConfig?.modelName ?? composer.model ?? null
4045
+ )
3666
4046
  );
3667
- if (messages.length === 0 && !hasSubagents) {
3668
- continue;
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 || !existsSync7(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 messages) {
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: messages.length,
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
- if (composer.name && typeof composer.name === "string" && composer.name.trim()) {
3881
- return composer.name.trim();
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?.trim();
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 parsed = JSON.parse(toolData.result);
3997
- state.output = parsed;
3998
- if (parsed.error || parsed.message || parsed.stderr) {
3999
- state.error = parsed.error || parsed.message || parsed.stderr;
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: ${toolName}`,
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.trim()) {
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 existsSync8(path2);
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
- var CACHE_VERSION = 6;
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 join8(homedir4(), ".cache", "codesesh");
4963
+ return join9(homedir4(), ".cache", "codesesh");
4410
4964
  }
4411
4965
  function getCachePath2() {
4412
- return join8(getCacheDir2(), CACHE_FILENAME);
4966
+ return join9(getCacheDir2(), CACHE_FILENAME);
4413
4967
  }
4414
4968
  function getLegacyCachePath() {
4415
- return join8(getCacheDir2(), LEGACY_CACHE_FILENAME);
4969
+ return join9(getCacheDir2(), LEGACY_CACHE_FILENAME);
4416
4970
  }
4417
4971
  function hasCacheStorage() {
4418
- return existsSync9(getCachePath2());
4972
+ return existsSync10(getCachePath2());
4419
4973
  }
4420
4974
  function withCacheDb(fn) {
4421
- const db = openDb(getCachePath2());
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 ensureSchema(db) {
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
- CREATE TABLE IF NOT EXISTS session_documents (
4453
- id INTEGER PRIMARY KEY AUTOINCREMENT,
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 DEFAULT 'path',
4460
- project_identity_key TEXT NOT NULL DEFAULT '',
4461
- project_display_name TEXT NOT NULL DEFAULT '',
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
- content_text TEXT NOT NULL,
4466
- content_hash TEXT NOT NULL,
4467
- indexed_at INTEGER NOT NULL,
4468
- UNIQUE(agent_name, session_id)
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 TABLE IF NOT EXISTS project_sessions (
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
- identity_kind TEXT NOT NULL,
4475
- identity_key TEXT NOT NULL,
4476
- display_name TEXT NOT NULL,
4477
- directory TEXT NOT NULL,
4478
- activity_time INTEGER NOT NULL,
4479
- PRIMARY KEY (agent_name, session_id)
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 idx_project_sessions_identity
4483
- ON project_sessions(identity_kind, identity_key);
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 VIEW IF NOT EXISTS project_groups_v AS
4486
- SELECT
4487
- identity_kind,
4488
- identity_key,
4489
- MIN(display_name) AS display_name,
4490
- GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
4491
- COUNT(*) AS session_count,
4492
- MAX(activity_time) AS last_activity
4493
- FROM project_sessions
4494
- GROUP BY identity_kind, identity_key;
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
- const sessionDocumentColumns = new Set(
4521
- db.prepare("PRAGMA table_info(session_documents)").all().map(
4522
- (row) => String(row.name)
4523
- )
4524
- );
4525
- if (!sessionDocumentColumns.has("project_identity_kind")) {
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
- ALTER TABLE session_documents ADD COLUMN project_identity_kind TEXT NOT NULL DEFAULT 'path';
4528
- ALTER TABLE session_documents ADD COLUMN project_identity_key TEXT NOT NULL DEFAULT '';
4529
- ALTER TABLE session_documents ADD COLUMN project_display_name TEXT NOT NULL DEFAULT '';
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
- const version = Number(versionRow?.value ?? 0);
4534
- if (version === CACHE_VERSION) {
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.exec(`
4538
- DELETE FROM agent_cache;
4539
- DELETE FROM cached_sessions;
4540
- DELETE FROM session_documents;
4541
- DELETE FROM project_sessions;
4542
- INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild');
4543
- INSERT INTO cache_meta(key, value)
4544
- VALUES ('version', '${CACHE_VERSION}')
4545
- ON CONFLICT(key) DO UPDATE SET value = excluded.value;
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.match(/"[^"]+"|\S+/g) ?? [];
4570
- return tokens.map((token) => {
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
- }).join(" ");
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 buildSessionContent(session) {
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
- appendPlainText(session.title, chunks);
4608
- for (const message of session.messages) {
4609
- chunks.push(message.role);
4610
- appendPlainText(message.agent, chunks);
4611
- appendPlainText(message.model, chunks);
4612
- for (const part of message.parts) {
4613
- appendPlainText(part.type, chunks);
4614
- appendPlainText(part.title, chunks);
4615
- appendPlainText(part.nickname, chunks);
4616
- appendPlainText(part.tool, chunks);
4617
- appendPlainText(part.text, chunks);
4618
- appendPlainText(part.input, chunks);
4619
- appendPlainText(part.output, chunks);
4620
- appendPlainText(part.state, chunks);
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 (!existsSync9(legacyPath)) {
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 session_json, meta_json
4648
- FROM cached_sessions
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 rowid
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
- if (!row.session_json) {
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 deleteSessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
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 insertSession = db.prepare(`
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
- deleteSessions.run(agentName);
6173
+ deleteLegacySessions.run(agentName);
4697
6174
  deleteProjectSessions.run(agentName);
4698
6175
  upsertAgent.run(agentName, timestamp);
4699
- for (const session of sessions) {
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
- insertSession.run(
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.id,
4704
- JSON.stringify(session),
4705
- meta[session.id] ? JSON.stringify(meta[session.id]) : null
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 (!existsSync9(filePath)) {
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 cached_sessions").get();
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
- if (!hasCacheStorage()) {
4763
- return;
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
- contentText: buildSessionContent(data),
4783
- contentHash: sessionContentHash(session)
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 write = db.transaction(() => {
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
- const identity = entry.session.project_identity ?? computeIdentity(entry.session.directory, realFs);
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
- write();
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 normalizedQuery = query.trim();
4853
- if (!normalizedQuery || !hasCacheStorage()) {
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
- d.agent_name,
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
- AND (? IS NULL OR d.agent_name = ?)
4876
- AND (? IS NULL OR d.project_identity_key = ? OR LOWER(d.directory) LIKE ?)
4877
- AND (? IS NULL OR d.activity_time >= ?)
4878
- AND (? IS NULL OR d.activity_time <= ?)
4879
- ORDER BY bm25(session_documents_fts, 8.0, 1.0), d.activity_time DESC
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.cwd ?? null,
4887
- options.cwd ? computeIdentity(options.cwd, realFs).key : null,
4888
- options.cwd ? `%${options.cwd.toLowerCase()}%` : null,
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
- return rows.map((row) => ({
4896
- agentName: String(row.agent_name),
4897
- session: {
4898
- id: String(row.session_id),
4899
- slug: String(row.slug),
4900
- title: String(row.title),
4901
- directory: String(row.directory),
4902
- time_created: Number(row.time_created),
4903
- time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
4904
- stats: {
4905
- message_count: 0,
4906
- total_input_tokens: 0,
4907
- total_output_tokens: 0,
4908
- total_cost: 0
4909
- }
4910
- },
4911
- snippet: String(row.snippet ?? "")
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
- return results ?? [];
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 filtered2 = filterSessions(tagged2.sessions, options);
5182
- return { agent, heads: filtered2, fromCache: true, refreshed: true };
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 filtered = filterSessions(tagged.sessions, options);
5192
- return { agent, heads: filtered, fromCache: true };
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 filtered = filterSessions(tagged.sessions, options);
5216
- return { agent, heads: filtered, fromCache: false };
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 BOOKMARK_DB_VERSION = 1;
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 join9(homedir5(), "Library", "Application Support", "codesesh");
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 join9(appData ?? join9(homedir5(), "AppData", "Roaming"), "codesesh");
7233
+ return join10(appData ?? join10(homedir5(), "AppData", "Roaming"), "codesesh");
5266
7234
  }
5267
- return join9(process.env.XDG_DATA_HOME ?? join9(homedir5(), ".local", "share"), "codesesh");
7235
+ return join10(process.env.XDG_DATA_HOME ?? join10(homedir5(), ".local", "share"), "codesesh");
5268
7236
  }
5269
7237
  function getStateDbPath() {
5270
- return join9(getStateDir(), BOOKMARK_DB_FILENAME);
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 ensureSchema2(db) {
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
- const version = Number(row?.value ?? 0);
5294
- if (version === BOOKMARK_DB_VERSION) return;
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(BOOKMARK_DB_VERSION));
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 db = openDb(getStateDbPath());
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-FZNZAMTZ.js.map
7598
+ //# sourceMappingURL=chunk-SQYHWMQV.js.map