@tonyclaw/agent-inspector 3.0.26 → 3.0.28

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 (86) hide show
  1. package/.output/cli.js +80 -11
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-SZRUDQc9.js → CompareDrawer-TghZr5t1.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-BJvwOrSi.js → ProxyViewerContainer-fShBAZjj.js} +201 -201
  5. package/.output/public/assets/{ReplayDialog-DgfmqiM3.js → ReplayDialog-CZMApaF4.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CS6c7Qfn.js → RequestAnatomy-DNWccR7O.js} +1 -1
  7. package/.output/public/assets/{ResponseView-C0Ri_5gm.js → ResponseView-d25MdFhY.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-7dYcHWyZ.js → StreamingChunkSequence-DIFZEnWe.js} +1 -1
  9. package/.output/public/assets/{_sessionId-D-V84cgw.js → _sessionId-DRfvuE2k.js} +1 -1
  10. package/.output/public/assets/{index-DgFWCxOs.js → index-B3aAuqWq.js} +1 -1
  11. package/.output/public/assets/index-BsxAGqMA.css +1 -0
  12. package/.output/public/assets/{index-DrVJsks2.js → index-D4HkS7Ct.js} +1 -1
  13. package/.output/public/assets/{index-CSIL7-MK.js → index-DzARL2Fi.js} +3 -3
  14. package/.output/public/assets/{json-viewer-C9Ji3gYN.js → json-viewer-wuJSKaed.js} +1 -1
  15. package/.output/server/_libs/lucide-react.mjs +39 -39
  16. package/.output/server/{_sessionId-BQ6YQOO1.mjs → _sessionId-BJTNiP_O.mjs} +4 -2
  17. package/.output/server/_ssr/{CompareDrawer-CqNzIeMR.mjs → CompareDrawer-BMRH3N97.mjs} +5 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-CMC7Vsbw.mjs → ProxyViewerContainer-AFwcHAIR.mjs} +330 -223
  19. package/.output/server/_ssr/{ReplayDialog-D9p_LaTr.mjs → ReplayDialog-CbWvRHiN.mjs} +6 -4
  20. package/.output/server/_ssr/{RequestAnatomy-B4ge3qNZ.mjs → RequestAnatomy-C4kcPyqN.mjs} +4 -2
  21. package/.output/server/_ssr/{ResponseView-BCqMKApI.mjs → ResponseView-D8VYt-gZ.mjs} +5 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-BA4zTLE4.mjs → StreamingChunkSequence-BUUo6vU0.mjs} +4 -2
  23. package/.output/server/_ssr/{index-CzxhDjlq.mjs → index-DTwMEEXO.mjs} +4 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-B3qV6iEJ.mjs → json-viewer-BqF1pSQD.mjs} +5 -3
  26. package/.output/server/_ssr/{router-CZe_R31M.mjs → router-C8BOns7y.mjs} +839 -368
  27. package/.output/server/{_tanstack-start-manifest_v-B50VTLrH.mjs → _tanstack-start-manifest_v-BBf2nRhb.mjs} +1 -1
  28. package/.output/server/index.mjs +70 -70
  29. package/README.md +8 -0
  30. package/package.json +4 -5
  31. package/src/cli.ts +43 -9
  32. package/src/components/ProxyViewer.tsx +57 -1
  33. package/src/components/clients/ClientLogo.tsx +43 -4
  34. package/src/components/inspector-pet/InspectorPet.tsx +0 -7
  35. package/src/components/providers/ProviderCard.tsx +3 -36
  36. package/src/components/providers/ProviderForm.tsx +13 -12
  37. package/src/components/providers/ProvidersPanel.tsx +2 -2
  38. package/src/components/providers/SettingsDialog.tsx +3 -3
  39. package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
  40. package/src/components/proxy-viewer/ConversationGroupList.tsx +233 -152
  41. package/src/components/proxy-viewer/ConversationHeader.tsx +29 -13
  42. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
  43. package/src/contracts/anthropic.ts +0 -5
  44. package/src/contracts/index.ts +0 -4
  45. package/src/contracts/log.ts +0 -1
  46. package/src/contracts/openai.ts +0 -3
  47. package/src/knowledge/openclawClient.ts +22 -9
  48. package/src/knowledge/openclawGatewayClient.ts +20 -13
  49. package/src/lib/alertContract.ts +0 -3
  50. package/src/lib/ecosystemContract.ts +0 -3
  51. package/src/lib/export-logs.ts +1 -4
  52. package/src/lib/jsonFile.ts +46 -0
  53. package/src/lib/safeFetch.ts +100 -0
  54. package/src/lib/ssrfGuard.ts +133 -0
  55. package/src/lib/stopReason.ts +1 -4
  56. package/src/lib/unknown.ts +27 -0
  57. package/src/lib/useProviders.ts +1 -1
  58. package/src/mcp/mode.ts +3 -9
  59. package/src/mcp/server.ts +5 -14
  60. package/src/mcp/toolHandlers.ts +45 -54
  61. package/src/proxy/constants.ts +17 -0
  62. package/src/proxy/evidenceAnalysis.ts +116 -117
  63. package/src/proxy/formats/anthropic/handler.ts +5 -1
  64. package/src/proxy/formats/anthropic/schemas.ts +1 -7
  65. package/src/proxy/formats/index.ts +1 -1
  66. package/src/proxy/formats/openai/schemas.ts +0 -3
  67. package/src/proxy/formats/registry.ts +0 -3
  68. package/src/proxy/groupEvidenceExporter.ts +4 -8
  69. package/src/proxy/groupStore.ts +3 -10
  70. package/src/proxy/handler.ts +91 -12
  71. package/src/proxy/identityProxy.ts +74 -1
  72. package/src/proxy/logImporter.ts +82 -12
  73. package/src/proxy/logIndex.ts +1 -4
  74. package/src/proxy/providerSecretStore.ts +177 -0
  75. package/src/proxy/providers.ts +84 -11
  76. package/src/proxy/runFailures.ts +4 -10
  77. package/src/proxy/runStore.ts +4 -11
  78. package/src/proxy/schemas.ts +1 -8
  79. package/src/proxy/sqliteLogIndex.ts +0 -16
  80. package/src/proxy/store.ts +1 -5
  81. package/src/proxy/toolSchemaWarnings.ts +1 -9
  82. package/src/routes/api/logs.$id.replay.ts +17 -5
  83. package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
  84. package/src/routes/api/providers.$providerId.ts +18 -5
  85. package/src/routes/api/providers.ts +18 -7
  86. package/.output/public/assets/index-CmeF5XXS.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, M as PATH_V1_MESSAGES, N as PATH_V1_CHAT_COMPLETIONS, Q as PATH_V1_RESPONSES, H as maskApiKey, 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-CZe_R31M.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-C8BOns7y.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";
@@ -9,7 +9,7 @@ import { u as useWindowVirtualizer, a as useVirtualizer } from "../_libs/tanstac
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { r as reactDomExports } from "../_libs/react-dom.mjs";
11
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 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";
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 CodeXml, a0 as Cpu, a1 as FolderOpen, a2 as Bell, a3 as Clock3, a4 as ShieldAlert, a5 as EyeOff, a6 as ExternalLink, a7 as Clock, a8 as RotateCw, a9 as CircleCheckBig, aa as Pencil, ab as MessageSquare, ac as Zap, ad as Wrench, ae as Brain, af as Minus, ag as CircleX, ah as ShieldCheck, ai as Save, aj as FileSearch, ak as CloudUpload, al as CircleQuestionMark, am as Server, an as Lock, ao as Wifi, ap as WifiOff, aq as OctagonAlert, ar as Radio, as as Globe, at as FileTerminal, au as ChevronsUp, av as ChevronsDown, aw as FileDiff, ax as RotateCcw, ay as GitCompareArrows, az as ArrowUp, aA as ArrowDown, aB as Ellipsis, aC as Blocks, aD as MousePointerClick, aE as FileText, aF as Rows3, aG as Columns2 } from "../_libs/lucide-react.mjs";
13
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";
14
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";
15
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";
@@ -391,9 +391,6 @@ async function fetchStreamingChunks(logId, signal) {
391
391
  return null;
392
392
  }
