@tonyclaw/agent-inspector 2.0.43 → 2.1.1

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 (44) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-CxM1gCfL.js → CompareDrawer-DkDpl_Sg.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-D_-e1qn7.js +117 -0
  4. package/.output/public/assets/{ReplayDialog-UseUkucS.js → ReplayDialog-CKqYfD0A.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-CQZl-YIx.js +1 -0
  6. package/.output/public/assets/ResponseView-DW9tFPi0.js +1 -0
  7. package/.output/public/assets/{StreamingChunkSequence-JSQEPeNS.js → StreamingChunkSequence-m8zKc3L1.js} +1 -1
  8. package/.output/public/assets/_sessionId-DyfzgUXv.js +1 -0
  9. package/.output/public/assets/index-B6bobhTz.js +1 -0
  10. package/.output/public/assets/index-CLQKZ5A5.css +1 -0
  11. package/.output/public/assets/{main-BpGVJcpB.js → main-BjXd3DR-.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +219 -178
  13. package/.output/server/{_sessionId-DGn-TENM.mjs → _sessionId-LjzdyWF4.mjs} +3 -3
  14. package/.output/server/_ssr/{CompareDrawer-CFElCSYY.mjs → CompareDrawer-YX2uOwh9.mjs} +4 -4
  15. package/.output/server/_ssr/{ProxyViewerContainer-B7SR9mrD.mjs → ProxyViewerContainer-BcKHl2E3.mjs} +1016 -582
  16. package/.output/server/_ssr/{ReplayDialog-DfKapj0k.mjs → ReplayDialog-B9AxvV2j.mjs} +5 -5
  17. package/.output/server/_ssr/{RequestAnatomy-CUxTCg31.mjs → RequestAnatomy-DxmUOcz8.mjs} +4 -4
  18. package/.output/server/_ssr/{ResponseView-BXY0w197.mjs → ResponseView-B73PHDQ0.mjs} +4 -4
  19. package/.output/server/_ssr/{StreamingChunkSequence-CzMnES9H.mjs → StreamingChunkSequence-Bkwxu5Ev.mjs} +4 -4
  20. package/.output/server/_ssr/{index-CVlT-REW.mjs → index-Dfl6uxit.mjs} +3 -3
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-B2d1LUx6.mjs → router-B5cpU5Hi.mjs} +458 -10
  23. package/.output/server/{_tanstack-start-manifest_v-Drpi28BM.mjs → _tanstack-start-manifest_v-CHhxRQY_.mjs} +1 -1
  24. package/.output/server/index.mjs +62 -62
  25. package/package.json +1 -1
  26. package/src/components/OnboardingBanner.tsx +60 -70
  27. package/src/components/ProxyViewer.tsx +838 -366
  28. package/src/components/ProxyViewerContainer.tsx +136 -5
  29. package/src/components/providers/SettingsDialog.tsx +0 -13
  30. package/src/components/proxy-viewer/LogEntry.tsx +198 -89
  31. package/src/components/proxy-viewer/LogEntryHeader.tsx +23 -13
  32. package/src/components/proxy-viewer/RequestToolsPanel.tsx +8 -8
  33. package/src/components/ui/crab-logo.tsx +0 -50
  34. package/src/components/ui/json-viewer.tsx +6 -0
  35. package/src/proxy/logIndex.ts +188 -1
  36. package/src/proxy/store.ts +405 -3
  37. package/src/routes/api/logs.ts +36 -0
  38. package/styles/globals.css +53 -8
  39. package/.output/public/assets/ProxyViewerContainer-TtRG-0E7.js +0 -117
  40. package/.output/public/assets/RequestAnatomy-aPxgEJ2L.js +0 -1
  41. package/.output/public/assets/ResponseView-DA-F4F97.js +0 -1
  42. package/.output/public/assets/_sessionId-BEXuCWq5.js +0 -1
  43. package/.output/public/assets/index-Bhsa_2xX.js +0 -1
  44. package/.output/public/assets/index-DOWlRJ0W.css +0 -1
@@ -1,10 +1,15 @@
1
1
  import { useState, useEffect, useCallback, useRef, useMemo, 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
5
  import { InspectorGroupsListResponseSchema, type InspectorGroup } from "../lib/groupContract";
5
6
  import { useStripConfig } from "../lib/useStripConfig";
6
7
  import { OnboardingBanner } from "./OnboardingBanner";
7
- import { ProxyViewer, type SessionMembershipEvidence } from "./ProxyViewer";
8
+ import {
9
+ ProxyViewer,
10
+ type LogPaginationControls,
11
+ type SessionMembershipEvidence,
12
+ } from "./ProxyViewer";
8
13
  import { dispatchLogFocusRequest } from "./proxy-viewer/logFocus";
9
14
 
10
15
  type SSEUpdate =
@@ -28,6 +33,24 @@ const SSEUpdateSchema = z.union([
28
33
  }),
29
34
  ]);
