claude-discover 1.9.4 → 1.9.5

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.
@@ -14,8 +14,8 @@ electron.contextBridge.exposeInMainWorld("api", {
14
14
  deactivateSwitch: (name) => electron.ipcRenderer.invoke("switch:deactivate", name),
15
15
  setSwitchKeepActive: (name, value) => electron.ipcRenderer.invoke("switch:keep-active", name, value),
16
16
  listSessions: (date, granularity = "day") => electron.ipcRenderer.invoke("sessions:list", date, granularity),
17
- readSession: (id, date = null, granularity = "day") => electron.ipcRenderer.invoke("sessions:read", id, date, granularity),
18
- readRequests: (id, date = null, granularity = "day") => electron.ipcRenderer.invoke("sessions:read-requests", id, date, granularity),
17
+ readSession: (filePath, date = null, granularity = "day") => electron.ipcRenderer.invoke("sessions:read", filePath, date, granularity),
18
+ readRequests: (filePath, date = null, granularity = "day") => electron.ipcRenderer.invoke("sessions:read-requests", filePath, date, granularity),
19
19
  onSessionsUpdate: subscribe("sessions:update"),
20
20
  onScanProgress: subscribe("sessions:scan-progress"),
21
21
  getWorkHours: () => electron.ipcRenderer.invoke("work-hours:get"),
@@ -9,7 +9,7 @@ const SOURCE_COLORS = {
9
9
  other: "#94a3b8"
10
10
  };
11
11
  const SOURCE_ORDER = ["cli", "claude-desktop", "sdk-cli", "sdk-ts", "sdk-py", "scheduled", "other"];
12
- const SOURCE_LABELS = {
12
+ const SOURCE_LABELS$1 = {
13
13
  scheduled: "Scheduled",
14
14
  cli: "CLI",
15
15
  "claude-desktop": "Desktop",
@@ -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.4") {
38
- const [MAJOR, MINOR, PATCH] = "1.9.4".split(".");
37
+ if (version !== "1.9.5") {
38
+ const [MAJOR, MINOR, PATCH] = "1.9.5".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.4");
43
+ localStorage.setItem(VERSION_KEY, "1.9.5");
44
44
  }
45
45
  }
46
46
  function useMouseFontScale(storageKey) {
@@ -104,12 +104,12 @@ function useMouse(ref, handlers) {
104
104
  }
105
105
  const isJournal = (s) => s.sessionId.endsWith("-journal");
106
106
  function subagentsByParent(sessions) {
107
- const ids = new Set(sessions.map((s) => s.sessionId));
107
+ const parents = new Set(sessions.map((s) => s.filePath));
108
108
  const byParent = /* @__PURE__ */ new Map();
109
109
  for (const s of sessions) {
110
- if (!ids.has(s.parentSessionId)) continue;
111
- let arr = byParent.get(s.parentSessionId);
112
- if (!arr) byParent.set(s.parentSessionId, arr = []);
110
+ if (!parents.has(s.parentFilePath)) continue;
111
+ let arr = byParent.get(s.parentFilePath);
112
+ if (!arr) byParent.set(s.parentFilePath, arr = []);
113
113
  arr.push(s);
114
114
  }
115
115
  return byParent;
@@ -365,7 +365,7 @@ function GanttChart({
365
365
  }, []);
366
366
  const { groups, totalHeight } = reactExports.useMemo(() => {
367
367
  const clusters = subagentClusters(sessions, SUBAGENT_SPLIT_GAP).filter((c) => c.length >= 2);
368
- const collapsed = new Set(clusters.flat().map((s) => s.sessionId));
368
+ const collapsed = new Set(clusters.flat().map((s) => s.filePath));
369
369
  const byKey = /* @__PURE__ */ new Map();
370
370
  const addItem = (s, item, cost) => {
371
371
  const key = s.project || "(no project)";
@@ -375,9 +375,9 @@ function GanttChart({
375
375
  group.cost += cost || 0;
376
376
  };
377
377
  for (const s of sessions) {
378
- if (collapsed.has(s.sessionId)) continue;
378
+ if (collapsed.has(s.filePath)) continue;
379
379
  addItem(s, {
380
- id: s.sessionId,
380
+ id: s.filePath,
381
381
  label: s.summary || s.firstUserPrompt || s.firstUserCommand || s.sessionId,
382
382
  start: s.startedAt,
383
383
  end: endOf(s),
@@ -388,7 +388,7 @@ function GanttChart({
388
388
  for (const subs of clusters) {
389
389
  const periods = subs.map((c) => ({ start: c.startedAt, end: endOf(c) }));
390
390
  addItem(subs[0], {
391
- id: `${subs[0].sessionId}::subagents`,
391
+ id: `${subs[0].filePath}::subagents`,
392
392
  start: periods[0].start,
393
393
  end: Math.max(...periods.map((p) => p.end)),
394
394
  source: subs[0].source,
@@ -498,7 +498,7 @@ function GanttChart({
498
498
  g.placed.map(({ item, lane }) => {
499
499
  const y = g.yOffset + lane * (bar.height + bar.row_gap);
500
500
  const color = SOURCE_COLORS[item.source] || SOURCE_COLORS.other;
501
- const isSelected = item.subs ? item.subs.some((c) => c.sessionId === selectedId) : item.id === selectedId;
501
+ const isSelected = item.subs ? item.subs.some((c) => c.filePath === selectedId) : item.id === selectedId;
502
502
  const periods = item.activityPeriods?.length ? item.activityPeriods : [{ start: item.start, end: item.end }];
503
503
  const rawX1 = xFor(item.start);
504
504
  const rawX2 = xFor(item.end);
@@ -509,7 +509,7 @@ function GanttChart({
509
509
  "g",
510
510
  {
511
511
  className: `bar ${isSelected ? "selected" : ""}`,
512
- onClick: () => onSelect(item.subs ? item.subs.at(-1).sessionId : item.id),
512
+ onClick: () => onSelect(item.subs ? item.subs.at(-1).filePath : item.id),
513
513
  children: [
514
514
  /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w, height: bar.height, rx: bar.radius, fill: color, className: "bar-fill" }),
515
515
  periods.slice(0, -1).map((p, i) => {
@@ -521,7 +521,7 @@ function GanttChart({
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
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
523
  isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width: w, height: bar.height, rx: bar.radius, className: "bar-outline" }),
524
- /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: `${item.subs ? `${item.subCount} subagents` : `${SOURCE_LABELS[item.source] || item.source} · ${item.label}`}
524
+ /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: `${item.subs ? `${item.subCount} subagents` : `${SOURCE_LABELS$1[item.source] || item.source} · ${item.label}`}
525
525
  ${g.key}
526
526
  ${new Date(item.start).toLocaleString()} → ${new Date(item.end).toLocaleString()}` })
527
527
  ]
@@ -616,7 +616,7 @@ function Toolbar({
616
616
  const dim = sourceFilter && !active2;
617
617
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: `legend-chip ${active2 ? "active" : ""} ${dim ? "dim" : ""}`, onClick: () => onToggleSourceFilter?.(k), children: [
618
618
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "swatch", style: { background: SOURCE_COLORS[k] || SOURCE_COLORS.other } }),
619
- SOURCE_LABELS[k] || k
619
+ SOURCE_LABELS$1[k] || k
620
620
  ] }, k);
621
621
  }) }),
622
622
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "gantt-toolbar-nav", children: [
@@ -701,6 +701,9 @@ function fmtNum(n) {
701
701
  if (n == null) return "—";
702
702
  return n.toLocaleString();
703
703
  }
704
+ function fmtToolCalls({ toolCalls, skillCalls }) {
705
+ return skillCalls ? `${fmtNum(toolCalls)}, ${fmtNum(skillCalls)} skill${skillCalls === 1 ? "" : "s"}` : fmtNum(toolCalls);
706
+ }
704
707
  function fmtUSD(n) {
705
708
  if (n == null) return "—";
706
709
  return `$${n.toFixed(2)}`;
@@ -882,6 +885,7 @@ const queuedPrompt = (it) => it.type === "attachment" && it.attachment?.type ===
882
885
  function flatten(items, instructions = []) {
883
886
  const turns = [];
884
887
  const results = {};
888
+ const companions = {};
885
889
  for (const it of items) {
886
890
  const queued = queuedPrompt(it);
887
891
  if (queued) {
@@ -901,6 +905,7 @@ function flatten(items, instructions = []) {
901
905
  continue;
902
906
  }
903
907
  if (it.type === "attachment" && it.attachment) {
908
+ if (it.attachment.type === "structured_output" && it.attachment.toolUseID) continue;
904
909
  const block = { type: "attachment", attachment: it.attachment };
905
910
  const last = turns[turns.length - 1];
906
911
  if (last && last.isMeta && last.blocks.every((b) => b.type === "attachment")) {
@@ -939,9 +944,19 @@ ${text}` }]
939
944
  });
940
945
  continue;
941
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
+ }
942
957
  if (it.type !== "user" && it.type !== "assistant") continue;
943
958
  const msg = it.message || {};
944
- const blocks = normalizeContent(msg.content).filter((b) => !(b.type === "thinking" && !b.thinking?.trim()));
959
+ const blocks = collapseRedactedThinking(normalizeContent(msg.content));
945
960
  if (blocks.length === 0) continue;
946
961
  for (const b of blocks) {
947
962
  if (b.type === "tool_result" && b.tool_use_id) results[b.tool_use_id] = b;
@@ -961,21 +976,21 @@ ${text}` }]
961
976
  });
962
977
  }
963
978
  for (const t of turns) {
964
- t.blocks = t.blocks.map((b) => b.type === "tool_use" ? { ...b, result: results[b.id] } : b).filter((b) => !(b.type === "tool_result" && results[b.tool_use_id]));
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]));
965
980
  }
966
981
  const out = turns.filter((t) => t.blocks.length > 0);
967
- for (const it of instructions) {
982
+ if (instructions.length) {
983
+ const first = instructions.find((it) => it.timestamp);
968
984
  const turn = {
969
- uuid: `instr-${it.hash ?? it.file_path}-${it.timestamp}`,
970
- // system prompts share a file_path, their hash disambiguates
985
+ uuid: "instructions",
971
986
  role: "instruction",
972
987
  isMeta: true,
973
- ts: it.timestamp ? Date.parse(it.timestamp) - 500 : null,
988
+ ts: first ? Date.parse(first.timestamp) - 500 : null,
974
989
  model: null,
975
990
  usage: null,
976
991
  tokenDelta: null,
977
992
  tokenTotal: null,
978
- blocks: [{ type: "instruction", it }]
993
+ blocks: instructions.map((it) => ({ type: "instruction", it }))
979
994
  };
980
995
  const i = out.findIndex((t) => t.ts != null && t.ts > turn.ts);
981
996
  i === -1 ? out.push(turn) : out.splice(i, 0, turn);
@@ -1003,6 +1018,21 @@ function contextWindow(models = []) {
1003
1018
  const is200k = (model) => SMALL_CONTEXT.has(model.replace(/[-@]\d{8}.*$/, ""));
1004
1019
  return models.some((m) => m && !is200k(m)) ? 1e6 : 2e5;
1005
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;
1006
1036
  function normalizeContent(content) {
1007
1037
  if (typeof content === "string") return [{ type: "text", text: content }];
1008
1038
  if (!Array.isArray(content)) return [];
@@ -1050,6 +1080,10 @@ function parseCommand(text) {
1050
1080
  caveat: field("local-command-caveat")
1051
1081
  };
1052
1082
  }
1083
+ const SOURCE_LABELS = { system: "system prompt", tools: "tools", message: "user message" };
1084
+ function groupInstructions(strips) {
1085
+ return Object.entries(SOURCE_LABELS).map(([source, label]) => [label, strips.filter((it) => it.source === source)]).filter(([, list]) => list.length);
1086
+ }
1053
1087
  function instructionTitle(it, model) {
1054
1088
  const parts = [it.memory_type, it.model && it.model !== model && it.model].filter(Boolean).join(", ");
1055
1089
  return parts ? `${it.name ?? it.file_path} (${parts})` : it.name ?? it.file_path;
@@ -1071,9 +1105,7 @@ function groupTurns(turns) {
1071
1105
  else pending.push(t);
1072
1106
  } else if (t.role === "instruction") {
1073
1107
  flush();
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] });
1108
+ groups.push({ kind: "instruction", turns: [t] });
1077
1109
  } else if (t.role === "user") {
1078
1110
  flush();
1079
1111
  groups.push({ kind: "user", turns: [...pending, t] });
@@ -1148,11 +1180,11 @@ function SessionList({ sessions, selectedId, deepLink, onSelect }) {
1148
1180
  const t = setInterval(() => tick((n) => n + 1), 6e4);
1149
1181
  return () => clearInterval(t);
1150
1182
  }, []);
1151
- const cmd = (text) => {
1152
- const c = parseCommand(text);
1153
- return c ? `${c.name || c.message} ${c.args}` : "";
1183
+ const labelCommand = (s) => !(s.aiTitle || s.summary || s.firstUserPrompt) && parseCommand(s.firstUserCommand);
1184
+ const sessionLabel = (s) => {
1185
+ const c = labelCommand(s);
1186
+ return c ? `${c.name || c.message} ${c.args}` : s.aiTitle || s.summary || s.firstUserPrompt || s.sessionId || "";
1154
1187
  };
1155
- const sessionLabel = (s) => s.aiTitle || s.summary || s.firstUserPrompt || cmd(s.firstUserCommand) || s.sessionId || "";
1156
1188
  const q = filter.trim().toLowerCase();
1157
1189
  const filtered = q ? sessions.filter((s) => [
1158
1190
  s.customTitle,
@@ -1170,9 +1202,9 @@ function SessionList({ sessions, selectedId, deepLink, onSelect }) {
1170
1202
  const sorted = sortBy === "cost" ? [...filtered].sort((a, b) => (b.cost || 0) - (a.cost || 0)) : sortBy === "tokens" ? [...filtered].sort((a, b) => (b.totalTokens || 0) - (a.totalTokens || 0)) : sortBy === "name" ? [...filtered].sort((a, b) => sessionLabel(a).toLowerCase().localeCompare(sessionLabel(b).toLowerCase())) : filtered;
1171
1203
  const grouped = reactExports.useMemo(() => {
1172
1204
  const byParent = subagentsByParent(sorted);
1173
- return sorted.flatMap((s) => byParent.has(s.parentSessionId) ? [] : [
1205
+ return sorted.flatMap((s) => byParent.has(s.parentFilePath) ? [] : [
1174
1206
  { session: s, isChild: false },
1175
- ...(byParent.get(s.sessionId) ?? []).map((c) => ({ session: c, isChild: true }))
1207
+ ...(byParent.get(s.filePath) ?? []).map((c) => ({ session: c, isChild: true }))
1176
1208
  ]);
1177
1209
  }, [sorted]);
1178
1210
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-list", children: [
@@ -1211,16 +1243,17 @@ function SessionList({ sessions, selectedId, deepLink, onSelect }) {
1211
1243
  const isSubagent = s.sessionId.startsWith("agent-");
1212
1244
  const journal = isJournal(s);
1213
1245
  const isFork = !!s.forkedFrom;
1214
- const linked = deepLink?.id === s.sessionId;
1246
+ const linked = !!deepLink && s.filePath === selectedId;
1215
1247
  const linkTip = linked && `Opened from deep link
1216
1248
  claude-discover://session?id=${deepLink.id}${deepLink.date ? `&date=${deepLink.date}` : ""}`;
1249
+ const command = labelCommand(s);
1217
1250
  const sessionName = s.customTitle && s.agentName && s.customTitle !== s.agentName ? `${s.customTitle} [${s.agentName}]` : s.customTitle || s.agentName || "";
1218
1251
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1219
1252
  "div",
1220
1253
  {
1221
- ref: selectedId === s.sessionId ? selectedRef : null,
1222
- className: `session-row ${selectedId === s.sessionId ? "selected" : ""} ${isChild ? "is-subagent-child" : ""} ${linked ? "deep-linked" : ""}`,
1223
- onClick: () => onSelect(selectedId === s.sessionId ? null : s.sessionId),
1254
+ ref: selectedId === s.filePath ? selectedRef : null,
1255
+ className: `session-row ${selectedId === s.filePath ? "selected" : ""} ${isChild ? "is-subagent-child" : ""} ${linked ? "deep-linked" : ""}`,
1256
+ onClick: () => onSelect(selectedId === s.filePath ? null : s.filePath),
1224
1257
  style: { borderLeftColor: SOURCE_COLORS[s.source] || SOURCE_COLORS.other },
1225
1258
  children: [
1226
1259
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-row-main", children: [
@@ -1256,7 +1289,11 @@ ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11
1256
1289
  ] }),
1257
1290
  isFork && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "fork-tag", title: `Forked from session ${s.forkedFrom.sessionId}`, children: "↳" }),
1258
1291
  sessionName && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-name", children: sessionName }),
1259
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-label-text", children: sessionLabel(s) })
1292
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-label-text", children: command ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1293
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-command", children: command.name || command.message }),
1294
+ " ",
1295
+ command.args
1296
+ ] }) : sessionLabel(s) })
1260
1297
  ] }),
1261
1298
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-time-bottom", children: [
1262
1299
  s.project && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-project", title: s.project, children: s.projectShort }),
@@ -1271,7 +1308,7 @@ ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11
1271
1308
  ] })
1272
1309
  ]
1273
1310
  },
1274
- s.sessionId
1311
+ s.filePath
1275
1312
  );
1276
1313
  })
1277
1314
  ] })