393
393
  }
394
- function isRecord(value) {
395
- return typeof value === "object" && value !== null && !Array.isArray(value);
396
- }
397
394
  function redactValue(value) {
398
395
  if (Array.isArray(value)) return value.map((item) => redactValue(item));
399
396
  if (!isRecord(value)) return value;
@@ -690,7 +687,7 @@ async function fetcher(url) {
690
687
  }
691
688
  function useProviders() {
692
689
  const response = useSWR(
693
- "/api/providers?includeSecrets=1",
690
+ "/api/providers",
694
691
  fetcher,
695
692
  {
696
693
  revalidateOnFocus: false,
@@ -877,7 +874,8 @@ const CLIENT_LABELS = {
877
874
  codex: "Codex",
878
875
  opencode: "OpenCode",
879
876
  "mimo-code": "MiMo Code",
880
- "claude-code": "Claude Code"
877
+ "claude-code": "Claude Code",
878
+ "ai-sdk": "AI SDK"
881
879
  };
882
880
  function normalize(value) {
883
881
  return value?.toLowerCase() ?? "";
@@ -905,6 +903,7 @@ function detectClientApp(input) {
905
903
  if (matchesAny(haystack, ["codex desktop", "codex-cli", "openai-codex", "codex"])) {
906
904
  return "codex";
907
905
  }
906
+ if (matchesAny(normalize(input.userAgent), ["ai-sdk", "ai/"])) return "ai-sdk";
908
907
  return "unknown";
909
908
  }
910
909
  function clientAppLabel(client) {
@@ -914,11 +913,25 @@ function clientAppLabel(client) {
914
913
  case "opencode":
915
914
  case "mimo-code":
916
915
  case "claude-code":
916
+ case "ai-sdk":
917
917
  return CLIENT_LABELS[client];
918
918
  case "unknown":
919
919
  return null;
920
920
  }
921
921
  }
922
+ function clientAppTypeLabel(client) {
923
+ switch (client) {
924
+ case "codex":
925
+ case "opencode":
926
+ case "mimo-code":
927
+ case "claude-code":
928
+ return "IDE";
929
+ case "agent-inspector":
930
+ case "ai-sdk":
931
+ case "unknown":
932
+ return "CLIENT";
933
+ }
934
+ }
922
935
  const CLIENT_LOGOS = {
923
936
  "agent-inspector": AgentInspectorLogo,
924
937
  codex: CodexLogoSvg,
@@ -942,11 +955,27 @@ const CLIENT_LOGO_IMAGES = {
942
955
  };
943
956
  function ClientLogo({
944
957
  client,
945
- className
958
+ className,
959
+ decorative = false
946
960
  }) {
947
961
  if (client === "unknown") return null;
948
962
  const label = clientAppLabel(client);
949
963
  if (label === null) return null;
964
+ if (client === "ai-sdk") {
965
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
966
+ "span",
967
+ {
968
+ className: cn(
969
+ "inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-cyan-200/20 bg-cyan-300/[0.08] text-cyan-100 shadow-sm",
970
+ className
971
+ ),
972
+ "aria-label": decorative ? void 0 : label,
973
+ "aria-hidden": decorative ? true : void 0,
974
+ title: label,
975
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(CodeXml, { className: "size-1/2", "aria-hidden": "true" })
976
+ }
977
+ );
978
+ }
950
979
  const logoSrc = CLIENT_LOGOS[client];
951
980
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
952
981
  "span",
@@ -956,7 +985,8 @@ function ClientLogo({
956
985
  CLIENT_LOGO_FRAMES[client],
957
986
  className
958
987
  ),
959
- "aria-label": label,
988
+ "aria-label": decorative ? void 0 : label,
989
+ "aria-hidden": decorative ? true : void 0,
960
990
  title: label,
961
991
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(
962
992
  "img",
@@ -1673,6 +1703,13 @@ function resolveLogFormat(log) {
1673
1703
  return pathFormat === "unknown" ? log.apiFormat : pathFormat;
1674
1704
  }
1675
1705
  const HEADER_ACTION_BUTTON_CLASS$1 = "bg-white/[0.045] hover:bg-white/[0.075] inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground";
1706
+ function isSyntheticSessionId(conversationId) {
1707
+ return conversationId.startsWith("PID:") || conversationId.startsWith("process:") || conversationId.startsWith("connection:") || conversationId.includes("|");
1708
+ }
1709
+ function compactSessionLabel(conversationId) {
1710
+ if (isSyntheticSessionId(conversationId)) return "Sessionless";
1711
+ return conversationId.length > 24 ? `${conversationId.slice(0, 12)}...${conversationId.slice(-12)}` : conversationId;
1712
+ }
1676
1713
  function HeaderMetric({
1677
1714
  icon,
1678
1715
  children,
@@ -1694,11 +1731,11 @@ function HeaderMetric({
1694
1731
  }
1695
1732
  );
1696
1733
  }
1697
- function PendingTurnBadge$1() {
1734
+ function PendingTurnBadge() {
1698
1735
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1699
1736
  "span",
1700
1737
  {
1701
- className: "inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md bg-amber-300/[0.10] px-1.5 font-mono text-[10px] uppercase tracking-[0.14em] text-amber-100/90 ring-1 ring-amber-200/10",
1738
+ className: "inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md bg-amber-300/[0.10] px-1.5 font-mono text-[10px] uppercase text-amber-100/90 ring-1 ring-amber-200/10",
1702
1739
  title: "This session has a turn that has not finished yet",
1703
1740
  children: [
1704
1741
  /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3 animate-spin" }),
@@ -1748,8 +1785,8 @@ function ConversationHeader({
1748
1785
  const hasUserAgent = userAgent !== null && userAgent !== void 0 && userAgent !== "";
1749
1786
  const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
1750
1787
  const clientTooltip = detectedClientLabel !== null && hasUserAgent ? `${detectedClientLabel} - ${userAgent}` : null;
1751
- const isFallbackConversation = conversationId.startsWith("PID:") || conversationId.includes("|");
1752
- const compactSessionLabel = conversationId.length > 24 ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12) : conversationId;
1788
+ const isFallbackConversation = isSyntheticSessionId(conversationId);
1789
+ const sessionLabel = compactSessionLabel(conversationId);
1753
1790
  const timeLabel = formatTimestampRange(startTime, endTime, timeDisplayFormat);
1754
1791
  const callsLabel = `${totalCalls} call${totalCalls !== 1 ? "s" : ""}`;
1755
1792
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -1760,8 +1797,8 @@ function ConversationHeader({
1760
1797
  "data-nav-id": `conv-${conversationId}`,
1761
1798
  "data-nav-action": expanded ? "collapse" : "expand",
1762
1799
  className: cn(
1763
- "bg-card/80 shadow-sm relative flex cursor-pointer items-start gap-3 overflow-hidden rounded-[8px] px-3 py-2.5 transition-colors",
1764
- "hover:bg-card",
1800
+ "bg-black/[0.10] relative flex cursor-pointer items-start gap-2.5 overflow-hidden rounded-[8px] px-3 py-2.5 transition-colors",
1801
+ "hover:bg-black/[0.16]",
1765
1802
  "select-none",
1766
1803
  "sticky top-0 z-10 mb-2",
1767
1804
  "focus-visible:ring-1 focus-visible:ring-cyan-300/35 focus-visible:outline-none"
@@ -1785,10 +1822,11 @@ function ConversationHeader({
1785
1822
  ),
1786
1823
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0 flex-1", children: [
1787
1824
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
1825
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 rounded bg-white/[0.04] px-1.5 py-0.5 font-mono text-[9px] uppercase text-muted-foreground/70", children: "Session" }),
1788
1826
  isFallbackConversation && showProcessMetadata ? clientPid !== null && clientPid !== void 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
1789
1827
  "span",
1790
1828
  {
1791
- className: "min-w-0 max-w-full truncate font-mono text-xs font-semibold text-violet-200",
1829
+ className: "min-w-0 max-w-full truncate font-mono text-xs font-semibold text-slate-100",
1792
1830
  title: `PID ${clientPid}`,
1793
1831
  children: [
1794
1832
  "PID ",
@@ -1798,16 +1836,16 @@ function ConversationHeader({
1798
1836
  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
1799
1837
  "span",
1800
1838
  {
1801
- className: "min-w-0 max-w-full truncate font-mono text-xs font-semibold text-violet-200",
1839
+ className: "min-w-0 max-w-full truncate font-mono text-xs font-semibold text-slate-100",
1802
1840
  title: conversationId,
1803
1841
  children: conversationId
1804
1842
  }
1805
1843
  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
1806
1844
  "span",
1807
1845
  {
1808
- className: "min-w-0 max-w-full truncate font-mono text-xs font-semibold text-violet-200",
1846
+ className: "min-w-0 max-w-full truncate font-mono text-xs font-semibold text-slate-100",
1809
1847
  title: conversationId,
1810
- children: isFallbackConversation ? "sessionless" : compactSessionLabel
1848
+ children: sessionLabel
1811
1849
  }
1812
1850
  ),
1813
1851
  !isFallbackConversation && showProcessMetadata && clientPid !== null && clientPid !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -1835,12 +1873,12 @@ function ConversationHeader({
1835
1873
  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",
1836
1874
  title: clientTooltip ?? clientDisplayLabel,
1837
1875
  children: [
1838
- /* @__PURE__ */ jsxRuntimeExports.jsx(ClientLogo, { client: clientApp, className: "size-4 shrink-0" }),
1876
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ClientLogo, { client: clientApp, className: "size-4 shrink-0", decorative: true }),
1839
1877
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono leading-snug tabular-nums", children: clientDisplayLabel })
1840
1878
  ]
1841
1879
  }
1842
1880
  ),
1843
- isLoading && /* @__PURE__ */ jsxRuntimeExports.jsx(PendingTurnBadge$1, {})
1881
+ isLoading && /* @__PURE__ */ jsxRuntimeExports.jsx(PendingTurnBadge, {})
1844
1882
  ] }),
1845
1883
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "mt-1.5 flex min-w-0 flex-wrap items-center gap-1.5", children: [
1846
1884
  /* @__PURE__ */ jsxRuntimeExports.jsx(HeaderMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3 shrink-0" }), title: timeLabel, children: timeLabel }),
@@ -2527,27 +2565,27 @@ function useCopyFeedback(text) {
2527
2565
  return { copied, copy };
2528
2566
  }
2529
2567
  const LazyCompareDrawer = reactExports.lazy(
2530
- () => import("./CompareDrawer-CqNzIeMR.mjs").then((m) => ({ default: m.CompareDrawer }))
2568
+ () => import("./CompareDrawer-BMRH3N97.mjs").then((m) => ({ default: m.CompareDrawer }))
2531
2569
  );
2532
2570
  const LazyReplayDialog = reactExports.lazy(
2533
- () => import("./ReplayDialog-D9p_LaTr.mjs").then((m) => ({ default: m.ReplayDialog }))
2571
+ () => import("./ReplayDialog-CbWvRHiN.mjs").then((m) => ({ default: m.ReplayDialog }))
2534
2572
  );
2535
2573
  const LazyRequestAnatomy = reactExports.lazy(
2536
- () => import("./RequestAnatomy-B4ge3qNZ.mjs").then((m) => ({ default: m.RequestAnatomy }))
2574
+ () => import("./RequestAnatomy-C4kcPyqN.mjs").then((m) => ({ default: m.RequestAnatomy }))
2537
2575
  );
2538
2576
  const LazyResponseView = reactExports.lazy(
2539
- () => import("./ResponseView-BCqMKApI.mjs").then((m) => ({ default: m.ResponseView }))
2577
+ () => import("./ResponseView-D8VYt-gZ.mjs").then((m) => ({ default: m.ResponseView }))
2540
2578
  );
2541
2579
  const LazyStreamingChunkSequence = reactExports.lazy(
2542
- () => import("./StreamingChunkSequence-BA4zTLE4.mjs").then((m) => ({
2580
+ () => import("./StreamingChunkSequence-BUUo6vU0.mjs").then((m) => ({
2543
2581
  default: m.StreamingChunkSequence
2544
2582
  }))
2545
2583
  );
2546
2584
  const LazyJsonViewer = reactExports.lazy(
2547
- () => import("./json-viewer-B3qV6iEJ.mjs").then((m) => ({ default: m.JsonViewer }))
2585
+ () => import("./json-viewer-BqF1pSQD.mjs").then((m) => ({ default: m.JsonViewer }))
2548
2586
  );
2549
2587
  const LazyJsonViewerFromString = reactExports.lazy(
2550
- () => import("./json-viewer-B3qV6iEJ.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2588
+ () => import("./json-viewer-BqF1pSQD.mjs").then((m) => ({ default: m.JsonViewerFromString }))
2551
2589
  );
2552
2590
  const HIGHLIGHT_DURATION_MS = 1200;
2553
2591
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -6173,7 +6211,7 @@ const ConversationGroup = reactExports.memo(function({
6173
6211
  window.removeEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
6174
6212
  };
6175
6213
  }, [group.logs, setExpanded]);
6176
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2", children: [
6214
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1.5", children: [
6177
6215
  /* @__PURE__ */ jsxRuntimeExports.jsx(
6178
6216
  ConversationHeader,
6179
6217
  {
@@ -9067,16 +9105,7 @@ function ProviderCard({
9067
9105
  onRefreshMetadata,
9068
9106
  isRefreshingMetadata = false
9069
9107
  }) {
9070
- const [showApiKey, setShowApiKey] = reactExports.useState(false);
9071
- const [copied, setCopied] = reactExports.useState(false);
9072
9108
  const [showDetails, setShowDetails] = reactExports.useState(false);
9073
- function handleCopy() {
9074
- void copyTextToClipboard(provider.apiKey).then((success) => {
9075
- if (!success) return;
9076
- setCopied(true);
9077
- setTimeout(() => setCopied(false), 2e3);
9078
- });
9079
- }
9080
9109
  const docsUrl = provider.apiDocsUrl ?? Object.entries(KNOWN_PROVIDER_DOCS).find(
9081
9110
  ([keyword]) => provider.name.toLowerCase().includes(keyword)
9082
9111
  )?.[1];
@@ -9163,28 +9192,9 @@ function ProviderCard({
9163
9192
  ] })
9164
9193
  ] }),
9165
9194
  showDetails && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 border-t border-border/60 pt-3", children: [
9166
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9167
- /* @__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) }),
9168
- /* @__PURE__ */ jsxRuntimeExports.jsx(
9169
- "button",
9170
- {
9171
- type: "button",
9172
- onClick: () => setShowApiKey((s) => !s),
9173
- className: "p-1 text-muted-foreground transition-colors hover:text-foreground",
9174
- "aria-label": showApiKey ? "Hide API key" : "Show API key",
9175
- children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-4" })
9176
- }
9177
- ),
9178
- /* @__PURE__ */ jsxRuntimeExports.jsx(
9179
- "button",
9180
- {
9181
- type: "button",
9182
- onClick: handleCopy,
9183
- className: "p-1 text-muted-foreground transition-colors hover:text-foreground",
9184
- "aria-label": "Copy API key",
9185
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4 text-cyan-300" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-4" })
9186
- }
9187
- )
9195
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
9196
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3.5 text-emerald-500" }),
9197
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "API key configured" })
9188
9198
  ] }),
9189
9199
  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)) }),
9190
9200
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "space-y-2", children: [
@@ -9500,7 +9510,7 @@ function EndpointUrlPreview({
9500
9510
  function ProviderForm({ provider, onSubmit, onCancel }) {
9501
9511
  const initialCapabilities = deriveProviderCapabilities(provider);
9502
9512
  const [name, setName] = reactExports.useState(provider?.name ?? "");
9503
- const [apiKey, setApiKey] = reactExports.useState(provider?.apiKey ?? "");
9513
+ const [apiKey, setApiKey] = reactExports.useState("");
9504
9514
  const [showApiKey, setShowApiKey] = reactExports.useState(false);
9505
9515
  const [copied, setCopied] = reactExports.useState(false);
9506
9516
  const initialModels = provider?.models;
@@ -9568,7 +9578,8 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9568
9578
  reactExports.useEffect(() => {
9569
9579
  if (provider) {
9570
9580
  setName(provider.name);
9571
- setApiKey(provider.apiKey);
9581
+ setApiKey("");
9582
+ setShowApiKey(false);
9572
9583
  const nextModels = (provider.models?.length ?? 0) > 0 ? provider.models : [""];
9573
9584
  setModels(nextModels);
9574
9585
  setModelMetadataDrafts(createMetadataDrafts(provider, nextModels));
@@ -9628,7 +9639,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
9628
9639
  if (!name.trim()) {
9629
9640
  newErrors.name = "Name is required";
9630
9641
  }
9631
- if (!apiKey.trim()) {
9642
+ if (provider === void 0 && !apiKey.trim()) {
9632
9643
  newErrors.apiKey = "API key is required";
9633
9644
  }
9634
9645
  if (models.length === 0 || models.every((m) => !m.trim())) {
@@ -10072,21 +10083,17 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
10072
10083
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
10073
10084
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-apikey", className: "text-sm font-medium", children: [
10074
10085
  "API Key ",
10075
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
10086
+ provider === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
10076
10087
  ] }),
10077
10088
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
10078
10089
  /* @__PURE__ */ jsxRuntimeExports.jsx(
10079
10090
  "input",
10080
10091
  {
10081
10092
  id: "provider-apikey",
10082
- type: "text",
10083
- value: showApiKey || apiKey.length === 0 ? apiKey : maskApiKey(apiKey),
10093
+ type: showApiKey ? "text" : "password",
10094
+ value: apiKey,
10084
10095
  onChange: (e) => setApiKey(e.target.value),
10085
- onFocus: () => {
10086
- if (!showApiKey && apiKey.length > 0) setShowApiKey(true);
10087
- },
10088
- placeholder: "sk-ant-...",
10089
- readOnly: !showApiKey && apiKey.length > 0,
10096
+ placeholder: provider === void 0 ? "sk-ant-..." : "Leave blank to keep current key",
10090
10097
  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"
10091
10098
  }
10092
10099
  ),
@@ -10111,6 +10118,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
10111
10118
  }
10112
10119
  )
10113
10120
  ] }),
10121
+ 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." }),
10114
10122
  errors.apiKey !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.apiKey })
10115
10123
  ] }),
10116
10124
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
@@ -10336,7 +10344,7 @@ function endpointPayloadValue(value, preserveEmptyEndpoints) {
10336
10344
  function createProviderPayload(data, preserveEmptyEndpoints = false) {
10337
10345
  return {
10338
10346
  name: data.name,
10339
- apiKey: data.apiKey,
10347
+ ...data.apiKey.trim() === "" ? {} : { apiKey: data.apiKey },
10340
10348
  models: data.models,
10341
10349
  anthropicBaseUrl: endpointPayloadValue(data.anthropicBaseUrl, preserveEmptyEndpoints),
10342
10350
  openaiBaseUrl: endpointPayloadValue(data.openaiBaseUrl, preserveEmptyEndpoints),
@@ -10636,7 +10644,7 @@ function ProvidersPanel({
10636
10644
  await refreshProviderMetadata(updated.id, data.modelMetadataUrl, false);
10637
10645
  }
10638
10646
  refreshProviders();
10639
- 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 ?? "");
10647
+ 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 ?? "");
10640
10648
  if (criticalFieldsChanged) {
10641
10649
  await runTest(updated.id);
10642
10650
  }
@@ -11283,9 +11291,9 @@ function McpSettingsTab() {
11283
11291
  value: `agent-inspector-mcp doctor --url ${endpoint}`
11284
11292
  },
11285
11293
  {
11286
- id: "readonly",
11287
- label: "Readonly mode",
11288
- value: "AGENT_INSPECTOR_MCP_READONLY=1 agent-inspector"
11294
+ id: "writes",
11295
+ label: "Enable write tools",
11296
+ value: "AGENT_INSPECTOR_MCP_WRITES=1 agent-inspector"
11289
11297
  },
11290
11298
  {
11291
11299
  id: "capabilities",
@@ -12449,8 +12457,8 @@ function resolveInspectorPetStatus(logs, nowMs) {
12449
12457
  capturedCount
12450
12458
  };
12451
12459
  }
12452
- const latestTimestamp = Date.parse(latest.timestamp);
12453
- if (Number.isFinite(latestTimestamp) && nowMs - latestTimestamp >= INSPECTOR_PET_SLEEP_AFTER_MS) {
12460
+ const latestTimestamp2 = Date.parse(latest.timestamp);
12461
+ if (Number.isFinite(latestTimestamp2) && nowMs - latestTimestamp2 >= INSPECTOR_PET_SLEEP_AFTER_MS) {
12454
12462
  return {
12455
12463
  mood: "sleeping",
12456
12464
  label: "All quiet",
@@ -13076,16 +13084,6 @@ function InspectorPet({ logs }) {
13076
13084
  ) })
13077
13085
  }
13078
13086
  ),
13079
- /* @__PURE__ */ jsxRuntimeExports.jsx(
13080
- "span",
13081
- {
13082
- className: cn(
13083
- "absolute right-0 top-2 size-3 rounded-full border border-background/90",
13084
- moodDotClass(status.mood),
13085
- status.mood === "active" && !reduceMotion && "animate-pulse"
13086
- )
13087
- }
13088
- ),
13089
13087
  reactionVisible && /* @__PURE__ */ jsxRuntimeExports.jsx(
13090
13088
  Heart,
13091
13089
  {
@@ -13301,6 +13299,11 @@ function compareField(previous, current) {
13301
13299
  if (current < previous) return { direction: "down", delta: previous - current };
13302
13300
  return null;
13303
13301
  }
13302
+ const COLLAPSED_CLIENTS_STORAGE_KEY = "agent-inspector.hierarchy.collapsedClients";
13303
+ const COLLAPSED_PROCESSES_STORAGE_KEY = "agent-inspector.hierarchy.collapsedProcesses";
13304
+ function hasPendingLogs(logs) {
13305
+ return logs.some((log) => log.responseStatus === null);
13306
+ }
13304
13307
  function countPendingTurns(logs) {
13305
13308
  return logs.filter((log) => log.responseStatus === null).length;
13306
13309
  }
@@ -13335,9 +13338,9 @@ function groupClient(group) {
13335
13338
  });
13336
13339
  }
13337
13340
  function fallbackClientLabel(userAgent) {
13338
- if (userAgent === null || userAgent === "") return "Unknown IDE";
13341
+ if (userAgent === null || userAgent === "") return "Unknown client";
13339
13342
  const firstToken = userAgent.split(/[ /;]/)[0] ?? "";
13340
- if (firstToken === "") return "Unknown IDE";
13343
+ if (firstToken === "") return "Unknown client";
13341
13344
  return firstToken;
13342
13345
  }
13343
13346
  function clientLabel(client, userAgent) {
@@ -13359,22 +13362,41 @@ function processKey(clientPid, projectFolder) {
13359
13362
  function sortLogsByTimestamp(logs) {
13360
13363
  return [...logs].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
13361
13364
  }
13362
- function firstTimestamp(logs) {
13363
- return sortLogsByTimestamp(logs)[0]?.timestamp ?? "";
13365
+ function latestTimestamp(logs) {
13366
+ let latest = "";
13367
+ for (const log of logs) {
13368
+ if (log.timestamp > latest) latest = log.timestamp;
13369
+ }
13370
+ return latest;
13364
13371
  }
13365
- function sortByFirstLogTime(items) {
13366
- return [...items].sort((a, b) => firstTimestamp(a.logs).localeCompare(firstTimestamp(b.logs)));
13372
+ function operationalPriority(logs) {
13373
+ if (hasPendingLogs(logs)) return 0;
13374
+ if (logs.some(
13375
+ (log) => log.error !== null && log.error !== void 0 || log.responseStatus !== null && log.responseStatus !== void 0 && log.responseStatus >= 400
13376
+ )) {
13377
+ return 1;
13378
+ }
13379
+ return 2;
13380
+ }
13381
+ function sortByOperationalPriority(items) {
13382
+ return [...items].sort((left, right) => {
13383
+ const priorityDifference = operationalPriority(left.logs) - operationalPriority(right.logs);
13384
+ if (priorityDifference !== 0) return priorityDifference;
13385
+ const recencyDifference = latestTimestamp(right.logs).localeCompare(latestTimestamp(left.logs));
13386
+ if (recencyDifference !== 0) return recencyDifference;
13387
+ return left.id.localeCompare(right.id);
13388
+ });
13367
13389
  }
13368
13390
  function buildConversationHierarchy(groups) {
13369
- const ideGroups = /* @__PURE__ */ new Map();
13391
+ const clientGroups = /* @__PURE__ */ new Map();
13370
13392
  for (const group of groups) {
13371
13393
  const userAgent = firstUserAgent(group.logs);
13372
13394
  const projectFolder = firstClientProjectFolder(group.logs);
13373
13395
  const client = groupClient(group);
13374
13396
  const currentIdeKey = ideKey(client, userAgent, projectFolder);
13375
- let ideGroup = ideGroups.get(currentIdeKey);
13376
- if (ideGroup === void 0) {
13377
- ideGroup = {
13397
+ let clientGroup = clientGroups.get(currentIdeKey);
13398
+ if (clientGroup === void 0) {
13399
+ clientGroup = {
13378
13400
  id: currentIdeKey,
13379
13401
  client,
13380
13402
  label: clientLabel(client, userAgent),
@@ -13382,12 +13404,12 @@ function buildConversationHierarchy(groups) {
13382
13404
  processes: /* @__PURE__ */ new Map(),
13383
13405
  logs: []
13384
13406
  };
13385
- ideGroups.set(currentIdeKey, ideGroup);
13407
+ clientGroups.set(currentIdeKey, clientGroup);
13386
13408
  }
13387
- ideGroup.logs.push(...group.logs);
13409
+ clientGroup.logs.push(...group.logs);
13388
13410
  const clientPid = firstClientPid(group.logs);
13389
13411
  const currentProcessKey = `${currentIdeKey}:${processKey(clientPid, projectFolder)}`;
13390
- let processGroup = ideGroup.processes.get(currentProcessKey);
13412
+ let processGroup = clientGroup.processes.get(currentProcessKey);
13391
13413
  if (processGroup === void 0) {
13392
13414
  processGroup = {
13393
13415
  id: currentProcessKey,
@@ -13396,32 +13418,32 @@ function buildConversationHierarchy(groups) {
13396
13418
  sessions: [],
13397
13419
  logs: []
13398
13420
  };
13399
- ideGroup.processes.set(currentProcessKey, processGroup);
13421
+ clientGroup.processes.set(currentProcessKey, processGroup);
13400
13422
  }
13401
13423
  processGroup.sessions.push(group);
13402
13424
  processGroup.logs.push(...group.logs);
13403
13425
  }
13404
13426
  const hierarchy = [];
13405
- for (const ideGroup of ideGroups.values()) {
13406
- const processes = sortByFirstLogTime(
13407
- [...ideGroup.processes.values()].map((processGroup) => ({
13427
+ for (const clientGroup of clientGroups.values()) {
13428
+ const processes = sortByOperationalPriority(
13429
+ [...clientGroup.processes.values()].map((processGroup) => ({
13408
13430
  id: processGroup.id,
13409
13431
  clientPid: processGroup.clientPid,
13410
13432
  clientProjectFolder: processGroup.clientProjectFolder,
13411
- sessions: sortByFirstLogTime(processGroup.sessions),
13433
+ sessions: sortByOperationalPriority(processGroup.sessions),
13412
13434
  logs: sortLogsByTimestamp(processGroup.logs)
13413
13435
  }))
13414
13436
  );
13415
13437
  hierarchy.push({
13416
- id: ideGroup.id,
13417
- client: ideGroup.client,
13418
- label: ideGroup.label,
13419
- userAgent: ideGroup.userAgent,
13438
+ id: clientGroup.id,
13439
+ client: clientGroup.client,
13440
+ label: clientGroup.label,
13441
+ userAgent: clientGroup.userAgent,
13420
13442
  processes,
13421
- logs: sortLogsByTimestamp(ideGroup.logs)
13443
+ logs: sortLogsByTimestamp(clientGroup.logs)
13422
13444
  });
13423
13445
  }
13424
- return sortByFirstLogTime(hierarchy);
13446
+ return sortByOperationalPriority(hierarchy);
13425
13447
  }
13426
13448
  function formatTokenCount(count) {
13427
13449
  if (count <= 0) return "0";
@@ -13445,6 +13467,30 @@ function countSessions(processes) {
13445
13467
  }
13446
13468
  return sessions;
13447
13469
  }
13470
+ function readStoredIdSet(storageKey) {
13471
+ if (typeof window === "undefined") return /* @__PURE__ */ new Set();
13472
+ try {
13473
+ const parsed = JSON.parse(window.localStorage.getItem(storageKey) ?? "[]");
13474
+ if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
13475
+ return new Set(parsed.filter((value) => typeof value === "string"));
13476
+ } catch {
13477
+ return /* @__PURE__ */ new Set();
13478
+ }
13479
+ }
13480
+ function persistIdSet(storageKey, values) {
13481
+ try {
13482
+ window.localStorage.setItem(storageKey, JSON.stringify([...values]));
13483
+ } catch {
13484
+ }
13485
+ }
13486
+ function removeIds(values, ids) {
13487
+ let changed = false;
13488
+ const next = new Set(values);
13489
+ for (const id of ids) {
13490
+ if (next.delete(id)) changed = true;
13491
+ }
13492
+ return changed ? next : values;
13493
+ }
13448
13494
  function shortProjectPath(projectFolder) {
13449
13495
  if (projectFolder === null || projectFolder === "") return null;
13450
13496
  const parts = projectFolder.replaceAll("\\", "/").split("/").filter((part) => part !== "");
@@ -13463,57 +13509,67 @@ function SummaryChip({
13463
13509
  "span",
13464
13510
  {
13465
13511
  className: cn(
13466
- "bg-white/[0.045] inline-flex h-7 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground",
13512
+ "bg-white/[0.04] inline-flex h-7 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground",
13467
13513
  className
13468
13514
  ),
13469
13515
  children: [
13470
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-white/35", children: label }),
13516
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-[10px] uppercase text-white/35", children: label }),
13471
13517
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums text-slate-100", children: value })
13472
13518
  ]
13473
13519
  }
13474
13520
  );
13475
13521
  }
13476
- function PendingTurnBadge({
13477
- count,
13478
- label = "Turn open"
13479
- }) {
13522
+ function AncestorActivityIndicator({ count }) {
13523
+ const label = `${count} open turn${count === 1 ? "" : "s"}`;
13480
13524
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
13481
13525
  "span",
13482
13526
  {
13483
- className: "inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md bg-amber-300/[0.10] px-1.5 font-mono text-[10px] uppercase tracking-[0.14em] text-amber-100/90 ring-1 ring-amber-200/10",
13484
- title: `${count} turn${count !== 1 ? "s" : ""} not finished yet`,
13527
+ className: "relative inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-amber-300/[0.08] text-amber-100/90 ring-1 ring-amber-200/10",
13528
+ "aria-label": label,
13529
+ title: label,
13485
13530
  children: [
13486
13531
  /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3 animate-spin" }),
13487
- label,
13488
- count > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "tabular-nums", children: [
13489
- "x",
13490
- count
13491
- ] })
13532
+ count > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute translate-x-2 -translate-y-2 rounded bg-background px-0.5 font-mono text-[8px] tabular-nums text-amber-100", children: count })
13492
13533
  ]
13493
13534
  }
13494
13535
  );
13495
13536
  }
13496
- function LoadingSweep() {
13497
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
13498
- "span",
13499
- {
13500
- className: "pointer-events-none absolute inset-x-3 bottom-0 h-px overflow-hidden rounded-full bg-white/[0.06]",
13501
- "aria-hidden": "true",
13502
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute inset-y-0 left-0 w-1/3 animate-pulse rounded-full bg-amber-200/65 shadow-[0_0_14px_rgba(252,211,77,0.32)]" })
13503
- }
13504
- );
13505
- }
13506
13537
  function ConversationGroupList({
13507
13538
  groups,
13508
13539
  ...groupProps
13509
13540
  }) {
13510
13541
  const hierarchy = reactExports.useMemo(() => buildConversationHierarchy(groups), [groups]);
13511
- const [collapsedIdeIds, setCollapsedIdeIds] = reactExports.useState(() => /* @__PURE__ */ new Set());
13542
+ const [collapsedClientIds, setCollapsedClientIds] = reactExports.useState(
13543
+ () => /* @__PURE__ */ new Set()
13544
+ );
13512
13545
  const [collapsedProcessIds, setCollapsedProcessIds] = reactExports.useState(
13513
13546
  () => /* @__PURE__ */ new Set()
13514
13547
  );
13515
- const toggleIde = reactExports.useCallback((id) => {
13516
- setCollapsedIdeIds((previous) => {
13548
+ const [storedCollapseStateLoaded, setStoredCollapseStateLoaded] = reactExports.useState(false);
13549
+ reactExports.useEffect(() => {
13550
+ setCollapsedClientIds(readStoredIdSet(COLLAPSED_CLIENTS_STORAGE_KEY));
13551
+ setCollapsedProcessIds(readStoredIdSet(COLLAPSED_PROCESSES_STORAGE_KEY));
13552
+ setStoredCollapseStateLoaded(true);
13553
+ }, []);
13554
+ reactExports.useEffect(() => {
13555
+ if (!storedCollapseStateLoaded) return;
13556
+ persistIdSet(COLLAPSED_CLIENTS_STORAGE_KEY, collapsedClientIds);
13557
+ persistIdSet(COLLAPSED_PROCESSES_STORAGE_KEY, collapsedProcessIds);
13558
+ }, [collapsedClientIds, collapsedProcessIds, storedCollapseStateLoaded]);
13559
+ reactExports.useEffect(() => {
13560
+ const activeClientIds = new Set(
13561
+ hierarchy.filter((group) => hasPendingLogs(group.logs)).map((group) => group.id)
13562
+ );
13563
+ const activeProcessIds = new Set(
13564
+ hierarchy.flatMap(
13565
+ (group) => group.processes.filter((process) => hasPendingLogs(process.logs)).map((process) => process.id)
13566
+ )
13567
+ );
13568
+ setCollapsedClientIds((previous) => removeIds(previous, activeClientIds));
13569
+ setCollapsedProcessIds((previous) => removeIds(previous, activeProcessIds));
13570
+ }, [hierarchy]);
13571
+ const toggleClient = reactExports.useCallback((id) => {
13572
+ setCollapsedClientIds((previous) => {
13517
13573
  const next = new Set(previous);
13518
13574
  if (next.has(id)) {
13519
13575
  next.delete(id);
@@ -13534,24 +13590,24 @@ function ConversationGroupList({
13534
13590
  return next;
13535
13591
  });
13536
13592
  }, []);
13537
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4", children: hierarchy.map((ideGroup) => /* @__PURE__ */ jsxRuntimeExports.jsx(
13538
- IdeGroupSection,
13593
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: hierarchy.map((clientGroup) => /* @__PURE__ */ jsxRuntimeExports.jsx(
13594
+ ClientGroupSection,
13539
13595
  {
13540
- group: ideGroup,
13541
- collapsed: collapsedIdeIds.has(ideGroup.id),
13596
+ group: clientGroup,
13597
+ collapsed: collapsedClientIds.has(clientGroup.id),
13542
13598
  collapsedProcessIds,
13543
- onToggleIde: () => toggleIde(ideGroup.id),
13599
+ onToggleClient: () => toggleClient(clientGroup.id),
13544
13600
  onToggleProcess: toggleProcess,
13545
13601
  conversationProps: groupProps
13546
13602
  },
13547
- ideGroup.id
13603
+ clientGroup.id
13548
13604
  )) });
13549
13605
  }
13550
- function IdeGroupSection({
13606
+ function ClientGroupSection({
13551
13607
  group,
13552
13608
  collapsed,
13553
13609
  collapsedProcessIds,
13554
- onToggleIde,
13610
+ onToggleClient,
13555
13611
  onToggleProcess,
13556
13612
  conversationProps
13557
13613
  }) {
@@ -13564,8 +13620,8 @@ function IdeGroupSection({
13564
13620
  "section",
13565
13621
  {
13566
13622
  className: cn(
13567
- "bg-card/75 shadow-sm relative isolate overflow-hidden rounded-[8px]",
13568
- isWorking && "bg-card/85"
13623
+ "bg-card/60 relative isolate overflow-hidden rounded-[8px] shadow-sm",
13624
+ isWorking && "bg-card/70"
13569
13625
  ),
13570
13626
  "data-ide-group": group.id,
13571
13627
  children: [
@@ -13573,12 +13629,19 @@ function IdeGroupSection({
13573
13629
  "button",
13574
13630
  {
13575
13631
  type: "button",
13576
- className: "flex w-full min-w-0 items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-white/[0.03] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300/45",
13577
- onClick: onToggleIde,
13632
+ className: "grid w-full min-w-0 grid-cols-[2.25rem_minmax(0,1fr)_auto] items-center gap-2.5 px-3 py-2.5 text-left transition-colors hover:bg-white/[0.025] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-300/40",
13633
+ onClick: onToggleClient,
13578
13634
  "aria-expanded": !collapsed,
13579
13635
  children: [
13580
- group.client === "unknown" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.045] inline-flex size-10 shrink-0 items-center justify-center rounded-[8px] text-slate-200", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { className: "size-5" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ClientLogo, { client: group.client, className: "size-10 rounded-[8px] text-cyan-100" }),
13581
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0 flex-1", children: [
13636
+ group.client === "unknown" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.04] inline-flex size-9 shrink-0 items-center justify-center rounded-md text-slate-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { className: "size-4" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
13637
+ ClientLogo,
13638
+ {
13639
+ client: group.client,
13640
+ className: "size-9 rounded-md text-cyan-100",
13641
+ decorative: true
13642
+ }
13643
+ ),
13644
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0", children: [
13582
13645
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
13583
13646
  /* @__PURE__ */ jsxRuntimeExports.jsx(
13584
13647
  "span",
@@ -13588,10 +13651,10 @@ function IdeGroupSection({
13588
13651
  children: group.label
13589
13652
  }
13590
13653
  ),
13591
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-cyan-300/[0.06] px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-[0.16em] text-cyan-100/75", children: "IDE" }),
13592
- isWorking && /* @__PURE__ */ jsxRuntimeExports.jsx(PendingTurnBadge, { count: pendingTurnCount, label: "Turn open" })
13654
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded bg-cyan-300/[0.05] px-1.5 py-0.5 font-mono text-[9px] uppercase text-cyan-100/70", children: clientAppTypeLabel(group.client) }),
13655
+ isWorking && /* @__PURE__ */ jsxRuntimeExports.jsx(AncestorActivityIndicator, { count: pendingTurnCount })
13593
13656
  ] }),
13594
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
13657
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground", children: [
13595
13658
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13596
13659
  group.processes.length,
13597
13660
  " process",
@@ -13604,21 +13667,21 @@ function IdeGroupSection({
13604
13667
  ] }),
13605
13668
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13606
13669
  logCount,
13607
- " log",
13608
- logCount !== 1 ? "s" : ""
13670
+ " req"
13609
13671
  ] })
13610
13672
  ] })
13611
13673
  ] }),
13612
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 items-center gap-2 md:flex", children: [
13613
- /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChip, { label: "IN", value: formatTokenCount(totals.inputTokens) }),
13614
- /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChip, { label: "OUT", value: formatTokenCount(totals.outputTokens) })
13615
- ] }),
13616
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.045] hover:bg-white/[0.075] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-200", children: collapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4" }) })
13674
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
13675
+ collapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 items-center gap-2 md:flex", children: [
13676
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChip, { label: "IN", value: formatTokenCount(totals.inputTokens) }),
13677
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChip, { label: "OUT", value: formatTokenCount(totals.outputTokens) })
13678
+ ] }),
13679
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.04] hover:bg-white/[0.07] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-300", children: collapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4" }) })
13680
+ ] })
13617
13681
  ]
