agentwrangler 0.1.0 → 0.1.1-next.2e8c141

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 (49) hide show
  1. package/README.md +152 -84
  2. package/dist/daemon/index.js +9 -3
  3. package/dist/daemon/readiness.js +26 -1
  4. package/dist/daemon/router.js +3 -1
  5. package/dist/db/migrations/016_ingest_offset_file_version.sql +8 -0
  6. package/dist/db/migrations/017_ingest_metric_events.sql +22 -0
  7. package/dist/detector/measurement.js +3 -2
  8. package/dist/hook/precompact-checkpoint-hook.mjs +51 -15
  9. package/dist/ingest/ingestor.js +151 -62
  10. package/dist/ingest/pricing.js +15 -0
  11. package/dist/ingest/tail.js +55 -11
  12. package/dist/query/api/outcomes.js +2 -1
  13. package/dist/query/api/overview.js +13 -3
  14. package/dist/query/settings-store.js +2 -0
  15. package/dist/ui/assets/BarChart-BwFZrcLn.js +3 -0
  16. package/dist/ui/assets/BriefsPage-CtJwHLOh.js +2 -0
  17. package/dist/ui/assets/CacheWriteSpikesChart-zJIJWO41.js +1 -0
  18. package/dist/ui/assets/CartesianChart-qApXl3Ak.js +84 -0
  19. package/dist/ui/assets/Chip-Cpt_fEc9.js +1 -0
  20. package/dist/ui/assets/ComposedChart-DYf1wymW.js +1 -0
  21. package/dist/ui/assets/EmptyState-DL8gAVnJ.js +1 -0
  22. package/dist/ui/assets/FlavorDecomposition-CvJW78M9.js +1 -0
  23. package/dist/ui/assets/FrictionCell-DciiSd3L.js +2 -0
  24. package/dist/ui/assets/GlossaryPage-BMkZGt2D.js +1 -0
  25. package/dist/ui/assets/HotSessionsPage-Ejn385bb.js +1 -0
  26. package/dist/ui/assets/InfoTip-Ckc9_LpJ.js +1 -0
  27. package/dist/ui/assets/Legend-C_YMp0Rv.js +5 -0
  28. package/dist/ui/assets/Line-DU4UvEBO.js +1 -0
  29. package/dist/ui/assets/OverviewPage-BcEKdWZj.js +2 -0
  30. package/dist/ui/assets/RecommendationsPage-CHfuNP_W.js +2 -0
  31. package/dist/ui/assets/Scatter-CNetVLuX.js +1 -0
  32. package/dist/ui/assets/SessionDetailPage-CYm5Gxo0.js +1 -0
  33. package/dist/ui/assets/SettingsPage-DjgGZj8N.js +25 -0
  34. package/dist/ui/assets/Skeleton-BO9mXDXU.js +1 -0
  35. package/dist/ui/assets/SpendPercentileChip-BvaZ8Lzi.js +1 -0
  36. package/dist/ui/assets/TrendChart-DKy9aH5G.js +1 -0
  37. package/dist/ui/assets/WorkspaceDetailPage-dutc4EpN.js +1 -0
  38. package/dist/ui/assets/WorkspacesPage-isSML2fh.js +1 -0
  39. package/dist/ui/assets/chart-theme-BPPMjbVX.js +1 -0
  40. package/dist/ui/assets/graphicalItemSelectors-DgKF1Dg2.js +1 -0
  41. package/dist/ui/assets/index-CUyiomzU.js +41 -0
  42. package/dist/ui/assets/index-SrfNUeBZ.css +1 -0
  43. package/dist/ui/assets/prompt-templates-DhR4Qoy9.js +13 -0
  44. package/dist/ui/assets/useExperimentalActions-BeDSmwBu.js +1 -0
  45. package/dist/ui/assets/workspace-label-CysPJNc0.js +1 -0
  46. package/dist/ui/index.html +2 -2
  47. package/package.json +21 -5
  48. package/dist/ui/assets/index-DnRKgc21.css +0 -1
  49. package/dist/ui/assets/index-h1Q1wWq5.js +0 -168
@@ -18,7 +18,7 @@ import { Health } from "./health.js";
18
18
  import { PARSER_VERSION, projectLine } from "./parser.js";
19
19
  import { PricingSnapshotStore, seedListPrices } from "./pricing.js";
20
20
  import { DEFAULT_RECONCILE_OPTIONS, reconcileSessions, } from "./reconcile.js";
21
- import { loadOffset, saveOffset, tailFile } from "./tail.js";
21
+ import { fileVersion, loadOffset, sameFileVersion, saveOffset, tailFile, } from "./tail.js";
22
22
  import { LONG_GAP_THRESHOLD_S } from "./types.js";
23
23
  import { backfillDiscoveredCwd, defaultReadCwd, defaultReadRemote, resolveWorkspaceMappings, } from "./workspace-mapping.js";
