claude-discover 1.10.1 → 1.10.2

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.
@@ -1,4 +1,4 @@
1
- import { r as reactExports, j as jsxRuntimeExports, f as format, s as startOfWeek, a as startOfMonth, b as startOfDay, e as endOfWeek, c as endOfMonth, d as endOfDay, g as addWeeks, h as addMonths, i as addDays, k as isSameWeek, l as isSameMonth, m as isSameDay, n as intervalToDuration, o as getDaysInMonth, p as addHours, q as encode, H as Hourglass, G as Gauge, M as MessageSquare, C as Clock, Z as Zap, D as DollarSign, R as React, t as Markdown$1, u as rehypeHighlight, v as remarkGfm, w as defaultUrlTransform, T as Terminal, J as JsonView, x as vscodeTheme, y as tippy, z as reactDomExports, A as an, B as qt, Q as Qt, E as nn, F as parse, I as delegate, K as clientExports } from "./vendors-D2DMOduT.js";
1
+ import { r as reactExports, j as jsxRuntimeExports, f as format, s as startOfWeek, a as startOfMonth, b as startOfDay, e as endOfWeek, c as endOfMonth, d as endOfDay, g as addWeeks, h as addMonths, i as addDays, k as isSameWeek, l as isSameMonth, m as isSameDay, n as intervalToDuration, o as getDaysInMonth, p as addHours, H as Hourglass, G as Gauge, M as MessageSquare, C as Clock, B as Brain, Z as Zap, D as DollarSign, q as encode, R as React, t as Markdown$1, u as rehypeHighlight, v as remarkGfm, w as defaultUrlTransform, L as LaptopMinimal, T as Terminal, x as Globe, J as JsonView, y as vscodeTheme, z as C, A as w, E as tippy, F as reactDomExports, I as ArrowDownToLine, K as an, N as qt, Q as Qt, O as nn, P as parse, S as delegate, U as clientExports } from "./vendors-CF1AEHsu.js";
2
2
  const SOURCE_COLORS = {
3
3
  scheduled: "#14b8a6",
4
4
  cli: "#3b82f6",
@@ -34,13 +34,13 @@ function useLocalStorage(key, initial) {
34
34
  }
35
35
  function clearOutdatedLocalStorage() {
36
36
  const version = localStorage.getItem(VERSION_KEY);
37
- if (version !== "1.10.1") {
38
- const [MAJOR, MINOR, PATCH] = "1.10.1".split(".");
37
+ if (version !== "1.10.2") {
38
+ const [MAJOR, MINOR, PATCH] = "1.10.2".split(".");
39
39
  const [major, minor, patch] = (version ?? "").split(".");
40
40
  if (MAJOR !== major || MINOR !== minor) {
41
41
  localStorage.clear();
42
42
  }
43
- localStorage.setItem(VERSION_KEY, "1.10.1");
43
+ localStorage.setItem(VERSION_KEY, "1.10.2");
44
44
  }
45
45
  }
46
46
  function useMouseFontScale(storageKey) {
@@ -242,7 +242,7 @@ function WorkTimeOverlay({
242
242
  const [dragging, setDragging] = reactExports.useState(null);
243
243
  const [now, setNow] = reactExports.useState(() => Date.now());
244
244
  reactExports.useEffect(() => {
245
- window.api.getWorkHours().then(({ work_hours: w }) => setWorkTime({ startMin: parseHM(w.start), endMin: parseHM(w.end) }));
245
+ window.api.getWorkHours().then(({ work_hours: w2 }) => setWorkTime({ startMin: parseHM(w2.start), endMin: parseHM(w2.end) }));
246
246
  }, []);
247
247
  reactExports.useEffect(() => {
248
248
  const id = setInterval(() => setNow(Date.now()), 6e4);
@@ -303,9 +303,9 @@ function WorkTimeOverlay({
303
303
  ] }),
304
304
  !showWorkBand && /* @__PURE__ */ jsxRuntimeExports.jsx("g", { className: "work-overlay", children: weekendSpans(viewStart, viewEnd).map(([start, end], i) => {
305
305
  const x = clamp(xForTs(start), chartLeft, chartRight);
306
- const w = clamp(xForTs(end), chartLeft, chartRight) - x;
307
- if (w <= 0) return null;
308
- return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { className: "work-offhours-shade", x, y: 0, width: w, height: totalHeight }, i);
306
+ const w2 = clamp(xForTs(end), chartLeft, chartRight) - x;
307
+ if (w2 <= 0) return null;
308
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { className: "work-offhours-shade", x, y: 0, width: w2, height: totalHeight }, i);
309
309
  }) }),
310
310
  showNow && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { className: "now-line", children: [
311
311
  /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: xForTs(now), x2: xForTs(now), y1: 0, y2: totalHeight }),
@@ -316,8 +316,8 @@ function WorkTimeOverlay({
316
316
  const HEADER_HEIGHT = 28;
317
317
  const PROJECTS_WIDTH = 220;
318
318
  const BARS = {
319
- day: { height: 22, min_width: 4, row_gap: 4, group_gap: 8, radius: 3 },
320
- week: { height: 15, min_width: 3, row_gap: 3, group_gap: 4, radius: 2 },
319
+ day: { height: 16, min_width: 4, row_gap: 2, group_gap: 6, radius: 3 },
320
+ week: { height: 12, min_width: 3, row_gap: 2, group_gap: 3, radius: 2 },
321
321
  month: { height: 10, min_width: 2, row_gap: 2, group_gap: 2, radius: 1 }
322
322
  };
323
323
  const SUBAGENT_SPLIT_GAP = 5 * 6e4;
@@ -483,7 +483,8 @@ function GanttChart({
483
483
  "text",
484
484
  {
485
485
  x: 8,
486
- y: g.yOffset + 14,
486
+ y: g.yOffset + bar.height / 2,
487
+ dominantBaseline: "central",
487
488
  className: `gantt-project gantt-project-clickable${projectFilter === g.key ? " gantt-project-active" : ""}`,
488
489
  onClick: (e) => {
489
490
  e.stopPropagation();
@@ -504,23 +505,23 @@ function GanttChart({
504
505
  const rawX2 = xFor(item.end);
505
506
  if (rawX2 < PROJECTS_WIDTH || rawX1 > width) return null;
506
507
  const x = Math.max(PROJECTS_WIDTH, rawX1);
507
- const w = Math.max(bar.min_width, Math.min(width, rawX2) - x);
508
+ const w2 = Math.max(bar.min_width, Math.min(width, rawX2) - x);
508
509
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
509
510
  "g",
510
511
  {
511
512
  className: `bar ${isSelected ? "selected" : ""}`,
512
513
  onClick: () => onSelect(item.subs ? item.subs.at(-1).filePath : item.id),
513
514
  children: [
514
- /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w, height: bar.height, rx: bar.radius, fill: color, className: "bar-fill" }),
515
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w2, height: bar.height, rx: bar.radius, fill: color, className: "bar-fill" }),
515
516
  periods.slice(0, -1).map((p, i) => {
516
517
  const gx1 = Math.max(PROJECTS_WIDTH, xFor(p.end));
517
518
  const gx2 = Math.min(width, xFor(periods[i + 1].start));
518
519
  if (gx2 <= gx1) return null;
519
520
  return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: gx1, y, width: gx2 - gx1, height: bar.height, className: "gantt-idle" }, i);
520
521
  }),
521
- item.subs && /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w, height: bar.height, rx: bar.radius, fill: "url(#gantt-subagent-hatch)", opacity: 0.5, pointerEvents: "none" }),
522
- item.subs && /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: x + w / 2, y: y + bar.height / 2, textAnchor: "middle", dominantBaseline: "central", className: "gantt-bar-count", children: item.subCount }),
523
- isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w, height: bar.height, rx: bar.radius, className: "bar-outline" }),
522
+ item.subs && /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w2, height: bar.height, rx: bar.radius, fill: "url(#gantt-subagent-hatch)", opacity: 0.5, pointerEvents: "none" }),
523
+ item.subs && /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: x + w2 / 2, y: y + bar.height / 2, textAnchor: "middle", dominantBaseline: "central", className: "gantt-bar-count", children: item.subCount }),
524
+ isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w2, height: bar.height, rx: bar.radius, className: "bar-outline" }),
524
525
  /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: `${item.subs ? `${item.subCount} subagents` : `${SOURCE_LABELS$1[item.source] || item.source} · ${item.label}`}
525
526
  ${g.key}
526
527
  ${new Date(item.start).toLocaleString()} → ${new Date(item.end).toLocaleString()}` })
@@ -590,23 +591,6 @@ function Toolbar({
590
591
  onToggleSourceFilter
591
592
  }) {
592
593
  const onToday = isSamePeriod(dayAnchor, Date.now(), granularity);
593
- const [menuOpen, setMenuOpen] = reactExports.useState(false);
594
- const menuRef = reactExports.useRef(null);
595
- reactExports.useEffect(() => {
596
- if (!menuOpen) return;
597
- const onDocClick = (e) => {
598
- if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
599
- };
600
- const onKeyDown = (e) => {
601
- if (e.key === "Escape") setMenuOpen(false);
602
- };
603
- document.addEventListener("mousedown", onDocClick);
604
- document.addEventListener("keydown", onKeyDown);
605
- return () => {
606
- document.removeEventListener("mousedown", onDocClick);
607
- document.removeEventListener("keydown", onKeyDown);
608
- };
609
- }, [menuOpen]);
610
594
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "gantt-toolbar", children: [
611
595
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "gantt-legend", children: (availableSources ?? []).slice().sort((a, b) => {
612
596
  const ia = SOURCE_ORDER.indexOf(a), ib = SOURCE_ORDER.indexOf(b);
@@ -621,38 +605,7 @@ function Toolbar({
621
605
  }) }),
622
606
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "gantt-toolbar-nav", children: [
623
607
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "gantt-nav-arrow", onClick: () => onShiftDay?.(-1), title: "Previous period", "aria-label": "Previous period", children: "‹" }),
624
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "gantt-period", ref: menuRef, children: [
625
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
626
- "button",
627
- {
628
- type: "button",
629
- className: "gantt-toolbar-title",
630
- onClick: () => setMenuOpen((o) => !o),
631
- "aria-haspopup": "listbox",
632
- "aria-expanded": menuOpen,
633
- title: "Change period granularity",
634
- children: [
635
- periodTitle(dayAnchor, granularity),
636
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "gantt-granularity-caret", children: "▾" })
637
- ]
638
- }
639
- ),
640
- menuOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "gantt-granularity-menu", role: "listbox", children: GRANULARITIES.map(({ key, label }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
641
- "button",
642
- {
643
- type: "button",
644
- role: "option",
645
- "aria-selected": granularity === key,
646
- className: `gantt-granularity-option${granularity === key ? " active" : ""}`,
647
- onClick: () => {
648
- onSetGranularity?.(key);
649
- setMenuOpen(false);
650
- },
651
- children: label
652
- },
653
- key
654
- )) })
655
- ] }),
608
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "gantt-toolbar-title", children: periodTitle(dayAnchor, granularity) }),
656
609
  /* @__PURE__ */ jsxRuntimeExports.jsx(
657
610
  "button",
658
611
  {
@@ -674,7 +627,18 @@ function Toolbar({
674
627
  }
675
628
  )
676
629
  ] }),
677
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", {})
630
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "gantt-granularity", role: "tablist", children: GRANULARITIES.map(({ key, label }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
631
+ "button",
632
+ {
633
+ type: "button",
634
+ role: "tab",
635
+ "aria-selected": granularity === key,
636
+ className: granularity === key ? "active" : "",
637
+ onClick: () => onSetGranularity?.(key),
638
+ children: label
639
+ },
640
+ key
641
+ )) })
678
642
  ] });
679
643
  }