13618
13682
  }
13619
13683
  ),
13620
- isWorking && /* @__PURE__ */ jsxRuntimeExports.jsx(LoadingSweep, {}),
13621
- !collapsed && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3 px-3 pb-3 pt-2 sm:px-4", children: group.processes.map((processGroup) => /* @__PURE__ */ jsxRuntimeExports.jsx(
13684
+ !collapsed && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 px-2 pb-2 pt-1", children: group.processes.map((processGroup) => /* @__PURE__ */ jsxRuntimeExports.jsx(
13622
13685
  ProcessGroupSection,
13623
13686
  {
13624
13687
  group: processGroup,
@@ -13644,69 +13707,66 @@ function ProcessGroupSection({
13644
13707
  const logCount = group.logs.length;
13645
13708
  const pendingTurnCount = countPendingTurns(group.logs);
13646
13709
  const isWorking = pendingTurnCount > 0;
13647
- const processLabel = group.clientPid !== null && group.clientPid !== void 0 ? `PID ${group.clientPid}` : "process";
13648
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "relative pl-5", children: [
13649
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute bottom-1 left-2 top-1 w-px bg-white/[0.055]", "aria-hidden": "true" }),
13650
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/[0.10] relative overflow-hidden rounded-[8px]", children: [
13651
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
13652
- "button",
13653
- {
13654
- type: "button",
13655
- className: "flex w-full min-w-0 items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-white/[0.035] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300/40",
13656
- onClick: onToggle,
13657
- "aria-expanded": !collapsed,
13658
- children: [
13659
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.045] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-amber-100", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cpu, { className: "size-4" }) }),
13660
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0 flex-1", children: [
13661
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
13662
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs font-semibold tabular-nums text-slate-100", children: processLabel }),
13663
- isWorking && /* @__PURE__ */ jsxRuntimeExports.jsx(PendingTurnBadge, { count: pendingTurnCount, label: "Turn open" }),
13664
- projectLabel !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
13665
- "span",
13666
- {
13667
- className: "inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground",
13668
- title: group.clientProjectFolder ?? projectLabel,
13669
- children: [
13670
- /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { className: "size-3 shrink-0" }),
13671
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate font-mono", children: projectLabel })
13672
- ]
13673
- }
13674
- )
13710
+ const processLabel = group.clientPid !== null && group.clientPid !== void 0 ? `PID ${group.clientPid}` : "PID unavailable";
13711
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "relative pl-4", children: [
13712
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13713
+ "button",
13714
+ {
13715
+ type: "button",
13716
+ className: "bg-black/[0.07] grid w-full min-w-0 grid-cols-[2rem_minmax(0,1fr)_auto] items-center gap-2.5 rounded-md px-2.5 py-2 text-left transition-colors hover:bg-white/[0.03] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-300/35",
13717
+ onClick: onToggle,
13718
+ "aria-expanded": !collapsed,
13719
+ children: [
13720
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.04] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-amber-100/85", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cpu, { className: "size-4" }) }),
13721
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0", children: [
13722
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
13723
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs font-semibold tabular-nums text-slate-100", children: processLabel }),
13724
+ isWorking && /* @__PURE__ */ jsxRuntimeExports.jsx(AncestorActivityIndicator, { count: pendingTurnCount }),
13725
+ projectLabel !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
13726
+ "span",
13727
+ {
13728
+ className: "inline-flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground",
13729
+ title: group.clientProjectFolder ?? projectLabel,
13730
+ children: [
13731
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { className: "size-3 shrink-0" }),
13732
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate font-mono", children: projectLabel })
13733
+ ]
13734
+ }
13735
+ )
13736
+ ] }),
13737
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground", children: [
13738
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13739
+ sessionCount,
13740
+ " session",
13741
+ sessionCount !== 1 ? "s" : ""
13675
13742
  ] }),
