claude-discover 1.9.0 → 1.9.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.
@@ -557,8 +557,11 @@
557
557
  &:hover { background: var(--bg-elev-2); }
558
558
  &.selected { background: var(--selected); }
559
559
  &.is-subagent-child { padding-left: 20px; }
560
+ &.deep-linked { background: rgba(52, 211, 153, 0.12); } /* last, so it wins over :hover and .selected */
560
561
  }
561
562
 
563
+ .deeplink-note { color: #34d399; }
564
+
562
565
  .session-row-main { flex: 1; min-width: 0; }
563
566
 
564
567
  .session-row-stats {
@@ -733,9 +736,9 @@
733
736
  }
734
737
 
735
738
  .conv-row + .conv-row { margin-top: 15px; }
739
+ /* One instruction row per API call (see groupTurns) — the gap keeps two calls' strips apart. */
736
740
  .conv-row-instruction + .conv-row { margin-top: 20px; }
737
741
  .conv-row-assistant + .conv-row-user { margin-top: 60px; }
738
- .conv-row-instruction + .conv-row-instruction,
739
742
  .conv-row-assistant + .conv-row-assistant { margin-top: 0; }
740
743
 
741
744
 
@@ -1077,6 +1080,18 @@ button.msg-summary:hover { color: var(--text); background: var(--bg-elev-2); bor
1077
1080
  margin-left: -10px;
1078
1081
  }
1079
1082
 
1083
+ /* Full-width toggle so the estimated token count can sit right-aligned, like msg-header-right. */
1084
+ .instruction-file .aux-toggle { display: flex; width: 100%; align-items: center; }
1085
+ .instruction-tokens { margin-left: auto; color: var(--text-faint); }
1086
+
1087
+ /* Summed token total under a run of instruction files: a rule over the right-aligned figure. */
1088
+ .instruction-total { display: flex; font-size: calc(11px * var(--font-scale, 1)); }
1089
+ .instruction-total .instruction-tokens {
1090
+ border-top: 1px solid var(--border);
1091
+ padding-top: 2px;
1092
+ font-weight: 600;
1093
+ }
1094
+
1080
1095
  /* Full path of an expanded memory file, first line of the body — one click selects it for copying. */
1081
1096
  .instruction-path {
1082
1097
  font-family: var(--font-mono), monospace;
@@ -1814,6 +1829,17 @@ button.msg-summary:hover { color: var(--text); background: var(--bg-elev-2); bor
1814
1829
  }
1815
1830
  }
1816
1831
 
1832
+ .session-missing {
1833
+ max-width: 520px;
1834
+ padding: 14px 18px;
1835
+ border: 1px solid rgba(251, 191, 36, 0.35);
1836
+ border-radius: 6px;
1837
+ background: rgba(251, 191, 36, 0.08);
1838
+ color: var(--warn);
1839
+ text-align: center;
1840
+ word-break: break-word;
1841
+ }
1842
+
1817
1843
  .view-body {
1818
1844
  flex: 1;
1819
1845
  display: flex;
@@ -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, H as Hourglass, G as Gauge, M as MessageSquare, C as Clock, Z as Zap, D as DollarSign, R as React, q as Markdown$1, t as rehypeHighlight, u as remarkGfm, T as Terminal, J as JsonView, v as vscodeTheme, w as tippy, x as reactDomExports, y as sn, X as Xt, z as Zt, A as tn, B as delegate, E as clientExports } from "./vendors-D6iOvAG3.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, 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, T as Terminal, J as JsonView, w as vscodeTheme, x as tippy, y as reactDomExports, z as sn, X as Xt, A as Zt, B as tn, E as parse, F as delegate, I as clientExports } from "./vendors-S1fS5xz9.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.9.0") {
38
- const [MAJOR, MINOR, PATCH] = "1.9.0".split(".");
37
+ if (version !== "1.9.2") {
38
+ const [MAJOR, MINOR, PATCH] = "1.9.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.9.0");
43
+ localStorage.setItem(VERSION_KEY, "1.9.2");
44
44
  }
45
45
  }
