@tonyclaw/agent-inspector 3.0.25 → 3.0.27

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 (81) hide show
  1. package/.output/cli.js +80 -11
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BnvOPwpe.js → CompareDrawer-BVCAAbbu.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-CYOXB81n.js +4234 -0
  5. package/.output/public/assets/{ReplayDialog-BJgoQWdW.js → ReplayDialog-DMX23O5G.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CpBZEHnM.js → RequestAnatomy-S17pYDRc.js} +1 -1
  7. package/.output/public/assets/{ResponseView-CWmyRb2M.js → ResponseView-Cf1XrawD.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-BWhC8bwx.js → StreamingChunkSequence-BdOrVlCi.js} +1 -1
  9. package/.output/public/assets/{_sessionId-Cq-2OqUU.js → _sessionId-BnKGmJrR.js} +1 -1
  10. package/.output/public/assets/{index-Cf8O0Oos.js → index-9fMLCU4d.js} +1 -1
  11. package/.output/public/assets/{index-BKi9IRtV.js → index-B1atk19i.js} +3 -3
  12. package/.output/public/assets/index-CmeF5XXS.css +1 -0
  13. package/.output/public/assets/{index-DdgTJ4QJ.js → index-gY3I-7-D.js} +1 -1
  14. package/.output/public/assets/{json-viewer-Bqu_4hmm.js → json-viewer-CpyYpPjx.js} +1 -1
  15. package/.output/server/_libs/lucide-react.mjs +6 -6
  16. package/.output/server/_libs/three.mjs +26016 -0
  17. package/.output/server/{_sessionId-Ba7H3Gjw.mjs → _sessionId-eA_h2_s6.mjs} +9 -6
  18. package/.output/server/_ssr/{CompareDrawer-aH59Ab6x.mjs → CompareDrawer-K2k9G7r1.mjs} +10 -7
  19. package/.output/server/_ssr/{ProxyViewerContainer-By-I0tJv.mjs → ProxyViewerContainer-CbyBpCAj.mjs} +804 -363
  20. package/.output/server/_ssr/{ReplayDialog-BO4Vy0et.mjs → ReplayDialog-DacI2Iv0.mjs} +10 -7
  21. package/.output/server/_ssr/{RequestAnatomy-FtOr7vSS.mjs → RequestAnatomy-gh-pAdkO.mjs} +9 -6
  22. package/.output/server/_ssr/{ResponseView-cvw6ajue.mjs → ResponseView-BRJKN50-.mjs} +10 -7
  23. package/.output/server/_ssr/{StreamingChunkSequence-Oc8S2kD6.mjs → StreamingChunkSequence-BQLVEuWY.mjs} +9 -6
  24. package/.output/server/_ssr/{index-BDRlQheR.mjs → index-DuSWrjbH.mjs} +9 -6
  25. package/.output/server/_ssr/index.mjs +2 -2
  26. package/.output/server/_ssr/{json-viewer-Cd6trhQr.mjs → json-viewer-DNs1ZjKI.mjs} +9 -6
  27. package/.output/server/_ssr/{router-bofP-Ekl.mjs → router-Dgx6-ot3.mjs} +713 -218
  28. package/.output/server/{_tanstack-start-manifest_v-BPuf86qB.mjs → _tanstack-start-manifest_v-CoGjnkQD.mjs} +1 -1
  29. package/.output/server/index.mjs +65 -65
  30. package/README.md +8 -0
  31. package/package.json +6 -5
  32. package/src/cli.ts +43 -9
  33. package/src/components/inspector-pet/InspectorPet.tsx +66 -51
  34. package/src/components/inspector-pet/InspectorPetCrab3D.tsx +455 -0
  35. package/src/components/inspector-pet/inspectorPetLogic.ts +28 -43
  36. package/src/components/providers/ProviderCard.tsx +3 -36
  37. package/src/components/providers/ProviderForm.tsx +251 -175
  38. package/src/components/providers/ProvidersPanel.tsx +18 -8
  39. package/src/components/providers/SettingsDialog.tsx +3 -3
  40. package/src/components/providers/providerFormCapabilities.ts +36 -0
  41. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
  42. package/src/contracts/anthropic.ts +0 -5
  43. package/src/contracts/index.ts +0 -4
  44. package/src/contracts/log.ts +0 -1
  45. package/src/contracts/openai.ts +0 -3
  46. package/src/knowledge/openclawClient.ts +22 -9
  47. package/src/knowledge/openclawGatewayClient.ts +20 -13
  48. package/src/lib/alertContract.ts +0 -3
  49. package/src/lib/ecosystemContract.ts +0 -3
  50. package/src/lib/export-logs.ts +1 -4
  51. package/src/lib/safeFetch.ts +100 -0
  52. package/src/lib/ssrfGuard.ts +133 -0
  53. package/src/lib/stopReason.ts +1 -4
  54. package/src/lib/unknown.ts +27 -0
  55. package/src/lib/useProviders.ts +1 -1
  56. package/src/mcp/mode.ts +3 -9
  57. package/src/mcp/server.ts +5 -14
  58. package/src/mcp/toolHandlers.ts +45 -54
  59. package/src/proxy/constants.ts +17 -0
  60. package/src/proxy/formats/anthropic/handler.ts +5 -1
  61. package/src/proxy/formats/anthropic/schemas.ts +1 -7
  62. package/src/proxy/formats/index.ts +1 -1
  63. package/src/proxy/formats/openai/schemas.ts +0 -3
  64. package/src/proxy/formats/registry.ts +0 -3
  65. package/src/proxy/handler.ts +91 -12
  66. package/src/proxy/identityProxy.ts +74 -1
  67. package/src/proxy/logImporter.ts +82 -12
  68. package/src/proxy/logIndex.ts +1 -4
  69. package/src/proxy/providerSecretStore.ts +181 -0
  70. package/src/proxy/providers.ts +84 -11
  71. package/src/proxy/schemas.ts +1 -8
  72. package/src/proxy/sqliteLogIndex.ts +0 -16
  73. package/src/proxy/store.ts +1 -5
  74. package/src/proxy/toolSchemaWarnings.ts +1 -9
  75. package/src/routes/api/logs.$id.replay.ts +17 -5
  76. package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
  77. package/src/routes/api/providers.$providerId.ts +18 -5
  78. package/src/routes/api/providers.ts +18 -7
  79. package/styles/globals.css +0 -61
  80. package/.output/public/assets/ProxyViewerContainer-DcqFqo1m.js +0 -127
  81. package/.output/public/assets/index-BMYuDL5X.css +0 -1
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, R as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, z as DeleteInspectorGroupsResponseSchema, 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, g as readStoredUiScalePreference, h as persistUiScalePreference, i as applyUiScalePreference, w as extractStopReason, p as packageJson, P as ProviderConfigSchema, U as UI_SCALE_STORAGE_KEY, j as normalizeUiScalePreference, q as apiFormatForPath, B as GroupEvidenceExportResultSchema, E as DeleteInspectorGroupResponseSchema, $ as PiAgentResponseSchema, v as isTurnBoundary, r as readStoredThemeMode, e as applyThemeMode, f as persistThemeMode, a2 as formatUiScale, x as AlertSummarySchema, y as AlertListResponseSchema, I as InspectorGroupsListResponseSchema, G as GroupEvidenceReadResponseSchema, V as createPendingProviderTestResults, W as ProviderTestResultsSchema, X as createFailedProviderTestResults, Y as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, Z as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, _ as TimeDisplayFormatSchema, T as THEME_MODE_STORAGE_KEY, n as normalizeThemeMode, H as maskApiKey, M as PATH_V1_MESSAGES, N as PATH_V1_CHAT_COMPLETIONS, Q as PATH_V1_RESPONSES, F as providerHasContextMetadata, l as parseOpenAIResponse, m as parseOpenAIResponsesResponse, O as OpenAIRequestSchema, o as OpenAIResponsesRequestSchema, A as AnthropicResponseSchema$1, k as AnthropicRequestSchema, s as safeGetOwnProperty, a0 as resolveProviderContextWindow, t as getSessionPath, J as findProviderModelMetadata, S as previewUpstreamUrl, K as KnowledgeCandidateSchema, a1 as isPlainRecord, u as stripClaudeCodeBillingHeader, L as LogBodyChunkSchema } from "./router-bofP-Ekl.mjs";
2
+ import { C as CapturedLogSchema, B as DeleteInspectorGroupsResponseSchema, 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, g as readStoredUiScalePreference, h as persistUiScalePreference, j as applyUiScalePreference, x as extractStopReason, p as packageJson, P as ProviderConfigSchema, U as UI_SCALE_STORAGE_KEY, k as normalizeUiScalePreference, t as apiFormatForPath, E as GroupEvidenceExportResultSchema, F as DeleteInspectorGroupResponseSchema, $ as PiAgentResponseSchema, w as isTurnBoundary, r as readStoredThemeMode, e as applyThemeMode, f as persistThemeMode, a2 as formatUiScale, y as AlertSummarySchema, z as AlertListResponseSchema, I as InspectorGroupsListResponseSchema, G as GroupEvidenceReadResponseSchema, V as createPendingProviderTestResults, W as ProviderTestResultsSchema, X as createFailedProviderTestResults, Y as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, Z as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, _ as TimeDisplayFormatSchema, i as isRecord, T as THEME_MODE_STORAGE_KEY, n as normalizeThemeMode, M as PATH_V1_MESSAGES, N as PATH_V1_CHAT_COMPLETIONS, Q as PATH_V1_RESPONSES, H as providerHasContextMetadata, m as parseOpenAIResponse, o as parseOpenAIResponsesResponse, O as OpenAIRequestSchema, q as OpenAIResponsesRequestSchema, A as AnthropicResponseSchema$1, l as AnthropicRequestSchema, s as safeGetOwnProperty, a0 as resolveProviderContextWindow, u as getSessionPath, J as findProviderModelMetadata, S as previewUpstreamUrl, K as KnowledgeCandidateSchema, a1 as isPlainRecord, v as stripClaudeCodeBillingHeader, L as LogBodyChunkSchema } from "./router-Dgx6-ot3.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";
@@ -7,7 +7,9 @@ import { t as twMerge } from "../_libs/tailwind-merge.mjs";
7
7
  import { c as cva } from "../_libs/class-variance-authority.mjs";
