@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-CxM1gCfL.js → CompareDrawer-DkDpl_Sg.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-D_-e1qn7.js +117 -0
- package/.output/public/assets/{ReplayDialog-UseUkucS.js → ReplayDialog-CKqYfD0A.js} +1 -1
- package/.output/public/assets/RequestAnatomy-CQZl-YIx.js +1 -0
- package/.output/public/assets/ResponseView-DW9tFPi0.js +1 -0
- package/.output/public/assets/{StreamingChunkSequence-JSQEPeNS.js → StreamingChunkSequence-m8zKc3L1.js} +1 -1
- package/.output/public/assets/_sessionId-DyfzgUXv.js +1 -0
- package/.output/public/assets/index-B6bobhTz.js +1 -0
- package/.output/public/assets/index-CLQKZ5A5.css +1 -0
- package/.output/public/assets/{main-BpGVJcpB.js → main-BjXd3DR-.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +219 -178
- package/.output/server/{_sessionId-DGn-TENM.mjs → _sessionId-LjzdyWF4.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-CFElCSYY.mjs → CompareDrawer-YX2uOwh9.mjs} +4 -4
- package/.output/server/_ssr/{ProxyViewerContainer-B7SR9mrD.mjs → ProxyViewerContainer-BcKHl2E3.mjs} +1016 -582
- package/.output/server/_ssr/{ReplayDialog-DfKapj0k.mjs → ReplayDialog-B9AxvV2j.mjs} +5 -5
- package/.output/server/_ssr/{RequestAnatomy-CUxTCg31.mjs → RequestAnatomy-DxmUOcz8.mjs} +4 -4
- package/.output/server/_ssr/{ResponseView-BXY0w197.mjs → ResponseView-B73PHDQ0.mjs} +4 -4
- package/.output/server/_ssr/{StreamingChunkSequence-CzMnES9H.mjs → StreamingChunkSequence-Bkwxu5Ev.mjs} +4 -4
- package/.output/server/_ssr/{index-CVlT-REW.mjs → index-Dfl6uxit.mjs} +3 -3
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-B2d1LUx6.mjs → router-B5cpU5Hi.mjs} +458 -10
- package/.output/server/{_tanstack-start-manifest_v-Drpi28BM.mjs → _tanstack-start-manifest_v-CHhxRQY_.mjs} +1 -1
- package/.output/server/index.mjs +62 -62
- package/package.json +1 -1
- package/src/components/OnboardingBanner.tsx +60 -70
- package/src/components/ProxyViewer.tsx +838 -366
- package/src/components/ProxyViewerContainer.tsx +136 -5
- package/src/components/providers/SettingsDialog.tsx +0 -13
- package/src/components/proxy-viewer/LogEntry.tsx +198 -89
- package/src/components/proxy-viewer/LogEntryHeader.tsx +23 -13
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +8 -8
- package/src/components/ui/crab-logo.tsx +0 -50
- package/src/components/ui/json-viewer.tsx +6 -0
- package/src/proxy/logIndex.ts +188 -1
- package/src/proxy/store.ts +405 -3
- package/src/routes/api/logs.ts +36 -0
- package/styles/globals.css +53 -8
- package/.output/public/assets/ProxyViewerContainer-TtRG-0E7.js +0 -117
- package/.output/public/assets/RequestAnatomy-aPxgEJ2L.js +0 -1
- package/.output/public/assets/ResponseView-DA-F4F97.js +0 -1
- package/.output/public/assets/_sessionId-BEXuCWq5.js +0 -1
- package/.output/public/assets/index-Bhsa_2xX.js +0 -1
- 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 {
|
|
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
|
|
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">
|