24
24
  export const DEFAULT_INGESTOR_OPTIONS = {
@@ -41,10 +41,9 @@ export class Ingestor {
41
41
  countedResults = new Set(); // toolUseIds already summed
42
42
  resultBytesByMsg = new Map(); // messageId → running byte sum
43
43
  lineCursor = new Map(); // filePath → complete lines consumed
44
- lastSize = new Map(); // filePath → last-seen size (skip unchanged files)
44
+ lastVersion = new Map(); // filePath → last-seen version
45
45
  discoveryCache = createDiscoveryCache();
46
46
  unresolvedRemotes = new Set();
47
- userTurnTsBySession = new Map(); // sessionId → epoch-ms of user turns
48
47
  // Prepared statements.
49
48
  stInsertTurn;
50
49
  stInsertSession;
@@ -52,7 +51,11 @@ export class Ingestor {
52
51
  stBumpUserTurnCount;
53
52
  stBumpFrictionCounts;
54
53
  stSetGapAggregates;
54
+ stInsertMetricEvent;
55
+ stGetMetricTimestamps;
55
56
  stGetGapN;
57
+ stGetMetricBaseline;
58
+ stSetMetricBaseline;
56
59
  stInsertToolEvent;
57
60
  stUpsertToolEventMetadata;
58
61
  stSetToolResult;
@@ -93,7 +96,16 @@ export class Ingestor {
93
96
  interrupt_count = interrupt_count + ?
94
97
  WHERE session_id = ?`);
95
98
  this.stSetGapAggregates = db.prepare("UPDATE sessions SET gap_median_s=?, gap_p90_s=?, long_gap_count=?, gap_n=? WHERE session_id=?");
96
- this.stGetGapN = db.prepare("SELECT gap_n FROM sessions WHERE session_id=?");
99
+ this.stInsertMetricEvent = db.prepare(`INSERT OR IGNORE INTO ingest_metric_events
100
+ (event_id, session_id, user_turn_ts, is_user_turn,
101
+ is_compact_summary, is_api_error, is_interrupt)
102
+ VALUES (?,?,?,?,?,?,?)`);
103
+ this.stGetMetricTimestamps = db.prepare(`SELECT user_turn_ts FROM ingest_metric_events
104
+ WHERE session_id = ? AND is_user_turn = 1 AND user_turn_ts IS NOT NULL`);
105
+ this.stGetGapN = db.prepare("SELECT gap_n FROM sessions WHERE session_id = ?");
106
+ this.stGetMetricBaseline = db.prepare("SELECT seeded_offset FROM ingest_metric_baselines WHERE file_path = ?");
107
+ this.stSetMetricBaseline = db.prepare(`INSERT INTO ingest_metric_baselines (file_path, seeded_offset) VALUES (?, ?)
108
+ ON CONFLICT(file_path) DO UPDATE SET seeded_offset = excluded.seeded_offset`);
97
109
  this.stInsertToolEvent = db.prepare(`INSERT INTO tool_events
98
110
  (event_id, session_id, ts, tool_name, input_bytes, result_bytes, input_hash, exit_class, commit_sha)
99
111
  VALUES (?,?,?,?,?,?,?,?,?)
@@ -236,9 +248,8 @@ export class Ingestor {
236
248
  this.countedResults.clear();
237
249
  this.resultBytesByMsg.clear();
238
250
  this.lineCursor.clear();
239
- this.lastSize.clear();
251
+ this.lastVersion.clear();
240
252
  this.unresolvedRemotes.clear();
241
- this.userTurnTsBySession.clear();
242
253
  }
243
254
  // ── internals ──────────────────────────────────────────────────────────────
244
255
  ingestAllKnown() {
@@ -278,31 +289,39 @@ export class Ingestor {
278
289
  }
279
290
  /** Tail one file from its stored offset and ingest the complete new lines. */
280
291
  ingestFile(filePath, projectSlug) {
281
- // Cheap size guard: transcripts are append-only, so an unchanged file size
282
- // means zero new complete lines. Skip it before touching the DB — this keeps
283
- // the 2s tail tick from doing ~4,700 sqlite writes across ~2,352 idle files
292
+ // Skip only when size, identity, and timestamps all match. This keeps the
293
+ // 2s tail tick from touching the DB for idle files while still noticing
294
+ // same-size rewrites and path replacement.
284
295
  // every tick, which was starving the daemon's event loop (outcomes pass).
285
296
  // Keep the per-file stat: directory mtimes only decide when to refresh paths;
286
297
  // they cannot safely replace append/rotation change detection for each file.
287
- let curSize;
298
+ let currentVersion;
288
299
  try {
289
- curSize = fs.statSync(filePath).size;
300
+ currentVersion = fileVersion(fs.statSync(filePath));
290
301
  }
291
302
  catch {
292
303
  return; // file vanished between discovery and tail; nothing to ingest
293
304
  }
294
- if (this.lastSize.get(filePath) === curSize)
295
- return; // unchanged since last tick — no new bytes
296
- this.lastSize.set(filePath, curSize);
305
+ const previousVersion = this.lastVersion.get(filePath);
306
+ if (previousVersion !== undefined && sameFileVersion(previousVersion, currentVersion))
307
+ return;
308
+ this.lastVersion.set(filePath, currentVersion);
297
309
  this.health.fileSeen();
298
310
  registerWorkspace(this.db, projectSlug);
299
311
  const stored = loadOffset(this.db, filePath);
300
- const result = tailFile(filePath, stored);
312
+ this.seedLegacyMetricPrefix(filePath, stored?.offset ?? null, currentVersion.size);
313
+ const result = tailFile(filePath, stored, currentVersion);
301
314
  if (result.wasReset)
302
315
  this.lineCursor.set(filePath, 0);
303
316
  if (result.lines.length === 0) {
304
- // Still persist head-hash/offset so first-touch rotation detection is armed.
305
- saveOffset(this.db, filePath, result.newOffset, result.newHeadHash);
317
+ if (stored === null ||
318
+ stored.offset !== result.newOffset ||
319
+ stored.headHash !== result.newHeadHash ||
320
+ stored.fileVersion === null ||
321
+ !sameFileVersion(stored.fileVersion, currentVersion)) {
322
+ saveOffset(this.db, filePath, result.newOffset, result.newHeadHash, currentVersion);
323
+ }
324
+ this.stSetMetricBaseline.run(filePath, result.newOffset);
306
325
  return;
307
326
  }
308
327
  const defaultSessionId = sessionStemFor(filePath);
@@ -317,9 +336,50 @@ export class Ingestor {
317
336
  });
318
337
  tx();
319
338
  this.lineCursor.set(filePath, base + result.lines.length);
320
- saveOffset(this.db, filePath, result.newOffset, result.newHeadHash);
339
+ saveOffset(this.db, filePath, result.newOffset, result.newHeadHash, currentVersion);
340
+ this.stSetMetricBaseline.run(filePath, result.newOffset);
321
341
  this.health.fileParsed();
322
342
  }
343
+ /**
344
+ * Upgrade bridge for offsets created before the metric-event ledger existed.
345
+ * Already-consumed complete lines are registered without advancing counters.
346
+ */
347
+ seedLegacyMetricPrefix(filePath, storedOffset, currentSize) {
348
+ const baseline = this.stGetMetricBaseline.get(filePath);
349
+ if (baseline !== undefined)
350
+ return;
351
+ if (storedOffset === null || storedOffset <= 0 || storedOffset > currentSize) {
352
+ this.stSetMetricBaseline.run(filePath, 0);
353
+ return;
354
+ }
355
+ const fd = fs.openSync(filePath, "r");
356
+ try {
357
+ const buf = Buffer.alloc(storedOffset);
358
+ const bytesRead = fs.readSync(fd, buf, 0, storedOffset, 0);
359
+ let lastNl = -1;
360
+ for (let i = bytesRead - 1; i >= 0; i--) {
361
+ if (buf[i] === 0x0a) {
362
+ lastNl = i;
363
+ break;
364
+ }
365
+ }
366
+ const lines = lastNl < 0 ? [] : buf.subarray(0, lastNl).toString("utf8").split("\n");
367
+ const defaultSessionId = sessionStemFor(filePath);
368
+ this.db.transaction(() => {
369
+ for (const raw of lines) {
370
+ if (raw.length === 0)
371
+ continue;
372
+ const proj = projectLine(raw, { defaultSessionId });
373
+ if (proj.kind === "record")
374
+ this.recordMetricEvent(raw, proj, false);
375
+ }
376
+ this.stSetMetricBaseline.run(filePath, storedOffset);
377
+ })();
378
+ }
379
+ finally {
380
+ fs.closeSync(fd);
381
+ }
382
+ }
323
383
  applyLine(raw, ctx, workspaceId, filePath, lineNo) {
324
384
  const proj = projectLine(raw, ctx);
325
385
  if (proj.kind === "quarantine") {
@@ -339,31 +399,13 @@ export class Ingestor {
339
399
  if (lineSessionId !== null) {
340
400
  this.ensureSession(lineSessionId, workspaceId, filePath, lineTs === "" ? null : lineTs);
341
401
  }
342
- if (proj.isUserTurn === true) {
343
- this.stBumpUserTurnCount.run(proj.sessionId);
344
- if (typeof proj.ts === "string" && proj.ts.length > 0) {
345
- const epochMs = Date.parse(proj.ts);
346
- if (Number.isFinite(epochMs)) {
347
- const list = this.userTurnTsBySession.get(proj.sessionId);
348
- if (list !== undefined) {
349
- list.push(epochMs);
350
- }
351
- else {
352
- this.userTurnTsBySession.set(proj.sessionId, [epochMs]);
353
- }
354
- this.applyGapAggregates(proj.sessionId);
355
- }
356
- }
357
- }
358
- // Friction counters (RV2a): compact summary, API error, interrupt.
359
- if (proj.isCompactSummary || proj.isApiErrorMessage || proj.isInterrupt) {
360
- // Ensure session row exists for lines that carry only top-level flags
361
- // (no turn, command, or tool events — so lineSessionId would be null).
362
- if (lineSessionId === null) {
363
- this.ensureSession(proj.sessionId, workspaceId, filePath, null);
364
- }
365
- this.stBumpFrictionCounts.run(proj.isCompactSummary ? 1 : 0, proj.isApiErrorMessage ? 1 : 0, proj.isInterrupt ? 1 : 0, proj.sessionId);
402
+ if (lineSessionId === null &&
403
+ (proj.isCompactSummary || proj.isApiErrorMessage || proj.isInterrupt)) {
404
+ this.ensureSession(proj.sessionId, workspaceId, filePath, null);
366
405
  }
406
+ // User/friction aggregates advance only for a structurally new source record.
407
+ // The persisted digest makes replay idempotent across restarts and files.
408
+ this.recordMetricEvent(raw, proj, true);
367
409
  // Command markers → tool_events(local_command) for hygiene evaluation.
368
410
  if (proj.command !== null) {
369
411
  this.recordCommand(proj.command.sessionId, proj.command.ts, proj.command.command);
@@ -457,25 +499,83 @@ export class Ingestor {
457
499
  .slice(0, 20)}`;
458
500
  this.stInsertToolEvent.run(eventId, sessionId, ts, "local_command", null, null, command, null, null);
459
501
  }
460
- applyGapAggregates(sessionId) {
461
- const tsList = this.userTurnTsBySession.get(sessionId);
462
- if (tsList === undefined || tsList.length === 0) {
463
- this.clearGapAggregatesUnlessRicher(sessionId);
502
+ recordMetricEvent(raw, proj, applyAggregates) {
503
+ if (!proj.isUserTurn &&
504
+ !proj.isCompactSummary &&
505
+ !proj.isApiErrorMessage &&
506
+ !proj.isInterrupt) {
464
507
  return;
465
508
  }
466
- const sorted = [...tsList].sort((a, b) => a - b);
509
+ const flags = [
510
+ proj.isUserTurn ? "u" : "",
511
+ proj.isCompactSummary ? "c" : "",
512
+ proj.isApiErrorMessage ? "a" : "",
513
+ proj.isInterrupt ? "i" : "",
514
+ ].join("");
515
+ let sourceIdentity;
516
+ try {
517
+ const parsed = JSON.parse(raw);
518
+ if (typeof parsed.uuid === "string" && parsed.uuid.trim() !== "") {
519
+ sourceIdentity = `uuid:${parsed.uuid}`;
520
+ }
521
+ else {
522
+ const message = parsed.message;
523
+ const messageId = typeof message === "object" && message !== null
524
+ ? message.id
525
+ : null;
526
+ if (typeof messageId === "string" && messageId.trim() !== "") {
527
+ sourceIdentity = `message:${String(parsed.type ?? "")}:${messageId}:${flags}`;
528
+ }
529
+ else {
530
+ sourceIdentity = `raw:${crypto.createHash("sha256").update(raw).digest("hex")}`;
531
+ }
532
+ }
533
+ }
534
+ catch {
535
+ // projectLine already parsed record lines; retain a safe fallback if that contract changes.
536
+ sourceIdentity = `raw:${crypto.createHash("sha256").update(raw).digest("hex")}`;
537
+ }
538
+ const eventId = crypto
539
+ .createHash("sha256")
540
+ .update(`${proj.sessionId}\0${sourceIdentity}`)
541
+ .digest("hex");
542
+ const validUserTs = proj.isUserTurn && typeof proj.ts === "string" && Number.isFinite(Date.parse(proj.ts))
543
+ ? proj.ts
544
+ : null;
545
+ const inserted = this.stInsertMetricEvent.run(eventId, proj.sessionId, validUserTs, proj.isUserTurn ? 1 : 0, proj.isCompactSummary ? 1 : 0, proj.isApiErrorMessage ? 1 : 0, proj.isInterrupt ? 1 : 0);
546
+ if (!applyAggregates || inserted.changes === 0)
547
+ return;
548
+ if (proj.isUserTurn)
549
+ this.stBumpUserTurnCount.run(proj.sessionId);
550
+ if (proj.isCompactSummary || proj.isApiErrorMessage || proj.isInterrupt) {
551
+ this.stBumpFrictionCounts.run(proj.isCompactSummary ? 1 : 0, proj.isApiErrorMessage ? 1 : 0, proj.isInterrupt ? 1 : 0, proj.sessionId);
552
+ }
553
+ if (validUserTs !== null)
554
+ this.applyGapAggregates(proj.sessionId);
555
+ }
556
+ applyGapAggregates(sessionId) {
557
+ const rows = this.stGetMetricTimestamps.all(sessionId);
558
+ const sorted = rows
559
+ .map((row) => Date.parse(row.user_turn_ts))
560
+ .filter((epochMs) => Number.isFinite(epochMs))
561
+ .sort((a, b) => a - b);
467
562
  const gaps = [];
468
563
  for (let i = 1; i < sorted.length; i++) {
469
564
  gaps.push((sorted[i] - sorted[i - 1]) / 1000);
470
565
  }
471
566
  const gapN = gaps.length;
567
+ const stored = this.stGetGapN.get(sessionId);
568
+ // During the 017 upgrade, a session's legacy timestamps can be spread across
569
+ // files that have not all established their metric-ledger baselines yet.
570
+ // Preserve the richer legacy aggregate until the ledger has caught up.
571
+ if (stored !== undefined && gapN < stored.gap_n)
572
+ return;
472
573
  if (gapN === 0) {
473
- this.clearGapAggregatesUnlessRicher(sessionId);
574
+ this.stSetGapAggregates.run(null, null, 0, 0, sessionId);
474
575
  return;
475
576
  }
476
- // gapN >= 1: a real gap was observed in-process. Always write it — even a partial,
477
- // post-restart sample reflects genuine current friction (e.g. a new long gap) and
478
- // must not be suppressed just because it's smaller than a pre-restart gap_n.
577
+ // The persisted event ledger supplies the complete known history across
578
+ // restarts and source files, so this aggregate can be recomputed deterministically.
479
579
  const g = [...gaps].sort((a, b) => a - b);
480
580
  const mid = g.length;
481
581
  const gapMedianS = mid % 2 === 1
@@ -486,17 +586,6 @@ export class Ingestor {
486
586
  const longGapCount = g.filter((x) => x > LONG_GAP_THRESHOLD_S).length;
487
587
  this.stSetGapAggregates.run(gapMedianS, gapP90S, longGapCount, gapN, sessionId);
488
588
  }
489
- // Write-guard for the "no computable gap" collapse (0 or 1 in-process user turns):
490
- // the in-memory turn-timestamp map resets on every daemon restart, so this state is
491
- // reached on the first post-restart user turn even when a prior cold runBackscan()
492
- // already persisted a richer gap_n. Skip the null/zero write in that case so it
493
- // doesn't clobber the richer aggregate.
494
- clearGapAggregatesUnlessRicher(sessionId) {
495
- const storedRow = this.stGetGapN.get(sessionId);
496
- if ((storedRow?.gap_n ?? 0) > 0)
497
- return;
498
- this.stSetGapAggregates.run(null, null, 0, 0, sessionId);
499
- }
500
589
  quarantine(filePath, lineNo, errorClass) {
501
590
  const qId = crypto
502
591
  .createHash("sha1")
@@ -62,6 +62,21 @@ export function modelTier(model) {
62
62
  }
63
63
  return null;
64
64
  }
65
+ /**
66
+ * SQL predicate matching premium-priced models on a `model` column: Opus plus
67
+ * the Fable/Mythos families (Fable's $10/$50 base is 2× Opus). Shared by every
68
+ * premium-share aggregate so the family list can't drift between queries.
69
+ */
70
+ export const PREMIUM_MODEL_SQL = "(model LIKE '%opus%' OR model LIKE '%fable%' OR model LIKE '%mythos%')";
71
+ /**
72
+ * Input list price ($/MTok) for a model, for ordering model lists
73
+ * most-expensive first. Derived from LIST_PRICES via modelTier so a new tier
74
+ * automatically sorts by its price; unpriceable models return 0 (sort last).
75
+ */
76
+ export function modelInputListPrice(model) {
77
+ const tier = modelTier(model);
78
+ return tier === null ? 0 : (LIST_PRICES[tier]?.[0] ?? 0);
79
+ }
65
80
  function addDaysIso(iso, days) {
66
81
  const d = new Date(iso);
67
82
  d.setUTCDate(d.getUTCDate() + days);
@@ -14,6 +14,22 @@
14
14
  import * as crypto from "node:crypto";
15
15
  import * as fs from "node:fs";
16
16
  const HEAD_BYTES = 256; // bytes hashed for rotation detection
17
+ export function fileVersion(stat) {
18
+ return {
19
+ size: stat.size,
20
+ dev: String(stat.dev),
21
+ ino: String(stat.ino),
22
+ mtimeMs: stat.mtimeMs,
23
+ ctimeMs: stat.ctimeMs,
24
+ };
25
+ }
26
+ export function sameFileVersion(a, b) {
27
+ return (a.size === b.size &&
28
+ a.dev === b.dev &&
29
+ a.ino === b.ino &&
30
+ a.mtimeMs === b.mtimeMs &&
31
+ a.ctimeMs === b.ctimeMs);
32
+ }
17
33
  /** SHA-256 of the first n bytes of a file (or fewer if the file is shorter). */
18
34
  function headHashN(filePath, n) {
19
35
  let fd;
@@ -51,12 +67,12 @@ export function headHash(filePath) {
51
67
  * Tail one file from its stored offset. Returns complete lines only.
52
68
  * See module header for the rotation/truncation contract.
53
69
  */
54
- export function tailFile(filePath, stored) {
70
+ export function tailFile(filePath, stored, currentVersion) {
55
71
  let storedOffset = stored?.offset ?? 0;
56
72
  let storedHead = stored?.headHash ?? null;
57
- let stat;
73
+ let version = currentVersion;
58
74
  try {
59
- stat = fs.statSync(filePath);
75
+ version ??= fileVersion(fs.statSync(filePath));
60
76
  }
61
77
  catch {
62
78
  return {
@@ -67,7 +83,7 @@ export function tailFile(filePath, stored) {
67
83
  wasReset: false,
68
84
  };
69
85
  }
70
- const fileSize = stat.size;
86
+ const fileSize = version.size;
71
87
  let event = null;
72
88
  let wasReset = false;
73
89
  const curHead = fileSize > 0 ? headHash(filePath) : "";
@@ -84,6 +100,12 @@ export function tailFile(filePath, stored) {
84
100
  }
85
101
  // fileSize < storedN: size regression — headChanged stays false; handled below.
86
102
  }
103
+ const storedVersion = stored?.fileVersion ?? null;
104
+ const identityChanged = storedVersion !== null &&
105
+ (storedVersion.dev !== version.dev || storedVersion.ino !== version.ino);
106
+ const equalSizeTimestampChanged = storedVersion !== null &&
107
+ storedVersion.size === version.size &&
108
+ (storedVersion.mtimeMs !== version.mtimeMs || storedVersion.ctimeMs !== version.ctimeMs);
87
109
  if (storedOffset > fileSize) {
88
110
  // Size regression always means truncation, regardless of head change.
89
111
  event = "TRUNCATION";
@@ -91,7 +113,7 @@ export function tailFile(filePath, stored) {
91
113
  wasReset = true;
92
114
  storedHead = curHead;
93
115
  }
94
- else if (headChanged) {
116
+ else if (identityChanged || equalSizeTimestampChanged || headChanged) {
95
117
  event = "ROTATION";
96
118
  storedOffset = 0;
97
119
  wasReset = true;
@@ -135,18 +157,40 @@ export function tailFile(filePath, stored) {
135
157
  /** Read the persisted offset for a file, or null if never tailed. */
136
158
  export function loadOffset(db, filePath) {
137
159
  const row = db
138
- .prepare("SELECT byte_offset, file_hash_head FROM ingest_offsets WHERE file_path = ?")
160
+ .prepare("SELECT byte_offset, file_hash_head, file_size, file_dev, file_ino, file_mtime_ms, file_ctime_ms FROM ingest_offsets WHERE file_path = ?")
139
161
  .get(filePath);
140
162
  if (row === undefined)
141
163
  return null;
142
- return { offset: row.byte_offset, headHash: row.file_hash_head };
164
+ const hasVersion = row.file_size !== null &&
165
+ row.file_dev !== null &&
166
+ row.file_ino !== null &&
167
+ row.file_mtime_ms !== null &&
168
+ row.file_ctime_ms !== null;
169
+ return {
170
+ offset: row.byte_offset,
171
+ headHash: row.file_hash_head,
172
+ fileVersion: hasVersion
173
+ ? {
174
+ size: row.file_size,
175
+ dev: row.file_dev,
176
+ ino: row.file_ino,
177
+ mtimeMs: row.file_mtime_ms,
178
+ ctimeMs: row.file_ctime_ms,
179
+ }
180
+ : null,
181
+ };
143
182
  }
144
183
  /** Upsert the persisted offset for a file. */
145
- export function saveOffset(db, filePath, offset, headHash) {
146
- db.prepare(`INSERT INTO ingest_offsets (file_path, byte_offset, file_hash_head, updated_at)
147
- VALUES (?,?,?,?)
184
+ export function saveOffset(db, filePath, offset, headHash, version) {
185
+ db.prepare(`INSERT INTO ingest_offsets (file_path, byte_offset, file_hash_head, file_size, file_dev, file_ino, file_mtime_ms, file_ctime_ms, updated_at)
186
+ VALUES (?,?,?,?,?,?,?,?,?)
148
187
  ON CONFLICT(file_path) DO UPDATE SET
149
188
  byte_offset = excluded.byte_offset,
150
189
  file_hash_head = excluded.file_hash_head,
151
- updated_at = excluded.updated_at`).run(filePath, offset, headHash, new Date().toISOString());
190
+ file_size = excluded.file_size,
191
+ file_dev = excluded.file_dev,
192
+ file_ino = excluded.file_ino,
193
+ file_mtime_ms = excluded.file_mtime_ms,
194
+ file_ctime_ms = excluded.file_ctime_ms,
195
+ updated_at = excluded.updated_at`).run(filePath, offset, headHash, version.size, version.dev, version.ino, version.mtimeMs, version.ctimeMs, new Date().toISOString());
152
196
  }
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * SQL references: Data Model v2 §2 lines 261-275 / 303-314 / 329-334.
13
13
  */
14
+ import { PREMIUM_MODEL_SQL } from "../../ingest/pricing.js";
14
15
  import { getQueryDb } from "../db-context.js";
15
16
  import { buildResponse } from "../envelope.js";
16
17
  // ---------------------------------------------------------------------------
@@ -136,7 +137,7 @@ export function listWorkspaceOutcomes() {
136
137
  const lrByWs = new Map(lrRows.map((r) => [r.workspace_id, r.linkage_rate]));
137
138
  const adherenceRows = db
138
139
  .prepare(`SELECT workspace_id, COUNT(*) AS n,
139
- AVG(CASE WHEN model LIKE '%opus%' THEN 1.0 ELSE 0.0 END) AS premium_share
140
+ AVG(CASE WHEN ${PREMIUM_MODEL_SQL} THEN 1.0 ELSE 0.0 END) AS premium_share
140
141
  FROM turns
141
142
  WHERE is_sidechain = 0 AND provisional = 0
142
143
  GROUP BY workspace_id`)
@@ -10,6 +10,7 @@
10
10
  * windows/cutoffs, maps rows to the frozen DTOs, and attaches the envelope.
11
11
  */
12
12
  import { loadConfig } from "../../daemon/config.js";
13
+ import { PREMIUM_MODEL_SQL, modelInputListPrice } from "../../ingest/pricing.js";
13
14
  import { getQueryDb } from "../db-context.js";
14
15
  import { forecastFromDb } from "../forecast.js";
15
16
  import { contextPerTurnByModel, globalSpend, hasStaleClaim, liveSessionCount, liveSessions, spendByWorkspace, } from "../spend.js";
@@ -167,14 +168,20 @@ export function getGlobalOverview(filters) {
167
168
  // counts sum to `turns_total`, NOT the reconciled `turns`. This is the
168
169
  // deliberate spend-vs-context difference (Data Model §2/§3) — a consumer must
169
170
  // not expect model_mix turns to reconcile with the reconciled `turns` figure.
170
- const context_per_turn = ctx.map((r) => ({
171
+ // Display order: most-expensive tier first (then turns, then name) — the
172
+ // premium models matter most per turn, and a price-stable order keeps the
173
+ // index-keyed card colors from shuffling as usage volume shifts.
174
+ const ctxSorted = [...ctx].sort((a, b) => modelInputListPrice(b.model) - modelInputListPrice(a.model) ||
175
+ b.n - a.n ||
176
+ a.model.localeCompare(b.model));
177
+ const context_per_turn = ctxSorted.map((r) => ({
171
178
  model: r.model,
172
179
  n: r.n,
173
180
  avg_context_per_turn: r.avg_context_per_turn,
174
181
  avg_output_per_turn: r.avg_output_per_turn,
175
182
  usd_per_turn: r.usd_per_turn,
176
183
  }));
177
- const model_mix = ctx.map((r) => ({ model: r.model, turns: r.n }));
184
+ const model_mix = ctxSorted.map((r) => ({ model: r.model, turns: r.n }));
178
185
  const data = {
179
186
  cost_equiv_u: g.cost_equiv_u,
180
187
  turns: g.turns,
@@ -224,7 +231,9 @@ export function listWorkspaces(filters) {
224
231
  + cache_write_5m + cache_write_1h + cache_write_other)
225
232
  ELSE NULL END AS cache_write_pct,
226
233
  CAST(SUM(CASE WHEN model LIKE '%opus%' THEN 1 ELSE 0 END) AS REAL)
227
- / NULLIF(COUNT(*), 0) AS opus_pct
234
+ / NULLIF(COUNT(*), 0) AS opus_pct,
235
+ CAST(SUM(CASE WHEN ${PREMIUM_MODEL_SQL} THEN 1 ELSE 0 END) AS REAL)
236
+ / NULLIF(COUNT(*), 0) AS premium_pct
228
237
  FROM turns WHERE ts >= ? AND ts < ?
229
238
  GROUP BY workspace_id`)
230
239
  .all(from, to));
@@ -245,6 +254,7 @@ export function listWorkspaces(filters) {
245
254
  avg_context_per_turn: agg?.avg_context_per_turn ?? null,
246
255
  cache_write_pct: agg?.cache_write_pct ?? null,
247
256
  opus_pct: agg?.opus_pct ?? null,
257
+ premium_pct: agg?.premium_pct ?? null,
248
258
  };
249
259
  });
250
260
  return makeResponse({ items, next_cursor: null }, {
@@ -407,6 +407,8 @@ export const RESET_DATA_TABLES = [
407
407
  "context_inventory_history",
408
408
  "workspaces",
409
409
  "ingest_quarantine",
410
+ "ingest_metric_events",
411
+ "ingest_metric_baselines",
410
412
  "ingest_offsets",
411
413
  ];
412
414
  /**
@@ -0,0 +1,3 @@
1
+ import{r as u}from"./index-CUyiomzU.js";import{aX as Le,a2 as V,aY as q,n as J,g as Te,aZ as Re,o as se,a1 as Fe,c as j,a_ as Ne,a$ as $e,s as F,a as We,d as T,e as R,f as ge,h as be,b0 as Ae,b1 as _e,b2 as Ge,k as ue,b3 as Ue,b4 as ce,x as Ve,L as _,p as Ze,r as Xe,D as ee,aN as Ye,t as Ie,v as He,S as qe,w as Je,Z as ae,y as O,z as Pe,A as Qe,B as ea,G as aa,E as ta,aO as ra,H as G,a9 as na,aP as ia,N as ke,J as oa,M as la,b5 as sa,b6 as ua,b7 as de,ay as Be,aR as Ee,aS as Se,aT as Oe,aA as ca,aa as we,U as da,W as va,_ as fa,$ as ma,l as pa,b8 as ha,a3 as ya,b9 as ve,a4 as xa,a5 as ga}from"./CartesianChart-qApXl3Ak.js";import{s as K,a as z}from"./graphicalItemSelectors-DgKF1Dg2.js";function ba(e,a){return ka(e)||Pa(e,a)||Ia(e,a)||Aa()}function Aa(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
2
+ In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function Ia(e,a){if(e){if(typeof e=="string")return fe(e,a);var t={}.toString.call(e).slice(8,-1);return t==="Object"&&e.constructor&&(t=e.constructor.name),t==="Map"||t==="Set"?Array.from(e):t==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?fe(e,a):void 0}}function fe(e,a){(a==null||a>e.length)&&(a=e.length);for(var t=0,r=Array(a);t<a;t++)r[t]=e[t];return r}function Pa(e,a){var t=e==null?null:typeof Symbol<"u"&&e[Symbol.iterator]||e["@@iterator"];if(t!=null){var r,n,i,o,s=[],c=!0,l=!1;try{if(i=(t=t.call(e)).next,a!==0)for(;!(c=(r=i.call(t)).done)&&(s.push(r.value),s.length!==a);c=!0);}catch(d){l=!0,n=d}finally{try{if(!c&&t.return!=null&&(o=t.return(),Object(o)!==o))return}finally{if(l)throw n}}return s}}function ka(e){if(Array.isArray(e))return e}var me=(e,a,t)=>{var r=t??e;if(!V(r))return q(r,a,0)},Ba=(e,a,t)=>{var r={},n=e.filter(Le),i=e.filter(l=>l.stackId==null),o=n.reduce((l,d)=>{var v=l[d.stackId];return v==null&&(v=[]),v.push(d),l[d.stackId]=v,l},r),s=Object.entries(o).map(l=>{var d,v=ba(l,2),f=v[0],y=v[1],m=y.map(h=>h.dataKey),x=me(a,t,(d=y[0])===null||d===void 0?void 0:d.barSize);return{stackId:f,dataKeys:m,barSize:x}}),c=i.map(l=>{var d=[l.dataKey].filter(f=>f!=null),v=me(a,t,l.barSize);return{stackId:void 0,dataKeys:d,barSize:v}});return[...s,...c]};function pe(e,a){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);a&&(r=r.filter(function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable})),t.push.apply(t,r)}return t}function W(e){for(var a=1;a<arguments.length;a++){var t=arguments[a]!=null?arguments[a]:{};a%2?pe(Object(t),!0).forEach(function(r){Ea(e,r,t[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):pe(Object(t)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r))})}return e}function Ea(e,a,t){return(a=Sa(a))in e?Object.defineProperty(e,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[a]=t,e}function Sa(e){var a=Oa(e,"string");return typeof a=="symbol"?a:a+""}function Oa(e,a){if(typeof e!="object"||!e)return e;var t=e[Symbol.toPrimitive];if(t!==void 0){var r=t.call(e,a);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(a==="string"?String:Number)(e)}function wa(e,a,t,r,n){var i,o=r.length;if(!(o<1)){var s=q(e,t,0,!0),c,l=[];if(J((i=r[0])===null||i===void 0?void 0:i.barSize)){var d=!1,v=t/o,f=r.reduce((g,I)=>g+(I.barSize||0),0);f+=(o-1)*s,f>=t&&(f-=(o-1)*s,s=0),f>=t&&v>0&&(d=!0,v*=.9,f=o*v);var y=Math.round((t-f)/2),m={offset:y-s,size:0};c=r.reduce((g,I)=>{var p,k={stackId:I.stackId,dataKeys:I.dataKeys,position:{offset:m.offset+m.size+s,size:d?v:(p=I.barSize)!==null&&p!==void 0?p:0}},B=[...g,k];return m=k.position,B},l)}else{var x=q(a,t,0,!0);t-2*x-(o-1)*s<=0&&(s=0);var h=(t-2*x-(o-1)*s)/o;h>1&&(h=Math.round(h));var b=J(n)?Math.min(h,n):h;c=r.reduce((g,I,p)=>[...g,{stackId:I.stackId,dataKeys:I.dataKeys,position:{offset:x+o*(h-b)/2+(b+s)*p,size:b}}],l)}return c}}var Ca=(e,a,t,r,n,i,o)=>{var s=V(o)?a:o,c=wa(t,r,n!==i?n:i,e,s);return n!==i&&c!=null&&(c=c.map(l=>W(W({},l),{},{position:W(W({},l.position),{},{offset:l.position.offset-n/2})}))),c},Da=(e,a)=>{var t=Te(a);if(!(!e||t==null||a==null)){var r=a.stackId;if(r!=null){var n=e[r];if(n){var i=n.stackedData;if(i)return i.find(o=>o.key===t)}}}},ja=(e,a)=>{if(!(e==null||a==null)){var t=e.find(r=>r.stackId===a.stackId&&a.dataKey!=null&&r.dataKeys.includes(a.dataKey));if(t!=null)return t.position}};function Ma(e,a){return e&&typeof e=="object"&&"zIndex"in e&&typeof e.zIndex=="number"&&J(e.zIndex)?e.zIndex:a}var Ka="Invariant failed";function za(e,a){throw new Error(Ka)}var La=["option"];function Ta(e,a){if(e==null)return{};var t,r,n=Ra(e,a);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)t=i[r],a.indexOf(t)===-1&&{}.propertyIsEnumerable.call(e,t)&&(n[t]=e[t])}return n}function Ra(e,a){if(e==null)return{};var t={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(a.indexOf(r)!==-1)continue;t[r]=e[r]}return t}var te=Re;function re(e){var a=e.option,t=Ta(e,La);return u.createElement(Fe,{option:a,DefaultShape:te,shapeProps:t,activeClassName:"recharts-active-bar",inActiveClassName:"recharts-inactive-bar"})}var Fa=function(a){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;return(r,n)=>{if(se(a))return a;var i=se(r)||V(r);return i?a(r,n):(i||za(),t)}},Na=(e,a,t)=>t,$a=(e,a)=>a,$=j([be,$a],(e,a)=>e.filter(t=>t.type==="bar").find(t=>t.id===a)),Wa=j([$],e=>e==null?void 0:e.maxBarSize),_a=(e,a,t,r)=>r,Ga=j([F,be,K,z,Na],(e,a,t,r,n)=>a.filter(i=>e==="horizontal"?i.xAxisId===t:i.yAxisId===r).filter(i=>i.isPanorama===n).filter(i=>i.hide===!1).filter(i=>i.type==="bar")),Ua=(e,a,t)=>{var r=F(e),n=K(e,a),i=z(e,a);if(!(n==null||i==null))return r==="horizontal"?ue(e,"yAxis",i,t):ue(e,"xAxis",n,t)},Va=(e,a)=>{var t=F(e),r=K(e,a),n=z(e,a);if(!(r==null||n==null))return t==="horizontal"?ce(e,"xAxis",r):ce(e,"yAxis",n)},Za=j([Ga,Ue,Va],Ba),Xa=(e,a,t)=>{var r,n,i=$(e,a);if(i==null)return 0;var o=K(e,a),s=z(e,a);if(o==null||s==null)return 0;var c=F(e),l=Ae(e),d=i.maxBarSize,v=V(d)?l:d,f,y;return c==="horizontal"?(f=T(e,"xAxis",o,t),y=R(e,"xAxis",o,t)):(f=T(e,"yAxis",s,t),y=R(e,"yAxis",s,t)),(r=(n=ge(f,y,!0))!==null&&n!==void 0?n:v)!==null&&r!==void 0?r:0},Ce=(e,a,t)=>{var r=F(e),n=K(e,a),i=z(e,a);if(!(n==null||i==null)){var o,s;return r==="horizontal"?(o=T(e,"xAxis",n,t),s=R(e,"xAxis",n,t)):(o=T(e,"yAxis",i,t),s=R(e,"yAxis",i,t)),ge(o,s)}},Ya=j([Za,Ae,_e,Ge,Xa,Ce,Wa],Ca),Ha=(e,a,t)=>{var r=K(e,a);if(r!=null)return T(e,"xAxis",r,t)},qa=(e,a,t)=>{var r=z(e,a);if(r!=null)return T(e,"yAxis",r,t)},Ja=(e,a,t)=>{var r=K(e,a);if(r!=null)return R(e,"xAxis",r,t)},Qa=(e,a,t)=>{var r=z(e,a);if(r!=null)return R(e,"yAxis",r,t)},et=j([Ya,$],ja),at=j([Ua,$],Da),tt=j([Ne,$e,Ha,qa,Ja,Qa,et,F,We,Ce,at,$,_a],(e,a,t,r,n,i,o,s,c,l,d,v,f)=>{var y=c.chartData,m=c.dataStartIndex,x=c.dataEndIndex;if(!(v==null||o==null||a==null||s!=="horizontal"&&s!=="vertical"||t==null||r==null||n==null||i==null||l==null)){var h=v.data,b;if(h!=null&&h.length>0?b=h:b=y==null?void 0:y.slice(m,x+1),b!=null)return zt({layout:s,barSettings:v,pos:o,parentViewBox:a,bandSize:l,xAxis:t,yAxis:r,xAxisTicks:n,yAxisTicks:i,stackedData:d,displayedData:b,offset:e,cells:f,dataStartIndex:m})}}),rt=["index"];function Q(){return Q=Object.assign?Object.assign.bind():function(e){for(var a=1;a<arguments.length;a++){var t=arguments[a];for(var r in t)({}).hasOwnProperty.call(t,r)&&(e[r]=t[r])}return e},Q.apply(null,arguments)}function nt(e,a){if(e==null)return{};var t,r,n=it(e,a);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)t=i[r],a.indexOf(t)===-1&&{}.propertyIsEnumerable.call(e,t)&&(n[t]=e[t])}return n}function it(e,a){if(e==null)return{};var t={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(a.indexOf(r)!==-1)continue;t[r]=e[r]}return t}var De=u.createContext(void 0),ot=e=>{var a=u.useContext(De);if(a!=null)return a.stackId;if(e!=null)return Ve(e)},lt=(e,a)=>"recharts-bar-stack-clip-path-".concat(e,"-").concat(a),st=e=>{var a=u.useContext(De);if(a!=null){var t=a.stackId;return"url(#".concat(lt(t,e),")")}},je=e=>{var a=e.index,t=nt(e,rt),r=st(a);return u.createElement(_,Q({className:"recharts-bar-stack-layer",clipPath:r},t))},ut=["onMouseEnter","onMouseLeave","onClick"],ct=["value","background","tooltipPosition"],dt=["id"],vt=["onMouseEnter","onClick","onMouseLeave"];function he(e,a){return ht(e)||pt(e,a)||mt(e,a)||ft()}function ft(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
3
+ In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function mt(e,a){if(e){if(typeof e=="string")return ye(e,a);var t={}.toString.call(e).slice(8,-1);return t==="Object"&&e.constructor&&(t=e.constructor.name),t==="Map"||t==="Set"?Array.from(e):t==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?ye(e,a):void 0}}function ye(e,a){(a==null||a>e.length)&&(a=e.length);for(var t=0,r=Array(a);t<a;t++)r[t]=e[t];return r}function pt(e,a){var t=e==null?null:typeof Symbol<"u"&&e[Symbol.iterator]||e["@@iterator"];if(t!=null){var r,n,i,o,s=[],c=!0,l=!1;try{if(i=(t=t.call(e)).next,a!==0)for(;!(c=(r=i.call(t)).done)&&(s.push(r.value),s.length!==a);c=!0);}catch(d){l=!0,n=d}finally{try{if(!c&&t.return!=null&&(o=t.return(),Object(o)!==o))return}finally{if(l)throw n}}return s}}function ht(e){if(Array.isArray(e))return e}function D(){return D=Object.assign?Object.assign.bind():function(e){for(var a=1;a<arguments.length;a++){var t=arguments[a];for(var r in t)({}).hasOwnProperty.call(t,r)&&(e[r]=t[r])}return e},D.apply(null,arguments)}function xe(e,a){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);a&&(r=r.filter(function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable})),t.push.apply(t,r)}return t}function A(e){for(var a=1;a<arguments.length;a++){var t=arguments[a]!=null?arguments[a]:{};a%2?xe(Object(t),!0).forEach(function(r){yt(e,r,t[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):xe(Object(t)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r))})}return e}function yt(e,a,t){return(a=xt(a))in e?Object.defineProperty(e,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[a]=t,e}function xt(e){var a=gt(e,"string");return typeof a=="symbol"?a:a+""}function gt(e,a){if(typeof e!="object"||!e)return e;var t=e[Symbol.toPrimitive];if(t!==void 0){var r=t.call(e,a);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(a==="string"?String:Number)(e)}function U(e,a){if(e==null)return{};var t,r,n=bt(e,a);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)t=i[r],a.indexOf(t)===-1&&{}.propertyIsEnumerable.call(e,t)&&(n[t]=e[t])}return n}function bt(e,a){if(e==null)return{};var t={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(a.indexOf(r)!==-1)continue;t[r]=e[r]}return t}var At=e=>{var a=e.dataKey,t=e.name,r=e.fill,n=e.legendType,i=e.hide;return[{inactive:i,dataKey:a,type:n,color:r,value:Pe(t,a),payload:e}]},It=u.memo(e=>{var a=e.dataKey,t=e.stroke,r=e.strokeWidth,n=e.fill,i=e.name,o=e.hide,s=e.unit,c=e.formatter,l=e.tooltipType,d=e.id,v={dataDefinedOnItem:void 0,getPosition:ea,settings:{stroke:t,strokeWidth:r,fill:n,dataKey:a,nameKey:void 0,name:Pe(i,a),hide:o,type:l,color:n,unit:s,formatter:c,graphicalItemId:d}};return u.createElement(Qe,{tooltipEntrySettings:v})});function Pt(e){var a=G(Be),t=e.data,r=e.dataKey,n=e.background,i=e.allOtherBarProps,o=i.onMouseEnter,s=i.onMouseLeave,c=i.onClick,l=U(i,ut),d=Ee(o,r,i.id),v=Se(s),f=Oe(c,r,i.id);if(!n||t==null)return null;var y=ca(n);return u.createElement(ae,{zIndex:Ma(n,ee.barBackground)},t.map((m,x)=>{m.value;var h=m.background;m.tooltipPosition;var b=U(m,ct);if(!h)return null;var g=d(m,m.originalDataIndex),I=v(m,m.originalDataIndex),p=f(m,m.originalDataIndex),k=A(A(A(A(A({option:n,isActive:String(m.originalDataIndex)===a},b),{},{fill:"#eee"},h),y),we(l,m,x)),{},{onMouseEnter:g,onMouseLeave:I,onClick:p,dataKey:r,index:x,className:"recharts-bar-background-rectangle"});return u.createElement(re,D({key:"background-bar-".concat(x)},k))}))}function kt(e){var a=e.showLabels,t=e.children,r=e.rects,n=r==null?void 0:r.map(i=>{var o={x:i.x,y:i.y,width:i.width,lowerWidth:i.width,upperWidth:i.width,height:i.height};return A(A({},o),{},{value:i.value,payload:i.payload,parentViewBox:i.parentViewBox,viewBox:o,fill:i.fill})});return u.createElement(ma,{value:a?n:void 0},t)}function Bt(e){var a=e.shape,t=e.activeBar,r=e.baseProps,n=e.entry,i=e.index,o=e.dataKey,s=G(Be),c=G(ha),l=t&&String(n.originalDataIndex)===s&&(c==null||o===c),d=s!=null&&(String(n.originalDataIndex)!==s||c!=null&&o!==c),v=u.useState(!1),f=he(v,2),y=f[0],m=f[1],x=u.useState(!1),h=he(x,2),b=h[0],g=h[1];u.useEffect(()=>{var P;return l?(m(!0),P=requestAnimationFrame(()=>{g(!0)})):(g(!1),d&&m(!1)),()=>{cancelAnimationFrame(P)}},[l,d]);var I=u.useCallback(()=>{l||m(!1)},[l]),p=l&&b,k=l||y,B;l?t===!0?B=a:B=t:B=a;var w=u.createElement(re,D({},r,{name:String(r.name)},n,{isActive:p,option:B,index:i,dataKey:o,animationElapsedTime:e.animationElapsedTime,isAnimating:e.isAnimating,isEntrance:e.isEntrance,onTransitionEnd:I}));return k?u.createElement(ae,{zIndex:ee.activeBar},u.createElement(je,{index:n.originalDataIndex},w)):w}function Et(e){var a=e.shape,t=e.baseProps,r=e.entry,n=e.index,i=e.dataKey;return u.createElement(re,D({},t,{name:String(t.name)},r,{isActive:!1,option:a,index:n,dataKey:i,animationElapsedTime:e.animationElapsedTime,isAnimating:e.isAnimating,isEntrance:e.isEntrance}))}function St(e){var a,t=e.data,r=e.props,n=e.animationElapsedTime,i=e.isAnimating,o=e.isEntrance,s=(a=pa(r))!==null&&a!==void 0?a:{},c=s.id,l=U(s,dt),d=r.shape,v=r.dataKey,f=r.activeBar,y=r.onMouseEnter,m=r.onClick,x=r.onMouseLeave,h=U(r,vt),b=Ee(y,v,c),g=Se(x),I=Oe(m,v,c);return t?u.createElement(u.Fragment,null,t.map((p,k)=>u.createElement(je,D({index:p.originalDataIndex,key:"rectangle-".concat(p==null?void 0:p.x,"-").concat(p==null?void 0:p.y,"-").concat(p==null?void 0:p.value,"-").concat(k),className:"recharts-bar-rectangle"},we(h,p,k),{onMouseEnter:b(p,p.originalDataIndex),onMouseLeave:g(p,p.originalDataIndex),onClick:I(p,p.originalDataIndex)}),f?u.createElement(Bt,{shape:d,activeBar:f,baseProps:l,entry:p,index:k,dataKey:v,animationElapsedTime:n,isAnimating:i,isEntrance:o}):u.createElement(Et,{shape:d,baseProps:l,entry:p,index:k,dataKey:v,animationElapsedTime:n,isAnimating:i,isEntrance:o})))):null}var Ot=(e,a,t)=>e==null?[]:a===1?e.flatMap(r=>r.status==="removed"?[]:[r.next]):e.flatMap(r=>{if(r.status==="removed")return t==="horizontal"?[A(A({},r.prev),{},{height:O(r.prev.height,0,a),y:O(r.prev.y,r.prev.y+r.prev.height,a)})]:[A(A({},r.prev),{},{width:O(r.prev.width,0,a)})];if(r.status==="matched")return[A(A({},r.next),{},{x:O(r.prev.x,r.next.x,a),y:O(r.prev.y,r.next.y,a),width:O(r.prev.width,r.next.width,a),height:O(r.prev.height,r.next.height,a)})];var n=r.next;return t==="horizontal"?[A(A({},n),{},{height:O(0,n.height,a),y:O(n.stackedBarStart,n.y,a)})]:[A(A({},n),{},{width:O(0,n.width,a),x:O(n.stackedBarStart,n.x,a)})]});function wt(e){var a=e.props,t=e.previousRectanglesRef,r=a.data,n=a.isAnimationActive,i=a.animationBegin,o=a.animationDuration,s=a.animationEasing,c=a.animationInterpolateFn,l=a.layout,d=da(a.onAnimationStart,a.onAnimationEnd),v=d.isAnimating,f=d.handleAnimationStart,y=d.handleAnimationEnd;return u.createElement(kt,{showLabels:!v,rects:r},u.createElement(va,{animationInput:r,animationIdPrefix:"recharts-bar-",items:r,previousItemsRef:t,isAnimationActive:n,animationBegin:i,animationDuration:o,animationEasing:s,onAnimationStart:f,onAnimationEnd:y,animationInterpolateFn:c,animationMatchBy:a.animationMatchBy,layout:l},(m,x,h)=>u.createElement(_,null,u.createElement(St,{props:a,data:m,animationElapsedTime:x,isAnimating:v||x<1,isEntrance:h}))),u.createElement(fa,{label:a.label}),a.children)}function Ct(e){var a=u.useRef(null);return u.createElement(wt,{previousRectanglesRef:a,props:e})}var Me=0,Dt=(e,a)=>{var t=Array.isArray(e.value)?e.value[1]:e.value;return{x:e.x,y:e.y,value:t,errorVal:ke(e,a)}};class jt extends u.PureComponent{render(){var a=this.props,t=a.hide,r=a.data,n=a.dataKey,i=a.className,o=a.xAxisId,s=a.yAxisId,c=a.needClip,l=a.background,d=a.id;if(t||r==null)return null;var v=oa("recharts-bar",i),f=d;return u.createElement(_,{className:v,id:d},c&&u.createElement("defs",null,u.createElement(la,{clipPathId:f,xAxisId:o,yAxisId:s})),u.createElement(_,{className:"recharts-bar-rectangles",clipPath:c?"url(#clipPath-".concat(f,")"):void 0},u.createElement(Pt,{data:r,dataKey:n,background:l,allOtherBarProps:this.props}),u.createElement(Ct,this.props)))}}var Mt={activeBar:!1,animationBegin:0,animationDuration:400,animationEasing:"ease",animationInterpolateFn:Ot,animationMatchBy:Ye,background:!1,hide:!1,isAnimationActive:"auto",label:!1,legendType:"rect",minPointSize:Me,shape:te,xAxisId:0,yAxisId:0,zIndex:ee.bar};function Kt(e){var a=e.xAxisId,t=e.yAxisId,r=e.hide,n=e.legendType,i=e.minPointSize,o=e.activeBar,s=e.animationBegin,c=e.animationDuration,l=e.animationEasing,d=e.isAnimationActive,v=aa(a,t),f=v.needClip,y=ta(),m=Ie(),x=ra(e.children,ia),h=G(I=>tt(I,e.id,m,x));if(y!=="vertical"&&y!=="horizontal")return null;var b,g=h==null?void 0:h[0];return g==null||g.height==null||g.width==null?b=0:b=y==="vertical"?g.height/2:g.width/2,u.createElement(na,{xAxisId:a,yAxisId:t,data:h,dataPointFormatter:Dt,errorBarOffset:b},u.createElement(jt,D({},e,{layout:y,needClip:f,data:h,xAxisId:a,yAxisId:t,hide:r,legendType:n,minPointSize:i,activeBar:o,animationBegin:s,animationDuration:c,animationEasing:l,isAnimationActive:d})))}function zt(e){var a=e.layout,t=e.barSettings,r=t.dataKey,n=t.minPointSize,i=t.hasCustomShape,o=e.pos,s=e.bandSize,c=e.xAxis,l=e.yAxis,d=e.xAxisTicks,v=e.yAxisTicks,f=e.stackedData,y=e.displayedData,m=e.offset,x=e.cells,h=e.parentViewBox,b=e.dataStartIndex,g=a==="horizontal"?l:c,I=f?g.scale.domain():null,p=sa({numericAxis:g}),k=g.scale.map(p);return y.map((B,w)=>{var P,L,M,E,S,Z;if(f){var ne=f[w+b];if(ne==null)return null;P=ua(ne,I)}else P=ke(B,r),Array.isArray(P)||(P=[p,P]);var C=Fa(n,Me)(P[1],w);if(a==="horizontal"){var X,Y=l.scale.map(P[0]),N=l.scale.map(P[1]);if(Y==null||N==null)return null;L=de({axis:c,ticks:d,bandSize:s,offset:o.offset,entry:B,index:w}),M=(X=N??Y)!==null&&X!==void 0?X:void 0,E=o.size;var ie=Y-N;if(S=ya(ie)?0:ie,Z={x:L,y:m.top,width:E,height:m.height},Math.abs(C)>0&&Math.abs(S)<Math.abs(C)){var oe=ve(S||C)*(Math.abs(C)-Math.abs(S));M-=oe,S+=oe}}else{var H=c.scale.map(P[0]),le=c.scale.map(P[1]);if(H==null||le==null)return null;if(L=H,M=de({axis:l,ticks:v,bandSize:s,offset:o.offset,entry:B,index:w}),E=le-H,S=o.size,Z={x:m.left,y:M,width:m.width,height:S},Math.abs(C)>0&&Math.abs(E)<Math.abs(C)){var Ke=ve(E||C)*(Math.abs(C)-Math.abs(E));E+=Ke}}if(L==null||M==null||E==null||S==null||!i&&(E===0||S===0))return null;var ze=A(A({},B),{},{stackedBarStart:k,x:L,y:M,width:E,height:S,value:f?P:P[1],payload:B,background:Z,tooltipPosition:{x:L+E/2,y:M+S/2},parentViewBox:h,originalDataIndex:w},x&&x[w]&&x[w].props);return ze}).filter(Boolean)}function Lt(e){var a=Xe(e,Mt),t=ot(a.stackId),r=Ie();return u.createElement(He,{id:a.id,type:"bar"},n=>u.createElement(u.Fragment,null,u.createElement(qe,{legendPayload:At(a)}),u.createElement(It,{dataKey:a.dataKey,stroke:a.stroke,strokeWidth:a.strokeWidth,fill:a.fill,name:a.name,hide:a.hide,unit:a.unit,formatter:a.formatter,tooltipType:a.tooltipType,id:n}),u.createElement(Je,{type:"bar",id:n,data:void 0,xAxisId:a.xAxisId,yAxisId:a.yAxisId,zAxisId:0,dataKey:a.dataKey,stackId:t,hide:a.hide,barSize:a.barSize,minPointSize:a.minPointSize,maxBarSize:a.maxBarSize,isPanorama:r,hasCustomShape:a.shape!=null&&a.shape!==te}),u.createElement(ae,{zIndex:a.zIndex},u.createElement(Kt,D({},a,{id:n})))))}var Tt=u.memo(Lt,Ze);Tt.displayName="Bar";var Rt=["axis","item"],Wt=u.forwardRef((e,a)=>u.createElement(xa,{chartName:"BarChart",defaultTooltipEventType:"axis",validateTooltipEventTypes:Rt,tooltipPayloadSearcher:ga,categoricalChartProps:e,ref:a}));export{Wt as B,Tt as a};
@@ -0,0 +1,2 @@
1
+ import{r as p,f as D,a as P,b as y,c as B,d as I,j as t}from"./index-CUyiomzU.js";import{w as q}from"./workspace-label-CysPJNc0.js";import{I as g}from"./InfoTip-Ckc9_LpJ.js";import{b as Y}from"./prompt-templates-DhR4Qoy9.js";import{f as G}from"./FrictionCell-DciiSd3L.js";import"./Chip-Cpt_fEc9.js";const V=1e6,R=5,K=3;function c(e){return Number.isFinite(e)?e:0}function m(e){return c(e)/V}function W(e){let r=0,s=0;for(const d of e.buckets)r+=c(d.cache_creation_tokens),s+=c(d.cache_read_tokens);const o=r+s;return o===0?0:r/o}function E(e,r){return e.filter(s=>r===null||s.workspace_id===r)}const A={LOW:0,ELEVATED:1,HIGH:2};function X(e){if(e.length===0)return null;let r="LOW";for(const s of e){const o=G({api_error_count:s.api_error_count,tool_error_count:s.tool_error_count,test_fail_count:s.test_fail_count,compaction_count:s.compaction_count,interrupt_count:s.interrupt_count,user_turn_count:s.user_turn_count,turn_count:s.turns});A[o]>A[r]&&(r=o)}return r}function j(e,r){return{current:e,prior:r,delta:r===null?null:e-r}}function z(e,r){return c(r.cost_equiv_u)-c(e.cost_equiv_u)||e.session_id.localeCompare(r.session_id)}function J(e,r){const s=+(e.flavor!=="TURNKEY")-+(r.flavor!=="TURNKEY"),o=e.modeled_savings_usd_per_wk??Number.NEGATIVE_INFINITY,d=r.modeled_savings_usd_per_wk??Number.NEGATIVE_INFINITY;return s||d-o||e.detector_id.localeCompare(r.detector_id)||e.id.localeCompare(r.id)}function Q(e){const r=e.hotSessions.filter(n=>e.scopeWorkspaceId===null||n.workspace_id===e.scopeWorkspaceId).slice().sort(z).slice(0,R).map(n=>({session_id:n.session_id,usd:m(n.cost_equiv_u),turns:c(n.turns),avg_context_tokens:c(n.avg_context_tokens),model:n.model}));let s=0,o=0,d=0;for(const n of e.cacheTrend.buckets)s+=c(n.cache_creation_tokens),o+=c(n.cache_read_tokens),d+=c(n.turns);const x=s+o,b=e.recs.map(n=>{const f=Y(n);return f===null?null:{id:n.rec_id,detector_id:n.detector_id,lever:n.lever,flavor:f.flavor,modeled_savings_usd_per_wk:n.modeled_savings_u_per_wk===null?null:m(n.modeled_savings_u_per_wk),prompt:f.text}}).filter(n=>n!==null).sort(J),h=E(e.hotSessions,e.scopeWorkspaceId),l=e.prior!==void 0&&c(e.prior.overview.turns_total)>0?e.prior:null;return{scope:{label:e.scopeLabel,workspace_id:e.scopeWorkspaceId},verdict:{cost_usd:m(e.overview.cost_equiv_u),hot_session_count:h.length,peak_friction:X(h)},deltas:{spend_usd:j(m(e.overview.cost_equiv_u),l===null?null:m(l.overview.cost_equiv_u)),cache_write_share:j(W(e.cacheTrend),l===null?null:W(l.cacheTrend)),hot_session_count:j(h.length,l===null?null:E(l.hotSessions,e.scopeWorkspaceId).length)},actions:b.slice(0,K),overview:{cost_usd:m(e.overview.cost_equiv_u),turns:c(e.overview.turns),turns_total:c(e.overview.turns_total)},attribution:{hot_sessions:r,context_per_turn:e.overview.context_per_turn.map(n=>({model:n.model,turns:c(n.n),avg_context_per_turn:c(n.avg_context_per_turn),avg_output_per_turn:c(n.avg_output_per_turn),usd_per_turn:n.usd_per_turn===null?null:c(n.usd_per_turn)})),model_mix:e.overview.model_mix.map(n=>({model:n.model,turns:c(n.turns)})),cache_mix:{bucket_count:e.cacheTrend.buckets.length,spike_bucket_count:e.cacheTrend.spike_buckets.length,cache_write_tokens:s,cache_read_tokens:o,cache_read_share:x===0?0:o/x,turns:d}}}}function a(e){return c(e).toLocaleString(void 0,{maximumFractionDigits:6})}function k(e){const r=e.prior===null?"—":a(e.prior),s=e.delta===null?"—":a(e.delta);return`${a(e.current)} (prior ${r}, delta ${s})`}function Z(e){const r=[`# Brief: ${e.scope.label}`,"","## Verdict",`- Cost equivalent USD: ${a(e.verdict.cost_usd)}`,`- Hot sessions: ${a(e.verdict.hot_session_count)}`,`- Peak friction: ${e.verdict.peak_friction??"none"}`,"","## Change vs prior 7 days",`- Spend equivalent USD: ${k(e.deltas.spend_usd)}`,`- Cache-write share: ${k(e.deltas.cache_write_share)}`,`- Hot sessions: ${k(e.deltas.hot_session_count)}`,"","## Do these three things",`- Actions: ${a(e.actions.length)}`];return e.actions.forEach((s,o)=>{r.push(`### ${o+1}. ${s.detector_id}: ${s.id}`),r.push(`- Lever: ${s.lever}`),r.push(`- Mode: ${s.flavor}`),r.push(`- Modeled savings USD/week: ${s.modeled_savings_usd_per_wk===null?"0":a(s.modeled_savings_usd_per_wk)}`),r.push("- Prompt artifact:"),r.push(s.prompt)}),r.push("","<details>","","## Attribution",`- Hot sessions: ${a(e.attribution.hot_sessions.length)}`,...e.attribution.hot_sessions.map(s=>`- Session ${s.session_id}: USD ${a(s.usd)}, turns ${a(s.turns)}, context ${a(s.avg_context_tokens)}, model ${s.model}`),`- Context models: ${a(e.attribution.context_per_turn.length)}`,...e.attribution.context_per_turn.map(s=>{const o=s.usd_per_turn===null?"unpriced 0":`USD/turn ${a(s.usd_per_turn)}`;return`- Model ${s.model}: turns ${a(s.turns)}, context/turn ${a(s.avg_context_per_turn)}, output/turn ${a(s.avg_output_per_turn)}, ${o}`}),`- Model mix entries: ${a(e.attribution.model_mix.length)}`,...e.attribution.model_mix.map(s=>`- Model mix ${s.model}: turns ${a(s.turns)}`),`- Cache mix: ${a(e.attribution.cache_mix.bucket_count)} buckets, ${a(e.attribution.cache_mix.spike_bucket_count)} spikes, ${a(e.attribution.cache_mix.cache_write_tokens)} write tokens, ${a(e.attribution.cache_mix.cache_read_tokens)} read tokens, ${a(e.attribution.cache_mix.cache_read_share)} read share, ${a(e.attribution.cache_mix.turns)} turns`,"","</details>"),r.join(`
2
+ `)}const M=7,L=1440*60*1e3;function ee(e=new Date){const r=e.getTime(),s=r-M*L,o=s-M*L;return{current:{from:new Date(s).toISOString(),to:new Date(r).toISOString()},prior:{from:new Date(o).toISOString(),to:new Date(s).toISOString()},label:`${new Date(s).toLocaleDateString()} – ${new Date(r).toLocaleDateString()}`}}function u(e){return e.toLocaleString(void 0,{maximumFractionDigits:2})}function v(e){return`$${u(e)}`}function O(e){return`${(e*100).toLocaleString(void 0,{maximumFractionDigits:1})}%`}function te(e,r){if(r===null)return"Global";const s=e.find(o=>o.workspace_id===r);return s===void 0?r:q(s)}function se({delta:e,format:r}){if(e.delta===null)return t.jsx("span",{className:"brief-tile-delta brief-tile-delta-none",title:"No prior-week data to compare",children:"— vs prior week"});const s=e.delta>0?"up":e.delta<0?"down":"flat",o=s==="up"?"▲":s==="down"?"▼":"▬",d=e.delta>0?"+":"";return t.jsxs("span",{className:`brief-tile-delta brief-tile-delta-${s}`,children:[o," ",d,r(e.delta)," vs prior week"]})}function N({label:e,value:r,delta:s,format:o}){return t.jsxs("div",{className:"brief-tile",children:[t.jsx("div",{className:"brief-tile-label",children:e}),t.jsx("div",{className:"brief-tile-value",children:r}),t.jsx(se,{delta:s,format:o})]})}function re({brief:e}){const{cache_mix:r,hot_sessions:s}=e.attribution;return t.jsxs("details",{className:"briefs-details",children:[t.jsx("summary",{children:"Attribution & hot sessions"}),t.jsx("div",{className:"table-wrap",children:t.jsxs("table",{children:[t.jsx("thead",{children:t.jsxs("tr",{children:[t.jsx("th",{scope:"col",children:"Measure"}),t.jsx("th",{scope:"col",children:"Value"})]})}),t.jsxs("tbody",{children:[t.jsxs("tr",{children:[t.jsx("td",{children:"Cost equivalent"}),t.jsx("td",{children:v(e.overview.cost_usd)})]}),t.jsxs("tr",{children:[t.jsx("td",{children:"Reconciled turns"}),t.jsx("td",{children:u(e.overview.turns)})]}),t.jsxs("tr",{children:[t.jsx("td",{children:"Total turns"}),t.jsx("td",{children:u(e.overview.turns_total)})]}),t.jsxs("tr",{children:[t.jsx("td",{children:"Cache write tokens"}),t.jsx("td",{children:u(r.cache_write_tokens)})]}),t.jsxs("tr",{children:[t.jsx("td",{children:"Cache read tokens"}),t.jsx("td",{children:u(r.cache_read_tokens)})]}),t.jsxs("tr",{children:[t.jsx("td",{children:"Hot sessions"}),t.jsx("td",{children:u(s.length)})]})]})]})}),s.length>0&&t.jsx("div",{className:"table-wrap",children:t.jsxs("table",{children:[t.jsx("thead",{children:t.jsxs("tr",{children:[t.jsx("th",{scope:"col",children:"Session"}),t.jsx("th",{scope:"col",children:"Cost"}),t.jsx("th",{scope:"col",children:"Turns"}),t.jsx("th",{scope:"col",children:"Context / turn"}),t.jsx("th",{scope:"col",children:"Model"})]})}),t.jsx("tbody",{children:s.map(o=>t.jsxs("tr",{children:[t.jsx("td",{children:o.session_id}),t.jsx("td",{children:v(o.usd)}),t.jsx("td",{children:u(o.turns)}),t.jsx("td",{children:u(o.avg_context_tokens)}),t.jsx("td",{children:o.model})]},o.session_id))})]})})]})}function de(){const[e,r]=p.useState({status:"loading"}),[s,o]=p.useState(null),[d,x]=p.useState(!1),[b,h]=p.useState(null),l=p.useMemo(()=>ee(),[]);p.useEffect(()=>{let i=!1;return Promise.all([D(l.current),P(l.current),y(l.current),B(),I(l.current),D(l.prior),y(l.prior),I(l.prior)]).then(([_,w,S,$,U,T,C,F])=>{if(!i){if(_.data===null||w.data===null||S.data===null||$.data===null||T.data===null||C.data===null)throw new Error("Brief data was unavailable");r({status:"ok",overview:_.data,workspaces:w.data.items,cacheTrend:S.data,recs:$.data,hotSessions:U,priorOverview:T.data,priorCacheTrend:C.data,priorHotSessions:F})}}).catch(_=>{i||r({status:"error",message:String(_)})}),()=>{i=!0}},[l]);const n=e.status==="ok"?Q({scopeLabel:te(e.workspaces,s),scopeWorkspaceId:s,overview:e.overview,hotSessions:e.hotSessions,cacheTrend:e.cacheTrend,recs:e.recs.active.filter(i=>s===null||i.scope_workspace_id===null||i.scope_workspace_id===s),prior:{overview:e.priorOverview,hotSessions:e.priorHotSessions,cacheTrend:e.priorCacheTrend}}):null;async function f(){n!==null&&(await navigator.clipboard.writeText(Z(n)),x(!0),window.setTimeout(()=>x(!1),2e3))}async function H(i,_){await navigator.clipboard.writeText(_),h(i),window.setTimeout(()=>h(w=>w===i?null:w),2e3)}return t.jsxs("div",{className:"briefs-page",children:[t.jsx("div",{className:"page-top",children:t.jsxs("div",{className:"page-title",children:[t.jsx("h1",{children:"Briefs"}),t.jsx("p",{className:"page-sub",children:"This week in one page: verdict, what changed, what to do"})]})}),e.status==="loading"&&t.jsx("div",{className:"card","aria-busy":"true",children:"Loading brief data..."}),e.status==="error"&&t.jsxs("div",{className:"banner banner-error",role:"alert",children:["Could not load brief: ",e.message]}),e.status==="ok"&&n!==null&&t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"briefs-controls",children:[t.jsxs("label",{className:"briefs-scope",htmlFor:"briefs-scope",children:["Scope",t.jsxs("select",{id:"briefs-scope",value:s??"",onChange:i=>o(i.target.value||null),children:[t.jsx("option",{value:"",children:"Global"}),e.workspaces.map(i=>t.jsx("option",{value:i.workspace_id,children:q(i)},i.workspace_id))]})]}),t.jsx("span",{className:"briefs-window",title:"Rolling seven-day window",children:l.label}),t.jsx("button",{type:"button",className:"btn-primary",onClick:()=>void f(),children:d?"Copied":"Copy as markdown"})]}),t.jsx("section",{className:"card briefs-verdict","aria-labelledby":"briefs-verdict-heading",children:t.jsxs("h2",{id:"briefs-verdict-heading",className:"briefs-verdict-line",children:[t.jsx("strong",{children:v(n.verdict.cost_usd)})," ",t.jsx(g,{label:"What cap-weighted equivalent means",content:"Cost weighted the way your usage cap counts it — cache reads count roughly a tenth of fresh tokens. It's the number that actually moves you toward a limit, not raw token cost.",children:"cap-weighted equivalent"}),t.jsx("span",{className:"briefs-verdict-sep",children:" · "}),u(n.verdict.hot_session_count)," hot sessions",t.jsx("span",{className:"briefs-verdict-sep",children:" · "}),n.verdict.peak_friction===null?t.jsx("span",{className:"briefs-friction",children:"no friction signal"}):t.jsxs("span",{className:`briefs-friction briefs-friction-${n.verdict.peak_friction}`,children:[t.jsx(g,{label:"What peak friction means",content:"The single worst per-session friction band across this scope's hot sessions, not an average. It flags whether any one session went badly, which an average would hide.",children:"peak friction"})," ",n.verdict.peak_friction]})]})}),t.jsxs("div",{className:"brief-tiles-head",children:[t.jsx("span",{className:"brief-tiles-heading",children:"Week-over-week changes"}),t.jsx(g,{label:"What the delta tiles show",content:"Change versus the prior 7 days for spend, cache-write share, and hot-session count. A green delta is improvement; a red one is where this week got worse."})]}),t.jsxs("div",{className:"brief-tiles",children:[t.jsx(N,{label:"Spend equivalent",value:v(n.deltas.spend_usd.current),delta:n.deltas.spend_usd,format:v}),t.jsx(N,{label:"Cache-write share",value:O(n.deltas.cache_write_share.current),delta:n.deltas.cache_write_share,format:O}),t.jsx(N,{label:"Hot sessions",value:u(n.deltas.hot_session_count.current),delta:n.deltas.hot_session_count,format:u})]}),t.jsxs("section",{className:"card briefs-actions","aria-labelledby":"briefs-actions-heading",children:[t.jsx("div",{className:"section-head",children:t.jsx("h2",{id:"briefs-actions-heading",children:"Do these three things"})}),n.actions.length===0?t.jsx("p",{className:"kpi-off-hint",children:"No active recommendations were returned for this scope."}):t.jsx("ol",{className:"briefs-action-list",children:n.actions.map(i=>t.jsxs("li",{className:"briefs-action",children:[t.jsxs("div",{className:"briefs-action-main",children:[t.jsx("strong",{children:i.lever}),t.jsxs("span",{className:"kpi-off-hint",children:[i.detector_id," · ",i.flavor," · modeled savings"," ",i.modeled_savings_usd_per_wk===null?"unavailable":`${v(i.modeled_savings_usd_per_wk)} / week`]})]}),t.jsx("button",{type:"button",className:"btn-secondary briefs-action-copy",onClick:()=>void H(i.id,i.prompt),children:b===i.id?"Copied":"Copy prompt"})]},i.id))})]}),t.jsx(re,{brief:n})]})]})}export{de as default};