codesesh 1.0.2 → 1.0.4

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 (64) hide show
  1. package/README.md +18 -10
  2. package/dist/chunk-3SXCTDD6.js +326 -0
  3. package/dist/chunk-3SXCTDD6.js.map +1 -0
  4. package/dist/chunk-BYB77UAJ.js +6054 -0
  5. package/dist/chunk-BYB77UAJ.js.map +1 -0
  6. package/dist/{chunk-PROMURPM.js → chunk-G4HRD3AR.js} +13 -8
  7. package/dist/chunk-G4HRD3AR.js.map +1 -0
  8. package/dist/chunk-MBZ4VV4C.js +670 -0
  9. package/dist/chunk-MBZ4VV4C.js.map +1 -0
  10. package/dist/chunk-QH2HOXJH.js +2 -0
  11. package/dist/chunk-RF3EEWPH.js +17 -0
  12. package/dist/chunk-RF3EEWPH.js.map +1 -0
  13. package/dist/chunk-W4PZHL2C.js +2035 -0
  14. package/dist/chunk-W4PZHL2C.js.map +1 -0
  15. package/dist/chunk-YCUN52HA.js +8422 -0
  16. package/dist/chunk-YCUN52HA.js.map +1 -0
  17. package/dist/discovery-K3GLISI6.js +102 -0
  18. package/dist/discovery-K3GLISI6.js.map +1 -0
  19. package/dist/index.js +3016 -1392
  20. package/dist/index.js.map +1 -1
  21. package/dist/project-identity-worker.js +29 -0
  22. package/dist/project-identity-worker.js.map +1 -0
  23. package/dist/scan-refresh-worker.js +129 -139
  24. package/dist/scan-refresh-worker.js.map +1 -1
  25. package/dist/search-index-worker.js +39 -13
  26. package/dist/search-index-worker.js.map +1 -1
  27. package/dist/smart-tag-worker.js +8 -5
  28. package/dist/smart-tag-worker.js.map +1 -1
  29. package/dist/web/assets/InteractiveReceipt-Dk50TBsH.js +1 -0
  30. package/dist/web/assets/OverviewScreen-5hvQVp4H.js +1 -0
  31. package/dist/web/assets/{PrismHighlighter-BZkkX0AU.js → PrismHighlighter-B1a2dPJO.js} +2 -2
  32. package/dist/web/assets/Projects-Cy_v2yep.js +1 -0
  33. package/dist/web/assets/SearchResultsPanel-DfheEwpz.js +1 -0
  34. package/dist/web/assets/SessionDetail-DRI8R3st.js +53 -0
  35. package/dist/web/assets/api-GP9UCedC.js +1 -0
  36. package/dist/web/assets/contract-xJyW5u7a.js +2 -0
  37. package/dist/web/assets/index-Bd-YkWkZ.css +1 -0
  38. package/dist/web/assets/index-DEjvzumm.js +1410 -0
  39. package/dist/web/assets/panel-ND0J63d3.js +1 -0
  40. package/dist/web/assets/{session-indexes-0oT5HSS5.js → session-indexes-BsgjUoc1.js} +2 -2
  41. package/dist/web/assets/utils-C7BqyxxV.js +1 -0
  42. package/dist/web/index.html +7 -11
  43. package/package.json +4 -10
  44. package/dist/chunk-PROMURPM.js.map +0 -1
  45. package/dist/chunk-VAUC2W7I.js +0 -16119
  46. package/dist/chunk-VAUC2W7I.js.map +0 -1
  47. package/dist/dist-6QTGTYZT.js +0 -175
  48. package/dist/web/assets/ErrorBoundary-D11tWzfn.js +0 -1
  49. package/dist/web/assets/InteractiveReceipt-DYGyHeZd.js +0 -1
  50. package/dist/web/assets/OverviewScreen-DriPmCam.js +0 -1
  51. package/dist/web/assets/Projects-BPDIcUBK.js +0 -1
  52. package/dist/web/assets/SearchFilterBar-Bx73B5e9.js +0 -1
  53. package/dist/web/assets/SearchResultsPanel-A6_3TPhW.js +0 -1
  54. package/dist/web/assets/SessionDetail-BnpvDwtH.js +0 -54
  55. package/dist/web/assets/SmartTagChips-ux6idUGJ.js +0 -1
  56. package/dist/web/assets/agents-BkRP_ww3.js +0 -1
  57. package/dist/web/assets/code-metrics-UYFsCC7E.js +0 -1
  58. package/dist/web/assets/contract-V6QuD0UY.js +0 -1
  59. package/dist/web/assets/index-99ZxpV2y.js +0 -1410
  60. package/dist/web/assets/index-B2RqUgZ5.css +0 -1
  61. package/dist/web/assets/panel-C-gc79Gz.js +0 -1
  62. package/dist/web/assets/session-title-DdtlxtlR.js +0 -1
  63. package/dist/web/assets/utils-DgUgu15E.js +0 -1
  64. /package/dist/{dist-6QTGTYZT.js.map → chunk-QH2HOXJH.js.map} +0 -0
