@circuitwall/jarela 0.9.3 → 0.10.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 (94) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  16. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/v1/dashboard/metrics/route.js +72 -5
  24. package/.next/standalone/.next/server/app/api/v1/dashboard/metrics/route.js.map +1 -1
  25. package/.next/standalone/.next/server/app/api/v1/extensions/route.js +2 -2
  26. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +2 -2
  27. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +136 -26
  28. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js.map +1 -1
  29. package/.next/standalone/.next/server/app/api/v1/tools/route.js +2 -2
  30. package/.next/standalone/.next/server/app/index.html +2 -2
  31. package/.next/standalone/.next/server/app/index.rsc +3 -3
  32. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  34. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/page.js +266 -40
  38. package/.next/standalone/.next/server/app/page.js.map +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/setup.html +1 -1
  42. package/.next/standalone/.next/server/app/setup.rsc +2 -2
  43. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +2 -2
  44. package/.next/standalone/.next/server/app/setup.segments/_head.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/setup.segments/_index.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  47. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +1 -1
  48. package/.next/standalone/.next/server/app/setup.segments/setup.segment.rsc +1 -1
  49. package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
  50. package/.next/standalone/.next/server/chunks/210.js +1 -1
  51. package/.next/standalone/.next/server/chunks/2151.js +60 -2
  52. package/.next/standalone/.next/server/chunks/2151.js.map +1 -1
  53. package/.next/standalone/.next/server/chunks/614.js +336 -93
  54. package/.next/standalone/.next/server/chunks/614.js.map +1 -1
  55. package/.next/standalone/.next/server/chunks/6765.js +35 -0
  56. package/.next/standalone/.next/server/chunks/6765.js.map +1 -1
  57. package/.next/standalone/.next/server/chunks/8697.js +15246 -15002
  58. package/.next/standalone/.next/server/chunks/8697.js.map +1 -1
  59. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  60. package/.next/standalone/.next/server/pages/404.html +2 -2
  61. package/.next/standalone/.next/server/pages/500.html +1 -1
  62. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  63. package/.next/standalone/.next/static/chunks/{3741-344e2bfc5028b9c8.js → 3741-2d64471ff763b8fa.js} +36 -1
  64. package/.next/standalone/.next/static/chunks/3741-2d64471ff763b8fa.js.map +1 -0
  65. package/.next/standalone/.next/static/chunks/app/{page-c77ab600642bbfc2.js → page-318743bf47fac345.js} +267 -41
  66. package/.next/standalone/.next/static/chunks/app/page-318743bf47fac345.js.map +1 -0
  67. package/.next/standalone/.next/static/css/b6b85b0f13bc0e98.css +5 -0
  68. package/.next/standalone/.next/static/css/b6b85b0f13bc0e98.css.map +1 -0
  69. package/.next/standalone/package.json +1 -1
  70. package/CHANGELOG.md +48 -0
  71. package/README.md +2 -0
  72. package/api/client.ts +37 -1
  73. package/api/types.ts +18 -0
  74. package/app/api/v1/threads/[thread_id]/run/route.ts +69 -22
  75. package/components/agents/AgentEditor.tsx +7 -4
  76. package/components/chat/MessageBubble.tsx +108 -1
  77. package/components/dashboard/DashboardPanel.tsx +79 -21
  78. package/hooks/useSSE.ts +22 -9
  79. package/lib/agents/prepare/system-prompt.ts +30 -0
  80. package/lib/agents/run-registry.test.ts +94 -0
  81. package/lib/agents/run-registry.ts +60 -1
  82. package/lib/stores/dashboard-metrics.test.ts +33 -0
  83. package/lib/stores/dashboard-metrics.ts +93 -1
  84. package/lib/tools/exec.ts +9 -5
  85. package/lib/tools/files.ts +6 -0
  86. package/lib/tools/safety.test.ts +95 -0
  87. package/lib/tools/safety.ts +147 -0
  88. package/package.json +1 -1
  89. package/.next/standalone/.next/static/chunks/3741-344e2bfc5028b9c8.js.map +0 -1
  90. package/.next/standalone/.next/static/chunks/app/page-c77ab600642bbfc2.js.map +0 -1
  91. package/.next/standalone/.next/static/css/53f85613a5500253.css +0 -5
  92. package/.next/standalone/.next/static/css/53f85613a5500253.css.map +0 -1
  93. /package/.next/standalone/.next/static/{6uLoytvvEtLKIblEB53e0 → 8qTBpUDFnSMYwe3Zc0bGV}/_buildManifest.js +0 -0
  94. /package/.next/standalone/.next/static/{6uLoytvvEtLKIblEB53e0 → 8qTBpUDFnSMYwe3Zc0bGV}/_ssgManifest.js +0 -0
