codesesh 0.18.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 (35) hide show
  1. package/README.md +4 -2
  2. package/dist/{chunk-HIYT2ZJL.js → chunk-G2BTNW3C.js} +1678 -217
  3. package/dist/chunk-G2BTNW3C.js.map +1 -0
  4. package/dist/{chunk-DHNP33L4.js → chunk-KD3DGWZY.js} +2 -2
  5. package/dist/{dist-DMGLFBW6.js → dist-ALXOBKV2.js} +14 -2
  6. package/dist/index.js +168 -43
  7. package/dist/index.js.map +1 -1
  8. package/dist/scan-refresh-worker.js +243 -24
  9. package/dist/scan-refresh-worker.js.map +1 -1
  10. package/dist/search-index-worker.js +2 -2
  11. package/dist/smart-tag-worker.js +2 -2
  12. package/dist/web/assets/{AgentIcon-DcUJM198.js → AgentIcon-CEj7HO1m.js} +1 -1
  13. package/dist/web/assets/BookmarkButton-DLEnIyeE.js +1 -0
  14. package/dist/web/assets/{Dashboard-wjTLT17d.js → Dashboard-DUG0IFcy.js} +2 -2
  15. package/dist/web/assets/DialogTitle-Dv6DQPIb.js +1 -0
  16. package/dist/web/assets/Projects-BMaZc6J5.js +1 -0
  17. package/dist/web/assets/SearchFilterBar-iP6pzlZo.js +1 -0
  18. package/dist/web/assets/SearchResultsPanel-RuiLPkbP.js +1 -0
  19. package/dist/web/assets/{SessionDetail-CgR7ipmi.js → SessionDetail-De1Wl3mu.js} +35 -35
  20. package/dist/web/assets/{index-DuB6tGoe.css → index-B-CV7pFu.css} +1 -1
  21. package/dist/web/assets/index-CQI8F_8X.js +1930 -0
  22. package/dist/web/assets/session-indexes-BuxBafvh.js +4 -0
  23. package/dist/web/index.html +7 -8
  24. package/package.json +1 -1
  25. package/dist/chunk-HIYT2ZJL.js.map +0 -1
  26. package/dist/web/assets/BookmarkButton-CJjTjyae.js +0 -1
  27. package/dist/web/assets/DialogTitle-Bz0eXTGu.js +0 -1
  28. package/dist/web/assets/Projects-DBxubQuM.js +0 -1
  29. package/dist/web/assets/SearchFilterBar-BG9ah_Po.js +0 -1
  30. package/dist/web/assets/SearchResultsPanel-Clmf2xKw.js +0 -1
  31. package/dist/web/assets/index-DR07vs5V.js +0 -1890
  32. package/dist/web/assets/preload-helper-CmKXJxR3.js +0 -1
  33. package/dist/web/assets/session-indexes-EHJdYE4j.js +0 -4
  34. /package/dist/{chunk-DHNP33L4.js.map → chunk-KD3DGWZY.js.map} +0 -0
  35. /package/dist/{dist-DMGLFBW6.js.map → dist-ALXOBKV2.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../core/dist/chunk-DKIXFG56.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
  }
@@ -123,14 +123,14 @@ function normalizeMessageParts(value) {
123
123
  function mergeSessionsUpdatedEvents(previous, next) {
124
124
  const changedSessionHeads = /* @__PURE__ */ new Map();
125
125
  const removedSessionRefs = /* @__PURE__ */ new Map();
126
- const sessionKey = (agentName, sessionId) => `${agentName}\0${sessionId}`;
126
+ const sessionKey2 = (agentName, sessionId) => `${agentName}\0${sessionId}`;
127
127
  const addChanged = (item) => {
128
- const key = sessionKey(item.reference.agentName, item.reference.sessionId);
128
+ const key = sessionKey2(item.reference.agentName, item.reference.sessionId);
129
129
  removedSessionRefs.delete(key);
130
130
  changedSessionHeads.set(key, item);
131
131
  };
132
132
  const addRemoved = (item) => {
133
- const key = sessionKey(item.agentName, item.sessionId);
133
+ const key = sessionKey2(item.agentName, item.sessionId);
134
134
  changedSessionHeads.delete(key);
135
135
  removedSessionRefs.set(key, item);
136
136
  };
@@ -253,6 +253,44 @@ function mergeSortedSessions(shards) {
253
253
  }
254
254
  return merged;
255
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
+ }
256
294
  var SAMPLE_SESSION_HEAD = {
257
295
  id: "session-1",
258
296
  slug: "claudecode/session-1",
@@ -352,28 +390,38 @@ import { createRequire } from "module";
352
390
  import { createHash } from "crypto";
353
391
  import { existsSync as existsSync7, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
354
392
  import { join as join8, basename as basename5, dirname as dirname4 } from "path";
355
- import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "fs";
356
- import { join as join9, basename as basename6 } from "path";
357
- import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync6 } from "fs";
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";
358
406
  import { homedir as homedir3, platform as platform2 } from "os";
359
- import { join as join10, normalize } from "path";
360
- import { existsSync as existsSync10 } from "fs";
361
- import { basename as basename7, join as join11 } from "path";
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";
362
410
  import { homedir as homedir4, platform as platform3 } from "os";
363
- import { join as join12 } from "path";
411
+ import { join as join13 } from "path";
364
412
  import { availableParallelism } from "os";
365
413
  import { Worker } from "worker_threads";
366
- import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
414
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
367
415
  import { spawnSync } from "child_process";
368
416
  import * as os from "os";
369
417
  import * as path from "path";
370
- import { resolve, sep } from "path";
371
- import { existsSync as existsSync13, rmSync as rmSync2, unlinkSync } from "fs";
372
- import { existsSync as existsSync12 } from "fs";
373
- import { join as join13 } from "path";
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";
374
422
  import { homedir as homedir6 } from "os";
375
423
  import { homedir as homedir7, platform as platform4 } from "os";
376
- import { join as join14 } from "path";
424
+ import { join as join15 } from "path";
377
425
  var registrations = [];
378
426
  function registerAgent(reg) {
379
427
  registrations.push(reg);
@@ -468,6 +516,9 @@ function diffSessionSources(refs, cachedSessions, cachedMeta, options) {
468
516
  return { changedIds, removedIds };
469
517
  }
470
518
  var BaseAgent = class {
519
+ filterCachedSessions(sessions) {
520
+ return sessions;
521
+ }
471
522
  getUri(sessionId) {
472
523
  return `${this.name}://${sessionId}`;
473
524
  }
@@ -475,6 +526,14 @@ var BaseAgent = class {
475
526
  var FileSystemSessionSource = class extends BaseAgent {
476
527
  sessionMetaMap = /* @__PURE__ */ new Map();
477
528
  sourceFileStats = /* @__PURE__ */ new Map();
529
+ /**
530
+ * 变更集合扩展:当某些会话变更会影响其他会话的派生数据时
531
+ * (如 subagent 文件变更需要父会话重新聚合 token 统计),
532
+ * 子类返回需要一并重解析的会话 ID 集合。默认无关联,原样返回。
533
+ */
534
+ expandChangedSessionIds(changedIds, _refs) {
535
+ return changedIds;
536
+ }
478
537
  scan(options) {
479
538
  const sources = this.listSessionSources(options);
480
539
  const sessions = [];
@@ -572,10 +631,11 @@ var FileSystemSessionSource = class extends BaseAgent {
572
631
  * refs 未传时回退为自行枚举,供独立调用方(如测试)沿用旧行为。
573
632
  */
574
633
  incrementalScan(cachedSessions, changedIds, refs) {
634
+ const sources = refs ?? this.listSessionSources();
575
635
  const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
576
- const changedSet = new Set(changedIds);
636
+ const changedSet = new Set(this.expandChangedSessionIds(changedIds, sources));
577
637
  const currentIds = /* @__PURE__ */ new Set();
578
- for (const ref of refs ?? this.listSessionSources()) {
638
+ for (const ref of sources) {
579
639
  currentIds.add(ref.sessionId);
580
640
  if (!changedSet.has(ref.sessionId)) continue;
581
641
  const head = this.scanSessionSource(ref.sourcePath);
@@ -1527,7 +1587,7 @@ var TranscriptBuilder = class {
1527
1587
  };
1528
1588
  }
1529
1589
  };
1530
- var HEAD_INDEX_VERSION = "claudecode-head-v2";
1590
+ var HEAD_INDEX_VERSION = "claudecode-head-v4";
1531
1591
  function resolveClaudeCodeDataRoot() {
1532
1592
  return resolveHomePath("CLAUDE_CONFIG_DIR", ".claude");
1533
1593
  }
@@ -1583,6 +1643,10 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1583
1643
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1584
1644
  sessionsIndexCache = {};
1585
1645
  sessionsIndexMtime = {};
1646
+ childContextsBySource = /* @__PURE__ */ new Map();
1647
+ childContextCache = /* @__PURE__ */ new Map();
1648
+ childSessionIdByToolUseId = /* @__PURE__ */ new Map();
1649
+ childIndexReady = false;
1586
1650
  findBasePath() {
1587
1651
  return firstExisting(join5(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1588
1652
  }
@@ -1615,18 +1679,97 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1615
1679
  const indexPath = this.getSessionsIndexPath(projectDir);
1616
1680
  indexMtimes.set(projectDir, this.readFileMtimeMs(indexPath));
1617
1681
  }
1618
- return this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1619
- recursive: false,
1620
- scanWindow: options
1621
- }).map(({ file, stat }) => ({
1622
- sessionId: basename3(file, ".jsonl"),
1623
- sourcePath: file,
1624
- fingerprint: this.sourceFingerprint(stat, indexMtimes.get(dirname2(file)) ?? null)
1625
- }));
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;
1626
1768
  }
1627
1769
  parseFileSessionHead(sourcePath) {
1628
- const projectDir = dirname2(sourcePath);
1629
- 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));
1630
1773
  }
1631
1774
  getSessionData(sessionId) {
1632
1775
  const meta = this.sessionMetaMap.get(sessionId);
@@ -1636,12 +1779,19 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1636
1779
  if (!existsSync5(meta.sourcePath)) {
1637
1780
  throw new Error(`Session file missing: ${meta.sourcePath}`);
1638
1781
  }
1782
+ this.ensureChildIndex();
1639
1783
  const builder = new TranscriptBuilder();
1640
1784
  const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
1641
1785
  const countedUsageKeys = /* @__PURE__ */ new Set();
1642
1786
  for (const record of readJsonlFile(meta.sourcePath)) {
1643
1787
  try {
1644
- this.convertRecord(record, builder, assistantUuidToToolCalls, countedUsageKeys);
1788
+ this.convertRecord(
1789
+ record,
1790
+ builder,
1791
+ assistantUuidToToolCalls,
1792
+ countedUsageKeys,
1793
+ this.childSessionIdByToolUseId
1794
+ );
1645
1795
  } catch {
1646
1796
  }
1647
1797
  }
@@ -1652,6 +1802,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1652
1802
  title: meta.title,
1653
1803
  slug: `claudecode/${meta.id}`,
1654
1804
  directory: meta.directory,
1805
+ parent_reference: meta.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: meta.parentSessionId },
1655
1806
  version: void 0,
1656
1807
  time_created: meta.createdAt,
1657
1808
  time_updated: meta.updatedAt,
@@ -1668,25 +1819,125 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1668
1819
  return [];
1669
1820
  }
1670
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
+ }
1671
1918
  createFileSessionMeta(head, source) {
1672
- const projectDir = dirname2(source.file);
1919
+ const child = this.getChildContext(source.file);
1920
+ const projectDir = child?.projectDir ?? dirname2(source.file);
1673
1921
  const indexPath = this.getSessionsIndexPath(projectDir);
1674
1922
  const indexMtime = this.readFileMtimeMs(indexPath);
1675
1923
  return this.buildFileSessionMeta({
1676
1924
  head,
1677
1925
  source,
1678
- fingerprint: this.sourceFingerprint(source.stat, indexMtime),
1926
+ fingerprint: this.sourceFingerprint(source.stat, indexMtime, child?.metaMtimeMs),
1679
1927
  extras: {
1680
1928
  indexPath: indexMtime === null ? null : indexPath,
1681
1929
  indexMtimeMs: indexMtime,
1682
1930
  headIndexVersion: HEAD_INDEX_VERSION,
1683
- model: head.stats.total_tokens ? "unknown" : void 0
1931
+ model: head.stats.total_tokens ? "unknown" : void 0,
1932
+ parentSessionId: head.parent_reference?.sessionId ?? null
1684
1933
  }
1685
1934
  });
1686
1935
  }
1687
1936
  /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
1688
- sourceFingerprint(stat, indexMtime) {
1689
- 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);
1690
1941
  }
1691
1942
  getSessionsIndexPath(projectDir) {
1692
1943
  return join5(projectDir, "sessions-index.json");
@@ -1716,11 +1967,11 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1716
1967
  this.sessionsIndexMtime[cacheKey] = mtime;
1717
1968
  return map;
1718
1969
  }
1719
- parseSessionHeadResult(filePath, projectDir) {
1720
- const sessionId = basename3(filePath, ".jsonl");
1970
+ parseSessionHeadResult(filePath, projectDir, child) {
1971
+ const sessionId = child?.sessionId ?? basename3(filePath, ".jsonl");
1721
1972
  const index = this.loadSessionsIndex(projectDir);
1722
1973
  const indexEntry = index.get(sessionId);
1723
- const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
1974
+ const explicitTitle = child?.explicitTitle ?? (indexEntry?.summary ? String(indexEntry.summary) : null);
1724
1975
  let createdAt = 0;
1725
1976
  let updatedAt = 0;
1726
1977
  let lineIndex = 0;
@@ -1819,6 +2070,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1819
2070
  slug: `claudecode/${sessionId}`,
1820
2071
  title,
1821
2072
  directory,
2073
+ parent_reference: child?.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: child.parentSessionId },
1822
2074
  time_created: createdAt,
1823
2075
  time_updated: updatedAt,
1824
2076
  stats: {
@@ -1851,19 +2103,25 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1851
2103
  return null;
1852
2104
  }
1853
2105
  // --- Record conversion ---
1854
- convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
2106
+ convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
1855
2107
  if (data["isMeta"] === true) return;
1856
2108
  const msgType = String(data["type"] ?? "");
1857
2109
  if (isInternalEventType(msgType)) return;
1858
2110
  if (msgType === "assistant") {
1859
- this.convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys);
2111
+ this.convertAssistantRecord(
2112
+ data,
2113
+ builder,
2114
+ assistantUuidToToolCalls,
2115
+ countedUsageKeys,
2116
+ childSessionIdByToolUseId
2117
+ );
1860
2118
  } else if (msgType === "user") {
1861
2119
  this.convertUserRecord(data, builder, assistantUuidToToolCalls);
1862
2120
  } else if (msgType === "tool_result") {
1863
2121
  this.convertToolResultRecord(data, builder);
1864
2122
  }
1865
2123
  }
1866
- convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
2124
+ convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
1867
2125
  const msg = asRecord(data["message"]) ?? {};
1868
2126
  const timestampMs = parseTimestampMs(data);
1869
2127
  const rawContent = asArray(msg["content"]) ?? [];
@@ -1903,12 +2161,14 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1903
2161
  }
