@tonyclaw/agent-inspector 2.1.15 → 2.1.16

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 (65) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-Ccrrcx1j.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +106 -0
  4. package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-C7axhr-l.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-WOptg8j_.js} +1 -1
  6. package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-CSAcxh_M.js} +2 -2
  7. package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-DJS5KhPx.js} +1 -1
  8. package/.output/public/assets/_sessionId-DUSalzKH.js +1 -0
  9. package/.output/public/assets/index-BPpA21dY.css +1 -0
  10. package/.output/public/assets/index-Bt0Az2I2.js +1 -0
  11. package/.output/public/assets/{index-DjKt8XKe.js → index-DMPNh46t.js} +1 -1
  12. package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-inX5QSa3.js} +1 -1
  13. package/.output/public/assets/{main-DpD1N0S8.js → main-aCAKYGSD.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +207 -171
  15. package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-C0fhvQnn.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-CO-Ti5sg.mjs} +3 -3
  17. package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-N4J7uBCX.mjs} +807 -386
  18. package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-J1VxC0In.mjs} +4 -4
  19. package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-DBH-F3rq.mjs} +3 -3
  20. package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-DLFwV7Hd.mjs} +3 -3
  21. package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-BywmuND0.mjs} +3 -3
  22. package/.output/server/_ssr/{index-47XVPghS.mjs → index-CStymIc0.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-DlQu0N1x.mjs} +3 -3
  25. package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-CQ4Zxhmr.mjs} +112 -46
  26. package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-BHeJBGfo.mjs} +1 -1
  27. package/.output/server/index.mjs +72 -72
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +146 -105
  30. package/src/components/ProxyViewerContainer.tsx +77 -67
  31. package/src/components/alerts/AlertsDialog.tsx +2 -1
  32. package/src/components/clients/ClientLogo.tsx +1 -1
  33. package/src/components/groups/GroupsDialog.tsx +19 -1
  34. package/src/components/providers/ProviderCard.tsx +1 -1
  35. package/src/components/providers/ProviderForm.tsx +18 -18
  36. package/src/components/providers/ProviderLogo.tsx +6 -1
  37. package/src/components/providers/ProvidersPanel.tsx +6 -6
  38. package/src/components/providers/SettingsDialog.tsx +13 -6
  39. package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
  40. package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
  41. package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
  42. package/src/components/proxy-viewer/ConversationHeader.tsx +44 -32
  43. package/src/components/proxy-viewer/LogEntry.tsx +21 -17
  44. package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -45
  45. package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
  46. package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
  47. package/src/components/proxy-viewer/ThreadConnector.tsx +4 -4
  48. package/src/components/proxy-viewer/TurnGroup.tsx +16 -11
  49. package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
  50. package/src/components/proxy-viewer/viewerState.ts +0 -4
  51. package/src/components/ui/icon-trigger.ts +2 -0
  52. package/src/components/ui/transient-toast.tsx +104 -0
  53. package/src/lib/sessionInfoContract.ts +4 -0
  54. package/src/mcp/server.ts +71 -27
  55. package/src/mcp/toolHandlers.ts +23 -4
  56. package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
  57. package/src/proxy/sessionInfo.ts +31 -2
  58. package/src/proxy/sessionSupervisor.ts +20 -1
  59. package/src/proxy/store.ts +1 -1
  60. package/src/routes/api/providers.$providerId.test.log.ts +8 -4
  61. package/styles/globals.css +180 -180
  62. package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
  63. package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
  64. package/.output/public/assets/index-CI1-G8ua.js +0 -1
  65. package/.output/public/assets/index-DdhFqPsI.css +0 -1
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type ChangeEvent,
3
3
  type JSX,
4
+ type ReactNode,
4
5
  useCallback,
5
6
  useEffect,
6
7
  useMemo,
@@ -47,6 +48,7 @@ import { crabVariants } from "./ui/crab-variants";
47
48
  import { McpLogo } from "./ui/mcp-logo";
48
49
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
49
50
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
51
+ import type { TransientToastInput } from "./ui/transient-toast";
50
52
  import { AlertsDialog } from "./alerts/AlertsDialog";
51
53
  import { GroupsDialog } from "./groups/GroupsDialog";
52
54
  import { SettingsDialog } from "./providers/SettingsDialog";
