@tonyclaw/agent-inspector 3.0.41 → 3.0.43

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 (60) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-D7CEZjaX.js → CompareDrawer-BAXxwTwT.js} +1 -1
  3. package/.output/public/assets/{InspectorPet-CZC5A3d3.js → InspectorPet-xYuGV4Qz.js} +167 -167
  4. package/.output/public/assets/ProxyViewerContainer-B6GZnUWi.js +126 -0
  5. package/.output/public/assets/{ReplayDialog-O7zJrccK.js → ReplayDialog-D73pyIg3.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-DzJwNU_L.js → RequestAnatomy-CSDta_nu.js} +1 -1
  7. package/.output/public/assets/{ResponseView-NZExNRvM.js → ResponseView-C4aIUPby.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-DXcrVPst.js → StreamingChunkSequence-D4xxosLx.js} +1 -1
  9. package/.output/public/assets/{_sessionId-BOAAnQhC.js → _sessionId-CZHbJyWO.js} +1 -1
  10. package/.output/public/assets/{_sessionId-B5lQVwmL.js → _sessionId-DvuNLzSE.js} +1 -1
  11. package/.output/public/assets/{index-BlSm4bqn.js → index-B17u7JMC.js} +16 -22
  12. package/.output/public/assets/index-BWjeqxbk.css +1 -0
  13. package/.output/public/assets/{index-DswDeL6m.js → index-C0g79BAs.js} +1 -1
  14. package/.output/public/assets/{index-zdDXZ8fd.js → index-CmEO8ykj.js} +1 -1
  15. package/.output/public/assets/{index-IpdII72q.js → index-DZ_wpqQM.js} +1 -1
  16. package/.output/public/assets/{json-viewer-C_vPvztu.js → json-viewer-CuWU-H6K.js} +1 -1
  17. package/.output/public/assets/{jszip.min-BLbXONM_.js → jszip.min-DPJI_UKW.js} +1 -1
  18. package/.output/server/{_sessionId-BDR3VoGL.mjs → _sessionId-CoFyeOti.mjs} +2 -2
  19. package/.output/server/{_sessionId-XMJZJjKn.mjs → _sessionId-Cun21KZF.mjs} +1 -1
  20. package/.output/server/_ssr/{CompareDrawer-Dc10NRP-.mjs → CompareDrawer-Bgc2TZh_.mjs} +2 -2
  21. package/.output/server/_ssr/{InspectorPet-bAbBl9xU.mjs → InspectorPet-DyK2ojeh.mjs} +20 -10
  22. package/.output/server/_ssr/{ProxyViewerContainer-C833zSld.mjs → ProxyViewerContainer-hvn6oVWD.mjs} +326 -235
  23. package/.output/server/_ssr/{ReplayDialog-irE6GkCe.mjs → ReplayDialog-KpnrGtZ-.mjs} +3 -3
  24. package/.output/server/_ssr/{RequestAnatomy-CCbfr4bA.mjs → RequestAnatomy-uCsfucn8.mjs} +2 -2
  25. package/.output/server/_ssr/{ResponseView-TE4dZ2zB.mjs → ResponseView-C8UPqoPO.mjs} +2 -2
  26. package/.output/server/_ssr/{StreamingChunkSequence-C3-k9wrY.mjs → StreamingChunkSequence-BP28I9sh.mjs} +2 -2
  27. package/.output/server/_ssr/{index-CQt9ZYRA.mjs → index-BVwVoQlZ.mjs} +1 -1
  28. package/.output/server/_ssr/{index-CORKk6yP.mjs → index-Du3YNDKx.mjs} +2 -2
  29. package/.output/server/_ssr/index.mjs +2 -2
  30. package/.output/server/_ssr/{json-viewer-CPCoU-8e.mjs → json-viewer-DrE0vtlB.mjs} +2 -2
  31. package/.output/server/_ssr/{router-CzPRr1HX.mjs → router-C6nWUZHK.mjs} +18 -24
  32. package/.output/server/{_tanstack-start-manifest_v-BRb947T9.mjs → _tanstack-start-manifest_v-rshlRaiN.mjs} +1 -1
  33. package/.output/server/index.mjs +96 -96
  34. package/package.json +9 -4
  35. package/scripts/generate-theme-scales.mjs +22 -0
  36. package/src/components/ProxyViewer.tsx +217 -103
  37. package/src/components/alerts/AlertsDialog.tsx +17 -17
  38. package/src/components/ecosystem/AgentLabDialog.tsx +11 -4
  39. package/src/components/groups/GroupsDialog.tsx +41 -26
  40. package/src/components/inspector-pet/InspectorPet.tsx +9 -9
  41. package/src/components/inspector-pet/inspectorPetLogic.ts +15 -1
  42. package/src/components/pi-agent/PiAgentPanel.tsx +12 -35
  43. package/src/components/pi-agent/piAgentChatLogic.ts +0 -24
  44. package/src/components/providers/ImportWizardDialog.tsx +1 -1
  45. package/src/components/providers/ProviderCard.tsx +4 -4
  46. package/src/components/providers/ProviderForm.tsx +11 -7
  47. package/src/components/providers/ProviderTestResultsView.tsx +15 -17
  48. package/src/components/providers/ProvidersPanel.tsx +1 -1
  49. package/src/components/providers/SettingsDialog.tsx +11 -7
  50. package/src/components/proxy-viewer/LogEntryHeader.tsx +2 -2
  51. package/src/components/proxy-viewer/TurnGroup.tsx +1 -1
  52. package/src/components/ui/icon-trigger.ts +1 -1
  53. package/src/lib/themeMode.ts +1 -5
  54. package/src/lib/uiScale.ts +8 -3
  55. package/src/routes/__root.tsx +5 -10
  56. package/styles/globals.css +35 -143
  57. package/styles/theme-scales.css +50 -0
  58. package/styles/themes.css +157 -0
  59. package/.output/public/assets/ProxyViewerContainer-DoCpLJM1.js +0 -126
  60. package/.output/public/assets/index-DKwrwHsI.css +0 -1
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type ChangeEvent,
3
3
  type JSX,