1904
2162
  if (partType !== "tool_use") continue;
1905
2163
  const toolCallId = String(part["id"] ?? "").trim();
1906
- const toolPart = this.buildToolPart(part, timestampMs);
2164
+ const subagentId = childSessionIdByToolUseId.get(toolCallId);
2165
+ const toolPart2 = this.buildToolPart(part, timestampMs);
1907
2166
  const message = builder.appendToolCall(
1908
- toolPart,
1909
- { id: uuid, timestampMs, agent: "claude" },
2167
+ toolPart2,
2168
+ { id: uuid, timestampMs, agent: "claude", subagentId },
1910
2169
  { modeOnCreate: "tool" }
1911
2170
  );
2171
+ if (subagentId) message.subagent_id = subagentId;
1912
2172
  this.applyAssistantMetadata(message, data, msg, countedUsageKeys);
1913
2173
  if (toolCallId) {
1914
2174
  toolCallIds.push(toolCallId);
@@ -2305,6 +2565,24 @@ function isSqliteAvailable() {
2305
2565
  return DatabaseConstructor !== null;
2306
2566
  }
2307
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
+ }
2308
2586
  function parseJsonRecord(raw, agentName, field) {
2309
2587
  const parsed = asRecord(JSON.parse(String(raw ?? "{}")));
2310
2588
  if (parsed) return parsed;
@@ -2360,28 +2638,48 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2360
2638
  const hasMessageTable = Boolean(
2361
2639
  db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get()
2362
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')" : "";
2363
2644
  let rows;
2645
+ const parentIdSelect = hasParentId ? ", s.parent_id" : "";
2364
2646
  if (hasMessageTable) {
2365
2647
  rows = db.prepare(`
2366
2648
  SELECT
2367
2649
  s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2368
- s.version, s.summary_files
2650
+ s.version, s.summary_files${parentIdSelect}
2369
2651
  FROM session s
2370
2652
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2371
- 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
2372
2655
  `).all(cutoffTime);
2373
2656
  } else {
2374
2657
  rows = db.prepare(`
2375
2658
  SELECT s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2376
- 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}
2377
2660
  FROM session s
2378
2661
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2379
- 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
2380
2664
  `).all(cutoffTime);
2381
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));
2382
2674
  const headContexts = hasMessageTable ? this.buildHeadContexts(
2383
- this.readHeadMessageRows(db, cutoffTime),
2384
- 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
2385
2683
  ) : /* @__PURE__ */ new Map();
2386
2684
  const heads = [];
2387
2685
  options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
@@ -2423,6 +2721,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2423
2721
  slug: `${this.name}/${id}`,
2424
2722
  title: resolveSessionTitle(String(row.title ?? ""), messageTitle, null),
2425
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) },
2426
2725
  time_created: timeCreated,
2427
2726
  time_updated: timeUpdated,
2428
2727
  stats: {
@@ -2434,28 +2733,103 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2434
2733
  }
2435
2734
  });
2436
2735
  }
2437
- readHeadMessageRows(db, cutoffTime) {
2438
- return db.prepare(
2439
- `
2440
- SELECT m.id, m.session_id, m.data, m.time_created
2441
- FROM message m
2442
- JOIN session s ON s.id = m.session_id
2443
- WHERE COALESCE(s.time_updated, s.time_created) >= ?
2444
- 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(
2445
2741
  `
2446
- ).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;
2447
2766
  }
2448
- readHeadPartRows(db, cutoffTime) {
2449
- 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(
2450
2802
  `
2451
- SELECT p.message_id, p.data, p.time_created
2452
- FROM part p
2453
- JOIN message m ON m.id = p.message_id
2454
- JOIN session s ON s.id = m.session_id
2455
- WHERE COALESCE(s.time_updated, s.time_created) >= ?
2456
- ORDER BY p.message_id, p.time_created ASC, p.id 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
2457
2809
  `
2458
- ).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;
2459
2833
  }
