@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
@@ -1,19 +1,19 @@
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, p as packageJson, P as ProviderConfigSchema, i as apiFormatForPath, n as DeleteInspectorGroupsResponseSchema, o as GroupEvidenceExportResultSchema, q as DeleteInspectorGroupResponseSchema, z as createPendingProviderTestResults, B as ProviderTestResultsSchema, E as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, F as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, l as AlertSummarySchema, m as AlertListResponseSchema, G as GroupEvidenceReadResponseSchema, t as maskApiKey, u as PATH_V1_MESSAGES, v as PATH_V1_CHAT_COMPLETIONS, w as PATH_V1_RESPONSES, r as providerHasContextMetadata, f as parseOpenAIResponse, g as parseOpenAIResponsesResponse, O as OpenAIRequestSchema, h as OpenAIResponsesRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, H as resolveProviderContextWindow, j as getSessionPath, x as findProviderModelMetadata, y as previewUpstreamUrl, K as KnowledgeCandidateSchema, J as isPlainRecord, k as stripClaudeCodeBillingHeader, L as LogBodyChunkSchema } from "./router-Zh2iZ_u0.mjs";
2
+ import { 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, i as apiFormatForPath, n as DeleteInspectorGroupsResponseSchema, o as GroupEvidenceExportResultSchema, q as DeleteInspectorGroupResponseSchema, l as AlertSummarySchema, m as AlertListResponseSchema, I as InspectorGroupsListResponseSchema, G as GroupEvidenceReadResponseSchema, z as createPendingProviderTestResults, B as ProviderTestResultsSchema, E as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, F as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, t as maskApiKey, u as PATH_V1_MESSAGES, v as PATH_V1_CHAT_COMPLETIONS, w as PATH_V1_RESPONSES, r as providerHasContextMetadata, f as parseOpenAIResponse, g as parseOpenAIResponsesResponse, O as OpenAIRequestSchema, h as OpenAIResponsesRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, H as resolveProviderContextWindow, j as getSessionPath, x as findProviderModelMetadata, y as previewUpstreamUrl, K as KnowledgeCandidateSchema, J as isPlainRecord, k as stripClaudeCodeBillingHeader, L as LogBodyChunkSchema } from "./router-g9mo0nWT.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
- 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";
7
+ import { R as Root, C as Content, a as Close, T as Title, D as Description, P as Portal$1, O as Overlay, b as Trigger$1 } from "../_libs/radix-ui__react-dialog.mjs";
8
8
  import { c as cva } from "../_libs/class-variance-authority.mjs";