8
8
  import { u as useWindowVirtualizer, a as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
- import { T as TriangleAlert, C as CircleCheck, X, I as Info, a as ChevronDown, b as Check, P as PanelRightOpen, c as PanelRightClose, S as Search, U as Upload, D as Download, F as FileBraces, R as RefreshCw, d as Trash2, H as Heart, e as Pause, f as Play, g as Plus, B as BrainCircuit, L as LoaderCircle, h as Send, A as ArrowLeft, i as ChevronUp, j as Copy, k as ChevronsLeft, l as ChevronLeft, m as ChevronRight, n as ChevronsRight, o as Clapperboard, p as Flag, q as Siren, r as Timer, G as Gauge, s as Sun, E as Eye, M as Moon, t as ALargeSmall, u as Layers, v as FlaskConical, w as Sparkles, x as Activity, y as Beaker, N as Network, z as Terminal, J as Settings, K as Monitor, O as Rocket, Q as PackageCheck, V as History, W as Scan, Y as CircleAlert, Z as ArrowUpRight, _ as ArrowDownRight, $ as Cpu, a0 as FolderOpen, a1 as Bell, a2 as Clock3, a3 as ShieldAlert, a4 as EyeOff, a5 as ExternalLink, a6 as Clock, a7 as RotateCw, a8 as Pencil, a9 as MessageSquare, aa as Zap, ab as Wrench, ac as Brain, ad as CircleCheckBig, ae as Minus, af as CircleX, ag as ShieldCheck, ah as Save, ai as FileSearch, aj as CloudUpload, ak as CircleQuestionMark, al as Server, am as Lock, an as Wifi, ao as WifiOff, ap as OctagonAlert, aq as Radio, ar as Globe, as as FileTerminal, at as ChevronsUp, au as ChevronsDown, av as FileDiff, aw as RotateCcw, ax as GitCompareArrows, ay as ArrowUp, az as ArrowDown, aA as Ellipsis, aB as CodeXml, aC as Blocks, aD as MousePointerClick, aE as FileText, aF as Rows3, aG as Columns2 } from "../_libs/lucide-react.mjs";
10
+ import { r as reactDomExports } from "../_libs/react-dom.mjs";
11
+ import { W as WebGLRenderer, S as SRGBColorSpace, A as ACESFilmicToneMapping, a as Scene, O as OrthographicCamera, M as MeshPhysicalMaterial, C as Color, G as Group, b as Mesh, c as CircleGeometry, d as MeshBasicMaterial, e as SphereGeometry, f as AmbientLight, H as HemisphereLight, D as DirectionalLight, P as PointLight, V as Vector3, g as CylinderGeometry } from "../_libs/three.mjs";
12
+ import { T as TriangleAlert, C as CircleCheck, X, I as Info, a as ChevronDown, b as Check, P as PanelRightOpen, c as PanelRightClose, S as Search, U as Upload, D as Download, F as FileBraces, R as RefreshCw, d as Trash2, H as Heart, e as Pause, f as Play, g as Plus, B as BrainCircuit, L as LoaderCircle, h as Send, A as ArrowLeft, i as ChevronUp, j as Copy, k as ChevronsLeft, l as ChevronLeft, m as ChevronRight, n as ChevronsRight, o as Clapperboard, p as Flag, q as Siren, r as Timer, G as Gauge, s as Sun, E as Eye, M as Moon, t as ALargeSmall, u as Layers, v as FlaskConical, w as Sparkles, x as Activity, y as Beaker, N as Network, z as Terminal, J as Settings, K as Monitor, O as Rocket, Q as PackageCheck, V as History, W as Scan, Y as CircleAlert, Z as ArrowUpRight, _ as ArrowDownRight, $ as Cpu, a0 as FolderOpen, a1 as Bell, a2 as Clock3, a3 as ShieldAlert, a4 as EyeOff, a5 as ExternalLink, a6 as Clock, a7 as RotateCw, a8 as CircleCheckBig, a9 as Pencil, aa as MessageSquare, ab as Zap, ac as Wrench, ad as Brain, ae as Minus, af as CircleX, ag as ShieldCheck, ah as Save, ai as FileSearch, aj as CloudUpload, ak as CircleQuestionMark, al as Server, am as Lock, an as Wifi, ao as WifiOff, ap as OctagonAlert, aq as Radio, ar as Globe, as as FileTerminal, at as ChevronsUp, au as ChevronsDown, av as FileDiff, aw as RotateCcw, ax as GitCompareArrows, ay as ArrowUp, az as ArrowDown, aA as Ellipsis, aB as CodeXml, aC as Blocks, aD as MousePointerClick, aE as FileText, aF as Rows3, aG as Columns2 } from "../_libs/lucide-react.mjs";
11
13
  import { u as union, o as object, e as array, l as literal, n as number, d as boolean, b as string, _ as _enum } from "../_libs/zod.mjs";
12
14
  import { S as Select$1, a as SelectTrigger$1, b as SelectIcon, c as SelectValue$1, d as SelectPortal, e as SelectContent$1, f as SelectViewport, g as SelectItem$1, h as SelectItemIndicator, i as SelectItemText, j as SelectScrollUpButton$1, k as SelectScrollDownButton$1 } from "../_libs/radix-ui__react-select.mjs";
13
15
  import { D as Dialog$1, a as DialogContent$1, b as DialogClose, c as DialogTitle$1, d as DialogDescription$1, e as DialogPortal$1, f as DialogOverlay$1, g as DialogTrigger$1 } from "../_libs/radix-ui__react-dialog.mjs";
@@ -389,9 +391,6 @@ async function fetchStreamingChunks(logId, signal) {
389
391
  return null;
390
392
  }
391
393
  }
392
- function isRecord(value) {
393
- return typeof value === "object" && value !== null && !Array.isArray(value);
394
- }
395
394
  function redactValue(value) {
396
395
  if (Array.isArray(value)) return value.map((item) => redactValue(item));
397
396
  if (!isRecord(value)) return value;
@@ -688,7 +687,7 @@ async function fetcher(url) {
688
687
  }
689
688
  function useProviders() {
690
689
  const response = useSWR(
691
- "/api/providers?includeSecrets=1",
690
+ "/api/providers",
692
691
  fetcher,
693
692
  {
694
693
  revalidateOnFocus: false,
@@ -2525,27 +2524,27 @@ function useCopyFeedback(text) {
2525
2524
  return { copied, copy };
2526
2525
  }
2527
2526
  const LazyCompareDrawer = reactExports.lazy(
2528
- () => import("./CompareDrawer-aH59Ab6x.mjs").then((m) => ({ default: m.CompareDrawer }))
2527
+ () => import("./CompareDrawer-K2k9G7r1.mjs").then((m) => ({ default: m.CompareDrawer }))
2529
2528
  );
2530
2529
  const LazyReplayDialog = reactExports.lazy(
2531
- () => import("./ReplayDialog-BO4Vy0et.mjs").then((m) => ({ default: m.ReplayDialog }))
2530
+ () => import("./ReplayDialog-DacI2Iv0.mjs").then((m) => ({ default: m.ReplayDialog }))
2532
2531
  );
2533
2532
  const LazyRequestAnatomy = reactExports.lazy(
2534
- () => import("./RequestAnatomy-FtOr7vSS.mjs").then((m) => ({ default: m.RequestAnatomy }))
2533
+ () => import("./RequestAnatomy-gh-pAdkO.mjs").then((m) => ({ default: m.RequestAnatomy }))
2535
2534
  );
2536
2535
  const LazyResponseView = reactExports.lazy(
2537
- () => import("./ResponseView-cvw6ajue.mjs").then((m) => ({ default: m.ResponseView }))
2536
+ () => import("./ResponseView-BRJKN50-.mjs").then((m) => ({ default: m.ResponseView }))
2538
2537
  );
2539
2538
  const LazyStreamingChunkSequence = reactExports.lazy(
2540
- () => import("./StreamingChunkSequence-Oc8S2kD6.mjs").then((m) => ({
2539
+ () => import("./StreamingChunkSequence-BQLVEuWY.mjs").then((m) => ({
2541
2540
  default: m.StreamingChunkSequence
2542
2541
  }))
2543
2542
  );
2544
2543
  const LazyJsonViewer = reactExports.lazy(
2545
- () => import("./json-viewer-Cd6trhQr.mjs").then((m) => ({ default: m.JsonViewer }))
2544
+ () => import("./json-viewer-DNs1ZjKI.mjs").then((m) => ({ default: m.JsonViewer }))
2546
2545
  );
2547
2546
  const LazyJsonViewerFromString = reactExports.lazy(
2548
- () => import("./json-viewer-Cd6trhQr.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2547
+ () => import("./json-viewer-DNs1ZjKI.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2549
2548
  );
2550
2549
  const HIGHLIGHT_DURATION_MS = 1200;
2551
2550
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -8935,7 +8934,7 @@ function ProviderTestPill({
8935
8934
  testResults,
8936
8935
  isTesting
8937
8936
  }) {
8938
- const hasEndpoint = providerHasEndpoint$1(provider.anthropicBaseUrl) || providerHasEndpoint$1(provider.openaiBaseUrl) || providerHasEndpoint$1(provider.openaiResponsesBaseUrl);
8937
+ const hasEndpoint2 = providerHasEndpoint$1(provider.anthropicBaseUrl) || providerHasEndpoint$1(provider.openaiBaseUrl) || providerHasEndpoint$1(provider.openaiResponsesBaseUrl);
8939
8938
  const summary = summarizeProviderTestResults(provider, testResults);
8940
8939
  if (isTesting) {
8941
8940
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded border border-cyan-300/25 bg-cyan-300/[0.08] px-2 py-1 text-xs text-cyan-100", children: [
@@ -8943,7 +8942,7 @@ function ProviderTestPill({
8943
8942
  "Testing"
8944
8943
  ] });
8945
8944
  }
8946
- if (!hasEndpoint) {
8945
+ if (!hasEndpoint2) {
8947
8946
  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded border border-border/70 bg-muted/20 px-2 py-1 text-xs text-muted-foreground", children: "No endpoint" });
8948
8947
  }
8949
8948
  if (testResults === void 0 || summary.total === 0) {
@@ -9065,16 +9064,7 @@ function ProviderCard({
9065
9064
  onRefreshMetadata,
9066
9065
  isRefreshingMetadata = false
9067
9066
  }) {
9068
- const [showApiKey, setShowApiKey] = reactExports.useState(false);
9069
- const [copied, setCopied] = reactExports.useState(false);
9070
9067
  const [showDetails, setShowDetails] = reactExports.useState(false);
9071
- function handleCopy() {
9072
- void copyTextToClipboard(provider.apiKey).then((success) => {
9073
- if (!success) return;
9074
- setCopied(true);
9075
- setTimeout(() => setCopied(false), 2e3);
9076
- });
9077
- }
9078
9068
  const docsUrl = provider.apiDocsUrl ?? Object.entries(KNOWN_PROVIDER_DOCS).find(
9079
9069
  ([keyword]) => provider.name.toLowerCase().includes(keyword)
9080
9070
  )?.[1];
@@ -9161,28 +9151,9 @@ function ProviderCard({
9161
9151
  ] })
9162
9152
  ] }),
9163
9153
  showDetails && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 border-t border-border/60 pt-3", children: [
9164
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9165
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "flex-1 truncate rounded bg-muted px-2 py-1 text-xs text-muted-foreground", children: showApiKey ? provider.apiKey : maskApiKey(provider.apiKey) }),
9166
- /* @__PURE__ */ jsxRuntimeExports.jsx(
9167
- "button",
9168
- {
9169
- type: "button",
9170
- onClick: () => setShowApiKey((s) => !s),
9171
- className: "p-1 text-muted-foreground transition-colors hover:text-foreground",
9172
- "aria-label": showApiKey ? "Hide API key" : "Show API key",
9173
- children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-4" })
9174
- }
9175
- ),
9176
- /* @__PURE__ */ jsxRuntimeExports.jsx(
9177
- "button",
9178
- {
9179
- type: "button",
9180
- onClick: handleCopy,
9181
- className: "p-1 text-muted-foreground transition-colors hover:text-foreground",
9182
- "aria-label": "Copy API key",
9183
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4 text-cyan-300" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-4" })
9184
- }
9185
- )
9154
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
9155
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3.5 text-emerald-500" }),
9156
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "API key configured" })
9186
9157
  ] }),
9187
9158
  provider.models !== void 0 && provider.models.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1", children: provider.models.map((model) => /* @__PURE__ */ jsxRuntimeExports.jsx(ModelChip, { provider, model }, model)) }),