@@ -1342,7 +1379,9 @@ function fenceTags(text) {
1342
1379
  if (close < 0) continue;
1343
1380
  const end = close + m[1].length + 3;
1344
1381
  if (end !== text.length && text[end] !== "\n" && text[end] !== "\r") continue;
1345
- out += text.slice(cursor, m.index) + "```xml\n" + text.slice(m.index, end) + "\n```";
1382
+ const gap = text.slice(cursor, m.index);
1383
+ if (out && !gap.trim()) out = out.slice(0, -4) + gap + text.slice(m.index, end) + "\n```";
1384
+ else out += gap + "```xml\n" + text.slice(m.index, end) + "\n```";
1346
1385
  cursor = startRe.lastIndex = end;
1347
1386
  }
1348
1387
  out += text.slice(cursor);
@@ -1495,6 +1534,7 @@ function AssistantCard({ turns, point, ctxLimit, duration: duration2, showAuthor
1495
1534
  const [open, setOpen] = useCollapsed(false);
1496
1535
  const toolBlocks = turns.flatMap((t) => t.blocks.filter((b) => b.type === "tool_use"));
1497
1536
  const errorCount = toolBlocks.filter((b) => b.result?.is_error).length;
1537
+ const skillCount = toolBlocks.filter((b) => b.name === "Skill").length;
1498
1538
  const isAux = (t) => !t.blocks.some((b) => b.type === "text");
1499
1539
  const foldable = toolBlocks.length > 0 && turns.some(isAux);
1500
1540
  const end = turns.findLast((t) => t.ts != null)?.ts ?? null;
@@ -1506,6 +1546,7 @@ function AssistantCard({ turns, point, ctxLimit, duration: duration2, showAuthor
1506
1546
  const anyVisible = turns.some((t) => !hidden(t));
1507
1547
  const parts = [
1508
1548
  toolBlocks.length > 0 && `${toolBlocks.length} tool call${toolBlocks.length === 1 ? "" : "s"}`,
1549
+ skillCount > 0 && `${skillCount} skill${skillCount === 1 ? "" : "s"}`,
1509
1550
  errorCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "msg-errors", children: [
1510
1551
  errorCount,
1511
1552
  " error",
@@ -1550,21 +1591,25 @@ function Block({ block }) {
1550
1591
  const cmd = parseCommand(block.text);
1551
1592
  if (cmd) {
1552
1593
  if (!cmd.name && !cmd.args && !cmd.stdout && !cmd.caveat) return null;
1553
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "block-command", children: [
1554
- cmd.name && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "cmd-name", children: cmd.name }),
1555
- cmd.args && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "cmd-args", children: [
1556
- " ",
1557
- cmd.args
1594
+ const promptArgs = cmd.args.trim().includes("\n");
1595
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1596
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "block-command", children: [
1597
+ cmd.name && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "cmd-name", children: cmd.name }),
1598
+ cmd.args && !promptArgs && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "cmd-args", children: [
1599
+ " ",
1600
+ cmd.args
1601
+ ] }),
1602
+ cmd.stdout && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cmd-stdout", children: cmd.stdout }),
1603
+ cmd.caveat && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cmd-stdout", children: cmd.caveat })
1558
1604
  ] }),
1559
- cmd.stdout && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cmd-stdout", children: cmd.stdout }),
1560
- cmd.caveat && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cmd-stdout", children: cmd.caveat })
1605
+ promptArgs && /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: cmd.args, autoFence: true })
1561
1606
  ] });
