@tonyclaw/agent-inspector 3.0.6 → 3.0.8

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 (70) hide show
  1. package/.output/cli.js +245 -171
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DsvO6QyJ.js → CompareDrawer-Bth_Ywyt.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-C73EU9Jq.js +106 -0
  5. package/.output/public/assets/ReplayDialog-C2YYAuTe.js +1 -0
  6. package/.output/public/assets/{RequestAnatomy-m1q-uVTl.js → RequestAnatomy-8k9dAX2j.js} +1 -1
  7. package/.output/public/assets/ResponseView-DEL6JcXm.js +3 -0
  8. package/.output/public/assets/StreamingChunkSequence-DngmoR9j.js +1 -0
  9. package/.output/public/assets/{_sessionId-CWP4pxOU.js → _sessionId-4vk9RMZ5.js} +1 -1
  10. package/.output/public/assets/index-DBEB1rHe.css +1 -0
  11. package/.output/public/assets/{index-zfZUz67K.js → index-DINLvnm9.js} +1 -1
  12. package/.output/public/assets/index-LPIGiUMP.js +1 -0
  13. package/.output/public/assets/{json-viewer-_4r1LBBE.js → json-viewer-CH5dvS2Y.js} +1 -1
  14. package/.output/public/assets/{main-C_nEHNUM.js → main-GWkZn4xy.js} +2 -2
  15. package/.output/server/{_sessionId-CTqCAmUF.mjs → _sessionId-EgsjdkN7.mjs} +3 -3
  16. package/.output/server/_ssr/{CompareDrawer-D6b6TZ-O.mjs → CompareDrawer-CaM8FYuD.mjs} +3 -3
  17. package/.output/server/_ssr/{ProxyViewerContainer-Dp7Va9_T.mjs → ProxyViewerContainer-EJIogcGd.mjs} +580 -344
  18. package/.output/server/_ssr/{ReplayDialog-D_KWQ1GY.mjs → ReplayDialog-BPpJ2-q8.mjs} +58 -23
  19. package/.output/server/_ssr/{RequestAnatomy-CPubXjMX.mjs → RequestAnatomy-DeQAWnZx.mjs} +2 -2
  20. package/.output/server/_ssr/{ResponseView-CsTiNoOE.mjs → ResponseView-Dshm8iQ7.mjs} +2 -2
  21. package/.output/server/_ssr/{StreamingChunkSequence-Bgbzt23U.mjs → StreamingChunkSequence-32yIU2hn.mjs} +30 -20
  22. package/.output/server/_ssr/{index-P1gSpuM5.mjs → index-XRcx9blU.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-10ciVGnc.mjs → json-viewer-C0aX5rqC.mjs} +2 -2
  25. package/.output/server/_ssr/{router-BJJ0VhsB.mjs → router-BI-Mvxz9.mjs} +441 -172
  26. package/.output/server/_tanstack-start-manifest_v-jKScklL_.mjs +4 -0
  27. package/.output/server/index.mjs +73 -73
  28. package/package.json +1 -1
  29. package/src/assets/IDE/opencode.svg +1 -1
  30. package/src/assets/IDE/xiaomimimo.svg +1 -1
  31. package/src/cli/detect-tools.ts +2 -27
  32. package/src/cli/templates/codex-skill-onboard.ts +1 -1
  33. package/src/cli/templates/skill-onboard.ts +2 -3
  34. package/src/cli.ts +9 -86
  35. package/src/components/ProxyViewer.tsx +68 -21
  36. package/src/components/ProxyViewerContainer.tsx +30 -19
  37. package/src/components/alerts/AlertsDialog.tsx +3 -3
  38. package/src/components/clients/ClientLogo.tsx +22 -2
  39. package/src/components/ecosystem/AgentLabDialog.tsx +215 -12
  40. package/src/components/groups/GroupsDialog.tsx +31 -43
  41. package/src/components/providers/ImportWizardDialog.tsx +52 -102
  42. package/src/components/providers/ProviderCard.tsx +4 -4
  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/CompareDrawer.tsx +1 -1
  47. package/src/components/proxy-viewer/ConversationHeader.tsx +2 -2
  48. package/src/components/proxy-viewer/ReplayDialog.tsx +65 -22
  49. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +33 -20
  50. package/src/lib/apiClient.ts +43 -10
  51. package/src/lib/ecosystemContract.ts +32 -0
  52. package/src/lib/export-logs.ts +37 -3
  53. package/src/proxy/ecosystemRegistry.ts +244 -0
  54. package/src/proxy/ecosystemTasks.ts +153 -0
  55. package/src/proxy/platformCommands.ts +212 -0
  56. package/src/proxy/socketTracker.ts +84 -143
  57. package/src/routes/api/ecosystem.packages.$packageId.help.ts +16 -0
  58. package/src/routes/api/ecosystem.packages.$packageId.install.ts +16 -0
  59. package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +19 -0
  60. package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +16 -0
  61. package/src/routes/api/ecosystem.packages.ts +1 -248
  62. package/src/routes/api/ecosystem.tasks.$taskId.ts +16 -0
  63. package/src/routes/api/ecosystem.tasks.ts +12 -0
  64. package/.output/public/assets/ProxyViewerContainer-CfP-komD.js +0 -106
  65. package/.output/public/assets/ReplayDialog-CQlLX1Uo.js +0 -1
  66. package/.output/public/assets/ResponseView-DuLLeYSh.js +0 -3
  67. package/.output/public/assets/StreamingChunkSequence-CN4aRFBx.js +0 -1
  68. package/.output/public/assets/index-0pt1umWP.css +0 -1
  69. package/.output/public/assets/index-DSUEOPbC.js +0 -1
  70. package/.output/server/_tanstack-start-manifest_v-CltlYL3I.mjs +0 -4
@@ -1,5 +1,5 @@
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-BJJ0VhsB.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-BI-Mvxz9.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";
@@ -50,20 +50,42 @@ 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);
65
79
  }