13676
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
13677
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13678
- sessionCount,
13679
- " session",
13680
- sessionCount !== 1 ? "s" : ""
13681
- ] }),
13682
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13683
- logCount,
13684
- " log",
13685
- logCount !== 1 ? "s" : ""
13686
- ] })
13743
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
13744
+ logCount,
13745
+ " req"
13687
13746
  ] })
13688
- ] }),
13689
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 items-center gap-2 md:flex", children: [
13747
+ ] })
13748
+ ] }),
13749
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
13750
+ collapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden shrink-0 items-center gap-2 md:flex", children: [
13690
13751
  /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChip, { label: "IN", value: formatTokenCount(totals.inputTokens) }),
13691
13752
  /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChip, { label: "OUT", value: formatTokenCount(totals.outputTokens) })
13692
13753
  ] }),
13693
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.045] hover:bg-white/[0.075] inline-flex size-7 shrink-0 items-center justify-center rounded-md text-slate-200", children: collapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4" }) })
13694
- ]
13695
- }
13696
- ),
13697
- isWorking && /* @__PURE__ */ jsxRuntimeExports.jsx(LoadingSweep, {}),
13698
- !collapsed && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 p-2 pt-1 sm:p-3 sm:pt-2", children: group.sessions.map((session) => /* @__PURE__ */ jsxRuntimeExports.jsx(
13699
- ConversationGroup,
13700
- {
13701
- group: session,
13702
- ...conversationProps,
13703
- showClientIdentity: false,
13704
- showLogClientMetadata: false,
13705
- showProcessMetadata: false
13706
- },
13707
- session.id
13708
- )) })
13709
- ] })
13754
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/[0.04] hover:bg-white/[0.07] inline-flex size-7 shrink-0 items-center justify-center rounded-md text-slate-300", children: collapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4" }) })
13755
+ ] })
13756
+ ]
13757
+ }
13758
+ ),
13759
+ !collapsed && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1.5 pb-1 pl-4 pr-1 pt-1.5", children: group.sessions.map((session) => /* @__PURE__ */ jsxRuntimeExports.jsx(
13760
+ ConversationGroup,
13761
+ {
13762
+ group: session,
13763
+ ...conversationProps,
13764
+ showClientIdentity: false,
13765
+ showLogClientMetadata: false,
13766
+ showProcessMetadata: false
13767
+ },
13768
+ session.id
13769
+ )) })
13710
13770
  ] });
