codesesh 0.17.0 → 0.19.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 +6 -4
  2. package/dist/{chunk-7APNDHQ6.js → chunk-G2BTNW3C.js} +2262 -495
  3. package/dist/chunk-G2BTNW3C.js.map +1 -0
  4. package/dist/{chunk-A4U2SMJJ.js → chunk-KD3DGWZY.js} +20 -12
  5. package/dist/chunk-KD3DGWZY.js.map +1 -0
  6. package/dist/{dist-KEPJFHOC.js → dist-ALXOBKV2.js} +26 -4
  7. package/dist/index.js +382 -114
  8. package/dist/index.js.map +1 -1
  9. package/dist/scan-refresh-worker.js +243 -24
  10. package/dist/scan-refresh-worker.js.map +1 -1
  11. package/dist/search-index-worker.js +73 -39
  12. package/dist/search-index-worker.js.map +1 -1
  13. package/dist/smart-tag-worker.js +2 -2
  14. package/dist/web/assets/AgentIcon-CEj7HO1m.js +1 -0
  15. package/dist/web/assets/BookmarkButton-DLEnIyeE.js +1 -0
  16. package/dist/web/assets/Dashboard-DUG0IFcy.js +39 -0
  17. package/dist/web/assets/DialogTitle-Dv6DQPIb.js +1 -0
  18. package/dist/web/assets/InteractiveReceipt-xnRqJvfd.js +1 -0
  19. package/dist/web/assets/PrismHighlighter-C7OfXHq2.js +6 -0
  20. package/dist/web/assets/Projects-BMaZc6J5.js +1 -0
  21. package/dist/web/assets/SearchFilterBar-iP6pzlZo.js +1 -0
  22. package/dist/web/assets/SearchResultsPanel-RuiLPkbP.js +1 -0
  23. package/dist/web/assets/SessionDetail-De1Wl3mu.js +54 -0
  24. package/dist/web/assets/index-B-CV7pFu.css +2 -0
  25. package/dist/web/assets/index-CQI8F_8X.js +1930 -0
  26. package/dist/web/assets/index.dom-CCLJ9_FR.js +1 -0
  27. package/dist/web/assets/jsx-runtime-n5LQ9ujS.js +1 -0
  28. package/dist/web/assets/session-indexes-BuxBafvh.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 +10 -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-ALXOBKV2.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-B67H5WZJ.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 sessionKey2 = (agentName, sessionId) => `${agentName}\0${sessionId}`;
127
+ const addChanged = (item) => {
128
+ const key = sessionKey2(item.reference.agentName, item.reference.sessionId);
129
+ removedSessionRefs.delete(key);
130
+ changedSessionHeads.set(key, item);
131
+ };
132
+ const addRemoved = (item) => {
133
+ const key = sessionKey2(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
  }
@@ -176,6 +253,44 @@ function mergeSortedSessions(shards) {
176
253
  }
177
254
  return merged;
178
255
  }
