codesesh 0.19.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 (66) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-G2BTNW3C.js → chunk-BEF6LLRG.js} +1230 -182
  3. package/dist/chunk-BEF6LLRG.js.map +1 -0
  4. package/dist/{chunk-KD3DGWZY.js → chunk-TIHF4T3K.js} +2 -2
  5. package/dist/{dist-ALXOBKV2.js → dist-3F467V44.js} +4 -2
  6. package/dist/index.js +53 -12
  7. package/dist/index.js.map +1 -1
  8. package/dist/scan-refresh-worker.js +2 -2
  9. package/dist/search-index-worker.js +9 -11
  10. package/dist/search-index-worker.js.map +1 -1
  11. package/dist/smart-tag-worker.js +2 -2
  12. package/dist/web/assets/ErrorBoundary-BY-N1QzJ.js +1 -0
  13. package/dist/web/assets/InteractiveReceipt-DYGyHeZd.js +1 -0
  14. package/dist/web/assets/OverviewScreen-_ZaVjhxN.js +1 -0
  15. package/dist/web/assets/PrismHighlighter-BZkkX0AU.js +6 -0
  16. package/dist/web/assets/Projects-q6cEm2dc.js +1 -0
  17. package/dist/web/assets/SearchFilterBar-CrVBpo40.js +1 -0
  18. package/dist/web/assets/SearchResultsPanel-Cg5V8Ia6.js +1 -0
  19. package/dist/web/assets/SessionDetail-BSSuddsZ.js +54 -0
  20. package/dist/web/assets/{session-title-CNhapdPJ.js → SmartTagChips-ux6idUGJ.js} +1 -1
  21. package/dist/web/assets/chunk-62JRHF6Z-BWRsEBnL.js +4 -0
  22. package/dist/web/assets/code-metrics-UYFsCC7E.js +1 -0
  23. package/dist/web/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  24. package/dist/web/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  25. package/dist/web/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  26. package/dist/web/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  27. package/dist/web/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  28. package/dist/web/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  29. package/dist/web/assets/index-C3KgxGwE.js +1410 -0
  30. package/dist/web/assets/index-DQX42cFQ.css +1 -0
  31. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  32. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  33. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  34. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  35. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  36. package/dist/web/assets/jsx-runtime-B-hcVAMW.js +1 -0
  37. package/dist/web/assets/panel-B7kuE60V.js +1 -0
  38. package/dist/web/assets/session-indexes-DmnjZ_JL.js +1 -0
  39. package/dist/web/assets/session-title-DdtlxtlR.js +1 -0
  40. package/dist/web/assets/space-grotesk-latin-ext-wght-normal-D9tNdqV9.woff2 +0 -0
  41. package/dist/web/assets/space-grotesk-latin-wght-normal-BhU9QXUp.woff2 +0 -0
  42. package/dist/web/assets/space-grotesk-vietnamese-wght-normal-D0rl6rjA.woff2 +0 -0
  43. package/dist/web/assets/utils-t2u9IbNT.js +1 -0
  44. package/dist/web/icon/agent/grok.svg +1 -0
  45. package/dist/web/index.html +43 -30
  46. package/package.json +8 -8
  47. package/dist/chunk-G2BTNW3C.js.map +0 -1
  48. package/dist/web/assets/AgentIcon-CEj7HO1m.js +0 -1
  49. package/dist/web/assets/BookmarkButton-DLEnIyeE.js +0 -1
  50. package/dist/web/assets/Dashboard-DUG0IFcy.js +0 -39
  51. package/dist/web/assets/DialogTitle-Dv6DQPIb.js +0 -1
  52. package/dist/web/assets/InteractiveReceipt-xnRqJvfd.js +0 -1
  53. package/dist/web/assets/PrismHighlighter-C7OfXHq2.js +0 -6
  54. package/dist/web/assets/Projects-BMaZc6J5.js +0 -1
  55. package/dist/web/assets/SearchFilterBar-iP6pzlZo.js +0 -1
  56. package/dist/web/assets/SearchResultsPanel-RuiLPkbP.js +0 -1
  57. package/dist/web/assets/SessionDetail-De1Wl3mu.js +0 -54
  58. package/dist/web/assets/index-B-CV7pFu.css +0 -2
  59. package/dist/web/assets/index-CQI8F_8X.js +0 -1930
  60. package/dist/web/assets/index.dom-CCLJ9_FR.js +0 -1
  61. package/dist/web/assets/jsx-runtime-n5LQ9ujS.js +0 -1
  62. package/dist/web/assets/session-indexes-BuxBafvh.js +0 -4
  63. package/dist/web/assets/useTheme-CwDcVdGz.js +0 -1
  64. package/dist/web/assets/utils-B6KiDbIe.js +0 -1
  65. /package/dist/{chunk-KD3DGWZY.js.map → chunk-TIHF4T3K.js.map} +0 -0
  66. /package/dist/{dist-ALXOBKV2.js.map → dist-3F467V44.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../core/dist/chunk-B67H5WZJ.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
  }
