codesesh 0.18.0 → 1.0.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 (68) hide show
  1. package/README.md +5 -2
  2. package/dist/{chunk-HIYT2ZJL.js → chunk-BEF6LLRG.js} +2982 -473
  3. package/dist/chunk-BEF6LLRG.js.map +1 -0
  4. package/dist/{chunk-DHNP33L4.js → chunk-TIHF4T3K.js} +2 -2
  5. package/dist/{dist-DMGLFBW6.js → dist-3F467V44.js} +16 -2
  6. package/dist/index.js +218 -52
  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 +9 -11
  11. package/dist/search-index-worker.js.map +1 -1
  12. package/dist/smart-tag-worker.js +2 -2
  13. package/dist/web/assets/ErrorBoundary-BY-N1QzJ.js +1 -0
  14. package/dist/web/assets/InteractiveReceipt-DYGyHeZd.js +1 -0
  15. package/dist/web/assets/OverviewScreen-_ZaVjhxN.js +1 -0
  16. package/dist/web/assets/PrismHighlighter-BZkkX0AU.js +6 -0
  17. package/dist/web/assets/Projects-q6cEm2dc.js +1 -0
  18. package/dist/web/assets/SearchFilterBar-CrVBpo40.js +1 -0
  19. package/dist/web/assets/SearchResultsPanel-Cg5V8Ia6.js +1 -0
  20. package/dist/web/assets/SessionDetail-BSSuddsZ.js +54 -0
  21. package/dist/web/assets/{session-title-CNhapdPJ.js → SmartTagChips-ux6idUGJ.js} +1 -1
  22. package/dist/web/assets/chunk-62JRHF6Z-BWRsEBnL.js +4 -0
  23. package/dist/web/assets/code-metrics-UYFsCC7E.js +1 -0
  24. package/dist/web/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  25. package/dist/web/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  26. package/dist/web/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  27. package/dist/web/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  28. package/dist/web/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  29. package/dist/web/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  30. package/dist/web/assets/index-C3KgxGwE.js +1410 -0
  31. package/dist/web/assets/index-DQX42cFQ.css +1 -0
  32. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  33. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  34. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  35. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  36. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  37. package/dist/web/assets/jsx-runtime-B-hcVAMW.js +1 -0
  38. package/dist/web/assets/panel-B7kuE60V.js +1 -0
  39. package/dist/web/assets/session-indexes-DmnjZ_JL.js +1 -0
  40. package/dist/web/assets/session-title-DdtlxtlR.js +1 -0
  41. package/dist/web/assets/space-grotesk-latin-ext-wght-normal-D9tNdqV9.woff2 +0 -0
  42. package/dist/web/assets/space-grotesk-latin-wght-normal-BhU9QXUp.woff2 +0 -0
  43. package/dist/web/assets/space-grotesk-vietnamese-wght-normal-D0rl6rjA.woff2 +0 -0
  44. package/dist/web/assets/utils-t2u9IbNT.js +1 -0
  45. package/dist/web/icon/agent/grok.svg +1 -0
  46. package/dist/web/index.html +43 -31
  47. package/package.json +8 -8
  48. package/dist/chunk-HIYT2ZJL.js.map +0 -1
  49. package/dist/web/assets/AgentIcon-DcUJM198.js +0 -1
  50. package/dist/web/assets/BookmarkButton-CJjTjyae.js +0 -1
  51. package/dist/web/assets/Dashboard-wjTLT17d.js +0 -39
  52. package/dist/web/assets/DialogTitle-Bz0eXTGu.js +0 -1
  53. package/dist/web/assets/InteractiveReceipt-xnRqJvfd.js +0 -1
  54. package/dist/web/assets/PrismHighlighter-C7OfXHq2.js +0 -6
  55. package/dist/web/assets/Projects-DBxubQuM.js +0 -1
  56. package/dist/web/assets/SearchFilterBar-BG9ah_Po.js +0 -1
  57. package/dist/web/assets/SearchResultsPanel-Clmf2xKw.js +0 -1
  58. package/dist/web/assets/SessionDetail-CgR7ipmi.js +0 -54
  59. package/dist/web/assets/index-DR07vs5V.js +0 -1890
  60. package/dist/web/assets/index-DuB6tGoe.css +0 -2
  61. package/dist/web/assets/index.dom-CCLJ9_FR.js +0 -1
  62. package/dist/web/assets/jsx-runtime-n5LQ9ujS.js +0 -1
  63. package/dist/web/assets/preload-helper-CmKXJxR3.js +0 -1
  64. package/dist/web/assets/session-indexes-EHJdYE4j.js +0 -4
  65. package/dist/web/assets/useTheme-CwDcVdGz.js +0 -1
  66. package/dist/web/assets/utils-B6KiDbIe.js +0 -1
  67. /package/dist/{chunk-DHNP33L4.js.map → chunk-TIHF4T3K.js.map} +0 -0
  68. /package/dist/{dist-DMGLFBW6.js.map → dist-3F467V44.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-TXYCGCSU.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,159 @@ function mergeSortedSessions(shards) {
253
253
  }
254
254
  return merged;
255
255
  }
256
+ function getSessionRouteKey(agentName, sessionId) {
257
+ return formatSessionReference({ agentName, sessionId });
258
+ }
259
+ function sessionKey(session) {
260
+ return getSessionRouteKey(getSessionAgentKey(session), session.id);
261
+ }
262
+ function parentKey(session) {
263
+ return session.parent_reference ? formatSessionReference(session.parent_reference) : null;
264
+ }
265
+ function activityTime(session) {
266
+ return session.time_updated ?? session.time_created;
267
+ }
268
+ function hasActivityInWindow(session, from, to) {
269
+ const activity = activityTime(session);
270
+ return (from == null || activity >= from) && (to == null || activity <= to);
271
+ }
272
+ function isChildSession(session) {
273
+ return session.parent_reference != null;
274
+ }
275
+ function getRootSessions(sessions) {
276
+ return sessions.filter((session) => !session.parent_reference);
277
+ }
278
+ function ownStats(session) {
279
+ const stats = session.stats;
280
+ const cost = stats.total_cost ?? 0;
281
+ return {
282
+ messageCount: stats.message_count,
283
+ inputTokens: stats.total_input_tokens,
284
+ outputTokens: stats.total_output_tokens,
285
+ cacheReadTokens: stats.total_cache_read_tokens ?? 0,
286
+ cacheCreateTokens: stats.total_cache_create_tokens ?? 0,
287
+ totalTokens: stats.total_tokens ?? stats.total_input_tokens + stats.total_output_tokens,
288
+ cost,
289
+ costSource: cost > 0 ? stats.cost_source ?? "recorded" : void 0
290
+ };
291
+ }
292
+ function addStats(target, source) {
293
+ target.messageCount += source.messageCount;
294
+ target.inputTokens += source.inputTokens;
295
+ target.outputTokens += source.outputTokens;
296
+ target.cacheReadTokens += source.cacheReadTokens;
297
+ target.cacheCreateTokens += source.cacheCreateTokens;
298
+ target.totalTokens += source.totalTokens;
299
+ target.cost += source.cost;
300
+ if (source.costSource === "estimated") target.costSource = "estimated";
301
+ }
302
+ function rollUpSubtree(entry) {
303
+ const order = [];
304
+ const pending2 = [entry];
305
+ while (pending2.length > 0) {
306
+ const node = pending2.pop();
307
+ order.push(node);
308
+ for (const child of node.children) pending2.push(child);
309
+ }
310
+ for (let index = order.length - 1; index >= 0; index -= 1) {
311
+ const node = order[index];
312
+ const stats = node.inclusiveStats;
313
+ let descendants = 0;
314
+ for (const child of node.children) {
315
+ descendants += 1 + child.descendantCount;
316
+ addStats(stats, child.inclusiveStats);
317
+ }
318
+ if (stats.cost > 0 && stats.costSource == null) stats.costSource = "recorded";
319
+ node.descendantCount = descendants;
320
+ }
321
+ }
322
+ function buildSessionTree(sessions) {
323
+ const byRouteKey = /* @__PURE__ */ new Map();
324
+ const nodes = sessions.map((session) => ({
325
+ session,
326
+ children: [],
327
+ descendantCount: 0,
328
+ inclusiveStats: ownStats(session)
329
+ }));
330
+ for (const node of nodes) {
331
+ const key = sessionKey(node.session);
332
+ if (!byRouteKey.has(key)) byRouteKey.set(key, node);
333
+ }
334
+ const chainTerminates = /* @__PURE__ */ new Map();
335
+ function reachesRoot(startKey) {
336
+ const path2 = [];
337
+ const onPath = /* @__PURE__ */ new Set();
338
+ let key = startKey;
339
+ let terminates = true;
340
+ while (key != null) {
341
+ const memo = chainTerminates.get(key);
342
+ if (memo != null) {
343
+ terminates = memo;
344
+ break;
345
+ }
346
+ if (onPath.has(key)) {
347
+ terminates = false;
348
+ break;
349
+ }
350
+ onPath.add(key);
351
+ path2.push(key);
352
+ const parent = parentKey(byRouteKey.get(key).session);
353
+ key = parent != null && byRouteKey.has(parent) ? parent : null;
354
+ }
355
+ for (const visited of path2) chainTerminates.set(visited, terminates);
356
+ return terminates;
357
+ }
358
+ const mountStates = /* @__PURE__ */ new Map();
359
+ const roots = [];
360
+ const orphans = [];
361
+ const entries = [];
362
+ for (const node of nodes) {
363
+ const key = sessionKey(node.session);
364
+ const parent = parentKey(node.session);
365
+ const state = parent == null ? "root" : byRouteKey.has(parent) && reachesRoot(key) ? "mounted-child" : "orphan";
366
+ mountStates.set(key, state);
367
+ if (state === "mounted-child") {
368
+ byRouteKey.get(parent).children.push(node);
369
+ continue;
370
+ }
371
+ (state === "root" ? roots : orphans).push(node);
372
+ entries.push(node);
373
+ }
374
+ for (const entry of entries) rollUpSubtree(entry);
375
+ return {
376
+ roots,
377
+ orphans,
378
+ entries,
379
+ byRouteKey,
380
+ mountStateOf(session) {
381
+ return mountStates.get(sessionKey(session)) ?? (session.parent_reference ? "orphan" : "root");
382
+ }
383
+ };
384
+ }
385
+ function filterSessionTreeByActivityWindow(sessions, from, to) {
386
+ if (from == null && to == null) return sessions;
387
+ const available = new Set(sessions.map(sessionKey));
388
+ const childrenByParent = /* @__PURE__ */ new Map();
389
+ const pending2 = [];
390
+ for (const session of sessions) {
391
+ const parent = parentKey(session);
392
+ if (parent != null && available.has(parent)) {
393
+ const children = childrenByParent.get(parent);
394
+ if (children) children.push(sessionKey(session));
395
+ else childrenByParent.set(parent, [sessionKey(session)]);
396
+ continue;
397
+ }
398
+ if (hasActivityInWindow(session, from, to)) pending2.push(sessionKey(session));
399
+ }
400
+ const visible = /* @__PURE__ */ new Set();
401
+ while (pending2.length > 0) {
402
+ const key = pending2.pop();
403
+ if (visible.has(key)) continue;
404
+ visible.add(key);
405
+ for (const childKey of childrenByParent.get(key) ?? []) pending2.push(childKey);
406
+ }
407
+ return sessions.filter((session) => visible.has(sessionKey(session)));
408
+ }
256
409
  var SAMPLE_SESSION_HEAD = {
257
410
  id: "session-1",
258
411
  slug: "claudecode/session-1",
@@ -301,9 +454,15 @@ var SAMPLE_DASHBOARD_DATA = {
301
454
  messages: 12,
302
455
  tokens: 6e3,
303
456
  cost: 0.042,
457
+ costRecorded: 0.042,
458
+ costEstimated: 0,
459
+ cacheReadTokens: 3e3,
304
460
  cost_source: "recorded",
305
- latestActivity: 17000036e5
461
+ latestActivity: 17000036e5,
462
+ latestActivityProject: "example/project",
463
+ latestActivityAgent: "claudecode"
306
464
  },
465
+ scopeCounts: { projects: 1, agents: 1 },
307
466
  perAgent: [
308
467
  {
309
468
  name: "claudecode",
@@ -311,14 +470,38 @@ var SAMPLE_DASHBOARD_DATA = {
311
470
  icon: "claude",
312
471
  sessions: 1,
313
472
  messages: 12,
314
- tokens: 6e3
473
+ tokens: 6e3,
474
+ cost: 0.042
315
475
  }
316
476
  ],
317
- dailyActivity: [{ date: "2023-11-14", sessions: 1, messages: 12 }],
318
- dailyTokenActivity: [
319
- { date: "2023-11-14", input: 700, output: 1800, cache_read: 3e3, cache_create: 500 }
477
+ dailyActivity: [
478
+ {
479
+ date: "2023-11-14",
480
+ sessions: 1,
481
+ messages: 12,
482
+ cost: 0.042,
483
+ input: 700,
484
+ output: 1800,
485
+ cache_read: 3e3,
486
+ cache_create: 500
487
+ }
320
488
  ],
321
489
  modelDistribution: [{ model: "claude-5-sonnet", tokens: 6e3, sessions: 1 }],
490
+ perProject: [
491
+ {
492
+ identityKind: "git_remote",
493
+ identityKey: "github.com/example/project",
494
+ displayName: "example/project",
495
+ sessions: 1,
496
+ messages: 12,
497
+ tokens: 6e3,
498
+ cost: 0.042,
499
+ cost_source: "recorded",
500
+ agents: ["claudecode"],
501
+ sparkline: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.042]
502
+ }
503
+ ],
504
+ projectRollup: { projects: 0, sessions: 0, tokens: 0, cost: 0 },
322
505
  recentSessions: [
323
506
  {
324
507
  reference: { agentName: "claudecode", sessionId: SAMPLE_SESSION_HEAD.id },
@@ -326,6 +509,7 @@ var SAMPLE_DASHBOARD_DATA = {
326
509
  }
327
510
  ],
328
511
  recentFileActivities: [],
512
+ modelCost: null,
329
513
  window: { from: 16999e8, to: 17000036e5, days: 1 }
330
514
  };
331
515
 
@@ -352,28 +536,40 @@ import { createRequire } from "module";
352
536
  import { createHash } from "crypto";
353
537
  import { existsSync as existsSync7, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
354
538
  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";
539
+ import { existsSync as existsSync8, readFileSync as readFileSync4, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
540
+ import { basename as basename6, dirname as dirname5, join as join9, resolve } from "path";
541
+ import {
542
+ closeSync as closeSync2,
543
+ existsSync as existsSync9,
544
+ openSync as openSync2,
545
+ readdirSync as readdirSync6,
546
+ readFileSync as readFileSync5,
547
+ readSync as readSync2,
548
+ statSync as statSync6
549
+ } from "fs";
550
+ import { join as join10, basename as basename7 } from "path";
551
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync7 } from "fs";
358
552
  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";
553
+ import { join as join11, normalize } from "path";
554
+ import { existsSync as existsSync11 } from "fs";
555
+ import { basename as basename8, join as join12 } from "path";
362
556
  import { homedir as homedir4, platform as platform3 } from "os";
363
- import { join as join12 } from "path";
557
+ import { join as join13 } from "path";
558
+ import { existsSync as existsSync12, readFileSync as readFileSync7, statSync as statSync8 } from "fs";
559
+ import { dirname as dirname6, join as join14 } from "path";
364
560
  import { availableParallelism } from "os";
365
561
  import { Worker } from "worker_threads";
366
- import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
562
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
367
563
  import { spawnSync } from "child_process";
368
564
  import * as os from "os";
369
565
  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";
566
+ import { resolve as resolve2, sep } from "path";
567
+ import { existsSync as existsSync15, rmSync as rmSync2, unlinkSync } from "fs";
568
+ import { existsSync as existsSync14 } from "fs";
569
+ import { join as join15 } from "path";
374
570
  import { homedir as homedir6 } from "os";
375
571
  import { homedir as homedir7, platform as platform4 } from "os";
376
- import { join as join14 } from "path";
572
+ import { join as join16 } from "path";
377
573
  var registrations = [];
378
574
  function registerAgent(reg) {
379
575
  registrations.push(reg);
@@ -432,9 +628,9 @@ function filteredSession(reason) {
432
628
  function getParsedSession(result) {
433
629
  return result.status === "parsed" ? result.data : null;
434
630
  }
435
- function matchesScanWindow(activityTime, options) {
436
- if (options?.from != null && activityTime < options.from) return false;
437
- if (options?.to != null && activityTime > options.to) return false;
631
+ function matchesScanWindow(activityTime2, options) {
632
+ if (options?.from != null && activityTime2 < options.from) return false;
633
+ if (options?.to != null && activityTime2 > options.to) return false;
438
634
  return true;
439
635
  }
440
636
  function readCachedMeta(meta, sessionId) {
@@ -468,6 +664,9 @@ function diffSessionSources(refs, cachedSessions, cachedMeta, options) {
468
664
  return { changedIds, removedIds };
469
665
  }
470
666
  var BaseAgent = class {
667
+ filterCachedSessions(sessions) {
668
+ return sessions;
669
+ }
471
670
  getUri(sessionId) {
472
671
  return `${this.name}://${sessionId}`;
473
672
  }
@@ -475,6 +674,14 @@ var BaseAgent = class {
475
674
  var FileSystemSessionSource = class extends BaseAgent {
476
675
  sessionMetaMap = /* @__PURE__ */ new Map();
477
676
  sourceFileStats = /* @__PURE__ */ new Map();
677
+ /**
678
+ * 变更集合扩展:当某些会话变更会影响其他会话的派生数据时
679
+ * (如 subagent 文件变更需要父会话重新聚合 token 统计),
680
+ * 子类返回需要一并重解析的会话 ID 集合。默认无关联,原样返回。
681
+ */
682
+ expandChangedSessionIds(changedIds, _refs) {
683
+ return changedIds;
684
+ }
478
685
  scan(options) {
479
686
  const sources = this.listSessionSources(options);
480
687
  const sessions = [];
@@ -572,10 +779,11 @@ var FileSystemSessionSource = class extends BaseAgent {
572
779
  * refs 未传时回退为自行枚举,供独立调用方(如测试)沿用旧行为。
573
780
  */
574
781
  incrementalScan(cachedSessions, changedIds, refs) {
782
+ const sources = refs ?? this.listSessionSources();
575
783
  const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
576
- const changedSet = new Set(changedIds);
784
+ const changedSet = new Set(this.expandChangedSessionIds(changedIds, sources));
577
785
  const currentIds = /* @__PURE__ */ new Set();
578
- for (const ref of refs ?? this.listSessionSources()) {
786
+ for (const ref of sources) {
579
787
  currentIds.add(ref.sessionId);
580
788
  if (!changedSet.has(ref.sessionId)) continue;
581
789
  const head = this.scanSessionSource(ref.sourcePath);
@@ -1527,7 +1735,7 @@ var TranscriptBuilder = class {
1527
1735
  };
1528
1736
  }
1529
1737
  };
1530
- var HEAD_INDEX_VERSION = "claudecode-head-v2";
1738
+ var HEAD_INDEX_VERSION = "claudecode-head-v4";
1531
1739
  function resolveClaudeCodeDataRoot() {
1532
1740
  return resolveHomePath("CLAUDE_CONFIG_DIR", ".claude");
1533
1741
  }
@@ -1583,6 +1791,10 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1583
1791
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1584
1792
  sessionsIndexCache = {};
1585
1793
  sessionsIndexMtime = {};
1794
+ childContextsBySource = /* @__PURE__ */ new Map();
1795
+ childContextCache = /* @__PURE__ */ new Map();
1796
+ childSessionIdByToolUseId = /* @__PURE__ */ new Map();
1797
+ childIndexReady = false;
1586
1798
  findBasePath() {
1587
1799
  return firstExisting(join5(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1588
1800
  }
@@ -1615,18 +1827,97 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1615
1827
  const indexPath = this.getSessionsIndexPath(projectDir);
1616
1828
  indexMtimes.set(projectDir, this.readFileMtimeMs(indexPath));
1617
1829
  }
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
- }));
1830
+ const projectDirSet = new Set(projectDirs);
1831
+ const allSources = this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1832
+ recursive: true
1833
+ });
1834
+ this.indexChildContexts(allSources, projectDirs);
1835
+ const indexedSources = allSources.flatMap((source) => {
1836
+ const child = this.childContextsBySource.get(source.file);
1837
+ if (!child && !projectDirSet.has(dirname2(source.file))) return [];
1838
+ return [
1839
+ {
1840
+ source,
1841
+ sessionId: child?.sessionId ?? basename3(source.file, ".jsonl"),
1842
+ child
1843
+ }
1844
+ ];
1845
+ });
1846
+ let selectedSources = indexedSources.filter(
1847
+ ({ source }) => matchesScanWindow(source.stat.mtimeMs, options)
1848
+ );
1849
+ if (options?.from != null || options?.to != null) {
1850
+ const childrenByParent = /* @__PURE__ */ new Map();
1851
+ for (const indexed of indexedSources) {
1852
+ const parentId = indexed.child?.parentSessionId;
1853
+ if (!parentId) continue;
1854
+ const children = childrenByParent.get(parentId);
1855
+ if (children) children.push(indexed);
1856
+ else childrenByParent.set(parentId, [indexed]);
1857
+ }
1858
+ const windowSelectedById = new Map(
1859
+ selectedSources.map((indexed) => [indexed.sessionId, indexed])
1860
+ );
1861
+ const connectedSelected = /* @__PURE__ */ new Map();
1862
+ const acceptedIds = /* @__PURE__ */ new Set();
1863
+ const visitingIds = /* @__PURE__ */ new Set();
1864
+ const hasSelectedParent = (sessionId) => {
1865
+ if (acceptedIds.has(sessionId)) return true;
1866
+ if (visitingIds.has(sessionId)) return false;
1867
+ const indexed = windowSelectedById.get(sessionId);
1868
+ if (!indexed) return false;
1869
+ const parentId = indexed.child?.parentSessionId;
1870
+ if (!parentId) {
1871
+ acceptedIds.add(sessionId);
1872
+ return true;
1873
+ }
1874
+ visitingIds.add(sessionId);
1875
+ const connected = hasSelectedParent(parentId);
1876
+ visitingIds.delete(sessionId);
1877
+ if (connected) acceptedIds.add(sessionId);
1878
+ return connected;
1879
+ };
1880
+ for (const indexed of selectedSources) {
1881
+ if (hasSelectedParent(indexed.sessionId)) {
1882
+ connectedSelected.set(indexed.sessionId, indexed);
1883
+ }
1884
+ }
1885
+ if (options?.includeRelatedSessions !== false) {
1886
+ const pending2 = [...connectedSelected.values()].filter(({ child }) => !child?.parentSessionId).map(({ sessionId }) => sessionId);
1887
+ while (pending2.length > 0) {
1888
+ const parentId = pending2.pop();
1889
+ for (const child of childrenByParent.get(parentId) ?? []) {
1890
+ if (connectedSelected.has(child.sessionId)) continue;
1891
+ connectedSelected.set(child.sessionId, child);
1892
+ pending2.push(child.sessionId);
1893
+ }
1894
+ }
1895
+ }
1896
+ selectedSources = [...connectedSelected.values()];
1897
+ }
1898
+ return selectedSources.map(({ source: { file, stat }, sessionId, child }) => {
1899
+ const projectDir = child?.projectDir ?? dirname2(file);
1900
+ return {
1901
+ sessionId,
1902
+ sourcePath: file,
1903
+ fingerprint: this.sourceFingerprint(
1904
+ stat,
1905
+ indexMtimes.get(projectDir) ?? null,
1906
+ child?.metaMtimeMs
1907
+ )
1908
+ };
1909
+ });
1910
+ }
1911
+ setSessionMetaMap(meta) {
1912
+ super.setSessionMetaMap(meta);
1913
+ this.childContextsBySource.clear();
1914
+ this.childSessionIdByToolUseId.clear();
1915
+ this.childIndexReady = false;
1626
1916
  }
1627
1917
  parseFileSessionHead(sourcePath) {
1628
- const projectDir = dirname2(sourcePath);
1629
- return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir));
1918
+ const child = this.getChildContext(sourcePath);
1919
+ const projectDir = child?.projectDir ?? dirname2(sourcePath);
1920
+ return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir, child));
1630
1921
  }
1631
1922
  getSessionData(sessionId) {
1632
1923
  const meta = this.sessionMetaMap.get(sessionId);
@@ -1636,12 +1927,19 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1636
1927
  if (!existsSync5(meta.sourcePath)) {
1637
1928
  throw new Error(`Session file missing: ${meta.sourcePath}`);
1638
1929
  }
1930
+ this.ensureChildIndex();
1639
1931
  const builder = new TranscriptBuilder();
1640
1932
  const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
1641
1933
  const countedUsageKeys = /* @__PURE__ */ new Set();
1642
1934
  for (const record of readJsonlFile(meta.sourcePath)) {
1643
1935
  try {
1644
- this.convertRecord(record, builder, assistantUuidToToolCalls, countedUsageKeys);
1936
+ this.convertRecord(
1937
+ record,
1938
+ builder,
1939
+ assistantUuidToToolCalls,
1940
+ countedUsageKeys,
1941
+ this.childSessionIdByToolUseId
1942
+ );
1645
1943
  } catch {
1646
1944
  }
1647
1945
  }
@@ -1652,6 +1950,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1652
1950
  title: meta.title,
1653
1951
  slug: `claudecode/${meta.id}`,
1654
1952
  directory: meta.directory,
1953
+ parent_reference: meta.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: meta.parentSessionId },
1655
1954
  version: void 0,
1656
1955
  time_created: meta.createdAt,
1657
1956
  time_updated: meta.updatedAt,
@@ -1668,25 +1967,125 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1668
1967
  return [];
1669
1968
  }
1670
1969
  }
1970
+ ensureChildIndex() {
1971
+ if (this.childIndexReady) return;
1972
+ this.basePath ??= this.findBasePath();
1973
+ if (!this.basePath) {
1974
+ this.childIndexReady = true;
1975
+ return;
1976
+ }
1977
+ const projectDirs = this.listProjectDirs();
1978
+ this.indexChildContexts(
1979
+ this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1980
+ recursive: true
1981
+ }),
1982
+ projectDirs
1983
+ );
1984
+ }
1985
+ indexChildContexts(sources, projectDirs) {
1986
+ this.childContextsBySource.clear();
1987
+ this.childSessionIdByToolUseId.clear();
1988
+ const projectDirSet = new Set(projectDirs);
1989
+ const contexts = /* @__PURE__ */ new Map();
1990
+ const knownSessionIds = /* @__PURE__ */ new Set();
1991
+ for (const source of sources) {
1992
+ const child = this.readChildContext(source.file);
1993
+ if (!child) {
1994
+ if (projectDirSet.has(dirname2(source.file))) {
1995
+ knownSessionIds.add(basename3(source.file, ".jsonl"));
1996
+ }
1997
+ continue;
1998
+ }
1999
+ contexts.set(source.file, child);
2000
+ knownSessionIds.add(child.sessionId);
2001
+ }
2002
+ for (const [sourcePath, child] of contexts) {
2003
+ const parentSessionId = child.parentSessionId && knownSessionIds.has(child.parentSessionId) ? child.parentSessionId : null;
2004
+ const normalized = parentSessionId === child.parentSessionId ? child : { ...child, parentSessionId };
2005
+ this.childContextsBySource.set(sourcePath, normalized);
2006
+ if (child.toolUseId) {
2007
+ this.childSessionIdByToolUseId.set(child.toolUseId, normalized.sessionId);
2008
+ }
2009
+ }
2010
+ this.childIndexReady = true;
2011
+ }
2012
+ getChildContext(sourcePath) {
2013
+ const cached = this.childContextsBySource.get(sourcePath);
2014
+ if (cached) return cached;
2015
+ const child = this.readChildContext(sourcePath);
2016
+ if (!child) return null;
2017
+ this.childContextsBySource.set(sourcePath, child);
2018
+ if (child.toolUseId) {
2019
+ this.childSessionIdByToolUseId.set(child.toolUseId, child.sessionId);
2020
+ }
2021
+ return child;
2022
+ }
2023
+ readChildContext(sourcePath) {
2024
+ const subagentsDir = dirname2(sourcePath);
2025
+ if (basename3(subagentsDir) !== "subagents") return null;
2026
+ const parentDir = dirname2(subagentsDir);
2027
+ const projectDir = dirname2(parentDir);
2028
+ const fileStem = basename3(sourcePath, ".jsonl");
2029
+ const metaPath = join5(subagentsDir, fileStem + ".meta.json");
2030
+ const metaMtimeMs = this.readFileMtimeMs(metaPath);
2031
+ const cached = this.childContextCache.get(sourcePath);
2032
+ if (cached?.metaMtimeMs === metaMtimeMs) return cached.context;
2033
+ let metadata = null;
2034
+ if (metaMtimeMs !== null) {
2035
+ try {
2036
+ metadata = asRecord(JSON.parse(readFileSync2(metaPath, "utf-8"))) ?? null;
2037
+ } catch {
2038
+ }
2039
+ }
2040
+ const sessionId = asString(metadata?.["agentId"])?.trim() || fileStem.replace(/^agent-/, "");
2041
+ if (!sessionId) return null;
2042
+ const parentAgentId = asString(metadata?.["parentAgentId"])?.trim();
2043
+ const candidateParentId = parentAgentId || basename3(parentDir) || null;
2044
+ const parentSessionId = candidateParentId && this.hasChildParent(sourcePath, projectDir, candidateParentId) ? candidateParentId : null;
2045
+ const name = asString(metadata?.["name"])?.trim();
2046
+ const description = asString(metadata?.["description"])?.trim();
2047
+ const context = {
2048
+ sessionId,
2049
+ projectDir,
2050
+ parentSessionId,
2051
+ explicitTitle: name || description || null,
2052
+ metaMtimeMs,
2053
+ toolUseId: asString(metadata?.["toolUseId"])?.trim() || null
2054
+ };
2055
+ this.childContextCache.set(sourcePath, { metaMtimeMs, context });
2056
+ return context;
2057
+ }
2058
+ hasChildParent(sourcePath, projectDir, parentSessionId) {
2059
+ const subagentsDir = dirname2(sourcePath);
2060
+ return [
2061
+ join5(projectDir, parentSessionId + ".jsonl"),
2062
+ join5(subagentsDir, "agent-" + parentSessionId + ".jsonl"),
2063
+ join5(subagentsDir, parentSessionId + ".jsonl")
2064
+ ].some((path2) => existsSync5(path2));
2065
+ }
1671
2066
  createFileSessionMeta(head, source) {
1672
- const projectDir = dirname2(source.file);
2067
+ const child = this.getChildContext(source.file);
2068
+ const projectDir = child?.projectDir ?? dirname2(source.file);
1673
2069
  const indexPath = this.getSessionsIndexPath(projectDir);
1674
2070
  const indexMtime = this.readFileMtimeMs(indexPath);
1675
2071
  return this.buildFileSessionMeta({
1676
2072
  head,
1677
2073
  source,
1678
- fingerprint: this.sourceFingerprint(source.stat, indexMtime),
2074
+ fingerprint: this.sourceFingerprint(source.stat, indexMtime, child?.metaMtimeMs),
1679
2075
  extras: {
1680
2076
  indexPath: indexMtime === null ? null : indexPath,
1681
2077
  indexMtimeMs: indexMtime,
1682
2078
  headIndexVersion: HEAD_INDEX_VERSION,
1683
- model: head.stats.total_tokens ? "unknown" : void 0
2079
+ model: head.stats.total_tokens ? "unknown" : void 0,
2080
+ parentSessionId: head.parent_reference?.sessionId ?? null
1684
2081
  }
1685
2082
  });
1686
2083
  }