680
644
  function fmtDuration(ms) {
@@ -691,12 +655,6 @@ function fmtAgo(date) {
691
655
  if (min < 24 * 60) return `${Math.floor(min / 60)}h ago`;
692
656
  return `${Math.floor(min / (24 * 60))}d ago`;
693
657
  }
694
- function fmtBytes(n) {
695
- if (n == null) return "—";
696
- if (n < 1024) return `${n} B`;
697
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
698
- return `${(n / 1024 / 1024).toFixed(2)} MB`;
699
- }
700
658
  function fmtNum(n) {
701
659
  if (n == null) return "—";
702
660
  return n.toLocaleString();
@@ -869,175 +827,13 @@ function PeriodSummary({ sessions, dayAnchor, granularity = "day" }) {
869
827
  }
870
828
  const HOUR = 60 * 60 * 1e3;
871
829
  const THRESHOLDS = {
872
- context: { warn: 1e5, danger: 2e5 },
830
+ context: { warn: 1e5, danger: 25e4 },
873
831
  messages: { warn: 250, danger: 500 },
874
832
  workTime: { warn: 0.5 * HOUR, danger: 2 * HOUR },
875
833
  cost: { warn: 3, danger: 8 },
876
834
  tokens: { warn: 3e6, danger: 8e6 }
877
835
  };
878
- const countTokens = (text, model = "") => {
879
- if (!text) return 0;
880
- const LEGACY_CLAUDE = /-(3|4)(-|$)/;
881
- const scale = LEGACY_CLAUDE.test(model) ? 1.13 : 1.5;
882
- return Math.round(encode(text).length * scale);
883
- };
884
- const queuedPrompt = (it) => it.type === "attachment" && it.attachment?.type === "queued_command" && it.attachment.commandMode === "prompt" && (it.attachment.origin?.kind ?? "human") === "human" && it.attachment.prompt?.trim() ? it.attachment.prompt : null;
885
- function flatten(items, instructions = []) {
886
- const turns = [];
887
- const results = {};
888
- const companions = {};
889
- for (const it of items) {
890
- const queued = queuedPrompt(it);
891
- if (queued) {
892
- turns.push({
893
- uuid: it.uuid,
894
- role: "user",
895
- isMeta: false,
896
- queued: true,
897
- ts: it.timestamp ? Date.parse(it.timestamp) : null,
898
- model: null,
899
- msgId: null,
900
- usage: null,
901
- tokenDelta: it._tokenDelta ?? null,
902
- tokenTotal: it._tokenTotal ?? null,
903
- blocks: [{ type: "text", text: queued }]
904
- });
905
- continue;
906
- }
907
- if (it.type === "attachment" && it.attachment) {
908
- if (it.attachment.type === "structured_output" && it.attachment.toolUseID) continue;
909
- const block = { type: "attachment", attachment: it.attachment };
910
- const last = turns[turns.length - 1];
911
- if (last && last.isMeta && last.blocks.every((b) => b.type === "attachment")) {
912
- last.blocks.push(block);
913
- if (it._tokenDelta != null) last.tokenDelta = (last.tokenDelta ?? 0) + it._tokenDelta;
914
- if (it._tokenTotal != null) last.tokenTotal = it._tokenTotal;
915
- } else {
916
- turns.push({
917
- uuid: it.uuid,
918
- role: last?.role === "assistant" || last?.role === "tool" ? "assistant" : "user",
919
- isMeta: true,
920
- ts: it.timestamp ? Date.parse(it.timestamp) : null,
921
- model: null,
922
- usage: null,
923
- tokenDelta: it._tokenDelta ?? null,
924
- tokenTotal: it._tokenTotal ?? null,
925
- blocks: [block]
926
- });
927
- }
928
- continue;
929
- }
930
- if (it.type === "result" && it.key && it.agentId) {
931
- const text = typeof it.result === "string" ? it.result : "```json\n" + JSON.stringify(it.result, null, 2) + "\n```";
932
- turns.push({
933
- uuid: `wf-${it.agentId}`,
934
- role: "assistant",
935
- isMeta: false,
936
- ts: null,
937
- model: null,
938
- usage: null,
939
- tokenDelta: null,
940
- tokenTotal: null,
941
- blocks: [{ type: "text", text: `**agent ${it.agentId}**
942
-
943
- ${text}` }]
944
- });
945
- continue;
946
- }
947
- if (it.turnCompanion && it.sourceToolUseID) {
948
- companions[it.sourceToolUseID] = companionText(it);
949
- continue;
950
- }
951
- const command = it.turnCompanion && turns.findLast((t) => t.uuid === it.parentUuid);
952
- if (command) {
953
- const name = command.blocks.map((b) => parseCommand(b.text)?.name).find(Boolean)?.replace(/^\//, "");
954
- command.blocks.push({ type: "skill", name, text: companionText(it) });
955
- continue;
956
- }
957
- if (it.type !== "user" && it.type !== "assistant") continue;
958
- const msg = it.message || {};
959
- const blocks = collapseRedactedThinking(normalizeContent(msg.content));
960
- if (blocks.length === 0) continue;
961
- for (const b of blocks) {
962
- if (b.type === "tool_result" && b.tool_use_id) results[b.tool_use_id] = b;
963
- }
964
- const role = it.type === "user" && blocks.every((b) => b.type === "tool_result") ? "tool" : it.type;
965
- turns.push({
966
- uuid: it.uuid,
967
- role,
968
- isMeta: !!it.isMeta,
969
- ts: it.timestamp ? Date.parse(it.timestamp) : null,
970
- model: msg.model || null,
971
- msgId: msg.id || null,
972
- usage: msg.usage || null,
973
- tokenDelta: it._tokenDelta ?? null,
974
- tokenTotal: it._tokenTotal ?? null,
975
- blocks
976
- });
977
- }
978
- for (const t of turns) {
979
- t.blocks = t.blocks.map((b) => b.type === "tool_use" ? { ...b, result: withCompanion(results[b.id], companions[b.id]) } : b).filter((b) => !(b.type === "tool_result" && results[b.tool_use_id]));
980
- }
981
- const out = turns.filter((t) => t.blocks.length > 0);
982
- if (instructions.length) {
983
- const first = instructions.find((it) => it.timestamp);
984
- const turn = {
985
- uuid: "instructions",
986
- role: "instruction",
987
- isMeta: true,
988
- ts: first ? Date.parse(first.timestamp) - 500 : null,
989
- model: null,
990
- usage: null,
991
- tokenDelta: null,
992
- tokenTotal: null,
993
- blocks: instructions.map((it) => ({ type: "instruction", it }))
994
- };
995
- const i = out.findIndex((t) => t.ts != null && t.ts > turn.ts);
996
- i === -1 ? out.push(turn) : out.splice(i, 0, turn);
997
- }
998
- return out;
999
- }
1000
- const currentModel = (turns) => turns.findLast((t) => t.model && t.model !== "<synthetic>")?.model;
1001
- const SMALL_CONTEXT = /* @__PURE__ */ new Set([
1002
- "claude-3-opus",
1003
- "claude-3-sonnet",
1004
- "claude-3-haiku",
1005
- "claude-3-5-sonnet",
1006
- "claude-3-5-haiku",
1007
- "claude-3-7-sonnet",
1008
- "claude-haiku-4-5",
1009
- "claude-opus-4",
1010
- "claude-opus-4-1",
1011
- "claude-opus-4-5",
1012
- "claude-opus-4-6",
1013
- "claude-sonnet-4",
1014
- "claude-sonnet-4-5",
1015
- "claude-sonnet-4-6"
1016
- ]);
1017
- function contextWindow(models = []) {
1018
- const is200k = (model) => SMALL_CONTEXT.has(model.replace(/[-@]\d{8}.*$/, ""));
1019
- return models.some((m) => m && !is200k(m)) ? 1e6 : 2e5;
1020
- }
1021
- function collapseRedactedThinking(blocks) {
1022
- const out = [];
1023
- for (const b of blocks) {
1024
- if (!(b.type === "thinking" && !b.thinking?.trim())) {
1025
- out.push(b);
1026
- continue;
1027
- }
1028
- const last = out[out.length - 1];
1029
- if (last?.redacted) last.redacted++;
1030
- else out.push({ type: "thinking", redacted: 1 });
1031
- }
1032
- return out;
1033
- }
1034
- const companionText = (it) => normalizeContent(it.message?.content).filter((b) => b.type === "text").map((b) => b.text).join("\n");
1035
- const withCompanion = (result, text) => text ? { ...result ?? { type: "tool_result" }, content: [...normalizeContent(result?.content), { type: "text", text }] } : result;
1036
- function normalizeContent(content) {
1037
- if (typeof content === "string") return [{ type: "text", text: content }];
1038
- if (!Array.isArray(content)) return [];
1039
- return content.map((b) => typeof b === "string" ? { type: "text", text: b } : b);
1040
- }
836
+ const humanize = (type) => type.replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
1041
837
  const clip = (s, n = 100) => {
1042
838
  s = String(s).replace(/\s+/g, " ").trim();
1043
839
  return s.length > n ? s.slice(0, n - 1) + "…" : s;
@@ -1080,6 +876,11 @@ function parseCommand(text) {
1080
876
  caveat: field("local-command-caveat")
1081
877
  };
1082
878
  }
879
+ function persistedOutput(text) {
880
+ if (typeof text !== "string" || !text.trimStart().startsWith("<persisted-output>")) return null;
881
+ const saved = text.match(/Full output saved to: (.+)/)?.[1].trim();
882
+ return saved ? { path: saved, href: saved.split(/[\\/]/).slice(-3).map(encodeURIComponent).join("/") } : null;
883
+ }
1083
884
  const SOURCE_LABELS = { system: "system prompt", tools: "tools", message: "user message" };
1084
885
  function groupInstructions(strips) {
1085
886
  return Object.entries(SOURCE_LABELS).map(([source, label]) => [label, strips.filter((it) => it.source === source)]).filter(([, list]) => list.length);
@@ -1088,86 +889,18 @@ function instructionTitle(it, model) {
1088
889
  const parts = [it.memory_type, it.model && it.model !== model && it.model].filter(Boolean).join(", ");
1089
890
  return parts ? `${it.name ?? it.file_path} (${parts})` : it.name ?? it.file_path;
1090
891
  }
1091
- const isContextTurn = (t) => t.isMeta && t.blocks.every((b) => b.type === "attachment");
1092
- function groupTurns(turns) {
1093
- const groups = [];
1094
- let cycle = null;
1095
- let pending = [];
1096
- const flush = () => {
1097
- if (cycle) {
1098
- groups.push(cycle);
1099
- cycle = null;
1100
- }
1101
- };
1102
- for (const t of turns) {
1103
- if (isContextTurn(t) && !cycle) {
1104
- if (groups.at(-1)?.kind === "user") groups.at(-1).turns.push(t);
1105
- else pending.push(t);
1106
- } else if (t.role === "instruction") {
1107
- flush();
1108
- groups.push({ kind: "instruction", turns: [t] });
1109
- } else if (t.role === "user") {
1110
- flush();
1111
- groups.push({ kind: "user", turns: [...pending, t] });
1112
- pending = [];
1113
- } else {
1114
- cycle ??= { kind: "assistant", turns: pending.splice(0) };
1115
- cycle.turns.push(t);
1116
- if (t.blocks.some((b) => b.type === "text")) flush();
1117
- }
1118
- }
1119
- flush();
1120
- if (pending.length) groups.push({ kind: "user", turns: pending });
1121
- return groups;
1122
- }
1123
- function cycleDurations(groups) {
1124
- let prevEnd = null;
1125
- return groups.map((g) => {
1126
- const tss = g.turns.map((t) => t.ts).filter((ts) => ts != null);
1127
- const end = tss.length ? tss[tss.length - 1] : null;
1128
- const duration2 = g.kind === "assistant" && end != null && prevEnd != null ? end - prevEnd : null;
1129
- if (end != null) prevEnd = end;
1130
- return duration2;
1131
- });
1132
- }
1133
- function tokenPoints(groups) {
1134
- let prev = 0;
1135
- return groups.map((g) => {
1136
- const turns = g.turns;
1137
- let total = null, ctx = null;
1138
- const byMsg = /* @__PURE__ */ new Map();
1139
- for (const t of turns) {
1140
- if (t.tokenTotal != null) total = t.tokenTotal;
1141
- const u = t.usage;
1142
- if (u) {
1143
- ctx = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0);
1144
- byMsg.set(t.msgId ?? t.uuid, u);
1145
- }
1146
- }
1147
- let usage = null;
1148
- for (const u of byMsg.values()) {
1149
- usage ??= { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, cacheCreation5m: 0, cacheCreation1h: 0 };
1150
- usage.input += u.input_tokens || 0;
1151
- usage.output += u.output_tokens || 0;
1152
- usage.cacheRead += u.cache_read_input_tokens || 0;
1153
- usage.cacheCreation += u.cache_creation_input_tokens || 0;
1154
- usage.cacheCreation5m += u.cache_creation?.ephemeral_5m_input_tokens || 0;
1155
- usage.cacheCreation1h += u.cache_creation?.ephemeral_1h_input_tokens || 0;
1156
- }
1157
- if (total == null || total <= prev) return null;
1158
- const delta = total - prev;
1159
- prev = total;
1160
- if (ctx == null && g.kind !== "assistant") ctx = delta;
1161
- const ts = turns.find((t) => t.ts != null)?.ts ?? null;
1162
- return { delta, total, ts, ctx, usage, role: g.kind === "assistant" ? "assistant" : turns[0].role };
1163
- });
892
+ function compactTitle({ trigger, preTokens }) {
893
+ const detail = [trigger, preTokens && `${fmtCompact(preTokens)} tokens before`].filter(Boolean).join(" · ");
894
+ return detail ? `Conversation compacted (${detail})` : "Conversation compacted";
1164
895
  }
1165
896
  const TIPS = {
1166
897
  cache1h: "Billed 2× — prefer the default 5m cache",
1167
898
  context: `Consider keeping it below ${fmtCompact(THRESHOLDS.context.warn)} tokens`,
1168
899
  messages: `Consider sessions below ${fmtCompact(THRESHOLDS.messages.warn)} messages`,
1169
- time: `Consider sessions under ${THRESHOLDS.workTime.warn / 6e4}m`
900
+ time: `Consider sessions under ${THRESHOLDS.workTime.warn / 6e4}m`,
901
+ effort: "Thinks longer and spends more output tokens than high"
1170
902
  };
903
+ const EFFORT_BADGE = { xhigh: "warn-badge", max: "danger-badge" };
1171
904
  function SessionList({ sessions, selectedId, deepLink, onSelect }) {
1172
905
  const selectedRef = reactExports.useRef(null);
1173
906
  const [sortBy, setSortBy] = useLocalStorage("session-list.sort", "time");
@@ -1248,6 +981,7 @@ function SessionList({ sessions, selectedId, deepLink, onSelect }) {
1248
981
  const linkTip = linked && `Opened from deep link
1249
982
  claude-discover://session?id=${deepLink.id}${deepLink.date ? `&date=${deepLink.date}` : ""}`;
1250
983
  const command = labelCommand(s);
984
+ const topEffort = ["max", "xhigh"].find((e) => s.efforts?.includes(e));
1251
985
  const sessionName = s.customTitle && s.agentName && s.customTitle !== s.agentName ? `${s.customTitle} [${s.agentName}]` : s.customTitle || s.agentName || "";
1252
986
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1253
987
  "div",
@@ -1273,6 +1007,8 @@ ${TIPS.messages}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare
1273
1007
  ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11 }) }),
1274
1008
  s.activeMs > THRESHOLDS.workTime.warn && s.activeMs <= THRESHOLDS.workTime.danger && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "warn-badge", title: `Working time: ${fmtDuration(s.activeMs)}
1275
1009
  ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11 }) }),
1010
+ topEffort && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: EFFORT_BADGE[topEffort], title: `Effort: ${topEffort}
1011
+ ${TIPS.effort}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { size: 11 }) }),
1276
1012
  s.speed === "fast" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `fast-badge ${s.fastPricingUnknown ? "fast-badge-unknown" : ""}`, title: s.fastPricingUnknown ? "Used fast mode — cost is INACCURATE: no fast-mode price multiplier known for this model" : "Used fast mode (speed: fast)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { size: 11 }) }),
1277
1013
  s.priceUnknown && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "danger-badge", title: `No price entry for ${s.models?.join(", ") || "this model"} — cost is missing or understated`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(DollarSign, { size: 11 }) }),
1278
1014
  isSubagent && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "subagent-tag", children: "[subagent]" }),
@@ -1302,7 +1038,10 @@ ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11
1302
1038
  s.tag
1303
1039
  ] }),
1304
1040
  s.project && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-project", title: s.project, children: s.projectShort }),
1305
- s.models?.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-model", children: s.models.map((m) => m.replace(/^claude-/, "")).join(", ") }),
1041
+ s.models?.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "session-model", children: [
1042
+ s.models.map((m) => m.replace(/^claude-/, "")).join(", "),
1043
+ s.efforts?.length > 0 && ` ${s.efforts.join(", ")}`
1044
+ ] }),
1306
1045
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: format(s.lastActivityAt, "MMM d, HH:mm:ss"), children: fmtAgo(s.lastActivityAt) }),
1307
1046
  linked && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "deeplink-note", "data-tippy-content": linkTip, "data-tippy-maxWidth": 560, children: "opened from deep link" })
1308
1047
  ] })
@@ -1319,6 +1058,317 @@ ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11
1319
1058
  ] })
1320
1059
  ] });
1321
1060
  }
