codesesh 0.17.0 → 0.18.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.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-A4U2SMJJ.js → chunk-DHNP33L4.js} +20 -12
  3. package/dist/chunk-DHNP33L4.js.map +1 -0
  4. package/dist/{chunk-7APNDHQ6.js → chunk-HIYT2ZJL.js} +659 -353
  5. package/dist/chunk-HIYT2ZJL.js.map +1 -0
  6. package/dist/{dist-KEPJFHOC.js → dist-DMGLFBW6.js} +14 -4
  7. package/dist/index.js +224 -81
  8. package/dist/index.js.map +1 -1
  9. package/dist/scan-refresh-worker.js +2 -2
  10. package/dist/search-index-worker.js +73 -39
  11. package/dist/search-index-worker.js.map +1 -1
  12. package/dist/smart-tag-worker.js +2 -2
  13. package/dist/web/assets/AgentIcon-DcUJM198.js +1 -0
  14. package/dist/web/assets/BookmarkButton-CJjTjyae.js +1 -0
  15. package/dist/web/assets/Dashboard-wjTLT17d.js +39 -0
  16. package/dist/web/assets/DialogTitle-Bz0eXTGu.js +1 -0
  17. package/dist/web/assets/InteractiveReceipt-xnRqJvfd.js +1 -0
  18. package/dist/web/assets/PrismHighlighter-C7OfXHq2.js +6 -0
  19. package/dist/web/assets/Projects-DBxubQuM.js +1 -0
  20. package/dist/web/assets/SearchFilterBar-BG9ah_Po.js +1 -0
  21. package/dist/web/assets/SearchResultsPanel-Clmf2xKw.js +1 -0
  22. package/dist/web/assets/SessionDetail-CgR7ipmi.js +54 -0
  23. package/dist/web/assets/index-DR07vs5V.js +1890 -0
  24. package/dist/web/assets/index-DuB6tGoe.css +2 -0
  25. package/dist/web/assets/index.dom-CCLJ9_FR.js +1 -0
  26. package/dist/web/assets/jsx-runtime-n5LQ9ujS.js +1 -0
  27. package/dist/web/assets/preload-helper-CmKXJxR3.js +1 -0
  28. package/dist/web/assets/session-indexes-EHJdYE4j.js +4 -0
  29. package/dist/web/assets/session-title-CNhapdPJ.js +1 -0
  30. package/dist/web/assets/useTheme-CwDcVdGz.js +1 -0
  31. package/dist/web/assets/utils-B6KiDbIe.js +1 -0
  32. package/dist/web/index.html +11 -7
  33. package/package.json +3 -3
  34. package/dist/chunk-7APNDHQ6.js.map +0 -1
  35. package/dist/chunk-A4U2SMJJ.js.map +0 -1
  36. package/dist/web/assets/index-B076CRkD.js +0 -109
  37. package/dist/web/assets/index-CWNbVhet.css +0 -2
  38. package/dist/web/assets/markdown-dR4kAewZ.js +0 -14
  39. package/dist/web/assets/react-D9s532MZ.js +0 -1821
  40. package/dist/web/assets/rolldown-runtime-QTnfLwEv.js +0 -1
  41. package/dist/web/assets/syntax-mZXQuVcd.js +0 -6
  42. package/dist/web/assets/vendor-i9U7_fNF.js +0 -41
  43. /package/dist/{dist-KEPJFHOC.js.map → dist-DMGLFBW6.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../core/dist/chunk-BOTICQC6.mjs
3
+ // ../core/dist/chunk-DKIXFG56.mjs
4
4
  function toRecord(value) {
5
5
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
6
6
  }
@@ -120,6 +120,76 @@ function normalizeMessageParts(value) {
120
120
  return normalized ? [normalized] : [];
121
121
  });
122
122
  }