@@ -2434,13 +2434,23 @@ function useSSE(onDone) {
2434
2434
  if (err.name !== "AbortError") {
2435
2435
  setError(String(err));
2436
2436
  }
2437
- setStreaming(false);
2438
- setStreamingContent("");
2439
- setThinkingContent("");
2440
- closeActivity();
2441
2437
  return {
2442
2438
  accepted: false
2443
2439
  };
2440
+ } finally{
2441
+ // Always release the gate when the stream ends — defends against the
2442
+ // consume() loop returning without ever observing a terminal `done`
2443
+ // event (e.g. the EventSource closed cleanly with zero events). If
2444
+ // we relied solely on the `done` branch inside consume(), the chat
2445
+ // would stay locked behind the Stop button forever.
2446
+ //
2447
+ // We intentionally do NOT clear streamingContent / thinkingContent
2448
+ // here — the consumer (ChatView) swaps them for the persisted
2449
+ // assistant bubble once the refetch lands; clearing now would yank
2450
+ // the text out from under the user. The next start()/attach() resets
2451
+ // them.
2452
+ setStreaming(false);
2453
+ closeActivity();
2444
2454
  }
2445
2455
  }, [
2446
2456
  consume,
@@ -2493,15 +2503,18 @@ function useSSE(onDone) {
2493
2503
  try {
2494
2504
  await consume((0,client/* subscribeRun */._C)(threadId, ctrl.signal));
2495
2505
  } catch (err) {
2496
- // Attach failures are non-fatal — clear the gate and let the consumer
2497
- // drain anything queued during session load. The common case is
2498
- // "no run to attach to" (server returns 404, EventSource fails to
2499
- // open) — completely normal when navigating into an idle session.
2500
- setStreaming(false);
2501
- closeActivity();
2502
2506
  if (err.name !== "AbortError") {
2503
2507
  onDone?.();
2504
2508
  }
2509
+ } finally{
2510
+ // Always release the optimistic gate when the stream ends — whether
2511
+ // via terminal `done`/`error` event, a thrown failure, or a clean
2512
+ // EventSource close with no events (idle thread, server returned 404
2513
+ // so the iterator exited without yielding). Without this, navigating
2514
+ // into an idle thread leaves streaming=true forever: the Stop button
2515
+ // hangs in the composer and the "Reconnecting…" badge never clears.
2516
+ setStreaming(false);
2517
+ closeActivity();
2505
2518
  }
2506
2519
  }, [
2507
2520
  consume,
@@ -32860,6 +32873,123 @@ function UserAvatar({ profile }) {
32860
32873
  // lines at the top of every bubble. Format is fixed by dispatcher; if either
32861
32874
  // side changes, update both.
32862
32875
  const parseBridgeContext = parseBridgePrompt;
32876
+ const SILENT_TRIGGER_RE = /\n+\[SILENT_TRIGGER\][^\n]*(?:\n(?!\n).*)*$/;
32877
+ // Strip the `[SILENT_TRIGGER] …` envelope that runTriggerAgent appends
32878
+ // in silent mode so the card can render the user's original prompt and
32879
+ // surface a separate "Silent" pill instead of leaking framework prose.
32880
+ function stripSilentEnvelope(text) {
32881
+ const m = SILENT_TRIGGER_RE.exec(text);
32882
+ if (!m) return {
32883
+ text,
32884
+ silent: false
32885
+ };
32886
+ return {
32887
+ text: text.slice(0, m.index).trimEnd(),
32888
+ silent: true
32889
+ };
32890
+ }
32891
+ function parseTriggerMessage(category, raw) {
32892
+ if (category === "scheduled_task") {
32893
+ const { text, silent } = stripSilentEnvelope(raw);
32894
+ return {
32895
+ kind: "scheduled_task",
32896
+ prompt: text,
32897
+ silent
32898
+ };
32899
+ }
32900
+ if (category === "watcher") {
32901
+ const { text, silent } = stripSilentEnvelope(raw);
32902
+ const headerRe = /^Watcher\s+"([^"]*)"\s+detected a change\.\s*\n+Tool:\s*([^\n]+)\s*\nArgs:\s*([\s\S]*?)\n+---\s*Diff[^\n]*---\s*\n([\s\S]*?)\n\n([\s\S]*)$/;
32903
+ const m = headerRe.exec(text);
32904
+ if (!m) return null;
32905
+ return {
32906
+ kind: "watcher",
32907
+ label: m[1],
32908
+ tool: m[2].trim(),
32909
+ args: m[3].trim(),
32910
+ diff: m[4].trim(),
32911
+ directive: m[5].trim(),
32912
+ silent
32913
+ };
32914
+ }
32915
+ return null;
32916
+ }
32917
+ // Compact header card for trigger-originated user messages (scheduled
32918
+ // tasks + watchers, ADR-0027/ADR-0032). Surfaces the trigger type with
32919
+ // an icon + label so the operator can tell at a glance that the prompt
32920
+ // came from automation and not from them. Watchers additionally expose
32921
+ // the diff context in a collapsed section to keep large diffs out of
32922
+ // the main bubble height.
32923
+ function TriggerMessageCard({ data }) {
32924
+ const [diffOpen, setDiffOpen] = (0,react.useState)(false);
32925
+ const Icon = data.kind === "scheduled_task" ? Clock : eye/* default */.A;
32926
+ const label = data.kind === "scheduled_task" ? "Scheduled task" : `Watcher: ${data.label || "(unnamed)"}`;
32927
+ return /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("div", {
32928
+ className: "flex flex-col gap-1.5 min-w-0",
32929
+ children: [
32930
+ /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("div", {
32931
+ className: "flex items-center gap-1.5 text-[11px] text-white/85 min-w-0",
32932
+ children: [
32933
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)(Icon, {
32934
+ size: 11,
32935
+ className: "shrink-0"
32936
+ }),
32937
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
32938
+ className: "font-medium truncate",
32939
+ children: label
32940
+ }),
32941
+ data.kind === "watcher" && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
32942
+ className: "px-1.5 py-0.5 rounded-full bg-white/15 text-[9.5px] uppercase tracking-wide shrink-0",
32943
+ children: data.tool
32944
+ }),
32945
+ data.silent && /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
32946
+ className: "flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-white/15 text-[9.5px] uppercase tracking-wide shrink-0",
32947
+ title: "Silent trigger: reply only if material",
32948
+ children: [
32949
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)(EyeOff, {
32950
+ size: 9
32951
+ }),
32952
+ "silent"
32953
+ ]
32954
+ })
32955
+ ]
32956
+ }),
32957
+ data.kind === "watcher" && /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("button", {
32958
+ type: "button",
32959
+ onClick: ()=>setDiffOpen((v)=>!v),
32960
+ className: "flex items-center gap-1 text-left text-[11px] text-white/75 hover:text-white/95",
32961
+ "aria-expanded": diffOpen,
32962
+ title: diffOpen ? "Hide diff" : "Show diff",
32963
+ children: [
32964
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)(ChevronRight, {
32965
+ size: 11,
32966
+ className: `shrink-0 transition-transform ${diffOpen ? "rotate-90" : ""}`
32967
+ }),
32968
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
32969
+ children: "Change context"
32970
+ })
32971
+ ]
32972
+ }),
32973
+ data.kind === "watcher" && diffOpen && /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("div", {
32974
+ className: "ml-4 flex flex-col gap-1",
32975
+ children: [
32976
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("pre", {
32977
+ className: "m-0 px-2 py-1.5 rounded bg-black/25 text-[11px] leading-snug whitespace-pre-wrap break-words text-white/90 max-h-72 overflow-auto",
32978
+ children: data.args
32979
+ }),
32980
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("pre", {
32981
+ className: "m-0 px-2 py-1.5 rounded bg-black/25 text-[11px] leading-snug whitespace-pre-wrap break-words text-white/90 max-h-96 overflow-auto",
32982
+ children: data.diff
32983
+ })
32984
+ ]
32985
+ }),
32986
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("p", {
32987
+ className: "whitespace-pre-wrap text-[13.5px] leading-relaxed",
32988
+ children: data.kind === "scheduled_task" ? data.prompt : data.directive
32989
+ })
32990
+ ]
32991
+ });
32992
+ }
32863
32993
  // Compact header card for inbound bridge messages. Shows sender + chat