66
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));
86
+ }
87
+ return parseJsonResponse$1(response, schema);
88
+ }
67
89
  const ImportLogsResponseSchema = object({
68
90
  sessionId: string(),
69
91
  imported: number().int().nonnegative(),
@@ -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;
@@ -849,8 +890,8 @@ 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";
854
895
  const CLIENT_LABELS = {
855
896
  codex: "Codex",
856
897
  opencode: "OpenCode",
@@ -897,6 +938,18 @@ const CLIENT_LOGOS = {
897
938
  "mimo-code": MiMoCodeLogoSvg,
898
939
  "claude-code": ClaudeCodeLogoSvg
899
940
  };
941
+ const CLIENT_LOGO_FRAMES = {
942
+ codex: "border-cyan-200/20 bg-cyan-300/[0.08]",
943
+ opencode: "border-sky-200/25 bg-sky-300/[0.12]",
944
+ "mimo-code": "border-orange-200/25 bg-orange-400/[0.12]",
945
+ "claude-code": "border-amber-100/20 bg-amber-300/[0.09]"
946
+ };
947
+ const CLIENT_LOGO_IMAGES = {
948
+ codex: "p-0.5",
949
+ opencode: "p-1 drop-shadow-[0_0_8px_rgba(125,211,252,0.35)]",
950
+ "mimo-code": "p-0.5 drop-shadow-[0_0_8px_rgba(255,105,0,0.35)]",
951
+ "claude-code": "p-0.5"
952
+ };
900
953
  function ClientLogo({
901
954
  client,
902
955
  className
@@ -909,12 +962,21 @@ function ClientLogo({
909
962
  "span",
910
963
  {
911
964
  className: cn(
912
- "border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md p-0.5",
965
+ "inline-flex size-6 shrink-0 items-center justify-center rounded-md border p-0 shadow-sm",
966
+ CLIENT_LOGO_FRAMES[client],
913
967
  className
914
968
  ),
915
969
  "aria-label": label,
916
970
  title: label,
917
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: logoSrc, alt: "", className: "size-full object-contain", "aria-hidden": "true" })
971
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
972
+ "img",
973
+ {
974
+ src: logoSrc,
975
+ alt: "",
976
+ className: cn("size-full object-contain", CLIENT_LOGO_IMAGES[client]),
977
+ "aria-hidden": "true"
978
+ }
979
+ )
918
980
  }
919
981
  );
920
982
  }
@@ -1611,11 +1673,11 @@ function ConversationHeader({
1611
1673
  showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1612
1674
  "span",
1613
1675
  {
1614
- className: "bg-white/[0.045] inline-flex h-6 max-w-[180px] shrink-0 items-center gap-1 rounded-md px-1.5 text-xs text-muted-foreground",
1676
+ className: "bg-white/[0.045] inline-flex min-h-6 shrink-0 flex-wrap items-center gap-1 rounded-md px-1.5 py-0.5 text-xs text-muted-foreground",
1615
1677
  title: clientTooltip ?? clientDisplayLabel,
1616
1678
  children: [
1617
1679
  /* @__PURE__ */ jsxRuntimeExports.jsx(ClientLogo, { client: clientApp, className: "size-4 shrink-0" }),
1618
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate font-mono tabular-nums", children: clientDisplayLabel })
1680
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono leading-snug tabular-nums", children: clientDisplayLabel })
1619
1681
  ]
1620
1682
  }
1621
1683
  ),
@@ -2306,27 +2368,27 @@ function useCopyFeedback(text) {
2306
2368
  return { copied, copy };
2307
2369
  }
2308
2370
  const LazyCompareDrawer = reactExports.lazy(
2309
- () => import("./CompareDrawer-D6b6TZ-O.mjs").then((m) => ({ default: m.CompareDrawer }))
2371
+ () => import("./CompareDrawer-CaM8FYuD.mjs").then((m) => ({ default: m.CompareDrawer }))
2310
2372
  );
2311
2373
  const LazyReplayDialog = reactExports.lazy(
2312
- () => import("./ReplayDialog-D_KWQ1GY.mjs").then((m) => ({ default: m.ReplayDialog }))
2374
+ () => import("./ReplayDialog-BPpJ2-q8.mjs").then((m) => ({ default: m.ReplayDialog }))
2313
2375
  );
2314
2376
  const LazyRequestAnatomy = reactExports.lazy(
2315
- () => import("./RequestAnatomy-CPubXjMX.mjs").then((m) => ({ default: m.RequestAnatomy }))
2377
+ () => import("./RequestAnatomy-DeQAWnZx.mjs").then((m) => ({ default: m.RequestAnatomy }))
2316
2378
  );
2317
2379
  const LazyResponseView = reactExports.lazy(
2318
- () => import("./ResponseView-CsTiNoOE.mjs").then((m) => ({ default: m.ResponseView }))
2380
+ () => import("./ResponseView-Dshm8iQ7.mjs").then((m) => ({ default: m.ResponseView }))
2319
2381
  );
2320
2382
  const LazyStreamingChunkSequence = reactExports.lazy(
2321
- () => import("./StreamingChunkSequence-Bgbzt23U.mjs").then((m) => ({
2383
+ () => import("./StreamingChunkSequence-32yIU2hn.mjs").then((m) => ({
2322
2384
  default: m.StreamingChunkSequence
2323
2385
  }))
2324
2386
  );
2325
2387
  const LazyJsonViewer = reactExports.lazy(
2326
- () => import("./json-viewer-10ciVGnc.mjs").then((m) => ({ default: m.JsonViewer }))
2388
+ () => import("./json-viewer-C0aX5rqC.mjs").then((m) => ({ default: m.JsonViewer }))
2327
2389
  );
2328
2390
  const LazyJsonViewerFromString = reactExports.lazy(
2329
- () => import("./json-viewer-10ciVGnc.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2391
+ () => import("./json-viewer-C0aX5rqC.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2330
2392
  );
2331
2393
  const HIGHLIGHT_DURATION_MS = 1200;
2332
2394
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -5260,6 +5322,9 @@ const CandidatePromotionFailureResponseSchema = object({
5260
5322
  const CandidateUpdateResponseSchema = object({
5261
5323
  candidate: KnowledgeCandidateSchema
5262
5324
  });
5325
+ const CANDIDATE_GENERATE_TIMEOUT_MS = 3e4;
5326
+ const CANDIDATE_PROMOTE_TIMEOUT_MS = 15e3;
5327
+ const CANDIDATE_UPDATE_TIMEOUT_MS = 15e3;
5263
5328
  const CANDIDATE_STATUS_CLASSES = {
5264
5329
  draft: "border-amber-500/30 bg-amber-500/10 text-amber-500",
5265
5330
  promoted: "border-emerald-500/30 bg-emerald-500/10 text-emerald-500",
@@ -5765,19 +5830,13 @@ function AgentTraceSummary({
5765
5830
  setCandidateState({ status: "loading", error: null });
5766
5831
  void (async () => {
5767
5832
  try {
5768
- const response = await fetch(
5833
+ const parsed = await fetchJsonWithTimeout(
5769
5834
  `/api/knowledge/sessions/${encodeURIComponent(scopeId)}/candidates`,
5770
- { method: "POST" }
5835
+ CandidateResponseSchema,
5836
+ CANDIDATE_GENERATE_TIMEOUT_MS,
5837
+ { method: "POST" },
5838
+ (response) => `Candidate generation failed with ${String(response.status)}`
5771
5839
  );
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
5840
  setCandidates(parsed.candidates);
5782
5841
  setCandidatesExpanded(parsed.candidates.length > 0);
5783
5842
  setCandidateState(
@@ -5800,8 +5859,9 @@ function AgentTraceSummary({
5800
5859
  });
5801
5860
  void (async () => {
5802
5861
  try {
5803
- const response = await fetch(
5862
+ const response = await fetchWithTimeout(
5804
5863
  `/api/knowledge/candidates/${encodeURIComponent(candidateId)}/promote`,
5864
+ CANDIDATE_PROMOTE_TIMEOUT_MS,
5805
5865
  { method: "POST" }
5806
5866
  );
5807
5867
  const raw = await response.json().catch(() => null);
@@ -5850,23 +5910,17 @@ function AgentTraceSummary({
5850
5910
  return next;
5851
5911
  });
5852
5912
  try {
5853
- const response = await fetch(
5913
+ const parsed = await fetchJsonWithTimeout(
5854
5914
  `/api/knowledge/candidates/${encodeURIComponent(candidateId)}`,
5915
+ CandidateUpdateResponseSchema,
5916
+ CANDIDATE_UPDATE_TIMEOUT_MS,
5855
5917
  {
5856
5918
  method: "PATCH",
5857
5919
  headers: { "Content-Type": "application/json" },
5858
5920
  body: JSON.stringify(update)
5859
- }
5921
+ },
5922
+ (response) => `Candidate update failed with ${String(response.status)}`
5860
5923
  );
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
5924
  setCandidates(
5871
5925
  (current) => current.map(
5872
5926
  (candidate) => candidate.id === parsed.candidate.id ? parsed.candidate : candidate
@@ -6556,7 +6610,7 @@ function AlertList({
6556
6610
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-xs font-semibold", severityText(alert.severity)), children: severityLabel(alert.severity) }),
6557
6611
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-xs text-muted-foreground", children: categoryLabel(alert.category) })
6558
6612
  ] }),
6559
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-sm font-semibold", children: alert.title }),
6613
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-sm font-semibold leading-snug", children: alert.title }),
6560
6614
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 line-clamp-2 text-xs text-muted-foreground", children: alert.message })
6561
6615
  ] }),
6562
6616
  alert.count > 1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "shrink-0", children: [
@@ -6564,8 +6618,8 @@ function AlertList({
6564
6618
  formatCount(alert.count)
6565
6619
  ] }) : null
6566
6620
  ] }),
6567
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex min-w-0 items-center justify-between gap-2 text-[11px] text-muted-foreground", children: [
6568
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: providerModel$1(alert) }),
6621
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex min-w-0 items-start justify-between gap-2 text-[11px] text-muted-foreground", children: [
6622
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 leading-snug", children: providerModel$1(alert) }),
6569
6623
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: formatTimestamp$1(alert.lastSeenAt) })
6570
6624
  ] })
6571
6625
  ]
@@ -6942,6 +6996,7 @@ function TransientToast({
6942
6996
  }
6943
6997
  );
6944
6998
  }
6999
+ const GROUP_OPERATION_TIMEOUT_MS = 2e4;
6945
7000
  function groupDeletePath(groupId, deleteEvidence) {
6946
7001
  const path = `/api/groups/${encodeURIComponent(groupId)}`;
6947
7002
  return deleteEvidence ? `${path}?deleteEvidence=1` : path;
@@ -7162,18 +7217,13 @@ function GroupsDialog() {
7162
7217
  setDeleteError(null);
7163
7218
  setExportError(null);
7164
7219
  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);
7220
+ const parsed = await fetchJsonWithTimeout(
7221
+ groupsClearPath(false),
7222
+ DeleteInspectorGroupsResponseSchema,
7223
+ GROUP_OPERATION_TIMEOUT_MS,
7224
+ { method: "DELETE" },
7225
+ (response) => `Clear failed with status ${String(response.status)}`
7226
+ );
7177
7227
  setExportResult(null);
7178
7228
  setSelectedGroupId(null);
7179
7229
  await mutate();
@@ -7196,18 +7246,17 @@ function GroupsDialog() {
7196
7246
  setExportingGroupId(group.id);
7197
7247
  setExportError(null);
7198
7248
  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);
7249
+ const parsed = await fetchJsonWithTimeout(
7250
+ `/api/groups/${encodeURIComponent(group.id)}/evidence`,
7251
+ GroupEvidenceExportResultSchema,
7252
+ GROUP_OPERATION_TIMEOUT_MS,
7253
+ {
7254
+ method: "POST",
7255
+ headers: { "content-type": "application/json" },
7256
+ body: JSON.stringify({ latestLogLimit: 10 })
7257
+ },
7258
+ (response) => `Export failed with status ${String(response.status)}`
7259
+ );
7211
7260
  setExportResult(parsed);
7212
7261
  await mutate();
7213
7262
  await mutateEvidence();
@@ -7242,16 +7291,13 @@ function GroupsDialog() {
7242
7291
  setDeletingGroupId(group.id);
7243
7292
  setDeleteError(null);
7244
7293
  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);
7294
+ await fetchJsonWithTimeout(
7295
+ groupDeletePath(group.id, deleteEvidence),
7296
+ DeleteInspectorGroupResponseSchema,
7297
+ GROUP_OPERATION_TIMEOUT_MS,
7298
+ { method: "DELETE" },
7299
+ (response) => `Delete failed with status ${String(response.status)}`
7300
+ );
7255
7301
  setExportResult(
7256
7302
  (current) => current !== null && current.group.id === group.id ? null : current
7257
7303
  );
@@ -7380,7 +7426,7 @@ function GroupList({
7380
7426
  ),
7381
7427
  children: [
7382
7428
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center justify-between gap-2", children: [
7383
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate text-sm font-medium", children: group.title }),
7429
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 text-sm font-medium leading-snug", children: group.title }),
7384
7430
  /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: statusClassName(group.status), children: statusLabel(group.status) })
7385
7431
  ] }),
7386
7432
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex items-center gap-2 text-muted-foreground", children: [
@@ -7429,7 +7475,7 @@ function GroupDetails({
7429
7475
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
7430
7476
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 space-y-1", children: [
7431
7477
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
7432
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "min-w-0 truncate text-base font-semibold", children: group.title }),
7478
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "min-w-0 text-base font-semibold leading-snug", children: group.title }),
7433
7479
  /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: statusClassName(group.status), children: statusLabel(group.status) }),
7434
7480
  /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: group.kind })
7435
7481
  ] }),