123
+ function mergeSessionsUpdatedEvents(previous, next) {
124
+ const changedSessionHeads = /* @__PURE__ */ new Map();
125
+ const removedSessionRefs = /* @__PURE__ */ new Map();
126
+ const sessionKey = (agentName, sessionId) => `${agentName}\0${sessionId}`;
127
+ const addChanged = (item) => {
128
+ const key = sessionKey(item.reference.agentName, item.reference.sessionId);
129
+ removedSessionRefs.delete(key);
130
+ changedSessionHeads.set(key, item);
131
+ };
132
+ const addRemoved = (item) => {
133
+ const key = sessionKey(item.agentName, item.sessionId);
134
+ changedSessionHeads.delete(key);
135
+ removedSessionRefs.set(key, item);
136
+ };
137
+ for (const item of previous.changedSessionHeads) addChanged(item);
138
+ for (const item of previous.removedSessionRefs) addRemoved(item);
139
+ for (const item of next.changedSessionHeads) addChanged(item);
140
+ for (const item of next.removedSessionRefs) addRemoved(item);
141
+ return {
142
+ type: "sessions-updated",
143
+ changedAgents: Array.from(/* @__PURE__ */ new Set([...previous.changedAgents, ...next.changedAgents])),
144
+ newSessions: previous.newSessions + next.newSessions,
145
+ updatedSessions: previous.updatedSessions + next.updatedSessions,
146
+ removedSessions: previous.removedSessions + next.removedSessions,
147
+ totalSessions: next.totalSessions,
148
+ timestamp: next.timestamp,
149
+ changedSessionHeads: [...changedSessionHeads.values()],
150
+ removedSessionRefs: [...removedSessionRefs.values()]
151
+ };
152
+ }
153
+ function startOfCalendarDay(timestamp) {
154
+ const date = new Date(timestamp);
155
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
156
+ }
157
+ function addCalendarDays(timestamp, days) {
158
+ const date = new Date(timestamp);
159
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days).getTime();
160
+ }
161
+ function toCalendarDayNumber(timestamp) {
162
+ const date = new Date(timestamp);
163
+ return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / 864e5;
164
+ }
165
+ function countCalendarDays(from, to) {
166
+ return Math.max(1, toCalendarDayNumber(to) - toCalendarDayNumber(from) + 1);
167
+ }
168
+ function toCalendarDayKey(timestamp) {
169
+ const date = new Date(timestamp);
170
+ return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(
171
+ 2,
172
+ "0"
173
+ )}-${String(date.getDate()).padStart(2, "0")}`;
174
+ }
175
+ var PROJECT_IDENTITY_KINDS = [
176
+ "git_remote",
177
+ "git_common_dir",
178
+ "manifest_path",
179
+ "synthetic",
180
+ "path",
181
+ "loose"
182
+ ];
183
+ var KIND_LOOKUP = new Set(PROJECT_IDENTITY_KINDS);
184
+ function isProjectIdentityKind(value) {
185
+ return KIND_LOOKUP.has(value);
186
+ }
187
+ function getProjectIdentityKey(identity) {
188
+ return `${identity.kind}:${identity.key}`;
189
+ }
190
+ function matchesProjectIdentity(identity, expected) {
191
+ return identity?.kind === expected.kind && identity.key === expected.key;
192
+ }
123
193
  var UNKNOWN_AGENT_NAME = "unknown";
124
194
  function normalizeSessionReference(reference) {
125
195
  return {
@@ -144,6 +214,13 @@ function formatSessionReference(reference) {
144
214
  function getSessionAgentKey(session) {
145
215
  return parseSessionReference(session.slug)?.agentName ?? UNKNOWN_AGENT_NAME;
146
216
  }
217
+ function agentRoutePath(agentName) {
218
+ return `/${encodeURIComponent(agentName.trim().toLowerCase())}`;
219
+ }
220
+ function sessionRoutePath(reference) {
221
+ const normalized = normalizeSessionReference(reference);
222
+ return `${agentRoutePath(normalized.agentName)}/${encodeURIComponent(normalized.sessionId)}`;
223
+ }
147
224
  function compareSessionActivityDesc(a, b) {
148
225
  return (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created);
149
226
  }
@@ -253,8 +330,8 @@ var SAMPLE_DASHBOARD_DATA = {
253
330
  };
254
331
 
255
332
  // ../core/dist/index.mjs
256
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
257
- import { join as join4, basename as basename2, dirname } from "path";
333
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
334
+ import { join as join5, basename as basename3, dirname as dirname2 } from "path";
258
335
  import { existsSync, readdirSync, statSync } from "fs";
259
336
  import { join } from "path";
260
337
  import { existsSync as existsSync2 } from "fs";
@@ -263,38 +340,40 @@ import { join as join2 } from "path";
263
340
  import { closeSync, openSync, readSync } from "fs";
264
341
  import { StringDecoder } from "string_decoder";
265
342
  import { basename } from "path";
266
- import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
343
+ import { existsSync as existsSync4, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
267
344
  import { homedir as homedir2 } from "os";
268
- import { join as join3 } from "path";
269
- import { join as join6 } from "path";
270
- import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
271
- import { basename as basename3, dirname as dirname2, join as join5 } from "path";
345
+ import { join as join4 } from "path";
346
+ import { chmodSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
347
+ import { basename as basename2, dirname, join as join3 } from "path";
348
+ import { join as join7 } from "path";
349
+ import { existsSync as existsSync6 } from "fs";
350
+ import { basename as basename4, dirname as dirname3, join as join6 } from "path";
272
351
  import { createRequire } from "module";
273
352
  import { createHash } from "crypto";
274
- import { existsSync as existsSync6, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
275
- import { join as join7, basename as basename4, dirname as dirname3 } from "path";
276
- import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync2, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync4 } from "fs";
277
- import { join as join8, basename as basename5 } from "path";
278
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
353
+ import { existsSync as existsSync7, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
354
+ import { join as join8, basename as basename5, dirname as dirname4 } from "path";
355
+ import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "fs";
356
+ import { join as join9, basename as basename6 } from "path";
357
+ import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync6 } from "fs";
279
358
  import { homedir as homedir3, platform as platform2 } from "os";
280
- import { join as join9, normalize } from "path";
281
- import { existsSync as existsSync9 } from "fs";
282
- import { basename as basename6, join as join10 } from "path";
359
+ import { join as join10, normalize } from "path";
360
+ import { existsSync as existsSync10 } from "fs";
361
+ import { basename as basename7, join as join11 } from "path";
283
362
  import { homedir as homedir4, platform as platform3 } from "os";
284
- import { join as join11 } from "path";
363
+ import { join as join12 } from "path";
285
364
  import { availableParallelism } from "os";
286
365
  import { Worker } from "worker_threads";
287
- import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
366
+ import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
288
367
  import { spawnSync } from "child_process";
289
368
  import * as os from "os";
290
369
  import * as path from "path";
291
370
  import { resolve, sep } from "path";
292
- import { existsSync as existsSync12, rmSync, unlinkSync } from "fs";
293
- import { existsSync as existsSync11 } from "fs";
294
- import { join as join12 } from "path";
371
+ import { existsSync as existsSync13, rmSync as rmSync2, unlinkSync } from "fs";
372
+ import { existsSync as existsSync12 } from "fs";
373
+ import { join as join13 } from "path";
295
374
  import { homedir as homedir6 } from "os";
296
375
  import { homedir as homedir7, platform as platform4 } from "os";
297
- import { join as join13 } from "path";
376
+ import { join as join14 } from "path";
298
377
  var registrations = [];
299
378
  function registerAgent(reg) {
300
379
  registrations.push(reg);
@@ -547,8 +626,41 @@ var SingleFileSessionSource = class extends FileSystemSessionSource {
547
626
  };
548
627
  }
549
628
  };
629
+ var SessionScanError = class extends Error {
630
+ constructor(agentName, stage, options) {
631
+ super(`${agentName} session scan failed while ${stage}`, options);
632
+ this.agentName = agentName;
633
+ this.stage = stage;
634
+ this.name = "SessionScanError";
635
+ }
636
+ agentName;
637
+ stage;
638
+ };
639
+ function sqliteSourceFiles(dbPath) {
640
+ return [dbPath, `${dbPath}-wal`];
641
+ }
642
+ function statOrNull(path2) {
643
+ try {
644
+ return statSync(path2);
645
+ } catch {
646
+ return null;
647
+ }
648
+ }
649
+ function sqliteSourceFingerprint(dbPath) {
650
+ return sqliteSourceFiles(dbPath).map((path2) => {
651
+ const stats = statOrNull(path2);
652
+ return stats ? `${stats.size}:${Math.round(stats.mtimeMs)}` : "-";
653
+ }).join("|");
654
+ }
655
+ function latestSqliteSourceMtime(dbPath) {
656
+ return sqliteSourceFiles(dbPath).reduce((latest, path2) => {
657
+ const stats = statOrNull(path2);
658
+ return stats && stats.mtimeMs > latest ? stats.mtimeMs : latest;
659
+ }, 0);
660
+ }
550
661
  var DatabaseSessionSource = class extends BaseAgent {
551
662
  sessionMetaMap = /* @__PURE__ */ new Map();
663
+ lastSourceFingerprint = null;
552
664
  /** 记录单个会话的缓存 meta(sourcePath = dbPath)。 */
553
665
  rememberSession(sessionId) {
554
666
  const dbPath = this.getDatabasePath();
@@ -562,7 +674,12 @@ var DatabaseSessionSource = class extends BaseAgent {
562
674
  this.sessionMetaMap = meta;
563
675
  }
564
676
  /**
565
- * 变更检测:数据库内部变更难以按行定位,按库文件 mtime 判定。
677
+ * 变更检测:数据库内部变更难以按行定位,按库文件集合的指纹判定。
678
+ *
679
+ * In WAL mode a commit appends to the sidecar and leaves the main file
680
+ * untouched until checkpoint, so watching the database alone misses recent
681
+ * writes entirely. Size is part of the fingerprint because an uncheckpointed
682
+ * commit may land in the same millisecond as the previous one.
566
683
  */
567
684
  checkForChanges(sinceTimestamp, _cachedSessions) {
568
685
  const dbPath = this.getDatabasePath();
@@ -570,7 +687,10 @@ var DatabaseSessionSource = class extends BaseAgent {
570
687
  return { hasChanges: false, timestamp: Date.now() };
571
688
  }
572
689
  try {
573
- const hasChanges = statSync(dbPath).mtimeMs > sinceTimestamp;
690
+ const fingerprint = sqliteSourceFingerprint(dbPath);
691
+ const previous = this.lastSourceFingerprint;
692
+ this.lastSourceFingerprint = fingerprint;
693
+ const hasChanges = previous == null ? latestSqliteSourceMtime(dbPath) > sinceTimestamp : fingerprint !== previous;
574
694
  return {
575
695
  hasChanges,
576
696
  timestamp: Date.now()
@@ -630,17 +750,17 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
630
750
  try {
631
751
  const buffer = Buffer.alloc(chunkBytes);
632
752
  const decoder = new StringDecoder("utf8");
633
- let pending = [];
753
+ let pending2 = [];
634
754
  let bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
635
755
  while (bytesRead > 0) {
636
756
  const decoded = decoder.write(buffer.subarray(0, bytesRead));
637
757
  const lastBreak = decoded.lastIndexOf("\n");
638
758
  if (lastBreak === -1) {
639
- if (decoded) pending.push(decoded);
759
+ if (decoded) pending2.push(decoded);
640
760
  } else {
641
- pending.push(decoded.slice(0, lastBreak));
642
- const complete = pending.join("");
643
- pending = [decoded.slice(lastBreak + 1)];
761
+ pending2.push(decoded.slice(0, lastBreak));
762
+ const complete = pending2.join("");
763
+ pending2 = [decoded.slice(lastBreak + 1)];
644
764
  for (const line of complete.split("\n")) {
645
765
  const trimmed = line.trim();
646
766
  if (trimmed) yield trimmed;
@@ -648,8 +768,8 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
648
768
  }
649
769
  bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
650
770
  }
651
- pending.push(decoder.end());
652
- const tail = pending.join("").trim();
771
+ pending2.push(decoder.end());
772
+ const tail = pending2.join("").trim();
653
773
  if (tail) yield tail;
654
774
  } finally {
655
775
  closeSync(fd);
@@ -833,6 +953,56 @@ var aliases_default = {
833
953
  "gpt-5.1-codex-high": "gpt-5.3-codex",
834
954
  "gpt-5.2-low": "gpt-5.2"
835
955
  };
956
+ var PRIVATE_DIR_MODE = 448;
957
+ var PRIVATE_FILE_MODE = 384;
958
+ var SQLITE_SIDECAR_SUFFIXES = ["-wal", "-shm", "-journal"];
959
+ var isPosix = process.platform !== "win32";
960
+ function reportChmodFailure(path2, error) {
961
+ getCoreDiagnostics()?.warn("storage.permissions_failed", {
962
+ path: path2,
963
+ message: error instanceof Error ? error.message : String(error)
964
+ });
965
+ }
966
+ function restrict(path2, mode) {
967
+ if (!isPosix || !existsSync3(path2)) return;
968
+ try {
969
+ if ((statSync2(path2).mode & 511) === mode) return;
970
+ chmodSync(path2, mode);
971
+ } catch (error) {
972
+ reportChmodFailure(path2, error);
973
+ }
974
+ }
975
+ function ensurePrivateDirectory(path2) {
976
+ mkdirSync(path2, { recursive: true, mode: PRIVATE_DIR_MODE });
977
+ restrict(path2, PRIVATE_DIR_MODE);
978
+ }
979
+ function restrictPrivateFile(path2) {
980
+ restrict(path2, PRIVATE_FILE_MODE);
981
+ }
982
+ function restrictExistingPrivateFiles(directory, owns) {
983
+ try {
984
+ for (const entry of readdirSync2(directory)) {
985
+ if (owns(entry)) restrictPrivateFile(join3(directory, entry));
986
+ }
987
+ } catch (error) {
988
+ reportChmodFailure(directory, error);
989
+ }
990
+ }
991
+ var sweptBackupsFor = /* @__PURE__ */ new Set();
992
+ function restrictExistingBackups(dbPath) {
993
+ if (sweptBackupsFor.has(dbPath)) return;
994
+ sweptBackupsFor.add(dbPath);
995
+ const prefix = `${basename2(dbPath)}.`;
996
+ restrictExistingPrivateFiles(
997
+ dirname(dbPath),
998
+ (name) => name.startsWith(prefix) && name.endsWith(".bak")
999
+ );
1000
+ }
1001
+ function restrictPrivateDatabase(dbPath) {
1002
+ restrictPrivateFile(dbPath);
1003
+ for (const suffix of SQLITE_SIDECAR_SUFFIXES) restrictPrivateFile(`${dbPath}${suffix}`);
1004
+ restrictExistingBackups(dbPath);
1005
+ }
836
1006
  var snapshot_default = {
837
1007
  "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
838
1008
  "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
@@ -879,7 +1049,9 @@ var snapshot_default = {
879
1049
  var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
880
1050
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
881
1051
  var WEB_SEARCH_COST = 0.01;
882
- var pricingCache = loadSnapshot();
1052
+ var REFRESH_TIMEOUT_MS = 1e4;
1053
+ var published = { id: 1, pricing: loadSnapshot() };
1054
+ var pending = null;
883
1055
  loadDiskCache();
884
1056
  function normalizeKey(key) {
885
1057
  return key.trim().toLowerCase();
@@ -888,10 +1060,10 @@ function costNumber(value, fallback) {
888
1060
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
889
1061
  }
890
1062
  function getCacheDir() {
891
- return join3(homedir2(), ".cache", "codesesh");
1063
+ return join4(homedir2(), ".cache", "codesesh");
892
1064
  }
893
1065
  function getCachePath() {
894
- return join3(getCacheDir(), "litellm-pricing.json");
1066
+ return join4(getCacheDir(), "litellm-pricing.json");
895
1067
  }
896
1068
  function loadSnapshot() {
897
1069
  const map = /* @__PURE__ */ new Map();
@@ -957,7 +1129,7 @@ function parseLiteLLMData(data) {
957
1129
  }
958
1130
  function loadDiskCache() {
959
1131
  const path2 = getCachePath();
960
- if (!existsSync3(path2)) return;
1132
+ if (!existsSync4(path2)) return;
961
1133
  try {
962
1134
  const cached = JSON.parse(readFileSync(path2, "utf-8"));
963
1135
  if (Date.now() - cached.timestamp <= CACHE_TTL_MS) {
@@ -967,20 +1139,51 @@ function loadDiskCache() {
967
1139
  if (!pricing) continue;
968
1140
  next.set(normalizeKey(name), pricing);
969
1141
  }
970
- pricingCache = next;
1142
+ published = { id: published.id + 1, pricing: next };
971
1143
  }
972
1144
  } catch {
973
1145
  }
974
1146
  }
975
1147
  function getPricingRegistry() {
976
- return pricingCache;
1148
+ return published.pricing;
1149
+ }
1150
+ function getPricingGeneration() {
1151
+ return published;
1152
+ }
1153
+ function publishPendingPricing() {
1154
+ if (!pending) return false;
1155
+ published = { id: published.id + 1, pricing: pending };
1156
+ pending = null;
1157
+ getCoreDiagnostics()?.info?.("pricing.generation.published", {
1158
+ generation: published.id,
1159
+ models: published.pricing.size
1160
+ });
1161
+ return true;
1162
+ }
1163
+ function hasPendingPricing() {
1164
+ return pending !== null;
977
1165
  }
978
1166
  function hasBillablePricing(pricing) {
979
1167
  return pricing.inputCostPerToken > 0 || pricing.outputCostPerToken > 0 || pricing.cacheReadCostPerToken > 0 || pricing.cacheCreateCostPerToken > 0;
980
1168
  }
981
- async function refreshPricingCache() {
1169
+ function writeDiskCacheAtomically(path2, pricing) {
1170
+ const temporaryPath = `${path2}.${process.pid}.tmp`;
1171
+ const payload = JSON.stringify({ timestamp: Date.now(), data: Object.fromEntries(pricing) });
1172
+ try {
1173
+ ensurePrivateDirectory(getCacheDir());
1174
+ writeFileSync(temporaryPath, payload);
1175
+ restrictPrivateFile(temporaryPath);
1176
+ renameSync(temporaryPath, path2);
1177
+ } catch (error) {
1178
+ rmSync(temporaryPath, { force: true });
1179
+ getCoreDiagnostics()?.warn("pricing.cache_write_failed", {
1180
+ message: error instanceof Error ? error.message : String(error)
1181
+ });
1182
+ }
1183
+ }
1184
+ async function refreshPricingCache(options = {}) {
982
1185
  const path2 = getCachePath();
983
- if (existsSync3(path2)) {
1186
+ if (existsSync4(path2)) {
984
1187
  try {
985
1188
  const cached = JSON.parse(readFileSync(path2, "utf-8"));
986
1189
  if (typeof cached.timestamp === "number" && Date.now() - cached.timestamp <= CACHE_TTL_MS) {
@@ -989,8 +1192,11 @@ async function refreshPricingCache() {
989
1192
  } catch {
990
1193
  }
991
1194
  }
1195
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? REFRESH_TIMEOUT_MS);
1196
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
1197
+ getCoreDiagnostics()?.info?.("pricing.refresh.started", { generation: published.id });
992
1198
  try {
993
- const response = await fetch(LITELLM_URL);
1199
+ const response = await fetch(LITELLM_URL, { signal });
994
1200
  if (!response.ok) return false;
995
1201
  const data = await response.json();
996
1202
  const remote = parseLiteLLMData(data);
@@ -999,11 +1205,19 @@ async function refreshPricingCache() {
999
1205
  for (const [name, pricing] of remote.entries()) {
1000
1206
  next.set(name, pricing);
1001
1207
  }
1002
- pricingCache = next;
1003
- mkdirSync(getCacheDir(), { recursive: true });
1004
- writeFileSync(path2, JSON.stringify({ timestamp: Date.now(), data: Object.fromEntries(next) }));
1208
+ pending = next;
1209
+ writeDiskCacheAtomically(path2, next);
1210
+ getCoreDiagnostics()?.info?.("pricing.refresh.completed", {
1211
+ generation: published.id,
1212
+ models: next.size
1213
+ });
1005
1214
  return true;
1006
- } catch {
1215
+ } catch (error) {
1216
+ getCoreDiagnostics()?.warn("pricing.refresh.failed", {
1217
+ generation: published.id,
1218
+ aborted: signal.aborted,
1219
+ message: error instanceof Error ? error.message : String(error)
1220
+ });
1007
1221
  return false;
1008
1222
  }
1009
1223
  }
@@ -1370,22 +1584,22 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1370
1584
  sessionsIndexCache = {};
1371
1585
  sessionsIndexMtime = {};
1372
1586
  findBasePath() {
1373
- return firstExisting(join4(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1587
+ return firstExisting(join5(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1374
1588
  }
1375
1589
  getSessionWatchPlan() {
1376
1590
  const dataRoot = resolveClaudeCodeDataRoot();
1377
1591
  return {
1378
1592
  status: "supported",
1379
- targets: [{ root: dataRoot, path: join4(dataRoot, "projects") }, { path: "data/claudecode" }]
1593
+ targets: [{ root: dataRoot, path: join5(dataRoot, "projects") }, { path: "data/claudecode" }]
1380
1594
  };
1381
1595
  }
1382
1596
  isAvailable() {
1383
1597
  this.basePath = this.findBasePath();
1384
1598
  if (!this.basePath) return false;
1385
1599
  try {
1386
- for (const entry of readdirSync2(this.basePath)) {
1387
- const dir = join4(this.basePath, entry);
1388
- if (existsSync4(dir) && readdirSync2(dir).some((f) => f.endsWith(".jsonl"))) {
1600
+ for (const entry of readdirSync3(this.basePath)) {
1601
+ const dir = join5(this.basePath, entry);
1602
+ if (existsSync5(dir) && readdirSync3(dir).some((f) => f.endsWith(".jsonl"))) {
1389
1603
  return true;
1390
1604
  }
1391
1605
  }
@@ -1405,13 +1619,13 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1405
1619
  recursive: false,
1406
1620
  scanWindow: options
1407
1621
  }).map(({ file, stat }) => ({
1408
- sessionId: basename2(file, ".jsonl"),
1622
+ sessionId: basename3(file, ".jsonl"),
1409
1623
  sourcePath: file,
1410
- fingerprint: this.sourceFingerprint(stat, indexMtimes.get(dirname(file)) ?? null)
1624
+ fingerprint: this.sourceFingerprint(stat, indexMtimes.get(dirname2(file)) ?? null)
1411
1625
  }));
1412
1626
  }
1413
1627
  parseFileSessionHead(sourcePath) {
1414
- const projectDir = dirname(sourcePath);
1628
+ const projectDir = dirname2(sourcePath);
1415
1629
  return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir));
1416
1630
  }
1417
1631
  getSessionData(sessionId) {
@@ -1419,7 +1633,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1419
1633
  if (!meta) {
1420
1634
  throw new Error(`Session not found: ${sessionId}`);
1421
1635
  }
1422
- if (!existsSync4(meta.sourcePath)) {
1636
+ if (!existsSync5(meta.sourcePath)) {
1423
1637
  throw new Error(`Session file missing: ${meta.sourcePath}`);
1424
1638
  }
1425
1639
  const builder = new TranscriptBuilder();
@@ -1449,13 +1663,13 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1449
1663
  listProjectDirs() {
1450
1664
  if (!this.basePath) return [];
1451
1665
  try {
1452
- return readdirSync2(this.basePath).map((e) => join4(this.basePath, e)).filter((p) => existsSync4(p));
1666
+ return readdirSync3(this.basePath).map((e) => join5(this.basePath, e)).filter((p) => existsSync5(p));
1453
1667
  } catch {
1454
1668
  return [];
1455
1669
  }
1456
1670
  }
1457
1671
  createFileSessionMeta(head, source) {
1458
- const projectDir = dirname(source.file);
1672
+ const projectDir = dirname2(source.file);
1459
1673
  const indexPath = this.getSessionsIndexPath(projectDir);
1460
1674
  const indexMtime = this.readFileMtimeMs(indexPath);
1461
1675
  return this.buildFileSessionMeta({
@@ -1475,17 +1689,17 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1475
1689
  return JSON.stringify([HEAD_INDEX_VERSION, stat.mtimeMs, stat.size, indexMtime]);
1476
1690
  }
1477
1691
  getSessionsIndexPath(projectDir) {
1478
- return join4(projectDir, "sessions-index.json");
1692
+ return join5(projectDir, "sessions-index.json");
1479
1693
  }
1480
1694
  loadSessionsIndex(projectDir) {
1481
- const cacheKey = basename2(projectDir);
1695
+ const cacheKey = basename3(projectDir);
1482
1696
  const indexPath = this.getSessionsIndexPath(projectDir);
1483
1697
  const mtime = this.readFileMtimeMs(indexPath);
1484
1698
  if (cacheKey in this.sessionsIndexCache && this.sessionsIndexMtime[cacheKey] === mtime) {
1485
1699
  return this.sessionsIndexCache[cacheKey];
1486
1700
  }
1487
1701
  const map = /* @__PURE__ */ new Map();
1488
- if (existsSync4(indexPath)) {
1702
+ if (existsSync5(indexPath)) {
1489
1703
  try {
1490
1704
  const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
1491
1705
  const entries = data?.entries ?? [];
@@ -1503,7 +1717,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1503
1717
  return map;
1504
1718
  }
1505
1719
  parseSessionHeadResult(filePath, projectDir) {
1506
- const sessionId = basename2(filePath, ".jsonl");
1720
+ const sessionId = basename3(filePath, ".jsonl");
1507
1721
  const index = this.loadSessionsIndex(projectDir);
1508
1722
  const indexEntry = index.get(sessionId);
1509
1723
  const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
@@ -1531,7 +1745,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1531
1745
  continue;
1532
1746
  }
1533
1747
  if (lineIndex === 0) {
1534
- createdAt = parseTimestampMs(data) || statSync2(filePath).mtimeMs;
1748
+ createdAt = parseTimestampMs(data) || statSync3(filePath).mtimeMs;
1535
1749
  updatedAt = createdAt;
1536
1750
  }
1537
1751
  const recordIndex = lineIndex;
@@ -1967,11 +2181,12 @@ function backupDatabase(db, dbPath, label) {
1967
2181
  return null;
1968
2182
  }
1969
2183
  const timestamp = new Date(Date.now()).toISOString().replaceAll(":", "").replaceAll(".", "-");
1970
- let backupPath = join5(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.bak`);
1971
- for (let counter = 1; existsSync5(backupPath); counter += 1) {
1972
- backupPath = join5(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.${counter}.bak`);
2184
+ let backupPath = join6(dirname3(dbPath), `${basename4(dbPath)}.${timestamp}.${label}.bak`);
2185
+ for (let counter = 1; existsSync6(backupPath); counter += 1) {
2186
+ backupPath = join6(dirname3(dbPath), `${basename4(dbPath)}.${timestamp}.${label}.${counter}.bak`);
1973
2187
  }
1974
2188
  db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
2189
+ restrictPrivateFile(backupPath);
1975
2190
  return backupPath;
1976
2191
  }
1977
2192
  function backupDatabaseIfPopulated(db, dbPath, label, tables) {
@@ -2066,8 +2281,9 @@ function openDb(dbPath) {
2066
2281
  return null;
2067
2282
  }
2068
2283
  try {
2069
- mkdirSync2(dirname2(dbPath), { recursive: true });
2284
+ ensurePrivateDirectory(dirname3(dbPath));
2070
2285
  const db = DatabaseConstructor(dbPath);
2286
+ restrictPrivateDatabase(dbPath);
2071
2287
  try {
2072
2288
  db.pragma("journal_mode = WAL");
2073
2289
  db.pragma("synchronous = NORMAL");
@@ -2138,7 +2354,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2138
2354
  scan(options) {
2139
2355
  if (!this.dbPath) return [];
2140
2356
  const db = openDbReadOnly(this.dbPath);
2141
- if (!db) return [];
2357
+ if (!db) throw new SessionScanError(this.name, "opening the database");
2142
2358
  try {
2143
2359
  const cutoffTime = options?.from ?? Date.now() - 3650 * 24 * 60 * 60 * 1e3;
2144
2360
  const hasMessageTable = Boolean(
@@ -2187,8 +2403,8 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2187
2403
  options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
2188
2404
  }
2189
2405
  return heads;
2190
- } catch {
2191
- return [];
2406
+ } catch (error) {
2407
+ throw new SessionScanError(this.name, "reading sessions", { cause: error });
2192
2408
  } finally {
2193
2409
  db.close();
2194
2410
  }
@@ -2237,7 +2453,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2237
2453
  JOIN message m ON m.id = p.message_id
2238
2454
  JOIN session s ON s.id = m.session_id
2239
2455
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2240
- ORDER BY p.message_id, p.time_created ASC
2456
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2241
2457
  `
2242
2458
  ).all(cutoffTime);
2243
2459
  }