46
46
  function useMouseFontScale(storageKey) {
@@ -721,8 +721,8 @@ function tone(value, { warn, danger }) {
721
721
  function fmtCompact(n) {
722
722
  if (n == null) return "—";
723
723
  if (n < 0) return `-${fmtCompact(-n)}`;
724
- if (n < 1e3) return `${n}`;
725
- if (n < 1e6) return `${+(n / 1e3).toFixed(1)}k`;
724
+ if (n < 100) return `${n}`;
725
+ if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
726
726
  return `${(n / 1e6).toFixed(1)}M`;
727
727
  }
728
728
  const BAR_MAX_PX = 52;
@@ -872,10 +872,34 @@ function PeriodSummary({ sessions, dayAnchor, granularity = "day" }) {
872
872
  ] })
873
873
  ] });
874
874
  }
875
+ const countTokens = (text, model = "") => {
876
+ if (!text) return 0;
877
+ const LEGACY_CLAUDE = /-(3|4)(-|$)/;
878
+ const scale = LEGACY_CLAUDE.test(model) ? 1.13 : 1.5;
879
+ return Math.round(encode(text).length * scale);
880
+ };
881
+ 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;
875
882
  function flatten(items, instructions = []) {
876
883
  const turns = [];
877
884
  const results = {};
878
885
  for (const it of items) {
886
+ const queued = queuedPrompt(it);
887
+ if (queued) {
888
+ turns.push({
889
+ uuid: it.uuid,
890
+ role: "user",
891
+ isMeta: false,
892
+ queued: true,
893
+ ts: it.timestamp ? Date.parse(it.timestamp) : null,
894
+ model: null,
895
+ msgId: null,
896
+ usage: null,
897
+ tokenDelta: it._tokenDelta ?? null,
898
+ tokenTotal: it._tokenTotal ?? null,
899
+ blocks: [{ type: "text", text: queued }]
900
+ });
901
+ continue;
902
+ }
879
903
  if (it.type === "attachment" && it.attachment) {
880
904
  const block = { type: "attachment", attachment: it.attachment };
881
905
  const last = turns[turns.length - 1];
@@ -959,6 +983,26 @@ ${text}` }]
959
983
  return out;
960
984
  }
961
985
  const currentModel = (turns) => turns.findLast((t) => t.model && t.model !== "<synthetic>")?.model;
986
+ const SMALL_CONTEXT = /* @__PURE__ */ new Set([
987
+ "claude-3-opus",
988
+ "claude-3-sonnet",
989
+ "claude-3-haiku",
990
+ "claude-3-5-sonnet",
991
+ "claude-3-5-haiku",
992
+ "claude-3-7-sonnet",
993
+ "claude-haiku-4-5",
994
+ "claude-opus-4",
995
+ "claude-opus-4-1",
996
+ "claude-opus-4-5",
997
+ "claude-opus-4-6",
998
+ "claude-sonnet-4",
999
+ "claude-sonnet-4-5",
1000
+ "claude-sonnet-4-6"
1001
+ ]);
1002
+ function contextWindow(models = []) {
1003
+ const is200k = (model) => SMALL_CONTEXT.has(model.replace(/[-@]\d{8}.*$/, ""));
1004
+ return models.some((m) => m && !is200k(m)) ? 1e6 : 2e5;
1005
+ }
962
1006
  function normalizeContent(content) {
963
1007
  if (typeof content === "string") return [{ type: "text", text: content }];
964
1008
  if (!Array.isArray(content)) return [];
@@ -1027,7 +1071,9 @@ function groupTurns(turns) {
1027
1071
  else pending.push(t);
1028
1072
  } else if (t.role === "instruction") {
1029
1073
  flush();
1030
- groups.push({ kind: "instruction", turn: t });
1074
+ const prev = groups.at(-1);
1075
+ if (prev?.kind === "instruction" && prev.turns.at(-1).ts === t.ts) prev.turns.push(t);
1076
+ else groups.push({ kind: "instruction", turns: [t] });
1031
1077
  } else if (t.role === "user") {
1032
1078
  flush();
1033
1079
  groups.push({ kind: "user", turns: [...pending, t] });
@@ -1045,7 +1091,7 @@ function groupTurns(turns) {
1045
1091
  function cycleDurations(groups) {
1046
1092
  let prevEnd = null;
1047
1093
  return groups.map((g) => {
1048
- const tss = (g.turns ?? [g.turn]).map((t) => t.ts).filter((ts) => ts != null);
1094
+ const tss = g.turns.map((t) => t.ts).filter((ts) => ts != null);
1049
1095
  const end = tss.length ? tss[tss.length - 1] : null;
1050
1096
  const duration2 = g.kind === "assistant" && end != null && prevEnd != null ? end - prevEnd : null;
1051
1097
  if (end != null) prevEnd = end;
@@ -1055,7 +1101,7 @@ function cycleDurations(groups) {
1055
1101
  function tokenPoints(groups) {
1056
1102
  let prev = 0;
1057
1103
  return groups.map((g) => {
1058
- const turns = g.turns ?? [g.turn];
1104
+ const turns = g.turns;
1059
1105
  let total = null, ctx = null;
1060
1106
  const byMsg = /* @__PURE__ */ new Map();
1061
1107
  for (const t of turns) {
@@ -1076,7 +1122,7 @@ function tokenPoints(groups) {
1076
1122
  usage.cacheCreation5m += u.cache_creation?.ephemeral_5m_input_tokens || 0;
1077
1123
  usage.cacheCreation1h += u.cache_creation?.ephemeral_1h_input_tokens || 0;
1078
1124
  }
1079
- if (total == null || total === prev) return null;
1125
+ if (total == null || total <= prev) return null;
1080
1126
  const delta = total - prev;
1081
1127
  prev = total;
1082
1128
  if (ctx == null && g.kind !== "assistant") ctx = delta;
@@ -1090,7 +1136,7 @@ const TIPS = {
1090
1136
  messages: `Consider sessions below ${fmtCompact(THRESHOLDS.messages.warn)} messages`,
1091
1137
  time: `Consider sessions under ${THRESHOLDS.workTime.warn / 6e4}m`
1092
1138
  };
1093
- function SessionList({ sessions, selectedId, onSelect }) {
1139
+ function SessionList({ sessions, selectedId, deepLink, onSelect }) {
1094
1140
  const selectedRef = reactExports.useRef(null);
1095
1141
  const [sortBy, setSortBy] = useLocalStorage("session-list.sort", "time");
1096
1142
  const [filter, setFilter] = reactExports.useState("");
@@ -1165,12 +1211,15 @@ function SessionList({ sessions, selectedId, onSelect }) {
1165
1211
  const isSubagent = s.sessionId.startsWith("agent-");
1166
1212
  const journal = isJournal(s);
1167
1213
  const isFork = !!s.forkedFrom;
1214
+ const linked = deepLink?.id === s.sessionId;
1215
+ const linkTip = linked && `Opened from deep link
1216
+ claude-discover://session?id=${deepLink.id}${deepLink.date ? `&date=${deepLink.date}` : ""}`;
1168
1217
  const sessionName = s.customTitle && s.agentName && s.customTitle !== s.agentName ? `${s.customTitle} [${s.agentName}]` : s.customTitle || s.agentName || "";
1169
1218
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1170
1219
  "div",
1171
1220
  {
1172
1221
  ref: selectedId === s.sessionId ? selectedRef : null,
1173
- className: `session-row ${selectedId === s.sessionId ? "selected" : ""} ${isChild ? "is-subagent-child" : ""}`,
1222
+ className: `session-row ${selectedId === s.sessionId ? "selected" : ""} ${isChild ? "is-subagent-child" : ""} ${linked ? "deep-linked" : ""}`,
1174
1223
  onClick: () => onSelect(selectedId === s.sessionId ? null : s.sessionId),
1175
1224
  style: { borderLeftColor: SOURCE_COLORS[s.source] || SOURCE_COLORS.other },
1176
1225
  children: [
@@ -1214,7 +1263,8 @@ ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11
1214
1263
  s.models?.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "session-model", children: [
1215
1264
  ", ",
1216
1265
  s.models.map((m) => m.replace(/^claude-/, "")).join(", ")
1217
- ] })
1266
+ ] }),
1267
+ linked && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "deeplink-note", "data-tippy-content": linkTip, "data-tippy-maxWidth": 560, children: ", opened from deep link" })
1218
1268
  ] })