1061
+ const countTokens = (text, model = "") => {
1062
+ if (!text) return 0;
1063
+ const LEGACY_CLAUDE = /-(3|4)(-|$)/;
1064
+ const scale = LEGACY_CLAUDE.test(model) ? 1.13 : 1.5;
1065
+ return Math.round(encode(text).length * scale);
1066
+ };
1067
+ const queuedPrompt = (it) => it.type === "attachment" && it.attachment?.type === "queued_command" && it.attachment.commandMode === "prompt" && (it.attachment.origin?.kind ?? "human") === "human" && it.attachment.prompt?.trim() ? it.attachment.prompt : null;
1068
+ const USER_ATTACHMENTS = /* @__PURE__ */ new Set(["opened_file_in_ide", "selected_lines_in_ide", "selected_lines_in_diff", "file", "already_read_file"]);
1069
+ function speaker(it, blocks = []) {
1070
+ if (it.type === "attachment") return USER_ATTACHMENTS.has(it.attachment.type) ? "user" : "system";
1071
+ if (it.type === "assistant") return it.message?.model === "<synthetic>" ? "system" : "assistant";
1072
+ return it.isMeta && !blocks.some((b) => parseCommand(b.text)?.name) ? "system" : "user";
1073
+ }
1074
+ const ENVELOPE = /* @__PURE__ */ new Set(["type", "subtype", "level", "content", "uuid", "parentUuid", "logicalParentUuid", "isSidechain", "isMeta", "userType", "entrypoint", "cwd", "sessionId", "session_id", "version", "gitBranch", "slug", "timestamp"]);
1075
+ function systemNote(it) {
1076
+ if (it.subtype === "turn_duration") return null;
1077
+ const title = humanize(String(it.subtype)).replace(/^Api\b/, "API");
1078
+ const payload = Object.fromEntries(Object.entries(it).filter(([k]) => !ENVELOPE.has(k)));
1079
+ const body = it.content || (Object.keys(payload).length ? JSON.stringify(payload, null, 2) : null);
1080
+ return { title, body, level: ["warning", "error"].includes(it.level) ? it.level : null };
1081
+ }
1082
+ const changed = (state, key, value) => {
1083
+ const prev = state[key];
1084
+ state[key] = value;
1085
+ return prev && prev !== value;
1086
+ };
1087
+ function stateNote(it, state) {
1088
+ if (it.type === "permission-mode") {
1089
+ return changed(state, "mode", it.permissionMode) ? { title: `Permission mode → ${it.permissionMode}`, kind: "mode" } : null;
1090
+ }
1091
+ if ((it.type === "user" || it.type === "assistant") && it.cwd) {
1092
+ return changed(state, "cwd", it.cwd) ? { title: `Working directory → ${it.cwd}` } : null;
1093
+ }
1094
+ if (it.type === "frame-link" && it.frameUrl && !state.artifacts.has(it.frameUrl)) {
1095
+ state.artifacts.add(it.frameUrl);
1096
+ return { title: `Artifact: ${it.title || it.frameUrl}`, url: it.frameUrl };
1097
+ }
1098
+ return null;
1099
+ }
1100
+ const metaTurn = (it, role, blocks, uuid = it.uuid) => ({
1101
+ uuid,
1102
+ role,
1103
+ ts: it.timestamp ? Date.parse(it.timestamp) : null,
1104
+ model: null,
1105
+ usage: null,
1106
+ tokenDelta: null,
1107
+ tokenTotal: null,
1108
+ blocks
1109
+ });
1110
+ const noteTurn = (it, note, uuid) => metaTurn(it, "system", [{ type: "system", ...note }], uuid);
1111
+ function flatten(items, instructions = []) {
1112
+ const turns = [];
1113
+ const results = {};
1114
+ const companions = {};
1115
+ const compactions = {};
1116
+ const state = { mode: null, cwd: null, artifacts: /* @__PURE__ */ new Set() };
1117
+ for (let it of items) {
1118
+ if (it._relogged) continue;
1119
+ const change = stateNote(it, state);
1120
+ if (change) turns.push(noteTurn(it, change, `state-${turns.length}`));
1121
+ if (it.subtype === "compact_boundary" || it.isCompactSummary) {
1122
+ const key = it.isCompactSummary ? it.parentUuid ?? it.uuid : it.uuid;
1123
+ let turn = compactions[key];
1124
+ if (!turn) turns.push(turn = compactions[key] = metaTurn(it, "compact", [{ type: "compact" }]));
1125
+ if (it.isCompactSummary) {
1126
+ turn.blocks[0].summary = messageText(it);
1127
+ turn.tokenDelta = it._tokenDelta ?? null;
1128
+ turn.tokenTotal = it._tokenTotal ?? null;
1129
+ } else {
1130
+ turn.blocks[0].trigger = it.compactMetadata?.trigger;
1131
+ turn.blocks[0].preTokens = it.compactMetadata?.preTokens;
1132
+ }
1133
+ continue;
1134
+ }
1135
+ const cmd = it.type === "system" && it.subtype === "local_command" && parseCommand(it.content);
1136
+ if (cmd) {
1137
+ if (!cmd.name && !cmd.stdout) continue;
1138
+ it = { ...it, type: "user", isMeta: true, message: { role: "user", content: it.content } };
1139
+ } else if (it.type === "system") {
1140
+ const note = systemNote(it);
1141
+ if (note) turns.push(noteTurn(it, note));
1142
+ continue;
1143
+ }
1144
+ const queued = queuedPrompt(it);
1145
+ if (queued) {
1146
+ turns.push({
1147
+ uuid: it.uuid,
1148
+ role: "user",
1149
+ queued: true,
1150
+ ts: it.timestamp ? Date.parse(it.timestamp) : null,
1151
+ model: null,
1152
+ msgId: null,
1153
+ usage: null,
1154
+ tokenDelta: it._tokenDelta ?? null,
1155
+ tokenTotal: it._tokenTotal ?? null,
1156
+ blocks: [{ type: "text", text: queued }]
1157
+ });
1158
+ continue;
1159
+ }
1160
+ if (it.type === "attachment" && it.attachment) {
1161
+ if (it.attachment.type === "structured_output" && it.attachment.toolUseID) continue;
1162
+ const block = { type: "attachment", attachment: it.attachment };
1163
+ const role2 = speaker(it);
1164
+ const last = turns[turns.length - 1];
1165
+ if (last && isContextTurn(last) && last.role === role2) {
1166
+ last.blocks.push(block);
1167
+ if (it._tokenDelta != null) last.tokenDelta = (last.tokenDelta ?? 0) + it._tokenDelta;
1168
+ if (it._tokenTotal != null) last.tokenTotal = it._tokenTotal;
1169
+ } else {
1170
+ turns.push({ ...metaTurn(it, role2, [block]), tokenDelta: it._tokenDelta ?? null, tokenTotal: it._tokenTotal ?? null });
1171
+ }
1172
+ continue;
1173
+ }
1174
+ if (it.type === "result" && it.key && it.agentId) {
1175
+ const text = typeof it.result === "string" ? it.result : "```json\n" + JSON.stringify(it.result, null, 2) + "\n```";
1176
+ turns.push({
1177
+ uuid: `wf-${it.agentId}`,
1178
+ role: "assistant",
1179
+ ts: null,
1180
+ model: null,
1181
+ usage: null,
1182
+ tokenDelta: null,
1183
+ tokenTotal: null,
1184
+ blocks: [{ type: "text", text: `**agent ${it.agentId}**
1185
+
1186
+ ${text}` }]
1187
+ });
1188
+ continue;
1189
+ }
1190
+ if (it.turnCompanion && it.sourceToolUseID) {
1191
+ companions[it.sourceToolUseID] = messageText(it);
1192
+ continue;
1193
+ }
1194
+ const command = it.turnCompanion && turns.findLast((t) => t.uuid === it.parentUuid);
1195
+ const name = command && command.blocks.map((b) => parseCommand(b.text)?.name).find(Boolean)?.replace(/^\//, "");
1196
+ if (name) {
1197
+ command.blocks.push({ type: "skill", name, text: messageText(it) });
1198
+ continue;
1199
+ }
1200
+ if (it.type !== "user" && it.type !== "assistant") continue;
1201
+ const msg = it.message || {};
1202
+ const blocks = collapseRedactedThinking(normalizeContent(msg.content));
1203
+ if (blocks.length === 0) continue;
1204
+ const role = it.type === "user" && blocks.every((b) => b.type === "tool_result") ? "tool" : speaker(it, blocks);
1205
+ if (it.type === "assistant" && role === "system") {
1206
+ turns.push(noteTurn(it, { title: messageText(it), level: it.isApiErrorMessage ? "error" : null }));
1207
+ continue;
1208
+ }
1209
+ for (const b of blocks) {
1210
+ if (b.type === "tool_result" && b.tool_use_id) results[b.tool_use_id] = b;
1211
+ }
1212
+ turns.push({
1213
+ uuid: it.uuid,
1214
+ role,
1215
+ // System prompting Claude itself (e.g. "Continue from where you left off.") — companions only annotate the turn before
1216
+ prompt: role === "system" && !it.turnCompanion,
1217
+ ts: it.timestamp ? Date.parse(it.timestamp) : null,
1218
+ model: msg.model || null,
1219
+ msgId: msg.id || null,
1220
+ usage: msg.usage || null,
1221
+ tokenDelta: it._tokenDelta ?? null,
1222
+ tokenTotal: it._tokenTotal ?? null,
1223
+ blocks
1224
+ });
1225
+ }
1226
+ const calls = new Set(turns.flatMap((t) => t.blocks.filter((b) => b.type === "tool_use").map((b) => b.id)));
1227
+ for (const t of turns) {
1228
+ t.blocks = t.blocks.map((b) => b.type === "tool_use" ? { ...b, result: withCompanion(results[b.id], companions[b.id]) } : b).filter((b) => !(b.type === "tool_result" && calls.has(b.tool_use_id)));
1229
+ }
1230
+ const out = turns.filter((t) => t.blocks.length > 0);
1231
+ if (instructions.length) {
1232
+ const first = instructions.find((it) => it.timestamp);
1233
+ const turn = {
1234
+ uuid: "instructions",
1235
+ role: "instruction",
1236
+ ts: first ? Date.parse(first.timestamp) - 500 : null,
1237
+ model: null,
1238
+ usage: null,
1239
+ tokenDelta: null,
1240
+ tokenTotal: null,
1241
+ blocks: instructions.map((it) => ({ type: "instruction", it }))
1242
+ };
1243
+ const i = out.findIndex((t) => t.ts != null && t.ts > turn.ts);
1244
+ i === -1 ? out.push(turn) : out.splice(i, 0, turn);
1245
+ }
1246
+ return out;
1247
+ }
1248
+ const currentModel = (turns) => turns.findLast((t) => t.model && t.model !== "<synthetic>")?.model;
1249
+ const SMALL_CONTEXT = /* @__PURE__ */ new Set([
1250
+ "claude-3-opus",
1251
+ "claude-3-sonnet",
1252
+ "claude-3-haiku",
1253
+ "claude-3-5-sonnet",
1254
+ "claude-3-5-haiku",
1255
+ "claude-3-7-sonnet",
1256
+ "claude-haiku-4-5",
1257
+ "claude-opus-4",
1258
+ "claude-opus-4-1",
1259
+ "claude-opus-4-5",
1260
+ "claude-opus-4-6",
1261
+ "claude-sonnet-4",
1262
+ "claude-sonnet-4-5",
1263
+ "claude-sonnet-4-6"
1264
+ ]);
1265
+ function contextWindow(models = []) {
1266
+ const is200k = (model) => SMALL_CONTEXT.has(model.replace(/[-@]\d{8}.*$/, ""));
1267
+ return models.some((m) => m && !is200k(m)) ? 1e6 : 2e5;
1268
+ }
1269
+ function collapseRedactedThinking(blocks) {
1270
+ const out = [];
1271
+ for (const b of blocks) {
1272
+ if (!(b.type === "thinking" && !b.thinking?.trim())) {
1273
+ out.push(b);
1274
+ continue;
1275
+ }
1276
+ const last = out[out.length - 1];
1277
+ if (last?.redacted) last.redacted++;
1278
+ else out.push({ type: "thinking", redacted: 1 });
1279
+ }
1280
+ return out;
1281
+ }
1282
+ const messageText = (it) => normalizeContent(it.message?.content).filter((b) => b.type === "text").map((b) => b.text).join("\n");
1283
+ const withCompanion = (result, text) => text ? { ...result ?? { type: "tool_result" }, content: [...normalizeContent(result?.content), { type: "text", text }] } : result;
1284
+ function normalizeContent(content) {
1285
+ if (typeof content === "string") return [{ type: "text", text: content }];
1286
+ if (!Array.isArray(content)) return [];
1287
+ return content.map((b) => typeof b === "string" ? { type: "text", text: b } : b);
1288
+ }
1289
+ const isContextTurn = (t) => t.blocks.every((b) => b.type === "attachment");
1290
+ function groupTurns(turns) {
1291
+ const groups = [];
1292
+ let cycle = null;
1293
+ let pending = [];
1294
+ const flush = () => {
1295
+ if (cycle) {
1296
+ groups.push(cycle);
1297
+ cycle = null;
1298
+ }
1299
+ };
1300
+ for (const t of turns) {
1301
+ if (t.role === "system" && (!cycle || t.prompt)) {
1302
+ flush();
1303
+ if (groups.at(-1)?.kind === "system") groups.at(-1).turns.push(t);
1304
+ else groups.push({ kind: "system", turns: [t] });
1305
+ } else if (isContextTurn(t) && !cycle) {
1306
+ const above = groups.findLast((g) => g.kind !== "system");
1307
+ if (above?.kind === "user") above.turns.push(t);
1308
+ else pending.push(t);
1309
+ } else if (t.role === "instruction" || t.role === "compact") {
1310
+ flush();
1311
+ groups.push({ kind: t.role, turns: [t] });
1312
+ } else if (t.role === "user") {
1313
+ flush();
1314
+ groups.push({ kind: "user", turns: [...pending, t] });
1315
+ pending = [];
1316
+ } else {
1317
+ cycle ??= { kind: "assistant", turns: pending.splice(0) };
1318
+ cycle.turns.push(t);
1319
+ if (t.role === "assistant" && t.blocks.some((b) => b.type === "text")) flush();
1320
+ }
1321
+ }
1322
+ flush();
1323
+ if (pending.length) groups.push({ kind: "user", turns: pending });
1324
+ return groups;
1325
+ }
1326
+ function cycleDurations(groups) {
1327
+ let prevEnd = null;
1328
+ return groups.map((g) => {
1329
+ const tss = g.turns.map((t) => t.ts).filter((ts) => ts != null);
1330
+ const end = tss.length ? tss[tss.length - 1] : null;
1331
+ const duration2 = g.kind === "assistant" && end != null && prevEnd != null ? end - prevEnd : null;
1332
+ if (end != null) prevEnd = end;
1333
+ return duration2;
1334
+ });
1335
+ }
1336
+ function tokenPoints(groups) {
1337
+ let prev = 0;
1338
+ return groups.map((g) => {
1339
+ const turns = g.turns;
1340
+ let total = null, ctx = null;
1341
+ const byMsg = /* @__PURE__ */ new Map();
1342
+ for (const t of turns) {
1343
+ if (t.tokenTotal != null) total = t.tokenTotal;
1344
+ const u = t.usage;
1345
+ if (u) {
1346
+ ctx = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0);
1347
+ byMsg.set(t.msgId ?? t.uuid, u);
1348
+ }
1349
+ }
1350
+ let usage = null;
1351
+ for (const u of byMsg.values()) {
1352
+ usage ??= { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, cacheCreation5m: 0, cacheCreation1h: 0 };
1353
+ usage.input += u.input_tokens || 0;
1354
+ usage.output += u.output_tokens || 0;
1355
+ usage.cacheRead += u.cache_read_input_tokens || 0;
1356
+ usage.cacheCreation += u.cache_creation_input_tokens || 0;
1357
+ usage.cacheCreation5m += u.cache_creation?.ephemeral_5m_input_tokens || 0;
1358
+ usage.cacheCreation1h += u.cache_creation?.ephemeral_1h_input_tokens || 0;
1359
+ }
1360
+ if (total == null || total <= prev) return null;
1361
+ const delta = total - prev;
1362
+ prev = total;
1363
+ if (ctx == null && g.kind !== "assistant") ctx = delta;
1364
+ const ts = turns.find((t) => t.ts != null)?.ts ?? null;
1365
+ return { delta, total, ts, ctx, usage, role: g.kind };
1366
+ });
1367
+ }
1368
+ function Divider({ children, onClick, title, className = "" }) {
1369
+ const Tag = onClick ? "button" : "div";
1370
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Tag, { type: onClick ? "button" : void 0, className: `divider ${className}`, onClick, title, children });
1371
+ }
1322
1372
  function LazyMount({ children, eager = false, forceMount = false, placeholderMinHeight = 120, rootRef = null, style, ...rest }) {
1323
1373
  const ref = reactExports.useRef(null);
1324
1374
  const [mounted, setMounted] = reactExports.useState(eager);
@@ -1410,6 +1460,14 @@ function splitMarkdown(text, size = 1e4) {
1410
1460
  if (buf.length) chunks.push(buf.join("\n"));
1411
1461
  return chunks;
1412
1462
  }
1463
+ function OpenLink({ href, basePath = null, children, ...props }) {
1464
+ const onClick = (e) => {
1465
+ e.preventDefault();
1466
+ e.stopPropagation();
1467
+ void window.api.openLink(href, basePath);
1468
+ };
1469
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href, onClick, ...props, children });
1470
+ }
1413
1471
  let active = false;