1562
1607
  }
1563
1608
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: block.text, autoFence: true });
1564
1609
  }
1565
1610
  if (block.type === "thinking") {
1566
- if (!block.thinking) return null;
1567
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: "thinking", defaultOpen: false, children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: block.thinking }) });
1611
+ if (block.redacted) return /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { title: `Thinking (encrypted${block.redacted > 1 ? ` · ${block.redacted}` : ""})` });
1612
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: "Thinking", defaultOpen: false, children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: block.thinking }) });
1568
1613
  }
1569
1614
  if (block.type === "tool_use") {
1570
1615
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Collapsible, { title: toolSummary(block.name, block.input), defaultOpen: false, children: [
@@ -1572,6 +1617,9 @@ function Block({ block }) {
1572
1617
  block.result && /* @__PURE__ */ jsxRuntimeExports.jsx(Block, { block: block.result })
1573
1618
  ] });
1574
1619
  }
1620
+ if (block.type === "skill") {
1621
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { title: block.name ? `Skill: ${block.name}` : "Skill", defaultOpen: false, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: block.text }) });
1622
+ }
1575
1623
  if (block.type === "tool_result") {
1576
1624
  const c = block.content;
1577
1625
  const parts = Array.isArray(c) ? c : [typeof c === "string" ? { type: "text", text: c } : c];
@@ -1675,12 +1723,15 @@ function JsonBlock({ value }) {
1675
1723
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: "```json\n" + safeJson(value) + "\n```" });
1676
1724
  }