1219
1269
  ] }),
1220
1270
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-row-stats", children: [
@@ -1381,22 +1431,23 @@ function ConversationView({ items, instructions = [], expandAll = null }) {
1381
1431
  const durations = reactExports.useMemo(() => cycleDurations(groups), [groups]);
1382
1432
  const findOpen = useFindActive();
1383
1433
  const [scale, zoomRef] = useMouseFontScale("font-scale.conversation");
1434
+ const model = currentModel(turns);
1435
+ const ctxLimit = contextWindow([model]);
1384
1436
  const hasTimeline = points.some(Boolean);
1385
1437
  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: [
1386
- /* @__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] }) : g.kind === "assistant" ? /* @__PURE__ */ jsxRuntimeExports.jsx(AssistantCard, { turns: g.turns, point: points[i], duration: durations[i], showAuthor: groups[i - 1]?.kind !== "assistant" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionFile, { it: g.turn.blocks[0].it, model: currentModel(turns) }) }),
1438
+ /* @__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 }) }),
1387
1439
  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 } })
1388
- ] }, g.kind === "instruction" ? g.turn.uuid : g.turns[0].uuid)) }) });
1440
+ ] }, g.turns[0].uuid)) }) });
1389
1441
  }
1390
1442
  const ClaudeIcon = () => /* @__PURE__ */ jsxRuntimeExports.jsx("img", { className: "msg-icon claude-icon", src: claudeIcon, alt: "" });
1391
- const CTX_LIMIT = THRESHOLDS.context.danger;
1392
1443
  function TokenPoint({ point: p }) {
1393
1444
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `token-point token-point-${p.role}${p.delta < 0 ? " negative" : ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "token-point-dot" }) });
1394
1445
  }
1395
- function TokenStats({ point }) {
1446
+ function TokenStats({ point, ctxLimit }) {
1396
1447
  if (!point) return null;
1397
1448
  const rows = [];
1398
1449
  if (point.ctx != null) {
1399
- const width = Math.min(point.ctx / CTX_LIMIT, 1) * 100;
1450
+ const width = Math.min(point.ctx / ctxLimit, 1) * 100;
1400
1451
  rows.push(`<div class="token-tooltip-ctx">Context <span class="token-point-ctx-bar"><span style="width:${width}%"></span></span> <span class="token-tooltip-ctx-val">${fmtCompact(point.ctx)}</span></div>`);
1401
1452
  }
1402
1453
  const u = point.usage;
@@ -1414,7 +1465,7 @@ function TokenStats({ point }) {
1414
1465
  rows.push(`<div class="token-tooltip-total"><span>Accumulated tokens</span><span>${fmtCompact(point.total)}</span></div>`);
1415
1466
  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-tokens", "data-tippy-html": `<div class="token-tooltip">${rows.join("")}</div>`, children: fmtCompact(point.total) });
1416
1467
  }
1417
- function UserRow({ turns, point }) {
1468
+ function UserRow({ turns, point, ctxLimit }) {
1418
1469
  const [open, setOpen] = useCollapsed(false);
1419
1470
  const msg = turns.find((t) => !isContextTurn(t));
1420
1471
  const ctxItems = turns.filter(isContextTurn).flatMap((t) => t.blocks);
@@ -1423,6 +1474,7 @@ function UserRow({ turns, point }) {
1423
1474
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "msg-header", children: [
1424
1475
  /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "msg-icon" }),
1425
1476
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-author", children: "You" }),
1477
+ msg.queued && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-summary", children: "queued during the previous turn" }),
1426
1478
  ctxItems.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "msg-summary", onClick: () => setOpen((v) => !v), children: [
1427
1479
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "aux-chevron", children: open ? "▾" : "▸" }),
1428
1480
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
@@ -1433,7 +1485,7 @@ function UserRow({ turns, point }) {
1433
1485
  ] })
1434
1486
  ] }),
1435
1487
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "msg-header-right", children: [
1436
- /* @__PURE__ */ jsxRuntimeExports.jsx(TokenStats, { point }),
1488
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TokenStats, { point, ctxLimit }),
1437
1489
  msg.ts != null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-time", children: format(msg.ts, "HH:mm:ss") })
1438
1490
  ] })
1439
1491
  ] }),
@@ -1441,7 +1493,7 @@ function UserRow({ turns, point }) {
1441
1493
  /* @__PURE__ */ jsxRuntimeExports.jsx(TurnRow, { turn: msg })
1442
1494
  ] });
1443
1495
  }
1444
- function AssistantCard({ turns, point, duration: duration2, showAuthor = true }) {
1496
+ function AssistantCard({ turns, point, ctxLimit, duration: duration2, showAuthor = true }) {
1445
1497
  const [open, setOpen] = useCollapsed(false);
1446
1498
  const toolBlocks = turns.flatMap((t) => t.blocks.filter((b) => b.type === "tool_use"));
1447
1499
  const errorCount = toolBlocks.filter((b) => b.result?.is_error).length;
@@ -1475,7 +1527,7 @@ function AssistantCard({ turns, point, duration: duration2, showAuthor = true })
1475
1527
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: summary })
1476
1528
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-summary", children: summary })),
1477
1529
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "msg-header-right", children: [
1478
- /* @__PURE__ */ jsxRuntimeExports.jsx(TokenStats, { point }),
1530
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TokenStats, { point, ctxLimit }),
1479
1531
  end != null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-time", title: duration2 > 0 ? `Response time: ${fmtDuration(duration2)}` : null, children: format(end, "HH:mm:ss") })
1480
1532
  ] })
1481
1533
  ] }),
@@ -1558,11 +1610,43 @@ const ATTACHMENT_RENDERERS = {
1558
1610
  title: `${a.addedNames?.length || 0} deferred tools ${a.removedNames?.length ? `(removed: ${a.removedNames.length})` : ""}`,
1559
1611
  body: safeJson({ added: a.addedNames, removed: a.removedNames }),
1560
1612
  defaultOpen: false
1613
+ }),
1614
+ // The one nested shape the fallback below can't flatten: files → diagnostics → range.
1615
+ diagnostics: (a) => {
1616
+ 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})` : ""}`));
1617
+ return { title: `Diagnostics: ${lines.length} in ${a.files?.length || 0} file${a.files?.length === 1 ? "" : "s"}`, body: lines.join("\n") };
1618
+ },
1619
+ // addedLines carry each agent's one-line description; addedTypes is just their names.
1620
+ agent_listing_delta: (a) => ({
1621
+ title: `${a.addedTypes?.length || 0} agent types${a.removedTypes?.length ? ` (removed: ${a.removedTypes.length})` : ""}`,
1622
+ body: (a.addedLines || a.addedTypes || []).join("\n"),
1623
+ defaultOpen: false
1561
1624
  })
1562
1625
  };
1626
+ function genericAttachment(att) {
1627
+ const short = [], long = [];
1628
+ const collect = (v, key) => {
1629
+ if (typeof v === "string") {
1630
+ if (v.trim()) (v.length > 100 ? long : short).push([key, v]);
1631
+ } else if (Array.isArray(v)) {
1632
+ for (const x of v) collect(x, key);
1633
+ } else if (v && typeof v === "object") {
1634
+ for (const [k, x] of Object.entries(v)) collect(x, k);
1635
+ }
1636
+ };
1637
+ for (const [k, v] of Object.entries(att || {})) {
1638
+ if (k !== "type") collect(v, k);
1639
+ }
1640
+ const detail = short.find(([k]) => /path|file|name|dir|uri|url|date/i.test(k))?.[1];
1641
+ const label = (att?.type || "attachment").replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
1642
+ return {
1643
+ title: detail ? `${label}: ${detail}` : label,
1644
+ body: long.length ? long.map(([, v]) => v).join("\n\n") : short.length > 1 ? safeJson(att) : null
1645
+ };
1646
+ }
1563
1647
  function Attachment({ att }) {
1564
1648
  const render = ATTACHMENT_RENDERERS[att?.type];
1565
- const { title, body, defaultOpen = false } = render ? render(att) : { title: `Attachment: ${att?.type || "unknown"}`, body: safeJson(att) };
1649
+ const { title, body, defaultOpen = false } = render ? render(att) : genericAttachment(att);
1566
1650
  if (body == null) return /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { title, className: "attachment" });
1567
1651
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title, className: "attachment", defaultOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: body }) });
1568
1652
  }
@@ -1592,12 +1676,26 @@ function safeJson(x) {
1592
1676
  function JsonBlock({ value }) {
1593
1677
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: "```json\n" + safeJson(value) + "\n```" });
1594
1678
  }
1595
- function InstructionFile({ it, model }) {
1679
+ function InstructionRun({ turns, model }) {
1680
+ const tokens2 = turns.map((t) => countTokens(t.blocks[0].it.content, t.blocks[0].it.model ?? model));
1681
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1682
+ turns.map((t, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionFile, { it: t.blocks[0].it, model, tokens: tokens2[i] }, t.uuid)),
1683
+ turns.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aux instruction-total", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "instruction-tokens", children: [
1684
+ "~ ",
1685
+ fmtCompact(tokens2.reduce((a, b) => a + b, 0))
1686
+ ] }) })
1687
+ ] });
1688
+ }
1689
+ function InstructionFile({ it, model, tokens: tokens2 }) {
1596
1690
  const [open, setOpen] = useCollapsed(false);
1597
1691
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "aux instruction-file", children: [
1598
1692
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "aux-toggle", onClick: () => setOpen((v) => !v), children: [
1599
1693
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "aux-chevron", children: open ? "▾" : "▸" }),
1600
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: instructionTitle(it, model) })
1694
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: instructionTitle(it, model) }),
1695
+ tokens2 > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "instruction-tokens", title: "Approx. tokens", children: [
1696
+ "~ ",
1697
+ fmtCompact(tokens2)
1698
+ ] })
1601
1699
  ] }),
1602
1700
  open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "aux-body", children: [
1603
1701
  it.name && it.name !== it.file_path && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "instruction-path", children: it.file_path }),
@@ -1831,7 +1929,6 @@ function RequestsView({ sessionId, date, granularity = "day", fileSize = 0, expa
1831
1929
  ] })
1832
1930
  ] });
1833
1931
  }
1834
- const CONTEXT_WINDOW$1 = THRESHOLDS.context.danger;
1835
1932
  let MAX_LINES = 10;
1836
1933
  let MAX_LINE_CHARS = 200;
1837
1934
  let MODEL = null;
@@ -1880,7 +1977,7 @@ function renderTurn(t) {
1880
1977
  if (t.role === "instruction") return `**Instructions loaded:**
1881
1978
 
1882
1979
  ${t.blocks.map(renderBlock).join("\n\n")}`;
1883
- const role = t.role === "user" ? "User" : `Assistant`;
1980
+ const role = t.role === "user" ? t.queued ? "User (queued)" : "User" : `Assistant`;
1884
1981
  const tokens2 = t.tokenTotal > 0 && t.tokenDelta != null ? ` (${t.tokenDelta >= 0 ? "+" : ""}${fmtCompact(t.tokenDelta)} / ${fmtCompact(t.tokenTotal)} tokens)` : "";
1885
1982
  return `**${role}${tokens2}:**
1886
1983
 
@@ -1894,7 +1991,8 @@ function markdownSession(meta, items, truncated, instructions = []) {
1894
1991
  const t = meta.tokens;
1895
1992
  const stu = meta.serverToolUse;
1896
1993
  const wallDuration = meta.lastActivityAt - meta.startedAt;
1897
- const contextPct = Math.round(meta.lastContextTokens / CONTEXT_WINDOW$1 * 100);
1994
+ const ctxLimit = contextWindow(meta.models);
1995
+ const contextPct = Math.round(meta.lastContextTokens / ctxLimit * 100);
1898
1996
  const cacheHitPct = (meta.cacheHitRatio * 100).toFixed(0);
1899
1997
  const turns = items ? flatten(items, instructions) : null;
1900
1998
  MODEL = turns ? currentModel(turns) : null;
@@ -1910,7 +2008,7 @@ ${transcript}`;
1910
2008
  - Estimated cost: ${fmtUSD(meta.cost)}
1911
2009
  - Total tokens: ${fmtCompact(meta.totalTokens)}
1912
2010
  - Working time: ${fmtDuration(meta.activeMs)}
1913
- - Context size: ${fmtCompact(meta.lastContextTokens)} / ${fmtCompact(CONTEXT_WINDOW$1)} (${contextPct}%)${meta.savings.using5mCache > 0 ? `
2011
+ - Context size: ${fmtCompact(meta.lastContextTokens)} / ${fmtCompact(ctxLimit)} (${contextPct}%)${meta.savings.using5mCache > 0 ? `
1914
2012
 
1915
2013
  ## Potential savings
1916
2014
  - with 5m cache: ${meta.cost > 0 ? `(${(meta.savings.using5mCache / meta.cost * 100).toFixed(0)}%) ` : ""}−${fmtUSD(meta.savings.using5mCache)}` : ""}
@@ -2078,7 +2176,6 @@ ${body}`;
2078
2176
  ] })
2079
2177
  ] });
2080
2178
  }
2081
- const CONTEXT_WINDOW = THRESHOLDS.context.danger;
2082
2179
  function SessionSummary({ meta, items, instructions, agent, onOpenAgent, granularity = "day" }) {
2083
2180
  const timeFormat = granularity === "day" ? "pp" : "MMM d, pp";
2084
2181
  const onAnalyze = () => {
@@ -2097,7 +2194,8 @@ ${body}`);
2097
2194
  const stu = meta.serverToolUse;
2098
2195
  const hasServerTools = stu.webSearch > 0 || stu.webFetch > 0;
2099
2196
  const modelLabel = meta.models.join(", ") || "—";
2100
- const contextRatio = meta.lastContextTokens / CONTEXT_WINDOW;
2197
+ const ctxLimit = contextWindow(meta.models);
2198
+ const ctxTone = tone(meta.lastContextTokens, THRESHOLDS.context);
2101
2199
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-summary", children: [
2102
2200
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "summary-ai", children: [
2103
2201
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "summary-ai-row", children: [
@@ -2132,7 +2230,7 @@ ${body}`);
2132
2230
  ] }) }),
2133
2231
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Total tokens", value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: tone(totalTokens2, THRESHOLDS.tokens), children: fmtCompact(totalTokens2) }) }),
2134
2232
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Working time", value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: tone(activeDuration, THRESHOLDS.workTime), children: fmtDuration(activeDuration) }) }),
2135
- /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Context size", value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: tone(meta.lastContextTokens, THRESHOLDS.context), children: `${fmtCompact(meta.lastContextTokens)} / ${fmtCompact(CONTEXT_WINDOW)}` }), below: /* @__PURE__ */ jsxRuntimeExports.jsx(Bar, { ratio: contextRatio, tone: tone(meta.lastContextTokens, THRESHOLDS.context) }) })
2233
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Context size", value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: ctxTone, children: `${fmtCompact(meta.lastContextTokens)} / ${fmtCompact(ctxLimit)}` }), below: /* @__PURE__ */ jsxRuntimeExports.jsx(Bar, { ratio: meta.lastContextTokens / ctxLimit, tone: ctxTone }) })
2136
2234
  ] }),
2137
2235
  meta.savings.using5mCache > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { title: "Potential savings", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2138
2236
  Field,
@@ -2261,7 +2359,7 @@ const TABS = [
2261
2359
  { key: "jsonl", label: "Raw Logs" },
2262
2360
  { key: "requests", label: "API Requests" }
2263
2361
  ];
2264
- function Session({ meta, date, granularity = "day" }) {
2362
+ function Session({ meta, missing, date, granularity = "day" }) {
2265
2363
  const [items, setItems] = reactExports.useState(null);
2266
2364
  const [instructions, setInstructions] = reactExports.useState([]);
2267
2365
  const [mode, setMode] = useLocalStorage("session.view-mode", "conversation");
@@ -2287,7 +2385,10 @@ function Session({ meta, date, granularity = "day" }) {
2287
2385
  };
2288
2386
  }, [sessionId, date, granularity, fileSize]);
2289
2387
  if (!meta) {
2290
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view empty", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Select a session to inspect." }) });
2388
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view empty", children: missing?.warn ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-missing", children: [
2389
+ "⚠ ",
2390
+ missing.text
2391
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: missing?.text || "Select a session to inspect." }) });
2291
2392
  }
2292
2393
  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: [
2293
2394
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "view-tab-pane-main", children: [
@@ -2471,6 +2572,7 @@ function App() {
2471
2572
  const [granularity, setGranularity] = useLocalStorage("gantt.granularity", "day");
2472
2573
  const [anchor, setAnchor] = reactExports.useState(() => startOfPeriod(Date.now(), granularity));
2473
2574
  const [selectedId, setSelectedId] = reactExports.useState(null);
2575
+ const [deepLink, setDeepLink] = reactExports.useState(null);
2474
2576
  const [sourceFilter, setSourceFilter] = reactExports.useState(null);
2475
2577
  const [projectFilter, setProjectFilter] = reactExports.useState(null);
2476
2578
  const { defaultLayout, onLayoutChanged } = sn({ id: "app.body", panelIds: ["list", "detail"], storage: localStorage });
@@ -2493,7 +2595,7 @@ function App() {
2493
2595
  setLoading(false);
2494
2596
  }
2495
2597
  });
2496
- }, 120);
2598
+ }, loading ? 0 : 120);
2497
2599
  const off = window.api.onSessionsUpdate((s) => {
2498
2600
  if (!cancelled) setSessions(s || []);
2499
2601
  });
@@ -2514,10 +2616,31 @@ function App() {
2514
2616
  () => sessions.find((s) => s.sessionId === selectedId) || null,
2515
2617
  [sessions, selectedId]
2516
2618
  );
2517
- const selectSession = reactExports.useCallback((id) => {
2619
+ let missing = null;
2620
+ if (!selected && scanProgress?.scanning === false) {
2621
+ const when = granularity === "day" ? `on ${format(anchor, "MMM d, yyyy")}` : `in this ${granularity}`;
2622
+ if (!sessions.length) missing = { text: `No sessions recorded ${when}.` };
2623
+ else if (deepLink?.date === format(anchor, "yyyy-MM-dd")) missing = { text: `Session ${selectedId} has no records ${when}.`, warn: true };
2624
+ }
2625
+ const selectSession = reactExports.useCallback((id, link = null) => {
2518
2626
  closeFind();
2519
2627
  setSelectedId(id);
2628
+ setDeepLink(link);
2520
2629
  }, []);
2630
+ reactExports.useEffect(() => {
2631
+ const open = ({ id, date }) => {
2632
+ const day = date && parse(date, "yyyy-MM-dd", /* @__PURE__ */ new Date());
2633
+ if (day && !isNaN(day)) {
2634
+ setGranularity("day");
2635
+ setAnchor(startOfPeriod(day, "day"));
2636
+ setSourceFilter(null);
2637
+ setProjectFilter(null);
2638
+ }
2639
+ selectSession(id, { id, date });
2640
+ };
2641
+ window.api.takeDeepLink().then((target) => target && open(target));
2642
+ return window.api.onDeepLink(open);
2643
+ }, [selectSession, setGranularity]);
2521
2644
  const shiftPeriod = reactExports.useCallback((delta) => {
2522
2645
  setProjectFilter(null);
2523
2646
  setAnchor((a) => startOfPeriod(addPeriod(a, granularity, delta), granularity));
@@ -2587,11 +2710,12 @@ function App() {
2587
2710
  {
2588
2711
  sessions: dayItems.past,
2589
2712
  selectedId,
2713
+ deepLink,
2590
2714
  onSelect: selectSession
2591
2715
  }
2592
2716
  ) }),
2593
2717
  /* @__PURE__ */ jsxRuntimeExports.jsx(tn, { className: "resize-handle resize-handle-v" }),
2594
- /* @__PURE__ */ jsxRuntimeExports.jsx(Zt, { id: "detail", minSize: 30, className: "body-pane", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Session, { meta: selected, date: format(anchor, "yyyy-MM-dd"), granularity }) })
2718
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zt, { id: "detail", minSize: 30, className: "body-pane", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Session, { meta: selected, missing, date: format(anchor, "yyyy-MM-dd"), granularity }) })
2595
2719
  ]
2596
2720
  }
2597
2721
  ) })