@tonyclaw/agent-inspector 3.0.7 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.output/cli.js +245 -171
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-C0Kbsm-Q.js → CompareDrawer-CJKL4LMm.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DBUy29O-.js +106 -0
  5. package/.output/public/assets/ReplayDialog-BYanl-a_.js +1 -0
  6. package/.output/public/assets/{RequestAnatomy-fa0JXy5p.js → RequestAnatomy-DvPz1jqE.js} +1 -1
  7. package/.output/public/assets/ResponseView-sZnxp9GN.js +3 -0
  8. package/.output/public/assets/StreamingChunkSequence-G-tiYiBd.js +1 -0
  9. package/.output/public/assets/{_sessionId-D5fYH2C0.js → _sessionId-_hDcsmXq.js} +1 -1
  10. package/.output/public/assets/agent-inspector-58K1_MsC.ico +0 -0
  11. package/.output/public/assets/index-DAPAWsAd.js +1 -0
  12. package/.output/public/assets/index-DspnLqUW.css +1 -0
  13. package/.output/public/assets/{index-BEsqvywM.js → index-SuOSQb2_.js} +1 -1
  14. package/.output/public/assets/{json-viewer-B0QRbMSf.js → json-viewer-yQdhgjaw.js} +1 -1
  15. package/.output/public/assets/{main-CuKZD7n0.js → main-CgjcDxjR.js} +2 -2
  16. package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -2
  17. package/.output/server/{_sessionId-DdBTK3qm.mjs → _sessionId-BGISf2En.mjs} +3 -3
  18. package/.output/server/_ssr/{CompareDrawer-m82cVZ73.mjs → CompareDrawer-s-UF9gW3.mjs} +2 -2
  19. package/.output/server/_ssr/{ProxyViewerContainer-7geuf78f.mjs → ProxyViewerContainer-C0Sbz9ED.mjs} +1033 -530
  20. package/.output/server/_ssr/{ReplayDialog-B8RlWLLH.mjs → ReplayDialog-DvS8gvJr.mjs} +58 -23
  21. package/.output/server/_ssr/{RequestAnatomy-BE7pmwpv.mjs → RequestAnatomy-BwhWglJL.mjs} +2 -2
  22. package/.output/server/_ssr/{ResponseView-D-sDsdQr.mjs → ResponseView-wBVreqQf.mjs} +2 -2
  23. package/.output/server/_ssr/{StreamingChunkSequence-DU_hThJr.mjs → StreamingChunkSequence-Dr6eg-MJ.mjs} +30 -20
  24. package/.output/server/_ssr/{index-D0sV8UbE.mjs → index-B98Wia1X.mjs} +2 -2
  25. package/.output/server/_ssr/index.mjs +2 -2
  26. package/.output/server/_ssr/{json-viewer-CaDh1zrw.mjs → json-viewer-D7L8y0FH.mjs} +2 -2
  27. package/.output/server/_ssr/{router-Zh2iZ_u0.mjs → router-g9mo0nWT.mjs} +459 -196
  28. package/.output/server/_tanstack-start-manifest_v-CbGc-o50.mjs +4 -0
  29. package/.output/server/index.mjs +76 -69
  30. package/package.json +1 -1
  31. package/src/assets/IDE/opencode.svg +1 -1
  32. package/src/assets/IDE/xiaomimimo.svg +1 -1
  33. package/src/cli/detect-tools.ts +2 -27
  34. package/src/cli/templates/codex-skill-onboard.ts +1 -1
  35. package/src/cli/templates/skill-onboard.ts +2 -3
  36. package/src/cli.ts +9 -86
  37. package/src/components/ProxyViewer.tsx +249 -37
  38. package/src/components/ProxyViewerContainer.tsx +167 -22
  39. package/src/components/clients/ClientLogo.tsx +38 -3
  40. package/src/components/ecosystem/AgentLabDialog.tsx +204 -3
  41. package/src/components/groups/GroupsDialog.tsx +28 -40
  42. package/src/components/providers/ImportWizardDialog.tsx +51 -101
  43. package/src/components/providers/ProvidersPanel.tsx +82 -68
  44. package/src/components/providers/SettingsDialog.tsx +11 -12
  45. package/src/components/proxy-viewer/AgentTraceSummary.tsx +15 -23
  46. package/src/components/proxy-viewer/ReplayDialog.tsx +65 -22
  47. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +33 -20
  48. package/src/components/proxy-viewer/TurnGroup.tsx +0 -16
  49. package/src/components/proxy-viewer/log-formats/anthropic.ts +5 -5
  50. package/src/components/proxy-viewer/log-formats/openai.ts +7 -10
  51. package/src/components/proxy-viewer/requestTools.ts +5 -2
  52. package/src/lib/apiClient.ts +43 -10
  53. package/src/lib/ecosystemContract.ts +32 -0
  54. package/src/lib/export-logs.ts +37 -3
  55. package/src/proxy/ecosystemRegistry.ts +244 -0
  56. package/src/proxy/ecosystemTasks.ts +153 -0
  57. package/src/proxy/platformCommands.ts +212 -0
  58. package/src/proxy/socketTracker.ts +84 -143
  59. package/src/routes/api/ecosystem.packages.$packageId.help.ts +16 -0
  60. package/src/routes/api/ecosystem.packages.$packageId.install.ts +16 -0
  61. package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +19 -0
  62. package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +16 -0
  63. package/src/routes/api/ecosystem.packages.ts +1 -248
  64. package/src/routes/api/ecosystem.tasks.$taskId.ts +16 -0
  65. package/src/routes/api/ecosystem.tasks.ts +12 -0
  66. package/src/routes/api/providers.$providerId.test.log.ts +28 -24
  67. package/.output/public/assets/ProxyViewerContainer-p-rpYyiF.js +0 -106
  68. package/.output/public/assets/ReplayDialog-CM3dhP8v.js +0 -1
  69. package/.output/public/assets/ResponseView-Bq6sC7Ay.js +0 -3
  70. package/.output/public/assets/StreamingChunkSequence-CGOun3Df.js +0 -1
  71. package/.output/public/assets/index-C29Olfzt.js +0 -1
  72. package/.output/public/assets/index-DgANE0r3.css +0 -1
  73. package/.output/server/_tanstack-start-manifest_v-LCE-2AaF.mjs +0 -4
