@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
@@ -928,13 +928,23 @@ function useSSE(onDone) {
928
928
  if (err.name !== "AbortError") {
929
929
  setError(String(err));
930
930
  }
931
- setStreaming(false);
932
- setStreamingContent("");
933
- setThinkingContent("");
934
- closeActivity();
935
931
  return {
936
932
  accepted: false
937
933
  };
934
+ } finally{
935
+ // Always release the gate when the stream ends — defends against the
936
+ // consume() loop returning without ever observing a terminal `done`
937
+ // event (e.g. the EventSource closed cleanly with zero events). If
938
+ // we relied solely on the `done` branch inside consume(), the chat
939
+ // would stay locked behind the Stop button forever.
940
+ //
941
+ // We intentionally do NOT clear streamingContent / thinkingContent
942
+ // here — the consumer (ChatView) swaps them for the persisted
943
+ // assistant bubble once the refetch lands; clearing now would yank
944
+ // the text out from under the user. The next start()/attach() resets
945
+ // them.
946
+ setStreaming(false);
947
+ closeActivity();
938
948
  }
939
949
  }, [
940
950
  consume,
@@ -987,15 +997,18 @@ function useSSE(onDone) {
987
997
  try {
988
998
  await consume((0,client/* subscribeRun */._C)(threadId, ctrl.signal));
989
999
  } catch (err) {
990
- // Attach failures are non-fatal — clear the gate and let the consumer
991
- // drain anything queued during session load. The common case is
992
- // "no run to attach to" (server returns 404, EventSource fails to
993
- // open) — completely normal when navigating into an idle session.
994
- setStreaming(false);
995
- closeActivity();
996
1000
  if (err.name !== "AbortError") {
997
1001
  onDone?.();
998
1002
  }
1003
+ } finally{
1004
+ // Always release the optimistic gate when the stream ends — whether
1005
+ // via terminal `done`/`error` event, a thrown failure, or a clean
1006
+ // EventSource close with no events (idle thread, server returned 404
1007
+ // so the iterator exited without yielding). Without this, navigating
1008
+ // into an idle thread leaves streaming=true forever: the Stop button
1009
+ // hangs in the composer and the "Reconnecting…" badge never clears.
1010
+ setStreaming(false);
1011
+ closeActivity();
999
1012
  }
1000
1013
  }, [
1001
1014
  consume,
@@ -2838,6 +2851,123 @@ function UserAvatar({ profile }) {
2838
2851
  // lines at the top of every bubble. Format is fixed by dispatcher; if either
2839
2852
  // side changes, update both.
2840
2853
  const parseBridgeContext = parseBridgePrompt;
2854
+ const SILENT_TRIGGER_RE = /\n+\[SILENT_TRIGGER\][^\n]*(?:\n(?!\n).*)*$/;
2855
+ // Strip the `[SILENT_TRIGGER] …` envelope that runTriggerAgent appends
2856
+ // in silent mode so the card can render the user's original prompt and
2857
+ // surface a separate "Silent" pill instead of leaking framework prose.
2858
+ function stripSilentEnvelope(text) {
2859
+ const m = SILENT_TRIGGER_RE.exec(text);
2860
+ if (!m) return {
2861
+ text,
2862
+ silent: false
2863
+ };
2864
+ return {
2865
+ text: text.slice(0, m.index).trimEnd(),
2866
+ silent: true
2867
+ };
2868
+ }
2869
+ function parseTriggerMessage(category, raw) {
2870
+ if (category === "scheduled_task") {
2871
+ const { text, silent } = stripSilentEnvelope(raw);
2872
+ return {
2873
+ kind: "scheduled_task",
2874
+ prompt: text,
2875
+ silent
2876
+ };
2877
+ }
2878
+ if (category === "watcher") {
2879
+ const { text, silent } = stripSilentEnvelope(raw);
2880
+ 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]*)$/;
2881
+ const m = headerRe.exec(text);
2882
+ if (!m) return null;
2883
+ return {
2884
+ kind: "watcher",
2885
+ label: m[1],
2886
+ tool: m[2].trim(),
2887
+ args: m[3].trim(),
2888
+ diff: m[4].trim(),
2889
+ directive: m[5].trim(),
2890
+ silent
2891
+ };
2892
+ }
2893
+ return null;
2894
+ }
2895
+ // Compact header card for trigger-originated user messages (scheduled
2896
+ // tasks + watchers, ADR-0027/ADR-0032). Surfaces the trigger type with
2897
+ // an icon + label so the operator can tell at a glance that the prompt
2898
+ // came from automation and not from them. Watchers additionally expose
2899
+ // the diff context in a collapsed section to keep large diffs out of
2900
+ // the main bubble height.
2901
+ function TriggerMessageCard({ data }) {
2902
+ const [diffOpen, setDiffOpen] = (0,react.useState)(false);
2903
+ const Icon = data.kind === "scheduled_task" ? clock/* default */.A : eye/* default */.A;
2904
+ const label = data.kind === "scheduled_task" ? "Scheduled task" : `Watcher: ${data.label || "(unnamed)"}`;
2905
+ return /*#__PURE__*/ (0,jsx_runtime.jsxs)("div", {
2906
+ className: "flex flex-col gap-1.5 min-w-0",
2907
+ children: [
2908
+ /*#__PURE__*/ (0,jsx_runtime.jsxs)("div", {
2909
+ className: "flex items-center gap-1.5 text-[11px] text-white/85 min-w-0",
2910
+ children: [
2911
+ /*#__PURE__*/ (0,jsx_runtime.jsx)(Icon, {
2912
+ size: 11,
2913
+ className: "shrink-0"
2914
+ }),
2915
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
2916
+ className: "font-medium truncate",
2917
+ children: label
2918
+ }),
2919
+ data.kind === "watcher" && /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
2920
+ className: "px-1.5 py-0.5 rounded-full bg-white/15 text-[9.5px] uppercase tracking-wide shrink-0",
2921
+ children: data.tool
2922
+ }),
2923
+ data.silent && /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
2924
+ 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",
2925
+ title: "Silent trigger: reply only if material",
2926
+ children: [
2927
+ /*#__PURE__*/ (0,jsx_runtime.jsx)(eye_off/* default */.A, {
2928
+ size: 9
2929
+ }),
2930
+ "silent"
2931
+ ]
2932
+ })
2933
+ ]
2934
+ }),
2935
+ data.kind === "watcher" && /*#__PURE__*/ (0,jsx_runtime.jsxs)("button", {
2936
+ type: "button",
2937
+ onClick: ()=>setDiffOpen((v)=>!v),
2938
+ className: "flex items-center gap-1 text-left text-[11px] text-white/75 hover:text-white/95",
2939
+ "aria-expanded": diffOpen,
2940
+ title: diffOpen ? "Hide diff" : "Show diff",
2941
+ children: [
2942
+ /*#__PURE__*/ (0,jsx_runtime.jsx)(chevron_right/* default */.A, {
2943
+ size: 11,
2944
+ className: `shrink-0 transition-transform ${diffOpen ? "rotate-90" : ""}`
2945
+ }),
2946
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
2947
+ children: "Change context"
2948
+ })
2949
+ ]
2950
+ }),
2951
+ data.kind === "watcher" && diffOpen && /*#__PURE__*/ (0,jsx_runtime.jsxs)("div", {
2952
+ className: "ml-4 flex flex-col gap-1",
2953
+ children: [
2954
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("pre", {
2955
+ 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",
2956
+ children: data.args
2957
+ }),
2958
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("pre", {
2959
+ 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",
2960
+ children: data.diff
2961
+ })
2962
+ ]
2963
+ }),
2964
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("p", {
2965
+ className: "whitespace-pre-wrap text-[13.5px] leading-relaxed",
2966
+ children: data.kind === "scheduled_task" ? data.prompt : data.directive
2967
+ })
2968
+ ]
2969
+ });
2970
+ }
2841
2971
  // Compact header card for inbound bridge messages. Shows sender + chat