9188
9159
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "space-y-2", children: [
@@ -9254,6 +9225,25 @@ function ProviderCard({
9254
9225
  }
9255
9226
  );
9256
9227
  }
9228
+ function hasEndpoint(value) {
9229
+ return value !== void 0 && value.trim() !== "";
9230
+ }
9231
+ function deriveProviderCapabilities(provider) {
9232
+ const anthropic = hasEndpoint(provider?.anthropicBaseUrl);
9233
+ const openaiChat = hasEndpoint(provider?.openaiBaseUrl);
9234
+ const openaiResponses = hasEndpoint(provider?.openaiResponsesBaseUrl);
9235
+ return {
9236
+ anthropic,
9237
+ openai: openaiChat || openaiResponses,
9238
+ openaiChat,
9239
+ openaiResponses
9240
+ };
9241
+ }
9242
+ function endpointForCapability(enabled, value, clearWhenDisabled) {
9243
+ if (!enabled) return clearWhenDisabled ? "" : void 0;
9244
+ const trimmed = value.trim();
9245
+ return trimmed === "" ? void 0 : trimmed;
9246
+ }
9257
9247
  const ZHIPU_PROVIDER_KEYWORDS = ["zhipu", "zhipuai", "bigmodel", "z.ai", "zai", "glm"];
9258
9248
  const ZHIPU_CODING_PROVIDER_KEYWORDS = [
9259
9249
  "zhipu coding",
@@ -9477,8 +9467,9 @@ function EndpointUrlPreview({
9477
9467
  ] });
9478
9468
  }
9479
9469
  function ProviderForm({ provider, onSubmit, onCancel }) {
9470
+ const initialCapabilities = deriveProviderCapabilities(provider);
9480
9471
  const [name, setName] = reactExports.useState(provider?.name ?? "");
9481
- const [apiKey, setApiKey] = reactExports.useState(provider?.apiKey ?? "");
9472
+ const [apiKey, setApiKey] = reactExports.useState("");
9482
9473
  const [showApiKey, setShowApiKey] = reactExports.useState(false);
9483
9474
  const [copied, setCopied] = reactExports.useState(false);
9484
9475
  const initialModels = provider?.models;
@@ -9491,7 +9482,12 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9491
9482
  initialModels !== void 0 && initialModels.length > 0 ? initialModels : [""]
9492
9483
  )
9493
9484
  );
9494
- const [activeTab, setActiveTab] = reactExports.useState("anthropic");
9485
+ const [supportsAnthropic, setSupportsAnthropic] = reactExports.useState(initialCapabilities.anthropic);
9486
+ const [supportsOpenai, setSupportsOpenai] = reactExports.useState(initialCapabilities.openai);
9487
+ const [supportsOpenaiChat, setSupportsOpenaiChat] = reactExports.useState(initialCapabilities.openaiChat);
9488
+ const [supportsOpenaiResponses, setSupportsOpenaiResponses] = reactExports.useState(
9489
+ initialCapabilities.openaiResponses
9490
+ );
9495
9491
  const [anthropicBaseUrl, setAnthropicBaseUrl] = reactExports.useState(
9496
9492
  stripEndpointSuffix(provider?.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT)
9497
9493
  );
@@ -9541,10 +9537,16 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9541
9537
  reactExports.useEffect(() => {
9542
9538
  if (provider) {
9543
9539
  setName(provider.name);
9544
- setApiKey(provider.apiKey);
9540
+ setApiKey("");
9541
+ setShowApiKey(false);
9545
9542
  const nextModels = (provider.models?.length ?? 0) > 0 ? provider.models : [""];
9546
9543
  setModels(nextModels);
9547
9544
  setModelMetadataDrafts(createMetadataDrafts(provider, nextModels));
9545
+ const nextCapabilities = deriveProviderCapabilities(provider);
9546
+ setSupportsAnthropic(nextCapabilities.anthropic);
9547
+ setSupportsOpenai(nextCapabilities.openai);
9548
+ setSupportsOpenaiChat(nextCapabilities.openaiChat);
9549
+ setSupportsOpenaiResponses(nextCapabilities.openaiResponses);
9548
9550
  setAnthropicBaseUrl(
9549
9551
  stripEndpointSuffix(provider.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT)
9550
9552
  );
@@ -9596,20 +9598,38 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9596
9598
  if (!name.trim()) {
9597
9599
  newErrors.name = "Name is required";
9598
9600
  }
9599
- if (!apiKey.trim()) {
9601
+ if (provider === void 0 && !apiKey.trim()) {
9600
9602
  newErrors.apiKey = "API key is required";
9601
9603
  }
9602
9604
  if (models.length === 0 || models.every((m) => !m.trim())) {
9603
9605
  newErrors.models = "At least one model is required";
9604
9606
  }
9605
- if (anthropicBaseUrl.trim() && !isValidUrl(anthropicBaseUrl.trim())) {
9606
- newErrors.anthropicBaseUrl = "Invalid URL format";
9607
+ if (!supportsAnthropic && !supportsOpenai) {
9608
+ newErrors.capabilities = "Select at least one API capability";
9607
9609
  }
9608
- if (openaiBaseUrl.trim() && !isValidUrl(openaiBaseUrl.trim())) {
9609
- newErrors.openaiBaseUrl = "Invalid URL format";
9610
+ if (supportsOpenai && !supportsOpenaiChat && !supportsOpenaiResponses) {
9611
+ newErrors.openaiCapabilities = "Select Chat Completions, Responses, or both";
9610
9612
  }
9611
- if (openaiResponsesBaseUrl.trim() && !isValidUrl(openaiResponsesBaseUrl.trim())) {
9612
- newErrors.openaiResponsesBaseUrl = "Invalid URL format";
9613
+ if (supportsAnthropic) {
9614
+ if (anthropicBaseUrl.trim() === "") {
9615
+ newErrors.anthropicBaseUrl = "Anthropic base URL is required";
9616
+ } else if (!isValidUrl(anthropicBaseUrl.trim())) {
9617
+ newErrors.anthropicBaseUrl = "Invalid URL format";
9618
+ }
9619
+ }
9620
+ if (supportsOpenai && supportsOpenaiChat) {
9621
+ if (openaiBaseUrl.trim() === "") {
9622
+ newErrors.openaiBaseUrl = "OpenAI Chat base URL is required";
9623
+ } else if (!isValidUrl(openaiBaseUrl.trim())) {
9624
+ newErrors.openaiBaseUrl = "Invalid URL format";
9625
+ }
9626
+ }
9627
+ if (supportsOpenai && supportsOpenaiResponses) {
9628
+ if (openaiResponsesBaseUrl.trim() === "") {
9629
+ newErrors.openaiResponsesBaseUrl = "OpenAI Responses base URL is required";
9630
+ } else if (!isValidUrl(openaiResponsesBaseUrl.trim())) {
9631
+ newErrors.openaiResponsesBaseUrl = "Invalid URL format";
9632
+ }
9613
9633
  }
9614
9634
  if (modelMetadataUrl.trim() && !isValidUrl(modelMetadataUrl.trim())) {
9615
9635
  newErrors.modelMetadataUrl = "Invalid URL format";
@@ -9627,9 +9647,6 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9627
9647
  newErrors[`modelMetadata.${index}.outputLimit`] = "Output limit should not exceed context window";
9628
9648
  }
9629
9649
  });
9630
- if (!anthropicBaseUrl.trim() && !openaiBaseUrl.trim() && !openaiResponsesBaseUrl.trim()) {
9631
- newErrors.format = "At least one format URL (Anthropic or OpenAI) is required";
9632
- }
9633
9650
  setErrors(newErrors);
9634
9651
  return Object.keys(newErrors).length === 0;
9635
9652
  }
@@ -9736,14 +9753,27 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9736
9753
  e.preventDefault();
9737
9754
  if (!validate()) return;
9738
9755
  setIsSubmitting(true);
9756
+ const clearDisabledEndpoints = provider !== void 0;
9739
9757
  try {
9740
9758
  onSubmit({
9741
9759
  name: name.trim(),
9742
9760
  apiKey: apiKey.trim(),
9743
9761
  models: models.map((m) => m.trim()).filter((m) => m !== ""),
9744
- anthropicBaseUrl: anthropicBaseUrl.trim() || void 0,
9745
- openaiBaseUrl: openaiBaseUrl.trim() || void 0,
9746
- openaiResponsesBaseUrl: openaiResponsesBaseUrl.trim() || void 0,
9762
+ anthropicBaseUrl: endpointForCapability(
9763
+ supportsAnthropic,
9764
+ anthropicBaseUrl,
9765
+ clearDisabledEndpoints
9766
+ ),
9767
+ openaiBaseUrl: endpointForCapability(
9768
+ supportsOpenai && supportsOpenaiChat,
9769
+ openaiBaseUrl,
9770
+ clearDisabledEndpoints
9771
+ ),
9772
+ openaiResponsesBaseUrl: endpointForCapability(
9773
+ supportsOpenai && supportsOpenaiResponses,
9774
+ openaiResponsesBaseUrl,
9775
+ clearDisabledEndpoints
9776
+ ),
9747
9777
  apiDocsUrl: apiDocsUrl.trim() || void 0,
9748
9778
  modelMetadataUrl: modelMetadataUrl.trim() || void 0,
9749
9779
  modelMetadata: buildModelMetadata(),
@@ -9772,6 +9802,205 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9772
9802
  ),
9773
9803
  errors.name !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.name })
9774
9804
  ] }),
9805
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("fieldset", { className: "space-y-3", children: [
9806
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("legend", { className: "text-sm font-medium", children: [
9807
+ "Capabilities ",
9808
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
9809
+ ] }),
9810
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "divide-y divide-border overflow-hidden rounded-md border border-border", children: [
9811
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: supportsOpenai ? "bg-primary/[0.04] px-3 py-3" : "px-3 py-3", children: [
9812
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
9813
+ "label",
9814
+ {
9815
+ htmlFor: "provider-capability-openai",
9816
+ className: "flex cursor-pointer items-start gap-3",
9817
+ children: [
9818
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9819
+ "input",
9820
+ {
9821
+ id: "provider-capability-openai",
9822
+ type: "checkbox",
9823
+ checked: supportsOpenai,
9824
+ onChange: (event) => setSupportsOpenai(event.target.checked),
9825
+ className: "mt-0.5 size-4 shrink-0 accent-primary"
9826
+ }
9827
+ ),
9828
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0", children: [
9829
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-sm font-medium", children: "OpenAI" }),
9830
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-xs text-muted-foreground", children: "Chat Completions and Responses" })
9831
+ ] })
9832
+ ]
9833
+ }
9834
+ ),
9835
+ supportsOpenai && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-7 mt-3 flex flex-wrap gap-x-5 gap-y-2 border-t border-border/70 pt-3", children: [
9836
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
9837
+ "label",
9838
+ {
9839
+ htmlFor: "provider-capability-openai-chat",
9840
+ className: "flex cursor-pointer items-center gap-2 text-sm",
9841
+ children: [
9842
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9843
+ "input",
9844
+ {
9845
+ id: "provider-capability-openai-chat",
9846
+ type: "checkbox",
9847
+ checked: supportsOpenaiChat,
9848
+ onChange: (event) => setSupportsOpenaiChat(event.target.checked),
9849
+ className: "size-4 shrink-0 accent-primary"
9850
+ }
9851
+ ),
9852
+ "Chat Completions"
9853
+ ]
9854
+ }
9855
+ ),
9856
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
9857
+ "label",
9858
+ {
9859
+ htmlFor: "provider-capability-openai-responses",
9860
+ className: "flex cursor-pointer items-center gap-2 text-sm",
9861
+ children: [
9862
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9863
+ "input",
9864
+ {
9865
+ id: "provider-capability-openai-responses",
9866
+ type: "checkbox",
9867
+ checked: supportsOpenaiResponses,
9868
+ onChange: (event) => setSupportsOpenaiResponses(event.target.checked),
9869
+ className: "size-4 shrink-0 accent-primary"
9870
+ }
9871
+ ),
9872
+ "Responses"
9873
+ ]
9874
+ }
9875
+ )
9876
+ ] }),
9877
+ errors.openaiCapabilities !== void 0 && supportsOpenai && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ml-7 mt-2 text-xs text-destructive", children: errors.openaiCapabilities })
9878
+ ] }),
9879
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
9880
+ "label",
9881
+ {
9882
+ htmlFor: "provider-capability-anthropic",
9883
+ className: `flex cursor-pointer items-start gap-3 px-3 py-3 ${supportsAnthropic ? "bg-primary/[0.04]" : ""}`,
9884
+ children: [
9885
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9886
+ "input",
9887
+ {
9888
+ id: "provider-capability-anthropic",
9889
+ type: "checkbox",
9890
+ checked: supportsAnthropic,
9891
+ onChange: (event) => setSupportsAnthropic(event.target.checked),
9892
+ className: "mt-0.5 size-4 shrink-0 accent-primary"
9893
+ }
9894
+ ),
9895
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0", children: [
9896
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-sm font-medium", children: "Anthropic" }),
9897
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-xs text-muted-foreground", children: "Messages" })
9898
+ ] })
9899
+ ]
9900
+ }
9901
+ )
9902
+ ] }),
9903
+ errors.capabilities !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.capabilities })
9904
+ ] }),
9905
+ (supportsAnthropic || supportsOpenai && (supportsOpenaiChat || supportsOpenaiResponses)) && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "space-y-5 border-l border-border pl-4", "aria-label": "API endpoints", children: [
9906
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-medium", children: "Endpoint Configuration" }),
9907
+ supportsAnthropic && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
9908
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-anthropic-base-url", className: "text-sm font-medium", children: [
9909
+ "Anthropic Messages Base URL ",
9910
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
9911
+ ] }),
9912
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]", children: [
9913
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9914
+ "input",
9915
+ {
9916
+ id: "provider-anthropic-base-url",
9917
+ type: "text",
9918
+ value: anthropicBaseUrl,
9919
+ onChange: (event) => {
9920
+ setManualAnthropicUrlOverride(true);
9921
+ setAnthropicBaseUrl(event.target.value);
9922
+ },
9923
+ placeholder: "https://api.anthropic.com",
9924
+ className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
9925
+ }
9926
+ ),
9927
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground", children: ANTHROPIC_MESSAGES_ENDPOINT })
9928
+ ] }),
9929
+ errors.anthropicBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.anthropicBaseUrl }),
9930
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9931
+ EndpointUrlPreview,
9932
+ {
9933
+ baseUrl: anthropicBaseUrl,
9934
+ endpoint: ANTHROPIC_MESSAGES_ENDPOINT,
9935
+ providerName: name
9936
+ }
9937
+ )
9938
+ ] }),
9939
+ supportsOpenai && supportsOpenaiChat && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
9940
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-openai-base-url", className: "text-sm font-medium", children: [
9941
+ "OpenAI Chat Base URL ",
9942
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
9943
+ ] }),
9944
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]", children: [
9945
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9946
+ "input",
9947
+ {
9948
+ id: "provider-openai-base-url",
9949
+ type: "text",
9950
+ value: openaiBaseUrl,
9951
+ onChange: (event) => {
9952
+ setManualOpenaiUrlOverride(true);
9953
+ setOpenaiBaseUrl(event.target.value);
9954
+ },
9955
+ placeholder: "https://api.openai.com",
9956
+ className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
9957
+ }
9958
+ ),
9959
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground", children: OPENAI_CHAT_COMPLETIONS_ENDPOINT })
9960
+ ] }),
9961
+ errors.openaiBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.openaiBaseUrl }),
9962
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9963
+ EndpointUrlPreview,
9964
+ {
9965
+ baseUrl: openaiBaseUrl,
9966
+ endpoint: OPENAI_CHAT_COMPLETIONS_ENDPOINT,
9967
+ providerName: name
9968
+ }
9969
+ )
9970
+ ] }),
9971
+ supportsOpenai && supportsOpenaiResponses && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
9972
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-openai-responses-base-url", className: "text-sm font-medium", children: [
9973
+ "OpenAI Responses Base URL ",
9974
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
9975
+ ] }),
9976
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]", children: [
9977
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9978
+ "input",
9979
+ {
9980
+ id: "provider-openai-responses-base-url",
9981
+ type: "text",
9982
+ value: openaiResponsesBaseUrl,
9983
+ onChange: (event) => {
9984
+ setManualOpenaiResponsesUrlOverride(true);
9985
+ setOpenaiResponsesBaseUrl(event.target.value);
9986
+ },
9987
+ placeholder: "https://api.openai.com",
9988
+ className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
9989
+ }
9990
+ ),
9991
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground", children: OPENAI_RESPONSES_ENDPOINT })
9992
+ ] }),
9993
+ errors.openaiResponsesBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.openaiResponsesBaseUrl }),
9994
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9995
+ EndpointUrlPreview,
9996
+ {
9997
+ baseUrl: openaiResponsesBaseUrl,
9998
+ endpoint: OPENAI_RESPONSES_ENDPOINT,
9999
+ providerName: name
10000
+ }
10001
+ )
10002
+ ] })
10003
+ ] }),
9775
10004
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
9776
10005
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-source", className: "text-sm font-medium", children: "Token Source" }),
9777
10006
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -9813,21 +10042,17 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9813
10042
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
9814
10043
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-apikey", className: "text-sm font-medium", children: [
9815
10044
  "API Key ",