1414
1472
  const listeners = /* @__PURE__ */ new Set();
1415
1473
  const setActive = (v) => {
@@ -1436,12 +1494,7 @@ function buildLinkComponents(basePath) {
1436
1494
  a: ({ href, children, ...props }) => {
1437
1495
  const clickable = href && (isExternal(href) || basePath);
1438
1496
  if (!clickable) return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children });
1439
- const onClick = (e) => {
1440
- e.preventDefault();
1441
- e.stopPropagation();
1442
- void window.api.openLink(href, basePath);
1443
- };
1444
- return /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href, onClick, ...props, children });
1497
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(OpenLink, { href, basePath, ...props, children });
1445
1498
  }
1446
1499
  };
1447
1500
  }
@@ -1458,6 +1511,7 @@ function Markdown({ text, basePath = null, autoFence = false, className = "", ..
1458
1511
  }
1459
1512
  const claudeIcon = "" + new URL("claude-icon-BqihzN4C.svg", import.meta.url).href;
1460
1513
  const ExpandAllContext = React.createContext(null);
1514
+ const TranscriptFileContext = React.createContext(null);
1461
1515
  function useCollapsed(defaultOpen) {
1462
1516
  const [open, setOpen] = reactExports.useState(defaultOpen);
1463
1517
  const expandAll = reactExports.useContext(ExpandAllContext);
@@ -1467,7 +1521,8 @@ function useCollapsed(defaultOpen) {
1467
1521
  return [open, setOpen];
1468
1522
  }
1469
1523
  const isCommandTurn = (t) => t.blocks.some((b) => b.type === "text" && parseCommand(b.text)?.name);
1470
- function ConversationView({ items, instructions = [], expandAll = null }) {
1524
+ const isClearTurn = (t) => t.blocks.some((b) => b.type === "text" && parseCommand(b.text)?.name === "/clear");
1525
+ function ConversationView({ items, instructions = [], filePath = null, expandAll = null, continuesFrom = null, continuesTo = null, onShowPeriodOf }) {
1471
1526
  const turns = reactExports.useMemo(() => flatten(items, instructions), [items, instructions]);
1472
1527
  const groups = reactExports.useMemo(() => groupTurns(turns), [turns]);
1473
1528
  const points = reactExports.useMemo(() => tokenPoints(groups), [groups]);
@@ -1477,10 +1532,44 @@ function ConversationView({ items, instructions = [], expandAll = null }) {
1477
1532
  const model = currentModel(turns);
1478
1533
  const ctxLimit = contextWindow([model]);
1479
1534
  const hasTimeline = points.some(Boolean);
1480
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandAllContext.Provider, { value: expandAll, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: zoomRef, className: `conversation${hasTimeline ? " has-token-timeline" : ""}`, style: { "--font-scale": scale }, children: groups.map((g, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `conv-row conv-row-${g.kind}`, children: [
1481
- /* @__PURE__ */ jsxRuntimeExports.jsx(LazyMount, { eager: i < 8, forceMount: findOpen, placeholderMinHeight: 80, children: g.kind === "user" ? /* @__PURE__ */ jsxRuntimeExports.jsx(UserRow, { turns: g.turns, point: points[i], ctxLimit }) : g.kind === "assistant" ? /* @__PURE__ */ jsxRuntimeExports.jsx(AssistantCard, { turns: g.turns, point: points[i], ctxLimit, duration: durations[i], showAuthor: groups[i - 1]?.kind !== "assistant" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionRun, { turns: g.turns, model }) }),
1482
- points[i] ? /* @__PURE__ */ jsxRuntimeExports.jsx(TokenPoint, { point: points[i] }) : hasTimeline && g.kind === "user" && g.turns.some(isCommandTurn) && /* @__PURE__ */ jsxRuntimeExports.jsx(TokenPoint, { point: { role: "command", delta: 0 } })
1483
- ] }, g.turns[0].uuid)) }) });
1535
+ const branch = branchPoint(items, groups);
1536
+ const branchDivider = branch && /* @__PURE__ */ jsxRuntimeExports.jsxs(Divider, { className: "conv-divider", title: branch.from, children: [
1537
+ "Branched from session ",
1538
+ branch.from.slice(0, 8)
1539
+ ] });
1540
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandAllContext.Provider, { value: expandAll, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TranscriptFileContext.Provider, { value: filePath, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: zoomRef, className: `conversation${hasTimeline ? " has-token-timeline" : ""}`, style: { "--font-scale": scale }, children: [
1541
+ continuesFrom != null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Divider, { className: "conv-continues-from", onClick: () => onShowPeriodOf(continuesFrom), title: "Show the earlier period", children: [
1542
+ "Continues from ",
1543
+ format(continuesFrom, "MMM d, yyyy HH:mm")
1544
+ ] }),
1545
+ groups.map((g, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
1546
+ i === branch?.index && branchDivider,
1547
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `conv-row conv-row-${g.kind}`, children: [
1548
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LazyMount, { eager: i < 8, forceMount: findOpen, placeholderMinHeight: 80, children: g.kind === "user" || g.kind === "system" ? /* @__PURE__ */ jsxRuntimeExports.jsx(InputRow, { kind: g.kind, turns: g.turns, point: points[i], ctxLimit }) : g.kind === "assistant" ? /* @__PURE__ */ jsxRuntimeExports.jsx(AssistantCard, { turns: g.turns, point: points[i], ctxLimit, duration: durations[i], showAuthor: groups[i - 1]?.kind !== "assistant" }) : g.kind === "compact" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Compaction, { block: g.turns[0].blocks[0] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionRun, { turns: g.turns, model }) }),
1549
+ points[i] ? /* @__PURE__ */ jsxRuntimeExports.jsx(TokenPoint, { point: points[i] }) : hasTimeline && g.kind === "user" && g.turns.some(isCommandTurn) && /* @__PURE__ */ jsxRuntimeExports.jsx(TokenPoint, { point: { role: "command", delta: 0 } })
1550
+ ] }),
1551
+ g.kind === "user" && g.turns.some(isClearTurn) && /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { className: "conv-divider", children: "Context cleared" })
1552
+ ] }, g.turns[0].uuid)),
1553
+ branch?.index === groups.length && branchDivider,
1554
+ continuesTo != null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Divider, { className: "conv-continues-to", onClick: () => onShowPeriodOf(continuesTo), title: "Show the later period", children: [
1555
+ "Continues on ",
1556
+ format(continuesTo, "MMM d, yyyy HH:mm")
1557
+ ] })
1558
+ ] }) }) });
1559
+ }
1560
+ function branchPoint(items, groups) {
1561
+ const from = items.find((it) => it.forkedFrom)?.forkedFrom.sessionId;
1562
+ const first = from && items.find((it) => it.timestamp && !it.forkedFrom);
1563
+ if (!first) return null;
1564
+ const ts = Date.parse(first.timestamp);
1565
+ const i = groups.findIndex((g) => g.turns.some((t) => t.ts >= ts));
1566
+ return { from, index: i === -1 ? groups.length : i };
1567
+ }
1568
+ function Compaction({ block }) {
1569
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1570
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { className: "conv-divider", children: compactTitle(block) }),
1571
+ block.summary && /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: "Summary", defaultOpen: false, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: block.summary, autoFence: true }) })
1572
+ ] });
1484
1573
  }
1485
1574
  const ClaudeIcon = () => /* @__PURE__ */ jsxRuntimeExports.jsx("img", { className: "msg-icon claude-icon", src: claudeIcon, alt: "" });
1486
1575
  function TokenPoint({ point: p }) {
@@ -1508,32 +1597,36 @@ function TokenStats({ point, ctxLimit }) {
1508
1597
  rows.push(`<div class="token-tooltip-total"><span>Accumulated tokens</span><span>${fmtCompact(point.total)}</span></div>`);
1509
1598
  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-tokens", "data-tippy-html": `<div class="token-tooltip">${rows.join("")}</div>`, children: fmtCompact(point.total) });
1510
1599
  }
1511
- function UserRow({ turns, point, ctxLimit }) {
1600
+ const AUTHORS = {
1601
+ user: { Icon: Terminal, name: "You" },
1602
+ system: { Icon: LaptopMinimal, name: "System" }
1603
+ };
1604
+ function InputRow({ kind, turns, point, ctxLimit }) {
1512
1605
  const [open, setOpen] = useCollapsed(false);
1513
- const msg = turns.find((t) => !isContextTurn(t));
1606
+ const msgs = turns.filter((t) => !isContextTurn(t));
1514
1607
  const ctxItems = turns.filter(isContextTurn).flatMap((t) => t.blocks);
1515
- if (!msg || msg.isMeta && !isCommandTurn(msg)) return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: turns.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TurnRow, { turn: t }, t.uuid)) });
1516
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "msg-user", children: [
1608
+ const ts = (msgs[0] ?? turns[0]).ts;
1609
+ const { Icon, name } = AUTHORS[kind];
1610
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `msg-${kind}`, children: [
1517
1611
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "msg-header", children: [
1518
- /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "msg-icon" }),
1519
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-author", children: "You" }),
1520
- msg.queued && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-summary", children: "queued during the previous turn" }),
1612
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: "msg-icon" }),
1613
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-author", children: name }),
1614
+ msgs.some((t) => t.queued) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-summary", children: "queued during the previous turn" }),
1521
1615
  ctxItems.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "msg-summary", onClick: () => setOpen((v) => !v), children: [
1522
1616
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "aux-chevron", children: open ? "▾" : "▸" }),
1523
1617
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1524
- "attachments · ",
1525
1618
  ctxItems.length,
1526
- " item",
1619
+ " attachment",
1527
1620
  ctxItems.length === 1 ? "" : "s"
1528
1621
  ] })
1529
1622
  ] }),
1530
1623
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "msg-header-right", children: [
1531
1624
  /* @__PURE__ */ jsxRuntimeExports.jsx(TokenStats, { point, ctxLimit }),
1532
- msg.ts != null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-time", children: format(msg.ts, "HH:mm:ss") })
1625
+ ts != null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-time", children: format(ts, "HH:mm:ss") })
1533
1626
  ] })
1534
1627
  ] }),
1535
1628
  open && ctxItems.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "msg-context", children: ctxItems.map((b, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(Attachment, { att: b.attachment }, i)) }),
1536
- /* @__PURE__ */ jsxRuntimeExports.jsx(TurnRow, { turn: msg })
1629
+ msgs.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TurnRow, { turn: t }, t.uuid))
1537
1630
  ] });
1538
1631
  }
1539
1632
  function AssistantCard({ turns, point, ctxLimit, duration: duration2, showAuthor = true }) {
@@ -1541,7 +1634,7 @@ function AssistantCard({ turns, point, ctxLimit, duration: duration2, showAuthor
1541
1634
  const toolBlocks = turns.flatMap((t) => t.blocks.filter((b) => b.type === "tool_use"));
1542
1635
  const errorCount = toolBlocks.filter((b) => b.result?.is_error).length;
1543
1636
  const skillCount = toolBlocks.filter((b) => b.name === "Skill").length;
1544
- const isAux = (t) => !t.blocks.some((b) => b.type === "text");
1637
+ const isAux = (t) => t.role === "system" ? !t.blocks.some((b) => b.level) : !t.blocks.some((b) => b.type === "text");
1545
1638
  const foldable = toolBlocks.length > 0 && turns.some(isAux);
1546
1639
  const end = turns.findLast((t) => t.ts != null)?.ts ?? null;
1547
1640
  const [everOpen, setEverOpen] = reactExports.useState(false);
@@ -1576,10 +1669,10 @@ function AssistantCard({ turns, point, ctxLimit, duration: duration2, showAuthor
1576
1669
  end != null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-time", title: duration2 > 0 ? `Response time: ${fmtDuration(duration2)}` : null, children: format(end, "HH:mm:ss") })
1577
1670
  ] })
1578
1671
  ] }),
1579
- (anyVisible || everOpen) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `assistant-card-body${anyVisible ? "" : " turn-hidden"}`, children: turns.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TurnRow, { turn: t, hidden: hidden(t) }, t.uuid)) })
1672
+ (anyVisible || everOpen) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `assistant-card-body${anyVisible ? "" : " turn-hidden"}`, children: turns.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TurnRow, { turn: t, hidden: hidden(t), speaker: t.role === "system" ? "System" : null }, t.uuid)) })
1580
1673
  ] });
1581
1674
  }
1582
- function TurnRow({ turn, hidden = false }) {
1675
+ function TurnRow({ turn, hidden = false, speaker: speaker2 = null }) {
1583
1676
  const contextBlocks = [], otherBlocks = [];
1584
1677
  const groupAttachments = turn.role === "user";
1585
1678
  for (const b of turn.blocks) {
@@ -1587,12 +1680,12 @@ function TurnRow({ turn, hidden = false }) {
1587
1680
  else otherBlocks.push(b);
1588
1681
  }
1589
1682
  const hasError = turn.blocks.some((b) => b.type === "tool_use" && b.result?.is_error);
1590
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `turn turn-${turn.role} ${turn.isMeta ? "turn-meta-note" : ""} ${hasError ? "turn-error" : ""} ${hidden ? "turn-hidden" : ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "turn-blocks", children: [
1591
- contextBlocks.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { className: "attachment", defaultOpen: false, title: `attachments · ${contextBlocks.length} item${contextBlocks.length === 1 ? "" : "s"}`, children: contextBlocks.map((b, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(Attachment, { att: b.attachment }, i)) }),
1592
- otherBlocks.map((b, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(Block, { block: b }, i))
1683
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `turn turn-${turn.role} ${hasError ? "turn-error" : ""} ${hidden ? "turn-hidden" : ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "turn-blocks", children: [
1684
+ contextBlocks.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { className: "attachment", defaultOpen: false, title: `${contextBlocks.length} attachment${contextBlocks.length === 1 ? "" : "s"}`, children: contextBlocks.map((b, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(Attachment, { att: b.attachment }, i)) }),
1685
+ otherBlocks.map((b, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(Block, { block: b, speaker: speaker2 }, i))
1593
1686
  ] }) });
1594
1687
  }
1595
- function Block({ block }) {
1688
+ function Block({ block, speaker: speaker2 = null }) {
1596
1689
  if (block.type === "text") {
1597
1690
  const cmd = parseCommand(block.text);
1598
1691
  if (cmd) {
@@ -1631,11 +1724,19 @@ function Block({ block }) {
1631
1724
  const parts = Array.isArray(c) ? c : [typeof c === "string" ? { type: "text", text: c } : c];
1632
1725
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `tool-result ${block.is_error ? "error" : ""}`, children: [
1633
1726
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tool-result-label", children: block.is_error ? "Error:" : "Result:" }),
1634
- parts.map((p, i) => p.type === "text" ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: p.text }, i) : /* @__PURE__ */ jsxRuntimeExports.jsx(Block, { block: p }, i))
1727
+ parts.map((p, i) => p.type === "text" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
1728
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SavedOutputLink, { text: p.text }),
1729
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: p.text })
1730
+ ] }, i) : /* @__PURE__ */ jsxRuntimeExports.jsx(Block, { block: p }, i))
1635
1731
  ] });
