@tonyclaw/agent-inspector 3.0.27 → 3.0.29

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 (43) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BVCAAbbu.js → CompareDrawer-Cb5ccPeX.js} +1 -1
  3. package/.output/public/assets/{ProxyViewerContainer-CYOXB81n.js → ProxyViewerContainer-COBe5GTT.js} +198 -198
  4. package/.output/public/assets/{ReplayDialog-DMX23O5G.js → ReplayDialog-CctnITbt.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-S17pYDRc.js → RequestAnatomy-BrN0RBbL.js} +1 -1
  6. package/.output/public/assets/{ResponseView-Cf1XrawD.js → ResponseView-Cqq87lX_.js} +2 -2
  7. package/.output/public/assets/{StreamingChunkSequence-BdOrVlCi.js → StreamingChunkSequence-CnTa9mCr.js} +1 -1
  8. package/.output/public/assets/{_sessionId-BnKGmJrR.js → _sessionId-DKM91udC.js} +1 -1
  9. package/.output/public/assets/{index-gY3I-7-D.js → index-Bq-nqolG.js} +1 -1
  10. package/.output/public/assets/{index-9fMLCU4d.js → index-DPiESBo2.js} +1 -1
  11. package/.output/public/assets/{index-B1atk19i.js → index-DTLNVcgc.js} +7 -7
  12. package/.output/public/assets/index-DliqmwUw.css +1 -0
  13. package/.output/public/assets/{json-viewer-CpyYpPjx.js → json-viewer-CfaDJwx8.js} +1 -1
  14. package/.output/server/_libs/lucide-react.mjs +39 -39
  15. package/.output/server/{_sessionId-eA_h2_s6.mjs → _sessionId-CwGXPD4C.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-K2k9G7r1.mjs → CompareDrawer-R_P1-VNr.mjs} +3 -3
  17. package/.output/server/_ssr/{ProxyViewerContainer-CbyBpCAj.mjs → ProxyViewerContainer-DUgxRNbj.mjs} +344 -191
  18. package/.output/server/_ssr/{ReplayDialog-DacI2Iv0.mjs → ReplayDialog-De9wKKzS.mjs} +4 -4
  19. package/.output/server/_ssr/{RequestAnatomy-gh-pAdkO.mjs → RequestAnatomy-Db4MxUYo.mjs} +2 -2
  20. package/.output/server/_ssr/{ResponseView-BRJKN50-.mjs → ResponseView-8rDXOiNB.mjs} +3 -3
  21. package/.output/server/_ssr/{StreamingChunkSequence-BQLVEuWY.mjs → StreamingChunkSequence-N9teZHkA.mjs} +2 -2
  22. package/.output/server/_ssr/{index-DuSWrjbH.mjs → index-D4DvByuW.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-DNs1ZjKI.mjs → json-viewer-BjoHRbjS.mjs} +3 -3
  25. package/.output/server/_ssr/{router-Dgx6-ot3.mjs → router-BYlePDj8.mjs} +138 -162
  26. package/.output/server/{_tanstack-start-manifest_v-CoGjnkQD.mjs → _tanstack-start-manifest_v-DMCA8KK3.mjs} +1 -1
  27. package/.output/server/index.mjs +68 -68
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +57 -1
  30. package/src/components/clients/ClientLogo.tsx +51 -9
  31. package/src/components/inspector-pet/InspectorPet.tsx +0 -7
  32. package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
  33. package/src/components/proxy-viewer/ConversationGroupList.tsx +251 -155
  34. package/src/components/proxy-viewer/ConversationHeader.tsx +43 -23
  35. package/src/lib/jsonFile.ts +46 -0
  36. package/src/lib/utils.ts +5 -1
  37. package/src/proxy/evidenceAnalysis.ts +116 -117
  38. package/src/proxy/groupEvidenceExporter.ts +4 -8
  39. package/src/proxy/groupStore.ts +3 -10
  40. package/src/proxy/providerSecretStore.ts +4 -8
  41. package/src/proxy/runFailures.ts +4 -10
  42. package/src/proxy/runStore.ts +4 -11
  43. package/.output/public/assets/index-CmeF5XXS.css +0 -1
