@tonyclaw/agent-inspector 2.1.15 → 2.1.17

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 +57 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-B9sLBHw5.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
  5. package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-D9I9W9n3.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-DGXK_Rii.js} +1 -1
  7. package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-df_JzwsS.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
  9. package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
  10. package/.output/public/assets/index-Blqvndcn.js +1 -0
  11. package/.output/public/assets/index-Cs79WRQj.css +1 -0
  12. package/.output/public/assets/{index-DjKt8XKe.js → index-Dqc2r1ea.js} +1 -1
  13. package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-D3QWQlSB.js} +1 -1
  14. package/.output/public/assets/{main-DpD1N0S8.js → main-ZhxhOCmF.js} +2 -2
  15. package/.output/server/_libs/lucide-react.mjs +255 -201
  16. package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
  17. package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +2732 -1849
  19. package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
  20. package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-HQfrxjO1.mjs} +3 -3
  21. package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-QSlSD11e.mjs} +3 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +3 -3
  23. package/.output/server/_ssr/{index-47XVPghS.mjs → index-D_EzfXaN.mjs} +2 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
  26. package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-__o2wrfO.mjs} +505 -136
  27. package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
  28. package/.output/server/index.mjs +63 -63
  29. package/README.md +57 -2
  30. package/package.json +1 -1
  31. package/src/cli/startupOutput.ts +16 -0
  32. package/src/cli.ts +61 -15
  33. package/src/components/ProxyViewer.tsx +535 -163
  34. package/src/components/ProxyViewerContainer.tsx +77 -67
  35. package/src/components/alerts/AlertsDialog.tsx +2 -1
  36. package/src/components/clients/ClientLogo.tsx +1 -1
  37. package/src/components/groups/GroupsDialog.tsx +19 -1
  38. package/src/components/providers/ProviderCard.tsx +133 -32
  39. package/src/components/providers/ProviderForm.tsx +61 -29
  40. package/src/components/providers/ProviderLogo.tsx +6 -1
  41. package/src/components/providers/ProvidersPanel.tsx +7 -7
  42. package/src/components/providers/SettingsDialog.tsx +13 -6
  43. package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
  44. package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
  45. package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
  46. package/src/components/proxy-viewer/ConversationHeader.tsx +153 -120
  47. package/src/components/proxy-viewer/LogEntry.tsx +130 -43
  48. package/src/components/proxy-viewer/LogEntryHeader.tsx +357 -354
  49. package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
  50. package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
  51. package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
  52. package/src/components/proxy-viewer/TurnGroup.tsx +33 -69
  53. package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
  54. package/src/components/proxy-viewer/bodyHydration.ts +56 -0
  55. package/src/components/proxy-viewer/viewerState.ts +0 -4
  56. package/src/components/ui/icon-trigger.ts +2 -0
  57. package/src/components/ui/transient-toast.tsx +104 -0
  58. package/src/lib/providerTestContract.ts +56 -10
  59. package/src/lib/providerTestPrompt.ts +23 -1
  60. package/src/lib/sessionInfoContract.ts +4 -0
  61. package/src/lib/upstreamUrl.ts +105 -1
  62. package/src/mcp/server.ts +71 -27
  63. package/src/mcp/toolHandlers.ts +24 -5
  64. package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
  65. package/src/proxy/sessionInfo.ts +31 -2
  66. package/src/proxy/sessionSupervisor.ts +20 -1
  67. package/src/proxy/store.ts +1 -1
  68. package/src/routes/api/providers.$providerId.test.log.ts +93 -14
  69. package/styles/globals.css +180 -180
  70. package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
  71. package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
  72. package/.output/public/assets/index-CI1-G8ua.js +0 -1
  73. package/.output/public/assets/index-DdhFqPsI.css +0 -1
