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.
- package/bin/claude/statusline.mjs +125 -124
- package/bin/update.mjs +33 -0
- package/out/main/main.js +129 -32
- package/out/preload/preload.cjs +3 -1
- package/out/renderer/assets/{index-FM6CSRHb.js → index-BV8MRn7r.js} +831 -674
- package/out/renderer/assets/{index-DnYN00T3.css → index-CR3tCQfY.css} +192 -303
- package/out/renderer/assets/{vendors-D2DMOduT.js → vendors-CF1AEHsu.js} +479 -340
- package/out/renderer/assets/wave-D-wH63sc.svg +3 -0
- package/out/renderer/index.html +3 -3
- package/package.json +80 -77
|
@@ -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,
|
|
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.
|
|
38
|
-
const [MAJOR, MINOR, PATCH] = "1.10.
|
|
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.
|
|
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:
|
|
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
|
|
307
|
-
if (
|
|
308
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { className: "work-offhours-shade", x, y: 0, width:
|
|
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:
|
|
320
|
-
week: { height:
|
|
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 +
|
|
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
|
|
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:
|
|
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:
|
|
522
|
-
item.subs && /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: x +
|
|
523
|
-
isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x, y, width:
|
|
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.
|
|
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:
|
|
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
|
|
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
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
-
|
|
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
|
|
1606
|
+
const msgs = turns.filter((t) => !isContextTurn(t));
|
|
1514
1607
|
const ctxItems = turns.filter(isContextTurn).flatMap((t) => t.blocks);
|
|
1515
|
-
|
|
1516
|
-
|
|
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(
|
|
1519
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "msg-author", children:
|
|
1520
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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:
|
|
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} ${
|
|
1591
|
-
contextBlocks.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { className: "attachment", defaultOpen: false, title:
|
|
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.
|
|
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")
|
|
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
|
|
2131
|
-
const [
|
|
2132
|
-
const
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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(
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
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
|
-
|
|
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 >${
|
|
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
|
|
2504
|
+
const analyze = async (meta, items, instructions) => {
|
|
2404
2505
|
setError("");
|
|
2405
2506
|
setOutput("");
|
|
2406
2507
|
setRunning(true);
|
|
2407
2508
|
try {
|
|
2408
|
-
const {
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
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.
|
|
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
|
);
|