1636
1732
  }
1637
1733
  if (block.type === "attachment") {
1638
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Attachment, { att: block.attachment });
1734
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Attachment, { att: block.attachment, speaker: speaker2 });
1735
+ }
1736
+ if (block.type === "system") {
1737
+ const className = `system-note ${block.level ?? ""} ${block.kind ?? ""}`;
1738
+ if (!block.body) return /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { title: said(speaker2, block.url ? /* @__PURE__ */ jsxRuntimeExports.jsx(OpenLink, { href: block.url, children: block.title }) : block.title), className });
1739
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: said(speaker2, block.title), className, defaultOpen: !!block.level, children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: block.body }) });
1639
1740
  }
1640
1741
  if (block.type === "image") {
1641
1742
  const src = block.source;
@@ -1646,6 +1747,15 @@ function Block({ block }) {
1646
1747
  }
1647
1748
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: block.type || "block", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: safeJson(block) }) });
1648
1749
  }
1750
+ function SavedOutputLink({ text }) {
1751
+ const baseFile = reactExports.useContext(TranscriptFileContext);
1752
+ const saved = persistedOutput(text);
1753
+ if (!saved || !baseFile) return null;
1754
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tool-result-label saved-output", children: [
1755
+ "Full output: ",
1756
+ /* @__PURE__ */ jsxRuntimeExports.jsx(OpenLink, { href: saved.href, basePath: baseFile, children: saved.path })
1757
+ ] });
1758
+ }
1649
1759
  const ATTACHMENT_RENDERERS = {
1650
1760
  selected_lines_in_ide: (a) => {
1651
1761
  const path = a.displayPath || a.filename || "";
@@ -1663,6 +1773,10 @@ const ATTACHMENT_RENDERERS = {
1663
1773
  body: safeJson({ added: a.addedNames, removed: a.removedNames }),
1664
1774
  defaultOpen: false
1665
1775
  }),
1776
+ prompt_snapshot: (a) => ({
1777
+ title: `Prompt snapshot: system prompt, ${a.tools?.length ?? 0} tools`,
1778
+ body: [...a.systemPrompt ?? [], `Tools: ${(a.tools ?? []).map((t) => t.name).join(", ")}`].join("\n\n")
1779
+ }),
1666
1780
  // The one nested shape the fallback below can't flatten: files → diagnostics → range.
1667
1781
  diagnostics: (a) => {
1668
1782
  const lines = (a.files || []).flatMap((f) => (f.diagnostics || []).map((d) => `${f.uri}:${(d.range?.start?.line ?? 0) + 1} ${d.severity}: ${d.message}${d.source ? ` (${d.source})` : ""}`));
@@ -1690,18 +1804,25 @@ function genericAttachment(att) {
1690
1804
  if (k !== "type") collect(v, k);
1691
1805
  }
1692
1806
  const detail = short.find(([k]) => /path|file|name|dir|uri|url|date/i.test(k))?.[1];
1693
- const label = (att?.type || "attachment").replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
1807
+ const label = humanize(att?.type || "attachment");
1694
1808
  return {
1695
1809
  title: detail ? `${label}: ${detail}` : label,
1696
1810
  body: long.length ? long.map(([, v]) => v).join("\n\n") : short.length > 1 ? safeJson(att) : null
1697
1811
  };
1698
1812
  }
1699
- function Attachment({ att }) {
1813
+ function Attachment({ att, speaker: speaker2 = null }) {
1700
1814
  const render = ATTACHMENT_RENDERERS[att?.type];
1701
1815
  const { title, body, defaultOpen = false } = render ? render(att) : genericAttachment(att);
1702
- if (body == null) return /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { title, className: "attachment" });
1703
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title, className: "attachment", defaultOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: body }) });
1816
+ if (body == null) return /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { title: said(speaker2, title), className: "attachment" });
1817
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: said(speaker2, title), className: "attachment", defaultOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: body }) });
1704
1818
  }
1819
+ const said = (speaker2, title) => speaker2 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1820
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "aux-speaker", children: [
1821
+ speaker2,
1822
+ ": "
1823
+ ] }),
1824
+ title
1825
+ ] }) : title;
1705
1826
  function Label({ title, className }) {
1706
1827
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `aux ${className || ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "aux-label", children: [
1707
1828
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "aux-chevron", children: "•" }),
@@ -1731,6 +1852,10 @@ function JsonBlock({ value }) {
1731
1852
  function InstructionRun({ turns, model }) {
1732
1853
  const strips = turns.flatMap((t) => t.blocks).map(({ it }) => ({ ...it, tokens: countTokens(it.content, it.model ?? model) }));
1733
1854
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1855
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "msg-header msg-proxy", children: [
1856
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { className: "msg-icon" }),
1857
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-author", children: "Proxy" })
1858
+ ] }),
1734
1859
  groupInstructions(strips).map(([label, list]) => /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
1735
1860
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aux instruction-source", children: label }),
1736
1861
  list.map((it) => /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionFile, { it, model, tokens: it.tokens }, `${it.hash ?? it.file_path}-${it.timestamp}`))
@@ -1844,6 +1969,44 @@ function JsonlView({ items, expandAll = null }) {
1844
1969
  ] })
1845
1970
  ] });
1846
1971
  }
1972
+ const message = (thrown) => w(thrown) ?? String(thrown);
1973
+ function ErrorBanner() {
1974
+ const [error, setError] = reactExports.useState(null);
1975
+ reactExports.useEffect(() => {
1976
+ const report = (thrown) => setError(message(thrown));
1977
+ const onError = (e) => report(e.error ?? e.message);
1978
+ const onRejection = (e) => report(e.reason);
1979
+ window.addEventListener("error", onError);
1980
+ window.addEventListener("unhandledrejection", onRejection);
1981
+ return () => {
1982
+ window.removeEventListener("error", onError);
1983
+ window.removeEventListener("unhandledrejection", onRejection);
1984
+ };
1985
+ }, []);
1986
+ if (!error) return null;
1987
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "error-banner", role: "alert", children: [
1988
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1989
+ "⚠ ",
1990
+ error
1991
+ ] }),
1992
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setError(null), title: "Dismiss", children: "×" })
1993
+ ] });
1994
+ }
1995
+ const ErrorMessage = ({ title, error }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "error-message", children: [
1996
+ "⚠ ",
1997
+ title,
1998
+ ": ",
1999
+ message(error)
2000
+ ] });
2001
+ const AppCrashed = ({ error }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "app-crash", children: [
2002
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2003
+ "⚠ ",
2004
+ message(error)
2005
+ ] }),
2006
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => location.reload(), children: "Reload" })
2007
+ ] });
2008
+ const AppErrorBoundary = ({ children }) => /* @__PURE__ */ jsxRuntimeExports.jsx(C, { FallbackComponent: AppCrashed, children });
2009
+ const PaneErrorBoundary = ({ title, resetKeys, children }) => /* @__PURE__ */ jsxRuntimeExports.jsx(C, { resetKeys, fallbackRender: ({ error }) => /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorMessage, { title, error }), children });
1847
2010
  const time = (ts) => ts ? new Date(ts).toLocaleTimeString([], { hour12: false }) : "";
1848
2011
  const duration = (ms) => ms == null ? "" : ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
1849
2012
  const splitUrl = (url) => {
@@ -1909,243 +2072,87 @@ const Pane = React.memo(function Pane2({ value, headers, seen, expandAll }) {
1909
2072
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-label", children: "Headers" }),
1910
2073
  /* @__PURE__ */ jsxRuntimeExports.jsx(Tree, { value: headers, expandAll, collapsed: true })
1911
2074
  ] }),
1912
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-label", children: [
1913
- "Body",
1914
- (paths.length > 0 || seen?.messages?.some(Boolean)) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-label-hint", children: "Content repeated from earlier requests is faded and collapsed" })
1915
- ] }),
1916
- value == null ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-pane-empty", children: "Not captured" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-doc", style: vscodeTheme, children: [
1917
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: open }),
1918
- /* @__PURE__ */ jsxRuntimeExports.jsx(Tree, { value: messages ? bodyNoMsgs : value, expandAll, seen: bodySeen }),
1919
- messages && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-msgs", children: [
1920
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1921
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "key", children: '"messages"' }),
1922
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "colon", children: ": " }),
1923
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: "[" })
1924
- ] }),
1925
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "items", children: messages.map((m, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1926
- LazyMount,
1927
- {
1928
- eager: i < 10,
1929
- forceMount: findOpen,
1930
- rootRef: paneRef,
1931
- placeholderMinHeight: 22,
1932
- className: seen?.messages?.[i] ? "requests-seen" : void 0,
1933
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Tree, { value: m, expandAll, collapsed: !!seen?.messages?.[i], seen: msgSeen(i) })
1934
- },
1935
- i
1936
- )) }),
1937
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: "]" })
1938
- ] }),
1939
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: close })
1940
- ] })
1941
- ] });
1942
- });
1943
- function RequestsView({ filePath, date, granularity = "day", fileSize = 0, expandAll = null }) {
1944
- const [records, setRecords] = reactExports.useState(null);
1945
- const [selected, setSelected] = reactExports.useState(0);
1946
- const [tab, setTab] = reactExports.useState("request");
1947
- const rec = records?.[selected];
1948
- const body = reactExports.useDeferredValue(tab === "request" ? rec?.request ?? rec : rec?.response);
1949
- const headers = reactExports.useDeferredValue(tab === "request" ? rec?.requestHeaders : rec?.responseHeaders);
1950
- const seen = reactExports.useDeferredValue(tab === "request" ? rec?.$seen : null);
1951
- reactExports.useEffect(() => {
1952
- setRecords(null);
1953
- setSelected(0);
1954
- }, [filePath, date, granularity]);
1955
- reactExports.useEffect(() => {
1956
- let cancelled = false;
1957
- window.api.readRequests(filePath, date || null, granularity).then((res) => {
1958
- if (!cancelled) setRecords(res);
1959
- });
1960
- return () => {
1961
- cancelled = true;
1962
- };
1963
- }, [filePath, date, granularity, fileSize]);
1964
- if (!records) return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-view", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-empty", children: "Loading…" }) });
1965
- if (!records.length) {
1966
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-view", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-empty", children: "No captured requests for this session. Requests are recorded only while the capture proxy is running." }) });
1967
- }
1968
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-view", children: [
1969
- /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "requests-list", children: records.map((r, i) => {
1970
- const [method, path] = splitUrl(r.url);
1971
- return /* @__PURE__ */ jsxRuntimeExports.jsx("li", { className: i > 0 && r.kind?.[0] === "main" ? "new-turn" : "", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: i === selected ? "active" : "", onClick: () => setSelected(i), children: [
1972
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `requests-method ${method.toLowerCase()}`, children: method }),
1973
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-url", children: path }),
1974
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `requests-status ${!r.status || r.status >= 400 ? "error" : "ok"}`, children: r.status ?? "—" }),
1975
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-meta", children: time(r.timestamp) }),
1976
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-meta requests-duration", children: duration(r.durationMs) }),
1977
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `requests-kind ${r.kind ? `kind-${r.kind[0]}` : ""}`, children: r.kind?.[1] }),
1978
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-meta requests-tokens", title: "total tokens (input + output + cache)", children: tokens(totalTokens(r)) })
1979
- ] }) }, i);
1980
- }) }),
1981
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-detail", children: [
1982
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-tabs", children: ["request", "response"].map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: t === tab ? "active" : "", onClick: () => setTab(t), children: t }, t)) }),
1983
- /* @__PURE__ */ jsxRuntimeExports.jsx(Pane, { value: body, headers, seen, expandAll })
1984
- ] })
1985
- ] });
1986
- }
1987
- let MAX_LINES = 10;
1988
- let MAX_LINE_CHARS = 200;
1989
- let MODEL = null;
1990
- function truncateLine(line) {
1991
- if (line.length <= MAX_LINE_CHARS) return line;
1992
- return line.slice(0, MAX_LINE_CHARS) + ` …(${line.length - MAX_LINE_CHARS} chars truncated)`;
1993
- }
1994
- function truncate(text) {
1995
- const lines = String(text ?? "").split("\n").map(truncateLine);
1996
- if (lines.length <= MAX_LINES) return lines.join("\n");
1997
- return lines.slice(0, MAX_LINES).join("\n") + `
1998
- --- ${lines.length - MAX_LINES} lines truncated ---`;
1999
- }
2000
- function fence(text, lang = "") {
2001
- return "```" + lang + "\n" + truncate(text) + "\n```";
2002
- }
2003
- function resultText(r) {
2004
- const parts = Array.isArray(r?.content) ? r.content : [r?.content];
2005
- return parts.map((p) => typeof p === "string" ? p : p?.text ?? JSON.stringify(p)).join("\n");
2006
- }
2007
- function renderBlock(b) {
2008
- if (b.type === "text") return truncate(b.text || "");
2009
- if (b.type === "thinking") return b.redacted ? `_Thinking (encrypted${b.redacted > 1 ? ` · ${b.redacted}` : ""})_` : `Thinking:
2010
- ${fence(b.thinking)}`;
2011
- if (b.type === "tool_use") {
2012
- const tail = b.result ? `
2013
- ${b.result.is_error ? "error" : "result"}:
2014
- ${fence(resultText(b.result))}` : "";
2015
- const title = toolSummary(b.name, b.input);
2016
- return `[${title}]
2017
- ${fence(JSON.stringify(b.input, null, 2), "json")}${tail}`;
2018
- }
2019
- if (b.type === "tool_result") return `${b.is_error ? "error" : "result"}:
2020
- ${fence(resultText(b))}`;
2021
- if (b.type === "skill") return `Skill${b.name ? `: ${b.name}` : ""}:
2022
- ${fence(b.text)}`;
2023
- if (b.type === "image") return "[image]";
2024
- if (b.type === "instruction") {
2025
- const it = b.it;
2026
- const path = it.name && it.name !== it.file_path ? `
2027
- \`${it.file_path}\`` : "";
2028
- return `_${instructionTitle(it, MODEL)}_${path}
2029
- ${fence(it.error ? `error: ${it.error}` : it.content || "")}`;
2030
- }
2031
- return fence(JSON.stringify(b, null, 2), "json");
2032
- }
2033
- function renderTurn(t) {
2034
- if (t.role === "instruction") {
2035
- return groupInstructions(t.blocks.map((b) => b.it)).map(([label, list]) => `**Instructions loaded (${label}):**
2036
-
2037
- ${list.map((it) => renderBlock({ type: "instruction", it })).join("\n\n")}`).join("\n\n");
2038
- }
2039
- const role = t.role === "user" ? t.queued ? "User (queued)" : "User" : `Assistant`;
2040
- const tokens2 = t.tokenTotal > 0 && t.tokenDelta != null ? ` (${t.tokenDelta >= 0 ? "+" : ""}${fmtCompact(t.tokenDelta)} / ${fmtCompact(t.tokenTotal)} tokens)` : "";
2041
- return `**${role}${tokens2}:**
2042
-
2043
- ${t.blocks.map(renderBlock).join("\n\n")}`;
2044
- }
2045
- const TRUNCATE_LINES = 10;
2046
- const TRUNCATE_LINE_CHARS = 200;
2047
- function markdownSession(meta, items, truncated, instructions = []) {
2048
- MAX_LINES = truncated ? TRUNCATE_LINES : Infinity;
2049
- MAX_LINE_CHARS = truncated ? TRUNCATE_LINE_CHARS : Infinity;
2050
- const t = meta.tokens;
2051
- const stu = meta.serverToolUse;
2052
- const wallDuration = meta.lastActivityAt - meta.startedAt;
2053
- const ctxLimit = contextWindow(meta.models);
2054
- const contextPct = Math.round(meta.lastContextTokens / ctxLimit * 100);
2055
- const cacheHitPct = (meta.cacheHitRatio * 100).toFixed(0);
2056
- const turns = items ? flatten(items, instructions) : null;
2057
- MODEL = turns ? currentModel(turns) : null;
2058
- const transcript = turns ? turns.map(renderTurn).join("\n\n---\n\n") : "_Loading…_";
2059
- const conversation = `
2060
- # Conversation
2061
-
2062
- ${transcript}`;
2063
- const summary = `
2064
- # Session: ${meta.sessionId}
2065
-
2066
- # Summary
2067
- - Estimated cost: ${fmtUSD(meta.cost)}
2068
- - Total tokens: ${fmtCompact(meta.totalTokens)}
2069
- - Working time: ${fmtDuration(meta.activeMs)}
2070
- - Context size: ${fmtCompact(meta.lastContextTokens)} / ${fmtCompact(ctxLimit)} (${contextPct}%)${meta.savings.using5mCache > 0 ? `
2071
-
2072
- ## Potential savings
2073
- - with 5m cache: ${meta.cost > 0 ? `(${(meta.savings.using5mCache / meta.cost * 100).toFixed(0)}%) ` : ""}−${fmtUSD(meta.savings.using5mCache)}` : ""}
2074
-
2075
- ## Tokens
2076
- - Input: ${fmtNum(t.input)}
2077
- - Output: ${fmtNum(t.output)}
2078
- - Cache write (5m): ${fmtNum(t.cacheCreation5m || 0)}
2079
- - Cache write (1h): ${fmtNum(t.cacheCreation1h || 0)}
2080
- - Cache read: ${fmtNum(t.cacheRead)}
2081
- - Cache hit ratio: ${cacheHitPct}%
2082
- - Server tools (search / fetch): ${fmtNum(stu.webSearch)} / ${fmtNum(stu.webFetch)}
2083
-
2084
- ## Activity
2085
- - Started: ${format(meta.startedAt, "pp")}
2086
- - Last activity: ${format(meta.lastActivityAt, "pp")}
2087
- - Wall duration: ${fmtDuration(wallDuration)}
2088
- - Active periods: ${fmtNum(meta.activityPeriods.length)}
2089
- - Messages: ${fmtNum(meta.messageCount)}
2090
- - Tool calls: ${fmtToolCalls(meta)}
2091
-
2092
- ## Identity
2093
- - Model: ${meta.models.join(", ") || "—"}
2094
- - Service tier: ${meta.serviceTier || "—"}
2095
- - Speed: ${meta.speed ? meta.speed === "fast" ? meta.fastPricingUnknown ? "fast (pricing unknown)" : "fast" : meta.speed : "—"}
2096
- ${meta.tag ? `- Tag: ${meta.tag}
2097
- ` : ""}- Git branch: ${meta.gitBranch || "—"}
2098
- - Source: ${meta.source || meta.entrypoint || "—"}
2099
- - Scheduled: ${meta.hasScheduledTask ? "yes" : "no"}
2100
- - CLI version: ${meta.version || "—"}
2101
- - Project: ${meta.project || "—"}
2102
- ${meta.worktreePath ? `- Worktree: ${meta.worktreePath}
2103
- ` : ""}- Log file: ${meta.filePath}
2104
- - File size: ${fmtBytes(meta.fileSize)}
2105
- `;
2106
- const body = `
2107
- <summary>
2108
- ${summary}
2109
- </summary>
2110
-
2111
-
2112
- <transcript>
2113
- ${conversation}
2114
- </transcript>`.trim();
2115
- return { body };
2116
- }
2117
- function Toggle({ checked, onChange, label, className = "" }) {
2118
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `toggle ${className}`, children: [
2119
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2120
- "input",
2121
- {
2122
- type: "checkbox",
2123
- checked,
2124
- onChange: (e) => onChange(e.target.checked)
2125
- }
2126
- ),
2127
- label && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "toggle-label", children: label })
2075
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-label", children: [
2076
+ "Body",
2077
+ (paths.length > 0 || seen?.messages?.some(Boolean)) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-label-hint", children: "Content repeated from earlier requests is faded and collapsed" })
2078
+ ] }),
2079
+ value == null ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-pane-empty", children: "Not captured" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-doc", style: vscodeTheme, children: [
2080
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: open }),
2081
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tree, { value: messages ? bodyNoMsgs : value, expandAll, seen: bodySeen }),
2082
+ messages && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-msgs", children: [
2083
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2084
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "key", children: '"messages"' }),
2085
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "colon", children: ": " }),
2086
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: "[" })
2087
+ ] }),
2088
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "items", children: messages.map((m, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2089
+ LazyMount,
2090
+ {
2091
+ eager: i < 10,
2092
+ forceMount: findOpen,
2093
+ rootRef: paneRef,
2094
+ placeholderMinHeight: 22,
2095
+ className: seen?.messages?.[i] ? "requests-seen" : void 0,
2096
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Tree, { value: m, expandAll, collapsed: !!seen?.messages?.[i], seen: msgSeen(i) })
2097
+ },
2098
+ i
2099
+ )) }),
2100
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: "]" })
2101
+ ] }),
2102
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bracket", children: close })
2103
+ ] })
2128
2104
  ] });