1687
2084
  /** 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]);
2085
+ sourceFingerprint(stat, indexMtime, metaMtime) {
2086
+ const fingerprint = [HEAD_INDEX_VERSION, stat.mtimeMs, stat.size, indexMtime];
2087
+ if (metaMtime !== void 0) fingerprint.push(metaMtime);
2088
+ return JSON.stringify(fingerprint);
1690
2089
  }
1691
2090
  getSessionsIndexPath(projectDir) {
1692
2091
  return join5(projectDir, "sessions-index.json");
@@ -1716,11 +2115,11 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1716
2115
  this.sessionsIndexMtime[cacheKey] = mtime;
1717
2116
  return map;
1718
2117
  }
1719
- parseSessionHeadResult(filePath, projectDir) {
1720
- const sessionId = basename3(filePath, ".jsonl");
2118
+ parseSessionHeadResult(filePath, projectDir, child) {
2119
+ const sessionId = child?.sessionId ?? basename3(filePath, ".jsonl");
1721
2120
  const index = this.loadSessionsIndex(projectDir);
1722
2121
  const indexEntry = index.get(sessionId);
1723
- const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
2122
+ const explicitTitle = child?.explicitTitle ?? (indexEntry?.summary ? String(indexEntry.summary) : null);
1724
2123
  let createdAt = 0;
1725
2124
  let updatedAt = 0;
1726
2125
  let lineIndex = 0;
@@ -1819,6 +2218,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1819
2218
  slug: `claudecode/${sessionId}`,
1820
2219
  title,
1821
2220
  directory,
2221
+ parent_reference: child?.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: child.parentSessionId },
1822
2222
  time_created: createdAt,
1823
2223
  time_updated: updatedAt,
1824
2224
  stats: {
@@ -1851,19 +2251,25 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1851
2251
  return null;
1852
2252
  }
1853
2253
  // --- Record conversion ---
1854
- convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
2254
+ convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
1855
2255
  if (data["isMeta"] === true) return;
1856
2256
  const msgType = String(data["type"] ?? "");
1857
2257
  if (isInternalEventType(msgType)) return;
1858
2258
  if (msgType === "assistant") {
1859
- this.convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys);
2259
+ this.convertAssistantRecord(
2260
+ data,
2261
+ builder,
2262
+ assistantUuidToToolCalls,
2263
+ countedUsageKeys,
2264
+ childSessionIdByToolUseId
2265
+ );
1860
2266
  } else if (msgType === "user") {
1861
2267
  this.convertUserRecord(data, builder, assistantUuidToToolCalls);
1862
2268
  } else if (msgType === "tool_result") {
1863
2269
  this.convertToolResultRecord(data, builder);
1864
2270
  }
1865
2271
  }
1866
- convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
2272
+ convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
1867
2273
  const msg = asRecord(data["message"]) ?? {};
1868
2274
  const timestampMs = parseTimestampMs(data);
1869
2275
  const rawContent = asArray(msg["content"]) ?? [];
@@ -1903,12 +2309,14 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1903
2309
  }
1904
2310
  if (partType !== "tool_use") continue;
1905
2311
  const toolCallId = String(part["id"] ?? "").trim();
1906
- const toolPart = this.buildToolPart(part, timestampMs);
2312
+ const subagentId = childSessionIdByToolUseId.get(toolCallId);
2313
+ const toolPart2 = this.buildToolPart(part, timestampMs);
1907
2314
  const message = builder.appendToolCall(
1908
- toolPart,
1909
- { id: uuid, timestampMs, agent: "claude" },
2315
+ toolPart2,
2316
+ { id: uuid, timestampMs, agent: "claude", subagentId },
1910
2317
  { modeOnCreate: "tool" }
1911
2318
  );
2319
+ if (subagentId) message.subagent_id = subagentId;
1912
2320
  this.applyAssistantMetadata(message, data, msg, countedUsageKeys);
1913
2321
  if (toolCallId) {
1914
2322
  toolCallIds.push(toolCallId);
@@ -2231,7 +2639,7 @@ function runSchemaMigrations(db, options) {
2231
2639
  migration.migrate(db);
2232
2640
  setUserVersion(db, migration.version);
2233
2641
  });
2234
- apply();
2642
+ apply.immediate();
2235
2643
  currentVersion = migration.version;
2236
2644
  getCoreDiagnostics()?.info?.("sqlite.migration.completed", {
2237
2645
  label: options.backupLabel,
@@ -2285,10 +2693,10 @@ function openDb(dbPath) {
2285
2693
  const db = DatabaseConstructor(dbPath);
2286
2694
  restrictPrivateDatabase(dbPath);
2287
2695
  try {
2696
+ db.pragma("busy_timeout = 5000");
2288
2697
  db.pragma("journal_mode = WAL");
2289
2698
  db.pragma("synchronous = NORMAL");
2290
2699
  db.pragma("foreign_keys = ON");
2291
- db.pragma("busy_timeout = 5000");
2292
2700
  } catch {
2293
2701
  }
2294
2702
  return db;
@@ -2305,6 +2713,24 @@ function isSqliteAvailable() {
2305
2713
  return DatabaseConstructor !== null;
2306
2714
  }
2307
2715
  var MESSAGE_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool"]);
2716
+ var SESSION_ID_QUERY_CHUNK_SIZE = 500;
2717
+ function compareSessionRowsByActivityDesc(left, right) {
2718
+ const leftUpdated = Number(left.time_updated ?? left.time_created ?? 0);
2719
+ const rightUpdated = Number(right.time_updated ?? right.time_created ?? 0);
2720
+ return rightUpdated - leftUpdated || Number(right.time_created ?? 0) - Number(left.time_created ?? 0) || String(right.id ?? "").localeCompare(String(left.id ?? ""));
2721
+ }
2722
+ function accumulateTokenStats(stats, msgData, agentName) {
2723
+ const cost = Number(msgData.cost ?? 0);
2724
+ const tokens = parseTokens(msgData.tokens, agentName);
2725
+ const inputTokens = Number(tokens?.input ?? 0);
2726
+ const outputTokens = Number(tokens?.output ?? 0);
2727
+ const model = parseModel(msgData.modelID, agentName);
2728
+ const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
2729
+ if (estimatedCost !== null) stats.cost_source = "estimated";
2730
+ stats.total_cost += cost || estimatedCost || 0;
2731
+ stats.total_input_tokens += inputTokens;
2732
+ stats.total_output_tokens += outputTokens;
2733
+ }
2308
2734
  function parseJsonRecord(raw, agentName, field) {
2309
2735
  const parsed = asRecord(JSON.parse(String(raw ?? "{}")));
2310
2736
  if (parsed) return parsed;
@@ -2360,28 +2786,48 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2360
2786
  const hasMessageTable = Boolean(
2361
2787
  db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get()
2362
2788
  );
2789
+ const hasTaskType = columnExists(db, "session", "task_type");
2790
+ const hasParentId = columnExists(db, "session", "parent_id");
2791
+ const childPredicate = hasParentId ? "AND s.parent_id IS NULL" : hasTaskType ? "AND (s.task_type IS NULL OR s.task_type != 'subagent_child')" : "";
2363
2792
  let rows;
2793
+ const parentIdSelect = hasParentId ? ", s.parent_id" : "";
2364
2794
  if (hasMessageTable) {
2365
2795
  rows = db.prepare(`
2366
2796
  SELECT
2367
2797
  s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2368
- s.version, s.summary_files
2798
+ s.version, s.summary_files${parentIdSelect}
2369
2799
  FROM session s
2370
2800
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2371
- ORDER BY s.time_created DESC
2801
+ ${childPredicate}
2802
+ ORDER BY COALESCE(s.time_updated, s.time_created) DESC, s.time_created DESC, s.id DESC
2372
2803
  `).all(cutoffTime);
2373
2804
  } else {
2374
2805
  rows = db.prepare(`
2375
2806
  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
2807
+ s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data${parentIdSelect}
2377
2808
  FROM session s
2378
2809
  WHERE COALESCE(s.time_updated, s.time_created) >= ?
2379
- ORDER BY s.time_created DESC
2810
+ ${childPredicate}
2811
+ ORDER BY COALESCE(s.time_updated, s.time_created) DESC, s.time_created DESC, s.id DESC
2380
2812
  `).all(cutoffTime);
2381
2813
  }
2814
+ if (hasParentId && options?.includeRelatedSessions !== false) {
2815
+ const rootIds = rows.map((row) => String(row.id ?? "")).filter(Boolean);
2816
+ const relatedRows = this.readRelatedSessionRows(db, rootIds);
2817
+ const knownIds = new Set(rootIds);
2818
+ rows.push(...relatedRows.filter((row) => !knownIds.has(String(row.id ?? ""))));
2819
+ }
2820
+ rows.sort(compareSessionRowsByActivityDesc);
2821
+ const sessionIds = new Set(rows.map((row) => String(row.id ?? "")).filter(Boolean));
2382
2822
  const headContexts = hasMessageTable ? this.buildHeadContexts(
2383
- this.readHeadMessageRows(db, cutoffTime),
2384
- this.readHeadPartRows(db, cutoffTime)
2823
+ this.readHeadMessageRows(
2824
+ db,
2825
+ cutoffTime,
2826
+ hasParentId,
2827
+ sessionIds.size > 0 ? sessionIds : void 0
2828
+ ),
2829
+ this.readHeadPartRows(db, cutoffTime, sessionIds.size > 0 ? sessionIds : void 0),
2830
+ hasParentId
2385
2831
  ) : /* @__PURE__ */ new Map();
2386
2832
  const heads = [];
2387
2833
  options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
@@ -2423,6 +2869,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2423
2869
  slug: `${this.name}/${id}`,
2424
2870
  title: resolveSessionTitle(String(row.title ?? ""), messageTitle, null),
2425
2871
  directory: String(row.directory ?? ""),
2872
+ parent_reference: row.parent_id == null || String(row.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(row.parent_id) },
2426
2873
  time_created: timeCreated,
2427
2874
  time_updated: timeUpdated,
2428
2875
  stats: {
@@ -2434,28 +2881,103 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2434
2881
  }
2435
2882
  });
2436
2883
  }
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
2884
+ readHeadMessageRows(db, cutoffTime, withParentId, sessionIds) {
2885
+ const parentIdSelect = withParentId ? ", s.parent_id" : "";
2886
+ const ids = sessionIds ? [...sessionIds] : [];
2887
+ if (ids.length === 0) {
2888
+ return db.prepare(
2889
+ `
2890
+ SELECT m.id, m.session_id, m.data, m.time_created${parentIdSelect}
2891
+ FROM message m
2892
+ JOIN session s ON s.id = m.session_id
2893
+ WHERE COALESCE(s.time_updated, s.time_created) >= ?
2894
+ ORDER BY m.session_id, m.time_created ASC
2895
+ `
2896
+ ).all(cutoffTime);
2897
+ }
2898
+ const rows = [];
2899
+ for (let offset = 0; offset < ids.length; offset += SESSION_ID_QUERY_CHUNK_SIZE) {
2900
+ const chunk = ids.slice(offset, offset + SESSION_ID_QUERY_CHUNK_SIZE);
2901
+ rows.push(
2902
+ ...db.prepare(
2903
+ `
2904
+ SELECT m.id, m.session_id, m.data, m.time_created${parentIdSelect}
2905
+ FROM message m
2906
+ JOIN session s ON s.id = m.session_id
2907
+ WHERE s.id IN (${chunk.map(() => "?").join(",")})
2908
+ ORDER BY m.session_id, m.time_created ASC
2909
+ `
2910
+ ).all(...chunk)
2911
+ );
2912
+ }
2913
+ return rows;
2914
+ }
2915
+ readHeadPartRows(db, cutoffTime, sessionIds) {
2916
+ const ids = sessionIds ? [...sessionIds] : [];
2917
+ if (ids.length === 0) {
2918
+ return db.prepare(
2445
2919
  `
2446
- ).all(cutoffTime);
2920
+ SELECT p.message_id, p.data, p.time_created
2921
+ FROM part p
2922
+ JOIN message m ON m.id = p.message_id
2923
+ JOIN session s ON s.id = m.session_id
2924
+ WHERE COALESCE(s.time_updated, s.time_created) >= ?
2925
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2926
+ `
2927
+ ).all(cutoffTime);
2928
+ }
2929
+ const rows = [];
2930
+ for (let offset = 0; offset < ids.length; offset += SESSION_ID_QUERY_CHUNK_SIZE) {
2931
+ const chunk = ids.slice(offset, offset + SESSION_ID_QUERY_CHUNK_SIZE);
2932
+ rows.push(
2933
+ ...db.prepare(
2934
+ `
2935
+ SELECT p.message_id, p.data, p.time_created
2936
+ FROM part p
2937
+ JOIN message m ON m.id = p.message_id
2938
+ JOIN session s ON s.id = m.session_id
2939
+ WHERE s.id IN (${chunk.map(() => "?").join(",")})
2940
+ ORDER BY p.message_id, p.time_created ASC, p.id ASC
2941
+ `
2942
+ ).all(...chunk)
2943
+ );
2944
+ }
2945
+ return rows;
2447
2946
  }
2448
- readHeadPartRows(db, cutoffTime) {
2449
- return db.prepare(
2947
+ readRelatedSessionRows(db, rootIds) {
2948
+ if (rootIds.length === 0) return [];
2949
+ const rows = db.prepare(
2450
2950
  `
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
2951
+ SELECT
2952
+ s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
2953
+ s.version, s.summary_files, s.parent_id
2954
+ FROM session s
2955
+ WHERE s.parent_id IS NOT NULL
2956
+ ORDER BY s.time_created ASC
2457
2957
  `
2458
- ).all(cutoffTime);
2958
+ ).all();
2959
+ const childrenByParent = /* @__PURE__ */ new Map();
2960
+ for (const row of rows) {
2961
+ const parentId = String(row.parent_id ?? "");
2962
+ if (!parentId) continue;
2963
+ const children = childrenByParent.get(parentId);
2964
+ if (children) children.push(row);
2965
+ else childrenByParent.set(parentId, [row]);
2966
+ }
2967
+ const result = [];
2968
+ const pending2 = [...rootIds];
2969
+ const seen = /* @__PURE__ */ new Set();
2970
+ while (pending2.length > 0) {
2971
+ const parentId = pending2.pop();
2972
+ for (const row of childrenByParent.get(parentId) ?? []) {
2973
+ const id = String(row.id ?? "");
2974
+ if (!id || seen.has(id)) continue;
2975
+ seen.add(id);
2976
+ result.push(row);
2977
+ pending2.push(id);
2978
+ }
2979
+ }
2980
+ return result;
2459
2981
  }
2460
2982
  parsePartRow(partRow) {
2461
2983
  const partData = parseJsonRecord(partRow.data, this.name, "part.data");
@@ -2498,16 +3020,10 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2498
3020
  }
2499
3021
  return partsByMessage;
2500
3022
  }