2460
2834
  parsePartRow(partRow) {
2461
2835
  const partData = parseJsonRecord(partRow.data, this.name, "part.data");
@@ -2498,16 +2872,10 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2498
2872
  }
2499
2873
  return partsByMessage;
2500
2874
  }
2501
- buildHeadContexts(messageRows, partRows) {
2875
+ buildHeadContexts(messageRows, partRows, withParentId) {
2502
2876
  const partsByMessage = this.buildPartsByMessage(partRows);
2503
2877
  const contexts = /* @__PURE__ */ new Map();
2504
- for (const row of messageRows) {
2505
- const sessionId = String(row.session_id ?? "");
2506
- if (!sessionId) continue;
2507
- const msgData = parseJsonRecord(row.data, this.name, "message.data");
2508
- if (isInternalEventType(msgData.type)) continue;
2509
- const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
2510
- if (parts.length === 0) continue;
2878
+ const ensureContext = (sessionId) => {
2511
2879
  let context = contexts.get(sessionId);
2512
2880
  if (!context) {
2513
2881
  context = {
@@ -2521,16 +2889,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2521
2889
  };
2522
2890
  contexts.set(sessionId, context);
2523
2891
  }
2524
- const cost = Number(msgData.cost ?? 0);
2525
- const tokens = parseTokens(msgData.tokens, this.name);
2526
- const inputTokens = Number(tokens?.input ?? 0);
2527
- const outputTokens = Number(tokens?.output ?? 0);
2528
- const model = parseModel(msgData.modelID, this.name);
2529
- const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
2530
- if (estimatedCost !== null) context.stats.cost_source = "estimated";
2531
- context.stats.total_cost += cost || estimatedCost || 0;
2532
- context.stats.total_input_tokens += inputTokens;
2533
- 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);
2534
2927
  context.stats.message_count += 1;
2535
2928
  if (!context.messageTitle && String(msgData.role ?? "") === "user") {
2536
2929
  context.messageTitle = firstUserMessageTitle([
@@ -2551,6 +2944,29 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2551
2944
  }
2552
2945
  return contexts;
2553
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
+ }
2554
2970
  getSessionData(sessionId) {
2555
2971
  if (!this.dbPath) {
2556
2972
  this.dbPath = this.findDbPath();
@@ -2573,9 +2989,6 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2573
2989
  const timeCreated = Number(sessionRow.time_created ?? 0);
2574
2990
  const timeUpdated = Number(sessionRow.time_updated ?? timeCreated);
2575
2991
  const messages = [];
2576
- let totalCost = 0;
2577
- let totalInputTokens = 0;
2578
- let totalOutputTokens = 0;
2579
2992
  let hasEstimatedCost = false;
2580
2993
  const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
2581
2994
  const partsByMessage = this.buildPartsByMessage(this.readSessionPartRows(db, sessionId));
@@ -2611,28 +3024,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2611
3024
  firstUserMessageTitle(cleanedMessages),
2612
3025
  null
2613
3026
  );
3027
+ const stats = {
3028
+ message_count: cleanedMessages.length,
3029
+ total_input_tokens: 0,
3030
+ total_output_tokens: 0,
3031
+ total_cost: 0
3032
+ };
2614
3033
  for (const message of cleanedMessages) {
2615
- totalCost += message.cost ?? 0;
2616
- totalInputTokens += message.tokens?.input ?? 0;
2617
- 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;
2618
3037
  if (message.cost_source === "estimated") hasEstimatedCost = true;
2619
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
+ }
2620
3045
  return {
2621
3046
  reference: { agentName: this.name, sessionId: id },
2622
3047
  id,
2623
3048
  title,
2624
3049
  slug,
2625
3050
  directory,
3051
+ parent_reference: sessionRow.parent_id == null || String(sessionRow.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(sessionRow.parent_id) },
2626
3052
  version: asString(sessionRow.version) ?? void 0,
2627
3053
  time_created: timeCreated,
2628
3054
  time_updated: timeUpdated,
2629
3055
  summary_files: sessionRow.summary_files ?? void 0,
2630
3056
  stats: {
2631
- message_count: cleanedMessages.length,
2632
- total_input_tokens: totalInputTokens,
2633
- total_output_tokens: totalOutputTokens,
2634
- total_cost: totalCost,
2635
- 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
2636
3062
  },
2637
3063
  messages: cleanedMessages
2638
3064
  };
@@ -3076,7 +3502,7 @@ var KimiAgent = class extends FileSystemSessionSource {
3076
3502
  const rawArgs = function_.arguments;
3077
3503
  const normalizedArgs = normalizeToolArguments(rawArgs);
3078
3504
  const buffer = typeof rawArgs === "string" && typeof normalizedArgs !== "string" ? rawArgs : null;
3079
- const toolPart = {
3505
+ const toolPart2 = {
3080
3506
  type: "tool",
3081
3507
  tool: toolName,
3082
3508
  callID: callId,
@@ -3085,7 +3511,7 @@ var KimiAgent = class extends FileSystemSessionSource {
3085
3511
  time_created: timestampMs
3086
3512
  };
3087
3513
  builder.appendToolCall(
3088
- toolPart,
3514
+ toolPart2,
3089
3515
  { id: `wire-${seq}`, timestampMs: 0, agent: "kimi" },
3090
3516
  { markModeAsTool: true, target: "current" }
3091
3517
  );
@@ -3265,22 +3691,592 @@ var KimiAgent = class extends FileSystemSessionSource {
3265
3691
  if (stats.total_cost > 0) {
3266
3692
  stats.cost_source = "estimated";
3267
3693
  }
3268
- return stats;
3694
+ return stats;
3695
+ }
3696
+ buildSessionData(meta, builder, stats) {
3697
+ const transcript = builder.finish(stats);
3698
+ return {
3699
+ reference: { agentName: this.name, sessionId: meta.id },
3700
+ id: meta.id,
3701
+ title: meta.title,
3702
+ slug: `kimi/${meta.id}`,
3703
+ directory: meta.cwd,
3704
+ time_created: meta.createdAt,
3705
+ time_updated: meta.createdAt,
3706
+ stats: transcript.stats,
3707
+ messages: transcript.messages
3708
+ };
3709
+ }
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
+ };
3269
4100
  }
3270
- buildSessionData(meta, builder, stats) {
3271
- const transcript = builder.finish(stats);
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);
3272
4106
  return {
3273
4107
  reference: { agentName: this.name, sessionId: meta.id },
3274
4108
  id: meta.id,
3275
4109
  title: meta.title,
3276
- slug: `kimi/${meta.id}`,
3277
- directory: meta.cwd,
4110
+ slug: `${this.name}/${meta.id}`,
4111
+ directory: meta.workDir,
3278
4112
  time_created: meta.createdAt,
3279
- time_updated: meta.createdAt,
4113
+ time_updated: meta.updatedAt,
3280
4114
  stats: transcript.stats,
3281
4115
  messages: transcript.messages
3282
4116
  };
3283
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
+ }
3284
4280
  };
3285
4281
  var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
3286
4282
  var EXEC_OUTPUT_ENVELOPE_RE = /^Script completed\nWall time [^\n]*\nOutput:\n?/;
@@ -3543,7 +4539,7 @@ var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
3543
4539
  var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
3544
4540
  var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
3545
4541
  var HEAD_INDEX_VERSION2 = "codex-head-v1";
3546
- var PARSER_VERSION = "codex-parser-v4";
4542
+ var PARSER_VERSION = "codex-parser-v6";
3547
4543
  function resolveCodexDataRoot() {
3548
4544
  return resolveHomePath("CODEX_HOME", ".codex");
3549
4545
  }
@@ -3566,7 +4562,7 @@ var CODEX_TOOL_TITLE_MAP = {
3566
4562
  subagent: "subagent"
3567
4563
  };
3568
4564
  function extractSessionId(filename) {
3569
- const stem = basename6(filename, ".jsonl");
4565
+ const stem = basename7(filename, ".jsonl");
3570
4566
  const parts = stem.split("-");
3571
4567
  if (parts.length >= 5) {
3572
4568
  return parts.slice(-5).join("-");
@@ -3595,6 +4591,14 @@ function narrowRecordField(value, field) {
3595
4591
  function extractPayload(data) {
3596
4592
  return narrowRecordField(data["payload"], "payload") ?? {};
3597
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
+ }
3598
4602
  function extractTokenUsage(payload) {
3599
4603
  const info = narrowRecordField(payload["info"], "token_count.info");
3600
4604
  return {
@@ -3620,7 +4624,7 @@ function resolveToolIdentity(name, namespace) {
3620
4624
  metadata: { name, namespace: namespaceText }
3621
4625
  };
3622
4626
  }
3623
- function normalizeToolArguments2(raw) {
4627
+ function normalizeToolArguments3(raw) {
3624
4628
  if (typeof raw === "string") {
3625
4629
  try {
3626
4630
  return JSON.parse(raw);
@@ -3647,6 +4651,16 @@ function flattenOutputText(output) {
3647
4651
  }
3648
4652
  return "";
3649
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
+ }
3650
4664
  var PATCH_BEGIN_RE = /\*\*\* Begin Patch/;
3651
4665
  var PATCH_END_RE = /\*\*\* End Patch/;
3652
4666
  var PATCH_HEADER_RE = /\*\*\*\s+(Add|Delete|Update|Move)\s+File:\s*(.+)/;
@@ -3729,6 +4743,18 @@ function extractPatchContent(lines, startIndex) {
3729
4743
  }
3730
4744
  return { text: contentLines.join("\n"), nextLineIndex: i };
3731
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
+ }
3732
4758
  var CodexAgent = class extends SingleFileSessionSource {
3733
4759
  name = "codex";
3734
4760
  displayName = "Codex";
@@ -3736,17 +4762,19 @@ var CodexAgent = class extends SingleFileSessionSource {
3736
4762
  sessionIndexCache = /* @__PURE__ */ new Map();
3737
4763
  sessionIndexMtime;
3738
4764
  sessionIndexPath;
4765
+ subagentIndex = null;
4766
+ subagentStatsByParent = /* @__PURE__ */ new Map();
3739
4767
  // ---- BaseAgent implementation ----
3740
4768
  findBasePath() {
3741
- return firstExisting(join9(resolveCodexDataRoot(), "sessions"));
4769
+ return firstExisting(join10(resolveCodexDataRoot(), "sessions"));
3742
4770
  }
3743
4771
  getSessionWatchPlan() {
3744
4772
  const dataRoot = resolveCodexDataRoot();
3745
4773
  return {
3746
4774
  status: "supported",
3747
4775
  targets: [
3748
- { path: join9(dataRoot, "sessions") },
3749
- { path: join9(dataRoot, "session_index.jsonl") }
4776
+ { path: join10(dataRoot, "sessions") },
4777
+ { path: join10(dataRoot, "session_index.jsonl") }
3750
4778
  ]
3751
4779
  };
3752
4780
  }
@@ -3758,16 +4786,119 @@ var CodexAgent = class extends SingleFileSessionSource {
3758
4786
  listSessionSources(options) {
3759
4787
  if (!this.basePath) return [];
3760
4788
  this.loadSessionIndex();
3761
- return this.listRolloutFiles(options).map(({ file, stat }) => ({
4789
+ return this.listScanSources(options).map(({ file, stat }) => ({
3762
4790
  sessionId: extractSessionId(file),
3763
4791
  sourcePath: file,
3764
4792
  fingerprint: this.sourceFingerprint(file, stat)
3765
4793
  }));
3766
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
+ }
3767
4897
  getSessionData(sessionId) {
3768
4898
  const meta = this.sessionMetaMap.get(sessionId);
3769
4899
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
3770
- if (!existsSync8(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();
3771
4902
  const transcript = new TranscriptBuilder();
3772
4903
  let totalInputTokens = 0;
3773
4904
  let totalOutputTokens = 0;
@@ -3850,18 +4981,119 @@ var CodexAgent = class extends SingleFileSessionSource {
3850
4981
  total_cost: totalCost,
3851
4982
  cost_source: totalCost > 0 ? "estimated" : void 0
3852
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;
3853
4994
  return {
3854
4995
  reference: { agentName: this.name, sessionId: meta.id },
3855
4996
  id: meta.id,
3856
4997
  title: meta.title,
3857
4998
  slug: `codex/${meta.id}`,
3858
4999
  directory: meta.directory,
5000
+ parent_reference: meta.parentThreadId == null ? void 0 : { agentName: this.name, sessionId: meta.parentThreadId },
3859
5001
  time_created: meta.createdAt,
3860
5002
  time_updated: meta.updatedAt,
3861
5003
  stats: result.stats,
3862
5004
  messages: result.messages
3863
5005
  };
3864
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
+ }
3865
5097
  // ---- File listing ----
3866
5098
  listRolloutFiles(options) {
3867
5099
  if (!this.basePath) return [];
@@ -3871,6 +5103,54 @@ var CodexAgent = class extends SingleFileSessionSource {
3871
5103
  { scanWindow: options }
3872
5104
  );
3873
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
+ }
3874
5154
  createFileSessionMeta(head, source) {
3875
5155
  const indexPath = this.getSessionIndexPath();
3876
5156
  const indexMtime = this.sessionIndexMtime ?? null;
@@ -3883,7 +5163,8 @@ var CodexAgent = class extends SingleFileSessionSource {
3883
5163
  indexMtimeMs: indexMtime,
3884
5164
  headIndexVersion: HEAD_INDEX_VERSION2,
3885
5165
  parserVersion: PARSER_VERSION,
3886
- model: null
5166
+ model: null,
5167
+ parentThreadId: head.parent_reference?.sessionId ?? null
3887
5168
  }
3888
5169
  });
3889
5170
  }
@@ -3899,7 +5180,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3899
5180
  ]);
3900
5181
  }
3901
5182
  getSessionIndexPath() {
3902
- this.sessionIndexPath ??= join9(resolveCodexDataRoot(), "session_index.jsonl");
5183
+ this.sessionIndexPath ??= join10(resolveCodexDataRoot(), "session_index.jsonl");
3903
5184
  return this.sessionIndexPath;
3904
5185
  }
3905
5186
  // ---- Session index ----
@@ -3913,7 +5194,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3913
5194
  return;
3914
5195
  }
3915
5196
  try {
3916
- const content = readFileSync4(indexPath, "utf-8");
5197
+ const content = readFileSync5(indexPath, "utf-8");
3917
5198
  const cache = /* @__PURE__ */ new Map();
3918
5199
  for (const record of parseJsonlLines(content)) {
3919
5200
  const sid = String(record["id"] ?? "").trim();
@@ -3945,7 +5226,9 @@ var CodexAgent = class extends SingleFileSessionSource {
3945
5226
  }
3946
5227
  parseFileSessionHead(filePath, options) {
3947
5228
  this.loadSessionIndex();
3948
- return this.parseSessionHead(filePath, options);
5229
+ const head = this.parseSessionHead(filePath, options);
5230
+ if (head) this.applyChildStats(head.stats, head.id);
5231
+ return head;
3949
5232
  }
3950
5233
  parseSessionHead(filePath, options) {
3951
5234
  return getParsedSession(this.parseSessionHeadResult(filePath, options));
@@ -3956,6 +5239,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3956
5239
  }
3957
5240
  const sessionId = extractSessionId(filePath);
3958
5241
  let firstPayload = {};
5242
+ let parentThreadId = null;
3959
5243
  let createdAt = 0;
3960
5244
  let lineCount = 0;
3961
5245
  let messageTitle = null;
@@ -3985,7 +5269,9 @@ var CodexAgent = class extends SingleFileSessionSource {
3985
5269
  return skippedSession("malformed first record");
3986
5270
  }
3987
5271
  firstPayload = extractPayload(firstRecord);
3988
- createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync5(filePath).mtimeMs;
5272
+ const threadMeta = extractThreadMeta(firstRecord);
5273
+ parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
5274
+ createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync6(filePath).mtimeMs;
3989
5275
  updatedAt = createdAt;
3990
5276
  }
3991
5277
  try {
@@ -4079,6 +5365,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4079
5365
  slug: `codex/${sessionId}`,
4080
5366
  title,
4081
5367
  directory,
5368
+ parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
4082
5369
  time_created: createdAt,
4083
5370
  time_updated: updatedAt,
4084
5371
  stats: {
@@ -4092,9 +5379,6 @@ var CodexAgent = class extends SingleFileSessionSource {
4092
5379
  model_usage: Object.keys(modelUsageMap).length > 0 ? modelUsageMap : void 0
4093
5380
  });
4094
5381
  }
4095
- parseFastSessionHead(filePath) {
4096
- return getParsedSession(this.parseFastSessionHeadResult(filePath));
4097
- }
4098
5382
  parseFastSessionHeadResult(filePath) {
4099
5383
  const prefix = this.readFilePrefix(filePath);
4100
5384
  const lines = prefix.split("\n").filter((l) => l.trim());
@@ -4106,8 +5390,10 @@ var CodexAgent = class extends SingleFileSessionSource {
4106
5390
  } catch {
4107
5391
  return skippedSession("malformed first record");
4108
5392
  }
5393
+ const threadMeta = extractThreadMeta(firstRecord);
5394
+ const parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
4109
5395
  const payload = extractPayload(firstRecord);
4110
- const stat = statSync5(filePath);
5396
+ const stat = statSync6(filePath);
4111
5397
  const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
4112
5398
  const indexTitle = this.getTitleForSession(sessionId);
4113
5399
  const messageTitle = this.extractTitleFromLines(lines);
@@ -4119,6 +5405,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4119
5405
  slug: `codex/${sessionId}`,
4120
5406
  title,
4121
5407
  directory,
5408
+ parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
4122
5409
  time_created: createdAt,
4123
5410
  time_updated: stat.mtimeMs,
4124
5411
  stats: {
@@ -4214,19 +5501,8 @@ var CodexAgent = class extends SingleFileSessionSource {
4214
5501
  }
4215
5502
  // ---- Assistant message ----
4216
5503
  convertAssistantMessage(payload, transcript, timestampMs, pendingPlan, activeModel) {
4217
- const content = payload["content"];
4218
- if (!Array.isArray(content)) return pendingPlan;
4219
- const textParts = [];
4220
- for (const item of content) {
4221
- const ci = asRecord(item);
4222
- if (!ci) continue;
4223
- if (String(ci["type"] ?? "") === "output_text") {
4224
- const text = String(ci["text"] ?? "");
4225
- if (text.trim()) textParts.push(text);
4226
- }
4227
- }
4228
- if (textParts.length === 0) return pendingPlan;
4229
- const fullText = textParts.join("\n");
5504
+ const fullText = extractAssistantOutputText(payload);
5505
+ if (!fullText) return pendingPlan;
4230
5506
  const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
4231
5507
  if (planMatch) {
4232
5508
  const planText2 = planMatch[1].trim();
@@ -4341,8 +5617,8 @@ var CodexAgent = class extends SingleFileSessionSource {
4341
5617
  const name = String(payload["name"] ?? "").trim();
4342
5618
  if (!name) return;
4343
5619
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
4344
- const arguments_ = normalizeToolArguments2(payload["arguments"]);
4345
- const toolPart = {
5620
+ const arguments_ = normalizeToolArguments3(payload["arguments"]);
5621
+ const toolPart2 = {
4346
5622
  type: "tool",
4347
5623
  tool: toolIdentity.tool,
4348
5624
  callID: callId,
@@ -4356,7 +5632,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4356
5632
  time_created: timestampMs
4357
5633
  };
4358
5634
  transcript.appendToolCall(
4359
- toolPart,
5635
+ toolPart2,
4360
5636
  { id: "", timestampMs, agent: "codex", model: activeModel },
4361
5637
  { markModeAsTool: true }
4362
5638
  );
@@ -4388,7 +5664,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4388
5664
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
4389
5665
  const rawInput = payload["input"];
4390
5666
  const normalizedInput = normalizeCustomToolArguments(name, rawInput);
4391
- const toolPart = {
5667
+ const toolPart2 = {
4392
5668
  type: "tool",
4393
5669
  tool: toolIdentity.tool,
4394
5670
  callID: callId,
@@ -4402,7 +5678,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4402
5678
  time_created: timestampMs
4403
5679
  };
4404
5680
  transcript.appendToolCall(
4405
- toolPart,
5681
+ toolPart2,
4406
5682
  { id: "", timestampMs, agent: "codex", model: activeModel },
4407
5683
  { markModeAsTool: true }
4408
5684
  );
@@ -4419,7 +5695,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4419
5695
  const { name, namespace } = splitExecToolName(call.name);
4420
5696
  const toolIdentity = resolveToolIdentity(name, namespace);
4421
5697
  const arguments_ = name === "apply_patch" ? parseApplyPatchInput(getExecPatchText(call.args)) : call.args;
4422
- const toolPart = {
5698
+ const toolPart2 = {
4423
5699
  type: "tool",
4424
5700
  tool: toolIdentity.tool,
4425
5701
  callID: callId,
@@ -4433,7 +5709,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4433
5709
  time_created: timestampMs
4434
5710
  };
4435
5711
  transcript.appendToolCall(
4436
- toolPart,
5712
+ toolPart2,
4437
5713
  { id: "", timestampMs, agent: "codex", model: activeModel },
4438
5714
  { markModeAsTool: true }
4439
5715
  );
@@ -4518,15 +5794,15 @@ function resolveCursorDataRoot() {
4518
5794
  if (override) return override;
4519
5795
  const currentPlatform = platform2();
4520
5796
  if (currentPlatform === "darwin") {
4521
- return firstExisting(join10(homedir3(), "Library", "Application Support", "Cursor", "User"));
5797
+ return firstExisting(join11(homedir3(), "Library", "Application Support", "Cursor", "User"));
4522
5798
  }
4523
5799
  if (currentPlatform === "linux") {
4524
- const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join10(homedir3(), ".config");
4525
- return firstExisting(join10(configRoot, "Cursor", "User"));
5800
+ const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join11(homedir3(), ".config");
5801
+ return firstExisting(join11(configRoot, "Cursor", "User"));
4526
5802
  }
4527
5803
  if (currentPlatform === "win32") {
4528
- const appData = readEnvPath("APPDATA") ?? join10(homedir3(), "AppData", "Roaming");
4529
- return firstExisting(join10(appData, "Cursor", "User"));
5804
+ const appData = readEnvPath("APPDATA") ?? join11(homedir3(), "AppData", "Roaming");
5805
+ return firstExisting(join11(appData, "Cursor", "User"));
4530
5806
  }
4531
5807
  return null;
4532
5808
  }
@@ -4655,7 +5931,7 @@ var CURSOR_TOOL_TITLE_MAP = {
4655
5931
  ripgrep_raw_search: "grep",
4656
5932
  glob_file_search: "glob"
4657
5933
  };
4658
- function mapToolTitle2(toolName) {
5934
+ function mapToolTitle3(toolName) {
4659
5935
  return CURSOR_TOOL_TITLE_MAP[toolName] ?? toolName;
4660
5936
  }
4661
5937
  function normalizeToolOutputParts2(output, timestampMs) {
@@ -4716,9 +5992,9 @@ function buildToolPart(action, timestampMs) {
4716
5992
  const toolName = action.tool ?? "unknown";
4717
5993
  return {
4718
5994
  type: "tool",
4719
- tool: mapToolTitle2(toolName),
5995
+ tool: mapToolTitle3(toolName),
4720
5996
  callID: action.type ? `${action.type}:${String(action.input?.id ?? "")}` : "",
4721
- title: `Tool: ${mapToolTitle2(toolName)}`,
5997
+ title: `Tool: ${mapToolTitle3(toolName)}`,
4722
5998
  state: buildToolState(action),
4723
5999
  time_created: timestampMs
4724
6000
  };
@@ -4764,7 +6040,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4764
6040
  if (!isSqliteAvailable()) return null;
4765
6041
  const dataPath = resolveCursorDataRoot();
4766
6042
  if (!dataPath) return null;
4767
- return join10(dataPath, "globalStorage", "state.vscdb");
6043
+ return join11(dataPath, "globalStorage", "state.vscdb");
4768
6044
  }
4769
6045
  getSessionWatchPlan() {
4770
6046
  const dataPath = resolveCursorDataRoot();
@@ -4773,9 +6049,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4773
6049
  targets: dataPath ? [
4774
6050
  {
4775
6051
  root: dataPath,
4776
- path: join10(dataPath, "globalStorage", "state.vscdb")
6052
+ path: join11(dataPath, "globalStorage", "state.vscdb")
4777
6053
  },
4778
- { root: dataPath, path: join10(dataPath, "workspaceStorage") }
6054
+ { root: dataPath, path: join11(dataPath, "workspaceStorage") }
4779
6055
  ] : []
4780
6056
  };
4781
6057
  }
@@ -4787,34 +6063,34 @@ var CursorAgent = class extends DatabaseSessionSource {
4787
6063
  const map = /* @__PURE__ */ new Map();
4788
6064
  const dataPath = resolveCursorDataRoot();
4789
6065
  if (!dataPath) return map;
4790
- const wsStoragePath = join10(dataPath, "workspaceStorage");
4791
- if (!existsSync9(wsStoragePath)) return map;
6066
+ const wsStoragePath = join11(dataPath, "workspaceStorage");
6067
+ if (!existsSync10(wsStoragePath)) return map;
4792
6068
  let entryNames;
4793
6069
  try {
4794
- entryNames = readdirSync5(wsStoragePath);
6070
+ entryNames = readdirSync7(wsStoragePath);
4795
6071
  } catch {
4796
6072
  return map;
4797
6073
  }
4798
6074
  for (const name of entryNames) {
4799
- const wsDir = join10(wsStoragePath, name);
6075
+ const wsDir = join11(wsStoragePath, name);
4800
6076
  try {
4801
- if (!statSync6(wsDir).isDirectory()) continue;
6077
+ if (!statSync7(wsDir).isDirectory()) continue;
4802
6078
  } catch {
4803
6079
  continue;
4804
6080
  }
4805
- const wsJsonPath = join10(wsDir, "workspace.json");
4806
- if (!existsSync9(wsJsonPath)) continue;
6081
+ const wsJsonPath = join11(wsDir, "workspace.json");
6082
+ if (!existsSync10(wsJsonPath)) continue;
4807
6083
  let workspacePath;
4808
6084
  try {
4809
- const data = asRecord(JSON.parse(readFileSync5(wsJsonPath, "utf-8")));
6085
+ const data = asRecord(JSON.parse(readFileSync6(wsJsonPath, "utf-8")));
4810
6086
  const uri = narrowString("workspaceJson.folder", data?.folder) ?? narrowString("workspaceJson.workspace", data?.workspace) ?? "";
4811
6087
  if (!uri) continue;
4812
6088
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
4813
6089
  } catch {
4814
6090
  continue;
4815
6091
  }
4816
- const wsDbPath = join10(wsDir, "state.vscdb");
4817
- if (!existsSync9(wsDbPath)) continue;
6092
+ const wsDbPath = join11(wsDir, "state.vscdb");
6093
+ if (!existsSync10(wsDbPath)) continue;
4818
6094
  const wsDb = openDbReadOnly(wsDbPath);
4819
6095
  if (!wsDb) continue;
4820
6096
  try {
@@ -4837,7 +6113,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4837
6113
  }
4838
6114
  isAvailable() {
4839
6115
  this.dbPath = this.findDbPath();
4840
- return this.dbPath !== null && existsSync9(this.dbPath);
6116
+ return this.dbPath !== null && existsSync10(this.dbPath);
4841
6117
  }
4842
6118
  scan(options) {
4843
6119
  if (!this.dbPath) return [];
@@ -5192,9 +6468,9 @@ var CursorAgent = class extends DatabaseSessionSource {
5192
6468
  parts.push({ type: "text", text, time_created: timestampMs });
5193
6469
  }
5194
6470
  if (bubble.toolFormerData) {
5195
- const toolPart = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
5196
- if (toolPart) {
5197
- parts.push(toolPart);
6471
+ const toolPart2 = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
6472
+ if (toolPart2) {
6473
+ parts.push(toolPart2);
5198
6474
  }
5199
6475
  }
5200
6476
  if (parts.length === 0) continue;
@@ -5225,7 +6501,7 @@ var CursorAgent = class extends DatabaseSessionSource {
5225
6501
  convertToolFormerData(toolData, timestampMs) {
5226
6502
  if (!toolData || !toolData.name) return null;
5227
6503
  const toolName = toolData.name;
5228
- const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle2(toolName);
6504
+ const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle3(toolName);
5229
6505
  const state = {
5230
6506
  status: toolData.status === "completed" ? "completed" : "running"
5231
6507
  };
@@ -5353,7 +6629,7 @@ function narrowTimestampMs(field, value) {
5353
6629
  return shaped === void 0 ? 0 : parseTimestampMs3(shaped);
5354
6630
  }
5355
6631
  function extractSessionIdFromFilename(filePath) {
5356
- const stem = basename7(filePath, ".jsonl");
6632
+ const stem = basename8(filePath, ".jsonl");
5357
6633
  const underscore = stem.indexOf("_");
5358
6634
  return underscore >= 0 ? stem.slice(underscore + 1) || stem : stem;
5359
6635
  }
@@ -5409,14 +6685,14 @@ var PiAgent = class extends SingleFileSessionSource {
5409
6685
  displayName = "Pi";
5410
6686
  basePath = null;
5411
6687
  findBasePath() {
5412
- return firstExisting(join11(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
6688
+ return firstExisting(join12(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
5413
6689
  }
5414
6690
  getSessionWatchPlan() {
5415
6691
  const dataRoot = resolvePiDataRoot();
5416
6692
  return {
5417
6693
  status: "supported",
5418
6694
  targets: [
5419
- { root: dataRoot, path: join11(dataRoot, "agent", "sessions") },
6695
+ { root: dataRoot, path: join12(dataRoot, "agent", "sessions") },
5420
6696
  { root: "data/pi", path: "data/pi" }
5421
6697
  ]
5422
6698
  };
@@ -5437,7 +6713,7 @@ var PiAgent = class extends SingleFileSessionSource {
5437
6713
  getSessionData(sessionId) {
5438
6714
  const meta = this.sessionMetaMap.get(sessionId);
5439
6715
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
5440
- if (!existsSync10(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
6716
+ if (!existsSync11(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5441
6717
  const parsed = this.parsePiFile(meta.sourcePath);
5442
6718
  const state = this.convertEntries(parsed.pathEntries);
5443
6719
  return {
@@ -5530,7 +6806,7 @@ var PiAgent = class extends SingleFileSessionSource {
5530
6806
  const sessionId = String(header["id"] ?? extractSessionIdFromFilename(filePath)).trim();
5531
6807
  if (!sessionId) throw new Error("missing session id");
5532
6808
  const stat = this.sessionSourceFile(filePath).stat;
5533
- const directory = String(header["cwd"] ?? "").trim() || basename7(filePath, ".jsonl");
6809
+ const directory = String(header["cwd"] ?? "").trim() || basename8(filePath, ".jsonl");
5534
6810
  const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
5535
6811
  const updatedAt = pathEntries.reduce(
5536
6812
  (max, entry) => Math.max(max, getEntryTimestamp(entry)),
@@ -5673,7 +6949,7 @@ var PiAgent = class extends SingleFileSessionSource {
5673
6949
  if (type === "toolCall") {
5674
6950
  const callId = String(item["id"] ?? "").trim();
5675
6951
  const toolName = String(item["name"] ?? "").trim() || "tool";
5676
- const toolPart = {
6952
+ const toolPart2 = {
5677
6953
  type: "tool",
5678
6954
  tool: toolName,
5679
6955
  title: `Tool: ${toolName}`,
@@ -5684,7 +6960,7 @@ var PiAgent = class extends SingleFileSessionSource {
5684
6960
  input: item["arguments"] ?? {}
5685
6961
  }
5686
6962
  };
5687
- parts.push(toolPart);
6963
+ parts.push(toolPart2);
5688
6964
  }
5689
6965
  }
5690
6966
  return parts;
@@ -5780,19 +7056,19 @@ var PiAgent = class extends SingleFileSessionSource {
5780
7056
  function resolveZCodeDataRoot() {
5781
7057
  const currentPlatform = platform3();
5782
7058
  if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
5783
- return join12(homedir4(), ".zcode");
7059
+ return join13(homedir4(), ".zcode");
5784
7060
  }
5785
7061
  function findZCodeDbPath() {
5786
7062
  if (!isSqliteAvailable()) return null;
5787
7063
  const dataRoot = resolveZCodeDataRoot();
5788
7064
  if (!dataRoot) return null;
5789
- return firstExisting(join12(dataRoot, "cli", "db", "db.sqlite"));
7065
+ return firstExisting(join13(dataRoot, "cli", "db", "db.sqlite"));
5790
7066
  }
5791
7067
  function getZCodeSessionWatchPlan() {
5792
7068
  const dataRoot = resolveZCodeDataRoot();
5793
7069
  return {
5794
7070
  status: "supported",
5795
- targets: dataRoot ? [{ root: dataRoot, path: join12(dataRoot, "cli", "db", "db.sqlite") }] : []
7071
+ targets: dataRoot ? [{ root: dataRoot, path: join13(dataRoot, "cli", "db", "db.sqlite") }] : []
5796
7072
  };
5797
7073
  }
5798
7074
  var ZCodeAgent = class extends OpenCodeSqliteAgent {
@@ -5834,6 +7110,13 @@ registerAgent({
5834
7110
  toolStrategy: "custom",
5835
7111
  create: () => new KimiAgent()
5836
7112
  });
7113
+ registerAgent({
7114
+ icon: "/icon/agent/kimi.svg",
7115
+ resolveDataRoot: resolveKimiCodeDataRoot,
7116
+ resumeCommandPrefix: "kimi -r",
7117
+ toolStrategy: "custom",
7118
+ create: () => new KimiCodeAgent()
7119
+ });
5837
7120
  registerAgent({
5838
7121
  icon: "/icon/agent/codex.svg",
5839
7122
  resolveDataRoot: resolveCodexDataRoot,
@@ -5863,11 +7146,11 @@ function fallbackDisplayName(input) {
5863
7146
  }
5864
7147
  var realFs = {
5865
7148
  exists(path2) {
5866
- return existsSync11(path2);
7149
+ return existsSync12(path2);
5867
7150
  },
5868
7151
  readText(path2) {
5869
7152
  try {
5870
- return readFileSync6(path2, "utf8");
7153
+ return readFileSync7(path2, "utf8");
5871
7154
  } catch {
5872
7155
  return null;
5873
7156
  }
@@ -6038,6 +7321,7 @@ function parseManifestName(file, text) {
6038
7321
  function buildProjectGroups(sessions) {
6039
7322
  const groups = /* @__PURE__ */ new Map();
6040
7323
  for (const session of sessions) {
7324
+ if (isChildSession(session)) continue;
6041
7325
  const identity = session.project_identity;
6042
7326
  if (!identity) continue;
6043
7327
  const activity = session.time_updated ?? session.time_created;
@@ -6090,7 +7374,7 @@ function isPathScopeMatch(queryPath, sessionPath) {
6090
7374
  return session === queryPath || session.startsWith(queryPath + "/") || queryPath.startsWith(session + "/");
6091
7375
  }
6092
7376
  function normalizeScopePath(path2) {
6093
- return resolve(path2).replaceAll(sep, "/");
7377
+ return resolve2(path2).replaceAll(sep, "/");
6094
7378
  }
6095
7379
  var TAG_ORDER = [
6096
7380
  "bugfix",
@@ -6365,16 +7649,16 @@ function setSchemaEnsuredPath(path2) {
6365
7649
  schemaEnsuredPath = path2;
6366
7650
  }
6367
7651
  function getCacheDir2() {
6368
- return join13(homedir6(), ".cache", "codesesh");
7652
+ return join14(homedir6(), ".cache", "codesesh");
6369
7653
  }
6370
7654
  function getCachePath2() {
6371
- return join13(getCacheDir2(), CACHE_FILENAME);
7655
+ return join14(getCacheDir2(), CACHE_FILENAME);
6372
7656
  }
6373
7657
  function getLegacyCachePath() {
6374
- return join13(getCacheDir2(), LEGACY_CACHE_FILENAME);
7658
+ return join14(getCacheDir2(), LEGACY_CACHE_FILENAME);
6375
7659
  }
6376
7660
  function hasCacheStorage() {
6377
- return existsSync12(getCachePath2());
7661
+ return existsSync13(getCachePath2());
6378
7662
  }
6379
7663
  function likePattern(value) {
6380
7664
  return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
@@ -6415,6 +7699,8 @@ function prepareUpsertSession(db) {
6415
7699
  title,
6416
7700
  source_path,
6417
7701
  directory,
7702
+ parent_agent_name,
7703
+ parent_session_id,
6418
7704
  project_identity_kind,
6419
7705
  project_identity_key,
6420
7706
  project_display_name,
@@ -6433,13 +7719,15 @@ function prepareUpsertSession(db) {
6433
7719
  smart_tags_json,
6434
7720
  smart_tags_source_updated_at,
6435
7721
  meta_json
6436
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7722
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6437
7723
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
6438
7724
  sort_index = excluded.sort_index,
6439
7725
  slug = excluded.slug,
6440
7726
  title = excluded.title,
6441
7727
  source_path = excluded.source_path,
6442
7728
  directory = excluded.directory,
7729
+ parent_agent_name = excluded.parent_agent_name,
7730
+ parent_session_id = excluded.parent_session_id,
6443
7731
  project_identity_kind = excluded.project_identity_kind,
6444
7732
  project_identity_key = excluded.project_identity_key,
6445
7733
  project_display_name = excluded.project_display_name,
@@ -6470,6 +7758,8 @@ function prepareUpsertIndexedSession(db) {
6470
7758
  title,
6471
7759
  source_path,
6472
7760
  directory,
7761
+ parent_agent_name,
7762
+ parent_session_id,
6473
7763
  project_identity_kind,
6474
7764
  project_identity_key,
6475
7765
  project_display_name,
@@ -6488,11 +7778,13 @@ function prepareUpsertIndexedSession(db) {
6488
7778
  smart_tags_json,
6489
7779
  smart_tags_source_updated_at,
6490
7780
  meta_json
6491
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7781
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6492
7782
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
6493
7783
  slug = excluded.slug,
6494
7784
  title = excluded.title,
6495
7785
  directory = excluded.directory,
7786
+ parent_agent_name = excluded.parent_agent_name,
7787
+ parent_session_id = excluded.parent_session_id,
6496
7788
  project_identity_kind = excluded.project_identity_kind,
6497
7789
  project_identity_key = excluded.project_identity_key,
6498
7790
  project_display_name = excluded.project_display_name,
@@ -6523,6 +7815,8 @@ function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, so
6523
7815
  session.title,
6524
7816
  sourcePath,
6525
7817
  session.directory,
7818
+ session.parent_reference?.agentName ?? null,
7819
+ session.parent_reference?.sessionId ?? null,
6526
7820
  identity.kind,
6527
7821
  identity.key,
6528
7822
  identity.displayName,
@@ -6600,6 +7894,12 @@ function sessionFromRow(row) {
6600
7894
  displayName: String(row.project_display_name ?? "")
6601
7895
  };
6602
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
+ }
6603
7903
  if (row.time_updated != null) {
6604
7904
  session.time_updated = Number(row.time_updated);
6605
7905
  }
@@ -6817,7 +8117,7 @@ function buildSessionContentFromMessages(title, messages) {
6817
8117
  }
6818
8118
  return chunks.join("\n");
6819
8119
  }
6820
- var CACHE_SCHEMA_VERSION = 17;
8120
+ var CACHE_SCHEMA_VERSION = 18;
6821
8121
  function withCacheDb(fn) {
6822
8122
  const cachePath = getCachePath2();
6823
8123
  const db = openDb(cachePath);
@@ -6917,6 +8217,8 @@ function createSessionTables(db) {
6917
8217
  title TEXT NOT NULL,
6918
8218
  source_path TEXT,
6919
8219
  directory TEXT NOT NULL,
8220
+ parent_agent_name TEXT,
8221
+ parent_session_id TEXT,
6920
8222
  project_identity_kind TEXT NOT NULL,
6921
8223
  project_identity_key TEXT NOT NULL,
6922
8224
  project_display_name TEXT NOT NULL,
@@ -6944,6 +8246,9 @@ function createSessionTables(db) {
6944
8246
  CREATE INDEX IF NOT EXISTS idx_sessions_project
6945
8247
  ON sessions(project_identity_kind, project_identity_key, activity_time);
6946
8248
 
8249
+ CREATE INDEX IF NOT EXISTS idx_sessions_parent
8250
+ ON sessions(parent_agent_name, parent_session_id);
8251
+
6947
8252
  CREATE TABLE IF NOT EXISTS messages (
6948
8253
  agent_name TEXT NOT NULL,
6949
8254
  session_id TEXT NOT NULL,
@@ -7233,6 +8538,7 @@ function createProjectGroupsView(db) {
7233
8538
  `);
7234
8539
  return;
7235
8540
  }
8541
+ const hasParentReference = columnExists(db, "sessions", "parent_agent_name") && columnExists(db, "sessions", "parent_session_id");
7236
8542
  db.exec(`
7237
8543
  CREATE VIEW IF NOT EXISTS project_groups_v AS
7238
8544
  SELECT
@@ -7243,6 +8549,7 @@ function createProjectGroupsView(db) {
7243
8549
  COUNT(*) AS session_count,
7244
8550
  MAX(activity_time) AS last_activity
7245
8551
  FROM sessions
8552
+ ${hasParentReference ? "WHERE parent_agent_name IS NULL OR parent_session_id IS NULL" : ""}
7246
8553
  GROUP BY project_identity_kind, project_identity_key;
7247
8554
  `);
7248
8555
  }
@@ -7640,6 +8947,19 @@ function addMessagePartsFormatVersion(db) {
7640
8947
  }
7641
8948
  db.exec("ALTER TABLE messages ADD COLUMN parts_format_version INTEGER NOT NULL DEFAULT 0");
7642
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
+ }
7643
8963
  var CODEX_EXEC_DECODE_MIGRATION_KEY = "codex_exec_decode_migrated_v3";
7644
8964
  function migrateCodexExecDecode(db) {
7645
8965
  if (!tableExists(db, "cache_meta")) return;
@@ -7654,6 +8974,30 @@ function migrateCodexExecDecode(db) {
7654
8974
  "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
7655
8975
  ).run(CODEX_EXEC_DECODE_MIGRATION_KEY);
7656
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
+ }
7657
9001
  function rebuildSearchIndex(db) {
7658
9002
  if (!tableExists(db, "session_documents_fts")) {
7659
9003
  return;
@@ -7763,6 +9107,8 @@ function ensureSchema(db, dbPath) {
7763
9107
  createLatestCacheSchema(db);
7764
9108
  setCacheSchemaVersion(db);
7765
9109
  migrateCodexExecDecode(db);
9110
+ migrateOpenCodeSubagentFold(db);
9111
+ migrateSubagentTree(db);
7766
9112
  return;
7767
9113
  }
7768
9114
  runSchemaMigrations(db, {
@@ -7794,7 +9140,13 @@ function ensureSchema(db, dbPath) {
7794
9140
  invalidateSearchContentHashes(db2);
7795
9141
  }
7796
9142
  },
7797
- { version: 7, migrate: backfillStructuredSessions },
9143
+ {
9144
+ version: 7,
9145
+ migrate(db2) {
9146
+ addSessionParentReference(db2);
9147
+ backfillStructuredSessions(db2);
9148
+ }
9149
+ },
7798
9150
  { version: 8, migrate: backfillFileActivity },
7799
9151
  {
7800
9152
  version: 9,
@@ -7825,7 +9177,8 @@ function ensureSchema(db, dbPath) {
7825
9177
  { version: 13, migrate: createCacheTables },
7826
9178
  { version: 14, migrate: addIndexedMessageCount },
7827
9179
  { version: 15, destructive: true, migrate: compactSessionDocuments },
7828
- { version: 17, migrate: addMessagePartsFormatVersion }
9180
+ { version: 17, migrate: addMessagePartsFormatVersion },
9181
+ { version: 18, migrate: addSessionParentReference }
7829
9182
  ]
7830
9183
  });
7831
9184
  createLatestCacheSchema(db);
@@ -7833,6 +9186,8 @@ function ensureSchema(db, dbPath) {
7833
9186
  setCacheSchemaVersion(db);
7834
9187
  }
7835
9188
  migrateCodexExecDecode(db);
9189
+ migrateOpenCodeSubagentFold(db);
9190
+ migrateSubagentTree(db);
7836
9191
  }
7837
9192
  function escapeFtsTerm(value) {
7838
9193
  return value.replaceAll('"', '""');
@@ -7963,6 +9318,7 @@ function readPendingReindexIds(db, agentName) {
7963
9318
  return new Set(rows.map((row) => String(row.session_id)));
7964
9319
  }
7965
9320
  var SEARCH_INDEX_STATE_BATCH_SIZE = 900;
9321
+ var SEARCH_INDEX_COMMIT_CHUNK_SIZE = 64;
7966
9322
  function shouldBulkSyncSearchIndex(options, changedCount) {
7967
9323
  if (options.isBulk != null) {
7968
9324
  return options.isBulk;
@@ -8222,6 +9578,32 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
8222
9578
  sortIndex: sessionSortIndexMap.get(session.id) ?? 0
8223
9579
  }));
8224
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
+ }
8225
9607
  const writeRows = () => {
8226
9608
  indexed = writeSearchIndexRows(
8227
9609
  db,
@@ -8847,6 +10229,7 @@ function searchFileActivitySessions(query, options = {}) {
8847
10229
  }));
8848
10230
  }
8849
10231
  var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
10232
+ var FULL_SYNC_CURSOR_PREFIX = "full_sync_cursor:";
8850
10233
  function parseCachedSessionMeta(value) {
8851
10234
  if (!value) return null;
8852
10235
  try {
@@ -8857,7 +10240,7 @@ function parseCachedSessionMeta(value) {
8857
10240
  }
8858
10241
  function deleteLegacyCacheFile() {
8859
10242
  const legacyPath = getLegacyCachePath();
8860
- if (!existsSync13(legacyPath)) {
10243
+ if (!existsSync14(legacyPath)) {
8861
10244
  return;
8862
10245
  }
8863
10246
  try {
@@ -8884,6 +10267,8 @@ function loadCachedSessions(agentName) {
8884
10267
  title,
8885
10268
  source_path,
8886
10269
  directory,
10270
+ parent_agent_name,
10271
+ parent_session_id,
8887
10272
  project_identity_kind,
8888
10273
  project_identity_key,
8889
10274
  project_display_name,
@@ -8946,6 +10331,43 @@ function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATIO
8946
10331
  ).run(agentName, Date.now(), indexVersion);
8947
10332
  });
8948
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
+ }
8949
10371
  function getAgentLastFullSyncAt(agentName) {
8950
10372
  if (!hasCacheStorage()) {
8951
10373
  return null;
@@ -8972,6 +10394,7 @@ function markAgentFullSyncCompleted(agentName) {
8972
10394
  `
8973
10395
  ).run(Date.now(), agentName);
8974
10396
  });
10397
+ clearAgentFullSyncCursor(agentName);
8975
10398
  }
8976
10399
  function loadCachedSessionRawEntry(agentName, sessionId) {
8977
10400
  if (!hasCacheStorage()) {
@@ -8987,6 +10410,8 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
8987
10410
  title,
8988
10411
  source_path,
8989
10412
  directory,
10413
+ parent_agent_name,
10414
+ parent_session_id,
8990
10415
  project_identity_kind,
8991
10416
  project_identity_key,
8992
10417
  project_display_name,
@@ -9114,9 +10539,6 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
9114
10539
  return persisted ?? false;
9115
10540
  }
9116
10541
  function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
9117
- if (changes.length === 0 && removedSessionIds.length === 0) {
9118
- return true;
9119
- }
9120
10542
  const persisted = withCacheDb((db) => {
9121
10543
  const deleteSession = db.prepare(
9122
10544
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
@@ -9191,7 +10613,7 @@ function clearCache() {
9191
10613
  const walPath = `${cachePath}-wal`;
9192
10614
  const shmPath = `${cachePath}-shm`;
9193
10615
  for (const filePath of [walPath, shmPath]) {
9194
- if (!existsSync13(filePath)) {
10616
+ if (!existsSync14(filePath)) {
9195
10617
  continue;
9196
10618
  }
9197
10619
  try {
@@ -9220,6 +10642,8 @@ function sessionSignature(session) {
9220
10642
  return JSON.stringify([
9221
10643
  session.title,
9222
10644
  session.directory,
10645
+ session.parent_reference?.agentName ?? null,
10646
+ session.parent_reference?.sessionId ?? null,
9223
10647
  session.time_created,
9224
10648
  session.time_updated ?? session.time_created,
9225
10649
  session.stats.message_count,
@@ -9273,13 +10697,7 @@ function filterSessions(sessions, options) {
9273
10697
  if (options.cwd) {
9274
10698
  result = filterSessionsByProjectScope(result, options.cwd);
9275
10699
  }
9276
- if (options.from != null) {
9277
- result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
9278
- }
9279
- if (options.to != null) {
9280
- result = result.filter((s) => (s.time_updated ?? s.time_created) <= options.to);
9281
- }
9282
- return result;
10700
+ return filterSessionTreeByActivityWindow(result, options.from, options.to);
9283
10701
  }
9284
10702
  function saveCachedSessionDiff(agent, cachedSessions, updatedSessions, changedIds = []) {
9285
10703
  const diff = computeSessionDiff(cachedSessions, updatedSessions, changedIds, sessionSignature);
@@ -9301,17 +10719,47 @@ function chunkSessions(items, chunkCount) {
9301
10719
  });
9302
10720
  return chunks.filter((chunk) => chunk.length > 0);
9303
10721
  }
9304
- function ensureSessionTagsSync(agent, sessions) {
10722
+ function ensureSessionTagsSync(agent, sessions, onProgress) {
9305
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
+ };
9306
10736
  const tagged = sessions.map((session) => {
9307
10737
  const sourceUpdatedAt = session.time_updated ?? session.time_created;
9308
10738
  const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
9309
10739
  if (currentTags && session.smart_tags_source_updated_at === sourceUpdatedAt) {
10740
+ timing.cacheHits += 1;
10741
+ processed += 1;
10742
+ onProgress?.(processed, total);
9310
10743
  return session;
9311
10744
  }
10745
+ timing.staleSessions += 1;
9312
10746
  try {
9313
- const data = agent.getSessionData(session.id);
9314
- 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
+ }
9315
10763
  changed = true;
9316
10764
  return {
9317
10765
  ...session,
@@ -9319,10 +10767,14 @@ function ensureSessionTagsSync(agent, sessions) {
9319
10767
  smart_tags_source_updated_at: getSmartTagSourceTimestamp(data)
9320
10768
  };
9321
10769
  } catch {
10770
+ timing.failedSessions += 1;
9322
10771
  return session;
10772
+ } finally {
10773
+ processed += 1;
10774
+ onProgress?.(processed, total);
9323
10775
  }
9324
10776
  });
9325
- return { sessions: tagged, changed };
10777
+ return { sessions: tagged, changed, timing };
9326
10778
  }
9327
10779
  async function classifySessionTagsInWorker(workerUrl, agentName, sessionIds, meta) {
9328
10780
  return new Promise((resolveWorker, rejectWorker) => {
@@ -9434,12 +10886,13 @@ async function scanAgentSmart(agent, options, onProgress) {
9434
10886
  }
9435
10887
  agent.setSessionMetaMap(metaMap);
9436
10888
  if (options.cacheOnly) {
10889
+ const visibleSessions = agent.filterCachedSessions(cached.sessions);
9437
10890
  onProgress?.({
9438
10891
  agent: agent.name,
9439
10892
  phase: "cache",
9440
- cachedCount: cached.sessions.length
10893
+ cachedCount: visibleSessions.length
9441
10894
  });
9442
- return finalizeAgentScan(agent, cached.sessions, {
10895
+ return finalizeAgentScan(agent, visibleSessions, {
9443
10896
  finalization: { kind: "cache-only", cached },
9444
10897
  options,
9445
10898
  timing,
@@ -9515,6 +10968,7 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
9515
10968
  from: options.from,
9516
10969
  to: options.to,
9517
10970
  fast: options.fast,
10971
+ includeRelatedSessions: true,
9518
10972
  onProgress: (progress) => {
9519
10973
  onProgress?.({
9520
10974
  agent: agent.name,
@@ -9750,16 +11204,16 @@ function getStateDir() {
9750
11204
  if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
9751
11205
  const currentPlatform = platform4();
9752
11206
  if (currentPlatform === "darwin") {
9753
- return join14(homedir7(), "Library", "Application Support", "codesesh");
11207
+ return join15(homedir7(), "Library", "Application Support", "codesesh");
9754
11208
  }
9755
11209
  if (currentPlatform === "win32") {
9756
11210
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
9757
- return join14(appData ?? join14(homedir7(), "AppData", "Roaming"), "codesesh");
11211
+ return join15(appData ?? join15(homedir7(), "AppData", "Roaming"), "codesesh");
9758
11212
  }
9759
- return join14(process.env.XDG_DATA_HOME ?? join14(homedir7(), ".local", "share"), "codesesh");
11213
+ return join15(process.env.XDG_DATA_HOME ?? join15(homedir7(), ".local", "share"), "codesesh");
9760
11214
  }
9761
11215
  function getStateDbPath() {
9762
- return join14(getStateDir(), STATE_DB_FILENAME);
11216
+ return join15(getStateDir(), STATE_DB_FILENAME);
9763
11217
  }
9764
11218
  function useMemoryStateStore() {
9765
11219
  return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
@@ -10203,7 +11657,9 @@ function buildDashboard(sessions, options) {
10203
11657
  dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
10204
11658
  }
10205
11659
  }
10206
- for (const session of sessions) {
11660
+ const visibleSessions = filterSessionTreeByActivityWindow(sessions, from, to);
11661
+ for (const session of visibleSessions) {
11662
+ if (isChildSession(session)) continue;
10207
11663
  const agentName = getSessionAgentName(session);
10208
11664
  if (scope.agent && agentName !== scope.agent) continue;
10209
11665
  if (scope.projectKind || scope.projectKey) {
@@ -10213,8 +11669,6 @@ function buildDashboard(sessions, options) {
10213
11669
  }
10214
11670
  }
10215
11671
  const activity = getSessionActivityTime(session);
10216
- if (from != null && activity < from) continue;
10217
- if (activity > to) continue;
10218
11672
  const messageCount = session.stats.message_count;
10219
11673
  const sessionTokens = getTotalTokens(session.stats);
10220
11674
  totalSessions += 1;
@@ -10319,6 +11773,7 @@ function emptyMetrics() {
10319
11773
  function attachProjectMetrics(projects, sessions) {
10320
11774
  const metrics = /* @__PURE__ */ new Map();
10321
11775
  for (const session of sessions) {
11776
+ if (isChildSession(session)) continue;
10322
11777
  const identity = session.project_identity;
10323
11778
  if (!identity) continue;
10324
11779
  const key = getProjectIdentityKey(identity);
@@ -10501,6 +11956,9 @@ export {
10501
11956
  getSessionAgentKey,
10502
11957
  sessionRoutePath,
10503
11958
  mergeSortedSessions,
11959
+ isChildSession,
11960
+ getRootSessions,
11961
+ filterSessionTreeByActivityWindow,
10504
11962
  registerAgent,
10505
11963
  createRegisteredAgents,
10506
11964
  getRegisteredAgents,
@@ -10529,6 +11987,9 @@ export {
10529
11987
  loadCachedSessions,
10530
11988
  isAgentCacheInitialized,
10531
11989
  markAgentCacheInitialized,
11990
+ markAgentFullSyncStarted,
11991
+ getAgentFullSyncCursor,
11992
+ markAgentFullSyncProgress,
10532
11993
  getAgentLastFullSyncAt,
10533
11994
  markAgentFullSyncCompleted,
10534
11995
  saveCachedSessions,
@@ -10558,4 +12019,4 @@ export {
10558
12019
  executeSessionSearch,
10559
12020
  filterSessionSearchCandidates
10560
12021
  };
10561
- //# sourceMappingURL=chunk-HIYT2ZJL.js.map
12022
+ //# sourceMappingURL=chunk-G2BTNW3C.js.map