@@ -7787,7 +7833,7 @@ function MemberRow({
7787
7833
  ] }),
7788
7834
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 truncate text-[11px] text-muted-foreground", children: displayText(member.agent) })
7789
7835
  ] }),
7790
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "max-w-52 px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block truncate", children: providerModel(member) }) }),
7836
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "max-w-52 px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block whitespace-normal leading-snug", children: providerModel(member) }) }),
7791
7837
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: statusClassName(member.status), children: statusLabel(member.status) }) }),
7792
7838
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 font-mono", children: requests === null ? "-" : formatNumber(requests) }),
7793
7839
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 font-mono", children: errors === null ? "-" : formatNumber(errors) }),
@@ -7934,70 +7980,6 @@ function MemberMetadata({ entries }) {
7934
7980
  }) })
7935
7981
  ] });
7936
7982
  }
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
7983
  const ExternalProviderSchema = object({
8002
7984
  name: string(),
8003
7985
  apiKey: string(),
@@ -8019,11 +8001,7 @@ const ImportResponseSchema$1 = object({
8019
8001
  message: string().optional(),
8020
8002
  errors: array(string()).optional()
8021
8003
  });
8022
- const sourceLogoMap = {
8023
- "claude-code": ClaudeCodeIcon,
8024
- opencode: OpenCodeIcon,
8025
- "mimo-code": MiMoCodeIcon
8026
- };
8004
+ const PROVIDER_IMPORT_TIMEOUT_MS$1 = 2e4;
8027
8005
  function ImportWizardDialog({
8028
8006
  open,
8029
8007
  onOpenChange,
@@ -8037,7 +8015,11 @@ function ImportWizardDialog({
8037
8015
  const [importing, setImporting] = reactExports.useState(false);
8038
8016
  const [importResult, setImportResult] = reactExports.useState(null);
8039
8017
  const [importError, setImportError] = reactExports.useState(null);
8018
+ const scanRequestIdRef = reactExports.useRef(0);
8019
+ const importRequestIdRef = reactExports.useRef(0);
8040
8020
  const scan = reactExports.useCallback(() => {
8021
+ const requestId = scanRequestIdRef.current + 1;
8022
+ scanRequestIdRef.current = requestId;
8041
8023
  setScanning(true);
8042
8024
  setScanError(null);
8043
8025
  setWarnings([]);
@@ -8045,29 +8027,26 @@ function ImportWizardDialog({
8045
8027
  setSelected(/* @__PURE__ */ new Set());
8046
8028
  setImportResult(null);
8047
8029
  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);
8030
+ void fetchJsonWithTimeout(
8031
+ "/api/providers/scan",
8032
+ ScanResponseSchema,
8033
+ PROVIDER_IMPORT_TIMEOUT_MS$1,
8034
+ void 0,
8035
+ (response) => `Scan failed (${String(response.status)})`
8036
+ ).then((data) => {
8037
+ if (scanRequestIdRef.current !== requestId) return;
8038
+ setProviders(data.providers);
8039
+ setWarnings(data.warnings ?? []);
8040
+ const indices = /* @__PURE__ */ new Set();
8041
+ data.providers.forEach((provider, index) => {
8042
+ if (!provider.alreadyExists) indices.add(index);
8067
8043
  });
8044
+ setSelected(indices);
8068
8045
  }).catch((err) => {
8046
+ if (scanRequestIdRef.current !== requestId) return;
8069
8047
  setScanError(err instanceof Error ? err.message : String(err));
8070
8048
  }).finally(() => {
8049
+ if (scanRequestIdRef.current !== requestId) return;
8071
8050
  setScanning(false);
8072
8051
  });
8073
8052
  }, []);
@@ -8076,6 +8055,13 @@ function ImportWizardDialog({
8076
8055
  scan();
8077
8056
  }
8078
8057
  }, [open, scan]);
8058
+ reactExports.useEffect(() => {
8059
+ if (open) return;
8060
+ scanRequestIdRef.current += 1;
8061
+ importRequestIdRef.current += 1;
8062
+ setScanning(false);
8063
+ setImporting(false);
8064
+ }, [open]);
8079
8065
  const toggleProvider = reactExports.useCallback((index) => {
8080
8066
  setSelected((prev) => {
8081
8067
  const next = new Set(prev);
@@ -8090,6 +8076,8 @@ function ImportWizardDialog({
8090
8076
  const importSelected = reactExports.useCallback(() => {
8091
8077
  const toImport = providers.filter((_, i) => selected.has(i));
8092
8078
  if (toImport.length === 0) return;
8079
+ const requestId = importRequestIdRef.current + 1;
8080
+ importRequestIdRef.current = requestId;
8093
8081
  setImporting(true);
8094
8082
  setImportError(null);
8095
8083
  setImportResult(null);
@@ -8106,17 +8094,18 @@ function ImportWizardDialog({
8106
8094
  createdAt: now,
8107
8095
  updatedAt: now
8108
8096
  }));
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;
8097
+ void fetchJsonWithTimeout(
8098
+ "/api/providers/import",
8099
+ ImportResponseSchema$1,
8100
+ PROVIDER_IMPORT_TIMEOUT_MS$1,
8101
+ {
8102
+ method: "POST",
8103
+ headers: { "Content-Type": "application/json" },
8104
+ body: JSON.stringify({ providers: providersPayload })
8105
+ },
8106
+ (response) => `Import failed (${String(response.status)})`
8107
+ ).then((result) => {
8108
+ if (importRequestIdRef.current !== requestId) return;
8120
8109
  setProviders(
8121
8110
  (prev) => prev.map((p, i) => selected.has(i) ? { ...p, alreadyExists: true } : p)
8122
8111
  );
@@ -8128,8 +8117,10 @@ function ImportWizardDialog({
8128
8117
  }
8129
8118
  onImportComplete();
8130
8119
  }).catch((err) => {
8120
+ if (importRequestIdRef.current !== requestId) return;
8131
8121
  setImportError(err instanceof Error ? err.message : String(err));
8132
8122
  }).finally(() => {
8123
+ if (importRequestIdRef.current !== requestId) return;
8133
8124
  setImporting(false);
8134
8125
  });
8135
8126
  }, [providers, selected, onImportComplete]);
@@ -8170,14 +8161,10 @@ function ImportWizardDialog({
8170
8161
  ),
8171
8162
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
8172
8163
  /* @__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
- })(),
8164
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ClientLogo, { client: p.sourceTool, className: "size-7 rounded-[8px]" }),
8178
8165
  p.alreadyExists && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: "Already added" })
8179
8166
  ] }),
8180
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-muted-foreground mt-1 truncate", children: [
8167
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 text-xs leading-snug text-muted-foreground", children: [
8181
8168
  p.models.slice(0, 4).join(", "),
8182
8169
  p.models.length > 4 ? ` +${p.models.length - 4} more` : ""
8183
8170
  ] }),
@@ -8693,13 +8680,13 @@ function ProviderCard({
8693
8680
  {
8694
8681
  className: `flex flex-col gap-3 rounded-[8px] border border-border bg-card p-4 shadow-sm transition-all duration-500 ${isHighlighted === true ? "ring-2 ring-primary shadow-md" : ""}`,
8695
8682
  children: [
8696
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
8683
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [
8697
8684
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 space-y-1", children: [
8698
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
8699
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate font-medium", children: provider.name }),
8685
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
8686
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium leading-snug", children: provider.name }),
8700
8687
  providerSourceBadge
8701
8688
  ] }),
8702
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-xs text-muted-foreground", children: compactModelLabel(provider) })
8689
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs leading-snug text-muted-foreground", children: compactModelLabel(provider) })
8703
8690
  ] }),
8704
8691
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
8705
8692
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -9848,6 +9835,13 @@ const ModelMetadataRefreshResponseSchema = object({
9848
9835
  sourceUrl: string()
9849
9836
  });
9850
9837
  const NETWORK_ERROR_MESSAGE = "Network error: could not reach the server. Is the proxy running?";
9838
+ const CONFIG_PATH_FETCH_TIMEOUT_MS = 5e3;
9839
+ const PROVIDER_TEST_LOG_TIMEOUT_MS = 5e3;
9840
+ const PROVIDER_WRITE_TIMEOUT_MS = 15e3;
9841
+ const PROVIDER_DELETE_TIMEOUT_MS = 1e4;
9842
+ const PROVIDER_EXPORT_TIMEOUT_MS = 2e4;
9843
+ const PROVIDER_IMPORT_TIMEOUT_MS = 2e4;
9844
+ const PROVIDER_METADATA_TIMEOUT_MS = 3e4;
9851
9845
  function createProviderPayload(data) {
9852
9846
  return {
9853
9847
  name: data.name,
@@ -9871,7 +9865,7 @@ async function persistProviderTestLog(providerId, results) {
9871
9865
  return;
9872
9866
  }
9873
9867
  try {
9874
- await fetch(`/api/providers/${providerId}/test/log`, {
9868
+ await fetchWithTimeout(`/api/providers/${providerId}/test/log`, PROVIDER_TEST_LOG_TIMEOUT_MS, {
9875
9869
  method: "POST",
9876
9870
  headers: { "Content-Type": "application/json" },
9877
9871
  body: JSON.stringify(results)
@@ -9939,11 +9933,12 @@ function ProvidersPanel({
9939
9933
  reactExports.useEffect(() => {
9940
9934
  void (async () => {
9941
9935
  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
- }
9936
+ const data = await fetchJsonWithTimeout(
9937
+ "/api/config/paths",
9938
+ ConfigPathsResponseSchema,
9939
+ CONFIG_PATH_FETCH_TIMEOUT_MS
9940
+ );
9941
+ setConfigPath(data.providerConfig);
9947
9942
  } catch {
9948
9943
  }
9949
9944
  })();
@@ -10017,30 +10012,31 @@ function ProvidersPanel({
10017
10012
  }
10018
10013
  }, 1e3);
10019
10014
  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 {
10029
- updateTestResults(
10030
- providerId,
10031
- createFailedProviderTestResults(
10032
- `HTTP ${res.status}: ${res.statusText}`,
10033
- "server_error"
10034
- )
10035
- );
10036
- }
10015
+ const results = await fetchJsonWithTimeout(
10016
+ `/api/providers/${providerId}/test`,
10017
+ ProviderTestResultsSchema,
10018
+ timeoutSeconds * 1e3 + 1e3,
10019
+ {
10020
+ method: "POST",
10021
+ signal: controller.signal
10022
+ },
10023
+ (response) => `HTTP ${response.status}: ${response.statusText}`
10024
+ );
10025
+ updateTestResults(providerId, results);
10026
+ await persistProviderTestLog(providerId, results);
10037
10027
  } catch (err) {
10038
10028
  const isAbort = err instanceof Error && err.name === "AbortError";
10039
- if (isAbort) {
10029
+ const isTimeout = err instanceof ApiTimeoutError;
10030
+ if (isAbort || isTimeout) {
10040
10031
  updateTestResults(
10041
10032
  providerId,
10042
10033
  createFailedProviderTestResults("Request timed out", "timeout")
10043
10034
  );
10035
+ } else if (err instanceof Error) {
10036
+ updateTestResults(
10037
+ providerId,
10038
+ createFailedProviderTestResults(err.message, "server_error")
10039
+ );
10044
10040
  }
10045
10041
  } finally {
10046
10042
  clearInterval(intervalId);
@@ -10065,18 +10061,17 @@ function ProvidersPanel({
10065
10061
  });
10066
10062
  try {
10067
10063
  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);
10064
+ const data = await fetchJsonWithTimeout(
10065
+ `/api/providers/${providerId}/model-metadata`,
10066
+ ModelMetadataRefreshResponseSchema,
10067
+ PROVIDER_METADATA_TIMEOUT_MS,
10068
+ {
10069
+ method: "POST",
10070
+ headers: { "Content-Type": "application/json" },
10071
+ body: JSON.stringify(payload)
10072
+ },
10073
+ () => "Failed to refresh model metadata"
10074
+ );
10080
10075
  if (data.imported === 0 && showError) {
10081
10076
  setError("No matching model metadata found for this provider.");
10082
10077
  } else {
@@ -10085,9 +10080,9 @@ function ProvidersPanel({
10085
10080
  triggerHighlight(data.provider.id);
10086
10081
  refreshProviders();
10087
10082
  return data.imported > 0;
10088
- } catch {
10083
+ } catch (err) {
10089
10084
  if (showError) {
10090
- setError(NETWORK_ERROR_MESSAGE);
10085
+ setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
10091
10086
  }
10092
10087
  return false;
10093
10088
  } finally {
@@ -10103,16 +10098,17 @@ function ProvidersPanel({
10103
10098
  function handleAddProvider(data) {
10104
10099
  void (async () => {
10105
10100
  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);
10101
+ const newProvider = await fetchJsonWithTimeout(
10102
+ "/api/providers",
10103
+ ProviderConfigSchema,
10104
+ PROVIDER_WRITE_TIMEOUT_MS,
10105
+ {
10106
+ method: "POST",
10107
+ headers: { "Content-Type": "application/json" },
10108
+ body: JSON.stringify(createProviderPayload(data))
10109
+ },
10110
+ () => "Failed to add provider"
10111
+ );
10116
10112
  setShowForm(false);
10117
10113
  triggerHighlight(newProvider.id);
10118
10114
  if (data.modelMetadataUrl !== void 0 && data.modelMetadataUrl.trim() !== "") {
@@ -10120,8 +10116,8 @@ function ProvidersPanel({
10120
10116
  }
10121
10117
  refreshProviders();
10122
10118
  await runTest(newProvider.id);
10123
- } catch {
10124
- setError(NETWORK_ERROR_MESSAGE);
10119
+ } catch (err) {
10120
+ setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
10125
10121
  }
10126
10122
  })();
10127
10123
  }
@@ -10129,16 +10125,17 @@ function ProvidersPanel({
10129
10125
  if (!editingProvider) return;
10130
10126
  void (async () => {
10131
10127
  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);
10128
+ const updated = await fetchJsonWithTimeout(
10129
+ `/api/providers/${editingProvider.id}`,
10130
+ ProviderConfigSchema,
10131
+ PROVIDER_WRITE_TIMEOUT_MS,
10132
+ {
10133
+ method: "PUT",
10134
+ headers: { "Content-Type": "application/json" },
10135
+ body: JSON.stringify(createProviderPayload(data))
10136
+ },
10137
+ () => "Failed to update provider"
10138
+ );
10142
10139
  setEditingProvider(void 0);
10143
10140
  triggerHighlight(updated.id);
10144
10141
  if (data.modelMetadataUrl !== void 0 && data.modelMetadataUrl.trim() !== "") {
@@ -10149,8 +10146,8 @@ function ProvidersPanel({
10149
10146
  if (criticalFieldsChanged) {
10150
10147
  await runTest(updated.id);
10151
10148
  }
10152
- } catch {
10153
- setError(NETWORK_ERROR_MESSAGE);
10149
+ } catch (err) {
10150
+ setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
10154
10151
  }
10155
10152
  })();
10156
10153
  }
@@ -10164,7 +10161,7 @@ function ProvidersPanel({
10164
10161
  void (async () => {
10165
10162
  let res;
10166
10163
  try {
10167
- res = await fetch(`/api/providers/${providerId}`, {
10164
+ res = await fetchWithTimeout(`/api/providers/${providerId}`, PROVIDER_DELETE_TIMEOUT_MS, {
10168
10165
  method: "DELETE"
10169
10166
  });
10170
10167
  } catch {
@@ -10183,7 +10180,7 @@ function ProvidersPanel({
10183
10180
  const url = `/api/providers/export${""}`;
10184
10181
  void (async () => {
10185
10182
  try {
10186
- const res = await fetch(url);
10183
+ const res = await fetchWithTimeout(url, PROVIDER_EXPORT_TIMEOUT_MS);
10187
10184
  if (!res.ok) {
10188
10185
  setError("Failed to export providers");
10189
10186
  return;
@@ -10211,7 +10208,7 @@ function ProvidersPanel({
10211
10208
  void (async () => {
10212
10209
  try {
10213
10210
  const text = await file.text();
10214
- const res = await fetch("/api/providers/import", {
10211
+ const res = await fetchWithTimeout("/api/providers/import", PROVIDER_IMPORT_TIMEOUT_MS, {
10215
10212
  method: "POST",
10216
10213
  headers: { "Content-Type": "application/json" },
10217
10214
  body: JSON.stringify(text)
@@ -10511,6 +10508,7 @@ const LogStorageStatsSchema = object({
10511
10508
  sessionArchiveFileCount: number(),
10512
10509
  sessionArchiveBytes: number()
10513
10510
  });
10511
+ const STORAGE_STATS_FETCH_TIMEOUT_MS = 1e4;
10514
10512
  function formatBytes(bytes) {
10515
10513
  if (bytes < 1024) return `${bytes} B`;
10516
10514
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
@@ -10526,18 +10524,14 @@ function StorageSettingsTab() {
10526
10524
  setLoading(true);
10527
10525
  setError(null);
10528
10526
  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);
10527
+ const data = await fetchJsonWithTimeout(
10528
+ "/api/logs?stats=1",
10529
+ LogStorageStatsSchema,
10530
+ STORAGE_STATS_FETCH_TIMEOUT_MS,
10531
+ void 0,
10532
+ (response) => `Failed to load storage stats: HTTP ${String(response.status)}`
10533
+ );
10534
+ setStats(data);
10541
10535
  } catch (err) {
10542
10536
  setError(err instanceof Error ? err.message : String(err));
10543
10537
  } finally {
@@ -11153,6 +11147,28 @@ const EcosystemPackagesResponseSchema = object({
11153
11147
  packages: array(EcosystemPackageSchema),
11154
11148
  runnerPresets: array(EcosystemRunnerPresetSchema)
11155
11149
  });
11150
+ const EcosystemTaskActionSchema = _enum(["install", "upgrade", "help", "runner-presets"]);
11151
+ const EcosystemTaskStatusSchema = _enum(["queued", "running", "succeeded", "failed"]);
11152
+ const EcosystemTaskSchema = object({
11153
+ id: string(),
11154
+ packageId: string(),
11155
+ npmName: string(),
11156
+ action: EcosystemTaskActionSchema,
11157
+ status: EcosystemTaskStatusSchema,
11158
+ command: string(),
11159
+ output: string(),
11160
+ createdAt: string(),
11161
+ startedAt: string().nullable(),
11162
+ finishedAt: string().nullable(),
11163
+ exitCode: number().nullable(),
11164
+ error: string().nullable()
11165
+ });
11166
+ const EcosystemTaskResponseSchema = object({
11167
+ task: EcosystemTaskSchema
11168
+ });
11169
+ object({
11170
+ tasks: array(EcosystemTaskSchema)
11171
+ });
11156
11172
  const WORKFLOWS = [
11157
11173
  {
11158
11174
  id: "observe",
@@ -11195,6 +11211,23 @@ async function fetchEcosystemPackages(url) {
11195
11211
  (response) => `Failed to load ecosystem packages: ${String(response.status)}`
11196
11212
  );
11197
11213
  }
11214
+ async function fetchEcosystemTask(url) {
11215
+ return fetchJson(
11216
+ url,
11217
+ EcosystemTaskResponseSchema,
11218
+ void 0,
11219
+ (response) => `Failed to load ecosystem task: ${String(response.status)}`
11220
+ );
11221
+ }
11222
+ async function startEcosystemTask(packageId, action) {
11223
+ const response = await fetchJson(
11224
+ `/api/ecosystem/packages/${encodeURIComponent(packageId)}/${action}`,
11225
+ EcosystemTaskResponseSchema,
11226
+ { method: "POST" },
11227
+ (result) => `Failed to start ecosystem task: ${String(result.status)}`
11228
+ );
11229
+ return response.task;
11230
+ }
11198
11231
  function stateLabel(state) {
11199
11232
  switch (state) {
11200
11233
  case "installed":
@@ -11232,15 +11265,57 @@ function versionLabel(pkg) {
11232
11265
  if (pkg.latestVersion !== null) return `latest v${pkg.latestVersion}`;
11233
11266
  return "not published";
11234
11267
  }
11235
- function PackageStatusCard({ pkg }) {
11268
+ function taskActionLabel(action) {
11269
+ switch (action) {
11270
+ case "install":
11271
+ return "Install";
11272
+ case "upgrade":
11273
+ return "Upgrade";
11274
+ case "help":
11275
+ return "Help";
11276
+ case "runner-presets":
11277
+ return "Presets";
11278
+ }
11279
+ }
11280
+ function taskStatusLabel(task) {
11281
+ switch (task.status) {
11282
+ case "queued":
11283
+ return "Queued";
11284
+ case "running":
11285
+ return `${taskActionLabel(task.action)} running`;
11286
+ case "succeeded":
11287
+ return `${taskActionLabel(task.action)} complete`;
11288
+ case "failed":
11289
+ return `${taskActionLabel(task.action)} failed`;
11290
+ }
11291
+ }
11292
+ function packagePrimaryAction(pkg) {
11293
+ switch (pkg.state) {
11294
+ case "available":
11295
+ return "install";
11296
+ case "update-available":
11297
+ return "upgrade";
11298
+ case "installed":
11299
+ case "planned":
11300
+ case "unknown":
11301
+ return null;
11302
+ }
11303
+ }
11304
+ function PackageStatusCard({
11305
+ pkg,
11306
+ activeTask,
11307
+ onStartTask
11308
+ }) {
11309
+ const primaryAction = packagePrimaryAction(pkg);
11310
+ const packageTaskRunning = activeTask !== null && activeTask.packageId === pkg.id && activeTask.status === "running";
11236
11311
  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
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
11312
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
11238
11313
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
11239
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
11314
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
11240
11315
  /* @__PURE__ */ jsxRuntimeExports.jsx(PackageCheck, { className: "size-4 shrink-0 text-white/55" }),
11241
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-sm font-semibold", children: pkg.name })
11316
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold leading-snug", children: pkg.name })
11242
11317
  ] }),
11243
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate font-mono text-[11px] text-white/35", children: pkg.npmName })
11318
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 break-all font-mono text-[11px] leading-snug text-white/35", children: pkg.npmName })
11244
11319
  ] }),
11245
11320
  /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { className: cn("shrink-0", stateClassName(pkg.state)), variant: "outline", children: stateLabel(pkg.state) })
11246
11321
  ] }),
@@ -11249,16 +11324,88 @@ function PackageStatusCard({ pkg }) {
11249
11324
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.035] px-2 py-1 font-mono text-white/50", children: versionLabel(pkg) }),
11250
11325
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.035] px-2 py-1 text-white/45", children: pkg.capability }),
11251
11326
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.035] px-2 py-1 font-mono text-white/35", children: pkg.installCommand })
11327
+ ] }),
11328
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [
11329
+ primaryAction !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
11330
+ Button,
11331
+ {
11332
+ type: "button",
11333
+ size: "sm",
11334
+ variant: "secondary",
11335
+ disabled: packageTaskRunning,
11336
+ onClick: () => onStartTask(pkg.id, primaryAction),
11337
+ children: [
11338
+ packageTaskRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3.5" }),
11339
+ taskActionLabel(primaryAction)
11340
+ ]
11341
+ }
11342
+ ),
11343
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
11344
+ Button,
11345
+ {
11346
+ type: "button",
11347
+ size: "sm",
11348
+ variant: "outline",
11349
+ disabled: packageTaskRunning || pkg.state === "planned",
11350
+ onClick: () => onStartTask(pkg.id, "help"),
11351
+ children: [
11352
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5" }),
11353
+ "Help"
11354
+ ]
11355
+ }
11356
+ ),
11357
+ pkg.id === "eval-harness" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
11358
+ Button,
11359
+ {
11360
+ type: "button",
11361
+ size: "sm",
11362
+ variant: "ghost",
11363
+ disabled: packageTaskRunning || pkg.state === "planned",
11364
+ onClick: () => onStartTask(pkg.id, "runner-presets"),
11365
+ children: [
11366
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Beaker, { className: "size-3.5" }),
11367
+ "Presets"
11368
+ ]
11369
+ }
11370
+ )
11252
11371
  ] })