@@ -0,0 +1,670 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addCalendarDays,
4
+ buildSessionTree,
5
+ countCalendarDays,
6
+ filterSessionTreeEntriesByActivityWindow,
7
+ getProjectIdentityKey,
8
+ getSessionAgentKey,
9
+ getSessionReferenceKey,
10
+ toCalendarDayKey
11
+ } from "./chunk-W4PZHL2C.js";
12
+
13
+ // ../core/dist/chunk-OGQ63HC5.mjs
14
+ var COST_ABSOLUTE_TOLERANCE = 1e-8;
15
+ var COST_RELATIVE_TOLERANCE = 1e-6;
16
+ function indexFacts(facts) {
17
+ const messagesBySession = /* @__PURE__ */ new Map();
18
+ const summariesBySession = /* @__PURE__ */ new Map();
19
+ if (!facts) return { messagesBySession, summariesBySession };
20
+ for (const summary of facts.sessions) {
21
+ summariesBySession.set(getSessionReferenceKey(summary.reference), summary);
22
+ }
23
+ for (const message of facts.messages) {
24
+ const key = getSessionReferenceKey(message.reference);
25
+ const messages = messagesBySession.get(key);
26
+ if (messages) messages.push(message);
27
+ else messagesBySession.set(key, [message]);
28
+ }
29
+ return { messagesBySession, summariesBySession };
30
+ }
31
+ function nonNegative(value) {
32
+ return Math.max(0, Number(value ?? 0));
33
+ }
34
+ function costsReconcile(left, right) {
35
+ const tolerance = Math.max(
36
+ COST_ABSOLUTE_TOLERANCE,
37
+ Math.max(Math.abs(left), Math.abs(right)) * COST_RELATIVE_TOLERANCE
38
+ );
39
+ return Math.abs(left - right) <= tolerance;
40
+ }
41
+ function hasDetailedCost(session, summary) {
42
+ const totalCost = Math.max(0, session.stats.total_cost);
43
+ return totalCost > 0 && summary != null && summary.untimedMessageCost <= COST_ABSOLUTE_TOLERANCE && costsReconcile(summary.messageCost, totalCost);
44
+ }
45
+ function hasDetailedMessages(session, summary) {
46
+ return summary != null && summary.untimedMessageCount === 0 && summary.messageCount === nonNegative(session.stats.message_count);
47
+ }
48
+ function detailedOutputIncludesReasoning(session, summary) {
49
+ if (!summary) return null;
50
+ const stats = session.stats;
51
+ const sessionInput = nonNegative(stats.total_input_tokens);
52
+ const sessionOutput = nonNegative(stats.total_output_tokens);
53
+ const sessionCacheRead = nonNegative(stats.total_cache_read_tokens);
54
+ const sessionCacheCreate = nonNegative(stats.total_cache_create_tokens);
55
+ const sessionTotal = nonNegative(stats.total_tokens ?? sessionInput + sessionOutput);
56
+ const outputIncludesReasoning = summary.outputTokens + summary.reasoningTokens;
57
+ const outputMatches = summary.outputTokens === sessionOutput;
58
+ const outputWithReasoningMatches = outputIncludesReasoning === sessionOutput;
59
+ const effectiveOutput = outputMatches ? summary.outputTokens : outputWithReasoningMatches ? outputIncludesReasoning : null;
60
+ if (effectiveOutput == null || summary.inputTokens !== sessionInput || summary.cacheReadTokens !== sessionCacheRead || summary.cacheCreateTokens !== sessionCacheCreate || summary.inputTokens + effectiveOutput !== sessionTotal || summary.untimedInputTokens > 0 || summary.untimedOutputTokens > 0 || summary.untimedReasoningTokens > 0 || summary.untimedCacheReadTokens > 0 || summary.untimedCacheCreateTokens > 0) {
61
+ return null;
62
+ }
63
+ return !outputMatches && outputWithReasoningMatches;
64
+ }
65
+ function isInWindow(time, from, to) {
66
+ return (from == null || time >= from) && time <= to;
67
+ }
68
+ function resolvedCostSource(session, source) {
69
+ return source ?? session.stats.cost_source ?? "recorded";
70
+ }
71
+ function fallbackModelCosts(summary, totalCost) {
72
+ if (!summary) return [];
73
+ const modelTotal = summary.modelCosts.reduce((sum, model) => sum + model.cost, 0);
74
+ return modelTotal > totalCost && !costsReconcile(modelTotal, totalCost) ? [] : summary.modelCosts;
75
+ }
76
+ function visitAttributedCosts(tree, options, visit) {
77
+ const factIndex = indexFacts(options.facts);
78
+ const factsAvailable = options.facts != null;
79
+ for (const entry of tree.entries) {
80
+ if (options.matchesEntry && !options.matchesEntry(entry.session)) continue;
81
+ const fallbackTime = entry.session.time_updated ?? entry.session.time_created;
82
+ const pending = [entry];
83
+ while (pending.length > 0) {
84
+ const node = pending.pop();
85
+ for (const child of node.children) pending.push(child);
86
+ const session = node.session;
87
+ const totalCost = Math.max(0, session.stats.total_cost);
88
+ if (totalCost <= 0) continue;
89
+ const key = getSessionReferenceKey(session.reference);
90
+ const summary = factIndex.summariesBySession.get(key);
91
+ if (factsAvailable && hasDetailedCost(session, summary)) {
92
+ for (const message of factIndex.messagesBySession.get(key) ?? []) {
93
+ if (!isInWindow(message.time, options.from, options.to) || message.cost <= 0) continue;
94
+ const source = resolvedCostSource(session, message.costSource);
95
+ visit({
96
+ entry,
97
+ time: message.time,
98
+ cost: message.cost,
99
+ source,
100
+ modelCosts: message.model ? [
101
+ {
102
+ model: message.model,
103
+ cost: message.cost,
104
+ costRecorded: source === "recorded" ? message.cost : 0
105
+ }
106
+ ] : []
107
+ });
108
+ }
109
+ continue;
110
+ }
111
+ if (!isInWindow(fallbackTime, options.from, options.to)) continue;
112
+ visit({
113
+ entry,
114
+ time: fallbackTime,
115
+ cost: totalCost,
116
+ source: resolvedCostSource(session),
117
+ modelCosts: factsAvailable ? fallbackModelCosts(summary, totalCost) : []
118
+ });
119
+ }
120
+ }
121
+ }
122
+ function visitAttributedUsage(tree, options, visit) {
123
+ const factIndex = indexFacts(options.facts);
124
+ const factsAvailable = options.facts != null;
125
+ for (const entry of tree.entries) {
126
+ if (options.matchesEntry && !options.matchesEntry(entry.session)) continue;
127
+ const fallbackTime = entry.session.time_updated ?? entry.session.time_created;
128
+ const pending = [entry];
129
+ while (pending.length > 0) {
130
+ const node = pending.pop();
131
+ for (const child of node.children) pending.push(child);
132
+ const session = node.session;
133
+ const key = getSessionReferenceKey(session.reference);
134
+ const summary = factIndex.summariesBySession.get(key);
135
+ const detailedMessages = factsAvailable && hasDetailedMessages(session, summary);
136
+ const outputIncludesReasoning = factsAvailable ? detailedOutputIncludesReasoning(session, summary) : null;
137
+ const detailedTokens = outputIncludesReasoning != null;
138
+ if (detailedMessages || detailedTokens) {
139
+ for (const message of factIndex.messagesBySession.get(key) ?? []) {
140
+ if (!isInWindow(message.time, options.from, options.to)) continue;
141
+ const outputTokens = detailedTokens ? message.outputTokens + (outputIncludesReasoning ? message.reasoningTokens : 0) : 0;
142
+ visit({
143
+ entry,
144
+ time: message.time,
145
+ messages: detailedMessages ? 1 : 0,
146
+ totalTokens: detailedTokens ? message.inputTokens + outputTokens : 0,
147
+ inputTokens: detailedTokens ? message.inputTokens : 0,
148
+ outputTokens,
149
+ cacheReadTokens: detailedTokens ? message.cacheReadTokens : 0,
150
+ cacheCreateTokens: detailedTokens ? message.cacheCreateTokens : 0
151
+ });
152
+ }
153
+ }
154
+ if (!isInWindow(fallbackTime, options.from, options.to)) continue;
155
+ if (!detailedMessages) {
156
+ visit({
157
+ entry,
158
+ time: fallbackTime,
159
+ messages: nonNegative(session.stats.message_count),
160
+ totalTokens: 0,
161
+ inputTokens: 0,
162
+ outputTokens: 0,
163
+ cacheReadTokens: 0,
164
+ cacheCreateTokens: 0
165
+ });
166
+ }
167
+ if (!detailedTokens) {
168
+ const inputTokens = nonNegative(session.stats.total_input_tokens);
169
+ const outputTokens = nonNegative(session.stats.total_output_tokens);
170
+ visit({
171
+ entry,
172
+ time: fallbackTime,
173
+ messages: 0,
174
+ totalTokens: nonNegative(session.stats.total_tokens ?? inputTokens + outputTokens),
175
+ inputTokens,
176
+ outputTokens,
177
+ cacheReadTokens: nonNegative(session.stats.total_cache_read_tokens),
178
+ cacheCreateTokens: nonNegative(session.stats.total_cache_create_tokens)
179
+ });
180
+ }
181
+ }
182
+ }
183
+ }
184
+ var DASHBOARD_RECENT_LIMIT = 10;
185
+ var DASHBOARD_PROJECT_LIMIT = 12;
186
+ var PROJECT_SPARKLINE_DAYS = 14;
187
+ var MODEL_COST_LIMIT = 20;
188
+ function getSessionAgentName(session) {
189
+ return getSessionAgentKey(session);
190
+ }
191
+ function getSessionActivityTime(session) {
192
+ return session.time_updated ?? session.time_created;
193
+ }
194
+ function matchesScope(session, scope) {
195
+ if (scope.agent && getSessionAgentName(session) !== scope.agent) return false;
196
+ if (scope.projectKind == null && scope.projectKey == null) return true;
197
+ const identity = session.project_identity;
198
+ return identity != null && identity.kind === scope.projectKind && identity.key === scope.projectKey;
199
+ }
200
+ function scopedEntries(tree, scope, from, to) {
201
+ return filterSessionTreeEntriesByActivityWindow(tree, from, to).filter(
202
+ (node) => matchesScope(node.session, scope)
203
+ );
204
+ }
205
+ function emptyDailyBucket(date) {
206
+ return {
207
+ date,
208
+ sessions: 0,
209
+ messages: 0,
210
+ cost: 0,
211
+ input: 0,
212
+ output: 0,
213
+ cache_read: 0,
214
+ cache_create: 0
215
+ };
216
+ }
217
+ function createAccumulator(byAgentNames, scope, to) {
218
+ const agents = /* @__PURE__ */ new Map();
219
+ for (const name of byAgentNames) {
220
+ const key = name.toLowerCase();
221
+ if (scope.agent && key !== scope.agent) continue;
222
+ agents.set(key, { name, sessions: 0, messages: 0, tokens: 0, cost: 0 });
223
+ }
224
+ const sparklineSlots = /* @__PURE__ */ new Map();
225
+ for (let slot = 0; slot < PROJECT_SPARKLINE_DAYS; slot += 1) {
226
+ const day = addCalendarDays(to, slot - (PROJECT_SPARKLINE_DAYS - 1));
227
+ sparklineSlots.set(toCalendarDayKey(day), slot);
228
+ }
229
+ return {
230
+ sessions: 0,
231
+ messages: 0,
232
+ tokens: 0,
233
+ cost: 0,
234
+ costRecorded: 0,
235
+ costEstimated: 0,
236
+ cacheReadTokens: 0,
237
+ hasEstimatedCost: false,
238
+ agents,
239
+ agentKeys: /* @__PURE__ */ new Set(),
240
+ daily: /* @__PURE__ */ new Map(),
241
+ models: /* @__PURE__ */ new Map(),
242
+ modelCosts: /* @__PURE__ */ new Map(),
243
+ projects: /* @__PURE__ */ new Map(),
244
+ projectKeys: /* @__PURE__ */ new Set(),
245
+ sparklineSlots,
246
+ recent: []
247
+ };
248
+ }
249
+ function foldModelUsage(node, into) {
250
+ if (node.session.model_usage) {
251
+ for (const [model, tokens] of Object.entries(node.session.model_usage)) {
252
+ into.set(model, (into.get(model) ?? 0) + tokens);
253
+ }
254
+ }
255
+ for (const child of node.children) foldModelUsage(child, into);
256
+ }
257
+ function getOrCreateProject(session, acc) {
258
+ const identity = session.project_identity;
259
+ if (!identity) return void 0;
260
+ const key = getProjectIdentityKey(identity);
261
+ let project = acc.projects.get(key);
262
+ if (!project) {
263
+ project = {
264
+ identityKind: identity.kind,
265
+ identityKey: identity.key,
266
+ displayName: identity.displayName,
267
+ sessions: 0,
268
+ messages: 0,
269
+ tokens: 0,
270
+ cost: 0,
271
+ hasEstimatedCost: false,
272
+ agentSessions: /* @__PURE__ */ new Map(),
273
+ sparkline: Array.from({ length: PROJECT_SPARKLINE_DAYS }, () => 0)
274
+ };
275
+ acc.projects.set(key, project);
276
+ }
277
+ return project;
278
+ }
279
+ function trackProjectActivity(node, acc, agentKey) {
280
+ const identity = node.session.project_identity;
281
+ const project = getOrCreateProject(node.session, acc);
282
+ if (!identity || !project) return;
283
+ project.sessions += 1;
284
+ project.agentSessions.set(agentKey, (project.agentSessions.get(agentKey) ?? 0) + 1);
285
+ acc.projectKeys.add(getProjectIdentityKey(identity));
286
+ }
287
+ function accumulateActivity(node, acc) {
288
+ const session = node.session;
289
+ const activity = getSessionActivityTime(session);
290
+ const agentKey = getSessionAgentName(session);
291
+ acc.sessions += 1;
292
+ acc.agentKeys.add(agentKey);
293
+ const metric = acc.agents.get(agentKey);
294
+ if (metric) {
295
+ metric.sessions += 1;
296
+ }
297
+ const dayKey = toCalendarDayKey(activity);
298
+ let bucket = acc.daily.get(dayKey);
299
+ if (!bucket) {
300
+ bucket = emptyDailyBucket(dayKey);
301
+ acc.daily.set(dayKey, bucket);
302
+ }
303
+ bucket.sessions += 1;
304
+ const usage = /* @__PURE__ */ new Map();
305
+ foldModelUsage(node, usage);
306
+ for (const [model, tokens] of usage) {
307
+ const entry = acc.models.get(model);
308
+ if (entry) {
309
+ entry.tokens += tokens;
310
+ entry.sessions += 1;
311
+ } else {
312
+ acc.models.set(model, { tokens, sessions: 1 });
313
+ }
314
+ }
315
+ trackProjectActivity(node, acc, agentKey);
316
+ let recentIndex = acc.recent.length;
317
+ for (let i = 0; i < acc.recent.length; i += 1) {
318
+ if (activity > acc.recent[i].activity) {
319
+ recentIndex = i;
320
+ break;
321
+ }
322
+ }
323
+ if (recentIndex < DASHBOARD_RECENT_LIMIT) {
324
+ acc.recent.splice(recentIndex, 0, { session, activity });
325
+ if (acc.recent.length > DASHBOARD_RECENT_LIMIT) acc.recent.pop();
326
+ }
327
+ }
328
+ function addUsage(acc, entry, time, usage) {
329
+ const { messages, totalTokens, inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens } = usage;
330
+ if (messages <= 0 && totalTokens <= 0 && inputTokens <= 0 && outputTokens <= 0 && cacheReadTokens <= 0 && cacheCreateTokens <= 0) {
331
+ return;
332
+ }
333
+ acc.messages += messages;
334
+ acc.tokens += totalTokens;
335
+ acc.cacheReadTokens += cacheReadTokens;
336
+ const agentKey = getSessionAgentName(entry.session);
337
+ let metric = acc.agents.get(agentKey);
338
+ if (!metric) {
339
+ metric = { name: agentKey, sessions: 0, messages: 0, tokens: 0, cost: 0 };
340
+ acc.agents.set(agentKey, metric);
341
+ }
342
+ metric.messages += messages;
343
+ metric.tokens += totalTokens;
344
+ const dayKey = toCalendarDayKey(time);
345
+ let bucket = acc.daily.get(dayKey);
346
+ if (!bucket) {
347
+ bucket = emptyDailyBucket(dayKey);
348
+ acc.daily.set(dayKey, bucket);
349
+ }
350
+ bucket.messages += messages;
351
+ bucket.input += Math.max(0, inputTokens - cacheReadTokens - cacheCreateTokens);
352
+ bucket.output += outputTokens;
353
+ bucket.cache_read += cacheReadTokens;
354
+ bucket.cache_create += cacheCreateTokens;
355
+ const project = getOrCreateProject(entry.session, acc);
356
+ if (!project) return;
357
+ project.messages += messages;
358
+ project.tokens += totalTokens;
359
+ }
360
+ function accumulateUsage(tree, scope, acc, from, to, costFacts) {
361
+ visitAttributedUsage(
362
+ tree,
363
+ { from, to, facts: costFacts, matchesEntry: (session) => matchesScope(session, scope) },
364
+ ({ entry, time, ...usage }) => addUsage(acc, entry, time, usage)
365
+ );
366
+ }
367
+ function addModelCost(acc, model, cost, recordedCost) {
368
+ if (!model || cost <= 0) return;
369
+ const recorded = Math.max(0, Math.min(cost, recordedCost));
370
+ const current = acc.modelCosts.get(model);
371
+ if (current) {
372
+ current.cost += cost;
373
+ current.costRecorded += recorded;
374
+ current.costEstimated += cost - recorded;
375
+ return;
376
+ }
377
+ acc.modelCosts.set(model, {
378
+ model,
379
+ cost,
380
+ costRecorded: recorded,
381
+ costEstimated: cost - recorded
382
+ });
383
+ }
384
+ function addCost(acc, entry, time, cost, source) {
385
+ if (cost <= 0) return;
386
+ acc.cost += cost;
387
+ if (source === "recorded") acc.costRecorded += cost;
388
+ else {
389
+ acc.costEstimated += cost;
390
+ acc.hasEstimatedCost = true;
391
+ }
392
+ const agentKey = getSessionAgentName(entry.session);
393
+ let metric = acc.agents.get(agentKey);
394
+ if (!metric) {
395
+ metric = { name: agentKey, sessions: 0, messages: 0, tokens: 0, cost: 0 };
396
+ acc.agents.set(agentKey, metric);
397
+ }
398
+ metric.cost += cost;
399
+ const dayKey = toCalendarDayKey(time);
400
+ let bucket = acc.daily.get(dayKey);
401
+ if (!bucket) {
402
+ bucket = emptyDailyBucket(dayKey);
403
+ acc.daily.set(dayKey, bucket);
404
+ }
405
+ bucket.cost += cost;
406
+ const project = getOrCreateProject(entry.session, acc);
407
+ if (!project) return;
408
+ project.cost += cost;
409
+ if (source === "estimated") project.hasEstimatedCost = true;
410
+ const slot = acc.sparklineSlots.get(dayKey);
411
+ if (slot != null) project.sparkline[slot] += cost;
412
+ }
413
+ function accumulateCosts(tree, scope, acc, from, to, costFacts) {
414
+ visitAttributedCosts(
415
+ tree,
416
+ { from, to, facts: costFacts, matchesEntry: (session) => matchesScope(session, scope) },
417
+ ({ entry, time, cost, source, modelCosts }) => {
418
+ addCost(acc, entry, time, cost, source);
419
+ for (const model of modelCosts) {
420
+ addModelCost(acc, model.model, model.cost, model.costRecorded);
421
+ }
422
+ }
423
+ );
424
+ }
425
+ function toPreviousTotals(acc) {
426
+ return { sessions: acc.sessions, messages: acc.messages, tokens: acc.tokens, cost: acc.cost };
427
+ }
428
+ function toProjectStat(project) {
429
+ return {
430
+ identityKind: project.identityKind,
431
+ identityKey: project.identityKey,
432
+ displayName: project.displayName,
433
+ sessions: project.sessions,
434
+ messages: project.messages,
435
+ tokens: project.tokens,
436
+ cost: project.cost,
437
+ cost_source: project.cost > 0 ? project.hasEstimatedCost ? "estimated" : "recorded" : void 0,
438
+ agents: [...project.agentSessions.entries()].sort((a, b) => b[1] - a[1]).map(([agentKey]) => agentKey),
439
+ sparkline: project.sparkline
440
+ };
441
+ }
442
+ function buildDashboard(sessions, options) {
443
+ const { byAgentNames, scope, from, to, agentInfoMap, compare, costFacts } = options;
444
+ const tree = buildSessionTree(sessions);
445
+ const costFactsAvailable = costFacts != null;
446
+ const acc = createAccumulator(byAgentNames, scope, to);
447
+ if (from != null) {
448
+ const bucketDays = countCalendarDays(from, to);
449
+ for (let i = 0; i < bucketDays; i += 1) {
450
+ const key = toCalendarDayKey(addCalendarDays(from, i));
451
+ acc.daily.set(key, emptyDailyBucket(key));
452
+ }
453
+ }
454
+ for (const node of scopedEntries(tree, scope, from, to)) accumulateActivity(node, acc);
455
+ accumulateUsage(tree, scope, acc, from, to, costFacts);
456
+ accumulateCosts(tree, scope, acc, from, to, costFacts);
457
+ let previous;
458
+ if (compare) {
459
+ const compareAcc = createAccumulator(byAgentNames, scope, compare.to);
460
+ for (const node of scopedEntries(tree, scope, compare.from, compare.to)) {
461
+ accumulateActivity(node, compareAcc);
462
+ }
463
+ accumulateUsage(tree, scope, compareAcc, compare.from, compare.to, costFacts);
464
+ accumulateCosts(tree, scope, compareAcc, compare.from, compare.to, costFacts);
465
+ previous = toPreviousTotals(compareAcc);
466
+ }
467
+ const perAgent = [...acc.agents.values()].map((metrics) => {
468
+ const info = agentInfoMap?.get(metrics.name);
469
+ return {
470
+ name: metrics.name,
471
+ displayName: info?.displayName ?? metrics.name,
472
+ icon: info?.icon ?? "",
473
+ iconColored: info?.iconColored,
474
+ sessions: metrics.sessions,
475
+ messages: metrics.messages,
476
+ tokens: metrics.tokens,
477
+ cost: metrics.cost
478
+ };
479
+ }).filter((item) => item.sessions > 0 || item.messages > 0 || item.tokens > 0 || item.cost > 0).sort((a, b) => b.sessions - a.sessions || b.cost - a.cost);
480
+ const dailyActivity = [...acc.daily.values()].sort((a, b) => a.date.localeCompare(b.date));
481
+ const modelDistribution = [...acc.models.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
482
+ const modelCost = costFactsAvailable ? [...acc.modelCosts.values()].sort((a, b) => b.cost - a.cost).slice(0, MODEL_COST_LIMIT) : null;
483
+ const rankedProjects = [...acc.projects.values()].sort((a, b) => b.cost - a.cost);
484
+ const perProject = rankedProjects.slice(0, DASHBOARD_PROJECT_LIMIT).map(toProjectStat);
485
+ const projectRollup = rankedProjects.slice(DASHBOARD_PROJECT_LIMIT).reduce(
486
+ (rollup, project) => ({
487
+ projects: rollup.projects + 1,
488
+ sessions: rollup.sessions + project.sessions,
489
+ tokens: rollup.tokens + project.tokens,
490
+ cost: rollup.cost + project.cost
491
+ }),
492
+ { projects: 0, sessions: 0, tokens: 0, cost: 0 }
493
+ );
494
+ const recentSessions = acc.recent.map(({ session }) => ({
495
+ reference: session.reference,
496
+ session
497
+ }));
498
+ const latest = acc.recent[0];
499
+ return {
500
+ totals: {
501
+ sessions: acc.sessions,
502
+ messages: acc.messages,
503
+ tokens: acc.tokens,
504
+ cost: acc.cost,
505
+ costRecorded: acc.costRecorded,
506
+ costEstimated: acc.costEstimated,
507
+ cacheReadTokens: acc.cacheReadTokens,
508
+ cost_source: acc.cost > 0 ? acc.hasEstimatedCost ? "estimated" : "recorded" : void 0,
509
+ latestActivity: latest?.activity || void 0,
510
+ latestActivityProject: latest?.session.project_identity?.displayName,
511
+ latestActivityAgent: latest ? getSessionAgentName(latest.session) : void 0,
512
+ previous
513
+ },
514
+ scopeCounts: { projects: acc.projectKeys.size, agents: acc.agentKeys.size },
515
+ perAgent,
516
+ dailyActivity,
517
+ modelDistribution,
518
+ modelCost,
519
+ perProject,
520
+ projectRollup,
521
+ recentSessions
522
+ };
523
+ }
524
+
525
+ // ../core/dist/runtime/projects.mjs
526
+ function summarizeProjects(projects) {
527
+ const summary = {
528
+ projects: projects.length,
529
+ sessions: 0,
530
+ tokens: 0,
531
+ cost: 0,
532
+ latestActivity: null
533
+ };
534
+ for (const project of projects) {
535
+ summary.sessions += project.sessionCount;
536
+ summary.tokens += project.tokens;
537
+ summary.cost += project.cost;
538
+ if (project.lastActivity != null) {
539
+ summary.latestActivity = summary.latestActivity == null ? project.lastActivity : Math.max(summary.latestActivity, project.lastActivity);
540
+ }
541
+ }
542
+ return summary;
543
+ }
544
+ function emptyMetrics() {
545
+ return {
546
+ sessions: 0,
547
+ messages: 0,
548
+ tokens: 0,
549
+ cost: 0,
550
+ hasEstimatedCost: false,
551
+ agentStats: /* @__PURE__ */ new Map()
552
+ };
553
+ }
554
+ function attachProjectMetricsFromTree(projects, tree, from, to, costFacts) {
555
+ return attachProjectMetricsToEntries(
556
+ projects,
557
+ tree,
558
+ filterSessionTreeEntriesByActivityWindow(tree, from, to),
559
+ from,
560
+ to,
561
+ costFacts
562
+ );
563
+ }
564
+ function attachProjectMetricsToEntries(projects, tree, entries, from, to, costFacts) {
565
+ const metrics = /* @__PURE__ */ new Map();
566
+ for (const node of entries) {
567
+ const identity = node.session.project_identity;
568
+ if (!identity) continue;
569
+ const key = getProjectIdentityKey(identity);
570
+ let current = metrics.get(key);
571
+ if (!current) {
572
+ current = emptyMetrics();
573
+ metrics.set(key, current);
574
+ }
575
+ current.sessions += 1;
576
+ const agentName = getSessionAgentName(node.session);
577
+ const agent = current.agentStats.get(agentName);
578
+ if (agent) {
579
+ agent.sessions += 1;
580
+ } else {
581
+ current.agentStats.set(agentName, {
582
+ name: agentName,
583
+ sessions: 1,
584
+ messages: 0,
585
+ tokens: 0,
586
+ cost: 0
587
+ });
588
+ }
589
+ }
590
+ visitAttributedUsage(
591
+ tree,
592
+ { from, to: to ?? Number.POSITIVE_INFINITY, facts: costFacts },
593
+ ({ entry, messages, totalTokens }) => {
594
+ const identity = entry.session.project_identity;
595
+ if (!identity) return;
596
+ const key = getProjectIdentityKey(identity);
597
+ let current = metrics.get(key);
598
+ if (!current) {
599
+ current = emptyMetrics();
600
+ metrics.set(key, current);
601
+ }
602
+ current.messages += messages;
603
+ current.tokens += totalTokens;
604
+ const agentName = getSessionAgentName(entry.session);
605
+ const agent = current.agentStats.get(agentName);
606
+ if (agent) {
607
+ agent.messages += messages;
608
+ agent.tokens += totalTokens;
609
+ } else {
610
+ current.agentStats.set(agentName, {
611
+ name: agentName,
612
+ sessions: 0,
613
+ messages,
614
+ tokens: totalTokens,
615
+ cost: 0
616
+ });
617
+ }
618
+ }
619
+ );
620
+ visitAttributedCosts(
621
+ tree,
622
+ { from, to: to ?? Number.POSITIVE_INFINITY, facts: costFacts },
623
+ ({ entry, cost, source }) => {
624
+ const identity = entry.session.project_identity;
625
+ if (!identity) return;
626
+ const key = getProjectIdentityKey(identity);
627
+ let current = metrics.get(key);
628
+ if (!current) {
629
+ current = emptyMetrics();
630
+ metrics.set(key, current);
631
+ }
632
+ current.cost += cost;
633
+ if (source === "estimated") current.hasEstimatedCost = true;
634
+ const agentName = getSessionAgentName(entry.session);
635
+ const agent = current.agentStats.get(agentName);
636
+ if (agent) agent.cost += cost;
637
+ else {
638
+ current.agentStats.set(agentName, {
639
+ name: agentName,
640
+ sessions: 0,
641
+ messages: 0,
642
+ tokens: 0,
643
+ cost
644
+ });
645
+ }
646
+ }
647
+ );
648
+ return projects.map((project) => {
649
+ const metric = metrics.get(
650
+ getProjectIdentityKey({ kind: project.identityKind, key: project.identityKey })
651
+ );
652
+ return {
653
+ ...project,
654
+ sessionCount: metric?.sessions ?? 0,
655
+ messages: metric?.messages ?? 0,
656
+ tokens: metric?.tokens ?? 0,
657
+ cost: metric?.cost ?? 0,
658
+ cost_source: metric && metric.cost > 0 ? metric.hasEstimatedCost ? "estimated" : "recorded" : void 0,
659
+ agentStats: [...metric?.agentStats.values() ?? []].sort((a, b) => b.sessions - a.sessions)
660
+ };
661
+ });
662
+ }
663
+
664
+ export {
665
+ getSessionActivityTime,
666
+ buildDashboard,
667
+ summarizeProjects,
668
+ attachProjectMetricsFromTree
669
+ };
670
+ //# sourceMappingURL=chunk-MBZ4VV4C.js.map