32864
32994
  // context as a single line of metadata above the actual message text, so a
32865
32995
  // WhatsApp DM looks like "Alice • DM\n<text>" and a group message looks
@@ -33667,6 +33797,11 @@ const MessageBubble = /*#__PURE__*/ (0,react.memo)(function MessageBubble({ mess
33667
33797
  className: `rounded-2xl px-4 py-3 text-sm leading-relaxed max-w-full overflow-hidden ${isUser ? "glass-bubble-accent text-white rounded-br-sm" : "glass-bubble text-fg rounded-bl-sm"}`,
33668
33798
  children: [
33669
33799
  typeof parsed === "string" ? isUser ? (()=>{
33800
+ const category = "category" in message ? message.category : null;
33801
+ const trigger = parseTriggerMessage(category, parsed);
33802
+ if (trigger) return /*#__PURE__*/ (0,react_jsx_runtime.jsx)(TriggerMessageCard, {
33803
+ data: trigger
33804
+ });
33670
33805
  const bridge = parseBridgeContext(parsed);
33671
33806
  if (bridge) return /*#__PURE__*/ (0,react_jsx_runtime.jsx)(BridgeMessageCard, {
33672
33807
  ctx: bridge
@@ -40569,7 +40704,9 @@ function ToolGroupBlock({ group, categories, advancedMode, selected, onToggleToo
40569
40704
  const selectedInGroup = allTools.filter((t)=>selected.includes(t.name)).length;
40570
40705
  const allOn = selectedInGroup === allTools.length;
40571
40706
  const someOn = selectedInGroup > 0 && !allOn;
40572
- const [open, setOpen] = (0,react.useState)(selectedInGroup > 0);
40707
+ // Collapsed by default to keep the editor compact on small / PWA viewports;
40708
+ // the header still surfaces the selected/total count.
40709
+ const [open, setOpen] = (0,react.useState)(false);
40573
40710
  const headerRef = (0,react.useRef)(null);
40574
40711
  process.env.__NEXT_PRIVATE_MINIMIZE_MACRO_FALSE && (0,react.useEffect)(()=>{
40575
40712
  if (headerRef.current) headerRef.current.indeterminate = someOn;
@@ -40599,13 +40736,14 @@ function ToolGroupBlock({ group, categories, advancedMode, selected, onToggleToo
40599
40736
  }
40600
40737
  // Collapsible per-category block with a tri-state header checkbox. The
40601
40738
  // header toggle flips the entire category on/off; individual tool checkboxes
40602
- // stay available for fine-grained control. Collapsed-by-default when no tool
40603
- // in the category is selected, to keep the editor compact.
40739
+ // stay available for fine-grained control. Collapsed by default to keep the
40740
+ // editor short on mobile / PWA viewports.
40604
40741
  function ToolCategoryBlock({ category, tools, advancedMode, selected, onToggleTool, onToggleCategory, onToggleCategoryPermission }) {
40605
40742
  const selectedInCat = tools.filter((t)=>selected.includes(t.name)).length;
40606
40743
  const allOn = selectedInCat === tools.length;
40607
40744
  const someOn = selectedInCat > 0 && !allOn;
40608
- const [open, setOpen] = (0,react.useState)(selectedInCat > 0);
40745
+ // Collapsed by default see ToolGroupBlock comment.
40746
+ const [open, setOpen] = (0,react.useState)(false);
40609
40747
  const headerRef = (0,react.useRef)(null);
40610
40748
  // Render the tri-state indeterminate dash via the DOM property (React
40611
40749
  // doesn't expose it as a JSX attribute).
@@ -52043,20 +52181,6 @@ function DashboardPanel() {
52043
52181
  className: "text-[11px] text-[var(--text-secondary)]",
52044
52182
  children: currencyInfo.source === "manual" ? `Currency manually set to ${currencyInfo.currency}.` : currencyInfo.source === "location" ? `Currency converted from USD to ${currencyInfo.currency} based on saved location.` : "Currency defaults to USD because no location-based currency is available."
52045
52183
  }),
52046
- /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("section", {
52047
- className: "rounded-2xl border border-[var(--border)] bg-[var(--bg-secondary)]/90 p-4 shadow-sm",
52048
- children: [
52049
- /*#__PURE__*/ (0,react_jsx_runtime.jsx)("h3", {
52050
- className: "text-sm font-medium text-[var(--text-primary)] mb-3",
52051
- children: "Token usage over time"
52052
- }),
52053
- /*#__PURE__*/ (0,react_jsx_runtime.jsx)(InteractiveTokenChart, {
52054
- series: series,
52055
- selectedDay: selectedDay,
52056
- onSelectDay: (day)=>setSelectedDay((prev)=>prev === day ? null : day)
52057
- })
52058
- ]
52059
- }),
52060
52184
  /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("section", {
52061
52185
  className: "rounded-2xl border border-[var(--border)] bg-[var(--bg-secondary)]/90 p-4 shadow-sm",
52062
52186
  children: [
@@ -52065,7 +52189,7 @@ function DashboardPanel() {
52065
52189
  children: [
52066
52190
  /*#__PURE__*/ (0,react_jsx_runtime.jsx)("h3", {
52067
52191
  className: "text-sm font-medium text-[var(--text-primary)]",
52068
- children: "Estimated cost over time"
52192
+ children: "Tokens & cost over time"
52069
52193
  }),
52070
52194
  selectedDay ? /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("button", {
52071
52195
  type: "button",
@@ -52086,11 +52210,19 @@ function DashboardPanel() {
52086
52210
  })
52087
52211
  ]
52088
52212
  }),
52089
- /*#__PURE__*/ (0,react_jsx_runtime.jsx)(InteractiveCostChart, {
52213
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)(InteractiveTokenChart, {
52090
52214
  series: series,
52091
- currencyInfo: currencyInfo,
52092
52215
  selectedDay: selectedDay,
52093
52216
  onSelectDay: (day)=>setSelectedDay((prev)=>prev === day ? null : day)
52217
+ }),
52218
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
52219
+ className: "mt-3 border-t border-[var(--border)]/60 pt-3",
52220
+ children: /*#__PURE__*/ (0,react_jsx_runtime.jsx)(InteractiveCostChart, {
52221
+ series: series,
52222
+ currencyInfo: currencyInfo,
52223
+ selectedDay: selectedDay,
52224
+ onSelectDay: (day)=>setSelectedDay((prev)=>prev === day ? null : day)
52225
+ })
52094
52226
  })
52095
52227
  ]
52096
52228
  }),
@@ -52485,6 +52617,13 @@ function InsightChip({ label, value, hint }) {
52485
52617
  ]
52486
52618
  });
52487
52619
  }
52620
+ // Per-tier breakdown colors shared by the stacked token chart legend.
52621
+ const TIER_COLORS = {
52622
+ hot: "#22d3ee",
52623
+ warm: "#f59e0b",
52624
+ facts: "#a78bfa",
52625
+ overhead: "#94a3b8"
52626
+ };
52488
52627
  function SharedDonutChart({ ariaLabel, size, centerAmount, slices, hovered, onHoverChange }) {
52489
52628
  const uid = (0,react.useId)().replace(/:/g, "");
52490
52629
  const [animCycle, setAnimCycle] = (0,react.useState)(0);
@@ -52894,6 +53033,15 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
52894
53033
  const totalHeight = hasData && maxTotal > 0 ? Math.max(4, Math.round(total / maxTotal * 150)) : 0;
52895
53034
  const inputHeight = hasData ? Math.round(point.input_tokens_est / total * totalHeight) : 0;
52896
53035
  const outputHeight = hasData ? Math.max(0, totalHeight - inputHeight) : 0;
53036
+ // Subdivide the input portion by measured tier breakdown when the
53037
+ // day has at least one snapshotted assistant turn. Legacy days
53038
+ // (measured_input_tokens === 0) fall back to a solid violet block.
53039
+ const tier = point.tier_tokens;
53040
+ const tierTotal = tier?.measured_input_tokens ?? 0;
53041
+ const hotPx = tierTotal > 0 ? Math.round(tier.hot_tokens / tierTotal * inputHeight) : 0;
53042
+ const warmPx = tierTotal > 0 ? Math.round(tier.warm_tokens / tierTotal * inputHeight) : 0;
53043
+ const factsPx = tierTotal > 0 ? Math.round(tier.facts_tokens / tierTotal * inputHeight) : 0;
53044
+ const overheadPx = tierTotal > 0 ? Math.max(0, inputHeight - hotPx - warmPx - factsPx) : 0;
52897
53045
  const isActive = idx === hovered;
52898
53046
  const isSelected = selectedDay === point.day;
52899
53047
  const clickable = !!onSelectDay && hasData;
@@ -52924,7 +53072,42 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
52924
53072
  transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18}ms`
52925
53073
  }
52926
53074
  }),
52927
- /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
53075
+ tierTotal > 0 ? /*#__PURE__*/ (0,react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, {
53076
+ children: [
53077
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
53078
+ className: "w-full",
53079
+ style: {
53080
+ background: TIER_COLORS.hot,
53081
+ height: barsReady ? `${hotPx}px` : "0px",
53082
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 30}ms`
53083
+ }
53084
+ }),
53085
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
53086
+ className: "w-full",
53087
+ style: {
53088
+ background: TIER_COLORS.warm,
53089
+ height: barsReady ? `${warmPx}px` : "0px",
53090
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 42}ms`
53091
+ }
53092
+ }),
53093
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
53094
+ className: "w-full",
53095
+ style: {
53096
+ background: TIER_COLORS.facts,
53097
+ height: barsReady ? `${factsPx}px` : "0px",
53098
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 54}ms`
53099
+ }
53100
+ }),
53101
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
53102
+ className: "w-full",
53103
+ style: {
53104
+ background: TIER_COLORS.overhead,
53105
+ height: barsReady ? `${overheadPx}px` : "0px",
53106
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 66}ms`
53107
+ }
53108
+ })
53109
+ ]
53110
+ }) : /*#__PURE__*/ (0,react_jsx_runtime.jsx)("div", {
52928
53111
  className: "w-full bg-gradient-to-t from-indigo-500 to-violet-400",
52929
53112
  style: {
52930
53113
  height: barsReady ? `${inputHeight}px` : "0px",
@@ -52947,13 +53130,61 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
52947
53130
  /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("div", {
52948
53131
  className: "mt-2 flex flex-wrap items-center gap-3 text-[11px] text-[var(--text-secondary)]",
52949
53132
  children: [
53133
+ /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
53134
+ className: "inline-flex items-center gap-1",
53135
+ children: [
53136
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
53137
+ className: "w-2 h-2 rounded-sm",
53138
+ style: {
53139
+ background: TIER_COLORS.hot
53140
+ }
53141
+ }),
53142
+ "hot"
53143
+ ]
53144
+ }),
53145
+ /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
53146
+ className: "inline-flex items-center gap-1",
53147
+ children: [
53148
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
53149
+ className: "w-2 h-2 rounded-sm",
53150
+ style: {
53151
+ background: TIER_COLORS.warm
53152
+ }
53153
+ }),
53154
+ "warm"
53155
+ ]
53156
+ }),
53157
+ /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
53158
+ className: "inline-flex items-center gap-1",
53159
+ children: [
53160
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
53161
+ className: "w-2 h-2 rounded-sm",
53162
+ style: {
53163
+ background: TIER_COLORS.facts
53164
+ }
53165
+ }),
53166
+ "facts"
53167
+ ]
53168
+ }),
53169
+ /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
53170
+ className: "inline-flex items-center gap-1",
53171
+ children: [
53172
+ /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
53173
+ className: "w-2 h-2 rounded-sm",
53174
+ style: {
53175
+ background: TIER_COLORS.overhead
53176
+ }
53177
+ }),
53178
+ "overhead"
53179
+ ]
53180
+ }),
52950
53181
  /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
52951
53182
  className: "inline-flex items-center gap-1",
52952
53183
  children: [
52953
53184
  /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
52954
53185
  className: "w-2 h-2 rounded-full bg-violet-400"
52955
53186
  }),
52956
- "input"
53187
+ "input (est)"
52957
53188
  ]
52958
53189
  }),
52959
53190
  /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
@@ -52965,14 +53196,9 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
52965
53196
  "output"
52966
53197
  ]
52967
53198
  }),
52968
- active && /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("span", {
53199
+ active && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("span", {
52969
53200
  className: "ml-auto text-[var(--text-primary)]",
52970
- children: [
52971
- "in ",
52972
- formatInt(active.input_tokens_est),
52973
- " \xb7 out ",
52974
- formatInt(active.output_tokens_est)
52975
- ]
53201
+ children: active.tier_tokens.measured_input_tokens > 0 ? `hot ${formatInt(active.tier_tokens.hot_tokens)} · warm ${formatInt(active.tier_tokens.warm_tokens)} · facts ${formatInt(active.tier_tokens.facts_tokens)} · overhead ${formatInt(active.tier_tokens.overhead_tokens)} · out ${formatInt(active.output_tokens_est)}` : `in ${formatInt(active.input_tokens_est)} · out ${formatInt(active.output_tokens_est)}`
52976
53202
  })
52977
53203
  ]
52978
53204
  })