11253
11372
  ] });
11254
11373
  }
11374
+ function TaskConsole({ task }) {
11375
+ if (task === null) return null;
11376
+ const isRunning = task.status === "queued" || task.status === "running";
11377
+ 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: [
11378
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
11379
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
11380
+ isRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-4 animate-spin text-cyan-200/75" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-4 text-white/55" }),
11381
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
11382
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold", children: taskStatusLabel(task) }),
11383
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "break-all font-mono text-[11px] text-white/35", children: task.command })
11384
+ ] })
11385
+ ] }),
11386
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
11387
+ Badge,
11388
+ {
11389
+ variant: "outline",
11390
+ className: cn(
11391
+ "shrink-0",
11392
+ 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"
11393
+ ),
11394
+ children: task.status
11395
+ }
11396
+ )
11397
+ ] }),
11398
+ /* @__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..." }),
11399
+ task.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-xs text-red-100", children: task.error })
11400
+ ] });
11401
+ }
11255
11402
  function WorkflowTile({ workflow }) {
11256
11403
  const Icon2 = workflow.icon;
11257
11404
  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: [
11258
11405
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
11259
11406
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
11260
11407
  /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { className: "size-4 shrink-0 text-cyan-200/75" }),
11261
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-sm font-semibold", children: workflow.title })
11408
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold", children: workflow.title })
11262
11409
  ] }),
11263
11410
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-white/[0.04] px-2 py-0.5 font-mono text-[10px] text-white/45", children: workflow.label })
11264
11411
  ] }),
@@ -11274,13 +11421,13 @@ function buildSessionCommand(sessionId) {
11274
11421
  }
11275
11422
  function RunnerPresetCard({ preset }) {
11276
11423
  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: [
11277
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
11424
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
11278
11425
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
11279
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
11426
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
11280
11427
  /* @__PURE__ */ jsxRuntimeExports.jsx(Rocket, { className: "size-4 shrink-0 text-cyan-200/75" }),