2842
2972
  // context as a single line of metadata above the actual message text, so a
2843
2973
  // WhatsApp DM looks like "Alice • DM\n<text>" and a group message looks
@@ -3654,6 +3784,11 @@ const MessageBubble = /*#__PURE__*/ (0,react.memo)(function MessageBubble({ mess
3654
3784
  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"}`,
3655
3785
  children: [
3656
3786
  typeof parsed === "string" ? isUser ? (()=>{
3787
+ const category = "category" in message ? message.category : null;
3788
+ const trigger = parseTriggerMessage(category, parsed);
3789
+ if (trigger) return /*#__PURE__*/ (0,jsx_runtime.jsx)(TriggerMessageCard, {
3790
+ data: trigger
3791
+ });
3657
3792
  const bridge = parseBridgeContext(parsed);
3658
3793
  if (bridge) return /*#__PURE__*/ (0,jsx_runtime.jsx)(BridgeMessageCard, {
3659
3794
  ctx: bridge
@@ -10015,7 +10150,9 @@ function ToolGroupBlock({ group, categories, advancedMode, selected, onToggleToo
10015
10150
  const selectedInGroup = allTools.filter((t)=>selected.includes(t.name)).length;
10016
10151
  const allOn = selectedInGroup === allTools.length;
10017
10152
  const someOn = selectedInGroup > 0 && !allOn;
10018
- const [open, setOpen] = (0,react.useState)(selectedInGroup > 0);
10153
+ // Collapsed by default to keep the editor compact on small / PWA viewports;
10154
+ // the header still surfaces the selected/total count.
10155
+ const [open, setOpen] = (0,react.useState)(false);
10019
10156
  const headerRef = (0,react.useRef)(null);
10020
10157
  (0,react.useEffect)(()=>{
10021
10158
  if (headerRef.current) headerRef.current.indeterminate = someOn;
@@ -10045,13 +10182,14 @@ function ToolGroupBlock({ group, categories, advancedMode, selected, onToggleToo
10045
10182
  }
10046
10183
  // Collapsible per-category block with a tri-state header checkbox. The
10047
10184
  // header toggle flips the entire category on/off; individual tool checkboxes
10048
- // stay available for fine-grained control. Collapsed-by-default when no tool
10049
- // in the category is selected, to keep the editor compact.
10185
+ // stay available for fine-grained control. Collapsed by default to keep the
10186
+ // editor short on mobile / PWA viewports.
10050
10187
  function ToolCategoryBlock({ category, tools, advancedMode, selected, onToggleTool, onToggleCategory, onToggleCategoryPermission }) {
10051
10188
  const selectedInCat = tools.filter((t)=>selected.includes(t.name)).length;
10052
10189
  const allOn = selectedInCat === tools.length;
10053
10190
  const someOn = selectedInCat > 0 && !allOn;
10054
- const [open, setOpen] = (0,react.useState)(selectedInCat > 0);
10191
+ // Collapsed by default see ToolGroupBlock comment.
10192
+ const [open, setOpen] = (0,react.useState)(false);
10055
10193
  const headerRef = (0,react.useRef)(null);
10056
10194
  // Render the tri-state indeterminate dash via the DOM property (React
10057
10195
  // doesn't expose it as a JSX attribute).
@@ -20348,20 +20486,6 @@ function DashboardPanel() {
20348
20486
  className: "text-[11px] text-[var(--text-secondary)]",
20349
20487
  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."
20350
20488
  }),
20351
- /*#__PURE__*/ (0,jsx_runtime.jsxs)("section", {
20352
- className: "rounded-2xl border border-[var(--border)] bg-[var(--bg-secondary)]/90 p-4 shadow-sm",
20353
- children: [
20354
- /*#__PURE__*/ (0,jsx_runtime.jsx)("h3", {
20355
- className: "text-sm font-medium text-[var(--text-primary)] mb-3",
20356
- children: "Token usage over time"
20357
- }),
20358
- /*#__PURE__*/ (0,jsx_runtime.jsx)(InteractiveTokenChart, {
20359
- series: series,
20360
- selectedDay: selectedDay,
20361
- onSelectDay: (day)=>setSelectedDay((prev)=>prev === day ? null : day)
20362
- })
20363
- ]
20364
- }),
20365
20489
  /*#__PURE__*/ (0,jsx_runtime.jsxs)("section", {
20366
20490
  className: "rounded-2xl border border-[var(--border)] bg-[var(--bg-secondary)]/90 p-4 shadow-sm",
20367
20491
  children: [
@@ -20370,7 +20494,7 @@ function DashboardPanel() {
20370
20494
  children: [
20371
20495
  /*#__PURE__*/ (0,jsx_runtime.jsx)("h3", {
20372
20496
  className: "text-sm font-medium text-[var(--text-primary)]",
20373
- children: "Estimated cost over time"
20497
+ children: "Tokens & cost over time"
20374
20498
  }),
20375
20499
  selectedDay ? /*#__PURE__*/ (0,jsx_runtime.jsxs)("button", {
20376
20500
  type: "button",
@@ -20391,11 +20515,19 @@ function DashboardPanel() {
20391
20515
  })
20392
20516
  ]
20393
20517
  }),
20394
- /*#__PURE__*/ (0,jsx_runtime.jsx)(InteractiveCostChart, {
20518
+ /*#__PURE__*/ (0,jsx_runtime.jsx)(InteractiveTokenChart, {
20395
20519
  series: series,
20396
- currencyInfo: currencyInfo,
20397
20520
  selectedDay: selectedDay,
20398
20521
  onSelectDay: (day)=>setSelectedDay((prev)=>prev === day ? null : day)
20522
+ }),
20523
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
20524
+ className: "mt-3 border-t border-[var(--border)]/60 pt-3",
20525
+ children: /*#__PURE__*/ (0,jsx_runtime.jsx)(InteractiveCostChart, {
20526
+ series: series,
20527
+ currencyInfo: currencyInfo,
20528
+ selectedDay: selectedDay,
20529
+ onSelectDay: (day)=>setSelectedDay((prev)=>prev === day ? null : day)
20530
+ })
20399
20531
  })
20400
20532
  ]
20401
20533
  }),
@@ -20790,6 +20922,13 @@ function InsightChip({ label, value, hint }) {
20790
20922
  ]
20791
20923
  });
20792
20924
  }
20925
+ // Per-tier breakdown colors shared by the stacked token chart legend.
20926
+ const TIER_COLORS = {
20927
+ hot: "#22d3ee",
20928
+ warm: "#f59e0b",
20929
+ facts: "#a78bfa",
20930
+ overhead: "#94a3b8"
20931
+ };
20793
20932
  function SharedDonutChart({ ariaLabel, size, centerAmount, slices, hovered, onHoverChange }) {
20794
20933
  const uid = (0,react.useId)().replace(/:/g, "");
20795
20934
  const [animCycle, setAnimCycle] = (0,react.useState)(0);
@@ -21199,6 +21338,15 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
21199
21338
  const totalHeight = hasData && maxTotal > 0 ? Math.max(4, Math.round(total / maxTotal * 150)) : 0;
21200
21339
  const inputHeight = hasData ? Math.round(point.input_tokens_est / total * totalHeight) : 0;
21201
21340
  const outputHeight = hasData ? Math.max(0, totalHeight - inputHeight) : 0;
21341
+ // Subdivide the input portion by measured tier breakdown when the
21342
+ // day has at least one snapshotted assistant turn. Legacy days
21343
+ // (measured_input_tokens === 0) fall back to a solid violet block.
21344
+ const tier = point.tier_tokens;
21345
+ const tierTotal = tier?.measured_input_tokens ?? 0;
21346
+ const hotPx = tierTotal > 0 ? Math.round(tier.hot_tokens / tierTotal * inputHeight) : 0;
21347
+ const warmPx = tierTotal > 0 ? Math.round(tier.warm_tokens / tierTotal * inputHeight) : 0;
21348
+ const factsPx = tierTotal > 0 ? Math.round(tier.facts_tokens / tierTotal * inputHeight) : 0;
21349
+ const overheadPx = tierTotal > 0 ? Math.max(0, inputHeight - hotPx - warmPx - factsPx) : 0;
21202
21350
  const isActive = idx === hovered;
21203
21351
  const isSelected = selectedDay === point.day;
21204
21352
  const clickable = !!onSelectDay && hasData;
@@ -21229,7 +21377,42 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
21229
21377
  transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18}ms`
21230
21378
  }
21231
21379
  }),
21232
- /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
21380
+ tierTotal > 0 ? /*#__PURE__*/ (0,jsx_runtime.jsxs)(jsx_runtime.Fragment, {
21381
+ children: [
21382
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
21383
+ className: "w-full",
21384
+ style: {
21385
+ background: TIER_COLORS.hot,
21386
+ height: barsReady ? `${hotPx}px` : "0px",
21387
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 30}ms`
21388
+ }
21389
+ }),
21390
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
21391
+ className: "w-full",
21392
+ style: {
21393
+ background: TIER_COLORS.warm,
21394
+ height: barsReady ? `${warmPx}px` : "0px",
21395
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 42}ms`
21396
+ }
21397
+ }),
21398
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
21399
+ className: "w-full",
21400
+ style: {
21401
+ background: TIER_COLORS.facts,
21402
+ height: barsReady ? `${factsPx}px` : "0px",
21403
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 54}ms`
21404
+ }
21405
+ }),
21406
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
21407
+ className: "w-full",
21408
+ style: {
21409
+ background: TIER_COLORS.overhead,
21410
+ height: barsReady ? `${overheadPx}px` : "0px",
21411
+ transition: `height 420ms cubic-bezier(0.2, 0.8, 0.2, 1) ${idx * 18 + 66}ms`
21412
+ }
21413
+ })
21414
+ ]
21415
+ }) : /*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
21233
21416
  className: "w-full bg-gradient-to-t from-indigo-500 to-violet-400",
21234
21417
  style: {
21235
21418
  height: barsReady ? `${inputHeight}px` : "0px",
@@ -21252,13 +21435,61 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
21252
21435
  /*#__PURE__*/ (0,jsx_runtime.jsxs)("div", {
21253
21436
  className: "mt-2 flex flex-wrap items-center gap-3 text-[11px] text-[var(--text-secondary)]",
21254
21437
  children: [
21438
+ /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
21439
+ className: "inline-flex items-center gap-1",
21440
+ children: [
21441
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
21442
+ className: "w-2 h-2 rounded-sm",
21443
+ style: {
21444
+ background: TIER_COLORS.hot
21445
+ }
21446
+ }),
21447
+ "hot"
21448
+ ]
21449
+ }),
21450
+ /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
21451
+ className: "inline-flex items-center gap-1",
21452
+ children: [
21453
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
21454
+ className: "w-2 h-2 rounded-sm",
21455
+ style: {
21456
+ background: TIER_COLORS.warm
21457
+ }
21458
+ }),
21459
+ "warm"
21460
+ ]
21461
+ }),
21462
+ /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
21463
+ className: "inline-flex items-center gap-1",
21464
+ children: [
21465
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
21466
+ className: "w-2 h-2 rounded-sm",
21467
+ style: {
21468
+ background: TIER_COLORS.facts
21469
+ }
21470
+ }),
21471
+ "facts"
21472
+ ]
21473
+ }),
21474
+ /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
21475
+ className: "inline-flex items-center gap-1",
21476
+ children: [
21477
+ /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
21478
+ className: "w-2 h-2 rounded-sm",
21479
+ style: {
21480
+ background: TIER_COLORS.overhead
21481
+ }
21482
+ }),
21483
+ "overhead"
21484
+ ]
21485
+ }),
21255
21486
  /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