9816
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
10045
+ provider === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
9817
10046
  ] }),
9818
10047
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9819
10048
  /* @__PURE__ */ jsxRuntimeExports.jsx(
9820
10049
  "input",
9821
10050
  {
9822
10051
  id: "provider-apikey",
9823
- type: "text",
9824
- value: showApiKey || apiKey.length === 0 ? apiKey : maskApiKey(apiKey),
10052
+ type: showApiKey ? "text" : "password",
10053
+ value: apiKey,
9825
10054
  onChange: (e) => setApiKey(e.target.value),
9826
- onFocus: () => {
9827
- if (!showApiKey && apiKey.length > 0) setShowApiKey(true);
9828
- },
9829
- placeholder: "sk-ant-...",
9830
- readOnly: !showApiKey && apiKey.length > 0,
10055
+ placeholder: provider === void 0 ? "sk-ant-..." : "Leave blank to keep current key",
9831
10056
  className: "flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
9832
10057
  }
9833
10058
  ),
@@ -9852,6 +10077,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9852
10077
  }
9853
10078
  )
9854
10079
  ] }),
10080
+ provider !== void 0 && apiKey.trim() === "" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "A key is configured. Enter a new value only to rotate it." }),
9855
10081
  errors.apiKey !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.apiKey })
9856
10082
  ] }),
9857
10083
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
@@ -10025,127 +10251,6 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
10025
10251
  ),
10026
10252
  errors.models !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.models })
10027
10253
  ] }),
10028
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
10029
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border bg-muted/40 flex gap-1 rounded-[8px] p-1", children: [
10030
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
10031
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10032
- "button",
10033
- {
10034
- type: "button",
10035
- onClick: () => setActiveTab("anthropic"),
10036
- className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "anthropic" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
10037
- children: "Anthropic Format"
10038
- }
10039
- ) }),
10040
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Anthropic Messages API format" })
10041
- ] }) }),
10042
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
10043
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10044
- "button",
10045
- {
10046
- type: "button",
10047
- onClick: () => setActiveTab("openai"),
10048
- className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "openai" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
10049
- children: "OpenAI Format"
10050
- }
10051
- ) }),
10052
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "OpenAI Chat Completions API format" })
10053
- ] }) })
10054
- ] }),
10055
- errors.format !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.format })
10056
- ] }),
10057
- activeTab === "anthropic" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
10058
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-anthropic-base-url", className: "text-sm font-medium", children: "Anthropic Base URL" }),
10059
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]", children: [
10060
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10061
- "input",
10062
- {
10063
- id: "provider-anthropic-base-url",
10064
- type: "text",
10065
- value: anthropicBaseUrl,
10066
- onChange: (e) => {
10067
- setManualAnthropicUrlOverride(true);
10068
- setAnthropicBaseUrl(e.target.value);
10069
- },
10070
- placeholder: "https://api.anthropic.com",
10071
- className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
10072
- }
10073
- ),
10074
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground", children: ANTHROPIC_MESSAGES_ENDPOINT })
10075
- ] }),
10076
- errors.anthropicBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.anthropicBaseUrl }),
10077
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10078
- EndpointUrlPreview,
10079
- {
10080
- baseUrl: anthropicBaseUrl,
10081
- endpoint: ANTHROPIC_MESSAGES_ENDPOINT,
10082
- providerName: name
10083
- }
10084
- ),
10085
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Anthropic-compatible base URL. Leave empty if this provider does not support Anthropic format." })
10086
- ] }),
10087
- activeTab === "openai" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
10088
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
10089
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-openai-base-url", className: "text-sm font-medium", children: "OpenAI Chat Base URL" }),
10090
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]", children: [
10091
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10092
- "input",
10093
- {
10094
- id: "provider-openai-base-url",
10095
- type: "text",
10096
- value: openaiBaseUrl,
10097
- onChange: (e) => {
10098
- setManualOpenaiUrlOverride(true);
10099
- setOpenaiBaseUrl(e.target.value);
10100
- },
10101
- placeholder: "https://api.openai.com",
10102
- className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
10103
- }
10104
- ),
10105
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground", children: OPENAI_CHAT_COMPLETIONS_ENDPOINT })
10106
- ] }),
10107
- errors.openaiBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.openaiBaseUrl }),
10108
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10109
- EndpointUrlPreview,
10110
- {
10111
- baseUrl: openaiBaseUrl,
10112
- endpoint: OPENAI_CHAT_COMPLETIONS_ENDPOINT,
10113
- providerName: name
10114
- }
10115
- ),
10116
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "OpenAI Chat Completions-compatible base URL. Leave empty if this provider only supports the Responses API." })
10117
- ] }),
10118
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
10119
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-openai-responses-base-url", className: "text-sm font-medium", children: "OpenAI Responses Base URL" }),
10120
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]", children: [
10121
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10122
- "input",
10123
- {
10124
- id: "provider-openai-responses-base-url",
10125
- type: "text",
10126
- value: openaiResponsesBaseUrl,
10127
- onChange: (e) => {
10128
- setManualOpenaiResponsesUrlOverride(true);
10129
- setOpenaiResponsesBaseUrl(e.target.value);
10130
- },
10131
- placeholder: "https://api.openai.com",
10132
- className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
10133
- }
10134
- ),
10135
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground", children: OPENAI_RESPONSES_ENDPOINT })
10136
- ] }),
10137
- errors.openaiResponsesBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.openaiResponsesBaseUrl }),
10138
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10139
- EndpointUrlPreview,
10140
- {
10141
- baseUrl: openaiResponsesBaseUrl,
10142
- endpoint: OPENAI_RESPONSES_ENDPOINT,
10143
- providerName: name
10144
- }
10145
- ),
10146
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Optional Responses-compatible base URL. Set only when the provider supports the Responses API." })
10147
- ] })
10148
- ] }),
10149
10254
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
10150
10255
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-api-docs-url", className: "text-sm font-medium", children: "API Docs URL" }),
10151
10256
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -10191,14 +10296,21 @@ const PROVIDER_DELETE_TIMEOUT_MS = 1e4;
10191
10296
  const PROVIDER_EXPORT_TIMEOUT_MS = 2e4;