@@ -253,14 +253,20 @@ function mergeSortedSessions(shards) {
253
253
  }
254
254
  return merged;
255
255
  }
256
- function referenceKey(reference) {
257
- return `${reference.agentName.trim().toLowerCase()}\0${reference.sessionId}`;
256
+ function getSessionRouteKey(agentName, sessionId) {
257
+ return formatSessionReference({ agentName, sessionId });
258
258
  }
259
259
  function sessionKey(session) {
260
- return `${getSessionAgentKey(session)}\0${session.id}`;
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;
261
267
  }
262
268
  function hasActivityInWindow(session, from, to) {
263
- const activity = session.time_updated ?? session.time_created;
269
+ const activity = activityTime(session);
264
270
  return (from == null || activity >= from) && (to == null || activity <= to);
265
271
  }
266
272
  function isChildSession(session) {
@@ -269,20 +275,129 @@ function isChildSession(session) {
269
275
  function getRootSessions(sessions) {
270
276
  return sessions.filter((session) => !session.parent_reference);
271
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
+ }
272
385
  function filterSessionTreeByActivityWindow(sessions, from, to) {
273
386
  if (from == null && to == null) return sessions;
274
387
  const available = new Set(sessions.map(sessionKey));
275
388
  const childrenByParent = /* @__PURE__ */ new Map();
389
+ const pending2 = [];
276
390
  for (const session of sessions) {
277
- const parent = session.parent_reference;
278
- if (!parent || !available.has(referenceKey(parent))) continue;
279
- const parentKey = referenceKey(parent);
280
- const children = childrenByParent.get(parentKey);
281
- if (children) children.push(sessionKey(session));
282
- else childrenByParent.set(parentKey, [sessionKey(session)]);
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));
283
399
  }
284
400
  const visible = /* @__PURE__ */ new Set();
285
- const pending2 = getRootSessions(sessions).filter((session) => hasActivityInWindow(session, from, to)).map(sessionKey);
286
401
  while (pending2.length > 0) {
287
402
  const key = pending2.pop();
288
403
  if (visible.has(key)) continue;
@@ -339,9 +454,15 @@ var SAMPLE_DASHBOARD_DATA = {
339
454
  messages: 12,
340
455
  tokens: 6e3,
341
456
  cost: 0.042,
457
+ costRecorded: 0.042,
458
+ costEstimated: 0,
459
+ cacheReadTokens: 3e3,
342
460
  cost_source: "recorded",
343
- latestActivity: 17000036e5
461
+ latestActivity: 17000036e5,
462
+ latestActivityProject: "example/project",
463
+ latestActivityAgent: "claudecode"
344
464
  },
465
+ scopeCounts: { projects: 1, agents: 1 },
345
466
  perAgent: [
346
467
  {
347
468
  name: "claudecode",
@@ -349,14 +470,38 @@ var SAMPLE_DASHBOARD_DATA = {
349
470
  icon: "claude",
350
471
  sessions: 1,
351
472
  messages: 12,
352
- tokens: 6e3
473
+ tokens: 6e3,
474
+ cost: 0.042
353
475
  }
354
476
  ],
355
- dailyActivity: [{ date: "2023-11-14", sessions: 1, messages: 12 }],
356
- dailyTokenActivity: [
357
- { 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
+ }
358
488
  ],
359
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 },
360
505
  recentSessions: [
361
506
  {
362
507
  reference: { agentName: "claudecode", sessionId: SAMPLE_SESSION_HEAD.id },
@@ -364,6 +509,7 @@ var SAMPLE_DASHBOARD_DATA = {
364
509
  }
365
510
  ],
366
511
  recentFileActivities: [],
512
+ modelCost: null,
367
513
  window: { from: 16999e8, to: 17000036e5, days: 1 }
368
514
  };
369
515
 
@@ -409,19 +555,21 @@ import { existsSync as existsSync11 } from "fs";
409
555
  import { basename as basename8, join as join12 } from "path";
410
556
  import { homedir as homedir4, platform as platform3 } from "os";
411
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";
412
560
  import { availableParallelism } from "os";
413
561
  import { Worker } from "worker_threads";
414
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
562
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
415
563
  import { spawnSync } from "child_process";
416
564
  import * as os from "os";
417
565
  import * as path from "path";
418
566
  import { resolve as resolve2, sep } from "path";
419
- import { existsSync as existsSync14, rmSync as rmSync2, unlinkSync } from "fs";
420
- import { existsSync as existsSync13 } from "fs";
421
- import { join as join14 } from "path";
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";
422
570
  import { homedir as homedir6 } from "os";
423
571
  import { homedir as homedir7, platform as platform4 } from "os";
424
- import { join as join15 } from "path";
572
+ import { join as join16 } from "path";
425
573
  var registrations = [];
426
574
  function registerAgent(reg) {
427
575
  registrations.push(reg);
@@ -480,9 +628,9 @@ function filteredSession(reason) {
480
628
  function getParsedSession(result) {
481
629
  return result.status === "parsed" ? result.data : null;
482
630
  }
483
- function matchesScanWindow(activityTime, options) {
484
- if (options?.from != null && activityTime < options.from) return false;
485
- 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;
486
634
  return true;
487
635
  }
488
636
  function readCachedMeta(meta, sessionId) {
@@ -2491,7 +2639,7 @@ function runSchemaMigrations(db, options) {
2491
2639
  migration.migrate(db);
2492
2640
  setUserVersion(db, migration.version);
2493
2641
  });
2494
- apply();
2642
+ apply.immediate();
2495
2643
  currentVersion = migration.version;
2496
2644
  getCoreDiagnostics()?.info?.("sqlite.migration.completed", {
2497
2645
  label: options.backupLabel,
@@ -2545,10 +2693,10 @@ function openDb(dbPath) {
2545
2693
  const db = DatabaseConstructor(dbPath);
2546
2694
  restrictPrivateDatabase(dbPath);
2547
2695
  try {
2696
+ db.pragma("busy_timeout = 5000");
2548
2697
  db.pragma("journal_mode = WAL");
2549
2698
  db.pragma("synchronous = NORMAL");
2550
2699
  db.pragma("foreign_keys = ON");
2551
- db.pragma("busy_timeout = 5000");
2552
2700
  } catch {
2553
2701
  }
2554
2702
  return db;
@@ -5506,13 +5654,13 @@ var CodexAgent = class extends SingleFileSessionSource {
5506
5654
  const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
5507
5655
  if (planMatch) {
5508
5656
  const planText2 = planMatch[1].trim();
5509
- const planPart = {
5657
+ const planPart2 = {
5510
5658
  type: "plan",
5511
5659
  text: planText2,
5512
5660
  approval_status: "success",
5513
5661
  time_created: timestampMs
5514
5662
  };
5515
- pendingPlan = planPart;
5663
+ pendingPlan = planPart2;
5516
5664
  }
5517
5665
  const displayText = cleanInternalText(fullText.replace(PROPOSED_PLAN_PATTERN, ""));
5518
5666
  if (!displayText) return pendingPlan;
@@ -7081,6 +7229,726 @@ var ZCodeAgent = class extends OpenCodeSqliteAgent {
7081
7229
  });
7082
7230
  }
7083
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" }]
7860
+ };
7861
+ }
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
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;
7922
+ }
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);
7927
+ return {
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
7939
+ };
7940
+ }
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
+ ]);
7950
+ }
7951
+ };
7084
7952
  registerAgent({
7085
7953
  icon: "/icon/agent/claudecode.svg",
7086
7954
  iconColored: true,
@@ -7124,6 +7992,13 @@ registerAgent({
7124
7992
  toolStrategy: "custom",
7125
7993
  create: () => new CodexAgent()
7126
7994
  });
7995
+ registerAgent({
7996
+ icon: "/icon/agent/grok.svg",
7997
+ resolveDataRoot: resolveGrokDataRoot,
7998
+ resumeCommandPrefix: "grok --resume",
7999
+ toolStrategy: "custom",
8000
+ create: () => new GrokAgent()
8001
+ });
7127
8002
  registerAgent({
7128
8003
  icon: "/icon/agent/pi.svg",
7129
8004
  resolveDataRoot: resolvePiDataRoot,
@@ -7146,11 +8021,11 @@ function fallbackDisplayName(input) {
7146
8021
  }
7147
8022
  var realFs = {
7148
8023
  exists(path2) {
7149
- return existsSync12(path2);
8024
+ return existsSync13(path2);
7150
8025
  },
7151
8026
  readText(path2) {
7152
8027
  try {
7153
- return readFileSync7(path2, "utf8");
8028
+ return readFileSync8(path2, "utf8");
7154
8029
  } catch {
7155
8030
  return null;
7156
8031
  }
@@ -7320,8 +8195,9 @@ function parseManifestName(file, text) {
7320
8195
  }
7321
8196
  function buildProjectGroups(sessions) {
7322
8197
  const groups = /* @__PURE__ */ new Map();
8198
+ const tree = buildSessionTree(sessions);
7323
8199
  for (const session of sessions) {
7324
- if (isChildSession(session)) continue;
8200
+ if (tree.mountStateOf(session) === "mounted-child") continue;
7325
8201
  const identity = session.project_identity;
7326
8202
  if (!identity) continue;
7327
8203
  const activity = session.time_updated ?? session.time_created;
@@ -7649,16 +8525,16 @@ function setSchemaEnsuredPath(path2) {
7649
8525
  schemaEnsuredPath = path2;
7650
8526
  }
7651
8527
  function getCacheDir2() {
7652
- return join14(homedir6(), ".cache", "codesesh");
8528
+ return join15(homedir6(), ".cache", "codesesh");
7653
8529
  }
7654
8530
  function getCachePath2() {
7655
- return join14(getCacheDir2(), CACHE_FILENAME);
8531
+ return join15(getCacheDir2(), CACHE_FILENAME);
7656
8532
  }
7657
8533
  function getLegacyCachePath() {
7658
- return join14(getCacheDir2(), LEGACY_CACHE_FILENAME);
8534
+ return join15(getCacheDir2(), LEGACY_CACHE_FILENAME);
7659
8535
  }
7660
8536
  function hasCacheStorage() {
7661
- return existsSync13(getCachePath2());
8537
+ return existsSync14(getCachePath2());
7662
8538
  }
7663
8539
  function likePattern(value) {
7664
8540
  return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
@@ -7806,7 +8682,7 @@ function prepareUpsertIndexedSession(db) {
7806
8682
  }
7807
8683
  function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, sourcePath) {
7808
8684
  const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
7809
- const activityTime = session.time_updated ?? session.time_created;
8685
+ const activityTime2 = session.time_updated ?? session.time_created;
7810
8686
  statement.run(
7811
8687
  agentName,
7812
8688
  session.id,
@@ -7822,7 +8698,7 @@ function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, so
7822
8698
  identity.displayName,
7823
8699
  session.time_created,
7824
8700
  session.time_updated ?? null,
7825
- activityTime,
8701
+ activityTime2,
7826
8702
  session.stats.message_count,
7827
8703
  session.stats.total_input_tokens,
7828
8704
  session.stats.total_output_tokens,
@@ -8130,7 +9006,9 @@ function withCacheDb(fn) {
8130
9006
  return fn(db);
8131
9007
  } catch (error) {
8132
9008
  getCoreDiagnostics()?.warn("cache.write_failed", {
8133
- 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
8134
9012
  });
8135
9013
  return null;
8136
9014
  } finally {
@@ -8171,7 +9049,7 @@ function runSearchIndexWrite(db, rebuild, write) {
8171
9049
  createMessageSearchTriggers(db);
8172
9050
  }
8173
9051
  return { value, rebuildDurationMs };
8174
- })();
9052
+ }).immediate();
8175
9053
  }
8176
9054
  function createCacheTables(db) {
8177
9055
  db.exec(`
@@ -9182,7 +10060,7 @@ function ensureSchema(db, dbPath) {
9182
10060
  ]
9183
10061
  });
9184
10062
  createLatestCacheSchema(db);
9185
- if (getUserVersion(db) <= CACHE_SCHEMA_VERSION) {
10063
+ if (getUserVersion(db) < CACHE_SCHEMA_VERSION) {
9186
10064
  setCacheSchemaVersion(db);
9187
10065
  }
9188
10066
  migrateCodexExecDecode(db);
@@ -10240,7 +11118,7 @@ function parseCachedSessionMeta(value) {
10240
11118
  }
10241
11119
  function deleteLegacyCacheFile() {
10242
11120
  const legacyPath = getLegacyCachePath();
10243
- if (!existsSync14(legacyPath)) {
11121
+ if (!existsSync15(legacyPath)) {
10244
11122
  return;
10245
11123
  }
10246
11124
  try {
@@ -10532,7 +11410,7 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
10532
11410
  );
10533
11411
  });
10534
11412
  });
10535
- write();
11413
+ write.immediate();
10536
11414
  deleteLegacyCacheFile();
10537
11415
  return true;
10538
11416
  });
@@ -10583,7 +11461,7 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
10583
11461
  );
10584
11462
  }
10585
11463
  });
10586
- write();
11464
+ write.immediate();
10587
11465
  deleteLegacyCacheFile();
10588
11466
  return true;
10589
11467
  });
@@ -10613,7 +11491,7 @@ function clearCache() {
10613
11491
  const walPath = `${cachePath}-wal`;
10614
11492
  const shmPath = `${cachePath}-shm`;
10615
11493
  for (const filePath of [walPath, shmPath]) {
10616
- if (!existsSync14(filePath)) {
11494
+ if (!existsSync15(filePath)) {
10617
11495
  continue;
10618
11496
  }
10619
11497
  try {
@@ -11184,6 +12062,66 @@ function listCachedProjectGroups(sessions) {
11184
12062
  lastActivity: row.last_activity == null ? null : Number(row.last_activity)
11185
12063
  }));
11186
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
+ }
11187
12125
  var STATE_DB_FILENAME = "state.db";
11188
12126
  var STATE_SCHEMA_VERSION = 2;
11189
12127
  var MEMORY_STATE_STORE = "memory";
@@ -11204,16 +12142,16 @@ function getStateDir() {
11204
12142
  if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
11205
12143
  const currentPlatform = platform4();
11206
12144
  if (currentPlatform === "darwin") {
11207
- return join15(homedir7(), "Library", "Application Support", "codesesh");
12145
+ return join16(homedir7(), "Library", "Application Support", "codesesh");
11208
12146
  }
11209
12147
  if (currentPlatform === "win32") {
11210
12148
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
11211
- return join15(appData ?? join15(homedir7(), "AppData", "Roaming"), "codesesh");
12149
+ return join16(appData ?? join16(homedir7(), "AppData", "Roaming"), "codesesh");
11212
12150
  }
11213
- return join15(process.env.XDG_DATA_HOME ?? join15(homedir7(), ".local", "share"), "codesesh");
12151
+ return join16(process.env.XDG_DATA_HOME ?? join16(homedir7(), ".local", "share"), "codesesh");
11214
12152
  }
11215
12153
  function getStateDbPath() {
11216
- return join15(getStateDir(), STATE_DB_FILENAME);
12154
+ return join16(getStateDir(), STATE_DB_FILENAME);
11217
12155
  }
11218
12156
  function useMemoryStateStore() {
11219
12157
  return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
@@ -11503,7 +12441,7 @@ function importBookmarks(bookmarks) {
11503
12441
  );
11504
12442
  }
11505
12443
  });
11506
- write();
12444
+ write.immediate();
11507
12445
  const rows = db.prepare(
11508
12446
  `
11509
12447
  SELECT
@@ -11621,160 +12559,267 @@ function deleteSessionAlias(reference) {
11621
12559
  });
11622
12560
  }
11623
12561
  var DASHBOARD_RECENT_LIMIT = 10;
11624
- function getTotalTokens(stats) {
11625
- return stats.total_tokens ?? stats.total_input_tokens + stats.total_output_tokens;
11626
- }
12562
+ var DASHBOARD_PROJECT_LIMIT = 12;
12563
+ var PROJECT_SPARKLINE_DAYS = 14;
11627
12564
  function getSessionAgentName(session) {
11628
12565
  return getSessionAgentKey(session);
11629
12566
  }
11630
12567
  function getSessionActivityTime(session) {
11631
12568
  return session.time_updated ?? session.time_created;
11632
12569
  }
11633
- function buildDashboard(sessions, options) {
11634
- const { byAgentNames, scope, from, to, agentInfoMap } = options;
11635
- const agentMetrics = /* @__PURE__ */ new Map();
11636
- 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();
11637
12594
  for (const name of byAgentNames) {
11638
- if (scope.agent && name.toLowerCase() !== scope.agent) continue;
11639
- agentMetrics.set(name, { sessions: 0, messages: 0, tokens: 0 });
11640
- 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 });
11641
12598
  }
11642
- let totalSessions = 0;
11643
- let totalMessages = 0;
11644
- let totalTokens = 0;
11645
- let totalCost = 0;
11646
- let hasEstimatedCost = false;
11647
- let latestActivity = 0;
11648
- const recentCandidates = [];
11649
- const modelAgg = /* @__PURE__ */ new Map();
11650
- const dailyMap = /* @__PURE__ */ new Map();
11651
- 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);
11652
12738
  if (from != null) {
11653
12739
  const bucketDays = countCalendarDays(from, to);
11654
12740
  for (let i = 0; i < bucketDays; i += 1) {
11655
12741
  const key = toCalendarDayKey(addCalendarDays(from, i));
11656
- dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
11657
- dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
11658
- }
11659
- }
11660
- const visibleSessions = filterSessionTreeByActivityWindow(sessions, from, to);
11661
- for (const session of visibleSessions) {
11662
- if (isChildSession(session)) continue;
11663
- const agentName = getSessionAgentName(session);
11664
- if (scope.agent && agentName !== scope.agent) continue;
11665
- if (scope.projectKind || scope.projectKey) {
11666
- const identity = session.project_identity;
11667
- if (!identity || !scope.projectKind || !scope.projectKey || identity.kind !== scope.projectKind || identity.key !== scope.projectKey) {
11668
- continue;
11669
- }
11670
- }
11671
- const activity = getSessionActivityTime(session);
11672
- const messageCount = session.stats.message_count;
11673
- const sessionTokens = getTotalTokens(session.stats);
11674
- totalSessions += 1;
11675
- totalMessages += messageCount;
11676
- totalTokens += sessionTokens;
11677
- totalCost += session.stats.total_cost ?? 0;
11678
- if (session.stats.cost_source === "estimated") hasEstimatedCost = true;
11679
- if (activity > latestActivity) latestActivity = activity;
11680
- const metricKey = agentMetricKeyByName.get(agentName);
11681
- if (metricKey) {
11682
- const metric = agentMetrics.get(metricKey);
11683
- metric.sessions += 1;
11684
- metric.messages += messageCount;
11685
- metric.tokens += sessionTokens;
11686
- }
11687
- const key = toCalendarDayKey(activity);
11688
- let bucket = dailyMap.get(key);
11689
- if (!bucket) {
11690
- bucket = { date: key, sessions: 0, messages: 0 };
11691
- dailyMap.set(key, bucket);
11692
- }
11693
- bucket.sessions += 1;
11694
- bucket.messages += messageCount;
11695
- let tokenBucket = dailyTokenMap.get(key);
11696
- if (!tokenBucket) {
11697
- tokenBucket = { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 };
11698
- dailyTokenMap.set(key, tokenBucket);
11699
- }
11700
- const cacheRead = session.stats.total_cache_read_tokens ?? 0;
11701
- const cacheCreate = session.stats.total_cache_create_tokens ?? 0;
11702
- const pureInput = session.stats.total_input_tokens - cacheRead - cacheCreate;
11703
- tokenBucket.input += Math.max(0, pureInput);
11704
- tokenBucket.output += session.stats.total_output_tokens;
11705
- tokenBucket.cache_read += cacheRead;
11706
- tokenBucket.cache_create += cacheCreate;
11707
- if (session.model_usage) {
11708
- for (const [model, tokens] of Object.entries(session.model_usage)) {
11709
- const entry = modelAgg.get(model);
11710
- if (entry) {
11711
- entry.tokens += tokens;
11712
- entry.sessions += 1;
11713
- } else {
11714
- modelAgg.set(model, { tokens, sessions: 1 });
11715
- }
11716
- }
12742
+ acc.daily.set(key, emptyDailyBucket(key));
11717
12743
  }
11718
- let recentIndex = recentCandidates.length;
11719
- for (let i = 0; i < recentCandidates.length; i += 1) {
11720
- if (activity > recentCandidates[i].activity) {
11721
- recentIndex = i;
11722
- break;
11723
- }
11724
- }
11725
- if (recentIndex < DASHBOARD_RECENT_LIMIT) {
11726
- recentCandidates.splice(recentIndex, 0, { session, activity });
11727
- if (recentCandidates.length > DASHBOARD_RECENT_LIMIT) recentCandidates.pop();
12744
+ }
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);
11728
12751
  }
12752
+ previous = toPreviousTotals(compareAcc);
11729
12753
  }
11730
- const perAgent = [...agentMetrics.entries()].map(([name, metrics]) => {
11731
- const info = agentInfoMap?.get(name);
12754
+ const perAgent = [...acc.agents.values()].map((metrics) => {
12755
+ const info = agentInfoMap?.get(metrics.name);
11732
12756
  return {
11733
- name,
11734
- displayName: info?.displayName ?? name,
12757
+ name: metrics.name,
12758
+ displayName: info?.displayName ?? metrics.name,
11735
12759
  icon: info?.icon ?? "",
11736
12760
  iconColored: info?.iconColored,
11737
12761
  sessions: metrics.sessions,
11738
12762
  messages: metrics.messages,
11739
- tokens: metrics.tokens
12763
+ tokens: metrics.tokens,
12764
+ cost: metrics.cost
11740
12765
  };
11741
12766
  }).filter((item) => item.sessions > 0).sort((a, b) => b.sessions - a.sessions);
11742
- const dailyActivity = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
11743
- const dailyTokenActivity = [...dailyTokenMap.values()].sort(
11744
- (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 }
11745
12779
  );
11746
- const modelDistribution = [...modelAgg.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
11747
- const recentSessions = recentCandidates.map(({ session }) => {
11748
- const agentKey = getSessionAgentName(session);
11749
- return {
11750
- reference: { agentName: agentKey, sessionId: session.id },
11751
- session
11752
- };
11753
- });
12780
+ const recentSessions = acc.recent.map(({ session }) => ({
12781
+ reference: { agentName: getSessionAgentName(session), sessionId: session.id },
12782
+ session
12783
+ }));
12784
+ const latest = acc.recent[0];
11754
12785
  return {
11755
12786
  totals: {
11756
- sessions: totalSessions,
11757
- messages: totalMessages,
11758
- tokens: totalTokens,
11759
- cost: totalCost,
11760
- cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0,
11761
- 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
11762
12799
  },
12800
+ scopeCounts: { projects: acc.projects.size, agents: acc.agentKeys.size },
11763
12801
  perAgent,
11764
12802
  dailyActivity,
11765
- dailyTokenActivity,
11766
12803
  modelDistribution,
12804
+ perProject,
12805
+ projectRollup,
11767
12806
  recentSessions
11768
12807
  };
11769
12808
  }
11770
12809
  function emptyMetrics() {
11771
- 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
+ };
11772
12818
  }
11773
12819
  function attachProjectMetrics(projects, sessions) {
11774
12820
  const metrics = /* @__PURE__ */ new Map();
11775
- for (const session of sessions) {
11776
- if (isChildSession(session)) continue;
11777
- const identity = session.project_identity;
12821
+ for (const node of buildSessionTree(sessions).entries) {
12822
+ const identity = node.session.project_identity;
11778
12823
  if (!identity) continue;
11779
12824
  const key = getProjectIdentityKey(identity);
11780
12825
  let current = metrics.get(key);
@@ -11782,26 +12827,26 @@ function attachProjectMetrics(projects, sessions) {
11782
12827
  current = emptyMetrics();
11783
12828
  metrics.set(key, current);
11784
12829
  }
11785
- const tokens = getTotalTokens(session.stats);
11786
- const cost = session.stats.total_cost ?? 0;
11787
- current.messages += session.stats.message_count;
11788
- current.tokens += tokens;
11789
- current.cost += cost;
11790
- if (session.stats.cost_source === "estimated") current.hasEstimatedCost = true;
11791
- 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);
11792
12837
  const agent = current.agentStats.get(agentName);
11793
12838
  if (agent) {
11794
12839
  agent.sessions += 1;
11795
- agent.messages += session.stats.message_count;
11796
- agent.tokens += tokens;
11797
- agent.cost += cost;
12840
+ agent.messages += stats.messageCount;
12841
+ agent.tokens += stats.totalTokens;
12842
+ agent.cost += stats.cost;
11798
12843
  } else {
11799
12844
  current.agentStats.set(agentName, {
11800
12845
  name: agentName,
11801
12846
  sessions: 1,
11802
- messages: session.stats.message_count,
11803
- tokens,
11804
- cost
12847
+ messages: stats.messageCount,
12848
+ tokens: stats.totalTokens,
12849
+ cost: stats.cost
11805
12850
  });
11806
12851
  }
11807
12852
  }
@@ -11811,6 +12856,7 @@ function attachProjectMetrics(projects, sessions) {
11811
12856
  );
11812
12857
  return {
11813
12858
  ...project,
12859
+ sessionCount: metric?.sessions ?? 0,
11814
12860
  messages: metric?.messages ?? 0,
11815
12861
  tokens: metric?.tokens ?? 0,
11816
12862
  cost: metric?.cost ?? 0,
@@ -11956,6 +13002,7 @@ export {
11956
13002
  getSessionAgentKey,
11957
13003
  sessionRoutePath,
11958
13004
  mergeSortedSessions,
13005
+ getSessionRouteKey,
11959
13006
  isChildSession,
11960
13007
  getRootSessions,
11961
13008
  filterSessionTreeByActivityWindow,
@@ -12004,6 +13051,7 @@ export {
12004
13051
  scanSessions,
12005
13052
  materializeSessionDetailResponse,
12006
13053
  listCachedProjectGroups,
13054
+ listModelCostDistribution,
12007
13055
  StateStorageUnavailableError,
12008
13056
  listBookmarks,
12009
13057
  upsertBookmark,
@@ -12019,4 +13067,4 @@ export {
12019
13067
  executeSessionSearch,
12020
13068
  filterSessionSearchCandidates
12021
13069
  };
12022
- //# sourceMappingURL=chunk-G2BTNW3C.js.map
13070
+ //# sourceMappingURL=chunk-BEF6LLRG.js.map