21256
21487
  className: "inline-flex items-center gap-1",
21257
21488
  children: [
21258
21489
  /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
21259
21490
  className: "w-2 h-2 rounded-full bg-violet-400"
21260
21491
  }),
21261
- "input"
21492
+ "input (est)"
21262
21493
  ]
21263
21494
  }),
21264
21495
  /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
@@ -21270,14 +21501,9 @@ function InteractiveTokenChart({ series, selectedDay, onSelectDay }) {
21270
21501
  "output"
21271
21502
  ]
21272
21503
  }),
21273
- active && /*#__PURE__*/ (0,jsx_runtime.jsxs)("span", {
21504
+ active && /*#__PURE__*/ (0,jsx_runtime.jsx)("span", {
21274
21505
  className: "ml-auto text-[var(--text-primary)]",
21275
- children: [
21276
- "in ",
21277
- formatInt(active.input_tokens_est),
21278
- " \xb7 out ",
21279
- formatInt(active.output_tokens_est)
21280
- ]
21506
+ 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)}`
21281
21507
  })
21282
21508
  ]
21283
21509
  })
@@ -21946,4 +22172,4 @@ Promise.resolve(/* import() eager */).then(__webpack_require__.bind(__webpack_re
21946
22172
  /******/ _N_E = __webpack_exports__;
21947
22173
  /******/ }
21948
22174
  ]);
21949
- //# sourceMappingURL=page-c77ab600642bbfc2.js.map
22175
+ //# sourceMappingURL=page-318743bf47fac345.js.map