10192
10297
  const PROVIDER_IMPORT_TIMEOUT_MS = 2e4;
10193
10298
  const PROVIDER_METADATA_TIMEOUT_MS = 3e4;
10194
- function createProviderPayload(data) {
10299
+ function endpointPayloadValue(value, preserveEmptyEndpoints) {
10300
+ if ((value?.length ?? 0) > 0) return value;
10301
+ return preserveEmptyEndpoints && value !== void 0 ? "" : void 0;
10302
+ }
10303
+ function createProviderPayload(data, preserveEmptyEndpoints = false) {
10195
10304
  return {
10196
10305
  name: data.name,
10197
- apiKey: data.apiKey,
10306
+ ...data.apiKey.trim() === "" ? {} : { apiKey: data.apiKey },
10198
10307
  models: data.models,
10199
- anthropicBaseUrl: (data.anthropicBaseUrl?.length ?? 0) > 0 ? data.anthropicBaseUrl : void 0,
10200
- openaiBaseUrl: (data.openaiBaseUrl?.length ?? 0) > 0 ? data.openaiBaseUrl : void 0,
10201
- openaiResponsesBaseUrl: (data.openaiResponsesBaseUrl?.length ?? 0) > 0 ? data.openaiResponsesBaseUrl : void 0,
10308
+ anthropicBaseUrl: endpointPayloadValue(data.anthropicBaseUrl, preserveEmptyEndpoints),
10309
+ openaiBaseUrl: endpointPayloadValue(data.openaiBaseUrl, preserveEmptyEndpoints),
10310
+ openaiResponsesBaseUrl: endpointPayloadValue(
10311
+ data.openaiResponsesBaseUrl,
10312
+ preserveEmptyEndpoints
10313
+ ),
10202
10314
  apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : void 0,
10203
10315
  modelMetadataUrl: (data.modelMetadataUrl?.length ?? 0) > 0 ? data.modelMetadataUrl : void 0,
10204
10316
  modelMetadata: data.modelMetadata,
@@ -10481,7 +10593,7 @@ function ProvidersPanel({
10481
10593
  {
10482
10594
  method: "PUT",
10483
10595
  headers: { "Content-Type": "application/json" },
10484
- body: JSON.stringify(createProviderPayload(data))
10596
+ body: JSON.stringify(createProviderPayload(data, true))
10485
10597
  },
10486
10598
  () => "Failed to update provider"
10487
10599
  );
@@ -10491,7 +10603,7 @@ function ProvidersPanel({
10491
10603
  await refreshProviderMetadata(updated.id, data.modelMetadataUrl, false);
10492
10604
  }
10493
10605
  refreshProviders();
10494
- const criticalFieldsChanged = (data.apiKey ?? "") !== (editingProvider.apiKey ?? "") || JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) || (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") || (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "") || (data.openaiResponsesBaseUrl ?? "") !== (editingProvider.openaiResponsesBaseUrl ?? "");
10606
+ const criticalFieldsChanged = data.apiKey.trim() !== "" || JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) || (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") || (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "") || (data.openaiResponsesBaseUrl ?? "") !== (editingProvider.openaiResponsesBaseUrl ?? "");
10495
10607
  if (criticalFieldsChanged) {
10496
10608
  await runTest(updated.id);
10497
10609
  }
@@ -11138,9 +11250,9 @@ function McpSettingsTab() {
11138
11250
  value: `agent-inspector-mcp doctor --url ${endpoint}`
11139
11251
  },
11140
11252
  {
11141
- id: "readonly",
11142
- label: "Readonly mode",
11143
- value: "AGENT_INSPECTOR_MCP_READONLY=1 agent-inspector"
11253
+ id: "writes",
11254
+ label: "Enable write tools",
11255
+ value: "AGENT_INSPECTOR_MCP_WRITES=1 agent-inspector"
11144
11256
  },
11145
11257
  {
11146
11258
  id: "capabilities",
@@ -12134,11 +12246,11 @@ function AgentLabDialog({ currentSessionId, logCount }) {
12134
12246
  ] })
12135
12247
  ] });
12136
12248
  }
12137
- const INSPECTOR_PET_SIZE = 56;
12249
+ const INSPECTOR_PET_SIZE = 112;
12138
12250
  const INSPECTOR_PET_MARGIN = 14;
12139
- const INSPECTOR_PET_EDGE_PEEK = 18;
12140
- const INSPECTOR_PET_MIN_STEP = 72;
12141
- const INSPECTOR_PET_MAX_STEP = 156;
12251
+ const INSPECTOR_PET_EDGE_GAP = 20;
12252
+ const INSPECTOR_PET_MIN_STEP = 36;
12253
+ const INSPECTOR_PET_MAX_STEP = 92;
12142
12254
  const INSPECTOR_PET_SLEEP_AFTER_MS = 45e3;
12143
12255
  const INSPECTOR_PET_PANEL_WIDTH = 192;
12144
12256
  const INSPECTOR_PET_PANEL_HEIGHT = 68;
@@ -12155,33 +12267,27 @@ function clampUnit(value) {
12155
12267
  return Math.min(Math.max(value, 0), 1);
12156
12268
  }
12157
12269
  function maximumPetX(viewport) {
12158
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12159
- return Math.max(
12160
- -edgePeek,
12161
- viewport.width - scaledMetric(INSPECTOR_PET_SIZE, viewport) + edgePeek
12162
- );
12270
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12271
+ return Math.max(edgeGap, viewport.width - scaledMetric(INSPECTOR_PET_SIZE, viewport) - edgeGap);
12163
12272
  }
12164
12273
  function maximumPetY(viewport) {
12165
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12166
- return Math.max(
12167
- -edgePeek,
12168
- viewport.height - scaledMetric(INSPECTOR_PET_SIZE, viewport) + edgePeek
12169
- );
12274
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12275
+ return Math.max(edgeGap, viewport.height - scaledMetric(INSPECTOR_PET_SIZE, viewport) - edgeGap);
12170
12276
  }
12171
12277
  function clampInspectorPetPosition(position, viewport) {
12172
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12278
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12173
12279
  return {
12174
- x: Math.min(Math.max(position.x, -edgePeek), maximumPetX(viewport)),
12175
- y: Math.min(Math.max(position.y, -edgePeek), maximumPetY(viewport))
12280
+ x: Math.min(Math.max(position.x, edgeGap), maximumPetX(viewport)),
12281
+ y: Math.min(Math.max(position.y, edgeGap), maximumPetY(viewport))
12176
12282
  };
12177
12283
  }