11281
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-sm font-semibold", children: preset.name })
11428
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold leading-snug", children: preset.name })
11282
11429
  ] }),
11283
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 font-mono text-[11px] text-white/35", children: [
11430
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 break-all font-mono text-[11px] leading-snug text-white/35", children: [
11284
11431
  preset.binary,
11285
11432
  " ",
11286
11433
  preset.modelFlag,
@@ -11307,10 +11454,23 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11307
11454
  const [open, setOpen] = reactExports.useState(false);
11308
11455
  const [copied, setCopied] = reactExports.useState(false);
11309
11456
  const [copiedPresets, setCopiedPresets] = reactExports.useState(false);
11457
+ const [activeTaskId, setActiveTaskId] = reactExports.useState(null);
11458
+ const [taskError, setTaskError] = reactExports.useState(null);
11310
11459
  const response = useSWR("/api/ecosystem/packages", fetchEcosystemPackages, {
11311
11460
  revalidateOnFocus: false,
11312
11461
  revalidateIfStale: false
11313
11462
  });
11463
+ const activeTaskResponse = useSWR(
11464
+ activeTaskId === null ? null : `/api/ecosystem/tasks/${activeTaskId}`,
11465
+ fetchEcosystemTask,
11466
+ {
11467
+ refreshInterval: (taskResponse) => {
11468
+ const task = taskResponse?.task;
11469
+ return task !== void 0 && (task.status === "queued" || task.status === "running") ? 900 : 0;
11470
+ },
11471
+ revalidateOnFocus: false
11472
+ }
11473
+ );
11314
11474
  const packages = response.data?.packages ?? [];