1677
1725
  function InstructionRun({ turns, model }) {
1678
- const tokens2 = turns.map((t) => countTokens(t.blocks[0].it.content, t.blocks[0].it.model ?? model));
1726
+ const strips = turns.flatMap((t) => t.blocks).map(({ it }) => ({ ...it, tokens: countTokens(it.content, it.model ?? model) }));
1679
1727
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1680
- turns.map((t, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionFile, { it: t.blocks[0].it, model, tokens: tokens2[i] }, t.uuid)),
1681
- turns.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aux instruction-total", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "instruction-tokens", children: [
1728
+ groupInstructions(strips).map(([label, list]) => /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
1729
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aux instruction-source", children: label }),
1730
+ list.map((it) => /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionFile, { it, model, tokens: it.tokens }, `${it.hash ?? it.file_path}-${it.timestamp}`))
1731
+ ] }, label)),
1732
+ strips.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aux instruction-total", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "instruction-tokens", children: [
1682
1733
  "~ ",
1683
- fmtCompact(tokens2.reduce((a, b) => a + b, 0))
1734
+ fmtCompact(strips.reduce((a, it) => a + it.tokens, 0))
1684
1735
  ] }) })
1685
1736
  ] });
1686
1737
  }
@@ -1883,7 +1934,7 @@ const Pane = React.memo(function Pane2({ value, headers, seen, expandAll }) {
1883
1934
  ] })
