@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
@@ -18,7 +18,7 @@ import type { CapturedLog } from "../../contracts";
18
18
  import { ConfirmDialog } from "../ui/confirm-dialog";
19
19
  import type { Provider } from "../providers/ProviderLogo";
20
20
  import { ClientLogo, clientAppLabel, detectClientApp } from "../clients/ClientLogo";
21
- import { ProviderLogoStack, ROW_CHEVRON_SLOT_CLASS } from "./ProviderLogoStack";
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 {
@@ -100,6 +106,11 @@ export function ConversationHeader({
100
106
  const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
101
107
  const clientTooltip =
102
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;
103
114
 
104
115
  return (
105
116
  <div
@@ -108,10 +119,10 @@ export function ConversationHeader({
108
119
  data-nav-id={`conv-${conversationId}`}
109
120
  data-nav-action={expanded ? "collapse" : "expand"}
110
121
  className={cn(
111
- "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
112
- "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",
113
124
  "select-none",
114
- "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
125
+ "sticky top-0 z-10 mb-2",
115
126
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
116
127
  )}
117
128
  onClick={onToggle}
@@ -126,36 +137,25 @@ export function ConversationHeader({
126
137
  {/* Provider logos: first visual element whenever model/provider logos are available. */}
127
138
  <ProviderLogoStack items={providerLogoItems} ariaLabel="Models used in this session" />
128
139
 
129
- {/* Expand chevron - shows spinner when collapsed and group has pending logs */}
130
- <span className={ROW_CHEVRON_SLOT_CLASS}>
131
- {expanded ? (
132
- <ChevronDown className="size-4 shrink-0" />
133
- ) : isLoading ? (
134
- <Loader2 className="size-4 shrink-0 animate-spin" />
135
- ) : (
136
- <ChevronRight className="size-4 shrink-0" />
137
- )}
138
- </span>
139
-
140
140
  {/* Session identifier.
141
- - 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
142
142
  standard ellipsis truncation; show `PID <n>` as a secondary pill
143
143
  when we know the originating process.
144
- - Fallback id (no sessionId was emitted) the conversationId is
144
+ - Fallback id (no sessionId was emitted) - the conversationId is
145
145
  already a synthesized `PID:<n>|<folder>` marker; render the PID
146
146
  as the primary identifier (purple, same weight as a real id)
147
147
  and the project folder as a secondary muted label. */}
148
- {conversationId.startsWith("PID:") || conversationId.includes("|") ? (
148
+ {isFallbackConversation && showProcessMetadata ? (
149
149
  <>
150
150
  {clientPid !== null && clientPid !== undefined ? (
151
151
  <span
152
- 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"
153
153
  title={`PID ${clientPid}`}
154
154
  >
155
155
  PID {clientPid}
156
156
  </span>
157
157
  ) : (
158
- <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">
159
159
  {conversationId}
160
160
  </span>
161
161
  )}
@@ -173,16 +173,14 @@ export function ConversationHeader({
173
173
  ) : (
174
174
  <>
175
175
  <span
176
- 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"
177
177
  title={conversationId}
178
178
  >
179
- {conversationId.length > 24
180
- ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
181
- : conversationId}
179
+ {isFallbackConversation ? "sessionless" : compactSessionLabel}
182
180
  </span>
183
- {clientPid !== null && clientPid !== undefined && (
181
+ {showProcessMetadata && clientPid !== null && clientPid !== undefined && (
184
182
  <span
185
- 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"
186
184
  title={`Client process ID ${clientPid}`}
187
185
  >
188
186
  PID {clientPid}
@@ -192,7 +190,7 @@ export function ConversationHeader({
192
190
  )}
193
191
 
194
192
  {/* Client app / User-Agent */}
195
- {clientDisplayLabel !== null && clientDisplayLabel !== "" && (
193
+ {showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && (
196
194
  <span
197
195
  className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
198
196
  title={clientTooltip ?? clientDisplayLabel}
@@ -244,7 +242,7 @@ export function ConversationHeader({
244
242
  }}
245
243
  aria-label={`Open session ${conversationId} in a new tab`}
246
244
  title="Open this session in a new tab"
247
- 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"
248
246
  >
249
247
  <ExternalLink className="size-3.5" />
250
248
  </button>
@@ -256,12 +254,26 @@ export function ConversationHeader({
256
254
  onClick={handleClearClick}
257
255
  aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
258
256
  title="Clear this group"
259
- 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"
260
258
  >
261
259
  <Trash2 className="size-3.5" />
262
260
  </button>
263
261
  )}
264
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
+
265
277
  <ConfirmDialog
266
278
  open={confirmOpen}
267
279
  onOpenChange={setConfirmOpen}
@@ -312,10 +324,10 @@ export function hasMixedApiFormat(logs: CapturedLog[]): boolean {
312
324
  }
313
325
 
314
326
  export function getConversationId(log: CapturedLog): string {
315
- if (log.isTest === true) return "provider-test";
316
327
  if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== undefined) {
317
328
  return log.sessionId;
318
329
  }
330
+ if (log.isTest === true) return "provider-test";
319
331
  const hasPid = log.clientPid !== null && log.clientPid !== undefined;
320
332
  const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== undefined;
321
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}>
@@ -297,6 +297,8 @@ export type LogEntryProps = {
297
297
  cacheTrend?: CacheTrendEntry | null;
298
298
  /** Callback to open CompareDrawer with this log and its immediate predecessor. */
299
299
  onCompareWithPrevious?: (log: CapturedLog) => void;
300
+ /** Show client PID/project metadata on the log header row. */
301
+ showClientMetadata?: boolean;
300
302
  };
301
303
 
302
304
  function DiffToggleButton({
@@ -316,7 +318,7 @@ function DiffToggleButton({
316
318
  "h-8 border-white/10 text-xs shadow-none",
317
319
  active
318
320
  ? "bg-cyan-300 text-black hover:bg-cyan-200"
319
- : "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",
320
322
  )}
321
323
  onClick={onClick}
322
324
  aria-pressed={active}
@@ -370,6 +372,7 @@ export const LogEntry = memo(function ({
370
372
  timeDisplayFormat,
371
373
  cacheTrend = null,
372
374
  onCompareWithPrevious,
375
+ showClientMetadata = true,
373
376
  }: LogEntryProps): JSX.Element {
374
377
  const [expanded, setExpanded] = useState<boolean>(false);
375
378
  const [replayOpen, setReplayOpen] = useState<boolean>(false);
@@ -432,7 +435,7 @@ export const LogEntry = memo(function ({
432
435
  );
433
436
 
434
437
  // Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
435
- // 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
436
439
  // otherwise copy "{}", which is misleading.
437
440
  const headersText = useMemo(
438
441
  () =>
@@ -718,16 +721,16 @@ export const LogEntry = memo(function ({
718
721
  <TooltipProvider>
719
722
  <div
720
723
  className={cn(
721
- "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]",
722
725
  expanded
723
- ? "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)]"
724
- : "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",
725
728
  )}
726
729
  >
727
730
  <div
728
731
  className={cn(
729
732
  "pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
730
- 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",
731
734
  )}
732
735
  aria-hidden="true"
733
736
  />
@@ -744,6 +747,7 @@ export const LogEntry = memo(function ({
744
747
  onToggle={handleToggleExpanded}
745
748
  cacheTrend={cacheTrend}
746
749
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
750
+ showClientMetadata={showClientMetadata}
747
751
  timeDisplayFormat={timeDisplayFormat}
748
752
  activeTab={activeTab}
749
753
  tabActions={tabActions}
@@ -758,18 +762,18 @@ export const LogEntry = memo(function ({
758
762
 
759
763
  {expanded && (
760
764
  <div
761
- 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"
762
766
  onClick={(e) => e.stopPropagation()}
763
767
  onKeyDown={(e) => e.stopPropagation()}
764
768
  >
765
769
  <div
766
- 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"
767
771
  aria-hidden="true"
768
772
  />
769
773
  <Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
770
774
  <TabsList
771
775
  variant="line"
772
- 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"
773
777
  >
774
778
  {viewMode === "full" && (
775
779
  <TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
@@ -32,10 +32,9 @@ import { Badge } from "../ui/badge";
32
32
  import { Button } from "../ui/button";
33
33
  import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
34
34
  import type { CacheTrend } from "./cacheTrend";
35
- import { ROW_CHEVRON_SLOT_CLASS } from "./ProviderLogoStack";
36
35
 
37
36
  const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
38
- 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",
39
38
  client_error: "bg-amber-300/[0.10] text-amber-100 border-amber-300/30",
40
39
  server_error: "bg-rose-400/[0.10] text-rose-100 border-rose-300/30",
41
40
  pending: "bg-cyan-300/[0.08] text-cyan-100 border-cyan-300/25",
@@ -46,36 +45,31 @@ const STATUS_CUE_CLASSES: Record<
46
45
  {
47
46
  frame: string;
48
47
  dot: string;
49
- rail: string;
50
48
  }
51
49
  > = {
52
50
  success: {
53
- frame: "border-emerald-300/20 bg-emerald-300/[0.06] text-emerald-100",
54
- dot: "bg-emerald-300 shadow-[0_0_14px_rgba(110,231,183,0.8)]",
55
- 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)]",
56
53
  },
57
54
  client_error: {
58
55
  frame: "border-amber-300/25 bg-amber-300/[0.08] text-amber-100",
59
56
  dot: "bg-amber-200 shadow-[0_0_14px_rgba(252,211,77,0.8)]",
60
- rail: "bg-amber-300/50 shadow-[0_0_22px_rgba(252,211,77,0.28)]",
61
57
  },
62
58
  server_error: {
63
59
  frame: "border-rose-300/25 bg-rose-300/[0.08] text-rose-100",
64
60
  dot: "bg-rose-300 shadow-[0_0_14px_rgba(253,164,175,0.8)]",
65
- rail: "bg-rose-300/50 shadow-[0_0_22px_rgba(253,164,175,0.28)]",
66
61
  },
67
62
  pending: {
68
63
  frame: "border-cyan-300/25 bg-cyan-300/[0.08] text-cyan-100",
69
64
  dot: "bg-cyan-200 shadow-[0_0_14px_rgba(103,232,249,0.8)]",
70
- rail: "bg-cyan-300/50 shadow-[0_0_22px_rgba(103,232,249,0.28)]",
71
65
  },
72
66
  };
73
67
 
74
68
  const HEADER_ACTION_BUTTON_CLASS =
75
- "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";
76
70
 
77
71
  const TELEMETRY_CHIP_CLASS =
78
- "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";
79
73
 
80
74
  function formatElapsed(ms: number): string {
81
75
  if (ms < 1000) return `${ms}ms`;
@@ -101,7 +95,7 @@ function statusCueLabel(category: StatusCategory, status: number | null): string
101
95
  }
102
96
 
103
97
  /**
104
- * Inline trend indicator: small arrow (green up / red down) plus the absolute
98
+ * Inline trend indicator: small arrow (up / down) plus the absolute
105
99
  * delta in compact form. Returns `null` when there is no trend to display.
106
100
  */
107
101
  function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Element | null {
@@ -111,7 +105,7 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
111
105
  const sign = isUp ? "+" : "-";
112
106
  return (
113
107
  <span className="flex items-center gap-0.5 text-muted-foreground tabular-nums">
114
- <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"} />
115
109
  <span className="font-mono">
116
110
  {sign}
117
111
  {formatTokens(trend.delta)}
@@ -122,12 +116,12 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
122
116
 
123
117
  /**
124
118
  * Per-tab action bundle surfaced in the header when the tab is active.
125
- * - `copyText === null` Copy button hidden (e.g. tab has no body to copy)
126
- * - `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
127
121
  * not valid JSON). When present, the button reflects the JSON viewer's
128
122
  * bulk-expansion state and the caller is responsible for forwarding the
129
123
  * matching `bulkDepth` / `bulkRevision` to the underlying viewer.
130
- * - `diffWithRaw` / `diffWithPrevious` optional Diff buttons. Undefined
124
+ * - `diffWithRaw` / `diffWithPrevious` - optional Diff buttons. Undefined
131
125
  * means "not applicable" (hidden). Today only the Request tab sets them.
132
126
  */
133
127
  export type HeaderTabAction = {
@@ -181,6 +175,8 @@ export type LogEntryHeaderProps = {
181
175
  onReplay?: () => void;
182
176
  /** Slow-response threshold in seconds. `0` disables the warning indicator. */
183
177
  slowResponseThresholdSeconds?: number;
178
+ /** Show client PID/project metadata on each log row. Parent process rows can disable this. */
179
+ showClientMetadata?: boolean;
184
180
  /** Controls whether timestamps render as compact local time or full ISO strings. */
185
181
  timeDisplayFormat: TimeDisplayFormat;
186
182
  };
@@ -197,6 +193,7 @@ export const LogEntryHeader = memo(function ({
197
193
  tabActions,
198
194
  onReplay,
199
195
  slowResponseThresholdSeconds = 0,
196
+ showClientMetadata = true,
200
197
  timeDisplayFormat,
201
198
  }: LogEntryHeaderProps): JSX.Element {
202
199
  const statusCategory = getStatusCategory(log.responseStatus);
@@ -233,9 +230,9 @@ export const LogEntryHeader = memo(function ({
233
230
  className={cn(
234
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]",
235
232
  expanded
236
- ? "bg-[linear-gradient(90deg,#0f1720_0%,#090d12_50%,#07090d_100%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.06)]"
237
- : "bg-[#080b10] hover:bg-[#0b1016]",
238
- "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",
239
236
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
240
237
  )}
241
238
  onClick={onToggle}
@@ -246,25 +243,10 @@ export const LogEntryHeader = memo(function ({
246
243
  }
247
244
  }}
248
245
  >
249
- <span
250
- className={cn(
251
- "absolute left-0 top-2 bottom-2 w-0.5 rounded-r-full transition-colors",
252
- statusCue.rail,
253
- )}
254
- aria-hidden="true"
255
- />
256
- <span className={ROW_CHEVRON_SLOT_CLASS}>
257
- {expanded ? (
258
- <ChevronDown className="size-4 shrink-0" />
259
- ) : (
260
- <ChevronRight className="size-4 shrink-0" />
261
- )}
262
- </span>
263
-
264
246
  {/* Request ID */}
265
247
  <span
266
248
  className={cn(
267
- "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",
268
250
  statusCue.frame,
269
251
  )}
270
252
  >
@@ -275,14 +257,14 @@ export const LogEntryHeader = memo(function ({
275
257
  </span>
276
258
 
277
259
  {/* Request start time */}
278
- <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">
279
261
  <Clock className="size-3" />
280
262
  <span className="font-mono tabular-nums" title={log.timestamp}>
281
263
  {formatTimestamp(log.timestamp, timeDisplayFormat)}
282
264
  </span>
283
265
  </span>
284
266
 
285
- {/* Response Status only shown for non-200 or pending. Each category
267
+ {/* Response Status - only shown for non-200 or pending. Each category
286
268
  carries a distinct icon in addition to color so the meaning is
287
269
  legible without color perception. */}
288
270
  <Badge
@@ -324,7 +306,7 @@ export const LogEntryHeader = memo(function ({
324
306
  )}
325
307
 
326
308
  {/* Elapsed time. Tooltip is only shown when this log exceeded the slow-
327
- response threshold for normal-speed logs the elapsed value is
309
+ response threshold - for normal-speed logs the elapsed value is
328
310
  self-explanatory, so we skip the tooltip wrapper to keep the row
329
311
  quiet. */}
330
312
  {log.elapsedMs !== null &&
@@ -369,7 +351,7 @@ export const LogEntryHeader = memo(function ({
369
351
  {log.streaming && tokenRateLabel !== null && (
370
352
  <Tooltip>
371
353
  <TooltipTrigger asChild>
372
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-emerald-300")}>
354
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
373
355
  <Gauge className="size-3" />
374
356
  <span className="font-mono tabular-nums">{tokenRateLabel}</span>
375
357
  </span>
@@ -389,7 +371,7 @@ export const LogEntryHeader = memo(function ({
389
371
  log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
390
372
  )}
391
373
  >
392
- IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : ""}
374
+ IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "n/a"}
393
375
  </span>
394
376
  <span className="text-muted-foreground">/</span>
395
377
  <span
@@ -398,7 +380,7 @@ export const LogEntryHeader = memo(function ({
398
380
  log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground",
399
381
  )}
400
382
  >
401
- OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : ""}
383
+ OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "n/a"}
402
384
  </span>
403
385
  </span>
404
386
  </span>
@@ -407,7 +389,7 @@ export const LogEntryHeader = memo(function ({
407
389
  {log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
408
390
  <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
409
391
  <CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
410
- <span className="font-mono tabular-nums text-emerald-400">
392
+ <span className="font-mono tabular-nums text-cyan-300">
411
393
  KV Cache +{formatTokens(log.cacheCreationInputTokens)}
412
394
  </span>
413
395
  </span>
@@ -451,7 +433,7 @@ export const LogEntryHeader = memo(function ({
451
433
  )}
452
434
 
453
435
  {/* Client info (PID + project folder) */}
454
- {(hasClientPid || hasClientProjectFolder) && (
436
+ {showClientMetadata && (hasClientPid || hasClientProjectFolder) && (
455
437
  <Tooltip>
456
438
  <TooltipTrigger asChild>
457
439
  <span className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}>
@@ -484,7 +466,7 @@ export const LogEntryHeader = memo(function ({
484
466
  {/* Spacer */}
485
467
  <span className="flex-1 min-w-0" />
486
468
 
487
- {/* Header actions only when expanded, so the collapsed view stays
469
+ {/* Header actions - only when expanded, so the collapsed view stays
488
470
  compact. Buttons stop propagation so they don't toggle the log. */}
489
471
  {expanded && (
490
472
  <span
@@ -583,7 +565,7 @@ export const LogEntryHeader = memo(function ({
583
565
  aria-label={action.copyCopied ? "Copied" : action.copyLabel}
584
566
  >
585
567
  {action.copyCopied ? (
586
- <Check className="size-3.5 text-emerald-500" />
568
+ <Check className="size-3.5 text-cyan-300" />
587
569
  ) : (
588
570
  <Copy className="size-3.5" />
589
571
  )}
@@ -615,6 +597,17 @@ export const LogEntryHeader = memo(function ({
615
597
  )}
616
598
  </span>
617
599
  )}
600
+
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
  );
@@ -39,7 +39,7 @@ export function ProviderLogoStack({
39
39
  <TooltipTrigger asChild>
40
40
  <span
41
41
  className={cn(
42
- "inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-white/10 bg-black/20 shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]",
42
+ "border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md",
43
43
  index > 0 && "-ml-2",
44
44
  )}
45
45
  >
@@ -50,7 +50,7 @@ export function ProviderLogoStack({
50
50
  </Tooltip>
51
51
  ))}
52
52
  {overflowCount > 0 && (
53
- <span className="-ml-2 inline-flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md border border-white/10 bg-black/40 px-1 font-mono text-[10px] text-muted-foreground">
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
54
  +{overflowCount}
55
55
  </span>
56
56
  )}