@tonyclaw/agent-inspector 3.0.3 → 3.0.5

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 (37) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-CufpM7_P.js → CompareDrawer-Co-35_Xw.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DPv1rBo7.js +106 -0
  4. package/.output/public/assets/{ReplayDialog-ClDmxv57.js → ReplayDialog-DghbvKdy.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-DcuIghmp.js → RequestAnatomy-CRXE5v8X.js} +1 -1
  6. package/.output/public/assets/{ResponseView-C7Tl47j3.js → ResponseView-CzxoQRjp.js} +2 -2
  7. package/.output/public/assets/{StreamingChunkSequence-DBK_R540.js → StreamingChunkSequence-DnFyF_Hr.js} +1 -1
  8. package/.output/public/assets/{_sessionId-D8Ke-EcI.js → _sessionId-DHRhWzKe.js} +1 -1
  9. package/.output/public/assets/index-D8cruW0P.css +1 -0
  10. package/.output/public/assets/index-DZyTpd2w.js +1 -0
  11. package/.output/public/assets/{index-CpWH7b2O.js → index-Dl1oki9E.js} +1 -1
  12. package/.output/public/assets/{json-viewer-BEifdKM_.js → json-viewer-B84f7oiG.js} +1 -1
  13. package/.output/public/assets/{main-DDfExS3_.js → main-DViDnJ9X.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +2 -2
  15. package/.output/server/{_sessionId-Di7z893V.mjs → _sessionId-C43vEAWe.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-ABv-8FMB.mjs → CompareDrawer-DOL6q0jD.mjs} +2 -2
  17. package/.output/server/_ssr/{ProxyViewerContainer-CGX-1aUr.mjs → ProxyViewerContainer-S9izw3ND.mjs} +572 -272
  18. package/.output/server/_ssr/{ReplayDialog-DD31ftzG.mjs → ReplayDialog-tqthwJh0.mjs} +3 -3
  19. package/.output/server/_ssr/{RequestAnatomy-54kNHAjH.mjs → RequestAnatomy-CgWDjFPR.mjs} +2 -2
  20. package/.output/server/_ssr/{ResponseView-CRnibtlX.mjs → ResponseView-Dk8Op622.mjs} +2 -2
  21. package/.output/server/_ssr/{StreamingChunkSequence-Cb5WGfMm.mjs → StreamingChunkSequence-AlS7PK8f.mjs} +2 -2
  22. package/.output/server/_ssr/{index-BJ-8DomR.mjs → index-BGwvMN2M.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-DB5188I7.mjs → json-viewer-CKHkh5U3.mjs} +2 -2
  25. package/.output/server/_ssr/{router-BKf70uDr.mjs → router-BJMaHkr1.mjs} +9 -9
  26. package/.output/server/{_tanstack-start-manifest_v-DUuf3Rgf.mjs → _tanstack-start-manifest_v-C_HvRzgP.mjs} +1 -1
  27. package/.output/server/index.mjs +66 -66
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +92 -25
  30. package/src/components/ProxyViewerContainer.tsx +154 -55
  31. package/src/components/providers/ProviderCard.tsx +413 -230
  32. package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
  33. package/src/components/proxy-viewer/ConversationHeader.tsx +4 -5
  34. package/src/lib/stopReason.ts +4 -0
  35. package/.output/public/assets/ProxyViewerContainer-Dppr2zGE.js +0 -106
  36. package/.output/public/assets/index-BskTYcAH.css +0 -1
  37. package/.output/public/assets/index-DRHfTEM5.js +0 -1
@@ -47,6 +47,8 @@ const LogCursorPageSchema = z.object({
47
47
 
48
48
  type LogCursorPage = z.infer<typeof LogCursorPageSchema>;
49
49
 
50
+ const SessionsResponseSchema = z.array(z.string());
51
+
50
52
  type SessionPageRequest =
51
53
  | { kind: "newest" }
52
54
  | { kind: "oldest" }
@@ -114,6 +116,8 @@ const DEBOUNCE_MS = 100;
114
116
  const HASH_SCROLL_ATTEMPTS = 12;
115
117
  const HASH_HIGHLIGHT_MS = 1800;
116
118
  const SESSION_PAGE_LIMIT = 100;
119
+ const SESSION_RELOAD_PAGE_LIMIT = 500;
120
+ const MAX_SESSION_RELOAD_PAGES = 200;
117
121
 
118
122
  function buildLogsStreamUrl(sessionId: string | undefined): string {
119
123
  const params = new URLSearchParams();
@@ -125,11 +129,15 @@ function buildLogsStreamUrl(sessionId: string | undefined): string {
125
129
  return query.length > 0 ? `/api/logs/stream?${query}` : "/api/logs/stream";
126
130
  }
127
131
 
128
- function buildSessionLogsPageUrl(sessionId: string, request: SessionPageRequest): string {
132
+ function buildSessionLogsPageUrl(
133
+ sessionId: string,
134
+ request: SessionPageRequest,
135
+ limit = SESSION_PAGE_LIMIT,
136
+ ): string {
129
137
  const params = new URLSearchParams({
130
138
  cursor: "1",
131
139
  compact: "1",
132
- limit: String(SESSION_PAGE_LIMIT),
140
+ limit: String(limit),
133
141
  sessionId,
134
142
  });
135
143
 
@@ -174,6 +182,20 @@ export function mergeLogsById(
174
182
  return [...byId.values()].sort((left, right) => left.id - right.id);
175
183
  }
176
184
 
185
+ export function mergeSessionIds(
186
+ previousSessions: readonly string[],
187
+ incomingSessions: readonly string[],
188
+ ): string[] {
189
+ const sessions = new Set<string>();
190
+ for (const session of previousSessions) {
191
+ if (session !== "") sessions.add(session);
192
+ }
193
+ for (const session of incomingSessions) {
194
+ if (session !== "") sessions.add(session);
195
+ }
196
+ return [...sessions].sort();
197
+ }
198
+
177
199
  export function ProxyViewerContainer({
178
200
  initialSessionId,
179
201
  }: {
@@ -195,6 +217,7 @@ export function ProxyViewerContainer({
195
217
  const [allLogs, setAllLogs] = useState<CapturedLog[]>([]);
196
218
  const [selectedSession, setSelectedSession] = useState(initialSessionId ?? "__all__");
197
219
  const [selectedModel, setSelectedModel] = useState("__all__");
220
+ const [knownSessions, setKnownSessions] = useState<string[]>([]);
198
221
  const { notice, showToast, dismissToast } = useTransientToast();
199
222
  const [streamInitialized, setStreamInitialized] = useState(initialSessionId === undefined);
200
223
  const [logPage, setLogPage] = useState<LogCursorPage | null>(null);
@@ -225,9 +248,22 @@ export function ProxyViewerContainer({
225
248
  () => filterLogs(allLogs, selectedSession, selectedModel),
226
249
  [allLogs, selectedSession, selectedModel],
227
250
  );
228
- const sessions = useMemo(() => extractSessions(allLogs), [allLogs]);
251
+ const sessions = useMemo(
252
+ () => mergeSessionIds(knownSessions, extractSessions(allLogs)),
253
+ [allLogs, knownSessions],
254
+ );
229
255
  const models = useMemo(() => extractModels(allLogs), [allLogs]);
230
256
 
257
+ const refreshSessions = useCallback(() => {
258
+ void fetchJson("/api/sessions", SessionsResponseSchema)
259
+ .then((sessionIds) => {
260
+ setKnownSessions((prev) => mergeSessionIds(prev, sessionIds));
261
+ })
262
+ .catch(() => {
263
+ // Session discovery is best-effort. Live SSE and session reloads still populate this list.
264
+ });
265
+ }, []);
266
+
231
267
  const flushUpdates = useCallback(() => {
232
268
  flushTimerRef.current = null;
233
269
  const updates = pendingUpdatesRef.current;
@@ -286,9 +322,14 @@ export function ProxyViewerContainer({
286
322
  logIndexRef.current = buildLogIndex(nextLogs);
287
323
  return nextLogs;
288
324
  });
325
+ setKnownSessions((prev) => mergeSessionIds(prev, extractSessions(update.logs)));
289
326
  setStreamInitialized(true);
290
327
  dismissToast();
291
328
  } else if (update.type === "update") {
329
+ setKnownSessions((prev) => {
330
+ if (update.log.sessionId === null || update.log.sessionId === "") return prev;
331
+ return mergeSessionIds(prev, [update.log.sessionId]);
332
+ });
292
333
  scheduleUpdate(update.log);
293
334
  }
294
335
  } catch {
@@ -338,6 +379,64 @@ export function ProxyViewerContainer({
338
379
  [dismissToast, initialSessionId, showErrorToast],
339
380
  );
340
381
 
382
+ const reloadSessionLogs = useCallback(
383
+ (sessionId: string) => {
384
+ const requestId = sessionPageRequestIdRef.current + 1;
385
+ sessionPageRequestIdRef.current = requestId;
386
+ setSessionPageLoading(true);
387
+ dismissToast();
388
+
389
+ void (async () => {
390
+ const collected: CapturedLog[] = [];
391
+ let page = await fetchJson(
392
+ buildSessionLogsPageUrl(sessionId, { kind: "oldest" }, SESSION_RELOAD_PAGE_LIMIT),
393
+ LogCursorPageSchema,
394
+ );
395
+ collected.push(...page.logs);
396
+
397
+ let loadedPages = 1;
398
+ while (
399
+ page.hasNewer &&
400
+ page.newestLogId !== null &&
401
+ loadedPages < MAX_SESSION_RELOAD_PAGES
402
+ ) {
403
+ page = await fetchJson(
404
+ buildSessionLogsPageUrl(
405
+ sessionId,
406
+ { kind: "newer", afterLogId: page.newestLogId },
407
+ SESSION_RELOAD_PAGE_LIMIT,
408
+ ),
409
+ LogCursorPageSchema,
410
+ );
411
+ collected.push(...page.logs);
412
+ loadedPages += 1;
413
+ }
414
+
415
+ if (sessionPageRequestIdRef.current !== requestId) return;
416
+ setAllLogs((prev) => {
417
+ const next = mergeLogsById(prev, collected);
418
+ logIndexRef.current = buildLogIndex(next);
419
+ return next;
420
+ });
421
+ setKnownSessions((prev) => mergeSessionIds(prev, [sessionId]));
422
+ setSessionPageLoading(false);
423
+ if (page.hasNewer) {
424
+ showToast({
425
+ message: `Loaded ${String(collected.length)} logs for this session. More history is available.`,
426
+ tone: "success",
427
+ });
428
+ } else {
429
+ dismissToast();
430
+ }
431
+ })().catch((err: unknown) => {
432
+ if (sessionPageRequestIdRef.current !== requestId) return;
433
+ setSessionPageLoading(false);
434
+ showErrorToast(err instanceof Error ? err.message : "Failed to reload session logs");
435
+ });
436
+ },
437
+ [dismissToast, showErrorToast, showToast],
438
+ );
439
+
341
440
  useEffect(() => {
342
441
  if (initialSessionId === undefined) {
343
442
  setSessionMemberships([]);
@@ -365,11 +464,21 @@ export function ProxyViewerContainer({
365
464
  };
366
465
  }, [initialSessionId]);
367
466
 
467
+ useEffect(() => {
468
+ refreshSessions();
469
+ }, [refreshSessions]);
470
+
368
471
  useEffect(() => {
369
472
  if (initialSessionId === undefined) return;
370
473
  loadSessionPage({ kind: "newest" });
371
474
  }, [initialSessionId, loadSessionPage]);
372
475
 
476
+ useEffect(() => {
477
+ if (initialSessionId !== undefined) return;
478
+ if (selectedSession === "__all__") return;
479
+ reloadSessionLogs(selectedSession);
480
+ }, [initialSessionId, reloadSessionLogs, selectedSession]);
481
+
373
482
  useEffect(() => {
374
483
  if (initialSessionId !== undefined) return undefined;
375
484
  connectSSE();
@@ -443,64 +552,45 @@ export function ProxyViewerContainer({
443
552
  }, [logs.length]);
444
553
 
445
554
  const handleClearAll = useCallback(() => {
446
- if (initialSessionId !== undefined && allLogs.length === 0) return;
447
- void (async () => {
448
- try {
449
- const body =
450
- initialSessionId === undefined
451
- ? undefined
452
- : JSON.stringify({ ids: allLogs.map((log) => log.id) });
453
- const res = await fetch("/api/logs", {
454
- method: "DELETE",
455
- ...(body === undefined
456
- ? {}
457
- : {
458
- headers: { "Content-Type": "application/json" },
459
- body,
460
- }),
461
- });
462
- if (!res.ok) {
463
- showErrorToast("Failed to clear logs");
464
- return;
465
- }
466
- logIndexRef.current.clear();
467
- setAllLogs([]);
468
- dismissToast();
469
- } catch (err) {
470
- showErrorToast(err instanceof Error ? err.message : "Unknown error clearing logs");
471
- }
472
- })();
473
- }, [allLogs, dismissToast, initialSessionId, showErrorToast]);
555
+ const clearedSessions = extractSessions(allLogs);
556
+ logIndexRef.current.clear();
557
+ setAllLogs([]);
558
+ setLogPage(null);
559
+ setKnownSessions((prev) => mergeSessionIds(prev, clearedSessions));
560
+ showToast({
561
+ message: "Cleared the current view. Stored logs are still reloadable.",
562
+ tone: "success",
563
+ });
564
+ }, [allLogs, showToast]);
474
565
 
475
566
  const handleClearGroup = useCallback(
476
567
  (ids: number[]) => {
477
568
  if (ids.length === 0) return;
478
- void (async () => {
479
- try {
480
- const res = await fetch("/api/logs", {
481
- method: "DELETE",
482
- headers: { "Content-Type": "application/json" },
483
- body: JSON.stringify({ ids }),
484
- });
485
- if (!res.ok) {
486
- showErrorToast("Failed to clear group");
487
- return;
488
- }
489
- const idSet = new Set(ids);
490
- setAllLogs((prev) => {
491
- const remaining = prev.filter((l) => !idSet.has(l.id));
492
- logIndexRef.current = buildLogIndex(remaining);
493
- return remaining;
494
- });
495
- dismissToast();
496
- } catch (err) {
497
- showErrorToast(err instanceof Error ? err.message : "Unknown error clearing group");
498
- }
499
- })();
569
+ const idSet = new Set(ids);
570
+ setAllLogs((prev) => {
571
+ const removed = prev.filter((log) => idSet.has(log.id));
572
+ const remaining = prev.filter((log) => !idSet.has(log.id));
573
+ setKnownSessions((known) => mergeSessionIds(known, extractSessions(removed)));
574
+ logIndexRef.current = buildLogIndex(remaining);
575
+ return remaining;
576
+ });
577
+ showToast({ message: "Cleared this group from the current view.", tone: "success" });
500
578
  },
501
- [dismissToast, showErrorToast],
579
+ [showToast],
502
580
  );
503
581
 
582
+ const handleReloadSession = useCallback(() => {
583
+ if (initialSessionId !== undefined) {
584
+ loadSessionPage({ kind: "newest" });
585
+ return;
586
+ }
587
+ if (selectedSession === "__all__") {
588
+ refreshSessions();
589
+ return;
590
+ }
591
+ reloadSessionLogs(selectedSession);
592
+ }, [initialSessionId, loadSessionPage, refreshSessions, reloadSessionLogs, selectedSession]);
593
+
504
594
  const handleImportLogs = useCallback(
505
595
  async (file: File): Promise<ImportLogsResponse> => {
506
596
  const formData = new FormData();
@@ -588,10 +678,19 @@ export function ProxyViewerContainer({
588
678
  onSessionChange={setSelectedSession}
589
679
  onModelChange={setSelectedModel}
590
680
  onClearAll={handleClearAll}
681
+ onReloadSession={
682
+ initialSessionId !== undefined || selectedSession !== "__all__"
683
+ ? handleReloadSession
684
+ : undefined
685
+ }
591
686
  onClearGroup={handleClearGroup}
592
687
  onNotify={showToast}
593
688
  onImportLogs={initialSessionId === undefined ? handleImportLogs : undefined}
594
- isLoading={initialSessionId === undefined ? !streamInitialized : sessionPageLoading}
689
+ isLoading={
690
+ initialSessionId === undefined
691
+ ? !streamInitialized || sessionPageLoading
692
+ : sessionPageLoading
693
+ }
595
694
  pagination={pagination}
596
695
  sessionMemberships={sessionMemberships}
597
696
  viewMode={viewMode}