9
9
  import { u as useWindowVirtualizer, a as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
10
10
  import { d as diffJson, a as diffLines } from "../_libs/diff.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
12
  import { T as TriangleAlert, C as CircleCheck, X, I as Info, a as ChevronDown, b as Check, R as RefreshCw, c as Trash2, S as Search, U as Upload, D as Download, F as FileBraces, P as Plus, A as ArrowLeft, d as ChevronUp, e as Copy, f as ChevronsLeft, g as ChevronLeft, h as ChevronRight, i as ChevronsRight, j as Clapperboard, k as Flag, l as Siren, m as Timer, G as Gauge, L as LoaderCircle, n as Layers, o as FlaskConical, p as Sparkles, q as Activity, B as Beaker, N as Network, r as Terminal, s as Settings, M as Monitor, t as Rocket, u as PackageCheck, v as Scan, w as CircleAlert, x as ArrowUpRight, y as ArrowDownRight, z as Cpu, E as FolderOpen, H as Bell, J as Clock3, K as ShieldAlert, O as EyeOff, Q as Eye, V as ExternalLink, W as Clock, Y as RotateCw, Z as Pencil, _ as MessageSquare, $ as Zap, a0 as Wrench, a1 as Brain, a2 as Minus, a3 as CircleCheckBig, a4 as CircleQuestionMark, a5 as Server, a6 as Lock, a7 as Wifi, a8 as WifiOff, a9 as CircleX, aa as ShieldCheck, ab as Save, ac as FileSearch, ad as CloudUpload, ae as OctagonAlert, af as Radio, ag as Globe, ah as FileTerminal, ai as ChevronsUp, aj as ChevronsDown, ak as FileDiff, al as History, am as RotateCcw, an as GitCompareArrows, ao as ArrowUp, ap as ArrowDown, aq as Ellipsis, ar as CodeXml, as as Blocks, at as MousePointerClick, au as FileText, av as Rows3, aw as Columns2 } from "../_libs/lucide-react.mjs";
13
- 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";
13
+ import { u as union, d as object, a as array, l as literal, n as number, c as boolean, b as string, _ as _enum } from "../_libs/zod.mjs";
14
14
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
15
15
  import { R as Root2$1, L as List, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
16
- import { P as Provider, R as Root3, T as Trigger$3, a as Portal$1, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
16
+ import { P as Provider, R as Root3, T as Trigger$3, a as Portal$2, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
17
17
  import { R as Root$1, C as CollapsibleContent$1, a as CollapsibleTrigger$1 } from "../_libs/radix-ui__react-collapsible.mjs";
18
18
  import { R as Root$2, V as Viewport$1, C as Corner, S as ScrollAreaScrollbar, a as ScrollAreaThumb } from "../_libs/radix-ui__react-scroll-area.mjs";
19
19
  const ApiErrorSchema = object({
@@ -50,19 +50,41 @@ class ApiTimeoutError extends Error {
50
50
  this.name = "ApiTimeoutError";
51
51
  }
52
52
  }
53
- async function fetchJsonWithTimeout(input, schema, timeoutMs, init, errorFallback) {
53
+ async function fetchWithTimeout(input, timeoutMs, init) {
54
54
  const controller = new AbortController();
55
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
55
+ let timedOut = false;
56
+ const timeout = setTimeout(() => {
57
+ timedOut = true;
58
+ controller.abort();
59
+ }, timeoutMs);
60
+ const callerSignal = init?.signal ?? void 0;
61
+ const abortFromCaller = () => controller.abort();
62
+ if (callerSignal !== void 0) {
63
+ if (callerSignal.aborted) {
64
+ controller.abort();
65
+ } else {
66
+ callerSignal.addEventListener("abort", abortFromCaller, { once: true });
67
+ }
68
+ }
56
69
  try {
57
- return await fetchJson(input, schema, { ...init, signal: controller.signal }, errorFallback);
70
+ return await fetch(input, { ...init, signal: controller.signal });
58
71
  } catch (error) {
59
- if (error instanceof DOMException && error.name === "AbortError") {
72
+ if (timedOut && error instanceof DOMException && error.name === "AbortError") {
60
73
  throw new ApiTimeoutError(timeoutMs);
61
74
  }
62
75
  throw error;
63
76
  } finally {
64
77
  clearTimeout(timeout);
78
+ callerSignal?.removeEventListener("abort", abortFromCaller);
79
+ }
80
+ }
81
+ async function fetchJsonWithTimeout(input, schema, timeoutMs, init, errorFallback) {
82
+ const response = await fetchWithTimeout(input, timeoutMs, init);
83
+ if (!response.ok) {
84
+ const fallback = errorFallback?.(response) ?? `Request failed with status ${response.status}`;
85
+ throw new Error(await readApiError(response, fallback));
65
86
  }
87
+ return parseJsonResponse$1(response, schema);
66
88
  }
67
89
  const ImportLogsResponseSchema = object({
68
90
  sessionId: string(),
@@ -321,6 +343,7 @@ const SECRET_TEXT_PATTERNS = [
321
343
  const MAX_EXPORT_LOGS = 200;
322
344
  const MAX_EXPORT_TEXT_CHARS = 25e6;
323
345
  const MAX_STREAMING_CHUNK_EXPORTS = 50;
346
+ const STREAMING_CHUNK_EXPORT_TIMEOUT_MS = 1e4;
324
347
  function formatApproxSize(chars) {
325
348
  if (chars < 1024) return `${chars} chars`;
326
349
  if (chars < 1024 * 1024) return `${Math.ceil(chars / 1024)} KB`;
@@ -346,9 +369,19 @@ function exportLimitMessage(logs) {
346
369
  }
347
370
  return null;
348
371
  }
349
- async function fetchStreamingChunks(logId) {
372
+ function exportCanceled(signal) {
373
+ if (signal?.aborted === true) {
374
+ return { ok: false, message: "Export canceled.", canceled: true };
375
+ }
376
+ return null;
377
+ }
378
+ async function fetchStreamingChunks(logId, signal) {
350
379
  try {
351
- const response = await fetch(`/api/logs/${logId}/chunks`);
380
+ const response = await fetchWithTimeout(
381
+ `/api/logs/${logId}/chunks`,
382
+ STREAMING_CHUNK_EXPORT_TIMEOUT_MS,
383
+ { signal }
384
+ );
352
385
  if (!response.ok) return null;
353
386
  const data = await response.json();
354
387
  return JSON.stringify(data, null, 2);
@@ -409,7 +442,9 @@ function buildExportManifest(logs, mode, streamingLogs, exportedAt = (/* @__PURE
409
442
  streamingChunkLogIds: streamingLogs.map((log) => log.id)
410
443
  };
411
444
  }
412
- async function exportLogsAsZip(logs, mode = "redacted") {
445
+ async function exportLogsAsZip(logs, mode = "redacted", options = {}) {
446
+ const initialCancel = exportCanceled(options.signal);
447
+ if (initialCancel !== null) return initialCancel;
413
448
  const limitMessage = exportLimitMessage(logs);
414
449
  if (limitMessage !== null) return { ok: false, message: limitMessage };
415
450
  const zip = new JSZip();
@@ -428,6 +463,8 @@ async function exportLogsAsZip(logs, mode = "redacted") {
428
463
  )
429
464
  );
430
465
  for (const log of logs) {
466
+ const canceled2 = exportCanceled(options.signal);
467
+ if (canceled2 !== null) return canceled2;
431
468
  if (log.rawRequestBody !== null) {
432
469
  zip.file(`#${log.id}.Request.json`, exportText(log.rawRequestBody, mode));
433
470
  }
@@ -436,12 +473,16 @@ async function exportLogsAsZip(logs, mode = "redacted") {
436
473
  }
437
474
  }
438
475
  for (const log of streamingLogs) {
439
- const chunks = await fetchStreamingChunks(log.id);
476
+ const canceled2 = exportCanceled(options.signal);
477
+ if (canceled2 !== null) return canceled2;
478
+ const chunks = await fetchStreamingChunks(log.id, options.signal);
440
479
  if (chunks !== null) {
441
480
  zip.file(`#${log.id}.SSE.Response.json`, exportText(chunks, mode));
442
481
  }
443
482
  }
444
483
  const blob = await zip.generateAsync({ type: "blob" });
484
+ const canceled = exportCanceled(options.signal);
485
+ if (canceled !== null) return canceled;
445
486
  const url = URL.createObjectURL(blob);
446
487
  const anchor = document.createElement("a");
447
488
  anchor.href = url;
@@ -697,7 +738,7 @@ function DialogTrigger({
697
738
  function DialogPortal({
698
739
  ...props
699
740
  }) {
700
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
741
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { "data-slot": "dialog-portal", ...props });
701
742
  }
702
743
  function DialogOverlay({
703
744
  className,
@@ -849,9 +890,11 @@ function ConfirmDialog({
849
890
  }
850
891
  const ClaudeCodeLogoSvg = "data:image/svg+xml,%3csvg%20height='1em'%20style='flex:none;line-height:1'%20viewBox='0%200%2024%2024'%20width='1em'%20xmlns='http://www.w3.org/2000/svg'%3e%3ctitle%3eClaude%20Code%3c/title%3e%3cpath%20clip-rule='evenodd'%20d='M20.998%2010.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6%2010.949h1.488V8.102H6v2.847zm10.51%200H18V8.102h-1.49v2.847z'%20fill='%23D97757'%20fill-rule='evenodd'%3e%3c/path%3e%3c/svg%3e";
851
892
  const CodexLogoSvg = "data:image/svg+xml,%3csvg%20height='1em'%20style='flex:none;line-height:1'%20viewBox='0%200%2024%2024'%20width='1em'%20xmlns='http://www.w3.org/2000/svg'%3e%3ctitle%3eCodex%3c/title%3e%3cpath%20d='M19.503%200H4.496A4.496%204.496%200%20000%204.496v15.007A4.496%204.496%200%20004.496%2024h15.007A4.496%204.496%200%200024%2019.503V4.496A4.496%204.496%200%200019.503%200z'%20fill='%23fff'%3e%3c/path%3e%3cpath%20d='M9.064%203.344a4.578%204.578%200%20012.285-.312c1%20.115%201.891.54%202.673%201.275.01.01.024.017.037.021a.09.09%200%2000.043%200%204.55%204.55%200%20013.046.275l.047.022.116.057a4.581%204.581%200%20012.188%202.399c.209.51.313%201.041.315%201.595a4.24%204.24%200%2001-.134%201.223.123.123%200%2000.03.115c.594.607.988%201.33%201.183%202.17.289%201.425-.007%202.71-.887%203.854l-.136.166a4.548%204.548%200%2001-2.201%201.388.123.123%200%2000-.081.076c-.191.551-.383%201.023-.74%201.494-.9%201.187-2.222%201.846-3.711%201.838-1.187-.006-2.239-.44-3.157-1.302a.107.107%200%2000-.105-.024c-.388.125-.78.143-1.204.138a4.441%204.441%200%2001-1.945-.466%204.544%204.544%200%2001-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81%205.81%200%2001-.37-.961%204.582%204.582%200%2001-.014-2.298.124.124%200%2000.006-.056.085.085%200%2000-.027-.048%204.467%204.467%200%2001-1.034-1.651%203.896%203.896%200%2001-.251-1.192%205.189%205.189%200%2001.141-1.6c.337-1.112.982-1.985%201.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098%200%2000.065-.066%204.51%204.51%200%2001.829-1.615%204.535%204.535%200%20011.837-1.388zm3.482%2010.565a.637.637%200%20000%201.272h3.636a.637.637%200%20100-1.272h-3.636zM8.462%209.23a.637.637%200%2000-1.106.631l1.272%202.224-1.266%202.136a.636.636%200%20101.095.649l1.454-2.455a.636.636%200%2000.005-.64L8.462%209.23z'%20fill='url(%23lobe-icons-codex-_R_0_)'%3e%3c/path%3e%3cdefs%3e%3clinearGradient%20gradientUnits='userSpaceOnUse'%20id='lobe-icons-codex-_R_0_'%20x1='12'%20x2='12'%20y1='3'%20y2='21'%3e%3cstop%20stop-color='%23B1A7FF'%3e%3c/stop%3e%3cstop%20offset='.5'%20stop-color='%237A9DFF'%3e%3c/stop%3e%3cstop%20offset='1'%20stop-color='%233941FF'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e";
852
- const OpenCodeLogoSvg = "data:image/svg+xml,%3csvg%20fill='currentColor'%20fill-rule='evenodd'%20height='1em'%20style='flex:none;line-height:1'%20viewBox='0%200%2024%2024'%20width='1em'%20xmlns='http://www.w3.org/2000/svg'%3e%3ctitle%3eopencode%3c/title%3e%3cpath%20d='M16%206H8v12h8V6zm4%2016H4V2h16v20z'%3e%3c/path%3e%3c/svg%3e";
853
- const MiMoCodeLogoSvg = "data:image/svg+xml,%3csvg%20fill='currentColor'%20fill-rule='evenodd'%20height='1em'%20style='flex:none;line-height:1'%20viewBox='0%200%2024%2024'%20width='1em'%20xmlns='http://www.w3.org/2000/svg'%3e%3ctitle%3eXiaomiMiMo%3c/title%3e%3cpath%20d='M.958%2015.936a.459.459%200%2001.459.44v2.729a.46.46%200%2001-.918%200v-2.729a.459.459%200%2001.459-.44zm4.814-2.035a.46.46%200%2001.553.45v4.754a.458.458%200%2011-.918%200V15.48L3.74%2017.202a.462.462%200%2001-.655.016.462.462%200%2001-.065-.082L.628%2014.67a.459.459%200%2001.658-.637l2.124%202.187%202.127-2.188a.46.46%200%2001.235-.13zm2.068.004a.46.46%200%2001.458.445v4.755a.46.46%200%2001-.458.458.459.459%200%2001-.458-.458V14.35a.459.459%200%2001.458-.445zm1.973%202.014a.46.46%200%2001.46.457v2.729a.46.46%200%2001-.784.324.46.46%200%2001-.134-.324v-2.729a.46.46%200%2001.458-.458zm.002-2.045a.458.458%200%2001.328.157l2.127%202.19%202.125-2.19a.459.459%200%2001.784.318v4.756a.46.46%200%2001-.455.458.46.46%200%2001-.458-.458V15.48l-1.667%201.723a.46.46%200%2001-.65.008l-.005-.005c0-.002-.002-.002-.004-.003l-2.455-2.534a.46.46%200%2001-.008-.667.461.461%200%2001.338-.128zm6.797%201.206a.46.46%200%2001.53.651A1.966%201.966%200%200019.81%2018.4a.462.462%200%2001.623.18.46.46%200%2001-.181.624%202.863%202.863%200%2001-1.38.353l-.142-.004a2.88%202.88%200%2001-2.393-4.263.461.461%200%2001.274-.21zm.864-.931a2.884%202.884%200%20013.915%203.914.46.46%200%2001-.402.24l-.057-.004a.458.458%200%2001-.164-.055.46.46%200%2001-.182-.622%201.967%201.967%200%2000-2.669-2.67.459.459%200%2011-.441-.803zM9.59%206.368c1.481%200%201.696%201.202%201.696%201.654v2.648h-.917v-.432c-.26.346-.792.535-1.36.535-.133%200-1.289-.03-1.384-1.136-.082-.932.675-1.61%202.053-1.61h.691c0-.563-.367-.886-.983-.886-.44.013-.864.174-1.2.458l-.36-.664c.484-.379%201.012-.567%201.764-.567zm4.427.1c1.263%200%202.082.97%202.083%202.15%200%201.181-.824%202.154-2.083%202.154-1.26%200-2.084-.972-2.084-2.152%200-1.18.82-2.153%202.084-2.153zm6.801.015c.68%200%201.202.465%201.197%201.548v2.642H21.1V8.29c0-.312-.002-.98-.63-.98s-.628.667-.628.838v2.524h-.89V8.148c0-.17-.001-.838-.63-.838-.628%200-.628.668-.628.98v2.383h-.917v-4.03h.917V7a1.22%201.22%200%2001.947-.516c.398%200%20.76.193.982.686a1.321%201.321%200%20011.195-.686zm-18.093.872l1.457-1.772H5.32L3.311%208.07l2.14%202.602H4.24L2.725%208.796%201.21%2010.672H0L2.138%208.07.13%205.583h1.138l1.458%201.772zm4.149%203.317h-.916V6.644h.916v4.028zm16.99%200h-.916V6.644h.916v4.028zM9.925%208.71c-1.055%200-1.359.412-1.326.742.032.329.324.537.757.537a1.013%201.013%200%20001.014-.968l.002-.31h-.447zM14.018%207.3c-.663%200-1.184.487-1.184%201.32%200%20.832.52%201.32%201.184%201.32.662%200%201.182-.49%201.182-1.32%200-.832-.52-1.32-1.182-1.32zM6.417%205.001a.568.568%200%2001.587.582.588.588%200%2001-1.175%200A.57.57%200%20016.417%205zm16.991%200a.57.57%200%2001.592.582.588.588%200%2001-1.174%200%20.57.57%200%2001.357-.542.572.572%200%2001.225-.04z'%3e%3c/path%3e%3c/svg%3e";
893
+ const OpenCodeLogoSvg = "data:image/svg+xml,%3csvg%20fill='%237dd3fc'%20fill-rule='evenodd'%20height='1em'%20style='flex:none;line-height:1'%20viewBox='0%200%2024%2024'%20width='1em'%20xmlns='http://www.w3.org/2000/svg'%3e%3ctitle%3eOpenCode%3c/title%3e%3cpath%20d='M16%206H8v12h8V6zm4%2016H4V2h16v20z'%3e%3c/path%3e%3c/svg%3e";
894
+ const MiMoCodeLogoSvg = "data:image/svg+xml,%3csvg%20fill='%23ff6900'%20fill-rule='evenodd'%20height='1em'%20style='flex:none;line-height:1'%20viewBox='0%200%2024%2024'%20width='1em'%20xmlns='http://www.w3.org/2000/svg'%3e%3ctitle%3eMiMo%20Code%3c/title%3e%3cpath%20d='M.958%2015.936a.459.459%200%2001.459.44v2.729a.46.46%200%2001-.918%200v-2.729a.459.459%200%2001.459-.44zm4.814-2.035a.46.46%200%2001.553.45v4.754a.458.458%200%2011-.918%200V15.48L3.74%2017.202a.462.462%200%2001-.655.016.462.462%200%2001-.065-.082L.628%2014.67a.459.459%200%2001.658-.637l2.124%202.187%202.127-2.188a.46.46%200%2001.235-.13zm2.068.004a.46.46%200%2001.458.445v4.755a.46.46%200%2001-.458.458.459.459%200%2001-.458-.458V14.35a.459.459%200%2001.458-.445zm1.973%202.014a.46.46%200%2001.46.457v2.729a.46.46%200%2001-.784.324.46.46%200%2001-.134-.324v-2.729a.46.46%200%2001.458-.458zm.002-2.045a.458.458%200%2001.328.157l2.127%202.19%202.125-2.19a.459.459%200%2001.784.318v4.756a.46.46%200%2001-.455.458.46.46%200%2001-.458-.458V15.48l-1.667%201.723a.46.46%200%2001-.65.008l-.005-.005c0-.002-.002-.002-.004-.003l-2.455-2.534a.46.46%200%2001-.008-.667.461.461%200%2001.338-.128zm6.797%201.206a.46.46%200%2001.53.651A1.966%201.966%200%200019.81%2018.4a.462.462%200%2001.623.18.46.46%200%2001-.181.624%202.863%202.863%200%2001-1.38.353l-.142-.004a2.88%202.88%200%2001-2.393-4.263.461.461%200%2001.274-.21zm.864-.931a2.884%202.884%200%20013.915%203.914.46.46%200%2001-.402.24l-.057-.004a.458.458%200%2001-.164-.055.46.46%200%2001-.182-.622%201.967%201.967%200%2000-2.669-2.67.459.459%200%2011-.441-.803zM9.59%206.368c1.481%200%201.696%201.202%201.696%201.654v2.648h-.917v-.432c-.26.346-.792.535-1.36.535-.133%200-1.289-.03-1.384-1.136-.082-.932.675-1.61%202.053-1.61h.691c0-.563-.367-.886-.983-.886-.44.013-.864.174-1.2.458l-.36-.664c.484-.379%201.012-.567%201.764-.567zm4.427.1c1.263%200%202.082.97%202.083%202.15%200%201.181-.824%202.154-2.083%202.154-1.26%200-2.084-.972-2.084-2.152%200-1.18.82-2.153%202.084-2.153zm6.801.015c.68%200%201.202.465%201.197%201.548v2.642H21.1V8.29c0-.312-.002-.98-.63-.98s-.628.667-.628.838v2.524h-.89V8.148c0-.17-.001-.838-.63-.838-.628%200-.628.668-.628.98v2.383h-.917v-4.03h.917V7a1.22%201.22%200%2001.947-.516c.398%200%20.76.193.982.686a1.321%201.321%200%20011.195-.686zm-18.093.872l1.457-1.772H5.32L3.311%208.07l2.14%202.602H4.24L2.725%208.796%201.21%2010.672H0L2.138%208.07.13%205.583h1.138l1.458%201.772zm4.149%203.317h-.916V6.644h.916v4.028zm16.99%200h-.916V6.644h.916v4.028zM9.925%208.71c-1.055%200-1.359.412-1.326.742.032.329.324.537.757.537a1.013%201.013%200%20001.014-.968l.002-.31h-.447zM14.018%207.3c-.663%200-1.184.487-1.184%201.32%200%20.832.52%201.32%201.184%201.32.662%200%201.182-.49%201.182-1.32%200-.832-.52-1.32-1.182-1.32zM6.417%205.001a.568.568%200%2001.587.582.588.588%200%2001-1.175%200A.57.57%200%20016.417%205zm16.991%200a.57.57%200%2001.592.582.588.588%200%2001-1.174%200%20.57.57%200%2001.357-.542.572.572%200%2001.225-.04z'%3e%3c/path%3e%3c/svg%3e";
895
+ const AgentInspectorLogo = "/assets/agent-inspector-58K1_MsC.ico";
854
896
  const CLIENT_LABELS = {
897
+ "agent-inspector": "Inspector",
855
898
  codex: "Codex",
856
899
  opencode: "OpenCode",
857
900
  "mimo-code": "MiMo Code",
@@ -872,6 +915,9 @@ function detectClientApp(input) {
872
915
  normalize(input.conversationId),
873
916
  normalize(input.clientProjectFolder)
874
917
  ].join(" ");
918
+ if (matchesAny(haystack, ["agent-inspector", "agent inspector", "provider-test"])) {
919
+ return "agent-inspector";
920
+ }
875
921
  if (matchesAny(haystack, ["mimocode", "mimo-code", "mimo code"])) return "mimo-code";
876
922
  if (matchesAny(haystack, ["opencode", "open-code", "open code"])) return "opencode";
877
923
  if (matchesAny(haystack, ["claude-code", "claude code", "claudecode"])) return "claude-code";
@@ -882,6 +928,7 @@ function detectClientApp(input) {
882
928
  }
883
929
  function clientAppLabel(client) {
884
930
  switch (client) {
931
+ case "agent-inspector":
885
932
  case "codex":
886
933
  case "opencode":
887
934
  case "mimo-code":
@@ -892,11 +939,26 @@ function clientAppLabel(client) {
892
939
  }
893
940
  }
894
941
  const CLIENT_LOGOS = {
942
+ "agent-inspector": AgentInspectorLogo,
895
943
  codex: CodexLogoSvg,
896
944
  opencode: OpenCodeLogoSvg,
897
945
  "mimo-code": MiMoCodeLogoSvg,
898
946
  "claude-code": ClaudeCodeLogoSvg
899
947
  };
948
+ const CLIENT_LOGO_FRAMES = {
949
+ "agent-inspector": "border-orange-200/25 bg-orange-300/[0.12]",
950
+ codex: "border-cyan-200/20 bg-cyan-300/[0.08]",
951
+ opencode: "border-sky-200/25 bg-sky-300/[0.12]",
952
+ "mimo-code": "border-orange-200/25 bg-orange-400/[0.12]",
953
+ "claude-code": "border-amber-100/20 bg-amber-300/[0.09]"
954
+ };
955
+ const CLIENT_LOGO_IMAGES = {
956
+ "agent-inspector": "p-0.5 drop-shadow-[0_0_8px_rgba(251,146,60,0.35)]",
957
+ codex: "p-0.5",
958
+ opencode: "p-1 drop-shadow-[0_0_8px_rgba(125,211,252,0.35)]",
959
+ "mimo-code": "p-0.5 drop-shadow-[0_0_8px_rgba(255,105,0,0.35)]",
960
+ "claude-code": "p-0.5"
961
+ };
900
962
  function ClientLogo({
901
963
  client,
902
964
  className
@@ -909,12 +971,21 @@ function ClientLogo({
909
971
  "span",
910
972
  {
911
973
  className: cn(
912
- "border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md p-0.5",
974
+ "inline-flex size-6 shrink-0 items-center justify-center rounded-md border p-0 shadow-sm",
975
+ CLIENT_LOGO_FRAMES[client],
913
976
  className
914
977
  ),
915
978
  "aria-label": label,
916
979
  title: label,
917
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: logoSrc, alt: "", className: "size-full object-contain", "aria-hidden": "true" })
980
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
981
+ "img",
982
+ {
983
+ src: logoSrc,
984
+ alt: "",
985
+ className: cn("size-full object-contain", CLIENT_LOGO_IMAGES[client]),
986
+ "aria-hidden": "true"
987
+ }
988
+ )
918
989
  }
919
990
  );
920
991
  }
@@ -1027,7 +1098,7 @@ function TooltipContent({
1027
1098
  children,
1028
1099
  ...props
1029
1100
  }) {
1030
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1101
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1031
1102
  Content2$1,
1032
1103
  {
1033
1104
  "data-slot": "tooltip-content",
@@ -1102,6 +1173,171 @@ function countCharacters(text) {
1102
1173
  });
1103
1174
  return n;
1104
1175
  }
1176
+ const CATEGORY_ORDER = [
1177
+ "file",
1178
+ "shell",
1179
+ "browser",
1180
+ "web",
1181
+ "mcp",
1182
+ "code",
1183
+ "other"
1184
+ ];
1185
+ function firstTextSlice(value) {
1186
+ const normalized = value.replace(/\s+/g, " ").trim();
1187
+ if (normalized.length <= 120) return normalized;
1188
+ return `${normalized.slice(0, 119)}...`;
1189
+ }
1190
+ function previewDescription(description) {
1191
+ if (description === null) return null;
1192
+ const normalized = description.replace(/\s+/g, " ").trim();
1193
+ if (normalized.length === 0) return null;
1194
+ const sentenceEnd = normalized.search(/[.!?。!?]/);
1195
+ if (sentenceEnd >= 24) return firstTextSlice(normalized.slice(0, sentenceEnd + 1));
1196
+ return firstTextSlice(normalized);
1197
+ }
1198
+ function lowerHaystack(name, description) {
1199
+ return `${name} ${description ?? ""}`.toLowerCase();
1200
+ }
1201
+ function includesAny(value, needles) {
1202
+ return needles.some((needle) => value.includes(needle));
1203
+ }
1204
+ function categorizeTool(name, description) {
1205
+ const haystack = lowerHaystack(name, description);
1206
+ if (name.startsWith("mcp__") || name.startsWith("inspector_") || includesAny(haystack, [" mcp", "model context protocol"])) {
1207
+ return "mcp";
1208
+ }
1209
+ if (includesAny(haystack, ["bash", "shell", "terminal", "command", "powershell", "exec", "process"])) {
1210
+ return "shell";
1211
+ }
1212
+ if (includesAny(haystack, [
1213
+ "browser",
1214
+ "playwright",
1215
+ "screenshot",
1216
+ "click",
1217
+ "navigate",
1218
+ "page",
1219
+ "tab"
1220
+ ])) {
1221
+ return "browser";
1222
+ }
1223
+ if (includesAny(haystack, [
1224
+ "read",
1225
+ "write",
1226
+ "edit",
1227
+ "file",
1228
+ "glob",
1229
+ "grep",
1230
+ "patch",
1231
+ "filesystem",
1232
+ "directory"
1233
+ ])) {
1234
+ return "file";
1235
+ }
1236
+ if (includesAny(haystack, ["web", "search", "http", "url", "fetch"])) {
1237
+ return "web";
1238
+ }
1239
+ if (includesAny(haystack, ["code", "typescript", "javascript", "python", "repl"])) {
1240
+ return "code";
1241
+ }
1242
+ return "other";
1243
+ }
1244
+ function parameterNames(schema) {
1245
+ const properties = safeGetOwnProperty(schema, "properties");
1246
+ if (properties === null || typeof properties !== "object" || Array.isArray(properties)) {
1247
+ return [];
1248
+ }
1249
+ return Object.keys(properties).sort((a, b) => a.localeCompare(b));
1250
+ }
1251
+ function requiredParameterNames(schema) {
1252
+ const required = safeGetOwnProperty(schema, "required");
1253
+ if (!Array.isArray(required)) return [];
1254
+ const names = [];
1255
+ for (const item of required) {
1256
+ if (typeof item === "string" && item.length > 0) names.push(item);
1257
+ }
1258
+ return names.sort((a, b) => a.localeCompare(b));
1259
+ }
1260
+ function parseAnthropicTool(tool) {
1261
+ const name = safeGetOwnProperty(tool, "name");
1262
+ if (typeof name !== "string" || name.length === 0) return null;
1263
+ const description = safeGetOwnProperty(tool, "description");
1264
+ const schema = safeGetOwnProperty(tool, "input_schema") ?? null;
1265
+ const normalizedDescription = typeof description === "string" ? description : null;
1266
+ return {
1267
+ name,
1268
+ description: normalizedDescription,
1269
+ descriptionPreview: previewDescription(normalizedDescription),
1270
+ category: categorizeTool(name, normalizedDescription),
1271
+ parameterCount: parameterNames(schema).length,
1272
+ requiredParameters: requiredParameterNames(schema),
1273
+ schema
1274
+ };
1275
+ }
1276
+ function parseOpenAITool(tool) {
1277
+ const fn = safeGetOwnProperty(tool, "function");
1278
+ const toolType = safeGetOwnProperty(tool, "type");
1279
+ if (fn === void 0 && typeof toolType !== "string") return null;
1280
+ const name = safeGetOwnProperty(fn, "name") ?? safeGetOwnProperty(tool, "name") ?? (typeof toolType === "string" ? toolType : void 0);
1281
+ if (typeof name !== "string" || name.length === 0) return null;
1282
+ const description = safeGetOwnProperty(fn, "description") ?? safeGetOwnProperty(tool, "description");
1283
+ const schema = safeGetOwnProperty(fn, "parameters") ?? safeGetOwnProperty(tool, "parameters") ?? null;
1284
+ const normalizedDescription = typeof description === "string" ? description : null;
1285
+ return {
1286
+ name,
1287
+ description: normalizedDescription,
1288
+ descriptionPreview: previewDescription(normalizedDescription),
1289
+ category: categorizeTool(name, normalizedDescription),
1290
+ parameterCount: parameterNames(schema).length,
1291
+ requiredParameters: requiredParameterNames(schema),
1292
+ schema
1293
+ };
1294
+ }
1295
+ function parseTool(tool) {
1296
+ const openAITool = parseOpenAITool(tool);
1297
+ if (openAITool !== null) return openAITool;
1298
+ return parseAnthropicTool(tool);
1299
+ }
1300
+ function formatToolChoice(choice) {
1301
+ if (choice === void 0 || choice === null) return null;
1302
+ if (typeof choice === "string") return choice;
1303
+ const type = safeGetOwnProperty(choice, "type");
1304
+ const name = safeGetOwnProperty(choice, "name");
1305
+ const fnName = safeGetOwnProperty(safeGetOwnProperty(choice, "function"), "name");
1306
+ if (typeof type === "string" && typeof fnName === "string" && fnName.length > 0) {
1307
+ return `${type}: ${fnName}`;
1308
+ }
1309
+ if (typeof type === "string" && typeof name === "string" && name.length > 0) {
1310
+ return `${type}: ${name}`;
1311
+ }
1312
+ if (typeof type === "string") return type;
1313
+ const encoded = JSON.stringify(choice);
1314
+ return encoded === void 0 ? null : firstTextSlice(encoded);
1315
+ }
1316
+ function summarizeCategories(tools) {
1317
+ const counts = /* @__PURE__ */ new Map();
1318
+ for (const tool of tools) {
1319
+ counts.set(tool.category, (counts.get(tool.category) ?? 0) + 1);
1320
+ }
1321
+ return CATEGORY_ORDER.flatMap((category) => {
1322
+ const count = counts.get(category) ?? 0;
1323
+ return count > 0 ? [{ category, count }] : [];
1324
+ });
1325
+ }
1326
+ function parseRequestTools(parsed) {
1327
+ const rawTools = safeGetOwnProperty(parsed, "tools");
1328
+ if (!Array.isArray(rawTools) || rawTools.length === 0) return null;
1329
+ const tools = [];
1330
+ for (const rawTool of rawTools) {
1331
+ const tool = parseTool(rawTool);
1332
+ if (tool !== null) tools.push(tool);
1333
+ }
1334
+ if (tools.length === 0) return null;
1335
+ return {
1336
+ tools,
1337
+ toolChoiceLabel: formatToolChoice(safeGetOwnProperty(parsed, "tool_choice")),
1338
+ categories: summarizeCategories(tools)
1339
+ };
1340
+ }
1105
1341
  function emptyRequestAnalysis(rawBody) {
1106
1342
  return {
1107
1343
  parsed: null,
@@ -1197,13 +1433,15 @@ const anthropicLogFormatAdapter = {
1197
1433
  analyzeRequest(rawBody) {
1198
1434
  if (rawBody === null) return emptyRequestAnalysis(rawBody);
1199
1435
  try {
1200
- const result = AnthropicRequestSchema.safeParse(JSON.parse(rawBody));
1436
+ const parsedBody = JSON.parse(rawBody);
1437
+ const toolCount = parseRequestTools(parsedBody)?.tools.length ?? null;
1438
+ const result = AnthropicRequestSchema.safeParse(parsedBody);
1201
1439
  if (!result.success) return emptyRequestAnalysis(rawBody);
1202
1440
  return {
1203
1441
  parsed: result.data,
1204
1442
  comparisonValue: result.data,
1205
1443
  messageCount: result.data.messages.length,
1206
- toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
1444
+ toolCount
1207
1445
  };
1208
1446
  } catch {
1209
1447
  return emptyRequestAnalysis(rawBody);
@@ -1354,22 +1592,24 @@ const openAILogFormatAdapter = {
1354
1592
  analyzeRequest(rawBody) {
1355
1593
  if (rawBody === null) return emptyRequestAnalysis(rawBody);
1356
1594
  try {
1357
- const result = OpenAIRequestSchema.safeParse(JSON.parse(rawBody));
1595
+ const parsedBody = JSON.parse(rawBody);
1596
+ const toolCount = parseRequestTools(parsedBody)?.tools.length ?? null;
1597
+ const result = OpenAIRequestSchema.safeParse(parsedBody);
1358
1598
  if (!result.success) {
1359
- const responsesResult = OpenAIResponsesRequestSchema.safeParse(JSON.parse(rawBody));
1599
+ const responsesResult = OpenAIResponsesRequestSchema.safeParse(parsedBody);
1360
1600
  if (!responsesResult.success) return emptyRequestAnalysis(rawBody);
1361
1601
  return {
1362
1602
  parsed: responsesResult.data,
1363
1603
  comparisonValue: responsesResult.data,
1364
1604
  messageCount: countResponsesInput(responsesResult.data.input),
1365
- toolCount: responsesResult.data.tools !== void 0 && responsesResult.data.tools.length > 0 ? responsesResult.data.tools.length : null
1605
+ toolCount
1366
1606
  };
1367
1607
  }
1368
1608
  return {
1369
1609
  parsed: result.data,
1370
1610
  comparisonValue: result.data,
1371
1611
  messageCount: result.data.messages.length,
1372
- toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
1612
+ toolCount
1373
1613
  };
1374
1614
  } catch {
1375
1615
  return emptyRequestAnalysis(rawBody);
@@ -2306,27 +2546,27 @@ function useCopyFeedback(text) {
2306
2546
  return { copied, copy };
2307
2547
  }
2308
2548
  const LazyCompareDrawer = reactExports.lazy(
2309
- () => import("./CompareDrawer-m82cVZ73.mjs").then((m) => ({ default: m.CompareDrawer }))
2549
+ () => import("./CompareDrawer-s-UF9gW3.mjs").then((m) => ({ default: m.CompareDrawer }))
2310
2550
  );
2311
2551
  const LazyReplayDialog = reactExports.lazy(
2312
- () => import("./ReplayDialog-B8RlWLLH.mjs").then((m) => ({ default: m.ReplayDialog }))
2552
+ () => import("./ReplayDialog-DvS8gvJr.mjs").then((m) => ({ default: m.ReplayDialog }))
2313
2553
  );
2314
2554
  const LazyRequestAnatomy = reactExports.lazy(
2315
- () => import("./RequestAnatomy-BE7pmwpv.mjs").then((m) => ({ default: m.RequestAnatomy }))
2555
+ () => import("./RequestAnatomy-BwhWglJL.mjs").then((m) => ({ default: m.RequestAnatomy }))
2316
2556
  );
2317
2557
  const LazyResponseView = reactExports.lazy(
2318
- () => import("./ResponseView-D-sDsdQr.mjs").then((m) => ({ default: m.ResponseView }))
2558
+ () => import("./ResponseView-wBVreqQf.mjs").then((m) => ({ default: m.ResponseView }))
2319
2559
  );
2320
2560
  const LazyStreamingChunkSequence = reactExports.lazy(
2321
- () => import("./StreamingChunkSequence-DU_hThJr.mjs").then((m) => ({
2561
+ () => import("./StreamingChunkSequence-Dr6eg-MJ.mjs").then((m) => ({
2322
2562
  default: m.StreamingChunkSequence
2323
2563
  }))
2324
2564
  );
2325
2565
  const LazyJsonViewer = reactExports.lazy(
2326
- () => import("./json-viewer-CaDh1zrw.mjs").then((m) => ({ default: m.JsonViewer }))
2566
+ () => import("./json-viewer-D7L8y0FH.mjs").then((m) => ({ default: m.JsonViewer }))
2327
2567
  );
2328
2568
  const LazyJsonViewerFromString = reactExports.lazy(
2329
- () => import("./json-viewer-CaDh1zrw.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2569
+ () => import("./json-viewer-D7L8y0FH.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2330
2570
  );
2331
2571
  const HIGHLIGHT_DURATION_MS = 1200;
2332
2572
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -3473,171 +3713,6 @@ function shouldShowHeadersDiffButton(viewMode, hasRawHeaders) {
3473
3713
  function shouldShowRequestDiffButton(apiFormat, viewMode, strip, hasRawRequest) {
3474
3714
  return apiFormat === "anthropic" && viewMode === "full" && strip && hasRawRequest;
3475
3715
  }
3476
- const CATEGORY_ORDER = [
3477
- "file",
3478
- "shell",
3479
- "browser",
3480
- "web",
3481
- "mcp",
3482
- "code",
3483
- "other"
3484
- ];
3485
- function firstTextSlice(value) {
3486
- const normalized = value.replace(/\s+/g, " ").trim();
3487
- if (normalized.length <= 120) return normalized;
3488
- return `${normalized.slice(0, 119)}...`;
3489
- }
3490
- function previewDescription(description) {
3491
- if (description === null) return null;
3492
- const normalized = description.replace(/\s+/g, " ").trim();
3493
- if (normalized.length === 0) return null;
3494
- const sentenceEnd = normalized.search(/[.!?。!?]/);
3495
- if (sentenceEnd >= 24) return firstTextSlice(normalized.slice(0, sentenceEnd + 1));
3496
- return firstTextSlice(normalized);
3497
- }
3498
- function lowerHaystack(name, description) {
3499
- return `${name} ${description ?? ""}`.toLowerCase();
3500
- }
3501
- function includesAny(value, needles) {
3502
- return needles.some((needle) => value.includes(needle));
3503
- }
3504
- function categorizeTool(name, description) {
3505
- const haystack = lowerHaystack(name, description);
3506
- if (name.startsWith("mcp__") || name.startsWith("inspector_") || includesAny(haystack, [" mcp", "model context protocol"])) {
3507
- return "mcp";
3508
- }
3509
- if (includesAny(haystack, ["bash", "shell", "terminal", "command", "powershell", "exec", "process"])) {
3510
- return "shell";
3511
- }
3512
- if (includesAny(haystack, [
3513
- "browser",
3514
- "playwright",
3515
- "screenshot",
3516
- "click",
3517
- "navigate",
3518
- "page",
3519
- "tab"
3520
- ])) {
3521
- return "browser";
3522
- }
3523
- if (includesAny(haystack, [
3524
- "read",
3525
- "write",
3526
- "edit",
3527
- "file",
3528
- "glob",
3529
- "grep",
3530
- "patch",
3531
- "filesystem",
3532
- "directory"
3533
- ])) {
3534
- return "file";
3535
- }
3536
- if (includesAny(haystack, ["web", "search", "http", "url", "fetch"])) {
3537
- return "web";
3538
- }
3539
- if (includesAny(haystack, ["code", "typescript", "javascript", "python", "repl"])) {
3540
- return "code";
3541
- }
3542
- return "other";
3543
- }
3544
- function parameterNames(schema) {
3545
- const properties = safeGetOwnProperty(schema, "properties");
3546
- if (properties === null || typeof properties !== "object" || Array.isArray(properties)) {
3547
- return [];
3548
- }
3549
- return Object.keys(properties).sort((a, b) => a.localeCompare(b));
3550
- }
3551
- function requiredParameterNames(schema) {
3552
- const required = safeGetOwnProperty(schema, "required");
3553
- if (!Array.isArray(required)) return [];
3554
- const names = [];
3555
- for (const item of required) {
3556
- if (typeof item === "string" && item.length > 0) names.push(item);
3557
- }
3558
- return names.sort((a, b) => a.localeCompare(b));
3559
- }
3560
- function parseAnthropicTool(tool) {
3561
- const name = safeGetOwnProperty(tool, "name");
3562
- if (typeof name !== "string" || name.length === 0) return null;
3563
- const description = safeGetOwnProperty(tool, "description");
3564
- const schema = safeGetOwnProperty(tool, "input_schema") ?? null;
3565
- const normalizedDescription = typeof description === "string" ? description : null;
3566
- return {
3567
- name,
3568
- description: normalizedDescription,
3569
- descriptionPreview: previewDescription(normalizedDescription),
3570
- category: categorizeTool(name, normalizedDescription),
3571
- parameterCount: parameterNames(schema).length,
3572
- requiredParameters: requiredParameterNames(schema),
3573
- schema
3574
- };
3575
- }
3576
- function parseOpenAITool(tool) {
3577
- const fn = safeGetOwnProperty(tool, "function");
3578
- const toolType = safeGetOwnProperty(tool, "type");
3579
- if (fn === void 0 && toolType !== "function") return null;
3580
- const name = safeGetOwnProperty(fn, "name") ?? safeGetOwnProperty(tool, "name");
3581
- if (typeof name !== "string" || name.length === 0) return null;
3582
- const description = safeGetOwnProperty(fn, "description") ?? safeGetOwnProperty(tool, "description");
3583
- const schema = safeGetOwnProperty(fn, "parameters") ?? safeGetOwnProperty(tool, "parameters") ?? null;
3584
- const normalizedDescription = typeof description === "string" ? description : null;
3585
- return {
3586
- name,
3587
- description: normalizedDescription,
3588
- descriptionPreview: previewDescription(normalizedDescription),
3589
- category: categorizeTool(name, normalizedDescription),
3590
- parameterCount: parameterNames(schema).length,
3591
- requiredParameters: requiredParameterNames(schema),
3592
- schema
3593
- };
3594
- }
3595
- function parseTool(tool) {
3596
- const openAITool = parseOpenAITool(tool);
3597
- if (openAITool !== null) return openAITool;
3598
- return parseAnthropicTool(tool);
3599
- }
3600
- function formatToolChoice(choice) {
3601
- if (choice === void 0 || choice === null) return null;
3602
- if (typeof choice === "string") return choice;
3603
- const type = safeGetOwnProperty(choice, "type");
3604
- const name = safeGetOwnProperty(choice, "name");
3605
- const fnName = safeGetOwnProperty(safeGetOwnProperty(choice, "function"), "name");
3606
- if (typeof type === "string" && typeof fnName === "string" && fnName.length > 0) {
3607
- return `${type}: ${fnName}`;
3608
- }
3609
- if (typeof type === "string" && typeof name === "string" && name.length > 0) {
3610
- return `${type}: ${name}`;
3611
- }
3612
- if (typeof type === "string") return type;
3613
- const encoded = JSON.stringify(choice);
3614
- return encoded === void 0 ? null : firstTextSlice(encoded);
3615
- }
3616
- function summarizeCategories(tools) {
3617
- const counts = /* @__PURE__ */ new Map();
3618
- for (const tool of tools) {
3619
- counts.set(tool.category, (counts.get(tool.category) ?? 0) + 1);
3620
- }
3621
- return CATEGORY_ORDER.flatMap((category) => {
3622
- const count = counts.get(category) ?? 0;
3623
- return count > 0 ? [{ category, count }] : [];
3624
- });
3625
- }
3626
- function parseRequestTools(parsed) {
3627
- const rawTools = safeGetOwnProperty(parsed, "tools");
3628
- if (!Array.isArray(rawTools) || rawTools.length === 0) return null;
3629
- const tools = [];
3630
- for (const rawTool of rawTools) {
3631
- const tool = parseTool(rawTool);
3632
- if (tool !== null) tools.push(tool);
3633
- }
3634
- if (tools.length === 0) return null;
3635
- return {
3636
- tools,
3637
- toolChoiceLabel: formatToolChoice(safeGetOwnProperty(parsed, "tool_choice")),
3638
- categories: summarizeCategories(tools)
3639
- };
3640
- }
3641
3716
  function TabFallback() {
3642
3717
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1", "aria-hidden": "true" });
3643
3718
  }
@@ -5005,7 +5080,6 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
5005
5080
  }
5006
5081
  },
5007
5082
  children: [
5008
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 shrink-0 flex items-start pt-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono tabular-nums leading-none select-none", children: turnIndex + 1 }) }),
5009
5083
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-6 shrink-0 flex flex-col items-center pt-1.5 pb-1.5", children: [
5010
5084
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center h-[calc(0.75rem-8px)]" }),
5011
5085
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -5112,7 +5186,6 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
5112
5186
  const { log, stopReason: reason } = entry;
5113
5187
  const isTurnStart = visibleIdx === 0;
5114
5188
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
5115
- isTurnStart ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 shrink-0 flex items-start pt-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono tabular-nums leading-none select-none", children: turnIndex + 1 }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 shrink-0" }),
5116
5189
  /* @__PURE__ */ jsxRuntimeExports.jsx(
5117
5190
  ThreadConnector,
5118
5191
  {
@@ -5260,6 +5333,9 @@ const CandidatePromotionFailureResponseSchema = object({
5260
5333
  const CandidateUpdateResponseSchema = object({
5261
5334
  candidate: KnowledgeCandidateSchema
5262
5335
  });
5336
+ const CANDIDATE_GENERATE_TIMEOUT_MS = 3e4;
5337
+ const CANDIDATE_PROMOTE_TIMEOUT_MS = 15e3;
5338
+ const CANDIDATE_UPDATE_TIMEOUT_MS = 15e3;
5263
5339
  const CANDIDATE_STATUS_CLASSES = {
5264
5340
  draft: "border-amber-500/30 bg-amber-500/10 text-amber-500",
5265
5341
  promoted: "border-emerald-500/30 bg-emerald-500/10 text-emerald-500",
@@ -5765,19 +5841,13 @@ function AgentTraceSummary({
5765
5841
  setCandidateState({ status: "loading", error: null });
5766
5842
  void (async () => {
5767
5843
  try {
5768
- const response = await fetch(
5844
+ const parsed = await fetchJsonWithTimeout(
5769
5845
  `/api/knowledge/sessions/${encodeURIComponent(scopeId)}/candidates`,
5770
- { method: "POST" }
5846
+ CandidateResponseSchema,
5847
+ CANDIDATE_GENERATE_TIMEOUT_MS,
5848
+ { method: "POST" },
5849
+ (response) => `Candidate generation failed with ${String(response.status)}`
5771
5850
  );
5772
- if (!response.ok) {
5773
- const message = await readApiError(
5774
- response,
5775
- `Candidate generation failed with ${String(response.status)}`
5776
- );
5777
- setCandidateState({ status: "failed", error: message });
5778
- return;
5779
- }
5780
- const parsed = await parseJsonResponse$1(response, CandidateResponseSchema);
5781
5851
  setCandidates(parsed.candidates);
5782
5852
  setCandidatesExpanded(parsed.candidates.length > 0);
5783
5853
  setCandidateState(
@@ -5800,8 +5870,9 @@ function AgentTraceSummary({
5800
5870
  });
5801
5871
  void (async () => {
5802
5872
  try {
5803
- const response = await fetch(
5873
+ const response = await fetchWithTimeout(
5804
5874
  `/api/knowledge/candidates/${encodeURIComponent(candidateId)}/promote`,
5875
+ CANDIDATE_PROMOTE_TIMEOUT_MS,
5805
5876
  { method: "POST" }
5806
5877
  );
5807
5878
  const raw = await response.json().catch(() => null);
@@ -5850,23 +5921,17 @@ function AgentTraceSummary({
5850
5921
  return next;
5851
5922
  });
5852
5923
  try {
5853
- const response = await fetch(
5924
+ const parsed = await fetchJsonWithTimeout(
5854
5925
  `/api/knowledge/candidates/${encodeURIComponent(candidateId)}`,
5926
+ CandidateUpdateResponseSchema,
5927
+ CANDIDATE_UPDATE_TIMEOUT_MS,
5855
5928
  {
5856
5929
  method: "PATCH",
5857
5930
  headers: { "Content-Type": "application/json" },
5858
5931
  body: JSON.stringify(update)
5859
- }
5932
+ },
5933
+ (response) => `Candidate update failed with ${String(response.status)}`
5860
5934
  );
5861
- if (!response.ok) {
5862
- const message = await readApiError(
5863
- response,
5864
- `Candidate update failed with ${String(response.status)}`
5865
- );
5866
- setCandidateState({ status: "failed", error: message });
5867
- return false;
5868
- }
5869
- const parsed = await parseJsonResponse$1(response, CandidateUpdateResponseSchema);
5870
5935
  setCandidates(
5871
5936
  (current) => current.map(
5872
5937
  (candidate) => candidate.id === parsed.candidate.id ? parsed.candidate : candidate
@@ -6942,6 +7007,7 @@ function TransientToast({
6942
7007
  }
6943
7008
  );
6944
7009
  }
7010
+ const GROUP_OPERATION_TIMEOUT_MS = 2e4;
6945
7011
  function groupDeletePath(groupId, deleteEvidence) {
6946
7012
  const path = `/api/groups/${encodeURIComponent(groupId)}`;
6947
7013
  return deleteEvidence ? `${path}?deleteEvidence=1` : path;
@@ -7162,18 +7228,13 @@ function GroupsDialog() {
7162
7228
  setDeleteError(null);
7163
7229
  setExportError(null);
7164
7230
  try {
7165
- const response = await fetch(groupsClearPath(false), { method: "DELETE" });
7166
- if (!response.ok) {
7167
- showToast({
7168
- message: await readApiError(
7169
- response,
7170
- `Clear failed with status ${String(response.status)}`
7171
- ),
7172
- tone: "error"
7173
- });
7174
- return;
7175
- }
7176
- const parsed = await parseJsonResponse$1(response, DeleteInspectorGroupsResponseSchema);
7231
+ const parsed = await fetchJsonWithTimeout(
7232
+ groupsClearPath(false),
7233
+ DeleteInspectorGroupsResponseSchema,
7234
+ GROUP_OPERATION_TIMEOUT_MS,
7235
+ { method: "DELETE" },
7236
+ (response) => `Clear failed with status ${String(response.status)}`
7237
+ );
7177
7238
  setExportResult(null);
7178
7239
  setSelectedGroupId(null);
7179
7240
  await mutate();
@@ -7196,18 +7257,17 @@ function GroupsDialog() {
7196
7257
  setExportingGroupId(group.id);
7197
7258
  setExportError(null);
7198
7259
  try {
7199
- const response = await fetch(`/api/groups/${encodeURIComponent(group.id)}/evidence`, {
7200
- method: "POST",
7201
- headers: { "content-type": "application/json" },
7202
- body: JSON.stringify({ latestLogLimit: 10 })
7203
- });
7204
- if (!response.ok) {
7205
- setExportError(
7206
- await readApiError(response, `Export failed with status ${String(response.status)}`)
7207
- );
7208
- return;
7209
- }
7210
- const parsed = await parseJsonResponse$1(response, GroupEvidenceExportResultSchema);
7260
+ const parsed = await fetchJsonWithTimeout(
7261
+ `/api/groups/${encodeURIComponent(group.id)}/evidence`,
7262
+ GroupEvidenceExportResultSchema,
7263
+ GROUP_OPERATION_TIMEOUT_MS,
7264
+ {
7265
+ method: "POST",
7266
+ headers: { "content-type": "application/json" },
7267
+ body: JSON.stringify({ latestLogLimit: 10 })
7268
+ },
7269
+ (response) => `Export failed with status ${String(response.status)}`
7270
+ );
7211
7271
  setExportResult(parsed);
7212
7272
  await mutate();
7213
7273
  await mutateEvidence();
@@ -7242,16 +7302,13 @@ function GroupsDialog() {
7242
7302
  setDeletingGroupId(group.id);
7243
7303
  setDeleteError(null);
7244
7304
  try {
7245
- const response = await fetch(groupDeletePath(group.id, deleteEvidence), {
7246
- method: "DELETE"
7247
- });
7248
- if (!response.ok) {
7249
- setDeleteError(
7250
- await readApiError(response, `Delete failed with status ${String(response.status)}`)
7251
- );
7252
- return;
7253
- }
7254
- await parseJsonResponse$1(response, DeleteInspectorGroupResponseSchema);
7305
+ await fetchJsonWithTimeout(
7306
+ groupDeletePath(group.id, deleteEvidence),
7307
+ DeleteInspectorGroupResponseSchema,
7308
+ GROUP_OPERATION_TIMEOUT_MS,
7309
+ { method: "DELETE" },
7310
+ (response) => `Delete failed with status ${String(response.status)}`
7311
+ );
7255
7312
  setExportResult(
7256
7313
  (current) => current !== null && current.group.id === group.id ? null : current
7257
7314
  );
@@ -7934,70 +7991,6 @@ function MemberMetadata({ entries }) {
7934
7991
  }) })
7935
7992
  ] });
7936
7993
  }
7937
- function ClaudeCodeIcon() {
7938
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
7939
- "svg",
7940
- {
7941
- fill: "currentColor",
7942
- fillRule: "evenodd",
7943
- viewBox: "0 0 24 24",
7944
- xmlns: "http://www.w3.org/2000/svg",
7945
- className: "size-6 shrink-0",
7946
- children: [
7947
- /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: "Claude Code" }),
7948
- /* @__PURE__ */ jsxRuntimeExports.jsx(
7949
- "path",
7950
- {
7951
- clipRule: "evenodd",
7952
- d: "M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"
7953
- }
7954
- )
7955
- ]
7956
- }
7957
- );
7958
- }
7959
- function OpenCodeIcon() {
7960
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
7961
- "svg",
7962
- {
7963
- fill: "currentColor",
7964
- fillRule: "evenodd",
7965
- viewBox: "0 0 24 24",
7966
- xmlns: "http://www.w3.org/2000/svg",
7967
- className: "size-6 shrink-0",
7968
- children: [
7969
- /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: "OpenCode" }),
7970
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M16 6H8v12h8V6zm4 16H4V2h16v20z" })
7971
- ]
7972
- }
7973
- );
7974
- }
7975
- function MiMoCodeIcon() {
7976
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
7977
- "svg",
7978
- {
7979
- fill: "currentColor",
7980
- viewBox: "0 0 24 24",
7981
- xmlns: "http://www.w3.org/2000/svg",
7982
- className: "size-6 shrink-0",
7983
- children: [
7984
- /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: "MiMo Code" }),
7985
- /* @__PURE__ */ jsxRuntimeExports.jsx(
7986
- "text",
7987
- {
7988
- x: "12",
7989
- y: "18",
7990
- textAnchor: "middle",
7991
- fontSize: "16",
7992
- fontWeight: "800",
7993
- fontFamily: "system-ui, sans-serif",
7994
- children: "M"
7995
- }
7996
- )
7997
- ]
7998
- }
7999
- );
8000
- }
8001
7994
  const ExternalProviderSchema = object({
8002
7995
  name: string(),
8003
7996
  apiKey: string(),
@@ -8019,11 +8012,7 @@ const ImportResponseSchema$1 = object({
8019
8012
  message: string().optional(),
8020
8013
  errors: array(string()).optional()
8021
8014
  });
8022
- const sourceLogoMap = {
8023
- "claude-code": ClaudeCodeIcon,
8024
- opencode: OpenCodeIcon,
8025
- "mimo-code": MiMoCodeIcon
8026
- };
8015
+ const PROVIDER_IMPORT_TIMEOUT_MS$1 = 2e4;
8027
8016
  function ImportWizardDialog({
8028
8017
  open,
8029
8018
  onOpenChange,
@@ -8037,7 +8026,11 @@ function ImportWizardDialog({
8037
8026
  const [importing, setImporting] = reactExports.useState(false);
8038
8027
  const [importResult, setImportResult] = reactExports.useState(null);
8039
8028
  const [importError, setImportError] = reactExports.useState(null);
8029
+ const scanRequestIdRef = reactExports.useRef(0);
8030
+ const importRequestIdRef = reactExports.useRef(0);
8040
8031
  const scan = reactExports.useCallback(() => {
8032
+ const requestId = scanRequestIdRef.current + 1;
8033
+ scanRequestIdRef.current = requestId;
8041
8034
  setScanning(true);
8042
8035
  setScanError(null);
8043
8036
  setWarnings([]);
@@ -8045,29 +8038,26 @@ function ImportWizardDialog({
8045
8038
  setSelected(/* @__PURE__ */ new Set());
8046
8039
  setImportResult(null);
8047
8040
  setImportError(null);
8048
- fetch("/api/providers/scan").then((res) => {
8049
- if (!res.ok) {
8050
- return res.text().then((text) => {
8051
- setScanError(`Scan failed (${res.status}): ${text}`);
8052
- });
8053
- }
8054
- return res.json().then((data) => {
8055
- const parsed = ScanResponseSchema.safeParse(data);
8056
- if (!parsed.success) {
8057
- setScanError(`Invalid response: ${parsed.error.message}`);
8058
- return;
8059
- }
8060
- setProviders(parsed.data.providers);
8061
- setWarnings(parsed.data.warnings ?? []);
8062
- const indices = /* @__PURE__ */ new Set();
8063
- parsed.data.providers.forEach((p, i) => {
8064
- if (!p.alreadyExists) indices.add(i);
8065
- });
8066
- setSelected(indices);
8041
+ void fetchJsonWithTimeout(
8042
+ "/api/providers/scan",
8043
+ ScanResponseSchema,
8044
+ PROVIDER_IMPORT_TIMEOUT_MS$1,
8045
+ void 0,
8046
+ (response) => `Scan failed (${String(response.status)})`
8047
+ ).then((data) => {
8048
+ if (scanRequestIdRef.current !== requestId) return;
8049
+ setProviders(data.providers);
8050
+ setWarnings(data.warnings ?? []);
8051
+ const indices = /* @__PURE__ */ new Set();
8052
+ data.providers.forEach((provider, index) => {
8053
+ if (!provider.alreadyExists) indices.add(index);
8067
8054
  });
8055
+ setSelected(indices);
8068
8056
  }).catch((err) => {
8057
+ if (scanRequestIdRef.current !== requestId) return;
8069
8058
  setScanError(err instanceof Error ? err.message : String(err));
8070
8059
  }).finally(() => {
8060
+ if (scanRequestIdRef.current !== requestId) return;
8071
8061
  setScanning(false);
8072
8062
  });
8073
8063
  }, []);
@@ -8076,6 +8066,13 @@ function ImportWizardDialog({
8076
8066
  scan();
8077
8067
  }
8078
8068
  }, [open, scan]);
8069
+ reactExports.useEffect(() => {
8070
+ if (open) return;
8071
+ scanRequestIdRef.current += 1;
8072
+ importRequestIdRef.current += 1;
8073
+ setScanning(false);
8074
+ setImporting(false);
8075
+ }, [open]);
8079
8076
  const toggleProvider = reactExports.useCallback((index) => {
8080
8077
  setSelected((prev) => {
8081
8078
  const next = new Set(prev);
@@ -8090,6 +8087,8 @@ function ImportWizardDialog({
8090
8087
  const importSelected = reactExports.useCallback(() => {
8091
8088
  const toImport = providers.filter((_, i) => selected.has(i));
8092
8089
  if (toImport.length === 0) return;
8090
+ const requestId = importRequestIdRef.current + 1;
8091
+ importRequestIdRef.current = requestId;
8093
8092
  setImporting(true);
8094
8093
  setImportError(null);
8095
8094
  setImportResult(null);
@@ -8106,17 +8105,18 @@ function ImportWizardDialog({
8106
8105
  createdAt: now,
8107
8106
  updatedAt: now
8108
8107
  }));
8109
- fetch("/api/providers/import", {
8110
- method: "POST",
8111
- headers: { "Content-Type": "application/json" },
8112
- body: JSON.stringify({ providers: providersPayload })
8113
- }).then((res) => res.json()).then((data) => {
8114
- const parsed = ImportResponseSchema$1.safeParse(data);
8115
- if (!parsed.success) {
8116
- setImportError(`Invalid response: ${parsed.error.message}`);
8117
- return;
8118
- }
8119
- const result = parsed.data;
8108
+ void fetchJsonWithTimeout(
8109
+ "/api/providers/import",
8110
+ ImportResponseSchema$1,
8111
+ PROVIDER_IMPORT_TIMEOUT_MS$1,
8112
+ {
8113
+ method: "POST",
8114
+ headers: { "Content-Type": "application/json" },
8115
+ body: JSON.stringify({ providers: providersPayload })
8116
+ },
8117
+ (response) => `Import failed (${String(response.status)})`
8118
+ ).then((result) => {
8119
+ if (importRequestIdRef.current !== requestId) return;
8120
8120
  setProviders(
8121
8121
  (prev) => prev.map((p, i) => selected.has(i) ? { ...p, alreadyExists: true } : p)
8122
8122
  );
@@ -8128,8 +8128,10 @@ function ImportWizardDialog({
8128
8128
  }
8129
8129
  onImportComplete();
8130
8130
  }).catch((err) => {
8131
+ if (importRequestIdRef.current !== requestId) return;
8131
8132
  setImportError(err instanceof Error ? err.message : String(err));
8132
8133
  }).finally(() => {
8134
+ if (importRequestIdRef.current !== requestId) return;
8133
8135
  setImporting(false);
8134
8136
  });
8135
8137
  }, [providers, selected, onImportComplete]);
@@ -8170,11 +8172,7 @@ function ImportWizardDialog({
8170
8172
  ),
8171
8173
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
8172
8174
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
8173
- (() => {
8174
- const LogoComponent = sourceLogoMap[p.sourceTool];
8175
- if (LogoComponent === void 0) return null;
8176
- return /* @__PURE__ */ jsxRuntimeExports.jsx(LogoComponent, {});
8177
- })(),
8175
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ClientLogo, { client: p.sourceTool, className: "size-7 rounded-[8px]" }),
8178
8176
  p.alreadyExists && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: "Already added" })
8179
8177
  ] }),
8180
8178
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 text-xs leading-snug text-muted-foreground", children: [
@@ -9848,6 +9846,13 @@ const ModelMetadataRefreshResponseSchema = object({
9848
9846
  sourceUrl: string()
9849
9847
  });
9850
9848
  const NETWORK_ERROR_MESSAGE = "Network error: could not reach the server. Is the proxy running?";
9849
+ const CONFIG_PATH_FETCH_TIMEOUT_MS = 5e3;
9850
+ const PROVIDER_TEST_LOG_TIMEOUT_MS = 5e3;
9851
+ const PROVIDER_WRITE_TIMEOUT_MS = 15e3;
9852
+ const PROVIDER_DELETE_TIMEOUT_MS = 1e4;
9853
+ const PROVIDER_EXPORT_TIMEOUT_MS = 2e4;
9854
+ const PROVIDER_IMPORT_TIMEOUT_MS = 2e4;
9855
+ const PROVIDER_METADATA_TIMEOUT_MS = 3e4;
9851
9856
  function createProviderPayload(data) {
9852
9857
  return {
9853
9858
  name: data.name,
@@ -9871,7 +9876,7 @@ async function persistProviderTestLog(providerId, results) {
9871
9876
  return;
9872
9877
  }
9873
9878
  try {
9874
- await fetch(`/api/providers/${providerId}/test/log`, {
9879
+ await fetchWithTimeout(`/api/providers/${providerId}/test/log`, PROVIDER_TEST_LOG_TIMEOUT_MS, {
9875
9880
  method: "POST",
9876
9881
  headers: { "Content-Type": "application/json" },
9877
9882
  body: JSON.stringify(results)
@@ -9939,11 +9944,12 @@ function ProvidersPanel({
9939
9944
  reactExports.useEffect(() => {
9940
9945
  void (async () => {
9941
9946
  try {
9942
- const res = await fetch("/api/config/paths");
9943
- if (res.ok) {
9944
- const data = await parseJsonResponse$1(res, ConfigPathsResponseSchema);
9945
- setConfigPath(data.providerConfig);
9946
- }
9947
+ const data = await fetchJsonWithTimeout(
9948
+ "/api/config/paths",
9949
+ ConfigPathsResponseSchema,
9950
+ CONFIG_PATH_FETCH_TIMEOUT_MS
9951
+ );
9952
+ setConfigPath(data.providerConfig);
9947
9953
  } catch {
9948
9954
  }
9949
9955
  })();
@@ -10017,29 +10023,30 @@ function ProvidersPanel({
10017
10023
  }
10018
10024
  }, 1e3);
10019
10025
  try {
10020
- const res = await fetch(`/api/providers/${providerId}/test`, {
10021
- method: "POST",
10022
- signal: controller.signal
10023
- });
10024
- if (res.ok) {
10025
- const results = await parseJsonResponse$1(res, ProviderTestResultsSchema);
10026
- updateTestResults(providerId, results);
10027
- await persistProviderTestLog(providerId, results);
10028
- } else {
10026
+ const results = await fetchJsonWithTimeout(
10027
+ `/api/providers/${providerId}/test`,
10028
+ ProviderTestResultsSchema,
10029
+ timeoutSeconds * 1e3 + 1e3,
10030
+ {
10031
+ method: "POST",
10032
+ signal: controller.signal
10033
+ },
10034
+ (response) => `HTTP ${response.status}: ${response.statusText}`
10035
+ );
10036
+ updateTestResults(providerId, results);
10037
+ await persistProviderTestLog(providerId, results);
10038
+ } catch (err) {
10039
+ const isAbort = err instanceof Error && err.name === "AbortError";
10040
+ const isTimeout = err instanceof ApiTimeoutError;
10041
+ if (isAbort || isTimeout) {
10029
10042
  updateTestResults(
10030
10043
  providerId,
10031
- createFailedProviderTestResults(
10032
- `HTTP ${res.status}: ${res.statusText}`,
10033
- "server_error"
10034
- )
10044
+ createFailedProviderTestResults("Request timed out", "timeout")
10035
10045
  );
10036
- }
10037
- } catch (err) {
10038
- const isAbort = err instanceof Error && err.name === "AbortError";
10039
- if (isAbort) {
10046
+ } else if (err instanceof Error) {
10040
10047
  updateTestResults(
10041
10048
  providerId,
10042
- createFailedProviderTestResults("Request timed out", "timeout")
10049
+ createFailedProviderTestResults(err.message, "server_error")
10043
10050
  );
10044
10051
  }
10045
10052
  } finally {
@@ -10065,18 +10072,17 @@ function ProvidersPanel({
10065
10072
  });
10066
10073
  try {
10067
10074
  const payload = url !== void 0 && url.trim() !== "" ? { url } : {};
10068
- const res = await fetch(`/api/providers/${providerId}/model-metadata`, {
10069
- method: "POST",
10070
- headers: { "Content-Type": "application/json" },
10071
- body: JSON.stringify(payload)
10072
- });
10073
- if (!res.ok) {
10074
- if (showError) {
10075
- setError(await readApiError(res, "Failed to refresh model metadata"));
10076
- }
10077
- return false;
10078
- }
10079
- const data = await parseJsonResponse$1(res, ModelMetadataRefreshResponseSchema);
10075
+ const data = await fetchJsonWithTimeout(
10076
+ `/api/providers/${providerId}/model-metadata`,
10077
+ ModelMetadataRefreshResponseSchema,
10078
+ PROVIDER_METADATA_TIMEOUT_MS,
10079
+ {
10080
+ method: "POST",
10081
+ headers: { "Content-Type": "application/json" },
10082
+ body: JSON.stringify(payload)
10083
+ },
10084
+ () => "Failed to refresh model metadata"
10085
+ );
10080
10086
  if (data.imported === 0 && showError) {
10081
10087
  setError("No matching model metadata found for this provider.");
10082
10088
  } else {
@@ -10085,9 +10091,9 @@ function ProvidersPanel({
10085
10091
  triggerHighlight(data.provider.id);
10086
10092
  refreshProviders();
10087
10093
  return data.imported > 0;
10088
- } catch {
10094
+ } catch (err) {
10089
10095
  if (showError) {
10090
- setError(NETWORK_ERROR_MESSAGE);
10096
+ setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
10091
10097
  }
10092
10098
  return false;
10093
10099
  } finally {
@@ -10103,16 +10109,17 @@ function ProvidersPanel({
10103
10109
  function handleAddProvider(data) {
10104
10110
  void (async () => {
10105
10111
  try {
10106
- const res = await fetch("/api/providers", {
10107
- method: "POST",
10108
- headers: { "Content-Type": "application/json" },
10109
- body: JSON.stringify(createProviderPayload(data))
10110
- });
10111
- if (!res.ok) {
10112
- setError(await readApiError(res, "Failed to add provider"));
10113
- return;
10114
- }
10115
- const newProvider = await parseJsonResponse$1(res, ProviderConfigSchema);
10112
+ const newProvider = await fetchJsonWithTimeout(
10113
+ "/api/providers",
10114
+ ProviderConfigSchema,
10115
+ PROVIDER_WRITE_TIMEOUT_MS,
10116
+ {
10117
+ method: "POST",
10118
+ headers: { "Content-Type": "application/json" },
10119
+ body: JSON.stringify(createProviderPayload(data))
10120
+ },
10121
+ () => "Failed to add provider"
10122
+ );
10116
10123
  setShowForm(false);
10117
10124
  triggerHighlight(newProvider.id);
10118
10125
  if (data.modelMetadataUrl !== void 0 && data.modelMetadataUrl.trim() !== "") {
@@ -10120,8 +10127,8 @@ function ProvidersPanel({
10120
10127
  }
10121
10128
  refreshProviders();
10122
10129
  await runTest(newProvider.id);
10123
- } catch {
10124
- setError(NETWORK_ERROR_MESSAGE);
10130
+ } catch (err) {
10131
+ setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
10125
10132
  }
10126
10133
  })();
10127
10134
  }
@@ -10129,16 +10136,17 @@ function ProvidersPanel({
10129
10136
  if (!editingProvider) return;
10130
10137
  void (async () => {
10131
10138
  try {
10132
- const res = await fetch(`/api/providers/${editingProvider.id}`, {
10133
- method: "PUT",
10134
- headers: { "Content-Type": "application/json" },
10135
- body: JSON.stringify(createProviderPayload(data))
10136
- });
10137
- if (!res.ok) {
10138
- setError(await readApiError(res, "Failed to update provider"));
10139
- return;
10140
- }
10141
- const updated = await parseJsonResponse$1(res, ProviderConfigSchema);
10139
+ const updated = await fetchJsonWithTimeout(
10140
+ `/api/providers/${editingProvider.id}`,
10141
+ ProviderConfigSchema,
10142
+ PROVIDER_WRITE_TIMEOUT_MS,
10143
+ {
10144
+ method: "PUT",
10145
+ headers: { "Content-Type": "application/json" },
10146
+ body: JSON.stringify(createProviderPayload(data))
10147
+ },
10148
+ () => "Failed to update provider"
10149
+ );
10142
10150
  setEditingProvider(void 0);
10143
10151
  triggerHighlight(updated.id);
10144
10152
  if (data.modelMetadataUrl !== void 0 && data.modelMetadataUrl.trim() !== "") {
@@ -10149,8 +10157,8 @@ function ProvidersPanel({
10149
10157
  if (criticalFieldsChanged) {
10150
10158
  await runTest(updated.id);
10151
10159
  }
10152
- } catch {
10153
- setError(NETWORK_ERROR_MESSAGE);
10160
+ } catch (err) {
10161
+ setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
10154
10162
  }
10155
10163
  })();
10156
10164
  }
@@ -10164,7 +10172,7 @@ function ProvidersPanel({
10164
10172
  void (async () => {
10165
10173
  let res;
10166
10174
  try {
10167
- res = await fetch(`/api/providers/${providerId}`, {
10175
+ res = await fetchWithTimeout(`/api/providers/${providerId}`, PROVIDER_DELETE_TIMEOUT_MS, {
10168
10176
  method: "DELETE"
10169
10177
  });
10170
10178
  } catch {
@@ -10183,7 +10191,7 @@ function ProvidersPanel({
10183
10191
  const url = `/api/providers/export${""}`;
10184
10192
  void (async () => {
10185
10193
  try {
10186
- const res = await fetch(url);
10194
+ const res = await fetchWithTimeout(url, PROVIDER_EXPORT_TIMEOUT_MS);
10187
10195
  if (!res.ok) {
10188
10196
  setError("Failed to export providers");
10189
10197
  return;
@@ -10211,7 +10219,7 @@ function ProvidersPanel({
10211
10219
  void (async () => {
10212
10220
  try {
10213
10221
  const text = await file.text();
10214
- const res = await fetch("/api/providers/import", {
10222
+ const res = await fetchWithTimeout("/api/providers/import", PROVIDER_IMPORT_TIMEOUT_MS, {
10215
10223
  method: "POST",
10216
10224
  headers: { "Content-Type": "application/json" },
10217
10225
  body: JSON.stringify(text)
@@ -10511,6 +10519,7 @@ const LogStorageStatsSchema = object({
10511
10519
  sessionArchiveFileCount: number(),
10512
10520
  sessionArchiveBytes: number()
10513
10521
  });
10522
+ const STORAGE_STATS_FETCH_TIMEOUT_MS = 1e4;
10514
10523
  function formatBytes(bytes) {
10515
10524
  if (bytes < 1024) return `${bytes} B`;
10516
10525
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
@@ -10526,18 +10535,14 @@ function StorageSettingsTab() {
10526
10535
  setLoading(true);
10527
10536
  setError(null);
10528
10537
  try {
10529
- const response = await fetch("/api/logs?stats=1");
10530
- if (!response.ok) {
10531
- setError(`Failed to load storage stats: HTTP ${String(response.status)}`);
10532
- return;
10533
- }
10534
- const data = await response.json();
10535
- const parsed = LogStorageStatsSchema.safeParse(data);
10536
- if (!parsed.success) {
10537
- setError("Storage stats response was not valid.");
10538
- return;
10539
- }
10540
- setStats(parsed.data);
10538
+ const data = await fetchJsonWithTimeout(
10539
+ "/api/logs?stats=1",
10540
+ LogStorageStatsSchema,
10541
+ STORAGE_STATS_FETCH_TIMEOUT_MS,
10542
+ void 0,
10543
+ (response) => `Failed to load storage stats: HTTP ${String(response.status)}`
10544
+ );
10545
+ setStats(data);
10541
10546
  } catch (err) {
10542
10547
  setError(err instanceof Error ? err.message : String(err));
10543
10548
  } finally {
@@ -11153,6 +11158,28 @@ const EcosystemPackagesResponseSchema = object({
11153
11158
  packages: array(EcosystemPackageSchema),
11154
11159
  runnerPresets: array(EcosystemRunnerPresetSchema)
11155
11160
  });
11161
+ const EcosystemTaskActionSchema = _enum(["install", "upgrade", "help", "runner-presets"]);
11162
+ const EcosystemTaskStatusSchema = _enum(["queued", "running", "succeeded", "failed"]);
11163
+ const EcosystemTaskSchema = object({
11164
+ id: string(),
11165
+ packageId: string(),
11166
+ npmName: string(),
11167
+ action: EcosystemTaskActionSchema,
11168
+ status: EcosystemTaskStatusSchema,
11169
+ command: string(),
11170
+ output: string(),
11171
+ createdAt: string(),
11172
+ startedAt: string().nullable(),
11173
+ finishedAt: string().nullable(),
11174
+ exitCode: number().nullable(),
11175
+ error: string().nullable()
11176
+ });
11177
+ const EcosystemTaskResponseSchema = object({
11178
+ task: EcosystemTaskSchema
11179
+ });
11180
+ object({
11181
+ tasks: array(EcosystemTaskSchema)
11182
+ });
11156
11183
  const WORKFLOWS = [
11157
11184
  {
11158
11185
  id: "observe",
@@ -11195,6 +11222,23 @@ async function fetchEcosystemPackages(url) {
11195
11222
  (response) => `Failed to load ecosystem packages: ${String(response.status)}`
11196
11223
  );
11197
11224
  }
11225
+ async function fetchEcosystemTask(url) {
11226
+ return fetchJson(
11227
+ url,
11228
+ EcosystemTaskResponseSchema,
11229
+ void 0,
11230
+ (response) => `Failed to load ecosystem task: ${String(response.status)}`
11231
+ );
11232
+ }
11233
+ async function startEcosystemTask(packageId, action) {
11234
+ const response = await fetchJson(
11235
+ `/api/ecosystem/packages/${encodeURIComponent(packageId)}/${action}`,
11236
+ EcosystemTaskResponseSchema,
11237
+ { method: "POST" },
11238
+ (result) => `Failed to start ecosystem task: ${String(result.status)}`
11239
+ );
11240
+ return response.task;
11241
+ }
11198
11242
  function stateLabel(state) {
11199
11243
  switch (state) {
11200
11244
  case "installed":
@@ -11232,7 +11276,49 @@ function versionLabel(pkg) {
11232
11276
  if (pkg.latestVersion !== null) return `latest v${pkg.latestVersion}`;
11233
11277
  return "not published";
11234
11278
  }
11235
- function PackageStatusCard({ pkg }) {
11279
+ function taskActionLabel(action) {
11280
+ switch (action) {
11281
+ case "install":
11282
+ return "Install";
11283
+ case "upgrade":
11284
+ return "Upgrade";
11285
+ case "help":
11286
+ return "Help";
11287
+ case "runner-presets":
11288
+ return "Presets";
11289
+ }
11290
+ }
11291
+ function taskStatusLabel(task) {
11292
+ switch (task.status) {
11293
+ case "queued":
11294
+ return "Queued";
11295
+ case "running":
11296
+ return `${taskActionLabel(task.action)} running`;
11297
+ case "succeeded":
11298
+ return `${taskActionLabel(task.action)} complete`;
11299
+ case "failed":
11300
+ return `${taskActionLabel(task.action)} failed`;
11301
+ }
11302
+ }
11303
+ function packagePrimaryAction(pkg) {
11304
+ switch (pkg.state) {
11305
+ case "available":
11306
+ return "install";
11307
+ case "update-available":
11308
+ return "upgrade";
11309
+ case "installed":
11310
+ case "planned":
11311
+ case "unknown":
11312
+ return null;
11313
+ }
11314
+ }
11315
+ function PackageStatusCard({
11316
+ pkg,
11317
+ activeTask,
11318
+ onStartTask
11319
+ }) {
11320
+ const primaryAction = packagePrimaryAction(pkg);
11321
+ const packageTaskRunning = activeTask !== null && activeTask.packageId === pkg.id && activeTask.status === "running";
11236
11322
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg bg-white/[0.025] p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]", children: [
11237
11323
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
11238
11324
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
@@ -11249,9 +11335,81 @@ function PackageStatusCard({ pkg }) {
11249
11335
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.035] px-2 py-1 font-mono text-white/50", children: versionLabel(pkg) }),
11250
11336
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.035] px-2 py-1 text-white/45", children: pkg.capability }),
11251
11337
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.035] px-2 py-1 font-mono text-white/35", children: pkg.installCommand })
11338
+ ] }),
11339
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [
11340
+ primaryAction !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
11341
+ Button,
11342
+ {
11343
+ type: "button",
11344
+ size: "sm",
11345
+ variant: "secondary",
11346
+ disabled: packageTaskRunning,
11347
+ onClick: () => onStartTask(pkg.id, primaryAction),
11348
+ children: [
11349
+ packageTaskRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3.5" }),
11350
+ taskActionLabel(primaryAction)
11351
+ ]
11352
+ }
11353
+ ),
11354
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
11355
+ Button,
11356
+ {
11357
+ type: "button",
11358
+ size: "sm",
11359
+ variant: "outline",
11360
+ disabled: packageTaskRunning || pkg.state === "planned",
11361
+ onClick: () => onStartTask(pkg.id, "help"),
11362
+ children: [
11363
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5" }),
11364
+ "Help"
11365
+ ]
11366
+ }
11367
+ ),
11368
+ pkg.id === "eval-harness" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
11369
+ Button,
11370
+ {
11371
+ type: "button",
11372
+ size: "sm",
11373
+ variant: "ghost",
11374
+ disabled: packageTaskRunning || pkg.state === "planned",
11375
+ onClick: () => onStartTask(pkg.id, "runner-presets"),
11376
+ children: [
11377
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Beaker, { className: "size-3.5" }),
11378
+ "Presets"
11379
+ ]
11380
+ }
11381
+ )
11252
11382
  ] })
11253
11383
  ] });
11254
11384
  }
11385
+ function TaskConsole({ task }) {
11386
+ if (task === null) return null;
11387
+ const isRunning = task.status === "queued" || task.status === "running";
11388
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mt-3 rounded-lg bg-white/[0.025] p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]", children: [
11389
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
11390
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
11391
+ isRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-4 animate-spin text-cyan-200/75" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-4 text-white/55" }),
11392
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
11393
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold", children: taskStatusLabel(task) }),
11394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "break-all font-mono text-[11px] text-white/35", children: task.command })
11395
+ ] })
11396
+ ] }),
11397
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
11398
+ Badge,
11399
+ {
11400
+ variant: "outline",
11401
+ className: cn(
11402
+ "shrink-0",
11403
+ task.status === "failed" ? "border-red-400/20 bg-red-400/8 text-red-100" : "border-cyan-300/20 bg-cyan-300/8 text-cyan-100"
11404
+ ),
11405
+ children: task.status
11406
+ }
11407
+ )
11408
+ ] }),
11409
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-3 max-h-56 overflow-auto whitespace-pre-wrap rounded-md bg-black/30 p-3 font-mono text-[11px] leading-5 text-white/60 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]", children: task.output.trim().length > 0 ? task.output : "Waiting for npm output..." }),
11410
+ task.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-xs text-red-100", children: task.error })
11411
+ ] });
11412
+ }
11255
11413
  function WorkflowTile({ workflow }) {
11256
11414
  const Icon2 = workflow.icon;
11257
11415
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg bg-white/[0.025] p-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]", children: [
@@ -11307,10 +11465,23 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11307
11465
  const [open, setOpen] = reactExports.useState(false);
11308
11466
  const [copied, setCopied] = reactExports.useState(false);
11309
11467
  const [copiedPresets, setCopiedPresets] = reactExports.useState(false);
11468
+ const [activeTaskId, setActiveTaskId] = reactExports.useState(null);
11469
+ const [taskError, setTaskError] = reactExports.useState(null);
11310
11470
  const response = useSWR("/api/ecosystem/packages", fetchEcosystemPackages, {
11311
11471
  revalidateOnFocus: false,
11312
11472
  revalidateIfStale: false
11313
11473
  });
11474
+ const activeTaskResponse = useSWR(
11475
+ activeTaskId === null ? null : `/api/ecosystem/tasks/${activeTaskId}`,
11476
+ fetchEcosystemTask,
11477
+ {
11478
+ refreshInterval: (taskResponse) => {
11479
+ const task = taskResponse?.task;
11480
+ return task !== void 0 && (task.status === "queued" || task.status === "running") ? 900 : 0;
11481
+ },
11482
+ revalidateOnFocus: false
11483
+ }
11484
+ );
11314
11485
  const packages = response.data?.packages ?? [];
11315
11486
  const runnerPresets = response.data?.runnerPresets ?? [];
11316
11487
  const installedCount = reactExports.useMemo(
@@ -11331,6 +11502,21 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11331
11502
  window.setTimeout(() => setCopiedPresets(false), 1200);
11332
11503
  });
11333
11504
  };
11505
+ const activeTask = activeTaskResponse.data?.task ?? null;
11506
+ reactExports.useEffect(() => {
11507
+ if (activeTask === null) return;
11508
+ if (activeTask.status !== "succeeded") return;
11509
+ void response.mutate();
11510
+ }, [activeTask, response]);
11511
+ const handleStartTask = (packageId, action) => {
11512
+ setTaskError(null);
11513
+ void startEcosystemTask(packageId, action).then((task) => {
11514
+ setActiveTaskId(task.id);
11515
+ void activeTaskResponse.mutate({ task }, { revalidate: true });
11516
+ }).catch((error) => {
11517
+ setTaskError(error instanceof Error ? error.message : "Failed to start ecosystem task");
11518
+ });
11519
+ };
11334
11520
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Dialog, { open, onOpenChange: setOpen, children: [
11335
11521
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
11336
11522
  Button,
@@ -11418,7 +11604,15 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11418
11604
  ] }),
11419
11605
  response.error !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg bg-red-500/8 p-3 text-xs text-red-100 shadow-[inset_0_0_0_1px_rgba(248,113,113,0.16)]", children: "Package status is temporarily unavailable." }),
11420
11606
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-3 md:grid-cols-3", children: [
11421
- packages.map((pkg) => /* @__PURE__ */ jsxRuntimeExports.jsx(PackageStatusCard, { pkg }, pkg.id)),
11607
+ packages.map((pkg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
11608
+ PackageStatusCard,
11609
+ {
11610
+ pkg,
11611
+ activeTask,
11612
+ onStartTask: handleStartTask
11613
+ },
11614
+ pkg.id
11615
+ )),
11422
11616
  response.isLoading && packages.length === 0 && [0, 1, 2].map((index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
11423
11617
  "div",
11424
11618
  {
@@ -11427,7 +11621,9 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11427
11621
  index
11428
11622
  ))
11429
11623
  ] })
11430
- ] })
11624
+ ] }),
11625
+ taskError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 rounded-lg bg-red-500/8 p-3 text-xs text-red-100 shadow-[inset_0_0_0_1px_rgba(248,113,113,0.16)]", children: taskError }),
11626
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TaskConsole, { task: activeTask })
11431
11627
  ] })
11432
11628
  ] })
11433
11629
  ] });
@@ -13592,6 +13788,36 @@ function LogPaginationButton({
13592
13788
  }
13593
13789
  );
13594
13790
  }
13791
+ function formatDurationSeconds(ms) {
13792
+ return `${Math.max(0, Math.ceil(ms / 1e3)).toString()}s`;
13793
+ }
13794
+ function SessionLoadProgressBar({ progress }) {
13795
+ const elapsedRatio = Math.min(1, progress.elapsedMs / progress.timeoutMs);
13796
+ const percent = Math.max(4, Math.round(elapsedRatio * 100));
13797
+ const remainingMs = Math.max(0, progress.timeoutMs - progress.elapsedMs);
13798
+ const nearTimeout = elapsedRatio >= 0.75;
13799
+ const details = [
13800
+ progress.loadedLogs > 0 ? `${progress.loadedLogs.toString()} logs` : null,
13801
+ progress.loadedPages > 0 ? `${progress.loadedPages.toString()} page${progress.loadedPages === 1 ? "" : "s"}` : null,
13802
+ `${formatDurationSeconds(remainingMs)} before timeout`
13803
+ ].filter((item) => item !== null);
13804
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 space-y-1 px-1 pb-1", children: [
13805
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center justify-between gap-3 font-mono text-[10px] text-muted-foreground", children: [
13806
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: progress.label }),
13807
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("shrink-0 tabular-nums", nearTimeout && "text-amber-300"), children: details.join(" / ") })
13808
+ ] }),
13809
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-white/[0.06]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
13810
+ "div",
13811
+ {
13812
+ className: cn(
13813
+ "h-full rounded-full transition-all duration-300",
13814
+ nearTimeout ? "bg-amber-300/80" : "bg-cyan-300/70"
13815
+ ),
13816
+ style: { width: `${percent.toString()}%` }
13817
+ }
13818
+ ) })
13819
+ ] });
13820
+ }
13595
13821
  function LogPaginationBar({
13596
13822
  logs,
13597
13823
  pagination,
@@ -13609,7 +13835,8 @@ function LogPaginationBar({
13609
13835
  children: [
13610
13836
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0 px-1 font-mono text-[11px] text-muted-foreground sm:flex-1", children: [
13611
13837
  formatPaginationStatus({ logs, pagination }),
13612
- pagination.isLoading && logs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-2", children: "Loading..." })
13838
+ pagination.isLoading && logs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-2", children: "Loading..." }),
13839
+ pagination.loadProgress !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(SessionLoadProgressBar, { progress: pagination.loadProgress })
13613
13840
  ] }),
13614
13841
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex w-full items-center gap-1 overflow-x-auto sm:w-auto", children: [
13615
13842
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -13747,16 +13974,6 @@ function CopyableCommand({ command }) {
13747
13974
  )
13748
13975
  ] });
13749
13976
  }
13750
- function McpReadyBadge() {
13751
- return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
13752
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("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", children: [
13753
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "size-1.5 rounded-full bg-cyan-200 shadow-[0_0_8px_rgba(125,211,252,0.75)]" }),
13754
- "MCP Ready",
13755
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden text-cyan-200/70 sm:inline", children: "/api/mcp" })
13756
- ] }) }),
13757
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { sideOffset: 8, className: "max-w-[320px] text-left leading-relaxed", children: "Coding agents can inspect logs, replay requests, test providers, and debug sessions through MCP at /api/mcp." })
13758
- ] }) });
13759
- }
13760
13977
  function SidebarPanel({
13761
13978
  label,
13762
13979
  children,
@@ -13801,7 +14018,7 @@ function CrabFamily({
13801
14018
  entrancePhase
13802
14019
  }) {
13803
14020
  if (compact) {
13804
- return /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-7 text-amber-500" });
14021
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-14 text-amber-500" });
13805
14022
  }
13806
14023
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex shrink-0 items-end gap-1 group cursor-default", "aria-hidden": "true", children: [
13807
14024
  /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" }),
@@ -13829,30 +14046,27 @@ function BrandHeader({
13829
14046
  logCount
13830
14047
  }) {
13831
14048
  if (compact) {
13832
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("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", children: [
13833
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
13834
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-fit items-start gap-2", children: [
13835
- /* @__PURE__ */ jsxRuntimeExports.jsx(CrabFamily, { compact: true, entrancePhase: crabEntrancePhase }),
13836
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-fit", children: [
13837
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-fit flex-wrap items-baseline gap-x-2 gap-y-0.5", children: [
13838
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-base font-bold", children: "Agent Inspector" }),
13839
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 font-mono text-[11px] font-semibold text-muted-foreground sm:inline", children: [
13840
- "v",
13841
- packageJson.version
13842
- ] })
13843
- ] }),
13844
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "hidden text-[11px] font-semibold text-muted-foreground sm:block", children: "Local First. Evidence First." })
13845
- ] })
13846
- ] }),
13847
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
13848
- /* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
13849
- /* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
13850
- /* @__PURE__ */ jsxRuntimeExports.jsx(AgentLabDialog, { currentSessionId, logCount }),
13851
- /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {})
14049
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("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", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
14050
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-fit items-start gap-3", children: [
14051
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CrabFamily, { compact: true, entrancePhase: crabEntrancePhase }),
14052
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-fit", children: [
14053
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-fit flex-wrap items-baseline gap-x-2 gap-y-0.5", children: [
14054
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-base font-bold", children: "Agent Inspector" }),
14055
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 font-mono text-[11px] font-semibold text-muted-foreground sm:inline", children: [
14056
+ "v",
14057
+ packageJson.version
14058
+ ] })
14059
+ ] }),
14060
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "hidden text-[11px] font-semibold text-muted-foreground sm:block", children: "Local First. Evidence First." })
13852
14061
  ] })