2129
- }
2130
- function PromptEditor({ source, edited, setEdited, pretty }) {
2131
- const [editing, setEditing] = reactExports.useState(false);
2132
- const value = edited || source;
2133
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "prompt-editor-wrap", children: [
2134
- edited && edited !== source && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "prompt-editor-reset", onClick: (e) => {
2135
- e.stopPropagation();
2136
- setEdited("");
2137
- }, children: "Reset" }),
2138
- pretty && !editing ? /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "prompt-editor-styled", text: value, onClick: () => setEditing(true), title: "Click to edit" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2139
- "textarea",
2140
- {
2141
- className: "prompt-editor-raw",
2142
- value,
2143
- spellCheck: true,
2144
- autoFocus: pretty,
2145
- onChange: (e) => setEdited(e.target.value),
2146
- onBlur: pretty ? () => setEditing(false) : void 0
2147
- }
2148
- )
2105
+ });
2106
+ function RequestsView({ filePath, date, granularity = "day", fileSize = 0, expandAll = null }) {
2107
+ const [records, setRecords] = reactExports.useState(null);
2108
+ const [selected, setSelected] = reactExports.useState(0);
2109
+ const [tab, setTab] = reactExports.useState("request");
2110
+ const [error, setError] = reactExports.useState(null);
2111
+ const rec = records?.[selected];
2112
+ const body = reactExports.useDeferredValue(tab === "request" ? rec?.request ?? rec : rec?.response);
2113
+ const headers = reactExports.useDeferredValue(tab === "request" ? rec?.requestHeaders : rec?.responseHeaders);
2114
+ const seen = reactExports.useDeferredValue(tab === "request" ? rec?.$seen : null);
2115
+ reactExports.useEffect(() => {
2116
+ setRecords(null);
2117
+ setSelected(0);
2118
+ setError(null);
2119
+ }, [filePath, date, granularity]);
2120
+ reactExports.useEffect(() => {
2121
+ let cancelled = false;
2122
+ window.api.readRequests(filePath, date || null, granularity).then((res) => {
2123
+ if (cancelled) return;
2124
+ setRecords(res);
2125
+ setError(null);
2126
+ }, (err) => {
2127
+ if (!cancelled) setError(err);
2128
+ });
2129
+ return () => {
2130
+ cancelled = true;
2131
+ };
2132
+ }, [filePath, date, granularity, fileSize]);
2133
+ if (!records) {
2134
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-view", children: error ? /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorMessage, { title: "Couldn't load the captured requests", error }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-empty", children: "Loading…" }) });
2135
+ }
2136
+ if (!records.length) {
2137
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-view", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-empty", children: "No captured requests for this session. Requests are recorded only while the capture proxy is running." }) });
2138
+ }
2139
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-view", children: [
2140
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "requests-list", children: records.map((r, i) => {
2141
+ const [method, path] = splitUrl(r.url);
2142
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("li", { className: i > 0 && r.kind?.[0] === "main" ? "new-turn" : "", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: i === selected ? "active" : "", onClick: () => setSelected(i), children: [
2143
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `requests-method ${method.toLowerCase()}`, children: method }),
2144
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-url", children: path }),
2145
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `requests-status ${!r.status || r.status >= 400 ? "error" : "ok"}`, children: r.status ?? "—" }),
2146
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-meta", children: time(r.timestamp) }),
2147
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-meta requests-duration", children: duration(r.durationMs) }),
2148
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `requests-kind ${r.kind ? `kind-${r.kind[0]}` : ""}`, children: r.kind?.[1] }),
2149
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "requests-meta requests-tokens", title: "total tokens (input + output + cache)", children: tokens(totalTokens(r)) })
2150
+ ] }) }, i);
2151
+ }) }),
2152
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "requests-detail", children: [
2153
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-tabs", children: ["request", "response"].map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: t === tab ? "active" : "", onClick: () => setTab(t), children: t }, t)) }),
2154
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Pane, { value: body, headers, seen, expandAll })
2155
+ ] })
2149
2156
  ] });
2150
2157
  }
2151
2158
  function createTypewriter(onText) {
@@ -2167,7 +2174,7 @@ function createTypewriter(onText) {
2167
2174
  };
2168
2175
  return push;
2169
2176
  }
2170
- function AgentOutput({ output, pretty, running, error }) {
2177
+ function AgentOutput({ output, running, error }) {
2171
2178
  const [displayed, setDisplayed] = reactExports.useState(output);
2172
2179
  const lastOutputRef = reactExports.useRef(output);
2173
2180
  const scrollRef = reactExports.useRef(null);
@@ -2188,7 +2195,7 @@ function AgentOutput({ output, pretty, running, error }) {
2188
2195
  }, [displayed, running, error]);
2189
2196
  if (!displayed && !running && !error) return null;
2190
2197
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-prompt-output", ref: scrollRef, children: [
2191
- pretty ? /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { text: displayed }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "agent-view-raw", children: displayed }),
2198
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { text: displayed }),
2192
2199
  running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-prompt-typing", "aria-label": "Running", children: [
2193
2200
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "dot" }),
2194
2201
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "dot" }),
@@ -2197,54 +2204,8 @@ function AgentOutput({ output, pretty, running, error }) {
2197
2204
  error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-prompt-error", children: error })
2198
2205
  ] });
2199
2206
  }
2200
- function AgentView({ meta, items, instructions, agent, onClose }) {
2201
- const [pretty, setPretty] = useLocalStorage("agent.pretty", true);
2202
- const [copied, setCopied] = reactExports.useState(false);
2203
- const { body } = markdownSession(meta, items, agent.truncated, instructions);
2204
- const combinedText = `${agent.prompt}
2205
-
2206
- ---
2207
- ${body}`;
2208
- const onCopy = async () => {
2209
- await navigator.clipboard.writeText(combinedText);
2210
- setCopied(true);
2211
- setTimeout(() => setCopied(false), 1500);
2212
- };
2213
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-view", children: [
2214
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-view-actions", children: [
2215
- /* @__PURE__ */ jsxRuntimeExports.jsx(Toggle, { checked: agent.truncated, onChange: agent.setTruncated, label: "truncated" }),
2216
- /* @__PURE__ */ jsxRuntimeExports.jsx(Toggle, { checked: pretty, onChange: setPretty, label: "human-friendly" }),
2217
- onClose && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "agent-view-close", onClick: onClose, "aria-label": "Close", children: "✕" })
2218
- ] }),
2219
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-view-left", children: [
2220
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-view-label", children: "Session" }),
2221
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-view-block", children: pretty ? /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { text: body }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "agent-view-raw", children: body }) })
2222
- ] }),
2223
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-view-right", children: [
2224
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-prompt-wrap", children: [
2225
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-view-label", children: "Prompt" }),
2226
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-view-block", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PromptEditor, { source: agent.defaultPrompt, edited: agent.editedPrompt, setEdited: agent.setEditedPrompt, pretty }) }),
2227
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-prompt-actions", children: [
2228
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "button-primary", onClick: onCopy, children: copied ? "Copied" : "Copy" }),
2229
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "button-primary", onClick: () => agent.send(combinedText), disabled: agent.running, children: "Send" })
2230
- ] })
2231
- ] }),
2232
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-view-output-wrap", children: [
2233
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-view-label", children: "AI Result" }),
2234
- /* @__PURE__ */ jsxRuntimeExports.jsx(AgentOutput, { output: agent.output, pretty, running: agent.running, error: agent.error })
2235
- ] })
2236
- ] })
2237
- ] });
2238
- }
2239
- function SessionSummary({ meta, items, instructions, agent, onOpenAgent, granularity = "day" }) {
2207
+ function SessionSummary({ meta, items, instructions, agent, granularity = "day" }) {
2240
2208
  const timeFormat = granularity === "day" ? "pp" : "MMM d, pp";
2241
- const onAnalyze = () => {
2242
- const { body } = markdownSession(meta, items, agent.truncated, instructions);
2243
- agent.send(`${agent.prompt}
2244
-
2245
- ---
2246
- ${body}`);
2247
- };
2248
2209
  const t = meta.tokens;
2249
2210
  const totalTokens2 = meta.totalTokens;
2250
2211
  const wallDuration = meta.lastActivityAt - meta.startedAt;
@@ -2258,30 +2219,20 @@ ${body}`);
2258
2219
  const ctxTone = tone(meta.lastContextTokens, THRESHOLDS.context);
2259
2220
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-summary", children: [
2260
2221
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "summary-ai", children: [
2261
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "summary-ai-row", children: [
2262
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2263
- "button",
2264
- {
2265
- type: "button",
2266
- className: "button-primary",
2267
- onClick: onAnalyze,
2268
- disabled: agent.running || !items,
2269
- children: agent.running ? "⚡︎ Analyzing…" : "⚡︎ AI Analyze"
2270
- }
2271
- ),
2272
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2273
- "button",
2274
- {
2275
- type: "button",
2276
- className: "summary-ai-gear",
2277
- onClick: onOpenAgent,
2278
- title: "Open Agent tab",
2279
- "aria-label": "Open Agent tab",
2280
- children: "⚙"
2281
- }
2282
- )
2283
- ] }),
2284
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "summary-ai-output", children: /* @__PURE__ */ jsxRuntimeExports.jsx(AgentOutput, { output: agent.output, pretty: true, running: agent.running, error: agent.error }) })
2222
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2223
+ "button",
2224
+ {
2225
+ type: "button",
2226
+ className: "button-primary summary-ai-button",
2227
+ onClick: () => agent.analyze(meta, items, instructions),
2228
+ disabled: agent.running || !items,
2229
+ children: [
2230
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: agent.running ? "hidden" : "", children: "⚡︎ AI Analyze" }),
2231
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: agent.running ? "" : "hidden", children: "⚡︎ Analyzing…" })
2232
+ ]
2233
+ }
2234
+ ),
2235
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AgentOutput, { output: agent.output, running: agent.running, error: agent.error })
2285
2236
  ] }),
2286
2237
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Section, { title: "Summary", children: [
2287
2238
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Estimated cost", value: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -2336,7 +2287,10 @@ ${body}`);
2336
2287
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Tool calls", value: fmtToolCalls(meta) })
2337
2288
  ] }),