4
+ type PointerEvent as ReactPointerEvent,
4
5
  type ReactNode,
5
6
  useCallback,
6
7
  useEffect,
@@ -189,16 +190,29 @@ function formatTimeRange(logs: CapturedLog[], timeDisplayFormat: TimeDisplayForm
189
190
  function slateMetricToneClass(tone: SessionSlateMetricTone): string {
190
191
  switch (tone) {
191
192
  case "cool":
192
- return "text-cyan-100";
193
+ return "text-status-cool";
193
194
  case "danger":
194
- return "text-red-100";
195
+ return "text-status-danger";
195
196
  case "watch":
196
- return "text-amber-100";
197
+ return "text-status-watch";
197
198
  case "neutral":
198
199
  return "text-foreground";
199
200
  }
200
201
  }
201
202
 
203
+ function statusMetricIcon(status: SessionSlateStatus): ReactNode {
204
+ switch (status) {
205
+ case "rolling":
206
+ return <RefreshCw className="size-3 text-status-cool" />;
207
+ case "failed":
208
+ return <Siren className="size-3 text-status-danger" />;
209
+ case "watch":
210
+ return <Timer className="size-3 text-status-watch" />;
211
+ case "clear":
212
+ return <Check className="size-3 text-status-success" />;
213
+ }
214
+ }
215
+
202
216
  function statusMetricTone(status: SessionSlateStatus): SessionSlateMetricTone {
203
217
  switch (status) {
204
218
  case "rolling":
@@ -232,7 +246,7 @@ function SessionSlateMetric({
232
246
  tone?: SessionSlateMetricTone;
233
247
  }): JSX.Element {
234
248
  return (
235
- <div className="min-w-0 border-t border-white/10 px-4 py-3">
249
+ <div className="min-w-0 border-t border-border/60 px-4 py-3">
236
250
  <div className="flex items-center gap-1 text-[10px] uppercase text-muted-foreground">
237
251
  {icon}
238
252
  {label}
@@ -249,7 +263,7 @@ function SessionOpeningSlate({ stats }: { stats: SessionSlateStats }): JSX.Eleme
249
263
  if (stats.requests === 0) return null;
250
264
 
251
265
  return (
252
- <div className="border border-border bg-card shadow-sm mb-4 overflow-hidden rounded-[8px]">
266
+ <div className="mb-4 overflow-hidden rounded-[8px] border border-border bg-card">
253
267
  <div className="px-4 py-3">
254
268
  <div className="flex flex-wrap items-end justify-between gap-3">
255
269
  <div className="min-w-0">
@@ -267,11 +281,12 @@ function SessionOpeningSlate({ stats }: { stats: SessionSlateStats }): JSX.Eleme
267
281
  </div>
268
282
  </div>
269
283
  </div>
270
- <div className="grid grid-cols-2 border-t border-white/10 md:grid-cols-3 2xl:grid-cols-6">
284
+ <div className="grid grid-cols-2 border-t border-border/60 md:grid-cols-3 2xl:grid-cols-6">
271
285
  <SessionSlateMetric
272
286
  label="Status"
273
287
  value={stats.statusLabel}
274
288
  detail={requestHealthDetail(stats)}
289
+ icon={statusMetricIcon(stats.status)}
275
290
  tone={statusMetricTone(stats.status)}
276
291
  />
277
292
  <SessionSlateMetric
@@ -770,14 +785,9 @@ function SidebarPanel({
770
785
  className?: string;
771
786
  }): JSX.Element {
772
787
  return (
773
- <section
774
- className={cn(
775
- "bg-white/[0.028] shadow-[inset_0_1px_0_rgba(255,255,255,0.035)] rounded-[8px]",
776
- className,
777
- )}
778
- >
788
+ <section className={cn("rounded-[8px] border border-border/60 bg-card/55", className)}>
779
789
  <div className="px-3 pt-3">
780
- <div className="font-mono text-[10px] font-semibold uppercase tracking-[0.14em] text-white/35">
790
+ <div className="font-mono text-[10px] font-semibold uppercase tracking-[0.14em] text-text-subtle">
781
791
  {label}
782
792
  </div>
783
793
  </div>
@@ -788,11 +798,11 @@ function SidebarPanel({
788
798
 
789
799
  function SidebarMetric({ label, value }: { label: string; value: string | number }): JSX.Element {
790
800
  return (
791
- <div className="bg-black/20 min-w-0 rounded-md px-2 py-2 shadow-[inset_0_1px_0_rgba(255,255,255,0.035)]">
792
- <div className="truncate font-mono text-lg font-semibold leading-6 text-slate-100">
801
+ <div className="min-w-0 rounded-md border border-border/50 bg-muted/30 px-2 py-2">
802
+ <div className="truncate font-mono text-lg font-semibold leading-6 text-text-strong">
793
803
  {value}
794
804
  </div>
795
- <div className="mt-0.5 truncate font-mono text-[10px] uppercase tracking-[0.12em] text-white/35">
805
+ <div className="mt-0.5 truncate font-mono text-[10px] uppercase tracking-[0.12em] text-text-subtle">
796
806
  {label}
797
807
  </div>
798
808
  </div>
@@ -804,7 +814,7 @@ function ThemeModeToggle(): JSX.Element {
804
814
 
805
815
  return (
806
816
  <div
807
- className="border-border/70 bg-muted/70 inline-flex h-8 items-center rounded-md border p-0.5 shadow-sm"
817
+ className="inline-flex h-9 items-center rounded-md border border-border/70 bg-muted/70 p-0.5"
808
818
  aria-label="Theme mode"
809
819
  role="group"
810
820
  >
@@ -812,7 +822,7 @@ function ThemeModeToggle(): JSX.Element {
812
822
  type="button"
813
823
  onClick={() => setThemeMode("light")}
814
824
  className={cn(
815
- "inline-flex size-7 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
825
+ "inline-flex size-8 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none max-lg:size-11",
816
826
  themeMode === "light" && "bg-background text-foreground shadow-sm",
817
827
  )}
818
828
  aria-label="Use light mode"
@@ -825,7 +835,7 @@ function ThemeModeToggle(): JSX.Element {
825
835
  type="button"
826
836
  onClick={() => setThemeMode("eye-care")}
827
837
  className={cn(
828
- "inline-flex size-7 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
838
+ "inline-flex size-8 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none max-lg:size-11",
829
839
  themeMode === "eye-care" && "bg-background text-foreground shadow-sm",
830
840
  )}
831
841
  aria-label="Use eye care mode"
@@ -838,7 +848,7 @@ function ThemeModeToggle(): JSX.Element {
838
848
  type="button"
839
849
  onClick={() => setThemeMode("dark")}
840
850
  className={cn(
841
- "inline-flex size-7 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
851
+ "inline-flex size-8 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none max-lg:size-11",
842
852
  themeMode === "dark" && "bg-background text-foreground shadow-sm",
843
853
  )}
844
854
  aria-label="Use dark mode"
@@ -896,7 +906,7 @@ function UiScaleControl({
896
906
  >
897
907
  <SelectTrigger
898
908
  size="sm"
899
- className="bg-white/[0.035] hover:bg-white/[0.065] h-8 min-w-[4rem] justify-center gap-1 border-0 px-2 py-0 text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] [&>svg:last-child]:hidden"
909
+ className="h-9 min-w-[4.5rem] justify-center gap-1 border-border/70 bg-muted/70 px-2 py-0 text-muted-foreground hover:bg-muted max-lg:h-11 [&>svg:last-child]:hidden"
900
910
  aria-label={`Interface size: ${currentLabel}`}
901
911
  >
902
912
  <ALargeSmall className="size-3.5" />
@@ -913,12 +923,42 @@ function UiScaleControl({
913
923
  }
914
924
 
915
925
  const DETAILS_PANEL_COLLAPSED_STORAGE_KEY = "agent-inspector.detailsPanelCollapsed";
926
+ const DETAILS_PANEL_WIDTH_STORAGE_KEY = "agent-inspector.detailsPanelWidth";
916
927
  const INSPECTOR_PET_VISIBLE_STORAGE_KEY = "agent-inspector.pet.visible";
928
+ const DETAILS_PANEL_DEFAULT_WIDTH = 360;
929
+ const DETAILS_PANEL_MIN_WIDTH = 288;
930
+ const DETAILS_PANEL_MAX_WIDTH = 520;
931
+
932
+ type DetailsPanelResizeState = {
933
+ startX: number;
934
+ startWidth: number;
935
+ pointerId: number;
936
+ };
937
+
938
+ function clampDetailsPanelWidth(width: number): number {
939
+ return Math.min(Math.max(width, DETAILS_PANEL_MIN_WIDTH), DETAILS_PANEL_MAX_WIDTH);
940
+ }
917
941
 
918
942
  function readStoredDetailsPanelCollapsed(): boolean {
919
943
  if (typeof window === "undefined") return true;
920
- const stored = window.localStorage.getItem(DETAILS_PANEL_COLLAPSED_STORAGE_KEY);
921
- return stored === null || stored === "1";
944
+ try {
945
+ const stored = window.localStorage.getItem(DETAILS_PANEL_COLLAPSED_STORAGE_KEY);
946
+ return stored === null || stored === "1";
947
+ } catch {
948
+ return true;
949
+ }
950
+ }
951
+
952
+ function readStoredDetailsPanelWidth(): number {
953
+ if (typeof window === "undefined") return DETAILS_PANEL_DEFAULT_WIDTH;
954
+ try {
955
+ const stored = window.localStorage.getItem(DETAILS_PANEL_WIDTH_STORAGE_KEY);
956
+ if (stored === null) return DETAILS_PANEL_DEFAULT_WIDTH;
957
+ const parsed = Number.parseInt(stored, 10);
958
+ return Number.isFinite(parsed) ? clampDetailsPanelWidth(parsed) : DETAILS_PANEL_DEFAULT_WIDTH;
959
+ } catch {
960
+ return DETAILS_PANEL_DEFAULT_WIDTH;
961
+ }
922
962
  }
923
963
 
924
964
  function readStoredInspectorPetVisible(): boolean {
@@ -1013,7 +1053,7 @@ function SessionModelContextRow({ summary }: { summary: SessionModelContextSumma
1013
1053
  <button
1014
1054
  type="button"
1015
1055
  onClick={openLatestContext}
1016
- className="min-w-0 truncate rounded-sm text-left font-mono font-semibold text-foreground transition-colors hover:text-cyan-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
1056
+ className="min-w-0 truncate rounded-sm text-left font-mono font-semibold text-foreground transition-colors hover:text-text-link focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
1017
1057
  title={`Open latest Context for ${summary.model}`}
1018
1058
  >
1019
1059
  {summary.model}
@@ -1328,6 +1368,8 @@ export function ProxyViewer({
1328
1368
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
1329
1369
  const [deletingCurrentView, setDeletingCurrentView] = useState(false);
1330
1370
  const [detailsPanelCollapsed, setDetailsPanelCollapsed] = useState(true);
1371
+ const [detailsPanelWidth, setDetailsPanelWidth] = useState(DETAILS_PANEL_DEFAULT_WIDTH);
1372
+ const [detailsPanelResizing, setDetailsPanelResizing] = useState(false);
1331
1373
  const [detailsPanelStorageLoaded, setDetailsPanelStorageLoaded] = useState(false);
1332
1374
  const [petVisible, setPetVisible] = useState(true);
1333
1375
  const [petVisibilityStorageLoaded, setPetVisibilityStorageLoaded] = useState(false);
@@ -1340,6 +1382,7 @@ export function ProxyViewer({
1340
1382
  const importInputRef = useRef<HTMLInputElement>(null);
1341
1383
  const exportAbortRef = useRef<AbortController | null>(null);
1342
1384
  const exportRequestIdRef = useRef(0);
1385
+ const detailsPanelResizeRef = useRef<DetailsPanelResizeState | null>(null);
1343
1386
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
1344
1387
  const proxyEndpoint = useMemo(() => {
1345
1388
  const origin = typeof window === "undefined" ? "http://localhost:9527" : window.location.origin;
@@ -1365,6 +1408,7 @@ export function ProxyViewer({
1365
1408
 
1366
1409
  useEffect(() => {
1367
1410
  setDetailsPanelCollapsed(readStoredDetailsPanelCollapsed());
1411
+ setDetailsPanelWidth(readStoredDetailsPanelWidth());
1368
1412
  setDetailsPanelStorageLoaded(true);
1369
1413
  }, []);
1370
1414
 
@@ -1376,6 +1420,15 @@ export function ProxyViewer({
1376
1420
  );
1377
1421
  }, [detailsPanelCollapsed, detailsPanelStorageLoaded]);
1378
1422
 
1423
+ useEffect(() => {
1424
+ if (!detailsPanelStorageLoaded) return;
1425
+ try {
1426
+ window.localStorage.setItem(DETAILS_PANEL_WIDTH_STORAGE_KEY, String(detailsPanelWidth));
1427
+ } catch {
1428
+ // Local storage can be unavailable in hardened browser profiles.
1429
+ }
1430
+ }, [detailsPanelStorageLoaded, detailsPanelWidth]);
1431
+
1379
1432
  useEffect(() => {
1380
1433
  setPetVisible(readStoredInspectorPetVisible());
1381
1434
  setPetVisibilityStorageLoaded(true);
@@ -1545,6 +1598,40 @@ export function ProxyViewer({
1545
1598
  const toggleDetailsPanelCollapsed = useCallback(() => {
1546
1599
  setDetailsPanelCollapsed((value) => !value);
1547
1600
  }, []);
1601
+ const handleDetailsPanelResizeStart = useCallback(
1602
+ (event: ReactPointerEvent<HTMLButtonElement>) => {
1603
+ detailsPanelResizeRef.current = {
1604
+ startX: event.clientX,
1605
+ startWidth: detailsPanelWidth,
1606
+ pointerId: event.pointerId,
1607
+ };
1608
+ event.currentTarget.setPointerCapture(event.pointerId);
1609
+ setDetailsPanelResizing(true);
1610
+ },
1611
+ [detailsPanelWidth],
1612
+ );
1613
+ const handleDetailsPanelResizeMove = useCallback(
1614
+ (event: ReactPointerEvent<HTMLButtonElement>) => {
1615
+ const resize = detailsPanelResizeRef.current;
1616
+ if (resize === null || resize.pointerId !== event.pointerId) return;
1617
+ setDetailsPanelWidth(
1618
+ clampDetailsPanelWidth(resize.startWidth + resize.startX - event.clientX),
1619
+ );
1620
+ },
1621
+ [],
1622
+ );
1623
+ const handleDetailsPanelResizeEnd = useCallback((event: ReactPointerEvent<HTMLButtonElement>) => {
1624
+ const resize = detailsPanelResizeRef.current;
1625
+ if (resize === null || resize.pointerId !== event.pointerId) return;
1626
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
1627
+ event.currentTarget.releasePointerCapture(event.pointerId);
1628
+ }
1629
+ detailsPanelResizeRef.current = null;
1630
+ setDetailsPanelResizing(false);
1631
+ }, []);
1632
+ const resetDetailsPanelWidth = useCallback(() => {
1633
+ setDetailsPanelWidth(DETAILS_PANEL_DEFAULT_WIDTH);
1634
+ }, []);
1548
1635
  const togglePetVisibility = useCallback(() => {
1549
1636
  setPetVisible((value) => !value);
1550
1637
  }, []);
@@ -1633,15 +1720,12 @@ export function ProxyViewer({
1633
1720
  type="button"
1634
1721
  onClick={toggleDetailsPanelCollapsed}
1635
1722
  className={cn(
1636
- "group fixed top-1/2 right-3 z-40 inline-flex h-14 w-7 -translate-y-1/2 items-center justify-center rounded-l-md border border-white/[0.08] bg-background/90 text-muted-foreground shadow-[0_12px_34px_rgba(0,0,0,0.26),inset_0_1px_0_rgba(255,255,255,0.06)] backdrop-blur transition-[right,background-color,color,border-color] duration-200 motion-reduce:transition-none hover:border-white/[0.16] hover:bg-white/[0.07] hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
1637
- detailsPanelCollapsed
1638
- ? "right-3"
1639
- : "lg:right-[calc(21.25rem-0.875rem)] xl:right-[calc(22.5rem-0.875rem)]",
1723
+ "group fixed top-1/2 right-3 z-50 inline-flex h-14 w-8 -translate-y-1/2 items-center justify-center rounded-l-md border border-border/70 bg-background/90 text-muted-foreground shadow-lg backdrop-blur transition-colors motion-reduce:transition-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
1640
1724
  )}
1641
1725
  aria-label={detailsPanelCollapsed ? "Show details panel" : "Hide details panel"}
1642
1726
  title={detailsPanelCollapsed ? "Show details panel" : "Hide details panel"}
1643
1727
  >
1644
- <span className="absolute left-1 top-2 bottom-2 w-px rounded-full bg-white/[0.16] transition-colors group-hover:bg-white/[0.28]" />
1728
+ <span className="absolute bottom-2 left-1 top-2 w-px rounded-full bg-border transition-colors group-hover:bg-foreground/35" />
1645
1729
  {detailsPanelCollapsed ? (
1646
1730
  <PanelRightOpen className="size-3.5" />
1647
1731
  ) : (
@@ -1657,94 +1741,124 @@ export function ProxyViewer({
1657
1741
  )}
1658
1742
  <div
1659
1743
  className={cn(
1660
- "grid w-full min-w-0 grid-cols-1 lg:min-h-0 lg:flex-1 lg:overflow-hidden",
1661
- detailsPanelCollapsed
1662
- ? "lg:grid-cols-[minmax(0,1fr)]"
1663
- : "lg:grid-cols-[minmax(0,1fr)_21.25rem] xl:grid-cols-[minmax(0,1fr)_22.5rem]",
1744
+ "flex w-full min-w-0 flex-col lg:min-h-0 lg:flex-1 lg:flex-row lg:overflow-hidden",
1745
+ detailsPanelResizing && "cursor-col-resize select-none",
1664
1746
  )}
1665
1747
  >
1666
1748
  {!detailsPanelCollapsed && (
1667
- <aside className="bg-background/95 order-1 border-b border-white/[0.045] lg:order-2 lg:h-full lg:overflow-y-auto lg:border-b-0 lg:border-l lg:border-white/[0.055] inspector-scrollbar">
1668
- <div className="flex min-h-full flex-col gap-4 p-4">
1669
- <div className="bg-background/95 sticky top-0 z-20 -mx-4 -mt-4 px-4 pt-4 pb-3 shadow-[0_12px_24px_rgba(0,0,0,0.18),inset_0_-1px_0_rgba(255,255,255,0.045)] backdrop-blur">
1670
- <SidebarPanel label="Tools">
1671
- <div className="flex flex-wrap items-center gap-1.5">
1672
- <ThemeModeToggle />
1673
- <UiScaleControl
1674
- preference={uiScalePreference}
1675
- resolvedScale={resolvedUiScale}
1676
- onChange={setUiScalePreference}
1749
+ <>
1750
+ <button
1751
+ type="button"
1752
+ className="fixed inset-0 z-30 bg-background/70 backdrop-blur-sm lg:hidden"
1753
+ onClick={toggleDetailsPanelCollapsed}
1754
+ aria-label="Close details panel"
1755
+ />
1756
+ <aside
1757
+ className="inspector-scrollbar fixed inset-x-3 bottom-3 top-24 z-40 order-1 overflow-y-auto rounded-lg border border-border bg-background/98 shadow-2xl max-lg:!w-auto lg:relative lg:inset-auto lg:order-2 lg:h-full lg:shrink-0 lg:rounded-none lg:border-y-0 lg:border-r-0 lg:shadow-none"
1758
+ style={{ width: detailsPanelWidth }}
1759
+ >
1760
+ <button
1761
+ type="button"
1762
+ className="absolute bottom-0 left-0 top-0 z-30 hidden w-2 -translate-x-1/2 cursor-col-resize touch-none items-center justify-center lg:flex"
1763
+ onPointerDown={handleDetailsPanelResizeStart}
1764
+ onPointerMove={handleDetailsPanelResizeMove}
1765
+ onPointerUp={handleDetailsPanelResizeEnd}
1766
+ onPointerCancel={handleDetailsPanelResizeEnd}
1767
+ onDoubleClick={resetDetailsPanelWidth}
1768
+ aria-label="Resize details panel"
1769
+ title="Drag to resize; double-click to reset"
1770
+ >
1771
+ <span className="h-12 w-1 rounded-full bg-border transition-colors hover:bg-primary" />
1772
+ </button>
1773
+ <div className="flex min-h-full flex-col gap-4 p-4">
1774
+ <div className="sticky top-0 z-20 -mx-4 -mt-4 grid gap-2 border-b border-border/60 bg-background/95 px-4 pb-3 pt-4 backdrop-blur">
1775
+ <SidebarPanel label="View">
1776
+ <div className="flex flex-wrap items-center gap-1.5">
1777
+ <ThemeModeToggle />
1778
+ <UiScaleControl
1779
+ preference={uiScalePreference}
1780
+ resolvedScale={resolvedUiScale}
1781
+ onChange={setUiScalePreference}
1782
+ />
1783
+ </div>
1784
+ </SidebarPanel>
1785
+ <SidebarPanel label="Workspace">
1786
+ <div className="flex flex-wrap items-center gap-2">
1787
+ <GroupsDialog labelledTrigger />
1788
+ <AgentLabDialog
1789
+ currentSessionId={labSessionId}
1790
+ logCount={logs.length}
1791
+ labelledTrigger
1792
+ />
1793
+ </div>
1794
+ </SidebarPanel>
1795
+ </div>
1796
+
1797
+ <SidebarPanel label="Current scope">
1798
+ <div className="mb-2 rounded-md border border-border/50 bg-muted/25 px-3 py-2">
1799
+ <div
1800
+ className="truncate font-mono text-xs font-semibold text-text-link"
1801
+ title={activeScopeLabel}
1802
+ >
1803
+ {activeScopeLabel}
1804
+ </div>
1805
+ <div className="mt-1 text-[11px] text-muted-foreground">
1806
+ {isPinnedSessionPage ? "Pinned session" : "Live Inspector data"}
1807
+ </div>
1808
+ </div>
1809
+ <div className="grid grid-cols-2 gap-2">
1810
+ <SidebarMetric
1811
+ label={assistantSearchResult === null ? "Requests" : "Results"}
1812
+ value={displayedLogs.length}
1813
+ />
1814
+ <SidebarMetric label="Sessions" value={sessions.length} />
1815
+ <SidebarMetric
1816
+ label="Input"
1817
+ value={formatTokens(hasSessionContext ? sessionContextTotalIn : totalIn)}
1818
+ />
1819
+ <SidebarMetric
1820
+ label="Output"
1821
+ value={formatTokens(hasSessionContext ? sessionContextTotalOut : totalOut)}
1677
1822
  />
1678
- <GroupsDialog />
1679
- <AgentLabDialog currentSessionId={labSessionId} logCount={logs.length} />
1680
1823
  </div>
1824
+ {sessionLoadProgress !== undefined && (
1825
+ <div className="mt-2 rounded-md border border-border/50 bg-muted/25 px-2 py-2">
1826
+ <SessionLoadProgressBar progress={sessionLoadProgress} />
1827
+ </div>
1828
+ )}
1681
1829
  </SidebarPanel>
1682
- </div>
1683
1830
 
1684
- <SidebarPanel label="Current scope">
1685
- <div className="mb-2 rounded-md bg-black/20 px-3 py-2 shadow-[inset_0_1px_0_rgba(255,255,255,0.035)]">
1686
- <div
1687
- className="truncate font-mono text-xs font-semibold text-cyan-100"
1688
- title={activeScopeLabel}
1689
- >
1690
- {activeScopeLabel}
1691
- </div>
1692
- <div className="mt-1 text-[11px] text-muted-foreground">
1693
- {isPinnedSessionPage ? "Pinned session" : "Live Inspector data"}
1694
- </div>
1695
- </div>
1696
- <div className="grid grid-cols-2 gap-2">
1697
- <SidebarMetric
1698
- label={assistantSearchResult === null ? "Requests" : "Results"}
1699
- value={displayedLogs.length}
1700
- />
1701
- <SidebarMetric label="Sessions" value={sessions.length} />
1702
- <SidebarMetric
1703
- label="Input"
1704
- value={formatTokens(hasSessionContext ? sessionContextTotalIn : totalIn)}
1705
- />
1706
- <SidebarMetric
1707
- label="Output"
1708
- value={formatTokens(hasSessionContext ? sessionContextTotalOut : totalOut)}
1831
+ {hasSessionContext && sessionContextScope !== null && (
1832
+ <SessionContextBar
1833
+ label={sessionContextScope.label}
1834
+ logs={sessionContextLogs}
1835
+ totalIn={sessionContextTotalIn}
1836
+ totalOut={sessionContextTotalOut}
1837
+ timeDisplayFormat={timeDisplayFormat}
1838
+ contextSummary={sessionContextSummary}
1839
+ showBackLink={sessionContextScope.showBackLink}
1709
1840
  />
1710
- </div>
1711
- {sessionLoadProgress !== undefined && (
1712
- <div className="mt-2 rounded-md bg-black/20 px-2 py-2 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]">
1713
- <SessionLoadProgressBar progress={sessionLoadProgress} />
1714
- </div>
1715
1841
  )}
1716
- </SidebarPanel>
1717
-
1718
- {hasSessionContext && sessionContextScope !== null && (
1719
- <SessionContextBar
1720
- label={sessionContextScope.label}
1721
- logs={sessionContextLogs}
1722
- totalIn={sessionContextTotalIn}
1723
- totalOut={sessionContextTotalOut}
1724
- timeDisplayFormat={timeDisplayFormat}
1725
- contextSummary={sessionContextSummary}
1726
- showBackLink={sessionContextScope.showBackLink}
1727
- />
1728
- )}
1729
1842
 
1730
- {hasSessionContext &&
1731
- pinnedSessionId !== undefined &&
1732
- sessionMemberships.length > 0 && (
1733
- <SessionMembershipPanel memberships={sessionMemberships} />
1734
- )}
1843
+ {hasSessionContext &&
1844
+ pinnedSessionId !== undefined &&
1845
+ sessionMemberships.length > 0 && (
1846
+ <SessionMembershipPanel memberships={sessionMemberships} />
1847
+ )}
1735
1848
 
1736
- <div className="flex-1" />
1849
+ <div className="flex-1" />
1737
1850
 
1738
- <PiAgentPanel
1739
- currentSessionId={labSessionId}
1740
- logCount={logs.length}
1741
- onResultChange={setAssistantSearchResult}
1742
- />
1743
- </div>
1744
- </aside>
1851
+ <PiAgentPanel
1852
+ currentSessionId={labSessionId}
1853
+ logCount={logs.length}
1854
+ onResultChange={setAssistantSearchResult}
1855
+ />
1856
+ </div>
1857
+ </aside>
1858
+ </>
1745
1859
  )}
1746
1860
 
1747
- <main className="order-2 min-w-0 lg:order-1 lg:h-full lg:overflow-y-auto inspector-scrollbar">
1861
+ <main className="inspector-scrollbar order-2 min-w-0 lg:order-1 lg:h-full lg:flex-1 lg:overflow-y-auto">
1748
1862
  <div className="bg-background/95 shadow-[0_12px_36px_rgba(0,0,0,0.18),inset_0_-1px_0_rgba(255,255,255,0.045)] sticky top-0 z-30 px-4 py-3 lg:px-5">
1749
1863
  <div className="flex flex-wrap items-center gap-2">
1750
1864
  <div className="min-w-[180px] flex-1">
@@ -74,33 +74,33 @@ function categoryLabel(category: AlertCategory): string {
74
74
  function severityAccent(severity: AlertSeverity): string {
75
75
  switch (severity) {
76
76
  case "critical":
77
- return "border-red-400/40 bg-red-500/12 text-red-100";
77
+ return "border-status-danger/40 bg-status-danger/10 text-status-danger";
78
78
  case "warning":
79
- return "border-amber-300/35 bg-amber-400/12 text-amber-100";
79
+ return "border-status-watch/40 bg-status-watch/10 text-status-watch";
80
80
  case "notice":
81
- return "border-sky-300/35 bg-sky-400/12 text-sky-100";
81
+ return "border-status-cool/40 bg-status-cool/10 text-status-cool";
82
82
  }
83
83
  }
84
84
 
85
85
  function severityDot(severity: AlertSeverity): string {
86
86
  switch (severity) {
87
87
  case "critical":
88
- return "bg-red-400";
88
+ return "bg-status-danger";
89
89
  case "warning":
90
- return "bg-amber-300";
90
+ return "bg-status-watch";
91
91
  case "notice":
92
- return "bg-sky-300";
92
+ return "bg-status-cool";
93
93
  }
94
94
  }
95
95
 
96
96
  function severityText(severity: AlertSeverity): string {
97
97
  switch (severity) {
98
98
  case "critical":
99
- return "text-red-200";
99
+ return "text-status-danger";
100
100
  case "warning":
101
- return "text-amber-100";
101
+ return "text-status-watch";
102
102
  case "notice":
103
- return "text-sky-100";
103
+ return "text-status-cool";
104
104
  }
105
105
  }
106
106
 
@@ -169,11 +169,11 @@ function AlertSummaryPill({
169
169
  }): JSX.Element {
170
170
  const toneClass =
171
171
  tone === "critical"
172
- ? "border-red-400/30 bg-red-500/10 text-red-100"
172
+ ? "border-status-danger/30 bg-status-danger/10 text-status-danger"
173
173
  : tone === "warning"
174
- ? "border-amber-300/30 bg-amber-400/10 text-amber-100"
174
+ ? "border-status-watch/30 bg-status-watch/10 text-status-watch"
175
175
  : tone === "notice"
176
- ? "border-sky-300/30 bg-sky-400/10 text-sky-100"
176
+ ? "border-status-cool/30 bg-status-cool/10 text-status-cool"
177
177
  : "border-border bg-muted/40 text-foreground";
178
178
 
179
179
  return (
@@ -263,7 +263,7 @@ function AlertList({
263
263
 
264
264
  if (error !== undefined) {
265
265
  return (
266
- <div className="rounded-md border border-red-300/30 bg-red-500/10 px-3 py-2 text-xs text-red-100">
266
+ <div className="rounded-md border border-status-danger/30 bg-status-danger/10 px-3 py-2 text-xs text-status-danger">
267
267
  {error.message}
268
268
  </div>
269
269
  );
@@ -470,10 +470,10 @@ export function AlertsDialog(): JSX.Element {
470
470
  const noticeCount = summary?.notice ?? 0;
471
471
  const triggerTone =
472
472
  criticalCount > 0
473
- ? "bg-red-600 text-white"
473
+ ? "bg-status-danger text-primary-foreground"
474
474
  : warningCount > 0
475
- ? "bg-amber-400 text-zinc-950"
476
- : "bg-sky-400 text-zinc-950";
475
+ ? "bg-status-watch text-primary-foreground"
476
+ : "bg-status-cool text-primary-foreground";
477
477
 
478
478
  return (
479
479
  <Dialog open={open} onOpenChange={setOpen}>
@@ -505,7 +505,7 @@ export function AlertsDialog(): JSX.Element {
505
505
  <DialogContent className="flex max-h-[86vh] max-w-6xl flex-col overflow-hidden">
506
506
  <DialogHeader>
507
507
  <DialogTitle className="flex items-center gap-2 pr-8">
508
- <Siren className="size-5 text-red-300" />
508
+ <Siren className="size-5 text-status-danger" />
509
509
  <span>Alerts</span>
510
510
  <Badge variant="outline">{formatCount(totalCount)}</Badge>
511
511
  </DialogTitle>
@@ -621,7 +621,11 @@ function ExecutionConfirmationDialog({
621
621
  );
622
622
  }
623
623
 
624
- export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogProps): JSX.Element {
624
+ export function AgentLabDialog({
625
+ currentSessionId,
626
+ logCount,
627
+ labelledTrigger = false,
628
+ }: AgentLabDialogProps & { labelledTrigger?: boolean }): JSX.Element {
625
629
  const [open, setOpen] = useState(false);
626
630
  const [copied, setCopied] = useState(false);
627
631
  const [copiedPresets, setCopiedPresets] = useState(false);
@@ -737,13 +741,16 @@ export function AgentLabDialog({ currentSessionId, logCount }: AgentLabDialogPro
737
741
  <DialogTrigger asChild>
738
742
  <Button
739
743
  variant="ghost"
740
- size="icon"
741
- className={INSPECTOR_ICON_TRIGGER_CLASS}
744
+ size={labelledTrigger ? "default" : "icon"}
745
+ className={cn(
746
+ INSPECTOR_ICON_TRIGGER_CLASS,
747
+ labelledTrigger && "w-auto gap-2 px-3 text-xs max-lg:h-11 max-lg:w-auto",
748
+ )}
742
749
  aria-label="TonyClaw Lab"
743
750
  title="TonyClaw Lab"
744
751
  >
745
752
  <FlaskConical className="size-4" />
746
- <span className="sr-only">TonyClaw Lab</span>
753
+ <span className={labelledTrigger ? "" : "sr-only"}>Lab</span>
747
754
  </Button>
748
755
  </DialogTrigger>
749
756
  <DialogContent className="flex max-h-[84vh] max-w-5xl flex-col overflow-hidden">