@@ -52,6 +52,8 @@ import { groupLogsByConversation, type ConversationGroupData } from "./proxy-vie
52
52
  import { CrabLogo } from "./ui/crab-logo";
53
53
  import { crabVariants } from "./ui/crab-variants";
54
54
  import { McpLogo } from "./ui/mcp-logo";
55
+ import { Button } from "./ui/button";
56
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog";
55
57
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
56
58
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
57
59
  import type { TransientToastInput } from "./ui/transient-toast";
@@ -149,6 +151,7 @@ export type SessionMembershipEvidence = {
149
151
 
150
152
  export type LogPaginationControls = {
151
153
  isLoading: boolean;
154
+ loadProgress?: SessionLoadProgress;
152
155
  total: number | null;
153
156
  pageSize: number;
154
157
  hasOlder: boolean;
@@ -161,6 +164,14 @@ export type LogPaginationControls = {
161
164
  onNewest: () => void;
162
165
  };
163
166
 
167
+ export type SessionLoadProgress = {
168
+ label: string;
169
+ elapsedMs: number;
170
+ timeoutMs: number;
171
+ loadedLogs: number;
172
+ loadedPages: number;
173
+ };
174
+
164
175
  function formatTimeRange(logs: CapturedLog[], timeDisplayFormat: TimeDisplayFormat): string | null {
165
176
  const first = logs[0];
166
177
  const last = logs[logs.length - 1];
@@ -1003,6 +1014,44 @@ function LogPaginationButton({
1003
1014
  );
1004
1015
  }
1005
1016
 
1017
+ function formatDurationSeconds(ms: number): string {
1018
+ return `${Math.max(0, Math.ceil(ms / 1000)).toString()}s`;
1019
+ }
1020
+
1021
+ function SessionLoadProgressBar({ progress }: { progress: SessionLoadProgress }): JSX.Element {
1022
+ const elapsedRatio = Math.min(1, progress.elapsedMs / progress.timeoutMs);
1023
+ const percent = Math.max(4, Math.round(elapsedRatio * 100));
1024
+ const remainingMs = Math.max(0, progress.timeoutMs - progress.elapsedMs);
1025
+ const nearTimeout = elapsedRatio >= 0.75;
1026
+ const details = [
1027
+ progress.loadedLogs > 0 ? `${progress.loadedLogs.toString()} logs` : null,
1028
+ progress.loadedPages > 0
1029
+ ? `${progress.loadedPages.toString()} page${progress.loadedPages === 1 ? "" : "s"}`
1030
+ : null,
1031
+ `${formatDurationSeconds(remainingMs)} before timeout`,
1032
+ ].filter((item): item is string => item !== null);
1033
+
1034
+ return (
1035
+ <div className="min-w-0 space-y-1 px-1 pb-1">
1036
+ <div className="flex min-w-0 items-center justify-between gap-3 font-mono text-[10px] text-muted-foreground">
1037
+ <span className="min-w-0 truncate">{progress.label}</span>
1038
+ <span className={cn("shrink-0 tabular-nums", nearTimeout && "text-amber-300")}>
1039
+ {details.join(" / ")}
1040
+ </span>
1041
+ </div>
1042
+ <div className="h-1.5 overflow-hidden rounded-full bg-white/[0.06]">
1043
+ <div
1044
+ className={cn(
1045
+ "h-full rounded-full transition-all duration-300",
1046
+ nearTimeout ? "bg-amber-300/80" : "bg-cyan-300/70",
1047
+ )}
1048
+ style={{ width: `${percent.toString()}%` }}
1049
+ />
1050
+ </div>
1051
+ </div>
1052
+ );
1053
+ }
1054
+
1006
1055
  function LogPaginationBar({
1007
1056
  logs,
1008
1057
  pagination,
@@ -1027,6 +1076,9 @@ function LogPaginationBar({
1027
1076
  <div className="w-full min-w-0 px-1 font-mono text-[11px] text-muted-foreground sm:flex-1">
1028
1077
  {formatPaginationStatus({ logs, pagination })}
1029
1078
  {pagination.isLoading && logs.length > 0 && <span className="ml-2">Loading...</span>}
1079
+ {pagination.loadProgress !== undefined && (
1080
+ <SessionLoadProgressBar progress={pagination.loadProgress} />
1081
+ )}
1030
1082
  </div>
1031
1083
  <div className="flex w-full items-center gap-1 overflow-x-auto sm:w-auto">
1032
1084
  <LogPaginationButton
@@ -1167,26 +1219,6 @@ function CopyableCommand({ command }: { command: string }): JSX.Element {
1167
1219
  );
1168
1220
  }
1169
1221
 
1170
- function McpReadyBadge(): JSX.Element {
1171
- return (
1172
- <TooltipProvider>
1173
- <Tooltip>
1174
- <TooltipTrigger asChild>
1175
- <span className="bg-cyan-300/[0.055] shadow-[inset_0_1px_0_rgba(255,255,255,0.05)] inline-flex h-7 items-center gap-2 rounded-md px-2.5 font-mono text-[11px] font-medium text-cyan-200">
1176
- <span className="size-1.5 rounded-full bg-cyan-200 shadow-[0_0_8px_rgba(125,211,252,0.75)]" />
1177
- MCP Ready
1178
- <span className="hidden text-cyan-200/70 sm:inline">/api/mcp</span>
1179
- </span>
1180
- </TooltipTrigger>
1181
- <TooltipContent sideOffset={8} className="max-w-[320px] text-left leading-relaxed">
1182
- Coding agents can inspect logs, replay requests, test providers, and debug sessions
1183
- through MCP at /api/mcp.
1184
- </TooltipContent>
1185
- </Tooltip>
1186
- </TooltipProvider>
1187
- );
1188
- }
1189
-
1190
1222
  function SidebarPanel({
1191
1223
  label,
1192
1224
  children,
@@ -1249,7 +1281,7 @@ function CrabFamily({
1249
1281
  entrancePhase: "hidden" | "playing" | "done";
1250
1282
  }): JSX.Element {
1251
1283
  if (compact) {
1252
- return <CrabLogo className="size-7 text-amber-500" />;
1284
+ return <CrabLogo className="size-14 text-amber-500" />;
1253
1285
  }
1254
1286
 
1255
1287
  return (
@@ -1295,7 +1327,7 @@ function BrandHeader({
1295
1327
  return (
1296
1328
  <div className="bg-white/[0.035] shadow-[inset_0_1px_0_rgba(255,255,255,0.045),0_16px_44px_rgba(0,0,0,0.18)] rounded-[8px] p-3">
1297
1329
  <div className="flex flex-wrap items-start justify-between gap-3">
1298
- <div className="flex min-w-fit items-start gap-2">
1330
+ <div className="flex min-w-fit items-start gap-3">
1299
1331
  <CrabFamily compact={true} entrancePhase={crabEntrancePhase} />
1300
1332
  <div className="min-w-fit">
1301
1333
  <div className="flex min-w-fit flex-wrap items-baseline gap-x-2 gap-y-0.5">
@@ -1316,9 +1348,6 @@ function BrandHeader({
1316
1348
  <SettingsDialog />
1317
1349
  </div>
1318
1350
  </div>
1319
- <div className="mt-2 hidden sm:block">
1320
- <McpReadyBadge />
1321
- </div>
1322
1351
  </div>
1323
1352
  );
1324
1353
  }
@@ -1341,7 +1370,6 @@ function BrandHeader({
1341
1370
  <span className="max-w-full text-sm font-semibold text-muted-foreground">
1342
1371
  Local First. Evidence First.
1343
1372
  </span>
1344
- <McpReadyBadge />
1345
1373
  </h1>
1346
1374
  <div className="flex flex-1 justify-end gap-1">
1347
1375
  <AlertsDialog />
@@ -1669,6 +1697,8 @@ export type ProxyViewerProps = {
1669
1697
  onSessionChange: (session: string) => void;
1670
1698
  onModelChange: (model: string) => void;
1671
1699
  onClearAll: () => void;
1700
+ /** Delete visible logs from persisted storage when the user explicitly confirms it. */
1701
+ onDeleteCurrentView?: (ids: number[]) => Promise<void>;
1672
1702
  /** Reload the selected/pinned session from stored logs without requiring a page refresh. */
1673
1703
  onReloadSession?: () => void;
1674
1704
  /** Clear only the logs whose ids are passed. Called by the per-group
@@ -1689,6 +1719,8 @@ export type ProxyViewerProps = {
1689
1719
  isLoading?: boolean;
1690
1720
  /** Cursor controls for history pages. Undefined keeps the live stream UI compact. */
1691
1721
  pagination?: LogPaginationControls;
1722
+ /** Active session log loading progress, shown near load/reload controls. */
1723
+ sessionLoadProgress?: SessionLoadProgress;
1692
1724
  /** Group/run member metadata attached to the pinned session id. */
1693
1725
  sessionMemberships?: SessionMembershipEvidence[];
1694
1726
  /** Hide the session filter dropdown. Used on `/session/$id` routes where
@@ -1708,6 +1740,7 @@ export function ProxyViewer({
1708
1740
  onSessionChange,
1709
1741
  onModelChange,
1710
1742
  onClearAll,
1743
+ onDeleteCurrentView,
1711
1744
  onReloadSession,
1712
1745
  onClearGroup,
1713
1746
  onNotify,
@@ -1719,6 +1752,7 @@ export function ProxyViewer({
1719
1752
  timeDisplayFormat,
1720
1753
  isLoading = false,
1721
1754
  pagination,
1755
+ sessionLoadProgress,
1722
1756
  sessionMemberships = [],
1723
1757
  hideSessionFilter = false,
1724
1758
  pinnedSessionId,
@@ -1730,6 +1764,9 @@ export function ProxyViewer({
1730
1764
  const [importing, setImporting] = useState(false);
1731
1765
  const [importStatus, setImportStatus] = useState<string | null>(null);
1732
1766
  const [importError, setImportError] = useState<string | null>(null);
1767
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
1768
+ const [deleteStoredData, setDeleteStoredData] = useState(false);
1769
+ const [deletingCurrentView, setDeletingCurrentView] = useState(false);
1733
1770
  const [comparePair, setComparePair] = useState<[CapturedLog, CapturedLog] | null>(null);
1734
1771
  const [crabEntrancePhase, setCrabEntrancePhase] = useState<"hidden" | "playing" | "done">(
1735
1772
  "hidden",
@@ -1737,8 +1774,27 @@ export function ProxyViewer({
1737
1774
  const logListRef = useRef<HTMLDivElement>(null);
1738
1775
  const logListWrapperRef = useRef<HTMLDivElement>(null);
1739
1776
  const importInputRef = useRef<HTMLInputElement>(null);
1777
+ const exportAbortRef = useRef<AbortController | null>(null);
1778
+ const exportRequestIdRef = useRef(0);
1740
1779
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
1741
1780
 
1781
+ const cancelTransientTasks = useCallback(() => {
1782
+ exportRequestIdRef.current += 1;
1783
+ exportAbortRef.current?.abort();
1784
+ exportAbortRef.current = null;
1785
+ setExporting(null);
1786
+ setExportError(null);
1787
+ }, []);
1788
+
1789
+ useEffect(
1790
+ () => () => {
1791
+ exportRequestIdRef.current += 1;
1792
+ exportAbortRef.current?.abort();
1793
+ exportAbortRef.current = null;
1794
+ },
1795
+ [],
1796
+ );
1797
+
1742
1798
  useEffect(() => {
1743
1799
  const perCrabDuration = 400;
1744
1800
  const startDelay = 50;
@@ -1762,11 +1818,18 @@ export function ProxyViewer({
1762
1818
 
1763
1819
  const handleExport = useCallback(
1764
1820
  async (mode: ExportMode) => {
1821
+ exportAbortRef.current?.abort();
1822
+ const controller = new AbortController();
1823
+ exportAbortRef.current = controller;
1824
+ const requestId = exportRequestIdRef.current + 1;
1825
+ exportRequestIdRef.current = requestId;
1765
1826
  setExporting(mode);
1766
1827
  setExportError(null);
1767
1828
  try {
1768
- const result = await exportLogsAsZip(logs, mode);
1829
+ const result = await exportLogsAsZip(logs, mode, { signal: controller.signal });
1830
+ if (exportRequestIdRef.current !== requestId) return;
1769
1831
  if (!result.ok) {
1832
+ if (result.canceled === true) return;
1770
1833
  if (onNotify !== undefined) {
1771
1834
  onNotify({ message: result.message, tone: "error" });
1772
1835
  } else {
@@ -1774,7 +1837,10 @@ export function ProxyViewer({
1774
1837
  }
1775
1838
  }
1776
1839
  } finally {
1777
- setExporting(null);
1840
+ if (exportRequestIdRef.current === requestId) {
1841
+ exportAbortRef.current = null;
1842
+ setExporting(null);
1843
+ }
1778
1844
  }
1779
1845
  },
1780
1846
  [logs, onNotify],
@@ -1819,8 +1885,61 @@ export function ProxyViewer({
1819
1885
  // Close the compare drawer when the user changes the session or model
1820
1886
  // filter, since the predecessor relationship may no longer be meaningful.
1821
1887
  useEffect(() => {
1888
+ cancelTransientTasks();
1889
+ setComparePair(null);
1890
+ }, [cancelTransientTasks, selectedSession, selectedModel]);
1891
+
1892
+ const handleClearCurrentView = useCallback(() => {
1893
+ cancelTransientTasks();
1894
+ setImportStatus(null);
1895
+ setImportError(null);
1822
1896
  setComparePair(null);
1823
- }, [selectedSession, selectedModel]);
1897
+ onClearAll();
1898
+ }, [cancelTransientTasks, onClearAll]);
1899
+
1900
+ const handleRequestDeleteCurrentView = useCallback(() => {
1901
+ setDeleteStoredData(false);
1902
+ setDeleteDialogOpen(true);
1903
+ }, []);
1904
+
1905
+ const handleConfirmDeleteCurrentView = useCallback(() => {
1906
+ cancelTransientTasks();
1907
+ setImportStatus(null);
1908
+ setImportError(null);
1909
+ setComparePair(null);
1910
+
1911
+ if (!deleteStoredData || onDeleteCurrentView === undefined) {
1912
+ setDeleteDialogOpen(false);
1913
+ onClearAll();
1914
+ return;
1915
+ }
1916
+
1917
+ setDeletingCurrentView(true);
1918
+ void onDeleteCurrentView(logs.map((log) => log.id))
1919
+ .then(() => {
1920
+ setDeleteDialogOpen(false);
1921
+ })
1922
+ .catch((err: unknown) => {
1923
+ const message = err instanceof Error ? err.message : "Failed to delete stored logs";
1924
+ if (onNotify !== undefined) {
1925
+ onNotify({ message, tone: "error" });
1926
+ } else {
1927
+ setImportError(message);
1928
+ }
1929
+ })
1930
+ .finally(() => {
1931
+ setDeletingCurrentView(false);
1932
+ });
1933
+ }, [cancelTransientTasks, deleteStoredData, logs, onClearAll, onDeleteCurrentView, onNotify]);
1934
+
1935
+ const handleReloadCurrentSession = useCallback(() => {
1936
+ if (onReloadSession === undefined) return;
1937
+ cancelTransientTasks();
1938
+ setImportStatus(null);
1939
+ setImportError(null);
1940
+ setComparePair(null);
1941
+ onReloadSession();
1942
+ }, [cancelTransientTasks, onReloadSession]);
1824
1943
 
1825
1944
  const closeCompare = useCallback(() => {
1826
1945
  setComparePair(null);
@@ -1927,7 +2046,7 @@ export function ProxyViewer({
1927
2046
  {onReloadSession !== undefined && (
1928
2047
  <button
1929
2048
  type="button"
1930
- onClick={onReloadSession}
2049
+ onClick={handleReloadCurrentSession}
1931
2050
  disabled={isLoading}
1932
2051
  className="bg-white/[0.035] hover:bg-white/[0.065] inline-flex h-8 min-w-0 items-center justify-center gap-1.5 rounded-md px-2 text-xs text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1933
2052
  title="Load stored logs for this session"
@@ -1938,18 +2057,37 @@ export function ProxyViewer({
1938
2057
  )}
1939
2058
  <button
1940
2059
  type="button"
1941
- onClick={onClearAll}
2060
+ onClick={handleClearCurrentView}
1942
2061
  disabled={!canClearCurrentView}
1943
2062
  className={cn(
1944
2063
  "bg-white/[0.035] hover:bg-white/[0.065] inline-flex h-8 min-w-0 items-center justify-center gap-1.5 rounded-md px-2 text-xs text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
1945
- onReloadSession === undefined && "col-span-2",
2064
+ onReloadSession === undefined &&
2065
+ onDeleteCurrentView === undefined &&
2066
+ "col-span-2",
1946
2067
  )}
1947
2068
  title="Clear the current view. Stored logs remain reloadable."
1948
2069
  >
1949
2070
  <Trash2 className="size-3.5" />
1950
- <span>Clear view</span>
2071
+ <span>Clear</span>
1951
2072
  </button>
2073
+ {onDeleteCurrentView !== undefined && (
2074
+ <button
2075
+ type="button"
2076
+ onClick={handleRequestDeleteCurrentView}
2077
+ disabled={!canClearCurrentView || deletingCurrentView}
2078
+ className="bg-red-500/[0.07] hover:bg-red-500/[0.12] inline-flex h-8 min-w-0 items-center justify-center gap-1.5 rounded-md px-2 text-xs text-red-200 shadow-[inset_0_0_0_1px_rgba(248,113,113,0.14)] transition-colors hover:text-red-100 disabled:cursor-not-allowed disabled:opacity-50"
2079
+ title="Choose whether to delete stored data for the current view."
2080
+ >
2081
+ <Trash2 className="size-3.5" />
2082
+ <span>Delete</span>
2083
+ </button>
2084
+ )}
1952
2085
  </div>
2086
+ {sessionLoadProgress !== undefined && (
2087
+ <div className="mt-2 rounded-md bg-black/20 px-2 py-2 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]">
2088
+ <SessionLoadProgressBar progress={sessionLoadProgress} />
2089
+ </div>
2090
+ )}
1953
2091
  </SidebarPanel>
1954
2092
 
1955
2093
  <SidebarPanel label="Workspace">
@@ -2128,7 +2266,7 @@ export function ProxyViewer({
2128
2266
  {onReloadSession !== undefined && (
2129
2267
  <button
2130
2268
  type="button"
2131
- onClick={onReloadSession}
2269
+ onClick={handleReloadCurrentSession}
2132
2270
  disabled={isLoading}
2133
2271
  className="bg-white/[0.035] hover:bg-white/[0.065] inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
2134
2272
  title="Reload this session from stored logs"
@@ -2139,15 +2277,32 @@ export function ProxyViewer({
2139
2277
  )}
2140
2278
  <button
2141
2279
  type="button"
2142
- onClick={onClearAll}
2280
+ onClick={handleClearCurrentView}
2143
2281
  disabled={!canClearCurrentView}
2144
2282
  className="bg-white/[0.035] hover:bg-white/[0.065] inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
2145
2283
  title="Clear the current view. Stored logs remain reloadable."
2146
2284
  >
2147
2285
  <Trash2 className="size-3.5" />
2148
- <span>Clear view</span>
2286
+ <span>Clear</span>
2149
2287
  </button>
2288
+ {onDeleteCurrentView !== undefined && (
2289
+ <button
2290
+ type="button"
2291
+ onClick={handleRequestDeleteCurrentView}
2292
+ disabled={!canClearCurrentView || deletingCurrentView}
2293
+ className="bg-red-500/[0.07] hover:bg-red-500/[0.12] inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-red-200 shadow-[inset_0_0_0_1px_rgba(248,113,113,0.14)] transition-colors hover:text-red-100 disabled:cursor-not-allowed disabled:opacity-50"
2294
+ title="Choose whether to delete stored data for the current view."
2295
+ >
2296
+ <Trash2 className="size-3.5" />
2297
+ <span>Delete</span>
2298
+ </button>
2299
+ )}
2150
2300
  </div>
2301
+ {pagination === undefined && sessionLoadProgress !== undefined && (
2302
+ <div className="mt-2 rounded-md bg-black/20 px-2 py-2 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]">
2303
+ <SessionLoadProgressBar progress={sessionLoadProgress} />
2304
+ </div>
2305
+ )}
2151
2306
  <LogPaginationBar logs={logs} pagination={pagination} embedded={true} />
2152
2307
  {onNotify === undefined && exportError !== null && (
2153
2308
  <div className="mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
@@ -2178,6 +2333,11 @@ export function ProxyViewer({
2178
2333
  <p className="inline-block max-w-full rounded bg-muted px-3 py-1 font-mono text-xs break-all">
2179
2334
  {truncateSessionId(selectedSession)}
2180
2335
  </p>
2336
+ {sessionLoadProgress !== undefined && (
2337
+ <div className="mx-auto max-w-md rounded-md bg-black/20 px-2 py-2 text-left shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]">
2338
+ <SessionLoadProgressBar progress={sessionLoadProgress} />
2339
+ </div>
2340
+ )}
2181
2341
  {sessionMemberships.length > 0 && (
2182
2342
  <div className="text-left">
2183
2343
  <SessionMembershipPanel memberships={sessionMemberships} />
@@ -2190,7 +2350,7 @@ export function ProxyViewer({
2190
2350
  {onReloadSession !== undefined && (
2191
2351
  <button
2192
2352
  type="button"
2193
- onClick={onReloadSession}
2353
+ onClick={handleReloadCurrentSession}
2194
2354
  className="bg-white/[0.045] hover:bg-white/[0.075] inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors hover:text-foreground"
2195
2355
  >
2196
2356
  <RefreshCw className="size-3.5" />
@@ -2268,6 +2428,58 @@ export function ProxyViewer({
2268
2428
  </main>
2269
2429
  </div>
2270
2430
 
2431
+ <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
2432
+ <DialogContent className="border-red-500/20 bg-zinc-950 text-zinc-100 sm:max-w-md">
2433
+ <DialogHeader>
2434
+ <DialogTitle>Delete current logs?</DialogTitle>
2435
+ <DialogDescription>
2436
+ Choose whether this only clears the UI or also deletes the stored log data for the
2437
+ current view.
2438
+ </DialogDescription>
2439
+ </DialogHeader>
2440
+ <label className="flex items-start gap-3 rounded-md bg-red-500/[0.08] px-3 py-3 text-sm text-red-100 shadow-[inset_0_0_0_1px_rgba(248,113,113,0.16)]">
2441
+ <input
2442
+ type="checkbox"
2443
+ className="mt-0.5 size-4 shrink-0"
2444
+ checked={deleteStoredData}
2445
+ disabled={deletingCurrentView}
2446
+ onChange={(event) => setDeleteStoredData(event.currentTarget.checked)}
2447
+ />
2448
+ <span className="min-w-0">
2449
+ <span className="block font-medium">Also delete stored data</span>
2450
+ <span className="mt-1 block text-xs text-red-200/80">
2451
+ Removes persisted log rows, streaming chunks, and session archive entries for these{" "}
2452
+ {logs.length.toString()} visible request{logs.length === 1 ? "" : "s"}.
2453
+ </span>
2454
+ </span>
2455
+ </label>
2456
+ <div className="flex justify-end gap-2">
2457
+ <Button
2458
+ type="button"
2459
+ variant="outline"
2460
+ size="sm"
2461
+ disabled={deletingCurrentView}
2462
+ onClick={() => setDeleteDialogOpen(false)}
2463
+ >
2464
+ Cancel
2465
+ </Button>
2466
+ <Button
2467
+ type="button"
2468
+ variant={deleteStoredData ? "destructive" : "secondary"}
2469
+ size="sm"
2470
+ disabled={deletingCurrentView}
2471
+ onClick={handleConfirmDeleteCurrentView}
2472
+ >
2473
+ {deletingCurrentView
2474
+ ? "Deleting..."
2475
+ : deleteStoredData
2476
+ ? "Delete data"
2477
+ : "Clear only"}
2478
+ </Button>
2479
+ </div>
2480
+ </DialogContent>
2481
+ </Dialog>
2482
+
2271
2483
  {/* Compare drawer: sibling of the log list, not a route change. */}
2272
2484
  {comparePair !== null && (
2273
2485
  <Suspense fallback={null}>