13853
14062
  ] }),
13854
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 hidden sm:block", children: /* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {}) })
13855
- ] });
14063
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
14064
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
14065
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
14066
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AgentLabDialog, { currentSessionId, logCount }),
14067
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {})
14068
+ ] })
14069
+ ] }) });
13856
14070
  }
13857
14071
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-x-4 gap-y-3 pb-6", children: [
13858
14072
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-w-10 flex-1", "aria-hidden": "true" }),
@@ -13869,8 +14083,7 @@ function BrandHeader({
13869
14083
  /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4 shrink-0 text-muted-foreground/70", "aria-hidden": "true" }),
13870
14084
  /* @__PURE__ */ jsxRuntimeExports.jsx(McpLogo, { className: "size-10 shrink-0" })
13871
14085
  ] }),
13872
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "max-w-full text-sm font-semibold text-muted-foreground", children: "Local First. Evidence First." }),
13873
- /* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {})
14086
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "max-w-full text-sm font-semibold text-muted-foreground", children: "Local First. Evidence First." })
13874
14087
  ] }),
13875
14088
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 justify-end gap-1", children: [
13876
14089
  /* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
@@ -14177,6 +14390,7 @@ function ProxyViewer({
14177
14390
  onSessionChange,
14178
14391
  onModelChange,
14179
14392
  onClearAll,
14393
+ onDeleteCurrentView,
14180
14394
  onReloadSession,
14181
14395
  onClearGroup,
14182
14396
  onNotify,
@@ -14188,6 +14402,7 @@ function ProxyViewer({
14188
14402
  timeDisplayFormat,
14189
14403
  isLoading = false,
14190
14404
  pagination,
14405
+ sessionLoadProgress,
14191
14406
  sessionMemberships = [],
14192
14407
  hideSessionFilter = false,
14193
14408
  pinnedSessionId
@@ -14199,6 +14414,9 @@ function ProxyViewer({
14199
14414
  const [importing, setImporting] = reactExports.useState(false);
14200
14415
  const [importStatus, setImportStatus] = reactExports.useState(null);
14201
14416
  const [importError, setImportError] = reactExports.useState(null);
14417
+ const [deleteDialogOpen, setDeleteDialogOpen] = reactExports.useState(false);
14418
+ const [deleteStoredData, setDeleteStoredData] = reactExports.useState(false);
14419
+ const [deletingCurrentView, setDeletingCurrentView] = reactExports.useState(false);
14202
14420
  const [comparePair, setComparePair] = reactExports.useState(null);
14203
14421
  const [crabEntrancePhase, setCrabEntrancePhase] = reactExports.useState(
14204
14422
  "hidden"
@@ -14206,7 +14424,24 @@ function ProxyViewer({
14206
14424
  const logListRef = reactExports.useRef(null);
14207
14425
  const logListWrapperRef = reactExports.useRef(null);
14208
14426
  const importInputRef = reactExports.useRef(null);
14427
+ const exportAbortRef = reactExports.useRef(null);
14428
+ const exportRequestIdRef = reactExports.useRef(0);
14209
14429
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
14430
+ const cancelTransientTasks = reactExports.useCallback(() => {
14431
+ exportRequestIdRef.current += 1;
14432
+ exportAbortRef.current?.abort();
14433
+ exportAbortRef.current = null;
14434
+ setExporting(null);
14435
+ setExportError(null);
14436
+ }, []);
14437
+ reactExports.useEffect(
14438
+ () => () => {
14439
+ exportRequestIdRef.current += 1;
14440
+ exportAbortRef.current?.abort();
14441
+ exportAbortRef.current = null;
14442
+ },
14443
+ []
14444
+ );
14210
14445
  reactExports.useEffect(() => {
14211
14446
  const perCrabDuration = 400;
14212
14447
  const startDelay = 50;
@@ -14228,11 +14463,18 @@ function ProxyViewer({
14228
14463
  }, [logs.length, pinnedSessionId]);
14229
14464
  const handleExport = reactExports.useCallback(
14230
14465
  async (mode) => {
14466
+ exportAbortRef.current?.abort();
14467
+ const controller = new AbortController();
14468
+ exportAbortRef.current = controller;
14469
+ const requestId = exportRequestIdRef.current + 1;
14470
+ exportRequestIdRef.current = requestId;
14231
14471
  setExporting(mode);
14232
14472
  setExportError(null);
14233
14473
  try {
14234
- const result = await exportLogsAsZip(logs, mode);
14474
+ const result = await exportLogsAsZip(logs, mode, { signal: controller.signal });
14475
+ if (exportRequestIdRef.current !== requestId) return;
14235
14476
  if (!result.ok) {
14477
+ if (result.canceled === true) return;
14236
14478
  if (onNotify !== void 0) {
14237
14479
  onNotify({ message: result.message, tone: "error" });
14238
14480
  } else {
@@ -14240,7 +14482,10 @@ function ProxyViewer({
14240
14482
  }
14241
14483
  }
14242
14484
  } finally {
14243
- setExporting(null);
14485
+ if (exportRequestIdRef.current === requestId) {
14486
+ exportAbortRef.current = null;
14487
+ setExporting(null);
14488
+ }
14244
14489
  }
14245
14490
  },
14246
14491
  [logs, onNotify]
@@ -14278,8 +14523,52 @@ function ProxyViewer({
14278
14523
  [onImportLogs, onNotify]
14279
14524
  );
14280
14525
  reactExports.useEffect(() => {
14526
+ cancelTransientTasks();
14527
+ setComparePair(null);
14528
+ }, [cancelTransientTasks, selectedSession, selectedModel]);
14529
+ const handleClearCurrentView = reactExports.useCallback(() => {
14530
+ cancelTransientTasks();
14531
+ setImportStatus(null);
14532
+ setImportError(null);
14533
+ setComparePair(null);
14534
+ onClearAll();
14535
+ }, [cancelTransientTasks, onClearAll]);
14536
+ const handleRequestDeleteCurrentView = reactExports.useCallback(() => {
14537
+ setDeleteStoredData(false);
14538
+ setDeleteDialogOpen(true);
14539
+ }, []);
14540
+ const handleConfirmDeleteCurrentView = reactExports.useCallback(() => {
14541
+ cancelTransientTasks();
14542
+ setImportStatus(null);
14543
+ setImportError(null);
14544
+ setComparePair(null);
14545
+ if (!deleteStoredData || onDeleteCurrentView === void 0) {
14546
+ setDeleteDialogOpen(false);
14547
+ onClearAll();
14548
+ return;
14549
+ }
14550
+ setDeletingCurrentView(true);
14551
+ void onDeleteCurrentView(logs.map((log) => log.id)).then(() => {
14552
+ setDeleteDialogOpen(false);
14553
+ }).catch((err) => {
14554
+ const message = err instanceof Error ? err.message : "Failed to delete stored logs";
14555
+ if (onNotify !== void 0) {
14556
+ onNotify({ message, tone: "error" });
14557
+ } else {
14558
+ setImportError(message);
14559
+ }
14560
+ }).finally(() => {
14561
+ setDeletingCurrentView(false);
14562
+ });
14563
+ }, [cancelTransientTasks, deleteStoredData, logs, onClearAll, onDeleteCurrentView, onNotify]);
14564
+ const handleReloadCurrentSession = reactExports.useCallback(() => {
14565
+ if (onReloadSession === void 0) return;
14566
+ cancelTransientTasks();
14567
+ setImportStatus(null);
14568
+ setImportError(null);
14281
14569
  setComparePair(null);
14282
- }, [selectedSession, selectedModel]);
14570
+ onReloadSession();
14571
+ }, [cancelTransientTasks, onReloadSession]);
14283
14572
  const closeCompare = reactExports.useCallback(() => {
14284
14573
  setComparePair(null);
14285
14574
  }, []);
@@ -14376,7 +14665,7 @@ function ProxyViewer({
14376
14665
  "button",
14377
14666
  {
14378
14667
  type: "button",
14379
- onClick: onReloadSession,
14668
+ onClick: handleReloadCurrentSession,
14380
14669
  disabled: isLoading,
14381
14670
  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",
14382
14671
  title: "Load stored logs for this session",
@@ -14390,20 +14679,35 @@ function ProxyViewer({
14390
14679
  "button",
14391
14680
  {
14392
14681
  type: "button",
14393
- onClick: onClearAll,
14682
+ onClick: handleClearCurrentView,
14394
14683
  disabled: !canClearCurrentView,
14395
14684
  className: cn(
14396
14685
  "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",
14397
- onReloadSession === void 0 && "col-span-2"
14686
+ onReloadSession === void 0 && onDeleteCurrentView === void 0 && "col-span-2"
14398
14687
  ),
14399
14688
  title: "Clear the current view. Stored logs remain reloadable.",
14400
14689
  children: [
14401
14690
  /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" }),
14402
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Clear view" })
14691
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Clear" })
14692
+ ]
14693
+ }
14694
+ ),
14695
+ onDeleteCurrentView !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
14696
+ "button",
14697
+ {
14698
+ type: "button",
14699
+ onClick: handleRequestDeleteCurrentView,
14700
+ disabled: !canClearCurrentView || deletingCurrentView,
14701
+ 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",
14702
+ title: "Choose whether to delete stored data for the current view.",
14703
+ children: [
14704
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" }),
14705
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Delete" })
14403
14706
  ]
14404
14707
  }
14405
14708
  )
14406
- ] })
14709
+ ] }),
14710
+ sessionLoadProgress !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("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)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SessionLoadProgressBar, { progress: sessionLoadProgress }) })
14407
14711
  ] }),
14408
14712
  /* @__PURE__ */ jsxRuntimeExports.jsx(SidebarPanel, { label: "Workspace", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/20 rounded-md px-3 py-2 shadow-[inset_0_1px_0_rgba(255,255,255,0.035)]", children: [
14409
14713
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -14547,7 +14851,7 @@ function ProxyViewer({
14547
14851
  "button",
14548
14852
  {
14549
14853
  type: "button",
14550
- onClick: onReloadSession,
14854
+ onClick: handleReloadCurrentSession,
14551
14855
  disabled: isLoading,
14552
14856
  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",
14553
14857
  title: "Reload this session from stored logs",
@@ -14561,17 +14865,32 @@ function ProxyViewer({
14561
14865
  "button",
14562
14866
  {
14563
14867
  type: "button",
14564
- onClick: onClearAll,
14868
+ onClick: handleClearCurrentView,
14565
14869
  disabled: !canClearCurrentView,
14566
14870
  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",
14567
14871
  title: "Clear the current view. Stored logs remain reloadable.",
14568
14872
  children: [
14569
14873
  /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" }),
14570
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Clear view" })
14874
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Clear" })
14875
+ ]
14876
+ }
14877
+ ),
14878
+ onDeleteCurrentView !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
14879
+ "button",
14880
+ {
14881
+ type: "button",
14882
+ onClick: handleRequestDeleteCurrentView,
14883
+ disabled: !canClearCurrentView || deletingCurrentView,
14884
+ 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",
14885
+ title: "Choose whether to delete stored data for the current view.",
14886
+ children: [
14887
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" }),
14888
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Delete" })
14571
14889
  ]
14572
14890
  }
14573
14891
  )
14574
14892
  ] }),
14893
+ pagination === void 0 && sessionLoadProgress !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("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)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SessionLoadProgressBar, { progress: sessionLoadProgress }) }),
14575
14894
  /* @__PURE__ */ jsxRuntimeExports.jsx(LogPaginationBar, { logs, pagination, embedded: true }),
14576
14895
  onNotify === void 0 && 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 }),
14577
14896
  onNotify === void 0 && 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 }),