1884
1935
  ] });
1885
1936
  });
1886
- function RequestsView({ sessionId, date, granularity = "day", fileSize = 0, expandAll = null }) {
1937
+ function RequestsView({ filePath, date, granularity = "day", fileSize = 0, expandAll = null }) {
1887
1938
  const [records, setRecords] = reactExports.useState(null);
1888
1939
  const [selected, setSelected] = reactExports.useState(0);
1889
1940
  const [tab, setTab] = reactExports.useState("request");
@@ -1894,16 +1945,16 @@ function RequestsView({ sessionId, date, granularity = "day", fileSize = 0, expa
1894
1945
  reactExports.useEffect(() => {
1895
1946
  setRecords(null);
1896
1947
  setSelected(0);
1897
- }, [sessionId, date, granularity]);
1948
+ }, [filePath, date, granularity]);
1898
1949
  reactExports.useEffect(() => {
1899
1950
  let cancelled = false;
1900
- window.api.readRequests(sessionId, date || null, granularity).then((res) => {
1951
+ window.api.readRequests(filePath, date || null, granularity).then((res) => {
1901
1952
  if (!cancelled) setRecords(res);
1902
1953
  });
1903
1954
  return () => {
1904
1955
  cancelled = true;
1905
1956
  };
1906
- }, [sessionId, date, granularity, fileSize]);
1957
+ }, [filePath, date, granularity, fileSize]);
1907
1958
  if (!records) return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-view", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "requests-empty", children: "Loading…" }) });
1908
1959
  if (!records.length) {
1909
1960
  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." }) });
@@ -1949,8 +2000,8 @@ function resultText(r) {
1949
2000
  }
1950
2001
  function renderBlock(b) {
1951
2002
  if (b.type === "text") return truncate(b.text || "");
1952
- if (b.type === "thinking") return `thinking:
1953
- ${fence(b.thinking || "")}`;
2003
+ if (b.type === "thinking") return b.redacted ? `_Thinking (encrypted${b.redacted > 1 ? ` · ${b.redacted}` : ""})_` : `Thinking:
2004
+ ${fence(b.thinking)}`;
1954
2005
  if (b.type === "tool_use") {
1955
2006
  const tail = b.result ? `
1956
2007
  ${b.result.is_error ? "error" : "result"}:
@@ -1961,6 +2012,8 @@ ${fence(JSON.stringify(b.input, null, 2), "json")}${tail}`;
1961
2012
  }
1962
2013
  if (b.type === "tool_result") return `${b.is_error ? "error" : "result"}:
1963
2014
  ${fence(resultText(b))}`;
2015
+ if (b.type === "skill") return `Skill${b.name ? `: ${b.name}` : ""}:
2016
+ ${fence(b.text)}`;
1964
2017
  if (b.type === "image") return "[image]";
1965
2018
  if (b.type === "instruction") {
1966
2019
  const it = b.it;
@@ -1972,9 +2025,11 @@ ${fence(it.error ? `error: ${it.error}` : it.content || "")}`;
1972
2025
  return fence(JSON.stringify(b, null, 2), "json");
1973
2026
  }
1974
2027
  function renderTurn(t) {
1975
- if (t.role === "instruction") return `**Instructions loaded:**
2028
+ if (t.role === "instruction") {
2029
+ return groupInstructions(t.blocks.map((b) => b.it)).map(([label, list]) => `**Instructions loaded (${label}):**
1976
2030
 
1977
- ${t.blocks.map(renderBlock).join("\n\n")}`;
2031
+ ${list.map((it) => renderBlock({ type: "instruction", it })).join("\n\n")}`).join("\n\n");
2032
+ }
1978
2033
  const role = t.role === "user" ? t.queued ? "User (queued)" : "User" : `Assistant`;
1979
2034
  const tokens2 = t.tokenTotal > 0 && t.tokenDelta != null ? ` (${t.tokenDelta >= 0 ? "+" : ""}${fmtCompact(t.tokenDelta)} / ${fmtCompact(t.tokenTotal)} tokens)` : "";
1980
2035
  return `**${role}${tokens2}:**
@@ -2026,7 +2081,7 @@ ${transcript}`;
2026
2081
  - Wall duration: ${fmtDuration(wallDuration)}
2027
2082
  - Active periods: ${fmtNum(meta.activityPeriods.length)}
2028
2083
  - Messages: ${fmtNum(meta.messageCount)}
2029
- - Tool calls: ${fmtNum(meta.toolCalls)}
2084
+ - Tool calls: ${fmtToolCalls(meta)}
2030
2085
 
2031
2086
  ## Identity
2032
2087
  - Model: ${meta.models.join(", ") || "—"}
@@ -2271,7 +2326,7 @@ ${body}`);
2271
2326
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Wall duration", value: fmtDuration(wallDuration) }),
2272
2327
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Active periods", value: fmtNum(meta.activityPeriods.length) }),
2273
2328
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Messages", value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: tone(meta.messageCount, THRESHOLDS.messages), children: fmtNum(meta.messageCount) }) }),
2274
- /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Tool calls", value: fmtNum(meta.toolCalls) })
2329
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Tool calls", value: fmtToolCalls(meta) })
2275
2330
  ] }),
2276
2331
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Section, { title: "Identity", children: [
2277
2332
  /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Model", value: modelLabel, mono: true }),
@@ -2363,17 +2418,17 @@ function Session({ meta, missing, date, granularity = "day" }) {
2363
2418
  const [mode, setMode] = useLocalStorage("session.view-mode", "conversation");
2364
2419
  const [agentOpen, setAgentOpen] = reactExports.useState(false);
2365
2420
  const [expandAll, setExpandAll] = reactExports.useState(null);
2366
- const sessionId = meta?.sessionId;
2421
+ const filePath = meta?.filePath;
2367
2422
  const fileSize = meta?.fileSize;
2368
- const agent = useAgent(`${sessionId}|${date}`);
2423
+ const agent = useAgent(`${filePath}|${date}`);
2369
2424
  reactExports.useEffect(() => {
2370
2425
  setItems(null);
2371
2426
  setInstructions([]);
2372
- }, [sessionId, date, granularity]);
2427
+ }, [filePath, date, granularity]);
2373
2428
  reactExports.useEffect(() => {
2374
- if (!sessionId) return;
2429
+ if (!filePath) return;
2375
2430
  let cancelled = false;
2376
- window.api.readSession(sessionId, date || null, granularity).then((res) => {
2431
+ window.api.readSession(filePath, date || null, granularity).then((res) => {
2377
2432
  if (cancelled || !res) return;
2378
2433
  setItems(res.items);
2379
2434
  setInstructions(res.instructions || []);
@@ -2381,7 +2436,7 @@ function Session({ meta, missing, date, granularity = "day" }) {
2381
2436
  return () => {
2382
2437
  cancelled = true;
2383
2438
  };
2384
- }, [sessionId, date, granularity, fileSize]);
2439
+ }, [filePath, date, granularity, fileSize]);
2385
2440
  if (!meta) {
2386
2441
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-view empty", children: missing?.warn ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-missing", children: [
2387
2442
  "⚠ ",
@@ -2410,7 +2465,7 @@ function Session({ meta, missing, date, granularity = "day" }) {
2410
2465
  }
2411
2466
  )
2412
2467
  ] }),
2413
- 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, { sessionId, date, granularity, fileSize, expandAll })
2468
+ 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 })
2414
2469
  ] }),