30
35
 
36
+ const LogCursorPageSchema = z.object({
37
+ logs: z.array(CapturedLogSchema),
38
+ total: z.number().int().nonnegative(),
39
+ limit: z.number().int().positive(),
40
+ hasOlder: z.boolean(),
41
+ hasNewer: z.boolean(),
42
+ oldestLogId: z.number().int().positive().nullable(),
43
+ newestLogId: z.number().int().positive().nullable(),
44
+ });
45
+
46
+ type LogCursorPage = z.infer<typeof LogCursorPageSchema>;
47
+
48
+ type SessionPageRequest =
49
+ | { kind: "newest" }
50
+ | { kind: "oldest" }
51
+ | { kind: "older"; beforeLogId: number }
52
+ | { kind: "newer"; afterLogId: number };
53
+
31
54
  function buildSessionMembershipEvidence(
32
55
  groups: readonly InspectorGroup[],
33
56
  sessionId: string,
@@ -89,6 +112,7 @@ const DEBOUNCE_MS = 50;
89
112
  const HASH_SCROLL_ATTEMPTS = 12;
90
113
  const HASH_HIGHLIGHT_MS = 1800;
91
114
  const MAX_CLIENT_LOGS = 500;
115
+ const SESSION_PAGE_LIMIT = 100;
92
116
 
93
117
  function buildLogsStreamUrl(sessionId: string | undefined): string {
94
118
  const params = new URLSearchParams();
@@ -100,6 +124,31 @@ function buildLogsStreamUrl(sessionId: string | undefined): string {
100
124
  return query.length > 0 ? `/api/logs/stream?${query}` : "/api/logs/stream";
101
125
  }
102
126
 
127
+ function buildSessionLogsPageUrl(sessionId: string, request: SessionPageRequest): string {
128
+ const params = new URLSearchParams({
129
+ cursor: "1",
130
+ compact: "1",
131
+ limit: String(SESSION_PAGE_LIMIT),
132
+ sessionId,
133
+ });
134
+
135
+ switch (request.kind) {
136
+ case "oldest":
137
+ params.set("anchor", "oldest");
138
+ break;
139
+ case "older":
140
+ params.set("beforeLogId", String(request.beforeLogId));
141
+ break;
142
+ case "newer":
143
+ params.set("afterLogId", String(request.afterLogId));
144
+ break;
145
+ case "newest":
146
+ break;
147
+ }
148
+
149
+ return `/api/logs?${params.toString()}`;
150
+ }
151
+
103
152
  function buildLogIndex(logs: readonly CapturedLog[]): Map<number, number> {
104
153
  const idx = new Map<number, number>();
105
154
  for (let i = 0; i < logs.length; i++) {
@@ -135,13 +184,15 @@ export function ProxyViewerContainer({
135
184
  const [allLogs, setAllLogs] = useState<CapturedLog[]>([]);
136
185
  const [selectedSession, setSelectedSession] = useState(initialSessionId ?? "__all__");
137
186
  const [selectedModel, setSelectedModel] = useState("__all__");
138
- const [viewMode, setViewMode] = useState<"simple" | "full">("simple");
139
187
  const [error, setError] = useState<string | null>(null);
140
188
  const [streamInitialized, setStreamInitialized] = useState(initialSessionId === undefined);
189
+ const [logPage, setLogPage] = useState<LogCursorPage | null>(null);
190
+ const [sessionPageLoading, setSessionPageLoading] = useState(initialSessionId !== undefined);
141
191
  const [sessionMemberships, setSessionMemberships] = useState<SessionMembershipEvidence[]>([]);
142
192
  const eventSourceRef = useRef<EventSource | null>(null);
143
193
  const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
144
194
  const handledHashRef = useRef<string | null>(null);
195
+ const sessionPageRequestIdRef = useRef(0);
145
196
 
146
197
  // O(1) log lookup by id
147
198
  const logIndexRef = useRef<Map<number, number>>(new Map());
@@ -245,6 +296,37 @@ export function ProxyViewerContainer({
245
296
  };
246
297
  }, [initialSessionId, scheduleUpdate]);
247
298
 
299
+ const loadSessionPage = useCallback(
300
+ (request: SessionPageRequest) => {
301
+ if (initialSessionId === undefined) return;
302
+
303
+ const requestId = sessionPageRequestIdRef.current + 1;
304
+ sessionPageRequestIdRef.current = requestId;
305
+ setSessionPageLoading(true);
306
+ setError(null);
307
+
308
+ void fetchJson(buildSessionLogsPageUrl(initialSessionId, request), LogCursorPageSchema)
309
+ .then((page) => {
310
+ if (sessionPageRequestIdRef.current !== requestId) return;
311
+ const nextLogs = page.logs;
312
+ logIndexRef.current = buildLogIndex(nextLogs);
313
+ setAllLogs(nextLogs);
314
+ setLogPage(page);
315
+ setSelectedSession(initialSessionId);
316
+ setSessionPageLoading(false);
317
+ setError(null);
318
+ })
319
+ .catch((err: unknown) => {
320
+ if (sessionPageRequestIdRef.current !== requestId) return;
321
+ setAllLogs([]);
322
+ setLogPage(null);
323
+ setSessionPageLoading(false);
324
+ setError(err instanceof Error ? err.message : "Failed to load session logs");
325
+ });
326
+ },
327
+ [initialSessionId],
328
+ );
329
+
248
330
  useEffect(() => {
249
331
  if (initialSessionId === undefined) {
250
332
  setSessionMemberships([]);
@@ -273,6 +355,12 @@ export function ProxyViewerContainer({
273
355
  }, [initialSessionId]);
274
356
 
275
357
  useEffect(() => {
358
+ if (initialSessionId === undefined) return;
359
+ loadSessionPage({ kind: "newest" });
360
+ }, [initialSessionId, loadSessionPage]);
361
+
362
+ useEffect(() => {
363
+ if (initialSessionId !== undefined) return undefined;
276
364
  connectSSE();
277
365
  return () => {
278
366
  if (eventSourceRef.current) {
@@ -288,7 +376,7 @@ export function ProxyViewerContainer({
288
376
  flushTimerRef.current = null;
289
377
  }
290
378
  };
291
- }, [connectSSE]);
379
+ }, [connectSSE, initialSessionId]);
292
380
 
293
381
  useEffect(() => {
294
382
  const hash = window.location.hash;
@@ -402,6 +490,49 @@ export function ProxyViewerContainer({
402
490
  // Read the strip config once at the container so the virtualized list does
403
491
  // not need N independent SWR subscriptions per row.
404
492
  const { strip, captureMode, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
493
+ const viewMode = captureMode;
494
+ const loadOldestPage = useCallback(() => {
495
+ loadSessionPage({ kind: "oldest" });
496
+ }, [loadSessionPage]);
497
+ const loadNewestPage = useCallback(() => {
498
+ loadSessionPage({ kind: "newest" });
499
+ }, [loadSessionPage]);
500
+ const loadOlderPage = useCallback(() => {
501
+ const firstLog = allLogs[0];
502
+ const beforeLogId = logPage?.oldestLogId ?? firstLog?.id ?? null;
503
+ if (beforeLogId === null) return;
504
+ loadSessionPage({ kind: "older", beforeLogId });
505
+ }, [allLogs, loadSessionPage, logPage]);
506
+ const loadNewerPage = useCallback(() => {
507
+ const lastLog = allLogs[allLogs.length - 1];
508
+ const afterLogId = logPage?.newestLogId ?? lastLog?.id ?? null;
509
+ if (afterLogId === null) return;
510
+ loadSessionPage({ kind: "newer", afterLogId });
511
+ }, [allLogs, loadSessionPage, logPage]);
512
+ const pagination = useMemo<LogPaginationControls | undefined>(() => {
513
+ if (initialSessionId === undefined) return undefined;
514
+ return {
515
+ isLoading: sessionPageLoading,
516
+ total: logPage?.total ?? null,
517
+ pageSize: logPage?.limit ?? SESSION_PAGE_LIMIT,
518
+ hasOlder: logPage?.hasOlder ?? false,
519
+ hasNewer: logPage?.hasNewer ?? false,
520
+ oldestLogId: logPage?.oldestLogId ?? null,
521
+ newestLogId: logPage?.newestLogId ?? null,
522
+ onOldest: loadOldestPage,
523
+ onOlder: loadOlderPage,
524
+ onNewer: loadNewerPage,
525
+ onNewest: loadNewestPage,
526
+ };
527
+ }, [
528
+ initialSessionId,
529
+ loadNewestPage,
530
+ loadNewerPage,
531
+ loadOlderPage,
532
+ loadOldestPage,
533
+ logPage,
534
+ sessionPageLoading,
535
+ ]);
405
536
 
406
537
  return (
407
538
  <>
@@ -421,10 +552,10 @@ export function ProxyViewerContainer({
421
552
  onModelChange={setSelectedModel}
422
553
  onClearAll={handleClearAll}
423
554
  onClearGroup={handleClearGroup}
424
- isLoading={initialSessionId !== undefined && !streamInitialized}
555
+ isLoading={initialSessionId === undefined ? !streamInitialized : sessionPageLoading}
556
+ pagination={pagination}
425
557
  sessionMemberships={sessionMemberships}
426
558
  viewMode={viewMode}
427
- onViewModeChange={setViewMode}
428
559
  captureMode={captureMode}
429
560
  strip={strip}
430
561
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
@@ -530,7 +530,6 @@ function McpSettingsTab(): JSX.Element {
530
530
  function ProxySettingsTab(): JSX.Element {
531
531
  const {
532
532
  strip,
533
- captureMode,
534
533
  slowResponseThresholdSeconds,
535
534
  providerTestTimeoutSeconds,
536
535
  timeDisplayFormat,
@@ -542,7 +541,6 @@ function ProxySettingsTab(): JSX.Element {
542
541
  } = useStripConfig();
543
542
  const [error, setError] = useState<string | null>(null);
544
543
  const [pending, setPending] = useState(false);
545
- const alternateCaptureMode = captureMode === "full" ? "simple" : "full";
546
544
 
547
545
  const handleToggle = useCallback(
548
546
  async (next: boolean) => {
@@ -606,17 +604,6 @@ function ProxySettingsTab(): JSX.Element {
606
604
 
607
605
  return (
608
606
  <div className="space-y-4">
609
- <div className="space-y-1">
610
- <h3 className="text-sm font-semibold">Capture mode</h3>
611
- <p className="text-xs text-muted-foreground">
612
- This process is running in <code>{captureMode}</code> mode. Restart with{" "}
613
- <code>agent-inspector --mode {alternateCaptureMode}</code> to change it.
614
- {captureMode === "simple"
615
- ? " Simple mode keeps replay, parsed requests, responses, timing, tools, and token usage while skipping raw headers, raw response retention, and detailed SSE chunk retention."
616
- : " Full mode retains raw headers, raw responses, and SSE chunks for diagnostics; use simple mode for everyday lower-overhead capture."}
617
- </p>
618
- </div>
619
-
620
607
  <div className="space-y-1">
621
608
  <h3 className="text-sm font-semibold">Claude Code billing header</h3>
622
609
  <p className="text-xs text-muted-foreground">