11315
11475
  const runnerPresets = response.data?.runnerPresets ?? [];
11316
11476
  const installedCount = reactExports.useMemo(
@@ -11331,6 +11491,21 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11331
11491
  window.setTimeout(() => setCopiedPresets(false), 1200);
11332
11492
  });
11333
11493
  };
11494
+ const activeTask = activeTaskResponse.data?.task ?? null;
11495
+ reactExports.useEffect(() => {
11496
+ if (activeTask === null) return;
11497
+ if (activeTask.status !== "succeeded") return;
11498
+ void response.mutate();
11499
+ }, [activeTask, response]);
11500
+ const handleStartTask = (packageId, action) => {
11501
+ setTaskError(null);
11502
+ void startEcosystemTask(packageId, action).then((task) => {
11503
+ setActiveTaskId(task.id);
11504
+ void activeTaskResponse.mutate({ task }, { revalidate: true });
11505
+ }).catch((error) => {
11506
+ setTaskError(error instanceof Error ? error.message : "Failed to start ecosystem task");
11507
+ });
11508
+ };
11334
11509
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Dialog, { open, onOpenChange: setOpen, children: [
11335
11510
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
11336
11511
  Button,
@@ -11418,7 +11593,15 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11418
11593
  ] }),
11419
11594
  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