12178
12284
  function nearestInspectorPetEdge(position, viewport) {
12179
12285
  const clamped = clampInspectorPetPosition(position, viewport);
12180
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12286
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12181
12287
  const distances = [
12182
- { edge: "left", distance: clamped.x + edgePeek },
12288
+ { edge: "left", distance: clamped.x - edgeGap },
12183
12289
  { edge: "right", distance: maximumPetX(viewport) - clamped.x },
12184
- { edge: "top", distance: clamped.y + INSPECTOR_PET_EDGE_PEEK },
12290
+ { edge: "top", distance: clamped.y - edgeGap },
12185
12291
  { edge: "bottom", distance: maximumPetY(viewport) - clamped.y }
12186
12292
  ];
12187
12293
  return distances.reduce((best, current) => current.distance < best.distance ? current : best).edge;
@@ -12189,33 +12295,26 @@ function nearestInspectorPetEdge(position, viewport) {
12189
12295
  function snapInspectorPetToEdge(position, viewport) {
12190
12296
  const clamped = clampInspectorPetPosition(position, viewport);
12191
12297
  const edge = nearestInspectorPetEdge(clamped, viewport);
12192
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12298
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12193
12299
  switch (edge) {
12194
12300
  case "left":
12195
- return { x: -edgePeek, y: clamped.y };
12301
+ return { x: edgeGap, y: clamped.y };
12196
12302
  case "right":
12197
12303
  return { x: maximumPetX(viewport), y: clamped.y };
12198
12304
  case "top":
12199
- return { x: clamped.x, y: -edgePeek };
12305
+ return { x: clamped.x, y: edgeGap };
12200
12306
  case "bottom":
12201
12307
  return { x: clamped.x, y: maximumPetY(viewport) };
12202
12308
  }
12203
12309
  }
12204
- function randomInspectorPetEdgePosition(viewport, edgeSample, ySample) {
12310
+ function randomInspectorPetEdgePosition(viewport, positionSample) {
12205
12311
  const maxX = maximumPetX(viewport);
12206
12312
  const maxY = maximumPetY(viewport);
12207
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12208
- const minimumSafeY = Math.min(scaledMetric(INSPECTOR_PET_VERTICAL_SAFE_MARGIN, viewport), maxY);
12209
- const randomY = minimumSafeY + clampUnit(ySample) * (maxY - minimumSafeY);
12210
- const edgeIndex = Math.min(1, Math.floor(clampUnit(edgeSample) * 2));
12211
- switch (edgeIndex) {
12212
- case 0:
12213
- return { x: -edgePeek, y: randomY };
12214
- case 1:
12215
- return { x: maxX, y: randomY };
12216
- default:
12217
- return { x: maxX, y: maxY };
12218
- }
12313
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12314
+ return {
12315
+ x: edgeGap + clampUnit(positionSample) * (maxX - edgeGap),
12316
+ y: maxY
12317
+ };
12219
12318
  }
12220
12319
  function nextInspectorPetWalkingPosition(position, viewport, stepSample, directionSample) {
12221
12320
  const clamped = snapInspectorPetToEdge(position, viewport);
@@ -12224,12 +12323,12 @@ function nextInspectorPetWalkingPosition(position, viewport, stepSample, directi
12224
12323
  const edge = nearestInspectorPetEdge(clamped, viewport);
12225
12324
  const maxX = maximumPetX(viewport);
12226
12325
  const maxY = maximumPetY(viewport);
12227
- const edgePeek = scaledMetric(INSPECTOR_PET_EDGE_PEEK, viewport);
12326
+ const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
12228
12327
  const minimumSafeY = Math.min(scaledMetric(INSPECTOR_PET_VERTICAL_SAFE_MARGIN, viewport), maxY);
12229
12328
  switch (edge) {
12230
12329
  case "left":
12231
12330
  return {
12232
- x: -edgePeek,
12331
+ x: edgeGap,
12233
12332
  y: Math.min(Math.max(clamped.y + step * direction, minimumSafeY), maxY)
12234
12333
  };
12235
12334
  case "right":
@@ -12239,12 +12338,12 @@ function nextInspectorPetWalkingPosition(position, viewport, stepSample, directi
12239
12338
  };
12240
12339
  case "top":
12241
12340
  return {
12242
- x: Math.min(Math.max(clamped.x + step * direction, -edgePeek), maxX),
12243
- y: -edgePeek
12341
+ x: Math.min(Math.max(clamped.x + step * direction, edgeGap), maxX),
12342
+ y: edgeGap
12244
12343
  };
12245
12344
  case "bottom":
12246
12345
  return {
12247
- x: Math.min(Math.max(clamped.x + step * direction, -edgePeek), maxX),
12346
+ x: Math.min(Math.max(clamped.x + step * direction, edgeGap), maxX),
12248
12347
  y: maxY
12249
12348
  };
12250
12349
  }
@@ -12333,12 +12432,344 @@ function resolveInspectorPetStatus(logs, nowMs) {
12333
12432
  capturedCount
12334
12433
  };
12335
12434
  }
12435
+ function createRod(start, end, radius, material) {
12436
+ const direction = end.clone().sub(start);
12437
+ const length = direction.length();
12438
+ const rod = new Mesh(new CylinderGeometry(radius, radius * 0.9, length, 12), material);
12439
+ rod.position.copy(start.clone().add(end).multiplyScalar(0.5));
12440
+ rod.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), direction.normalize());
12441
+ return rod;
12442
+ }
12443
+ function addClaw(root, side, shellMaterial, accentMaterial) {
12444
+ const clawScale = side < 0 ? 1.5 : 0.68;
12445
+ const clawGroup = new Group();
12446
+ const pincers = [];
12447
+ root.add(clawGroup);
12448
+ const shoulder = new Vector3(side * 1.2, 0.25, -0.05);
12449
+ const wrist = new Vector3(side * (1.65 + clawScale * 0.18), 0.5, 0);
12450
+ const arm = createRod(shoulder, wrist, 0.16 * clawScale, accentMaterial);
12451
+ clawGroup.add(arm);
12452
+ const palm = new Mesh(new SphereGeometry(0.55, 24, 16), shellMaterial);
12453
+ palm.position.set(side * (1.95 + clawScale * 0.16), 0.62, 0.05);
12454
+ palm.scale.set(1.05 * clawScale, 0.72 * clawScale, 0.55 * clawScale);
12455
+ clawGroup.add(palm);
12456
+ for (const pincerDirection of [-1, 1]) {
12457
+ const pincer = new Mesh(new SphereGeometry(0.36, 20, 14), accentMaterial);
12458
+ pincer.position.set(
12459
+ side * (2.22 + clawScale * 0.26),
12460
+ 0.62 + pincerDirection * 0.24 * clawScale,
12461
+ 0.08
12462
+ );
12463
+ pincer.scale.set(1.05 * clawScale, 0.34 * clawScale, 0.42 * clawScale);
12464
+ pincer.rotation.z = pincerDirection * side * 0.42;
12465
+ clawGroup.add(pincer);
12466
+ pincers.push({
12467
+ direction: pincerDirection,
12468
+ mesh: pincer,
12469
+ restRotationZ: pincer.rotation.z,
12470
+ restY: pincer.position.y
12471
+ });
12472
+ }
12473
+ return { group: clawGroup, pincers, scale: clawScale };
12474
+ }
12475
+ function pinchPulse(cycleTime, start, duration) {
12476
+ const progress = (cycleTime - start) / duration;
12477
+ if (progress <= 0 || progress >= 1) return 0;
12478
+ return Math.sin(progress * Math.PI);
12479
+ }
12480
+ function dominantClawGrip(elapsed) {
12481
+ const cycleTime = elapsed % 6.8;
12482
+ return Math.max(pinchPulse(cycleTime, 0.72, 0.82), pinchPulse(cycleTime, 1.72, 0.68));
12483
+ }
12484
+ function addEyes(root, shellMaterial, eyeMaterial) {
12485
+ const eyes = [];
12486
+ for (const side of [-1, 1]) {
12487
+ const stalkStart = new Vector3(side * 0.48, 0.68, 0.05);
12488
+ const stalkEnd = new Vector3(side * 0.1, 0.82, 0.07);
12489
+ const eyeGroup = new Group();
12490
+ eyeGroup.position.copy(stalkStart);
12491
+ eyeGroup.add(createRod(new Vector3(0, 0, 0), stalkEnd, 0.1, shellMaterial));
12492
+ const eye = new Mesh(new SphereGeometry(0.24, 20, 16), eyeMaterial);
12493
+ eye.position.copy(stalkEnd);
12494
+ eye.scale.set(1, 1, 0.85);
12495
+ eyeGroup.add(eye);
12496
+ const pupil = new Mesh(
12497
+ new SphereGeometry(0.105, 16, 12),
12498
+ new MeshPhysicalMaterial({ color: 1120295, roughness: 0.25 })
12499
+ );
12500
+ const pupilRest = new Vector3(stalkEnd.x, stalkEnd.y, stalkEnd.z + 0.2);
12501
+ pupil.position.copy(pupilRest);
12502
+ eyeGroup.add(pupil);
12503
+ root.add(eyeGroup);
12504
+ eyes.push({
12505
+ group: eyeGroup,
12506
+ phase: side < 0 ? 0 : Math.PI / 3,
12507
+ pupil,
12508
+ pupilRest
12509
+ });
12510
+ }
12511
+ return eyes;
12512
+ }
12513
+ function addLegs(root, legMaterial) {
12514
+ const legs = [];
12515
+ for (const side of [-1, 1]) {
12516
+ for (let index = 0; index < 4; index += 1) {
12517
+ const leg = new Group();
12518
+ const baseY = -0.3 - index * 0.22;
12519
+ leg.position.set(side * (0.8 + index * 0.12), baseY, -0.18);
12520
+ leg.add(
12521
+ createRod(
12522
+ new Vector3(0, 0, 0),
12523
+ new Vector3(side * (0.62 + index * 0.06), -0.34, 0),
12524
+ 0.095,
12525
+ legMaterial
12526
+ )
12527
+ );
12528
+ leg.add(
12529
+ createRod(
12530
+ new Vector3(side * (0.58 + index * 0.06), -0.31, 0),
12531
+ new Vector3(side * (1.02 + index * 0.08), -0.88 + index * 0.07, 0.04),
12532
+ 0.075,
12533
+ legMaterial
12534
+ )
12535
+ );
12536
+ root.add(leg);
12537
+ legs.push({ group: leg, phase: index * Math.PI + (side > 0 ? Math.PI / 2 : 0), side });
12538
+ }
12539
+ }
12540
+ return legs;
12541
+ }
12542
+ function createCrabScene(scene) {
12543
+ const shellMaterial = new MeshPhysicalMaterial({
12544
+ color: 16096779,
12545
+ roughness: 0.34,
12546
+ metalness: 0.04,
12547
+ clearcoat: 0.78,
12548
+ clearcoatRoughness: 0.18,
12549
+ emissive: new Color(0),
12550
+ emissiveIntensity: 0
12551
+ });
12552
+ const accentMaterial = new MeshPhysicalMaterial({
12553
+ color: 16486972,
12554
+ roughness: 0.42,
12555
+ metalness: 0.02,
12556
+ clearcoat: 0.52,
12557
+ clearcoatRoughness: 0.28
12558
+ });
12559
+ const legMaterial = new MeshPhysicalMaterial({
12560
+ color: 14251782,
12561
+ roughness: 0.48,
12562
+ clearcoat: 0.35,
12563
+ clearcoatRoughness: 0.35
12564
+ });
12565
+ const eyeMaterial = new MeshPhysicalMaterial({
12566
+ color: 16776171,
12567
+ roughness: 0.22,
12568
+ clearcoat: 0.8,
12569
+ clearcoatRoughness: 0.12
12570
+ });
12571
+ const root = new Group();
12572
+ root.rotation.x = -0.12;
12573
+ root.rotation.y = -0.08;
12574
+ root.scale.setScalar(1.08);
12575
+ scene.add(root);
12576
+ const shadow = new Mesh(
12577
+ new CircleGeometry(1.65, 40),
12578
+ new MeshBasicMaterial({ color: 1120295, transparent: true, opacity: 0.16, depthWrite: false })
12579
+ );
12580
+ shadow.position.set(0, -1.33, -0.65);
12581
+ shadow.scale.set(1.4, 0.34, 1);
12582
+ root.add(shadow);
12583
+ const body = new Mesh(new SphereGeometry(1, 36, 24), shellMaterial);
12584
+ body.position.set(0, 0.02, 0);
12585
+ body.scale.set(1.58, 0.92, 0.68);
12586
+ root.add(body);
12587
+ const lowerShell = new Mesh(new SphereGeometry(0.92, 28, 20), accentMaterial);
12588
+ lowerShell.position.set(0, -0.48, 0.22);
12589
+ lowerShell.scale.set(1.28, 0.42, 0.56);
12590
+ root.add(lowerShell);
12591
+ const dominantClaw = addClaw(root, -1, shellMaterial, accentMaterial);
12592
+ addClaw(root, 1, shellMaterial, accentMaterial);
12593
+ const eyes = addEyes(root, shellMaterial, eyeMaterial);
12594
+ const legs = addLegs(root, legMaterial);
12595
+ scene.add(new AmbientLight(16775149, 1.65));
12596
+ scene.add(new HemisphereLight(16775149, 1515571, 2.15));
12597
+ const keyLight = new DirectionalLight(16777215, 3.6);
12598
+ keyLight.position.set(3.5, 4.5, 6);
12599
+ scene.add(keyLight);
12600
+ const fillLight = new DirectionalLight(16498468, 1.35);
12601
+ fillLight.position.set(-4, 1.5, 4);
12602
+ scene.add(fillLight);
12603
+ const rimLight = new PointLight(16096779, 1.8, 12, 2);
12604
+ rimLight.position.set(0, 1.8, 4.5);
12605
+ scene.add(rimLight);
12606
+ return { bodyMaterial: shellMaterial, dominantClaw, eyes, legs, rimLight, root };
12607
+ }
12608
+ function moodColor(mood) {
12609
+ switch (mood) {
12610
+ case "active":
12611
+ return 2282478;
12612
+ case "attention":
12613
+ return 15680580;
12614
+ case "idle":
12615
+ return 16096779;
12616
+ case "sleeping":
12617
+ return 9741240;
12618
+ }
12619
+ }
12620
+ function isDisposableMesh(object2) {
12621
+ return object2 instanceof Mesh;
12622
+ }
12623
+ function disposeScene(scene) {
12624
+ const materials = /* @__PURE__ */ new Set();
12625
+ scene.traverse((object2) => {
12626
+ if (!isDisposableMesh(object2)) return;
12627
+ object2.geometry.dispose();
12628
+ const material = object2.material;
12629
+ if (Array.isArray(material)) {
12630
+ for (const item of material) materials.add(item);
12631
+ return;
12632
+ }
12633
+ materials.add(material);
12634
+ });
12635
+ for (const material of materials) material.dispose();
12636
+ }
12637
+ function InspectorPetCrab3D({
12638
+ mood,
12639
+ moving,
12640
+ reduceMotion
12641
+ }) {
12642
+ const canvasRef = reactExports.useRef(null);
12643
+ const moodRef = reactExports.useRef(mood);
12644
+ const movingRef = reactExports.useRef(moving);
12645
+ const reduceMotionRef = reactExports.useRef(reduceMotion);
12646
+ const [renderFailed, setRenderFailed] = reactExports.useState(false);
12647
+ reactExports.useEffect(() => {
12648
+ moodRef.current = mood;
12649
+ }, [mood]);
12650
+ reactExports.useEffect(() => {
12651
+ movingRef.current = moving;
12652
+ }, [moving]);
12653
+ reactExports.useEffect(() => {
12654
+ reduceMotionRef.current = reduceMotion;
12655
+ }, [reduceMotion]);
12656
+ reactExports.useEffect(() => {
12657
+ const canvas = canvasRef.current;
12658
+ if (canvas === null) return void 0;
12659
+ let renderer;
12660
+ try {
12661
+ renderer = new WebGLRenderer({
12662
+ alpha: true,
12663
+ antialias: true,
12664
+ canvas,
12665
+ powerPreference: "low-power",
12666
+ preserveDrawingBuffer: true
12667
+ });
12668
+ } catch {
12669
+ setRenderFailed(true);
12670
+ return void 0;
12671
+ }
12672
+ renderer.setClearColor(0, 0);
12673
+ renderer.outputColorSpace = SRGBColorSpace;
12674
+ renderer.toneMapping = ACESFilmicToneMapping;
12675
+ renderer.toneMappingExposure = 1.18;
12676
+ const scene = new Scene();
12677
+ const camera = new OrthographicCamera(-3.65, 3.65, 3.65, -3.65, 0.1, 20);
12678
+ camera.position.set(0, 0.05, 8);
12679
+ camera.lookAt(0, 0, 0);
12680
+ const parts = createCrabScene(scene);
12681
+ const startedAt = window.performance.now();
12682
+ let lastFrameAt = 0;
12683
+ const renderFrame = (timestamp) => {
12684
+ if (timestamp - lastFrameAt < 32) return;
12685
+ lastFrameAt = timestamp;
12686
+ const elapsed = (timestamp - startedAt) / 1e3;
12687
+ const motionAllowed = !reduceMotionRef.current;
12688
+ const gait = movingRef.current ? 1 : moodRef.current === "active" ? 0.42 : 0;
12689
+ const idleMotion = motionAllowed ? Math.sin(elapsed * 0.85) : 0;
12690
+ const stepMotion = motionAllowed ? Math.sin(elapsed * 3.4) : 0;
12691
+ const clawGrip = motionAllowed ? dominantClawGrip(elapsed) : 0;
12692
+ parts.root.position.y = idleMotion * 0.035 + Math.abs(stepMotion) * 0.055 * gait;
12693
+ parts.root.rotation.y = -0.08 + idleMotion * 0.055;
12694
+ parts.root.rotation.z = stepMotion * 0.028 * gait;
12695
+ parts.dominantClaw.group.rotation.z = -clawGrip * 0.035;
12696
+ for (const pincer of parts.dominantClaw.pincers) {
12697
+ pincer.mesh.position.y = pincer.restY - pincer.direction * parts.dominantClaw.scale * 0.16 * clawGrip;
12698
+ pincer.mesh.rotation.z = pincer.restRotationZ * (1 - clawGrip * 0.72);
12699
+ }
12700
+ for (const leg of parts.legs) {
12701
+ leg.group.rotation.z = motionAllowed && gait > 0 ? Math.sin(elapsed * 4.25 + leg.phase) * 0.2 * leg.side * gait : 0;
12702
+ leg.group.rotation.x = motionAllowed && gait > 0 ? stepMotion * 0.045 * gait : 0;
12703
+ }
12704
+ for (const eye of parts.eyes) {
12705
+ const gaze = motionAllowed ? Math.sin(elapsed * 0.72 + eye.phase) : 0;
12706
+ const lift = motionAllowed ? Math.cos(elapsed * 0.9 + eye.phase) : 0;
12707
+ eye.group.rotation.z = gaze * 0.075;
12708
+ eye.group.rotation.x = lift * 0.025;
12709
+ eye.pupil.position.set(
12710
+ eye.pupilRest.x + gaze * 0.055,
12711
+ eye.pupilRest.y + lift * 0.025,
12712
+ eye.pupilRest.z
12713
+ );
12714
+ }
12715
+ const color = moodColor(moodRef.current);
12716
+ parts.rimLight.color.setHex(color);
12717
+ parts.rimLight.intensity = moodRef.current === "attention" ? 3.2 : 1.8;
12718
+ parts.bodyMaterial.emissive.setHex(color);
12719
+ parts.bodyMaterial.emissiveIntensity = moodRef.current === "active" ? 0.12 : 0.035;
12720
+ renderer.render(scene, camera);
12721
+ };
12722
+ const resize = () => {
12723
+ const width = Math.max(canvas.clientWidth, 1);
12724
+ const height = Math.max(canvas.clientHeight, 1);
12725
+ renderer.setDrawingBufferSize(width, height, Math.min(window.devicePixelRatio, 2));
12726
+ renderer.render(scene, camera);
12727
+ };
12728
+ const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(resize);
12729
+ if (resizeObserver === null) {
12730
+ window.addEventListener("resize", resize);
12731
+ } else {
12732
+ resizeObserver.observe(canvas);
12733
+ }
12734
+ resize();
12735
+ const handleContextLost = (event) => {
12736
+ event.preventDefault();
12737
+ setRenderFailed(true);
12738
+ };
12739
+ canvas.addEventListener("webglcontextlost", handleContextLost);
12740
+ renderer.setAnimationLoop(renderFrame);
12741
+ return () => {
12742
+ renderer.setAnimationLoop(null);
12743
+ if (resizeObserver === null) {
12744
+ window.removeEventListener("resize", resize);
12745
+ } else {
12746
+ resizeObserver.disconnect();
12747
+ }
12748
+ canvas.removeEventListener("webglcontextlost", handleContextLost);
12749
+ disposeScene(scene);
12750
+ renderer.dispose();
12751
+ };
12752
+ }, []);
12753
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "relative flex size-24 items-center justify-center", "aria-hidden": "true", children: [
12754
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
12755
+ "canvas",
12756
+ {
12757
+ ref: canvasRef,
12758
+ className: renderFailed ? "hidden" : "block size-full bg-transparent",
12759
+ "data-inspector-pet-canvas": "true"
12760
+ }
12761
+ ),
12762
+ renderFailed && /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-22 text-amber-500 drop-shadow-[0_0_18px_rgba(245,158,11,0.28)]" })
12763
+ ] });
12764
+ }
12336
12765
  const INSPECTOR_PET_ROAMING_STORAGE_KEY = "agent-inspector.pet.roaming";
