codesesh 0.1.5 → 0.3.0

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  // ../core/dist/index.mjs
4
4
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
5
- import { join as join2, basename as basename2 } from "path";
5
+ import { join as join2, basename as basename2, dirname } from "path";
6
6
  import { existsSync } from "fs";
7
7
  import { homedir, platform } from "os";
8
8
  import { join } from "path";
@@ -10,16 +10,18 @@ import { readFileSync } from "fs";
10
10
  import { basename } from "path";
11
11
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
12
12
  import { join as join3 } from "path";
13
+ import { mkdirSync } from "fs";
14
+ import { dirname as dirname2 } from "path";
13
15
  import { createRequire } from "module";
14
16
  import { createHash } from "crypto";
15
17
  import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
16
- import { join as join4, basename as basename3, dirname } from "path";
18
+ import { join as join4, basename as basename3, dirname as dirname3 } from "path";
17
19
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
18
20
  import { join as join5, basename as basename4 } from "path";
19
21
  import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
20
22
  import { join as join6, normalize } from "path";
21
23
  import { resolve, sep } from "path";
22
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync, mkdirSync } from "fs";
24
+ import { existsSync as existsSync7, rmSync, unlinkSync } from "fs";
23
25
  import { join as join7 } from "path";
24
26
  import { homedir as homedir2 } from "os";
25
27
  var registrations = [];
@@ -205,6 +207,7 @@ var PerfTracer = class {
205
207
  }
206
208
  };
207
209
  var perf = new PerfTracer();