@@ -14582,6 +14901,7 @@ function ProxyViewer({
14582
14901
  logs.length === 0 ? selectedSession !== "__all__" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-16 text-center text-muted-foreground", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white/[0.035] shadow-[inset_0_1px_0_rgba(255,255,255,0.045),0_18px_52px_rgba(0,0,0,0.18)] mx-auto max-w-xl space-y-4 rounded-[8px] px-4 py-8", children: [
14583
14902
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium", children: isLoading ? "Loading session data..." : "No logs loaded for this session" }),
14584
14903
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "inline-block max-w-full rounded bg-muted px-3 py-1 font-mono text-xs break-all", children: truncateSessionId(selectedSession) }),
14904
+ sessionLoadProgress !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("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)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SessionLoadProgressBar, { progress: sessionLoadProgress }) }),
14585
14905
  sessionMemberships.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-left", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SessionMembershipPanel, { memberships: sessionMemberships }) }),
14586
14906
  !isLoading && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-3 text-xs", children: [
14587
14907
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Stored logs may still be available." }),
@@ -14590,7 +14910,7 @@ function ProxyViewer({
14590
14910
  "button",
14591
14911
  {
14592
14912
  type: "button",
14593
- onClick: onReloadSession,
14913
+ onClick: handleReloadCurrentSession,
14594
14914
  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",
14595
14915
  children: [
14596
14916
  /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "size-3.5" }),
@@ -14653,6 +14973,59 @@ function ProxyViewer({
14653
14973
  ] })
14654
14974
  ] })