12337
- const INSPECTOR_PET_MIN_DRIFT_MS = 5200;
12338
- const INSPECTOR_PET_DRIFT_WINDOW_MS = 2400;
12766
+ const INSPECTOR_PET_MIN_PAUSE_MS = 11e3;
12767
+ const INSPECTOR_PET_PAUSE_WINDOW_MS = 9e3;
12768
+ const INSPECTOR_PET_SCUTTLE_MS = 7200;
12339
12769
  const INSPECTOR_PET_PANEL_DISMISS_MS = 6e3;
12340
12770
  const INSPECTOR_PET_REACTION_MS = 760;
12341
12771
  const INSPECTOR_PET_DRAG_THRESHOLD = 4;
12772
+ const INSPECTOR_PET_TOP_LAYER_Z_INDEX = 2147483647;
12342
12773
  function readViewport() {
12343
12774
  if (typeof window === "undefined") return { width: 1280, height: 720, scale: 1 };
12344
12775
  const rawScale = window.getComputedStyle(document.documentElement).getPropertyValue("--inspector-ui-scale");
@@ -12383,13 +12814,14 @@ function moodMotionClass(mood) {
12383
12814
  }
12384
12815
  }
12385
12816
  function InspectorPet({ logs }) {
12817
+ const [portalHost, setPortalHost] = reactExports.useState(null);
12386
12818
  const [viewport, setViewport] = reactExports.useState(readViewport);
12387
12819
  const [position, setPosition] = reactExports.useState({
12388
12820
  x: INSPECTOR_PET_MARGIN,
12389
12821
  y: INSPECTOR_PET_MARGIN
12390
12822
  });
12391
12823
  const [clockMs, setClockMs] = reactExports.useState(() => Date.now());
12392
- const [motionTick, setMotionTick] = reactExports.useState(0);
12824
+ const [scuttling, setScuttling] = reactExports.useState(false);
12393
12825
  const [panelTick, setPanelTick] = reactExports.useState(0);
12394
12826
  const [panelVisible, setPanelVisible] = reactExports.useState(false);
12395
12827
  const [reactionTick, setReactionTick] = reactExports.useState(0);
@@ -12404,10 +12836,11 @@ function InspectorPet({ logs }) {
12404
12836
  () => inspectorPetPanelPosition(position, viewport),
12405
12837
  [position, viewport]
12406
12838
  );
12839
+ reactExports.useEffect(() => setPortalHost(document.body), []);
12407
12840
  reactExports.useEffect(() => {
12408
12841
  const nextViewport = readViewport();
12409
12842
  setViewport(nextViewport);
12410
- setPosition(randomInspectorPetEdgePosition(nextViewport, Math.random(), Math.random()));
12843
+ setPosition(randomInspectorPetEdgePosition(nextViewport, Math.random()));
12411
12844
  }, []);
12412
12845
  reactExports.useEffect(() => {
12413
12846
  const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
@@ -12428,25 +12861,31 @@ function InspectorPet({ logs }) {
12428
12861
  }, [roaming]);
12429
12862
  reactExports.useEffect(() => {
12430
12863
  if (!roaming || dragging || reduceMotion) return void 0;
12431
- let timeoutId = null;
12432
- const scheduleDrift = () => {
12433
- const delay = INSPECTOR_PET_MIN_DRIFT_MS + Math.random() * INSPECTOR_PET_DRIFT_WINDOW_MS;
12434
- timeoutId = window.setTimeout(() => {
12435
- setMotionTick((current) => current + 1);
12864
+ let pauseTimeoutId = null;
12865
+ let scuttleTimeoutId = null;
12866
+ const scheduleScuttle = () => {
12867
+ const delay = INSPECTOR_PET_MIN_PAUSE_MS + Math.random() * INSPECTOR_PET_PAUSE_WINDOW_MS;
12868
+ pauseTimeoutId = window.setTimeout(() => {
12869
+ setScuttling(true);
12436
12870
  setPosition(
12437
12871
  (current) => nextInspectorPetWalkingPosition(current, viewport, Math.random(), Math.random())
12438
12872
  );
12439
- scheduleDrift();
12873
+ scuttleTimeoutId = window.setTimeout(() => {
12874
+ setScuttling(false);
12875
+ scheduleScuttle();
12876
+ }, INSPECTOR_PET_SCUTTLE_MS);
12440
12877
  }, delay);
12441
12878
  };
12442
- scheduleDrift();
12879
+ scheduleScuttle();
12443
12880
  return () => {
12444
- if (timeoutId !== null) window.clearTimeout(timeoutId);
12881
+ if (pauseTimeoutId !== null) window.clearTimeout(pauseTimeoutId);
12882
+ if (scuttleTimeoutId !== null) window.clearTimeout(scuttleTimeoutId);
12445
12883
  };
12446
12884
  }, [dragging, reduceMotion, roaming, viewport]);
12447
12885
  reactExports.useEffect(() => {
12448
12886
  const handleResize = () => {
12449
12887
  const nextViewport = readViewport();
12888
+ setScuttling(false);
12450
12889
  setViewport(nextViewport);
12451
12890
  setPosition((current) => snapInspectorPetToEdge(current, nextViewport));
12452
12891
  };
@@ -12489,6 +12928,7 @@ function InspectorPet({ logs }) {
12489
12928
  moved: false
12490
12929
  };
12491
12930
  event.currentTarget.setPointerCapture(event.pointerId);
12931
+ setScuttling(false);
12492
12932
  setPanelVisible(false);
12493
12933
  setDragging(true);
12494
12934
  }, []);
@@ -12533,135 +12973,136 @@ function InspectorPet({ logs }) {
12533
12973
  dragOccurredRef.current = false;
12534
12974
  setDragging(false);
12535
12975
  }, []);
12976
+ const handleToggleRoaming = reactExports.useCallback(() => {
12977
+ setScuttling(false);
12978
+ setRoaming((current) => !current);
12979
+ }, []);
12536
12980
  const petStyle = reactExports.useMemo(
12537
12981
  () => ({
12538
- transform: `translate3d(${String(position.x)}px, ${String(position.y)}px, 0) rotate(${motionTick % 2 === 0 ? "-4deg" : "5deg"}) scale(${dragging ? "1.08" : "1"})`,
12539
- transition: dragging ? "none" : "transform 4800ms cubic-bezier(0.33, 1, 0.68, 1)",
12982
+ transform: `translate3d(${String(position.x)}px, ${String(position.y)}px, 0) scale(${dragging ? "1.04" : "1"})`,
12983
+ transition: dragging ? "none" : scuttling ? `transform ${String(INSPECTOR_PET_SCUTTLE_MS)}ms cubic-bezier(0.22, 0.72, 0.32, 1)` : "transform 900ms cubic-bezier(0.22, 0.72, 0.32, 1)",
12540
12984
  touchAction: "none"
12541
12985
  }),
12542
- [dragging, motionTick, position.x, position.y]
12986
+ [dragging, position.x, position.y, scuttling]
12543
12987
  );
12544
12988
  const panelStyle = reactExports.useMemo(
12545
12989
  () => ({ left: panelPosition.x, top: panelPosition.y }),
12546
12990
  [panelPosition.x, panelPosition.y]
12547
12991
  );
12548
12992
  const roamingLabel = roaming ? "Pause pet roaming" : "Resume pet roaming";
12549
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
12550
- /* @__PURE__ */ jsxRuntimeExports.jsx(
12551
- "button",
12993
+ if (portalHost === null) return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {});
12994
+ return reactDomExports.createPortal(
12995
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
12996
+ "div",
12552
12997
  {
12553
- type: "button",
12554
- className: cn(
12555
- "fixed left-0 top-0 z-30 flex size-14 items-center justify-center bg-transparent text-foreground outline-none transition-[filter,opacity] duration-200 hover:opacity-95 hover:drop-shadow-[0_0_24px_rgba(245,158,11,0.36)] focus-visible:drop-shadow-[0_0_12px_rgba(251,191,36,0.72)]",
12556
- dragging ? "cursor-grabbing" : "cursor-grab"
12557
- ),
12558
- style: petStyle,
12559
- "data-pet-mood": status.mood,
12560
- onClick: handlePet,
12561
- onPointerDown: handlePointerDown,
12562
- onPointerMove: handlePointerMove,
12563
- onPointerUp: handlePointerUp,
12564
- onPointerCancel: handlePointerCancel,
12565
- "aria-label": `Inspector pet: ${status.label}`,
12566
- title: status.label,
12567
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
12568
- "span",
12569
- {
12570
- className: cn(
12571
- "relative flex size-12 items-center justify-center",
12572
- reactionVisible && "inspector-pet-patted"
12573
- ),
12574
- "aria-hidden": "true",
12575
- children: [
12576
- /* @__PURE__ */ jsxRuntimeExports.jsx(
12998
+ className: "pointer-events-none fixed inset-0 isolate",
12999
+ style: { zIndex: INSPECTOR_PET_TOP_LAYER_Z_INDEX },
13000
+ "data-inspector-pet-layer": "true",
13001
+ children: [
13002
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13003
+ "button",
13004
+ {
13005
+ type: "button",
13006
+ className: cn(
13007
+ "pointer-events-auto fixed left-0 top-0 z-10 flex size-28 items-center justify-center bg-transparent text-foreground outline-none transition-[filter,opacity] duration-200 hover:opacity-95 hover:drop-shadow-[0_0_28px_rgba(245,158,11,0.32)] focus-visible:drop-shadow-[0_0_14px_rgba(251,191,36,0.72)]",
13008
+ dragging ? "cursor-grabbing" : "cursor-grab"
13009
+ ),
13010
+ style: petStyle,
13011
+ "data-pet-mood": status.mood,
13012
+ "data-pet-moving": scuttling ? "true" : "false",
13013
+ onClick: handlePet,
13014
+ onPointerDown: handlePointerDown,
13015
+ onPointerMove: handlePointerMove,
13016
+ onPointerUp: handlePointerUp,
13017
+ onPointerCancel: handlePointerCancel,
13018
+ "aria-label": `Inspector pet: ${status.label}`,
13019
+ title: status.label,
13020
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
12577
13021
  "span",
12578
13022
  {
12579
13023
  className: cn(
12580
- "relative flex size-12 items-center justify-center",
12581
- moodMotionClass(status.mood)
13024
+ "relative z-10 flex size-24 items-center justify-center",
13025
+ reactionVisible && "inspector-pet-patted"
12582
13026
  ),
12583
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
12584
- "span",
12585
- {
12586
- className: cn(
12587
- "relative flex size-12 items-center justify-center",
12588
- !dragging && "floating-crab-body"
12589
- ),
12590
- children: [
12591
- /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "relative z-10 size-11 text-amber-500 drop-shadow-[0_0_15px_rgba(245,158,11,0.30)]" }),
12592
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "pointer-events-none absolute inset-x-0 bottom-0 z-0 flex items-end justify-center gap-1 text-amber-400/80", children: [0, 1, 2, 3, 4, 5].map((leg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
12593
- "span",
13027
+ "aria-hidden": "true",
13028
+ children: [
13029
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13030
+ "span",
13031
+ {
13032
+ className: cn(
13033
+ "relative flex size-24 items-center justify-center",
13034
+ moodMotionClass(status.mood)
13035
+ ),
13036
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "relative flex size-24 items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
13037
+ InspectorPetCrab3D,
12594
13038
  {
12595
- className: cn(
12596
- "floating-crab-leg h-2 w-0.5 rounded-full bg-current shadow-[0_0_7px_rgba(245,158,11,0.24)]",
12597
- leg < 3 ? "floating-crab-leg-left" : "floating-crab-leg-right",
12598
- dragging && "animate-none"
12599
- ),
12600
- style: { animationDelay: `${String(leg % 2 === 0 ? 0 : 180)}ms` }
12601
- },
12602
- leg
12603
- )) })
12604
- ]
12605
- }
12606
- )
12607
- }
12608
- ),
12609
- /* @__PURE__ */ jsxRuntimeExports.jsx(
12610
- "span",
12611
- {
12612
- className: cn(
12613
- "absolute right-0 top-1 size-2 rounded-full border border-background/90",
12614
- moodDotClass(status.mood),
12615
- status.mood === "active" && !reduceMotion && "animate-pulse"
12616
- )
13039
+ mood: status.mood,
13040
+ moving: scuttling && !dragging,
13041
+ reduceMotion
13042
+ }
13043
+ ) })
13044
+ }
13045
+ ),
13046
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13047
+ "span",
13048
+ {
13049
+ className: cn(
13050
+ "absolute right-0 top-2 size-3 rounded-full border border-background/90",
13051
+ moodDotClass(status.mood),
13052
+ status.mood === "active" && !reduceMotion && "animate-pulse"
13053
+ )
13054
+ }
13055
+ ),
13056
+ reactionVisible && /* @__PURE__ */ jsxRuntimeExports.jsx(
13057
+ Heart,
13058
+ {
13059
+ className: "inspector-pet-heart absolute -right-2 -top-3 size-5 fill-amber-400 text-amber-400"
13060
+ },
13061
+ reactionTick
13062
+ )
13063
+ ]
12617
13064
  }
