@tonyclaw/agent-inspector 3.0.7 → 3.0.9

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 (73) hide show
  1. package/.output/cli.js +245 -171
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-C0Kbsm-Q.js → CompareDrawer-CJKL4LMm.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DBUy29O-.js +106 -0
  5. package/.output/public/assets/ReplayDialog-BYanl-a_.js +1 -0
  6. package/.output/public/assets/{RequestAnatomy-fa0JXy5p.js → RequestAnatomy-DvPz1jqE.js} +1 -1
  7. package/.output/public/assets/ResponseView-sZnxp9GN.js +3 -0
  8. package/.output/public/assets/StreamingChunkSequence-G-tiYiBd.js +1 -0
  9. package/.output/public/assets/{_sessionId-D5fYH2C0.js → _sessionId-_hDcsmXq.js} +1 -1
  10. package/.output/public/assets/agent-inspector-58K1_MsC.ico +0 -0
  11. package/.output/public/assets/index-DAPAWsAd.js +1 -0
  12. package/.output/public/assets/index-DspnLqUW.css +1 -0
  13. package/.output/public/assets/{index-BEsqvywM.js → index-SuOSQb2_.js} +1 -1
  14. package/.output/public/assets/{json-viewer-B0QRbMSf.js → json-viewer-yQdhgjaw.js} +1 -1
  15. package/.output/public/assets/{main-CuKZD7n0.js → main-CgjcDxjR.js} +2 -2
  16. package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -2
  17. package/.output/server/{_sessionId-DdBTK3qm.mjs → _sessionId-BGISf2En.mjs} +3 -3
  18. package/.output/server/_ssr/{CompareDrawer-m82cVZ73.mjs → CompareDrawer-s-UF9gW3.mjs} +2 -2
  19. package/.output/server/_ssr/{ProxyViewerContainer-7geuf78f.mjs → ProxyViewerContainer-C0Sbz9ED.mjs} +1033 -530
  20. package/.output/server/_ssr/{ReplayDialog-B8RlWLLH.mjs → ReplayDialog-DvS8gvJr.mjs} +58 -23
  21. package/.output/server/_ssr/{RequestAnatomy-BE7pmwpv.mjs → RequestAnatomy-BwhWglJL.mjs} +2 -2
  22. package/.output/server/_ssr/{ResponseView-D-sDsdQr.mjs → ResponseView-wBVreqQf.mjs} +2 -2
  23. package/.output/server/_ssr/{StreamingChunkSequence-DU_hThJr.mjs → StreamingChunkSequence-Dr6eg-MJ.mjs} +30 -20
  24. package/.output/server/_ssr/{index-D0sV8UbE.mjs → index-B98Wia1X.mjs} +2 -2
  25. package/.output/server/_ssr/index.mjs +2 -2
  26. package/.output/server/_ssr/{json-viewer-CaDh1zrw.mjs → json-viewer-D7L8y0FH.mjs} +2 -2
  27. package/.output/server/_ssr/{router-Zh2iZ_u0.mjs → router-g9mo0nWT.mjs} +459 -196
  28. package/.output/server/_tanstack-start-manifest_v-CbGc-o50.mjs +4 -0
  29. package/.output/server/index.mjs +76 -69
  30. package/package.json +1 -1
  31. package/src/assets/IDE/opencode.svg +1 -1
  32. package/src/assets/IDE/xiaomimimo.svg +1 -1
  33. package/src/cli/detect-tools.ts +2 -27
  34. package/src/cli/templates/codex-skill-onboard.ts +1 -1
  35. package/src/cli/templates/skill-onboard.ts +2 -3
  36. package/src/cli.ts +9 -86
  37. package/src/components/ProxyViewer.tsx +249 -37
  38. package/src/components/ProxyViewerContainer.tsx +167 -22
  39. package/src/components/clients/ClientLogo.tsx +38 -3
  40. package/src/components/ecosystem/AgentLabDialog.tsx +204 -3
  41. package/src/components/groups/GroupsDialog.tsx +28 -40
  42. package/src/components/providers/ImportWizardDialog.tsx +51 -101
  43. package/src/components/providers/ProvidersPanel.tsx +82 -68
  44. package/src/components/providers/SettingsDialog.tsx +11 -12
  45. package/src/components/proxy-viewer/AgentTraceSummary.tsx +15 -23
  46. package/src/components/proxy-viewer/ReplayDialog.tsx +65 -22
  47. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +33 -20
  48. package/src/components/proxy-viewer/TurnGroup.tsx +0 -16
  49. package/src/components/proxy-viewer/log-formats/anthropic.ts +5 -5
  50. package/src/components/proxy-viewer/log-formats/openai.ts +7 -10
  51. package/src/components/proxy-viewer/requestTools.ts +5 -2
  52. package/src/lib/apiClient.ts +43 -10
  53. package/src/lib/ecosystemContract.ts +32 -0
  54. package/src/lib/export-logs.ts +37 -3
  55. package/src/proxy/ecosystemRegistry.ts +244 -0
  56. package/src/proxy/ecosystemTasks.ts +153 -0
  57. package/src/proxy/platformCommands.ts +212 -0
  58. package/src/proxy/socketTracker.ts +84 -143
  59. package/src/routes/api/ecosystem.packages.$packageId.help.ts +16 -0
  60. package/src/routes/api/ecosystem.packages.$packageId.install.ts +16 -0
  61. package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +19 -0
  62. package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +16 -0
  63. package/src/routes/api/ecosystem.packages.ts +1 -248
  64. package/src/routes/api/ecosystem.tasks.$taskId.ts +16 -0
  65. package/src/routes/api/ecosystem.tasks.ts +12 -0
  66. package/src/routes/api/providers.$providerId.test.log.ts +28 -24
  67. package/.output/public/assets/ProxyViewerContainer-p-rpYyiF.js +0 -106
  68. package/.output/public/assets/ReplayDialog-CM3dhP8v.js +0 -1
  69. package/.output/public/assets/ResponseView-Bq6sC7Ay.js +0 -3
  70. package/.output/public/assets/StreamingChunkSequence-CGOun3Df.js +0 -1
  71. package/.output/public/assets/index-C29Olfzt.js +0 -1
  72. package/.output/public/assets/index-DgANE0r3.css +0 -1
  73. package/.output/server/_tanstack-start-manifest_v-LCE-2AaF.mjs +0 -4
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect, useCallback, useRef, useMemo, useTransition, type JSX } from "react";
2
2
  import { z } from "zod";