14655
14975
  ] }),
14976
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "border-red-500/20 bg-zinc-950 text-zinc-100 sm:max-w-md", children: [
14977
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { children: [
14978
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Delete current logs?" }),
14979
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogDescription, { children: "Choose whether this only clears the UI or also deletes the stored log data for the current view." })
14980
+ ] }),
14981
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("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)]", children: [
14982
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14983
+ "input",
14984
+ {
14985
+ type: "checkbox",
14986
+ className: "mt-0.5 size-4 shrink-0",
14987
+ checked: deleteStoredData,
14988
+ disabled: deletingCurrentView,
14989
+ onChange: (event) => setDeleteStoredData(event.currentTarget.checked)
14990
+ }
14991
+ ),
14992
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0", children: [
14993
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block font-medium", children: "Also delete stored data" }),
14994
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "mt-1 block text-xs text-red-200/80", children: [
14995
+ "Removes persisted log rows, streaming chunks, and session archive entries for these",
14996
+ " ",
14997
+ logs.length.toString(),
14998
+ " visible request",
14999
+ logs.length === 1 ? "" : "s",
15000
+ "."
15001
+ ] })
15002
+ ] })
15003
+ ] }),
15004
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2", children: [
15005
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
15006
+ Button,
15007
+ {
15008
+ type: "button",
15009
+ variant: "outline",
15010
+ size: "sm",
15011
+ disabled: deletingCurrentView,
15012
+ onClick: () => setDeleteDialogOpen(false),
15013
+ children: "Cancel"
15014
+ }
15015
+ ),
15016
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
15017
+ Button,
15018
+ {
15019
+ type: "button",
15020
+ variant: deleteStoredData ? "destructive" : "secondary",
15021
+ size: "sm",
15022
+ disabled: deletingCurrentView,
15023
+ onClick: handleConfirmDeleteCurrentView,
15024
+ children: deletingCurrentView ? "Deleting..." : deleteStoredData ? "Delete data" : "Clear only"
15025
+ }
15026
+ )
15027
+ ] })
15028
+ ] }) }),
14656
15029
  comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