@@ -1,4 +1,4 @@
1
- import { useCallback, useState } from "react";
1
+ import { type ReactNode, useCallback, useState } from "react";
2
2
  import {
3
3
  ChevronDown,
4
4
  ChevronRight,
@@ -18,9 +18,37 @@ 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
+ const HEADER_ACTION_BUTTON_CLASS =
25
+ "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";
26
+
27
+ function HeaderMetric({
28
+ icon,
29
+ children,
30
+ title,
31
+ className,
32
+ }: {
33
+ icon: ReactNode;
34
+ children: ReactNode;
35
+ title?: string;
36
+ className?: string;
37
+ }): JSX.Element {
38
+ return (
39
+ <span
40
+ className={cn(
41
+ "border border-border bg-muted/30 inline-flex h-7 min-w-0 items-center gap-1.5 rounded-md px-2 text-xs text-muted-foreground",
42
+ className,
43
+ )}
44
+ title={title}
45
+ >
46
+ {icon}
47
+ <span className="min-w-0 font-mono tabular-nums">{children}</span>
48
+ </span>
49
+ );
50
+ }
51
+
24
52
  export type ConversationHeaderProps = {
25
53
  conversationId: string;
26
54
  startTime: string;
@@ -31,8 +59,8 @@ export type ConversationHeaderProps = {
31
59
  expanded: boolean;
32
60
  onToggle: () => void;
33
61
  /** 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
62
+ * model names. Rendered as a row of provider logos next to the header, one
63
+ * per provider so model-switching inside a single session is visible.
36
64
  * at a glance. `undefined` hides the strip (default). */
37
65
  providers?: { provider: Provider; models: string[] }[];
38
66
  /** When true and the group is collapsed, show a spinner instead of the
@@ -50,6 +78,10 @@ export type ConversationHeaderProps = {
50
78
  * Only shown as a secondary label when the session lacks a real
51
79
  * `sessionId` (the same fallback path used for `clientPid`). */
52
80
  clientProjectFolder?: string | null;
81
+ /** Show IDE identity in the header. Parent IDE rows can disable this to avoid repetition. */
82
+ showClientIdentity?: boolean;
83
+ /** Show process/project metadata in the header. Parent process rows can disable this. */
84
+ showProcessMetadata?: boolean;
53
85
  /** Controls whether timestamps render as compact local time or full ISO strings. */
54
86
  timeDisplayFormat: TimeDisplayFormat;
55
87
  /** Clear all logs in this group. After confirmation the parent removes them
@@ -71,6 +103,8 @@ export function ConversationHeader({
71
103
  userAgent,
72
104
  clientPid,
73
105
  clientProjectFolder,
106
+ showClientIdentity = true,
107
+ showProcessMetadata = true,
74
108
  timeDisplayFormat,
75
109
  onClear,
76
110
  }: ConversationHeaderProps): JSX.Element {
@@ -100,6 +134,14 @@ export function ConversationHeader({
100
134
  const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
101
135
  const clientTooltip =
102
136
  detectedClientLabel !== null && hasUserAgent ? `${detectedClientLabel} - ${userAgent}` : null;
137
+ const isFallbackConversation = conversationId.startsWith("PID:") || conversationId.includes("|");
138
+ const compactSessionLabel =
139
+ conversationId.length > 24
140
+ ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
141
+ : conversationId;
142
+
143
+ const timeLabel = formatTimestampRange(startTime, endTime, timeDisplayFormat);
144
+ const callsLabel = `${totalCalls} call${totalCalls !== 1 ? "s" : ""}`;
103
145
 
104
146
  return (
105
147
  <div
@@ -108,11 +150,11 @@ export function ConversationHeader({
108
150
  data-nav-id={`conv-${conversationId}`}
109
151
  data-nav-action={expanded ? "collapse" : "expand"}
110
152
  className={cn(
111
- "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
112
- "hover:bg-muted/50",
153
+ "border border-border bg-card shadow-sm flex cursor-pointer items-start gap-3 rounded-[8px] px-3 py-2.5 transition-colors",
154
+ "hover:border-cyan-200/20",
113
155
  "select-none",
114
- "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
115
- "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
156
+ "sticky top-0 z-10 mb-2",
157
+ "focus-visible:ring-1 focus-visible:ring-cyan-300/35 focus-visible:outline-none",
116
158
  )}
117
159
  onClick={onToggle}
118
160
  onKeyDown={(e) => {
@@ -124,143 +166,134 @@ export function ConversationHeader({
124
166
  }}
125
167
  >
126
168
  {/* Provider logos: first visual element whenever model/provider logos are available. */}
127
- <ProviderLogoStack items={providerLogoItems} ariaLabel="Models used in this session" />
128
-
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>
169
+ <ProviderLogoStack
170
+ items={providerLogoItems}
171
+ ariaLabel="Models used in this session"
172
+ className="mt-0.5"
173
+ />
139
174
 
140
- {/* Session identifier.
141
- - Real session id (e.g. `sess_abc123...`) → render the id with the
142
- standard ellipsis truncation; show `PID <n>` as a secondary pill
143
- when we know the originating process.
144
- - Fallback id (no sessionId was emitted) → the conversationId is
145
- already a synthesized `PID:<n>|<folder>` marker; render the PID
146
- as the primary identifier (purple, same weight as a real id)
147
- and the project folder as a secondary muted label. */}
148
- {conversationId.startsWith("PID:") || conversationId.includes("|") ? (
149
- <>
150
- {clientPid !== null && clientPid !== undefined ? (
175
+ <span className="min-w-0 flex-1">
176
+ <span className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
177
+ {isFallbackConversation && showProcessMetadata ? (
178
+ clientPid !== null && clientPid !== undefined ? (
179
+ <span
180
+ className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-violet-200"
181
+ title={`PID ${clientPid}`}
182
+ >
183
+ PID {clientPid}
184
+ </span>
185
+ ) : (
186
+ <span
187
+ className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-violet-200"
188
+ title={conversationId}
189
+ >
190
+ {conversationId}
191
+ </span>
192
+ )
193
+ ) : (
151
194
  <span
152
- className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
153
- title={`PID ${clientPid}`}
195
+ className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-violet-200"
196
+ title={conversationId}
154
197
  >
155
- PID {clientPid}
156
- </span>
157
- ) : (
158
- <span className="text-purple-400/90 font-mono text-xs font-semibold shrink-0">
159
- {conversationId}
198
+ {isFallbackConversation ? "sessionless" : compactSessionLabel}
160
199
  </span>
161
200
  )}
162
- {clientProjectFolder !== null &&
201
+
202
+ {!isFallbackConversation &&
203
+ showProcessMetadata &&
204
+ clientPid !== null &&
205
+ clientPid !== undefined && (
206
+ <span
207
+ 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"
208
+ title={`Client process ID ${clientPid}`}
209
+ >
210
+ PID {clientPid}
211
+ </span>
212
+ )}
213
+
214
+ {isFallbackConversation &&
215
+ showProcessMetadata &&
216
+ clientProjectFolder !== null &&
163
217
  clientProjectFolder !== undefined &&
164
218
  clientProjectFolder !== "" && (
165
219
  <span
166
- className="text-muted-foreground text-xs font-mono truncate max-w-[200px] shrink-0"
220
+ className="min-w-0 max-w-[240px] truncate font-mono text-xs text-muted-foreground"
167
221
  title={clientProjectFolder}
168
222
  >
169
223
  {clientProjectFolder}
170
224
  </span>
171
225
  )}
172
- </>
173
- ) : (
174
- <>
175
- <span
176
- className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
177
- title={conversationId}
178
- >
179
- {conversationId.length > 24
180
- ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
181
- : conversationId}
182
- </span>
183
- {clientPid !== null && clientPid !== undefined && (
226
+
227
+ {showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && (
184
228
  <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"
186
- title={`Client process ID ${clientPid}`}
229
+ className="border border-border bg-muted/30 inline-flex h-6 max-w-[180px] shrink-0 items-center gap-1 rounded-md px-1.5 text-xs text-muted-foreground"
230
+ title={clientTooltip ?? clientDisplayLabel}
187
231
  >
188
- PID {clientPid}
232
+ <ClientLogo client={clientApp} className="size-4 shrink-0" />
233
+ <span className="truncate font-mono tabular-nums">{clientDisplayLabel}</span>
189
234
  </span>
190
235
  )}
191
- </>
192
- )}
193
-
194
- {/* Client app / User-Agent */}
195
- {clientDisplayLabel !== null && clientDisplayLabel !== "" && (
196
- <span
197
- className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
198
- title={clientTooltip ?? clientDisplayLabel}
199
- >
200
- <ClientLogo client={clientApp} className="size-5" />
201
- <span className="font-mono tabular-nums truncate max-w-[120px]">
202
- {clientDisplayLabel}
203
- </span>
204
- </span>
205
- )}
206
-
207
- {/* Time range */}
208
- <span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
209
- <Clock className="size-3" />
210
- <span className="font-mono tabular-nums">
211
- {formatTimestampRange(startTime, endTime, timeDisplayFormat)}
212
236
  </span>
213
- </span>
214
237
 
215
- {/* Total calls */}
216
- <span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
217
- <MessageSquare className="size-3" />
218
- <span className="font-mono tabular-nums">
219
- {totalCalls} call{totalCalls !== 1 ? "s" : ""}
238
+ <span className="mt-1.5 flex min-w-0 flex-wrap items-center gap-1.5">
239
+ <HeaderMetric icon={<Clock className="size-3 shrink-0" />} title={timeLabel}>
240
+ {timeLabel}
241
+ </HeaderMetric>
242
+ <HeaderMetric icon={<MessageSquare className="size-3 shrink-0" />}>
243
+ {callsLabel}
244
+ </HeaderMetric>
245
+ <HeaderMetric icon={<Zap className="size-3 shrink-0" />} className="max-w-full">
246
+ <span className="text-blue-400">{formatTokens(totalInputTokens)}</span>
247
+ {" / "}
248
+ <span className="text-amber-400">{formatTokens(totalOutputTokens)}</span>
249
+ </HeaderMetric>
220
250
  </span>
221
251
  </span>
222
252
 
223
- {/* Token counts */}
224
- <span className="flex items-center gap-1 text-xs shrink-0">
225
- <Zap className="size-3 text-muted-foreground" />
226
- <span className="font-mono tabular-nums">
227
- <span className="text-blue-400">{formatTokens(totalInputTokens)}</span>
228
- {" / "}
229
- <span className="text-amber-400">{formatTokens(totalOutputTokens)}</span>
230
- </span>
231
- </span>
232
-
233
- {/* Spacer */}
234
- <span className="flex-1 min-w-0" />
235
-
236
- {/* Open this session in a new tab - deep link to /session/$id */}
237
- <button
238
- type="button"
239
- onClick={handleOpenInNewTab}
240
- onKeyDown={(e) => {
241
- if (e.key === "Enter" || e.key === " ") {
242
- handleOpenInNewTab(e);
243
- }
244
- }}
245
- aria-label={`Open session ${conversationId} in a new tab`}
246
- 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"
248
- >
249
- <ExternalLink className="size-3.5" />
250
- </button>
251
-
252
- {/* Per-group Clear - does not toggle the group's expand state */}
253
- {onClear !== undefined && (
253
+ <span className="flex shrink-0 items-start gap-1.5">
254
+ {/* Open this session in a new tab - deep link to /session/$id */}
254
255
  <button
255
256
  type="button"
256
- onClick={handleClearClick}
257
- aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
258
- 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
+ onClick={handleOpenInNewTab}
258
+ onKeyDown={(e) => {
259
+ if (e.key === "Enter" || e.key === " ") {
260
+ handleOpenInNewTab(e);
261
+ }
262
+ }}
263
+ aria-label={`Open session ${conversationId} in a new tab`}
264
+ title="Open this session in a new tab"
265
+ className={HEADER_ACTION_BUTTON_CLASS}
260
266
  >
261
- <Trash2 className="size-3.5" />
267
+ <ExternalLink className="size-3.5" />
262
268
  </button>
263
- )}
269
+
270
+ {/* Per-group Clear - does not toggle the group's expand state */}
271
+ {onClear !== undefined && (
272
+ <button
273
+ type="button"
274
+ onClick={handleClearClick}
275
+ aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
276
+ title="Clear this group"
277
+ className={HEADER_ACTION_BUTTON_CLASS}
278
+ >
279
+ <Trash2 className="size-3.5" />
280
+ </button>
281
+ )}
282
+
283
+ {/* Expand chevron - shows spinner when collapsed and group has pending logs */}
284
+ <span
285
+ 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"
286
+ aria-hidden="true"
287
+ >
288
+ {expanded ? (
289
+ <ChevronDown className="size-4 shrink-0" />
290
+ ) : isLoading ? (
291
+ <Loader2 className="size-4 shrink-0 animate-spin" />
292
+ ) : (
293
+ <ChevronRight className="size-4 shrink-0" />
294
+ )}
295
+ </span>
296
+ </span>
264
297
 
265
298
  <ConfirmDialog
266
299
  open={confirmOpen}
@@ -312,10 +345,10 @@ export function hasMixedApiFormat(logs: CapturedLog[]): boolean {
312
345
  }
313
346
 
314
347
  export function getConversationId(log: CapturedLog): string {
315
- if (log.isTest === true) return "provider-test";
316
348
  if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== undefined) {
317
349
  return log.sessionId;
318
350
  }
351
+ if (log.isTest === true) return "provider-test";
319
352
  const hasPid = log.clientPid !== null && log.clientPid !== undefined;
320
353
  const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== undefined;
321
354
  if (!hasPid && !hasFolder) return "default";