13711
13771
  }
13712
13772
  const NAV_ATTR = "data-nav-id";
@@ -15454,6 +15514,27 @@ function ThemeModeToggle() {
15454
15514
  }
15455
15515
  );
15456
15516
  }
15517
+ function PetVisibilityToggle({
15518
+ visible,
15519
+ onToggle
15520
+ }) {
15521
+ const label = visible ? "Hide Inspector pet" : "Summon Inspector pet";
15522
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
15523
+ "button",
15524
+ {
15525
+ type: "button",
15526
+ onClick: onToggle,
15527
+ className: cn(
15528
+ "border-border/70 bg-muted/70 inline-flex size-8 shrink-0 items-center justify-center rounded-md border text-muted-foreground shadow-sm transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
15529
+ visible && "bg-background text-amber-500 shadow-sm"
15530
+ ),
15531
+ "aria-label": label,
15532
+ "aria-pressed": visible,
15533
+ title: label,
15534
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: cn("size-4 transition-opacity", !visible && "opacity-45") })
15535
+ }
15536
+ );
15537
+ }
15457
15538
  function UiScaleControl({
15458
15539
  preference,
15459
15540
  resolvedScale,
@@ -15565,11 +15646,20 @@ function BrandHeader({
15565
15646
  ] });
15566
15647
  }
