@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
@@ -7,7 +7,6 @@ import {
7
7
  Loader2,
8
8
  MessageSquare,
9
9
  Trash2,
10
- User,
11
10
  Zap,
12
11
  } from "lucide-react";
13
12
  import type { JSX } from "react";
@@ -17,8 +16,9 @@ import { formatTimestampRange } from "../../lib/timeDisplay";
17
16
  import { cn, formatTokens } from "../../lib/utils";
18
17
  import type { CapturedLog } from "../../contracts";
19
18
  import { ConfirmDialog } from "../ui/confirm-dialog";
20
- import { ProviderLogo, type Provider } from "../providers/ProviderLogo";
21
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
19
+ import type { Provider } from "../providers/ProviderLogo";
20
+ import { ClientLogo, clientAppLabel, detectClientApp } from "../clients/ClientLogo";
21
+ import { ProviderLogoStack } from "./ProviderLogoStack";
22
22
  import { resolveLogFormat } from "./log-formats";
23
23
 
24
24
  export type ConversationHeaderProps = {
@@ -31,8 +31,8 @@ export type ConversationHeaderProps = {
31
31
  expanded: boolean;
32
32
  onToggle: () => void;
33
33
  /** Unique providers whose models appear in this group, with their distinct
34
- * model names. Rendered as a row of provider logos next to the header
35
- * one per provider so model-switching inside a single session is visible
34
+ * model names. Rendered as a row of provider logos next to the header, one
35
+ * per provider so model-switching inside a single session is visible.
36
36
  * at a glance. `undefined` hides the strip (default). */
37
37
  providers?: { provider: Provider; models: string[] }[];
38
38
  /** When true and the group is collapsed, show a spinner instead of the
@@ -50,6 +50,10 @@ export type ConversationHeaderProps = {
50
50
  * Only shown as a secondary label when the session lacks a real
51
51
  * `sessionId` (the same fallback path used for `clientPid`). */
52
52
  clientProjectFolder?: string | null;
53
+ /** Show IDE identity in the header. Parent IDE rows can disable this to avoid repetition. */
54
+ showClientIdentity?: boolean;
55
+ /** Show process/project metadata in the header. Parent process rows can disable this. */
56
+ showProcessMetadata?: boolean;
53
57
  /** Controls whether timestamps render as compact local time or full ISO strings. */
54
58
  timeDisplayFormat: TimeDisplayFormat;
55
59
  /** Clear all logs in this group. After confirmation the parent removes them
@@ -71,6 +75,8 @@ export function ConversationHeader({
71
75
  userAgent,
72
76
  clientPid,
73
77
  clientProjectFolder,
78
+ showClientIdentity = true,
79
+ showProcessMetadata = true,
74
80
  timeDisplayFormat,
75
81
  onClear,
76
82
  }: ConversationHeaderProps): JSX.Element {
@@ -89,6 +95,22 @@ export function ConversationHeader({
89
95
  },
90
96
  [conversationId],
91
97
  );
98
+ const providerLogoItems =
99
+ providers?.map(({ provider, models }) => ({
100
+ provider,
101
+ tooltip: models.length === 1 ? (models[0] ?? provider) : models.join(", "),
102
+ })) ?? [];
103
+ const clientApp = detectClientApp({ userAgent, conversationId, clientProjectFolder });
104
+ const detectedClientLabel = clientAppLabel(clientApp);
105
+ const hasUserAgent = userAgent !== null && userAgent !== undefined && userAgent !== "";
106
+ const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
107
+ const clientTooltip =
108
+ detectedClientLabel !== null && hasUserAgent ? `${detectedClientLabel} - ${userAgent}` : null;
109
+ const isFallbackConversation = conversationId.startsWith("PID:") || conversationId.includes("|");
110
+ const compactSessionLabel =
111
+ conversationId.length > 24
112
+ ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
113
+ : conversationId;
92
114
 
93
115
  return (
94
116
  <div
@@ -97,10 +119,10 @@ export function ConversationHeader({
97
119
  data-nav-id={`conv-${conversationId}`}
98
120
  data-nav-action={expanded ? "collapse" : "expand"}
99
121
  className={cn(
100
- "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
101
- "hover:bg-muted/50",
122
+ "border border-border bg-card shadow-sm flex cursor-pointer items-center gap-3 rounded-[8px] px-3 py-2 transition-colors",
123
+ "hover:border-cyan-200/20",
102
124
  "select-none",
103
- "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
125
+ "sticky top-0 z-10 mb-2",
104
126
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
105
127
  )}
106
128
  onClick={onToggle}
@@ -112,34 +134,28 @@ export function ConversationHeader({
112
134
  }
113
135
  }}
114
136
  >
115
- {/* Expand chevron - shows spinner when collapsed and group has pending logs */}
116
- {expanded ? (
117
- <ChevronDown className="size-4 text-muted-foreground shrink-0" />
118
- ) : isLoading ? (
119
- <Loader2 className="size-4 animate-spin text-muted-foreground shrink-0" />
120
- ) : (
121
- <ChevronRight className="size-4 text-muted-foreground shrink-0" />
122
- )}
137
+ {/* Provider logos: first visual element whenever model/provider logos are available. */}
138
+ <ProviderLogoStack items={providerLogoItems} ariaLabel="Models used in this session" />
123
139
 
124
140
  {/* Session identifier.
125
- - Real session id (e.g. `sess_abc123...`) render the id with the
141
+ - Real session id (e.g. `sess_abc123...`) - render the id with the
126
142
  standard ellipsis truncation; show `PID <n>` as a secondary pill
127
143
  when we know the originating process.
128
- - Fallback id (no sessionId was emitted) the conversationId is
144
+ - Fallback id (no sessionId was emitted) - the conversationId is
129
145
  already a synthesized `PID:<n>|<folder>` marker; render the PID
130
146
  as the primary identifier (purple, same weight as a real id)
131
147
  and the project folder as a secondary muted label. */}
132
- {conversationId.startsWith("PID:") || conversationId.includes("|") ? (
148
+ {isFallbackConversation && showProcessMetadata ? (
133
149
  <>
134
150
  {clientPid !== null && clientPid !== undefined ? (
135
151
  <span
136
- className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
152
+ className="shrink-0 font-mono text-xs font-semibold text-violet-200"
137
153
  title={`PID ${clientPid}`}
138
154
  >
139
155
  PID {clientPid}
140
156
  </span>
141
157
  ) : (
142
- <span className="text-purple-400/90 font-mono text-xs font-semibold shrink-0">
158
+ <span className="shrink-0 font-mono text-xs font-semibold text-violet-200">
143
159
  {conversationId}
144
160
  </span>
145
161
  )}
@@ -157,16 +173,14 @@ export function ConversationHeader({
157
173
  ) : (
158
174
  <>
159
175
  <span
160
- className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
176
+ className="shrink-0 font-mono text-xs font-semibold text-violet-200"
161
177
  title={conversationId}
162
178
  >
163
- {conversationId.length > 24
164
- ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
165
- : conversationId}
179
+ {isFallbackConversation ? "sessionless" : compactSessionLabel}
166
180
  </span>
167
- {clientPid !== null && clientPid !== undefined && (
181
+ {showProcessMetadata && clientPid !== null && clientPid !== undefined && (
168
182
  <span
169
- className="text-muted-foreground text-xs font-mono tabular-nums shrink-0 px-1.5 py-0.5 rounded border border-border bg-muted/40"
183
+ className="border border-border bg-muted/40 shrink-0 rounded px-1.5 py-0.5 font-mono text-xs tabular-nums text-muted-foreground"
170
184
  title={`Client process ID ${clientPid}`}
171
185
  >
172
186
  PID {clientPid}
@@ -175,41 +189,17 @@ export function ConversationHeader({
175
189
  </>
176
190
  )}
177
191
 
178
- {/* User-Agent */}
179
- {userAgent !== null && userAgent !== undefined && userAgent !== "" && (
192
+ {/* Client app / User-Agent */}
193
+ {showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && (
180
194
  <span
181
195
  className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
182
- title={userAgent}
196
+ title={clientTooltip ?? clientDisplayLabel}
183
197
  >
184
- <User className="size-3" />
185
- <span className="font-mono tabular-nums truncate max-w-[120px]">{userAgent}</span>
186
- </span>
187
- )}
188
-
189
- {/* Provider logos — one per unique provider used in this session. When
190
- a session switched models mid-stream (e.g. claude-3-5 → deepseek-v4)
191
- multiple logos appear side-by-side; tooltip lists every distinct
192
- model name. */}
193
- {providers !== undefined && providers.length > 0 && (
194
- <TooltipProvider delayDuration={150}>
195
- <span
196
- className="flex items-center gap-0.5 shrink-0"
197
- aria-label="Models used in this session"
198
- >
199
- {providers.map(({ provider, models }) => (
200
- <Tooltip key={provider}>
201
- <TooltipTrigger asChild>
202
- <span className="shrink-0">
203
- <ProviderLogo provider={provider} className="size-4" />
204
- </span>
205
- </TooltipTrigger>
206
- <TooltipContent>
207
- {models.length === 1 ? models[0] : models.join(", ")}
208
- </TooltipContent>
209
- </Tooltip>
210
- ))}
198
+ <ClientLogo client={clientApp} className="size-5" />
199
+ <span className="font-mono tabular-nums truncate max-w-[120px]">
200
+ {clientDisplayLabel}
211
201
  </span>
212
- </TooltipProvider>
202
+ </span>
213
203
  )}
214
204
 
215
205
  {/* Time range */}
@@ -252,7 +242,7 @@ export function ConversationHeader({
252
242
  }}
253
243
  aria-label={`Open session ${conversationId} in a new tab`}
254
244
  title="Open this session in a new tab"
255
- className="text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer"
245
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
256
246
  >
257
247
  <ExternalLink className="size-3.5" />
258
248
  </button>
@@ -264,12 +254,26 @@ export function ConversationHeader({
264
254
  onClick={handleClearClick}
265
255
  aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
266
256
  title="Clear this group"
267
- className="text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer"
257
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
268
258
  >
269
259
  <Trash2 className="size-3.5" />
270
260
  </button>
271
261
  )}
272
262
 
263
+ {/* Expand chevron - shows spinner when collapsed and group has pending logs */}
264
+ <span
265
+ 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"
266
+ aria-hidden="true"
267
+ >
268
+ {expanded ? (
269
+ <ChevronDown className="size-4 shrink-0" />
270
+ ) : isLoading ? (
271
+ <Loader2 className="size-4 shrink-0 animate-spin" />
272
+ ) : (
273
+ <ChevronRight className="size-4 shrink-0" />
274
+ )}
275
+ </span>
276
+
273
277
  <ConfirmDialog
274
278
  open={confirmOpen}
275
279
  onOpenChange={setConfirmOpen}
@@ -320,10 +324,10 @@ export function hasMixedApiFormat(logs: CapturedLog[]): boolean {
320
324
  }
321
325
 
322
326
  export function getConversationId(log: CapturedLog): string {
323
- if (log.isTest === true) return "provider-test";
324
327
  if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== undefined) {
325
328
  return log.sessionId;
326
329
  }
330
+ if (log.isTest === true) return "provider-test";
327
331
  const hasPid = log.clientPid !== null && log.clientPid !== undefined;
328
332
  const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== undefined;
329
333
  if (!hasPid && !hasFolder) return "default";
@@ -75,7 +75,7 @@ const BODY_CHUNK_BYTES = 256 * 1024;
75
75
  const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
76
76
 
77
77
  const TAB_TRIGGER_CLASS =
78
- "h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:bg-cyan-400/[0.05] hover:text-foreground data-[state=active]:border-cyan-300 data-[state=active]:bg-cyan-300/[0.04] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
78
+ "h-9 flex-none rounded-md border border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:border-white/10 hover:bg-white/[0.045] hover:text-foreground data-[state=active]:border-cyan-200/25 data-[state=active]:bg-cyan-300/[0.08] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
79
79
 
80
80
  function createEmptyBodyPreviewState(totalBytes: number | null = null): BodyPreviewState {
81
81
  return {
@@ -140,7 +140,7 @@ function ExpandedPanel({
140
140
  return (
141
141
  <div
142
142
  className={cn(
143
- "inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-md border border-cyan-300/10 bg-[#05070a] p-3 shadow-[0_16px_45px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.05)]",
143
+ "border border-border bg-card shadow-sm inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-[8px] p-3",
144
144
  className,
145
145
  )}
146
146
  >
@@ -151,7 +151,7 @@ function ExpandedPanel({
151
151
 
152
152
  function EmptyState({ children }: { children: ReactNode }): JSX.Element {
153
153
  return (
154
- <div className="rounded-md border border-dashed border-white/10 bg-white/[0.02] px-3 py-5 text-center text-xs italic text-muted-foreground">
154
+ <div className="border border-border bg-muted/40 rounded-md border-dashed px-3 py-5 text-center text-xs italic text-muted-foreground">
155
155
  {children}
156
156
  </div>
157
157
  );
@@ -159,7 +159,7 @@ function EmptyState({ children }: { children: ReactNode }): JSX.Element {
159
159
 
160
160
  function RawTextBlock({ children }: { children: ReactNode }): JSX.Element {
161
161
  return (
162
- <pre className="inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-md border border-white/10 bg-black/30 p-3 font-mono text-xs leading-relaxed text-muted-foreground">
162
+ <pre className="border border-border bg-card shadow-sm inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-[8px] p-3 font-mono text-xs leading-relaxed text-muted-foreground">
163
163
  {children}
164
164
  </pre>
165
165
  );
@@ -231,7 +231,7 @@ function BodyPreviewNotice({
231
231
  type="button"
232
232
  variant="outline"
233
233
  size="sm"
234
- className="h-7 border-white/10 bg-black/20 text-xs text-cyan-100 hover:border-cyan-300/35 hover:bg-cyan-400/10"
234
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 text-xs text-cyan-100"
235
235
  onClick={onLoadMore}
236
236
  >
237
237
  {actionLabel}
@@ -257,13 +257,13 @@ function HeaderRows({
257
257
  if (entries.length === 0) return <EmptyState>{emptyLabel}</EmptyState>;
258
258
 
259
259
  return (
260
- <div className="overflow-hidden rounded-md border border-white/10 bg-black/20 font-mono text-xs">
260
+ <div className="border border-border bg-card shadow-sm overflow-hidden rounded-[8px] font-mono text-xs">
261
261
  {entries.map(([key, value]) => (
262
262
  <div
263
263
  key={key}
264
- className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/5 last:border-b-0"
264
+ className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/10 last:border-b-0"
265
265
  >
266
- <div className="min-w-0 truncate border-r border-white/5 bg-white/[0.03] px-2.5 py-2 font-semibold text-cyan-200">
266
+ <div className="min-w-0 truncate border-r border-white/10 bg-white/[0.035] px-2.5 py-2 font-semibold text-cyan-200">
267
267
  {key}
268
268
  </div>
269
269
  <div className="min-w-0 truncate px-2.5 py-2 text-muted-foreground" title={value}>
@@ -277,6 +277,8 @@ function HeaderRows({
277
277
 
278
278
  export type LogEntryProps = {
279
279
  log: CapturedLog;
280
+ /** Human-facing ordinal inside the current session. Internal lookups still use `log.id`. */
281
+ displayNumber: number;
280
282
  viewMode?: "simple" | "full";
281
283
  /** Live "strip Claude Code billing header" flag, sourced once at the viewer
282
284
  * container. Hoisted out of `LogEntry` so a single SWR subscription serves
@@ -295,6 +297,8 @@ export type LogEntryProps = {
295
297
  cacheTrend?: CacheTrendEntry | null;
296
298
  /** Callback to open CompareDrawer with this log and its immediate predecessor. */
297
299
  onCompareWithPrevious?: (log: CapturedLog) => void;
300
+ /** Show client PID/project metadata on the log header row. */
301
+ showClientMetadata?: boolean;
298
302
  };
299
303
 
300
304
  function DiffToggleButton({
@@ -314,7 +318,7 @@ function DiffToggleButton({
314
318
  "h-8 border-white/10 text-xs shadow-none",
315
319
  active
316
320
  ? "bg-cyan-300 text-black hover:bg-cyan-200"
317
- : "bg-[#0a0d11] text-muted-foreground hover:border-cyan-300/30 hover:bg-cyan-400/10 hover:text-cyan-100",
321
+ : "border border-input bg-background hover:bg-accent hover:text-accent-foreground text-muted-foreground hover:text-cyan-100",
318
322
  )}
319
323
  onClick={onClick}
320
324
  aria-pressed={active}
@@ -361,12 +365,14 @@ const HeadersDiffContent = memo(function ({
361
365
 
362
366
  export const LogEntry = memo(function ({
363
367
  log,
368
+ displayNumber,
364
369
  viewMode = "simple",
365
370
  strip,
366
371
  slowResponseThresholdSeconds,
367
372
  timeDisplayFormat,
368
373
  cacheTrend = null,
369
374
  onCompareWithPrevious,
375
+ showClientMetadata = true,
370
376
  }: LogEntryProps): JSX.Element {
371
377
  const [expanded, setExpanded] = useState<boolean>(false);
372
378
  const [replayOpen, setReplayOpen] = useState<boolean>(false);
@@ -429,7 +435,7 @@ export const LogEntry = memo(function ({
429
435
  );
430
436
 
431
437
  // Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
432
- // Only build the string when there's at least one entry empty headers would
438
+ // Only build the string when there's at least one entry; empty headers would
433
439
  // otherwise copy "{}", which is misleading.
434
440
  const headersText = useMemo(
435
441
  () =>
@@ -715,16 +721,16 @@ export const LogEntry = memo(function ({
715
721
  <TooltipProvider>
716
722
  <div
717
723
  className={cn(
718
- "relative mb-2 overflow-hidden rounded-lg border bg-[#07090d] transition-[background-color,border-color,box-shadow]",
724
+ "border border-border bg-card shadow-sm relative mb-2 overflow-hidden rounded-[8px] transition-[background-color,border-color,box-shadow]",
719
725
  expanded
720
- ? "border-cyan-300/25 shadow-[0_24px_80px_rgba(0,0,0,0.34),0_0_0_1px_rgba(103,232,249,0.04),inset_0_1px_0_rgba(255,255,255,0.06)]"
721
- : "border-white/10 shadow-[0_6px_24px_rgba(0,0,0,0.16)] hover:border-cyan-300/15",
726
+ ? "border-cyan-200/25 shadow-[0_24px_80px_rgba(0,0,0,0.36),0_0_0_1px_rgba(103,232,249,0.05),inset_0_1px_0_rgba(255,255,255,0.14)]"
727
+ : "hover:border-cyan-200/18",
722
728
  )}
723
729
  >
724
730
  <div
725
731
  className={cn(
726
732
  "pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
727
- expanded ? "bg-cyan-300/45 shadow-[0_0_20px_rgba(103,232,249,0.28)]" : "bg-white/10",
733
+ expanded ? "bg-cyan-200/55 shadow-[0_0_20px_rgba(103,232,249,0.26)]" : "bg-white/12",
728
734
  )}
729
735
  aria-hidden="true"
730
736
  />
@@ -734,12 +740,14 @@ export const LogEntry = memo(function ({
734
740
  />
735
741
  <LogEntryHeader
736
742
  log={displayLog}
743
+ displayNumber={displayNumber}
737
744
  messageCount={requestAnalysis.messageCount}
738
745
  toolCount={requestAnalysis.toolCount}
739
746
  expanded={expanded}
740
747
  onToggle={handleToggleExpanded}
741
748
  cacheTrend={cacheTrend}
742
749
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
750
+ showClientMetadata={showClientMetadata}
743
751
  timeDisplayFormat={timeDisplayFormat}
744
752
  activeTab={activeTab}
745
753
  tabActions={tabActions}
@@ -754,18 +762,18 @@ export const LogEntry = memo(function ({
754
762
 
755
763
  {expanded && (
756
764
  <div
757
- className="relative border-t border-cyan-300/10 bg-[linear-gradient(180deg,#06090d_0%,#040506_100%)] pt-2"
765
+ className="relative border-t border-white/10 bg-black/[0.18] pt-2"
758
766
  onClick={(e) => e.stopPropagation()}
759
767
  onKeyDown={(e) => e.stopPropagation()}
760
768
  >
761
769
  <div
762
- className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-cyan-300/[0.035] to-transparent"
770
+ className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-white/[0.05] to-transparent"
763
771
  aria-hidden="true"
764
772
  />
765
773
  <Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
766
774
  <TabsList
767
775
  variant="line"
768
- className="inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-none border-b border-cyan-300/10 bg-black/10 p-0"
776
+ className="border border-border bg-muted/40 inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-[8px] p-1"
769
777
  >
770
778
  {viewMode === "full" && (
771
779
  <TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
@@ -1051,7 +1059,12 @@ export const LogEntry = memo(function ({
1051
1059
  )}
1052
1060
  </div>
1053
1061
  <Suspense fallback={null}>
1054
- <LazyReplayDialog log={displayLog} open={replayOpen} onOpenChange={setReplayOpen} />
1062
+ <LazyReplayDialog
1063
+ log={displayLog}
1064
+ displayNumber={displayNumber}
1065
+ open={replayOpen}
1066
+ onOpenChange={setReplayOpen}
1067
+ />
1055
1068
  </Suspense>
1056
1069
  </TooltipProvider>
1057
1070
  );