@tonyclaw/agent-inspector 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BLYcm6Dg.js → CompareDrawer-SAn2KFl2.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DaiTFf7n.js +117 -0
  4. package/.output/public/assets/{ReplayDialog-_kCy9L9E.js → ReplayDialog-CCwz0YUV.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-DWp5pWsv.js → RequestAnatomy-DXcC4ydl.js} +1 -1
  6. package/.output/public/assets/{ResponseView-Ci92o6mt.js → ResponseView-Bh9t9aZF.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-C-1ZQl_b.js → StreamingChunkSequence-B69M2rr-.js} +1 -1
  8. package/.output/public/assets/_sessionId-6fF5JpyQ.js +1 -0
  9. package/.output/public/assets/index-Bcq8bZoK.css +1 -0
  10. package/.output/public/assets/index-DBSbw8Gd.js +1 -0
  11. package/.output/public/assets/{main-RgLvmIQk.js → main-DZVKUeg6.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +204 -173
  13. package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -0
  14. package/.output/server/{_sessionId-DnOYbKO7.mjs → _sessionId-COVUf_EG.mjs} +3 -3
  15. package/.output/server/_ssr/{CompareDrawer-D44_F13_.mjs → CompareDrawer-DqQHO4tk.mjs} +3 -3
  16. package/.output/server/_ssr/{ProxyViewerContainer-Cx4p_awW.mjs → ProxyViewerContainer-BEeF4V-C.mjs} +1006 -75
  17. package/.output/server/_ssr/{ReplayDialog-wScCF3Oc.mjs → ReplayDialog-DUZbsd5x.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-Cf7R7PXS.mjs → RequestAnatomy-DKuIxoBj.mjs} +2 -2
  19. package/.output/server/_ssr/{ResponseView-7pVIZLL-.mjs → ResponseView-BB9CYmTJ.mjs} +3 -3
  20. package/.output/server/_ssr/{StreamingChunkSequence-Bs57_GbC.mjs → StreamingChunkSequence-CkY3uTjF.mjs} +3 -3
  21. package/.output/server/_ssr/{index-6eY__la9.mjs → index-CHTchkrO.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-DRZPhZB6.mjs → router-C8DJ5pQZ.mjs} +1972 -221
  24. package/.output/server/_tanstack-start-manifest_v-Z1ffcxDr.mjs +4 -0
  25. package/.output/server/index.mjs +68 -68
  26. package/package.json +2 -1
  27. package/src/components/ProxyViewer.tsx +87 -1
  28. package/src/components/ProxyViewerContainer.tsx +26 -0
  29. package/src/components/alerts/AlertsDialog.tsx +610 -0
  30. package/src/components/groups/GroupsDialog.tsx +41 -13
  31. package/src/components/proxy-viewer/LogEntry.tsx +324 -51
  32. package/src/components/ui/dialog.tsx +1 -0
  33. package/src/contracts/index.ts +15 -2
  34. package/src/contracts/log.ts +18 -0
  35. package/src/lib/alertContract.ts +79 -0
  36. package/src/lib/apiClient.ts +39 -0
  37. package/src/lib/export-logs.ts +47 -9
  38. package/src/lib/groupContract.ts +1 -0
  39. package/src/lib/logImportContract.ts +12 -0
  40. package/src/lib/useAlerts.ts +82 -0
  41. package/src/lib/useGroupEvidence.ts +6 -2
  42. package/src/lib/useGroups.ts +6 -2
  43. package/src/proxy/alerts.ts +664 -0
  44. package/src/proxy/logBodyChunks.ts +91 -0
  45. package/src/proxy/logImporter.ts +491 -0
  46. package/src/proxy/logIndex.ts +63 -2
  47. package/src/proxy/sqliteLogIndex.ts +612 -0
  48. package/src/proxy/store.ts +32 -7
  49. package/src/routes/api/alerts.summary.ts +24 -0
  50. package/src/routes/api/alerts.ts +52 -0
  51. package/src/routes/api/logs.$id.body.ts +44 -0
  52. package/src/routes/api/logs.import.ts +50 -0
  53. package/src/services/alerts.ts +12 -0
  54. package/src/services/groups.ts +1 -0
  55. package/.output/public/assets/ProxyViewerContainer-Ba5wFn00.js +0 -117
  56. package/.output/public/assets/_sessionId-DKzAmSNP.js +0 -1
  57. package/.output/public/assets/index-B-MZ9lQM.css +0 -1
  58. package/.output/public/assets/index-ZZHwgkWJ.js +0 -1
  59. package/.output/server/_tanstack-start-manifest_v-DfAVFkZ5.mjs +0 -4
@@ -1,15 +1,15 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { I as InspectorGroupsListResponseSchema, C as CapturedLogSchema, D as DEFAULT_CAPTURE_MODE, a as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, b as DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS, c as DEFAULT_TIME_DISPLAY_FORMAT, d as RuntimeConfigSchema, v as packageJson, P as ProviderConfigSchema, f as apiFormatForPath, i as GroupEvidenceExportResultSchema, j as DeleteInspectorGroupResponseSchema, n as createPendingProviderTestResults, o as ProviderTestResultsSchema, q as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, r as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, g as getSessionPath, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, t as resolveProviderContextWindow, m as maskApiKey, k as providerHasContextMetadata, l as findProviderModelMetadata, K as KnowledgeCandidateSchema, h as stripClaudeCodeBillingHeader, u as isPlainRecord, G as GroupEvidenceReadResponseSchema } from "./router-DRZPhZB6.mjs";
2
+ import { I as InspectorGroupsListResponseSchema, C as CapturedLogSchema, D as DEFAULT_CAPTURE_MODE, a as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, b as DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS, c as DEFAULT_TIME_DISPLAY_FORMAT, d as RuntimeConfigSchema, p as packageJson, P as ProviderConfigSchema, g as apiFormatForPath, l as GroupEvidenceExportResultSchema, m as DeleteInspectorGroupResponseSchema, r as createPendingProviderTestResults, t as ProviderTestResultsSchema, u as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, v as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, h as getSessionPath, f as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, w as resolveProviderContextWindow, j as AlertSummarySchema, k as AlertListResponseSchema, G as GroupEvidenceReadResponseSchema, q as maskApiKey, n as providerHasContextMetadata, o as findProviderModelMetadata, K as KnowledgeCandidateSchema, i as stripClaudeCodeBillingHeader, L as LogBodyChunkSchema, x as isPlainRecord } from "./router-C8DJ5pQZ.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
6
6
  import { t as twMerge } from "../_libs/tailwind-merge.mjs";
7
7
  import { c as cva } from "../_libs/class-variance-authority.mjs";
8
- import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as Portal$2, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
8
+ import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, D as Description, P as Portal$2, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { u as useWindowVirtualizer, a as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
11
11
  import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
12
- import { I as Info, C as ChevronDown, a as Check, X, D as Download, F as FileBraces, T as Trash2, P as Plus, A as ArrowLeft, b as ChevronUp, c as Copy, d as ChevronsLeft, e as ChevronLeft, f as ChevronRight, g as ChevronsRight, h as Clapperboard, S as Siren, G as Gauge, L as Layers, i as LoaderCircle, R as RefreshCw, j as Settings, U as Upload, k as Scan, l as CircleAlert, m as Terminal, n as ArrowUpRight, o as ArrowDownRight, p as User, q as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, W as Wrench, r as TriangleAlert, B as Brain, s as EyeOff, t as Eye, u as RotateCw, v as Pencil, w as CircleX, x as ShieldCheck, y as Save, z as FileSearch, H as CircleCheck, J as CloudUpload, O as OctagonAlert, K as Radio, N as Globe, Q as FileTerminal, V as ChevronsUp, Y as ChevronsDown, _ as FileDiff, $ as History, a0 as RotateCcw, a1 as Search, a2 as GitCompareArrows, a3 as Minus, a4 as CircleCheckBig, a5 as ArrowUp, a6 as ArrowDown, a7 as CircleQuestionMark, a8 as Server, a9 as Lock, aa as Wifi, ab as WifiOff, ac as Ellipsis, ad as CodeXml, ae as Blocks, af as MousePointerClick, ag as FileText, ah as Rows3, ai as Columns2 } from "../_libs/lucide-react.mjs";
12
+ import { I as Info, C as ChevronDown, a as Check, X, U as Upload, D as Download, F as FileBraces, T as Trash2, P as Plus, A as ArrowLeft, b as ChevronUp, c as Copy, d as ChevronsLeft, e as ChevronLeft, f as ChevronRight, g as ChevronsRight, h as Clapperboard, S as Siren, G as Gauge, L as LoaderCircle, R as RefreshCw, i as Layers, j as Settings, k as Scan, l as CircleAlert, m as Terminal, n as ArrowUpRight, o as ArrowDownRight, p as User, q as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, W as Wrench, r as TriangleAlert, B as Brain, s as Bell, t as Clock3, u as ShieldAlert, v as EyeOff, w as Eye, x as RotateCw, y as Pencil, z as CircleX, H as ShieldCheck, J as Save, K as FileSearch, N as CircleCheck, O as CloudUpload, Q as OctagonAlert, V as Radio, Y as Globe, _ as FileTerminal, $ as ChevronsUp, a0 as ChevronsDown, a1 as FileDiff, a2 as History, a3 as RotateCcw, a4 as Search, a5 as GitCompareArrows, a6 as Minus, a7 as CircleCheckBig, a8 as ArrowUp, a9 as ArrowDown, aa as CircleQuestionMark, ab as Server, ac as Lock, ad as Wifi, ae as WifiOff, af as Ellipsis, ag as CodeXml, ah as Blocks, ai as MousePointerClick, aj as FileText, ak as Rows3, al as Columns2 } from "../_libs/lucide-react.mjs";
13
13
  import { M as Markdown } from "../_libs/react-markdown.mjs";
14
14
  import { u as union, d as object, a as array, l as literal, b as string, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
15
15
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
@@ -41,6 +41,37 @@ async function fetchJson(input, schema, init, errorFallback) {
41
41
  }
42
42
  return parseJsonResponse$1(response, schema);
43
43
  }
44
+ function formatTimeout(timeoutMs) {
45
+ if (timeoutMs < 1e3) return `${String(timeoutMs)}ms`;
46
+ return `${String(Math.round(timeoutMs / 1e3))}s`;
47
+ }
48
+ class ApiTimeoutError extends Error {
49
+ constructor(timeoutMs) {
50
+ super(`Request timed out after ${formatTimeout(timeoutMs)}`);
51
+ this.name = "ApiTimeoutError";
52
+ }
53
+ }
54
+ async function fetchJsonWithTimeout(input, schema, timeoutMs, init, errorFallback) {
55
+ const controller = new AbortController();
56
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
57
+ try {
58
+ return await fetchJson(input, schema, { ...init, signal: controller.signal }, errorFallback);
59
+ } catch (error) {
60
+ if (error instanceof DOMException && error.name === "AbortError") {
61
+ throw new ApiTimeoutError(timeoutMs);
62
+ }
63
+ throw error;
64
+ } finally {
65
+ clearTimeout(timeout);
66
+ }
67
+ }
68
+ const ImportLogsResponseSchema = object({
69
+ sessionId: string(),
70
+ imported: number().int().nonnegative(),
71
+ skipped: number().int().nonnegative(),
72
+ warnings: array(string()),
73
+ logs: array(CapturedLogSchema)
74
+ });
44
75
  const STRIP_CONFIG_SWR_KEY = "/api/config";
45
76
  async function fetcher$2(url) {
46
77
  return fetchJson(
@@ -356,6 +387,29 @@ function redactJsonText(text) {
356
387
  function exportText(text, mode) {
357
388
  return mode === "redacted" ? redactJsonText(text) : text;
358
389
  }
390
+ function buildExportLogMetadata(log, mode) {
391
+ return {
392
+ ...log,
393
+ rawRequestBody: null,
394
+ responseText: null,
395
+ rawHeaders: mode === "raw" ? log.rawHeaders : void 0,
396
+ headers: mode === "raw" ? log.headers : void 0,
397
+ streamingChunks: void 0,
398
+ bodyContentMode: "compact"
399
+ };
400
+ }
401
+ function buildExportManifest(logs, mode, streamingLogs, exportedAt = (/* @__PURE__ */ new Date()).toISOString()) {
402
+ return {
403
+ version: packageJson.version,
404
+ exportedAt,
405
+ mode,
406
+ redacted: mode === "redacted",
407
+ logCount: logs.length,
408
+ logIds: logs.map((log) => log.id),
409
+ streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
410
+ streamingChunkLogIds: streamingLogs.map((log) => log.id)
411
+ };
412
+ }
359
413
  async function exportLogsAsZip(logs, mode = "redacted") {
360
414
  const limitMessage = exportLimitMessage(logs);
361
415
  if (limitMessage !== null) return { ok: false, message: limitMessage };
@@ -364,16 +418,12 @@ async function exportLogsAsZip(logs, mode = "redacted") {
364
418
  const streamingLogs = logs.filter((log) => log.streaming).slice(0, MAX_STREAMING_CHUNK_EXPORTS);
365
419
  zip.file(
366
420
  "manifest.json",
421
+ JSON.stringify(buildExportManifest(logs, mode, streamingLogs), null, 2)
422
+ );
423
+ zip.file(
424
+ "logs.json",
367
425
  JSON.stringify(
368
- {
369
- exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
370
- mode,
371
- redacted: mode === "redacted",
372
- logCount: logs.length,
373
- logIds: logs.map((log) => log.id),
374
- streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
375
- streamingChunkLogIds: streamingLogs.map((log) => log.id)
376
- },
426
+ logs.map((log) => buildExportLogMetadata(log, mode)),
377
427
  null,
378
428
  2
379
429
  )
@@ -458,7 +508,7 @@ async function copyTextToClipboard(text) {
458
508
  }
459
509
  return copyWithTextareaFallback(text);
460
510
  }
461
- function formatTimestamp$1(iso, format) {
511
+ function formatTimestamp$2(iso, format) {
462
512
  switch (format) {
463
513
  case "full":
464
514
  return iso;
@@ -471,7 +521,7 @@ function formatTimestamp$1(iso, format) {
471
521
  }
472
522
  }
473
523
  function formatTimestampRange(startedAt, endedAt, format) {
474
- return `${formatTimestamp$1(startedAt, format)} - ${formatTimestamp$1(endedAt, format)}`;
524
+ return `${formatTimestamp$2(startedAt, format)} - ${formatTimestamp$2(endedAt, format)}`;
475
525
  }
476
526
  function cn(...inputs) {
477
527
  return twMerge(clsx(inputs));
@@ -675,6 +725,19 @@ function DialogTitle({
675
725
  }
676
726
  );
677
727
  }
728
+ function DialogDescription({
729
+ className,
730
+ ...props
731
+ }) {
732
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
733
+ Description,
734
+ {
735
+ "data-slot": "dialog-description",
736
+ className: cn("text-sm text-muted-foreground", className),
737
+ ...props
738
+ }
739
+ );
740
+ }
678
741
  const buttonVariants = cva(
679
742
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
680
743
  {
@@ -1926,19 +1989,19 @@ function useCopyFeedback(text) {
1926
1989
  return { copied, copy };
1927
1990
  }
1928
1991
  const LazyCompareDrawer = reactExports.lazy(
1929
- () => import("./CompareDrawer-D44_F13_.mjs").then((m) => ({ default: m.CompareDrawer }))
1992
+ () => import("./CompareDrawer-DqQHO4tk.mjs").then((m) => ({ default: m.CompareDrawer }))
1930
1993
  );
1931
1994
  const LazyReplayDialog = reactExports.lazy(
1932
- () => import("./ReplayDialog-wScCF3Oc.mjs").then((m) => ({ default: m.ReplayDialog }))
1995
+ () => import("./ReplayDialog-DUZbsd5x.mjs").then((m) => ({ default: m.ReplayDialog }))
1933
1996
  );
1934
1997
  const LazyRequestAnatomy = reactExports.lazy(
1935
- () => import("./RequestAnatomy-Cf7R7PXS.mjs").then((m) => ({ default: m.RequestAnatomy }))
1998
+ () => import("./RequestAnatomy-DKuIxoBj.mjs").then((m) => ({ default: m.RequestAnatomy }))
1936
1999
  );
1937
2000
  const LazyResponseView = reactExports.lazy(
1938
- () => import("./ResponseView-7pVIZLL-.mjs").then((m) => ({ default: m.ResponseView }))
2001
+ () => import("./ResponseView-BB9CYmTJ.mjs").then((m) => ({ default: m.ResponseView }))
1939
2002
  );
1940
2003
  const LazyStreamingChunkSequence = reactExports.lazy(
1941
- () => import("./StreamingChunkSequence-Bs57_GbC.mjs").then((m) => ({
2004
+ () => import("./StreamingChunkSequence-CkY3uTjF.mjs").then((m) => ({
1942
2005
  default: m.StreamingChunkSequence
1943
2006
  }))
1944
2007
  );
@@ -2411,7 +2474,7 @@ const LogEntryHeader = reactExports.memo(function({
2411
2474
  ] }),
2412
2475
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
2413
2476
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
2414
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", title: log.timestamp, children: formatTimestamp$1(log.timestamp, timeDisplayFormat) })
2477
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", title: log.timestamp, children: formatTimestamp$2(log.timestamp, timeDisplayFormat) })
2415
2478
  ] }),
2416
2479
  log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2417
2480
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4" }) }) }),
@@ -3505,10 +3568,53 @@ function resolveFocusedTab(tab, anatomySegments) {
3505
3568
  return "request";
3506
3569
  }
3507
3570
  }
3571
+ const FULL_LOG_HYDRATION_TIMEOUT_MS = 15e3;
3572
+ const BODY_CHUNK_TIMEOUT_MS = 15e3;
3573
+ const BODY_CHUNK_BYTES = 256 * 1024;
3574
+ const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
3508
3575
  const TAB_TRIGGER_CLASS = "h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none hover:bg-cyan-400/[0.04] hover:text-foreground data-[state=active]:border-cyan-300 data-[state=active]:bg-transparent data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
3509
- function shouldHydrateLogBody(log) {
3576
+ function createEmptyBodyPreviewState(totalBytes = null) {
3577
+ return {
3578
+ status: "idle",
3579
+ text: "",
3580
+ offset: 0,
3581
+ totalBytes,
3582
+ hasMore: totalBytes !== null && totalBytes > 0,
3583
+ error: null
3584
+ };
3585
+ }
3586
+ function bodyByteCount(value) {
3587
+ return value ?? 0;
3588
+ }
3589
+ function isCompactBody(log) {
3510
3590
  return log.bodyContentMode === "compact" || log.bodyContentMode === "truncated";
3511
3591
  }
3592
+ function shouldUseChunkedBodyLoading(log) {
3593
+ if (!isCompactBody(log)) return false;
3594
+ return bodyByteCount(log.rawRequestBodyBytes) > CHUNKED_BODY_THRESHOLD_BYTES || bodyByteCount(log.responseTextBytes) > CHUNKED_BODY_THRESHOLD_BYTES;
3595
+ }
3596
+ function shouldHydrateLogBody(log) {
3597
+ return isCompactBody(log) && !shouldUseChunkedBodyLoading(log);
3598
+ }
3599
+ function formatBytes$1(bytes) {
3600
+ if (bytes < 1024) return `${bytes.toLocaleString()} B`;
3601
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
3602
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
3603
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GiB`;
3604
+ }
3605
+ function bodyPreviewErrorMessage(error) {
3606
+ if (error instanceof ApiTimeoutError) {
3607
+ return "Body preview timed out. The log is still available on disk; try loading again.";
3608
+ }
3609
+ if (error instanceof Error) return error.message;
3610
+ return "Body preview could not be loaded.";
3611
+ }
3612
+ function bodyPreviewTextForView(enabled, state) {
3613
+ if (!enabled) return null;
3614
+ if (state.text !== "") return state.text;
3615
+ if (state.status === "success") return "";
3616
+ return null;
3617
+ }
3512
3618
  function ExpandedPanel({
3513
3619
  children,
3514
3620
  className
@@ -3554,6 +3660,41 @@ function NoticeBlock({
3554
3660
  }
3555
3661
  );
3556
3662
  }
3663
+ function BodyPreviewNotice({
3664
+ label,
3665
+ state,
3666
+ expectedBytes,
3667
+ onLoadMore
3668
+ }) {
3669
+ const totalBytes = state.totalBytes ?? expectedBytes;
3670
+ const loadedLabel = totalBytes === null ? formatBytes$1(state.offset) : `${formatBytes$1(state.offset)} / ${formatBytes$1(totalBytes)}`;
3671
+ const showLoadMore = state.status !== "loading" && state.hasMore;
3672
+ const actionLabel = state.status === "failed" ? "Retry" : "Load more";
3673
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3674
+ NoticeBlock,
3675
+ {
3676
+ tone: state.status === "failed" ? "warning" : "loading",
3677
+ title: `${label} body preview`,
3678
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
3679
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
3680
+ state.status === "idle" && totalBytes !== null ? `${label} body is ${formatBytes$1(totalBytes)}. Loading the first preview chunk.` : `Loaded ${loadedLabel}.`,
3681
+ state.status === "failed" && state.error !== null && ` ${state.error}`
3682
+ ] }),
3683
+ showLoadMore && /* @__PURE__ */ jsxRuntimeExports.jsx(
3684
+ Button,
3685
+ {
3686
+ type: "button",
3687
+ variant: "outline",
3688
+ size: "sm",
3689
+ className: "h-7 border-white/10 bg-black/20 text-xs text-cyan-100 hover:border-cyan-300/35 hover:bg-cyan-400/10",
3690
+ onClick: onLoadMore,
3691
+ children: actionLabel
3692
+ }
3693
+ )
3694
+ ] })
3695
+ }
3696
+ );
3697
+ }
3557
3698
  function HeaderRows({
3558
3699
  headers,
3559
3700
  emptyLabel
@@ -3633,8 +3774,24 @@ const LogEntry = reactExports.memo(function({
3633
3774
  const [expandToPath, setExpandToPath] = reactExports.useState(null);
3634
3775
  const [hydratedLog, setHydratedLog] = reactExports.useState(null);
3635
3776
  const [bodyHydrationState, setBodyHydrationState] = reactExports.useState("idle");
3777
+ const [requestPreview, setRequestPreview] = reactExports.useState(
3778
+ () => createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null)
3779
+ );
3780
+ const [responsePreview, setResponsePreview] = reactExports.useState(
3781
+ () => createEmptyBodyPreviewState(log.responseTextBytes ?? null)
3782
+ );
3636
3783
  const requestJsonRef = reactExports.useRef(null);
3784
+ const logIdRef = reactExports.useRef(log.id);
3637
3785
  const displayLog = hydratedLog ?? log;
3786
+ const useChunkedBody = hydratedLog === null && shouldUseChunkedBodyLoading(log);
3787
+ const requestPreviewBody = bodyPreviewTextForView(useChunkedBody, requestPreview);
3788
+ const responsePreviewBody = bodyPreviewTextForView(useChunkedBody, responsePreview);
3789
+ const rawRequestBodyForView = displayLog.rawRequestBody ?? requestPreviewBody;
3790
+ const responseTextForView = displayLog.responseText ?? responsePreviewBody;
3791
+ const requestPreviewIsActive = displayLog.rawRequestBody === null && requestPreviewBody !== null;
3792
+ const responsePreviewIsActive = displayLog.responseText === null && responsePreviewBody !== null;
3793
+ const requestPreviewShouldParse = !requestPreviewIsActive || requestPreview.hasMore === false && bodyByteCount(requestPreview.totalBytes) <= CHUNKED_BODY_THRESHOLD_BYTES;
3794
+ const responsePreviewShouldParse = !responsePreviewIsActive || responsePreview.hasMore === false && bodyByteCount(responsePreview.totalBytes) <= CHUNKED_BODY_THRESHOLD_BYTES;
3638
3795
  const resolvedFormat = resolveLogFormat(displayLog);
3639
3796
  const adapter = getLogFormatAdapter(resolvedFormat);
3640
3797
  const requestAnalysis = reactExports.useMemo(
@@ -3651,10 +3808,16 @@ const LogEntry = reactExports.memo(function({
3651
3808
  }
3652
3809
  return stripClaudeCodeBillingHeader(displayLog.rawRequestBody).body;
3653
3810
  }, [displayLog.rawRequestBody, resolvedFormat, strip]);
3654
- const displayedRequestBody = strippedRequestBody ?? displayLog.rawRequestBody;
3655
- const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
3656
- const rawRequestExpansion = useJsonBulkExpansion(displayLog.rawRequestBody);
3657
- const responseExpansion = useJsonBulkExpansion(displayLog.responseText);
3811
+ const displayedRequestBody = strippedRequestBody ?? rawRequestBodyForView;
3812
+ const requestExpansion = useJsonBulkExpansion(
3813
+ requestPreviewShouldParse ? displayedRequestBody : null
3814
+ );
3815
+ const rawRequestExpansion = useJsonBulkExpansion(
3816
+ requestPreviewShouldParse ? rawRequestBodyForView : null
3817
+ );
3818
+ const responseExpansion = useJsonBulkExpansion(
3819
+ responsePreviewShouldParse ? responseTextForView : null
3820
+ );
3658
3821
  const headersText = reactExports.useMemo(
3659
3822
  () => displayLog.headers && Object.keys(displayLog.headers).length > 0 ? JSON.stringify(displayLog.headers, null, 2) : null,
3660
3823
  [displayLog.headers]
@@ -3664,22 +3827,22 @@ const LogEntry = reactExports.memo(function({
3664
3827
  [displayLog.rawHeaders]
3665
3828
  );
3666
3829
  const requestCopy = useCopyFeedback(displayedRequestBody);
3667
- const rawRequestCopy = useCopyFeedback(displayLog.rawRequestBody);
3830
+ const rawRequestCopy = useCopyFeedback(rawRequestBodyForView);
3668
3831
  const headersCopy = useCopyFeedback(headersText);
3669
3832
  const rawHeadersCopy = useCopyFeedback(rawHeadersText);
3670
- const responseCopy = useCopyFeedback(displayLog.responseText);
3833
+ const responseCopy = useCopyFeedback(responseTextForView);
3671
3834
  const tabActions = reactExports.useMemo(
3672
3835
  () => ({
3673
3836
  request: {
3674
- copyLabel: "Copy request body",
3837
+ copyLabel: requestPreviewIsActive ? "Copy loaded request preview" : "Copy request body",
3675
3838
  copyText: displayedRequestBody,
3676
3839
  copyCopied: requestCopy.copied,
3677
3840
  onCopy: requestCopy.copy,
3678
- expansion: {
3841
+ expansion: requestPreviewShouldParse ? {
3679
3842
  isExpanded: requestExpansion.isExpanded,
3680
3843
  isPending: requestExpansion.isPending,
3681
3844
  onToggle: requestExpansion.toggle
3682
- },
3845
+ } : null,
3683
3846
  // "Diff with Raw" only makes sense when there's a raw body to compare
3684
3847
  // against (Anthropic + strip pipeline produces one). "Diff with
3685
3848
  // Previous" only exists when the parent wired up the compare drawer.
@@ -3694,15 +3857,15 @@ const LogEntry = reactExports.memo(function({
3694
3857
  }
3695
3858
  },
3696
3859
  "raw-request": {
3697
- copyLabel: "Copy raw request",
3698
- copyText: displayLog.rawRequestBody,
3860
+ copyLabel: requestPreviewIsActive ? "Copy loaded raw request preview" : "Copy raw request",
3861
+ copyText: rawRequestBodyForView,
3699
3862
  copyCopied: rawRequestCopy.copied,
3700
3863
  onCopy: rawRequestCopy.copy,
3701
- expansion: {
3864
+ expansion: requestPreviewShouldParse ? {
3702
3865
  isExpanded: rawRequestExpansion.isExpanded,
3703
3866
  isPending: rawRequestExpansion.isPending,
3704
3867
  onToggle: rawRequestExpansion.toggle
3705
- }
3868
+ } : null
3706
3869
  },
3707
3870
  headers: {
3708
3871
  copyLabel: "Copy headers",
@@ -3720,24 +3883,27 @@ const LogEntry = reactExports.memo(function({
3720
3883
  expansion: null
3721
3884
  },
3722
3885
  raw: {
3723
- copyLabel: "Copy response",
3724
- copyText: displayLog.responseText,
3886
+ copyLabel: responsePreviewIsActive ? "Copy loaded response preview" : "Copy response",
3887
+ copyText: responseTextForView,
3725
3888
  copyCopied: responseCopy.copied,
3726
3889
  onCopy: responseCopy.copy,
3727
- expansion: {
3890
+ expansion: responsePreviewShouldParse ? {
3728
3891
  isExpanded: responseExpansion.isExpanded,
3729
3892
  isPending: responseExpansion.isPending,
3730
3893
  onToggle: responseExpansion.toggle
3731
- }
3894
+ } : null
3732
3895
  }
3733
3896
  }),
3734
3897
  [
3735
3898
  displayedRequestBody,
3899
+ requestPreviewIsActive,
3900
+ requestPreviewShouldParse,
3736
3901
  requestCopy,
3737
3902
  requestExpansion,
3738
3903
  requestDiff,
3739
3904
  displayLog,
3740
3905
  displayLog.rawRequestBody,
3906
+ rawRequestBodyForView,
3741
3907
  rawRequestCopy,
3742
3908
  rawRequestExpansion,
3743
3909
  headersText,
@@ -3745,6 +3911,9 @@ const LogEntry = reactExports.memo(function({
3745
3911
  rawHeadersText,
3746
3912
  rawHeadersCopy,
3747
3913
  displayLog.responseText,
3914
+ responsePreviewIsActive,
3915
+ responsePreviewShouldParse,
3916
+ responseTextForView,
3748
3917
  responseCopy,
3749
3918
  responseExpansion,
3750
3919
  resolvedFormat,
@@ -3773,6 +3942,44 @@ const LogEntry = reactExports.memo(function({
3773
3942
  if (activeTab !== "request") setActiveTab("request");
3774
3943
  }
3775
3944
  });
3945
+ const loadBodyChunk = reactExports.useCallback(
3946
+ (part, offset) => {
3947
+ const setPreview = part === "request" ? setRequestPreview : setResponsePreview;
3948
+ setPreview((current) => ({
3949
+ ...current,
3950
+ status: "loading",
3951
+ error: null
3952
+ }));
3953
+ const params = new URLSearchParams({
3954
+ part,
3955
+ offset: String(offset),
3956
+ limit: String(BODY_CHUNK_BYTES)
3957
+ });
3958
+ void fetchJsonWithTimeout(
3959
+ `/api/logs/${String(log.id)}/body?${params.toString()}`,
3960
+ LogBodyChunkSchema,
3961
+ BODY_CHUNK_TIMEOUT_MS
3962
+ ).then((chunk) => {
3963
+ if (logIdRef.current !== log.id) return;
3964
+ setPreview((current) => ({
3965
+ status: "success",
3966
+ text: chunk.offset === 0 ? chunk.text : `${current.text}${chunk.text}`,
3967
+ offset: chunk.nextOffset ?? chunk.totalBytes,
3968
+ totalBytes: chunk.totalBytes,
3969
+ hasMore: chunk.hasMore,
3970
+ error: null
3971
+ }));
3972
+ }).catch((error) => {
3973
+ if (logIdRef.current !== log.id) return;
3974
+ setPreview((current) => ({
3975
+ ...current,
3976
+ status: "failed",
3977
+ error: bodyPreviewErrorMessage(error)
3978
+ }));
3979
+ });
3980
+ },
3981
+ [log.id]
3982
+ );
3776
3983
  const handleToggleExpanded = reactExports.useCallback(() => {
3777
3984
  const nextExpanded = !expanded;
3778
3985
  if (nextExpanded) {
@@ -3781,16 +3988,23 @@ const LogEntry = reactExports.memo(function({
3781
3988
  setExpanded(nextExpanded);
3782
3989
  }, [anatomySegments, expanded]);
3783
3990
  reactExports.useEffect(() => {
3991
+ logIdRef.current = log.id;
3784
3992
  setHydratedLog(null);
3785
3993
  setBodyHydrationState("idle");
3786
- }, [log.id]);
3994
+ setRequestPreview(createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null));
3995
+ setResponsePreview(createEmptyBodyPreviewState(log.responseTextBytes ?? null));
3996
+ }, [log.id, log.rawRequestBodyBytes, log.responseTextBytes]);
3787
3997
  reactExports.useEffect(() => {
3788
3998
  if (!expanded) return;
3789
3999
  if (!shouldHydrateLogBody(log)) return;
3790
4000
  if (hydratedLog !== null || bodyHydrationState !== "idle") return;
3791
4001
  let cancelled = false;
3792
4002
  setBodyHydrationState("loading");
3793
- void fetchJson(`/api/logs/${String(log.id)}`, CapturedLogSchema).then((fullLog) => {
4003
+ void fetchJsonWithTimeout(
4004
+ `/api/logs/${String(log.id)}`,
4005
+ CapturedLogSchema,
4006
+ FULL_LOG_HYDRATION_TIMEOUT_MS
4007
+ ).then((fullLog) => {
3794
4008
  if (cancelled) return;
3795
4009
  setHydratedLog(fullLog);
3796
4010
  setBodyHydrationState("idle");
@@ -3801,7 +4015,29 @@ const LogEntry = reactExports.memo(function({
3801
4015
  return () => {
3802
4016
  cancelled = true;
3803
4017
  };
3804
- }, [bodyHydrationState, expanded, hydratedLog, log.bodyContentMode, log.id]);
4018
+ }, [
4019
+ bodyHydrationState,
4020
+ expanded,
4021
+ hydratedLog,
4022
+ log.bodyContentMode,
4023
+ log.id,
4024
+ log.rawRequestBodyBytes,
4025
+ log.responseTextBytes
4026
+ ]);
4027
+ reactExports.useEffect(() => {
4028
+ if (!expanded) return;
4029
+ if (!useChunkedBody) return;
4030
+ if (requestPreview.status !== "idle") return;
4031
+ if (activeTab !== "request" && activeTab !== "raw-request" && activeTab !== "anatomy") return;
4032
+ loadBodyChunk("request", 0);
4033
+ }, [activeTab, expanded, loadBodyChunk, requestPreview.status, useChunkedBody]);
4034
+ reactExports.useEffect(() => {
4035
+ if (!expanded) return;
4036
+ if (!useChunkedBody) return;
4037
+ if (responsePreview.status !== "idle") return;
4038
+ if (activeTab !== "parsed" && activeTab !== "raw") return;
4039
+ loadBodyChunk("response", 0);
4040
+ }, [activeTab, expanded, loadBodyChunk, responsePreview.status, useChunkedBody]);
3805
4041
  reactExports.useEffect(() => {
3806
4042
  const handleLogFocusRequest = (event) => {
3807
4043
  const request = readLogFocusRequest(event);
@@ -3865,17 +4101,37 @@ const LogEntry = reactExports.memo(function({
3865
4101
  ]
3866
4102
  }
3867
4103
  ),
3868
- shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedPanel, { children: displayLog.rawRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: "No request body" }) : rawRequestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3869
- LazyJsonViewer,
3870
- {
3871
- data: rawRequestExpansion.parsedData,
3872
- bulkDepth: rawRequestExpansion.bulkDepth,
3873
- bulkRevision: rawRequestExpansion.bulkRevision
3874
- }
3875
- ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: displayLog.rawRequestBody }) }) }),
4104
+ shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { children: [
4105
+ useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
4106
+ BodyPreviewNotice,
4107
+ {
4108
+ label: "Request",
4109
+ state: requestPreview,
4110
+ expectedBytes: log.rawRequestBodyBytes ?? null,
4111
+ onLoadMore: () => loadBodyChunk("request", requestPreview.offset)
4112
+ }
4113
+ ),
4114
+ rawRequestBodyForView === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: useChunkedBody ? "Request preview is loading" : "No request body" }) : rawRequestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4115
+ LazyJsonViewer,
4116
+ {
4117
+ data: rawRequestExpansion.parsedData,
4118
+ bulkDepth: rawRequestExpansion.bulkDepth,
4119
+ bulkRevision: rawRequestExpansion.bulkRevision
4120
+ }
4121
+ ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: rawRequestBodyForView })
4122
+ ] }) }),
3876
4123
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { children: [
3877
4124
  bodyHydrationState === "loading" && /* @__PURE__ */ jsxRuntimeExports.jsx(NoticeBlock, { tone: "loading", children: "Loading full request and response bodies..." }),
3878
4125
  bodyHydrationState === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx(NoticeBlock, { tone: "warning", children: "Full request and response bodies could not be loaded. Compact metadata is still available." }),
4126
+ useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
4127
+ BodyPreviewNotice,
4128
+ {
4129
+ label: "Request",
4130
+ state: requestPreview,
4131
+ expectedBytes: log.rawRequestBodyBytes ?? null,
4132
+ onLoadMore: () => loadBodyChunk("request", requestPreview.offset)
4133
+ }
4134
+ ),
3879
4135
  warnings.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(NoticeBlock, { tone: "warning", children: [
3880
4136
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 font-semibold text-amber-300", children: [
3881
4137
  /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" }),
@@ -3897,7 +4153,7 @@ const LogEntry = reactExports.memo(function({
3897
4153
  displayedBody: displayedRequestBody,
3898
4154
  emptyLabel: "No transformation applied; raw and sent request bodies are identical."
3899
4155
  }
3900
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: requestJsonRef, children: displayedRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: "No request body" }) : requestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4156
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: requestJsonRef, children: displayedRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: useChunkedBody ? "Request preview is loading" : "No request body" }) : requestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3901
4157
  LazyJsonViewer,
3902
4158
  {
3903
4159
  data: requestExpansion.parsedData,
@@ -3949,16 +4205,25 @@ const LogEntry = reactExports.memo(function({
3949
4205
  }
3950
4206
  ) }) }),
3951
4207
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: activeTab === "raw" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { className: "space-y-3", children: [
4208
+ useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
4209
+ BodyPreviewNotice,
4210
+ {
4211
+ label: "Response",
4212
+ state: responsePreview,
4213
+ expectedBytes: log.responseTextBytes ?? null,
4214
+ onLoadMore: () => loadBodyChunk("response", responsePreview.offset)
4215
+ }
4216
+ ),
3952
4217
  displayLog.error !== void 0 && displayLog.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(NoticeBlock, { tone: "danger", title: "SSE Error", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: displayLog.error }) }),
3953
- displayLog.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4218
+ responseTextForView !== null && responsePreviewShouldParse ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3954
4219
  LazyJsonViewerFromString,
3955
4220
  {
3956
- text: displayLog.responseText,
4221
+ text: responseTextForView,
3957
4222
  defaultExpandDepth: 0,
3958
4223
  bulkDepth: responseExpansion.bulkDepth,
3959
4224
  bulkRevision: responseExpansion.bulkRevision
3960
4225
  }
3961
- ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: "No response" }),
4226
+ ) }) : responseTextForView !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: responseTextForView }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: useChunkedBody ? "Response preview is loading" : "No response" }),
3962
4227
  displayLog.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3963
4228
  LazyStreamingChunkSequence,
3964
4229
  {
@@ -3967,20 +4232,31 @@ const LogEntry = reactExports.memo(function({
3967
4232
  }
3968
4233
  ) })
3969
4234
  ] }) }),
3970
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedPanel, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3971
- LazyResponseView,
3972
- {
3973
- responseText: displayLog.responseText,
3974
- responseStatus: displayLog.responseStatus,
3975
- streaming: displayLog.streaming,
3976
- inputTokens: displayLog.inputTokens,
3977
- outputTokens: displayLog.outputTokens,
3978
- cacheCreationInputTokens: displayLog.cacheCreationInputTokens,
3979
- cacheReadInputTokens: displayLog.cacheReadInputTokens,
3980
- apiFormat: resolvedFormat,
3981
- error: displayLog.error
3982
- }
3983
- ) }) }) })
4235
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { children: [
4236
+ useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
4237
+ BodyPreviewNotice,
4238
+ {
4239
+ label: "Response",
4240
+ state: responsePreview,
4241
+ expectedBytes: log.responseTextBytes ?? null,
4242
+ onLoadMore: () => loadBodyChunk("response", responsePreview.offset)
4243
+ }
4244
+ ),
4245
+ responsePreviewIsActive && !responsePreviewShouldParse ? /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: responseTextForView }) : /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4246
+ LazyResponseView,
4247
+ {
4248
+ responseText: responseTextForView,
4249
+ responseStatus: displayLog.responseStatus,
4250
+ streaming: displayLog.streaming,
4251
+ inputTokens: displayLog.inputTokens,
4252
+ outputTokens: displayLog.outputTokens,
4253
+ cacheCreationInputTokens: displayLog.cacheCreationInputTokens,
4254
+ cacheReadInputTokens: displayLog.cacheReadInputTokens,
4255
+ apiFormat: resolvedFormat,
4256
+ error: displayLog.error
4257
+ }
4258
+ ) })
4259
+ ] }) })
3984
4260
  ] })
3985
4261
  }
3986
4262
  )
@@ -5562,12 +5838,562 @@ function SelectScrollDownButton({
5562
5838
  }
5563
5839
  );
5564
5840
  }
5841
+ const ALERT_SUMMARY_FETCH_TIMEOUT_MS = 8e3;
5842
+ const ALERT_LIST_FETCH_TIMEOUT_MS = 1e4;
5843
+ function alertsPath(filters) {
5844
+ const params = new URLSearchParams();
5845
+ if (filters.severity !== void 0) params.set("severity", filters.severity);
5846
+ if (filters.category !== void 0) params.set("category", filters.category);
5847
+ if (filters.source !== void 0) params.set("source", filters.source);
5848
+ if (filters.limit !== void 0) params.set("limit", String(filters.limit));
5849
+ const query = params.toString();
5850
+ return query.length > 0 ? `/api/alerts?${query}` : "/api/alerts";
5851
+ }
5852
+ function useAlerts(filters, enabled) {
5853
+ const response = useSWR(
5854
+ enabled ? alertsPath(filters) : null,
5855
+ (url) => fetchJsonWithTimeout(url, AlertListResponseSchema, ALERT_LIST_FETCH_TIMEOUT_MS)
5856
+ );
5857
+ return {
5858
+ alerts: response.data?.alerts ?? [],
5859
+ total: response.data?.total ?? 0,
5860
+ summary: response.data?.summary ?? null,
5861
+ isLoading: response.isLoading,
5862
+ error: response.error,
5863
+ mutate: response.mutate
5864
+ };
5865
+ }
5866
+ function useAlertSummary() {
5867
+ const response = useSWR(
5868
+ "/api/alerts/summary",
5869
+ (url) => fetchJsonWithTimeout(url, AlertSummarySchema, ALERT_SUMMARY_FETCH_TIMEOUT_MS),
5870
+ {
5871
+ refreshInterval: 5e3,
5872
+ revalidateOnFocus: true
5873
+ }
5874
+ );
5875
+ return {
5876
+ summary: response.data ?? null,
5877
+ isLoading: response.isLoading,
5878
+ error: response.error,
5879
+ mutate: response.mutate
5880
+ };
5881
+ }
5882
+ const ALERT_CATEGORIES = [
5883
+ "request-failure",
5884
+ "request-timeout",
5885
+ "slow-response",
5886
+ "tool-schema",
5887
+ "run-failure",
5888
+ "group-failure",
5889
+ "group-incomplete"
5890
+ ];
5891
+ function severityLabel(severity) {
5892
+ switch (severity) {
5893
+ case "critical":
5894
+ return "Critical";
5895
+ case "warning":
5896
+ return "Warning";
5897
+ case "notice":
5898
+ return "Notice";
5899
+ }
5900
+ }
5901
+ function sourceLabel(source) {
5902
+ switch (source) {
5903
+ case "proxy":
5904
+ return "Proxy";
5905
+ case "tool-schema":
5906
+ return "Tool schema";
5907
+ case "run":
5908
+ return "Run";
5909
+ case "group":
5910
+ return "Group";
5911
+ }
5912
+ }
5913
+ function categoryLabel(category) {
5914
+ switch (category) {
5915
+ case "request-failure":
5916
+ return "Request failure";
5917
+ case "request-timeout":
5918
+ return "Timeout";
5919
+ case "slow-response":
5920
+ return "Slow response";
5921
+ case "tool-schema":
5922
+ return "Tool calling";
5923
+ case "run-failure":
5924
+ return "Run failure";
5925
+ case "group-failure":
5926
+ return "Group failure";
5927
+ case "group-incomplete":
5928
+ return "Group incomplete";
5929
+ }
5930
+ }
5931
+ function severityAccent(severity) {
5932
+ switch (severity) {
5933
+ case "critical":
5934
+ return "border-red-400/40 bg-red-500/12 text-red-100";
5935
+ case "warning":
5936
+ return "border-amber-300/35 bg-amber-400/12 text-amber-100";
5937
+ case "notice":
5938
+ return "border-sky-300/35 bg-sky-400/12 text-sky-100";
5939
+ }
5940
+ }
5941
+ function severityDot(severity) {
5942
+ switch (severity) {
5943
+ case "critical":
5944
+ return "bg-red-400";
5945
+ case "warning":
5946
+ return "bg-amber-300";
5947
+ case "notice":
5948
+ return "bg-sky-300";
5949
+ }
5950
+ }
5951
+ function severityText(severity) {
5952
+ switch (severity) {
5953
+ case "critical":
5954
+ return "text-red-200";
5955
+ case "warning":
5956
+ return "text-amber-100";
5957
+ case "notice":
5958
+ return "text-sky-100";
5959
+ }
5960
+ }
5961
+ function categoryIcon(category) {
5962
+ switch (category) {
5963
+ case "request-failure":
5964
+ case "run-failure":
5965
+ case "group-failure":
5966
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldAlert, { className: "size-3.5" });
5967
+ case "request-timeout":
5968
+ case "slow-response":
5969
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Clock3, { className: "size-3.5" });
5970
+ case "tool-schema":
5971
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" });
5972
+ case "group-incomplete":
5973
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Bell, { className: "size-3.5" });
5974
+ }
5975
+ }
5976
+ function formatTimestamp$1(value) {
5977
+ const millis = Date.parse(value);
5978
+ if (!Number.isFinite(millis)) return value;
5979
+ return new Intl.DateTimeFormat(void 0, {
5980
+ month: "short",
5981
+ day: "2-digit",
5982
+ hour: "2-digit",
5983
+ minute: "2-digit",
5984
+ hour12: false
5985
+ }).format(new Date(millis));
5986
+ }
5987
+ function formatCount(value) {
5988
+ return value.toLocaleString();
5989
+ }
5990
+ function providerModel$1(alert) {
5991
+ if (alert.provider !== null && alert.model !== null) return `${alert.provider} / ${alert.model}`;
5992
+ if (alert.provider !== null) return alert.provider;
5993
+ if (alert.model !== null) return alert.model;
5994
+ return "Unknown target";
5995
+ }
5996
+ function filtersFrom(severity, category) {
5997
+ const filters = { limit: 100 };
5998
+ if (severity !== "all") filters.severity = severity;
5999
+ if (category !== "all") filters.category = category;
6000
+ return filters;
6001
+ }
6002
+ function selectedAlertFrom(alerts, selectedAlertId) {
6003
+ if (selectedAlertId !== null) {
6004
+ const selected = alerts.find((alert) => alert.id === selectedAlertId);
6005
+ if (selected !== void 0) return selected;
6006
+ }
6007
+ return alerts[0] ?? null;
6008
+ }
6009
+ function AlertSummaryPill({
6010
+ label,
6011
+ count,
6012
+ tone
6013
+ }) {
6014
+ const toneClass = tone === "critical" ? "border-red-400/30 bg-red-500/10 text-red-100" : tone === "warning" ? "border-amber-300/30 bg-amber-400/10 text-amber-100" : tone === "notice" ? "border-sky-300/30 bg-sky-400/10 text-sky-100" : "border-border bg-muted/40 text-foreground";
6015
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("rounded-md border px-3 py-2", toneClass), children: [
6016
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-semibold uppercase opacity-75", children: label }),
6017
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-lg font-semibold leading-none", children: formatCount(count) })
6018
+ ] });
6019
+ }
6020
+ function SeverityFilterButton({
6021
+ value,
6022
+ active,
6023
+ label,
6024
+ count,
6025
+ onClick
6026
+ }) {
6027
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6028
+ Button,
6029
+ {
6030
+ type: "button",
6031
+ variant: active ? "secondary" : "ghost",
6032
+ size: "sm",
6033
+ "aria-pressed": active,
6034
+ onClick: () => onClick(value),
6035
+ className: "h-7 px-2",
6036
+ children: [
6037
+ label,
6038
+ count !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatCount(count) }) : null
6039
+ ]
6040
+ }
6041
+ );
6042
+ }
6043
+ function CategoryFilterButton({
6044
+ category,
6045
+ active,
6046
+ count,
6047
+ onClick
6048
+ }) {
6049
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6050
+ Button,
6051
+ {
6052
+ type: "button",
6053
+ variant: active ? "secondary" : "ghost",
6054
+ size: "sm",
6055
+ "aria-pressed": active,
6056
+ onClick: () => onClick(category),
6057
+ className: "h-7 px-2",
6058
+ children: [
6059
+ categoryIcon(category),
6060
+ categoryLabel(category),
6061
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatCount(count) })
6062
+ ]
6063
+ }
6064
+ );
6065
+ }
6066
+ function AlertList({
6067
+ alerts,
6068
+ selectedAlertId,
6069
+ isLoading,
6070
+ error,
6071
+ onSelect
6072
+ }) {
6073
+ if (isLoading) {
6074
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed border-border text-xs text-muted-foreground", children: [
6075
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "mr-2 size-3.5 animate-spin" }),
6076
+ "Loading alerts..."
6077
+ ] });
6078
+ }
6079
+ if (error !== void 0) {
6080
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-red-300/30 bg-red-500/10 px-3 py-2 text-xs text-red-100", children: error.message });
6081
+ }
6082
+ if (alerts.length === 0) {
6083
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed border-border px-4 text-center text-xs text-muted-foreground", children: "No alerts match the current filters." });
6084
+ }
6085
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-0 space-y-2 overflow-y-auto pr-1", children: alerts.map((alert) => {
6086
+ const selected = alert.id === selectedAlertId;
6087
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6088
+ "button",
6089
+ {
6090
+ type: "button",
6091
+ onClick: () => onSelect(alert.id),
6092
+ className: cn(
6093
+ "w-full rounded-md border px-3 py-3 text-left transition-colors",
6094
+ selected ? "border-primary/60 bg-primary/12" : "border-border bg-background/70 hover:border-primary/30 hover:bg-muted/60"
6095
+ ),
6096
+ children: [
6097
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
6098
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
6099
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
6100
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("size-2 rounded-full", severityDot(alert.severity)) }),
6101
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-xs font-semibold", severityText(alert.severity)), children: severityLabel(alert.severity) }),
6102
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-xs text-muted-foreground", children: categoryLabel(alert.category) })
6103
+ ] }),
6104
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-sm font-semibold", children: alert.title }),
6105
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 line-clamp-2 text-xs text-muted-foreground", children: alert.message })
6106
+ ] }),
6107
+ alert.count > 1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "shrink-0", children: [
6108
+ "x",
6109
+ formatCount(alert.count)
6110
+ ] }) : null
6111
+ ] }),
6112
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex min-w-0 items-center justify-between gap-2 text-[11px] text-muted-foreground", children: [
6113
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: providerModel$1(alert) }),
6114
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: formatTimestamp$1(alert.lastSeenAt) })
6115
+ ] })
6116
+ ]
6117
+ },
6118
+ alert.id
6119
+ );
6120
+ }) });
6121
+ }
6122
+ function AlertEvidenceLinks({
6123
+ alert,
6124
+ onNavigate
6125
+ }) {
6126
+ if (alert.evidence.length === 0) {
6127
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: "No linked evidence" });
6128
+ }
6129
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-2", children: alert.evidence.map((link) => {
6130
+ if (link.href === null) {
6131
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: link.label }, `${link.kind}:${link.id}`);
6132
+ }
6133
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: link.href, onClick: onNavigate, children: link.label }) }, `${link.kind}:${link.id}`);
6134
+ }) });
6135
+ }
6136
+ function AlertDetails({
6137
+ alert,
6138
+ onNavigate
6139
+ }) {
6140
+ if (alert === null) {
6141
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed border-border px-4 text-center text-xs text-muted-foreground", children: "Select an alert to inspect its evidence." });
6142
+ }
6143
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-h-0 overflow-y-auto rounded-md border border-border bg-background/70", children: [
6144
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("border-b px-4 py-3", severityAccent(alert.severity)), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
6145
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
6146
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
6147
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: severityLabel(alert.severity) }),
6148
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: sourceLabel(alert.source) }),
6149
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: categoryLabel(alert.category) })
6150
+ ] }),
6151
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "mt-3 text-base font-semibold", children: alert.title }),
6152
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 text-sm opacity-85", children: alert.message })
6153
+ ] }),
6154
+ alert.count > 1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "shrink-0", children: [
6155
+ formatCount(alert.count),
6156
+ " hits"
6157
+ ] }) : null
6158
+ ] }) }),
6159
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 p-4", children: [
6160
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 text-xs sm:grid-cols-2", children: [
6161
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "Target", value: providerModel$1(alert) }),
6162
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "Last seen", value: formatTimestamp$1(alert.lastSeenAt) }),
6163
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "First seen", value: formatTimestamp$1(alert.firstSeenAt) }),
6164
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "Protocol", value: alert.apiFormat ?? "n/a" })
6165
+ ] }),
6166
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
6167
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-semibold text-muted-foreground", children: "Evidence" }),
6168
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertEvidenceLinks, { alert, onNavigate })
6169
+ ] }),
6170
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 text-xs sm:grid-cols-2", children: [
6171
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6172
+ DetailRow,
6173
+ {
6174
+ label: "Logs",
6175
+ value: alert.logIds.length === 0 ? "n/a" : formatCount(alert.logIds.length)
6176
+ }
6177
+ ),
6178
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6179
+ DetailRow,
6180
+ {
6181
+ label: "Sessions",
6182
+ value: alert.sessionIds.length === 0 ? "n/a" : formatCount(alert.sessionIds.length)
6183
+ }
6184
+ ),
6185
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6186
+ DetailRow,
6187
+ {
6188
+ label: "Runs",
6189
+ value: alert.runIds.length === 0 ? "n/a" : formatCount(alert.runIds.length)
6190
+ }
6191
+ ),
6192
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6193
+ DetailRow,
6194
+ {
6195
+ label: "Groups",
6196
+ value: alert.groupIds.length === 0 ? "n/a" : formatCount(alert.groupIds.length)
6197
+ }
6198
+ )
6199
+ ] })
6200
+ ] })
6201
+ ] });
6202
+ }
6203
+ function DetailRow({ label, value }) {
6204
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-border bg-muted/30 px-3 py-2", children: [
6205
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-semibold uppercase text-muted-foreground", children: label }),
6206
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate font-mono text-[11px] text-foreground", children: value })
6207
+ ] });
6208
+ }
6209
+ function AlertsDialog() {
6210
+ const [open, setOpen] = reactExports.useState(false);
6211
+ const [severityFilter, setSeverityFilter] = reactExports.useState("all");
6212
+ const [categoryFilter, setCategoryFilter] = reactExports.useState("all");
6213
+ const [selectedAlertId, setSelectedAlertId] = reactExports.useState(null);
6214
+ const { summary, isLoading: summaryLoading } = useAlertSummary();
6215
+ const filters = reactExports.useMemo(
6216
+ () => filtersFrom(severityFilter, categoryFilter),
6217
+ [categoryFilter, severityFilter]
6218
+ );
6219
+ const { alerts, total, isLoading, error, mutate } = useAlerts(filters, open);
6220
+ const selectedAlert = reactExports.useMemo(
6221
+ () => selectedAlertFrom(alerts, selectedAlertId),
6222
+ [alerts, selectedAlertId]
6223
+ );
6224
+ reactExports.useEffect(() => {
6225
+ if (!open) return;
6226
+ const selectedExists = selectedAlertId !== null && alerts.some((alert) => alert.id === selectedAlertId);
6227
+ if (selectedExists) return;
6228
+ setSelectedAlertId(alerts[0]?.id ?? null);
6229
+ }, [alerts, open, selectedAlertId]);
6230
+ const totalCount = summary?.total ?? 0;
6231
+ const criticalCount = summary?.critical ?? 0;
6232
+ const warningCount = summary?.warning ?? 0;
6233
+ const noticeCount = summary?.notice ?? 0;
6234
+ const triggerTone = criticalCount > 0 ? "bg-red-500" : warningCount > 0 ? "bg-amber-400" : "bg-sky-400";
6235
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Dialog, { open, onOpenChange: setOpen, children: [
6236
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
6237
+ Button,
6238
+ {
6239
+ type: "button",
6240
+ variant: "ghost",
6241
+ size: "icon",
6242
+ className: "relative size-8",
6243
+ "aria-label": "Alerts",
6244
+ title: "Alerts",
6245
+ children: [
6246
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Siren, { className: "size-4" }),
6247
+ summaryLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "absolute -right-0.5 -top-0.5 size-3 animate-spin text-muted-foreground" }) : totalCount > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
6248
+ "span",
6249
+ {
6250
+ className: cn(
6251
+ "absolute -right-1 -top-1 min-w-4 rounded-full px-1 text-[10px] font-semibold leading-4 text-white",
6252
+ triggerTone
6253
+ ),
6254
+ children: formatCount(totalCount)
6255
+ }
6256
+ ) : null,
6257
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Alerts" })
6258
+ ]
6259
+ }
6260
+ ) }),
6261
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "flex max-h-[86vh] max-w-6xl flex-col overflow-hidden", children: [
6262
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { children: [
6263
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { className: "flex items-center gap-2 pr-8", children: [
6264
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Siren, { className: "size-5 text-red-300" }),
6265
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Alerts" }),
6266
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatCount(totalCount) })
6267
+ ] }),
6268
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogDescription, { className: "sr-only", children: "Local alert board for captured Inspector evidence." })
6269
+ ] }),
6270
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 sm:grid-cols-4", children: [
6271
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Open", count: summary?.open ?? 0, tone: "neutral" }),
6272
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Critical", count: criticalCount, tone: "critical" }),
6273
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Warning", count: warningCount, tone: "warning" }),
6274
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Notice", count: noticeCount, tone: "notice" })
6275
+ ] }),
6276
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-0 flex-wrap items-center justify-between gap-2 border-y border-border py-3", children: [
6277
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1", children: [
6278
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6279
+ SeverityFilterButton,
6280
+ {
6281
+ value: "all",
6282
+ active: severityFilter === "all",
6283
+ label: "All",
6284
+ count: summary?.total ?? null,
6285
+ onClick: setSeverityFilter
6286
+ }
6287
+ ),
6288
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6289
+ SeverityFilterButton,
6290
+ {
6291
+ value: "critical",
6292
+ active: severityFilter === "critical",
6293
+ label: "Critical",
6294
+ count: criticalCount,
6295
+ onClick: setSeverityFilter
6296
+ }
6297
+ ),
6298
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6299
+ SeverityFilterButton,
6300
+ {
6301
+ value: "warning",
6302
+ active: severityFilter === "warning",
6303
+ label: "Warning",
6304
+ count: warningCount,
6305
+ onClick: setSeverityFilter
6306
+ }
6307
+ ),
6308
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6309
+ SeverityFilterButton,
6310
+ {
6311
+ value: "notice",
6312
+ active: severityFilter === "notice",
6313
+ label: "Notice",
6314
+ count: noticeCount,
6315
+ onClick: setSeverityFilter
6316
+ }
6317
+ )
6318
+ ] }),
6319
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
6320
+ Button,
6321
+ {
6322
+ type: "button",
6323
+ variant: "outline",
6324
+ size: "sm",
6325
+ className: "h-7 shrink-0 px-2",
6326
+ onClick: () => void mutate(),
6327
+ children: [
6328
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "size-3.5" }),
6329
+ "Refresh"
6330
+ ]
6331
+ }
6332
+ )
6333
+ ] }),
6334
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1", children: [
6335
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6336
+ Button,
6337
+ {
6338
+ type: "button",
6339
+ variant: categoryFilter === "all" ? "secondary" : "ghost",
6340
+ size: "sm",
6341
+ "aria-pressed": categoryFilter === "all",
6342
+ onClick: () => setCategoryFilter("all"),
6343
+ className: "h-7 px-2",
6344
+ children: "All categories"
6345
+ }
6346
+ ),
6347
+ ALERT_CATEGORIES.map((category) => {
6348
+ const categoryCount = summary?.byCategory.find((item) => item.category === category)?.count ?? 0;
6349
+ if (categoryCount === 0) return null;
6350
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
6351
+ CategoryFilterButton,
6352
+ {
6353
+ category,
6354
+ active: categoryFilter === category,
6355
+ count: categoryCount,
6356
+ onClick: setCategoryFilter
6357
+ },
6358
+ category
6359
+ );
6360
+ })
6361
+ ] }),
6362
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid min-h-0 flex-1 gap-4 md:grid-cols-[360px_minmax(0,1fr)]", children: [
6363
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-0 flex-col", children: [
6364
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2 flex items-center justify-between text-xs text-muted-foreground", children: [
6365
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
6366
+ formatCount(total),
6367
+ " matched"
6368
+ ] }),
6369
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: alerts.length < total ? `${formatCount(alerts.length)} shown` : "Latest" })
6370
+ ] }),
6371
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
6372
+ AlertList,
6373
+ {
6374
+ alerts,
6375
+ selectedAlertId: selectedAlert?.id ?? null,
6376
+ isLoading,
6377
+ error,
6378
+ onSelect: setSelectedAlertId
6379
+ }
6380
+ )
6381
+ ] }),
6382
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDetails, { alert: selectedAlert, onNavigate: () => setOpen(false) })
6383
+ ] })
6384
+ ] })
6385
+ ] });
6386
+ }
6387
+ const GROUP_EVIDENCE_FETCH_TIMEOUT_MS = 12e3;
5565
6388
  function evidenceKey(group) {
5566
6389
  if (group === null || group.evidence === null) return null;
5567
6390
  return `/api/groups/${encodeURIComponent(group.id)}/evidence`;
5568
6391
  }
5569
6392
  function useGroupEvidence(group) {
5570
- const response = useSWR(evidenceKey(group), (url) => fetchJson(url, GroupEvidenceReadResponseSchema));
6393
+ const response = useSWR(
6394
+ evidenceKey(group),
6395
+ (url) => fetchJsonWithTimeout(url, GroupEvidenceReadResponseSchema, GROUP_EVIDENCE_FETCH_TIMEOUT_MS)
6396
+ );
5571
6397
  return {
5572
6398
  evidenceResponse: response.data ?? null,
5573
6399
  isLoading: response.isLoading,
@@ -5575,8 +6401,12 @@ function useGroupEvidence(group) {
5575
6401
  mutate: response.mutate
5576
6402
  };
5577
6403
  }
6404
+ const GROUPS_FETCH_TIMEOUT_MS = 8e3;
5578
6405
  function useGroups() {
5579
- const response = useSWR("/api/groups", (url) => fetchJson(url, InspectorGroupsListResponseSchema));
6406
+ const response = useSWR(
6407
+ "/api/groups",
6408
+ (url) => fetchJsonWithTimeout(url, InspectorGroupsListResponseSchema, GROUPS_FETCH_TIMEOUT_MS)
6409
+ );
5580
6410
  return {
5581
6411
  groups: response.data?.groups ?? [],
5582
6412
  isLoading: response.isLoading,
@@ -5620,14 +6450,22 @@ function summarizeGroup(group) {
5620
6450
  missingStatusMembers
5621
6451
  };
5622
6452
  }
5623
- function buildGroupMemberRows(group, exportResult) {
6453
+ function buildGroupMemberRows(group, exportResult, evidenceResponse = null) {
5624
6454
  if (exportResult !== null && exportResult.group.id === group.id) {
5625
6455
  return exportResult.members.map((exportMember) => ({
5626
6456
  member: exportMember.member,
5627
- exportMember
6457
+ exportMember,
6458
+ source: "export"
6459
+ }));
6460
+ }
6461
+ if (evidenceResponse !== null && evidenceResponse.group.id === group.id && evidenceResponse.members !== null) {
6462
+ return evidenceResponse.members.map((exportMember) => ({
6463
+ member: exportMember.member,
6464
+ exportMember,
6465
+ source: "evidence"
5628
6466
  }));
5629
6467
  }
5630
- return group.members.map((member) => ({ member, exportMember: null }));
6468
+ return group.members.map((member) => ({ member, exportMember: null, source: "group" }));
5631
6469
  }
5632
6470
  function statusClassName(status) {
5633
6471
  switch (status) {
@@ -5980,7 +6818,7 @@ function GroupDetails({
5980
6818
  const exportedSummary = exportSummaryFor(group, exportResult, evidenceResponse);
5981
6819
  const evidenceReport = evidenceReportFor(group, exportResult, evidenceResponse);
5982
6820
  const evidence = evidenceFor(group, exportResult);
5983
- const rows = buildGroupMemberRows(group, exportResult);
6821
+ const rows = buildGroupMemberRows(group, exportResult, evidenceResponse);
5984
6822
  const isExporting = exportingGroupId === group.id;
5985
6823
  const isDeleting = deletingGroupId === group.id;
5986
6824
  const isConfirmingDelete = deleteTarget?.id === group.id;
@@ -6259,6 +7097,7 @@ function MemberRow({ row }) {
6259
7097
  const session = row.exportMember?.session ?? null;
6260
7098
  const log = latestLog(row);
6261
7099
  const sessionPath = session?.inspectorPath ?? getSessionPath(member.sessionId);
7100
+ const canOpenLiveSession = row.source !== "evidence";
6262
7101
  const requests = session?.requestCount ?? null;
6263
7102
  const errors = session?.errorCount ?? null;
6264
7103
  const tokens = session?.tokenUsage.total ?? null;
@@ -6266,7 +7105,7 @@ function MemberRow({ row }) {
6266
7105
  const sessionLabel = firstNonEmpty(member.label, member.sessionId);
6267
7106
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-border last:border-0", children: [
6268
7107
  /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "max-w-56 px-3 py-2", children: [
6269
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
7108
+ canOpenLiveSession ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
6270
7109
  "a",
6271
7110
  {
6272
7111
  href: sessionPath,
@@ -6280,6 +7119,16 @@ function MemberRow({ row }) {
6280
7119
  /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3 shrink-0 text-muted-foreground" })
6281
7120
  ]
6282
7121
  }
7122
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
7123
+ "span",
7124
+ {
7125
+ className: "inline-flex max-w-full min-w-0 items-center gap-2 font-mono text-xs text-foreground",
7126
+ title: `Evidence snapshot for ${sessionLabel}`,
7127
+ children: [
7128
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: sessionLabel }),
7129
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "shrink-0 border-zinc-200 bg-zinc-50 text-zinc-600", children: "evidence" })
7130
+ ]
7131
+ }
6283
7132
  ),
6284
7133
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 truncate text-[11px] text-muted-foreground", children: displayText(member.agent) })
6285
7134
  ] }),
@@ -10851,6 +11700,7 @@ function BrandHeader({
10851
11700
  ] })
10852
11701
  ] }),
10853
11702
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
11703
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
10854
11704
  /* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
10855
11705
  /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {})
10856
11706
  ] })
@@ -10877,6 +11727,7 @@ function BrandHeader({
10877
11727
  /* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {})
10878
11728
  ] }),
10879
11729
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "justify-self-end flex items-center gap-1", children: [
11730
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
10880
11731
  /* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
10881
11732
  /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {})
10882
11733
  ] })
@@ -11180,6 +12031,7 @@ function ProxyViewer({
11180
12031
  onModelChange,
11181
12032
  onClearAll,
11182
12033
  onClearGroup,
12034
+ onImportLogs,
11183
12035
  viewMode,
11184
12036
  captureMode,
11185
12037
  strip,
@@ -11195,12 +12047,16 @@ function ProxyViewer({
11195
12047
  const { providers } = useProviders();
11196
12048
  const [exporting, setExporting] = reactExports.useState(null);
11197
12049
  const [exportError, setExportError] = reactExports.useState(null);
12050
+ const [importing, setImporting] = reactExports.useState(false);
12051
+ const [importStatus, setImportStatus] = reactExports.useState(null);
12052
+ const [importError, setImportError] = reactExports.useState(null);
11198
12053
  const [comparePair, setComparePair] = reactExports.useState(null);
11199
12054
  const [crabEntrancePhase, setCrabEntrancePhase] = reactExports.useState(
11200
12055
  "hidden"
11201
12056
  );
11202
12057
  const logListRef = reactExports.useRef(null);
11203
12058
  const logListWrapperRef = reactExports.useRef(null);
12059
+ const importInputRef = reactExports.useRef(null);
11204
12060
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
11205
12061
  reactExports.useEffect(() => {
11206
12062
  const perCrabDuration = 400;
@@ -11236,6 +12092,30 @@ function ProxyViewer({
11236
12092
  },
11237
12093
  [logs]
11238
12094
  );
12095
+ const handleImportClick = reactExports.useCallback(() => {
12096
+ importInputRef.current?.click();
12097
+ }, []);
12098
+ const handleImportFileChange = reactExports.useCallback(
12099
+ (event) => {
12100
+ const file = event.currentTarget.files?.[0];
12101
+ event.currentTarget.value = "";
12102
+ if (file === void 0 || onImportLogs === void 0) return;
12103
+ setImporting(true);
12104
+ setImportError(null);
12105
+ setImportStatus(null);
12106
+ void onImportLogs(file).then((result) => {
12107
+ const skipped = result.skipped > 0 ? `, skipped ${String(result.skipped)}` : "";
12108
+ setImportStatus(
12109
+ `Imported ${String(result.imported)} logs as ${result.sessionId}${skipped}.`
12110
+ );
12111
+ }).catch((err) => {
12112
+ setImportError(err instanceof Error ? err.message : "Failed to import logs");
12113
+ }).finally(() => {
12114
+ setImporting(false);
12115
+ });
12116
+ },
12117
+ [onImportLogs]
12118
+ );
11239
12119
  reactExports.useEffect(() => {
11240
12120
  setComparePair(null);
11241
12121
  }, [selectedSession, selectedModel]);
@@ -11348,6 +12228,32 @@ function ProxyViewer({
11348
12228
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-foreground", children: "Logs" }),
11349
12229
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 font-mono text-[11px] text-muted-foreground", children: activeScopeLabel })
11350
12230
  ] }),
12231
+ onImportLogs !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
12232
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
12233
+ "input",
12234
+ {
12235
+ ref: importInputRef,
12236
+ type: "file",
12237
+ accept: ".zip,.json,.jsonl,application/zip,application/json",
12238
+ className: "hidden",
12239
+ onChange: handleImportFileChange
12240
+ }
12241
+ ),
12242
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
12243
+ "button",
12244
+ {
12245
+ type: "button",
12246
+ onClick: handleImportClick,
12247
+ disabled: importing,
12248
+ className: "inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
12249
+ title: "Import Agent Inspector export",
12250
+ children: importing ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Importing..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
12251
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3.5" }),
12252
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Import" })
12253
+ ] })
12254
+ }
12255
+ )
12256
+ ] }),
11351
12257
  exportActionVisibility.redacted && /* @__PURE__ */ jsxRuntimeExports.jsx(
11352
12258
  "button",
11353
12259
  {
@@ -11395,7 +12301,9 @@ function ProxyViewer({
11395
12301
  )
11396
12302
  ] }),
11397
12303
  /* @__PURE__ */ jsxRuntimeExports.jsx(LogPaginationBar, { logs, pagination, embedded: true }),
11398
- exportError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: exportError })
12304
+ exportError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: exportError }),
12305
+ importError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: importError }),
12306
+ importStatus !== null && importError === null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-emerald-500/25 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-200", children: importStatus })
11399
12307
  ] }),
11400
12308
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-4 lg:px-5", children: [
11401
12309
  /* @__PURE__ */ jsxRuntimeExports.jsx(SessionOpeningSlate, { stats: slateStats }),
@@ -11827,6 +12735,28 @@ function ProxyViewerContainer({
11827
12735
  }
11828
12736
  })();
11829
12737
  }, []);
12738
+ const handleImportLogs = reactExports.useCallback(async (file) => {
12739
+ const formData = new FormData();
12740
+ formData.set("file", file);
12741
+ const result = await fetchJson("/api/logs/import", ImportLogsResponseSchema, {
12742
+ method: "POST",
12743
+ body: formData
12744
+ });
12745
+ setAllLogs((prev) => {
12746
+ const importedIds = new Set(result.logs.map((log) => log.id));
12747
+ const next = [...prev.filter((log) => !importedIds.has(log.id)), ...result.logs].sort(
12748
+ (left, right) => left.id - right.id
12749
+ );
12750
+ const trimmed = trimClientLogs(next);
12751
+ logIndexRef.current = buildLogIndex(trimmed);
12752
+ return trimmed;
12753
+ });
12754
+ setSelectedSession(result.sessionId);
12755
+ setSelectedModel("__all__");
12756
+ setLogPage(null);
12757
+ setError(null);
12758
+ return result;
12759
+ }, []);
11830
12760
  const { strip, captureMode, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
11831
12761
  const viewMode = captureMode;
11832
12762
  const loadOldestPage = reactExports.useCallback(() => {
@@ -11886,6 +12816,7 @@ function ProxyViewerContainer({
11886
12816
  onModelChange: setSelectedModel,
11887
12817
  onClearAll: handleClearAll,
11888
12818
  onClearGroup: handleClearGroup,
12819
+ onImportLogs: initialSessionId === void 0 ? handleImportLogs : void 0,
11889
12820
  isLoading: initialSessionId === void 0 ? !streamInitialized : sessionPageLoading,
11890
12821
  pagination,
11891
12822
  sessionMemberships,