@@ -2250,9 +2466,21 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2250
2466
  ]);
2251
2467
  return part ? cleanMessagePart(part) : null;
2252
2468
  }
2253
- readMessageParts(db, messageId) {
2254
- const partRows = db.prepare("SELECT data, time_created FROM part WHERE message_id = ? ORDER BY time_created ASC").all(messageId);
2255
- return partRows.map((partRow) => this.parsePartRow(partRow)).filter((part) => part !== null);
2469
+ /**
2470
+ * Every part of one session in a single read. Fetching per message turned a
2471
+ * detail into M+2 queries, and without an index on part(message_id) each one
2472
+ * scanned the whole table.
2473
+ */
2474
+ readSessionPartRows(db, sessionId) {
2475
+ return db.prepare(
2476
+ `
2477
+ SELECT p.message_id, p.data, p.time_created
2478
+ FROM part p
2479
+ JOIN message m ON m.id = p.message_id
2480
+ WHERE m.session_id = ?
2481
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2482
+ `
2483
+ ).all(sessionId);
2256
2484
  }
2257
2485
  buildPartsByMessage(partRows) {
2258
2486
  const partsByMessage = /* @__PURE__ */ new Map();
@@ -2349,7 +2577,8 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2349
2577
  let totalInputTokens = 0;
2350
2578
  let totalOutputTokens = 0;
2351
2579
  let hasEstimatedCost = false;
2352
- const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
2580
+ const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
2581
+ const partsByMessage = this.buildPartsByMessage(this.readSessionPartRows(db, sessionId));
2353
2582
  for (const msgRow of msgRows) {
2354
2583
  const msgData = parseJsonRecord(msgRow.data, this.name, "message.data");
2355
2584
  if (isInternalEventType(msgData.type)) continue;
@@ -2360,7 +2589,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2360
2589
  const model = parseModel(msgData.modelID, this.name);
2361
2590
  const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
2362
2591
  const resolvedCost = cost || estimatedCost || 0;
2363
- const parts = this.readMessageParts(db, msgRow.id);
2592
+ const parts = partsByMessage.get(String(msgRow.id ?? "")) ?? [];
2364
2593
  if (parts.length === 0) continue;
2365
2594
  messages.push({
2366
2595
  id: String(msgRow.id ?? ""),
@@ -2413,18 +2642,18 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2413
2642
  }
2414
2643
  };
2415
2644
  function resolveOpenCodeDataRoot() {
2416
- return join6(resolveDataHome(), "opencode");
2645
+ return join7(resolveDataHome(), "opencode");
2417
2646
  }
2418
2647
  function findOpenCodeDbPath() {
2419
2648
  if (!isSqliteAvailable()) return null;
2420
- return firstExisting(join6(resolveOpenCodeDataRoot(), "opencode.db"), "data/opencode/opencode.db");
2649
+ return firstExisting(join7(resolveOpenCodeDataRoot(), "opencode.db"), "data/opencode/opencode.db");
2421
2650
  }
2422
2651
  function getOpenCodeSessionWatchPlan() {
2423
2652
  const dataRoot = resolveOpenCodeDataRoot();
2424
2653
  return {
2425
2654
  status: "supported",
2426
2655
  targets: [
2427
- { root: dataRoot, path: join6(dataRoot, "opencode.db") },
2656
+ { root: dataRoot, path: join7(dataRoot, "opencode.db") },
2428
2657
  { root: "data/opencode", path: "data/opencode/opencode.db" }
2429
2658
  ]
2430
2659
  };
@@ -2521,14 +2750,14 @@ function kimiContentText(content) {
2521
2750
  }).join(" ");
2522
2751
  }
2523
2752
  function extractFirstUserTitle(contextFile, wireFile) {
2524
- if (contextFile && existsSync6(contextFile)) {
2753
+ if (contextFile && existsSync7(contextFile)) {
2525
2754
  for (const record of readJsonlFile(contextFile)) {
2526
2755
  if (record.role !== "user") continue;
2527
2756
  const title = normalizeTitleText(kimiContentText(record.content));
2528
2757
  if (title) return title;
2529
2758
  }
2530
2759
  }
2531
- if (wireFile && existsSync6(wireFile)) {
2760
+ if (wireFile && existsSync7(wireFile)) {
2532
2761
  for (const record of readJsonlFile(wireFile)) {
2533
2762
  const message = asRecord(record.message) ?? {};
2534
2763
  if (message.type !== "TurnBegin") continue;
@@ -2548,25 +2777,25 @@ var KimiAgent = class extends FileSystemSessionSource {
2548
2777
  projectMap = /* @__PURE__ */ new Map();
2549
2778
  defaultModel = null;
2550
2779
  findBasePath() {
2551
- return firstExisting(join7(resolveKimiDataRoot(), "sessions"), "data/kimi");
2780
+ return firstExisting(join8(resolveKimiDataRoot(), "sessions"), "data/kimi");
2552
2781
  }
2553
2782
  getSessionWatchPlan() {
2554
2783
  const dataRoot = resolveKimiDataRoot();
2555
2784
  return {
2556
2785
  status: "supported",
2557
- targets: [{ root: dataRoot, path: join7(dataRoot, "sessions") }, { path: "data/kimi" }]
2786
+ targets: [{ root: dataRoot, path: join8(dataRoot, "sessions") }, { path: "data/kimi" }]
2558
2787
  };
2559
2788
  }
2560
2789
  /** Parse kimi.json and build md5(project_path) → cwd mapping */
2561
2790
  loadKimiConfig() {
2562
2791
  const dataRoot = resolveKimiDataRoot();
2563
- const configPath = join7(dataRoot, "kimi.json");
2564
- const tomlPath = join7(dataRoot, "config.toml");
2565
- if (existsSync6(tomlPath)) {
2792
+ const configPath = join8(dataRoot, "kimi.json");
2793
+ const tomlPath = join8(dataRoot, "config.toml");
2794
+ if (existsSync7(tomlPath)) {
2566
2795
  const configText = readFileSync3(tomlPath, "utf-8");
2567
2796
  this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
2568
2797
  }
2569
- if (!existsSync6(configPath)) return;
2798
+ if (!existsSync7(configPath)) return;
2570
2799
  try {
2571
2800
  const raw = asRecord(JSON.parse(readFileSync3(configPath, "utf-8")));
2572
2801
  const workDirs = asArray(raw?.work_dirs);
@@ -2595,14 +2824,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2595
2824
  if (!this.basePath) return [];
2596
2825
  const dirs = [];
2597
2826
  try {
2598
- for (const hashEntry of readdirSync3(this.basePath, { withFileTypes: true })) {
2827
+ for (const hashEntry of readdirSync4(this.basePath, { withFileTypes: true })) {
2599
2828
  if (!hashEntry.isDirectory()) continue;
2600
- const hashPath = join7(this.basePath, hashEntry.name);
2829
+ const hashPath = join8(this.basePath, hashEntry.name);
2601
2830
  try {
2602
- for (const sessionEntry of readdirSync3(hashPath, { withFileTypes: true })) {
2831
+ for (const sessionEntry of readdirSync4(hashPath, { withFileTypes: true })) {
2603
2832
  if (!sessionEntry.isDirectory()) continue;
2604
- const sessionPath = join7(hashPath, sessionEntry.name);
2605
- if (existsSync6(join7(sessionPath, "metadata.json")) || existsSync6(join7(sessionPath, "state.json"))) {
2833
+ const sessionPath = join8(hashPath, sessionEntry.name);
2834
+ if (existsSync7(join8(sessionPath, "metadata.json")) || existsSync7(join8(sessionPath, "state.json"))) {
2606
2835
  dirs.push(sessionPath);
2607
2836
  }
2608
2837
  }
@@ -2619,32 +2848,32 @@ var KimiAgent = class extends FileSystemSessionSource {
2619
2848
  */
2620
2849
  resolveSessionSourceResult(sessionDir) {
2621
2850
  try {
2622
- const sessionId = basename4(sessionDir);
2623
- const projectHash = basename4(dirname3(sessionDir));
2624
- const contextFile = join7(sessionDir, "context.jsonl");
2625
- const wireFile = join7(sessionDir, "wire.jsonl");
2626
- const existingContextFile = existsSync6(contextFile) ? contextFile : null;
2627
- const existingWireFile = existsSync6(wireFile) ? wireFile : null;
2851
+ const sessionId = basename5(sessionDir);
2852
+ const projectHash = basename5(dirname4(sessionDir));
2853
+ const contextFile = join8(sessionDir, "context.jsonl");
2854
+ const wireFile = join8(sessionDir, "wire.jsonl");
2855
+ const existingContextFile = existsSync7(contextFile) ? contextFile : null;
2856
+ const existingWireFile = existsSync7(wireFile) ? wireFile : null;
2628
2857
  if (!existingContextFile && !existingWireFile) {
2629
2858
  return skippedSession("missing transcript");
2630
2859
  }
2631
- const statePath = join7(sessionDir, "state.json");
2632
- const metaPath = join7(sessionDir, "metadata.json");
2860
+ const statePath = join8(sessionDir, "state.json");
2861
+ const metaPath = join8(sessionDir, "metadata.json");
2633
2862
  let explicitTitle = "";
2634
2863
  let wireMtime = null;
2635
2864
  let metaFile = "";
2636
- if (existsSync6(statePath)) {
2865
+ if (existsSync7(statePath)) {
2637
2866
  const state = asRecord(JSON.parse(readFileSync3(statePath, "utf-8"))) ?? {};
2638
2867
  explicitTitle = String(state.custom_title ?? "");
2639
2868
  wireMtime = readWireMtime(state);
2640
2869
  metaFile = statePath;
2641
- } else if (existsSync6(metaPath)) {
2870
+ } else if (existsSync7(metaPath)) {
2642
2871
  const meta = asRecord(JSON.parse(readFileSync3(metaPath, "utf-8"))) ?? {};
2643
2872
  explicitTitle = String(meta.title ?? "");
2644
2873
  wireMtime = readWireMtime(meta);
2645
2874
  metaFile = metaPath;
2646
2875
  }
2647
- const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync3(metaFile).mtimeMs : statSync3(sessionDir).mtimeMs;
2876
+ const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync4(metaFile).mtimeMs : statSync4(sessionDir).mtimeMs;
2648
2877
  return parsedSession({
2649
2878
  id: sessionId,
2650
2879
  sourcePath: sessionDir,
@@ -2765,8 +2994,8 @@ var KimiAgent = class extends FileSystemSessionSource {
2765
2994
  return this.buildSessionData(meta, builder, stats);
2766
2995
  }
2767
2996
  getSessionDataFromWire(meta) {
2768
- const wirePath = meta.wireFile ?? join7(meta.sourcePath, "wire.jsonl");
2769
- if (!existsSync6(wirePath)) throw new Error("wire.jsonl is missing");
2997
+ const wirePath = meta.wireFile ?? join8(meta.sourcePath, "wire.jsonl");
2998
+ if (!existsSync7(wirePath)) throw new Error("wire.jsonl is missing");
2770
2999
  const builder = new TranscriptBuilder();
2771
3000
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
2772
3001
  const openToolArgumentBuffer = /* @__PURE__ */ new Map();
@@ -3004,10 +3233,10 @@ var KimiAgent = class extends FileSystemSessionSource {
3004
3233
  total_tokens: 0,
3005
3234
  message_count: 0
3006
3235
  };
3007
- const wirePath = join7(sessionDir, "wire.jsonl");
3008
- if (!existsSync6(wirePath)) return stats;
3009
- const contextPath = join7(sessionDir, "context.jsonl");
3010
- const hasContext = existsSync6(contextPath);
3236
+ const wirePath = join8(sessionDir, "wire.jsonl");
3237
+ if (!existsSync7(wirePath)) return stats;
3238
+ const contextPath = join8(sessionDir, "context.jsonl");
3239
+ const hasContext = existsSync7(contextPath);
3011
3240
  try {
3012
3241
  for (const record of readJsonlFile(wirePath)) {
3013
3242
  const tokenUsage = asRecord(asRecord(record.message)?.usage);
@@ -3337,7 +3566,7 @@ var CODEX_TOOL_TITLE_MAP = {
3337
3566
  subagent: "subagent"
3338
3567
  };
3339
3568
  function extractSessionId(filename) {
3340
- const stem = basename5(filename, ".jsonl");
3569
+ const stem = basename6(filename, ".jsonl");
3341
3570
  const parts = stem.split("-");
3342
3571
  if (parts.length >= 5) {
3343
3572
  return parts.slice(-5).join("-");
@@ -3509,15 +3738,15 @@ var CodexAgent = class extends SingleFileSessionSource {
3509
3738
  sessionIndexPath;
3510
3739
  // ---- BaseAgent implementation ----
3511
3740
  findBasePath() {
3512
- return firstExisting(join8(resolveCodexDataRoot(), "sessions"));
3741
+ return firstExisting(join9(resolveCodexDataRoot(), "sessions"));
3513
3742
  }
3514
3743
  getSessionWatchPlan() {
3515
3744
  const dataRoot = resolveCodexDataRoot();
3516
3745
  return {
3517
3746
  status: "supported",
3518
3747
  targets: [
3519
- { path: join8(dataRoot, "sessions") },
3520
- { path: join8(dataRoot, "session_index.jsonl") }
3748
+ { path: join9(dataRoot, "sessions") },
3749
+ { path: join9(dataRoot, "session_index.jsonl") }
3521
3750
  ]
3522
3751
  };
3523
3752
  }
@@ -3538,7 +3767,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3538
3767
  getSessionData(sessionId) {
3539
3768
  const meta = this.sessionMetaMap.get(sessionId);
3540
3769
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
3541
- if (!existsSync7(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
3770
+ if (!existsSync8(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
3542
3771
  const transcript = new TranscriptBuilder();
3543
3772
  let totalInputTokens = 0;
3544
3773
  let totalOutputTokens = 0;
@@ -3670,7 +3899,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3670
3899
  ]);
3671
3900
  }
3672
3901
  getSessionIndexPath() {
3673
- this.sessionIndexPath ??= join8(resolveCodexDataRoot(), "session_index.jsonl");
3902
+ this.sessionIndexPath ??= join9(resolveCodexDataRoot(), "session_index.jsonl");
3674
3903
  return this.sessionIndexPath;
3675
3904
  }
3676
3905
  // ---- Session index ----
@@ -3756,7 +3985,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3756
3985
  return skippedSession("malformed first record");
3757
3986
  }
3758
3987
  firstPayload = extractPayload(firstRecord);
3759
- createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync4(filePath).mtimeMs;
3988
+ createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync5(filePath).mtimeMs;
3760
3989
  updatedAt = createdAt;
3761
3990
  }
3762
3991
  try {
@@ -3878,7 +4107,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3878
4107
  return skippedSession("malformed first record");
3879
4108
  }
3880
4109
  const payload = extractPayload(firstRecord);
3881
- const stat = statSync4(filePath);
4110
+ const stat = statSync5(filePath);
3882
4111
  const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
3883
4112
  const indexTitle = this.getTitleForSession(sessionId);
3884
4113
  const messageTitle = this.extractTitleFromLines(lines);
@@ -4289,15 +4518,15 @@ function resolveCursorDataRoot() {
4289
4518
  if (override) return override;
4290
4519
  const currentPlatform = platform2();
4291
4520
  if (currentPlatform === "darwin") {
4292
- return firstExisting(join9(homedir3(), "Library", "Application Support", "Cursor", "User"));
4521
+ return firstExisting(join10(homedir3(), "Library", "Application Support", "Cursor", "User"));
4293
4522
  }
4294
4523
  if (currentPlatform === "linux") {
4295
- const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join9(homedir3(), ".config");
4296
- return firstExisting(join9(configRoot, "Cursor", "User"));
4524
+ const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join10(homedir3(), ".config");
4525
+ return firstExisting(join10(configRoot, "Cursor", "User"));
4297
4526
  }
4298
4527
  if (currentPlatform === "win32") {
4299
- const appData = readEnvPath("APPDATA") ?? join9(homedir3(), "AppData", "Roaming");
4300
- return firstExisting(join9(appData, "Cursor", "User"));
4528
+ const appData = readEnvPath("APPDATA") ?? join10(homedir3(), "AppData", "Roaming");
4529
+ return firstExisting(join10(appData, "Cursor", "User"));
4301
4530
  }
4302
4531
  return null;
4303
4532
  }
@@ -4358,6 +4587,24 @@ function parseComposerRow(value) {
4358
4587
  chatMessages: parseChatMessages(record.chatMessages)
4359
4588
  };
4360
4589
  }
4590
+ function composerIdFromBubbleKey(key) {
4591
+ const start = key.indexOf(":") + 1;
4592
+ const end = key.indexOf(":", start);
4593
+ return end === -1 ? key.slice(start) : key.slice(start, end);
4594
+ }
4595
+ function groupBubbleRows(rows) {
4596
+ const byKey = [];
4597
+ for (const row of rows) {
4598
+ const bubble = parseBubbleRow(row.value);
4599
+ if (bubble) byKey.push({ rowId: row.row_id, key: row.key, bubble });
4600
+ }
4601
+ byKey.sort((left, right) => left.key.localeCompare(right.key));
4602
+ return {
4603
+ composerId: rows.length > 0 ? composerIdFromBubbleKey(rows[0].key) : "",
4604
+ byKey,
4605
+ byRowId: [...byKey].sort((left, right) => left.rowId - right.rowId)
4606
+ };
4607
+ }
4361
4608
  function parseBubbleRow(value) {
4362
4609
  const record = safeParseJsonRecord(value);
4363
4610
  if (!record) return null;
@@ -4517,7 +4764,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4517
4764
  if (!isSqliteAvailable()) return null;
4518
4765
  const dataPath = resolveCursorDataRoot();
4519
4766
  if (!dataPath) return null;
4520
- return join9(dataPath, "globalStorage", "state.vscdb");
4767
+ return join10(dataPath, "globalStorage", "state.vscdb");
4521
4768
  }
4522
4769
  getSessionWatchPlan() {
4523
4770
  const dataPath = resolveCursorDataRoot();
@@ -4526,9 +4773,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4526
4773
  targets: dataPath ? [
4527
4774
  {
4528
4775
  root: dataPath,
4529
- path: join9(dataPath, "globalStorage", "state.vscdb")
4776
+ path: join10(dataPath, "globalStorage", "state.vscdb")
4530
4777
  },
4531
- { root: dataPath, path: join9(dataPath, "workspaceStorage") }
4778
+ { root: dataPath, path: join10(dataPath, "workspaceStorage") }
4532
4779
  ] : []
4533
4780
  };
4534
4781
  }
@@ -4540,23 +4787,23 @@ var CursorAgent = class extends DatabaseSessionSource {
4540
4787
  const map = /* @__PURE__ */ new Map();
4541
4788
  const dataPath = resolveCursorDataRoot();
4542
4789
  if (!dataPath) return map;
4543
- const wsStoragePath = join9(dataPath, "workspaceStorage");
4544
- if (!existsSync8(wsStoragePath)) return map;
4790
+ const wsStoragePath = join10(dataPath, "workspaceStorage");
4791
+ if (!existsSync9(wsStoragePath)) return map;
4545
4792
  let entryNames;
4546
4793
  try {
4547
- entryNames = readdirSync4(wsStoragePath);
4794
+ entryNames = readdirSync5(wsStoragePath);
4548
4795
  } catch {
4549
4796
  return map;
4550
4797
  }
4551
4798
  for (const name of entryNames) {
4552
- const wsDir = join9(wsStoragePath, name);
4799
+ const wsDir = join10(wsStoragePath, name);
4553
4800
  try {
4554
- if (!statSync5(wsDir).isDirectory()) continue;
4801
+ if (!statSync6(wsDir).isDirectory()) continue;
4555
4802
  } catch {
4556
4803
  continue;
4557
4804
  }
4558
- const wsJsonPath = join9(wsDir, "workspace.json");
4559
- if (!existsSync8(wsJsonPath)) continue;
4805
+ const wsJsonPath = join10(wsDir, "workspace.json");
4806
+ if (!existsSync9(wsJsonPath)) continue;
4560
4807
  let workspacePath;
4561
4808
  try {
4562
4809
  const data = asRecord(JSON.parse(readFileSync5(wsJsonPath, "utf-8")));
@@ -4566,8 +4813,8 @@ var CursorAgent = class extends DatabaseSessionSource {
4566
4813
  } catch {
4567
4814
  continue;
4568
4815
  }
4569
- const wsDbPath = join9(wsDir, "state.vscdb");
4570
- if (!existsSync8(wsDbPath)) continue;
4816
+ const wsDbPath = join10(wsDir, "state.vscdb");
4817
+ if (!existsSync9(wsDbPath)) continue;
4571
4818
  const wsDb = openDbReadOnly(wsDbPath);
4572
4819
  if (!wsDb) continue;
4573
4820
  try {
@@ -4590,7 +4837,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4590
4837
  }
4591
4838
  isAvailable() {
4592
4839
  this.dbPath = this.findDbPath();
4593
- return this.dbPath !== null && existsSync8(this.dbPath);
4840
+ return this.dbPath !== null && existsSync9(this.dbPath);
4594
4841
  }
4595
4842
  scan(options) {
4596
4843
  if (!this.dbPath) return [];
@@ -4598,13 +4845,15 @@ var CursorAgent = class extends DatabaseSessionSource {
4598
4845
  const dbMarker = perf.start("openDatabase");
4599
4846
  const db = this.openDatabase();
4600
4847
  perf.end(dbMarker);
4601
- if (!db) return [];
4848
+ if (!db) throw new SessionScanError(this.name, "opening the database");
4602
4849
  const wsMarker = perf.start("buildWorkspacePathMap");
4603
4850
  const workspacePathMap = this.buildWorkspacePathMap();
4604
4851
  perf.end(wsMarker);
4605
4852
  try {
4606
4853
  const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'").all();
4607
- const heads = [];
4854
+ const emitted = /* @__PURE__ */ new Map();
4855
+ const pending2 = [];
4856
+ let order = 0;
4608
4857
  options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
4609
4858
  let processed = 0;
4610
4859
  for (const row of rows) {
@@ -4620,8 +4869,8 @@ var CursorAgent = class extends DatabaseSessionSource {
4620
4869
  const fastMessageCount = composer.chatMessages?.length ?? 0;
4621
4870
  const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
4622
4871
  if (options?.fast) {
4623
- const directory2 = workspacePathMap.get(composerId) ?? "";
4624
- const totalCost2 = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
4872
+ const directory = workspacePathMap.get(composerId) ?? "";
4873
+ const totalCost = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
4625
4874
  input: composer.inputTokenCount ?? 0,
4626
4875
  output: composer.outputTokenCount ?? 0
4627
4876
  }) ?? 0;
@@ -4630,20 +4879,21 @@ var CursorAgent = class extends DatabaseSessionSource {
4630
4879
  id: composerId,
4631
4880
  slug: `cursor/${composerId}`,
4632
4881
  title: fastTitle,
4633
- directory: directory2,
4882
+ directory,
4634
4883
  time_created: createdAt,
4635
4884
  time_updated: updatedAt || void 0,
4636
4885
  stats: {
4637
4886
  message_count: fastMessageCount,
4638
4887
  total_input_tokens: composer.inputTokenCount ?? 0,
4639
4888
  total_output_tokens: composer.outputTokenCount ?? 0,
4640
- total_cost: totalCost2,
4641
- cost_source: totalCost2 > 0 ? "estimated" : void 0
4889
+ total_cost: totalCost,
4890
+ cost_source: totalCost > 0 ? "estimated" : void 0
4642
4891
  }
4643
4892
  })
4644
4893
  );
4645
4894
  if (!head) continue;
4646
- heads.push(head);
4895
+ emitted.set(order, head);
4896
+ order += 1;
4647
4897
  this.composerCache.set(composerId, composer);
4648
4898
  this.sessionMetaMap.set(composerId, {
4649
4899
  id: composerId,
@@ -4651,74 +4901,38 @@ var CursorAgent = class extends DatabaseSessionSource {
4651
4901
  });
4652
4902
  continue;
4653
4903
  }
4654
- const requestId = this.extractRequestIdFromBubbles(db, composerId);
4655
- const sessionId = requestId || composerId;
4656
- const parsedMessages = cleanParsedMessages(
4657
- this.loadMessagesFromBubbles(
4658
- db,
4659
- composerId,
4660
- sessionId,
4661
- composer.modelConfig?.modelName ?? composer.model ?? null
4662
- )
4663
- );
4664
- const messages = getParsedSession(
4665
- parsedMessages.length === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession(parsedMessages)
4666
- );
4667
- if (!messages) continue;
4668
- const messageCount = messages.length;
4669
- const title = this.extractTitle(composer, messages);
4670
- const directory = workspacePathMap.get(composerId) ?? "";
4671
- const modelUsageMap = {};
4672
- let totalCost = 0;
4673
- for (const msg of messages) {
4674
- totalCost += msg.cost ?? 0;
4675
- if (msg.model) {
4676
- const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
4677
- if (msgTokens > 0) {
4678
- modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
4679
- }
4680
- }
4681
- }
4682
- const hasModelUsage = Object.keys(modelUsageMap).length > 0;
4683
- heads.push({
4684
- id: sessionId,
4685
- slug: `cursor/${sessionId}`,
4686
- title,
4687
- directory,
4688
- time_created: createdAt,
4689
- time_updated: updatedAt || void 0,
4690
- stats: {
4691
- message_count: messageCount,
4692
- total_input_tokens: composer.inputTokenCount ?? 0,
4693
- total_output_tokens: composer.outputTokenCount ?? 0,
4694
- total_cost: totalCost,
4695
- cost_source: totalCost > 0 ? "estimated" : void 0
4696
- },
4697
- model_usage: hasModelUsage ? modelUsageMap : void 0
4698
- });
4699
- this.composerCache.set(sessionId, composer);
4700
- this.composerCache.set(`__mapping__${composerId}`, {
4701
- sessionId
4702
- });
4703
- if (directory) {
4704
- this.composerCache.set(`__dir__${composerId}`, {
4705
- directory
4706
- });
4707
- }
4708
- this.sessionMetaMap.set(sessionId, {
4709
- id: sessionId,
4710
- sourcePath: this.dbPath || ""
4711
- });
4904
+ pending2.push({ composer, composerId, createdAt, updatedAt, hasSubagents, order });
4905
+ order += 1;
4712
4906
  } catch {
4713
4907
  } finally {
4714
4908
  processed += 1;
4715
- options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
4909
+ options?.onProgress?.({ total: rows.length, processed, sessions: emitted.size });
4716
4910
  }
4717
4911
  }
4912
+ const bubbleMarker = perf.start("cursor:bubbles");
4913
+ const wanted = new Map(pending2.map((entry) => [entry.composerId, entry]));
4914
+ this.forEachComposerBubbles(db, wanted, (bubbles) => {
4915
+ const entry = wanted.get(bubbles.composerId);
4916
+ if (!entry) return;
4917
+ wanted.delete(bubbles.composerId);
4918
+ const head = this.buildScanHead(entry, bubbles, workspacePathMap);
4919
+ if (head) emitted.set(entry.order, head);
4920
+ });
4921
+ for (const entry of wanted.values()) {
4922
+ const head = this.buildScanHead(
4923
+ entry,
4924
+ { composerId: entry.composerId, byKey: [], byRowId: [] },
4925
+ workspacePathMap
4926
+ );
4927
+ if (head) emitted.set(entry.order, head);
4928
+ }
4929
+ perf.end(bubbleMarker);
4930
+ const heads = [...emitted.entries()].sort(([left], [right]) => left - right).map(([, head]) => head);
4931
+ options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
4718
4932
  perf.end(scanMarker);
4719
4933
  return heads;
4720
- } catch {
4721
- return [];
4934
+ } catch (error) {
4935
+ throw new SessionScanError(this.name, "reading composers", { cause: error });
4722
4936
  } finally {
4723
4937
  db.close();
4724
4938
  }
@@ -4806,20 +5020,90 @@ var CursorAgent = class extends DatabaseSessionSource {
4806
5020
  if (!this.dbPath) return null;
4807
5021
  return openDbReadOnly(this.dbPath);
4808
5022
  }
4809
- /** Extract requestId from bubbles for a composer (like agent-dump) */
4810
- extractRequestIdFromBubbles(db, composerId) {
4811
- try {
4812
- const rows = db.prepare("SELECT value FROM cursorDiskKV WHERE key LIKE ? ORDER BY key").all(`bubbleId:${composerId}:%`);
4813
- for (const row of rows) {
4814
- try {
4815
- const bubble = parseBubbleRow(row.value);
4816
- if (bubble?.requestId?.trim()) {
4817
- return bubble.requestId.trim();
4818
- }
4819
- } catch {
5023
+ /**
5024
+ * Streams every bubble row once, in key order, handing each composer's group
5025
+ * to the caller. A key-ordered scan keeps a composer's bubbles contiguous, so
5026
+ * only one group is materialized at a time.
5027
+ */
5028
+ forEachComposerBubbles(db, wanted, onGroup) {
5029
+ if (wanted.size === 0) return;
5030
+ let currentId = null;
5031
+ let group = [];
5032
+ const flush = () => {
5033
+ if (currentId != null && group.length > 0) onGroup(groupBubbleRows(group));
5034
+ group = [];
5035
+ };
5036
+ const rows = db.prepare(
5037
+ "SELECT rowid AS row_id, key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%' ORDER BY key"
5038
+ ).iterate();
5039
+ for (const row of rows) {
5040
+ const composerId = composerIdFromBubbleKey(row.key);
5041
+ if (composerId !== currentId) {
5042
+ flush();
5043
+ currentId = composerId;
5044
+ }
5045
+ if (wanted.has(composerId)) group.push(row);
5046
+ }
5047
+ flush();
5048
+ }
5049
+ /** Builds the head for one composer from bubbles that were parsed once. */
5050
+ buildScanHead(entry, bubbles, workspacePathMap) {
5051
+ const { composer, composerId, createdAt, updatedAt, hasSubagents } = entry;
5052
+ const requestId = this.requestIdFromBubbles(bubbles);
5053
+ const sessionId = requestId || composerId;
5054
+ const parsedMessages = cleanParsedMessages(
5055
+ this.messagesFromBubbles(bubbles, composer.modelConfig?.modelName ?? composer.model ?? null)
5056
+ );
5057
+ const messages = getParsedSession(
5058
+ parsedMessages.length === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession(parsedMessages)
5059
+ );
5060
+ if (!messages) return null;
5061
+ const title = this.extractTitle(composer, messages);
5062
+ const directory = workspacePathMap.get(composerId) ?? "";
5063
+ const modelUsageMap = {};
5064
+ let totalCost = 0;
5065
+ for (const msg of messages) {
5066
+ totalCost += msg.cost ?? 0;
5067
+ if (msg.model) {
5068
+ const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
5069
+ if (msgTokens > 0) {
5070
+ modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
4820
5071
  }
4821
5072
  }
4822
- } catch {
5073
+ }
5074
+ const hasModelUsage = Object.keys(modelUsageMap).length > 0;
5075
+ this.composerCache.set(sessionId, composer);
5076
+ this.composerCache.set(`__mapping__${composerId}`, {
5077
+ sessionId
5078
+ });
5079
+ if (directory) {
5080
+ this.composerCache.set(`__dir__${composerId}`, {
5081
+ directory
5082
+ });
5083
+ }
5084
+ this.sessionMetaMap.set(sessionId, { id: sessionId, sourcePath: this.dbPath || "" });
5085
+ return {
5086
+ id: sessionId,
5087
+ slug: `cursor/${sessionId}`,
5088
+ title,
5089
+ directory,
5090
+ time_created: createdAt,
5091
+ time_updated: updatedAt || void 0,
5092
+ stats: {
5093
+ message_count: messages.length,
5094
+ total_input_tokens: composer.inputTokenCount ?? 0,
5095
+ total_output_tokens: composer.outputTokenCount ?? 0,
5096
+ total_cost: totalCost,
5097
+ cost_source: totalCost > 0 ? "estimated" : void 0
5098
+ },
5099
+ model_usage: hasModelUsage ? modelUsageMap : void 0
5100
+ };
5101
+ }
5102
+ /** First request id in key order, matching what agent-dump reports. */
5103
+ requestIdFromBubbles(bubbles) {
5104
+ for (const entry of bubbles.byKey) {
5105
+ const requestId = entry.bubble.requestId?.trim();
5106
+ if (requestId) return requestId;
4823
5107
  }
4824
5108
  return null;
4825
5109
  }
@@ -4868,18 +5152,26 @@ var CursorAgent = class extends DatabaseSessionSource {
4868
5152
  return 0;
4869
5153
  }
4870
5154
  }
4871
- /** Load messages from bubbles (like agent-dump) */
5155
+ /** Load one composer's bubbles, for the detail path that only needs a single session. */
4872
5156
  loadMessagesFromBubbles(db, composerId, _sessionId, initialModelName) {
4873
- const messages = [];
4874
5157
  try {
4875
- const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ORDER BY rowid ASC").all(`bubbleId:${composerId}:%`);
5158
+ const rows = db.prepare("SELECT rowid AS row_id, key, value FROM cursorDiskKV WHERE key LIKE ?").all(`bubbleId:${composerId}:%`);
5159
+ return this.messagesFromBubbles(groupBubbleRows(rows), initialModelName);
5160
+ } catch {
5161
+ return [];
5162
+ }
5163
+ }
5164
+ /** Build messages from bubbles already parsed once, in insertion order. */
5165
+ messagesFromBubbles(bubbles, initialModelName) {
5166
+ const messages = [];
5167
+ {
4876
5168
  let activeModelName = initialModelName;
4877
5169
  let messageIndex = 0;
4878
- for (const row of rows) {
4879
- try {
4880
- const bubble = parseBubbleRow(row.value);
4881
- if (!bubble || isInternalBubble(bubble)) continue;
4882
- const bubbleId = row.key.split(":").pop() || String(messageIndex);
5170
+ for (const entry of bubbles.byRowId) {
5171
+ {
5172
+ const bubble = entry.bubble;
5173
+ if (isInternalBubble(bubble)) continue;
5174
+ const bubbleId = entry.key.split(":").pop() || String(messageIndex);
4883
5175
  const role = bubble.type === 2 ? "assistant" : "user";
4884
5176
  let timestampMs = 0;
4885
5177
  if (bubble.timingInfo?.clientRpcSendTime) {
@@ -4910,7 +5202,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4910
5202
  const tokens = { input: inputTokens, output: outputTokens };
4911
5203
  const cost = estimateTokenCost(modelName, tokens);
4912
5204
  messages.push({
4913
- id: `cursor-${composerId}-${bubbleId}`,
5205
+ id: `cursor-${bubbles.composerId}-${bubbleId}`,
4914
5206
  role,
4915
5207
  agent: "cursor",
4916
5208
  time_created: timestampMs,
@@ -4924,10 +5216,8 @@ var CursorAgent = class extends DatabaseSessionSource {
4924
5216
  parts
4925
5217
  });
4926
5218
  messageIndex++;
4927
- } catch {
4928
5219
  }
4929
5220
  }
4930
- } catch {
4931
5221
  }
4932
5222
  return messages;
4933
5223
  }
@@ -5063,7 +5353,7 @@ function narrowTimestampMs(field, value) {
5063
5353
  return shaped === void 0 ? 0 : parseTimestampMs3(shaped);
5064
5354
  }
5065
5355
  function extractSessionIdFromFilename(filePath) {
5066
- const stem = basename6(filePath, ".jsonl");
5356
+ const stem = basename7(filePath, ".jsonl");
5067
5357
  const underscore = stem.indexOf("_");
5068
5358
  return underscore >= 0 ? stem.slice(underscore + 1) || stem : stem;
5069
5359
  }
@@ -5119,14 +5409,14 @@ var PiAgent = class extends SingleFileSessionSource {
5119
5409
  displayName = "Pi";
5120
5410
  basePath = null;
5121
5411
  findBasePath() {
5122
- return firstExisting(join10(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
5412
+ return firstExisting(join11(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
5123
5413
  }
5124
5414
  getSessionWatchPlan() {
5125
5415
  const dataRoot = resolvePiDataRoot();
5126
5416
  return {
5127
5417
  status: "supported",
5128
5418
  targets: [
5129
- { root: dataRoot, path: join10(dataRoot, "agent", "sessions") },
5419
+ { root: dataRoot, path: join11(dataRoot, "agent", "sessions") },
5130
5420
  { root: "data/pi", path: "data/pi" }
5131
5421
  ]
5132
5422
  };
@@ -5147,7 +5437,7 @@ var PiAgent = class extends SingleFileSessionSource {
5147
5437
  getSessionData(sessionId) {
5148
5438
  const meta = this.sessionMetaMap.get(sessionId);
5149
5439
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
5150
- if (!existsSync9(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5440
+ if (!existsSync10(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5151
5441
  const parsed = this.parsePiFile(meta.sourcePath);
5152
5442
  const state = this.convertEntries(parsed.pathEntries);
5153
5443
  return {
@@ -5240,7 +5530,7 @@ var PiAgent = class extends SingleFileSessionSource {
5240
5530
  const sessionId = String(header["id"] ?? extractSessionIdFromFilename(filePath)).trim();
5241
5531
  if (!sessionId) throw new Error("missing session id");
5242
5532
  const stat = this.sessionSourceFile(filePath).stat;
5243
- const directory = String(header["cwd"] ?? "").trim() || basename6(filePath, ".jsonl");
5533
+ const directory = String(header["cwd"] ?? "").trim() || basename7(filePath, ".jsonl");
5244
5534
  const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
5245
5535
  const updatedAt = pathEntries.reduce(
5246
5536
  (max, entry) => Math.max(max, getEntryTimestamp(entry)),
@@ -5490,19 +5780,19 @@ var PiAgent = class extends SingleFileSessionSource {
5490
5780
  function resolveZCodeDataRoot() {
5491
5781
  const currentPlatform = platform3();
5492
5782
  if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
5493
- return join11(homedir4(), ".zcode");
5783
+ return join12(homedir4(), ".zcode");
5494
5784
  }
5495
5785
  function findZCodeDbPath() {
5496
5786
  if (!isSqliteAvailable()) return null;
5497
5787
  const dataRoot = resolveZCodeDataRoot();
5498
5788
  if (!dataRoot) return null;
5499
- return firstExisting(join11(dataRoot, "cli", "db", "db.sqlite"));
5789
+ return firstExisting(join12(dataRoot, "cli", "db", "db.sqlite"));
5500
5790
  }
5501
5791
  function getZCodeSessionWatchPlan() {
5502
5792
  const dataRoot = resolveZCodeDataRoot();
5503
5793
  return {
5504
5794
  status: "supported",
5505
- targets: dataRoot ? [{ root: dataRoot, path: join11(dataRoot, "cli", "db", "db.sqlite") }] : []
5795
+ targets: dataRoot ? [{ root: dataRoot, path: join12(dataRoot, "cli", "db", "db.sqlite") }] : []
5506
5796
  };
5507
5797
  }
5508
5798
  var ZCodeAgent = class extends OpenCodeSqliteAgent {
@@ -5573,7 +5863,7 @@ function fallbackDisplayName(input) {
5573
5863
  }
5574
5864
  var realFs = {
5575
5865
  exists(path2) {
5576
- return existsSync10(path2);
5866
+ return existsSync11(path2);
5577
5867
  },
5578
5868
  readText(path2) {
5579
5869
  try {
@@ -5602,23 +5892,6 @@ var MANIFESTS = [
5602
5892
  var PARSEABLE_MANIFESTS = ["package.json", "Cargo.toml", "pyproject.toml"];
5603
5893
  var LOOSE_DIRS = /* @__PURE__ */ new Set(["/tmp", "/private/tmp"]);
5604
5894
  var LOOSE_HOME_DIRS = ["Desktop", "Downloads", "Documents"];
5605
- var PROJECT_IDENTITY_KINDS = /* @__PURE__ */ new Set([
5606
- "git_remote",
5607
- "git_common_dir",
5608
- "manifest_path",
5609
- "synthetic",
5610
- "path",
5611
- "loose"
5612
- ]);
5613
- function isProjectIdentityKind(value) {
5614
- return PROJECT_IDENTITY_KINDS.has(value);
5615
- }
5616
- function getProjectIdentityKey(identity) {
5617
- return `${identity.kind}:${identity.key}`;
5618
- }
5619
- function matchesProjectIdentity(identity, expected) {
5620
- return identity?.kind === expected.kind && identity.key === expected.key;
5621
- }
5622
5895
  function normalizeGitRemote(url) {
5623
5896
  if (!url) return null;
5624
5897
  let value = url.trim().replace(/\.git$/, "");
@@ -6092,16 +6365,16 @@ function setSchemaEnsuredPath(path2) {
6092
6365
  schemaEnsuredPath = path2;
6093
6366
  }
6094
6367
  function getCacheDir2() {
6095
- return join12(homedir6(), ".cache", "codesesh");
6368
+ return join13(homedir6(), ".cache", "codesesh");
6096
6369
  }
6097
6370
  function getCachePath2() {
6098
- return join12(getCacheDir2(), CACHE_FILENAME);
6371
+ return join13(getCacheDir2(), CACHE_FILENAME);
6099
6372
  }
6100
6373
  function getLegacyCachePath() {
6101
- return join12(getCacheDir2(), LEGACY_CACHE_FILENAME);
6374
+ return join13(getCacheDir2(), LEGACY_CACHE_FILENAME);
6102
6375
  }
6103
6376
  function hasCacheStorage() {
6104
- return existsSync11(getCachePath2());
6377
+ return existsSync12(getCachePath2());
6105
6378
  }
6106
6379
  function likePattern(value) {
6107
6380
  return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
@@ -8445,55 +8718,90 @@ function buildFileActivityWhere(options) {
8445
8718
  params
8446
8719
  };
8447
8720
  }
8721
+ var FILE_ACTIVITY_COLUMNS = `
8722
+ fa.agent_name,
8723
+ fa.session_id,
8724
+ fa.project_identity_key,
8725
+ fa.path,
8726
+ fa.kind,
8727
+ fa.count,
8728
+ fa.latest_time,
8729
+ s.slug,
8730
+ s.title,
8731
+ s.directory,
8732
+ s.project_identity_kind,
8733
+ s.project_display_name,
8734
+ s.time_created,
8735
+ s.time_updated,
8736
+ s.message_count,
8737
+ s.total_input_tokens,
8738
+ s.total_output_tokens,
8739
+ s.total_cache_read_tokens,
8740
+ s.total_cache_create_tokens,
8741
+ s.total_cost,
8742
+ s.cost_source,
8743
+ s.total_tokens,
8744
+ s.model_usage_json,
8745
+ s.smart_tags_json,
8746
+ s.smart_tags_source_updated_at
8747
+ `;
8748
+ var FILE_ACTIVITY_JOIN = `
8749
+ FROM session_file_activity fa
8750
+ JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
8751
+ `;
8752
+ var FILE_ACTIVITY_ORDER = "fa.latest_time DESC, fa.count DESC, fa.path";
8448
8753
  function listFileActivity(options = {}) {
8449
8754
  return queryFileActivity(options);
8450
8755
  }
8451
- function queryFileActivity(options, sessionSearchOptions) {
8452
- if (!hasCacheStorage()) {
8453
- return [];
8454
- }
8455
- const filters = buildFileActivityWhere(options);
8456
- const sessionFilters = sessionSearchOptions ? buildSessionSearchFilters(sessionSearchOptions) : { where: "", params: [] };
8457
- const whereClauses = [
8756
+ function fileActivityWhere(query) {
8757
+ const filters = buildFileActivityWhere(query.options);
8758
+ const sessionFilters = query.sessionSearchOptions ? buildSessionSearchFilters(query.sessionSearchOptions) : { where: "", params: [] };
8759
+ const clauses = [
8458
8760
  filters.where.replace(/^WHERE /, ""),
8459
8761
  sessionFilters.where.replace(/^ AND /, "")
8460
8762
  ].filter(Boolean);
8461
- const where = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
8462
- const queryRows = (db) => db.prepare(
8463
- `
8464
- SELECT
8465
- fa.agent_name,
8466
- fa.session_id,
8467
- fa.project_identity_key,
8468
- fa.path,
8469
- fa.kind,
8470
- fa.count,
8471
- fa.latest_time,
8472
- s.slug,
8473
- s.title,
8474
- s.directory,
8475
- s.project_identity_kind,
8476
- s.project_display_name,
8477
- s.time_created,
8478
- s.time_updated,
8479
- s.message_count,
8480
- s.total_input_tokens,
8481
- s.total_output_tokens,
8482
- s.total_cache_read_tokens,
8483
- s.total_cache_create_tokens,
8484
- s.total_cost,
8485
- s.cost_source,
8486
- s.total_tokens,
8487
- s.model_usage_json,
8488
- s.smart_tags_json,
8489
- s.smart_tags_source_updated_at
8490
- FROM session_file_activity fa
8491
- JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
8492
- ${where}
8493
- ORDER BY fa.latest_time DESC, fa.count DESC, fa.path
8494
- LIMIT ?
8495
- `
8496
- ).all(...filters.params, ...sessionFilters.params, options.limit ?? 50);
8763
+ return {
8764
+ where: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
8765
+ params: [...filters.params, ...sessionFilters.params]
8766
+ };
8767
+ }
8768
+ function fileActivitySql(query, where) {
8769
+ if (!query.onePerSession) {
8770
+ return `
8771
+ SELECT ${FILE_ACTIVITY_COLUMNS}
8772
+ ${FILE_ACTIVITY_JOIN}
8773
+ ${where}
8774
+ ORDER BY ${FILE_ACTIVITY_ORDER}
8775
+ LIMIT ?
8776
+ `;
8777
+ }
8778
+ return `
8779
+ SELECT ${FILE_ACTIVITY_COLUMNS}
8780
+ FROM (
8781
+ SELECT
8782
+ fa.rowid AS activity_rowid,
8783
+ ROW_NUMBER() OVER (
8784
+ PARTITION BY fa.agent_name, fa.session_id
8785
+ ORDER BY ${FILE_ACTIVITY_ORDER}
8786
+ ) AS session_rank
8787
+ ${FILE_ACTIVITY_JOIN}
8788
+ ${where}
8789
+ ) ranked
8790
+ JOIN session_file_activity fa ON fa.rowid = ranked.activity_rowid
8791
+ JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
8792
+ WHERE ranked.session_rank = 1
8793
+ ORDER BY ${FILE_ACTIVITY_ORDER}
8794
+ LIMIT ?
8795
+ `;
8796
+ }
8797
+ function queryFileActivity(options, sessionSearchOptions, onePerSession = false) {
8798
+ if (!hasCacheStorage()) {
8799
+ return [];
8800
+ }
8801
+ const query = { options, sessionSearchOptions, onePerSession };
8802
+ const { where, params } = fileActivityWhere(query);
8803
+ const sql = fileActivitySql(query, where);
8804
+ const queryRows = (db) => db.prepare(sql).all(...params, options.limit ?? 50);
8497
8805
  let rows = withCacheDbReadOnly(queryRows);
8498
8806
  if (rows == null && options.path) {
8499
8807
  rows = withCacheDb(queryRows);
@@ -8526,25 +8834,17 @@ function searchFileActivitySessions(query, options = {}) {
8526
8834
  {
8527
8835
  path: path2,
8528
8836
  kind: search.options.fileKind,
8529
- limit: (search.options.limit ?? 50) * 3
8837
+ limit: search.options.limit ?? 50
8530
8838
  },
8531
- search.options
8839
+ search.options,
8840
+ true
8532
8841
  );
8533
- const seen = /* @__PURE__ */ new Set();
8534
- const results = [];
8535
- for (const row of rows) {
8536
- const key = `${row.reference.agentName}/${row.reference.sessionId}`;
8537
- if (seen.has(key)) continue;
8538
- seen.add(key);
8539
- results.push({
8540
- reference: row.reference,
8541
- session: row.session,
8542
- snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
8543
- matchType: "file_path"
8544
- });
8545
- if (results.length >= (search.options.limit ?? 50)) break;
8546
- }
8547
- return results;
8842
+ return rows.map((row) => ({
8843
+ reference: row.reference,
8844
+ session: row.session,
8845
+ snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
8846
+ matchType: "file_path"
8847
+ }));
8548
8848
  }
8549
8849
  var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
8550
8850
  function parseCachedSessionMeta(value) {
@@ -8557,7 +8857,7 @@ function parseCachedSessionMeta(value) {
8557
8857
  }
8558
8858
  function deleteLegacyCacheFile() {
8559
8859
  const legacyPath = getLegacyCachePath();
8560
- if (!existsSync12(legacyPath)) {
8860
+ if (!existsSync13(legacyPath)) {
8561
8861
  return;
8562
8862
  }
8563
8863
  try {
@@ -8757,7 +9057,7 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
8757
9057
  });
8758
9058
  }
8759
9059
  function saveCachedSessions(agentName, sessions, meta = {}) {
8760
- withCacheDb((db) => {
9060
+ const persisted = withCacheDb((db) => {
8761
9061
  const deleteSession = db.prepare(
8762
9062
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
8763
9063
  );
@@ -8809,13 +9109,15 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
8809
9109
  });
8810
9110
  write();
8811
9111
  deleteLegacyCacheFile();
9112
+ return true;
8812
9113
  });
9114
+ return persisted ?? false;
8813
9115
  }
8814
9116
  function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
8815
9117
  if (changes.length === 0 && removedSessionIds.length === 0) {
8816
- return;
9118
+ return true;
8817
9119
  }
8818
- withCacheDb((db) => {
9120
+ const persisted = withCacheDb((db) => {
8819
9121
  const deleteSession = db.prepare(
8820
9122
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
8821
9123
  );
@@ -8861,7 +9163,9 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8861
9163
  });
8862
9164
  write();
8863
9165
  deleteLegacyCacheFile();
9166
+ return true;
8864
9167
  });
9168
+ return persisted ?? false;
8865
9169
  }
8866
9170
  function clearCache() {
8867
9171
  setSchemaEnsuredPath(null);
@@ -8887,11 +9191,11 @@ function clearCache() {
8887
9191
  const walPath = `${cachePath}-wal`;
8888
9192
  const shmPath = `${cachePath}-shm`;
8889
9193
  for (const filePath of [walPath, shmPath]) {
8890
- if (!existsSync12(filePath)) {
9194
+ if (!existsSync13(filePath)) {
8891
9195
  continue;
8892
9196
  }
8893
9197
  try {
8894
- rmSync(filePath, { force: true });
9198
+ rmSync2(filePath, { force: true });
8895
9199
  } catch {
8896
9200
  }
8897
9201
  }
@@ -9231,11 +9535,17 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
9231
9535
  timing.tags = performance.now() - t2;
9232
9536
  const meta = buildAgentCacheMeta(agent);
9233
9537
  if (options.writeCache !== false) {
9234
- if (options.from == null && options.to == null) {
9235
- saveCachedSessions(agent.name, tagged.sessions, meta);
9236
- markAgentFullSyncCompleted(agent.name);
9538
+ const isFullWindow = options.from == null && options.to == null;
9539
+ const persisted = isFullWindow ? saveCachedSessions(agent.name, tagged.sessions, meta) : true;
9540
+ if (persisted) {
9541
+ if (isFullWindow) markAgentFullSyncCompleted(agent.name);
9542
+ markAgentCacheInitialized(agent.name);
9543
+ } else {
9544
+ getCoreDiagnostics()?.warn("cache.save_failed", {
9545
+ agent: agent.name,
9546
+ sessions: tagged.sessions.length
9547
+ });
9237
9548
  }
9238
- markAgentCacheInitialized(agent.name);
9239
9549
  }
9240
9550
  onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
9241
9551
  const filtered = filterSessions(tagged.sessions, options);
@@ -9440,16 +9750,16 @@ function getStateDir() {
9440
9750
  if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
9441
9751
  const currentPlatform = platform4();
9442
9752
  if (currentPlatform === "darwin") {
9443
- return join13(homedir7(), "Library", "Application Support", "codesesh");
9753
+ return join14(homedir7(), "Library", "Application Support", "codesesh");
9444
9754
  }
9445
9755
  if (currentPlatform === "win32") {
9446
9756
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
9447
- return join13(appData ?? join13(homedir7(), "AppData", "Roaming"), "codesesh");
9757
+ return join14(appData ?? join14(homedir7(), "AppData", "Roaming"), "codesesh");
9448
9758
  }
9449
- return join13(process.env.XDG_DATA_HOME ?? join13(homedir7(), ".local", "share"), "codesesh");
9759
+ return join14(process.env.XDG_DATA_HOME ?? join14(homedir7(), ".local", "share"), "codesesh");
9450
9760
  }
9451
9761
  function getStateDbPath() {
9452
- return join13(getStateDir(), STATE_DB_FILENAME);
9762
+ return join14(getStateDir(), STATE_DB_FILENAME);
9453
9763
  }
9454
9764
  function useMemoryStateStore() {
9455
9765
  return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
@@ -9866,17 +10176,6 @@ function getSessionAgentName(session) {
9866
10176
  function getSessionActivityTime(session) {
9867
10177
  return session.time_updated ?? session.time_created;
9868
10178
  }
9869
- function toLocalDateKey(ts) {
9870
- const d = new Date(ts);
9871
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(
9872
- d.getDate()
9873
- ).padStart(2, "0")}`;
9874
- }
9875
- function startOfLocalDay(ts) {
9876
- const d = new Date(ts);
9877
- d.setHours(0, 0, 0, 0);
9878
- return d.getTime();
9879
- }
9880
10179
  function buildDashboard(sessions, options) {
9881
10180
  const { byAgentNames, scope, from, to, agentInfoMap } = options;
9882
10181
  const agentMetrics = /* @__PURE__ */ new Map();
@@ -9897,11 +10196,9 @@ function buildDashboard(sessions, options) {
9897
10196
  const dailyMap = /* @__PURE__ */ new Map();
9898
10197
  const dailyTokenMap = /* @__PURE__ */ new Map();
9899
10198
  if (from != null) {
9900
- const bucketStart = startOfLocalDay(from);
9901
- const bucketDays = Math.floor((startOfLocalDay(to) - bucketStart) / 864e5) + 1;
10199
+ const bucketDays = countCalendarDays(from, to);
9902
10200
  for (let i = 0; i < bucketDays; i += 1) {
9903
- const ts = bucketStart + i * 864e5;
9904
- const key = toLocalDateKey(ts);
10201
+ const key = toCalendarDayKey(addCalendarDays(from, i));
9905
10202
  dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
9906
10203
  dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
9907
10204
  }
@@ -9933,7 +10230,7 @@ function buildDashboard(sessions, options) {
9933
10230
  metric.messages += messageCount;
9934
10231
  metric.tokens += sessionTokens;
9935
10232
  }
9936
- const key = toLocalDateKey(activity);
10233
+ const key = toCalendarDayKey(activity);
9937
10234
  let bucket = dailyMap.get(key);
9938
10235
  if (!bucket) {
9939
10236
  bucket = { date: key, sessions: 0, messages: 0 };
@@ -10016,9 +10313,6 @@ function buildDashboard(sessions, options) {
10016
10313
  recentSessions
10017
10314
  };
10018
10315
  }
10019
- function getProjectGroupKey(identityKind, identityKey) {
10020
- return `${identityKind}:${identityKey}`;
10021
- }
10022
10316
  function emptyMetrics() {
10023
10317
  return { messages: 0, tokens: 0, cost: 0, hasEstimatedCost: false, agentStats: /* @__PURE__ */ new Map() };
10024
10318
  }
@@ -10027,7 +10321,7 @@ function attachProjectMetrics(projects, sessions) {
10027
10321
  for (const session of sessions) {
10028
10322
  const identity = session.project_identity;
10029
10323
  if (!identity) continue;
10030
- const key = getProjectGroupKey(identity.kind, identity.key);
10324
+ const key = getProjectIdentityKey(identity);
10031
10325
  let current = metrics.get(key);
10032
10326
  if (!current) {
10033
10327
  current = emptyMetrics();
@@ -10057,7 +10351,9 @@ function attachProjectMetrics(projects, sessions) {
10057
10351
  }
10058
10352
  }
10059
10353
  return projects.map((project) => {
10060
- const metric = metrics.get(getProjectGroupKey(project.identityKind, project.identityKey));
10354
+ const metric = metrics.get(
10355
+ getProjectIdentityKey({ kind: project.identityKind, key: project.identityKey })
10356
+ );
10061
10357
  return {
10062
10358
  ...project,
10063
10359
  messages: metric?.messages ?? 0,
@@ -10194,9 +10490,16 @@ function searchIndexedSessions(query, textQuery, parsed, options) {
10194
10490
  }
10195
10491
 
10196
10492
  export {
10493
+ mergeSessionsUpdatedEvents,
10494
+ startOfCalendarDay,
10495
+ addCalendarDays,
10496
+ countCalendarDays,
10497
+ isProjectIdentityKind,
10498
+ matchesProjectIdentity,
10197
10499
  normalizeSessionReference,
10198
10500
  formatSessionReference,
10199
10501
  getSessionAgentKey,
10502
+ sessionRoutePath,
10200
10503
  mergeSortedSessions,
10201
10504
  registerAgent,
10202
10505
  createRegisteredAgents,
@@ -10206,10 +10509,14 @@ export {
10206
10509
  diffSessionSources,
10207
10510
  BaseAgent,
10208
10511
  FileSystemSessionSource,
10512
+ ensurePrivateDirectory,
10513
+ restrictPrivateFile,
10514
+ restrictExistingPrivateFiles,
10515
+ getPricingGeneration,
10516
+ publishPendingPricing,
10517
+ hasPendingPricing,
10209
10518
  refreshPricingCache,
10210
10519
  perf,
10211
- isProjectIdentityKind,
10212
- matchesProjectIdentity,
10213
10520
  createProjectScopeMatcher,
10214
10521
  matchesProjectScope,
10215
10522
  getSmartTagSourceTimestamp,
@@ -10246,10 +10553,9 @@ export {
10246
10553
  upsertSessionAlias,
10247
10554
  deleteSessionAlias,
10248
10555
  getSessionActivityTime,
10249
- startOfLocalDay,
10250
10556
  buildDashboard,
10251
10557
  attachProjectMetrics,
10252
10558
  executeSessionSearch,
10253
10559
  filterSessionSearchCandidates
10254
10560
  };
10255
- //# sourceMappingURL=chunk-7APNDHQ6.js.map
10561
+ //# sourceMappingURL=chunk-HIYT2ZJL.js.map