3
3
  import { CapturedLogSchema, type CapturedLog } from "../contracts";
4
- import { fetchJson } from "../lib/apiClient";
4
+ import { fetchJson, fetchJsonWithTimeout } from "../lib/apiClient";
5
5
  import { InspectorGroupsListResponseSchema, type InspectorGroup } from "../lib/groupContract";
6
6
  import { ImportLogsResponseSchema, type ImportLogsResponse } from "../lib/logImportContract";
7
7
  import { useStripConfig } from "../lib/useStripConfig";
@@ -9,6 +9,7 @@ import { OnboardingBanner } from "./OnboardingBanner";
9
9
  import {
10
10
  ProxyViewer,
11
11
  type LogPaginationControls,
12
+ type SessionLoadProgress,
12
13
  type SessionMembershipEvidence,
13
14
  } from "./ProxyViewer";
14
15
  import { dispatchLogFocusRequest } from "./proxy-viewer/logFocus";
@@ -49,6 +50,14 @@ type LogCursorPage = z.infer<typeof LogCursorPageSchema>;
49
50
 
50
51
  const SessionsResponseSchema = z.array(z.string());
51
52
 
53
+ const DeleteLogsResponseSchema = z.object({
54
+ success: z.boolean(),
55
+ cleared: z.number().int().nonnegative(),
56
+ logFilesRewritten: z.number().int().nonnegative().optional(),
57
+ chunkFilesDeleted: z.number().int().nonnegative(),
58
+ sessionArchiveLogsDeleted: z.number().int().nonnegative().optional(),
59
+ });
60
+
52
61
  type SessionPageRequest =
53
62
  | { kind: "newest" }
54
63
  | { kind: "oldest" }