14657
15030
  LazyCompareDrawer,
14658
15031
  {
@@ -14685,6 +15058,13 @@ const LogCursorPageSchema = object({
14685
15058
  newestLogId: number().int().positive().nullable()
14686
15059
  });
14687
15060
  const SessionsResponseSchema = array(string());
15061
+ const DeleteLogsResponseSchema = object({
15062
+ success: boolean(),
15063
+ cleared: number().int().nonnegative(),
15064
+ logFilesRewritten: number().int().nonnegative().optional(),
15065
+ chunkFilesDeleted: number().int().nonnegative(),
15066
+ sessionArchiveLogsDeleted: number().int().nonnegative().optional()
15067
+ });
14688
15068
  function buildSessionMembershipEvidence(groups, sessionId) {
14689
15069
  const memberships = [];
14690
15070
  for (const group of groups) {
@@ -14732,6 +15112,8 @@ const HASH_HIGHLIGHT_MS = 1800;
14732
15112
  const SESSION_PAGE_LIMIT = 100;
14733
15113
  const SESSION_RELOAD_PAGE_LIMIT = 500;
14734
15114
  const MAX_SESSION_RELOAD_PAGES = 200;
15115
+ const SESSION_LOGS_FETCH_TIMEOUT_MS = 2e4;
15116
+ const SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS = 1e4;
14735
15117
  function buildLogsStreamUrl(sessionId) {
14736
15118
  const params = new URLSearchParams();
14737
15119
  if (sessionId !== void 0) {
@@ -14769,6 +15151,13 @@ function buildLogIndex(logs) {
14769
15151
  }
14770
15152
  return idx;
14771
15153
  }
15154
+ function fetchSessionLogsPage(sessionId, request, limit = SESSION_PAGE_LIMIT) {
15155
+ return fetchJsonWithTimeout(
15156
+ buildSessionLogsPageUrl(sessionId, request, limit),
15157
+ LogCursorPageSchema,
15158
+ SESSION_LOGS_FETCH_TIMEOUT_MS
15159
+ );
15160
+ }
14772
15161
  function mergeLogsById(previousLogs, incomingLogs) {
14773
15162
  if (incomingLogs.length === 0) return [...previousLogs];
14774
15163
  const byId = /* @__PURE__ */ new Map();
@@ -14801,12 +15190,17 @@ function ProxyViewerContainer({
14801
15190
  const [streamInitialized, setStreamInitialized] = reactExports.useState(initialSessionId === void 0);
14802
15191
  const [logPage, setLogPage] = reactExports.useState(null);
14803
15192
  const [sessionPageLoading, setSessionPageLoading] = reactExports.useState(initialSessionId !== void 0);
15193
+ const [sessionLoadProgress, setSessionLoadProgress] = reactExports.useState(
15194
+ void 0
15195
+ );
14804
15196
  const [sessionMemberships, setSessionMemberships] = reactExports.useState([]);
14805
15197
  const [, startLogTransition] = reactExports.useTransition();
14806
15198
  const eventSourceRef = reactExports.useRef(null);
14807
15199
  const reconnectTimeoutRef = reactExports.useRef(null);
14808
15200
  const handledHashRef = reactExports.useRef(null);
14809
15201
  const sessionPageRequestIdRef = reactExports.useRef(0);
15202
+ const sessionLoadStartedAtRef = reactExports.useRef(null);
15203
+ const sessionLoadTimerRef = reactExports.useRef(null);
14810
15204
  const logIndexRef = reactExports.useRef(/* @__PURE__ */ new Map());
14811
15205
  const pendingUpdatesRef = reactExports.useRef([]);
14812
15206
  const flushTimerRef = reactExports.useRef(null);
@@ -14816,6 +15210,50 @@ function ProxyViewerContainer({
14816
15210
  },
14817
15211
  [showToast]
14818
15212
  );
15213
+ const stopSessionLoadProgress = reactExports.useCallback(() => {
15214
+ if (sessionLoadTimerRef.current !== null) {
15215
+ clearInterval(sessionLoadTimerRef.current);
15216
+ sessionLoadTimerRef.current = null;
15217
+ }
15218
+ sessionLoadStartedAtRef.current = null;
15219
+ setSessionLoadProgress(void 0);
15220
+ }, []);
15221
+ const startSessionLoadProgress = reactExports.useCallback(
15222
+ (label, requestId, loadedLogs = 0, loadedPages = 0) => {
15223
+ if (sessionLoadTimerRef.current !== null) {
15224
+ clearInterval(sessionLoadTimerRef.current);
15225
+ }
15226
+ const startedAt = Date.now();
15227
+ sessionLoadStartedAtRef.current = startedAt;
15228
+ const update = () => {
15229
+ if (sessionPageRequestIdRef.current !== requestId) return;
15230
+ setSessionLoadProgress({
15231
+ label,
15232
+ elapsedMs: Date.now() - startedAt,
15233
+ timeoutMs: SESSION_LOGS_FETCH_TIMEOUT_MS,
15234
+ loadedLogs,
15235
+ loadedPages
15236
+ });
15237
+ };
15238
+ update();
15239
+ sessionLoadTimerRef.current = setInterval(update, 250);
15240
+ },
15241
+ []
15242
+ );
15243
+ const updateSessionLoadProgress = reactExports.useCallback(
15244
+ (label, requestId, loadedLogs, loadedPages) => {
15245
+ const startedAt = sessionLoadStartedAtRef.current;
15246
+ if (startedAt === null || sessionPageRequestIdRef.current !== requestId) return;
15247
+ setSessionLoadProgress({
15248
+ label,
15249
+ elapsedMs: Date.now() - startedAt,
15250
+ timeoutMs: SESSION_LOGS_FETCH_TIMEOUT_MS,
15251
+ loadedLogs,
15252
+ loadedPages
15253
+ });
15254
+ },
15255
+ []
15256
+ );
14819
15257
  const logs = reactExports.useMemo(
14820
15258
  () => filterLogs(allLogs, selectedSession, selectedModel),
14821
15259
  [allLogs, selectedSession, selectedModel]
@@ -14911,8 +15349,9 @@ function ProxyViewerContainer({
14911
15349
  const requestId = sessionPageRequestIdRef.current + 1;
14912
15350
  sessionPageRequestIdRef.current = requestId;
14913
15351
  setSessionPageLoading(true);
15352
+ startSessionLoadProgress("Loading session logs", requestId);
14914
15353
  dismissToast();
14915
- void fetchJson(buildSessionLogsPageUrl(initialSessionId, request), LogCursorPageSchema).then((page) => {
15354
+ void fetchSessionLogsPage(initialSessionId, request).then((page) => {
14916
15355
  if (sessionPageRequestIdRef.current !== requestId) return;
14917
15356
  const nextLogs = page.logs;
14918
15357
  logIndexRef.current = buildLogIndex(nextLogs);
@@ -14920,42 +15359,56 @@ function ProxyViewerContainer({
14920
15359
  setLogPage(page);
14921
15360
  setSelectedSession(initialSessionId);
14922
15361
  setSessionPageLoading(false);
15362
+ stopSessionLoadProgress();
14923
15363
  dismissToast();
14924
15364
  }).catch((err) => {
14925
15365
  if (sessionPageRequestIdRef.current !== requestId) return;
14926
15366
  setAllLogs([]);
14927
15367
  setLogPage(null);
14928
15368
  setSessionPageLoading(false);
15369
+ stopSessionLoadProgress();
14929
15370
  showErrorToast(err instanceof Error ? err.message : "Failed to load session logs");
14930
15371
  });
14931
15372
  },
14932
- [dismissToast, initialSessionId, showErrorToast]
15373
+ [
15374
+ dismissToast,
15375
+ initialSessionId,
15376
+ showErrorToast,
15377
+ startSessionLoadProgress,
15378
+ stopSessionLoadProgress
15379
+ ]
14933
15380
  );
14934
15381
  const reloadSessionLogs = reactExports.useCallback(
14935
15382
  (sessionId) => {
14936
15383
  const requestId = sessionPageRequestIdRef.current + 1;
14937
15384
  sessionPageRequestIdRef.current = requestId;
14938
15385
  setSessionPageLoading(true);
15386
+ startSessionLoadProgress("Reloading full session", requestId);
14939
15387
  dismissToast();
14940
15388
  void (async () => {
14941
15389
  const collected = [];
14942
- let page = await fetchJson(
14943
- buildSessionLogsPageUrl(sessionId, { kind: "oldest" }, SESSION_RELOAD_PAGE_LIMIT),
14944
- LogCursorPageSchema
15390
+ let page = await fetchSessionLogsPage(
15391
+ sessionId,
15392
+ { kind: "oldest" },
15393
+ SESSION_RELOAD_PAGE_LIMIT
14945
15394
  );
14946
15395
  collected.push(...page.logs);
15396
+ updateSessionLoadProgress("Reloading full session", requestId, collected.length, 1);
14947
15397
  let loadedPages = 1;
14948
15398
  while (page.hasNewer && page.newestLogId !== null && loadedPages < MAX_SESSION_RELOAD_PAGES) {
14949
- page = await fetchJson(
14950
- buildSessionLogsPageUrl(
14951
- sessionId,
14952
- { kind: "newer", afterLogId: page.newestLogId },
14953
- SESSION_RELOAD_PAGE_LIMIT
14954
- ),
14955
- LogCursorPageSchema
15399
+ page = await fetchSessionLogsPage(
15400
+ sessionId,
15401
+ { kind: "newer", afterLogId: page.newestLogId },
15402
+ SESSION_RELOAD_PAGE_LIMIT
14956
15403
  );
14957
15404
  collected.push(...page.logs);
14958
15405
  loadedPages += 1;
15406
+ updateSessionLoadProgress(
15407
+ "Reloading full session",
15408
+ requestId,
15409
+ collected.length,
15410
+ loadedPages
15411
+ );
14959
15412
  }
14960
15413
  if (sessionPageRequestIdRef.current !== requestId) return;
14961
15414
  setAllLogs((prev) => {
@@ -14965,6 +15418,7 @@ function ProxyViewerContainer({
14965
15418
  });
14966
15419
  setKnownSessions((prev) => mergeSessionIds(prev, [sessionId]));
14967
15420
  setSessionPageLoading(false);
15421
+ stopSessionLoadProgress();
14968
15422
  if (page.hasNewer) {
14969
15423
  showToast({
14970
15424
  message: `Loaded ${String(collected.length)} logs for this session. More history is available.`,
@@ -14976,10 +15430,18 @@ function ProxyViewerContainer({
14976
15430
  })().catch((err) => {
14977
15431
  if (sessionPageRequestIdRef.current !== requestId) return;
14978
15432
  setSessionPageLoading(false);
15433
+ stopSessionLoadProgress();
14979
15434
  showErrorToast(err instanceof Error ? err.message : "Failed to reload session logs");
14980
15435
  });
14981
15436
  },
14982
- [dismissToast, showErrorToast, showToast]
15437
+ [
15438
+ dismissToast,
15439
+ showErrorToast,
15440
+ showToast,
15441
+ startSessionLoadProgress,
15442
+ stopSessionLoadProgress,
15443
+ updateSessionLoadProgress
15444
+ ]
14983
15445
  );
14984
15446
  reactExports.useEffect(() => {
14985
15447
  if (initialSessionId === void 0) {
@@ -14987,12 +15449,11 @@ function ProxyViewerContainer({
14987
15449
  return;
14988
15450
  }
14989
15451
  let cancelled = false;
14990
- void fetch("/api/groups").then(async (response) => {
14991
- if (!response.ok) return null;
14992
- const raw = await response.json();
14993
- const parsed = InspectorGroupsListResponseSchema.safeParse(raw);
14994
- return parsed.success ? parsed.data.groups : null;
14995
- }).then((groups) => {
15452
+ void fetchJsonWithTimeout(
15453
+ "/api/groups",
15454
+ InspectorGroupsListResponseSchema,
15455
+ SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS
15456
+ ).then((response) => response.groups).then((groups) => {
14996
15457
  if (cancelled || groups === null) return;
14997
15458
  setSessionMemberships(buildSessionMembershipEvidence(groups, initialSessionId));
14998
15459
  }).catch(() => {
@@ -15005,6 +15466,11 @@ function ProxyViewerContainer({
15005
15466
  reactExports.useEffect(() => {
15006
15467
  refreshSessions();
15007
15468
  }, [refreshSessions]);
15469
+ reactExports.useEffect(() => {
15470
+ return () => {
15471
+ stopSessionLoadProgress();
15472
+ };
15473
+ }, [stopSessionLoadProgress]);
15008
15474
  reactExports.useEffect(() => {
15009
15475
  if (initialSessionId === void 0) return;
15010
15476
  loadSessionPage({ kind: "newest" });
@@ -15082,6 +15548,8 @@ function ProxyViewerContainer({
15082
15548
  }, [logs.length]);
15083
15549
  const handleClearAll = reactExports.useCallback(() => {
15084
15550
  const clearedSessions = extractSessions(allLogs);
15551
+ stopSessionLoadProgress();
15552
+ setSessionPageLoading(false);
15085
15553
  logIndexRef.current.clear();
15086
15554
  setAllLogs([]);
15087
15555
  setLogPage(null);
@@ -15090,7 +15558,7 @@ function ProxyViewerContainer({
15090
15558
  message: "Cleared the current view. Stored logs are still reloadable.",
15091
15559
  tone: "success"
15092
15560
  });
15093
- }, [allLogs, showToast]);
15561
+ }, [allLogs, showToast, stopSessionLoadProgress]);
15094
15562
  const handleClearGroup = reactExports.useCallback(
15095
15563
  (ids) => {
15096
15564
  if (ids.length === 0) return;
@@ -15106,6 +15574,35 @@ function ProxyViewerContainer({
15106
15574
  },
15107
15575
  [showToast]
15108
15576
  );
15577
+ const handleDeleteCurrentView = reactExports.useCallback(
15578
+ async (ids) => {
15579
+ if (ids.length === 0) return;
15580
+ const idSet = new Set(ids);
15581
+ const removed = allLogs.filter((log) => idSet.has(log.id));
15582
+ const result = await fetchJson("/api/logs", DeleteLogsResponseSchema, {
15583
+ method: "DELETE",
15584
+ headers: { "content-type": "application/json" },
15585
+ body: JSON.stringify({ ids })
15586
+ });
15587
+ setAllLogs((prev) => {
15588
+ const remaining = prev.filter((log) => !idSet.has(log.id));
15589
+ logIndexRef.current = buildLogIndex(remaining);
15590
+ return remaining;
15591
+ });
15592
+ setLogPage(
15593
+ (prev) => prev === null ? null : {
15594
+ ...prev,
15595
+ logs: prev.logs.filter((log) => !idSet.has(log.id))
15596
+ }
15597
+ );
15598
+ setKnownSessions((prev) => mergeSessionIds(prev, extractSessions(removed)));
15599
+ showToast({
15600
+ message: `Deleted ${result.cleared.toString()} stored log${result.cleared === 1 ? "" : "s"} from the current view.`,
15601
+ tone: "success"
15602
+ });
15603
+ },
15604
+ [allLogs, showToast]
15605
+ );
15109
15606
  const handleReloadSession = reactExports.useCallback(() => {
15110
15607
  if (initialSessionId !== void 0) {
15111
15608
  loadSessionPage({ kind: "newest" });
@@ -15166,6 +15663,7 @@ function ProxyViewerContainer({
15166
15663
  if (initialSessionId === void 0) return void 0;
15167
15664
  return {
15168
15665
  isLoading: sessionPageLoading,
15666
+ loadProgress: sessionLoadProgress,
15169
15667
  total: logPage?.total ?? null,
15170
15668
  pageSize: logPage?.limit ?? SESSION_PAGE_LIMIT,
15171
15669
  hasOlder: logPage?.hasOlder ?? false,
@@ -15184,6 +15682,7 @@ function ProxyViewerContainer({
15184
15682
  loadOlderPage,
15185
15683
  loadOldestPage,
15186
15684
  logPage,
15685
+ sessionLoadProgress,
15187
15686
  sessionPageLoading
15188
15687
  ]);
15189
15688
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -15200,12 +15699,14 @@ function ProxyViewerContainer({
15200
15699
  onSessionChange: setSelectedSession,
15201
15700
  onModelChange: setSelectedModel,
15202
15701
  onClearAll: handleClearAll,
15702
+ onDeleteCurrentView: handleDeleteCurrentView,
15203
15703
  onReloadSession: initialSessionId !== void 0 || selectedSession !== "__all__" ? handleReloadSession : void 0,
15204
15704
  onClearGroup: handleClearGroup,
15205
15705
  onNotify: showToast,
15206
15706
  onImportLogs: initialSessionId === void 0 ? handleImportLogs : void 0,
15207
15707
  isLoading: initialSessionId === void 0 ? !streamInitialized || sessionPageLoading : sessionPageLoading,
15208
15708
  pagination,
15709
+ sessionLoadProgress,
15209
15710
  sessionMemberships,
15210
15711
  viewMode,
15211
15712
  captureMode,
@@ -15219,13 +15720,15 @@ function ProxyViewerContainer({
15219
15720
  ] });
15220
15721
  }
15221
15722
  export {
15222
- ANATOMY_ROLE_LABELS as A,
15723
+ ApiTimeoutError as A,
15223
15724
  Badge as B,
15224
15725
  CONTEXT_USAGE_THRESHOLDS as C,
15225
15726
  Dialog as D,
15226
- LazyJsonViewer as E,
15227
- safeJsonValue as F,
15228
- parseJsonText as G,
15727
+ CollapsibleContent as E,
15728
+ ScrollArea as F,
15729
+ LazyJsonViewer as G,
15730
+ safeJsonValue as H,
15731
+ parseJsonText as I,
15229
15732
  LazyJsonViewerFromString as L,
15230
15733
  ProxyViewerContainer as P,
15231
15734
  ROLE_COLOR_CLASSES as R,
@@ -15234,27 +15737,27 @@ export {
15234
15737
  getConversationId as a,
15235
15738
  copyTextToClipboard as b,
15236
15739
  cn as c,
15237
- DialogContent as d,
15238
- DialogHeader as e,
15740
+ fetchJsonWithTimeout as d,
15741
+ DialogContent as e,
15239
15742
  formatTokens as f,
15240
15743
  getLogFormatAdapter as g,
15241
- DialogTitle as h,
15242
- TabsList as i,
15243
- TabsTrigger as j,
15244
- TabsContent as k,
15245
- Button as l,
15246
- TooltipProvider as m,
15247
- Tooltip as n,
15248
- TooltipTrigger as o,
15249
- TooltipContent as p,
15250
- dispatchLogFocusRequest as q,
15744
+ DialogHeader as h,
15745
+ DialogTitle as i,
15746
+ TabsList as j,
15747
+ TabsTrigger as k,
15748
+ TabsContent as l,
15749
+ Button as m,
15750
+ TooltipProvider as n,
15751
+ Tooltip as o,
15752
+ TooltipTrigger as p,
15753
+ TooltipContent as q,
15251
15754
  resolveLogFormat as r,
15252
- analyzeContextIntelligence as s,
15253
- formatContextWindowTokens as t,
15755
+ dispatchLogFocusRequest as s,
15756
+ analyzeContextIntelligence as t,
15254
15757
  useProviders as u,
15255
- getStatusCategory as v,
15256
- Collapsible as w,
15257
- CollapsibleTrigger as x,
15258
- CollapsibleContent as y,
15259
- ScrollArea as z
15758
+ ANATOMY_ROLE_LABELS as v,
15759
+ formatContextWindowTokens as w,
15760
+ getStatusCategory as x,
15761
+ Collapsible as y,
15762
+ CollapsibleTrigger as z
15260
15763
  };