210
+ var RECENT_SESSION_REVALIDATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
208
211
  function parseTimestampMs(data) {
209
212
  const raw = String(data["timestamp"] ?? "").trim();
210
213
  if (!raw) return 0;
@@ -348,17 +351,30 @@ var ClaudeCodeAgent = class extends BaseAgent {
348
351
  if (!this.basePath) {
349
352
  return { hasChanges: false, timestamp: Date.now() };
350
353
  }
351
- const changedIds = [];
352
- for (const session of cachedSessions) {
354
+ const now = Date.now();
355
+ const changedIds = /* @__PURE__ */ new Set();
356
+ const recentSessions = cachedSessions.filter(
357
+ (session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS
358
+ );
359
+ for (const session of recentSessions) {
360
+ changedIds.add(session.id);
353
361
  const meta = this.sessionMetaMap.get(session.id);
354
362
  if (!meta) continue;
363
+ delete this.sessionsIndexCache[basename2(dirname(meta.sourcePath))];
364
+ }
365
+ for (const session of cachedSessions) {
366
+ const meta = this.sessionMetaMap.get(session.id);
367
+ if (!meta) {
368
+ changedIds.add(session.id);
369
+ continue;
370
+ }
355
371
  try {
356
372
  const stat = statSync(meta.sourcePath);
357
373
  if (stat.mtimeMs > sinceTimestamp) {
358
- changedIds.push(session.id);
374
+ changedIds.add(session.id);
359
375
  }
360
376
  } catch {
361
- changedIds.push(session.id);
377
+ changedIds.add(session.id);
362
378
  }
363
379
  }
364
380
  try {
@@ -368,14 +384,14 @@ var ClaudeCodeAgent = class extends BaseAgent {
368
384
  }
369
385
  const hasNewFiles = totalFiles > cachedSessions.length;
370
386
  return {
371
- hasChanges: changedIds.length > 0 || hasNewFiles,
372
- changedIds,
387
+ hasChanges: changedIds.size > 0 || hasNewFiles,
388
+ changedIds: Array.from(changedIds),
373
389
  timestamp: Date.now()
374
390
  };
375
391
  } catch {
376
392
  return {
377
- hasChanges: changedIds.length > 0,
378
- changedIds,
393
+ hasChanges: changedIds.size > 0,
394
+ changedIds: Array.from(changedIds),
379
395
  timestamp: Date.now()
380
396
  };
381
397
  }
@@ -968,6 +984,19 @@ function openDbReadOnly(dbPath) {
968
984
  return null;
969
985
  }
970
986
  }
987
+ function openDb(dbPath) {
988
+ if (!DatabaseConstructor) return null;
989
+ try {
990
+ mkdirSync(dirname2(dbPath), { recursive: true });
991
+ const db = DatabaseConstructor(dbPath);
992
+ db.pragma("journal_mode = WAL");
993
+ db.pragma("synchronous = NORMAL");
994
+ db.pragma("foreign_keys = ON");
995
+ return db;
996
+ } catch {
997
+ return null;
998
+ }
999
+ }
971
1000
  function isSqliteAvailable() {
972
1001
  return DatabaseConstructor !== null;
973
1002
  }
@@ -1024,6 +1053,7 @@ var OpenCodeAgent = class extends BaseAgent {
1024
1053
  const timeUpdated = Number(row.time_updated ?? timeCreated);
1025
1054
  const slug = `opencode/${id}`;
1026
1055
  const directory = String(row.directory ?? "");
1056
+ const stats = hasMessageTable ? this.readSessionStats(db, id) : null;
1027
1057
  heads.push({
1028
1058
  id,
1029
1059
  slug,
@@ -1032,10 +1062,10 @@ var OpenCodeAgent = class extends BaseAgent {
1032
1062
  time_created: timeCreated,
1033
1063
  time_updated: timeUpdated,
1034
1064
  stats: {
1035
- message_count: Number(row.message_count ?? 0),
1036
- total_input_tokens: 0,
1037
- total_output_tokens: 0,
1038
- total_cost: 0
1065
+ message_count: stats?.message_count ?? Number(row.message_count ?? 0),
1066
+ total_input_tokens: stats?.total_input_tokens ?? 0,
1067
+ total_output_tokens: stats?.total_output_tokens ?? 0,
1068
+ total_cost: stats?.total_cost ?? 0
1039
1069
  }
1040
1070
  });
1041
1071
  if (this.dbPath) {
@@ -1088,6 +1118,30 @@ var OpenCodeAgent = class extends BaseAgent {
1088
1118
  incrementalScan(_cachedSessions, _changedIds) {
1089
1119
  return this.scan();
1090
1120
  }
1121
+ readSessionStats(db, sessionId) {
1122
+ try {
1123
+ const rows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
1124
+ let totalCost = 0;
1125
+ let totalInputTokens = 0;
1126
+ let totalOutputTokens = 0;
1127
+ for (const row of rows) {
1128
+ const msgData = JSON.parse(String(row.data ?? "{}"));
1129
+ const cost = Number(msgData.cost ?? 0);
1130
+ const tokens = msgData.tokens;
1131
+ totalCost += cost;
1132
+ totalInputTokens += Number(tokens?.input ?? 0);
1133
+ totalOutputTokens += Number(tokens?.output ?? 0);
1134
+ }
1135
+ return {
1136
+ message_count: rows.length,
1137
+ total_input_tokens: totalInputTokens,
1138
+ total_output_tokens: totalOutputTokens,
1139
+ total_cost: totalCost
1140
+ };
1141
+ } catch {
1142
+ return null;
1143
+ }
1144
+ }
1091
1145
  getSessionData(sessionId) {
1092
1146
  if (!this.dbPath) {
1093
1147
  this.dbPath = this.findDbPath();
@@ -1301,7 +1355,7 @@ var KimiAgent = class extends BaseAgent {
1301
1355
  parseSessionDir(sessionDir) {
1302
1356
  try {
1303
1357
  const sessionId = basename3(sessionDir);
1304
- const projectHash = basename3(dirname(sessionDir));
1358
+ const projectHash = basename3(dirname3(sessionDir));
1305
1359
  const contextFile = join4(sessionDir, "context.jsonl");
1306
1360
  const wireFile = join4(sessionDir, "wire.jsonl");
1307
1361
  if (!existsSync4(contextFile) && !existsSync4(wireFile)) return null;
@@ -1351,6 +1405,7 @@ var KimiAgent = class extends BaseAgent {
1351
1405
  perf.end(parseMarker);
1352
1406
  if (!meta) continue;
1353
1407
  this.sessionMetaMap.set(meta.id, meta);
1408
+ const stats = this.extractStats(meta.sourcePath);
1354
1409
  heads.push({
1355
1410
  id: meta.id,
1356
1411
  slug: `kimi/${meta.id}`,
@@ -1358,12 +1413,7 @@ var KimiAgent = class extends BaseAgent {
1358
1413
  directory: meta.cwd,
1359
1414
  time_created: meta.createdAt,
1360
1415
  time_updated: meta.createdAt,
1361
- stats: {
1362
- message_count: 0,
1363
- total_input_tokens: 0,
1364
- total_output_tokens: 0,
1365
- total_cost: 0
1366
- }
1416
+ stats
1367
1417
  });
1368
1418
  } catch {
1369
1419
  }
@@ -1421,6 +1471,7 @@ var KimiAgent = class extends BaseAgent {
1421
1471
  if (!meta) continue;
1422
1472
  if (changedIds.includes(meta.id)) {
1423
1473
  this.sessionMetaMap.set(meta.id, meta);
1474
+ const stats = this.extractStats(meta.sourcePath);
1424
1475
  sessionMap.set(meta.id, {
1425
1476
  id: meta.id,
1426
1477
  slug: `kimi/${meta.id}`,
@@ -1428,12 +1479,7 @@ var KimiAgent = class extends BaseAgent {
1428
1479
  directory: meta.cwd,
1429
1480
  time_created: meta.createdAt,
1430
1481
  time_updated: meta.createdAt,
1431
- stats: {
1432
- message_count: 0,
1433
- total_input_tokens: 0,
1434
- total_output_tokens: 0,
1435
- total_cost: 0
1436
- }
1482
+ stats
1437
1483
  });
1438
1484
  }
1439
1485
  } catch {
@@ -1861,6 +1907,7 @@ var CODEX_TOOL_TITLE_MAP = {
1861
1907
  spawn_agent: "subagent",
1862
1908
  subagent: "subagent"
1863
1909
  };
1910
+ var RECENT_SESSION_REVALIDATION_WINDOW_MS2 = 24 * 60 * 60 * 1e3;
1864
1911
  function extractSessionId(filename) {
1865
1912
  const stem = basename4(filename, ".jsonl");
1866
1913
  const parts = stem.split("-");
@@ -1878,6 +1925,9 @@ function parseTimestampMs2(data) {
1878
1925
  return 0;
1879
1926
  }
1880
1927
  }
1928
+ function extractModelName(raw) {
1929
+ return typeof raw === "string" && raw.trim() ? raw.trim() : null;
1930
+ }
1881
1931
  function normalizeTitleText3(text) {
1882
1932
  const line = text.split("\n").find((l) => l.trim());
1883
1933
  return line?.trim().slice(0, 80) || "";
@@ -2051,34 +2101,43 @@ var CodexAgent = class extends BaseAgent {
2051
2101
  if (!this.basePath) {
2052
2102
  return { hasChanges: false, timestamp: Date.now() };
2053
2103
  }
2054
- const changedIds = [];
2104
+ const now = Date.now();
2105
+ const changedIds = /* @__PURE__ */ new Set();
2106
+ const currentFiles = this.listRolloutFiles();
2107
+ const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
2108
+ const cachedIds = new Set(cachedSessions.map((session) => session.id));
2109
+ const recentIds = cachedSessions.filter((session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS2).map((session) => session.id);
2110
+ for (const sessionId of recentIds) {
2111
+ changedIds.add(sessionId);
2112
+ }
2055
2113
  for (const session of cachedSessions) {
2056
2114
  const meta = this.sessionMetaMap.get(session.id);
2057
- if (!meta) continue;
2115
+ if (!currentIds.has(session.id)) {
2116
+ changedIds.add(session.id);
2117
+ continue;
2118
+ }
2119
+ if (!meta) {
2120
+ changedIds.add(session.id);
2121
+ continue;
2122
+ }
2058
2123
  try {
2059
2124
  const stat = statSync4(meta.sourcePath);
2060
2125
  if (stat.mtimeMs > sinceTimestamp) {
2061
- changedIds.push(session.id);
2126
+ changedIds.add(session.id);
2062
2127
  }
2063
2128
  } catch {
2064
- changedIds.push(session.id);
2129
+ changedIds.add(session.id);
2065
2130
  }
2066
2131
  }
2067
- try {
2068
- const allFiles = this.listRolloutFiles();
2069
- const hasNewFiles = allFiles.length > cachedSessions.length;
2070
- return {
2071
- hasChanges: changedIds.length > 0 || hasNewFiles,
2072
- changedIds,
2073
- timestamp: Date.now()
2074
- };
2075
- } catch {
2076
- return {
2077
- hasChanges: changedIds.length > 0,
2078
- changedIds,
2079
- timestamp: Date.now()
2080
- };
2132
+ const hasAddedSessions = currentFiles.some((file) => !cachedIds.has(extractSessionId(file)));
2133
+ if (recentIds.length > 0) {
2134
+ this.sessionIndexCache.clear();
2081
2135
  }
2136
+ return {
2137
+ hasChanges: changedIds.size > 0 || hasAddedSessions,
2138
+ changedIds: Array.from(changedIds),
2139
+ timestamp: Date.now()
2140
+ };
2082
2141
  }
2083
2142
  /**
2084
2143
  * 增量扫描
@@ -2086,10 +2145,19 @@ var CodexAgent = class extends BaseAgent {
2086
2145
  incrementalScan(cachedSessions, changedIds) {
2087
2146
  if (!this.basePath) return cachedSessions;
2088
2147
  const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
2089
- for (const file of this.listRolloutFiles()) {
2148
+ const changedSet = new Set(changedIds);
2149
+ const currentFiles = this.listRolloutFiles();
2150
+ const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
2151
+ for (const session of cachedSessions) {
2152
+ if (!currentIds.has(session.id)) {
2153
+ sessionMap.delete(session.id);
2154
+ this.sessionMetaMap.delete(session.id);
2155
+ }
2156
+ }
2157
+ for (const file of currentFiles) {
2090
2158
  try {
2091
2159
  const sessionId = extractSessionId(file);
2092
- if (changedIds.includes(sessionId)) {
2160
+ if (changedSet.has(sessionId)) {
2093
2161
  const head = this.parseSessionHead(file);
2094
2162
  if (head) {
2095
2163
  sessionMap.set(head.id, head);
@@ -2108,7 +2176,7 @@ var CodexAgent = class extends BaseAgent {
2108
2176
  } catch {
2109
2177
  }
2110
2178
  }
2111
- for (const file of this.listRolloutFiles()) {
2179
+ for (const file of currentFiles) {
2112
2180
  try {
2113
2181
  const sessionId = extractSessionId(file);
2114
2182
  if (!sessionMap.has(sessionId)) {
@@ -2144,13 +2212,18 @@ var CodexAgent = class extends BaseAgent {
2144
2212
  let currentAssistantIndex = null;
2145
2213
  let latestAssistantTextIndex = null;
2146
2214
  let pendingPlan = null;
2215
+ let activeModel = meta.model;
2147
2216
  let prevCumulativeTotal = 0;
2148
2217
  let prevInput = 0;
2149
- let prevCached = 0;
2150
2218
  let prevOutput = 0;
2151
2219
  let prevReasoning = 0;
2152
2220
  for (const record of parseJsonlLines(content)) {
2153
2221
  try {
2222
+ const recordType = String(record["type"] ?? "");
2223
+ if (recordType === "turn_context") {
2224
+ const payload = record["payload"] ?? {};
2225
+ activeModel = extractModelName(payload["model"]) ?? activeModel;
2226
+ }
2154
2227
  const result = this.convertRecord(
2155
2228
  record,
2156
2229
  messages,
@@ -2163,7 +2236,12 @@ var CodexAgent = class extends BaseAgent {
2163
2236
  currentAssistantIndex = result.currentAssistantIndex;
2164
2237
  latestAssistantTextIndex = result.latestAssistantTextIndex;
2165
2238
  pendingPlan = result.pendingPlan;
2166
- const recordType = String(record["type"] ?? "");
2239
+ if (currentAssistantIndex !== null && activeModel) {
2240
+ const message = messages[currentAssistantIndex];
2241
+ if (message?.role === "assistant" && !message.model) {
2242
+ message.model = activeModel;
2243
+ }
2244
+ }
2167
2245
  if (recordType === "event_msg") {
2168
2246
  const payload = record["payload"] ?? {};
2169
2247
  if (String(payload["type"] ?? "") === "token_count") {
@@ -2175,33 +2253,29 @@ var CodexAgent = class extends BaseAgent {
2175
2253
  prevCumulativeTotal = cumulativeTotal;
2176
2254
  const lastUsage = info?.["last_token_usage"];
2177
2255
  let inputTokens = 0;
2178
- let cachedInputTokens = 0;
2179
2256
  let outputTokens = 0;
2180
2257
  let reasoningTokens = 0;
2181
2258
  if (lastUsage) {
2182
2259
  inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2183
- cachedInputTokens = Number(lastUsage["cached_input_tokens"] ?? 0);
2184
2260
  outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2185
2261
  reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2186
2262
  } else if (cumulativeTotal > 0 && totalUsage) {
2187
2263
  inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
2188
- cachedInputTokens = Number(totalUsage["cached_input_tokens"] ?? 0) - prevCached;
2189
2264
  outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
2190
2265
  reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
2191
2266
  prevInput = Number(totalUsage["input_tokens"] ?? 0);
2192
- prevCached = Number(totalUsage["cached_input_tokens"] ?? 0);
2193
2267
  prevOutput = Number(totalUsage["output_tokens"] ?? 0);
2194
2268
  prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2195
2269
  }
2196
- const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
2197
- if (uncachedInput || outputTokens || reasoningTokens) {
2198
- totalInputTokens += uncachedInput;
2270
+ const totalInput = Math.max(0, inputTokens);
2271
+ if (totalInput || outputTokens || reasoningTokens) {
2272
+ totalInputTokens += totalInput;
2199
2273
  totalOutputTokens += outputTokens + reasoningTokens;
2200
2274
  for (let i = messages.length - 1; i >= 0; i--) {
2201
2275
  const msg = messages[i];
2202
2276
  if (msg.role === "assistant" && !msg.tokens) {
2203
2277
  msg.tokens = {
2204
- input: uncachedInput,
2278
+ input: totalInput,
2205
2279
  output: outputTokens + reasoningTokens
2206
2280
  };
2207
2281
  break;
@@ -2277,6 +2351,7 @@ var CodexAgent = class extends BaseAgent {
2277
2351
  }
2278
2352
  }
2279
2353
  getTitleForSession(sessionId) {
2354
+ this.loadSessionIndex();
2280
2355
  return this.sessionIndexCache.get(sessionId) ?? null;
2281
2356
  }
2282
2357
  // ---- Session head parsing ----
@@ -2292,7 +2367,7 @@ var CodexAgent = class extends BaseAgent {
2292
2367
  return null;
2293
2368
  }
2294
2369
  const payload = firstRecord["payload"] ?? {};
2295
- const createdAt = parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
2370
+ const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
2296
2371
  const indexTitle = this.getTitleForSession(sessionId);
2297
2372
  const messageTitle = this.extractTitleFromLines(lines);
2298
2373
  const directoryTitle = basenameTitle(payload["cwd"] ? String(payload["cwd"]) : null);
@@ -2304,7 +2379,6 @@ var CodexAgent = class extends BaseAgent {
2304
2379
  let totalOutputTokens = 0;
2305
2380
  let scanPrevCumulativeTotal = 0;
2306
2381
  let scanPrevInput = 0;
2307
- let scanPrevCached = 0;
2308
2382
  let scanPrevOutput = 0;
2309
2383
  let scanPrevReasoning = 0;
2310
2384
  const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
@@ -2312,10 +2386,13 @@ var CodexAgent = class extends BaseAgent {
2312
2386
  try {
2313
2387
  const data = JSON.parse(line);
2314
2388
  const recordType = String(data["type"] ?? "");
2315
- if (recordType === "session_meta") {
2316
- const p = data["payload"] ?? {};
2317
- const ts = parseTimestampMs2(p);
2318
- if (ts > updatedAt) updatedAt = ts;
2389
+ const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
2390
+ if (recordTs > updatedAt) updatedAt = recordTs;
2391
+ if (recordType === "session_meta" || recordType === "turn_context") {
2392
+ const payload2 = data["payload"] ?? {};
2393
+ if (!model) {
2394
+ model = extractModelName(payload2["model"]);
2395
+ }
2319
2396
  continue;
2320
2397
  }
2321
2398
  if (recordType === "response_item") {
@@ -2340,26 +2417,22 @@ var CodexAgent = class extends BaseAgent {
2340
2417
  scanPrevCumulativeTotal = cumulativeTotal;
2341
2418
  const lastUsage = info?.["last_token_usage"];
2342
2419
  let inputTokens = 0;
2343
- let cachedInputTokens = 0;
2344
2420
  let outputTokens = 0;
2345
2421
  let reasoningTokens = 0;
2346
2422
  if (lastUsage) {
2347
2423
  inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2348
- cachedInputTokens = Number(lastUsage["cached_input_tokens"] ?? 0);
2349
2424
  outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2350
2425
  reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2351
2426
  } else if (cumulativeTotal > 0 && totalUsage) {
2352
2427
  inputTokens = Number(totalUsage["input_tokens"] ?? 0) - scanPrevInput;
2353
- cachedInputTokens = Number(totalUsage["cached_input_tokens"] ?? 0) - scanPrevCached;
2354
2428
  outputTokens = Number(totalUsage["output_tokens"] ?? 0) - scanPrevOutput;
2355
2429
  reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - scanPrevReasoning;
2356
2430
  scanPrevInput = Number(totalUsage["input_tokens"] ?? 0);
2357
- scanPrevCached = Number(totalUsage["cached_input_tokens"] ?? 0);
2358
2431
  scanPrevOutput = Number(totalUsage["output_tokens"] ?? 0);
2359
2432
  scanPrevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2360
2433
  }
2361
- const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
2362
- totalInputTokens += uncachedInput;
2434
+ const totalInput = Math.max(0, inputTokens);
2435
+ totalInputTokens += totalInput;
2363
2436
  totalOutputTokens += outputTokens + reasoningTokens;
2364
2437
  }
2365
2438
  }
@@ -3002,7 +3075,12 @@ var CursorAgent = class extends BaseAgent {
3002
3075
  const title = this.extractTitle(composer);
3003
3076
  const createdAt = composer.createdAt ?? 0;
3004
3077
  const updatedAt = composer.updatedAt ?? createdAt;
3005
- const messages = this.loadMessagesFromBubbles(db, composerId, sessionId);
3078
+ const messages = this.loadMessagesFromBubbles(
3079
+ db,
3080
+ composerId,
3081
+ sessionId,
3082
+ composer.modelConfig?.modelName ?? composer.model ?? null
3083
+ );
3006
3084
  const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
3007
3085
  if (messages.length === 0 && !hasSubagents) {
3008
3086
  continue;
@@ -3114,7 +3192,12 @@ var CursorAgent = class extends BaseAgent {
3114
3192
  const title = this.extractTitle(composer);
3115
3193
  const createdAt = composer.createdAt ?? 0;
3116
3194
  const updatedAt = composer.updatedAt ?? createdAt;
3117
- const messages = this.loadMessagesFromBubbles(db, composerId, resolvedSessionId);
3195
+ const messages = this.loadMessagesFromBubbles(
3196
+ db,
3197
+ composerId,
3198
+ resolvedSessionId,
3199
+ composer.modelConfig?.modelName ?? composer.model ?? null
3200
+ );
3118
3201
  let totalInputTokens = 0;
3119
3202
  let totalOutputTokens = 0;
3120
3203
  for (const msg of messages) {
@@ -3222,11 +3305,11 @@ var CursorAgent = class extends BaseAgent {
3222
3305
  }
3223
3306
  }
3224
3307
  /** Load messages from bubbles (like agent-dump) */
3225
- loadMessagesFromBubbles(db, composerId, _sessionId) {
3308
+ loadMessagesFromBubbles(db, composerId, _sessionId, initialModelName) {
3226
3309
  const messages = [];
3227
3310
  try {
3228
3311
  const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ORDER BY rowid ASC").all(`bubbleId:${composerId}:%`);
3229
- let activeModelName = null;
3312
+ let activeModelName = initialModelName;
3230
3313
  let messageIndex = 0;
3231
3314
  for (const row of rows) {
3232
3315
  try {
@@ -3241,7 +3324,7 @@ var CursorAgent = class extends BaseAgent {
3241
3324
  } else if (bubble.timestamp) {
3242
3325
  timestampMs = bubble.timestamp;
3243
3326
  }
3244
- if (role === "user" && bubble.modelInfo?.modelName) {
3327
+ if (bubble.modelInfo?.modelName) {
3245
3328
  activeModelName = bubble.modelInfo.modelName;
3246
3329
  }
3247
3330
  const inputTokens = bubble.tokenCount?.inputTokens ?? 0;
@@ -3265,7 +3348,7 @@ var CursorAgent = class extends BaseAgent {
3265
3348
  time_created: timestampMs,
3266
3349
  time_completed: null,
3267
3350
  mode: role === "assistant" && parts.some((p) => p.type === "tool") ? "tool" : null,
3268
- model: activeModelName,
3351
+ model: bubble.modelInfo?.modelName ?? activeModelName,
3269
3352
  provider: null,
3270
3353
  tokens: { input: inputTokens, output: outputTokens },
3271
3354
  cost: 0,
@@ -3424,89 +3507,438 @@ registerAgent({
3424
3507
  icon: "/icon/agent/cursor.svg",
3425
3508
  create: () => new CursorAgent()
3426
3509
  });
3427
- var CACHE_VERSION = 2;
3428
- var CACHE_FILENAME = "scan-cache.json";
3510
+ var CACHE_VERSION = 3;
3511
+ var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3512
+ var CACHE_FILENAME = "codesesh.db";
3513
+ var LEGACY_CACHE_FILENAME = "scan-cache.json";
3514
+ function getCacheDir() {
3515
+ return join7(homedir2(), ".cache", "codesesh");
3516
+ }
3429
3517
  function getCachePath() {
3430
- return join7(homedir2(), ".cache", "codesesh", CACHE_FILENAME);
3518
+ return join7(getCacheDir(), CACHE_FILENAME);
3431
3519
  }
3432
- function ensureCacheDir() {
3433
- const cacheDir = join7(homedir2(), ".cache", "codesesh");
3434
- if (!existsSync7(cacheDir)) {
3435
- mkdirSync(cacheDir, { recursive: true });
3436
- }
3520
+ function getLegacyCachePath() {
3521
+ return join7(getCacheDir(), LEGACY_CACHE_FILENAME);
3437
3522
  }
3438
- function loadCachedSessions(agentName) {
3523
+ function hasCacheStorage() {
3524
+ return existsSync7(getCachePath());
3525
+ }
3526
+ function withCacheDb(fn) {
3527
+ const db = openDb(getCachePath());
3528
+ if (!db) return null;
3439
3529
  try {
3440
- const cachePath = getCachePath();
3441
- if (!existsSync7(cachePath)) return null;
3442
- const data = JSON.parse(readFileSync6(cachePath, "utf-8"));
3443
- if (data.version !== CACHE_VERSION) return null;
3444
- const entry = data.entries[agentName];
3445
- if (!entry) return null;
3446
- const CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3447
- if (Date.now() - entry.timestamp > CACHE_TTL) return null;
3448
- return { sessions: entry.sessions, meta: entry.meta || {}, timestamp: entry.timestamp };
3530
+ ensureSchema(db);
3531
+ return fn(db);
3449
3532
  } catch {
3450
3533
  return null;
3534
+ } finally {
3535
+ db.close();
3451
3536
  }
3452
3537
  }
3453
- function saveCachedSessions(agentName, sessions, meta = {}) {
3454
- try {
3455
- ensureCacheDir();
3456
- const cachePath = getCachePath();
3457
- let data;
3458
- if (existsSync7(cachePath)) {
3459
- try {
3460
- data = JSON.parse(readFileSync6(cachePath, "utf-8"));
3461
- if (data.version !== CACHE_VERSION) {
3462
- data = { version: CACHE_VERSION, entries: {}, lastScanTime: 0 };
3463
- }
3464
- } catch {
3465
- data = { version: CACHE_VERSION, entries: {}, lastScanTime: 0 };
3466
- }
3467
- } else {
3468
- data = { version: CACHE_VERSION, entries: {}, lastScanTime: 0 };
3538
+ function ensureSchema(db) {
3539
+ db.exec(`
3540
+ CREATE TABLE IF NOT EXISTS cache_meta (
3541
+ key TEXT PRIMARY KEY,
3542
+ value TEXT NOT NULL
3543
+ );
3544
+
3545
+ CREATE TABLE IF NOT EXISTS agent_cache (
3546
+ agent_name TEXT PRIMARY KEY,
3547
+ timestamp INTEGER NOT NULL
3548
+ );
3549
+
3550
+ CREATE TABLE IF NOT EXISTS cached_sessions (
3551
+ agent_name TEXT NOT NULL,
3552
+ session_id TEXT NOT NULL,
3553
+ session_json TEXT NOT NULL,
3554
+ meta_json TEXT,
3555
+ PRIMARY KEY (agent_name, session_id)
3556
+ );
3557
+
3558
+ CREATE TABLE IF NOT EXISTS session_documents (
3559
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3560
+ agent_name TEXT NOT NULL,
3561
+ session_id TEXT NOT NULL,
3562
+ slug TEXT NOT NULL,
3563
+ title TEXT NOT NULL,
3564
+ directory TEXT NOT NULL,
3565
+ time_created INTEGER NOT NULL,
3566
+ time_updated INTEGER,
3567
+ activity_time INTEGER NOT NULL,
3568
+ content_text TEXT NOT NULL,
3569
+ content_hash TEXT NOT NULL,
3570
+ indexed_at INTEGER NOT NULL,
3571
+ UNIQUE(agent_name, session_id)
3572
+ );
3573
+
3574
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
3575
+ title,
3576
+ content_text,
3577
+ content='session_documents',
3578
+ content_rowid='id'
3579
+ );
3580
+
3581
+ CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
3582
+ INSERT INTO session_documents_fts(rowid, title, content_text)
3583
+ VALUES (new.id, new.title, new.content_text);
3584
+ END;
3585
+
3586
+ CREATE TRIGGER IF NOT EXISTS session_documents_ad AFTER DELETE ON session_documents BEGIN
3587
+ INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3588
+ VALUES ('delete', old.id, old.title, old.content_text);
3589
+ END;
3590
+
3591
+ CREATE TRIGGER IF NOT EXISTS session_documents_au AFTER UPDATE ON session_documents BEGIN
3592
+ INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3593
+ VALUES ('delete', old.id, old.title, old.content_text);
3594
+ INSERT INTO session_documents_fts(rowid, title, content_text)
3595
+ VALUES (new.id, new.title, new.content_text);
3596
+ END;
3597
+ `);
3598
+ const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
3599
+ const version = Number(versionRow?.value ?? 0);
3600
+ if (version === CACHE_VERSION) {
3601
+ return;
3602
+ }
3603
+ db.exec(`
3604
+ DELETE FROM agent_cache;
3605
+ DELETE FROM cached_sessions;
3606
+ DELETE FROM session_documents;
3607
+ INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild');
3608
+ INSERT INTO cache_meta(key, value)
3609
+ VALUES ('version', '${CACHE_VERSION}')
3610
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value;
3611
+ `);
3612
+ }
3613
+ function sessionContentHash(session) {
3614
+ return JSON.stringify([
3615
+ session.slug,
3616
+ session.title,
3617
+ session.directory,
3618
+ session.time_created,
3619
+ session.time_updated ?? session.time_created,
3620
+ session.stats.message_count,
3621
+ session.stats.total_input_tokens,
3622
+ session.stats.total_output_tokens,
3623
+ session.stats.total_cost,
3624
+ session.stats.total_tokens ?? 0
3625
+ ]);
3626
+ }
3627
+ function escapeFtsTerm(value) {
3628
+ return value.replaceAll('"', '""');
3629
+ }
3630
+ function toFtsQuery(input) {
3631
+ const tokens = input.match(/"[^"]+"|\S+/g) ?? [];
3632
+ return tokens.map((token) => {
3633
+ if (/^OR$/i.test(token)) {
3634
+ return "OR";
3469
3635
  }
3470
- data.entries[agentName] = {
3471
- sessions,
3472
- meta,
3473
- timestamp: Date.now(),
3474
- version: CACHE_VERSION
3475
- };
3476
- data.lastScanTime = Date.now();
3477
- writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
3636
+ if (token.startsWith('"') && token.endsWith('"')) {
3637
+ return `"${escapeFtsTerm(token.slice(1, -1))}"`;
3638
+ }
3639
+ return `"${escapeFtsTerm(token)}"`;
3640
+ }).join(" ");
3641
+ }
3642
+ function appendPlainText(value, chunks) {
3643
+ if (value == null) return;
3644
+ if (typeof value === "string") {
3645
+ const normalized = value.trim();
3646
+ if (normalized) {
3647
+ chunks.push(normalized);
3648
+ }
3649
+ return;
3650
+ }
3651
+ if (typeof value === "number" || typeof value === "boolean") {
3652
+ chunks.push(String(value));
3653
+ return;
3654
+ }
3655
+ if (Array.isArray(value)) {
3656
+ for (const item of value) {
3657
+ appendPlainText(item, chunks);
3658
+ }
3659
+ return;
3660
+ }
3661
+ if (typeof value === "object") {
3662
+ for (const nested of Object.values(value)) {
3663
+ appendPlainText(nested, chunks);
3664
+ }
3665
+ }
3666
+ }
3667
+ function buildSessionContent(session) {
3668
+ const chunks = [];
3669
+ appendPlainText(session.title, chunks);
3670
+ for (const message of session.messages) {
3671
+ chunks.push(message.role);
3672
+ appendPlainText(message.agent, chunks);
3673
+ appendPlainText(message.model, chunks);
3674
+ for (const part of message.parts) {
3675
+ appendPlainText(part.type, chunks);
3676
+ appendPlainText(part.title, chunks);
3677
+ appendPlainText(part.nickname, chunks);
3678
+ appendPlainText(part.tool, chunks);
3679
+ appendPlainText(part.text, chunks);
3680
+ appendPlainText(part.input, chunks);
3681
+ appendPlainText(part.output, chunks);
3682
+ appendPlainText(part.state, chunks);
3683
+ }
3684
+ }
3685
+ return chunks.join("\n");
3686
+ }
3687
+ function deleteLegacyCacheFile() {
3688
+ const legacyPath = getLegacyCachePath();
3689
+ if (!existsSync7(legacyPath)) {
3690
+ return;
3691
+ }
3692
+ try {
3693
+ unlinkSync(legacyPath);
3478
3694
  } catch {
3479
3695
  }
3480
3696
  }
3697
+ function loadCachedSessions(agentName) {
3698
+ if (!hasCacheStorage()) {
3699
+ return null;
3700
+ }
3701
+ return withCacheDb((db) => {
3702
+ const timestampRow = db.prepare("SELECT timestamp AS value FROM agent_cache WHERE agent_name = ?").get(agentName);
3703
+ const timestamp = Number(timestampRow?.value ?? 0);
3704
+ if (!timestamp || Date.now() - timestamp > CACHE_TTL) {
3705
+ return null;
3706
+ }
3707
+ const rows = db.prepare(
3708
+ `
3709
+ SELECT session_json, meta_json
3710
+ FROM cached_sessions
3711
+ WHERE agent_name = ?
3712
+ ORDER BY rowid
3713
+ `
3714
+ ).all(agentName);
3715
+ const sessions = [];
3716
+ const meta = {};
3717
+ for (const row of rows) {
3718
+ if (!row.session_json) {
3719
+ continue;
3720
+ }
3721
+ const session = JSON.parse(row.session_json);
3722
+ sessions.push(session);
3723
+ if (row.meta_json) {
3724
+ meta[session.id] = JSON.parse(row.meta_json);
3725
+ }
3726
+ }
3727
+ return { sessions, meta, timestamp };
3728
+ });
3729
+ }
3730
+ function saveCachedSessions(agentName, sessions, meta = {}) {
3731
+ withCacheDb((db) => {
3732
+ const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
3733
+ const deleteSessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
3734
+ const upsertAgent = db.prepare(`
3735
+ INSERT INTO agent_cache(agent_name, timestamp)
3736
+ VALUES (?, ?)
3737
+ ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
3738
+ `);
3739
+ const insertSession = db.prepare(`
3740
+ INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
3741
+ VALUES (?, ?, ?, ?)
3742
+ `);
3743
+ const write = db.transaction(() => {
3744
+ const timestamp = Date.now();
3745
+ deleteAgent.run(agentName);
3746
+ deleteSessions.run(agentName);
3747
+ upsertAgent.run(agentName, timestamp);
3748
+ for (const session of sessions) {
3749
+ insertSession.run(
3750
+ agentName,
3751
+ session.id,
3752
+ JSON.stringify(session),
3753
+ meta[session.id] ? JSON.stringify(meta[session.id]) : null
3754
+ );
3755
+ }
3756
+ });
3757
+ write();
3758
+ deleteLegacyCacheFile();
3759
+ });
3760
+ }
3481
3761
  function clearCache() {
3482
- try {
3483
- const cachePath = getCachePath();
3484
- if (existsSync7(cachePath)) {
3485
- const data = {
3486
- version: CACHE_VERSION,
3487
- entries: {},
3488
- lastScanTime: 0
3489
- };
3490
- writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
3762
+ if (!hasCacheStorage()) {
3763
+ deleteLegacyCacheFile();
3764
+ return;
3765
+ }
3766
+ withCacheDb((db) => {
3767
+ db.exec(`
3768
+ DELETE FROM agent_cache;
3769
+ DELETE FROM cached_sessions;
3770
+ `);
3771
+ });
3772
+ deleteLegacyCacheFile();
3773
+ const cachePath = getCachePath();
3774
+ const walPath = `${cachePath}-wal`;
3775
+ const shmPath = `${cachePath}-shm`;
3776
+ for (const filePath of [walPath, shmPath]) {
3777
+ if (!existsSync7(filePath)) {
3778
+ continue;
3779
+ }
3780
+ try {
3781
+ rmSync(filePath, { force: true });
3782
+ } catch {
3491
3783
  }
3492
- } catch {
3493
3784
  }
3494
3785
  }
3495
3786
  function getCacheInfo() {
3496
- try {
3497
- const cachePath = getCachePath();
3498
- if (!existsSync7(cachePath)) {
3499
- return { lastScanTime: null, size: 0 };
3500
- }
3501
- const data = JSON.parse(readFileSync6(cachePath, "utf-8"));
3502
- const size = Object.values(data.entries).reduce((sum, entry) => sum + entry.sessions.length, 0);
3503
- return {
3504
- lastScanTime: data.lastScanTime || null,
3505
- size
3506
- };
3507
- } catch {
3787
+ if (!hasCacheStorage()) {
3508
3788
  return { lastScanTime: null, size: 0 };
3509
3789
  }
3790
+ const info = withCacheDb((db) => {
3791
+ const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
3792
+ const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM cached_sessions").get();
3793
+ const lastScanTime = Number(timestampRow?.value ?? 0) || null;
3794
+ const size = Number(sizeRow?.value ?? 0);
3795
+ return { lastScanTime, size };
3796
+ });
3797
+ return info ?? { lastScanTime: null, size: 0 };
3798
+ }
3799
+ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
3800
+ if (!hasCacheStorage()) {
3801
+ return;
3802
+ }
3803
+ withCacheDb((db) => {
3804
+ const existingRows = db.prepare(
3805
+ "SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
3806
+ ).all(agentName);
3807
+ const existingMap = new Map(
3808
+ existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
3809
+ );
3810
+ const sessionMap = new Map(sessions.map((session) => [session.id, session]));
3811
+ const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
3812
+ const toUpsert = sessions.filter(
3813
+ (session) => existingMap.get(session.id) !== sessionContentHash(session)
3814
+ );
3815
+ const loaded = toUpsert.map((session) => {
3816
+ try {
3817
+ const data = loadSessionData(session.id);
3818
+ return {
3819
+ session,
3820
+ contentText: buildSessionContent(data),
3821
+ contentHash: sessionContentHash(session)
3822
+ };
3823
+ } catch {
3824
+ return null;
3825
+ }
3826
+ }).filter((entry) => entry !== null);
3827
+ const deleteRow = db.prepare(
3828
+ "DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
3829
+ );
3830
+ const upsertRow = db.prepare(`
3831
+ INSERT INTO session_documents(
3832
+ agent_name,
3833
+ session_id,
3834
+ slug,
3835
+ title,
3836
+ directory,
3837
+ time_created,
3838
+ time_updated,
3839
+ activity_time,
3840
+ content_text,
3841
+ content_hash,
3842
+ indexed_at
3843
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3844
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
3845
+ slug = excluded.slug,
3846
+ title = excluded.title,
3847
+ directory = excluded.directory,
3848
+ time_created = excluded.time_created,
3849
+ time_updated = excluded.time_updated,
3850
+ activity_time = excluded.activity_time,
3851
+ content_text = excluded.content_text,
3852
+ content_hash = excluded.content_hash,
3853
+ indexed_at = excluded.indexed_at
3854
+ `);
3855
+ const write = db.transaction(() => {
3856
+ for (const sessionId of toDelete) {
3857
+ deleteRow.run(agentName, sessionId);
3858
+ }
3859
+ for (const entry of loaded) {
3860
+ const activityTime = entry.session.time_updated ?? entry.session.time_created;
3861
+ upsertRow.run(
3862
+ agentName,
3863
+ entry.session.id,
3864
+ entry.session.slug,
3865
+ entry.session.title,
3866
+ entry.session.directory,
3867
+ entry.session.time_created,
3868
+ entry.session.time_updated ?? null,
3869
+ activityTime,
3870
+ entry.contentText,
3871
+ entry.contentHash,
3872
+ Date.now()
3873
+ );
3874
+ }
3875
+ });
3876
+ write();
3877
+ });
3878
+ }
3879
+ function searchSessions(query, options = {}) {
3880
+ const normalizedQuery = query.trim();
3881
+ if (!normalizedQuery || !hasCacheStorage()) {
3882
+ return [];
3883
+ }
3884
+ const ftsQuery = toFtsQuery(normalizedQuery);
3885
+ const results = withCacheDb((db) => {
3886
+ const rows = db.prepare(
3887
+ `
3888
+ SELECT
3889
+ d.agent_name,
3890
+ d.session_id,
3891
+ d.slug,
3892
+ d.title,
3893
+ d.directory,
3894
+ d.time_created,
3895
+ d.time_updated,
3896
+ COALESCE(
3897
+ NULLIF(snippet(session_documents_fts, 1, '<mark>', '</mark>', ' \u2026 ', 18), ''),
3898
+ highlight(session_documents_fts, 0, '<mark>', '</mark>')
3899
+ ) AS snippet
3900
+ FROM session_documents_fts
3901
+ JOIN session_documents d ON d.id = session_documents_fts.rowid
3902
+ WHERE session_documents_fts MATCH ?
3903
+ AND (? IS NULL OR d.agent_name = ?)
3904
+ AND (? IS NULL OR LOWER(d.directory) LIKE ?)
3905
+ AND (? IS NULL OR d.activity_time >= ?)
3906
+ AND (? IS NULL OR d.activity_time <= ?)
3907
+ ORDER BY bm25(session_documents_fts, 8.0, 1.0), d.activity_time DESC
3908
+ LIMIT ?
3909
+ `
3910
+ ).all(
3911
+ ftsQuery,
3912
+ options.agent ?? null,
3913
+ options.agent ?? null,
3914
+ options.cwd?.toLowerCase() ?? null,
3915
+ options.cwd ? `%${options.cwd.toLowerCase()}%` : null,
3916
+ options.from ?? null,
3917
+ options.from ?? null,
3918
+ options.to ?? null,
3919
+ options.to ?? null,
3920
+ options.limit ?? 50
3921
+ );
3922
+ return rows.map((row) => ({
3923
+ agentName: String(row.agent_name),
3924
+ session: {
3925
+ id: String(row.session_id),
3926
+ slug: String(row.slug),
3927
+ title: String(row.title),
3928
+ directory: String(row.directory),
3929
+ time_created: Number(row.time_created),
3930
+ time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
3931
+ stats: {
3932
+ message_count: 0,
3933
+ total_input_tokens: 0,
3934
+ total_output_tokens: 0,
3935
+ total_cost: 0
3936
+ }
3937
+ },
3938
+ snippet: String(row.snippet ?? "")
3939
+ }));
3940
+ });
3941
+ return results ?? [];
3510
3942
  }
3511
3943
  function isPathScopeMatch(queryPath, sessionPath) {
3512
3944
  if (!sessionPath) return false;
@@ -3531,9 +3963,18 @@ function filterSessions(sessions, options) {
3531
3963
  }
3532
3964
  return result;
3533
3965
  }
3966
+ function buildAgentCacheMeta(agent) {
3967
+ const metaMap = agent.getSessionMetaMap?.();
3968
+ const meta = {};
3969
+ if (!metaMap) return meta;
3970
+ for (const [id, data] of metaMap.entries()) {
3971
+ meta[id] = { id, ...data };
3972
+ }
3973
+ return meta;
3974
+ }
3534
3975
  async function scanAgentSmart(agent, options, onProgress) {
3535
3976
  const useCache = options.useCache ?? true;
3536
- const smartRefresh = options.smartRefresh ?? true;
3977
+ const canValidateCache = Boolean(agent.checkForChanges && agent.incrementalScan);
3537
3978
  if (useCache) {
3538
3979
  const cached = loadCachedSessions(agent.name);
3539
3980
  if (cached !== null) {
@@ -3544,16 +3985,39 @@ async function scanAgentSmart(agent, options, onProgress) {
3544
3985
  }
3545
3986
  agent.setSessionMetaMap(metaMap);
3546
3987
  }
3547
- agent.isAvailable();
3988
+ const isAvail = agent.isAvailable();
3989
+ if (!isAvail) {
3990
+ return null;
3991
+ }
3548
3992
  onProgress?.({
3549
3993
  agent: agent.name,
3550
3994
  phase: "cache",
3551
3995
  cachedCount: cached.sessions.length
3552
3996
  });
3553
- if (smartRefresh && agent.checkForChanges) {
3554
- setTimeout(async () => {
3555
- await refreshAgentAsync(agent, cached.sessions, cached.timestamp, onProgress);
3556
- }, 0);
3997
+ if (canValidateCache) {
3998
+ onProgress?.({ agent: agent.name, phase: "checking" });
3999
+ const checkResult = await Promise.resolve(
4000
+ agent.checkForChanges(cached.timestamp, cached.sessions)
4001
+ );
4002
+ if (checkResult.hasChanges) {
4003
+ onProgress?.({
4004
+ agent: agent.name,
4005
+ phase: "incremental",
4006
+ changedCount: checkResult.changedIds?.length
4007
+ });
4008
+ const updatedSessions = await Promise.resolve(
4009
+ agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
4010
+ );
4011
+ saveCachedSessions(agent.name, updatedSessions, buildAgentCacheMeta(agent));
4012
+ onProgress?.({
4013
+ agent: agent.name,
4014
+ phase: "complete",
4015
+ newCount: updatedSessions.length
4016
+ });
4017
+ const filtered2 = filterSessions(updatedSessions, options);
4018
+ return { agent, heads: filtered2, fromCache: true, refreshed: true };
4019
+ }
4020
+ onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
3557
4021
  }
3558
4022
  const filtered = filterSessions(cached.sessions, options);
3559
4023
  return { agent, heads: filtered, fromCache: true };
@@ -3561,41 +4025,6 @@ async function scanAgentSmart(agent, options, onProgress) {
3561
4025
  }
3562
4026
  return scanAgentFull(agent, options, onProgress);
3563
4027
  }
3564
- async function refreshAgentAsync(agent, cachedSessions, cacheTimestamp, onProgress) {
3565
- try {
3566
- onProgress?.({ agent: agent.name, phase: "checking" });
3567
- const checkResult = await Promise.resolve(
3568
- agent.checkForChanges(cacheTimestamp, cachedSessions)
3569
- );
3570
- if (!checkResult.hasChanges) {
3571
- onProgress?.({ agent: agent.name, phase: "complete" });
3572
- return;
3573
- }
3574
- onProgress?.({
3575
- agent: agent.name,
3576
- phase: "incremental",
3577
- changedCount: checkResult.changedIds?.length
3578
- });
3579
- const updatedSessions = await Promise.resolve(
3580
- agent.incrementalScan(cachedSessions, checkResult.changedIds || [])
3581
- );
3582
- const metaMap = agent.getSessionMetaMap?.();
3583
- const meta = {};
3584
- if (metaMap) {
3585
- for (const [id, data] of metaMap.entries()) {
3586
- meta[id] = { id, ...data };
3587
- }
3588
- }
3589
- saveCachedSessions(agent.name, updatedSessions, meta);
3590
- onProgress?.({
3591
- agent: agent.name,
3592
- phase: "complete",
3593
- newCount: updatedSessions.length
3594
- });
3595
- } catch (err) {
3596
- console.error(`[${agent.name}] Background refresh failed:`, err);
3597
- }
3598
- }
3599
4028
  async function scanAgentFull(agent, options, onProgress) {
3600
4029
  const availMarker = perf.start(`agent:${agent.name}:isAvailable`);
3601
4030
  const isAvail = agent.isAvailable();
@@ -3607,13 +4036,7 @@ async function scanAgentFull(agent, options, onProgress) {
3607
4036
  const scanMarker = perf.start(`agent:${agent.name}:scan`);
3608
4037
  const heads = agent.scan();
3609
4038
  perf.end(scanMarker);
3610
- const metaMap = agent.getSessionMetaMap?.();
3611
- const meta = {};
3612
- if (metaMap) {
3613
- for (const [id, data] of metaMap.entries()) {
3614
- meta[id] = { id, ...data };
3615
- }
3616
- }
4039
+ const meta = buildAgentCacheMeta(agent);
3617
4040
  saveCachedSessions(agent.name, heads, meta);
3618
4041
  onProgress?.({ agent: agent.name, phase: "complete", newCount: heads.length });
3619
4042
  const filtered = filterSessions(heads, options);
@@ -3669,12 +4092,16 @@ export {
3669
4092
  resolveSessionTitle,
3670
4093
  perf,
3671
4094
  openDbReadOnly,
4095
+ openDb,
3672
4096
  isSqliteAvailable,
3673
4097
  loadCachedSessions,
3674
4098
  saveCachedSessions,
3675
4099
  clearCache,
3676
4100
  getCacheInfo,
4101
+ syncSessionSearchIndex,
4102
+ searchSessions,
4103
+ filterSessions,
3677
4104
  scanSessions,
3678
4105
  scanSessionsAsync
3679
4106
  };
3680
- //# sourceMappingURL=chunk-FG2FZIU5.js.map
4107
+ //# sourceMappingURL=chunk-UQI7CTEK.js.map