@@ -118,6 +127,8 @@ const HASH_HIGHLIGHT_MS = 1800;
118
127
  const SESSION_PAGE_LIMIT = 100;
119
128
  const SESSION_RELOAD_PAGE_LIMIT = 500;
120
129
  const MAX_SESSION_RELOAD_PAGES = 200;
130
+ const SESSION_LOGS_FETCH_TIMEOUT_MS = 20_000;
131
+ const SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS = 10_000;
121
132
 
122
133
  function buildLogsStreamUrl(sessionId: string | undefined): string {
123
134
  const params = new URLSearchParams();
@@ -167,6 +178,18 @@ function buildLogIndex(logs: readonly CapturedLog[]): Map<number, number> {
167
178
  return idx;
168
179
  }
169
180
 
181
+ function fetchSessionLogsPage(
182
+ sessionId: string,
183
+ request: SessionPageRequest,
184
+ limit = SESSION_PAGE_LIMIT,
185
+ ): Promise<LogCursorPage> {
186
+ return fetchJsonWithTimeout(
187
+ buildSessionLogsPageUrl(sessionId, request, limit),
188
+ LogCursorPageSchema,
189
+ SESSION_LOGS_FETCH_TIMEOUT_MS,
190
+ );
191
+ }
192
+
170
193
  export function mergeLogsById(
171
194
  previousLogs: readonly CapturedLog[],
172
195
  incomingLogs: readonly CapturedLog[],
@@ -222,12 +245,17 @@ export function ProxyViewerContainer({
222
245
  const [streamInitialized, setStreamInitialized] = useState(initialSessionId === undefined);
223
246
  const [logPage, setLogPage] = useState<LogCursorPage | null>(null);
224
247
  const [sessionPageLoading, setSessionPageLoading] = useState(initialSessionId !== undefined);
248
+ const [sessionLoadProgress, setSessionLoadProgress] = useState<SessionLoadProgress | undefined>(
249
+ undefined,
250
+ );
225
251
  const [sessionMemberships, setSessionMemberships] = useState<SessionMembershipEvidence[]>([]);
226
252
  const [, startLogTransition] = useTransition();
227
253
  const eventSourceRef = useRef<EventSource | null>(null);
228
254
  const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
229
255
  const handledHashRef = useRef<string | null>(null);
230
256
  const sessionPageRequestIdRef = useRef(0);
257
+ const sessionLoadStartedAtRef = useRef<number | null>(null);
258
+ const sessionLoadTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
231
259
 
232
260
  // O(1) log lookup by id
233
261
  const logIndexRef = useRef<Map<number, number>>(new Map());
@@ -242,6 +270,53 @@ export function ProxyViewerContainer({
242
270
  [showToast],
243
271
  );
244
272
 
273
+ const stopSessionLoadProgress = useCallback(() => {
274
+ if (sessionLoadTimerRef.current !== null) {
275
+ clearInterval(sessionLoadTimerRef.current);
276
+ sessionLoadTimerRef.current = null;
277
+ }
278
+ sessionLoadStartedAtRef.current = null;
279
+ setSessionLoadProgress(undefined);
280
+ }, []);
281
+
282
+ const startSessionLoadProgress = useCallback(
283
+ (label: string, requestId: number, loadedLogs = 0, loadedPages = 0) => {
284
+ if (sessionLoadTimerRef.current !== null) {
285
+ clearInterval(sessionLoadTimerRef.current);
286
+ }
287
+ const startedAt = Date.now();
288
+ sessionLoadStartedAtRef.current = startedAt;
289
+ const update = (): void => {
290
+ if (sessionPageRequestIdRef.current !== requestId) return;
291
+ setSessionLoadProgress({
292
+ label,
293
+ elapsedMs: Date.now() - startedAt,
294
+ timeoutMs: SESSION_LOGS_FETCH_TIMEOUT_MS,
295
+ loadedLogs,
296
+ loadedPages,
297
+ });
298
+ };
299
+ update();
300
+ sessionLoadTimerRef.current = setInterval(update, 250);
301
+ },
302
+ [],
303
+ );
304
+
305
+ const updateSessionLoadProgress = useCallback(
306
+ (label: string, requestId: number, loadedLogs: number, loadedPages: number) => {
307
+ const startedAt = sessionLoadStartedAtRef.current;
308
+ if (startedAt === null || sessionPageRequestIdRef.current !== requestId) return;
309
+ setSessionLoadProgress({
310
+ label,
311
+ elapsedMs: Date.now() - startedAt,
312
+ timeoutMs: SESSION_LOGS_FETCH_TIMEOUT_MS,
313
+ loadedLogs,
314
+ loadedPages,
315
+ });
316
+ },
317
+ [],
318
+ );
319
+
245
320
  // Derived view: the logs the user actually sees. Recomputed only when the
246
321
  // underlying set or filter changes — never on every SSE message.
247
322
  const logs = useMemo(
@@ -355,9 +430,10 @@ export function ProxyViewerContainer({
355
430
  const requestId = sessionPageRequestIdRef.current + 1;
356
431
  sessionPageRequestIdRef.current = requestId;
357
432
  setSessionPageLoading(true);
433
+ startSessionLoadProgress("Loading session logs", requestId);
358
434
  dismissToast();
359
435
 
360
- void fetchJson(buildSessionLogsPageUrl(initialSessionId, request), LogCursorPageSchema)
436
+ void fetchSessionLogsPage(initialSessionId, request)
361
437
  .then((page) => {
362
438
  if (sessionPageRequestIdRef.current !== requestId) return;
363
439
  const nextLogs = page.logs;
@@ -366,6 +442,7 @@ export function ProxyViewerContainer({
366
442
  setLogPage(page);
367
443
  setSelectedSession(initialSessionId);
368
444
  setSessionPageLoading(false);
445
+ stopSessionLoadProgress();
369
446
  dismissToast();
370
447
  })
371
448
  .catch((err: unknown) => {
@@ -373,10 +450,17 @@ export function ProxyViewerContainer({
373
450
  setAllLogs([]);
374
451
  setLogPage(null);
375
452
  setSessionPageLoading(false);
453
+ stopSessionLoadProgress();
376
454
  showErrorToast(err instanceof Error ? err.message : "Failed to load session logs");
377
455
  });
378
456
  },
379
- [dismissToast, initialSessionId, showErrorToast],
457
+ [
458
+ dismissToast,
459
+ initialSessionId,
460
+ showErrorToast,
461
+ startSessionLoadProgress,
462
+ stopSessionLoadProgress,
463
+ ],
380
464
  );
381
465
 
382
466
  const reloadSessionLogs = useCallback(
@@ -384,15 +468,18 @@ export function ProxyViewerContainer({
384
468
  const requestId = sessionPageRequestIdRef.current + 1;
385
469
  sessionPageRequestIdRef.current = requestId;
386
470
  setSessionPageLoading(true);
471
+ startSessionLoadProgress("Reloading full session", requestId);
387
472
  dismissToast();
388
473
 
389
474
  void (async () => {
390
475
  const collected: CapturedLog[] = [];
391
- let page = await fetchJson(
392
- buildSessionLogsPageUrl(sessionId, { kind: "oldest" }, SESSION_RELOAD_PAGE_LIMIT),
393
- LogCursorPageSchema,
476
+ let page = await fetchSessionLogsPage(
477
+ sessionId,
478
+ { kind: "oldest" },
479
+ SESSION_RELOAD_PAGE_LIMIT,
394
480
  );
395
481
  collected.push(...page.logs);
482
+ updateSessionLoadProgress("Reloading full session", requestId, collected.length, 1);
396
483
 
397
484
  let loadedPages = 1;
398
485
  while (
@@ -400,16 +487,19 @@ export function ProxyViewerContainer({
400
487
  page.newestLogId !== null &&
401
488
  loadedPages < MAX_SESSION_RELOAD_PAGES
402
489
  ) {
403
- page = await fetchJson(
404
- buildSessionLogsPageUrl(
405
- sessionId,
406
- { kind: "newer", afterLogId: page.newestLogId },
407
- SESSION_RELOAD_PAGE_LIMIT,
408
- ),
409
- LogCursorPageSchema,
490
+ page = await fetchSessionLogsPage(
491
+ sessionId,
492
+ { kind: "newer", afterLogId: page.newestLogId },
493
+ SESSION_RELOAD_PAGE_LIMIT,
410
494
  );
411
495
  collected.push(...page.logs);
412
496
  loadedPages += 1;
497
+ updateSessionLoadProgress(
498
+ "Reloading full session",
499
+ requestId,
500
+ collected.length,
501
+ loadedPages,
502
+ );
413
503
  }
414
504
 
415
505
  if (sessionPageRequestIdRef.current !== requestId) return;
@@ -420,6 +510,7 @@ export function ProxyViewerContainer({
420
510
  });
421
511
  setKnownSessions((prev) => mergeSessionIds(prev, [sessionId]));
422
512
  setSessionPageLoading(false);
513
+ stopSessionLoadProgress();
423
514
  if (page.hasNewer) {
424
515
  showToast({
425
516
  message: `Loaded ${String(collected.length)} logs for this session. More history is available.`,
@@ -431,10 +522,18 @@ export function ProxyViewerContainer({
431
522
  })().catch((err: unknown) => {
432
523
  if (sessionPageRequestIdRef.current !== requestId) return;
433
524
  setSessionPageLoading(false);
525
+ stopSessionLoadProgress();
434
526
  showErrorToast(err instanceof Error ? err.message : "Failed to reload session logs");
435
527
  });
436
528
  },
437
- [dismissToast, showErrorToast, showToast],
529
+ [
530
+ dismissToast,
531
+ showErrorToast,
532
+ showToast,
533
+ startSessionLoadProgress,
534
+ stopSessionLoadProgress,
535
+ updateSessionLoadProgress,
536
+ ],
438
537
  );
439
538
 
440
539
  useEffect(() => {
@@ -444,13 +543,12 @@ export function ProxyViewerContainer({
444
543
  }
445
544
 
446
545
  let cancelled = false;
447
- void fetch("/api/groups")
448
- .then(async (response) => {
449
- if (!response.ok) return null;
450
- const raw: unknown = await response.json();
451
- const parsed = InspectorGroupsListResponseSchema.safeParse(raw);
452
- return parsed.success ? parsed.data.groups : null;
453
- })
546
+ void fetchJsonWithTimeout(
547
+ "/api/groups",
548
+ InspectorGroupsListResponseSchema,
549
+ SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS,
550
+ )
551
+ .then((response) => response.groups)
454
552
  .then((groups) => {
455
553
  if (cancelled || groups === null) return;
456
554
  setSessionMemberships(buildSessionMembershipEvidence(groups, initialSessionId));
@@ -468,6 +566,12 @@ export function ProxyViewerContainer({
468
566
  refreshSessions();
469
567
  }, [refreshSessions]);
470
568
 
569
+ useEffect(() => {
570
+ return () => {
571
+ stopSessionLoadProgress();
572
+ };
573
+ }, [stopSessionLoadProgress]);
574
+
471
575
  useEffect(() => {
472
576
  if (initialSessionId === undefined) return;
473
577
  loadSessionPage({ kind: "newest" });
@@ -553,6 +657,8 @@ export function ProxyViewerContainer({
553
657
 
554
658
  const handleClearAll = useCallback(() => {
555
659
  const clearedSessions = extractSessions(allLogs);
660
+ stopSessionLoadProgress();
661
+ setSessionPageLoading(false);
556
662
  logIndexRef.current.clear();
557
663
  setAllLogs([]);
558
664
  setLogPage(null);
@@ -561,7 +667,7 @@ export function ProxyViewerContainer({
561
667
  message: "Cleared the current view. Stored logs are still reloadable.",
562
668
  tone: "success",
563
669
  });
564
- }, [allLogs, showToast]);
670
+ }, [allLogs, showToast, stopSessionLoadProgress]);
565
671
 
566
672
  const handleClearGroup = useCallback(
567
673
  (ids: number[]) => {
@@ -579,6 +685,41 @@ export function ProxyViewerContainer({
579
685
  [showToast],
580
686
  );
581
687
 
688
+ const handleDeleteCurrentView = useCallback(
689
+ async (ids: number[]): Promise<void> => {
690
+ if (ids.length === 0) return;
691
+ const idSet = new Set(ids);
692
+ const removed = allLogs.filter((log) => idSet.has(log.id));
693
+ const result = await fetchJson("/api/logs", DeleteLogsResponseSchema, {
694
+ method: "DELETE",
695
+ headers: { "content-type": "application/json" },
696
+ body: JSON.stringify({ ids }),
697
+ });
698
+
699
+ setAllLogs((prev) => {
700
+ const remaining = prev.filter((log) => !idSet.has(log.id));
701
+ logIndexRef.current = buildLogIndex(remaining);
702
+ return remaining;
703
+ });
704
+ setLogPage((prev) =>
705
+ prev === null
706
+ ? null
707
+ : {
708
+ ...prev,
709
+ logs: prev.logs.filter((log) => !idSet.has(log.id)),
710
+ },
711
+ );
712
+ setKnownSessions((prev) => mergeSessionIds(prev, extractSessions(removed)));
713
+ showToast({
714
+ message: `Deleted ${result.cleared.toString()} stored log${
715
+ result.cleared === 1 ? "" : "s"
716
+ } from the current view.`,
717
+ tone: "success",
718
+ });
719
+ },
720
+ [allLogs, showToast],
721
+ );
722
+
582
723
  const handleReloadSession = useCallback(() => {
583
724
  if (initialSessionId !== undefined) {
584
725
  loadSessionPage({ kind: "newest" });
@@ -644,6 +785,7 @@ export function ProxyViewerContainer({
644
785
  if (initialSessionId === undefined) return undefined;
645
786
  return {
646
787
  isLoading: sessionPageLoading,
788
+ loadProgress: sessionLoadProgress,
647
789
  total: logPage?.total ?? null,
648
790
  pageSize: logPage?.limit ?? SESSION_PAGE_LIMIT,
649
791
  hasOlder: logPage?.hasOlder ?? false,
@@ -662,6 +804,7 @@ export function ProxyViewerContainer({
662
804
  loadOlderPage,
663
805
  loadOldestPage,
664
806
  logPage,
807
+ sessionLoadProgress,
665
808
  sessionPageLoading,
666
809
  ]);
667
810
 
@@ -678,6 +821,7 @@ export function ProxyViewerContainer({
678
821
  onSessionChange={setSelectedSession}
679
822
  onModelChange={setSelectedModel}
680
823
  onClearAll={handleClearAll}
824
+ onDeleteCurrentView={handleDeleteCurrentView}
681
825
  onReloadSession={
682
826
  initialSessionId !== undefined || selectedSession !== "__all__"
683
827
  ? handleReloadSession
@@ -692,6 +836,7 @@ export function ProxyViewerContainer({
692
836
  : sessionPageLoading
693
837
  }
694
838
  pagination={pagination}
839
+ sessionLoadProgress={sessionLoadProgress}
695
840
  sessionMemberships={sessionMemberships}
696
841
  viewMode={viewMode}
697
842
  captureMode={captureMode}
@@ -4,8 +4,15 @@ import ClaudeCodeLogoSvg from "../../assets/IDE/claudecode-color.svg";
4
4
  import CodexLogoSvg from "../../assets/IDE/codex-color.svg";
5
5
  import OpenCodeLogoSvg from "../../assets/IDE/opencode.svg";
6
6
  import MiMoCodeLogoSvg from "../../assets/IDE/xiaomimimo.svg";
7
+ import AgentInspectorLogo from "../../assets/agent-inspector.ico";
7
8
 
8
- export type ClientApp = "codex" | "opencode" | "mimo-code" | "claude-code" | "unknown";
9
+ export type ClientApp =
10
+ | "agent-inspector"
11
+ | "codex"
12
+ | "opencode"
13
+ | "mimo-code"
14
+ | "claude-code"
15
+ | "unknown";
9
16
 
10
17
  export type ClientAppDetectionInput = {
11
18
  userAgent?: string | null;
@@ -14,6 +21,7 @@ export type ClientAppDetectionInput = {
14
21
  };
15
22
 
16
23
  const CLIENT_LABELS: Record<Exclude<ClientApp, "unknown">, string> = {
24
+ "agent-inspector": "Inspector",
17
25
  codex: "Codex",
18
26
  opencode: "OpenCode",
19
27
  "mimo-code": "MiMo Code",
@@ -38,6 +46,9 @@ export function detectClientApp(input: ClientAppDetectionInput): ClientApp {
38
46
  normalize(input.clientProjectFolder),
39
47
  ].join(" ");
40
48
 
49
+ if (matchesAny(haystack, ["agent-inspector", "agent inspector", "provider-test"])) {
50
+ return "agent-inspector";
51
+ }
41
52
  if (matchesAny(haystack, ["mimocode", "mimo-code", "mimo code"])) return "mimo-code";
42
53
  if (matchesAny(haystack, ["opencode", "open-code", "open code"])) return "opencode";
43
54
  if (matchesAny(haystack, ["claude-code", "claude code", "claudecode"])) return "claude-code";
@@ -50,6 +61,7 @@ export function detectClientApp(input: ClientAppDetectionInput): ClientApp {
50
61
 
51
62
  export function clientAppLabel(client: ClientApp): string | null {
52
63
  switch (client) {
64
+ case "agent-inspector":
53
65
  case "codex":
54
66
  case "opencode":
55
67
  case "mimo-code":
@@ -61,12 +73,29 @@ export function clientAppLabel(client: ClientApp): string | null {
61
73
  }
62
74
 
63
75
  const CLIENT_LOGOS: Record<Exclude<ClientApp, "unknown">, string> = {
76
+ "agent-inspector": AgentInspectorLogo,
64
77
  codex: CodexLogoSvg,
65
78
  opencode: OpenCodeLogoSvg,
66
79
  "mimo-code": MiMoCodeLogoSvg,
67
80
  "claude-code": ClaudeCodeLogoSvg,
68
81
  };
69
82
 
83
+ const CLIENT_LOGO_FRAMES: Record<Exclude<ClientApp, "unknown">, string> = {
84
+ "agent-inspector": "border-orange-200/25 bg-orange-300/[0.12]",
85
+ codex: "border-cyan-200/20 bg-cyan-300/[0.08]",
86
+ opencode: "border-sky-200/25 bg-sky-300/[0.12]",
87
+ "mimo-code": "border-orange-200/25 bg-orange-400/[0.12]",
88
+ "claude-code": "border-amber-100/20 bg-amber-300/[0.09]",
89
+ };
90
+
91
+ const CLIENT_LOGO_IMAGES: Record<Exclude<ClientApp, "unknown">, string> = {
92
+ "agent-inspector": "p-0.5 drop-shadow-[0_0_8px_rgba(251,146,60,0.35)]",
93
+ codex: "p-0.5",
94
+ opencode: "p-1 drop-shadow-[0_0_8px_rgba(125,211,252,0.35)]",
95
+ "mimo-code": "p-0.5 drop-shadow-[0_0_8px_rgba(255,105,0,0.35)]",
96
+ "claude-code": "p-0.5",
97
+ };
98
+
70
99
  export function ClientLogo({
71
100
  client,
72
101
  className,
@@ -82,13 +111,19 @@ export function ClientLogo({
82
111
  return (
83
112
  <span
84
113
  className={cn(
85
- "border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md p-0.5",
114
+ "inline-flex size-6 shrink-0 items-center justify-center rounded-md border p-0 shadow-sm",
115
+ CLIENT_LOGO_FRAMES[client],
86
116
  className,
87
117
  )}
88
118
  aria-label={label}
89
119
  title={label}
90
120
  >
91
- <img src={logoSrc} alt="" className="size-full object-contain" aria-hidden="true" />
121
+ <img
122
+ src={logoSrc}
123
+ alt=""
124
+ className={cn("size-full object-contain", CLIENT_LOGO_IMAGES[client])}
125
+ aria-hidden="true"
126
+ />
92
127
  </span>
93
128
  );
94
129
  }