@@ -266,7 +268,7 @@ function slateStatusClass(status: SessionSlateStatus): string {
266
268
  case "watch":
267
269
  return "border-amber-300/35 bg-amber-400/10 text-amber-100";
268
270
  case "clear":
269
- return "border-emerald-400/30 bg-emerald-500/10 text-emerald-100";
271
+ return "border-slate-300/20 bg-slate-300/[0.08] text-slate-100";
270
272
  }
271
273
  }
272
274
 
@@ -274,9 +276,9 @@ function SessionOpeningSlate({ stats }: { stats: SessionSlateStats }): JSX.Eleme
274
276
  if (stats.requests === 0) return null;
275
277
 
276
278
  return (
277
- <div className="mb-4 rounded-md border border-border bg-[#0c0d0f] shadow-[0_14px_50px_rgba(0,0,0,0.25)]">
279
+ <div className="border border-border bg-card shadow-sm mb-4 rounded-[8px]">
278
280
  <div className="grid grid-cols-2 gap-0 md:grid-cols-[minmax(220px,1.2fr)_repeat(4,minmax(120px,1fr))]">
279
- <div className="col-span-2 border-b border-border/70 px-4 py-3 md:col-span-1 md:border-b-0 md:border-r">
281
+ <div className="col-span-2 border-b border-white/10 px-4 py-3 md:col-span-1 md:border-b-0 md:border-r">
280
282
  <div className="flex items-center gap-2 text-[10px] font-semibold uppercase text-muted-foreground">
281
283
  <Clapperboard className="size-3.5 text-cyan-300" />
282
284
  Opening Slate
@@ -288,7 +290,7 @@ function SessionOpeningSlate({ stats }: { stats: SessionSlateStats }): JSX.Eleme
288
290
  {stats.timeRange ?? "Timeline pending"}
289
291
  </div>
290
292
  </div>
291
- <div className="border-r border-b border-border/70 px-4 py-3 md:border-b-0">
293
+ <div className="border-r border-b border-white/10 px-4 py-3 md:border-b-0">
292
294
  <div className="text-[10px] uppercase text-muted-foreground">Status</div>
293
295
  <div
294
296
  className={cn(
@@ -299,13 +301,13 @@ function SessionOpeningSlate({ stats }: { stats: SessionSlateStats }): JSX.Eleme
299
301
  {stats.statusLabel}
300
302
  </div>
301
303
  </div>
302
- <div className="border-b border-border/70 px-4 py-3 md:border-b-0 md:border-r">
304
+ <div className="border-b border-white/10 px-4 py-3 md:border-b-0 md:border-r">
303
305
  <div className="text-[10px] uppercase text-muted-foreground">Requests</div>
304
306
  <div className="mt-2 font-mono text-xl font-semibold text-foreground">
305
307
  {stats.requests}
306
308
  </div>
307
309
  </div>
308
- <div className="border-r border-border/70 px-4 py-3 md:border-r">
310
+ <div className="border-r border-white/10 px-4 py-3 md:border-r">
309
311
  <div className="flex items-center gap-1 text-[10px] uppercase text-muted-foreground">
310
312
  <Siren className="size-3 text-red-300" />
311
313
  Failure
@@ -411,7 +413,7 @@ function sessionRiskBarClass(risk: ContextRiskLevel): string {
411
413
  case "unknown":
412
414
  return "bg-muted-foreground/50";
413
415
  case "ok":
414
- return "bg-emerald-400";
416
+ return "bg-cyan-300";
415
417
  }
416
418
  }
417
419
 
@@ -470,7 +472,7 @@ function evidenceChipClass(value: string): string {
470
472
  normalized.includes("complete") ||
471
473
  normalized.includes("accept")
472
474
  ) {
473
- return "border-emerald-400/30 bg-emerald-500/10 text-emerald-100";
475
+ return "border-cyan-300/25 bg-cyan-400/10 text-cyan-100";
474
476
  }
475
477
  return "border-border/70 bg-background/70 text-muted-foreground";
476
478
  }
@@ -513,11 +515,11 @@ function SessionMembershipPanel({
513
515
  );
514
516
 
515
517
  return (
516
- <div className="mb-3 overflow-hidden rounded-md border border-border bg-[#0c0d0f] text-xs">
518
+ <div className="border border-border bg-card shadow-sm mb-3 rounded-[8px] text-xs">
517
519
  <button
518
520
  type="button"
519
521
  onClick={() => setOpen((value) => !value)}
520
- className="flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-muted/30"
522
+ className="flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-white/[0.04]"
521
523
  aria-expanded={open}
522
524
  >
523
525
  <div className="min-w-0 flex-1">
@@ -555,7 +557,7 @@ function SessionMembershipPanel({
555
557
  )}
556
558
  </button>
557
559
  {open && (
558
- <div className="space-y-2 border-t border-border/70 px-3 py-2">
560
+ <div className="space-y-2 border-t border-white/10 px-3 py-2">
559
561
  {memberships.map((membership) => {
560
562
  const member = membership.member;
561
563
  const metadataRows = getMemberMetadataRows(member);
@@ -595,7 +597,7 @@ function SessionMembershipPanel({
595
597
  {metadataRows.map((row) => (
596
598
  <div
597
599
  key={row.key}
598
- className="min-w-0 rounded border border-border/60 px-2 py-1"
600
+ className="border border-border bg-muted/40 min-w-0 rounded-md px-2 py-1"
599
601
  >
600
602
  <div className="font-mono text-[10px] text-muted-foreground">{row.key}</div>
601
603
  <div
@@ -653,7 +655,7 @@ function LogPaginationButton({
653
655
  type="button"
654
656
  onClick={onClick}
655
657
  disabled={disabled}
656
- className="inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-45"
658
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs text-muted-foreground transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-45"
657
659
  title={title}
658
660
  >
659
661
  {icon}
@@ -679,8 +681,8 @@ function LogPaginationBar({
679
681
  className={cn(
680
682
  "flex min-h-9 flex-col gap-2 px-1 py-1 sm:flex-row sm:items-center",
681
683
  embedded
682
- ? "border-t border-border/70 pt-2"
683
- : "mb-3 rounded-md border border-border bg-muted/10 px-2",
684
+ ? "border-t border-white/10 pt-2"
685
+ : "border border-border bg-card shadow-sm mb-3 rounded-[8px] px-2",
684
686
  )}
685
687
  >
686
688
  <div className="w-full min-w-0 px-1 font-mono text-[11px] text-muted-foreground sm:flex-1">
@@ -738,7 +740,7 @@ function sessionRiskClass(risk: ContextRiskLevel): string {
738
740
  case "unknown":
739
741
  return "text-muted-foreground";
740
742
  case "ok":
741
- return "text-emerald-300";
743
+ return "text-cyan-200";
742
744
  }
743
745
  }
744
746
 
@@ -749,7 +751,7 @@ function sessionHealthClass(level: ContextHealthLevel): string {
749
751
  case "optimizable":
750
752
  return "border-amber-400/25 bg-amber-400/8 text-amber-100";
751
753
  case "ok":
752
- return "border-emerald-400/25 bg-emerald-400/8 text-emerald-100";
754
+ return "border-cyan-300/25 bg-cyan-400/8 text-cyan-100";
753
755
  }
754
756
  }
755
757
 
@@ -812,12 +814,12 @@ function CopyableCommand({ command }: { command: string }): JSX.Element {
812
814
  }, [command]);
813
815
 
814
816
  return (
815
- <div className="inline-flex items-center gap-2 bg-muted rounded-md px-3 py-2">
816
- <pre className="text-blue-500 font-mono text-sm m-0">{command}</pre>
817
+ <div className="border border-border bg-muted/40 inline-flex max-w-full items-center gap-2 rounded-md px-3 py-2">
818
+ <pre className="m-0 overflow-x-auto font-mono text-sm text-cyan-200">{command}</pre>
817
819
  <button
818
820
  type="button"
819
821
  onClick={handleCopy}
820
- className="text-muted-foreground hover:text-foreground transition-colors shrink-0 cursor-pointer"
822
+ className="shrink-0 cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
821
823
  aria-label="Copy command"
822
824
  >
823
825
  {copied ? <Check className="size-4" /> : <Copy className="size-4" />}
@@ -831,8 +833,8 @@ function McpReadyBadge(): JSX.Element {
831
833
  <TooltipProvider>
832
834
  <Tooltip>
833
835
  <TooltipTrigger asChild>
834
- <span className="inline-flex h-7 items-center gap-2 rounded-md border border-cyan-400/30 bg-cyan-500/10 px-2.5 font-mono text-[11px] font-medium text-cyan-300 shadow-[0_0_16px_rgba(34,211,238,0.08)]">
835
- <span className="size-1.5 rounded-full bg-emerald-300 shadow-[0_0_8px_rgba(110,231,183,0.8)]" />
836
+ <span className="border border-border bg-muted/40 inline-flex h-7 items-center gap-2 rounded-md px-2.5 font-mono text-[11px] font-medium text-cyan-200">
837
+ <span className="size-1.5 rounded-full bg-cyan-200 shadow-[0_0_8px_rgba(125,211,252,0.75)]" />
836
838
  MCP Ready
837
839
  <span className="hidden text-cyan-200/70 sm:inline">/api/mcp</span>
838
840
  </span>
@@ -846,6 +848,40 @@ function McpReadyBadge(): JSX.Element {
846
848
  );
847
849
  }
848
850
 
851
+ function SidebarPanel({
852
+ label,
853
+ children,
854
+ className,
855
+ }: {
856
+ label: string;
857
+ children: ReactNode;
858
+ className?: string;
859
+ }): JSX.Element {
860
+ return (
861
+ <section className={cn("border border-border bg-card shadow-sm rounded-[8px]", className)}>
862
+ <div className="border-b border-white/10 px-3 py-2">
863
+ <div className="font-mono text-[10px] font-semibold uppercase tracking-[0.14em] text-white/40">
864
+ {label}
865
+ </div>
866
+ </div>
867
+ <div className="p-3">{children}</div>
868
+ </section>
869
+ );
870
+ }
871
+
872
+ function SidebarMetric({ label, value }: { label: string; value: string | number }): JSX.Element {
873
+ return (
874
+ <div className="border border-border bg-muted/40 min-w-0 rounded-md px-2 py-2">
875
+ <div className="truncate font-mono text-lg font-semibold leading-6 text-slate-100">
876
+ {value}
877
+ </div>
878
+ <div className="mt-0.5 truncate font-mono text-[10px] uppercase tracking-[0.12em] text-white/35">
879
+ {label}
880
+ </div>
881
+ </div>
882
+ );
883
+ }
884
+
849
885
  const CRAB_VARIANT_COLORS = [
850
886
  "text-amber-500",
851
887
  "text-rose-500",
@@ -909,7 +945,7 @@ function BrandHeader({
909
945
  }): JSX.Element {
910
946
  if (compact) {
911
947
  return (
912
- <div className="pb-3">
948
+ <div className="border border-border bg-card shadow-sm rounded-[8px] p-3">
913
949
  <div className="flex items-start justify-between gap-3">
914
950
  <div className="flex min-w-0 items-start gap-2">
915
951
  <CrabFamily compact={true} entrancePhase={crabEntrancePhase} />
@@ -972,7 +1008,7 @@ function SessionContextLogButton({ label, logId }: { label: string; logId: numbe
972
1008
  <button
973
1009
  type="button"
974
1010
  onClick={() => openLogContext(logId)}
975
- className="inline-flex h-5 items-center rounded px-1 font-mono text-[10px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
1011
+ className="inline-flex h-5 items-center rounded px-1 font-mono text-[10px] text-muted-foreground transition-colors hover:bg-white/[0.06] hover:text-foreground"
976
1012
  title={`Open Context for ${formatLogReference(logId)}`}
977
1013
  >
978
1014
  {label} {formatLogReference(logId)}
@@ -1009,7 +1045,7 @@ function SessionContextProgressBar({
1009
1045
  title="Open latest Context details"
1010
1046
  >
1011
1047
  <span
1012
- className="relative block h-2.5 overflow-hidden rounded-full border border-border/70 bg-background transition-colors group-hover:border-foreground/30"
1048
+ className="relative block h-2.5 overflow-hidden rounded-full border border-white/10 bg-black/25 transition-colors group-hover:border-cyan-200/35"
1013
1049
  role="meter"
1014
1050
  aria-label={ariaLabel}
1015
1051
  aria-valuemin={0}
@@ -1044,7 +1080,7 @@ function SessionModelContextRow({ summary }: { summary: SessionModelContextSumma
1044
1080
  };
1045
1081
 
1046
1082
  return (
1047
- <div className="grid gap-x-4 gap-y-2 border-t border-border/60 py-2 first:border-t-0 lg:grid-cols-[minmax(180px,1fr)_minmax(260px,1fr)_minmax(260px,1.4fr)]">
1083
+ <div className="grid gap-x-4 gap-y-2 border-t border-white/10 py-2 first:border-t-0 lg:grid-cols-[minmax(180px,1fr)_minmax(260px,1fr)_minmax(260px,1.4fr)]">
1048
1084
  <div className="min-w-0">
1049
1085
  <div className="flex min-w-0 items-center gap-2">
1050
1086
  <button
@@ -1099,7 +1135,7 @@ function SessionModelContextRow({ summary }: { summary: SessionModelContextSumma
1099
1135
  <TrendIcon
1100
1136
  className={cn(
1101
1137
  "size-3",
1102
- trend !== null && trend > 0 ? "text-amber-300" : "text-emerald-300",
1138
+ trend !== null && trend > 0 ? "text-amber-300" : "text-cyan-300",
1103
1139
  )}
1104
1140
  />
1105
1141
  )}
@@ -1137,7 +1173,7 @@ function SessionContextSummaryPanel({
1137
1173
  }): JSX.Element | null {
1138
1174
  if (summary.models.length === 0) return null;
1139
1175
  return (
1140
- <div className="mt-3 border-t border-border/70 pt-2">
1176
+ <div className="mt-3 border-t border-white/10 pt-2">
1141
1177
  <div className="mb-1 flex items-center justify-between gap-2">
1142
1178
  <div className="text-[11px] font-semibold uppercase tracking-normal text-muted-foreground">
1143
1179
  Session Context
@@ -1205,12 +1241,12 @@ function SessionContextBar({
1205
1241
  }, []);
1206
1242
 
1207
1243
  return (
1208
- <div className="mb-3 rounded-md border border-border bg-muted/15 px-3 py-2 text-xs">
1244
+ <div className="border border-border bg-card shadow-sm mb-3 rounded-[8px] px-3 py-2 text-xs">
1209
1245
  <div className="flex flex-wrap items-center gap-2">
1210
1246
  {showBackLink && (
1211
1247
  <a
1212
1248
  href="/"
1213
- className="inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
1249
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
1214
1250
  aria-label="Back to all sessions"
1215
1251
  title="Back to all sessions"
1216
1252
  >
@@ -1247,7 +1283,7 @@ function SessionContextBar({
1247
1283
  <button
1248
1284
  type="button"
1249
1285
  onClick={() => setDetailsOpen((value) => !value)}
1250
- className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-border bg-background/60 px-2.5 font-mono text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
1286
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md px-2.5 font-mono text-[11px] text-muted-foreground transition-colors hover:text-foreground"
1251
1287
  aria-expanded={detailsOpen}
1252
1288
  title={detailsOpen ? "Hide context details" : "Show context details"}
1253
1289
  >
@@ -1262,7 +1298,7 @@ function SessionContextBar({
1262
1298
  <button
1263
1299
  type="button"
1264
1300
  onClick={handleCopyLink}
1265
- className="inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
1301
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
1266
1302
  aria-label={copied ? "Copied session link" : "Copy session link"}
1267
1303
  title={copied ? "Copied session link" : "Copy session link"}
1268
1304
  >
@@ -1286,6 +1322,8 @@ export type ProxyViewerProps = {
1286
1322
  /** Clear only the logs whose ids are passed. Called by the per-group
1287
1323
  * Clear button on each conversation header. */
1288
1324
  onClearGroup: (ids: number[]) => void;
1325
+ /** Surface short-lived action feedback without adding sticky page chrome. */
1326
+ onNotify?: (notice: TransientToastInput) => void;
1289
1327
  onImportLogs?: (file: File) => Promise<ImportLogsResponse>;
1290
1328
  viewMode: "simple" | "full";
1291
1329
  captureMode: CaptureMode;
@@ -1319,6 +1357,7 @@ export function ProxyViewer({
1319
1357
  onModelChange,
1320
1358
  onClearAll,
1321
1359
  onClearGroup,
1360
+ onNotify,
1322
1361
  onImportLogs,
1323
1362
  viewMode,
1324
1363
  captureMode,
@@ -1375,13 +1414,17 @@ export function ProxyViewer({
1375
1414
  try {
1376
1415
  const result = await exportLogsAsZip(logs, mode);
1377
1416
  if (!result.ok) {
1378
- setExportError(result.message);
1417
+ if (onNotify !== undefined) {
1418
+ onNotify({ message: result.message, tone: "error" });
1419
+ } else {
1420
+ setExportError(result.message);
1421
+ }
1379
1422
  }
1380
1423
  } finally {
1381
1424
  setExporting(null);
1382
1425
  }
1383
1426
  },
1384
- [logs],
1427
+ [logs, onNotify],
1385
1428
  );
1386
1429
  const handleImportClick = useCallback(() => {
1387
1430
  importInputRef.current?.click();
@@ -1399,18 +1442,26 @@ export function ProxyViewer({
1399
1442
  void onImportLogs(file)
1400
1443
  .then((result) => {
1401
1444
  const skipped = result.skipped > 0 ? `, skipped ${String(result.skipped)}` : "";
1402
- setImportStatus(
1403
- `Imported ${String(result.imported)} logs as ${result.sessionId}${skipped}.`,
1404
- );
1445
+ const message = `Imported ${String(result.imported)} logs as ${result.sessionId}${skipped}.`;
1446
+ if (onNotify !== undefined) {
1447
+ onNotify({ message, tone: "success" });
1448
+ } else {
1449
+ setImportStatus(message);
1450
+ }
1405
1451
  })
1406
1452
  .catch((err: unknown) => {
1407
- setImportError(err instanceof Error ? err.message : "Failed to import logs");
1453
+ const message = err instanceof Error ? err.message : "Failed to import logs";
1454
+ if (onNotify !== undefined) {
1455
+ onNotify({ message, tone: "error" });
1456
+ } else {
1457
+ setImportError(message);
1458
+ }
1408
1459
  })
1409
1460
  .finally(() => {
1410
1461
  setImporting(false);
1411
1462
  });
1412
1463
  },
1413
- [onImportLogs],
1464
+ [onImportLogs, onNotify],
1414
1465
  );
1415
1466
  // Close the compare drawer when the user changes the session or model
1416
1467
  // filter, since the predecessor relationship may no longer be meaningful.
@@ -1467,38 +1518,30 @@ export function ProxyViewer({
1467
1518
  );
1468
1519
 
1469
1520
  return (
1470
- <div className="min-h-screen w-full bg-[#070809] text-foreground">
1521
+ <div className="bg-background min-h-screen w-full text-foreground">
1471
1522
  <div className="grid min-h-screen w-full grid-cols-1 lg:grid-cols-[340px_minmax(0,1fr)] xl:grid-cols-[360px_minmax(0,1fr)]">
1472
- <aside className="border-b border-border bg-[#090a0c] lg:sticky lg:top-0 lg:h-screen lg:overflow-y-auto lg:border-b-0 lg:border-r">
1523
+ <aside className="bg-background border-b lg:sticky lg:top-0 lg:h-screen lg:overflow-y-auto lg:border-b-0 lg:border-r">
1473
1524
  <div className="space-y-4 p-4">
1474
1525
  <BrandHeader compact={true} crabEntrancePhase={crabEntrancePhase} />
1475
1526
 
1476
- <div className="grid grid-cols-3 gap-2 border-y border-border/70 py-3">
1477
- <div className="min-w-0">
1478
- <div className="font-mono text-lg font-semibold text-foreground">{logs.length}</div>
1479
- <div className="text-[10px] uppercase text-muted-foreground">Requests</div>
1480
- </div>
1481
- <div className="min-w-0">
1482
- <div className="font-mono text-lg font-semibold text-foreground">
1483
- {formatTokens(hasSessionContext ? sessionContextTotalIn : totalIn)}
1484
- </div>
1485
- <div className="text-[10px] uppercase text-muted-foreground">Input</div>
1486
- </div>
1487
- <div className="min-w-0">
1488
- <div className="font-mono text-lg font-semibold text-foreground">
1489
- {formatTokens(hasSessionContext ? sessionContextTotalOut : totalOut)}
1490
- </div>
1491
- <div className="text-[10px] uppercase text-muted-foreground">Output</div>
1527
+ <SidebarPanel label="Session">
1528
+ <div className="grid grid-cols-3 gap-2">
1529
+ <SidebarMetric label="Requests" value={logs.length} />
1530
+ <SidebarMetric
1531
+ label="Input"
1532
+ value={formatTokens(hasSessionContext ? sessionContextTotalIn : totalIn)}
1533
+ />
1534
+ <SidebarMetric
1535
+ label="Output"
1536
+ value={formatTokens(hasSessionContext ? sessionContextTotalOut : totalOut)}
1537
+ />
1492
1538
  </div>
1493
- </div>
1539
+ </SidebarPanel>
1494
1540
 
1495
- <div className="space-y-2">
1496
- <div className="text-[10px] font-semibold uppercase tracking-normal text-muted-foreground">
1497
- Workspace
1498
- </div>
1499
- <div className="rounded-md border border-border bg-[#0c0d0f] px-3 py-2">
1541
+ <SidebarPanel label="Workspace">
1542
+ <div className="border border-border bg-muted/40 rounded-md px-3 py-2">
1500
1543
  <div
1501
- className="font-mono text-xs font-semibold text-purple-300"
1544
+ className="truncate font-mono text-xs font-semibold text-cyan-100"
1502
1545
  title={activeScopeLabel}
1503
1546
  >
1504
1547
  {activeScopeLabel}
@@ -1507,7 +1550,7 @@ export function ProxyViewer({
1507
1550
  {isPinnedSessionPage ? "Session workspace" : "Live workspace"}
1508
1551
  </div>
1509
1552
  </div>
1510
- </div>
1553
+ </SidebarPanel>
1511
1554
 
1512
1555
  {hasSessionContext && sessionContextScope !== null && (
1513
1556
  <SessionContextBar
@@ -1527,44 +1570,43 @@ export function ProxyViewer({
1527
1570
  <SessionMembershipPanel memberships={sessionMemberships} />
1528
1571
  )}
1529
1572
 
1530
- <div className="space-y-2">
1531
- <div className="text-[10px] font-semibold uppercase tracking-normal text-muted-foreground">
1532
- Filters
1533
- </div>
1534
- {!hideSessionFilter && (
1535
- <Select value={selectedSession} onValueChange={onSessionChange}>
1536
- <SelectTrigger className="h-9 w-full text-xs">
1537
- <SelectValue placeholder="All sessions" />
1573
+ <SidebarPanel label="Filters">
1574
+ <div className="space-y-2">
1575
+ {!hideSessionFilter && (
1576
+ <Select value={selectedSession} onValueChange={onSessionChange}>
1577
+ <SelectTrigger className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 w-full text-xs">
1578
+ <SelectValue placeholder="All sessions" />
1579
+ </SelectTrigger>
1580
+ <SelectContent>
1581
+ <SelectItem value="__all__">All sessions</SelectItem>
1582
+ {sessions.map((s) => (
1583
+ <SelectItem key={s} value={s}>
1584
+ {truncateSessionId(s)}
1585
+ </SelectItem>
1586
+ ))}
1587
+ </SelectContent>
1588
+ </Select>
1589
+ )}
1590
+ <Select value={selectedModel} onValueChange={onModelChange}>
1591
+ <SelectTrigger className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 w-full text-xs">
1592
+ <SelectValue placeholder="All models" />
1538
1593
  </SelectTrigger>
1539
1594
  <SelectContent>
1540
- <SelectItem value="__all__">All sessions</SelectItem>
1541
- {sessions.map((s) => (
1542
- <SelectItem key={s} value={s}>
1543
- {truncateSessionId(s)}
1595
+ <SelectItem value="__all__">All models</SelectItem>
1596
+ {models.map((m) => (
1597
+ <SelectItem key={m} value={m}>
1598
+ {m}
1544
1599
  </SelectItem>
1545
1600
  ))}
1546
1601
  </SelectContent>
1547
1602
  </Select>
1548
- )}
1549
- <Select value={selectedModel} onValueChange={onModelChange}>
1550
- <SelectTrigger className="h-9 w-full text-xs">
1551
- <SelectValue placeholder="All models" />
1552
- </SelectTrigger>
1553
- <SelectContent>
1554
- <SelectItem value="__all__">All models</SelectItem>
1555
- {models.map((m) => (
1556
- <SelectItem key={m} value={m}>
1557
- {m}
1558
- </SelectItem>
1559
- ))}
1560
- </SelectContent>
1561
- </Select>
1562
- </div>
1603
+ </div>
1604
+ </SidebarPanel>
1563
1605
  </div>
1564
1606
  </aside>
1565
1607
 
1566
1608
  <main className="min-w-0">
1567
- <div className="sticky top-0 z-30 border-b border-border bg-[#08090b]/95 px-4 py-3 shadow-[0_12px_40px_rgba(0,0,0,0.24)] backdrop-blur lg:px-5">
1609
+ <div className="border border-border bg-background/95 shadow-sm sticky top-0 z-30 border-b px-4 py-3 lg:px-5">
1568
1610
  <div className="flex flex-wrap items-center gap-2">
1569
1611
  <div className="min-w-[180px] flex-1">
1570
1612
  <div className="text-sm font-semibold text-foreground">Logs</div>
@@ -1585,7 +1627,7 @@ export function ProxyViewer({
1585
1627
  type="button"
1586
1628
  onClick={handleImportClick}
1587
1629
  disabled={importing}
1588
- className="inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1630
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1589
1631
  title="Import Agent Inspector export"
1590
1632
  >
1591
1633
  {importing ? (
@@ -1606,7 +1648,7 @@ export function ProxyViewer({
1606
1648
  void handleExport("redacted");
1607
1649
  }}
1608
1650
  disabled={exporting !== null}
1609
- className="inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1651
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1610
1652
  title="Export redacted logs as JSON ZIP"
1611
1653
  >
1612
1654
  {exporting === "redacted" ? (
@@ -1626,7 +1668,7 @@ export function ProxyViewer({
1626
1668
  void handleExport("raw");
1627
1669
  }}
1628
1670
  disabled={exporting !== null}
1629
- className="inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1671
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs text-muted-foreground transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
1630
1672
  title="Export raw logs without redaction"
1631
1673
  >
1632
1674
  {exporting === "raw" ? (
@@ -1642,7 +1684,7 @@ export function ProxyViewer({
1642
1684
  <button
1643
1685
  type="button"
1644
1686
  onClick={onClearAll}
1645
- className="inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
1687
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:text-foreground"
1646
1688
  title="Clear all logs"
1647
1689
  >
1648
1690
  <Trash2 className="size-3.5" />
@@ -1650,18 +1692,18 @@ export function ProxyViewer({
1650
1692
  </button>
1651
1693
  </div>
1652
1694
  <LogPaginationBar logs={logs} pagination={pagination} embedded={true} />
1653
- {exportError !== null && (
1695
+ {onNotify === undefined && exportError !== null && (
1654
1696
  <div className="mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
1655
1697
  {exportError}
1656
1698
  </div>
1657
1699
  )}
1658
- {importError !== null && (
1700
+ {onNotify === undefined && importError !== null && (
1659
1701
  <div className="mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
1660
1702
  {importError}
1661
1703
  </div>
1662
1704
  )}
1663
- {importStatus !== null && importError === null && (
1664
- <div className="mt-2 rounded-md border border-emerald-500/25 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-200">
1705
+ {onNotify === undefined && importStatus !== null && importError === null && (
1706
+ <div className="mt-2 rounded-md border border-cyan-400/25 bg-cyan-500/10 px-3 py-2 text-xs text-cyan-100">
1665
1707
  {importStatus}
1666
1708
  </div>
1667
1709
  )}
@@ -1672,7 +1714,7 @@ export function ProxyViewer({
1672
1714
  {logs.length === 0 ? (
1673
1715
  selectedSession !== "__all__" ? (
1674
1716
  <div className="py-16 text-center text-muted-foreground">
1675
- <div className="mx-auto max-w-xl space-y-4 rounded-md border border-border bg-muted/10 px-4 py-8">
1717
+ <div className="border border-border bg-card shadow-sm mx-auto max-w-xl space-y-4 rounded-[8px] px-4 py-8">
1676
1718
  <p className="text-sm font-medium">
1677
1719
  {isLoading ? "Loading session data..." : "Session not found"}
1678
1720
  </p>
@@ -1706,7 +1748,7 @@ export function ProxyViewer({
1706
1748
  </div>
1707
1749
  ) : (
1708
1750
  <div className="py-16 text-center text-muted-foreground">
1709
- <div className="mx-auto max-w-2xl space-y-4 rounded-md border border-border bg-muted/10 px-4 py-8">
1751
+ <div className="border border-border bg-card shadow-sm mx-auto max-w-2xl space-y-4 rounded-[8px] px-4 py-8">
1710
1752
  <p className="text-sm">No requests captured yet.</p>
1711
1753
  <div className="flex flex-col items-center gap-2">
1712
1754
  <CopyableCommand command="ANTHROPIC_BASE_URL=http://localhost:25947/proxy <your-tool>" />
@@ -1736,8 +1778,7 @@ export function ProxyViewer({
1736
1778
  onCompareWithPrevious={handleCompareWithPrevious}
1737
1779
  comparisonPredecessors={comparisonPredecessors}
1738
1780
  onClearGroup={onClearGroup}
1739
- standalone={groups.length === 1}
1740
- hasPinnedSessionContext={hasSessionContext}
1781
+ defaultExpanded={groups.length === 1}
1741
1782
  timeDisplayFormat={timeDisplayFormat}
1742
1783
  />
1743
1784
  </div>