12618
- ),
12619
- reactionVisible && /* @__PURE__ */ jsxRuntimeExports.jsx(
12620
- Heart,
12621
- {
12622
- className: "inspector-pet-heart absolute -right-1 -top-2 size-3.5 fill-amber-400 text-amber-400"
12623
- },
12624
- reactionTick
12625
13065
  )
12626
- ]
12627
- }
12628
- )
12629
- }
12630
- ),
12631
- panelVisible && /* @__PURE__ */ jsxRuntimeExports.jsxs(
12632
- "div",
12633
- {
12634
- className: "fixed z-50 w-48 rounded-md border border-border/80 bg-popover/95 px-3 py-2 text-popover-foreground shadow-[0_14px_34px_rgba(0,0,0,0.28)] backdrop-blur",
12635
- style: panelStyle,
12636
- role: "status",
12637
- "aria-live": "polite",
12638
- children: [
12639
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
12640
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("size-2 shrink-0 rounded-full", moodDotClass(status.mood)) }),
12641
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", children: status.label }),
12642
- /* @__PURE__ */ jsxRuntimeExports.jsx(
12643
- "button",
12644
- {
12645
- type: "button",
12646
- className: "inline-flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
12647
- onClick: () => setRoaming((current) => !current),
12648
- "aria-label": roamingLabel,
12649
- title: roamingLabel,
12650
- children: roaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "size-3.5" })
12651
- }
12652
- )
12653
- ] }),
12654
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex items-center justify-between gap-3 font-mono text-[10px] text-muted-foreground", children: [
12655
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
12656
- String(status.capturedCount),
12657
- " captured"
12658
- ] }),
12659
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: status.pendingCount > 0 ? `${String(status.pendingCount)} active` : roaming ? "Roaming" : "Resting" })
12660
- ] })
13066
+ }
13067
+ ),
13068
+ panelVisible && /* @__PURE__ */ jsxRuntimeExports.jsxs(
13069
+ "div",
13070
+ {
13071
+ className: "pointer-events-auto fixed z-20 w-48 rounded-md border border-border/80 bg-popover/95 px-3 py-2 text-popover-foreground shadow-[0_14px_34px_rgba(0,0,0,0.28)] backdrop-blur",
13072
+ style: panelStyle,
13073
+ role: "status",
13074
+ "aria-live": "polite",
13075
+ children: [
13076
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
13077
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("size-2 shrink-0 rounded-full", moodDotClass(status.mood)) }),
13078
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", children: status.label }),
13079
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13080
+ "button",
13081
+ {
13082
+ type: "button",
13083
+ className: "inline-flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
13084
+ onClick: handleToggleRoaming,
13085
+ "aria-label": roamingLabel,
13086
+ title: roamingLabel,
13087
+ children: roaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "size-3.5" })
13088
+ }
13089
+ )
13090
+ ] }),
13091
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex items-center justify-between gap-3 font-mono text-[10px] text-muted-foreground", children: [
13092
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13093
+ String(status.capturedCount),
13094
+ " captured"
13095
+ ] }),
13096
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: status.pendingCount > 0 ? `${String(status.pendingCount)} active` : scuttling ? "Scuttling" : roaming ? "Resting" : "Paused" })
13097
+ ] })
13098
+ ]
13099
+ }
13100
+ )
12661
13101
  ]
12662
13102
  }
12663
- )
12664
- ] });
13103
+ ),
13104
+ portalHost
13105
+ );
12665
13106
  }
12666
13107
  const DEFAULT_QUESTION = "Analyze the current Inspector session. What looks abnormal, slow, or likely to hang?";
12667
13108
  function providerSupportsPiAgent(provider) {