2338
2289
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Section, { title: "Identity", children: [
2290
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Session ID", value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-id", children: meta.sessionId }), mono: true, full: true, autoselect: true }),
2291
+ " ",
2339
2292
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Model", value: modelLabel, mono: true }),
2293
+ meta.efforts.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Effort", value: meta.efforts.join(", ") }),
2340
2294
  meta.serviceTier && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Service tier", value: meta.serviceTier }),
2341
2295
  meta.speed && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Speed", value: meta.speed === "fast" ? meta.fastPricingUnknown ? "fast (pricing unknown)" : "fast" : meta.speed }),
2342
2296
  meta.tag && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Tag", value: meta.tag, mono: true }),
@@ -2344,11 +2298,10 @@ ${body}`);
2344
2298
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Source", value: meta.source || meta.entrypoint || "—" }),
2345
2299
  meta.hasScheduledTask && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Scheduled", value: "yes" }),
2346
2300
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "CLI version", value: meta.version || "—", mono: true }),
2347
- /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Session ID", value: meta.sessionId, mono: true, full: true, autoselect: true }),
2348
2301
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Project", value: meta.project || "—", mono: true, full: true, autoselect: true }),
2349
2302
  meta.worktreePath && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Worktree", value: meta.worktreePath, mono: true, full: true, autoselect: true }),
2350
2303
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Log file", value: meta.filePath, mono: true, full: true, autoselect: true }),
2351
- /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "File size", value: fmtBytes(meta.fileSize) })
2304
+ " "
2352
2305
  ] })
2353
2306
  ] });
2354
2307
  }
@@ -2384,28 +2337,180 @@ function Field({ label, value, mono, full, below, autoselect }) {
2384
2337
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `field-value ${mono ? "mono" : ""}`, onClick: selectAll, children: value })
2385
2338
  ] });
2386
2339
  }
2340
+ function Toggle({ checked, onChange, label, className = "" }) {
2341
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `toggle ${className}`, children: [
2342
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2343
+ "input",
2344
+ {
2345
+ type: "checkbox",
2346
+ checked,
2347
+ onChange: (e) => onChange(e.target.checked)
2348
+ }
2349
+ ),
2350
+ label && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "toggle-label", children: label })
2351
+ ] });
2352
+ }
2353
+ const MAX_LINES = 10;
2354
+ const MAX_LINE_CHARS = 200;
2355
+ let MODEL = null;
2356
+ function truncateLine(line) {
2357
+ if (line.length <= MAX_LINE_CHARS) return line;
2358
+ return line.slice(0, MAX_LINE_CHARS) + ` …(${line.length - MAX_LINE_CHARS} chars truncated)`;
2359
+ }
2360
+ function truncate(text) {
2361
+ const lines = String(text ?? "").split("\n").map(truncateLine);
2362
+ if (lines.length <= MAX_LINES) return lines.join("\n");
2363
+ return lines.slice(0, MAX_LINES).join("\n") + `
2364
+ --- ${lines.length - MAX_LINES} lines truncated ---`;
2365
+ }
2366
+ function fence(text, lang = "") {
2367
+ return "```" + lang + "\n" + truncate(text) + "\n```";
2368
+ }
2369
+ function resultText(r) {
2370
+ const parts = Array.isArray(r?.content) ? r.content : [r?.content];
2371
+ return parts.map((p) => typeof p === "string" ? p : p?.text ?? JSON.stringify(p)).join("\n");
2372
+ }
2373
+ function renderBlock(b) {
2374
+ if (b.type === "text") return truncate(b.text || "");
2375
+ if (b.type === "thinking") return b.redacted ? `_Thinking (encrypted${b.redacted > 1 ? ` · ${b.redacted}` : ""})_` : `Thinking:
2376
+ ${fence(b.thinking)}`;
2377
+ if (b.type === "tool_use") {
2378
+ const tail = b.result ? `
2379
+ ${b.result.is_error ? "error" : "result"}:
2380
+ ${fence(resultText(b.result))}` : "";
2381
+ const title = toolSummary(b.name, b.input);
2382
+ return `[${title}]
2383
+ ${fence(JSON.stringify(b.input, null, 2), "json")}${tail}`;
2384
+ }
2385
+ if (b.type === "tool_result") return `${b.is_error ? "error" : "result"}:
2386
+ ${fence(resultText(b))}`;
2387
+ if (b.type === "skill") return `Skill${b.name ? `: ${b.name}` : ""}:
2388
+ ${fence(b.text)}`;
2389
+ if (b.type === "system") return `_${b.title}_${b.url ? ` (${b.url})` : ""}${b.body ? `
2390
+ ${fence(b.body)}` : ""}`;
2391
+ if (b.type === "image") return "[image]";
2392
+ if (b.type === "instruction") {
2393
+ const it = b.it;
2394
+ const path = it.name && it.name !== it.file_path ? `
2395
+ \`${it.file_path}\`` : "";
2396
+ return `_${instructionTitle(it, MODEL)}_${path}
2397
+ ${fence(it.error ? `error: ${it.error}` : it.content || "")}`;
2398
+ }
2399
+ return fence(JSON.stringify(b, null, 2), "json");
2400
+ }
2401
+ function renderTurn(t) {
2402
+ if (t.role === "instruction") {
2403
+ return "**Proxy:**\n\n" + groupInstructions(t.blocks.map((b) => b.it)).map(([label, list]) => `**Instructions loaded (${label}):**
2404
+
2405
+ ${list.map((it) => renderBlock({ type: "instruction", it })).join("\n\n")}`).join("\n\n");
2406
+ }
2407
+ if (t.role === "compact") {
2408
+ const b = t.blocks[0];
2409
+ return `**${compactTitle(b)}**${b.summary ? `
2410
+
2411
+ Summary:
2412
+ ${fence(b.summary)}` : ""}`;
2413
+ }
2414
+ const role = t.role === "system" ? "System" : t.role === "user" ? t.queued ? "User (queued)" : "User" : `Assistant`;
2415
+ const tokens2 = t.tokenTotal > 0 && t.tokenDelta != null ? ` (${t.tokenDelta >= 0 ? "+" : ""}${fmtCompact(t.tokenDelta)} / ${fmtCompact(t.tokenTotal)} tokens)` : "";
2416
+ return `**${role}${tokens2}:**
2417
+
2418
+ ${t.blocks.map(renderBlock).join("\n\n")}`;
2419
+ }
2420
+ function markdownSession(meta, items, instructions = []) {
2421
+ const t = meta.tokens;
2422
+ const stu = meta.serverToolUse;
2423
+ const wallDuration = meta.lastActivityAt - meta.startedAt;
2424
+ const ctxLimit = contextWindow(meta.models);
2425
+ const contextPct = Math.round(meta.lastContextTokens / ctxLimit * 100);
2426
+ const cacheHitPct = (meta.cacheHitRatio * 100).toFixed(0);
2427
+ const turns = items ? flatten(items, instructions) : null;
2428
+ MODEL = turns ? currentModel(turns) : null;
2429
+ const transcript = turns ? turns.map(renderTurn).join("\n\n---\n\n") : "_Loading…_";
2430
+ const conversation = `
2431
+ # Conversation
2432
+
2433
+ ${transcript}`;
2434
+ const summary = `
2435
+ # Session: ${meta.sessionId}
2436
+
2437
+ # Summary
2438
+ - Estimated cost: ${fmtUSD(meta.cost)}
2439
+ - Total tokens: ${fmtCompact(meta.totalTokens)}
2440
+ - Working time: ${fmtDuration(meta.activeMs)}
2441
+ - Context size: ${fmtCompact(meta.lastContextTokens)} / ${fmtCompact(ctxLimit)} (${contextPct}%)${meta.savings.using5mCache > 0 ? `
2442
+
2443
+ ## Potential savings
2444
+ - with 5m cache: ${meta.cost > 0 ? `(${(meta.savings.using5mCache / meta.cost * 100).toFixed(0)}%) ` : ""}−${fmtUSD(meta.savings.using5mCache)}` : ""}
2445
+
2446
+ ## Tokens
2447
+ - Input: ${fmtNum(t.input)}
2448
+ - Output: ${fmtNum(t.output)}
2449
+ - Cache write (5m): ${fmtNum(t.cacheCreation5m || 0)}
2450
+ - Cache write (1h): ${fmtNum(t.cacheCreation1h || 0)}
2451
+ - Cache read: ${fmtNum(t.cacheRead)}
2452
+ - Cache hit ratio: ${cacheHitPct}%
2453
+ - Server tools (search / fetch): ${fmtNum(stu.webSearch)} / ${fmtNum(stu.webFetch)}
2454
+
2455
+ ## Activity
2456
+ ${meta.continuesFrom ? `- Continues from: ${format(meta.continuesFrom, "PPpp")} (earlier messages not included)
2457
+ ` : ""}- Started: ${format(meta.startedAt, "pp")}
2458
+ - Last activity: ${format(meta.lastActivityAt, "pp")}
2459
+ ${meta.continuesTo ? `- Continues on: ${format(meta.continuesTo, "PPpp")} (later messages not included)
2460
+ ` : ""}- Wall duration: ${fmtDuration(wallDuration)}
2461
+ - Active periods: ${fmtNum(meta.activityPeriods.length)}
2462
+ - Messages: ${fmtNum(meta.messageCount)}
2463
+ - Tool calls: ${fmtToolCalls(meta)}
2464
+
2465
+ ## Identity
2466
+ - Session ID: ${meta.sessionId}
2467
+ - Model: ${meta.models.join(", ") || "—"}
2468
+ - Effort: ${meta.efforts.join(", ") || "—"}
2469
+ - Service tier: ${meta.serviceTier || "—"}
2470
+ - Speed: ${meta.speed ? meta.speed === "fast" ? meta.fastPricingUnknown ? "fast (pricing unknown)" : "fast" : meta.speed : "—"}
2471
+ ${meta.tag ? `- Tag: ${meta.tag}
2472
+ ` : ""}- Git branch: ${meta.gitBranch || "—"}
2473
+ - Source: ${meta.source || meta.entrypoint || "—"}
2474
+ - Scheduled: ${meta.hasScheduledTask ? "yes" : "no"}
2475
+ - CLI version: ${meta.version || "—"}
2476
+ - Project: ${meta.project || "—"}
2477
+ ${meta.worktreePath ? `- Worktree: ${meta.worktreePath}
2478
+ ` : ""}- Log file: ${meta.filePath}
2479
+ `;
2480
+ const body = `
2481
+ <summary>
2482
+ ${summary}
2483
+ </summary>
2484
+
2485
+
2486
+ <transcript>
2487
+ ${conversation}
2488
+ </transcript>`.trim();
2489
+ return { body };
2490
+ }
2387
2491
  const ANALYZE_PROMPT = "# Role\r\n\r\nYou are reviewing a Claude Code session and giving the user practical advice on how to run a similar session more cheaply next time.\r\n\r\nThe user controls the prompts and the request settings around them: **model** (Opus / Sonnet / Haiku), **prompt caching**, enabled **system tools** (web search, web fetch, bash, MCP, etc.), context-window size, and CLAUDE.md. They cannot change Claude's internal reasoning. Look for missed opportunities to apply Anthropic's official Claude Code guidance.\r\n\r\n# Levers\r\n\r\nThe list below covers the most common waste patterns and should be your starting point — but it is **not exhaustive**. If you spot a different concrete inefficiency in this session that the user could change next time, raise it. Don't force-fit a session into a listed lever, and don't ignore real waste just because it isn't named here.\r\n\r\n**Out of scope — do NOT suggest these.** Anything about *how Claude itself works internally* is not under user control and must not appear in the output:\r\n\r\n - How Claude batches or sequences tool calls (e.g. \"group edits per file\", \"batch related changes into one pass\", \"make parallel tool calls\").\r\n - How Claude searches, reads, or explores (e.g. \"use Grep before Read\", \"use Glob to narrow first\").\r\n - How Claude reasons, plans, or thinks.\r\n - Anything the user would have to micromanage Claude into doing turn-by-turn — even if technically prompt-able, it isn't a realistic next-time change.\r\n\r\nStick to changes the user makes in *their* prompt, *their* settings, or *their* session structure.\r\n\r\n### Better initial prompt\r\nThe single biggest lever — vague prompts cause exploration, wrong-problem code, and correction rounds.\r\n\r\n - Scope the task, name the file (`@path/to/file`), describe the symptom and likely location, reference an existing pattern.\r\n - Provide verification criteria upfront: tests, expected outputs, screenshots, error messages.\r\n - Paste rich content directly (errors, logs, screenshots, URLs) instead of asking Claude to reproduce, search, or guess.\r\n - Use plan mode for uncertain / multi-file work; skip it for trivial fixes.\r\n\r\n### Context management\r\nEach later turn re-pays the full prior context.\r\n\r\n - `/clear` between unrelated tasks (\"kitchen sink session\" anti-pattern) — often the biggest win.\r\n - `/compact <focus>` with explicit preserve-instructions before context drifts past ~50%.\r\n - `/rewind` to redirect early. After two failed corrections, `/clear` and restart with a better prompt.\r\n - Subagents for investigation — they explore in a separate context and report a summary.\r\n - Bloated CLAUDE.md is loaded every turn. If the session shows Claude ignoring a rule that's *in* CLAUDE.md, the file is probably too long.\r\n\r\n\r\n### Request settings\r\nVisible in `<summary>`.\r\n\r\n - **Model**: Opus used for work Sonnet or Haiku could have done.\r\n - **Caching**: a low cache-hit ratio means re-paying for the same context every turn. Bursty sessions, mid-session CLAUDE.md edits, tool-list changes, or long idle gaps bust the cache.\r\n - **5m vs 1h cache**: 5m is the right default for almost all sessions and should be used most of the time. A 1h write costs ~2× a 5m write, so it only pays off in narrow cases — when the session reliably has gaps >5 min between turns (scheduled / background tasks, long manual review breaks, sessions resumed hours later). Most interactive coding does NOT qualify. Only flag 1h-caching as a fix when the activity timestamps in `<summary>` clearly show long gaps AND the cache-hit ratio is poor. Conversely, if the session is tight (<5 min gaps) but `Cache write (1h)` is non-trivial, that's overspend — recommend dropping back to 5m.\r\n - **Unused system tools** (web search/fetch, MCP, bash, etc.) add tokens every turn even when they don't fire — disable what wasn't needed. Expensive web-search/fetch loops can also be short-circuited by pasting a URL or doc.\r\n - **Context cap**: 1M-context for work that fits in 200k makes every cached token more expensive.\r\n\r\n\r\n\r\n# Input\r\n\r\n`<summary>` is rich — the app pre-computes it for you. Read every field and reason from numbers, not vibes\r\n`<transcript>` shows the actual turns, labeled with per-turn token deltas like `(+12k / 340k tokens)`. Use the deltas to find expensive moments and to ground each recommendation in a concrete event. **Instructions loaded** rows are CLAUDE.md / memory files Claude Code injected into the system prompt — they re-pay on every turn, so their size and relevance are a direct cost lever.\r\n{{TRUNCATION_NOTE}}\r\n\r\n# How to analyze\r\nSilently — don't show your work.\r\n\r\n 1. **Read `<summary>` first.** Several recommendations come straight from these numbers without needing the transcript.\r\n 2. **Then scan `<transcript>`** for the 2–4 most expensive moments via token deltas. What was happening? Was the prompt vague? Was context already large?\r\n 3. Cross-reference: do the transcript moments confirm or contradict the `<summary>` signals? E.g. low cache-hit ratio + bursty activity timestamps + small mid-session edits → cache-busting pattern.\r\n 4. Match findings against the levers above, OR identify other concrete inefficiencies the user could fix. Pick up to 3 changes that would have saved the most tokens *for this specific session*.\r\n 5. Drop any item you can't tie to a concrete signal (a number from `<summary>` or a moment in `<transcript>`).\r\n 6. Estimate a single combined savings figure (conservative). No per-item dollars.\r\n\r\n# Output\r\n\r\n## Rules\r\n - Audience: a technical user in a hurry. Skimmable in ~10 seconds. No preamble, no closing remarks, no hedging. Avoid jargon, model internals, and acronyms.\r\n - Each item: short instruction and why, tied to this session. No second sentence, no dollar figure.\r\n - Rank by impact. Fewer items are fine — one strong item beats many weak ones.\r\n - If the session was already efficient (no meaningful waste), output the one-line summary, then `I don't see any meaningful savings.` on its own line, and stop. Do not invent items to fill the list.\r\n\r\n## Format\r\n\r\n<one-line session summary, ≤20 words>\r\n\r\n**How to optimize?**\r\n\r\nN. **<instruction, ≤8 words>** — <why, tied to this session, ≤10 words>\r\n\r\n**Estimated savings: X% (about $X)**\r\n";