@@ -1,12 +1,20 @@
1
- import { type JSX, useCallback, useMemo, useState } from "react";
1
+ import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { ChevronDown, ChevronRight, Cpu, FolderOpen, Loader2, Monitor } from "lucide-react";
3
3
  import type { CapturedLog } from "../../contracts";
4
4
  import { cn, formatTokens } from "../../lib/utils";
5
- import { ClientLogo, clientAppLabel, detectClientApp, type ClientApp } from "../clients/ClientLogo";
5
+ import {
6
+ ClientLogo,
7
+ clientAppLabel,
8
+ clientAppTypeLabel,
9
+ detectClientApp,
10
+ type ClientApp,
11
+ } from "../clients/ClientLogo";
6
12
  import { ConversationGroup, type ConversationGroupProps } from "./ConversationGroup";
7
13
  import type { ConversationGroupData } from "./ConversationHeader";
8
14
 
9
15
  const VIRTUALIZE_GROUP_THRESHOLD = 30;
16
+ const COLLAPSED_CLIENTS_STORAGE_KEY = "agent-inspector.hierarchy.collapsedClients";
17
+ const COLLAPSED_PROCESSES_STORAGE_KEY = "agent-inspector.hierarchy.collapsedProcesses";
10
18
 
11
19
  type ConversationGroupListProps = Omit<
12
20
  ConversationGroupProps,
@@ -23,7 +31,7 @@ export type ProcessGroupData = {
23
31
  logs: CapturedLog[];
24
32
  };
25
33
 