11595
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-3 md:grid-cols-3", children: [
11421
- packages.map((pkg) => /* @__PURE__ */ jsxRuntimeExports.jsx(PackageStatusCard, { pkg }, pkg.id)),
11596
+ packages.map((pkg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
11597
+ PackageStatusCard,
11598
+ {
11599
+ pkg,
11600
+ activeTask,
11601
+ onStartTask: handleStartTask
11602
+ },
11603
+ pkg.id
11604
+ )),
11422
11605
  response.isLoading && packages.length === 0 && [0, 1, 2].map((index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
11423
11606
  "div",
11424
11607
  {
@@ -11427,7 +11610,9 @@ function AgentLabDialog({ currentSessionId, logCount }) {
11427
11610
  index
11428
11611
  ))
11429
11612
  ] })
11430
- ] })
11613
+ ] }),
11614
+ 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 }),
11615
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TaskConsole, { task: activeTask })
11431
11616
  ] })
11432
11617
  ] })
11433
11618
  ] });
@@ -13830,12 +14015,12 @@ function BrandHeader({
13830
14015
  }) {
13831
14016
  if (compact) {
13832
14017
  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 items-start justify-between gap-3", children: [
13834
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
14018
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
14019
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-fit items-start gap-2", children: [
13835
14020
  /* @__PURE__ */ jsxRuntimeExports.jsx(CrabFamily, { compact: true, entrancePhase: crabEntrancePhase }),
13836
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
13837
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-baseline gap-2", children: [
13838
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-base font-bold", children: "Inspector" }),
14021
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-fit", children: [
14022
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-fit flex-wrap items-baseline gap-x-2 gap-y-0.5", children: [
14023
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-base font-bold", children: "Agent Inspector" }),
13839
14024
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 font-mono text-[11px] font-semibold text-muted-foreground sm:inline", children: [
13840
14025
  "v",
13841
14026
  packageJson.version
@@ -13854,13 +14039,13 @@ function BrandHeader({
13854
14039
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 hidden sm:block", children: /* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {}) })
13855
14040
  ] });
13856
14041
  }
13857
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[1fr_auto_1fr] items-start gap-3 pb-6", children: [
13858
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}),
13859
- /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "flex min-w-0 flex-col items-center gap-2 text-center", children: [
13860
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex max-w-[calc(100vw-7rem)] items-end gap-2 whitespace-nowrap", children: [
14042
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-x-4 gap-y-3 pb-6", children: [
14043
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-w-10 flex-1", "aria-hidden": "true" }),
14044
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "flex min-w-fit flex-col items-center gap-2 text-center", children: [
14045
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex max-w-full flex-wrap items-end justify-center gap-x-2 gap-y-1", children: [
13861
14046
  /* @__PURE__ */ jsxRuntimeExports.jsx(CrabFamily, { compact: false, entrancePhase: crabEntrancePhase }),
13862
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-0 items-baseline gap-2 pl-1", children: [
13863
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-lg font-bold", children: "Agent Inspector" }),
14047
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-fit flex-wrap items-baseline justify-center gap-x-2 gap-y-0.5 pl-1", children: [
14048
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-lg font-bold", children: "Agent Inspector" }),
13864
14049
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 font-mono text-xs font-semibold text-muted-foreground", children: [
13865
14050
  "v",
13866
14051
  packageJson.version
@@ -13869,10 +14054,10 @@ function BrandHeader({
13869
14054
  /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4 shrink-0 text-muted-foreground/70", "aria-hidden": "true" }),
13870
14055
  /* @__PURE__ */ jsxRuntimeExports.jsx(McpLogo, { className: "size-10 shrink-0" })
13871
14056
  ] }),
13872
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "max-w-[calc(100vw-7rem)] text-sm font-semibold text-muted-foreground", children: "Local First. Evidence First." }),
14057
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "max-w-full text-sm font-semibold text-muted-foreground", children: "Local First. Evidence First." }),
13873
14058
  /* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {})
13874
14059
  ] }),
13875
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "justify-self-end flex items-center gap-1", children: [
14060
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 justify-end gap-1", children: [
13876
14061
  /* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
13877
14062
  /* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
13878
14063
  /* @__PURE__ */ jsxRuntimeExports.jsx(AgentLabDialog, { currentSessionId, logCount }),