2388
- const TRUNCATE_NOTE = ` - Note: long content was truncated for display (lines >${TRUNCATE_LINE_CHARS} chars, blocks >${TRUNCATE_LINES} lines). The original session had the full content — base advice on what was clearly happening, not on the truncation.`;
2492
+ const TRUNCATE_NOTE = ` - Note: long content was truncated for display (lines >${MAX_LINE_CHARS} chars, blocks >${MAX_LINES} lines). The original session had the full content — base advice on what was clearly happening, not on the truncation.`;
2493
+ const PROMPT = String(ANALYZE_PROMPT).replace("{{TRUNCATION_NOTE}}", TRUNCATE_NOTE);
2389
2494
  function useAgent(resetKey) {
2390
- const [truncated, setTruncated] = useLocalStorage("agent.truncated", true);
2391
- const [editedPrompt, setEditedPrompt] = useLocalStorage("agent.prompt", "");
2392
2495
  const [running, setRunning] = reactExports.useState(false);
2393
2496
  const [output, setOutput] = reactExports.useState("");
2394
2497
  const [error, setError] = reactExports.useState("");
2395
- const defaultPrompt = String(ANALYZE_PROMPT).replace("{{TRUNCATION_NOTE}}", truncated ? TRUNCATE_NOTE : "");
2396
- const prompt = editedPrompt || defaultPrompt;
2397
2498
  reactExports.useEffect(() => window.api.onAgentOutput((chunk) => setOutput((p) => p + chunk)), []);
2398
2499
  reactExports.useEffect(() => {
2399
2500
  setError("");
2400
2501
  setRunning(false);
2401
2502
  setOutput("");
2402
2503
  }, [resetKey]);
2403
- const send = async (text) => {
2504
+ const analyze = async (meta, items, instructions) => {
2404
2505
  setError("");
2405
2506
  setOutput("");
2406
2507
  setRunning(true);
2407
2508
  try {
2408
- const { code } = await window.api.runAgentPrompt(text);
2509
+ const { body } = markdownSession(meta, items, instructions);
2510
+ const { code } = await window.api.runAgentPrompt(`${PROMPT}
2511
+
2512
+ ---
2513
+ ${body}`);
2409
2514
  if (code !== 0) setError(`Claude exited with code ${code}`);
2410
2515
  } catch (err) {
2411
2516
  setError(err.message || String(err));
@@ -2413,18 +2518,18 @@ function useAgent(resetKey) {
2413
2518
  setRunning(false);
2414
2519
  }
2415
2520
  };
2416
- return { running, error, send, prompt, editedPrompt, setEditedPrompt, output, truncated, setTruncated, defaultPrompt };
2521
+ return { running, error, output, analyze };
2417
2522
  }
2418
2523
  const TABS = [
2419
2524
  { key: "conversation", label: "Conversation" },
2420
2525
  { key: "jsonl", label: "Raw Logs" },
2421
2526
  { key: "requests", label: "API Requests" }
2422
2527
  ];
2423
- function Session({ meta, missing, date, granularity = "day" }) {
2528
+ function Session({ meta, missing, date, granularity = "day", onShowPeriodOf }) {
2424
2529
  const [items, setItems] = reactExports.useState(null);
2425
2530
  const [instructions, setInstructions] = reactExports.useState([]);
2531
+ const [error, setError] = reactExports.useState(null);
2426
2532
  const [mode, setMode] = useLocalStorage("session.view-mode", "conversation");
2427
- const [agentOpen, setAgentOpen] = reactExports.useState(false);
2428
2533
  const [expandAll, setExpandAll] = reactExports.useState(null);
2429
2534
  const filePath = meta?.filePath;
2430
2535
  const fileSize = meta?.fileSize;
@@ -2432,6 +2537,7 @@ function Session({ meta, missing, date, granularity = "day" }) {
2432
2537
  reactExports.useEffect(() => {
2433
2538
  setItems(null);
2434
2539
  setInstructions([]);
2540
+ setError(null);
2435
2541
  }, [filePath, date, granularity]);
2436
2542
  reactExports.useEffect(() => {
2437
2543
  if (!filePath) return;
@@ -2440,18 +2546,22 @@ function Session({ meta, missing, date, granularity = "day" }) {
2440
2546
  if (cancelled || !res) return;
2441
2547
  setItems(res.items);
2442
2548
  setInstructions(res.instructions || []);
2549
+ setError(null);
2550
+ }, (err) => {
2551
+ if (!cancelled) setError(err);
2443
2552
  });
2444
2553
  return () => {
2445
2554
  cancelled = true;
2446
2555
  };
2447
2556
  }, [filePath, date, granularity, fileSize]);
2448
2557
  if (!meta) {
2449
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view empty", children: missing?.warn ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-missing", children: [
2558
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view empty", children: missing?.error ? /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorMessage, { title: missing.text, error: missing.error }) : missing?.warn ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-missing", children: [
2450
2559
  "⚠ ",
2451
2560
  missing.text
2452
2561
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: missing?.text || "Select a session to inspect." }) });
2453
2562
  }
2454
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-body", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-conversation", children: agentOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(AgentView, { meta, items, instructions, agent, onClose: () => setAgentOpen(false) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-tab-pane", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "view-tab-pane-row", children: [
2563
+ const loadError = error && /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorMessage, { title: "Couldn't load this session", error });
2564
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-body", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-conversation", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-tab-pane", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "view-tab-pane-row", children: [
2455
2565
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "view-tab-pane-main", children: [
2456
2566
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "view-tabs-bar", children: [
2457
2567
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-tabs", children: TABS.map(({ key, label }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -2473,9 +2583,9 @@ function Session({ meta, missing, date, granularity = "day" }) {
2473
2583
  }
2474
2584
  )
2475
2585
  ] }),
2476
- mode === "conversation" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-tab-pane-content", children: items ? /* @__PURE__ */ jsxRuntimeExports.jsx(ConversationView, { items, instructions, expandAll }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "empty", children: "Loading conversation…" }) }) : mode === "jsonl" ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonlView, { items, expandAll }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RequestsView, { filePath, date, granularity, fileSize, expandAll })
2586
+ mode === "conversation" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-tab-pane-content", children: items ? /* @__PURE__ */ jsxRuntimeExports.jsx(ConversationView, { items, instructions, filePath: meta.parentFilePath ?? meta.filePath, expandAll, continuesFrom: meta.continuesFrom, continuesTo: meta.continuesTo, onShowPeriodOf }) : loadError || /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "empty", children: "Loading conversation…" }) }) : mode === "jsonl" ? !items && loadError || /* @__PURE__ */ jsxRuntimeExports.jsx(JsonlView, { items, expandAll }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RequestsView, { filePath, date, granularity, fileSize, expandAll })
2477
2587
  ] }),
2478
- /* @__PURE__ */ jsxRuntimeExports.jsx(SessionSummary, { meta, items, instructions, agent, onOpenAgent: () => setAgentOpen(true), granularity })
2588
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SessionSummary, { meta, items, instructions, agent, granularity })
2479
2589
  ] }) }) }) }) });
2480
2590
  }
2481
2591
  function useSwitch({ name, isOn = () => false }) {
@@ -2576,6 +2686,23 @@ const claudeDirTooltip = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExport
2576
2686
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Previous directories are listed in the File menu (press Alt)." })
2577
2687
  ] });
2578
2688
  const claudeDirChanges = /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: `"claudeDir": "…the chosen folder"` });
2689
+ const updateTooltip = ({ current, latest }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2690
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
2691
+ "A new version is available: ",
2692
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("b", { children: [
2693
+ "v",
2694
+ current,
2695
+ " → v",
2696
+ latest
2697
+ ] })
2698
+ ] }),
2699
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "The app will restart to install it - this takes about a minute." })
2700
+ ] });
2701
+ const npxTooltip = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2702
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "This copy runs from the npx cache, which npm clears from time to time." }),
2703
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Install it globally to keep it, with in-app updates and settings that stay active after closing." }),
2704
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "The app will restart from the global install - this takes about a minute." })
2705
+ ] });
2579
2706
  const ONE_YEAR_DAYS = 365;
2580
2707
  function humanizeDays(days) {
2581
2708
  if (days < ONE_YEAR_DAYS) return `${days}d`;
@@ -2588,6 +2715,16 @@ function StatusBar({ progress, sessionCount = 0 }) {
2588
2715
  const statusline = useSwitch({ name: "statusline", isOn: (s) => s?.installed });
2589
2716
  const retention = useSwitch({ name: "retention", isOn: (s) => s?.raised });
2590
2717
  const claudedir = useSwitch({ name: "claudedir" });
2718
+ const [update, setUpdate] = reactExports.useState(null);
2719
+ const [updating, setUpdating] = reactExports.useState(false);
2720
+ reactExports.useEffect(() => {
2721
+ window.api.checkUpdate().then(setUpdate);
2722
+ }, []);
2723
+ const updater = { status: update, busy: updating, toggle: async () => {
2724
+ setUpdating(true);
2725
+ await window.api.installUpdate();
2726
+ setUpdating(false);
2727
+ } };
2591
2728
  const proxyRunning = proxy.status?.running;
2592
2729
  const proxyDown = proxy.status?.configured && proxyRunning === false;
2593
2730
  const retentionRaised = retention.status?.raised;
@@ -2605,6 +2742,14 @@ function StatusBar({ progress, sessionCount = 0 }) {
2605
2742
  ] }),
2606
2743
  scanning && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "progress-bar", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "progress-bar-fill", style: { width: `${pct}%` } }) })
2607
2744
  ] }),
2745
+ update?.fromNpx && /* @__PURE__ */ jsxRuntimeExports.jsxs(StatusSwitch, { service: updater, button: "Install globally", className: "statusbar-update", tooltip: npxTooltip, changes: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: `npm i -g claude-discover@${update.latest}` }), changesTitle: "Runs after the app closes", children: [
2746
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownToLine, { size: 12 }),
2747
+ " Install this app"
2748
+ ] }),
2749
+ update && !update.fromNpx && /* @__PURE__ */ jsxRuntimeExports.jsxs(StatusSwitch, { service: updater, button: `Update to v${update.latest}`, className: "statusbar-update", tooltip: updateTooltip(update), changes: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: `npm i -g claude-discover@${update.latest}` }), changesTitle: "Runs after the app closes", children: [
2750
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownToLine, { size: 12 }),
2751
+ " Update available"
2752
+ ] }),
2608
2753
  /* @__PURE__ */ jsxRuntimeExports.jsxs(StatusSwitch, { service: retention, on: retentionRaised, warn: !!(retention.status && !retentionRaised), tooltip: retentionTooltip, changes: retentionChanges, children: [
2609
2754
  "Session logs ",
2610
2755
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "statusbar-state", children: retention.status ? humanizeDays(retention.status.days) : "…" })
@@ -2626,6 +2771,7 @@ function StatusBar({ progress, sessionCount = 0 }) {
2626
2771
  function App() {
2627
2772
  const [sessions, setSessions] = reactExports.useState([]);
2628
2773
  const [loading, setLoading] = reactExports.useState(true);
2774
+ const [listError, setListError] = reactExports.useState(null);
2629
2775
  const [scanProgress, setScanProgress] = reactExports.useState(null);
2630
2776
  const [granularity, setGranularity] = useLocalStorage("gantt.granularity", "day");
2631
2777
  const [anchor, setAnchor] = reactExports.useState(() => startOfPeriod(Date.now(), granularity));
@@ -2659,6 +2805,13 @@ function App() {
2659
2805
  window.api.listSessions(format(anchor, "yyyy-MM-dd"), granularity).then((s) => {
2660
2806
  if (!cancelled) {
2661
2807
  setSessions(s || []);
2808
+ setListError(null);
2809
+ setLoading(false);
2810
+ }
2811
+ }, (err) => {
2812
+ if (!cancelled) {
2813
+ setSessions([]);
2814
+ setListError(err);
2662
2815
  setLoading(false);
2663
2816
  }
2664
2817
  });
@@ -2684,7 +2837,8 @@ function App() {
2684
2837
  [sessions, selectedId]
2685
2838
  );
2686
2839
  let missing = null;
2687
- if (!selected && scanProgress?.scanning === false) {
2840
+ if (listError) missing = { text: "Couldn't load sessions", error: listError };
2841
+ else if (!selected && scanProgress?.scanning === false) {
2688
2842
  const when = granularity === "day" ? `on ${format(anchor, "MMM d, yyyy")}` : `in this ${granularity}`;
2689
2843
  if (!sessions.length) missing = { text: `No sessions recorded ${when}.` };
2690
2844
  else if (deepLink?.date === format(anchor, "yyyy-MM-dd")) missing = { text: `Session ${selectedId} has no records ${when}.`, warn: true };
@@ -2782,7 +2936,7 @@ function App() {
2782
2936
  }
2783
2937
  ) }),
2784
2938
  /* @__PURE__ */ jsxRuntimeExports.jsx(nn, { className: "resize-handle resize-handle-v" }),
2785
- /* @__PURE__ */ jsxRuntimeExports.jsx(Qt, { id: "detail", minSize: 30, className: "body-pane", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Session, { meta: selected, missing, date: format(anchor, "yyyy-MM-dd"), granularity }) })
2939
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Qt, { id: "detail", minSize: 30, className: "body-pane", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PaneErrorBoundary, { title: "Couldn't render this session", resetKeys: [selected?.filePath, anchor, granularity], children: /* @__PURE__ */ jsxRuntimeExports.jsx(Session, { meta: selected, missing, date: format(anchor, "yyyy-MM-dd"), granularity, onShowPeriodOf: (ts) => setAnchor(startOfPeriod(ts, granularity)) }) }) })
2786
2940
  ]
2787
2941
  }
2788
2942
  ) })
@@ -2816,5 +2970,8 @@ delegate("body", {
2816
2970
  }
2817
2971
  });
2818
2972
  clientExports.createRoot(document.getElementById("root")).render(
2819
- /* @__PURE__ */ jsxRuntimeExports.jsx(React.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) })
2973
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(React.StrictMode, { children: [
2974
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AppErrorBoundary, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) }),
2975
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorBanner, {})
2976
+ ] })
2820
2977
  );