15567
15648
  const DETAILS_PANEL_COLLAPSED_STORAGE_KEY = "agent-inspector.detailsPanelCollapsed";
15649
+ const INSPECTOR_PET_VISIBLE_STORAGE_KEY = "agent-inspector.pet.visible";
15568
15650
  function readStoredDetailsPanelCollapsed() {
15569
15651
  if (typeof window === "undefined") return true;
15570
15652
  const stored = window.localStorage.getItem(DETAILS_PANEL_COLLAPSED_STORAGE_KEY);
15571
15653
  return stored === null || stored === "1";
15572
15654
  }
15655
+ function readStoredInspectorPetVisible() {
15656
+ if (typeof window === "undefined") return true;
15657
+ try {
15658
+ return window.localStorage.getItem(INSPECTOR_PET_VISIBLE_STORAGE_KEY) !== "0";
15659
+ } catch {
15660
+ return true;
15661
+ }
15662
+ }
15573
15663
  function SessionContextLogButton({ label, logId }) {
15574
15664
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
15575
15665
  "button",
@@ -15896,6 +15986,8 @@ function ProxyViewer({
15896
15986
  const [deletingCurrentView, setDeletingCurrentView] = reactExports.useState(false);
15897
15987
  const [detailsPanelCollapsed, setDetailsPanelCollapsed] = reactExports.useState(true);
15898
15988
  const [detailsPanelStorageLoaded, setDetailsPanelStorageLoaded] = reactExports.useState(false);
15989
+ const [petVisible, setPetVisible] = reactExports.useState(true);
15990
+ const [petVisibilityStorageLoaded, setPetVisibilityStorageLoaded] = reactExports.useState(false);
15899
15991
  const [comparePair, setComparePair] = reactExports.useState(null);
15900
15992
  const [crabEntrancePhase, setCrabEntrancePhase] = reactExports.useState(
15901
15993
  "hidden"
@@ -15943,6 +16035,17 @@ function ProxyViewer({
15943
16035
  detailsPanelCollapsed ? "1" : "0"
15944
16036
  );
15945
16037
  }, [detailsPanelCollapsed, detailsPanelStorageLoaded]);
16038
+ reactExports.useEffect(() => {
16039
+ setPetVisible(readStoredInspectorPetVisible());
16040
+ setPetVisibilityStorageLoaded(true);
16041
+ }, []);
16042
+ reactExports.useEffect(() => {
16043
+ if (!petVisibilityStorageLoaded) return;
16044
+ try {
16045
+ window.localStorage.setItem(INSPECTOR_PET_VISIBLE_STORAGE_KEY, petVisible ? "1" : "0");
16046
+ } catch {
16047
+ }
16048
+ }, [petVisibilityStorageLoaded, petVisible]);
15946
16049
  reactExports.useEffect(() => {
15947
16050
  if (pinnedSessionId === void 0) {
15948
16051
  document.title = "Agent Inspector";
@@ -16060,6 +16163,9 @@ function ProxyViewer({
16060
16163
  const toggleDetailsPanelCollapsed = reactExports.useCallback(() => {
16061
16164
  setDetailsPanelCollapsed((value) => !value);
16062
16165
  }, []);
16166
+ const togglePetVisibility = reactExports.useCallback(() => {
16167
+ setPetVisible((value) => !value);
16168
+ }, []);
16063
16169
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
16064
16170
  const displayNumberByLogId = reactExports.useMemo(() => buildDisplayNumberByLogId(groups), [groups]);
16065
16171
  const sessionContextScope = reactExports.useMemo(
@@ -16138,7 +16244,7 @@ function ProxyViewer({
16138
16244
  ]
16139
16245
  }
16140
16246
  ),
16141
- detailsPanelCollapsed && /* @__PURE__ */ jsxRuntimeExports.jsx(InspectorPet, { logs }),
16247
+ petVisibilityStorageLoaded && petVisible && /* @__PURE__ */ jsxRuntimeExports.jsx(InspectorPet, { logs }),
16142
16248
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
16143
16249
  "div",
16144
16250
  {
@@ -16262,6 +16368,7 @@ function ProxyViewer({
16262
16368
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-foreground", children: "Logs" }),
16263
16369
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 font-mono text-[11px] text-muted-foreground", children: activeScopeLabel })
16264
16370
  ] }),
16371
+ /* @__PURE__ */ jsxRuntimeExports.jsx(PetVisibilityToggle, { visible: petVisible, onToggle: togglePetVisibility }),
16265
16372
  onImportLogs !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
16266
16373
  /* @__PURE__ */ jsxRuntimeExports.jsx(
16267
16374
  "input",