@@ -14206,7 +14391,24 @@ function ProxyViewer({
14206
14391
  const logListRef = reactExports.useRef(null);
14207
14392
  const logListWrapperRef = reactExports.useRef(null);
14208
14393
  const importInputRef = reactExports.useRef(null);
14394
+ const exportAbortRef = reactExports.useRef(null);
14395
+ const exportRequestIdRef = reactExports.useRef(0);
14209
14396
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
14397
+ const cancelTransientTasks = reactExports.useCallback(() => {
14398
+ exportRequestIdRef.current += 1;
14399
+ exportAbortRef.current?.abort();
14400
+ exportAbortRef.current = null;
14401
+ setExporting(null);
14402
+ setExportError(null);
14403
+ }, []);
14404
+ reactExports.useEffect(
14405
+ () => () => {
14406
+ exportRequestIdRef.current += 1;
14407
+ exportAbortRef.current?.abort();
14408
+ exportAbortRef.current = null;
14409
+ },
14410
+ []
14411
+ );
14210
14412
  reactExports.useEffect(() => {
14211
14413
  const perCrabDuration = 400;
14212
14414
  const startDelay = 50;
@@ -14228,11 +14430,18 @@ function ProxyViewer({
14228
14430
  }, [logs.length, pinnedSessionId]);
14229
14431
  const handleExport = reactExports.useCallback(
14230
14432
  async (mode) => {
14433
+ exportAbortRef.current?.abort();
14434
+ const controller = new AbortController();
14435
+ exportAbortRef.current = controller;
14436
+ const requestId = exportRequestIdRef.current + 1;
14437
+ exportRequestIdRef.current = requestId;
14231
14438
  setExporting(mode);
14232
14439
  setExportError(null);
14233
14440
  try {
14234
- const result = await exportLogsAsZip(logs, mode);
14441
+ const result = await exportLogsAsZip(logs, mode, { signal: controller.signal });
14442
+ if (exportRequestIdRef.current !== requestId) return;
14235
14443
  if (!result.ok) {
14444
+ if (result.canceled === true) return;
14236
14445
  if (onNotify !== void 0) {
14237
14446
  onNotify({ message: result.message, tone: "error" });
14238
14447
  } else {
@@ -14240,7 +14449,10 @@ function ProxyViewer({
14240
14449
  }
14241
14450
  }
14242
14451
  } finally {
14243
- setExporting(null);
14452
+ if (exportRequestIdRef.current === requestId) {
14453
+ exportAbortRef.current = null;
14454
+ setExporting(null);
14455
+ }
14244
14456
  }
14245
14457
  },
14246
14458
  [logs, onNotify]
@@ -14278,8 +14490,24 @@ function ProxyViewer({
14278
14490
  [onImportLogs, onNotify]
14279
14491
  );
14280
14492
  reactExports.useEffect(() => {
14493
+ cancelTransientTasks();
14281
14494
  setComparePair(null);
14282
- }, [selectedSession, selectedModel]);
14495
+ }, [cancelTransientTasks, selectedSession, selectedModel]);
14496
+ const handleClearCurrentView = reactExports.useCallback(() => {
14497
+ cancelTransientTasks();
14498
+ setImportStatus(null);
14499
+ setImportError(null);
14500
+ setComparePair(null);
14501
+ onClearAll();
14502
+ }, [cancelTransientTasks, onClearAll]);
14503
+ const handleReloadCurrentSession = reactExports.useCallback(() => {
14504
+ if (onReloadSession === void 0) return;
14505
+ cancelTransientTasks();
14506
+ setImportStatus(null);
14507
+ setImportError(null);
14508
+ setComparePair(null);
14509
+ onReloadSession();
14510
+ }, [cancelTransientTasks, onReloadSession]);
14283
14511
  const closeCompare = reactExports.useCallback(() => {
14284
14512
  setComparePair(null);
14285
14513
  }, []);
@@ -14376,7 +14604,7 @@ function ProxyViewer({
14376
14604
  "button",
14377
14605
  {
14378
14606
  type: "button",
14379
- onClick: onReloadSession,
14607
+ onClick: handleReloadCurrentSession,
14380
14608
  disabled: isLoading,
14381
14609
  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
14610
  title: "Load stored logs for this session",
@@ -14390,7 +14618,7 @@ function ProxyViewer({
14390
14618
  "button",
14391
14619
  {
14392
14620
  type: "button",
14393
- onClick: onClearAll,
14621
+ onClick: handleClearCurrentView,
14394
14622
  disabled: !canClearCurrentView,
14395
14623
  className: cn(
14396
14624
  "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",
@@ -14547,7 +14775,7 @@ function ProxyViewer({
14547
14775
  "button",
14548
14776
  {
14549
14777
  type: "button",
14550
- onClick: onReloadSession,
14778
+ onClick: handleReloadCurrentSession,
14551
14779
  disabled: isLoading,
14552
14780
  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
14781
  title: "Reload this session from stored logs",
@@ -14561,7 +14789,7 @@ function ProxyViewer({
14561
14789
  "button",
14562
14790
  {
14563
14791
  type: "button",
14564
- onClick: onClearAll,
14792
+ onClick: handleClearCurrentView,
14565
14793
  disabled: !canClearCurrentView,
14566
14794
  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
14795
  title: "Clear the current view. Stored logs remain reloadable.",
@@ -14590,7 +14818,7 @@ function ProxyViewer({
14590
14818
  "button",
14591
14819
  {
14592
14820
  type: "button",
14593
- onClick: onReloadSession,
14821
+ onClick: handleReloadCurrentSession,
14594
14822
  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
14823
  children: [
14596
14824
  /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "size-3.5" }),
@@ -14732,6 +14960,8 @@ const HASH_HIGHLIGHT_MS = 1800;
14732
14960
  const SESSION_PAGE_LIMIT = 100;
14733
14961
  const SESSION_RELOAD_PAGE_LIMIT = 500;
14734
14962
  const MAX_SESSION_RELOAD_PAGES = 200;
14963
+ const SESSION_LOGS_FETCH_TIMEOUT_MS = 2e4;
14964
+ const SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS = 1e4;
14735
14965
  function buildLogsStreamUrl(sessionId) {
14736
14966
  const params = new URLSearchParams();
14737
14967
  if (sessionId !== void 0) {
@@ -14769,6 +14999,13 @@ function buildLogIndex(logs) {
14769
14999
  }
14770
15000
  return idx;
14771
15001
  }
15002
+ function fetchSessionLogsPage(sessionId, request, limit = SESSION_PAGE_LIMIT) {
15003
+ return fetchJsonWithTimeout(
15004
+ buildSessionLogsPageUrl(sessionId, request, limit),
15005
+ LogCursorPageSchema,
15006
+ SESSION_LOGS_FETCH_TIMEOUT_MS
15007
+ );
15008
+ }
14772
15009
  function mergeLogsById(previousLogs, incomingLogs) {
14773
15010
  if (incomingLogs.length === 0) return [...previousLogs];
14774
15011
  const byId = /* @__PURE__ */ new Map();
@@ -14912,7 +15149,7 @@ function ProxyViewerContainer({
14912
15149
  sessionPageRequestIdRef.current = requestId;
14913
15150
  setSessionPageLoading(true);
14914
15151
  dismissToast();
14915
- void fetchJson(buildSessionLogsPageUrl(initialSessionId, request), LogCursorPageSchema).then((page) => {
15152
+ void fetchSessionLogsPage(initialSessionId, request).then((page) => {
14916
15153
  if (sessionPageRequestIdRef.current !== requestId) return;
14917
15154
  const nextLogs = page.logs;
14918
15155
  logIndexRef.current = buildLogIndex(nextLogs);
@@ -14939,20 +15176,18 @@ function ProxyViewerContainer({
14939
15176
  dismissToast();
14940
15177
  void (async () => {
14941
15178
  const collected = [];
14942
- let page = await fetchJson(
14943
- buildSessionLogsPageUrl(sessionId, { kind: "oldest" }, SESSION_RELOAD_PAGE_LIMIT),
14944
- LogCursorPageSchema
15179
+ let page = await fetchSessionLogsPage(
15180
+ sessionId,
15181
+ { kind: "oldest" },
15182
+ SESSION_RELOAD_PAGE_LIMIT
14945
15183
  );
14946
15184
  collected.push(...page.logs);
14947
15185
  let loadedPages = 1;
14948
15186
  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
15187
+ page = await fetchSessionLogsPage(
15188
+ sessionId,
15189
+ { kind: "newer", afterLogId: page.newestLogId },
15190
+ SESSION_RELOAD_PAGE_LIMIT
14956
15191
  );
14957
15192
  collected.push(...page.logs);
14958
15193
  loadedPages += 1;
@@ -14987,12 +15222,11 @@ function ProxyViewerContainer({
14987
15222
  return;
14988
15223
  }
14989
15224
  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) => {
15225
+ void fetchJsonWithTimeout(
15226
+ "/api/groups",
15227
+ InspectorGroupsListResponseSchema,
15228
+ SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS
15229
+ ).then((response) => response.groups).then((groups) => {
14996
15230
  if (cancelled || groups === null) return;
14997
15231
  setSessionMemberships(buildSessionMembershipEvidence(groups, initialSessionId));
14998
15232
  }).catch(() => {
@@ -15219,13 +15453,15 @@ function ProxyViewerContainer({
15219
15453
  ] });
15220
15454
  }
15221
15455
  export {
15222
- ANATOMY_ROLE_LABELS as A,
15456
+ ApiTimeoutError as A,
15223
15457
  Badge as B,
15224
15458
  CONTEXT_USAGE_THRESHOLDS as C,
15225
15459
  Dialog as D,
15226
- LazyJsonViewer as E,
15227
- safeJsonValue as F,
15228
- parseJsonText as G,
15460
+ CollapsibleContent as E,
15461
+ ScrollArea as F,
15462
+ LazyJsonViewer as G,
15463
+ safeJsonValue as H,
15464
+ parseJsonText as I,
15229
15465
  LazyJsonViewerFromString as L,
15230
15466
  ProxyViewerContainer as P,
15231
15467
  ROLE_COLOR_CLASSES as R,
@@ -15234,27 +15470,27 @@ export {
15234
15470
  getConversationId as a,
15235
15471
  copyTextToClipboard as b,
15236
15472
  cn as c,
15237
- DialogContent as d,
15238
- DialogHeader as e,
15473
+ fetchJsonWithTimeout as d,
15474
+ DialogContent as e,
15239
15475
  formatTokens as f,
15240
15476
  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,
15477
+ DialogHeader as h,
15478
+ DialogTitle as i,
15479
+ TabsList as j,
15480
+ TabsTrigger as k,
15481
+ TabsContent as l,
15482
+ Button as m,
15483
+ TooltipProvider as n,
15484
+ Tooltip as o,
15485
+ TooltipTrigger as p,
15486
+ TooltipContent as q,
15251
15487
  resolveLogFormat as r,
15252
- analyzeContextIntelligence as s,
15253
- formatContextWindowTokens as t,
15488
+ dispatchLogFocusRequest as s,
15489
+ analyzeContextIntelligence as t,
15254
15490
  useProviders as u,
15255
- getStatusCategory as v,
15256
- Collapsible as w,
15257
- CollapsibleTrigger as x,
15258
- CollapsibleContent as y,
15259
- ScrollArea as z
15491
+ ANATOMY_ROLE_LABELS as v,
15492
+ formatContextWindowTokens as w,
15493
+ getStatusCategory as x,
15494
+ Collapsible as y,
15495
+ CollapsibleTrigger as z
15260
15496
  };