26
- export type IdeGroupData = {
34
+ export type ClientGroupData = {
27
35
  id: string;
28
36
  client: ClientApp;
29
37
  label: string;
@@ -40,7 +48,7 @@ type MutableProcessGroup = {
40
48
  logs: CapturedLog[];
41
49
  };
42
50
 
43
- type MutableIdeGroup = {
51
+ type MutableClientGroup = {
44
52
  id: string;
45
53
  client: ClientApp;
46
54
  label: string;
@@ -109,9 +117,9 @@ function groupClient(group: ConversationGroupData): ClientApp {
109
117
  }
110
118
 
111
119
  function fallbackClientLabel(userAgent: string | null): string {
112
- if (userAgent === null || userAgent === "") return "Unknown IDE";
120
+ if (userAgent === null || userAgent === "") return "Unknown client";
113
121
  const firstToken = userAgent.split(/[ /;]/)[0] ?? "";
114
- if (firstToken === "") return "Unknown IDE";
122
+ if (firstToken === "") return "Unknown client";
115
123
  return firstToken;
116
124
  }
117
125
 
@@ -139,28 +147,56 @@ function sortLogsByTimestamp(logs: readonly CapturedLog[]): CapturedLog[] {
139
147
  return [...logs].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
140
148
  }
141
149
 
142
- function firstTimestamp(logs: readonly CapturedLog[]): string {
143
- return sortLogsByTimestamp(logs)[0]?.timestamp ?? "";
150
+ function latestTimestamp(logs: readonly CapturedLog[]): string {
151
+ let latest = "";
152
+ for (const log of logs) {
153
+ if (log.timestamp > latest) latest = log.timestamp;
154
+ }
155
+ return latest;
144
156
  }
145
157
 
146
- function sortByFirstLogTime<T extends { logs: readonly CapturedLog[] }>(items: readonly T[]): T[] {
147
- return [...items].sort((a, b) => firstTimestamp(a.logs).localeCompare(firstTimestamp(b.logs)));
158
+ export function operationalPriority(logs: readonly CapturedLog[]): number {
159
+ if (hasPendingLogs(logs)) return 0;
160
+ if (
161
+ logs.some(
162
+ (log) =>
163
+ (log.error !== null && log.error !== undefined) ||
164
+ (log.responseStatus !== null &&
165
+ log.responseStatus !== undefined &&
166
+ log.responseStatus >= 400),
167
+ )
168
+ ) {
169
+ return 1;
170
+ }
171
+ return 2;
172
+ }
173
+
174
+ function sortByOperationalPriority<T extends { id: string; logs: readonly CapturedLog[] }>(
175
+ items: readonly T[],
176
+ ): T[] {
177
+ return [...items].sort((left, right) => {
178
+ const priorityDifference = operationalPriority(left.logs) - operationalPriority(right.logs);
179
+ if (priorityDifference !== 0) return priorityDifference;
180
+ const recencyDifference = latestTimestamp(right.logs).localeCompare(latestTimestamp(left.logs));
181
+ if (recencyDifference !== 0) return recencyDifference;
182
+ return left.id.localeCompare(right.id);
183
+ });
148
184
  }
149
185
 
150
186
  export function buildConversationHierarchy(
151
187
  groups: readonly ConversationGroupData[],
152
- ): IdeGroupData[] {
153
- const ideGroups = new Map<string, MutableIdeGroup>();
188
+ ): ClientGroupData[] {
189
+ const clientGroups = new Map<string, MutableClientGroup>();
154
190
 
155
191
  for (const group of groups) {
156
192
  const userAgent = firstUserAgent(group.logs);
157
193
  const projectFolder = firstClientProjectFolder(group.logs);
158
194
  const client = groupClient(group);
159
195
  const currentIdeKey = ideKey(client, userAgent, projectFolder);
160
- let ideGroup = ideGroups.get(currentIdeKey);
196
+ let clientGroup = clientGroups.get(currentIdeKey);
161
197
 
162
- if (ideGroup === undefined) {
163
- ideGroup = {
198
+ if (clientGroup === undefined) {
199
+ clientGroup = {
164
200
  id: currentIdeKey,
165
201
  client,
166
202
  label: clientLabel(client, userAgent),
@@ -168,14 +204,14 @@ export function buildConversationHierarchy(
168
204
  processes: new Map(),
169
205
  logs: [],
170
206
  };
171
- ideGroups.set(currentIdeKey, ideGroup);
207
+ clientGroups.set(currentIdeKey, clientGroup);
172
208
  }
173
209
 
174
- ideGroup.logs.push(...group.logs);
210
+ clientGroup.logs.push(...group.logs);
175
211
 
176
212
  const clientPid = firstClientPid(group.logs);
177
213
  const currentProcessKey = `${currentIdeKey}:${processKey(clientPid, projectFolder)}`;
178
- let processGroup = ideGroup.processes.get(currentProcessKey);
214
+ let processGroup = clientGroup.processes.get(currentProcessKey);
179
215
 
180
216
  if (processGroup === undefined) {
181
217
  processGroup = {
@@ -185,37 +221,37 @@ export function buildConversationHierarchy(
185
221
  sessions: [],
186
222
  logs: [],
187
223
  };
188
- ideGroup.processes.set(currentProcessKey, processGroup);
224
+ clientGroup.processes.set(currentProcessKey, processGroup);
189
225
  }
190
226
 
191
227
  processGroup.sessions.push(group);
192
228
  processGroup.logs.push(...group.logs);
193
229
  }
194
230
 
195
- const hierarchy: IdeGroupData[] = [];
231
+ const hierarchy: ClientGroupData[] = [];
196
232
 
197
- for (const ideGroup of ideGroups.values()) {
198
- const processes: ProcessGroupData[] = sortByFirstLogTime(
199
- [...ideGroup.processes.values()].map((processGroup) => ({
233
+ for (const clientGroup of clientGroups.values()) {
234
+ const processes: ProcessGroupData[] = sortByOperationalPriority(
235
+ [...clientGroup.processes.values()].map((processGroup) => ({
200
236
  id: processGroup.id,
201
237
  clientPid: processGroup.clientPid,
202
238
  clientProjectFolder: processGroup.clientProjectFolder,
203
- sessions: sortByFirstLogTime(processGroup.sessions),
239
+ sessions: sortByOperationalPriority(processGroup.sessions),
204
240
  logs: sortLogsByTimestamp(processGroup.logs),
205
241
  })),
206
242
  );
207
243
 
208
244
  hierarchy.push({
209
- id: ideGroup.id,
210
- client: ideGroup.client,
211
- label: ideGroup.label,
212
- userAgent: ideGroup.userAgent,
245
+ id: clientGroup.id,
246
+ client: clientGroup.client,
247
+ label: clientGroup.label,
248
+ userAgent: clientGroup.userAgent,
213
249
  processes,
214
- logs: sortLogsByTimestamp(ideGroup.logs),
250
+ logs: sortLogsByTimestamp(clientGroup.logs),
215
251
  });
216
252
  }
217
253
 
218
- return sortByFirstLogTime(hierarchy);
254
+ return sortByOperationalPriority(hierarchy);
219
255
  }
220
256
 
221
257
  function formatTokenCount(count: number): string {
@@ -248,6 +284,51 @@ function countSessions(processes: readonly ProcessGroupData[]): number {
248
284
  return sessions;
249
285
  }
250
286
 
287
+ function readStoredIdSet(storageKey: string): ReadonlySet<string> | null {
288
+ if (typeof window === "undefined") return null;
289
+ try {
290
+ const storedValue = window.localStorage.getItem(storageKey);
291
+ if (storedValue === null) return null;
292
+ const parsed: unknown = JSON.parse(storedValue);
293
+ if (!Array.isArray(parsed)) return null;
294
+ return new Set(parsed.filter((value): value is string => typeof value === "string"));
295
+ } catch {
296
+ return null;
297
+ }
298
+ }
299
+
300
+ function persistIdSet(storageKey: string, values: ReadonlySet<string>): void {
301
+ try {
302
+ window.localStorage.setItem(storageKey, JSON.stringify([...values]));
303
+ } catch {
304
+ // Local storage can be unavailable in hardened browser profiles.
305
+ }
306
+ }
307
+
308
+ function removeIds(values: ReadonlySet<string>, ids: ReadonlySet<string>): ReadonlySet<string> {
309
+ let changed = false;
310
+ const next = new Set(values);
311
+ for (const id of ids) {
312
+ if (next.delete(id)) changed = true;
313
+ }
314
+ return changed ? next : values;
315
+ }
316
+
317
+ export function buildDefaultCollapsedProcessIds(
318
+ hierarchy: readonly ClientGroupData[],
319
+ ): ReadonlySet<string> {
320
+ const collapsedIds = new Set<string>();
321
+ for (const clientGroup of hierarchy) {
322
+ for (let index = 1; index < clientGroup.processes.length; index++) {
323
+ const processGroup = clientGroup.processes[index];
324
+ if (processGroup !== undefined && !hasPendingLogs(processGroup.logs)) {
325
+ collapsedIds.add(processGroup.id);
326
+ }
327
+ }
328
+ }
329
+ return collapsedIds;
330
+ }
331
+
251
332
  function shortProjectPath(projectFolder: string | null): string | null {
252
333
  if (projectFolder === null || projectFolder === "") return null;
253
334
  const parts = projectFolder
@@ -273,44 +354,30 @@ function SummaryChip({
273
354
  return (
274
355
  <span
275
356
  className={cn(
276
- "bg-white/[0.045] inline-flex h-7 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground",
357
+ "inline-flex h-7 items-center gap-1 rounded-md bg-[var(--inspector-surface-low)] px-2 text-xs text-muted-foreground",
277
358
  className,
278
359
  )}
279
360
  >
280
- <span className="font-mono text-[10px] uppercase tracking-[0.12em] text-white/35">
281
- {label}
282
- </span>
283
- <span className="font-mono tabular-nums text-slate-100">{value}</span>
361
+ <span className="font-mono text-[10px] uppercase text-muted-foreground/70">{label}</span>
362
+ <span className="font-mono tabular-nums text-foreground">{value}</span>
284
363
  </span>
285
364
  );
286
365
  }
287
366
 
288
- function PendingTurnBadge({
289
- count,
290
- label = "Turn open",
291
- }: {
292
- count: number;
293
- label?: string;
294
- }): JSX.Element {
367
+ function AncestorActivityIndicator({ count }: { count: number }): JSX.Element {
368
+ const label = `${count} open turn${count === 1 ? "" : "s"}`;
295
369
  return (
296
370
  <span
297
- className="inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md bg-amber-300/[0.10] px-1.5 font-mono text-[10px] uppercase tracking-[0.14em] text-amber-100/90 ring-1 ring-amber-200/10"
298
- title={`${count} turn${count !== 1 ? "s" : ""} not finished yet`}
371
+ className="relative inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-[var(--inspector-surface-medium)] text-[var(--inspector-status-watch)] ring-1 ring-border/50"
372
+ aria-label={label}
373
+ title={label}
299
374
  >
300
375
  <Loader2 className="size-3 animate-spin" />
301
- {label}
302
- {count > 1 && <span className="tabular-nums">x{count}</span>}
303
- </span>
304
- );
305
- }
306
-
307
- function LoadingSweep(): JSX.Element {
308
- return (
309
- <span
310
- className="pointer-events-none absolute inset-x-3 bottom-0 h-px overflow-hidden rounded-full bg-white/[0.06]"
311
- aria-hidden="true"
312
- >
313
- <span className="absolute inset-y-0 left-0 w-1/3 animate-pulse rounded-full bg-amber-200/65 shadow-[0_0_14px_rgba(252,211,77,0.32)]" />
376
+ {count > 1 && (
377
+ <span className="absolute translate-x-2 -translate-y-2 rounded bg-card px-0.5 font-mono text-[8px] tabular-nums text-[var(--inspector-status-watch)]">
378
+ {count}
379
+ </span>
380
+ )}
314
381
  </span>
315
382
  );
316
383
  }
@@ -320,13 +387,45 @@ export function ConversationGroupList({
320
387
  ...groupProps
321
388
  }: ConversationGroupListProps): JSX.Element {
322
389
  const hierarchy = useMemo(() => buildConversationHierarchy(groups), [groups]);
323
- const [collapsedIdeIds, setCollapsedIdeIds] = useState<ReadonlySet<string>>(() => new Set());
324
- const [collapsedProcessIds, setCollapsedProcessIds] = useState<ReadonlySet<string>>(
390
+ const [collapsedClientIds, setCollapsedClientIds] = useState<ReadonlySet<string>>(
325
391
  () => new Set(),
326
392
  );
393
+ const [collapsedProcessIds, setCollapsedProcessIds] = useState<ReadonlySet<string>>(() =>
394
+ buildDefaultCollapsedProcessIds(hierarchy),
395
+ );
396
+ const [storedCollapseStateLoaded, setStoredCollapseStateLoaded] = useState(false);
397
+
398
+ useEffect(() => {
399
+ const storedClientIds = readStoredIdSet(COLLAPSED_CLIENTS_STORAGE_KEY);
400
+ const storedProcessIds = readStoredIdSet(COLLAPSED_PROCESSES_STORAGE_KEY);
401
+ if (storedClientIds !== null) setCollapsedClientIds(storedClientIds);
402
+ if (storedProcessIds !== null) setCollapsedProcessIds(storedProcessIds);
403
+ setStoredCollapseStateLoaded(true);
404
+ }, []);
405
+
406
+ useEffect(() => {
407
+ if (!storedCollapseStateLoaded) return;
408
+ persistIdSet(COLLAPSED_CLIENTS_STORAGE_KEY, collapsedClientIds);
409
+ persistIdSet(COLLAPSED_PROCESSES_STORAGE_KEY, collapsedProcessIds);
410
+ }, [collapsedClientIds, collapsedProcessIds, storedCollapseStateLoaded]);
327
411
 
328
- const toggleIde = useCallback((id: string) => {
329
- setCollapsedIdeIds((previous) => {
412
+ useEffect(() => {
413
+ const activeClientIds = new Set(
414
+ hierarchy.filter((group) => hasPendingLogs(group.logs)).map((group) => group.id),
415
+ );
416
+ const activeProcessIds = new Set(
417
+ hierarchy.flatMap((group) =>
418
+ group.processes
419
+ .filter((process) => hasPendingLogs(process.logs))
420
+ .map((process) => process.id),
421
+ ),
422
+ );
423
+ setCollapsedClientIds((previous) => removeIds(previous, activeClientIds));
424
+ setCollapsedProcessIds((previous) => removeIds(previous, activeProcessIds));
425
+ }, [hierarchy]);
426
+
427
+ const toggleClient = useCallback((id: string) => {
428
+ setCollapsedClientIds((previous) => {
330
429
  const next = new Set(previous);
331
430
  if (next.has(id)) {
332
431
  next.delete(id);
@@ -350,14 +449,14 @@ export function ConversationGroupList({
350
449
  }, []);
351
450
 
352
451
  return (
353
- <div className="space-y-4">
354
- {hierarchy.map((ideGroup) => (
355
- <IdeGroupSection
356
- key={ideGroup.id}
357
- group={ideGroup}
358
- collapsed={collapsedIdeIds.has(ideGroup.id)}
452
+ <div className="space-y-2">
453
+ {hierarchy.map((clientGroup) => (
454
+ <ClientGroupSection
455
+ key={clientGroup.id}
456
+ group={clientGroup}
457
+ collapsed={collapsedClientIds.has(clientGroup.id)}
359
458
  collapsedProcessIds={collapsedProcessIds}
360
- onToggleIde={() => toggleIde(ideGroup.id)}
459
+ onToggleClient={() => toggleClient(clientGroup.id)}
361
460
  onToggleProcess={toggleProcess}
362
461
  conversationProps={groupProps}
363
462
  />
@@ -366,18 +465,18 @@ export function ConversationGroupList({
366
465
  );
367
466
  }
368
467
 
369
- function IdeGroupSection({
468
+ function ClientGroupSection({
370
469
  group,
371
470
  collapsed,
372
471
  collapsedProcessIds,
373
- onToggleIde,
472
+ onToggleClient,
374
473
  onToggleProcess,
375
474
  conversationProps,
376
475
  }: {
377
- group: IdeGroupData;
476
+ group: ClientGroupData;
378
477
  collapsed: boolean;
379
478
  collapsedProcessIds: ReadonlySet<string>;
380
- onToggleIde: () => void;
479
+ onToggleClient: () => void;
381
480
  onToggleProcess: (id: string) => void;
382
481
  conversationProps: Omit<ConversationGroupProps, "group" | "expanded" | "onExpandedChange">;
383
482
  }): JSX.Element {
@@ -390,64 +489,64 @@ function IdeGroupSection({
390
489
  return (
391
490
  <section
392
491
  className={cn(
393
- "bg-card/75 shadow-sm relative isolate overflow-hidden rounded-[8px]",
394
- isWorking && "bg-card/85",
492
+ "relative isolate overflow-hidden rounded-[8px] bg-card/65",
493
+ isWorking && "bg-card/80",
395
494
  )}
396
495
  data-ide-group={group.id}
397
496
  >
398
497
  <button
399
498
  type="button"
400
- className="flex w-full min-w-0 items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-white/[0.03] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300/45"
401
- onClick={onToggleIde}
499
+ className="grid w-full min-w-0 grid-cols-[2.25rem_minmax(0,1fr)_auto] items-center gap-2.5 px-3 py-2.5 text-left transition-colors hover:bg-[var(--inspector-surface-low)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
500
+ onClick={onToggleClient}
402
501
  aria-expanded={!collapsed}
403
502
  >
404
503
  {group.client === "unknown" ? (
405
- <span className="bg-white/[0.045] inline-flex size-10 shrink-0 items-center justify-center rounded-[8px] text-slate-200">
406
- <Monitor className="size-5" />
504
+ <span className="inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-[var(--inspector-surface-low)] text-muted-foreground">
505
+ <Monitor className="size-4" />
407
506
  </span>
408
507
  ) : (
409
- <ClientLogo client={group.client} className="size-10 rounded-[8px] text-cyan-100" />
508
+ <ClientLogo client={group.client} className="size-9 rounded-md" decorative={true} />
410
509
  )}
411
510
 
412
- <span className="min-w-0 flex-1">
511
+ <span className="min-w-0">
413
512
  <span className="flex min-w-0 items-center gap-2">
414
513
  <span
415
- className="truncate text-sm font-semibold text-slate-50"
514
+ className="truncate text-sm font-semibold text-foreground"
416
515
  title={group.userAgent ?? group.label}
417
516
  >
418
517
  {group.label}
419
518
  </span>
420
- <span className="rounded bg-cyan-300/[0.06] px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-[0.16em] text-cyan-100/75">
421
- IDE
519
+ <span className="rounded bg-[var(--inspector-surface-medium)] px-1.5 py-0.5 font-mono text-[9px] uppercase text-muted-foreground">
520
+ {clientAppTypeLabel(group.client)}
422
521
  </span>
423
- {isWorking && <PendingTurnBadge count={pendingTurnCount} label="Turn open" />}
522
+ {isWorking && <AncestorActivityIndicator count={pendingTurnCount} />}
424
523
  </span>
425
- <span className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
524
+ <span className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground">
426
525
  <span>
427
526
  {group.processes.length} process{group.processes.length !== 1 ? "es" : ""}
428
527
  </span>
429
528
  <span>
430
529
  {sessionCount} session{sessionCount !== 1 ? "s" : ""}
431
530
  </span>
432
- <span>
433
- {logCount} log{logCount !== 1 ? "s" : ""}
434
- </span>
531
+ <span>{logCount} req</span>
435
532
  </span>
436
533
  </span>
437
534
 
438
- <span className="hidden shrink-0 items-center gap-2 md:flex">
439
- <SummaryChip label="IN" value={formatTokenCount(totals.inputTokens)} />
440
- <SummaryChip label="OUT" value={formatTokenCount(totals.outputTokens)} />
441
- </span>
442
-
443
- <span className="bg-white/[0.045] hover:bg-white/[0.075] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-200">
444
- {collapsed ? <ChevronRight className="size-4" /> : <ChevronDown className="size-4" />}
535
+ <span className="flex shrink-0 items-center gap-2">
536
+ {collapsed && (
537
+ <span className="hidden shrink-0 items-center gap-2 md:flex">
538
+ <SummaryChip label="IN" value={formatTokenCount(totals.inputTokens)} />
539
+ <SummaryChip label="OUT" value={formatTokenCount(totals.outputTokens)} />
540
+ </span>
541
+ )}
542
+ <span className="inline-flex size-8 shrink-0 items-center justify-center rounded-md bg-[var(--inspector-surface-low)] text-muted-foreground transition-colors group-hover:bg-[var(--inspector-surface-medium)]">
543
+ {collapsed ? <ChevronRight className="size-4" /> : <ChevronDown className="size-4" />}
544
+ </span>
445
545
  </span>
446
546
  </button>
447
- {isWorking && <LoadingSweep />}
448
547
 
449
548
  {!collapsed && (
450
- <div className="space-y-3 px-3 pb-3 pt-2 sm:px-4">
549
+ <div className="space-y-0.5 px-2 pb-2 pt-0.5">
451
550
  {group.processes.map((processGroup) => (
452
551
  <ProcessGroupSection
453
552
  key={processGroup.id}
@@ -483,74 +582,71 @@ function ProcessGroupSection({
483
582
  const processLabel =
484
583
  group.clientPid !== null && group.clientPid !== undefined
485
584
  ? `PID ${group.clientPid}`
486
- : "process";
585
+ : "Process unavailable";
487
586
 
488
587
  return (
489
- <section className="relative pl-5">
490
- <span className="absolute bottom-1 left-2 top-1 w-px bg-white/[0.055]" aria-hidden="true" />
491
- <div className="bg-black/[0.10] relative overflow-hidden rounded-[8px]">
492
- <button
493
- type="button"
494
- className="flex w-full min-w-0 items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-white/[0.035] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300/40"
495
- onClick={onToggle}
496
- aria-expanded={!collapsed}
497
- >
498
- <span className="bg-white/[0.045] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-amber-100">
499
- <Cpu className="size-4" />
500
- </span>
588
+ <section className="relative pl-3">
589
+ <button
590
+ type="button"
591
+ className="grid w-full min-w-0 grid-cols-[2rem_minmax(0,1fr)_auto] items-center gap-2.5 rounded-md bg-[var(--inspector-surface-low)] px-2.5 py-2 text-left transition-colors hover:bg-[var(--inspector-surface-medium)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
592
+ onClick={onToggle}
593
+ aria-expanded={!collapsed}
594
+ >
595
+ <span className="inline-flex size-8 shrink-0 items-center justify-center text-[var(--inspector-status-watch)]">
596
+ <Cpu className="size-4" />
597
+ </span>
501
598
 
502
- <span className="min-w-0 flex-1">
503
- <span className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
504
- <span className="font-mono text-xs font-semibold tabular-nums text-slate-100">
505
- {processLabel}
506
- </span>
507
- {isWorking && <PendingTurnBadge count={pendingTurnCount} label="Turn open" />}
508
- {projectLabel !== null && (
509
- <span
510
- className="inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground"
511
- title={group.clientProjectFolder ?? projectLabel}
512
- >
513
- <FolderOpen className="size-3 shrink-0" />
514
- <span className="truncate font-mono">{projectLabel}</span>
515
- </span>
516
- )}
599
+ <span className="min-w-0">
600
+ <span className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
601
+ <span className="font-mono text-xs font-semibold tabular-nums text-foreground">
602
+ {processLabel}
517
603
  </span>
518
- <span className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
519
- <span>
520
- {sessionCount} session{sessionCount !== 1 ? "s" : ""}
604
+ {isWorking && <AncestorActivityIndicator count={pendingTurnCount} />}
605
+ {projectLabel !== null && (
606
+ <span
607
+ className="inline-flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground"
608
+ title={group.clientProjectFolder ?? projectLabel}
609
+ >
610
+ <FolderOpen className="size-3 shrink-0" />
611
+ <span className="truncate font-mono">{projectLabel}</span>
521
612
  </span>
522
- <span>
523
- {logCount} log{logCount !== 1 ? "s" : ""}
524
- </span>
525
- </span>
613
+ )}
526
614
  </span>
527
-
528
- <span className="hidden shrink-0 items-center gap-2 md:flex">
529
- <SummaryChip label="IN" value={formatTokenCount(totals.inputTokens)} />
530
- <SummaryChip label="OUT" value={formatTokenCount(totals.outputTokens)} />
615
+ <span className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground">
616
+ <span>
617
+ {sessionCount} session{sessionCount !== 1 ? "s" : ""}
618
+ </span>
619
+ <span>{logCount} req</span>
531
620
  </span>
621
+ </span>
532
622
 
533
- <span className="bg-white/[0.045] hover:bg-white/[0.075] inline-flex size-7 shrink-0 items-center justify-center rounded-md text-slate-200">
623
+ <span className="flex shrink-0 items-center gap-2">
624
+ {collapsed && (
625
+ <span className="hidden shrink-0 items-center gap-2 md:flex">
626
+ <SummaryChip label="IN" value={formatTokenCount(totals.inputTokens)} />
627
+ <SummaryChip label="OUT" value={formatTokenCount(totals.outputTokens)} />
628
+ </span>
629
+ )}
630
+ <span className="inline-flex size-7 shrink-0 items-center justify-center rounded-md bg-[var(--inspector-surface-low)] text-muted-foreground transition-colors hover:bg-[var(--inspector-surface-medium)]">
534
631
  {collapsed ? <ChevronRight className="size-4" /> : <ChevronDown className="size-4" />}
535
632
  </span>
536
- </button>
537
- {isWorking && <LoadingSweep />}
538
-
539
- {!collapsed && (
540
- <div className="space-y-2 p-2 pt-1 sm:p-3 sm:pt-2">
541
- {group.sessions.map((session) => (
542
- <ConversationGroup
543
- key={session.id}
544
- group={session}
545
- {...conversationProps}
546
- showClientIdentity={false}
547
- showLogClientMetadata={false}
548
- showProcessMetadata={false}
549
- />
550
- ))}
551
- </div>
552
- )}
553
- </div>
633
+ </span>
634
+ </button>
635
+
636
+ {!collapsed && (
637
+ <div className="space-y-1 pb-1 pl-3 pr-1 pt-1">
638
+ {group.sessions.map((session) => (
639
+ <ConversationGroup
640
+ key={session.id}
641
+ group={session}
642
+ {...conversationProps}
643
+ showClientIdentity={false}
644
+ showLogClientMetadata={false}
645
+ showProcessMetadata={false}
646
+ />
647
+ ))}
648
+ </div>
649
+ )}
554
650
  </section>
555
651
  );
556
652
  }