256
+ function referenceKey(reference) {
257
+ return `${reference.agentName.trim().toLowerCase()}\0${reference.sessionId}`;
258
+ }
259
+ function sessionKey(session) {
260
+ return `${getSessionAgentKey(session)}\0${session.id}`;
261
+ }
262
+ function hasActivityInWindow(session, from, to) {
263
+ const activity = session.time_updated ?? session.time_created;
264
+ return (from == null || activity >= from) && (to == null || activity <= to);
265
+ }
266
+ function isChildSession(session) {
267
+ return session.parent_reference != null;
268
+ }
269
+ function getRootSessions(sessions) {
270
+ return sessions.filter((session) => !session.parent_reference);
271
+ }
272
+ function filterSessionTreeByActivityWindow(sessions, from, to) {
273
+ if (from == null && to == null) return sessions;
274
+ const available = new Set(sessions.map(sessionKey));
275
+ const childrenByParent = /* @__PURE__ */ new Map();
276
+ for (const session of sessions) {
277
+ const parent = session.parent_reference;
278
+ if (!parent || !available.has(referenceKey(parent))) continue;
279
+ const parentKey = referenceKey(parent);
280
+ const children = childrenByParent.get(parentKey);
281
+ if (children) children.push(sessionKey(session));
282
+ else childrenByParent.set(parentKey, [sessionKey(session)]);
283
+ }
284
+ const visible = /* @__PURE__ */ new Set();
285
+ const pending2 = getRootSessions(sessions).filter((session) => hasActivityInWindow(session, from, to)).map(sessionKey);
286
+ while (pending2.length > 0) {
287
+ const key = pending2.pop();
288
+ if (visible.has(key)) continue;
289
+ visible.add(key);
290
+ for (const childKey of childrenByParent.get(key) ?? []) pending2.push(childKey);
291
+ }
292
+ return sessions.filter((session) => visible.has(sessionKey(session)));
293
+ }
179
294
  var SAMPLE_SESSION_HEAD = {
180
295
  id: "session-1",
181
296
  slug: "claudecode/session-1",
@@ -253,8 +368,8 @@ var SAMPLE_DASHBOARD_DATA = {
253
368
  };
254
369
 
255
370
  // ../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";
371
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
372
+ import { join as join5, basename as basename3, dirname as dirname2 } from "path";
258
373
  import { existsSync, readdirSync, statSync } from "fs";
259
374
  import { join } from "path";
260
375
  import { existsSync as existsSync2 } from "fs";
@@ -263,38 +378,50 @@ import { join as join2 } from "path";
263
378
  import { closeSync, openSync, readSync } from "fs";
264
379
  import { StringDecoder } from "string_decoder";
265
380
  import { basename } from "path";
266
- import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
381
+ import { existsSync as existsSync4, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
267
382
  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";
383
+ import { join as join4 } from "path";
384
+ import { chmodSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
385
+ import { basename as basename2, dirname, join as join3 } from "path";
386
+ import { join as join7 } from "path";
387
+ import { existsSync as existsSync6 } from "fs";
388
+ import { basename as basename4, dirname as dirname3, join as join6 } from "path";
272
389
  import { createRequire } from "module";
273
390
  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";
391
+ import { existsSync as existsSync7, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
392
+ import { join as join8, basename as basename5, dirname as dirname4 } from "path";
393
+ import { existsSync as existsSync8, readFileSync as readFileSync4, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
394
+ import { basename as basename6, dirname as dirname5, join as join9, resolve } from "path";
395
+ import {
396
+ closeSync as closeSync2,
397
+ existsSync as existsSync9,
398
+ openSync as openSync2,
399
+ readdirSync as readdirSync6,
400
+ readFileSync as readFileSync5,
401
+ readSync as readSync2,
402
+ statSync as statSync6
403
+ } from "fs";
404
+ import { join as join10, basename as basename7 } from "path";
405
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync7 } from "fs";
279
406
  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";
407
+ import { join as join11, normalize } from "path";
408
+ import { existsSync as existsSync11 } from "fs";
409
+ import { basename as basename8, join as join12 } from "path";
283
410
  import { homedir as homedir4, platform as platform3 } from "os";
284
- import { join as join11 } from "path";
411
+ import { join as join13 } from "path";
285
412
  import { availableParallelism } from "os";
286
413
  import { Worker } from "worker_threads";
287
- import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
414
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
288
415
  import { spawnSync } from "child_process";
289
416
  import * as os from "os";
290
417
  import * as path from "path";
291
- 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";
418
+ import { resolve as resolve2, sep } from "path";
419
+ import { existsSync as existsSync14, rmSync as rmSync2, unlinkSync } from "fs";
420
+ import { existsSync as existsSync13 } from "fs";
421
+ import { join as join14 } from "path";
295
422
  import { homedir as homedir6 } from "os";
296
423
  import { homedir as homedir7, platform as platform4 } from "os";
297
- import { join as join13 } from "path";
424
+ import { join as join15 } from "path";
298
425
  var registrations = [];
299
426
  function registerAgent(reg) {
300
427
  registrations.push(reg);
@@ -389,6 +516,9 @@ function diffSessionSources(refs, cachedSessions, cachedMeta, options) {
389
516
  return { changedIds, removedIds };
390
517
  }
391
518
  var BaseAgent = class {
519
+ filterCachedSessions(sessions) {
520
+ return sessions;
521
+ }
392
522
  getUri(sessionId) {
393
523
  return `${this.name}://${sessionId}`;
394
524
  }
@@ -396,6 +526,14 @@ var BaseAgent = class {
396
526
  var FileSystemSessionSource = class extends BaseAgent {
397
527
  sessionMetaMap = /* @__PURE__ */ new Map();
398
528
  sourceFileStats = /* @__PURE__ */ new Map();
529
+ /**
530
+ * 变更集合扩展:当某些会话变更会影响其他会话的派生数据时
531
+ * (如 subagent 文件变更需要父会话重新聚合 token 统计),
532
+ * 子类返回需要一并重解析的会话 ID 集合。默认无关联,原样返回。
533
+ */
534
+ expandChangedSessionIds(changedIds, _refs) {
535
+ return changedIds;
536
+ }
399
537
  scan(options) {
400
538
  const sources = this.listSessionSources(options);
401
539
  const sessions = [];
@@ -493,10 +631,11 @@ var FileSystemSessionSource = class extends BaseAgent {
493
631
  * refs 未传时回退为自行枚举,供独立调用方(如测试)沿用旧行为。
494
632
  */
495
633
  incrementalScan(cachedSessions, changedIds, refs) {
634
+ const sources = refs ?? this.listSessionSources();
496
635
  const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
497
- const changedSet = new Set(changedIds);
636
+ const changedSet = new Set(this.expandChangedSessionIds(changedIds, sources));
498
637
  const currentIds = /* @__PURE__ */ new Set();
499
- for (const ref of refs ?? this.listSessionSources()) {
638
+ for (const ref of sources) {
500
639
  currentIds.add(ref.sessionId);
501
640
  if (!changedSet.has(ref.sessionId)) continue;
502
641
  const head = this.scanSessionSource(ref.sourcePath);
@@ -547,8 +686,41 @@ var SingleFileSessionSource = class extends FileSystemSessionSource {
547
686
  };
548
687
  }
549
688
  };
689
+ var SessionScanError = class extends Error {
690
+ constructor(agentName, stage, options) {
691
+ super(`${agentName} session scan failed while ${stage}`, options);
692
+ this.agentName = agentName;
693
+ this.stage = stage;
694
+ this.name = "SessionScanError";
695
+ }
696
+ agentName;
697
+ stage;
698
+ };
699
+ function sqliteSourceFiles(dbPath) {
700
+ return [dbPath, `${dbPath}-wal`];
701
+ }
702
+ function statOrNull(path2) {
703
+ try {
704
+ return statSync(path2);
705
+ } catch {
706
+ return null;
707
+ }
708
+ }
709
+ function sqliteSourceFingerprint(dbPath) {
710
+ return sqliteSourceFiles(dbPath).map((path2) => {
711
+ const stats = statOrNull(path2);
712
+ return stats ? `${stats.size}:${Math.round(stats.mtimeMs)}` : "-";
713
+ }).join("|");
714
+ }
715
+ function latestSqliteSourceMtime(dbPath) {
716
+ return sqliteSourceFiles(dbPath).reduce((latest, path2) => {
717
+ const stats = statOrNull(path2);
718
+ return stats && stats.mtimeMs > latest ? stats.mtimeMs : latest;
719
+ }, 0);
720
+ }
550
721
  var DatabaseSessionSource = class extends BaseAgent {
551
722
  sessionMetaMap = /* @__PURE__ */ new Map();
723
+ lastSourceFingerprint = null;
552
724
  /** 记录单个会话的缓存 meta(sourcePath = dbPath)。 */
553
725
  rememberSession(sessionId) {
554
726
  const dbPath = this.getDatabasePath();
@@ -562,7 +734,12 @@ var DatabaseSessionSource = class extends BaseAgent {
562
734
  this.sessionMetaMap = meta;
563
735
  }
564
736
  /**
565
- * 变更检测:数据库内部变更难以按行定位,按库文件 mtime 判定。
737
+ * 变更检测:数据库内部变更难以按行定位,按库文件集合的指纹判定。
738
+ *
739
+ * In WAL mode a commit appends to the sidecar and leaves the main file
740
+ * untouched until checkpoint, so watching the database alone misses recent
741
+ * writes entirely. Size is part of the fingerprint because an uncheckpointed
742
+ * commit may land in the same millisecond as the previous one.
566
743
  */
567
744
  checkForChanges(sinceTimestamp, _cachedSessions) {
568
745
  const dbPath = this.getDatabasePath();
@@ -570,7 +747,10 @@ var DatabaseSessionSource = class extends BaseAgent {
570
747
  return { hasChanges: false, timestamp: Date.now() };
571
748
  }
572
749
  try {
573
- const hasChanges = statSync(dbPath).mtimeMs > sinceTimestamp;
750
+ const fingerprint = sqliteSourceFingerprint(dbPath);
751
+ const previous = this.lastSourceFingerprint;
752
+ this.lastSourceFingerprint = fingerprint;
753
+ const hasChanges = previous == null ? latestSqliteSourceMtime(dbPath) > sinceTimestamp : fingerprint !== previous;
574
754
  return {
575
755
  hasChanges,
576
756
  timestamp: Date.now()
@@ -630,17 +810,17 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
630
810
  try {
631
811
  const buffer = Buffer.alloc(chunkBytes);
632
812
  const decoder = new StringDecoder("utf8");
633
- let pending = [];
813
+ let pending2 = [];
634
814
  let bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
635
815
  while (bytesRead > 0) {
636
816
  const decoded = decoder.write(buffer.subarray(0, bytesRead));
637
817
  const lastBreak = decoded.lastIndexOf("\n");
638
818
  if (lastBreak === -1) {
639
- if (decoded) pending.push(decoded);
819
+ if (decoded) pending2.push(decoded);
640
820
  } else {
641
- pending.push(decoded.slice(0, lastBreak));
642
- const complete = pending.join("");
643
- pending = [decoded.slice(lastBreak + 1)];
821
+ pending2.push(decoded.slice(0, lastBreak));
822
+ const complete = pending2.join("");
823
+ pending2 = [decoded.slice(lastBreak + 1)];
644
824
  for (const line of complete.split("\n")) {
645
825
  const trimmed = line.trim();
646
826
  if (trimmed) yield trimmed;
@@ -648,8 +828,8 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
648
828
  }
649
829
  bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
650
830
  }
651
- pending.push(decoder.end());
652
- const tail = pending.join("").trim();
831
+ pending2.push(decoder.end());
832
+ const tail = pending2.join("").trim();
653
833
  if (tail) yield tail;
654
834
  } finally {
655
835
  closeSync(fd);
@@ -833,6 +1013,56 @@ var aliases_default = {
833
1013
  "gpt-5.1-codex-high": "gpt-5.3-codex",
834
1014
  "gpt-5.2-low": "gpt-5.2"
835
1015
  };
1016
+ var PRIVATE_DIR_MODE = 448;
1017
+ var PRIVATE_FILE_MODE = 384;
1018
+ var SQLITE_SIDECAR_SUFFIXES = ["-wal", "-shm", "-journal"];
1019
+ var isPosix = process.platform !== "win32";
1020
+ function reportChmodFailure(path2, error) {
1021
+ getCoreDiagnostics()?.warn("storage.permissions_failed", {
1022
+ path: path2,
1023
+ message: error instanceof Error ? error.message : String(error)
1024
+ });
1025
+ }
1026
+ function restrict(path2, mode) {
1027
+ if (!isPosix || !existsSync3(path2)) return;
1028
+ try {
1029
+ if ((statSync2(path2).mode & 511) === mode) return;
1030
+ chmodSync(path2, mode);
1031
+ } catch (error) {
1032
+ reportChmodFailure(path2, error);
1033
+ }
1034
+ }
1035
+ function ensurePrivateDirectory(path2) {
1036
+ mkdirSync(path2, { recursive: true, mode: PRIVATE_DIR_MODE });
1037
+ restrict(path2, PRIVATE_DIR_MODE);
1038
+ }
1039
+ function restrictPrivateFile(path2) {
1040
+ restrict(path2, PRIVATE_FILE_MODE);
1041
+ }
1042
+ function restrictExistingPrivateFiles(directory, owns) {
1043
+ try {
1044
+ for (const entry of readdirSync2(directory)) {
1045
+ if (owns(entry)) restrictPrivateFile(join3(directory, entry));
1046
+ }
1047
+ } catch (error) {
1048
+ reportChmodFailure(directory, error);
1049
+ }
1050
+ }
1051
+ var sweptBackupsFor = /* @__PURE__ */ new Set();
1052
+ function restrictExistingBackups(dbPath) {
1053
+ if (sweptBackupsFor.has(dbPath)) return;
1054
+ sweptBackupsFor.add(dbPath);
1055
+ const prefix = `${basename2(dbPath)}.`;
1056
+ restrictExistingPrivateFiles(
1057
+ dirname(dbPath),
1058
+ (name) => name.startsWith(prefix) && name.endsWith(".bak")
1059
+ );
1060
+ }
1061
+ function restrictPrivateDatabase(dbPath) {
1062
+ restrictPrivateFile(dbPath);
1063
+ for (const suffix of SQLITE_SIDECAR_SUFFIXES) restrictPrivateFile(`${dbPath}${suffix}`);
1064
+ restrictExistingBackups(dbPath);
1065
+ }
836
1066
  var snapshot_default = {
837
1067
  "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
838
1068
  "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
@@ -879,7 +1109,9 @@ var snapshot_default = {
879
1109
  var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
880
1110
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
881
1111
  var WEB_SEARCH_COST = 0.01;
882
- var pricingCache = loadSnapshot();
1112
+ var REFRESH_TIMEOUT_MS = 1e4;
1113
+ var published = { id: 1, pricing: loadSnapshot() };
1114
+ var pending = null;
883
1115
  loadDiskCache();
884
1116
  function normalizeKey(key) {
885
1117
  return key.trim().toLowerCase();
@@ -888,10 +1120,10 @@ function costNumber(value, fallback) {
888
1120
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
889
1121
  }
890
1122
  function getCacheDir() {
891
- return join3(homedir2(), ".cache", "codesesh");
1123
+ return join4(homedir2(), ".cache", "codesesh");
892
1124
  }
893
1125
  function getCachePath() {
894
- return join3(getCacheDir(), "litellm-pricing.json");
1126
+ return join4(getCacheDir(), "litellm-pricing.json");
895
1127
  }
896
1128
  function loadSnapshot() {
897
1129
  const map = /* @__PURE__ */ new Map();
@@ -957,7 +1189,7 @@ function parseLiteLLMData(data) {
957
1189
  }
958
1190
  function loadDiskCache() {
959
1191
  const path2 = getCachePath();
960
- if (!existsSync3(path2)) return;
1192
+ if (!existsSync4(path2)) return;
961
1193
  try {
962
1194
  const cached = JSON.parse(readFileSync(path2, "utf-8"));
963
1195
  if (Date.now() - cached.timestamp <= CACHE_TTL_MS) {
@@ -967,20 +1199,51 @@ function loadDiskCache() {
967
1199
  if (!pricing) continue;
968
1200
  next.set(normalizeKey(name), pricing);
969
1201
  }
970
- pricingCache = next;
1202
+ published = { id: published.id + 1, pricing: next };
971
1203
  }
972
1204
  } catch {
973
1205
  }
974
1206
  }
975
1207
  function getPricingRegistry() {
976
- return pricingCache;
1208
+ return published.pricing;
1209
+ }
1210
+ function getPricingGeneration() {
1211
+ return published;
1212
+ }
1213
+ function publishPendingPricing() {
1214
+ if (!pending) return false;
1215
+ published = { id: published.id + 1, pricing: pending };
1216
+ pending = null;
1217
+ getCoreDiagnostics()?.info?.("pricing.generation.published", {
1218
+ generation: published.id,
1219
+ models: published.pricing.size
1220
+ });
1221
+ return true;
1222
+ }
1223
+ function hasPendingPricing() {
1224
+ return pending !== null;
977
1225
  }
978
1226
  function hasBillablePricing(pricing) {
979
1227
  return pricing.inputCostPerToken > 0 || pricing.outputCostPerToken > 0 || pricing.cacheReadCostPerToken > 0 || pricing.cacheCreateCostPerToken > 0;
980
1228
  }
981
- async function refreshPricingCache() {
1229
+ function writeDiskCacheAtomically(path2, pricing) {
1230
+ const temporaryPath = `${path2}.${process.pid}.tmp`;
1231
+ const payload = JSON.stringify({ timestamp: Date.now(), data: Object.fromEntries(pricing) });
1232
+ try {
1233
+ ensurePrivateDirectory(getCacheDir());
1234
+ writeFileSync(temporaryPath, payload);
1235
+ restrictPrivateFile(temporaryPath);
1236
+ renameSync(temporaryPath, path2);
1237
+ } catch (error) {
1238
+ rmSync(temporaryPath, { force: true });
1239
+ getCoreDiagnostics()?.warn("pricing.cache_write_failed", {
1240
+ message: error instanceof Error ? error.message : String(error)
1241
+ });
1242
+ }
1243
+ }
1244
+ async function refreshPricingCache(options = {}) {
982
1245
  const path2 = getCachePath();
983
- if (existsSync3(path2)) {
1246
+ if (existsSync4(path2)) {
984
1247
  try {
985
1248
  const cached = JSON.parse(readFileSync(path2, "utf-8"));
986
1249
  if (typeof cached.timestamp === "number" && Date.now() - cached.timestamp <= CACHE_TTL_MS) {
@@ -989,8 +1252,11 @@ async function refreshPricingCache() {
989
1252
  } catch {
990
1253
  }
991
1254
  }
1255
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? REFRESH_TIMEOUT_MS);
1256
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
1257
+ getCoreDiagnostics()?.info?.("pricing.refresh.started", { generation: published.id });
992
1258
  try {
993
- const response = await fetch(LITELLM_URL);
1259
+ const response = await fetch(LITELLM_URL, { signal });
994
1260
  if (!response.ok) return false;
995
1261
  const data = await response.json();
996
1262
  const remote = parseLiteLLMData(data);
@@ -999,11 +1265,19 @@ async function refreshPricingCache() {
999
1265
  for (const [name, pricing] of remote.entries()) {
1000
1266
  next.set(name, pricing);
1001
1267
  }
1002
- pricingCache = next;
1003
- mkdirSync(getCacheDir(), { recursive: true });
1004
- writeFileSync(path2, JSON.stringify({ timestamp: Date.now(), data: Object.fromEntries(next) }));
1268
+ pending = next;
1269
+ writeDiskCacheAtomically(path2, next);
1270
+ getCoreDiagnostics()?.info?.("pricing.refresh.completed", {
1271
+ generation: published.id,
1272
+ models: next.size
1273
+ });
1005
1274
  return true;
1006
- } catch {
1275
+ } catch (error) {
1276
+ getCoreDiagnostics()?.warn("pricing.refresh.failed", {
1277
+ generation: published.id,
1278
+ aborted: signal.aborted,
1279
+ message: error instanceof Error ? error.message : String(error)
1280
+ });
1007
1281
  return false;
1008
1282
  }
1009
1283
  }
@@ -1313,7 +1587,7 @@ var TranscriptBuilder = class {
1313
1587
  };
1314
1588
  }
1315
1589
  };
1316
- var HEAD_INDEX_VERSION = "claudecode-head-v2";
1590
+ var HEAD_INDEX_VERSION = "claudecode-head-v4";
1317
1591
  function resolveClaudeCodeDataRoot() {
1318
1592
  return resolveHomePath("CLAUDE_CONFIG_DIR", ".claude");
1319
1593
  }
@@ -1369,23 +1643,27 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1369
1643
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1370
1644
  sessionsIndexCache = {};
1371
1645
  sessionsIndexMtime = {};
1646
+ childContextsBySource = /* @__PURE__ */ new Map();
1647
+ childContextCache = /* @__PURE__ */ new Map();
1648
+ childSessionIdByToolUseId = /* @__PURE__ */ new Map();
1649
+ childIndexReady = false;
1372
1650
  findBasePath() {
1373
- return firstExisting(join4(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1651
+ return firstExisting(join5(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1374
1652
  }
1375
1653
  getSessionWatchPlan() {
1376
1654
  const dataRoot = resolveClaudeCodeDataRoot();
1377
1655
  return {
1378
1656
  status: "supported",
1379
- targets: [{ root: dataRoot, path: join4(dataRoot, "projects") }, { path: "data/claudecode" }]
1657
+ targets: [{ root: dataRoot, path: join5(dataRoot, "projects") }, { path: "data/claudecode" }]
1380
1658
  };
1381
1659
  }
1382
1660
  isAvailable() {
1383
1661
  this.basePath = this.findBasePath();
1384
1662
  if (!this.basePath) return false;
1385
1663
  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"))) {
1664
+ for (const entry of readdirSync3(this.basePath)) {
1665
+ const dir = join5(this.basePath, entry);
1666
+ if (existsSync5(dir) && readdirSync3(dir).some((f) => f.endsWith(".jsonl"))) {
1389
1667
  return true;
1390
1668
  }
1391
1669
  }
@@ -1401,33 +1679,119 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1401
1679
  const indexPath = this.getSessionsIndexPath(projectDir);
1402
1680
  indexMtimes.set(projectDir, this.readFileMtimeMs(indexPath));
1403
1681
  }
1404
- return this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1405
- recursive: false,
1406
- scanWindow: options
1407
- }).map(({ file, stat }) => ({
1408
- sessionId: basename2(file, ".jsonl"),
1409
- sourcePath: file,
1410
- fingerprint: this.sourceFingerprint(stat, indexMtimes.get(dirname(file)) ?? null)
1411
- }));
1682
+ const projectDirSet = new Set(projectDirs);
1683
+ const allSources = this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1684
+ recursive: true
1685
+ });
1686
+ this.indexChildContexts(allSources, projectDirs);
1687
+ const indexedSources = allSources.flatMap((source) => {
1688
+ const child = this.childContextsBySource.get(source.file);
1689
+ if (!child && !projectDirSet.has(dirname2(source.file))) return [];
1690
+ return [
1691
+ {
1692
+ source,
1693
+ sessionId: child?.sessionId ?? basename3(source.file, ".jsonl"),
1694
+ child
1695
+ }
1696
+ ];
1697
+ });
1698
+ let selectedSources = indexedSources.filter(
1699
+ ({ source }) => matchesScanWindow(source.stat.mtimeMs, options)
1700
+ );
1701
+ if (options?.from != null || options?.to != null) {
1702
+ const childrenByParent = /* @__PURE__ */ new Map();
1703
+ for (const indexed of indexedSources) {
1704
+ const parentId = indexed.child?.parentSessionId;
1705
+ if (!parentId) continue;
1706
+ const children = childrenByParent.get(parentId);
1707
+ if (children) children.push(indexed);
1708
+ else childrenByParent.set(parentId, [indexed]);
1709
+ }
1710
+ const windowSelectedById = new Map(
1711
+ selectedSources.map((indexed) => [indexed.sessionId, indexed])
1712
+ );
1713
+ const connectedSelected = /* @__PURE__ */ new Map();
1714
+ const acceptedIds = /* @__PURE__ */ new Set();
1715
+ const visitingIds = /* @__PURE__ */ new Set();
1716
+ const hasSelectedParent = (sessionId) => {
1717
+ if (acceptedIds.has(sessionId)) return true;
1718
+ if (visitingIds.has(sessionId)) return false;
1719
+ const indexed = windowSelectedById.get(sessionId);
1720
+ if (!indexed) return false;
1721
+ const parentId = indexed.child?.parentSessionId;
1722
+ if (!parentId) {
1723
+ acceptedIds.add(sessionId);
1724
+ return true;
1725
+ }
1726
+ visitingIds.add(sessionId);
1727
+ const connected = hasSelectedParent(parentId);
1728
+ visitingIds.delete(sessionId);
1729
+ if (connected) acceptedIds.add(sessionId);
1730
+ return connected;
1731
+ };
1732
+ for (const indexed of selectedSources) {
1733
+ if (hasSelectedParent(indexed.sessionId)) {
1734
+ connectedSelected.set(indexed.sessionId, indexed);
1735
+ }
1736
+ }
1737
+ if (options?.includeRelatedSessions !== false) {
1738
+ const pending2 = [...connectedSelected.values()].filter(({ child }) => !child?.parentSessionId).map(({ sessionId }) => sessionId);
1739
+ while (pending2.length > 0) {
1740
+ const parentId = pending2.pop();
1741
+ for (const child of childrenByParent.get(parentId) ?? []) {
1742
+ if (connectedSelected.has(child.sessionId)) continue;
1743
+ connectedSelected.set(child.sessionId, child);
1744
+ pending2.push(child.sessionId);
1745
+ }
1746
+ }
1747
+ }
1748
+ selectedSources = [...connectedSelected.values()];
1749
+ }
1750
+ return selectedSources.map(({ source: { file, stat }, sessionId, child }) => {
1751
+ const projectDir = child?.projectDir ?? dirname2(file);
1752
+ return {
1753
+ sessionId,
1754
+ sourcePath: file,
1755
+ fingerprint: this.sourceFingerprint(
1756
+ stat,
1757
+ indexMtimes.get(projectDir) ?? null,
1758
+ child?.metaMtimeMs
1759
+ )
1760
+ };
1761
+ });
1762
+ }
1763
+ setSessionMetaMap(meta) {
1764
+ super.setSessionMetaMap(meta);
1765
+ this.childContextsBySource.clear();
1766
+ this.childSessionIdByToolUseId.clear();
1767
+ this.childIndexReady = false;
1412
1768
  }
1413
1769
  parseFileSessionHead(sourcePath) {
1414
- const projectDir = dirname(sourcePath);
1415
- return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir));
1770
+ const child = this.getChildContext(sourcePath);
1771
+ const projectDir = child?.projectDir ?? dirname2(sourcePath);
1772
+ return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir, child));
1416
1773
  }
1417
1774
  getSessionData(sessionId) {
1418
1775
  const meta = this.sessionMetaMap.get(sessionId);
1419
1776
  if (!meta) {
1420
1777
  throw new Error(`Session not found: ${sessionId}`);
1421
1778
  }
1422
- if (!existsSync4(meta.sourcePath)) {
1779
+ if (!existsSync5(meta.sourcePath)) {
1423
1780
  throw new Error(`Session file missing: ${meta.sourcePath}`);
1424
1781
  }
1782
+ this.ensureChildIndex();
1425
1783
  const builder = new TranscriptBuilder();
1426
1784
  const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
1427
1785
  const countedUsageKeys = /* @__PURE__ */ new Set();
1428
1786
  for (const record of readJsonlFile(meta.sourcePath)) {
1429
1787
  try {
1430
- this.convertRecord(record, builder, assistantUuidToToolCalls, countedUsageKeys);
1788
+ this.convertRecord(
1789
+ record,
1790
+ builder,
1791
+ assistantUuidToToolCalls,
1792
+ countedUsageKeys,
1793
+ this.childSessionIdByToolUseId
1794
+ );
1431
1795
  } catch {
1432
1796
  }
1433
1797
  }
@@ -1438,6 +1802,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1438
1802
  title: meta.title,
1439
1803
  slug: `claudecode/${meta.id}`,
1440
1804
  directory: meta.directory,
1805
+ parent_reference: meta.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: meta.parentSessionId },
1441
1806
  version: void 0,
1442
1807
  time_created: meta.createdAt,
1443
1808
  time_updated: meta.updatedAt,
@@ -1449,43 +1814,143 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1449
1814
  listProjectDirs() {
1450
1815
  if (!this.basePath) return [];
1451
1816
  try {
1452
- return readdirSync2(this.basePath).map((e) => join4(this.basePath, e)).filter((p) => existsSync4(p));
1817
+ return readdirSync3(this.basePath).map((e) => join5(this.basePath, e)).filter((p) => existsSync5(p));
1453
1818
  } catch {
1454
1819
  return [];
1455
1820
  }
1456
1821
  }
1822
+ ensureChildIndex() {
1823
+ if (this.childIndexReady) return;
1824
+ this.basePath ??= this.findBasePath();
1825
+ if (!this.basePath) {
1826
+ this.childIndexReady = true;
1827
+ return;
1828
+ }
1829
+ const projectDirs = this.listProjectDirs();
1830
+ this.indexChildContexts(
1831
+ this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1832
+ recursive: true
1833
+ }),
1834
+ projectDirs
1835
+ );
1836
+ }
1837
+ indexChildContexts(sources, projectDirs) {
1838
+ this.childContextsBySource.clear();
1839
+ this.childSessionIdByToolUseId.clear();
1840
+ const projectDirSet = new Set(projectDirs);
1841
+ const contexts = /* @__PURE__ */ new Map();
1842
+ const knownSessionIds = /* @__PURE__ */ new Set();
1843
+ for (const source of sources) {
1844
+ const child = this.readChildContext(source.file);
1845
+ if (!child) {
1846
+ if (projectDirSet.has(dirname2(source.file))) {
1847
+ knownSessionIds.add(basename3(source.file, ".jsonl"));
1848
+ }
1849
+ continue;
1850
+ }
1851
+ contexts.set(source.file, child);
1852
+ knownSessionIds.add(child.sessionId);
1853
+ }
1854
+ for (const [sourcePath, child] of contexts) {
1855
+ const parentSessionId = child.parentSessionId && knownSessionIds.has(child.parentSessionId) ? child.parentSessionId : null;
1856
+ const normalized = parentSessionId === child.parentSessionId ? child : { ...child, parentSessionId };
1857
+ this.childContextsBySource.set(sourcePath, normalized);
1858
+ if (child.toolUseId) {
1859
+ this.childSessionIdByToolUseId.set(child.toolUseId, normalized.sessionId);
1860
+ }
1861
+ }
1862
+ this.childIndexReady = true;
1863
+ }
1864
+ getChildContext(sourcePath) {
1865
+ const cached = this.childContextsBySource.get(sourcePath);
1866
+ if (cached) return cached;
1867
+ const child = this.readChildContext(sourcePath);
1868
+ if (!child) return null;
1869
+ this.childContextsBySource.set(sourcePath, child);
1870
+ if (child.toolUseId) {
1871
+ this.childSessionIdByToolUseId.set(child.toolUseId, child.sessionId);
1872
+ }
1873
+ return child;
1874
+ }
1875
+ readChildContext(sourcePath) {
1876
+ const subagentsDir = dirname2(sourcePath);
1877
+ if (basename3(subagentsDir) !== "subagents") return null;
1878
+ const parentDir = dirname2(subagentsDir);
1879
+ const projectDir = dirname2(parentDir);
1880
+ const fileStem = basename3(sourcePath, ".jsonl");
1881
+ const metaPath = join5(subagentsDir, fileStem + ".meta.json");
1882
+ const metaMtimeMs = this.readFileMtimeMs(metaPath);
1883
+ const cached = this.childContextCache.get(sourcePath);
1884
+ if (cached?.metaMtimeMs === metaMtimeMs) return cached.context;
1885
+ let metadata = null;
1886
+ if (metaMtimeMs !== null) {
1887
+ try {
1888
+ metadata = asRecord(JSON.parse(readFileSync2(metaPath, "utf-8"))) ?? null;
1889
+ } catch {
1890
+ }
1891
+ }
1892
+ const sessionId = asString(metadata?.["agentId"])?.trim() || fileStem.replace(/^agent-/, "");
1893
+ if (!sessionId) return null;
1894
+ const parentAgentId = asString(metadata?.["parentAgentId"])?.trim();
1895
+ const candidateParentId = parentAgentId || basename3(parentDir) || null;
1896
+ const parentSessionId = candidateParentId && this.hasChildParent(sourcePath, projectDir, candidateParentId) ? candidateParentId : null;
1897
+ const name = asString(metadata?.["name"])?.trim();
1898
+ const description = asString(metadata?.["description"])?.trim();
1899
+ const context = {
1900
+ sessionId,
1901
+ projectDir,
1902
+ parentSessionId,
1903
+ explicitTitle: name || description || null,
1904
+ metaMtimeMs,
1905
+ toolUseId: asString(metadata?.["toolUseId"])?.trim() || null
1906
+ };
1907
+ this.childContextCache.set(sourcePath, { metaMtimeMs, context });
1908
+ return context;
1909
+ }
1910
+ hasChildParent(sourcePath, projectDir, parentSessionId) {
1911
+ const subagentsDir = dirname2(sourcePath);
1912
+ return [
1913
+ join5(projectDir, parentSessionId + ".jsonl"),
1914
+ join5(subagentsDir, "agent-" + parentSessionId + ".jsonl"),
1915
+ join5(subagentsDir, parentSessionId + ".jsonl")
1916
+ ].some((path2) => existsSync5(path2));
1917
+ }
1457
1918
  createFileSessionMeta(head, source) {
1458
- const projectDir = dirname(source.file);
1919
+ const child = this.getChildContext(source.file);
1920
+ const projectDir = child?.projectDir ?? dirname2(source.file);
1459
1921
  const indexPath = this.getSessionsIndexPath(projectDir);
1460
1922
  const indexMtime = this.readFileMtimeMs(indexPath);
1461
1923
  return this.buildFileSessionMeta({
1462
1924
  head,
1463
1925
  source,
1464
- fingerprint: this.sourceFingerprint(source.stat, indexMtime),
1926
+ fingerprint: this.sourceFingerprint(source.stat, indexMtime, child?.metaMtimeMs),
1465
1927
  extras: {
1466
1928
  indexPath: indexMtime === null ? null : indexPath,
1467
1929
  indexMtimeMs: indexMtime,
1468
1930
  headIndexVersion: HEAD_INDEX_VERSION,
1469
- model: head.stats.total_tokens ? "unknown" : void 0
1931
+ model: head.stats.total_tokens ? "unknown" : void 0,
1932
+ parentSessionId: head.parent_reference?.sessionId ?? null
1470
1933
  }
1471
1934
  });
1472
1935
  }
1473
1936
  /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
1474
- sourceFingerprint(stat, indexMtime) {
1475
- return JSON.stringify([HEAD_INDEX_VERSION, stat.mtimeMs, stat.size, indexMtime]);
1937
+ sourceFingerprint(stat, indexMtime, metaMtime) {
1938
+ const fingerprint = [HEAD_INDEX_VERSION, stat.mtimeMs, stat.size, indexMtime];
1939
+ if (metaMtime !== void 0) fingerprint.push(metaMtime);
1940
+ return JSON.stringify(fingerprint);
1476
1941
  }
1477
1942
  getSessionsIndexPath(projectDir) {
1478
- return join4(projectDir, "sessions-index.json");
1943
+ return join5(projectDir, "sessions-index.json");
1479
1944
  }
1480
1945
  loadSessionsIndex(projectDir) {
1481
- const cacheKey = basename2(projectDir);
1946
+ const cacheKey = basename3(projectDir);
1482
1947
  const indexPath = this.getSessionsIndexPath(projectDir);
1483
1948
  const mtime = this.readFileMtimeMs(indexPath);
1484
1949
  if (cacheKey in this.sessionsIndexCache && this.sessionsIndexMtime[cacheKey] === mtime) {
1485
1950
  return this.sessionsIndexCache[cacheKey];
1486
1951
  }
1487
1952
  const map = /* @__PURE__ */ new Map();
1488
- if (existsSync4(indexPath)) {
1953
+ if (existsSync5(indexPath)) {
1489
1954
  try {
1490
1955
  const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
1491
1956
  const entries = data?.entries ?? [];
@@ -1502,11 +1967,11 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1502
1967
  this.sessionsIndexMtime[cacheKey] = mtime;
1503
1968
  return map;
1504
1969
  }
1505
- parseSessionHeadResult(filePath, projectDir) {
1506
- const sessionId = basename2(filePath, ".jsonl");
1970
+ parseSessionHeadResult(filePath, projectDir, child) {
1971
+ const sessionId = child?.sessionId ?? basename3(filePath, ".jsonl");
1507
1972
  const index = this.loadSessionsIndex(projectDir);
1508
1973
  const indexEntry = index.get(sessionId);
1509
- const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
1974
+ const explicitTitle = child?.explicitTitle ?? (indexEntry?.summary ? String(indexEntry.summary) : null);
1510
1975
  let createdAt = 0;
1511
1976
  let updatedAt = 0;
1512
1977
  let lineIndex = 0;
@@ -1531,7 +1996,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1531
1996
  continue;
1532
1997
  }
1533
1998
  if (lineIndex === 0) {
1534
- createdAt = parseTimestampMs(data) || statSync2(filePath).mtimeMs;
1999
+ createdAt = parseTimestampMs(data) || statSync3(filePath).mtimeMs;
1535
2000
  updatedAt = createdAt;
1536
2001
  }
1537
2002
  const recordIndex = lineIndex;
@@ -1605,6 +2070,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1605
2070
  slug: `claudecode/${sessionId}`,
1606
2071
  title,
1607
2072
  directory,
2073
+ parent_reference: child?.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: child.parentSessionId },
1608
2074
  time_created: createdAt,
1609
2075
  time_updated: updatedAt,
1610
2076
  stats: {
@@ -1637,19 +2103,25 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1637
2103
  return null;
1638
2104
  }
1639
2105
  // --- Record conversion ---
1640
- convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
2106
+ convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
1641
2107
  if (data["isMeta"] === true) return;
1642
2108
  const msgType = String(data["type"] ?? "");
1643
2109
  if (isInternalEventType(msgType)) return;
1644
2110
  if (msgType === "assistant") {
1645
- this.convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys);
2111
+ this.convertAssistantRecord(
2112
+ data,
2113
+ builder,
2114
+ assistantUuidToToolCalls,
2115
+ countedUsageKeys,
2116
+ childSessionIdByToolUseId
2117
+ );
1646
2118
  } else if (msgType === "user") {
1647
2119
  this.convertUserRecord(data, builder, assistantUuidToToolCalls);
1648
2120
  } else if (msgType === "tool_result") {
1649
2121
  this.convertToolResultRecord(data, builder);
1650
2122
  }
1651
2123
  }
1652
- convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
2124
+ convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
1653
2125
  const msg = asRecord(data["message"]) ?? {};
1654
2126
  const timestampMs = parseTimestampMs(data);
1655
2127
  const rawContent = asArray(msg["content"]) ?? [];
@@ -1689,12 +2161,14 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1689
2161
  }
1690
2162
  if (partType !== "tool_use") continue;
1691
2163
  const toolCallId = String(part["id"] ?? "").trim();
1692
- const toolPart = this.buildToolPart(part, timestampMs);
2164
+ const subagentId = childSessionIdByToolUseId.get(toolCallId);
2165
+ const toolPart2 = this.buildToolPart(part, timestampMs);
1693
2166
  const message = builder.appendToolCall(
1694
- toolPart,
1695
- { id: uuid, timestampMs, agent: "claude" },
2167
+ toolPart2,
2168
+ { id: uuid, timestampMs, agent: "claude", subagentId },
1696
2169
  { modeOnCreate: "tool" }
1697
2170
  );
2171
+ if (subagentId) message.subagent_id = subagentId;
1698
2172
  this.applyAssistantMetadata(message, data, msg, countedUsageKeys);
1699
2173
  if (toolCallId) {
1700
2174
  toolCallIds.push(toolCallId);
@@ -1967,11 +2441,12 @@ function backupDatabase(db, dbPath, label) {
1967
2441
  return null;
1968
2442
  }
1969
2443
  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`);
2444
+ let backupPath = join6(dirname3(dbPath), `${basename4(dbPath)}.${timestamp}.${label}.bak`);
2445
+ for (let counter = 1; existsSync6(backupPath); counter += 1) {
2446
+ backupPath = join6(dirname3(dbPath), `${basename4(dbPath)}.${timestamp}.${label}.${counter}.bak`);
1973
2447
  }
1974
2448
  db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
2449
+ restrictPrivateFile(backupPath);
1975
2450
  return backupPath;
1976
2451
  }
1977
2452
  function backupDatabaseIfPopulated(db, dbPath, label, tables) {
@@ -2066,8 +2541,9 @@ function openDb(dbPath) {
2066
2541
  return null;
2067
2542
  }
2068
2543
  try {
2069
- mkdirSync2(dirname2(dbPath), { recursive: true });
2544
+ ensurePrivateDirectory(dirname3(dbPath));
2070
2545
  const db = DatabaseConstructor(dbPath);
2546
+ restrictPrivateDatabase(dbPath);
2071
2547
  try {
2072
2548
  db.pragma("journal_mode = WAL");
2073
2549
  db.pragma("synchronous = NORMAL");
@@ -2089,6 +2565,24 @@ function isSqliteAvailable() {
2089
2565
  return DatabaseConstructor !== null;
2090
2566
  }
2091
2567
  var MESSAGE_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool"]);
2568
+ var SESSION_ID_QUERY_CHUNK_SIZE = 500;
2569
+ function compareSessionRowsByActivityDesc(left, right) {
2570
+ const leftUpdated = Number(left.time_updated ?? left.time_created ?? 0);
2571
+ const rightUpdated = Number(right.time_updated ?? right.time_created ?? 0);
2572
+ return rightUpdated - leftUpdated || Number(right.time_created ?? 0) - Number(left.time_created ?? 0) || String(right.id ?? "").localeCompare(String(left.id ?? ""));
2573
+ }
2574
+ function accumulateTokenStats(stats, msgData, agentName) {
2575
+ const cost = Number(msgData.cost ?? 0);
2576
+ const tokens = parseTokens(msgData.tokens, agentName);
2577
+ const inputTokens = Number(tokens?.input ?? 0);
2578
+ const outputTokens = Number(tokens?.output ?? 0);
2579
+ const model = parseModel(msgData.modelID, agentName);
2580
+ const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
2581
+ if (estimatedCost !== null) stats.cost_source = "estimated";
2582
+ stats.total_cost += cost || estimatedCost || 0;
2583
+ stats.total_input_tokens += inputTokens;
2584
+ stats.total_output_tokens += outputTokens;
2585
+ }
2092
2586
  function parseJsonRecord(raw, agentName, field) {
2093
2587
  const parsed = asRecord(JSON.parse(String(raw ?? "{}")));
2094
2588
  if (parsed) return parsed;
@@ -2138,34 +2632,54 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2138
2632
  scan(options) {
2139
2633
  if (!this.dbPath) return [];
2140
2634
  const db = openDbReadOnly(this.dbPath);
2141
- if (!db) return [];
2635
+ if (!db) throw new SessionScanError(this.name, "opening the database");
2142
2636
  try {
2143
2637
  const cutoffTime = options?.from ?? Date.now() - 3650 * 24 * 60 * 60 * 1e3;
2144
2638
  const hasMessageTable = Boolean(
2145
2639
  db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get()
2146
2640
  );
2641
+ const hasTaskType = columnExists(db, "session", "task_type");
2642
+ const hasParentId = columnExists(db, "session", "parent_id");
2643
+ const childPredicate = hasParentId ? "AND s.parent_id IS NULL" : hasTaskType ? "AND (s.task_type IS NULL OR s.task_type != 'subagent_child')" : "";
2147
2644
  let rows;
2645
+ const parentIdSelect = hasParentId ? ", s.parent_id" : "";
2148
2646
  if (hasMessageTable) {
2149
2647
  rows = db.prepare(`
2150
2648
  SELECT
2151
2649
  s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2152
- s.version, s.summary_files
2650
+ s.version, s.summary_files${parentIdSelect}
2153
2651
  FROM session s
2154
2652
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2155
- ORDER BY s.time_created DESC
2653
+ ${childPredicate}
2654
+ ORDER BY COALESCE(s.time_updated, s.time_created) DESC, s.time_created DESC, s.id DESC
2156
2655
  `).all(cutoffTime);
2157
2656
  } else {
2158
2657
  rows = db.prepare(`
2159
2658
  SELECT s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2160
- s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data
2659
+ s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data${parentIdSelect}
2161
2660
  FROM session s
2162
2661
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2163
- ORDER BY s.time_created DESC
2662
+ ${childPredicate}
2663
+ ORDER BY COALESCE(s.time_updated, s.time_created) DESC, s.time_created DESC, s.id DESC
2164
2664
  `).all(cutoffTime);
2165
2665
  }
2666
+ if (hasParentId && options?.includeRelatedSessions !== false) {
2667
+ const rootIds = rows.map((row) => String(row.id ?? "")).filter(Boolean);
2668
+ const relatedRows = this.readRelatedSessionRows(db, rootIds);
2669
+ const knownIds = new Set(rootIds);
2670
+ rows.push(...relatedRows.filter((row) => !knownIds.has(String(row.id ?? ""))));
2671
+ }
2672
+ rows.sort(compareSessionRowsByActivityDesc);
2673
+ const sessionIds = new Set(rows.map((row) => String(row.id ?? "")).filter(Boolean));
2166
2674
  const headContexts = hasMessageTable ? this.buildHeadContexts(
2167
- this.readHeadMessageRows(db, cutoffTime),
2168
- this.readHeadPartRows(db, cutoffTime)
2675
+ this.readHeadMessageRows(
2676
+ db,
2677
+ cutoffTime,
2678
+ hasParentId,
2679
+ sessionIds.size > 0 ? sessionIds : void 0
2680
+ ),
2681
+ this.readHeadPartRows(db, cutoffTime, sessionIds.size > 0 ? sessionIds : void 0),
2682
+ hasParentId
2169
2683
  ) : /* @__PURE__ */ new Map();
2170
2684
  const heads = [];
2171
2685
  options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
@@ -2187,8 +2701,8 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2187
2701
  options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
2188
2702
  }
2189
2703
  return heads;
2190
- } catch {
2191
- return [];
2704
+ } catch (error) {
2705
+ throw new SessionScanError(this.name, "reading sessions", { cause: error });
2192
2706
  } finally {
2193
2707
  db.close();
2194
2708
  }
@@ -2207,6 +2721,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2207
2721
  slug: `${this.name}/${id}`,
2208
2722
  title: resolveSessionTitle(String(row.title ?? ""), messageTitle, null),
2209
2723
  directory: String(row.directory ?? ""),
2724
+ parent_reference: row.parent_id == null || String(row.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(row.parent_id) },
2210
2725
  time_created: timeCreated,
2211
2726
  time_updated: timeUpdated,
2212
2727
  stats: {
@@ -2218,28 +2733,103 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2218
2733
  }
2219
2734
  });
2220
2735
  }
2221
- readHeadMessageRows(db, cutoffTime) {
2222
- return db.prepare(
2223
- `
2224
- SELECT m.id, m.session_id, m.data, m.time_created
2225
- FROM message m
2226
- JOIN session s ON s.id = m.session_id
2227
- WHERE COALESCE(s.time_updated, s.time_created) >= ?
2228
- ORDER BY m.session_id, m.time_created ASC
2736
+ readHeadMessageRows(db, cutoffTime, withParentId, sessionIds) {
2737
+ const parentIdSelect = withParentId ? ", s.parent_id" : "";
2738
+ const ids = sessionIds ? [...sessionIds] : [];
2739
+ if (ids.length === 0) {
2740
+ return db.prepare(
2229
2741
  `
2230
- ).all(cutoffTime);
2742
+ SELECT m.id, m.session_id, m.data, m.time_created${parentIdSelect}
2743
+ FROM message m
2744
+ JOIN session s ON s.id = m.session_id
2745
+ WHERE COALESCE(s.time_updated, s.time_created) >= ?
2746
+ ORDER BY m.session_id, m.time_created ASC
2747
+ `
2748
+ ).all(cutoffTime);
2749
+ }
2750
+ const rows = [];
2751
+ for (let offset = 0; offset < ids.length; offset += SESSION_ID_QUERY_CHUNK_SIZE) {
2752
+ const chunk = ids.slice(offset, offset + SESSION_ID_QUERY_CHUNK_SIZE);
2753
+ rows.push(
2754
+ ...db.prepare(
2755
+ `
2756
+ SELECT m.id, m.session_id, m.data, m.time_created${parentIdSelect}
2757
+ FROM message m
2758
+ JOIN session s ON s.id = m.session_id
2759
+ WHERE s.id IN (${chunk.map(() => "?").join(",")})
2760
+ ORDER BY m.session_id, m.time_created ASC
2761
+ `
2762
+ ).all(...chunk)
2763
+ );
2764
+ }
2765
+ return rows;
2231
2766
  }
2232
- readHeadPartRows(db, cutoffTime) {
2233
- return db.prepare(
2767
+ readHeadPartRows(db, cutoffTime, sessionIds) {
2768
+ const ids = sessionIds ? [...sessionIds] : [];
2769
+ if (ids.length === 0) {
2770
+ return db.prepare(
2771
+ `
2772
+ SELECT p.message_id, p.data, p.time_created
2773
+ FROM part p
2774
+ JOIN message m ON m.id = p.message_id
2775
+ JOIN session s ON s.id = m.session_id
2776
+ WHERE COALESCE(s.time_updated, s.time_created) >= ?
2777
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2778
+ `
2779
+ ).all(cutoffTime);
2780
+ }
2781
+ const rows = [];
2782
+ for (let offset = 0; offset < ids.length; offset += SESSION_ID_QUERY_CHUNK_SIZE) {
2783
+ const chunk = ids.slice(offset, offset + SESSION_ID_QUERY_CHUNK_SIZE);
2784
+ rows.push(
2785
+ ...db.prepare(
2786
+ `
2787
+ SELECT p.message_id, p.data, p.time_created
2788
+ FROM part p
2789
+ JOIN message m ON m.id = p.message_id
2790
+ JOIN session s ON s.id = m.session_id
2791
+ WHERE s.id IN (${chunk.map(() => "?").join(",")})
2792
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2793
+ `
2794
+ ).all(...chunk)
2795
+ );
2796
+ }
2797
+ return rows;
2798
+ }
2799
+ readRelatedSessionRows(db, rootIds) {
2800
+ if (rootIds.length === 0) return [];
2801
+ const rows = db.prepare(
2234
2802
  `
2235
- SELECT p.message_id, p.data, p.time_created
2236
- FROM part p
2237
- JOIN message m ON m.id = p.message_id
2238
- JOIN session s ON s.id = m.session_id
2239
- WHERE COALESCE(s.time_updated, s.time_created) >= ?
2240
- ORDER BY p.message_id, p.time_created ASC
2803
+ SELECT
2804
+ s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2805
+ s.version, s.summary_files, s.parent_id
2806
+ FROM session s
2807
+ WHERE s.parent_id IS NOT NULL
2808
+ ORDER BY s.time_created ASC
2241
2809
  `
2242
- ).all(cutoffTime);
2810
+ ).all();
2811
+ const childrenByParent = /* @__PURE__ */ new Map();
2812
+ for (const row of rows) {
2813
+ const parentId = String(row.parent_id ?? "");
2814
+ if (!parentId) continue;
2815
+ const children = childrenByParent.get(parentId);
2816
+ if (children) children.push(row);
2817
+ else childrenByParent.set(parentId, [row]);
2818
+ }
2819
+ const result = [];
2820
+ const pending2 = [...rootIds];
2821
+ const seen = /* @__PURE__ */ new Set();
2822
+ while (pending2.length > 0) {
2823
+ const parentId = pending2.pop();
2824
+ for (const row of childrenByParent.get(parentId) ?? []) {
2825
+ const id = String(row.id ?? "");
2826
+ if (!id || seen.has(id)) continue;
2827
+ seen.add(id);
2828
+ result.push(row);
2829
+ pending2.push(id);
2830
+ }
2831
+ }
2832
+ return result;
2243
2833
  }
2244
2834
  parsePartRow(partRow) {
2245
2835
  const partData = parseJsonRecord(partRow.data, this.name, "part.data");
@@ -2250,9 +2840,21 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2250
2840
  ]);
2251
2841
  return part ? cleanMessagePart(part) : null;
2252
2842
  }
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);
2843
+ /**
2844
+ * Every part of one session in a single read. Fetching per message turned a
2845
+ * detail into M+2 queries, and without an index on part(message_id) each one
2846
+ * scanned the whole table.
2847
+ */
2848
+ readSessionPartRows(db, sessionId) {
2849
+ return db.prepare(
2850
+ `
2851
+ SELECT p.message_id, p.data, p.time_created
2852
+ FROM part p
2853
+ JOIN message m ON m.id = p.message_id
2854
+ WHERE m.session_id = ?
2855
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2856
+ `
2857
+ ).all(sessionId);
2256
2858
  }
2257
2859
  buildPartsByMessage(partRows) {
2258
2860
  const partsByMessage = /* @__PURE__ */ new Map();
@@ -2270,16 +2872,10 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2270
2872
  }
2271
2873
  return partsByMessage;
2272
2874
  }
2273
- buildHeadContexts(messageRows, partRows) {
2875
+ buildHeadContexts(messageRows, partRows, withParentId) {
2274
2876
  const partsByMessage = this.buildPartsByMessage(partRows);
2275
2877
  const contexts = /* @__PURE__ */ new Map();
2276
- for (const row of messageRows) {
2277
- const sessionId = String(row.session_id ?? "");
2278
- if (!sessionId) continue;
2279
- const msgData = parseJsonRecord(row.data, this.name, "message.data");
2280
- if (isInternalEventType(msgData.type)) continue;
2281
- const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
2282
- if (parts.length === 0) continue;
2878
+ const ensureContext = (sessionId) => {
2283
2879
  let context = contexts.get(sessionId);
2284
2880
  if (!context) {
2285
2881
  context = {
@@ -2293,16 +2889,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2293
2889
  };
2294
2890
  contexts.set(sessionId, context);
2295
2891
  }
2296
- const cost = Number(msgData.cost ?? 0);
2297
- const tokens = parseTokens(msgData.tokens, this.name);
2298
- const inputTokens = Number(tokens?.input ?? 0);
2299
- const outputTokens = Number(tokens?.output ?? 0);
2300
- const model = parseModel(msgData.modelID, this.name);
2301
- const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
2302
- if (estimatedCost !== null) context.stats.cost_source = "estimated";
2303
- context.stats.total_cost += cost || estimatedCost || 0;
2304
- context.stats.total_input_tokens += inputTokens;
2305
- context.stats.total_output_tokens += outputTokens;
2892
+ return context;
2893
+ };
2894
+ for (const row of messageRows) {
2895
+ const sessionId = String(row.session_id ?? "");
2896
+ if (!sessionId) continue;
2897
+ const msgData = parseJsonRecord(row.data, this.name, "message.data");
2898
+ if (isInternalEventType(msgData.type)) continue;
2899
+ const parentId = withParentId ? String(row.parent_id ?? "") : "";
2900
+ const isChild = parentId !== "";
2901
+ if (isChild) {
2902
+ const childContext = ensureContext(sessionId);
2903
+ const parts2 = partsByMessage.get(String(row.id ?? "")) ?? [];
2904
+ if (parts2.length > 0) {
2905
+ accumulateTokenStats(childContext.stats, msgData, this.name);
2906
+ childContext.stats.message_count += 1;
2907
+ if (!childContext.messageTitle && String(msgData.role ?? "") === "user") {
2908
+ childContext.messageTitle = firstUserMessageTitle([
2909
+ {
2910
+ id: String(row.id ?? ""),
2911
+ role: "user",
2912
+ agent: null,
2913
+ time_created: Number(row.time_created ?? 0),
2914
+ parts: parts2
2915
+ }
2916
+ ]);
2917
+ }
2918
+ }
2919
+ const parentContext = ensureContext(parentId);
2920
+ accumulateTokenStats(parentContext.stats, msgData, this.name);
2921
+ continue;
2922
+ }
2923
+ const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
2924
+ if (parts.length === 0) continue;
2925
+ const context = ensureContext(sessionId);
2926
+ accumulateTokenStats(context.stats, msgData, this.name);
2306
2927
  context.stats.message_count += 1;
2307
2928
  if (!context.messageTitle && String(msgData.role ?? "") === "user") {
2308
2929
  context.messageTitle = firstUserMessageTitle([
@@ -2323,6 +2944,29 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2323
2944
  }
2324
2945
  return contexts;
2325
2946
  }
2947
+ sumChildTokenStats(db, parentSessionId) {
2948
+ if (!columnExists(db, "session", "parent_id")) return [];
2949
+ const childRows = db.prepare("SELECT id FROM session WHERE parent_id = ?").all(parentSessionId);
2950
+ const results = [];
2951
+ for (const child of childRows) {
2952
+ const childId = String(child.id ?? "");
2953
+ if (!childId) continue;
2954
+ const msgRows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(childId);
2955
+ const stats = {
2956
+ message_count: 0,
2957
+ total_input_tokens: 0,
2958
+ total_output_tokens: 0,
2959
+ total_cost: 0
2960
+ };
2961
+ for (const row of msgRows) {
2962
+ const msgData = parseJsonRecord(row.data, this.name, "message.data");
2963
+ if (isInternalEventType(msgData.type)) continue;
2964
+ accumulateTokenStats(stats, msgData, this.name);
2965
+ }
2966
+ results.push(stats);
2967
+ }
2968
+ return results;
2969
+ }
2326
2970
  getSessionData(sessionId) {
2327
2971
  if (!this.dbPath) {
2328
2972
  this.dbPath = this.findDbPath();
@@ -2345,11 +2989,9 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2345
2989
  const timeCreated = Number(sessionRow.time_created ?? 0);
2346
2990
  const timeUpdated = Number(sessionRow.time_updated ?? timeCreated);
2347
2991
  const messages = [];
2348
- let totalCost = 0;
2349
- let totalInputTokens = 0;
2350
- let totalOutputTokens = 0;
2351
2992
  let hasEstimatedCost = false;
2352
- const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
2993
+ const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
2994
+ const partsByMessage = this.buildPartsByMessage(this.readSessionPartRows(db, sessionId));
2353
2995
  for (const msgRow of msgRows) {
2354
2996
  const msgData = parseJsonRecord(msgRow.data, this.name, "message.data");
2355
2997
  if (isInternalEventType(msgData.type)) continue;
@@ -2360,7 +3002,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2360
3002
  const model = parseModel(msgData.modelID, this.name);
2361
3003
  const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
2362
3004
  const resolvedCost = cost || estimatedCost || 0;
2363
- const parts = this.readMessageParts(db, msgRow.id);
3005
+ const parts = partsByMessage.get(String(msgRow.id ?? "")) ?? [];
2364
3006
  if (parts.length === 0) continue;
2365
3007
  messages.push({
2366
3008
  id: String(msgRow.id ?? ""),
@@ -2382,28 +3024,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2382
3024
  firstUserMessageTitle(cleanedMessages),
2383
3025
  null
2384
3026
  );
3027
+ const stats = {
3028
+ message_count: cleanedMessages.length,
3029
+ total_input_tokens: 0,
3030
+ total_output_tokens: 0,
3031
+ total_cost: 0
3032
+ };
2385
3033
  for (const message of cleanedMessages) {
2386
- totalCost += message.cost ?? 0;
2387
- totalInputTokens += message.tokens?.input ?? 0;
2388
- totalOutputTokens += message.tokens?.output ?? 0;
3034
+ stats.total_cost += message.cost ?? 0;
3035
+ stats.total_input_tokens += message.tokens?.input ?? 0;
3036
+ stats.total_output_tokens += message.tokens?.output ?? 0;
2389
3037
  if (message.cost_source === "estimated") hasEstimatedCost = true;
2390
3038
  }
3039
+ for (const childStats of this.sumChildTokenStats(db, sessionId)) {
3040
+ stats.total_cost += childStats.total_cost;
3041
+ stats.total_input_tokens += childStats.total_input_tokens;
3042
+ stats.total_output_tokens += childStats.total_output_tokens;
3043
+ if (childStats.cost_source === "estimated") hasEstimatedCost = true;
3044
+ }
2391
3045
  return {
2392
3046
  reference: { agentName: this.name, sessionId: id },
2393
3047
  id,
2394
3048
  title,
2395
3049
  slug,
2396
3050
  directory,
3051
+ parent_reference: sessionRow.parent_id == null || String(sessionRow.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(sessionRow.parent_id) },
2397
3052
  version: asString(sessionRow.version) ?? void 0,
2398
3053
  time_created: timeCreated,
2399
3054
  time_updated: timeUpdated,
2400
3055
  summary_files: sessionRow.summary_files ?? void 0,
2401
3056
  stats: {
2402
- message_count: cleanedMessages.length,
2403
- total_input_tokens: totalInputTokens,
2404
- total_output_tokens: totalOutputTokens,
2405
- total_cost: totalCost,
2406
- cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
3057
+ message_count: stats.message_count,
3058
+ total_input_tokens: stats.total_input_tokens,
3059
+ total_output_tokens: stats.total_output_tokens,
3060
+ total_cost: stats.total_cost,
3061
+ cost_source: stats.total_cost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
2407
3062
  },
2408
3063
  messages: cleanedMessages
2409
3064
  };
@@ -2413,18 +3068,18 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2413
3068
  }
2414
3069
  };
2415
3070
  function resolveOpenCodeDataRoot() {
2416
- return join6(resolveDataHome(), "opencode");
3071
+ return join7(resolveDataHome(), "opencode");
2417
3072
  }
2418
3073
  function findOpenCodeDbPath() {
2419
3074
  if (!isSqliteAvailable()) return null;
2420
- return firstExisting(join6(resolveOpenCodeDataRoot(), "opencode.db"), "data/opencode/opencode.db");
3075
+ return firstExisting(join7(resolveOpenCodeDataRoot(), "opencode.db"), "data/opencode/opencode.db");
2421
3076
  }
2422
3077
  function getOpenCodeSessionWatchPlan() {
2423
3078
  const dataRoot = resolveOpenCodeDataRoot();
2424
3079
  return {
2425
3080
  status: "supported",
2426
3081
  targets: [
2427
- { root: dataRoot, path: join6(dataRoot, "opencode.db") },
3082
+ { root: dataRoot, path: join7(dataRoot, "opencode.db") },
2428
3083
  { root: "data/opencode", path: "data/opencode/opencode.db" }
2429
3084
  ]
2430
3085
  };
@@ -2521,14 +3176,14 @@ function kimiContentText(content) {
2521
3176
  }).join(" ");
2522
3177
  }
2523
3178
  function extractFirstUserTitle(contextFile, wireFile) {
2524
- if (contextFile && existsSync6(contextFile)) {
3179
+ if (contextFile && existsSync7(contextFile)) {
2525
3180
  for (const record of readJsonlFile(contextFile)) {
2526
3181
  if (record.role !== "user") continue;
2527
3182
  const title = normalizeTitleText(kimiContentText(record.content));
2528
3183
  if (title) return title;
2529
3184
  }
2530
3185
  }
2531
- if (wireFile && existsSync6(wireFile)) {
3186
+ if (wireFile && existsSync7(wireFile)) {
2532
3187
  for (const record of readJsonlFile(wireFile)) {
2533
3188
  const message = asRecord(record.message) ?? {};
2534
3189
  if (message.type !== "TurnBegin") continue;
@@ -2548,25 +3203,25 @@ var KimiAgent = class extends FileSystemSessionSource {
2548
3203
  projectMap = /* @__PURE__ */ new Map();
2549
3204
  defaultModel = null;
2550
3205
  findBasePath() {
2551
- return firstExisting(join7(resolveKimiDataRoot(), "sessions"), "data/kimi");
3206
+ return firstExisting(join8(resolveKimiDataRoot(), "sessions"), "data/kimi");
2552
3207
  }
2553
3208
  getSessionWatchPlan() {
2554
3209
  const dataRoot = resolveKimiDataRoot();
2555
3210
  return {
2556
3211
  status: "supported",
2557
- targets: [{ root: dataRoot, path: join7(dataRoot, "sessions") }, { path: "data/kimi" }]
3212
+ targets: [{ root: dataRoot, path: join8(dataRoot, "sessions") }, { path: "data/kimi" }]
2558
3213
  };
2559
3214
  }
2560
3215
  /** Parse kimi.json and build md5(project_path) → cwd mapping */
2561
3216
  loadKimiConfig() {
2562
3217
  const dataRoot = resolveKimiDataRoot();
2563
- const configPath = join7(dataRoot, "kimi.json");
2564
- const tomlPath = join7(dataRoot, "config.toml");
2565
- if (existsSync6(tomlPath)) {
3218
+ const configPath = join8(dataRoot, "kimi.json");
3219
+ const tomlPath = join8(dataRoot, "config.toml");
3220
+ if (existsSync7(tomlPath)) {
2566
3221
  const configText = readFileSync3(tomlPath, "utf-8");
2567
3222
  this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
2568
3223
  }
2569
- if (!existsSync6(configPath)) return;
3224
+ if (!existsSync7(configPath)) return;
2570
3225
  try {
2571
3226
  const raw = asRecord(JSON.parse(readFileSync3(configPath, "utf-8")));
2572
3227
  const workDirs = asArray(raw?.work_dirs);
@@ -2595,14 +3250,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2595
3250
  if (!this.basePath) return [];
2596
3251
  const dirs = [];
2597
3252
  try {
2598
- for (const hashEntry of readdirSync3(this.basePath, { withFileTypes: true })) {
3253
+ for (const hashEntry of readdirSync4(this.basePath, { withFileTypes: true })) {
2599
3254
  if (!hashEntry.isDirectory()) continue;
2600
- const hashPath = join7(this.basePath, hashEntry.name);
3255
+ const hashPath = join8(this.basePath, hashEntry.name);
2601
3256
  try {
2602
- for (const sessionEntry of readdirSync3(hashPath, { withFileTypes: true })) {
3257
+ for (const sessionEntry of readdirSync4(hashPath, { withFileTypes: true })) {
2603
3258
  if (!sessionEntry.isDirectory()) continue;
2604
- const sessionPath = join7(hashPath, sessionEntry.name);
2605
- if (existsSync6(join7(sessionPath, "metadata.json")) || existsSync6(join7(sessionPath, "state.json"))) {
3259
+ const sessionPath = join8(hashPath, sessionEntry.name);
3260
+ if (existsSync7(join8(sessionPath, "metadata.json")) || existsSync7(join8(sessionPath, "state.json"))) {
2606
3261
  dirs.push(sessionPath);
2607
3262
  }
2608
3263
  }
@@ -2619,32 +3274,32 @@ var KimiAgent = class extends FileSystemSessionSource {
2619
3274
  */
2620
3275
  resolveSessionSourceResult(sessionDir) {
2621
3276
  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;
3277
+ const sessionId = basename5(sessionDir);
3278
+ const projectHash = basename5(dirname4(sessionDir));
3279
+ const contextFile = join8(sessionDir, "context.jsonl");
3280
+ const wireFile = join8(sessionDir, "wire.jsonl");
3281
+ const existingContextFile = existsSync7(contextFile) ? contextFile : null;
3282
+ const existingWireFile = existsSync7(wireFile) ? wireFile : null;
2628
3283
  if (!existingContextFile && !existingWireFile) {
2629
3284
  return skippedSession("missing transcript");
2630
3285
  }
2631
- const statePath = join7(sessionDir, "state.json");
2632
- const metaPath = join7(sessionDir, "metadata.json");
3286
+ const statePath = join8(sessionDir, "state.json");
3287
+ const metaPath = join8(sessionDir, "metadata.json");
2633
3288
  let explicitTitle = "";
2634
3289
  let wireMtime = null;
2635
3290
  let metaFile = "";
2636
- if (existsSync6(statePath)) {
3291
+ if (existsSync7(statePath)) {
2637
3292
  const state = asRecord(JSON.parse(readFileSync3(statePath, "utf-8"))) ?? {};
2638
3293
  explicitTitle = String(state.custom_title ?? "");
2639
3294
  wireMtime = readWireMtime(state);
2640
3295
  metaFile = statePath;
2641
- } else if (existsSync6(metaPath)) {
3296
+ } else if (existsSync7(metaPath)) {
2642
3297
  const meta = asRecord(JSON.parse(readFileSync3(metaPath, "utf-8"))) ?? {};
2643
3298
  explicitTitle = String(meta.title ?? "");
2644
3299
  wireMtime = readWireMtime(meta);
2645
3300
  metaFile = metaPath;
2646
3301
  }
2647
- const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync3(metaFile).mtimeMs : statSync3(sessionDir).mtimeMs;
3302
+ const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync4(metaFile).mtimeMs : statSync4(sessionDir).mtimeMs;
2648
3303
  return parsedSession({
2649
3304
  id: sessionId,
2650
3305
  sourcePath: sessionDir,
@@ -2765,8 +3420,8 @@ var KimiAgent = class extends FileSystemSessionSource {
2765
3420
  return this.buildSessionData(meta, builder, stats);
2766
3421
  }
2767
3422
  getSessionDataFromWire(meta) {
2768
- const wirePath = meta.wireFile ?? join7(meta.sourcePath, "wire.jsonl");
2769
- if (!existsSync6(wirePath)) throw new Error("wire.jsonl is missing");
3423
+ const wirePath = meta.wireFile ?? join8(meta.sourcePath, "wire.jsonl");
3424
+ if (!existsSync7(wirePath)) throw new Error("wire.jsonl is missing");
2770
3425
  const builder = new TranscriptBuilder();
2771
3426
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
2772
3427
  const openToolArgumentBuffer = /* @__PURE__ */ new Map();
@@ -2847,7 +3502,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2847
3502
  const rawArgs = function_.arguments;
2848
3503
  const normalizedArgs = normalizeToolArguments(rawArgs);
2849
3504
  const buffer = typeof rawArgs === "string" && typeof normalizedArgs !== "string" ? rawArgs : null;
2850
- const toolPart = {
3505
+ const toolPart2 = {
2851
3506
  type: "tool",
2852
3507
  tool: toolName,
2853
3508
  callID: callId,
@@ -2856,7 +3511,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2856
3511
  time_created: timestampMs
2857
3512
  };
2858
3513
  builder.appendToolCall(
2859
- toolPart,
3514
+ toolPart2,
2860
3515
  { id: `wire-${seq}`, timestampMs: 0, agent: "kimi" },
2861
3516
  { markModeAsTool: true, target: "current" }
2862
3517
  );
@@ -3004,10 +3659,10 @@ var KimiAgent = class extends FileSystemSessionSource {
3004
3659
  total_tokens: 0,
3005
3660
  message_count: 0
3006
3661
  };
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);
3662
+ const wirePath = join8(sessionDir, "wire.jsonl");
3663
+ if (!existsSync7(wirePath)) return stats;
3664
+ const contextPath = join8(sessionDir, "context.jsonl");
3665
+ const hasContext = existsSync7(contextPath);
3011
3666
  try {
3012
3667
  for (const record of readJsonlFile(wirePath)) {
3013
3668
  const tokenUsage = asRecord(asRecord(record.message)?.usage);
@@ -3053,6 +3708,576 @@ var KimiAgent = class extends FileSystemSessionSource {
3053
3708
  };
3054
3709
  }
3055
3710
  };
3711
+ var KIMI_CODE_TOOL_TITLE_MAP = {
3712
+ Read: "read",
3713
+ Write: "write",
3714
+ Edit: "edit",
3715
+ Bash: "bash",
3716
+ TodoList: "todo",
3717
+ AskUserQuestion: "ask",
3718
+ EnterPlanMode: "plan mode",
3719
+ ExitPlanMode: "plan approved",
3720
+ ReadFile: "read",
3721
+ Glob: "glob",
3722
+ StrReplaceFile: "edit",
3723
+ Grep: "grep",
3724
+ WriteFile: "write",
3725
+ Shell: "bash"
3726
+ };
3727
+ var KIMI_CODE_IGNORED_TOOLS = /* @__PURE__ */ new Set(["SetTodoList"]);
3728
+ function resolveKimiCodeDataRoot() {
3729
+ return resolveHomePath("KIMI_CODE_HOME", ".kimi-code");
3730
+ }
3731
+ function mapToolTitle2(toolName) {
3732
+ return KIMI_CODE_TOOL_TITLE_MAP[toolName] ?? toolName;
3733
+ }
3734
+ function normalizeToolArguments2(raw) {
3735
+ if (typeof raw !== "string") return raw;
3736
+ try {
3737
+ return JSON.parse(raw);
3738
+ } catch {
3739
+ return raw;
3740
+ }
3741
+ }
3742
+ function parseTimestamp(raw) {
3743
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
3744
+ if (typeof raw !== "string" || raw.trim() === "") return null;
3745
+ const numeric = Number(raw);
3746
+ if (Number.isFinite(numeric)) return numeric;
3747
+ const parsed = Date.parse(raw);
3748
+ return Number.isFinite(parsed) ? parsed : null;
3749
+ }
3750
+ function timestampFromRecord(record) {
3751
+ return parseTimestamp(record.time) ?? 0;
3752
+ }
3753
+ function contentText(content) {
3754
+ if (typeof content === "string") return content;
3755
+ if (!Array.isArray(content)) {
3756
+ const record = asRecord(content);
3757
+ return record ? String(record.text ?? "") : "";
3758
+ }
3759
+ return content.map((item) => {
3760
+ if (typeof item === "string") return item;
3761
+ const record = asRecord(item);
3762
+ return record ? String(record.text ?? "") : "";
3763
+ }).join(" ");
3764
+ }
3765
+ function contentParts(content, timestampMs) {
3766
+ const values = Array.isArray(content) ? content : [content];
3767
+ const parts = [];
3768
+ for (const value of values) {
3769
+ if (typeof value === "string") {
3770
+ const text = cleanInternalText(value);
3771
+ if (text) parts.push({ type: "text", text, time_created: timestampMs });
3772
+ continue;
3773
+ }
3774
+ const record = asRecord(value);
3775
+ if (!record) continue;
3776
+ const type = asString(record.type) ?? "";
3777
+ if (type === "text") {
3778
+ const text = cleanInternalText(asString(record.text) ?? "");
3779
+ if (text) parts.push({ type: "text", text, time_created: timestampMs });
3780
+ continue;
3781
+ }
3782
+ if (type === "think") {
3783
+ const text = cleanInternalText(asString(record.think) ?? "");
3784
+ if (text) parts.push({ type: "reasoning", text, time_created: timestampMs });
3785
+ continue;
3786
+ }
3787
+ if (type === "plan") {
3788
+ const text = cleanInternalText(asString(record.text) ?? "");
3789
+ if (text) {
3790
+ parts.push({
3791
+ type: "plan",
3792
+ text,
3793
+ approval_status: record.approved === false ? "fail" : "success",
3794
+ time_created: timestampMs
3795
+ });
3796
+ }
3797
+ continue;
3798
+ }
3799
+ if (type === "image") {
3800
+ const source = asRecord(record.source);
3801
+ const imageUrl = asRecord(record.imageUrl);
3802
+ const url = asString(record.url) ?? asString(imageUrl?.url) ?? (source?.kind === "url" ? asString(source.url) : void 0);
3803
+ const data = asString(record.data) ?? (source?.kind === "base64" ? asString(source.data) : void 0);
3804
+ const mimeType = asString(record.mime_type) ?? asString(record.media_type) ?? (source?.kind === "base64" ? asString(source.media_type) : void 0) ?? "application/octet-stream";
3805
+ if (url) parts.push({ type: "image", url, mime_type: mimeType, time_created: timestampMs });
3806
+ else if (data) {
3807
+ parts.push({
3808
+ type: "image",
3809
+ data,
3810
+ mime_type: mimeType,
3811
+ time_created: timestampMs
3812
+ });
3813
+ }
3814
+ continue;
3815
+ }
3816
+ if (type === "image_url") {
3817
+ const imageUrl = asRecord(record.imageUrl);
3818
+ const url = asString(imageUrl?.url) ?? asString(record.url);
3819
+ if (url) parts.push({ type: "image", url, time_created: timestampMs });
3820
+ }
3821
+ }
3822
+ return parts;
3823
+ }
3824
+ function toolOutputParts(output, timestampMs) {
3825
+ if (typeof output === "string") {
3826
+ const text2 = cleanInternalText(output);
3827
+ return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
3828
+ }
3829
+ if (Array.isArray(output)) return contentParts(output, timestampMs);
3830
+ if (output == null) return [];
3831
+ const record = asRecord(output);
3832
+ if (record?.type === "text") return contentParts([record], timestampMs);
3833
+ const text = cleanInternalText(JSON.stringify(output, null, 2));
3834
+ return text ? [{ type: "text", text, time_created: timestampMs }] : [];
3835
+ }
3836
+ function toolPart(toolName, callId, input, timestampMs) {
3837
+ return {
3838
+ type: "tool",
3839
+ tool: toolName,
3840
+ callID: callId,
3841
+ title: mapToolTitle2(toolName),
3842
+ state: {
3843
+ status: "running",
3844
+ ...input === void 0 ? {} : { input },
3845
+ output: null
3846
+ },
3847
+ time_created: timestampMs
3848
+ };
3849
+ }
3850
+ function toolCallParts(message, timestampMs, ignoredToolCallIds) {
3851
+ const calls = asArray(message.toolCalls) ?? [];
3852
+ const parts = [];
3853
+ for (const call of calls) {
3854
+ const callRecord = asRecord(call);
3855
+ const functionRecord = asRecord(callRecord?.function) ?? callRecord;
3856
+ const toolName = asString(functionRecord?.name)?.trim() ?? "";
3857
+ const callId = asString(callRecord?.id)?.trim() ?? "";
3858
+ if (!toolName || !callId) continue;
3859
+ if (KIMI_CODE_IGNORED_TOOLS.has(toolName)) {
3860
+ ignoredToolCallIds.add(callId);
3861
+ continue;
3862
+ }
3863
+ const rawArguments = functionRecord?.arguments ?? callRecord?.arguments;
3864
+ parts.push(toolPart(toolName, callId, normalizeToolArguments2(rawArguments), timestampMs));
3865
+ }
3866
+ return parts;
3867
+ }
3868
+ function addToolResolution(builder, callId, output, timestampMs, isError = false, note) {
3869
+ const outputParts = toolOutputParts(output, timestampMs);
3870
+ return builder.resolveToolCall(callId, {
3871
+ output: outputParts,
3872
+ status: isError ? "error" : "completed",
3873
+ ...note ? { metadata: { note: cleanInternalText(note) } } : {}
3874
+ });
3875
+ }
3876
+ function usageNumber(usage, field) {
3877
+ return asNumber(usage[field]) ?? 0;
3878
+ }
3879
+ function emptyStats() {
3880
+ return {
3881
+ message_count: 0,
3882
+ total_input_tokens: 0,
3883
+ total_output_tokens: 0,
3884
+ total_cost: 0
3885
+ };
3886
+ }
3887
+ function buildUsageTotals() {
3888
+ return {
3889
+ totalCost: 0,
3890
+ totalInputTokens: 0,
3891
+ totalOutputTokens: 0,
3892
+ totalCacheReadTokens: 0,
3893
+ totalCacheCreateTokens: 0,
3894
+ modelUsage: {}
3895
+ };
3896
+ }
3897
+ function applyUsage(builder, record, activeModel, totals) {
3898
+ const usage = asRecord(record.usage);
3899
+ if (!usage) return;
3900
+ const cacheRead = usageNumber(usage, "inputCacheRead");
3901
+ const cacheCreate = usageNumber(usage, "inputCacheCreation");
3902
+ const inputOther = usageNumber(usage, "inputOther");
3903
+ const output = usageNumber(usage, "output");
3904
+ const input = inputOther + cacheRead + cacheCreate;
3905
+ const model = asString(record.model) ?? activeModel;
3906
+ const tokens = {
3907
+ input,
3908
+ output,
3909
+ cache_read: cacheRead,
3910
+ cache_create: cacheCreate
3911
+ };
3912
+ const cost = estimateTokenCost(model, tokens);
3913
+ totals.totalInputTokens += input;
3914
+ totals.totalOutputTokens += output;
3915
+ totals.totalCacheReadTokens += cacheRead;
3916
+ totals.totalCacheCreateTokens += cacheCreate;
3917
+ if (model) totals.modelUsage[model] = (totals.modelUsage[model] ?? 0) + input + output;
3918
+ if (cost !== null) totals.totalCost += cost;
3919
+ builder.attachUsageToLatestAssistant(tokens, {
3920
+ model,
3921
+ cost: cost ?? void 0,
3922
+ costSource: cost === null ? void 0 : "estimated"
3923
+ });
3924
+ }
3925
+ function readState(stateFile) {
3926
+ try {
3927
+ return asRecord(JSON.parse(readFileSync4(stateFile, "utf-8"))) ?? null;
3928
+ } catch {
3929
+ return null;
3930
+ }
3931
+ }
3932
+ var KimiCodeAgent = class extends FileSystemSessionSource {
3933
+ name = "kimi-code";
3934
+ displayName = "Kimi-Code";
3935
+ basePath = null;
3936
+ workDirBySessionPath = /* @__PURE__ */ new Map();
3937
+ findBasePath() {
3938
+ const sessionsPath = join9(resolveKimiCodeDataRoot(), "sessions");
3939
+ return existsSync8(sessionsPath) ? sessionsPath : null;
3940
+ }
3941
+ getSessionWatchPlan() {
3942
+ const dataRoot = resolveKimiCodeDataRoot();
3943
+ return {
3944
+ status: "supported",
3945
+ targets: [
3946
+ { root: dataRoot, path: join9(dataRoot, "sessions") },
3947
+ { root: dataRoot, path: join9(dataRoot, "session_index.jsonl") }
3948
+ ]
3949
+ };
3950
+ }
3951
+ isAvailable() {
3952
+ this.basePath = this.findBasePath();
3953
+ if (!this.basePath) return false;
3954
+ try {
3955
+ return this.listSessionDirs().length > 0;
3956
+ } catch {
3957
+ return false;
3958
+ }
3959
+ }
3960
+ loadSessionIndex() {
3961
+ this.workDirBySessionPath.clear();
3962
+ if (!this.basePath) return;
3963
+ const indexPath = join9(dirname5(this.basePath), "session_index.jsonl");
3964
+ if (!existsSync8(indexPath)) return;
3965
+ for (const record of readJsonlFile(indexPath)) {
3966
+ const sessionPath = asString(record.sessionDir);
3967
+ const workDir = asString(record.workDir);
3968
+ if (!sessionPath || !workDir) continue;
3969
+ this.workDirBySessionPath.set(resolve(sessionPath), workDir);
3970
+ }
3971
+ }
3972
+ listSessionDirs() {
3973
+ if (!this.basePath) return [];
3974
+ const dirs = [];
3975
+ try {
3976
+ for (const bucket of readdirSync5(this.basePath, { withFileTypes: true })) {
3977
+ if (!bucket.isDirectory()) continue;
3978
+ const bucketPath = join9(this.basePath, bucket.name);
3979
+ for (const session of readdirSync5(bucketPath, { withFileTypes: true })) {
3980
+ if (!session.isDirectory()) continue;
3981
+ const sessionPath = join9(bucketPath, session.name);
3982
+ if (existsSync8(join9(sessionPath, "state.json")) && existsSync8(join9(sessionPath, "agents", "main", "wire.jsonl"))) {
3983
+ dirs.push(sessionPath);
3984
+ }
3985
+ }
3986
+ }
3987
+ } catch {
3988
+ return dirs;
3989
+ }
3990
+ return dirs;
3991
+ }
3992
+ resolveSessionSourceResult(sessionDir) {
3993
+ try {
3994
+ const stateFile = join9(sessionDir, "state.json");
3995
+ const wireFile = join9(sessionDir, "agents", "main", "wire.jsonl");
3996
+ if (!existsSync8(stateFile) || !existsSync8(wireFile)) return skippedSession("missing wire");
3997
+ const state = readState(stateFile);
3998
+ if (!state) return skippedSession("malformed state");
3999
+ const stateMtime = statSync5(stateFile).mtimeMs;
4000
+ const wireMtime = statSync5(wireFile).mtimeMs;
4001
+ const createdAt = parseTimestamp(state.createdAt) ?? parseTimestamp(state.created_at) ?? stateMtime;
4002
+ const updatedAt = Math.max(
4003
+ parseTimestamp(state.updatedAt) ?? parseTimestamp(state.updated_at) ?? createdAt,
4004
+ wireMtime
4005
+ );
4006
+ const custom = asRecord(state.custom);
4007
+ const workDir = asString(state.workDir) ?? asString(custom?.cwd) ?? this.workDirBySessionPath.get(resolve(sessionDir)) ?? "";
4008
+ const explicitTitle = asString(state.title) ?? asString(state.customTitle) ?? "";
4009
+ return parsedSession({
4010
+ id: basename6(sessionDir),
4011
+ sourcePath: sessionDir,
4012
+ stateFile,
4013
+ wireFile,
4014
+ workDir,
4015
+ createdAt,
4016
+ updatedAt,
4017
+ explicitTitle
4018
+ });
4019
+ } catch {
4020
+ return skippedSession("malformed session");
4021
+ }
4022
+ }
4023
+ sourceFingerprint(source) {
4024
+ return JSON.stringify([
4025
+ this.readFileMtimeMs(source.stateFile),
4026
+ this.readFileMtimeMs(source.wireFile),
4027
+ source.workDir
4028
+ ]);
4029
+ }
4030
+ listSessionSources(options) {
4031
+ if (!this.basePath) return [];
4032
+ this.loadSessionIndex();
4033
+ const refs = [];
4034
+ for (const sessionDir of this.listSessionDirs()) {
4035
+ const source = getParsedSession(this.resolveSessionSourceResult(sessionDir));
4036
+ if (!source || !matchesScanWindow(source.createdAt, options)) continue;
4037
+ refs.push({
4038
+ sessionId: source.id,
4039
+ sourcePath: source.sourcePath,
4040
+ fingerprint: this.sourceFingerprint(source)
4041
+ });
4042
+ }
4043
+ return refs;
4044
+ }
4045
+ checkForChanges(sinceTimestamp, cachedSessions) {
4046
+ const result = super.checkForChanges(sinceTimestamp, cachedSessions);
4047
+ const emptySessionIds = cachedSessions.filter((session) => !this.hasMessages(session)).map((session) => session.id);
4048
+ if (emptySessionIds.length === 0) return result;
4049
+ return {
4050
+ ...result,
4051
+ hasChanges: true,
4052
+ changedIds: [.../* @__PURE__ */ new Set([...result.changedIds ?? [], ...emptySessionIds])]
4053
+ };
4054
+ }
4055
+ filterCachedSessions(sessions) {
4056
+ return this.removeEmptyCachedSessions(sessions);
4057
+ }
4058
+ incrementalScan(cachedSessions, changedIds, refs) {
4059
+ const visibleSessions = this.removeEmptyCachedSessions(cachedSessions);
4060
+ return super.incrementalScan(visibleSessions, changedIds, refs);
4061
+ }
4062
+ hasMessages(session) {
4063
+ return session.stats.message_count > 0;
4064
+ }
4065
+ removeEmptyCachedSessions(sessions) {
4066
+ return sessions.filter((session) => {
4067
+ if (this.hasMessages(session)) return true;
4068
+ this.sessionMetaMap.delete(session.id);
4069
+ return false;
4070
+ });
4071
+ }
4072
+ scanSessionSource(sourcePath) {
4073
+ this.loadSessionIndex();
4074
+ const source = getParsedSession(this.resolveSessionSourceResult(sourcePath));
4075
+ if (!source) return null;
4076
+ const parsed = this.parseWire(source);
4077
+ const transcript = parsed.builder.finish(parsed.stats);
4078
+ if (transcript.messages.length === 0) {
4079
+ this.sessionMetaMap.delete(source.id);
4080
+ return null;
4081
+ }
4082
+ const title = resolveSessionTitle(source.explicitTitle, parsed.firstUserTitle, null);
4083
+ const meta = {
4084
+ ...source,
4085
+ title,
4086
+ sourceMtimeMs: source.createdAt,
4087
+ sourceFingerprint: this.sourceFingerprint(source)
4088
+ };
4089
+ this.sessionMetaMap.set(meta.id, meta);
4090
+ return {
4091
+ id: meta.id,
4092
+ slug: `${this.name}/${meta.id}`,
4093
+ title: meta.title,
4094
+ directory: meta.workDir,
4095
+ time_created: meta.createdAt,
4096
+ time_updated: meta.updatedAt,
4097
+ stats: transcript.stats,
4098
+ ...Object.keys(parsed.modelUsage).length > 0 ? { model_usage: parsed.modelUsage } : {}
4099
+ };
4100
+ }
4101
+ getSessionData(sessionId) {
4102
+ const meta = this.sessionMetaMap.get(sessionId);
4103
+ if (!meta) throw new Error(`Session not found: ${sessionId}`);
4104
+ const parsed = this.parseWire(meta);
4105
+ const transcript = parsed.builder.finish(parsed.stats);
4106
+ return {
4107
+ reference: { agentName: this.name, sessionId: meta.id },
4108
+ id: meta.id,
4109
+ title: meta.title,
4110
+ slug: `${this.name}/${meta.id}`,
4111
+ directory: meta.workDir,
4112
+ time_created: meta.createdAt,
4113
+ time_updated: meta.updatedAt,
4114
+ stats: transcript.stats,
4115
+ messages: transcript.messages
4116
+ };
4117
+ }
4118
+ parseWire(source) {
4119
+ const builder = new TranscriptBuilder();
4120
+ const totals = buildUsageTotals();
4121
+ const ignoredToolCallIds = /* @__PURE__ */ new Set();
4122
+ let activeModel = null;
4123
+ let activeProvider = null;
4124
+ let firstUserTitle = null;
4125
+ let sequence = 0;
4126
+ for (const record of readJsonlFile(source.wireFile)) {
4127
+ sequence += 1;
4128
+ try {
4129
+ const timestampMs = timestampFromRecord(record);
4130
+ const recordType = asString(record.type) ?? "";
4131
+ if (recordType === "llm.request") {
4132
+ activeModel = asString(record.model) ?? activeModel;
4133
+ activeProvider = asString(record.provider) ?? activeProvider;
4134
+ continue;
4135
+ }
4136
+ if (recordType === "config.update") {
4137
+ activeModel = asString(record.modelAlias) ?? activeModel;
4138
+ continue;
4139
+ }
4140
+ if (recordType === "usage.record") {
4141
+ applyUsage(builder, record, activeModel, totals);
4142
+ continue;
4143
+ }
4144
+ if (recordType === "context.append_message") {
4145
+ const message = asRecord(record.message);
4146
+ if (!message) continue;
4147
+ const role = asString(message.role) ?? "";
4148
+ const messageTimestamp = timestampMs;
4149
+ if (role === "user") {
4150
+ const text = normalizeTitleText(contentText(message.content));
4151
+ if (!firstUserTitle && text) firstUserTitle = text;
4152
+ }
4153
+ if (role === "tool") {
4154
+ const callId = asString(message.toolCallId)?.trim() ?? "";
4155
+ const output = message.content;
4156
+ if (callId && ignoredToolCallIds.has(callId)) continue;
4157
+ if (callId && addToolResolution(builder, callId, output, messageTimestamp)) continue;
4158
+ const outputParts = toolOutputParts(output, messageTimestamp);
4159
+ if (outputParts.length > 0) {
4160
+ builder.appendMessage({
4161
+ id: `wire-${sequence}`,
4162
+ role: "tool",
4163
+ timestampMs: messageTimestamp,
4164
+ parts: outputParts
4165
+ });
4166
+ }
4167
+ continue;
4168
+ }
4169
+ if (role !== "user" && role !== "assistant") continue;
4170
+ const parts = [
4171
+ ...contentParts(message.content, messageTimestamp),
4172
+ ...role === "assistant" ? toolCallParts(message, messageTimestamp, ignoredToolCallIds) : []
4173
+ ];
4174
+ if (parts.length === 0) continue;
4175
+ const allTools = parts.every((part) => part.type === "tool");
4176
+ const input = {
4177
+ id: `wire-${sequence}`,
4178
+ role,
4179
+ timestampMs: messageTimestamp,
4180
+ parts,
4181
+ ...role === "assistant" ? {
4182
+ agent: this.name,
4183
+ mode: allTools ? "tool" : void 0,
4184
+ model: activeModel,
4185
+ provider: activeProvider
4186
+ } : {}
4187
+ };
4188
+ builder.appendMessage(input);
4189
+ continue;
4190
+ }
4191
+ if (recordType === "context.append_loop_event") {
4192
+ const event = asRecord(record.event);
4193
+ if (!event) continue;
4194
+ const eventType = asString(event.type) ?? "";
4195
+ const metadata = {
4196
+ id: `wire-${sequence}`,
4197
+ timestampMs,
4198
+ agent: this.name,
4199
+ model: activeModel,
4200
+ provider: activeProvider
4201
+ };
4202
+ if (eventType === "step.begin") {
4203
+ builder.beginTurn();
4204
+ continue;
4205
+ }
4206
+ if (eventType === "content.part") {
4207
+ const part = asRecord(event.part);
4208
+ if (!part) continue;
4209
+ const parts = contentParts([part], timestampMs);
4210
+ for (const contentPart of parts) {
4211
+ if (contentPart.type === "tool") continue;
4212
+ if (contentPart.type === "image" && builder.appendToCurrentAssistant(contentPart)) {
4213
+ continue;
4214
+ }
4215
+ builder.appendAssistantPart(contentPart, metadata, { grouping: "current" });
4216
+ }
4217
+ continue;
4218
+ }
4219
+ if (eventType === "tool.call") {
4220
+ const toolName = asString(event.name)?.trim() ?? "";
4221
+ const callId = asString(event.toolCallId)?.trim() ?? "";
4222
+ if (!toolName || !callId) continue;
4223
+ if (KIMI_CODE_IGNORED_TOOLS.has(toolName)) {
4224
+ ignoredToolCallIds.add(callId);
4225
+ continue;
4226
+ }
4227
+ builder.appendToolCall(toolPart(toolName, callId, event.args, timestampMs), metadata, {
4228
+ markModeAsTool: true,
4229
+ target: "current"
4230
+ });
4231
+ continue;
4232
+ }
4233
+ if (eventType === "tool.result") {
4234
+ const callId = asString(event.toolCallId)?.trim() ?? "";
4235
+ const result = asRecord(event.result);
4236
+ if (!callId || ignoredToolCallIds.has(callId)) continue;
4237
+ const output = result?.output;
4238
+ const isError = result?.isError === true;
4239
+ const note = asString(result?.note);
4240
+ if (addToolResolution(builder, callId, output, timestampMs, isError, note)) continue;
4241
+ const outputParts = toolOutputParts(output, timestampMs);
4242
+ if (outputParts.length > 0) {
4243
+ builder.appendMessage({
4244
+ id: `wire-${sequence}`,
4245
+ role: "tool",
4246
+ timestampMs,
4247
+ parts: outputParts
4248
+ });
4249
+ }
4250
+ }
4251
+ continue;
4252
+ }
4253
+ if (recordType === "context.apply_compaction") {
4254
+ const summary = cleanInternalText(asString(record.summary) ?? "");
4255
+ if (summary) {
4256
+ builder.appendMessage({
4257
+ id: `wire-${sequence}`,
4258
+ role: "user",
4259
+ timestampMs,
4260
+ parts: [{ type: "text", text: summary, time_created: timestampMs }]
4261
+ });
4262
+ }
4263
+ }
4264
+ } catch {
4265
+ continue;
4266
+ }
4267
+ }
4268
+ const stats = {
4269
+ ...emptyStats(),
4270
+ total_input_tokens: totals.totalInputTokens,
4271
+ total_output_tokens: totals.totalOutputTokens,
4272
+ total_cost: Number(totals.totalCost.toFixed(8)),
4273
+ total_tokens: totals.totalInputTokens + totals.totalOutputTokens,
4274
+ ...totals.totalCacheReadTokens > 0 ? { total_cache_read_tokens: totals.totalCacheReadTokens } : {},
4275
+ ...totals.totalCacheCreateTokens > 0 ? { total_cache_create_tokens: totals.totalCacheCreateTokens } : {},
4276
+ ...totals.totalCost > 0 ? { cost_source: "estimated" } : {}
4277
+ };
4278
+ return { builder, stats, firstUserTitle, modelUsage: totals.modelUsage };
4279
+ }
4280
+ };
3056
4281
  var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
3057
4282
  var EXEC_OUTPUT_ENVELOPE_RE = /^Script completed\nWall time [^\n]*\nOutput:\n?/;
3058
4283
  function stripExecOutputEnvelope(text) {
@@ -3314,7 +4539,7 @@ var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
3314
4539
  var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
3315
4540
  var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
3316
4541
  var HEAD_INDEX_VERSION2 = "codex-head-v1";
3317
- var PARSER_VERSION = "codex-parser-v4";
4542
+ var PARSER_VERSION = "codex-parser-v6";
3318
4543
  function resolveCodexDataRoot() {
3319
4544
  return resolveHomePath("CODEX_HOME", ".codex");
3320
4545
  }
@@ -3337,7 +4562,7 @@ var CODEX_TOOL_TITLE_MAP = {
3337
4562
  subagent: "subagent"
3338
4563
  };
3339
4564
  function extractSessionId(filename) {
3340
- const stem = basename5(filename, ".jsonl");
4565
+ const stem = basename7(filename, ".jsonl");
3341
4566
  const parts = stem.split("-");
3342
4567
  if (parts.length >= 5) {
3343
4568
  return parts.slice(-5).join("-");
@@ -3366,6 +4591,14 @@ function narrowRecordField(value, field) {
3366
4591
  function extractPayload(data) {
3367
4592
  return narrowRecordField(data["payload"], "payload") ?? {};
3368
4593
  }
4594
+ function extractThreadMeta(firstRecord) {
4595
+ if (firstRecord["type"] !== "session_meta") return null;
4596
+ const payload = extractPayload(firstRecord);
4597
+ const threadSource = asString(payload["thread_source"]) ?? "";
4598
+ const parentThreadId = asString(payload["parent_thread_id"]) ?? null;
4599
+ const agentNickname = asString(payload["agent_nickname"]) ?? null;
4600
+ return { threadSource, parentThreadId, agentNickname };
4601
+ }
3369
4602
  function extractTokenUsage(payload) {
3370
4603
  const info = narrowRecordField(payload["info"], "token_count.info");
3371
4604
  return {
@@ -3391,7 +4624,7 @@ function resolveToolIdentity(name, namespace) {
3391
4624
  metadata: { name, namespace: namespaceText }
3392
4625
  };
3393
4626
  }
3394
- function normalizeToolArguments2(raw) {
4627
+ function normalizeToolArguments3(raw) {
3395
4628
  if (typeof raw === "string") {
3396
4629
  try {
3397
4630
  return JSON.parse(raw);
@@ -3418,6 +4651,16 @@ function flattenOutputText(output) {
3418
4651
  }
3419
4652
  return "";
3420
4653
  }
4654
+ function extractAssistantOutputText(payload) {
4655
+ const content = payload["content"];
4656
+ if (Array.isArray(content)) {
4657
+ return content.map((item) => {
4658
+ const record = asRecord(item);
4659
+ return record && String(record["type"] ?? "") === "output_text" ? String(record["text"] ?? "") : "";
4660
+ }).filter(Boolean).join("\n");
4661
+ }
4662
+ return "";
4663
+ }
3421
4664
  var PATCH_BEGIN_RE = /\*\*\* Begin Patch/;
3422
4665
  var PATCH_END_RE = /\*\*\* End Patch/;
3423
4666
  var PATCH_HEADER_RE = /\*\*\*\s+(Add|Delete|Update|Move)\s+File:\s*(.+)/;
@@ -3500,6 +4743,18 @@ function extractPatchContent(lines, startIndex) {
3500
4743
  }
3501
4744
  return { text: contentLines.join("\n"), nextLineIndex: i };
3502
4745
  }
4746
+ function compareSourceActivityDesc(left, right) {
4747
+ const leftTimestamp = sourceTimestamp(left.file, left.stat.mtimeMs);
4748
+ const rightTimestamp = sourceTimestamp(right.file, right.stat.mtimeMs);
4749
+ return rightTimestamp - leftTimestamp || left.file.localeCompare(right.file);
4750
+ }
4751
+ function sourceTimestamp(filePath, fallback) {
4752
+ const match = basename7(filePath).match(/^rollout-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-/);
4753
+ if (!match) return fallback;
4754
+ const timestamp = match[1].replace(/-(\d{2})-(\d{2})$/, ":$1:$2");
4755
+ const parsed = Date.parse(timestamp);
4756
+ return Number.isFinite(parsed) ? parsed : fallback;
4757
+ }
3503
4758
  var CodexAgent = class extends SingleFileSessionSource {
3504
4759
  name = "codex";
3505
4760
  displayName = "Codex";
@@ -3507,17 +4762,19 @@ var CodexAgent = class extends SingleFileSessionSource {
3507
4762
  sessionIndexCache = /* @__PURE__ */ new Map();
3508
4763
  sessionIndexMtime;
3509
4764
  sessionIndexPath;
4765
+ subagentIndex = null;
4766
+ subagentStatsByParent = /* @__PURE__ */ new Map();
3510
4767
  // ---- BaseAgent implementation ----
3511
4768
  findBasePath() {
3512
- return firstExisting(join8(resolveCodexDataRoot(), "sessions"));
4769
+ return firstExisting(join10(resolveCodexDataRoot(), "sessions"));
3513
4770
  }
3514
4771
  getSessionWatchPlan() {
3515
4772
  const dataRoot = resolveCodexDataRoot();
3516
4773
  return {
3517
4774
  status: "supported",
3518
4775
  targets: [
3519
- { path: join8(dataRoot, "sessions") },
3520
- { path: join8(dataRoot, "session_index.jsonl") }
4776
+ { path: join10(dataRoot, "sessions") },
4777
+ { path: join10(dataRoot, "session_index.jsonl") }
3521
4778
  ]
3522
4779
  };
3523
4780
  }
@@ -3529,16 +4786,119 @@ var CodexAgent = class extends SingleFileSessionSource {
3529
4786
  listSessionSources(options) {
3530
4787
  if (!this.basePath) return [];
3531
4788
  this.loadSessionIndex();
3532
- return this.listRolloutFiles(options).map(({ file, stat }) => ({
4789
+ return this.listScanSources(options).map(({ file, stat }) => ({
3533
4790
  sessionId: extractSessionId(file),
3534
4791
  sourcePath: file,
3535
4792
  fingerprint: this.sourceFingerprint(file, stat)
3536
4793
  }));
3537
4794
  }
4795
+ setSessionMetaMap(meta) {
4796
+ super.setSessionMetaMap(meta);
4797
+ this.subagentIndex = null;
4798
+ this.subagentStatsByParent.clear();
4799
+ }
4800
+ /**
4801
+ * A changed subagent file leaves its parent's aggregated token stats stale,
4802
+ * so the parent must re-parse alongside the child.
4803
+ */
4804
+ expandChangedSessionIds(changedIds, refs) {
4805
+ if (changedIds.length === 0) return changedIds;
4806
+ const pathById = new Map((refs ?? []).map((ref) => [ref.sessionId, ref.sourcePath]));
4807
+ const expanded = new Set(changedIds);
4808
+ for (const id of changedIds) {
4809
+ const sourcePath = pathById.get(id) ?? this.sessionMetaMap.get(id)?.sourcePath;
4810
+ const threadMeta = sourcePath ? this.readThreadMeta(sourcePath) : null;
4811
+ const parentId = threadMeta ? threadMeta.threadSource === "subagent" ? threadMeta.parentThreadId : null : this.sessionMetaMap.get(id)?.parentThreadId ?? null;
4812
+ if (!parentId) continue;
4813
+ expanded.add(parentId);
4814
+ this.subagentIndex = null;
4815
+ this.subagentStatsByParent.delete(parentId);
4816
+ }
4817
+ return [...expanded];
4818
+ }
4819
+ readThreadMeta(filePath) {
4820
+ try {
4821
+ const firstLine = this.readFilePrefix(filePath).split("\n").filter((l) => l.trim())[0];
4822
+ if (!firstLine) return null;
4823
+ return extractThreadMeta(JSON.parse(firstLine));
4824
+ } catch {
4825
+ return null;
4826
+ }
4827
+ }
4828
+ parseTokenStats(filePath) {
4829
+ let totalInputTokens = 0;
4830
+ let totalOutputTokens = 0;
4831
+ let totalCacheReadTokens = 0;
4832
+ let totalCost = 0;
4833
+ let activeModel = null;
4834
+ let prevCumulativeTotal = 0;
4835
+ let prevInput = 0;
4836
+ let prevOutput = 0;
4837
+ let prevReasoning = 0;
4838
+ let prevCachedInput = 0;
4839
+ for (const line of readJsonlFileLines(filePath)) {
4840
+ try {
4841
+ const data = JSON.parse(line);
4842
+ const recordType = String(data["type"] ?? "");
4843
+ const payload = extractPayload(data);
4844
+ if (recordType === "session_meta" || recordType === "turn_context") {
4845
+ const nextModel = extractModelName(payload["model"]);
4846
+ if (nextModel) activeModel = nextModel;
4847
+ continue;
4848
+ }
4849
+ if (recordType === "event_msg" && String(payload["type"] ?? "") === "token_count") {
4850
+ const { totalUsage, lastUsage } = extractTokenUsage(payload);
4851
+ const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
4852
+ if (cumulativeTotal <= 0 || cumulativeTotal === prevCumulativeTotal) continue;
4853
+ prevCumulativeTotal = cumulativeTotal;
4854
+ let inputTokens = 0;
4855
+ let outputTokens = 0;
4856
+ let reasoningTokens = 0;
4857
+ let cacheReadTokens = 0;
4858
+ if (lastUsage) {
4859
+ inputTokens = Number(lastUsage["input_tokens"] ?? 0);
4860
+ outputTokens = Number(lastUsage["output_tokens"] ?? 0);
4861
+ reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
4862
+ cacheReadTokens = extractCachedInputTokens(lastUsage);
4863
+ } else if (totalUsage) {
4864
+ inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
4865
+ outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
4866
+ reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
4867
+ cacheReadTokens = extractCachedInputTokens(totalUsage) - prevCachedInput;
4868
+ prevInput = Number(totalUsage["input_tokens"] ?? 0);
4869
+ prevOutput = Number(totalUsage["output_tokens"] ?? 0);
4870
+ prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
4871
+ prevCachedInput = extractCachedInputTokens(totalUsage);
4872
+ }
4873
+ const totalInput = Math.max(0, inputTokens);
4874
+ const totalCacheRead = Math.max(0, cacheReadTokens);
4875
+ totalInputTokens += totalInput;
4876
+ totalOutputTokens += outputTokens + reasoningTokens;
4877
+ totalCacheReadTokens += totalCacheRead;
4878
+ totalCost += estimateTokenCost(activeModel, {
4879
+ input: totalInput,
4880
+ output: outputTokens,
4881
+ reasoning: reasoningTokens || void 0,
4882
+ cache_read: totalCacheRead || void 0
4883
+ }) ?? 0;
4884
+ }
4885
+ } catch {
4886
+ }
4887
+ }
4888
+ return {
4889
+ message_count: 0,
4890
+ total_input_tokens: totalInputTokens,
4891
+ total_output_tokens: totalOutputTokens,
4892
+ total_cache_read_tokens: totalCacheReadTokens || void 0,
4893
+ total_cost: totalCost,
4894
+ cost_source: totalCost > 0 ? "estimated" : void 0
4895
+ };
4896
+ }
3538
4897
  getSessionData(sessionId) {
3539
4898
  const meta = this.sessionMetaMap.get(sessionId);
3540
4899
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
3541
- if (!existsSync7(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
4900
+ if (!existsSync9(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
4901
+ this.basePath ??= this.findBasePath();
3542
4902
  const transcript = new TranscriptBuilder();
3543
4903
  let totalInputTokens = 0;
3544
4904
  let totalOutputTokens = 0;
@@ -3621,18 +4981,119 @@ var CodexAgent = class extends SingleFileSessionSource {
3621
4981
  total_cost: totalCost,
3622
4982
  cost_source: totalCost > 0 ? "estimated" : void 0
3623
4983
  });
4984
+ this.applyChildStats(result.stats, meta.id);
4985
+ const childMessages = this.collectChildMessages(meta.id);
4986
+ for (const message of childMessages) {
4987
+ const messageText = message.parts.find((part) => part.type === "text")?.text;
4988
+ const alreadyVisible = result.messages.some(
4989
+ (existing) => message.subagent_id !== void 0 && existing.subagent_id === message.subagent_id || existing.subagent_id === void 0 && message.nickname !== void 0 && messageText !== void 0 && existing.nickname === message.nickname && existing.parts.some((part) => part.type === "text" && part.text === messageText)
4990
+ );
4991
+ if (!alreadyVisible) result.messages.push(message);
4992
+ }
4993
+ result.stats.message_count = result.messages.length;
3624
4994
  return {
3625
4995
  reference: { agentName: this.name, sessionId: meta.id },
3626
4996
  id: meta.id,
3627
4997
  title: meta.title,
3628
4998
  slug: `codex/${meta.id}`,
3629
4999
  directory: meta.directory,
5000
+ parent_reference: meta.parentThreadId == null ? void 0 : { agentName: this.name, sessionId: meta.parentThreadId },
3630
5001
  time_created: meta.createdAt,
3631
5002
  time_updated: meta.updatedAt,
3632
5003
  stats: result.stats,
3633
5004
  messages: result.messages
3634
5005
  };
3635
5006
  }
5007
+ /**
5008
+ * Builds the complete parent→children map in one prefix sweep. A cache miss
5009
+ * afterwards means "no children", so per-session directory rescans (the old
5010
+ * O(N²) finalization hotspot) never happen.
5011
+ */
5012
+ ensureSubagentIndex() {
5013
+ if (this.subagentIndex) return this.subagentIndex;
5014
+ this.basePath ??= this.findBasePath();
5015
+ const index = { childFilesByParent: /* @__PURE__ */ new Map(), subagentFiles: /* @__PURE__ */ new Set() };
5016
+ for (const file of this.listRolloutFilePaths()) {
5017
+ const threadMeta = this.readThreadMeta(file);
5018
+ if (threadMeta?.threadSource !== "subagent") continue;
5019
+ index.subagentFiles.add(file);
5020
+ if (!threadMeta.parentThreadId) continue;
5021
+ const files = index.childFilesByParent.get(threadMeta.parentThreadId);
5022
+ if (files) files.push(file);
5023
+ else index.childFilesByParent.set(threadMeta.parentThreadId, [file]);
5024
+ }
5025
+ this.subagentIndex = index;
5026
+ return index;
5027
+ }
5028
+ applyChildStats(target, sessionId) {
5029
+ for (const stats of this.collectChildStats(sessionId)) {
5030
+ target.total_input_tokens += stats.total_input_tokens ?? 0;
5031
+ target.total_output_tokens += stats.total_output_tokens ?? 0;
5032
+ target.total_cost += stats.total_cost ?? 0;
5033
+ if (stats.total_cache_read_tokens) {
5034
+ target.total_cache_read_tokens = (target.total_cache_read_tokens ?? 0) + stats.total_cache_read_tokens;
5035
+ }
5036
+ }
5037
+ }
5038
+ collectChildStats(parentSessionId) {
5039
+ const files = this.collectChildFiles(parentSessionId);
5040
+ if (files.length === 0) return [];
5041
+ const cached = this.subagentStatsByParent.get(parentSessionId);
5042
+ if (cached) return cached;
5043
+ const stats = files.map((file) => this.parseTokenStats(file));
5044
+ this.subagentStatsByParent.set(parentSessionId, stats);
5045
+ return stats;
5046
+ }
5047
+ collectChildFiles(parentSessionId) {
5048
+ return this.ensureSubagentIndex().childFilesByParent.get(parentSessionId) ?? [];
5049
+ }
5050
+ collectChildMessages(parentSessionId) {
5051
+ return this.collectChildFiles(parentSessionId).flatMap((file) => {
5052
+ const message = this.parseChildFinalMessage(file);
5053
+ return message ? [message] : [];
5054
+ }).sort((left, right) => left.time_created - right.time_created);
5055
+ }
5056
+ parseChildFinalMessage(filePath) {
5057
+ const sessionId = extractSessionId(filePath);
5058
+ const threadMeta = this.readThreadMeta(filePath);
5059
+ let latestOutput = null;
5060
+ let finalOutput = null;
5061
+ for (const record of readJsonlFile(filePath)) {
5062
+ try {
5063
+ const recordType = String(record["type"] ?? "");
5064
+ if (recordType !== "response_item") continue;
5065
+ const payload = extractPayload(record);
5066
+ if (String(payload["type"] ?? "") !== "message") continue;
5067
+ if (String(payload["role"] ?? "") !== "assistant") continue;
5068
+ const text = cleanInternalText(extractAssistantOutputText(payload));
5069
+ if (!text) continue;
5070
+ const candidate = {
5071
+ id: asString(payload["id"]) ?? `codex-subagent-${sessionId}`,
5072
+ text,
5073
+ timestampMs: parseTimestampMs2(record) || parseTimestampMs2(payload) || statSync6(filePath).mtimeMs,
5074
+ isFinal: String(record["phase"] ?? "") === "final_answer" || String(payload["phase"] ?? "") === "final_answer"
5075
+ };
5076
+ latestOutput = candidate;
5077
+ if (candidate.isFinal) finalOutput = candidate;
5078
+ } catch {
5079
+ }
5080
+ }
5081
+ const selected = finalOutput ?? latestOutput;
5082
+ if (!selected) return null;
5083
+ return {
5084
+ id: selected.id,
5085
+ role: "assistant",
5086
+ agent: "codex",
5087
+ time_created: selected.timestampMs,
5088
+ mode: null,
5089
+ model: null,
5090
+ provider: null,
5091
+ cost: 0,
5092
+ subagent_id: sessionId,
5093
+ nickname: threadMeta?.agentNickname ?? void 0,
5094
+ parts: [{ type: "text", text: selected.text, time_created: selected.timestampMs }]
5095
+ };
5096
+ }
3636
5097
  // ---- File listing ----
3637
5098
  listRolloutFiles(options) {
3638
5099
  if (!this.basePath) return [];
@@ -3642,6 +5103,54 @@ var CodexAgent = class extends SingleFileSessionSource {
3642
5103
  { scanWindow: options }
3643
5104
  );
3644
5105
  }
5106
+ /** Path-only listing for the subagent index: no per-file stat needed. */
5107
+ listRolloutFilePaths() {
5108
+ if (!this.basePath) return [];
5109
+ const paths = [];
5110
+ const walk = (directory) => {
5111
+ let entries;
5112
+ try {
5113
+ entries = readdirSync6(directory, { withFileTypes: true });
5114
+ } catch {
5115
+ return;
5116
+ }
5117
+ for (const entry of entries) {
5118
+ const filePath = join10(directory, entry.name);
5119
+ if (entry.isDirectory()) walk(filePath);
5120
+ else if (entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
5121
+ paths.push(filePath);
5122
+ }
5123
+ }
5124
+ };
5125
+ walk(this.basePath);
5126
+ return paths;
5127
+ }
5128
+ listScanSources(options) {
5129
+ const windowed = this.listRolloutFiles(options).sort(compareSourceActivityDesc);
5130
+ if (options?.from == null && options?.to == null) return windowed;
5131
+ const { childFilesByParent, subagentFiles } = this.ensureSubagentIndex();
5132
+ const rootFiles = windowed.filter((source) => !subagentFiles.has(source.file));
5133
+ const rootIds = new Set(rootFiles.map(({ file }) => extractSessionId(file)));
5134
+ if (rootIds.size === 0) return rootFiles;
5135
+ const selected = new Map(rootFiles.map((source) => [source.file, source]));
5136
+ const pending2 = [...rootIds];
5137
+ const seenParents = /* @__PURE__ */ new Set();
5138
+ while (pending2.length > 0) {
5139
+ const parentId = pending2.pop();
5140
+ if (seenParents.has(parentId)) continue;
5141
+ seenParents.add(parentId);
5142
+ for (const file of childFilesByParent.get(parentId) ?? []) {
5143
+ if (selected.has(file)) continue;
5144
+ try {
5145
+ selected.set(file, this.sessionSourceFile(file));
5146
+ } catch {
5147
+ continue;
5148
+ }
5149
+ pending2.push(extractSessionId(file));
5150
+ }
5151
+ }
5152
+ return [...selected.values()].sort(compareSourceActivityDesc);
5153
+ }
3645
5154
  createFileSessionMeta(head, source) {
3646
5155
  const indexPath = this.getSessionIndexPath();
3647
5156
  const indexMtime = this.sessionIndexMtime ?? null;
@@ -3654,7 +5163,8 @@ var CodexAgent = class extends SingleFileSessionSource {
3654
5163
  indexMtimeMs: indexMtime,
3655
5164
  headIndexVersion: HEAD_INDEX_VERSION2,
3656
5165
  parserVersion: PARSER_VERSION,
3657
- model: null
5166
+ model: null,
5167
+ parentThreadId: head.parent_reference?.sessionId ?? null
3658
5168
  }
3659
5169
  });
3660
5170
  }
@@ -3670,7 +5180,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3670
5180
  ]);
3671
5181
  }
3672
5182
  getSessionIndexPath() {
3673
- this.sessionIndexPath ??= join8(resolveCodexDataRoot(), "session_index.jsonl");
5183
+ this.sessionIndexPath ??= join10(resolveCodexDataRoot(), "session_index.jsonl");
3674
5184
  return this.sessionIndexPath;
3675
5185
  }
3676
5186
  // ---- Session index ----
@@ -3684,7 +5194,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3684
5194
  return;
3685
5195
  }
3686
5196
  try {
3687
- const content = readFileSync4(indexPath, "utf-8");
5197
+ const content = readFileSync5(indexPath, "utf-8");
3688
5198
  const cache = /* @__PURE__ */ new Map();
3689
5199
  for (const record of parseJsonlLines(content)) {
3690
5200
  const sid = String(record["id"] ?? "").trim();
@@ -3716,7 +5226,9 @@ var CodexAgent = class extends SingleFileSessionSource {
3716
5226
  }
3717
5227
  parseFileSessionHead(filePath, options) {
3718
5228
  this.loadSessionIndex();
3719
- return this.parseSessionHead(filePath, options);
5229
+ const head = this.parseSessionHead(filePath, options);
5230
+ if (head) this.applyChildStats(head.stats, head.id);
5231
+ return head;
3720
5232
  }
3721
5233
  parseSessionHead(filePath, options) {
3722
5234
  return getParsedSession(this.parseSessionHeadResult(filePath, options));
@@ -3727,6 +5239,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3727
5239
  }
3728
5240
  const sessionId = extractSessionId(filePath);
3729
5241
  let firstPayload = {};
5242
+ let parentThreadId = null;
3730
5243
  let createdAt = 0;
3731
5244
  let lineCount = 0;
3732
5245
  let messageTitle = null;
@@ -3756,7 +5269,9 @@ var CodexAgent = class extends SingleFileSessionSource {
3756
5269
  return skippedSession("malformed first record");
3757
5270
  }
3758
5271
  firstPayload = extractPayload(firstRecord);
3759
- createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync4(filePath).mtimeMs;
5272
+ const threadMeta = extractThreadMeta(firstRecord);
5273
+ parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
5274
+ createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync6(filePath).mtimeMs;
3760
5275
  updatedAt = createdAt;
3761
5276
  }
3762
5277
  try {
@@ -3850,6 +5365,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3850
5365
  slug: `codex/${sessionId}`,
3851
5366
  title,
3852
5367
  directory,
5368
+ parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
3853
5369
  time_created: createdAt,
3854
5370
  time_updated: updatedAt,
3855
5371
  stats: {
@@ -3863,9 +5379,6 @@ var CodexAgent = class extends SingleFileSessionSource {
3863
5379
  model_usage: Object.keys(modelUsageMap).length > 0 ? modelUsageMap : void 0
3864
5380
  });
3865
5381
  }
3866
- parseFastSessionHead(filePath) {
3867
- return getParsedSession(this.parseFastSessionHeadResult(filePath));
3868
- }
3869
5382
  parseFastSessionHeadResult(filePath) {
3870
5383
  const prefix = this.readFilePrefix(filePath);
3871
5384
  const lines = prefix.split("\n").filter((l) => l.trim());
@@ -3877,8 +5390,10 @@ var CodexAgent = class extends SingleFileSessionSource {
3877
5390
  } catch {
3878
5391
  return skippedSession("malformed first record");
3879
5392
  }
5393
+ const threadMeta = extractThreadMeta(firstRecord);
5394
+ const parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
3880
5395
  const payload = extractPayload(firstRecord);
3881
- const stat = statSync4(filePath);
5396
+ const stat = statSync6(filePath);
3882
5397
  const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
3883
5398
  const indexTitle = this.getTitleForSession(sessionId);
3884
5399
  const messageTitle = this.extractTitleFromLines(lines);
@@ -3890,6 +5405,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3890
5405
  slug: `codex/${sessionId}`,
3891
5406
  title,
3892
5407
  directory,
5408
+ parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
3893
5409
  time_created: createdAt,
3894
5410
  time_updated: stat.mtimeMs,
3895
5411
  stats: {
@@ -3985,19 +5501,8 @@ var CodexAgent = class extends SingleFileSessionSource {
3985
5501
  }
3986
5502
  // ---- Assistant message ----
3987
5503
  convertAssistantMessage(payload, transcript, timestampMs, pendingPlan, activeModel) {
3988
- const content = payload["content"];
3989
- if (!Array.isArray(content)) return pendingPlan;
3990
- const textParts = [];
3991
- for (const item of content) {
3992
- const ci = asRecord(item);
3993
- if (!ci) continue;
3994
- if (String(ci["type"] ?? "") === "output_text") {
3995
- const text = String(ci["text"] ?? "");
3996
- if (text.trim()) textParts.push(text);
3997
- }
3998
- }
3999
- if (textParts.length === 0) return pendingPlan;
4000
- const fullText = textParts.join("\n");
5504
+ const fullText = extractAssistantOutputText(payload);
5505
+ if (!fullText) return pendingPlan;
4001
5506
  const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
4002
5507
  if (planMatch) {
4003
5508
  const planText2 = planMatch[1].trim();
@@ -4112,8 +5617,8 @@ var CodexAgent = class extends SingleFileSessionSource {
4112
5617
  const name = String(payload["name"] ?? "").trim();
4113
5618
  if (!name) return;
4114
5619
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
4115
- const arguments_ = normalizeToolArguments2(payload["arguments"]);
4116
- const toolPart = {
5620
+ const arguments_ = normalizeToolArguments3(payload["arguments"]);
5621
+ const toolPart2 = {
4117
5622
  type: "tool",
4118
5623
  tool: toolIdentity.tool,
4119
5624
  callID: callId,
@@ -4127,7 +5632,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4127
5632
  time_created: timestampMs
4128
5633
  };
4129
5634
  transcript.appendToolCall(
4130
- toolPart,
5635
+ toolPart2,
4131
5636
  { id: "", timestampMs, agent: "codex", model: activeModel },
4132
5637
  { markModeAsTool: true }
4133
5638
  );
@@ -4159,7 +5664,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4159
5664
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
4160
5665
  const rawInput = payload["input"];
4161
5666
  const normalizedInput = normalizeCustomToolArguments(name, rawInput);
4162
- const toolPart = {
5667
+ const toolPart2 = {
4163
5668
  type: "tool",
4164
5669
  tool: toolIdentity.tool,
4165
5670
  callID: callId,
@@ -4173,7 +5678,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4173
5678
  time_created: timestampMs
4174
5679
  };
4175
5680
  transcript.appendToolCall(
4176
- toolPart,
5681
+ toolPart2,
4177
5682
  { id: "", timestampMs, agent: "codex", model: activeModel },
4178
5683
  { markModeAsTool: true }
4179
5684
  );
@@ -4190,7 +5695,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4190
5695
  const { name, namespace } = splitExecToolName(call.name);
4191
5696
  const toolIdentity = resolveToolIdentity(name, namespace);
4192
5697
  const arguments_ = name === "apply_patch" ? parseApplyPatchInput(getExecPatchText(call.args)) : call.args;
4193
- const toolPart = {
5698
+ const toolPart2 = {
4194
5699
  type: "tool",
4195
5700
  tool: toolIdentity.tool,
4196
5701
  callID: callId,
@@ -4204,7 +5709,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4204
5709
  time_created: timestampMs
4205
5710
  };
4206
5711
  transcript.appendToolCall(
4207
- toolPart,
5712
+ toolPart2,
4208
5713
  { id: "", timestampMs, agent: "codex", model: activeModel },
4209
5714
  { markModeAsTool: true }
4210
5715
  );
@@ -4289,15 +5794,15 @@ function resolveCursorDataRoot() {
4289
5794
  if (override) return override;
4290
5795
  const currentPlatform = platform2();
4291
5796
  if (currentPlatform === "darwin") {
4292
- return firstExisting(join9(homedir3(), "Library", "Application Support", "Cursor", "User"));
5797
+ return firstExisting(join11(homedir3(), "Library", "Application Support", "Cursor", "User"));
4293
5798
  }
4294
5799
  if (currentPlatform === "linux") {
4295
- const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join9(homedir3(), ".config");
4296
- return firstExisting(join9(configRoot, "Cursor", "User"));
5800
+ const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join11(homedir3(), ".config");
5801
+ return firstExisting(join11(configRoot, "Cursor", "User"));
4297
5802
  }
4298
5803
  if (currentPlatform === "win32") {
4299
- const appData = readEnvPath("APPDATA") ?? join9(homedir3(), "AppData", "Roaming");
4300
- return firstExisting(join9(appData, "Cursor", "User"));
5804
+ const appData = readEnvPath("APPDATA") ?? join11(homedir3(), "AppData", "Roaming");
5805
+ return firstExisting(join11(appData, "Cursor", "User"));
4301
5806
  }
4302
5807
  return null;
4303
5808
  }
@@ -4358,6 +5863,24 @@ function parseComposerRow(value) {
4358
5863
  chatMessages: parseChatMessages(record.chatMessages)
4359
5864
  };
4360
5865
  }
5866
+ function composerIdFromBubbleKey(key) {
5867
+ const start = key.indexOf(":") + 1;
5868
+ const end = key.indexOf(":", start);
5869
+ return end === -1 ? key.slice(start) : key.slice(start, end);
5870
+ }
5871
+ function groupBubbleRows(rows) {
5872
+ const byKey = [];
5873
+ for (const row of rows) {
5874
+ const bubble = parseBubbleRow(row.value);
5875
+ if (bubble) byKey.push({ rowId: row.row_id, key: row.key, bubble });
5876
+ }
5877
+ byKey.sort((left, right) => left.key.localeCompare(right.key));
5878
+ return {
5879
+ composerId: rows.length > 0 ? composerIdFromBubbleKey(rows[0].key) : "",
5880
+ byKey,
5881
+ byRowId: [...byKey].sort((left, right) => left.rowId - right.rowId)
5882
+ };
5883
+ }
4361
5884
  function parseBubbleRow(value) {
4362
5885
  const record = safeParseJsonRecord(value);
4363
5886
  if (!record) return null;
@@ -4408,7 +5931,7 @@ var CURSOR_TOOL_TITLE_MAP = {
4408
5931
  ripgrep_raw_search: "grep",
4409
5932
  glob_file_search: "glob"
4410
5933
  };
4411
- function mapToolTitle2(toolName) {
5934
+ function mapToolTitle3(toolName) {
4412
5935
  return CURSOR_TOOL_TITLE_MAP[toolName] ?? toolName;
4413
5936
  }
4414
5937
  function normalizeToolOutputParts2(output, timestampMs) {
@@ -4469,9 +5992,9 @@ function buildToolPart(action, timestampMs) {
4469
5992
  const toolName = action.tool ?? "unknown";
4470
5993
  return {
4471
5994
  type: "tool",
4472
- tool: mapToolTitle2(toolName),
5995
+ tool: mapToolTitle3(toolName),
4473
5996
  callID: action.type ? `${action.type}:${String(action.input?.id ?? "")}` : "",
4474
- title: `Tool: ${mapToolTitle2(toolName)}`,
5997
+ title: `Tool: ${mapToolTitle3(toolName)}`,
4475
5998
  state: buildToolState(action),
4476
5999
  time_created: timestampMs
4477
6000
  };
@@ -4517,7 +6040,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4517
6040
  if (!isSqliteAvailable()) return null;
4518
6041
  const dataPath = resolveCursorDataRoot();
4519
6042
  if (!dataPath) return null;
4520
- return join9(dataPath, "globalStorage", "state.vscdb");
6043
+ return join11(dataPath, "globalStorage", "state.vscdb");
4521
6044
  }
4522
6045
  getSessionWatchPlan() {
4523
6046
  const dataPath = resolveCursorDataRoot();
@@ -4526,9 +6049,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4526
6049
  targets: dataPath ? [
4527
6050
  {
4528
6051
  root: dataPath,
4529
- path: join9(dataPath, "globalStorage", "state.vscdb")
6052
+ path: join11(dataPath, "globalStorage", "state.vscdb")
4530
6053
  },
4531
- { root: dataPath, path: join9(dataPath, "workspaceStorage") }
6054
+ { root: dataPath, path: join11(dataPath, "workspaceStorage") }
4532
6055
  ] : []
4533
6056
  };
4534
6057
  }
@@ -4540,34 +6063,34 @@ var CursorAgent = class extends DatabaseSessionSource {
4540
6063
  const map = /* @__PURE__ */ new Map();
4541
6064
  const dataPath = resolveCursorDataRoot();
4542
6065
  if (!dataPath) return map;
4543
- const wsStoragePath = join9(dataPath, "workspaceStorage");
4544
- if (!existsSync8(wsStoragePath)) return map;
6066
+ const wsStoragePath = join11(dataPath, "workspaceStorage");
6067
+ if (!existsSync10(wsStoragePath)) return map;
4545
6068
  let entryNames;
4546
6069
  try {
4547
- entryNames = readdirSync4(wsStoragePath);
6070
+ entryNames = readdirSync7(wsStoragePath);
4548
6071
  } catch {
4549
6072
  return map;
4550
6073
  }
4551
6074
  for (const name of entryNames) {
4552
- const wsDir = join9(wsStoragePath, name);
6075
+ const wsDir = join11(wsStoragePath, name);
4553
6076
  try {
4554
- if (!statSync5(wsDir).isDirectory()) continue;
6077
+ if (!statSync7(wsDir).isDirectory()) continue;
4555
6078
  } catch {
4556
6079
  continue;
4557
6080
  }
4558
- const wsJsonPath = join9(wsDir, "workspace.json");
4559
- if (!existsSync8(wsJsonPath)) continue;
6081
+ const wsJsonPath = join11(wsDir, "workspace.json");
6082
+ if (!existsSync10(wsJsonPath)) continue;
4560
6083
  let workspacePath;
4561
6084
  try {
4562
- const data = asRecord(JSON.parse(readFileSync5(wsJsonPath, "utf-8")));
6085
+ const data = asRecord(JSON.parse(readFileSync6(wsJsonPath, "utf-8")));
4563
6086
  const uri = narrowString("workspaceJson.folder", data?.folder) ?? narrowString("workspaceJson.workspace", data?.workspace) ?? "";
4564
6087
  if (!uri) continue;
4565
6088
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
4566
6089
  } catch {
4567
6090
  continue;
4568
6091
  }
4569
- const wsDbPath = join9(wsDir, "state.vscdb");
4570
- if (!existsSync8(wsDbPath)) continue;
6092
+ const wsDbPath = join11(wsDir, "state.vscdb");
6093
+ if (!existsSync10(wsDbPath)) continue;
4571
6094
  const wsDb = openDbReadOnly(wsDbPath);
4572
6095
  if (!wsDb) continue;
4573
6096
  try {
@@ -4590,7 +6113,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4590
6113
  }
4591
6114
  isAvailable() {
4592
6115
  this.dbPath = this.findDbPath();
4593
- return this.dbPath !== null && existsSync8(this.dbPath);
6116
+ return this.dbPath !== null && existsSync10(this.dbPath);
4594
6117
  }
4595
6118
  scan(options) {
4596
6119
  if (!this.dbPath) return [];
@@ -4598,13 +6121,15 @@ var CursorAgent = class extends DatabaseSessionSource {
4598
6121
  const dbMarker = perf.start("openDatabase");
4599
6122
  const db = this.openDatabase();
4600
6123
  perf.end(dbMarker);
4601
- if (!db) return [];
6124
+ if (!db) throw new SessionScanError(this.name, "opening the database");
4602
6125
  const wsMarker = perf.start("buildWorkspacePathMap");
4603
6126
  const workspacePathMap = this.buildWorkspacePathMap();
4604
6127
  perf.end(wsMarker);
4605
6128
  try {
4606
6129
  const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'").all();
4607
- const heads = [];
6130
+ const emitted = /* @__PURE__ */ new Map();
6131
+ const pending2 = [];
6132
+ let order = 0;
4608
6133
  options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
4609
6134
  let processed = 0;
4610
6135
  for (const row of rows) {
@@ -4620,8 +6145,8 @@ var CursorAgent = class extends DatabaseSessionSource {
4620
6145
  const fastMessageCount = composer.chatMessages?.length ?? 0;
4621
6146
  const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
4622
6147
  if (options?.fast) {
4623
- const directory2 = workspacePathMap.get(composerId) ?? "";
4624
- const totalCost2 = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
6148
+ const directory = workspacePathMap.get(composerId) ?? "";
6149
+ const totalCost = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
4625
6150
  input: composer.inputTokenCount ?? 0,
4626
6151
  output: composer.outputTokenCount ?? 0
4627
6152
  }) ?? 0;
@@ -4630,20 +6155,21 @@ var CursorAgent = class extends DatabaseSessionSource {
4630
6155
  id: composerId,
4631
6156
  slug: `cursor/${composerId}`,
4632
6157
  title: fastTitle,
4633
- directory: directory2,
6158
+ directory,
4634
6159
  time_created: createdAt,
4635
6160
  time_updated: updatedAt || void 0,
4636
6161
  stats: {
4637
6162
  message_count: fastMessageCount,
4638
6163
  total_input_tokens: composer.inputTokenCount ?? 0,
4639
6164
  total_output_tokens: composer.outputTokenCount ?? 0,
4640
- total_cost: totalCost2,
4641
- cost_source: totalCost2 > 0 ? "estimated" : void 0
6165
+ total_cost: totalCost,
6166
+ cost_source: totalCost > 0 ? "estimated" : void 0
4642
6167
  }
4643
6168
  })
4644
6169
  );
4645
6170
  if (!head) continue;
4646
- heads.push(head);
6171
+ emitted.set(order, head);
6172
+ order += 1;
4647
6173
  this.composerCache.set(composerId, composer);
4648
6174
  this.sessionMetaMap.set(composerId, {
4649
6175
  id: composerId,
@@ -4651,74 +6177,38 @@ var CursorAgent = class extends DatabaseSessionSource {
4651
6177
  });
4652
6178
  continue;
4653
6179
  }
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
- });
6180
+ pending2.push({ composer, composerId, createdAt, updatedAt, hasSubagents, order });
6181
+ order += 1;
4712
6182
  } catch {
4713
6183
  } finally {
4714
6184
  processed += 1;
4715
- options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
6185
+ options?.onProgress?.({ total: rows.length, processed, sessions: emitted.size });
4716
6186
  }
4717
6187
  }
6188
+ const bubbleMarker = perf.start("cursor:bubbles");
6189
+ const wanted = new Map(pending2.map((entry) => [entry.composerId, entry]));
6190
+ this.forEachComposerBubbles(db, wanted, (bubbles) => {
6191
+ const entry = wanted.get(bubbles.composerId);
6192
+ if (!entry) return;
6193
+ wanted.delete(bubbles.composerId);
6194
+ const head = this.buildScanHead(entry, bubbles, workspacePathMap);
6195
+ if (head) emitted.set(entry.order, head);
6196
+ });
6197
+ for (const entry of wanted.values()) {
6198
+ const head = this.buildScanHead(
6199
+ entry,
6200
+ { composerId: entry.composerId, byKey: [], byRowId: [] },
6201
+ workspacePathMap
6202
+ );
6203
+ if (head) emitted.set(entry.order, head);
6204
+ }
6205
+ perf.end(bubbleMarker);
6206
+ const heads = [...emitted.entries()].sort(([left], [right]) => left - right).map(([, head]) => head);
6207
+ options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
4718
6208
  perf.end(scanMarker);
4719
6209
  return heads;
4720
- } catch {
4721
- return [];
6210
+ } catch (error) {
6211
+ throw new SessionScanError(this.name, "reading composers", { cause: error });
4722
6212
  } finally {
4723
6213
  db.close();
4724
6214
  }
@@ -4806,20 +6296,90 @@ var CursorAgent = class extends DatabaseSessionSource {
4806
6296
  if (!this.dbPath) return null;
4807
6297
  return openDbReadOnly(this.dbPath);
4808
6298
  }
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 {
6299
+ /**
6300
+ * Streams every bubble row once, in key order, handing each composer's group
6301
+ * to the caller. A key-ordered scan keeps a composer's bubbles contiguous, so
6302
+ * only one group is materialized at a time.
6303
+ */
6304
+ forEachComposerBubbles(db, wanted, onGroup) {
6305
+ if (wanted.size === 0) return;
6306
+ let currentId = null;
6307
+ let group = [];
6308
+ const flush = () => {
6309
+ if (currentId != null && group.length > 0) onGroup(groupBubbleRows(group));
6310
+ group = [];
6311
+ };
6312
+ const rows = db.prepare(
6313
+ "SELECT rowid AS row_id, key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%' ORDER BY key"
6314
+ ).iterate();
6315
+ for (const row of rows) {
6316
+ const composerId = composerIdFromBubbleKey(row.key);
6317
+ if (composerId !== currentId) {
6318
+ flush();
6319
+ currentId = composerId;
6320
+ }
6321
+ if (wanted.has(composerId)) group.push(row);
6322
+ }
6323
+ flush();
6324
+ }
6325
+ /** Builds the head for one composer from bubbles that were parsed once. */
6326
+ buildScanHead(entry, bubbles, workspacePathMap) {
6327
+ const { composer, composerId, createdAt, updatedAt, hasSubagents } = entry;
6328
+ const requestId = this.requestIdFromBubbles(bubbles);
6329
+ const sessionId = requestId || composerId;
6330
+ const parsedMessages = cleanParsedMessages(
6331
+ this.messagesFromBubbles(bubbles, composer.modelConfig?.modelName ?? composer.model ?? null)
6332
+ );
6333
+ const messages = getParsedSession(
6334
+ parsedMessages.length === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession(parsedMessages)
6335
+ );
6336
+ if (!messages) return null;
6337
+ const title = this.extractTitle(composer, messages);
6338
+ const directory = workspacePathMap.get(composerId) ?? "";
6339
+ const modelUsageMap = {};
6340
+ let totalCost = 0;
6341
+ for (const msg of messages) {
6342
+ totalCost += msg.cost ?? 0;
6343
+ if (msg.model) {
6344
+ const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
6345
+ if (msgTokens > 0) {
6346
+ modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
4820
6347
  }
4821
6348
  }
4822
- } catch {
6349
+ }
6350
+ const hasModelUsage = Object.keys(modelUsageMap).length > 0;
6351
+ this.composerCache.set(sessionId, composer);
6352
+ this.composerCache.set(`__mapping__${composerId}`, {
6353
+ sessionId
6354
+ });
6355
+ if (directory) {
6356
+ this.composerCache.set(`__dir__${composerId}`, {
6357
+ directory
6358
+ });
6359
+ }
6360
+ this.sessionMetaMap.set(sessionId, { id: sessionId, sourcePath: this.dbPath || "" });
6361
+ return {
6362
+ id: sessionId,
6363
+ slug: `cursor/${sessionId}`,
6364
+ title,
6365
+ directory,
6366
+ time_created: createdAt,
6367
+ time_updated: updatedAt || void 0,
6368
+ stats: {
6369
+ message_count: messages.length,
6370
+ total_input_tokens: composer.inputTokenCount ?? 0,
6371
+ total_output_tokens: composer.outputTokenCount ?? 0,
6372
+ total_cost: totalCost,
6373
+ cost_source: totalCost > 0 ? "estimated" : void 0
6374
+ },
6375
+ model_usage: hasModelUsage ? modelUsageMap : void 0
6376
+ };
6377
+ }
6378
+ /** First request id in key order, matching what agent-dump reports. */
6379
+ requestIdFromBubbles(bubbles) {
6380
+ for (const entry of bubbles.byKey) {
6381
+ const requestId = entry.bubble.requestId?.trim();
6382
+ if (requestId) return requestId;
4823
6383
  }
4824
6384
  return null;
4825
6385
  }
@@ -4868,18 +6428,26 @@ var CursorAgent = class extends DatabaseSessionSource {
4868
6428
  return 0;
4869
6429
  }
4870
6430
  }
4871
- /** Load messages from bubbles (like agent-dump) */
6431
+ /** Load one composer's bubbles, for the detail path that only needs a single session. */
4872
6432
  loadMessagesFromBubbles(db, composerId, _sessionId, initialModelName) {
4873
- const messages = [];
4874
6433
  try {
4875
- const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ORDER BY rowid ASC").all(`bubbleId:${composerId}:%`);
6434
+ const rows = db.prepare("SELECT rowid AS row_id, key, value FROM cursorDiskKV WHERE key LIKE ?").all(`bubbleId:${composerId}:%`);
6435
+ return this.messagesFromBubbles(groupBubbleRows(rows), initialModelName);
6436
+ } catch {
6437
+ return [];
6438
+ }
6439
+ }
6440
+ /** Build messages from bubbles already parsed once, in insertion order. */
6441
+ messagesFromBubbles(bubbles, initialModelName) {
6442
+ const messages = [];
6443
+ {
4876
6444
  let activeModelName = initialModelName;
4877
6445
  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);
6446
+ for (const entry of bubbles.byRowId) {
6447
+ {
6448
+ const bubble = entry.bubble;
6449
+ if (isInternalBubble(bubble)) continue;
6450
+ const bubbleId = entry.key.split(":").pop() || String(messageIndex);
4883
6451
  const role = bubble.type === 2 ? "assistant" : "user";
4884
6452
  let timestampMs = 0;
4885
6453
  if (bubble.timingInfo?.clientRpcSendTime) {
@@ -4900,9 +6468,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4900
6468
  parts.push({ type: "text", text, time_created: timestampMs });
4901
6469
  }
4902
6470
  if (bubble.toolFormerData) {
4903
- const toolPart = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
4904
- if (toolPart) {
4905
- parts.push(toolPart);
6471
+ const toolPart2 = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
6472
+ if (toolPart2) {
6473
+ parts.push(toolPart2);
4906
6474
  }
4907
6475
  }
4908
6476
  if (parts.length === 0) continue;
@@ -4910,7 +6478,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4910
6478
  const tokens = { input: inputTokens, output: outputTokens };
4911
6479
  const cost = estimateTokenCost(modelName, tokens);
4912
6480
  messages.push({
4913
- id: `cursor-${composerId}-${bubbleId}`,
6481
+ id: `cursor-${bubbles.composerId}-${bubbleId}`,
4914
6482
  role,
4915
6483
  agent: "cursor",
4916
6484
  time_created: timestampMs,
@@ -4924,10 +6492,8 @@ var CursorAgent = class extends DatabaseSessionSource {
4924
6492
  parts
4925
6493
  });
4926
6494
  messageIndex++;
4927
- } catch {
4928
6495
  }
4929
6496
  }
4930
- } catch {
4931
6497
  }
4932
6498
  return messages;
4933
6499
  }
@@ -4935,7 +6501,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4935
6501
  convertToolFormerData(toolData, timestampMs) {
4936
6502
  if (!toolData || !toolData.name) return null;
4937
6503
  const toolName = toolData.name;
4938
- const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle2(toolName);
6504
+ const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle3(toolName);
4939
6505
  const state = {
4940
6506
  status: toolData.status === "completed" ? "completed" : "running"
4941
6507
  };
@@ -5063,7 +6629,7 @@ function narrowTimestampMs(field, value) {
5063
6629
  return shaped === void 0 ? 0 : parseTimestampMs3(shaped);
5064
6630
  }
5065
6631
  function extractSessionIdFromFilename(filePath) {
5066
- const stem = basename6(filePath, ".jsonl");
6632
+ const stem = basename8(filePath, ".jsonl");
5067
6633
  const underscore = stem.indexOf("_");
5068
6634
  return underscore >= 0 ? stem.slice(underscore + 1) || stem : stem;
5069
6635
  }
@@ -5119,14 +6685,14 @@ var PiAgent = class extends SingleFileSessionSource {
5119
6685
  displayName = "Pi";
5120
6686
  basePath = null;
5121
6687
  findBasePath() {
5122
- return firstExisting(join10(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
6688
+ return firstExisting(join12(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
5123
6689
  }
5124
6690
  getSessionWatchPlan() {
5125
6691
  const dataRoot = resolvePiDataRoot();
5126
6692
  return {
5127
6693
  status: "supported",
5128
6694
  targets: [
5129
- { root: dataRoot, path: join10(dataRoot, "agent", "sessions") },
6695
+ { root: dataRoot, path: join12(dataRoot, "agent", "sessions") },
5130
6696
  { root: "data/pi", path: "data/pi" }
5131
6697
  ]
5132
6698
  };
@@ -5147,7 +6713,7 @@ var PiAgent = class extends SingleFileSessionSource {
5147
6713
  getSessionData(sessionId) {
5148
6714
  const meta = this.sessionMetaMap.get(sessionId);
5149
6715
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
5150
- if (!existsSync9(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
6716
+ if (!existsSync11(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5151
6717
  const parsed = this.parsePiFile(meta.sourcePath);
5152
6718
  const state = this.convertEntries(parsed.pathEntries);
5153
6719
  return {
@@ -5240,7 +6806,7 @@ var PiAgent = class extends SingleFileSessionSource {
5240
6806
  const sessionId = String(header["id"] ?? extractSessionIdFromFilename(filePath)).trim();
5241
6807
  if (!sessionId) throw new Error("missing session id");
5242
6808
  const stat = this.sessionSourceFile(filePath).stat;
5243
- const directory = String(header["cwd"] ?? "").trim() || basename6(filePath, ".jsonl");
6809
+ const directory = String(header["cwd"] ?? "").trim() || basename8(filePath, ".jsonl");
5244
6810
  const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
5245
6811
  const updatedAt = pathEntries.reduce(
5246
6812
  (max, entry) => Math.max(max, getEntryTimestamp(entry)),
@@ -5383,7 +6949,7 @@ var PiAgent = class extends SingleFileSessionSource {
5383
6949
  if (type === "toolCall") {
5384
6950
  const callId = String(item["id"] ?? "").trim();
5385
6951
  const toolName = String(item["name"] ?? "").trim() || "tool";
5386
- const toolPart = {
6952
+ const toolPart2 = {
5387
6953
  type: "tool",
5388
6954
  tool: toolName,
5389
6955
  title: `Tool: ${toolName}`,
@@ -5394,7 +6960,7 @@ var PiAgent = class extends SingleFileSessionSource {
5394
6960
  input: item["arguments"] ?? {}
5395
6961
  }
5396
6962
  };
5397
- parts.push(toolPart);
6963
+ parts.push(toolPart2);
5398
6964
  }
5399
6965
  }
5400
6966
  return parts;
@@ -5490,19 +7056,19 @@ var PiAgent = class extends SingleFileSessionSource {
5490
7056
  function resolveZCodeDataRoot() {
5491
7057
  const currentPlatform = platform3();
5492
7058
  if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
5493
- return join11(homedir4(), ".zcode");
7059
+ return join13(homedir4(), ".zcode");
5494
7060
  }
5495
7061
  function findZCodeDbPath() {
5496
7062
  if (!isSqliteAvailable()) return null;
5497
7063
  const dataRoot = resolveZCodeDataRoot();
5498
7064
  if (!dataRoot) return null;
5499
- return firstExisting(join11(dataRoot, "cli", "db", "db.sqlite"));
7065
+ return firstExisting(join13(dataRoot, "cli", "db", "db.sqlite"));
5500
7066
  }
5501
7067
  function getZCodeSessionWatchPlan() {
5502
7068
  const dataRoot = resolveZCodeDataRoot();
5503
7069
  return {
5504
7070
  status: "supported",
5505
- targets: dataRoot ? [{ root: dataRoot, path: join11(dataRoot, "cli", "db", "db.sqlite") }] : []
7071
+ targets: dataRoot ? [{ root: dataRoot, path: join13(dataRoot, "cli", "db", "db.sqlite") }] : []
5506
7072
  };
5507
7073
  }
5508
7074
  var ZCodeAgent = class extends OpenCodeSqliteAgent {
@@ -5544,6 +7110,13 @@ registerAgent({
5544
7110
  toolStrategy: "custom",
5545
7111
  create: () => new KimiAgent()
5546
7112
  });
7113
+ registerAgent({
7114
+ icon: "/icon/agent/kimi.svg",
7115
+ resolveDataRoot: resolveKimiCodeDataRoot,
7116
+ resumeCommandPrefix: "kimi -r",
7117
+ toolStrategy: "custom",
7118
+ create: () => new KimiCodeAgent()
7119
+ });
5547
7120
  registerAgent({
5548
7121
  icon: "/icon/agent/codex.svg",
5549
7122
  resolveDataRoot: resolveCodexDataRoot,
@@ -5573,11 +7146,11 @@ function fallbackDisplayName(input) {
5573
7146
  }
5574
7147
  var realFs = {
5575
7148
  exists(path2) {
5576
- return existsSync10(path2);
7149
+ return existsSync12(path2);
5577
7150
  },
5578
7151
  readText(path2) {
5579
7152
  try {
5580
- return readFileSync6(path2, "utf8");
7153
+ return readFileSync7(path2, "utf8");
5581
7154
  } catch {
5582
7155
  return null;
5583
7156
  }
@@ -5602,23 +7175,6 @@ var MANIFESTS = [
5602
7175
  var PARSEABLE_MANIFESTS = ["package.json", "Cargo.toml", "pyproject.toml"];
5603
7176
  var LOOSE_DIRS = /* @__PURE__ */ new Set(["/tmp", "/private/tmp"]);
5604
7177
  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
7178
  function normalizeGitRemote(url) {
5623
7179
  if (!url) return null;
5624
7180
  let value = url.trim().replace(/\.git$/, "");
@@ -5765,6 +7321,7 @@ function parseManifestName(file, text) {
5765
7321
  function buildProjectGroups(sessions) {
5766
7322
  const groups = /* @__PURE__ */ new Map();
5767
7323
  for (const session of sessions) {
7324
+ if (isChildSession(session)) continue;
5768
7325
  const identity = session.project_identity;
5769
7326
  if (!identity) continue;
5770
7327
  const activity = session.time_updated ?? session.time_created;
@@ -5817,7 +7374,7 @@ function isPathScopeMatch(queryPath, sessionPath) {
5817
7374
  return session === queryPath || session.startsWith(queryPath + "/") || queryPath.startsWith(session + "/");
5818
7375
  }
5819
7376
  function normalizeScopePath(path2) {
5820
- return resolve(path2).replaceAll(sep, "/");
7377
+ return resolve2(path2).replaceAll(sep, "/");
5821
7378
  }
5822
7379
  var TAG_ORDER = [
5823
7380
  "bugfix",
@@ -6092,16 +7649,16 @@ function setSchemaEnsuredPath(path2) {
6092
7649
  schemaEnsuredPath = path2;
6093
7650
  }
6094
7651
  function getCacheDir2() {
6095
- return join12(homedir6(), ".cache", "codesesh");
7652
+ return join14(homedir6(), ".cache", "codesesh");
6096
7653
  }
6097
7654
  function getCachePath2() {
6098
- return join12(getCacheDir2(), CACHE_FILENAME);
7655
+ return join14(getCacheDir2(), CACHE_FILENAME);
6099
7656
  }
6100
7657
  function getLegacyCachePath() {
6101
- return join12(getCacheDir2(), LEGACY_CACHE_FILENAME);
7658
+ return join14(getCacheDir2(), LEGACY_CACHE_FILENAME);
6102
7659
  }
6103
7660
  function hasCacheStorage() {
6104
- return existsSync11(getCachePath2());
7661
+ return existsSync13(getCachePath2());
6105
7662
  }
6106
7663
  function likePattern(value) {
6107
7664
  return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
@@ -6142,6 +7699,8 @@ function prepareUpsertSession(db) {
6142
7699
  title,
6143
7700
  source_path,
6144
7701
  directory,
7702
+ parent_agent_name,
7703
+ parent_session_id,
6145
7704
  project_identity_kind,
6146
7705
  project_identity_key,
6147
7706
  project_display_name,
@@ -6160,13 +7719,15 @@ function prepareUpsertSession(db) {
6160
7719
  smart_tags_json,
6161
7720
  smart_tags_source_updated_at,
6162
7721
  meta_json
6163
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7722
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6164
7723
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
6165
7724
  sort_index = excluded.sort_index,
6166
7725
  slug = excluded.slug,
6167
7726
  title = excluded.title,
6168
7727
  source_path = excluded.source_path,
6169
7728
  directory = excluded.directory,
7729
+ parent_agent_name = excluded.parent_agent_name,
7730
+ parent_session_id = excluded.parent_session_id,
6170
7731
  project_identity_kind = excluded.project_identity_kind,
6171
7732
  project_identity_key = excluded.project_identity_key,
6172
7733
  project_display_name = excluded.project_display_name,
@@ -6197,6 +7758,8 @@ function prepareUpsertIndexedSession(db) {
6197
7758
  title,
6198
7759
  source_path,
6199
7760
  directory,
7761
+ parent_agent_name,
7762
+ parent_session_id,
6200
7763
  project_identity_kind,
6201
7764
  project_identity_key,
6202
7765
  project_display_name,
@@ -6215,11 +7778,13 @@ function prepareUpsertIndexedSession(db) {
6215
7778
  smart_tags_json,
6216
7779
  smart_tags_source_updated_at,
6217
7780
  meta_json
6218
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7781
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6219
7782
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
6220
7783
  slug = excluded.slug,
6221
7784
  title = excluded.title,
6222
7785
  directory = excluded.directory,
7786
+ parent_agent_name = excluded.parent_agent_name,
7787
+ parent_session_id = excluded.parent_session_id,
6223
7788
  project_identity_kind = excluded.project_identity_kind,
6224
7789
  project_identity_key = excluded.project_identity_key,
6225
7790
  project_display_name = excluded.project_display_name,
@@ -6250,6 +7815,8 @@ function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, so
6250
7815
  session.title,
6251
7816
  sourcePath,
6252
7817
  session.directory,
7818
+ session.parent_reference?.agentName ?? null,
7819
+ session.parent_reference?.sessionId ?? null,
6253
7820
  identity.kind,
6254
7821
  identity.key,
6255
7822
  identity.displayName,
@@ -6327,6 +7894,12 @@ function sessionFromRow(row) {
6327
7894
  displayName: String(row.project_display_name ?? "")
6328
7895
  };
6329
7896
  }
7897
+ if (row.parent_agent_name && row.parent_session_id) {
7898
+ session.parent_reference = {
7899
+ agentName: String(row.parent_agent_name),
7900
+ sessionId: String(row.parent_session_id)
7901
+ };
7902
+ }
6330
7903
  if (row.time_updated != null) {
6331
7904
  session.time_updated = Number(row.time_updated);
6332
7905
  }
@@ -6544,7 +8117,7 @@ function buildSessionContentFromMessages(title, messages) {
6544
8117
  }
6545
8118
  return chunks.join("\n");
6546
8119
  }
6547
- var CACHE_SCHEMA_VERSION = 17;
8120
+ var CACHE_SCHEMA_VERSION = 18;
6548
8121
  function withCacheDb(fn) {
6549
8122
  const cachePath = getCachePath2();
6550
8123
  const db = openDb(cachePath);
@@ -6644,6 +8217,8 @@ function createSessionTables(db) {
6644
8217
  title TEXT NOT NULL,
6645
8218
  source_path TEXT,
6646
8219
  directory TEXT NOT NULL,
8220
+ parent_agent_name TEXT,
8221
+ parent_session_id TEXT,
6647
8222
  project_identity_kind TEXT NOT NULL,
6648
8223
  project_identity_key TEXT NOT NULL,
6649
8224
  project_display_name TEXT NOT NULL,
@@ -6671,6 +8246,9 @@ function createSessionTables(db) {
6671
8246
  CREATE INDEX IF NOT EXISTS idx_sessions_project
6672
8247
  ON sessions(project_identity_kind, project_identity_key, activity_time);
6673
8248
 
8249
+ CREATE INDEX IF NOT EXISTS idx_sessions_parent
8250
+ ON sessions(parent_agent_name, parent_session_id);
8251
+
6674
8252
  CREATE TABLE IF NOT EXISTS messages (
6675
8253
  agent_name TEXT NOT NULL,
6676
8254
  session_id TEXT NOT NULL,
@@ -6960,6 +8538,7 @@ function createProjectGroupsView(db) {
6960
8538
  `);
6961
8539
  return;
6962
8540
  }
8541
+ const hasParentReference = columnExists(db, "sessions", "parent_agent_name") && columnExists(db, "sessions", "parent_session_id");
6963
8542
  db.exec(`
6964
8543
  CREATE VIEW IF NOT EXISTS project_groups_v AS
6965
8544
  SELECT
@@ -6970,6 +8549,7 @@ function createProjectGroupsView(db) {
6970
8549
  COUNT(*) AS session_count,
6971
8550
  MAX(activity_time) AS last_activity
6972
8551
  FROM sessions
8552
+ ${hasParentReference ? "WHERE parent_agent_name IS NULL OR parent_session_id IS NULL" : ""}
6973
8553
  GROUP BY project_identity_kind, project_identity_key;
6974
8554
  `);
6975
8555
  }
@@ -7367,6 +8947,19 @@ function addMessagePartsFormatVersion(db) {
7367
8947
  }
7368
8948
  db.exec("ALTER TABLE messages ADD COLUMN parts_format_version INTEGER NOT NULL DEFAULT 0");
7369
8949
  }
8950
+ function addSessionParentReference(db) {
8951
+ if (!tableExists(db, "sessions")) return;
8952
+ if (!columnExists(db, "sessions", "parent_agent_name")) {
8953
+ db.exec("ALTER TABLE sessions ADD COLUMN parent_agent_name TEXT");
8954
+ }
8955
+ if (!columnExists(db, "sessions", "parent_session_id")) {
8956
+ db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
8957
+ }
8958
+ db.exec(
8959
+ "CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_agent_name, parent_session_id)"
8960
+ );
8961
+ recreateProjectGroupsView(db);
8962
+ }
7370
8963
  var CODEX_EXEC_DECODE_MIGRATION_KEY = "codex_exec_decode_migrated_v3";
7371
8964
  function migrateCodexExecDecode(db) {
7372
8965
  if (!tableExists(db, "cache_meta")) return;
@@ -7381,6 +8974,30 @@ function migrateCodexExecDecode(db) {
7381
8974
  "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
7382
8975
  ).run(CODEX_EXEC_DECODE_MIGRATION_KEY);
7383
8976
  }
8977
+ var OPENCODE_SUBAGENT_FOLD_KEY = "opencode_subagent_fold_v1";
8978
+ var SUBAGENT_TREE_KEY = "subagent_tree_v1";
8979
+ function migrateOpenCodeSubagentFold(db) {
8980
+ if (!tableExists(db, "cache_meta")) return;
8981
+ const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(OPENCODE_SUBAGENT_FOLD_KEY);
8982
+ if (done) return;
8983
+ if (tableExists(db, "agent_cache")) {
8984
+ db.prepare("DELETE FROM agent_cache WHERE agent_name IN ('zcode', 'opencode')").run();
8985
+ }
8986
+ db.prepare(
8987
+ "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
8988
+ ).run(OPENCODE_SUBAGENT_FOLD_KEY);
8989
+ }
8990
+ function migrateSubagentTree(db) {
8991
+ if (!tableExists(db, "cache_meta")) return;
8992
+ const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(SUBAGENT_TREE_KEY);
8993
+ if (done) return;
8994
+ if (tableExists(db, "agent_cache")) {
8995
+ db.prepare("DELETE FROM agent_cache WHERE agent_name IN ('codex', 'zcode', 'opencode')").run();
8996
+ }
8997
+ db.prepare(
8998
+ "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
8999
+ ).run(SUBAGENT_TREE_KEY);
9000
+ }
7384
9001
  function rebuildSearchIndex(db) {
7385
9002
  if (!tableExists(db, "session_documents_fts")) {
7386
9003
  return;
@@ -7490,6 +9107,8 @@ function ensureSchema(db, dbPath) {
7490
9107
  createLatestCacheSchema(db);
7491
9108
  setCacheSchemaVersion(db);
7492
9109
  migrateCodexExecDecode(db);
9110
+ migrateOpenCodeSubagentFold(db);
9111
+ migrateSubagentTree(db);
7493
9112
  return;
7494
9113
  }
7495
9114
  runSchemaMigrations(db, {
@@ -7521,7 +9140,13 @@ function ensureSchema(db, dbPath) {
7521
9140
  invalidateSearchContentHashes(db2);
7522
9141
  }
7523
9142
  },
7524
- { version: 7, migrate: backfillStructuredSessions },
9143
+ {
9144
+ version: 7,
9145
+ migrate(db2) {
9146
+ addSessionParentReference(db2);
9147
+ backfillStructuredSessions(db2);
9148
+ }
9149
+ },
7525
9150
  { version: 8, migrate: backfillFileActivity },
7526
9151
  {
7527
9152
  version: 9,
@@ -7552,7 +9177,8 @@ function ensureSchema(db, dbPath) {
7552
9177
  { version: 13, migrate: createCacheTables },
7553
9178
  { version: 14, migrate: addIndexedMessageCount },
7554
9179
  { version: 15, destructive: true, migrate: compactSessionDocuments },
7555
- { version: 17, migrate: addMessagePartsFormatVersion }
9180
+ { version: 17, migrate: addMessagePartsFormatVersion },
9181
+ { version: 18, migrate: addSessionParentReference }
7556
9182
  ]
7557
9183
  });
7558
9184
  createLatestCacheSchema(db);
@@ -7560,6 +9186,8 @@ function ensureSchema(db, dbPath) {
7560
9186
  setCacheSchemaVersion(db);
7561
9187
  }
7562
9188
  migrateCodexExecDecode(db);
9189
+ migrateOpenCodeSubagentFold(db);
9190
+ migrateSubagentTree(db);
7563
9191
  }
7564
9192
  function escapeFtsTerm(value) {
7565
9193
  return value.replaceAll('"', '""');
@@ -7690,6 +9318,7 @@ function readPendingReindexIds(db, agentName) {
7690
9318
  return new Set(rows.map((row) => String(row.session_id)));
7691
9319
  }
7692
9320
  var SEARCH_INDEX_STATE_BATCH_SIZE = 900;
9321
+ var SEARCH_INDEX_COMMIT_CHUNK_SIZE = 64;
7693
9322
  function shouldBulkSyncSearchIndex(options, changedCount) {
7694
9323
  if (options.isBulk != null) {
7695
9324
  return options.isBulk;
@@ -7949,6 +9578,32 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
7949
9578
  sortIndex: sessionSortIndexMap.get(session.id) ?? 0
7950
9579
  }));
7951
9580
  let indexed = 0;
9581
+ if (changes.length > SEARCH_INDEX_COMMIT_CHUNK_SIZE) {
9582
+ runSearchIndexWrite(db, false, () => {
9583
+ indexed += writeSearchIndexRows(db, agentName, toDelete, []);
9584
+ });
9585
+ for (let offset = 0; offset < changes.length; offset += SEARCH_INDEX_COMMIT_CHUNK_SIZE) {
9586
+ const chunk = changes.slice(offset, offset + SEARCH_INDEX_COMMIT_CHUNK_SIZE);
9587
+ runSearchIndexWrite(db, false, () => {
9588
+ indexed += writeSearchIndexRows(
9589
+ db,
9590
+ agentName,
9591
+ [],
9592
+ loadSearchIndexEntries(agentName, chunk, loadSessionData)
9593
+ );
9594
+ });
9595
+ }
9596
+ return {
9597
+ agentName,
9598
+ mode: "incremental",
9599
+ sessions: sessions.length,
9600
+ changed: toUpsert.length,
9601
+ deleted: toDelete.length,
9602
+ indexed,
9603
+ skipped: toUpsert.length - indexed,
9604
+ durationMs: performance.now() - startedAt
9605
+ };
9606
+ }
7952
9607
  const writeRows = () => {
7953
9608
  indexed = writeSearchIndexRows(
7954
9609
  db,
@@ -8445,55 +10100,90 @@ function buildFileActivityWhere(options) {
8445
10100
  params
8446
10101
  };
8447
10102
  }
10103
+ var FILE_ACTIVITY_COLUMNS = `
10104
+ fa.agent_name,
10105
+ fa.session_id,
10106
+ fa.project_identity_key,
10107
+ fa.path,
10108
+ fa.kind,
10109
+ fa.count,
10110
+ fa.latest_time,
10111
+ s.slug,
10112
+ s.title,
10113
+ s.directory,
10114
+ s.project_identity_kind,
10115
+ s.project_display_name,
10116
+ s.time_created,
10117
+ s.time_updated,
10118
+ s.message_count,
10119
+ s.total_input_tokens,
10120
+ s.total_output_tokens,
10121
+ s.total_cache_read_tokens,
10122
+ s.total_cache_create_tokens,
10123
+ s.total_cost,
10124
+ s.cost_source,
10125
+ s.total_tokens,
10126
+ s.model_usage_json,
10127
+ s.smart_tags_json,
10128
+ s.smart_tags_source_updated_at
10129
+ `;
10130
+ var FILE_ACTIVITY_JOIN = `
10131
+ FROM session_file_activity fa
10132
+ JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
10133
+ `;
10134
+ var FILE_ACTIVITY_ORDER = "fa.latest_time DESC, fa.count DESC, fa.path";
8448
10135
  function listFileActivity(options = {}) {
8449
10136
  return queryFileActivity(options);
8450
10137
  }
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 = [
10138
+ function fileActivityWhere(query) {
10139
+ const filters = buildFileActivityWhere(query.options);
10140
+ const sessionFilters = query.sessionSearchOptions ? buildSessionSearchFilters(query.sessionSearchOptions) : { where: "", params: [] };
10141
+ const clauses = [
8458
10142
  filters.where.replace(/^WHERE /, ""),
8459
10143
  sessionFilters.where.replace(/^ AND /, "")
8460
10144
  ].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);
10145
+ return {
10146
+ where: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
10147
+ params: [...filters.params, ...sessionFilters.params]
10148
+ };
10149
+ }
10150
+ function fileActivitySql(query, where) {
10151
+ if (!query.onePerSession) {
10152
+ return `
10153
+ SELECT ${FILE_ACTIVITY_COLUMNS}
10154
+ ${FILE_ACTIVITY_JOIN}
10155
+ ${where}
10156
+ ORDER BY ${FILE_ACTIVITY_ORDER}
10157
+ LIMIT ?
10158
+ `;
10159
+ }
10160
+ return `
10161
+ SELECT ${FILE_ACTIVITY_COLUMNS}
10162
+ FROM (
10163
+ SELECT
10164
+ fa.rowid AS activity_rowid,
10165
+ ROW_NUMBER() OVER (
10166
+ PARTITION BY fa.agent_name, fa.session_id
10167
+ ORDER BY ${FILE_ACTIVITY_ORDER}
10168
+ ) AS session_rank
10169
+ ${FILE_ACTIVITY_JOIN}
10170
+ ${where}
10171
+ ) ranked
10172
+ JOIN session_file_activity fa ON fa.rowid = ranked.activity_rowid
10173
+ JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
10174
+ WHERE ranked.session_rank = 1
10175
+ ORDER BY ${FILE_ACTIVITY_ORDER}
10176
+ LIMIT ?
10177
+ `;
10178
+ }
10179
+ function queryFileActivity(options, sessionSearchOptions, onePerSession = false) {
10180
+ if (!hasCacheStorage()) {
10181
+ return [];
10182
+ }
10183
+ const query = { options, sessionSearchOptions, onePerSession };
10184
+ const { where, params } = fileActivityWhere(query);
10185
+ const sql = fileActivitySql(query, where);
10186
+ const queryRows = (db) => db.prepare(sql).all(...params, options.limit ?? 50);
8497
10187
  let rows = withCacheDbReadOnly(queryRows);
8498
10188
  if (rows == null && options.path) {
8499
10189
  rows = withCacheDb(queryRows);
@@ -8526,27 +10216,20 @@ function searchFileActivitySessions(query, options = {}) {
8526
10216
  {
8527
10217
  path: path2,
8528
10218
  kind: search.options.fileKind,
8529
- limit: (search.options.limit ?? 50) * 3
10219
+ limit: search.options.limit ?? 50
8530
10220
  },
8531
- search.options
10221
+ search.options,
10222
+ true
8532
10223
  );
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;
10224
+ return rows.map((row) => ({
10225
+ reference: row.reference,
10226
+ session: row.session,
10227
+ snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
10228
+ matchType: "file_path"
10229
+ }));
8548
10230
  }
8549
10231
  var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
10232
+ var FULL_SYNC_CURSOR_PREFIX = "full_sync_cursor:";
8550
10233
  function parseCachedSessionMeta(value) {
8551
10234
  if (!value) return null;
8552
10235
  try {
@@ -8557,7 +10240,7 @@ function parseCachedSessionMeta(value) {
8557
10240
  }
8558
10241
  function deleteLegacyCacheFile() {
8559
10242
  const legacyPath = getLegacyCachePath();
8560
- if (!existsSync12(legacyPath)) {
10243
+ if (!existsSync14(legacyPath)) {
8561
10244
  return;
8562
10245
  }
8563
10246
  try {
@@ -8584,6 +10267,8 @@ function loadCachedSessions(agentName) {
8584
10267
  title,
8585
10268
  source_path,
8586
10269
  directory,
10270
+ parent_agent_name,
10271
+ parent_session_id,
8587
10272
  project_identity_kind,
8588
10273
  project_identity_key,
8589
10274
  project_display_name,
@@ -8646,6 +10331,43 @@ function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATIO
8646
10331
  ).run(agentName, Date.now(), indexVersion);
8647
10332
  });
8648
10333
  }
10334
+ function markAgentFullSyncStarted(agentName) {
10335
+ withCacheDb((db) => {
10336
+ db.prepare(
10337
+ `
10338
+ UPDATE cache_initialization
10339
+ SET last_sync_at = 0
10340
+ WHERE agent_name = ?
10341
+ `
10342
+ ).run(agentName);
10343
+ });
10344
+ }
10345
+ function getAgentFullSyncCursor(agentName) {
10346
+ if (!hasCacheStorage()) return null;
10347
+ return withCacheDbReadOnly((db) => {
10348
+ const row = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(`${FULL_SYNC_CURSOR_PREFIX}${agentName}`);
10349
+ return row?.value || null;
10350
+ }) ?? null;
10351
+ }
10352
+ function markAgentFullSyncProgress(agentName, cursor) {
10353
+ if (!cursor) return;
10354
+ withCacheDb((db) => {
10355
+ db.prepare(
10356
+ `
10357
+ INSERT INTO cache_meta(key, value)
10358
+ VALUES (?, ?)
10359
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
10360
+ `
10361
+ ).run(`${FULL_SYNC_CURSOR_PREFIX}${agentName}`, cursor);
10362
+ });
10363
+ }
10364
+ function clearAgentFullSyncCursor(agentName) {
10365
+ withCacheDb((db) => {
10366
+ db.prepare("DELETE FROM cache_meta WHERE key = ?").run(
10367
+ `${FULL_SYNC_CURSOR_PREFIX}${agentName}`
10368
+ );
10369
+ });
10370
+ }
8649
10371
  function getAgentLastFullSyncAt(agentName) {
8650
10372
  if (!hasCacheStorage()) {
8651
10373
  return null;
@@ -8672,6 +10394,7 @@ function markAgentFullSyncCompleted(agentName) {
8672
10394
  `
8673
10395
  ).run(Date.now(), agentName);
8674
10396
  });
10397
+ clearAgentFullSyncCursor(agentName);
8675
10398
  }
8676
10399
  function loadCachedSessionRawEntry(agentName, sessionId) {
8677
10400
  if (!hasCacheStorage()) {
@@ -8687,6 +10410,8 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
8687
10410
  title,
8688
10411
  source_path,
8689
10412
  directory,
10413
+ parent_agent_name,
10414
+ parent_session_id,
8690
10415
  project_identity_kind,
8691
10416
  project_identity_key,
8692
10417
  project_display_name,
@@ -8757,7 +10482,7 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
8757
10482
  });
8758
10483
  }
8759
10484
  function saveCachedSessions(agentName, sessions, meta = {}) {
8760
- withCacheDb((db) => {
10485
+ const persisted = withCacheDb((db) => {
8761
10486
  const deleteSession = db.prepare(
8762
10487
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
8763
10488
  );
@@ -8809,13 +10534,12 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
8809
10534
  });
8810
10535
  write();
8811
10536
  deleteLegacyCacheFile();
10537
+ return true;
8812
10538
  });
10539
+ return persisted ?? false;
8813
10540
  }
8814
10541
  function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
8815
- if (changes.length === 0 && removedSessionIds.length === 0) {
8816
- return;
8817
- }
8818
- withCacheDb((db) => {
10542
+ const persisted = withCacheDb((db) => {
8819
10543
  const deleteSession = db.prepare(
8820
10544
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
8821
10545
  );
@@ -8861,7 +10585,9 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8861
10585
  });
8862
10586
  write();
8863
10587
  deleteLegacyCacheFile();
10588
+ return true;
8864
10589
  });
10590
+ return persisted ?? false;
8865
10591
  }
8866
10592
  function clearCache() {
8867
10593
  setSchemaEnsuredPath(null);
@@ -8887,11 +10613,11 @@ function clearCache() {
8887
10613
  const walPath = `${cachePath}-wal`;
8888
10614
  const shmPath = `${cachePath}-shm`;
8889
10615
  for (const filePath of [walPath, shmPath]) {
8890
- if (!existsSync12(filePath)) {
10616
+ if (!existsSync14(filePath)) {
8891
10617
  continue;
8892
10618
  }
8893
10619
  try {
8894
- rmSync(filePath, { force: true });
10620
+ rmSync2(filePath, { force: true });
8895
10621
  } catch {
8896
10622
  }
8897
10623
  }
@@ -8916,6 +10642,8 @@ function sessionSignature(session) {
8916
10642
  return JSON.stringify([
8917
10643
  session.title,
8918
10644
  session.directory,
10645
+ session.parent_reference?.agentName ?? null,
10646
+ session.parent_reference?.sessionId ?? null,
8919
10647
  session.time_created,
8920
10648
  session.time_updated ?? session.time_created,
8921
10649
  session.stats.message_count,
@@ -8969,13 +10697,7 @@ function filterSessions(sessions, options) {
8969
10697
  if (options.cwd) {
8970
10698
  result = filterSessionsByProjectScope(result, options.cwd);
8971
10699
  }
8972
- if (options.from != null) {
8973
- result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
8974
- }
8975
- if (options.to != null) {
8976
- result = result.filter((s) => (s.time_updated ?? s.time_created) <= options.to);
8977
- }
8978
- return result;
10700
+ return filterSessionTreeByActivityWindow(result, options.from, options.to);
8979
10701
  }
8980
10702
  function saveCachedSessionDiff(agent, cachedSessions, updatedSessions, changedIds = []) {
8981
10703
  const diff = computeSessionDiff(cachedSessions, updatedSessions, changedIds, sessionSignature);
@@ -8997,17 +10719,47 @@ function chunkSessions(items, chunkCount) {
8997
10719
  });
8998
10720
  return chunks.filter((chunk) => chunk.length > 0);
8999
10721
  }
9000
- function ensureSessionTagsSync(agent, sessions) {
10722
+ function ensureSessionTagsSync(agent, sessions, onProgress) {
9001
10723
  let changed = false;
10724
+ let processed = 0;
10725
+ const total = sessions.length;
10726
+ const timing = {
10727
+ sessions: total,
10728
+ cacheHits: 0,
10729
+ staleSessions: 0,
10730
+ failedSessions: 0,
10731
+ getSessionDataCalls: 0,
10732
+ getSessionDataMs: 0,
10733
+ classifySessionTagsCalls: 0,
10734
+ classifySessionTagsMs: 0
10735
+ };
9002
10736
  const tagged = sessions.map((session) => {
9003
10737
  const sourceUpdatedAt = session.time_updated ?? session.time_created;
9004
10738
  const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
9005
10739
  if (currentTags && session.smart_tags_source_updated_at === sourceUpdatedAt) {
10740
+ timing.cacheHits += 1;
10741
+ processed += 1;
10742
+ onProgress?.(processed, total);
9006
10743
  return session;
9007
10744
  }
10745
+ timing.staleSessions += 1;
9008
10746
  try {
9009
- const data = agent.getSessionData(session.id);
9010
- const tags = classifySessionTags(data);
10747
+ timing.getSessionDataCalls += 1;
10748
+ const getSessionDataStartedAt = performance.now();
10749
+ let data;
10750
+ try {
10751
+ data = agent.getSessionData(session.id);
10752
+ } finally {
10753
+ timing.getSessionDataMs += performance.now() - getSessionDataStartedAt;
10754
+ }
10755
+ timing.classifySessionTagsCalls += 1;
10756
+ const classifySessionTagsStartedAt = performance.now();
10757
+ let tags;
10758
+ try {
10759
+ tags = classifySessionTags(data);
10760
+ } finally {
10761
+ timing.classifySessionTagsMs += performance.now() - classifySessionTagsStartedAt;
10762
+ }
9011
10763
  changed = true;
9012
10764
  return {
9013
10765
  ...session,
@@ -9015,10 +10767,14 @@ function ensureSessionTagsSync(agent, sessions) {
9015
10767
  smart_tags_source_updated_at: getSmartTagSourceTimestamp(data)
9016
10768
  };
9017
10769
  } catch {
10770
+ timing.failedSessions += 1;
9018
10771
  return session;
10772
+ } finally {
10773
+ processed += 1;
10774
+ onProgress?.(processed, total);
9019
10775
  }
9020
10776
  });
9021
- return { sessions: tagged, changed };
10777
+ return { sessions: tagged, changed, timing };
9022
10778
  }
9023
10779
  async function classifySessionTagsInWorker(workerUrl, agentName, sessionIds, meta) {
9024
10780
  return new Promise((resolveWorker, rejectWorker) => {
@@ -9130,12 +10886,13 @@ async function scanAgentSmart(agent, options, onProgress) {
9130
10886
  }
9131
10887
  agent.setSessionMetaMap(metaMap);
9132
10888
  if (options.cacheOnly) {
10889
+ const visibleSessions = agent.filterCachedSessions(cached.sessions);
9133
10890
  onProgress?.({
9134
10891
  agent: agent.name,
9135
10892
  phase: "cache",
9136
- cachedCount: cached.sessions.length
10893
+ cachedCount: visibleSessions.length
9137
10894
  });
9138
- return finalizeAgentScan(agent, cached.sessions, {
10895
+ return finalizeAgentScan(agent, visibleSessions, {
9139
10896
  finalization: { kind: "cache-only", cached },
9140
10897
  options,
9141
10898
  timing,
@@ -9211,6 +10968,7 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
9211
10968
  from: options.from,
9212
10969
  to: options.to,
9213
10970
  fast: options.fast,
10971
+ includeRelatedSessions: true,
9214
10972
  onProgress: (progress) => {
9215
10973
  onProgress?.({
9216
10974
  agent: agent.name,
@@ -9231,11 +10989,17 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
9231
10989
  timing.tags = performance.now() - t2;
9232
10990
  const meta = buildAgentCacheMeta(agent);
9233
10991
  if (options.writeCache !== false) {
9234
- if (options.from == null && options.to == null) {
9235
- saveCachedSessions(agent.name, tagged.sessions, meta);
9236
- markAgentFullSyncCompleted(agent.name);
10992
+ const isFullWindow = options.from == null && options.to == null;
10993
+ const persisted = isFullWindow ? saveCachedSessions(agent.name, tagged.sessions, meta) : true;
10994
+ if (persisted) {
10995
+ if (isFullWindow) markAgentFullSyncCompleted(agent.name);
10996
+ markAgentCacheInitialized(agent.name);
10997
+ } else {
10998
+ getCoreDiagnostics()?.warn("cache.save_failed", {
10999
+ agent: agent.name,
11000
+ sessions: tagged.sessions.length
11001
+ });
9237
11002
  }
9238
- markAgentCacheInitialized(agent.name);
9239
11003
  }
9240
11004
  onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
9241
11005
  const filtered = filterSessions(tagged.sessions, options);
@@ -9440,16 +11204,16 @@ function getStateDir() {
9440
11204
  if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
9441
11205
  const currentPlatform = platform4();
9442
11206
  if (currentPlatform === "darwin") {
9443
- return join13(homedir7(), "Library", "Application Support", "codesesh");
11207
+ return join15(homedir7(), "Library", "Application Support", "codesesh");
9444
11208
  }
9445
11209
  if (currentPlatform === "win32") {
9446
11210
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
9447
- return join13(appData ?? join13(homedir7(), "AppData", "Roaming"), "codesesh");
11211
+ return join15(appData ?? join15(homedir7(), "AppData", "Roaming"), "codesesh");
9448
11212
  }
9449
- return join13(process.env.XDG_DATA_HOME ?? join13(homedir7(), ".local", "share"), "codesesh");
11213
+ return join15(process.env.XDG_DATA_HOME ?? join15(homedir7(), ".local", "share"), "codesesh");
9450
11214
  }
9451
11215
  function getStateDbPath() {
9452
- return join13(getStateDir(), STATE_DB_FILENAME);
11216
+ return join15(getStateDir(), STATE_DB_FILENAME);
9453
11217
  }
9454
11218
  function useMemoryStateStore() {
9455
11219
  return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
@@ -9866,17 +11630,6 @@ function getSessionAgentName(session) {
9866
11630
  function getSessionActivityTime(session) {
9867
11631
  return session.time_updated ?? session.time_created;
9868
11632
  }
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
11633
  function buildDashboard(sessions, options) {
9881
11634
  const { byAgentNames, scope, from, to, agentInfoMap } = options;
9882
11635
  const agentMetrics = /* @__PURE__ */ new Map();
@@ -9897,16 +11650,16 @@ function buildDashboard(sessions, options) {
9897
11650
  const dailyMap = /* @__PURE__ */ new Map();
9898
11651
  const dailyTokenMap = /* @__PURE__ */ new Map();
9899
11652
  if (from != null) {
9900
- const bucketStart = startOfLocalDay(from);
9901
- const bucketDays = Math.floor((startOfLocalDay(to) - bucketStart) / 864e5) + 1;
11653
+ const bucketDays = countCalendarDays(from, to);
9902
11654
  for (let i = 0; i < bucketDays; i += 1) {
9903
- const ts = bucketStart + i * 864e5;
9904
- const key = toLocalDateKey(ts);
11655
+ const key = toCalendarDayKey(addCalendarDays(from, i));
9905
11656
  dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
9906
11657
  dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
9907
11658
  }
9908
11659
  }
9909
- for (const session of sessions) {
11660
+ const visibleSessions = filterSessionTreeByActivityWindow(sessions, from, to);
11661
+ for (const session of visibleSessions) {
11662
+ if (isChildSession(session)) continue;
9910
11663
  const agentName = getSessionAgentName(session);
9911
11664
  if (scope.agent && agentName !== scope.agent) continue;
9912
11665
  if (scope.projectKind || scope.projectKey) {
@@ -9916,8 +11669,6 @@ function buildDashboard(sessions, options) {
9916
11669
  }
9917
11670
  }
9918
11671
  const activity = getSessionActivityTime(session);
9919
- if (from != null && activity < from) continue;
9920
- if (activity > to) continue;
9921
11672
  const messageCount = session.stats.message_count;
9922
11673
  const sessionTokens = getTotalTokens(session.stats);
9923
11674
  totalSessions += 1;
@@ -9933,7 +11684,7 @@ function buildDashboard(sessions, options) {
9933
11684
  metric.messages += messageCount;
9934
11685
  metric.tokens += sessionTokens;
9935
11686
  }
9936
- const key = toLocalDateKey(activity);
11687
+ const key = toCalendarDayKey(activity);
9937
11688
  let bucket = dailyMap.get(key);
9938
11689
  if (!bucket) {
9939
11690
  bucket = { date: key, sessions: 0, messages: 0 };
@@ -10016,18 +11767,16 @@ function buildDashboard(sessions, options) {
10016
11767
  recentSessions
10017
11768
  };
10018
11769
  }
10019
- function getProjectGroupKey(identityKind, identityKey) {
10020
- return `${identityKind}:${identityKey}`;
10021
- }
10022
11770
  function emptyMetrics() {
10023
11771
  return { messages: 0, tokens: 0, cost: 0, hasEstimatedCost: false, agentStats: /* @__PURE__ */ new Map() };
10024
11772
  }
10025
11773
  function attachProjectMetrics(projects, sessions) {
10026
11774
  const metrics = /* @__PURE__ */ new Map();
10027
11775
  for (const session of sessions) {
11776
+ if (isChildSession(session)) continue;
10028
11777
  const identity = session.project_identity;
10029
11778
  if (!identity) continue;
10030
- const key = getProjectGroupKey(identity.kind, identity.key);
11779
+ const key = getProjectIdentityKey(identity);
10031
11780
  let current = metrics.get(key);
10032
11781
  if (!current) {
10033
11782
  current = emptyMetrics();
@@ -10057,7 +11806,9 @@ function attachProjectMetrics(projects, sessions) {
10057
11806
  }
10058
11807
  }
10059
11808
  return projects.map((project) => {
10060
- const metric = metrics.get(getProjectGroupKey(project.identityKind, project.identityKey));
11809
+ const metric = metrics.get(
11810
+ getProjectIdentityKey({ kind: project.identityKind, key: project.identityKey })
11811
+ );
10061
11812
  return {
10062
11813
  ...project,
10063
11814
  messages: metric?.messages ?? 0,
@@ -10194,10 +11945,20 @@ function searchIndexedSessions(query, textQuery, parsed, options) {
10194
11945
  }
10195
11946
 
10196
11947
  export {
11948
+ mergeSessionsUpdatedEvents,
11949
+ startOfCalendarDay,
11950
+ addCalendarDays,
11951
+ countCalendarDays,
11952
+ isProjectIdentityKind,
11953
+ matchesProjectIdentity,
10197
11954
  normalizeSessionReference,
10198
11955
  formatSessionReference,
10199
11956
  getSessionAgentKey,
11957
+ sessionRoutePath,
10200
11958
  mergeSortedSessions,
11959
+ isChildSession,
11960
+ getRootSessions,
11961
+ filterSessionTreeByActivityWindow,
10201
11962
  registerAgent,
10202
11963
  createRegisteredAgents,
10203
11964
  getRegisteredAgents,
@@ -10206,10 +11967,14 @@ export {
10206
11967
  diffSessionSources,
10207
11968
  BaseAgent,
10208
11969
  FileSystemSessionSource,
11970
+ ensurePrivateDirectory,
11971
+ restrictPrivateFile,
11972
+ restrictExistingPrivateFiles,
11973
+ getPricingGeneration,
11974
+ publishPendingPricing,
11975
+ hasPendingPricing,
10209
11976
  refreshPricingCache,
10210
11977
  perf,
10211
- isProjectIdentityKind,
10212
- matchesProjectIdentity,
10213
11978
  createProjectScopeMatcher,
10214
11979
  matchesProjectScope,
10215
11980
  getSmartTagSourceTimestamp,
@@ -10222,6 +11987,9 @@ export {
10222
11987
  loadCachedSessions,
10223
11988
  isAgentCacheInitialized,
10224
11989
  markAgentCacheInitialized,
11990
+ markAgentFullSyncStarted,
11991
+ getAgentFullSyncCursor,
11992
+ markAgentFullSyncProgress,
10225
11993
  getAgentLastFullSyncAt,
10226
11994
  markAgentFullSyncCompleted,
10227
11995
  saveCachedSessions,
@@ -10246,10 +12014,9 @@ export {
10246
12014
  upsertSessionAlias,
10247
12015
  deleteSessionAlias,
10248
12016
  getSessionActivityTime,
10249
- startOfLocalDay,
10250
12017
  buildDashboard,
10251
12018
  attachProjectMetrics,
10252
12019
  executeSessionSearch,
10253
12020
  filterSessionSearchCandidates
10254
12021
  };
10255
- //# sourceMappingURL=chunk-7APNDHQ6.js.map
12022
+ //# sourceMappingURL=chunk-G2BTNW3C.js.map