2415
2470
  /* @__PURE__ */ jsxRuntimeExports.jsx(SessionSummary, { meta, items, instructions, agent, onOpenAgent: () => setAgentOpen(true), granularity })
2416
2471
  ] }) }) }) }) });
@@ -2477,10 +2532,9 @@ function StatusSwitch({ service, on, warn, button, className = "", tooltip, chan
2477
2532
  )
2478
2533
  ] });
2479
2534
  }
2480
- const proxyTooltip = /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
2481
- "Captures Claude Code's raw API traffic - system prompt, tool definitions, injected reminders, responses.",
2482
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", {}),
2483
- "Applies only to newly started Claude Code sessions."
2535
+ const proxyTooltip = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2536
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Captures Claude Code's raw API traffic - system prompt, tool definitions, injected reminders, responses." }),
2537
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Applies only to newly started Claude Code sessions." })
2484
2538
  ] });
2485
2539
  const proxyChanges = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2486
2540
  /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: `"env": {
@@ -2578,6 +2632,15 @@ function App() {
2578
2632
  const end = endOfPeriod(anchor, granularity);
2579
2633
  return { start, end };
2580
2634
  }, [anchor, granularity]);
2635
+ reactExports.useEffect(() => {
2636
+ const shown = startOfPeriod(Date.now(), granularity);
2637
+ if (anchor !== shown) return;
2638
+ const id = setInterval(() => {
2639
+ const current = startOfPeriod(Date.now(), granularity);
2640
+ if (current !== shown) setAnchor(current);
2641
+ }, 6e4);
2642
+ return () => clearInterval(id);
2643
+ }, [anchor, granularity]);
2581
2644
  reactExports.useEffect(() => {
2582
2645
  let cancelled = false;
2583
2646
  setScanProgress(null);
@@ -2609,7 +2672,7 @@ function App() {
2609
2672
  return { sourceFiltered, past, availableSources };
2610
2673
  }, [sessions, sourceFilter, projectFilter]);
2611
2674
  const selected = reactExports.useMemo(
2612
- () => sessions.find((s) => s.sessionId === selectedId) || null,
2675
+ () => sessions.find((s) => s.filePath === selectedId || s.sessionId === selectedId) || null,
2613
2676
  [sessions, selectedId]
2614
2677
  );
2615
2678
  let missing = null;
@@ -2682,7 +2745,7 @@ function App() {
2682
2745
  dayRange,
2683
2746
  sessions: dayItems.sourceFiltered,
2684
2747
  onSelect: selectSession,
2685
- selectedId,
2748
+ selectedId: selected?.filePath ?? null,
2686
2749
  dayAnchor: anchor,
2687
2750
  granularity,
2688
2751
  projectFilter,
@@ -2705,7 +2768,7 @@ function App() {
2705
2768
  SessionList,
2706
2769
  {
2707
2770
  sessions: dayItems.past,
2708
- selectedId,
2771
+ selectedId: selected?.filePath ?? null,
2709
2772
  deepLink,
2710
2773
  onSelect: selectSession
2711
2774
  }
@@ -616,6 +616,8 @@
616
616
  color: var(--accent, #4ea1ff);
617
617
  }
618
618
 
619
+ .session-command { color: var(--accent); }
620
+
619
621
  .subagent-tag,
620
622
  .worktree-tag {
621
623
  font-family: var(--font-mono), monospace;
@@ -721,7 +723,7 @@
721
723
  }
722
724
 
723
725
  .conv-row + .conv-row { margin-top: 15px; }
724
- /* One instruction row per API call (see groupTurns) — the gap keeps two calls' strips apart. */
726
+ /* The session's one instruction row (see flatten) — the gap keeps its strips off the first message. */
725
727
  .conv-row-instruction + .conv-row { margin-top: 20px; }
726
728
  .conv-row-assistant + .conv-row-user { margin-top: 60px; }
727
729
  .conv-row-assistant + .conv-row-assistant { margin-top: 0; }
@@ -921,6 +923,7 @@ button.msg-summary:hover { color: var(--text); background: var(--bg-elev-2); bor
921
923
  text-align: left;
922
924
  margin: 0;
923
925
  }
926
+ & .block-command + .block-text { margin-top: 4px; }
924
927
  }
925
928
 
926
929
  .turn-assistant .block-text { color: var(--text); }
@@ -1052,13 +1055,26 @@ button.msg-summary:hover { color: var(--text); background: var(--bg-elev-2); bor
1052
1055
  }
1053
1056
  }
1054
1057
 
1055
- /* Instruction files (system prompt / CLAUDE.md / memory) extracted from the captured API requests. */
1056
- .instruction-file {
1058
+ /* Instruction files (system prompt / CLAUDE.md / memory) extracted from the captured API requests,
1059
+ and the label over each group — one unbroken strip down the whole run. */
1060
+ .instruction-file,
1061
+ .instruction-source {
1057
1062
  border-left: 2px solid #a78bfa;
1058
1063
  padding-left: 8px;
1059
1064
  margin-left: -10px;
1060
1065
  }
1061
1066
 
1067
+ /* Group label over the strips read from one part of the request (system prompt / tools / user message). */
1068
+ .instruction-source {
1069
+ font-family: var(--font-mono), monospace;
1070
+ font-size: calc(10.5px * var(--font-scale, 1));
1071
+ color: var(--text-faint);
1072
+ text-transform: uppercase;
1073
+ letter-spacing: 0.04em;
1074
+ padding-top: 6px;
1075
+ }
1076
+ .instruction-source:first-child { padding-top: 0; }
1077
+
1062
1078
  /* Full-width toggle so the estimated token count can sit right-aligned, like msg-header-right. */
1063
1079
  .instruction-file .aux-toggle { display: flex; width: 100%; align-items: center; }
1064
1080
  .instruction-tokens { margin-left: auto; color: var(--text-faint); }
@@ -2319,4 +2335,4 @@ button:hover { background: var(--border); }
2319
2335
  .kind-suggest { color: #6ea882; }
2320
2336
  .kind-web { color: #5fa8ad; }
2321
2337
  .kind-security { color: #c4937b; }
2322
- .kind-quota, .kind-status, .kind-count { color: var(--text-faint); }
2338
+ .kind-quota, .kind-status, .kind-count, .kind-aux { color: var(--text-faint); }