2501
- buildHeadContexts(messageRows, partRows) {
3023
+ buildHeadContexts(messageRows, partRows, withParentId) {
2502
3024
  const partsByMessage = this.buildPartsByMessage(partRows);
2503
3025
  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;
3026
+ const ensureContext = (sessionId) => {
2511
3027
  let context = contexts.get(sessionId);
2512
3028
  if (!context) {
2513
3029
  context = {
@@ -2521,16 +3037,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2521
3037
  };
2522
3038
  contexts.set(sessionId, context);
2523
3039
  }
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;
3040
+ return context;
3041
+ };
3042
+ for (const row of messageRows) {
3043
+ const sessionId = String(row.session_id ?? "");
3044
+ if (!sessionId) continue;
3045
+ const msgData = parseJsonRecord(row.data, this.name, "message.data");
3046
+ if (isInternalEventType(msgData.type)) continue;
3047
+ const parentId = withParentId ? String(row.parent_id ?? "") : "";
3048
+ const isChild = parentId !== "";
3049
+ if (isChild) {
3050
+ const childContext = ensureContext(sessionId);
3051
+ const parts2 = partsByMessage.get(String(row.id ?? "")) ?? [];
3052
+ if (parts2.length > 0) {
3053
+ accumulateTokenStats(childContext.stats, msgData, this.name);
3054
+ childContext.stats.message_count += 1;
3055
+ if (!childContext.messageTitle && String(msgData.role ?? "") === "user") {
3056
+ childContext.messageTitle = firstUserMessageTitle([
3057
+ {
3058
+ id: String(row.id ?? ""),
3059
+ role: "user",
3060
+ agent: null,
3061
+ time_created: Number(row.time_created ?? 0),
3062
+ parts: parts2
3063
+ }
3064
+ ]);
3065
+ }
3066
+ }
3067
+ const parentContext = ensureContext(parentId);
3068
+ accumulateTokenStats(parentContext.stats, msgData, this.name);
3069
+ continue;
3070
+ }
3071
+ const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
3072
+ if (parts.length === 0) continue;
3073
+ const context = ensureContext(sessionId);
3074
+ accumulateTokenStats(context.stats, msgData, this.name);
2534
3075
  context.stats.message_count += 1;
2535
3076
  if (!context.messageTitle && String(msgData.role ?? "") === "user") {
2536
3077
  context.messageTitle = firstUserMessageTitle([
@@ -2551,6 +3092,29 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2551
3092
  }
2552
3093
  return contexts;
2553
3094
  }
3095
+ sumChildTokenStats(db, parentSessionId) {
3096
+ if (!columnExists(db, "session", "parent_id")) return [];
3097
+ const childRows = db.prepare("SELECT id FROM session WHERE parent_id = ?").all(parentSessionId);
3098
+ const results = [];
3099
+ for (const child of childRows) {
3100
+ const childId = String(child.id ?? "");
3101
+ if (!childId) continue;
3102
+ const msgRows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(childId);
3103
+ const stats = {
3104
+ message_count: 0,
3105
+ total_input_tokens: 0,
3106
+ total_output_tokens: 0,
3107
+ total_cost: 0
3108
+ };
3109
+ for (const row of msgRows) {
3110
+ const msgData = parseJsonRecord(row.data, this.name, "message.data");
3111
+ if (isInternalEventType(msgData.type)) continue;
3112
+ accumulateTokenStats(stats, msgData, this.name);
3113
+ }
3114
+ results.push(stats);
3115
+ }
3116
+ return results;
3117
+ }
2554
3118
  getSessionData(sessionId) {
2555
3119
  if (!this.dbPath) {
2556
3120
  this.dbPath = this.findDbPath();
@@ -2573,9 +3137,6 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2573
3137
  const timeCreated = Number(sessionRow.time_created ?? 0);
2574
3138
  const timeUpdated = Number(sessionRow.time_updated ?? timeCreated);
2575
3139
  const messages = [];
2576
- let totalCost = 0;
2577
- let totalInputTokens = 0;
2578
- let totalOutputTokens = 0;
2579
3140
  let hasEstimatedCost = false;
2580
3141
  const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
2581
3142
  const partsByMessage = this.buildPartsByMessage(this.readSessionPartRows(db, sessionId));
@@ -2611,28 +3172,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2611
3172
  firstUserMessageTitle(cleanedMessages),
2612
3173
  null
2613
3174
  );
3175
+ const stats = {
3176
+ message_count: cleanedMessages.length,
3177
+ total_input_tokens: 0,
3178
+ total_output_tokens: 0,
3179
+ total_cost: 0
3180
+ };
2614
3181
  for (const message of cleanedMessages) {
2615
- totalCost += message.cost ?? 0;
2616
- totalInputTokens += message.tokens?.input ?? 0;
2617
- totalOutputTokens += message.tokens?.output ?? 0;
3182
+ stats.total_cost += message.cost ?? 0;
3183
+ stats.total_input_tokens += message.tokens?.input ?? 0;
3184
+ stats.total_output_tokens += message.tokens?.output ?? 0;
2618
3185
  if (message.cost_source === "estimated") hasEstimatedCost = true;
2619
3186
  }
3187
+ for (const childStats of this.sumChildTokenStats(db, sessionId)) {
3188
+ stats.total_cost += childStats.total_cost;
3189
+ stats.total_input_tokens += childStats.total_input_tokens;
3190
+ stats.total_output_tokens += childStats.total_output_tokens;
3191
+ if (childStats.cost_source === "estimated") hasEstimatedCost = true;
3192
+ }
2620
3193
  return {
2621
3194
  reference: { agentName: this.name, sessionId: id },
2622
3195
  id,
2623
3196
  title,
2624
3197
  slug,
2625
3198
  directory,
3199
+ parent_reference: sessionRow.parent_id == null || String(sessionRow.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(sessionRow.parent_id) },
2626
3200
  version: asString(sessionRow.version) ?? void 0,
2627
3201
  time_created: timeCreated,
2628
3202
  time_updated: timeUpdated,
2629
3203
  summary_files: sessionRow.summary_files ?? void 0,
2630
3204
  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
3205
+ message_count: stats.message_count,
3206
+ total_input_tokens: stats.total_input_tokens,
3207
+ total_output_tokens: stats.total_output_tokens,
3208
+ total_cost: stats.total_cost,
3209
+ cost_source: stats.total_cost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
2636
3210
  },
2637
3211
  messages: cleanedMessages
2638
3212
  };
@@ -3076,7 +3650,7 @@ var KimiAgent = class extends FileSystemSessionSource {
3076
3650
  const rawArgs = function_.arguments;
3077
3651
  const normalizedArgs = normalizeToolArguments(rawArgs);
3078
3652
  const buffer = typeof rawArgs === "string" && typeof normalizedArgs !== "string" ? rawArgs : null;
3079
- const toolPart = {
3653
+ const toolPart2 = {
3080
3654
  type: "tool",
3081
3655
  tool: toolName,
3082
3656
  callID: callId,
@@ -3085,7 +3659,7 @@ var KimiAgent = class extends FileSystemSessionSource {
3085
3659
  time_created: timestampMs
3086
3660
  };
3087
3661
  builder.appendToolCall(
3088
- toolPart,
3662
+ toolPart2,
3089
3663
  { id: `wire-${seq}`, timestampMs: 0, agent: "kimi" },
3090
3664
  { markModeAsTool: true, target: "current" }
3091
3665
  );
@@ -3282,66 +3856,636 @@ var KimiAgent = class extends FileSystemSessionSource {
3282
3856
  };
3283
3857
  }
3284
3858
  };
3285
- var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
3286
- var EXEC_OUTPUT_ENVELOPE_RE = /^Script completed\nWall time [^\n]*\nOutput:\n?/;
3287
- function stripExecOutputEnvelope(text) {
3288
- return text.replace(EXEC_OUTPUT_ENVELOPE_RE, "");
3859
+ var KIMI_CODE_TOOL_TITLE_MAP = {
3860
+ Read: "read",
3861
+ Write: "write",
3862
+ Edit: "edit",
3863
+ Bash: "bash",
3864
+ TodoList: "todo",
3865
+ AskUserQuestion: "ask",
3866
+ EnterPlanMode: "plan mode",
3867
+ ExitPlanMode: "plan approved",
3868
+ ReadFile: "read",
3869
+ Glob: "glob",
3870
+ StrReplaceFile: "edit",
3871
+ Grep: "grep",
3872
+ WriteFile: "write",
3873
+ Shell: "bash"
3874
+ };
3875
+ var KIMI_CODE_IGNORED_TOOLS = /* @__PURE__ */ new Set(["SetTodoList"]);
3876
+ function resolveKimiCodeDataRoot() {
3877
+ return resolveHomePath("KIMI_CODE_HOME", ".kimi-code");
3289
3878
  }
3290
- function splitExecToolName(name) {
3291
- if (name.startsWith("mcp__")) {
3292
- const separator = name.lastIndexOf("__");
3293
- if (separator > 0 && separator + 2 < name.length) {
3294
- return { name: name.slice(separator + 2), namespace: name.slice(0, separator + 2) };
3295
- }
3296
- }
3297
- return { name };
3879
+ function mapToolTitle2(toolName) {
3880
+ return KIMI_CODE_TOOL_TITLE_MAP[toolName] ?? toolName;
3298
3881
  }
3299
- function pickExecOutputTarget(calls) {
3300
- for (let index = calls.length - 1; index >= 0; index -= 1) {
3301
- const { name } = splitExecToolName(calls[index].name);
3302
- if (name !== "apply_patch" && name !== "update_plan") return index;
3882
+ function normalizeToolArguments2(raw) {
3883
+ if (typeof raw !== "string") return raw;
3884
+ try {
3885
+ return JSON.parse(raw);
3886
+ } catch {
3887
+ return raw;
3303
3888
  }
3304
- return calls.length - 1;
3305
3889
  }
3306
- function getExecPatchText(args) {
3307
- if (typeof args === "string") return args;
3308
- if (args && typeof args === "object") {
3309
- const patch = args["patch"];
3310
- if (typeof patch === "string") return patch;
3890
+ function parseTimestamp(raw) {
3891
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
3892
+ if (typeof raw !== "string" || raw.trim() === "") return null;
3893
+ const numeric = Number(raw);
3894
+ if (Number.isFinite(numeric)) return numeric;
3895
+ const parsed = Date.parse(raw);
3896
+ return Number.isFinite(parsed) ? parsed : null;
3897
+ }
3898
+ function timestampFromRecord(record) {
3899
+ return parseTimestamp(record.time) ?? 0;
3900
+ }
3901
+ function contentText(content) {
3902
+ if (typeof content === "string") return content;
3903
+ if (!Array.isArray(content)) {
3904
+ const record = asRecord(content);
3905
+ return record ? String(record.text ?? "") : "";
3311
3906
  }
3312
- return "";
3907
+ return content.map((item) => {
3908
+ if (typeof item === "string") return item;
3909
+ const record = asRecord(item);
3910
+ return record ? String(record.text ?? "") : "";
3911
+ }).join(" ");
3313
3912
  }
3314
- function decodeExecCalls(input) {
3315
- if (typeof input !== "string" || !input.includes("tools.")) return [];
3316
- const scope = collectStringVars(input);
3317
- const calls = [];
3318
- const callRe = /tools\.([A-Za-z_$][\w$]*)\s*\(/g;
3319
- let match;
3320
- while ((match = callRe.exec(input)) !== null) {
3321
- const reader = new JsValueReader(input, callRe.lastIndex, scope);
3322
- const args = reader.parseValue();
3323
- if (args !== PARSE_FAIL) {
3324
- calls.push({ name: match[1], args });
3325
- callRe.lastIndex = reader.pos;
3913
+ function contentParts(content, timestampMs) {
3914
+ const values = Array.isArray(content) ? content : [content];
3915
+ const parts = [];
3916
+ for (const value of values) {
3917
+ if (typeof value === "string") {
3918
+ const text = cleanInternalText(value);
3919
+ if (text) parts.push({ type: "text", text, time_created: timestampMs });
3920
+ continue;
3921
+ }
3922
+ const record = asRecord(value);
3923
+ if (!record) continue;
3924
+ const type = asString(record.type) ?? "";
3925
+ if (type === "text") {
3926
+ const text = cleanInternalText(asString(record.text) ?? "");
3927
+ if (text) parts.push({ type: "text", text, time_created: timestampMs });
3928
+ continue;
3929
+ }
3930
+ if (type === "think") {
3931
+ const text = cleanInternalText(asString(record.think) ?? "");
3932
+ if (text) parts.push({ type: "reasoning", text, time_created: timestampMs });
3933
+ continue;
3934
+ }
3935
+ if (type === "plan") {
3936
+ const text = cleanInternalText(asString(record.text) ?? "");
3937
+ if (text) {
3938
+ parts.push({
3939
+ type: "plan",
3940
+ text,
3941
+ approval_status: record.approved === false ? "fail" : "success",
3942
+ time_created: timestampMs
3943
+ });
3944
+ }
3945
+ continue;
3946
+ }
3947
+ if (type === "image") {
3948
+ const source = asRecord(record.source);
3949
+ const imageUrl = asRecord(record.imageUrl);
3950
+ const url = asString(record.url) ?? asString(imageUrl?.url) ?? (source?.kind === "url" ? asString(source.url) : void 0);
3951
+ const data = asString(record.data) ?? (source?.kind === "base64" ? asString(source.data) : void 0);
3952
+ const mimeType = asString(record.mime_type) ?? asString(record.media_type) ?? (source?.kind === "base64" ? asString(source.media_type) : void 0) ?? "application/octet-stream";
3953
+ if (url) parts.push({ type: "image", url, mime_type: mimeType, time_created: timestampMs });
3954
+ else if (data) {
3955
+ parts.push({
3956
+ type: "image",
3957
+ data,
3958
+ mime_type: mimeType,
3959
+ time_created: timestampMs
3960
+ });
3961
+ }
3962
+ continue;
3963
+ }
3964
+ if (type === "image_url") {
3965
+ const imageUrl = asRecord(record.imageUrl);
3966
+ const url = asString(imageUrl?.url) ?? asString(record.url);
3967
+ if (url) parts.push({ type: "image", url, time_created: timestampMs });
3326
3968
  }
3327
3969
  }
3328
- return calls;
3970
+ return parts;
3329
3971
  }
3330
- function collectStringVars(input) {
3331
- const scope = /* @__PURE__ */ new Map();
3332
- const assignRe = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*/g;
3333
- let match;
3334
- while ((match = assignRe.exec(input)) !== null) {
3335
- const reader = new JsValueReader(input, assignRe.lastIndex, scope);
3336
- const value = reader.parseValue();
3337
- if (value !== PARSE_FAIL) {
3338
- if (typeof value === "string") scope.set(match[1], value);
3339
- assignRe.lastIndex = reader.pos;
3340
- }
3972
+ function toolOutputParts(output, timestampMs) {
3973
+ if (typeof output === "string") {
3974
+ const text2 = cleanInternalText(output);
3975
+ return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
3341
3976
  }
3342
- return scope;
3977
+ if (Array.isArray(output)) return contentParts(output, timestampMs);
3978
+ if (output == null) return [];
3979
+ const record = asRecord(output);
3980
+ if (record?.type === "text") return contentParts([record], timestampMs);
3981
+ const text = cleanInternalText(JSON.stringify(output, null, 2));
3982
+ return text ? [{ type: "text", text, time_created: timestampMs }] : [];
3343
3983
  }
3344
- var IDENT_START_RE = /[A-Za-z_$]/;
3984
+ function toolPart(toolName, callId, input, timestampMs) {
3985
+ return {
3986
+ type: "tool",
3987
+ tool: toolName,
3988
+ callID: callId,
3989
+ title: mapToolTitle2(toolName),
3990
+ state: {
3991
+ status: "running",
3992
+ ...input === void 0 ? {} : { input },
3993
+ output: null
3994
+ },
3995
+ time_created: timestampMs
3996
+ };
3997
+ }
3998
+ function toolCallParts(message, timestampMs, ignoredToolCallIds) {
3999
+ const calls = asArray(message.toolCalls) ?? [];
4000
+ const parts = [];
4001
+ for (const call of calls) {
4002
+ const callRecord = asRecord(call);
4003
+ const functionRecord = asRecord(callRecord?.function) ?? callRecord;
4004
+ const toolName = asString(functionRecord?.name)?.trim() ?? "";
4005
+ const callId = asString(callRecord?.id)?.trim() ?? "";
4006
+ if (!toolName || !callId) continue;
4007
+ if (KIMI_CODE_IGNORED_TOOLS.has(toolName)) {
4008
+ ignoredToolCallIds.add(callId);
4009
+ continue;
4010
+ }
4011
+ const rawArguments = functionRecord?.arguments ?? callRecord?.arguments;
4012
+ parts.push(toolPart(toolName, callId, normalizeToolArguments2(rawArguments), timestampMs));
4013
+ }
4014
+ return parts;
4015
+ }
4016
+ function addToolResolution(builder, callId, output, timestampMs, isError = false, note) {
4017
+ const outputParts = toolOutputParts(output, timestampMs);
4018
+ return builder.resolveToolCall(callId, {
4019
+ output: outputParts,
4020
+ status: isError ? "error" : "completed",
4021
+ ...note ? { metadata: { note: cleanInternalText(note) } } : {}
4022
+ });
4023
+ }
4024
+ function usageNumber(usage, field) {
4025
+ return asNumber(usage[field]) ?? 0;
4026
+ }
4027
+ function emptyStats() {
4028
+ return {
4029
+ message_count: 0,
4030
+ total_input_tokens: 0,
4031
+ total_output_tokens: 0,
4032
+ total_cost: 0
4033
+ };
4034
+ }
4035
+ function buildUsageTotals() {
4036
+ return {
4037
+ totalCost: 0,
4038
+ totalInputTokens: 0,
4039
+ totalOutputTokens: 0,
4040
+ totalCacheReadTokens: 0,
4041
+ totalCacheCreateTokens: 0,
4042
+ modelUsage: {}
4043
+ };
4044
+ }
4045
+ function applyUsage(builder, record, activeModel, totals) {
4046
+ const usage = asRecord(record.usage);
4047
+ if (!usage) return;
4048
+ const cacheRead = usageNumber(usage, "inputCacheRead");
4049
+ const cacheCreate = usageNumber(usage, "inputCacheCreation");
4050
+ const inputOther = usageNumber(usage, "inputOther");
4051
+ const output = usageNumber(usage, "output");
4052
+ const input = inputOther + cacheRead + cacheCreate;
4053
+ const model = asString(record.model) ?? activeModel;
4054
+ const tokens = {
4055
+ input,
4056
+ output,
4057
+ cache_read: cacheRead,
4058
+ cache_create: cacheCreate
4059
+ };
4060
+ const cost = estimateTokenCost(model, tokens);
4061
+ totals.totalInputTokens += input;
4062
+ totals.totalOutputTokens += output;
4063
+ totals.totalCacheReadTokens += cacheRead;
4064
+ totals.totalCacheCreateTokens += cacheCreate;
4065
+ if (model) totals.modelUsage[model] = (totals.modelUsage[model] ?? 0) + input + output;
4066
+ if (cost !== null) totals.totalCost += cost;
4067
+ builder.attachUsageToLatestAssistant(tokens, {
4068
+ model,
4069
+ cost: cost ?? void 0,
4070
+ costSource: cost === null ? void 0 : "estimated"
4071
+ });
4072
+ }
4073
+ function readState(stateFile) {
4074
+ try {
4075
+ return asRecord(JSON.parse(readFileSync4(stateFile, "utf-8"))) ?? null;
4076
+ } catch {
4077
+ return null;
4078
+ }
4079
+ }
4080
+ var KimiCodeAgent = class extends FileSystemSessionSource {
4081
+ name = "kimi-code";
4082
+ displayName = "Kimi-Code";
4083
+ basePath = null;
4084
+ workDirBySessionPath = /* @__PURE__ */ new Map();
4085
+ findBasePath() {
4086
+ const sessionsPath = join9(resolveKimiCodeDataRoot(), "sessions");
4087
+ return existsSync8(sessionsPath) ? sessionsPath : null;
4088
+ }
4089
+ getSessionWatchPlan() {
4090
+ const dataRoot = resolveKimiCodeDataRoot();
4091
+ return {
4092
+ status: "supported",
4093
+ targets: [
4094
+ { root: dataRoot, path: join9(dataRoot, "sessions") },
4095
+ { root: dataRoot, path: join9(dataRoot, "session_index.jsonl") }
4096
+ ]
4097
+ };
4098
+ }
4099
+ isAvailable() {
4100
+ this.basePath = this.findBasePath();
4101
+ if (!this.basePath) return false;
4102
+ try {
4103
+ return this.listSessionDirs().length > 0;
4104
+ } catch {
4105
+ return false;
4106
+ }
4107
+ }
4108
+ loadSessionIndex() {
4109
+ this.workDirBySessionPath.clear();
4110
+ if (!this.basePath) return;
4111
+ const indexPath = join9(dirname5(this.basePath), "session_index.jsonl");
4112
+ if (!existsSync8(indexPath)) return;
4113
+ for (const record of readJsonlFile(indexPath)) {
4114
+ const sessionPath = asString(record.sessionDir);
4115
+ const workDir = asString(record.workDir);
4116
+ if (!sessionPath || !workDir) continue;
4117
+ this.workDirBySessionPath.set(resolve(sessionPath), workDir);
4118
+ }
4119
+ }
4120
+ listSessionDirs() {
4121
+ if (!this.basePath) return [];
4122
+ const dirs = [];
4123
+ try {
4124
+ for (const bucket of readdirSync5(this.basePath, { withFileTypes: true })) {
4125
+ if (!bucket.isDirectory()) continue;
4126
+ const bucketPath = join9(this.basePath, bucket.name);
4127
+ for (const session of readdirSync5(bucketPath, { withFileTypes: true })) {
4128
+ if (!session.isDirectory()) continue;
4129
+ const sessionPath = join9(bucketPath, session.name);
4130
+ if (existsSync8(join9(sessionPath, "state.json")) && existsSync8(join9(sessionPath, "agents", "main", "wire.jsonl"))) {
4131
+ dirs.push(sessionPath);
4132
+ }
4133
+ }
4134
+ }
4135
+ } catch {
4136
+ return dirs;
4137
+ }
4138
+ return dirs;
4139
+ }
4140
+ resolveSessionSourceResult(sessionDir) {
4141
+ try {
4142
+ const stateFile = join9(sessionDir, "state.json");
4143
+ const wireFile = join9(sessionDir, "agents", "main", "wire.jsonl");
4144
+ if (!existsSync8(stateFile) || !existsSync8(wireFile)) return skippedSession("missing wire");
4145
+ const state = readState(stateFile);
4146
+ if (!state) return skippedSession("malformed state");
4147
+ const stateMtime = statSync5(stateFile).mtimeMs;
4148
+ const wireMtime = statSync5(wireFile).mtimeMs;
4149
+ const createdAt = parseTimestamp(state.createdAt) ?? parseTimestamp(state.created_at) ?? stateMtime;
4150
+ const updatedAt = Math.max(
4151
+ parseTimestamp(state.updatedAt) ?? parseTimestamp(state.updated_at) ?? createdAt,
4152
+ wireMtime
4153
+ );
4154
+ const custom = asRecord(state.custom);
4155
+ const workDir = asString(state.workDir) ?? asString(custom?.cwd) ?? this.workDirBySessionPath.get(resolve(sessionDir)) ?? "";
4156
+ const explicitTitle = asString(state.title) ?? asString(state.customTitle) ?? "";
4157
+ return parsedSession({
4158
+ id: basename6(sessionDir),
4159
+ sourcePath: sessionDir,
4160
+ stateFile,
4161
+ wireFile,
4162
+ workDir,
4163
+ createdAt,
4164
+ updatedAt,
4165
+ explicitTitle
4166
+ });
4167
+ } catch {
4168
+ return skippedSession("malformed session");
4169
+ }
4170
+ }
4171
+ sourceFingerprint(source) {
4172
+ return JSON.stringify([
4173
+ this.readFileMtimeMs(source.stateFile),
4174
+ this.readFileMtimeMs(source.wireFile),
4175
+ source.workDir
4176
+ ]);
4177
+ }
4178
+ listSessionSources(options) {
4179
+ if (!this.basePath) return [];
4180
+ this.loadSessionIndex();
4181
+ const refs = [];
4182
+ for (const sessionDir of this.listSessionDirs()) {
4183
+ const source = getParsedSession(this.resolveSessionSourceResult(sessionDir));
4184
+ if (!source || !matchesScanWindow(source.createdAt, options)) continue;
4185
+ refs.push({
4186
+ sessionId: source.id,
4187
+ sourcePath: source.sourcePath,
4188
+ fingerprint: this.sourceFingerprint(source)
4189
+ });
4190
+ }
4191
+ return refs;
4192
+ }
4193
+ checkForChanges(sinceTimestamp, cachedSessions) {
4194
+ const result = super.checkForChanges(sinceTimestamp, cachedSessions);
4195
+ const emptySessionIds = cachedSessions.filter((session) => !this.hasMessages(session)).map((session) => session.id);
4196
+ if (emptySessionIds.length === 0) return result;
4197
+ return {
4198
+ ...result,
4199
+ hasChanges: true,
4200
+ changedIds: [.../* @__PURE__ */ new Set([...result.changedIds ?? [], ...emptySessionIds])]
4201
+ };
4202
+ }
4203
+ filterCachedSessions(sessions) {
4204
+ return this.removeEmptyCachedSessions(sessions);
4205
+ }
4206
+ incrementalScan(cachedSessions, changedIds, refs) {
4207
+ const visibleSessions = this.removeEmptyCachedSessions(cachedSessions);
4208
+ return super.incrementalScan(visibleSessions, changedIds, refs);
4209
+ }
4210
+ hasMessages(session) {
4211
+ return session.stats.message_count > 0;
4212
+ }
4213
+ removeEmptyCachedSessions(sessions) {
4214
+ return sessions.filter((session) => {
4215
+ if (this.hasMessages(session)) return true;
4216
+ this.sessionMetaMap.delete(session.id);
4217
+ return false;
4218
+ });
4219
+ }
4220
+ scanSessionSource(sourcePath) {
4221
+ this.loadSessionIndex();
4222
+ const source = getParsedSession(this.resolveSessionSourceResult(sourcePath));
4223
+ if (!source) return null;
4224
+ const parsed = this.parseWire(source);
4225
+ const transcript = parsed.builder.finish(parsed.stats);
4226
+ if (transcript.messages.length === 0) {
4227
+ this.sessionMetaMap.delete(source.id);
4228
+ return null;
4229
+ }
4230
+ const title = resolveSessionTitle(source.explicitTitle, parsed.firstUserTitle, null);
4231
+ const meta = {
4232
+ ...source,
4233
+ title,
4234
+ sourceMtimeMs: source.createdAt,
4235
+ sourceFingerprint: this.sourceFingerprint(source)
4236
+ };
4237
+ this.sessionMetaMap.set(meta.id, meta);
4238
+ return {
4239
+ id: meta.id,
4240
+ slug: `${this.name}/${meta.id}`,
4241
+ title: meta.title,
4242
+ directory: meta.workDir,
4243
+ time_created: meta.createdAt,
4244
+ time_updated: meta.updatedAt,
4245
+ stats: transcript.stats,
4246
+ ...Object.keys(parsed.modelUsage).length > 0 ? { model_usage: parsed.modelUsage } : {}
4247
+ };
4248
+ }
4249
+ getSessionData(sessionId) {
4250
+ const meta = this.sessionMetaMap.get(sessionId);
4251
+ if (!meta) throw new Error(`Session not found: ${sessionId}`);
4252
+ const parsed = this.parseWire(meta);
4253
+ const transcript = parsed.builder.finish(parsed.stats);
4254
+ return {
4255
+ reference: { agentName: this.name, sessionId: meta.id },
4256
+ id: meta.id,
4257
+ title: meta.title,
4258
+ slug: `${this.name}/${meta.id}`,
4259
+ directory: meta.workDir,
4260
+ time_created: meta.createdAt,
4261
+ time_updated: meta.updatedAt,
4262
+ stats: transcript.stats,
4263
+ messages: transcript.messages
4264
+ };
4265
+ }
4266
+ parseWire(source) {
4267
+ const builder = new TranscriptBuilder();
4268
+ const totals = buildUsageTotals();
4269
+ const ignoredToolCallIds = /* @__PURE__ */ new Set();
4270
+ let activeModel = null;
4271
+ let activeProvider = null;
4272
+ let firstUserTitle = null;
4273
+ let sequence = 0;
4274
+ for (const record of readJsonlFile(source.wireFile)) {
4275
+ sequence += 1;
4276
+ try {
4277
+ const timestampMs = timestampFromRecord(record);
4278
+ const recordType = asString(record.type) ?? "";
4279
+ if (recordType === "llm.request") {
4280
+ activeModel = asString(record.model) ?? activeModel;
4281
+ activeProvider = asString(record.provider) ?? activeProvider;
4282
+ continue;
4283
+ }
4284
+ if (recordType === "config.update") {
4285
+ activeModel = asString(record.modelAlias) ?? activeModel;
4286
+ continue;
4287
+ }
4288
+ if (recordType === "usage.record") {
4289
+ applyUsage(builder, record, activeModel, totals);
4290
+ continue;
4291
+ }
4292
+ if (recordType === "context.append_message") {
4293
+ const message = asRecord(record.message);
4294
+ if (!message) continue;
4295
+ const role = asString(message.role) ?? "";
4296
+ const messageTimestamp = timestampMs;
4297
+ if (role === "user") {
4298
+ const text = normalizeTitleText(contentText(message.content));
4299
+ if (!firstUserTitle && text) firstUserTitle = text;
4300
+ }
4301
+ if (role === "tool") {
4302
+ const callId = asString(message.toolCallId)?.trim() ?? "";
4303
+ const output = message.content;
4304
+ if (callId && ignoredToolCallIds.has(callId)) continue;
4305
+ if (callId && addToolResolution(builder, callId, output, messageTimestamp)) continue;
4306
+ const outputParts = toolOutputParts(output, messageTimestamp);
4307
+ if (outputParts.length > 0) {
4308
+ builder.appendMessage({
4309
+ id: `wire-${sequence}`,
4310
+ role: "tool",
4311
+ timestampMs: messageTimestamp,
4312
+ parts: outputParts
4313
+ });
4314
+ }
4315
+ continue;
4316
+ }
4317
+ if (role !== "user" && role !== "assistant") continue;
4318
+ const parts = [
4319
+ ...contentParts(message.content, messageTimestamp),
4320
+ ...role === "assistant" ? toolCallParts(message, messageTimestamp, ignoredToolCallIds) : []
4321
+ ];
4322
+ if (parts.length === 0) continue;
4323
+ const allTools = parts.every((part) => part.type === "tool");
4324
+ const input = {
4325
+ id: `wire-${sequence}`,
4326
+ role,
4327
+ timestampMs: messageTimestamp,
4328
+ parts,
4329
+ ...role === "assistant" ? {
4330
+ agent: this.name,
4331
+ mode: allTools ? "tool" : void 0,
4332
+ model: activeModel,
4333
+ provider: activeProvider
4334
+ } : {}
4335
+ };
4336
+ builder.appendMessage(input);
4337
+ continue;
4338
+ }
4339
+ if (recordType === "context.append_loop_event") {
4340
+ const event = asRecord(record.event);
4341
+ if (!event) continue;
4342
+ const eventType = asString(event.type) ?? "";
4343
+ const metadata = {
4344
+ id: `wire-${sequence}`,
4345
+ timestampMs,
4346
+ agent: this.name,
4347
+ model: activeModel,
4348
+ provider: activeProvider
4349
+ };
4350
+ if (eventType === "step.begin") {
4351
+ builder.beginTurn();
4352
+ continue;
4353
+ }
4354
+ if (eventType === "content.part") {
4355
+ const part = asRecord(event.part);
4356
+ if (!part) continue;
4357
+ const parts = contentParts([part], timestampMs);
4358
+ for (const contentPart of parts) {
4359
+ if (contentPart.type === "tool") continue;
4360
+ if (contentPart.type === "image" && builder.appendToCurrentAssistant(contentPart)) {
4361
+ continue;
4362
+ }
4363
+ builder.appendAssistantPart(contentPart, metadata, { grouping: "current" });
4364
+ }
4365
+ continue;
4366
+ }
4367
+ if (eventType === "tool.call") {
4368
+ const toolName = asString(event.name)?.trim() ?? "";
4369
+ const callId = asString(event.toolCallId)?.trim() ?? "";
4370
+ if (!toolName || !callId) continue;
4371
+ if (KIMI_CODE_IGNORED_TOOLS.has(toolName)) {
4372
+ ignoredToolCallIds.add(callId);
4373
+ continue;
4374
+ }
4375
+ builder.appendToolCall(toolPart(toolName, callId, event.args, timestampMs), metadata, {
4376
+ markModeAsTool: true,
4377
+ target: "current"
4378
+ });
4379
+ continue;
4380
+ }
4381
+ if (eventType === "tool.result") {
4382
+ const callId = asString(event.toolCallId)?.trim() ?? "";
4383
+ const result = asRecord(event.result);
4384
+ if (!callId || ignoredToolCallIds.has(callId)) continue;
4385
+ const output = result?.output;
4386
+ const isError = result?.isError === true;
4387
+ const note = asString(result?.note);
4388
+ if (addToolResolution(builder, callId, output, timestampMs, isError, note)) continue;
4389
+ const outputParts = toolOutputParts(output, timestampMs);
4390
+ if (outputParts.length > 0) {
4391
+ builder.appendMessage({
4392
+ id: `wire-${sequence}`,
4393
+ role: "tool",
4394
+ timestampMs,
4395
+ parts: outputParts
4396
+ });
4397
+ }
4398
+ }
4399
+ continue;
4400
+ }
4401
+ if (recordType === "context.apply_compaction") {
4402
+ const summary = cleanInternalText(asString(record.summary) ?? "");
4403
+ if (summary) {
4404
+ builder.appendMessage({
4405
+ id: `wire-${sequence}`,
4406
+ role: "user",
4407
+ timestampMs,
4408
+ parts: [{ type: "text", text: summary, time_created: timestampMs }]
4409
+ });
4410
+ }
4411
+ }
4412
+ } catch {
4413
+ continue;
4414
+ }
4415
+ }
4416
+ const stats = {
4417
+ ...emptyStats(),
4418
+ total_input_tokens: totals.totalInputTokens,
4419
+ total_output_tokens: totals.totalOutputTokens,
4420
+ total_cost: Number(totals.totalCost.toFixed(8)),
4421
+ total_tokens: totals.totalInputTokens + totals.totalOutputTokens,
4422
+ ...totals.totalCacheReadTokens > 0 ? { total_cache_read_tokens: totals.totalCacheReadTokens } : {},
4423
+ ...totals.totalCacheCreateTokens > 0 ? { total_cache_create_tokens: totals.totalCacheCreateTokens } : {},
4424
+ ...totals.totalCost > 0 ? { cost_source: "estimated" } : {}
4425
+ };
4426
+ return { builder, stats, firstUserTitle, modelUsage: totals.modelUsage };
4427
+ }
4428
+ };
4429
+ var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
4430
+ var EXEC_OUTPUT_ENVELOPE_RE = /^Script completed\nWall time [^\n]*\nOutput:\n?/;
4431
+ function stripExecOutputEnvelope(text) {
4432
+ return text.replace(EXEC_OUTPUT_ENVELOPE_RE, "");
4433
+ }
4434
+ function splitExecToolName(name) {
4435
+ if (name.startsWith("mcp__")) {
4436
+ const separator = name.lastIndexOf("__");
4437
+ if (separator > 0 && separator + 2 < name.length) {
4438
+ return { name: name.slice(separator + 2), namespace: name.slice(0, separator + 2) };
4439
+ }
4440
+ }
4441
+ return { name };
4442
+ }
4443
+ function pickExecOutputTarget(calls) {
4444
+ for (let index = calls.length - 1; index >= 0; index -= 1) {
4445
+ const { name } = splitExecToolName(calls[index].name);
4446
+ if (name !== "apply_patch" && name !== "update_plan") return index;
4447
+ }
4448
+ return calls.length - 1;
4449
+ }
4450
+ function getExecPatchText(args) {
4451
+ if (typeof args === "string") return args;
4452
+ if (args && typeof args === "object") {
4453
+ const patch = args["patch"];
4454
+ if (typeof patch === "string") return patch;
4455
+ }
4456
+ return "";
4457
+ }
4458
+ function decodeExecCalls(input) {
4459
+ if (typeof input !== "string" || !input.includes("tools.")) return [];
4460
+ const scope = collectStringVars(input);
4461
+ const calls = [];
4462
+ const callRe = /tools\.([A-Za-z_$][\w$]*)\s*\(/g;
4463
+ let match;
4464
+ while ((match = callRe.exec(input)) !== null) {
4465
+ const reader = new JsValueReader(input, callRe.lastIndex, scope);
4466
+ const args = reader.parseValue();
4467
+ if (args !== PARSE_FAIL) {
4468
+ calls.push({ name: match[1], args });
4469
+ callRe.lastIndex = reader.pos;
4470
+ }
4471
+ }
4472
+ return calls;
4473
+ }
4474
+ function collectStringVars(input) {
4475
+ const scope = /* @__PURE__ */ new Map();
4476
+ const assignRe = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*/g;
4477
+ let match;
4478
+ while ((match = assignRe.exec(input)) !== null) {
4479
+ const reader = new JsValueReader(input, assignRe.lastIndex, scope);
4480
+ const value = reader.parseValue();
4481
+ if (value !== PARSE_FAIL) {
4482
+ if (typeof value === "string") scope.set(match[1], value);
4483
+ assignRe.lastIndex = reader.pos;
4484
+ }
4485
+ }
4486
+ return scope;
4487
+ }
4488
+ var IDENT_START_RE = /[A-Za-z_$]/;
3345
4489
  var IDENT_PART_RE = /[\w$]/;
3346
4490
  var JsValueReader = class {
3347
4491
  pos;
@@ -3543,7 +4687,7 @@ var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
3543
4687
  var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
3544
4688
  var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
3545
4689
  var HEAD_INDEX_VERSION2 = "codex-head-v1";
3546
- var PARSER_VERSION = "codex-parser-v4";
4690
+ var PARSER_VERSION = "codex-parser-v6";
3547
4691
  function resolveCodexDataRoot() {
3548
4692
  return resolveHomePath("CODEX_HOME", ".codex");
3549
4693
  }
@@ -3566,7 +4710,7 @@ var CODEX_TOOL_TITLE_MAP = {
3566
4710
  subagent: "subagent"
3567
4711
  };
3568
4712
  function extractSessionId(filename) {
3569
- const stem = basename6(filename, ".jsonl");
4713
+ const stem = basename7(filename, ".jsonl");
3570
4714
  const parts = stem.split("-");
3571
4715
  if (parts.length >= 5) {
3572
4716
  return parts.slice(-5).join("-");
@@ -3595,6 +4739,14 @@ function narrowRecordField(value, field) {
3595
4739
  function extractPayload(data) {
3596
4740
  return narrowRecordField(data["payload"], "payload") ?? {};
3597
4741
  }
4742
+ function extractThreadMeta(firstRecord) {
4743
+ if (firstRecord["type"] !== "session_meta") return null;
4744
+ const payload = extractPayload(firstRecord);
4745
+ const threadSource = asString(payload["thread_source"]) ?? "";
4746
+ const parentThreadId = asString(payload["parent_thread_id"]) ?? null;
4747
+ const agentNickname = asString(payload["agent_nickname"]) ?? null;
4748
+ return { threadSource, parentThreadId, agentNickname };
4749
+ }
3598
4750
  function extractTokenUsage(payload) {
3599
4751
  const info = narrowRecordField(payload["info"], "token_count.info");
3600
4752
  return {
@@ -3620,7 +4772,7 @@ function resolveToolIdentity(name, namespace) {
3620
4772
  metadata: { name, namespace: namespaceText }
3621
4773
  };
3622
4774
  }
3623
- function normalizeToolArguments2(raw) {
4775
+ function normalizeToolArguments3(raw) {
3624
4776
  if (typeof raw === "string") {
3625
4777
  try {
3626
4778
  return JSON.parse(raw);
@@ -3647,6 +4799,16 @@ function flattenOutputText(output) {
3647
4799
  }
3648
4800
  return "";
3649
4801
  }
4802
+ function extractAssistantOutputText(payload) {
4803
+ const content = payload["content"];
4804
+ if (Array.isArray(content)) {
4805
+ return content.map((item) => {
4806
+ const record = asRecord(item);
4807
+ return record && String(record["type"] ?? "") === "output_text" ? String(record["text"] ?? "") : "";
4808
+ }).filter(Boolean).join("\n");
4809
+ }
4810
+ return "";
4811
+ }
3650
4812
  var PATCH_BEGIN_RE = /\*\*\* Begin Patch/;
3651
4813
  var PATCH_END_RE = /\*\*\* End Patch/;
3652
4814
  var PATCH_HEADER_RE = /\*\*\*\s+(Add|Delete|Update|Move)\s+File:\s*(.+)/;
@@ -3729,6 +4891,18 @@ function extractPatchContent(lines, startIndex) {
3729
4891
  }
3730
4892
  return { text: contentLines.join("\n"), nextLineIndex: i };
3731
4893
  }
4894
+ function compareSourceActivityDesc(left, right) {
4895
+ const leftTimestamp = sourceTimestamp(left.file, left.stat.mtimeMs);
4896
+ const rightTimestamp = sourceTimestamp(right.file, right.stat.mtimeMs);
4897
+ return rightTimestamp - leftTimestamp || left.file.localeCompare(right.file);
4898
+ }
4899
+ function sourceTimestamp(filePath, fallback) {
4900
+ const match = basename7(filePath).match(/^rollout-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-/);
4901
+ if (!match) return fallback;
4902
+ const timestamp = match[1].replace(/-(\d{2})-(\d{2})$/, ":$1:$2");
4903
+ const parsed = Date.parse(timestamp);
4904
+ return Number.isFinite(parsed) ? parsed : fallback;
4905
+ }
3732
4906
  var CodexAgent = class extends SingleFileSessionSource {
3733
4907
  name = "codex";
3734
4908
  displayName = "Codex";
@@ -3736,38 +4910,143 @@ var CodexAgent = class extends SingleFileSessionSource {
3736
4910
  sessionIndexCache = /* @__PURE__ */ new Map();
3737
4911
  sessionIndexMtime;
3738
4912
  sessionIndexPath;
4913
+ subagentIndex = null;
4914
+ subagentStatsByParent = /* @__PURE__ */ new Map();
3739
4915
  // ---- BaseAgent implementation ----
3740
4916
  findBasePath() {
3741
- return firstExisting(join9(resolveCodexDataRoot(), "sessions"));
4917
+ return firstExisting(join10(resolveCodexDataRoot(), "sessions"));
3742
4918
  }
3743
4919
  getSessionWatchPlan() {
3744
4920
  const dataRoot = resolveCodexDataRoot();
3745
4921
  return {
3746
4922
  status: "supported",
3747
4923
  targets: [
3748
- { path: join9(dataRoot, "sessions") },
3749
- { path: join9(dataRoot, "session_index.jsonl") }
4924
+ { path: join10(dataRoot, "sessions") },
4925
+ { path: join10(dataRoot, "session_index.jsonl") }
3750
4926
  ]
3751
4927
  };
3752
4928
  }
3753
- isAvailable() {
3754
- this.basePath = this.findBasePath();
3755
- if (!this.basePath) return false;
3756
- return this.listRolloutFiles().length > 0;
3757
- }
3758
- listSessionSources(options) {
3759
- if (!this.basePath) return [];
3760
- this.loadSessionIndex();
3761
- return this.listRolloutFiles(options).map(({ file, stat }) => ({
3762
- sessionId: extractSessionId(file),
3763
- sourcePath: file,
3764
- fingerprint: this.sourceFingerprint(file, stat)
3765
- }));
3766
- }
4929
+ isAvailable() {
4930
+ this.basePath = this.findBasePath();
4931
+ if (!this.basePath) return false;
4932
+ return this.listRolloutFiles().length > 0;
4933
+ }
4934
+ listSessionSources(options) {
4935
+ if (!this.basePath) return [];
4936
+ this.loadSessionIndex();
4937
+ return this.listScanSources(options).map(({ file, stat }) => ({
4938
+ sessionId: extractSessionId(file),
4939
+ sourcePath: file,
4940
+ fingerprint: this.sourceFingerprint(file, stat)
4941
+ }));
4942
+ }
4943
+ setSessionMetaMap(meta) {
4944
+ super.setSessionMetaMap(meta);
4945
+ this.subagentIndex = null;
4946
+ this.subagentStatsByParent.clear();
4947
+ }
4948
+ /**
4949
+ * A changed subagent file leaves its parent's aggregated token stats stale,
4950
+ * so the parent must re-parse alongside the child.
4951
+ */
4952
+ expandChangedSessionIds(changedIds, refs) {
4953
+ if (changedIds.length === 0) return changedIds;
4954
+ const pathById = new Map((refs ?? []).map((ref) => [ref.sessionId, ref.sourcePath]));
4955
+ const expanded = new Set(changedIds);
4956
+ for (const id of changedIds) {
4957
+ const sourcePath = pathById.get(id) ?? this.sessionMetaMap.get(id)?.sourcePath;
4958
+ const threadMeta = sourcePath ? this.readThreadMeta(sourcePath) : null;
4959
+ const parentId = threadMeta ? threadMeta.threadSource === "subagent" ? threadMeta.parentThreadId : null : this.sessionMetaMap.get(id)?.parentThreadId ?? null;
4960
+ if (!parentId) continue;
4961
+ expanded.add(parentId);
4962
+ this.subagentIndex = null;
4963
+ this.subagentStatsByParent.delete(parentId);
4964
+ }
4965
+ return [...expanded];
4966
+ }
4967
+ readThreadMeta(filePath) {
4968
+ try {
4969
+ const firstLine = this.readFilePrefix(filePath).split("\n").filter((l) => l.trim())[0];
4970
+ if (!firstLine) return null;
4971
+ return extractThreadMeta(JSON.parse(firstLine));
4972
+ } catch {
4973
+ return null;
4974
+ }
4975
+ }
4976
+ parseTokenStats(filePath) {
4977
+ let totalInputTokens = 0;
4978
+ let totalOutputTokens = 0;
4979
+ let totalCacheReadTokens = 0;
4980
+ let totalCost = 0;
4981
+ let activeModel = null;
4982
+ let prevCumulativeTotal = 0;
4983
+ let prevInput = 0;
4984
+ let prevOutput = 0;
4985
+ let prevReasoning = 0;
4986
+ let prevCachedInput = 0;
4987
+ for (const line of readJsonlFileLines(filePath)) {
4988
+ try {
4989
+ const data = JSON.parse(line);
4990
+ const recordType = String(data["type"] ?? "");
4991
+ const payload = extractPayload(data);
4992
+ if (recordType === "session_meta" || recordType === "turn_context") {
4993
+ const nextModel = extractModelName(payload["model"]);
4994
+ if (nextModel) activeModel = nextModel;
4995
+ continue;
4996
+ }
4997
+ if (recordType === "event_msg" && String(payload["type"] ?? "") === "token_count") {
4998
+ const { totalUsage, lastUsage } = extractTokenUsage(payload);
4999
+ const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
5000
+ if (cumulativeTotal <= 0 || cumulativeTotal === prevCumulativeTotal) continue;
5001
+ prevCumulativeTotal = cumulativeTotal;
5002
+ let inputTokens = 0;
5003
+ let outputTokens = 0;
5004
+ let reasoningTokens = 0;
5005
+ let cacheReadTokens = 0;
5006
+ if (lastUsage) {
5007
+ inputTokens = Number(lastUsage["input_tokens"] ?? 0);
5008
+ outputTokens = Number(lastUsage["output_tokens"] ?? 0);
5009
+ reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
5010
+ cacheReadTokens = extractCachedInputTokens(lastUsage);
5011
+ } else if (totalUsage) {
5012
+ inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
5013
+ outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
5014
+ reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
5015
+ cacheReadTokens = extractCachedInputTokens(totalUsage) - prevCachedInput;
5016
+ prevInput = Number(totalUsage["input_tokens"] ?? 0);
5017
+ prevOutput = Number(totalUsage["output_tokens"] ?? 0);
5018
+ prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
5019
+ prevCachedInput = extractCachedInputTokens(totalUsage);
5020
+ }
5021
+ const totalInput = Math.max(0, inputTokens);
5022
+ const totalCacheRead = Math.max(0, cacheReadTokens);
5023
+ totalInputTokens += totalInput;
5024
+ totalOutputTokens += outputTokens + reasoningTokens;
5025
+ totalCacheReadTokens += totalCacheRead;
5026
+ totalCost += estimateTokenCost(activeModel, {
5027
+ input: totalInput,
5028
+ output: outputTokens,
5029
+ reasoning: reasoningTokens || void 0,
5030
+ cache_read: totalCacheRead || void 0
5031
+ }) ?? 0;
5032
+ }
5033
+ } catch {
5034
+ }
5035
+ }
5036
+ return {
5037
+ message_count: 0,
5038
+ total_input_tokens: totalInputTokens,
5039
+ total_output_tokens: totalOutputTokens,
5040
+ total_cache_read_tokens: totalCacheReadTokens || void 0,
5041
+ total_cost: totalCost,
5042
+ cost_source: totalCost > 0 ? "estimated" : void 0
5043
+ };
5044
+ }
3767
5045
  getSessionData(sessionId) {
3768
5046
  const meta = this.sessionMetaMap.get(sessionId);
3769
5047
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
3770
- if (!existsSync8(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5048
+ if (!existsSync9(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5049
+ this.basePath ??= this.findBasePath();
3771
5050
  const transcript = new TranscriptBuilder();
3772
5051
  let totalInputTokens = 0;
3773
5052
  let totalOutputTokens = 0;
@@ -3850,18 +5129,119 @@ var CodexAgent = class extends SingleFileSessionSource {
3850
5129
  total_cost: totalCost,
3851
5130
  cost_source: totalCost > 0 ? "estimated" : void 0
3852
5131
  });
5132
+ this.applyChildStats(result.stats, meta.id);
5133
+ const childMessages = this.collectChildMessages(meta.id);
5134
+ for (const message of childMessages) {
5135
+ const messageText = message.parts.find((part) => part.type === "text")?.text;
5136
+ const alreadyVisible = result.messages.some(
5137
+ (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)
5138
+ );
5139
+ if (!alreadyVisible) result.messages.push(message);
5140
+ }
5141
+ result.stats.message_count = result.messages.length;
3853
5142
  return {
3854
5143
  reference: { agentName: this.name, sessionId: meta.id },
3855
5144
  id: meta.id,
3856
5145
  title: meta.title,
3857
5146
  slug: `codex/${meta.id}`,
3858
5147
  directory: meta.directory,
5148
+ parent_reference: meta.parentThreadId == null ? void 0 : { agentName: this.name, sessionId: meta.parentThreadId },
3859
5149
  time_created: meta.createdAt,
3860
5150
  time_updated: meta.updatedAt,
3861
5151
  stats: result.stats,
3862
5152
  messages: result.messages
3863
5153
  };
3864
5154
  }
5155
+ /**
5156
+ * Builds the complete parent→children map in one prefix sweep. A cache miss
5157
+ * afterwards means "no children", so per-session directory rescans (the old
5158
+ * O(N²) finalization hotspot) never happen.
5159
+ */
5160
+ ensureSubagentIndex() {
5161
+ if (this.subagentIndex) return this.subagentIndex;
5162
+ this.basePath ??= this.findBasePath();
5163
+ const index = { childFilesByParent: /* @__PURE__ */ new Map(), subagentFiles: /* @__PURE__ */ new Set() };
5164
+ for (const file of this.listRolloutFilePaths()) {
5165
+ const threadMeta = this.readThreadMeta(file);
5166
+ if (threadMeta?.threadSource !== "subagent") continue;
5167
+ index.subagentFiles.add(file);
5168
+ if (!threadMeta.parentThreadId) continue;
5169
+ const files = index.childFilesByParent.get(threadMeta.parentThreadId);
5170
+ if (files) files.push(file);
5171
+ else index.childFilesByParent.set(threadMeta.parentThreadId, [file]);
5172
+ }
5173
+ this.subagentIndex = index;
5174
+ return index;
5175
+ }
5176
+ applyChildStats(target, sessionId) {
5177
+ for (const stats of this.collectChildStats(sessionId)) {
5178
+ target.total_input_tokens += stats.total_input_tokens ?? 0;
5179
+ target.total_output_tokens += stats.total_output_tokens ?? 0;
5180
+ target.total_cost += stats.total_cost ?? 0;
5181
+ if (stats.total_cache_read_tokens) {
5182
+ target.total_cache_read_tokens = (target.total_cache_read_tokens ?? 0) + stats.total_cache_read_tokens;
5183
+ }
5184
+ }
5185
+ }
5186
+ collectChildStats(parentSessionId) {
5187
+ const files = this.collectChildFiles(parentSessionId);
5188
+ if (files.length === 0) return [];
5189
+ const cached = this.subagentStatsByParent.get(parentSessionId);
5190
+ if (cached) return cached;
5191
+ const stats = files.map((file) => this.parseTokenStats(file));
5192
+ this.subagentStatsByParent.set(parentSessionId, stats);
5193
+ return stats;
5194
+ }
5195
+ collectChildFiles(parentSessionId) {
5196
+ return this.ensureSubagentIndex().childFilesByParent.get(parentSessionId) ?? [];
5197
+ }
5198
+ collectChildMessages(parentSessionId) {
5199
+ return this.collectChildFiles(parentSessionId).flatMap((file) => {
5200
+ const message = this.parseChildFinalMessage(file);
5201
+ return message ? [message] : [];
5202
+ }).sort((left, right) => left.time_created - right.time_created);
5203
+ }
5204
+ parseChildFinalMessage(filePath) {
5205
+ const sessionId = extractSessionId(filePath);
5206
+ const threadMeta = this.readThreadMeta(filePath);
5207
+ let latestOutput = null;
5208
+ let finalOutput = null;
5209
+ for (const record of readJsonlFile(filePath)) {
5210
+ try {
5211
+ const recordType = String(record["type"] ?? "");
5212
+ if (recordType !== "response_item") continue;
5213
+ const payload = extractPayload(record);
5214
+ if (String(payload["type"] ?? "") !== "message") continue;
5215
+ if (String(payload["role"] ?? "") !== "assistant") continue;
5216
+ const text = cleanInternalText(extractAssistantOutputText(payload));
5217
+ if (!text) continue;
5218
+ const candidate = {
5219
+ id: asString(payload["id"]) ?? `codex-subagent-${sessionId}`,
5220
+ text,
5221
+ timestampMs: parseTimestampMs2(record) || parseTimestampMs2(payload) || statSync6(filePath).mtimeMs,
5222
+ isFinal: String(record["phase"] ?? "") === "final_answer" || String(payload["phase"] ?? "") === "final_answer"
5223
+ };
5224
+ latestOutput = candidate;
5225
+ if (candidate.isFinal) finalOutput = candidate;
5226
+ } catch {
5227
+ }
5228
+ }
5229
+ const selected = finalOutput ?? latestOutput;
5230
+ if (!selected) return null;
5231
+ return {
5232
+ id: selected.id,
5233
+ role: "assistant",
5234
+ agent: "codex",
5235
+ time_created: selected.timestampMs,
5236
+ mode: null,
5237
+ model: null,
5238
+ provider: null,
5239
+ cost: 0,
5240
+ subagent_id: sessionId,
5241
+ nickname: threadMeta?.agentNickname ?? void 0,
5242
+ parts: [{ type: "text", text: selected.text, time_created: selected.timestampMs }]
5243
+ };
5244
+ }
3865
5245
  // ---- File listing ----
3866
5246
  listRolloutFiles(options) {
3867
5247
  if (!this.basePath) return [];
@@ -3871,6 +5251,54 @@ var CodexAgent = class extends SingleFileSessionSource {
3871
5251
  { scanWindow: options }
3872
5252
  );
3873
5253
  }
5254
+ /** Path-only listing for the subagent index: no per-file stat needed. */
5255
+ listRolloutFilePaths() {
5256
+ if (!this.basePath) return [];
5257
+ const paths = [];
5258
+ const walk = (directory) => {
5259
+ let entries;
5260
+ try {
5261
+ entries = readdirSync6(directory, { withFileTypes: true });
5262
+ } catch {
5263
+ return;
5264
+ }
5265
+ for (const entry of entries) {
5266
+ const filePath = join10(directory, entry.name);
5267
+ if (entry.isDirectory()) walk(filePath);
5268
+ else if (entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
5269
+ paths.push(filePath);
5270
+ }
5271
+ }
5272
+ };
5273
+ walk(this.basePath);
5274
+ return paths;
5275
+ }
5276
+ listScanSources(options) {
5277
+ const windowed = this.listRolloutFiles(options).sort(compareSourceActivityDesc);
5278
+ if (options?.from == null && options?.to == null) return windowed;
5279
+ const { childFilesByParent, subagentFiles } = this.ensureSubagentIndex();
5280
+ const rootFiles = windowed.filter((source) => !subagentFiles.has(source.file));
5281
+ const rootIds = new Set(rootFiles.map(({ file }) => extractSessionId(file)));
5282
+ if (rootIds.size === 0) return rootFiles;
5283
+ const selected = new Map(rootFiles.map((source) => [source.file, source]));
5284
+ const pending2 = [...rootIds];
5285
+ const seenParents = /* @__PURE__ */ new Set();
5286
+ while (pending2.length > 0) {
5287
+ const parentId = pending2.pop();
5288
+ if (seenParents.has(parentId)) continue;
5289
+ seenParents.add(parentId);
5290
+ for (const file of childFilesByParent.get(parentId) ?? []) {
5291
+ if (selected.has(file)) continue;
5292
+ try {
5293
+ selected.set(file, this.sessionSourceFile(file));
5294
+ } catch {
5295
+ continue;
5296
+ }
5297
+ pending2.push(extractSessionId(file));
5298
+ }
5299
+ }
5300
+ return [...selected.values()].sort(compareSourceActivityDesc);
5301
+ }
3874
5302
  createFileSessionMeta(head, source) {
3875
5303
  const indexPath = this.getSessionIndexPath();
3876
5304
  const indexMtime = this.sessionIndexMtime ?? null;
@@ -3883,7 +5311,8 @@ var CodexAgent = class extends SingleFileSessionSource {
3883
5311
  indexMtimeMs: indexMtime,
3884
5312
  headIndexVersion: HEAD_INDEX_VERSION2,
3885
5313
  parserVersion: PARSER_VERSION,
3886
- model: null
5314
+ model: null,
5315
+ parentThreadId: head.parent_reference?.sessionId ?? null
3887
5316
  }
3888
5317
  });
3889
5318
  }
@@ -3899,7 +5328,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3899
5328
  ]);
3900
5329
  }
3901
5330
  getSessionIndexPath() {
3902
- this.sessionIndexPath ??= join9(resolveCodexDataRoot(), "session_index.jsonl");
5331
+ this.sessionIndexPath ??= join10(resolveCodexDataRoot(), "session_index.jsonl");
3903
5332
  return this.sessionIndexPath;
3904
5333
  }
3905
5334
  // ---- Session index ----
@@ -3913,7 +5342,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3913
5342
  return;
3914
5343
  }
3915
5344
  try {
3916
- const content = readFileSync4(indexPath, "utf-8");
5345
+ const content = readFileSync5(indexPath, "utf-8");
3917
5346
  const cache = /* @__PURE__ */ new Map();
3918
5347
  for (const record of parseJsonlLines(content)) {
3919
5348
  const sid = String(record["id"] ?? "").trim();
@@ -3945,7 +5374,9 @@ var CodexAgent = class extends SingleFileSessionSource {
3945
5374
  }
3946
5375
  parseFileSessionHead(filePath, options) {
3947
5376
  this.loadSessionIndex();
3948
- return this.parseSessionHead(filePath, options);
5377
+ const head = this.parseSessionHead(filePath, options);
5378
+ if (head) this.applyChildStats(head.stats, head.id);
5379
+ return head;
3949
5380
  }
3950
5381
  parseSessionHead(filePath, options) {
3951
5382
  return getParsedSession(this.parseSessionHeadResult(filePath, options));
@@ -3956,6 +5387,7 @@ var CodexAgent = class extends SingleFileSessionSource {
3956
5387
  }
3957
5388
  const sessionId = extractSessionId(filePath);
3958
5389
  let firstPayload = {};
5390
+ let parentThreadId = null;
3959
5391
  let createdAt = 0;
3960
5392
  let lineCount = 0;
3961
5393
  let messageTitle = null;
@@ -3985,7 +5417,9 @@ var CodexAgent = class extends SingleFileSessionSource {
3985
5417
  return skippedSession("malformed first record");
3986
5418
  }
3987
5419
  firstPayload = extractPayload(firstRecord);
3988
- createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync5(filePath).mtimeMs;
5420
+ const threadMeta = extractThreadMeta(firstRecord);
5421
+ parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
5422
+ createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync6(filePath).mtimeMs;
3989
5423
  updatedAt = createdAt;
3990
5424
  }
3991
5425
  try {
@@ -4079,6 +5513,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4079
5513
  slug: `codex/${sessionId}`,
4080
5514
  title,
4081
5515
  directory,
5516
+ parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
4082
5517
  time_created: createdAt,
4083
5518
  time_updated: updatedAt,
4084
5519
  stats: {
@@ -4092,9 +5527,6 @@ var CodexAgent = class extends SingleFileSessionSource {
4092
5527
  model_usage: Object.keys(modelUsageMap).length > 0 ? modelUsageMap : void 0
4093
5528
  });
4094
5529
  }
4095
- parseFastSessionHead(filePath) {
4096
- return getParsedSession(this.parseFastSessionHeadResult(filePath));
4097
- }
4098
5530
  parseFastSessionHeadResult(filePath) {
4099
5531
  const prefix = this.readFilePrefix(filePath);
4100
5532
  const lines = prefix.split("\n").filter((l) => l.trim());
@@ -4106,8 +5538,10 @@ var CodexAgent = class extends SingleFileSessionSource {
4106
5538
  } catch {
4107
5539
  return skippedSession("malformed first record");
4108
5540
  }
5541
+ const threadMeta = extractThreadMeta(firstRecord);
5542
+ const parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
4109
5543
  const payload = extractPayload(firstRecord);
4110
- const stat = statSync5(filePath);
5544
+ const stat = statSync6(filePath);
4111
5545
  const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
4112
5546
  const indexTitle = this.getTitleForSession(sessionId);
4113
5547
  const messageTitle = this.extractTitleFromLines(lines);
@@ -4119,6 +5553,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4119
5553
  slug: `codex/${sessionId}`,
4120
5554
  title,
4121
5555
  directory,
5556
+ parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
4122
5557
  time_created: createdAt,
4123
5558
  time_updated: stat.mtimeMs,
4124
5559
  stats: {
@@ -4214,29 +5649,18 @@ var CodexAgent = class extends SingleFileSessionSource {
4214
5649
  }
4215
5650
  // ---- Assistant message ----
4216
5651
  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");
5652
+ const fullText = extractAssistantOutputText(payload);
5653
+ if (!fullText) return pendingPlan;
4230
5654
  const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
4231
5655
  if (planMatch) {
4232
5656
  const planText2 = planMatch[1].trim();
4233
- const planPart = {
5657
+ const planPart2 = {
4234
5658
  type: "plan",
4235
5659
  text: planText2,
4236
5660
  approval_status: "success",
4237
5661
  time_created: timestampMs
4238
5662
  };
4239
- pendingPlan = planPart;
5663
+ pendingPlan = planPart2;
4240
5664
  }
4241
5665
  const displayText = cleanInternalText(fullText.replace(PROPOSED_PLAN_PATTERN, ""));
4242
5666
  if (!displayText) return pendingPlan;
@@ -4341,8 +5765,8 @@ var CodexAgent = class extends SingleFileSessionSource {
4341
5765
  const name = String(payload["name"] ?? "").trim();
4342
5766
  if (!name) return;
4343
5767
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
4344
- const arguments_ = normalizeToolArguments2(payload["arguments"]);
4345
- const toolPart = {
5768
+ const arguments_ = normalizeToolArguments3(payload["arguments"]);
5769
+ const toolPart2 = {
4346
5770
  type: "tool",
4347
5771
  tool: toolIdentity.tool,
4348
5772
  callID: callId,
@@ -4356,7 +5780,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4356
5780
  time_created: timestampMs
4357
5781
  };
4358
5782
  transcript.appendToolCall(
4359
- toolPart,
5783
+ toolPart2,
4360
5784
  { id: "", timestampMs, agent: "codex", model: activeModel },
4361
5785
  { markModeAsTool: true }
4362
5786
  );
@@ -4388,7 +5812,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4388
5812
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
4389
5813
  const rawInput = payload["input"];
4390
5814
  const normalizedInput = normalizeCustomToolArguments(name, rawInput);
4391
- const toolPart = {
5815
+ const toolPart2 = {
4392
5816
  type: "tool",
4393
5817
  tool: toolIdentity.tool,
4394
5818
  callID: callId,
@@ -4402,7 +5826,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4402
5826
  time_created: timestampMs
4403
5827
  };
4404
5828
  transcript.appendToolCall(
4405
- toolPart,
5829
+ toolPart2,
4406
5830
  { id: "", timestampMs, agent: "codex", model: activeModel },
4407
5831
  { markModeAsTool: true }
4408
5832
  );
@@ -4419,7 +5843,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4419
5843
  const { name, namespace } = splitExecToolName(call.name);
4420
5844
  const toolIdentity = resolveToolIdentity(name, namespace);
4421
5845
  const arguments_ = name === "apply_patch" ? parseApplyPatchInput(getExecPatchText(call.args)) : call.args;
4422
- const toolPart = {
5846
+ const toolPart2 = {
4423
5847
  type: "tool",
4424
5848
  tool: toolIdentity.tool,
4425
5849
  callID: callId,
@@ -4433,7 +5857,7 @@ var CodexAgent = class extends SingleFileSessionSource {
4433
5857
  time_created: timestampMs
4434
5858
  };
4435
5859
  transcript.appendToolCall(
4436
- toolPart,
5860
+ toolPart2,
4437
5861
  { id: "", timestampMs, agent: "codex", model: activeModel },
4438
5862
  { markModeAsTool: true }
4439
5863
  );
@@ -4518,15 +5942,15 @@ function resolveCursorDataRoot() {
4518
5942
  if (override) return override;
4519
5943
  const currentPlatform = platform2();
4520
5944
  if (currentPlatform === "darwin") {
4521
- return firstExisting(join10(homedir3(), "Library", "Application Support", "Cursor", "User"));
5945
+ return firstExisting(join11(homedir3(), "Library", "Application Support", "Cursor", "User"));
4522
5946
  }
4523
5947
  if (currentPlatform === "linux") {
4524
- const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join10(homedir3(), ".config");
4525
- return firstExisting(join10(configRoot, "Cursor", "User"));
5948
+ const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join11(homedir3(), ".config");
5949
+ return firstExisting(join11(configRoot, "Cursor", "User"));
4526
5950
  }
4527
5951
  if (currentPlatform === "win32") {
4528
- const appData = readEnvPath("APPDATA") ?? join10(homedir3(), "AppData", "Roaming");
4529
- return firstExisting(join10(appData, "Cursor", "User"));
5952
+ const appData = readEnvPath("APPDATA") ?? join11(homedir3(), "AppData", "Roaming");
5953
+ return firstExisting(join11(appData, "Cursor", "User"));
4530
5954
  }
4531
5955
  return null;
4532
5956
  }
@@ -4655,7 +6079,7 @@ var CURSOR_TOOL_TITLE_MAP = {
4655
6079
  ripgrep_raw_search: "grep",
4656
6080
  glob_file_search: "glob"
4657
6081
  };
4658
- function mapToolTitle2(toolName) {
6082
+ function mapToolTitle3(toolName) {
4659
6083
  return CURSOR_TOOL_TITLE_MAP[toolName] ?? toolName;
4660
6084
  }
4661
6085
  function normalizeToolOutputParts2(output, timestampMs) {
@@ -4716,9 +6140,9 @@ function buildToolPart(action, timestampMs) {
4716
6140
  const toolName = action.tool ?? "unknown";
4717
6141
  return {
4718
6142
  type: "tool",
4719
- tool: mapToolTitle2(toolName),
6143
+ tool: mapToolTitle3(toolName),
4720
6144
  callID: action.type ? `${action.type}:${String(action.input?.id ?? "")}` : "",
4721
- title: `Tool: ${mapToolTitle2(toolName)}`,
6145
+ title: `Tool: ${mapToolTitle3(toolName)}`,
4722
6146
  state: buildToolState(action),
4723
6147
  time_created: timestampMs
4724
6148
  };
@@ -4764,7 +6188,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4764
6188
  if (!isSqliteAvailable()) return null;
4765
6189
  const dataPath = resolveCursorDataRoot();
4766
6190
  if (!dataPath) return null;
4767
- return join10(dataPath, "globalStorage", "state.vscdb");
6191
+ return join11(dataPath, "globalStorage", "state.vscdb");
4768
6192
  }
4769
6193
  getSessionWatchPlan() {
4770
6194
  const dataPath = resolveCursorDataRoot();
@@ -4773,9 +6197,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4773
6197
  targets: dataPath ? [
4774
6198
  {
4775
6199
  root: dataPath,
4776
- path: join10(dataPath, "globalStorage", "state.vscdb")
6200
+ path: join11(dataPath, "globalStorage", "state.vscdb")
4777
6201
  },
4778
- { root: dataPath, path: join10(dataPath, "workspaceStorage") }
6202
+ { root: dataPath, path: join11(dataPath, "workspaceStorage") }
4779
6203
  ] : []
4780
6204
  };
4781
6205
  }
@@ -4787,34 +6211,34 @@ var CursorAgent = class extends DatabaseSessionSource {
4787
6211
  const map = /* @__PURE__ */ new Map();
4788
6212
  const dataPath = resolveCursorDataRoot();
4789
6213
  if (!dataPath) return map;
4790
- const wsStoragePath = join10(dataPath, "workspaceStorage");
4791
- if (!existsSync9(wsStoragePath)) return map;
6214
+ const wsStoragePath = join11(dataPath, "workspaceStorage");
6215
+ if (!existsSync10(wsStoragePath)) return map;
4792
6216
  let entryNames;
4793
6217
  try {
4794
- entryNames = readdirSync5(wsStoragePath);
6218
+ entryNames = readdirSync7(wsStoragePath);
4795
6219
  } catch {
4796
6220
  return map;
4797
6221
  }
4798
6222
  for (const name of entryNames) {
4799
- const wsDir = join10(wsStoragePath, name);
6223
+ const wsDir = join11(wsStoragePath, name);
4800
6224
  try {
4801
- if (!statSync6(wsDir).isDirectory()) continue;
6225
+ if (!statSync7(wsDir).isDirectory()) continue;
4802
6226
  } catch {
4803
6227
  continue;
4804
6228
  }
4805
- const wsJsonPath = join10(wsDir, "workspace.json");
4806
- if (!existsSync9(wsJsonPath)) continue;
6229
+ const wsJsonPath = join11(wsDir, "workspace.json");
6230
+ if (!existsSync10(wsJsonPath)) continue;
4807
6231
  let workspacePath;
4808
6232
  try {
4809
- const data = asRecord(JSON.parse(readFileSync5(wsJsonPath, "utf-8")));
6233
+ const data = asRecord(JSON.parse(readFileSync6(wsJsonPath, "utf-8")));
4810
6234
  const uri = narrowString("workspaceJson.folder", data?.folder) ?? narrowString("workspaceJson.workspace", data?.workspace) ?? "";
4811
6235
  if (!uri) continue;
4812
6236
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
4813
6237
  } catch {
4814
6238
  continue;
4815
6239
  }
4816
- const wsDbPath = join10(wsDir, "state.vscdb");
4817
- if (!existsSync9(wsDbPath)) continue;
6240
+ const wsDbPath = join11(wsDir, "state.vscdb");
6241
+ if (!existsSync10(wsDbPath)) continue;
4818
6242
  const wsDb = openDbReadOnly(wsDbPath);
4819
6243
  if (!wsDb) continue;
4820
6244
  try {
@@ -4837,7 +6261,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4837
6261
  }
4838
6262
  isAvailable() {
4839
6263
  this.dbPath = this.findDbPath();
4840
- return this.dbPath !== null && existsSync9(this.dbPath);
6264
+ return this.dbPath !== null && existsSync10(this.dbPath);
4841
6265
  }
4842
6266
  scan(options) {
4843
6267
  if (!this.dbPath) return [];
@@ -5192,9 +6616,9 @@ var CursorAgent = class extends DatabaseSessionSource {
5192
6616
  parts.push({ type: "text", text, time_created: timestampMs });
5193
6617
  }
5194
6618
  if (bubble.toolFormerData) {
5195
- const toolPart = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
5196
- if (toolPart) {
5197
- parts.push(toolPart);
6619
+ const toolPart2 = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
6620
+ if (toolPart2) {
6621
+ parts.push(toolPart2);
5198
6622
  }
5199
6623
  }
5200
6624
  if (parts.length === 0) continue;
@@ -5225,7 +6649,7 @@ var CursorAgent = class extends DatabaseSessionSource {
5225
6649
  convertToolFormerData(toolData, timestampMs) {
5226
6650
  if (!toolData || !toolData.name) return null;
5227
6651
  const toolName = toolData.name;
5228
- const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle2(toolName);
6652
+ const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle3(toolName);
5229
6653
  const state = {
5230
6654
  status: toolData.status === "completed" ? "completed" : "running"
5231
6655
  };
@@ -5353,7 +6777,7 @@ function narrowTimestampMs(field, value) {
5353
6777
  return shaped === void 0 ? 0 : parseTimestampMs3(shaped);
5354
6778
  }
5355
6779
  function extractSessionIdFromFilename(filePath) {
5356
- const stem = basename7(filePath, ".jsonl");
6780
+ const stem = basename8(filePath, ".jsonl");
5357
6781
  const underscore = stem.indexOf("_");
5358
6782
  return underscore >= 0 ? stem.slice(underscore + 1) || stem : stem;
5359
6783
  }
@@ -5409,14 +6833,14 @@ var PiAgent = class extends SingleFileSessionSource {
5409
6833
  displayName = "Pi";
5410
6834
  basePath = null;
5411
6835
  findBasePath() {
5412
- return firstExisting(join11(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
6836
+ return firstExisting(join12(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
5413
6837
  }
5414
6838
  getSessionWatchPlan() {
5415
6839
  const dataRoot = resolvePiDataRoot();
5416
6840
  return {
5417
6841
  status: "supported",
5418
6842
  targets: [
5419
- { root: dataRoot, path: join11(dataRoot, "agent", "sessions") },
6843
+ { root: dataRoot, path: join12(dataRoot, "agent", "sessions") },
5420
6844
  { root: "data/pi", path: "data/pi" }
5421
6845
  ]
5422
6846
  };
@@ -5437,7 +6861,7 @@ var PiAgent = class extends SingleFileSessionSource {
5437
6861
  getSessionData(sessionId) {
5438
6862
  const meta = this.sessionMetaMap.get(sessionId);
5439
6863
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
5440
- if (!existsSync10(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
6864
+ if (!existsSync11(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
5441
6865
  const parsed = this.parsePiFile(meta.sourcePath);
5442
6866
  const state = this.convertEntries(parsed.pathEntries);
5443
6867
  return {
@@ -5530,7 +6954,7 @@ var PiAgent = class extends SingleFileSessionSource {
5530
6954
  const sessionId = String(header["id"] ?? extractSessionIdFromFilename(filePath)).trim();
5531
6955
  if (!sessionId) throw new Error("missing session id");
5532
6956
  const stat = this.sessionSourceFile(filePath).stat;
5533
- const directory = String(header["cwd"] ?? "").trim() || basename7(filePath, ".jsonl");
6957
+ const directory = String(header["cwd"] ?? "").trim() || basename8(filePath, ".jsonl");
5534
6958
  const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
5535
6959
  const updatedAt = pathEntries.reduce(
5536
6960
  (max, entry) => Math.max(max, getEntryTimestamp(entry)),
@@ -5673,7 +7097,7 @@ var PiAgent = class extends SingleFileSessionSource {
5673
7097
  if (type === "toolCall") {
5674
7098
  const callId = String(item["id"] ?? "").trim();
5675
7099
  const toolName = String(item["name"] ?? "").trim() || "tool";
5676
- const toolPart = {
7100
+ const toolPart2 = {
5677
7101
  type: "tool",
5678
7102
  tool: toolName,
5679
7103
  title: `Tool: ${toolName}`,
@@ -5684,7 +7108,7 @@ var PiAgent = class extends SingleFileSessionSource {
5684
7108
  input: item["arguments"] ?? {}
5685
7109
  }
5686
7110
  };
5687
- parts.push(toolPart);
7111
+ parts.push(toolPart2);
5688
7112
  }
5689
7113
  }
5690
7114
  return parts;
@@ -5739,70 +7163,790 @@ var PiAgent = class extends SingleFileSessionSource {
5739
7163
  const text = cleanInternalText(rawText);
5740
7164
  if (!text) return null;
5741
7165
  return {
5742
- id: narrowPiField("summary.id", entry["id"], asString) ?? "",
5743
- role: type === "custom_message" ? "user" : "assistant",
5744
- agent: type === "custom_message" ? void 0 : "pi",
5745
- timestampMs,
5746
- parts: [{ type: "text", text, time_created: timestampMs }]
7166
+ id: narrowPiField("summary.id", entry["id"], asString) ?? "",
7167
+ role: type === "custom_message" ? "user" : "assistant",
7168
+ agent: type === "custom_message" ? void 0 : "pi",
7169
+ timestampMs,
7170
+ parts: [{ type: "text", text, time_created: timestampMs }]
7171
+ };
7172
+ }
7173
+ normalizeUsage(raw) {
7174
+ const usage = narrowPiField("message.usage", raw, asRecord) ?? {};
7175
+ const inputTokens = narrowPiField("message.usage.input", usage["input"], asNumber) ?? 0;
7176
+ const outputTokens = narrowPiField("message.usage.output", usage["output"], asNumber) ?? 0;
7177
+ const cacheReadTokens = narrowPiField("message.usage.cacheRead", usage["cacheRead"], asNumber) ?? 0;
7178
+ const cacheCreateTokens = narrowPiField("message.usage.cacheWrite", usage["cacheWrite"], asNumber) ?? 0;
7179
+ const totalTokens = narrowPiField("message.usage.totalTokens", usage["totalTokens"], asNumber) ?? inputTokens + outputTokens + cacheReadTokens + cacheCreateTokens;
7180
+ const cost = isObject(usage["cost"]) ? Number(usage["cost"]["total"] ?? 0) : null;
7181
+ return {
7182
+ inputTokens: inputTokens + cacheReadTokens + cacheCreateTokens,
7183
+ outputTokens,
7184
+ cacheReadTokens,
7185
+ cacheCreateTokens,
7186
+ totalTokens,
7187
+ cost: cost && Number.isFinite(cost) ? cost : null,
7188
+ tokens: {
7189
+ input: inputTokens + cacheReadTokens + cacheCreateTokens,
7190
+ output: outputTokens,
7191
+ cache_read: cacheReadTokens || void 0,
7192
+ cache_create: cacheCreateTokens || void 0
7193
+ }
7194
+ };
7195
+ }
7196
+ emptyUsageResult(message) {
7197
+ return {
7198
+ message,
7199
+ totalTokens: 0,
7200
+ model: null
7201
+ };
7202
+ }
7203
+ };
7204
+ function resolveZCodeDataRoot() {
7205
+ const currentPlatform = platform3();
7206
+ if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
7207
+ return join13(homedir4(), ".zcode");
7208
+ }
7209
+ function findZCodeDbPath() {
7210
+ if (!isSqliteAvailable()) return null;
7211
+ const dataRoot = resolveZCodeDataRoot();
7212
+ if (!dataRoot) return null;
7213
+ return firstExisting(join13(dataRoot, "cli", "db", "db.sqlite"));
7214
+ }
7215
+ function getZCodeSessionWatchPlan() {
7216
+ const dataRoot = resolveZCodeDataRoot();
7217
+ return {
7218
+ status: "supported",
7219
+ targets: dataRoot ? [{ root: dataRoot, path: join13(dataRoot, "cli", "db", "db.sqlite") }] : []
7220
+ };
7221
+ }
7222
+ var ZCodeAgent = class extends OpenCodeSqliteAgent {
7223
+ constructor() {
7224
+ super({
7225
+ name: "zcode",
7226
+ displayName: "ZCode",
7227
+ findDbPath: findZCodeDbPath,
7228
+ getSessionWatchPlan: getZCodeSessionWatchPlan
7229
+ });
7230
+ }
7231
+ };
7232
+ var HEAD_INDEX_VERSION4 = "grok-head-v1";
7233
+ var PARSER_VERSION3 = "grok-parser-v1";
7234
+ var SUMMARY_FILE = "summary.json";
7235
+ var UPDATES_FILE = "updates.jsonl";
7236
+ var ACP_UPDATE_METHOD = "session/update";
7237
+ var XAI_UPDATE_METHOD = "_x.ai/session/update";
7238
+ var USD_TICKS_PER_USD = 1e10;
7239
+ var GROK_TOOL_TITLE_MAP = {
7240
+ get_command_or_subagent_output: "task",
7241
+ grep: "grep",
7242
+ list_dir: "list",
7243
+ read_file: "read",
7244
+ run_terminal_command: "bash",
7245
+ search_replace: "edit",
7246
+ todo_write: "todo",
7247
+ web_fetch: "web fetch"
7248
+ };
7249
+ function resolveGrokDataRoot() {
7250
+ return resolveHomePath("GROK_HOME", ".grok");
7251
+ }
7252
+ function safeCount(value) {
7253
+ const number = asNumber(value);
7254
+ return number !== void 0 && Number.isSafeInteger(number) && number >= 0 ? number : 0;
7255
+ }
7256
+ function addCount(total, value) {
7257
+ return Math.min(Number.MAX_SAFE_INTEGER, total + value);
7258
+ }
7259
+ function timestampFromIso(value) {
7260
+ const text = asString(value);
7261
+ if (!text) return null;
7262
+ const timestamp = Date.parse(text);
7263
+ return Number.isFinite(timestamp) ? timestamp : null;
7264
+ }
7265
+ function readJsonRecord(filePath) {
7266
+ try {
7267
+ return asRecord(JSON.parse(readFileSync7(filePath, "utf8"))) ?? null;
7268
+ } catch {
7269
+ return null;
7270
+ }
7271
+ }
7272
+ function parseSummary(source) {
7273
+ const summary = readJsonRecord(source.file);
7274
+ const info = asRecord(summary?.info);
7275
+ const id = asString(info?.id)?.trim();
7276
+ const cwd = asString(info?.cwd)?.trim();
7277
+ if (!id || !cwd) return null;
7278
+ const createdAt = timestampFromIso(summary?.created_at) ?? source.stat.mtimeMs;
7279
+ const updatedAt = timestampFromIso(summary?.last_active_at) ?? timestampFromIso(summary?.updated_at) ?? createdAt;
7280
+ const generatedTitle = asString(summary?.generated_title)?.trim();
7281
+ const sessionSummary = asString(summary?.session_summary)?.trim();
7282
+ const parentSessionId = asString(summary?.parent_session_id)?.trim() || null;
7283
+ return {
7284
+ id,
7285
+ cwd,
7286
+ title: generatedTitle || sessionSummary || null,
7287
+ createdAt,
7288
+ updatedAt,
7289
+ messageCount: safeCount(summary?.num_chat_messages),
7290
+ currentModel: asString(summary?.current_model_id)?.trim() || null,
7291
+ parentSessionId: parentSessionId === id ? null : parentSessionId
7292
+ };
7293
+ }
7294
+ function optionalFileFingerprint(filePath) {
7295
+ try {
7296
+ const stat = statSync8(filePath);
7297
+ return [stat.mtimeMs, stat.size];
7298
+ } catch {
7299
+ return null;
7300
+ }
7301
+ }
7302
+ function unpackUpdate(record, fallbackTimestampMs) {
7303
+ const method = asString(record.method) ?? ACP_UPDATE_METHOD;
7304
+ const params = asRecord(record.params) ?? record;
7305
+ const update = asRecord(params.update);
7306
+ if (!update) return null;
7307
+ const paramsMeta = asRecord(params._meta);
7308
+ const agentTimestampMs = asNumber(paramsMeta?.agentTimestampMs);
7309
+ const envelopeTimestamp = asNumber(record.timestamp);
7310
+ const timestampMs = agentTimestampMs !== void 0 && Number.isSafeInteger(agentTimestampMs) && agentTimestampMs >= 0 ? agentTimestampMs : envelopeTimestamp !== void 0 && Number.isSafeInteger(envelopeTimestamp) && envelopeTimestamp >= 0 && envelopeTimestamp <= Number.MAX_SAFE_INTEGER / 1e3 ? envelopeTimestamp * 1e3 : fallbackTimestampMs;
7311
+ return { method, params, update, timestampMs };
7312
+ }
7313
+ function isHostTurn(update) {
7314
+ return asRecord(update._meta)?.hostTurn === true;
7315
+ }
7316
+ function updateType(update) {
7317
+ return asString(update.update.sessionUpdate) ?? "";
7318
+ }
7319
+ function parseUsage(update) {
7320
+ const usage = asRecord(update.usage);
7321
+ if (!usage) return null;
7322
+ const input = safeCount(usage.inputTokens);
7323
+ const output = safeCount(usage.outputTokens);
7324
+ const reasoning = safeCount(usage.reasoningTokens);
7325
+ const cacheRead = safeCount(usage.cachedReadTokens);
7326
+ const cacheCreate = safeCount(usage.cacheCreationTokens);
7327
+ const totalTokens = safeCount(usage.totalTokens) || addCount(input, output);
7328
+ const isIncomplete = usage.usageIsIncomplete === true || usage.costIsPartial === true;
7329
+ const costTicks = safeCount(usage.costUsdTicks);
7330
+ const cost = !isIncomplete && costTicks > 0 ? costTicks / USD_TICKS_PER_USD : null;
7331
+ const modelUsage = {};
7332
+ const modelUsageRecord = asRecord(usage.modelUsage);
7333
+ if (modelUsageRecord) {
7334
+ for (const [model, rawUsage] of Object.entries(modelUsageRecord)) {
7335
+ const modelRecord = asRecord(rawUsage);
7336
+ if (!modelRecord) continue;
7337
+ const modelTotal = safeCount(modelRecord.totalTokens) || addCount(safeCount(modelRecord.inputTokens), safeCount(modelRecord.outputTokens));
7338
+ if (modelTotal > 0) modelUsage[model] = modelTotal;
7339
+ }
7340
+ }
7341
+ const models = Object.keys(modelUsage);
7342
+ return {
7343
+ tokens: {
7344
+ input,
7345
+ output,
7346
+ reasoning: reasoning || void 0,
7347
+ cache_read: cacheRead || void 0,
7348
+ cache_create: cacheCreate || void 0
7349
+ },
7350
+ totalTokens,
7351
+ cost,
7352
+ modelUsage,
7353
+ primaryModel: models.length === 1 ? models[0] : null
7354
+ };
7355
+ }
7356
+ function textFromContentBlock(value) {
7357
+ const content = asRecord(value);
7358
+ if (!content) return "";
7359
+ if (content.type === "text") return asString(content.text) ?? "";
7360
+ const resource = asRecord(content.resource);
7361
+ return asString(resource?.text) ?? "";
7362
+ }
7363
+ function messagePartFromContentBlock(value, timestampMs) {
7364
+ const content = asRecord(value);
7365
+ if (!content) return null;
7366
+ if (content.type === "text") {
7367
+ const text2 = asString(content.text) ?? "";
7368
+ return text2 ? { type: "text", text: text2, time_created: timestampMs } : null;
7369
+ }
7370
+ if (content.type === "image") {
7371
+ const data = asString(content.data);
7372
+ const url = asString(content.uri) ?? asString(content.url);
7373
+ const mimeType = asString(content.mimeType) ?? asString(content.mime_type);
7374
+ if (data && mimeType) {
7375
+ return { type: "image", data, mime_type: mimeType, url, time_created: timestampMs };
7376
+ }
7377
+ if (url) return { type: "image", url, mime_type: mimeType, time_created: timestampMs };
7378
+ }
7379
+ const text = textFromContentBlock(content);
7380
+ return text ? { type: "text", text, time_created: timestampMs } : null;
7381
+ }
7382
+ var GrokMessageCounter = class {
7383
+ firstUserText = null;
7384
+ messageCount = 0;
7385
+ activeAssistant = false;
7386
+ activeAssistantPromptId = null;
7387
+ activeUser = false;
7388
+ activeUserPromptIndex = null;
7389
+ process(envelope) {
7390
+ if (envelope.method === XAI_UPDATE_METHOD) return;
7391
+ switch (updateType(envelope)) {
7392
+ case "user_message_chunk":
7393
+ this.appendUser(envelope);
7394
+ break;
7395
+ case "agent_thought_chunk":
7396
+ case "agent_message_chunk":
7397
+ if (cleanInternalText(textFromContentBlock(envelope.update.content))) {
7398
+ this.appendAssistant(envelope);
7399
+ }
7400
+ break;
7401
+ case "tool_call":
7402
+ if (asString(envelope.update.toolCallId)?.trim()) this.appendAssistant(envelope);
7403
+ break;
7404
+ case "plan":
7405
+ if (planPart(envelope.update, envelope.timestampMs)) this.appendAssistant(envelope);
7406
+ break;
7407
+ }
7408
+ }
7409
+ appendUser(envelope) {
7410
+ if (isHostTurn(envelope.update)) return;
7411
+ const part = messagePartFromContentBlock(envelope.update.content, envelope.timestampMs);
7412
+ if (!part) return;
7413
+ if (part.type === "text" && !cleanInternalText(part.text)) return;
7414
+ const promptIndexValue = asNumber(asRecord(envelope.update._meta)?.promptIndex);
7415
+ const promptIndex = promptIndexValue !== void 0 && Number.isSafeInteger(promptIndexValue) ? promptIndexValue : null;
7416
+ const canReuse = this.activeUser && (promptIndex === null || this.activeUserPromptIndex === null || promptIndex === this.activeUserPromptIndex);
7417
+ if (!canReuse) this.messageCount += 1;
7418
+ if (this.firstUserText === null && part.type === "text") {
7419
+ this.firstUserText = cleanInternalText(part.text) || null;
7420
+ }
7421
+ this.activeUser = true;
7422
+ this.activeUserPromptIndex = promptIndex;
7423
+ this.activeAssistant = false;
7424
+ this.activeAssistantPromptId = null;
7425
+ }
7426
+ appendAssistant(envelope) {
7427
+ const promptId = asString(asRecord(envelope.params._meta)?.promptId)?.trim() || null;
7428
+ if (promptId && this.activeAssistantPromptId && promptId !== this.activeAssistantPromptId) {
7429
+ this.activeAssistant = false;
7430
+ }
7431
+ if (promptId) this.activeAssistantPromptId = promptId;
7432
+ this.activeUser = false;
7433
+ this.activeUserPromptIndex = null;
7434
+ if (!this.activeAssistant) this.messageCount += 1;
7435
+ this.activeAssistant = true;
7436
+ }
7437
+ };
7438
+ function scanCanonicalMessages(updatesPath, fallbackTimestampMs) {
7439
+ const rewindIndex = new RewindIndex();
7440
+ let recordIndex = 0;
7441
+ for (const record of readJsonlFile(updatesPath)) {
7442
+ rewindIndex.process(record, recordIndex, fallbackTimestampMs);
7443
+ recordIndex += 1;
7444
+ }
7445
+ const survivingIndexes = new Set(rewindIndex.survivingRecordIndexes);
7446
+ const counter = new GrokMessageCounter();
7447
+ recordIndex = 0;
7448
+ for (const record of readJsonlFile(updatesPath)) {
7449
+ if (survivingIndexes.has(recordIndex)) {
7450
+ const envelope = unpackUpdate(record, fallbackTimestampMs);
7451
+ if (envelope) counter.process(envelope);
7452
+ }
7453
+ recordIndex += 1;
7454
+ }
7455
+ return counter;
7456
+ }
7457
+ function scanHeadData(updatesPath, fallbackTimestampMs) {
7458
+ let totalInputTokens = 0;
7459
+ let totalOutputTokens = 0;
7460
+ let totalCacheReadTokens = 0;
7461
+ let totalCacheCreateTokens = 0;
7462
+ let totalTokens = 0;
7463
+ let totalCost = 0;
7464
+ let hasUnknownCost = false;
7465
+ let hasRewinds = false;
7466
+ const modelUsage = {};
7467
+ let messageCounter = new GrokMessageCounter();
7468
+ if (updatesPath && existsSync12(updatesPath)) {
7469
+ for (const line of readJsonlFileLines(updatesPath)) {
7470
+ const affectsMessages = line.includes('"user_message_chunk"') || line.includes('"agent_thought_chunk"') || line.includes('"agent_message_chunk"') || line.includes('"tool_call"') || line.includes('"plan"');
7471
+ const isTurnCompleted = line.includes('"turn_completed"');
7472
+ const isRewind = line.includes('"rewind_marker"');
7473
+ if (!affectsMessages && !isTurnCompleted && !isRewind) continue;
7474
+ let record;
7475
+ try {
7476
+ record = asRecord(JSON.parse(line)) ?? {};
7477
+ } catch {
7478
+ continue;
7479
+ }
7480
+ const envelope = unpackUpdate(record, fallbackTimestampMs);
7481
+ if (!envelope) continue;
7482
+ const type = updateType(envelope);
7483
+ if (affectsMessages) messageCounter.process(envelope);
7484
+ if (envelope.method === XAI_UPDATE_METHOD && type === "rewind_marker") {
7485
+ hasRewinds = true;
7486
+ }
7487
+ if (envelope.method !== XAI_UPDATE_METHOD || type !== "turn_completed") continue;
7488
+ const usage = parseUsage(envelope.update);
7489
+ if (!usage) continue;
7490
+ totalInputTokens = addCount(totalInputTokens, usage.tokens.input ?? 0);
7491
+ totalOutputTokens = addCount(totalOutputTokens, usage.tokens.output ?? 0);
7492
+ totalCacheReadTokens = addCount(totalCacheReadTokens, usage.tokens.cache_read ?? 0);
7493
+ totalCacheCreateTokens = addCount(totalCacheCreateTokens, usage.tokens.cache_create ?? 0);
7494
+ totalTokens = addCount(totalTokens, usage.totalTokens);
7495
+ if (usage.cost === null && usage.totalTokens > 0) hasUnknownCost = true;
7496
+ else totalCost += usage.cost ?? 0;
7497
+ for (const [model, tokens] of Object.entries(usage.modelUsage)) {
7498
+ modelUsage[model] = addCount(modelUsage[model] ?? 0, tokens);
7499
+ }
7500
+ }
7501
+ if (hasRewinds) messageCounter = scanCanonicalMessages(updatesPath, fallbackTimestampMs);
7502
+ }
7503
+ return {
7504
+ firstUserText: messageCounter.firstUserText,
7505
+ visibleMessageCount: messageCounter.messageCount,
7506
+ stats: {
7507
+ total_input_tokens: totalInputTokens,
7508
+ total_output_tokens: totalOutputTokens,
7509
+ total_cache_read_tokens: totalCacheReadTokens || void 0,
7510
+ total_cache_create_tokens: totalCacheCreateTokens || void 0,
7511
+ total_tokens: totalTokens || addCount(totalInputTokens, totalOutputTokens),
7512
+ total_cost: totalCost,
7513
+ cost_source: totalCost > 0 && !hasUnknownCost ? "recorded" : void 0
7514
+ },
7515
+ modelUsage
7516
+ };
7517
+ }
7518
+ function toolTitle(toolName) {
7519
+ return GROK_TOOL_TITLE_MAP[toolName] ?? toolName;
7520
+ }
7521
+ function toolStatus(value) {
7522
+ switch (asString(value)?.toLowerCase()) {
7523
+ case "completed":
7524
+ return "completed";
7525
+ case "failed":
7526
+ return "error";
7527
+ case "in_progress":
7528
+ case "pending":
7529
+ return "running";
7530
+ default:
7531
+ return null;
7532
+ }
7533
+ }
7534
+ function toolOutput(update) {
7535
+ if (update.rawOutput !== void 0 && update.rawOutput !== null) return update.rawOutput;
7536
+ const content = asArray(update.content);
7537
+ if (!content) return void 0;
7538
+ const texts = content.flatMap((item) => {
7539
+ const wrapper = asRecord(item);
7540
+ const block = wrapper?.type === "content" ? wrapper.content : item;
7541
+ const text = textFromContentBlock(block);
7542
+ return text ? [text] : [];
7543
+ });
7544
+ if (texts.length === 0) return content;
7545
+ return texts.join("\n");
7546
+ }
7547
+ function planPart(update, timestampMs) {
7548
+ const entries = asArray(update.entries)?.flatMap((value) => {
7549
+ const entry = asRecord(value);
7550
+ const content = cleanInternalText(asString(entry?.content) ?? "");
7551
+ if (!entry || !content) return [];
7552
+ return [{ content, status: asString(entry.status) ?? "pending" }];
7553
+ });
7554
+ if (!entries || entries.length === 0) return null;
7555
+ const text = entries.map(({ content, status }) => `- [${status === "completed" ? "x" : " "}] ${content}`).join("\n");
7556
+ return {
7557
+ type: "plan",
7558
+ text,
7559
+ approval_status: entries.every(({ status }) => status === "completed") ? "success" : "fail",
7560
+ time_created: timestampMs
7561
+ };
7562
+ }
7563
+ var GrokTranscriptReducer = class {
7564
+ constructor(sessionId, currentModel, fallbackTimestampMs) {
7565
+ this.sessionId = sessionId;
7566
+ this.fallbackTimestampMs = fallbackTimestampMs;
7567
+ this.currentModel = currentModel;
7568
+ }
7569
+ sessionId;
7570
+ fallbackTimestampMs;
7571
+ builder = new TranscriptBuilder();
7572
+ activeAssistant = null;
7573
+ activeAssistantPromptId = null;
7574
+ activeUser = null;
7575
+ activeUserPromptIndex = null;
7576
+ latestPlan = null;
7577
+ currentMode = null;
7578
+ currentModel;
7579
+ process(record) {
7580
+ const envelope = unpackUpdate(record, this.fallbackTimestampMs);
7581
+ if (!envelope) return;
7582
+ if (envelope.method === XAI_UPDATE_METHOD) {
7583
+ this.processXaiUpdate(envelope);
7584
+ return;
7585
+ }
7586
+ this.processAcpUpdate(envelope);
7587
+ }
7588
+ finish(stats) {
7589
+ return this.builder.finish(stats);
7590
+ }
7591
+ processAcpUpdate(envelope) {
7592
+ switch (updateType(envelope)) {
7593
+ case "user_message_chunk":
7594
+ this.appendUserChunk(envelope);
7595
+ break;
7596
+ case "agent_thought_chunk":
7597
+ this.appendAssistantText(envelope, "reasoning");
7598
+ break;
7599
+ case "agent_message_chunk":
7600
+ this.appendAssistantText(envelope, "text");
7601
+ break;
7602
+ case "tool_call":
7603
+ this.appendToolCall(envelope);
7604
+ break;
7605
+ case "tool_call_update":
7606
+ this.updateToolCall(envelope);
7607
+ break;
7608
+ case "plan":
7609
+ this.updatePlan(envelope);
7610
+ break;
7611
+ case "current_mode_update":
7612
+ this.currentMode = asString(envelope.update.currentModeId)?.trim() || null;
7613
+ break;
7614
+ }
7615
+ }
7616
+ processXaiUpdate(envelope) {
7617
+ switch (updateType(envelope)) {
7618
+ case "turn_completed": {
7619
+ const usage = parseUsage(envelope.update);
7620
+ if (!usage) return;
7621
+ this.builder.attachUsageToLatestAssistant(usage.tokens, {
7622
+ model: usage.primaryModel ?? this.currentModel,
7623
+ cost: usage.cost ?? void 0,
7624
+ costSource: usage.cost === null ? void 0 : "recorded"
7625
+ });
7626
+ return;
7627
+ }
7628
+ case "model_changed":
7629
+ this.currentModel = asString(envelope.update.model_id)?.trim() || this.currentModel;
7630
+ return;
7631
+ case "model_auto_switched":
7632
+ this.currentModel = asString(envelope.update.new_model_id)?.trim() || this.currentModel;
7633
+ return;
7634
+ case "subagent_spawned": {
7635
+ const childSessionId = asString(envelope.update.child_session_id)?.trim();
7636
+ if (childSessionId && this.activeAssistant && !this.activeAssistant.subagent_id) {
7637
+ this.activeAssistant.subagent_id = childSessionId;
7638
+ }
7639
+ }
7640
+ }
7641
+ }
7642
+ appendUserChunk(envelope) {
7643
+ if (isHostTurn(envelope.update)) return;
7644
+ const part = messagePartFromContentBlock(envelope.update.content, envelope.timestampMs);
7645
+ if (!part) return;
7646
+ const promptIndexValue = asNumber(asRecord(envelope.update._meta)?.promptIndex);
7647
+ const promptIndex = promptIndexValue !== void 0 && Number.isSafeInteger(promptIndexValue) ? promptIndexValue : null;
7648
+ const canReuse = this.activeUser !== null && (promptIndex === null || this.activeUserPromptIndex === null || promptIndex === this.activeUserPromptIndex);
7649
+ if (canReuse) {
7650
+ const tail = this.activeUser.parts.at(-1);
7651
+ if (tail?.type === "text" && part.type === "text") tail.text += part.text;
7652
+ else this.activeUser.parts.push(part);
7653
+ } else {
7654
+ const eventId = asString(asRecord(envelope.params._meta)?.eventId);
7655
+ this.activeUser = this.builder.appendMessage({
7656
+ id: eventId || `${this.sessionId}-user-${envelope.timestampMs}`,
7657
+ role: "user",
7658
+ timestampMs: envelope.timestampMs,
7659
+ parts: [part]
7660
+ });
7661
+ }
7662
+ this.activeUserPromptIndex = promptIndex;
7663
+ this.activeAssistant = null;
7664
+ this.activeAssistantPromptId = null;
7665
+ this.latestPlan = null;
7666
+ }
7667
+ prepareAssistant(envelope) {
7668
+ const promptId = asString(asRecord(envelope.params._meta)?.promptId)?.trim() || null;
7669
+ if (promptId && this.activeAssistantPromptId && promptId !== this.activeAssistantPromptId) {
7670
+ this.builder.beginTurn();
7671
+ this.activeAssistant = null;
7672
+ this.latestPlan = null;
7673
+ }
7674
+ if (promptId) this.activeAssistantPromptId = promptId;
7675
+ this.activeUser = null;
7676
+ this.activeUserPromptIndex = null;
7677
+ }
7678
+ assistantInput(envelope) {
7679
+ const paramsMeta = asRecord(envelope.params._meta);
7680
+ return {
7681
+ id: asString(paramsMeta?.promptId) || asString(paramsMeta?.eventId) || `${this.sessionId}-assistant-${envelope.timestampMs}`,
7682
+ timestampMs: envelope.timestampMs,
7683
+ agent: "grok",
7684
+ mode: this.currentMode,
7685
+ model: this.currentModel,
7686
+ provider: "xai"
7687
+ };
7688
+ }
7689
+ appendAssistantText(envelope, type) {
7690
+ const text = textFromContentBlock(envelope.update.content);
7691
+ if (!text) return;
7692
+ this.prepareAssistant(envelope);
7693
+ const tail = this.activeAssistant?.parts.at(-1);
7694
+ if (tail?.type === type) {
7695
+ tail.text += text;
7696
+ return;
7697
+ }
7698
+ this.activeAssistant = this.builder.appendAssistantPart(
7699
+ { type, text, time_created: envelope.timestampMs },
7700
+ this.assistantInput(envelope),
7701
+ { grouping: "current" }
7702
+ );
7703
+ }
7704
+ appendToolCall(envelope) {
7705
+ const callId = asString(envelope.update.toolCallId)?.trim();
7706
+ const updateMeta = asRecord(envelope.update._meta);
7707
+ const nativeTool = asRecord(updateMeta?.["x.ai/tool"]);
7708
+ const name = asString(nativeTool?.name)?.trim() || asString(envelope.update.title)?.trim() || "tool";
7709
+ if (!callId) return;
7710
+ this.prepareAssistant(envelope);
7711
+ const metadata = {
7712
+ kind: asString(nativeTool?.kind) ?? asString(envelope.update.kind),
7713
+ locations: envelope.update.locations
7714
+ };
7715
+ const part = {
7716
+ type: "tool",
7717
+ tool: name,
7718
+ title: toolTitle(name),
7719
+ callID: callId,
7720
+ state: {
7721
+ status: toolStatus(envelope.update.status) ?? "running",
7722
+ input: envelope.update.rawInput,
7723
+ output: null,
7724
+ metadata
7725
+ },
7726
+ time_created: envelope.timestampMs
7727
+ };
7728
+ this.activeAssistant = this.builder.appendToolCall(part, this.assistantInput(envelope), {
7729
+ markModeAsTool: true,
7730
+ target: "current"
7731
+ });
7732
+ }
7733
+ updateToolCall(envelope) {
7734
+ const callId = asString(envelope.update.toolCallId)?.trim();
7735
+ if (!callId) return;
7736
+ this.builder.updateToolCall(callId, (part) => {
7737
+ const status = toolStatus(envelope.update.status);
7738
+ if (status) part.state.status = status;
7739
+ if (envelope.update.rawInput !== void 0) part.state.input = envelope.update.rawInput;
7740
+ const output = toolOutput(envelope.update);
7741
+ if (output !== void 0) {
7742
+ if (status === "error") part.state.error = output;
7743
+ else part.state.output = output;
7744
+ }
7745
+ const metadata = asRecord(part.state.metadata) ?? {};
7746
+ if (envelope.update.kind !== void 0) metadata.kind = envelope.update.kind;
7747
+ if (envelope.update.locations !== void 0) metadata.locations = envelope.update.locations;
7748
+ part.state.metadata = metadata;
7749
+ });
7750
+ }
7751
+ updatePlan(envelope) {
7752
+ const part = planPart(envelope.update, envelope.timestampMs);
7753
+ if (!part) return;
7754
+ this.prepareAssistant(envelope);
7755
+ if (this.latestPlan) {
7756
+ this.latestPlan.text = part.text;
7757
+ this.latestPlan.approval_status = part.approval_status;
7758
+ this.latestPlan.time_created = part.time_created;
7759
+ return;
7760
+ }
7761
+ this.latestPlan = part;
7762
+ this.activeAssistant = this.builder.appendAssistantPart(part, this.assistantInput(envelope), {
7763
+ grouping: "current"
7764
+ });
7765
+ }
7766
+ };
7767
+ var UserRunTracker = class {
7768
+ hasSeenPromptIndex = false;
7769
+ isInUserRun = false;
7770
+ currentPromptIndex = null;
7771
+ onUser(promptIndex) {
7772
+ if (promptIndex !== null) this.hasSeenPromptIndex = true;
7773
+ const isCounted = this.hasSeenPromptIndex ? promptIndex !== null : true;
7774
+ const isNewRun = !this.isInUserRun || (this.hasSeenPromptIndex || promptIndex !== null) && promptIndex !== this.currentPromptIndex;
7775
+ this.isInUserRun = true;
7776
+ if (!isNewRun) return false;
7777
+ this.currentPromptIndex = promptIndex;
7778
+ return isCounted;
7779
+ }
7780
+ onNonUser() {
7781
+ this.isInUserRun = false;
7782
+ this.currentPromptIndex = null;
7783
+ }
7784
+ };
7785
+ var RewindIndex = class {
7786
+ survivingRecordIndexes = [];
7787
+ hasRewinds = false;
7788
+ promptStarts = [];
7789
+ userRuns = new UserRunTracker();
7790
+ process(record, recordIndex, fallbackTimestampMs) {
7791
+ const envelope = unpackUpdate(record, fallbackTimestampMs);
7792
+ if (!envelope) {
7793
+ this.userRuns.onNonUser();
7794
+ this.survivingRecordIndexes.push(recordIndex);
7795
+ return;
7796
+ }
7797
+ const type = updateType(envelope);
7798
+ if (envelope.method === XAI_UPDATE_METHOD && type === "rewind_marker") {
7799
+ const target = asNumber(envelope.update.target_prompt_index);
7800
+ if (target === void 0 || !Number.isSafeInteger(target) || target < 0) {
7801
+ this.userRuns.onNonUser();
7802
+ this.survivingRecordIndexes.push(recordIndex);
7803
+ return;
7804
+ }
7805
+ const truncateAt = this.promptStarts[target] ?? this.survivingRecordIndexes.length;
7806
+ this.survivingRecordIndexes.length = truncateAt;
7807
+ this.promptStarts.length = Math.min(this.promptStarts.length, target);
7808
+ this.userRuns.onNonUser();
7809
+ this.hasRewinds = true;
7810
+ return;
7811
+ }
7812
+ if (envelope.method !== XAI_UPDATE_METHOD && type === "user_message_chunk" && !isHostTurn(envelope.update)) {
7813
+ const rawPromptIndex = asNumber(asRecord(envelope.update._meta)?.promptIndex);
7814
+ const promptIndex = rawPromptIndex !== void 0 && Number.isSafeInteger(rawPromptIndex) && rawPromptIndex >= 0 ? rawPromptIndex : null;
7815
+ if (this.userRuns.onUser(promptIndex)) {
7816
+ this.promptStarts.push(this.survivingRecordIndexes.length);
7817
+ }
7818
+ } else {
7819
+ this.userRuns.onNonUser();
7820
+ }
7821
+ this.survivingRecordIndexes.push(recordIndex);
7822
+ }
7823
+ };
7824
+ function parseTranscript(meta) {
7825
+ const reducer = new GrokTranscriptReducer(meta.id, meta.currentModel, meta.createdAt);
7826
+ if (!meta.updatesPath || !existsSync12(meta.updatesPath)) return reducer.finish(meta.stats);
7827
+ const rewindIndex = new RewindIndex();
7828
+ let recordIndex = 0;
7829
+ for (const record of readJsonlFile(meta.updatesPath)) {
7830
+ rewindIndex.process(record, recordIndex, meta.createdAt);
7831
+ reducer.process(record);
7832
+ recordIndex += 1;
7833
+ }
7834
+ if (!rewindIndex.hasRewinds) return reducer.finish(meta.stats);
7835
+ const survivingIndexes = new Set(rewindIndex.survivingRecordIndexes);
7836
+ const replay = new GrokTranscriptReducer(meta.id, meta.currentModel, meta.createdAt);
7837
+ recordIndex = 0;
7838
+ for (const record of readJsonlFile(meta.updatesPath)) {
7839
+ if (survivingIndexes.has(recordIndex)) replay.process(record);
7840
+ recordIndex += 1;
7841
+ }
7842
+ return replay.finish(meta.stats);
7843
+ }
7844
+ var GrokAgent = class extends FileSystemSessionSource {
7845
+ name = "grok";
7846
+ displayName = "Grok";
7847
+ basePath = null;
7848
+ findBasePath() {
7849
+ return firstExisting(join14(resolveGrokDataRoot(), "sessions"), "data/grok");
7850
+ }
7851
+ isAvailable() {
7852
+ this.basePath = this.findBasePath();
7853
+ return this.basePath !== null && this.listSessionSources().length > 0;
7854
+ }
7855
+ getSessionWatchPlan() {
7856
+ const dataRoot = resolveGrokDataRoot();
7857
+ return {
7858
+ status: "supported",
7859
+ targets: [{ root: dataRoot, path: join14(dataRoot, "sessions") }, { path: "data/grok" }]
5747
7860
  };
5748
7861
  }
5749
- normalizeUsage(raw) {
5750
- const usage = narrowPiField("message.usage", raw, asRecord) ?? {};
5751
- const inputTokens = narrowPiField("message.usage.input", usage["input"], asNumber) ?? 0;
5752
- const outputTokens = narrowPiField("message.usage.output", usage["output"], asNumber) ?? 0;
5753
- const cacheReadTokens = narrowPiField("message.usage.cacheRead", usage["cacheRead"], asNumber) ?? 0;
5754
- const cacheCreateTokens = narrowPiField("message.usage.cacheWrite", usage["cacheWrite"], asNumber) ?? 0;
5755
- const totalTokens = narrowPiField("message.usage.totalTokens", usage["totalTokens"], asNumber) ?? inputTokens + outputTokens + cacheReadTokens + cacheCreateTokens;
5756
- const cost = isObject(usage["cost"]) ? Number(usage["cost"]["total"] ?? 0) : null;
5757
- return {
5758
- inputTokens: inputTokens + cacheReadTokens + cacheCreateTokens,
5759
- outputTokens,
5760
- cacheReadTokens,
5761
- cacheCreateTokens,
5762
- totalTokens,
5763
- cost: cost && Number.isFinite(cost) ? cost : null,
5764
- tokens: {
5765
- input: inputTokens + cacheReadTokens + cacheCreateTokens,
5766
- output: outputTokens,
5767
- cache_read: cacheReadTokens || void 0,
5768
- cache_create: cacheCreateTokens || void 0
5769
- }
7862
+ listSessionSources(options) {
7863
+ this.basePath ??= this.findBasePath();
7864
+ if (!this.basePath) return [];
7865
+ const sources = this.walkFiles(this.basePath, (entry) => entry.name === SUMMARY_FILE);
7866
+ return sources.flatMap((source) => {
7867
+ const summary = parseSummary(source);
7868
+ if (!summary || !matchesScanWindow(summary.updatedAt, options)) return [];
7869
+ return [
7870
+ {
7871
+ sessionId: summary.id,
7872
+ sourcePath: source.file,
7873
+ fingerprint: this.sourceFingerprint(source)
7874
+ }
7875
+ ];
7876
+ });
7877
+ }
7878
+ scanSessionSource(sourcePath) {
7879
+ const source = this.sessionSourceFile(sourcePath);
7880
+ const summary = parseSummary(source);
7881
+ if (!summary) return null;
7882
+ const sessionDir = dirname6(source.file);
7883
+ const updatesPath = join14(sessionDir, UPDATES_FILE);
7884
+ const existingUpdatesPath = existsSync12(updatesPath) ? updatesPath : null;
7885
+ const headData = scanHeadData(existingUpdatesPath, summary.createdAt);
7886
+ const messageCount = headData.visibleMessageCount || summary.messageCount;
7887
+ if (messageCount === 0) return null;
7888
+ const title = resolveSessionTitle(
7889
+ summary.title,
7890
+ headData.firstUserText,
7891
+ basenameTitle(summary.cwd)
7892
+ );
7893
+ const stats = { message_count: messageCount, ...headData.stats };
7894
+ const fingerprint = this.sourceFingerprint(source);
7895
+ const head = {
7896
+ id: summary.id,
7897
+ slug: `grok/${summary.id}`,
7898
+ title,
7899
+ directory: summary.cwd,
7900
+ parent_reference: summary.parentSessionId ? { agentName: this.name, sessionId: summary.parentSessionId } : void 0,
7901
+ time_created: summary.createdAt,
7902
+ time_updated: summary.updatedAt,
7903
+ stats,
7904
+ model_usage: Object.keys(headData.modelUsage).length > 0 ? headData.modelUsage : void 0
5770
7905
  };
7906
+ this.sessionMetaMap.set(summary.id, {
7907
+ id: summary.id,
7908
+ title,
7909
+ sourcePath: source.file,
7910
+ sourceFingerprint: fingerprint,
7911
+ sourceMtimeMs: summary.updatedAt,
7912
+ directory: summary.cwd,
7913
+ messageCount,
7914
+ createdAt: summary.createdAt,
7915
+ updatedAt: summary.updatedAt,
7916
+ currentModel: summary.currentModel,
7917
+ parentSessionId: summary.parentSessionId,
7918
+ updatesPath: existingUpdatesPath,
7919
+ stats
7920
+ });
7921
+ return head;
5771
7922
  }
5772
- emptyUsageResult(message) {
7923
+ getSessionData(sessionId) {
7924
+ const meta = this.sessionMetaMap.get(sessionId);
7925
+ if (!meta) throw new Error(`Session not found: ${sessionId}`);
7926
+ const transcript = parseTranscript(meta);
5773
7927
  return {
5774
- message,
5775
- totalTokens: 0,
5776
- model: null
7928
+ reference: { agentName: this.name, sessionId: meta.id },
7929
+ id: meta.id,
7930
+ title: meta.title,
7931
+ slug: `grok/${meta.id}`,
7932
+ directory: meta.directory,
7933
+ parent_reference: meta.parentSessionId ? { agentName: this.name, sessionId: meta.parentSessionId } : void 0,
7934
+ version: void 0,
7935
+ time_created: meta.createdAt,
7936
+ time_updated: meta.updatedAt,
7937
+ stats: transcript.stats,
7938
+ messages: transcript.messages
5777
7939
  };
5778
7940
  }
5779
- };
5780
- function resolveZCodeDataRoot() {
5781
- const currentPlatform = platform3();
5782
- if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
5783
- return join12(homedir4(), ".zcode");
5784
- }
5785
- function findZCodeDbPath() {
5786
- if (!isSqliteAvailable()) return null;
5787
- const dataRoot = resolveZCodeDataRoot();
5788
- if (!dataRoot) return null;
5789
- return firstExisting(join12(dataRoot, "cli", "db", "db.sqlite"));
5790
- }
5791
- function getZCodeSessionWatchPlan() {
5792
- const dataRoot = resolveZCodeDataRoot();
5793
- return {
5794
- status: "supported",
5795
- targets: dataRoot ? [{ root: dataRoot, path: join12(dataRoot, "cli", "db", "db.sqlite") }] : []
5796
- };
5797
- }
5798
- var ZCodeAgent = class extends OpenCodeSqliteAgent {
5799
- constructor() {
5800
- super({
5801
- name: "zcode",
5802
- displayName: "ZCode",
5803
- findDbPath: findZCodeDbPath,
5804
- getSessionWatchPlan: getZCodeSessionWatchPlan
5805
- });
7941
+ sourceFingerprint(source) {
7942
+ const sessionDir = dirname6(source.file);
7943
+ return JSON.stringify([
7944
+ HEAD_INDEX_VERSION4,
7945
+ PARSER_VERSION3,
7946
+ source.stat.mtimeMs,
7947
+ source.stat.size,
7948
+ optionalFileFingerprint(join14(sessionDir, UPDATES_FILE))
7949
+ ]);
5806
7950
  }
5807
7951
  };
5808
7952
  registerAgent({
@@ -5834,6 +7978,13 @@ registerAgent({
5834
7978
  toolStrategy: "custom",
5835
7979
  create: () => new KimiAgent()
5836
7980
  });
7981
+ registerAgent({
7982
+ icon: "/icon/agent/kimi.svg",
7983
+ resolveDataRoot: resolveKimiCodeDataRoot,
7984
+ resumeCommandPrefix: "kimi -r",
7985
+ toolStrategy: "custom",
7986
+ create: () => new KimiCodeAgent()
7987
+ });
5837
7988
  registerAgent({
5838
7989
  icon: "/icon/agent/codex.svg",
5839
7990
  resolveDataRoot: resolveCodexDataRoot,
@@ -5841,6 +7992,13 @@ registerAgent({
5841
7992
  toolStrategy: "custom",
5842
7993
  create: () => new CodexAgent()
5843
7994
  });
7995
+ registerAgent({
7996
+ icon: "/icon/agent/grok.svg",
7997
+ resolveDataRoot: resolveGrokDataRoot,
7998
+ resumeCommandPrefix: "grok --resume",
7999
+ toolStrategy: "custom",
8000
+ create: () => new GrokAgent()
8001
+ });
5844
8002
  registerAgent({
5845
8003
  icon: "/icon/agent/pi.svg",
5846
8004
  resolveDataRoot: resolvePiDataRoot,
@@ -5863,11 +8021,11 @@ function fallbackDisplayName(input) {
5863
8021
  }
5864
8022
  var realFs = {
5865
8023
  exists(path2) {
5866
- return existsSync11(path2);
8024
+ return existsSync13(path2);
5867
8025
  },
5868
8026
  readText(path2) {
5869
8027
  try {
5870
- return readFileSync6(path2, "utf8");
8028
+ return readFileSync8(path2, "utf8");
5871
8029
  } catch {
5872
8030
  return null;
5873
8031
  }
@@ -6037,7 +8195,9 @@ function parseManifestName(file, text) {
6037
8195
  }
6038
8196
  function buildProjectGroups(sessions) {
6039
8197
  const groups = /* @__PURE__ */ new Map();
8198
+ const tree = buildSessionTree(sessions);
6040
8199
  for (const session of sessions) {
8200
+ if (tree.mountStateOf(session) === "mounted-child") continue;
6041
8201
  const identity = session.project_identity;
6042
8202
  if (!identity) continue;
6043
8203
  const activity = session.time_updated ?? session.time_created;
@@ -6090,7 +8250,7 @@ function isPathScopeMatch(queryPath, sessionPath) {
6090
8250
  return session === queryPath || session.startsWith(queryPath + "/") || queryPath.startsWith(session + "/");
6091
8251
  }
6092
8252
  function normalizeScopePath(path2) {
6093
- return resolve(path2).replaceAll(sep, "/");
8253
+ return resolve2(path2).replaceAll(sep, "/");
6094
8254
  }
6095
8255
  var TAG_ORDER = [
6096
8256
  "bugfix",
@@ -6365,16 +8525,16 @@ function setSchemaEnsuredPath(path2) {
6365
8525
  schemaEnsuredPath = path2;
6366
8526
  }
6367
8527
  function getCacheDir2() {
6368
- return join13(homedir6(), ".cache", "codesesh");
8528
+ return join15(homedir6(), ".cache", "codesesh");
6369
8529
  }
6370
8530
  function getCachePath2() {
6371
- return join13(getCacheDir2(), CACHE_FILENAME);
8531
+ return join15(getCacheDir2(), CACHE_FILENAME);
6372
8532
  }
6373
8533
  function getLegacyCachePath() {
6374
- return join13(getCacheDir2(), LEGACY_CACHE_FILENAME);
8534
+ return join15(getCacheDir2(), LEGACY_CACHE_FILENAME);
6375
8535
  }
6376
8536
  function hasCacheStorage() {
6377
- return existsSync12(getCachePath2());
8537
+ return existsSync14(getCachePath2());
6378
8538
  }
6379
8539
  function likePattern(value) {
6380
8540
  return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
@@ -6415,6 +8575,8 @@ function prepareUpsertSession(db) {
6415
8575
  title,
6416
8576
  source_path,
6417
8577
  directory,
8578
+ parent_agent_name,
8579
+ parent_session_id,
6418
8580
  project_identity_kind,
6419
8581
  project_identity_key,
6420
8582
  project_display_name,
@@ -6433,13 +8595,15 @@ function prepareUpsertSession(db) {
6433
8595
  smart_tags_json,
6434
8596
  smart_tags_source_updated_at,
6435
8597
  meta_json
6436
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8598
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6437
8599
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
6438
8600
  sort_index = excluded.sort_index,
6439
8601
  slug = excluded.slug,
6440
8602
  title = excluded.title,
6441
8603
  source_path = excluded.source_path,
6442
8604
  directory = excluded.directory,
8605
+ parent_agent_name = excluded.parent_agent_name,
8606
+ parent_session_id = excluded.parent_session_id,
6443
8607
  project_identity_kind = excluded.project_identity_kind,
6444
8608
  project_identity_key = excluded.project_identity_key,
6445
8609
  project_display_name = excluded.project_display_name,
@@ -6470,6 +8634,8 @@ function prepareUpsertIndexedSession(db) {
6470
8634
  title,
6471
8635
  source_path,
6472
8636
  directory,
8637
+ parent_agent_name,
8638
+ parent_session_id,
6473
8639
  project_identity_kind,
6474
8640
  project_identity_key,
6475
8641
  project_display_name,
@@ -6488,11 +8654,13 @@ function prepareUpsertIndexedSession(db) {
6488
8654
  smart_tags_json,
6489
8655
  smart_tags_source_updated_at,
6490
8656
  meta_json
6491
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8657
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6492
8658
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
6493
8659
  slug = excluded.slug,
6494
8660
  title = excluded.title,
6495
8661
  directory = excluded.directory,
8662
+ parent_agent_name = excluded.parent_agent_name,
8663
+ parent_session_id = excluded.parent_session_id,
6496
8664
  project_identity_kind = excluded.project_identity_kind,
6497
8665
  project_identity_key = excluded.project_identity_key,
6498
8666
  project_display_name = excluded.project_display_name,
@@ -6514,7 +8682,7 @@ function prepareUpsertIndexedSession(db) {
6514
8682
  }
6515
8683
  function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, sourcePath) {
6516
8684
  const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
6517
- const activityTime = session.time_updated ?? session.time_created;
8685
+ const activityTime2 = session.time_updated ?? session.time_created;
6518
8686
  statement.run(
6519
8687
  agentName,
6520
8688
  session.id,
@@ -6523,12 +8691,14 @@ function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, so
6523
8691
  session.title,
6524
8692
  sourcePath,
6525
8693
  session.directory,
8694
+ session.parent_reference?.agentName ?? null,
8695
+ session.parent_reference?.sessionId ?? null,
6526
8696
  identity.kind,
6527
8697
  identity.key,
6528
8698
  identity.displayName,
6529
8699
  session.time_created,
6530
8700
  session.time_updated ?? null,
6531
- activityTime,
8701
+ activityTime2,
6532
8702
  session.stats.message_count,
6533
8703
  session.stats.total_input_tokens,
6534
8704
  session.stats.total_output_tokens,
@@ -6600,6 +8770,12 @@ function sessionFromRow(row) {
6600
8770
  displayName: String(row.project_display_name ?? "")
6601
8771
  };
6602
8772
  }
8773
+ if (row.parent_agent_name && row.parent_session_id) {
8774
+ session.parent_reference = {
8775
+ agentName: String(row.parent_agent_name),
8776
+ sessionId: String(row.parent_session_id)
8777
+ };
8778
+ }
6603
8779
  if (row.time_updated != null) {
6604
8780
  session.time_updated = Number(row.time_updated);
6605
8781
  }
@@ -6817,7 +8993,7 @@ function buildSessionContentFromMessages(title, messages) {
6817
8993
  }
6818
8994
  return chunks.join("\n");
6819
8995
  }
6820
- var CACHE_SCHEMA_VERSION = 17;
8996
+ var CACHE_SCHEMA_VERSION = 18;
6821
8997
  function withCacheDb(fn) {
6822
8998
  const cachePath = getCachePath2();
6823
8999
  const db = openDb(cachePath);
@@ -6830,7 +9006,9 @@ function withCacheDb(fn) {
6830
9006
  return fn(db);
6831
9007
  } catch (error) {
6832
9008
  getCoreDiagnostics()?.warn("cache.write_failed", {
6833
- message: error instanceof Error ? error.message : String(error)
9009
+ message: error instanceof Error ? error.message : String(error),
9010
+ code: error?.code,
9011
+ stack: error instanceof Error ? error.stack : void 0
6834
9012
  });
6835
9013
  return null;
6836
9014
  } finally {
@@ -6871,7 +9049,7 @@ function runSearchIndexWrite(db, rebuild, write) {
6871
9049
  createMessageSearchTriggers(db);
6872
9050
  }
6873
9051
  return { value, rebuildDurationMs };
6874
- })();
9052
+ }).immediate();
6875
9053
  }
6876
9054
  function createCacheTables(db) {
6877
9055
  db.exec(`
@@ -6917,6 +9095,8 @@ function createSessionTables(db) {
6917
9095
  title TEXT NOT NULL,
6918
9096
  source_path TEXT,
6919
9097
  directory TEXT NOT NULL,
9098
+ parent_agent_name TEXT,
9099
+ parent_session_id TEXT,
6920
9100
  project_identity_kind TEXT NOT NULL,
6921
9101
  project_identity_key TEXT NOT NULL,
6922
9102
  project_display_name TEXT NOT NULL,
@@ -6944,6 +9124,9 @@ function createSessionTables(db) {
6944
9124
  CREATE INDEX IF NOT EXISTS idx_sessions_project
6945
9125
  ON sessions(project_identity_kind, project_identity_key, activity_time);
6946
9126
 
9127
+ CREATE INDEX IF NOT EXISTS idx_sessions_parent
9128
+ ON sessions(parent_agent_name, parent_session_id);
9129
+
6947
9130
  CREATE TABLE IF NOT EXISTS messages (
6948
9131
  agent_name TEXT NOT NULL,
6949
9132
  session_id TEXT NOT NULL,
@@ -7233,6 +9416,7 @@ function createProjectGroupsView(db) {
7233
9416
  `);
7234
9417
  return;
7235
9418
  }
9419
+ const hasParentReference = columnExists(db, "sessions", "parent_agent_name") && columnExists(db, "sessions", "parent_session_id");
7236
9420
  db.exec(`
7237
9421
  CREATE VIEW IF NOT EXISTS project_groups_v AS
7238
9422
  SELECT
@@ -7243,6 +9427,7 @@ function createProjectGroupsView(db) {
7243
9427
  COUNT(*) AS session_count,
7244
9428
  MAX(activity_time) AS last_activity
7245
9429
  FROM sessions
9430
+ ${hasParentReference ? "WHERE parent_agent_name IS NULL OR parent_session_id IS NULL" : ""}
7246
9431
  GROUP BY project_identity_kind, project_identity_key;
7247
9432
  `);
7248
9433
  }
@@ -7640,6 +9825,19 @@ function addMessagePartsFormatVersion(db) {
7640
9825
  }
7641
9826
  db.exec("ALTER TABLE messages ADD COLUMN parts_format_version INTEGER NOT NULL DEFAULT 0");
7642
9827
  }
9828
+ function addSessionParentReference(db) {
9829
+ if (!tableExists(db, "sessions")) return;
9830
+ if (!columnExists(db, "sessions", "parent_agent_name")) {
9831
+ db.exec("ALTER TABLE sessions ADD COLUMN parent_agent_name TEXT");
9832
+ }
9833
+ if (!columnExists(db, "sessions", "parent_session_id")) {
9834
+ db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
9835
+ }
9836
+ db.exec(
9837
+ "CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_agent_name, parent_session_id)"
9838
+ );
9839
+ recreateProjectGroupsView(db);
9840
+ }
7643
9841
  var CODEX_EXEC_DECODE_MIGRATION_KEY = "codex_exec_decode_migrated_v3";
7644
9842
  function migrateCodexExecDecode(db) {
7645
9843
  if (!tableExists(db, "cache_meta")) return;
@@ -7654,6 +9852,30 @@ function migrateCodexExecDecode(db) {
7654
9852
  "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
7655
9853
  ).run(CODEX_EXEC_DECODE_MIGRATION_KEY);
7656
9854
  }
9855
+ var OPENCODE_SUBAGENT_FOLD_KEY = "opencode_subagent_fold_v1";
9856
+ var SUBAGENT_TREE_KEY = "subagent_tree_v1";
9857
+ function migrateOpenCodeSubagentFold(db) {
9858
+ if (!tableExists(db, "cache_meta")) return;
9859
+ const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(OPENCODE_SUBAGENT_FOLD_KEY);
9860
+ if (done) return;
9861
+ if (tableExists(db, "agent_cache")) {
9862
+ db.prepare("DELETE FROM agent_cache WHERE agent_name IN ('zcode', 'opencode')").run();
9863
+ }
9864
+ db.prepare(
9865
+ "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
9866
+ ).run(OPENCODE_SUBAGENT_FOLD_KEY);
9867
+ }
9868
+ function migrateSubagentTree(db) {
9869
+ if (!tableExists(db, "cache_meta")) return;
9870
+ const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(SUBAGENT_TREE_KEY);
9871
+ if (done) return;
9872
+ if (tableExists(db, "agent_cache")) {
9873
+ db.prepare("DELETE FROM agent_cache WHERE agent_name IN ('codex', 'zcode', 'opencode')").run();
9874
+ }
9875
+ db.prepare(
9876
+ "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
9877
+ ).run(SUBAGENT_TREE_KEY);
9878
+ }
7657
9879
  function rebuildSearchIndex(db) {
7658
9880
  if (!tableExists(db, "session_documents_fts")) {
7659
9881
  return;
@@ -7763,6 +9985,8 @@ function ensureSchema(db, dbPath) {
7763
9985
  createLatestCacheSchema(db);
7764
9986
  setCacheSchemaVersion(db);
7765
9987
  migrateCodexExecDecode(db);
9988
+ migrateOpenCodeSubagentFold(db);
9989
+ migrateSubagentTree(db);
7766
9990
  return;
7767
9991
  }
7768
9992
  runSchemaMigrations(db, {
@@ -7794,7 +10018,13 @@ function ensureSchema(db, dbPath) {
7794
10018
  invalidateSearchContentHashes(db2);
7795
10019
  }
7796
10020
  },
7797
- { version: 7, migrate: backfillStructuredSessions },
10021
+ {
10022
+ version: 7,
10023
+ migrate(db2) {
10024
+ addSessionParentReference(db2);
10025
+ backfillStructuredSessions(db2);
10026
+ }
10027
+ },
7798
10028
  { version: 8, migrate: backfillFileActivity },
7799
10029
  {
7800
10030
  version: 9,
@@ -7825,14 +10055,17 @@ function ensureSchema(db, dbPath) {
7825
10055
  { version: 13, migrate: createCacheTables },
7826
10056
  { version: 14, migrate: addIndexedMessageCount },
7827
10057
  { version: 15, destructive: true, migrate: compactSessionDocuments },
7828
- { version: 17, migrate: addMessagePartsFormatVersion }
10058
+ { version: 17, migrate: addMessagePartsFormatVersion },
10059
+ { version: 18, migrate: addSessionParentReference }
7829
10060
  ]
7830
10061
  });
7831
10062
  createLatestCacheSchema(db);
7832
- if (getUserVersion(db) <= CACHE_SCHEMA_VERSION) {
10063
+ if (getUserVersion(db) < CACHE_SCHEMA_VERSION) {
7833
10064
  setCacheSchemaVersion(db);
7834
10065
  }
7835
10066
  migrateCodexExecDecode(db);
10067
+ migrateOpenCodeSubagentFold(db);
10068
+ migrateSubagentTree(db);
7836
10069
  }
7837
10070
  function escapeFtsTerm(value) {
7838
10071
  return value.replaceAll('"', '""');
@@ -7963,6 +10196,7 @@ function readPendingReindexIds(db, agentName) {
7963
10196
  return new Set(rows.map((row) => String(row.session_id)));
7964
10197
  }
7965
10198
  var SEARCH_INDEX_STATE_BATCH_SIZE = 900;
10199
+ var SEARCH_INDEX_COMMIT_CHUNK_SIZE = 64;
7966
10200
  function shouldBulkSyncSearchIndex(options, changedCount) {
7967
10201
  if (options.isBulk != null) {
7968
10202
  return options.isBulk;
@@ -8222,6 +10456,32 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
8222
10456
  sortIndex: sessionSortIndexMap.get(session.id) ?? 0
8223
10457
  }));
8224
10458
  let indexed = 0;
10459
+ if (changes.length > SEARCH_INDEX_COMMIT_CHUNK_SIZE) {
10460
+ runSearchIndexWrite(db, false, () => {
10461
+ indexed += writeSearchIndexRows(db, agentName, toDelete, []);
10462
+ });
10463
+ for (let offset = 0; offset < changes.length; offset += SEARCH_INDEX_COMMIT_CHUNK_SIZE) {
10464
+ const chunk = changes.slice(offset, offset + SEARCH_INDEX_COMMIT_CHUNK_SIZE);
10465
+ runSearchIndexWrite(db, false, () => {
10466
+ indexed += writeSearchIndexRows(
10467
+ db,
10468
+ agentName,
10469
+ [],
10470
+ loadSearchIndexEntries(agentName, chunk, loadSessionData)
10471
+ );
10472
+ });
10473
+ }
10474
+ return {
10475
+ agentName,
10476
+ mode: "incremental",
10477
+ sessions: sessions.length,
10478
+ changed: toUpsert.length,
10479
+ deleted: toDelete.length,
10480
+ indexed,
10481
+ skipped: toUpsert.length - indexed,
10482
+ durationMs: performance.now() - startedAt
10483
+ };
10484
+ }
8225
10485
  const writeRows = () => {
8226
10486
  indexed = writeSearchIndexRows(
8227
10487
  db,
@@ -8847,6 +11107,7 @@ function searchFileActivitySessions(query, options = {}) {
8847
11107
  }));
8848
11108
  }
8849
11109
  var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
11110
+ var FULL_SYNC_CURSOR_PREFIX = "full_sync_cursor:";
8850
11111
  function parseCachedSessionMeta(value) {
8851
11112
  if (!value) return null;
8852
11113
  try {
@@ -8857,7 +11118,7 @@ function parseCachedSessionMeta(value) {
8857
11118
  }
8858
11119
  function deleteLegacyCacheFile() {
8859
11120
  const legacyPath = getLegacyCachePath();
8860
- if (!existsSync13(legacyPath)) {
11121
+ if (!existsSync15(legacyPath)) {
8861
11122
  return;
8862
11123
  }
8863
11124
  try {
@@ -8884,6 +11145,8 @@ function loadCachedSessions(agentName) {
8884
11145
  title,
8885
11146
  source_path,
8886
11147
  directory,
11148
+ parent_agent_name,
11149
+ parent_session_id,
8887
11150
  project_identity_kind,
8888
11151
  project_identity_key,
8889
11152
  project_display_name,
@@ -8946,6 +11209,43 @@ function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATIO
8946
11209
  ).run(agentName, Date.now(), indexVersion);
8947
11210
  });
8948
11211
  }
11212
+ function markAgentFullSyncStarted(agentName) {
11213
+ withCacheDb((db) => {
11214
+ db.prepare(
11215
+ `
11216
+ UPDATE cache_initialization
11217
+ SET last_sync_at = 0
11218
+ WHERE agent_name = ?
11219
+ `
11220
+ ).run(agentName);
11221
+ });
11222
+ }
11223
+ function getAgentFullSyncCursor(agentName) {
11224
+ if (!hasCacheStorage()) return null;
11225
+ return withCacheDbReadOnly((db) => {
11226
+ const row = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(`${FULL_SYNC_CURSOR_PREFIX}${agentName}`);
11227
+ return row?.value || null;
11228
+ }) ?? null;
11229
+ }
11230
+ function markAgentFullSyncProgress(agentName, cursor) {
11231
+ if (!cursor) return;
11232
+ withCacheDb((db) => {
11233
+ db.prepare(
11234
+ `
11235
+ INSERT INTO cache_meta(key, value)
11236
+ VALUES (?, ?)
11237
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
11238
+ `
11239
+ ).run(`${FULL_SYNC_CURSOR_PREFIX}${agentName}`, cursor);
11240
+ });
11241
+ }
11242
+ function clearAgentFullSyncCursor(agentName) {
11243
+ withCacheDb((db) => {
11244
+ db.prepare("DELETE FROM cache_meta WHERE key = ?").run(
11245
+ `${FULL_SYNC_CURSOR_PREFIX}${agentName}`
11246
+ );
11247
+ });
11248
+ }
8949
11249
  function getAgentLastFullSyncAt(agentName) {
8950
11250
  if (!hasCacheStorage()) {
8951
11251
  return null;
@@ -8972,6 +11272,7 @@ function markAgentFullSyncCompleted(agentName) {
8972
11272
  `
8973
11273
  ).run(Date.now(), agentName);
8974
11274
  });
11275
+ clearAgentFullSyncCursor(agentName);
8975
11276
  }
8976
11277
  function loadCachedSessionRawEntry(agentName, sessionId) {
8977
11278
  if (!hasCacheStorage()) {
@@ -8987,6 +11288,8 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
8987
11288
  title,
8988
11289
  source_path,
8989
11290
  directory,
11291
+ parent_agent_name,
11292
+ parent_session_id,
8990
11293
  project_identity_kind,
8991
11294
  project_identity_key,
8992
11295
  project_display_name,
@@ -9107,16 +11410,13 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
9107
11410
  );
9108
11411
  });
9109
11412
  });
9110
- write();
11413
+ write.immediate();
9111
11414
  deleteLegacyCacheFile();
9112
11415
  return true;
9113
11416
  });
9114
11417
  return persisted ?? false;
9115
11418
  }
9116
11419
  function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
9117
- if (changes.length === 0 && removedSessionIds.length === 0) {
9118
- return true;
9119
- }
9120
11420
  const persisted = withCacheDb((db) => {
9121
11421
  const deleteSession = db.prepare(
9122
11422
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
@@ -9161,7 +11461,7 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
9161
11461
  );
9162
11462
  }
9163
11463
  });
9164
- write();
11464
+ write.immediate();
9165
11465
  deleteLegacyCacheFile();
9166
11466
  return true;
9167
11467
  });
@@ -9191,7 +11491,7 @@ function clearCache() {
9191
11491
  const walPath = `${cachePath}-wal`;
9192
11492
  const shmPath = `${cachePath}-shm`;
9193
11493
  for (const filePath of [walPath, shmPath]) {
9194
- if (!existsSync13(filePath)) {
11494
+ if (!existsSync15(filePath)) {
9195
11495
  continue;
9196
11496
  }
9197
11497
  try {
@@ -9220,6 +11520,8 @@ function sessionSignature(session) {
9220
11520
  return JSON.stringify([
9221
11521
  session.title,
9222
11522
  session.directory,
11523
+ session.parent_reference?.agentName ?? null,
11524
+ session.parent_reference?.sessionId ?? null,
9223
11525
  session.time_created,
9224
11526
  session.time_updated ?? session.time_created,
9225
11527
  session.stats.message_count,
@@ -9273,13 +11575,7 @@ function filterSessions(sessions, options) {
9273
11575
  if (options.cwd) {
9274
11576
  result = filterSessionsByProjectScope(result, options.cwd);
9275
11577
  }
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;
11578
+ return filterSessionTreeByActivityWindow(result, options.from, options.to);
9283
11579
  }
9284
11580
  function saveCachedSessionDiff(agent, cachedSessions, updatedSessions, changedIds = []) {
9285
11581
  const diff = computeSessionDiff(cachedSessions, updatedSessions, changedIds, sessionSignature);
@@ -9301,17 +11597,47 @@ function chunkSessions(items, chunkCount) {
9301
11597
  });
9302
11598
  return chunks.filter((chunk) => chunk.length > 0);
9303
11599
  }
9304
- function ensureSessionTagsSync(agent, sessions) {
11600
+ function ensureSessionTagsSync(agent, sessions, onProgress) {
9305
11601
  let changed = false;
11602
+ let processed = 0;
11603
+ const total = sessions.length;
11604
+ const timing = {
11605
+ sessions: total,
11606
+ cacheHits: 0,
11607
+ staleSessions: 0,
11608
+ failedSessions: 0,
11609
+ getSessionDataCalls: 0,
11610
+ getSessionDataMs: 0,
11611
+ classifySessionTagsCalls: 0,
11612
+ classifySessionTagsMs: 0
11613
+ };
9306
11614
  const tagged = sessions.map((session) => {
9307
11615
  const sourceUpdatedAt = session.time_updated ?? session.time_created;
9308
11616
  const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
9309
11617
  if (currentTags && session.smart_tags_source_updated_at === sourceUpdatedAt) {
11618
+ timing.cacheHits += 1;
11619
+ processed += 1;
11620
+ onProgress?.(processed, total);
9310
11621
  return session;
9311
11622
  }
11623
+ timing.staleSessions += 1;
9312
11624
  try {
9313
- const data = agent.getSessionData(session.id);
9314
- const tags = classifySessionTags(data);
11625
+ timing.getSessionDataCalls += 1;
11626
+ const getSessionDataStartedAt = performance.now();
11627
+ let data;
11628
+ try {
11629
+ data = agent.getSessionData(session.id);
11630
+ } finally {
11631
+ timing.getSessionDataMs += performance.now() - getSessionDataStartedAt;
11632
+ }
11633
+ timing.classifySessionTagsCalls += 1;
11634
+ const classifySessionTagsStartedAt = performance.now();
11635
+ let tags;
11636
+ try {
11637
+ tags = classifySessionTags(data);
11638
+ } finally {
11639
+ timing.classifySessionTagsMs += performance.now() - classifySessionTagsStartedAt;
11640
+ }
9315
11641
  changed = true;
9316
11642
  return {
9317
11643
  ...session,
@@ -9319,10 +11645,14 @@ function ensureSessionTagsSync(agent, sessions) {
9319
11645
  smart_tags_source_updated_at: getSmartTagSourceTimestamp(data)
9320
11646
  };
9321
11647
  } catch {
11648
+ timing.failedSessions += 1;
9322
11649
  return session;
11650
+ } finally {
11651
+ processed += 1;
11652
+ onProgress?.(processed, total);
9323
11653
  }
9324
11654
  });
9325
- return { sessions: tagged, changed };
11655
+ return { sessions: tagged, changed, timing };
9326
11656
  }
9327
11657
  async function classifySessionTagsInWorker(workerUrl, agentName, sessionIds, meta) {
9328
11658
  return new Promise((resolveWorker, rejectWorker) => {
@@ -9434,12 +11764,13 @@ async function scanAgentSmart(agent, options, onProgress) {
9434
11764
  }
9435
11765
  agent.setSessionMetaMap(metaMap);
9436
11766
  if (options.cacheOnly) {
11767
+ const visibleSessions = agent.filterCachedSessions(cached.sessions);
9437
11768
  onProgress?.({
9438
11769
  agent: agent.name,
9439
11770
  phase: "cache",
9440
- cachedCount: cached.sessions.length
11771
+ cachedCount: visibleSessions.length
9441
11772
  });
9442
- return finalizeAgentScan(agent, cached.sessions, {
11773
+ return finalizeAgentScan(agent, visibleSessions, {
9443
11774
  finalization: { kind: "cache-only", cached },
9444
11775
  options,
9445
11776
  timing,
@@ -9515,6 +11846,7 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
9515
11846
  from: options.from,
9516
11847
  to: options.to,
9517
11848
  fast: options.fast,
11849
+ includeRelatedSessions: true,
9518
11850
  onProgress: (progress) => {
9519
11851
  onProgress?.({
9520
11852
  agent: agent.name,
@@ -9730,6 +12062,66 @@ function listCachedProjectGroups(sessions) {
9730
12062
  lastActivity: row.last_activity == null ? null : Number(row.last_activity)
9731
12063
  }));
9732
12064
  }
12065
+ var DEFAULT_MODEL_COST_LIMIT = 20;
12066
+ function buildModelCostWhere(options) {
12067
+ const clauses = ["m.model IS NOT NULL", "m.model <> ''"];
12068
+ const params = [];
12069
+ if (options.agent != null) {
12070
+ clauses.push("s.agent_name = ?");
12071
+ params.push(options.agent);
12072
+ }
12073
+ if (options.projectKind != null || options.projectKey != null) {
12074
+ if (options.projectKind != null && options.projectKey != null) {
12075
+ clauses.push("s.project_identity_kind = ? AND s.project_identity_key = ?");
12076
+ params.push(options.projectKind, options.projectKey);
12077
+ } else {
12078
+ clauses.push("0");
12079
+ }
12080
+ }
12081
+ if (options.from != null) {
12082
+ clauses.push("s.activity_time >= ?");
12083
+ params.push(options.from);
12084
+ }
12085
+ if (options.to != null) {
12086
+ clauses.push("s.activity_time <= ?");
12087
+ params.push(options.to);
12088
+ }
12089
+ return { where: `WHERE ${clauses.join(" AND ")}`, params };
12090
+ }
12091
+ var MODEL_COST_SQL = `
12092
+ SELECT
12093
+ m.model AS model,
12094
+ SUM(COALESCE(m.cost, 0)) AS cost,
12095
+ SUM(CASE WHEN m.cost_source = 'recorded' THEN COALESCE(m.cost, 0) ELSE 0 END) AS cost_recorded,
12096
+ SUM(CASE WHEN m.cost_source = 'recorded' THEN 0 ELSE COALESCE(m.cost, 0) END) AS cost_estimated
12097
+ FROM messages m
12098
+ JOIN sessions s ON s.agent_name = m.agent_name AND s.session_id = m.session_id
12099
+ `;
12100
+ function listModelCostDistribution(options = {}) {
12101
+ if (!hasCacheStorage()) {
12102
+ return null;
12103
+ }
12104
+ const { where, params } = buildModelCostWhere(options);
12105
+ const sql = `
12106
+ ${MODEL_COST_SQL}
12107
+ ${where}
12108
+ GROUP BY m.model
12109
+ ORDER BY cost DESC, m.model
12110
+ LIMIT ?
12111
+ `;
12112
+ const rows = withCacheDbReadOnly(
12113
+ (db) => db.prepare(sql).all(...params, options.limit ?? DEFAULT_MODEL_COST_LIMIT)
12114
+ );
12115
+ if (rows == null) {
12116
+ return null;
12117
+ }
12118
+ return rows.map((row) => ({
12119
+ model: String(row.model ?? ""),
12120
+ cost: Number(row.cost ?? 0),
12121
+ costRecorded: Number(row.cost_recorded ?? 0),
12122
+ costEstimated: Number(row.cost_estimated ?? 0)
12123
+ }));
12124
+ }
9733
12125
  var STATE_DB_FILENAME = "state.db";
9734
12126
  var STATE_SCHEMA_VERSION = 2;
9735
12127
  var MEMORY_STATE_STORE = "memory";
@@ -9750,16 +12142,16 @@ function getStateDir() {
9750
12142
  if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
9751
12143
  const currentPlatform = platform4();
9752
12144
  if (currentPlatform === "darwin") {
9753
- return join14(homedir7(), "Library", "Application Support", "codesesh");
12145
+ return join16(homedir7(), "Library", "Application Support", "codesesh");
9754
12146
  }
9755
12147
  if (currentPlatform === "win32") {
9756
12148
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
9757
- return join14(appData ?? join14(homedir7(), "AppData", "Roaming"), "codesesh");
12149
+ return join16(appData ?? join16(homedir7(), "AppData", "Roaming"), "codesesh");
9758
12150
  }
9759
- return join14(process.env.XDG_DATA_HOME ?? join14(homedir7(), ".local", "share"), "codesesh");
12151
+ return join16(process.env.XDG_DATA_HOME ?? join16(homedir7(), ".local", "share"), "codesesh");
9760
12152
  }
9761
12153
  function getStateDbPath() {
9762
- return join14(getStateDir(), STATE_DB_FILENAME);
12154
+ return join16(getStateDir(), STATE_DB_FILENAME);
9763
12155
  }
9764
12156
  function useMemoryStateStore() {
9765
12157
  return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
@@ -10049,7 +12441,7 @@ function importBookmarks(bookmarks) {
10049
12441
  );
10050
12442
  }
10051
12443
  });
10052
- write();
12444
+ write.immediate();
10053
12445
  const rows = db.prepare(
10054
12446
  `
10055
12447
  SELECT
@@ -10167,159 +12559,267 @@ function deleteSessionAlias(reference) {
10167
12559
  });
10168
12560
  }
10169
12561
  var DASHBOARD_RECENT_LIMIT = 10;
10170
- function getTotalTokens(stats) {
10171
- return stats.total_tokens ?? stats.total_input_tokens + stats.total_output_tokens;
10172
- }
12562
+ var DASHBOARD_PROJECT_LIMIT = 12;
12563
+ var PROJECT_SPARKLINE_DAYS = 14;
10173
12564
  function getSessionAgentName(session) {
10174
12565
  return getSessionAgentKey(session);
10175
12566
  }
10176
12567
  function getSessionActivityTime(session) {
10177
12568
  return session.time_updated ?? session.time_created;
10178
12569
  }
10179
- function buildDashboard(sessions, options) {
10180
- const { byAgentNames, scope, from, to, agentInfoMap } = options;
10181
- const agentMetrics = /* @__PURE__ */ new Map();
10182
- const agentMetricKeyByName = /* @__PURE__ */ new Map();
12570
+ function matchesScope(session, scope) {
12571
+ if (scope.agent && getSessionAgentName(session) !== scope.agent) return false;
12572
+ if (scope.projectKind == null && scope.projectKey == null) return true;
12573
+ const identity = session.project_identity;
12574
+ return identity != null && identity.kind === scope.projectKind && identity.key === scope.projectKey;
12575
+ }
12576
+ function scopedEntries(sessions, scope, from, to) {
12577
+ const tree = buildSessionTree(filterSessionTreeByActivityWindow(sessions, from, to));
12578
+ return tree.entries.filter((node) => matchesScope(node.session, scope));
12579
+ }
12580
+ function emptyDailyBucket(date) {
12581
+ return {
12582
+ date,
12583
+ sessions: 0,
12584
+ messages: 0,
12585
+ cost: 0,
12586
+ input: 0,
12587
+ output: 0,
12588
+ cache_read: 0,
12589
+ cache_create: 0
12590
+ };
12591
+ }
12592
+ function createAccumulator(byAgentNames, scope, to) {
12593
+ const agents = /* @__PURE__ */ new Map();
10183
12594
  for (const name of byAgentNames) {
10184
- if (scope.agent && name.toLowerCase() !== scope.agent) continue;
10185
- agentMetrics.set(name, { sessions: 0, messages: 0, tokens: 0 });
10186
- agentMetricKeyByName.set(name.toLowerCase(), name);
12595
+ const key = name.toLowerCase();
12596
+ if (scope.agent && key !== scope.agent) continue;
12597
+ agents.set(key, { name, sessions: 0, messages: 0, tokens: 0, cost: 0 });
10187
12598
  }
10188
- let totalSessions = 0;
10189
- let totalMessages = 0;
10190
- let totalTokens = 0;
10191
- let totalCost = 0;
10192
- let hasEstimatedCost = false;
10193
- let latestActivity = 0;
10194
- const recentCandidates = [];
10195
- const modelAgg = /* @__PURE__ */ new Map();
10196
- const dailyMap = /* @__PURE__ */ new Map();
10197
- const dailyTokenMap = /* @__PURE__ */ new Map();
12599
+ const sparklineSlots = /* @__PURE__ */ new Map();
12600
+ for (let slot = 0; slot < PROJECT_SPARKLINE_DAYS; slot += 1) {
12601
+ const day = addCalendarDays(to, slot - (PROJECT_SPARKLINE_DAYS - 1));
12602
+ sparklineSlots.set(toCalendarDayKey(day), slot);
12603
+ }
12604
+ return {
12605
+ sessions: 0,
12606
+ messages: 0,
12607
+ tokens: 0,
12608
+ cost: 0,
12609
+ costRecorded: 0,
12610
+ costEstimated: 0,
12611
+ cacheReadTokens: 0,
12612
+ hasEstimatedCost: false,
12613
+ agents,
12614
+ agentKeys: /* @__PURE__ */ new Set(),
12615
+ daily: /* @__PURE__ */ new Map(),
12616
+ models: /* @__PURE__ */ new Map(),
12617
+ projects: /* @__PURE__ */ new Map(),
12618
+ sparklineSlots,
12619
+ recent: []
12620
+ };
12621
+ }
12622
+ function foldModelUsage(node, into) {
12623
+ if (node.session.model_usage) {
12624
+ for (const [model, tokens] of Object.entries(node.session.model_usage)) {
12625
+ into.set(model, (into.get(model) ?? 0) + tokens);
12626
+ }
12627
+ }
12628
+ for (const child of node.children) foldModelUsage(child, into);
12629
+ }
12630
+ function trackProject(node, acc, dayKey, agentKey) {
12631
+ const identity = node.session.project_identity;
12632
+ if (!identity) return;
12633
+ const key = getProjectIdentityKey(identity);
12634
+ let project = acc.projects.get(key);
12635
+ if (!project) {
12636
+ project = {
12637
+ identityKind: identity.kind,
12638
+ identityKey: identity.key,
12639
+ displayName: identity.displayName,
12640
+ sessions: 0,
12641
+ messages: 0,
12642
+ tokens: 0,
12643
+ cost: 0,
12644
+ hasEstimatedCost: false,
12645
+ agentSessions: /* @__PURE__ */ new Map(),
12646
+ sparkline: Array.from({ length: PROJECT_SPARKLINE_DAYS }, () => 0)
12647
+ };
12648
+ acc.projects.set(key, project);
12649
+ }
12650
+ const stats = node.inclusiveStats;
12651
+ project.sessions += 1;
12652
+ project.messages += stats.messageCount;
12653
+ project.tokens += stats.totalTokens;
12654
+ project.cost += stats.cost;
12655
+ if (stats.costSource === "estimated") project.hasEstimatedCost = true;
12656
+ project.agentSessions.set(agentKey, (project.agentSessions.get(agentKey) ?? 0) + 1);
12657
+ const slot = acc.sparklineSlots.get(dayKey);
12658
+ if (slot != null) project.sparkline[slot] += stats.cost;
12659
+ }
12660
+ function accumulate(node, acc) {
12661
+ const session = node.session;
12662
+ const stats = node.inclusiveStats;
12663
+ const activity = getSessionActivityTime(session);
12664
+ const agentKey = getSessionAgentName(session);
12665
+ acc.sessions += 1;
12666
+ acc.messages += stats.messageCount;
12667
+ acc.tokens += stats.totalTokens;
12668
+ acc.cost += stats.cost;
12669
+ acc.cacheReadTokens += stats.cacheReadTokens;
12670
+ acc.agentKeys.add(agentKey);
12671
+ if (stats.costSource === "recorded") acc.costRecorded += stats.cost;
12672
+ else acc.costEstimated += stats.cost;
12673
+ if (stats.costSource === "estimated") acc.hasEstimatedCost = true;
12674
+ const metric = acc.agents.get(agentKey);
12675
+ if (metric) {
12676
+ metric.sessions += 1;
12677
+ metric.messages += stats.messageCount;
12678
+ metric.tokens += stats.totalTokens;
12679
+ metric.cost += stats.cost;
12680
+ }
12681
+ const dayKey = toCalendarDayKey(activity);
12682
+ let bucket = acc.daily.get(dayKey);
12683
+ if (!bucket) {
12684
+ bucket = emptyDailyBucket(dayKey);
12685
+ acc.daily.set(dayKey, bucket);
12686
+ }
12687
+ bucket.sessions += 1;
12688
+ bucket.messages += stats.messageCount;
12689
+ bucket.cost += stats.cost;
12690
+ bucket.input += Math.max(0, stats.inputTokens - stats.cacheReadTokens - stats.cacheCreateTokens);
12691
+ bucket.output += stats.outputTokens;
12692
+ bucket.cache_read += stats.cacheReadTokens;
12693
+ bucket.cache_create += stats.cacheCreateTokens;
12694
+ const usage = /* @__PURE__ */ new Map();
12695
+ foldModelUsage(node, usage);
12696
+ for (const [model, tokens] of usage) {
12697
+ const entry = acc.models.get(model);
12698
+ if (entry) {
12699
+ entry.tokens += tokens;
12700
+ entry.sessions += 1;
12701
+ } else {
12702
+ acc.models.set(model, { tokens, sessions: 1 });
12703
+ }
12704
+ }
12705
+ trackProject(node, acc, dayKey, agentKey);
12706
+ let recentIndex = acc.recent.length;
12707
+ for (let i = 0; i < acc.recent.length; i += 1) {
12708
+ if (activity > acc.recent[i].activity) {
12709
+ recentIndex = i;
12710
+ break;
12711
+ }
12712
+ }
12713
+ if (recentIndex < DASHBOARD_RECENT_LIMIT) {
12714
+ acc.recent.splice(recentIndex, 0, { session, activity });
12715
+ if (acc.recent.length > DASHBOARD_RECENT_LIMIT) acc.recent.pop();
12716
+ }
12717
+ }
12718
+ function toPreviousTotals(acc) {
12719
+ return { sessions: acc.sessions, messages: acc.messages, tokens: acc.tokens, cost: acc.cost };
12720
+ }
12721
+ function toProjectStat(project) {
12722
+ return {
12723
+ identityKind: project.identityKind,
12724
+ identityKey: project.identityKey,
12725
+ displayName: project.displayName,
12726
+ sessions: project.sessions,
12727
+ messages: project.messages,
12728
+ tokens: project.tokens,
12729
+ cost: project.cost,
12730
+ cost_source: project.cost > 0 ? project.hasEstimatedCost ? "estimated" : "recorded" : void 0,
12731
+ agents: [...project.agentSessions.entries()].sort((a, b) => b[1] - a[1]).map(([agentKey]) => agentKey),
12732
+ sparkline: project.sparkline
12733
+ };
12734
+ }
12735
+ function buildDashboard(sessions, options) {
12736
+ const { byAgentNames, scope, from, to, agentInfoMap, compare } = options;
12737
+ const acc = createAccumulator(byAgentNames, scope, to);
10198
12738
  if (from != null) {
10199
12739
  const bucketDays = countCalendarDays(from, to);
10200
12740
  for (let i = 0; i < bucketDays; i += 1) {
10201
12741
  const key = toCalendarDayKey(addCalendarDays(from, i));
10202
- dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
10203
- dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
12742
+ acc.daily.set(key, emptyDailyBucket(key));
10204
12743
  }
10205
12744
  }
10206
- for (const session of sessions) {
10207
- const agentName = getSessionAgentName(session);
10208
- if (scope.agent && agentName !== scope.agent) continue;
10209
- if (scope.projectKind || scope.projectKey) {
10210
- const identity = session.project_identity;
10211
- if (!identity || !scope.projectKind || !scope.projectKey || identity.kind !== scope.projectKind || identity.key !== scope.projectKey) {
10212
- continue;
10213
- }
10214
- }
10215
- const activity = getSessionActivityTime(session);
10216
- if (from != null && activity < from) continue;
10217
- if (activity > to) continue;
10218
- const messageCount = session.stats.message_count;
10219
- const sessionTokens = getTotalTokens(session.stats);
10220
- totalSessions += 1;
10221
- totalMessages += messageCount;
10222
- totalTokens += sessionTokens;
10223
- totalCost += session.stats.total_cost ?? 0;
10224
- if (session.stats.cost_source === "estimated") hasEstimatedCost = true;
10225
- if (activity > latestActivity) latestActivity = activity;
10226
- const metricKey = agentMetricKeyByName.get(agentName);
10227
- if (metricKey) {
10228
- const metric = agentMetrics.get(metricKey);
10229
- metric.sessions += 1;
10230
- metric.messages += messageCount;
10231
- metric.tokens += sessionTokens;
10232
- }
10233
- const key = toCalendarDayKey(activity);
10234
- let bucket = dailyMap.get(key);
10235
- if (!bucket) {
10236
- bucket = { date: key, sessions: 0, messages: 0 };
10237
- dailyMap.set(key, bucket);
10238
- }
10239
- bucket.sessions += 1;
10240
- bucket.messages += messageCount;
10241
- let tokenBucket = dailyTokenMap.get(key);
10242
- if (!tokenBucket) {
10243
- tokenBucket = { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 };
10244
- dailyTokenMap.set(key, tokenBucket);
10245
- }
10246
- const cacheRead = session.stats.total_cache_read_tokens ?? 0;
10247
- const cacheCreate = session.stats.total_cache_create_tokens ?? 0;
10248
- const pureInput = session.stats.total_input_tokens - cacheRead - cacheCreate;
10249
- tokenBucket.input += Math.max(0, pureInput);
10250
- tokenBucket.output += session.stats.total_output_tokens;
10251
- tokenBucket.cache_read += cacheRead;
10252
- tokenBucket.cache_create += cacheCreate;
10253
- if (session.model_usage) {
10254
- for (const [model, tokens] of Object.entries(session.model_usage)) {
10255
- const entry = modelAgg.get(model);
10256
- if (entry) {
10257
- entry.tokens += tokens;
10258
- entry.sessions += 1;
10259
- } else {
10260
- modelAgg.set(model, { tokens, sessions: 1 });
10261
- }
10262
- }
10263
- }
10264
- let recentIndex = recentCandidates.length;
10265
- for (let i = 0; i < recentCandidates.length; i += 1) {
10266
- if (activity > recentCandidates[i].activity) {
10267
- recentIndex = i;
10268
- break;
10269
- }
10270
- }
10271
- if (recentIndex < DASHBOARD_RECENT_LIMIT) {
10272
- recentCandidates.splice(recentIndex, 0, { session, activity });
10273
- if (recentCandidates.length > DASHBOARD_RECENT_LIMIT) recentCandidates.pop();
12745
+ for (const node of scopedEntries(sessions, scope, from, to)) accumulate(node, acc);
12746
+ let previous;
12747
+ if (compare) {
12748
+ const compareAcc = createAccumulator(byAgentNames, scope, compare.to);
12749
+ for (const node of scopedEntries(sessions, scope, compare.from, compare.to)) {
12750
+ accumulate(node, compareAcc);
10274
12751
  }
12752
+ previous = toPreviousTotals(compareAcc);
10275
12753
  }
10276
- const perAgent = [...agentMetrics.entries()].map(([name, metrics]) => {
10277
- const info = agentInfoMap?.get(name);
12754
+ const perAgent = [...acc.agents.values()].map((metrics) => {
12755
+ const info = agentInfoMap?.get(metrics.name);
10278
12756
  return {
10279
- name,
10280
- displayName: info?.displayName ?? name,
12757
+ name: metrics.name,
12758
+ displayName: info?.displayName ?? metrics.name,
10281
12759
  icon: info?.icon ?? "",
10282
12760
  iconColored: info?.iconColored,
10283
12761
  sessions: metrics.sessions,
10284
12762
  messages: metrics.messages,
10285
- tokens: metrics.tokens
12763
+ tokens: metrics.tokens,
12764
+ cost: metrics.cost
10286
12765
  };
10287
12766
  }).filter((item) => item.sessions > 0).sort((a, b) => b.sessions - a.sessions);
10288
- const dailyActivity = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
10289
- const dailyTokenActivity = [...dailyTokenMap.values()].sort(
10290
- (a, b) => a.date.localeCompare(b.date)
12767
+ const dailyActivity = [...acc.daily.values()].sort((a, b) => a.date.localeCompare(b.date));
12768
+ const modelDistribution = [...acc.models.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
12769
+ const rankedProjects = [...acc.projects.values()].sort((a, b) => b.cost - a.cost);
12770
+ const perProject = rankedProjects.slice(0, DASHBOARD_PROJECT_LIMIT).map(toProjectStat);
12771
+ const projectRollup = rankedProjects.slice(DASHBOARD_PROJECT_LIMIT).reduce(
12772
+ (rollup, project) => ({
12773
+ projects: rollup.projects + 1,
12774
+ sessions: rollup.sessions + project.sessions,
12775
+ tokens: rollup.tokens + project.tokens,
12776
+ cost: rollup.cost + project.cost
12777
+ }),
12778
+ { projects: 0, sessions: 0, tokens: 0, cost: 0 }
10291
12779
  );
10292
- const modelDistribution = [...modelAgg.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
10293
- const recentSessions = recentCandidates.map(({ session }) => {
10294
- const agentKey = getSessionAgentName(session);
10295
- return {
10296
- reference: { agentName: agentKey, sessionId: session.id },
10297
- session
10298
- };
10299
- });
12780
+ const recentSessions = acc.recent.map(({ session }) => ({
12781
+ reference: { agentName: getSessionAgentName(session), sessionId: session.id },
12782
+ session
12783
+ }));
12784
+ const latest = acc.recent[0];
10300
12785
  return {
10301
12786
  totals: {
10302
- sessions: totalSessions,
10303
- messages: totalMessages,
10304
- tokens: totalTokens,
10305
- cost: totalCost,
10306
- cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0,
10307
- latestActivity: latestActivity || void 0
12787
+ sessions: acc.sessions,
12788
+ messages: acc.messages,
12789
+ tokens: acc.tokens,
12790
+ cost: acc.cost,
12791
+ costRecorded: acc.costRecorded,
12792
+ costEstimated: acc.costEstimated,
12793
+ cacheReadTokens: acc.cacheReadTokens,
12794
+ cost_source: acc.cost > 0 ? acc.hasEstimatedCost ? "estimated" : "recorded" : void 0,
12795
+ latestActivity: latest?.activity || void 0,
12796
+ latestActivityProject: latest?.session.project_identity?.displayName,
12797
+ latestActivityAgent: latest ? getSessionAgentName(latest.session) : void 0,
12798
+ previous
10308
12799
  },
12800
+ scopeCounts: { projects: acc.projects.size, agents: acc.agentKeys.size },
10309
12801
  perAgent,
10310
12802
  dailyActivity,
10311
- dailyTokenActivity,
10312
12803
  modelDistribution,
12804
+ perProject,
12805
+ projectRollup,
10313
12806
  recentSessions
10314
12807
  };
10315
12808
  }
10316
12809
  function emptyMetrics() {
10317
- return { messages: 0, tokens: 0, cost: 0, hasEstimatedCost: false, agentStats: /* @__PURE__ */ new Map() };
12810
+ return {
12811
+ sessions: 0,
12812
+ messages: 0,
12813
+ tokens: 0,
12814
+ cost: 0,
12815
+ hasEstimatedCost: false,
12816
+ agentStats: /* @__PURE__ */ new Map()
12817
+ };
10318
12818
  }
10319
12819
  function attachProjectMetrics(projects, sessions) {
10320
12820
  const metrics = /* @__PURE__ */ new Map();
10321
- for (const session of sessions) {
10322
- const identity = session.project_identity;
12821
+ for (const node of buildSessionTree(sessions).entries) {
12822
+ const identity = node.session.project_identity;
10323
12823
  if (!identity) continue;
10324
12824
  const key = getProjectIdentityKey(identity);
10325
12825
  let current = metrics.get(key);
@@ -10327,26 +12827,26 @@ function attachProjectMetrics(projects, sessions) {
10327
12827
  current = emptyMetrics();
10328
12828
  metrics.set(key, current);
10329
12829
  }
10330
- const tokens = getTotalTokens(session.stats);
10331
- const cost = session.stats.total_cost ?? 0;
10332
- current.messages += session.stats.message_count;
10333
- current.tokens += tokens;
10334
- current.cost += cost;
10335
- if (session.stats.cost_source === "estimated") current.hasEstimatedCost = true;
10336
- const agentName = getSessionAgentName(session);
12830
+ const stats = node.inclusiveStats;
12831
+ current.sessions += 1;
12832
+ current.messages += stats.messageCount;
12833
+ current.tokens += stats.totalTokens;
12834
+ current.cost += stats.cost;
12835
+ if (stats.costSource === "estimated") current.hasEstimatedCost = true;
12836
+ const agentName = getSessionAgentName(node.session);
10337
12837
  const agent = current.agentStats.get(agentName);
10338
12838
  if (agent) {
10339
12839
  agent.sessions += 1;
10340
- agent.messages += session.stats.message_count;
10341
- agent.tokens += tokens;
10342
- agent.cost += cost;
12840
+ agent.messages += stats.messageCount;
12841
+ agent.tokens += stats.totalTokens;
12842
+ agent.cost += stats.cost;
10343
12843
  } else {
10344
12844
  current.agentStats.set(agentName, {
10345
12845
  name: agentName,
10346
12846
  sessions: 1,
10347
- messages: session.stats.message_count,
10348
- tokens,
10349
- cost
12847
+ messages: stats.messageCount,
12848
+ tokens: stats.totalTokens,
12849
+ cost: stats.cost
10350
12850
  });
10351
12851
  }
10352
12852
  }
@@ -10356,6 +12856,7 @@ function attachProjectMetrics(projects, sessions) {
10356
12856
  );
10357
12857
  return {
10358
12858
  ...project,
12859
+ sessionCount: metric?.sessions ?? 0,
10359
12860
  messages: metric?.messages ?? 0,
10360
12861
  tokens: metric?.tokens ?? 0,
10361
12862
  cost: metric?.cost ?? 0,
@@ -10501,6 +13002,10 @@ export {
10501
13002
  getSessionAgentKey,
10502
13003
  sessionRoutePath,
10503
13004
  mergeSortedSessions,
13005
+ getSessionRouteKey,
13006
+ isChildSession,
13007
+ getRootSessions,
13008
+ filterSessionTreeByActivityWindow,
10504
13009
  registerAgent,
10505
13010
  createRegisteredAgents,
10506
13011
  getRegisteredAgents,
@@ -10529,6 +13034,9 @@ export {
10529
13034
  loadCachedSessions,
10530
13035
  isAgentCacheInitialized,
10531
13036
  markAgentCacheInitialized,
13037
+ markAgentFullSyncStarted,
13038
+ getAgentFullSyncCursor,
13039
+ markAgentFullSyncProgress,
10532
13040
  getAgentLastFullSyncAt,
10533
13041
  markAgentFullSyncCompleted,
10534
13042
  saveCachedSessions,
@@ -10543,6 +13051,7 @@ export {
10543
13051
  scanSessions,
10544
13052
  materializeSessionDetailResponse,
10545
13053
  listCachedProjectGroups,
13054
+ listModelCostDistribution,
10546
13055
  StateStorageUnavailableError,
10547
13056
  listBookmarks,
10548
13057
  upsertBookmark,
@@ -10558,4 +13067,4 @@ export {
10558
13067
  executeSessionSearch,
10559
13068
  filterSessionSearchCandidates
10560
13069
  };
10561
- //# sourceMappingURL=chunk-HIYT2ZJL.js.map
13070
+ //# sourceMappingURL=chunk-BEF6LLRG.js.map