@tonyclaw/agent-inspector 2.1.14 → 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 (70) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-Ccrrcx1j.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +106 -0
  4. package/.output/public/assets/ReplayDialog-C7axhr-l.js +1 -0
  5. package/.output/public/assets/{RequestAnatomy-BNahe83D.js → RequestAnatomy-WOptg8j_.js} +1 -1
  6. package/.output/public/assets/{ResponseView-DSOnGqi6.js → ResponseView-CSAcxh_M.js} +2 -2
  7. package/.output/public/assets/{StreamingChunkSequence-BEKTDklB.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-DWOkqdCa.js → index-DMPNh46t.js} +1 -1
  12. package/.output/public/assets/{json-viewer-C2JpgcW0.js → json-viewer-inX5QSa3.js} +1 -1
  13. package/.output/public/assets/{main-CSONBwwn.js → main-aCAKYGSD.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +224 -194
  15. package/.output/server/{_sessionId-CPAa37n5.mjs → _sessionId-C0fhvQnn.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-ceW5VxMo.mjs → CompareDrawer-CO-Ti5sg.mjs} +36 -15
  17. package/.output/server/_ssr/{ProxyViewerContainer-CDfEE_w-.mjs → ProxyViewerContainer-N4J7uBCX.mjs} +1153 -494
  18. package/.output/server/_ssr/{ReplayDialog-V0s_eEbR.mjs → ReplayDialog-J1VxC0In.mjs} +14 -7
  19. package/.output/server/_ssr/{RequestAnatomy-f1ccwR9d.mjs → RequestAnatomy-DBH-F3rq.mjs} +3 -3
  20. package/.output/server/_ssr/{ResponseView-BIRrqG4H.mjs → ResponseView-DLFwV7Hd.mjs} +3 -3
  21. package/.output/server/_ssr/{StreamingChunkSequence-V3JFjCgX.mjs → StreamingChunkSequence-BywmuND0.mjs} +3 -3
  22. package/.output/server/_ssr/{index-DsykulzS.mjs → index-CStymIc0.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-Dcnm0Ivf.mjs → json-viewer-DlQu0N1x.mjs} +3 -3
  25. package/.output/server/_ssr/{router-4bdm6Mt2.mjs → router-CQ4Zxhmr.mjs} +118 -47
  26. package/.output/server/{_tanstack-start-manifest_v-Xp4CO64V.mjs → _tanstack-start-manifest_v-BHeJBGfo.mjs} +1 -1
  27. package/.output/server/index.mjs +75 -75
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +174 -107
  30. package/src/components/ProxyViewerContainer.tsx +77 -67
  31. package/src/components/alerts/AlertsDialog.tsx +2 -1
  32. package/src/components/clients/ClientLogo.tsx +132 -0
  33. package/src/components/groups/GroupsDialog.tsx +37 -14
  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 +62 -29
  40. package/src/components/proxy-viewer/CompareDrawer.tsx +38 -8
  41. package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
  42. package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
  43. package/src/components/proxy-viewer/ConversationHeader.tsx +65 -61
  44. package/src/components/proxy-viewer/LogEntry.tsx +31 -18
  45. package/src/components/proxy-viewer/LogEntryHeader.tsx +42 -49
  46. package/src/components/proxy-viewer/ProviderLogoStack.tsx +60 -0
  47. package/src/components/proxy-viewer/ReplayDialog.tsx +8 -2
  48. package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
  49. package/src/components/proxy-viewer/ThreadConnector.tsx +22 -11
  50. package/src/components/proxy-viewer/TurnGroup.tsx +30 -36
  51. package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
  52. package/src/components/proxy-viewer/viewerState.ts +5 -6
  53. package/src/components/ui/icon-trigger.ts +2 -0
  54. package/src/components/ui/transient-toast.tsx +104 -0
  55. package/src/lib/sessionInfoContract.ts +5 -0
  56. package/src/lib/stopReason.ts +48 -16
  57. package/src/mcp/server.ts +71 -27
  58. package/src/mcp/toolHandlers.ts +23 -4
  59. package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
  60. package/src/proxy/sessionInfo.ts +36 -3
  61. package/src/proxy/sessionSupervisor.ts +20 -1
  62. package/src/proxy/store.ts +1 -1
  63. package/src/routes/api/providers.$providerId.test.log.ts +8 -4
  64. package/styles/globals.css +180 -180
  65. package/.output/public/assets/CompareDrawer-DjgjIFx7.js +0 -1
  66. package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +0 -106
  67. package/.output/public/assets/ReplayDialog-CU0Tbb2c.js +0 -1
  68. package/.output/public/assets/_sessionId-Cif8JZdn.js +0 -1
  69. package/.output/public/assets/index-D_WfwzUi.js +0 -1
  70. package/.output/public/assets/index-DtLuQrs0.css +0 -1
@@ -34,7 +34,7 @@ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/
34
34
  import type { CacheTrend } from "./cacheTrend";
35
35
 
36
36
  const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
37
- success: "bg-emerald-400/[0.08] text-emerald-200 border-emerald-300/25",
37
+ success: "bg-slate-300/[0.08] text-slate-100 border-slate-300/20",
38
38
  client_error: "bg-amber-300/[0.10] text-amber-100 border-amber-300/30",
39
39
  server_error: "bg-rose-400/[0.10] text-rose-100 border-rose-300/30",
40
40
  pending: "bg-cyan-300/[0.08] text-cyan-100 border-cyan-300/25",
@@ -45,36 +45,31 @@ const STATUS_CUE_CLASSES: Record<
45
45
  {
46
46
  frame: string;
47
47
  dot: string;
48
- rail: string;
49
48
  }
50
49
  > = {
51
50
  success: {
52
- frame: "border-emerald-300/20 bg-emerald-300/[0.06] text-emerald-100",
53
- dot: "bg-emerald-300 shadow-[0_0_14px_rgba(110,231,183,0.8)]",
54
- rail: "bg-emerald-300/45 shadow-[0_0_22px_rgba(110,231,183,0.28)]",
51
+ frame: "border-cyan-200/15 bg-white/[0.04] text-slate-100",
52
+ dot: "bg-cyan-200/80 shadow-[0_0_14px_rgba(125,211,252,0.65)]",
55
53
  },
56
54
  client_error: {
57
55
  frame: "border-amber-300/25 bg-amber-300/[0.08] text-amber-100",
58
56
  dot: "bg-amber-200 shadow-[0_0_14px_rgba(252,211,77,0.8)]",
59
- rail: "bg-amber-300/50 shadow-[0_0_22px_rgba(252,211,77,0.28)]",
60
57
  },
61
58
  server_error: {
62
59
  frame: "border-rose-300/25 bg-rose-300/[0.08] text-rose-100",
63
60
  dot: "bg-rose-300 shadow-[0_0_14px_rgba(253,164,175,0.8)]",
64
- rail: "bg-rose-300/50 shadow-[0_0_22px_rgba(253,164,175,0.28)]",
65
61
  },
66
62
  pending: {
67
63
  frame: "border-cyan-300/25 bg-cyan-300/[0.08] text-cyan-100",
68
64
  dot: "bg-cyan-200 shadow-[0_0_14px_rgba(103,232,249,0.8)]",
69
- rail: "bg-cyan-300/50 shadow-[0_0_22px_rgba(103,232,249,0.28)]",
70
65
  },
71
66
  };
72
67
 
73
68
  const HEADER_ACTION_BUTTON_CLASS =
74
- "size-8 border-white/10 bg-[#0a0d11] text-muted-foreground shadow-none hover:border-cyan-300/30 hover:bg-cyan-400/10 hover:text-cyan-100";
69
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground size-8 text-muted-foreground shadow-none hover:text-cyan-100";
75
70
 
76
71
  const TELEMETRY_CHIP_CLASS =
77
- "flex h-7 items-center gap-1 rounded-md border border-white/10 bg-black/20 px-2 text-xs shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]";
72
+ "border border-border bg-muted/40 flex h-7 items-center gap-1 rounded-md px-2 text-xs";
78
73
 
79
74
  function formatElapsed(ms: number): string {
80
75
  if (ms < 1000) return `${ms}ms`;
@@ -100,7 +95,7 @@ function statusCueLabel(category: StatusCategory, status: number | null): string
100
95
  }
101
96
 
102
97
  /**
103
- * Inline trend indicator: small arrow (green up / red down) plus the absolute
98
+ * Inline trend indicator: small arrow (up / down) plus the absolute
104
99
  * delta in compact form. Returns `null` when there is no trend to display.
105
100
  */
106
101
  function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Element | null {
@@ -110,7 +105,7 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
110
105
  const sign = isUp ? "+" : "-";
111
106
  return (
112
107
  <span className="flex items-center gap-0.5 text-muted-foreground tabular-nums">
113
- <Icon className={isUp ? "size-3 text-emerald-400" : "size-3 text-rose-400"} />
108
+ <Icon className={isUp ? "size-3 text-cyan-300" : "size-3 text-rose-400"} />
114
109
  <span className="font-mono">
115
110
  {sign}
116
111
  {formatTokens(trend.delta)}
@@ -121,12 +116,12 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
121
116
 
122
117
  /**
123
118
  * Per-tab action bundle surfaced in the header when the tab is active.
124
- * - `copyText === null` Copy button hidden (e.g. tab has no body to copy)
125
- * - `expansion === null` Expand-all button hidden (e.g. flat dict, or
119
+ * - `copyText === null` - Copy button hidden (e.g. tab has no body to copy)
120
+ * - `expansion === null` - Expand-all button hidden (e.g. flat dict, or
126
121
  * not valid JSON). When present, the button reflects the JSON viewer's
127
122
  * bulk-expansion state and the caller is responsible for forwarding the
128
123
  * matching `bulkDepth` / `bulkRevision` to the underlying viewer.
129
- * - `diffWithRaw` / `diffWithPrevious` optional Diff buttons. Undefined
124
+ * - `diffWithRaw` / `diffWithPrevious` - optional Diff buttons. Undefined
130
125
  * means "not applicable" (hidden). Today only the Request tab sets them.
131
126
  */
132
127
  export type HeaderTabAction = {
@@ -155,6 +150,8 @@ export type HeaderTabActions = Record<string, HeaderTabAction | undefined>;
155
150
 
156
151
  export type LogEntryHeaderProps = {
157
152
  log: CapturedLog;
153
+ /** Human-facing ordinal inside the current session. Internal actions still use `log.id`. */
154
+ displayNumber: number;
158
155
  /** Number of messages in the request (supports both Anthropic and OpenAI formats). */
159
156
  messageCount?: number | null;
160
157
  /** Number of tools defined in the request (supports both Anthropic and OpenAI formats). */
@@ -178,12 +175,15 @@ export type LogEntryHeaderProps = {
178
175
  onReplay?: () => void;
179
176
  /** Slow-response threshold in seconds. `0` disables the warning indicator. */
180
177
  slowResponseThresholdSeconds?: number;
178
+ /** Show client PID/project metadata on each log row. Parent process rows can disable this. */
179
+ showClientMetadata?: boolean;
181
180
  /** Controls whether timestamps render as compact local time or full ISO strings. */
182
181
  timeDisplayFormat: TimeDisplayFormat;
183
182
  };
184
183
 
185
184
  export const LogEntryHeader = memo(function ({
186
185
  log,
186
+ displayNumber,
187
187
  messageCount = null,
188
188
  toolCount = null,
189
189
  expanded,
@@ -193,6 +193,7 @@ export const LogEntryHeader = memo(function ({
193
193
  tabActions,
194
194
  onReplay,
195
195
  slowResponseThresholdSeconds = 0,
196
+ showClientMetadata = true,
196
197
  timeDisplayFormat,
197
198
  }: LogEntryHeaderProps): JSX.Element {
198
199
  const statusCategory = getStatusCategory(log.responseStatus);
@@ -229,9 +230,9 @@ export const LogEntryHeader = memo(function ({
229
230
  className={cn(
230
231
  "group/header relative isolate flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 overflow-hidden px-3 py-2 cursor-pointer transition-[background-color,border-color,box-shadow]",
231
232
  expanded
232
- ? "bg-[linear-gradient(90deg,#0f1720_0%,#090d12_50%,#07090d_100%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.06)]"
233
- : "bg-[#080b10] hover:bg-[#0b1016]",
234
- "select-none before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-white/10",
233
+ ? "bg-white/[0.035] shadow-[inset_0_1px_0_rgba(255,255,255,0.10)]"
234
+ : "bg-transparent hover:bg-white/[0.035]",
235
+ "select-none before:pointer-events-none before:absolute before:inset-x-3 before:top-0 before:h-px before:bg-white/12",
235
236
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
236
237
  )}
237
238
  onClick={onToggle}
@@ -242,40 +243,28 @@ export const LogEntryHeader = memo(function ({
242
243
  }
243
244
  }}
244
245
  >
245
- <span
246
- className={cn(
247
- "absolute left-0 top-2 bottom-2 w-0.5 rounded-r-full transition-colors",
248
- statusCue.rail,
249
- )}
250
- aria-hidden="true"
251
- />
252
246
  {/* Request ID */}
253
247
  <span
254
248
  className={cn(
255
- "inline-flex h-8 shrink-0 items-center gap-2 rounded-md border px-2 font-mono shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]",
249
+ "border border-border bg-muted/40 inline-flex h-8 shrink-0 items-center gap-2 rounded-md px-2 font-mono",
256
250
  statusCue.frame,
257
251
  )}
258
252
  >
259
253
  <span className={cn("size-1.5 rounded-full", statusCue.dot)} aria-hidden="true" />
260
- <span className="text-[9px] font-semibold uppercase tracking-normal text-current/65">
261
- Shot
254
+ <span className="text-xs font-semibold tabular-nums" title={`Log ID ${String(log.id)}`}>
255
+ #{displayNumber}
262
256
  </span>
263
- <span className="text-xs font-semibold tabular-nums">#{log.id}</span>
264
257
  </span>
265
258
 
266
259
  {/* Request start time */}
267
- <span className="flex h-7 items-center gap-1 rounded-md border border-white/10 bg-black/20 px-2 text-muted-foreground text-xs shrink-0">
260
+ <span className="border border-border bg-muted/40 flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground">
268
261
  <Clock className="size-3" />
269
262
  <span className="font-mono tabular-nums" title={log.timestamp}>
270
263
  {formatTimestamp(log.timestamp, timeDisplayFormat)}
271
264
  </span>
272
265
  </span>
273
266
 
274
- {/* Model logo moved to the session/conversation header per-log rows
275
- no longer render the logo (sessions with many logs would show the
276
- same provider logo dozens of times). */}
277
-
278
- {/* Response Status — only shown for non-200 or pending. Each category
267
+ {/* Response Status - only shown for non-200 or pending. Each category
279
268
  carries a distinct icon in addition to color so the meaning is
280
269
  legible without color perception. */}
281
270
  <Badge
@@ -317,7 +306,7 @@ export const LogEntryHeader = memo(function ({
317
306
  )}
318
307
 
319
308
  {/* Elapsed time. Tooltip is only shown when this log exceeded the slow-
320
- response threshold for normal-speed logs the elapsed value is
309
+ response threshold - for normal-speed logs the elapsed value is
321
310
  self-explanatory, so we skip the tooltip wrapper to keep the row
322
311
  quiet. */}
323
312
  {log.elapsedMs !== null &&
@@ -362,7 +351,7 @@ export const LogEntryHeader = memo(function ({
362
351
  {log.streaming && tokenRateLabel !== null && (
363
352
  <Tooltip>
364
353
  <TooltipTrigger asChild>
365
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-emerald-300")}>
354
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
366
355
  <Gauge className="size-3" />
367
356
  <span className="font-mono tabular-nums">{tokenRateLabel}</span>
368
357
  </span>
@@ -382,7 +371,7 @@ export const LogEntryHeader = memo(function ({
382
371
  log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
383
372
  )}
384
373
  >
385
- IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : ""}
374
+ IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "n/a"}
386
375
  </span>
387
376
  <span className="text-muted-foreground">/</span>
388
377
  <span
@@ -391,7 +380,7 @@ export const LogEntryHeader = memo(function ({
391
380
  log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground",
392
381
  )}
393
382
  >
394
- OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : ""}
383
+ OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "n/a"}
395
384
  </span>
396
385
  </span>
397
386
  </span>
@@ -400,7 +389,7 @@ export const LogEntryHeader = memo(function ({
400
389
  {log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
401
390
  <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
402
391
  <CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
403
- <span className="font-mono tabular-nums text-emerald-400">
392
+ <span className="font-mono tabular-nums text-cyan-300">
404
393
  KV Cache +{formatTokens(log.cacheCreationInputTokens)}
405
394
  </span>
406
395
  </span>
@@ -444,7 +433,7 @@ export const LogEntryHeader = memo(function ({
444
433
  )}
445
434
 
446
435
  {/* Client info (PID + project folder) */}
447
- {(hasClientPid || hasClientProjectFolder) && (
436
+ {showClientMetadata && (hasClientPid || hasClientProjectFolder) && (
448
437
  <Tooltip>
449
438
  <TooltipTrigger asChild>
450
439
  <span className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}>
@@ -477,7 +466,7 @@ export const LogEntryHeader = memo(function ({
477
466
  {/* Spacer */}
478
467
  <span className="flex-1 min-w-0" />
479
468
 
480
- {/* Header actions only when expanded, so the collapsed view stays
469
+ {/* Header actions - only when expanded, so the collapsed view stays
481
470
  compact. Buttons stop propagation so they don't toggle the log. */}
482
471
  {expanded && (
483
472
  <span
@@ -576,7 +565,7 @@ export const LogEntryHeader = memo(function ({
576
565
  aria-label={action.copyCopied ? "Copied" : action.copyLabel}
577
566
  >
578
567
  {action.copyCopied ? (
579
- <Check className="size-3.5 text-emerald-500" />
568
+ <Check className="size-3.5 text-cyan-300" />
580
569
  ) : (
581
570
  <Copy className="size-3.5" />
582
571
  )}
@@ -609,12 +598,16 @@ export const LogEntryHeader = memo(function ({
609
598
  </span>
610
599
  )}
611
600
 
612
- {/* Expand chevron */}
613
- {expanded ? (
614
- <ChevronDown className="size-4 text-muted-foreground shrink-0" />
615
- ) : (
616
- <ChevronRight className="size-4 text-muted-foreground shrink-0" />
617
- )}
601
+ <span
602
+ 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-slate-200"
603
+ aria-hidden="true"
604
+ >
605
+ {expanded ? (
606
+ <ChevronDown className="size-4 shrink-0" />
607
+ ) : (
608
+ <ChevronRight className="size-4 shrink-0" />
609
+ )}
610
+ </span>
618
611
  </div>
619
612
  </TooltipProvider>
620
613
  );
@@ -0,0 +1,60 @@
1
+ import type { JSX } from "react";
2
+ import { cn } from "../../lib/utils";
3
+ import { ProviderLogo, type Provider } from "../providers/ProviderLogo";
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
5
+
6
+ const MAX_VISIBLE_LOGOS = 3;
7
+
8
+ export const ROW_CHEVRON_SLOT_CLASS =
9
+ "flex h-8 w-5 shrink-0 items-center justify-center text-muted-foreground";
10
+
11
+ export type ProviderLogoStackItem = {
12
+ provider: Provider;
13
+ tooltip: string;
14
+ };
15
+
16
+ export function ProviderLogoStack({
17
+ items,
18
+ ariaLabel,
19
+ className,
20
+ }: {
21
+ items: readonly ProviderLogoStackItem[];
22
+ ariaLabel: string;
23
+ className?: string;
24
+ }): JSX.Element {
25
+ if (items.length === 0) {
26
+ return (
27
+ <span className={cn("flex h-8 w-12 shrink-0 items-center", className)} aria-hidden="true" />
28
+ );
29
+ }
30
+
31
+ const visibleItems = items.slice(0, MAX_VISIBLE_LOGOS);
32
+ const overflowCount = items.length - visibleItems.length;
33
+
34
+ return (
35
+ <TooltipProvider delayDuration={150}>
36
+ <span className={cn("flex h-8 w-12 shrink-0 items-center", className)} aria-label={ariaLabel}>
37
+ {visibleItems.map((item, index) => (
38
+ <Tooltip key={`${item.provider}-${String(index)}`}>
39
+ <TooltipTrigger asChild>
40
+ <span
41
+ className={cn(
42
+ "border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md",
43
+ index > 0 && "-ml-2",
44
+ )}
45
+ >
46
+ <ProviderLogo provider={item.provider} className="size-4" />
47
+ </span>
48
+ </TooltipTrigger>
49
+ <TooltipContent>{item.tooltip}</TooltipContent>
50
+ </Tooltip>
51
+ ))}
52
+ {overflowCount > 0 && (
53
+ <span className="border border-border bg-muted/40 -ml-2 inline-flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md px-1 font-mono text-[10px] text-muted-foreground">
54
+ +{overflowCount}
55
+ </span>
56
+ )}
57
+ </span>
58
+ </TooltipProvider>
59
+ );
60
+ }
@@ -35,6 +35,7 @@ type ReplayResult = z.infer<typeof ReplayResultSchema>;
35
35
 
36
36
  type ReplayDialogProps = {
37
37
  log: CapturedLog;
38
+ displayNumber: number;
38
39
  open: boolean;
39
40
  onOpenChange: (open: boolean) => void;
40
41
  };
@@ -159,7 +160,12 @@ function ReplayComparisonTable({
159
160
  );
160
161
  }
161
162
 
162
- export function ReplayDialog({ log, open, onOpenChange }: ReplayDialogProps): JSX.Element {
163
+ export function ReplayDialog({
164
+ log,
165
+ displayNumber,
166
+ open,
167
+ onOpenChange,
168
+ }: ReplayDialogProps): JSX.Element {
163
169
  const { providers } = useProviders();
164
170
  const originalBody = log.rawRequestBody ?? "{}";
165
171
  const [modifiedBody, setModifiedBody] = useState<string>(() => {
@@ -310,7 +316,7 @@ export function ReplayDialog({ log, open, onOpenChange }: ReplayDialogProps): JS
310
316
  <DialogHeader>
311
317
  <DialogTitle className="flex items-center gap-2">
312
318
  <RotateCcw className="size-4" />
313
- Replay Request #{log.id}
319
+ <span title={`Log ID ${String(log.id)}`}>Replay Request #{displayNumber}</span>
314
320
  </DialogTitle>
315
321
  </DialogHeader>
316
322
 
@@ -101,7 +101,7 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
101
101
 
102
102
  return (
103
103
  <Collapsible open={open} onOpenChange={setOpen}>
104
- <div className="rounded-md border border-white/10 bg-black/20 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
104
+ <div className="border border-border bg-card shadow-sm rounded-[8px]">
105
105
  <div className="flex min-w-0 items-center gap-2 px-2.5 py-2">
106
106
  <button
107
107
  type="button"
@@ -140,7 +140,7 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
140
140
  </button>
141
141
  <button
142
142
  type="button"
143
- className="inline-flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
143
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
144
144
  onClick={nameCopy.copy}
145
145
  aria-label={nameCopy.copied ? "Copied tool name" : "Copy tool name"}
146
146
  >
@@ -153,13 +153,13 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
153
153
  </div>
154
154
  <CollapsibleContent>
155
155
  {open && (
156
- <div className="space-y-2 border-t border-white/10 bg-white/[0.02] px-2.5 py-2">
156
+ <div className="space-y-2 border-t border-white/10 bg-white/[0.025] px-2.5 py-2">
157
157
  {tool.description !== null && (
158
158
  <p className="text-xs leading-relaxed text-muted-foreground">{tool.description}</p>
159
159
  )}
160
160
  <RequiredParameters names={tool.requiredParameters} />
161
161
  {tool.schema !== null && (
162
- <ScrollArea className="max-h-64 rounded-md border border-white/10 bg-[#06080b] p-2">
162
+ <ScrollArea className="border border-border bg-card shadow-sm max-h-64 rounded-[8px] p-2">
163
163
  <Suspense
164
164
  fallback={<div className="text-xs text-muted-foreground">Loading JSON...</div>}
165
165
  >
@@ -203,7 +203,7 @@ export const RequestTools = memo(function RequestTools({
203
203
  const hiddenPreviewCount = summary.tools.length - previewTools.length;
204
204
 
205
205
  return (
206
- <section className="mb-3 overflow-hidden rounded-md border border-white/10 bg-[#080b0f] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]">
206
+ <section className="border border-border bg-card shadow-sm mb-3 overflow-hidden rounded-[8px]">
207
207
  <div className="flex min-w-0 flex-wrap items-center gap-2 px-3 py-2">
208
208
  <div className="flex min-w-0 flex-1 items-center gap-2">
209
209
  <Wrench className="size-4 shrink-0 text-sky-300/80" />
@@ -242,7 +242,7 @@ export const RequestTools = memo(function RequestTools({
242
242
  type="button"
243
243
  variant="ghost"
244
244
  size="sm"
245
- className="h-7 border border-white/10 bg-black/20 px-2 text-muted-foreground hover:border-cyan-300/30 hover:bg-cyan-400/10 hover:text-cyan-100"
245
+ className="h-7 px-2 text-muted-foreground hover:text-cyan-100"
246
246
  onClick={() => setExpanded((value) => !value)}
247
247
  aria-expanded={expanded}
248
248
  >
@@ -251,7 +251,7 @@ export const RequestTools = memo(function RequestTools({
251
251
  </Button>
252
252
  </div>
253
253
 
254
- <div className="flex min-w-0 flex-wrap gap-1.5 border-t border-white/10 bg-black/10 px-3 py-2">
254
+ <div className="flex min-w-0 flex-wrap gap-1.5 border-t border-white/10 bg-white/[0.025] px-3 py-2">
255
255
  {previewTools.map((tool) => (
256
256
  <Badge
257
257
  key={tool.name}
@@ -272,8 +272,8 @@ export const RequestTools = memo(function RequestTools({
272
272
  </div>
273
273
 
274
274
  {expanded && (
275
- <div className="space-y-2 border-t border-white/10 bg-black/15 px-3 py-3">
276
- <label className="flex h-8 items-center gap-2 rounded-md border border-white/10 bg-[#06080b] px-2 text-xs">
275
+ <div className="space-y-2 border-t border-white/10 bg-black/10 px-3 py-3">
276
+ <label className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex h-8 items-center gap-2 rounded-md px-2 text-xs">
277
277
  <Search className="size-3.5 shrink-0 text-muted-foreground" />
278
278
  <input
279
279
  value={query}
@@ -1,6 +1,6 @@
1
1
  import { type JSX, useMemo } from "react";
2
2
  import { cn } from "../../lib/utils";
3
- import type { StopReason } from "../../lib/stopReason";
3
+ import { isTurnBoundary, type StopReason } from "../../lib/stopReason";
4
4
  import { getCrabVariant, getInteriorCrabVariant } from "../ui/crab-variants";
5
5
 
6
6
  export type ThreadConnectorProps = {
@@ -18,10 +18,24 @@ export type ThreadConnectorProps = {
18
18
  onToggle?: () => void;
19
19
  };
20
20
 
21
+ function boundaryTitle(stopReason: StopReason): string {
22
+ switch (stopReason) {
23
+ case "end_turn":
24
+ return "End of Turn (Anthropic)";
25
+ case "stop":
26
+ return "End of Turn (OpenAI)";
27
+ case "length":
28
+ return "End of Turn (length limit)";
29
+ case "tool_use":
30
+ case null:
31
+ return "End of Turn";
32
+ }
33
+ }
34
+
21
35
  /**
22
36
  * Vertical timeline connector. Top spacer uses a calc() that
23
37
  * mixes rem (py-1 + half-line) and px (border) so the crab
24
- * centre-line matches the LogEntry `#id` index regardless of the
38
+ * centre-line matches the LogEntry display number regardless of the
25
39
  * root font-size. The bottom spacer is flex-1 so the outgoing
26
40
  * line adapts to whatever height the LogEntry occupies.
27
41
  */
@@ -35,7 +49,7 @@ export function ThreadConnector({
35
49
  collapsible = false,
36
50
  onToggle,
37
51
  }: ThreadConnectorProps): JSX.Element {
38
- const isBoundary = stopReason === "end_turn" || stopReason === "stop";
52
+ const isBoundary = isTurnBoundary(stopReason);
39
53
  const isFusedBoundary = isOnlyEntry && isTurnStart && isBoundary;
40
54
  const isRunning = isPending && !isBoundary;
41
55
  const Crab = useMemo(() => getCrabVariant(crabIndex), [crabIndex]);
@@ -77,17 +91,14 @@ export function ThreadConnector({
77
91
  <span title="Start and end of turn">
78
92
  <FusedCrab
79
93
  className={cn(
80
- "size-3.5 text-[#80FF00]",
94
+ "size-3.5 text-cyan-300/90",
81
95
  "animate-crab-settle",
82
- "drop-shadow-[0_0_4px_rgba(128,255,0,0.5)]",
96
+ "drop-shadow-[0_0_4px_rgba(103,232,249,0.35)]",
83
97
  )}
84
98
  />
85
99
  </span>
86
100
  ) : isBoundary ? (
87
- <span
88
- title={stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"}
89
- {...interactiveProps}
90
- >
101
+ <span title={boundaryTitle(stopReason)} {...interactiveProps}>
91
102
  <Crab
92
103
  className={cn(
93
104
  "size-3.5 text-amber-400",
@@ -100,9 +111,9 @@ export function ThreadConnector({
100
111
  <span title="Start of turn" {...interactiveProps}>
101
112
  <Crab
102
113
  className={cn(
103
- "size-3.5 text-emerald-400",
114
+ "size-3.5 text-cyan-300/85",
104
115
  "animate-crab-appear",
105
- "drop-shadow-[0_0_4px_rgba(52,211,153,0.5)]",
116
+ "drop-shadow-[0_0_4px_rgba(103,232,249,0.32)]",
106
117
  )}
107
118
  />
108
119
  </span>