@tonyclaw/agent-inspector 2.1.2 → 2.1.4

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 (58) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BULPis8W.js → CompareDrawer-BihEPd3v.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DYqXb6WW.js +117 -0
  4. package/.output/public/assets/{ReplayDialog-3ln-D7OS.js → ReplayDialog-DnX-6kY8.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-D8d1xV8O.js → RequestAnatomy-CIkHAxZO.js} +1 -1
  6. package/.output/public/assets/{ResponseView-CJijikn-.js → ResponseView-BFc8jjZM.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-C-RCuOTp.js → StreamingChunkSequence-p8qkpF6K.js} +1 -1
  8. package/.output/public/assets/_sessionId-Csx4T_y6.js +1 -0
  9. package/.output/public/assets/index-Bcq8bZoK.css +1 -0
  10. package/.output/public/assets/index-KMuh-31x.js +1 -0
  11. package/.output/public/assets/{main-D3pBFhb_.js → main-xlSdu7_I.js} +7 -7
  12. package/.output/server/_libs/lucide-react.mjs +204 -173
  13. package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -0
  14. package/.output/server/{_sessionId-BFMVfrw3.mjs → _sessionId-CwVQgxoK.mjs} +3 -3
  15. package/.output/server/_ssr/{CompareDrawer-CwNPZgc_.mjs → CompareDrawer-Br9Ec0ah.mjs} +3 -3
  16. package/.output/server/_ssr/{ProxyViewerContainer-HpLc7F_J.mjs → ProxyViewerContainer-Oe5Tr3C2.mjs} +982 -70
  17. package/.output/server/_ssr/{ReplayDialog-DBaFhSv6.mjs → ReplayDialog-C1_QsoA-.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-D0E0e2DT.mjs → RequestAnatomy-cn08QSoz.mjs} +2 -2
  19. package/.output/server/_ssr/{ResponseView-D2FFSRQ8.mjs → ResponseView-CIqUCCTG.mjs} +3 -3
  20. package/.output/server/_ssr/{StreamingChunkSequence-Cdu36-rL.mjs → StreamingChunkSequence-0MhVWD2F.mjs} +3 -3
  21. package/.output/server/_ssr/{index-CtBfmJYM.mjs → index-jpnI5ghp.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-Dzx1sbAQ.mjs → router-SSOn7c09.mjs} +1971 -222
  24. package/.output/server/_tanstack-start-manifest_v-BCgy_9er.mjs +4 -0
  25. package/.output/server/index.mjs +62 -62
  26. package/package.json +2 -1
  27. package/src/assets/agent-inspector.ico +0 -0
  28. package/src/assets/favicon.svg +21 -31
  29. package/src/components/ProxyViewer.tsx +87 -1
  30. package/src/components/ProxyViewerContainer.tsx +26 -0
  31. package/src/components/alerts/AlertsDialog.tsx +610 -0
  32. package/src/components/proxy-viewer/LogEntry.tsx +324 -51
  33. package/src/components/ui/dialog.tsx +1 -0
  34. package/src/contracts/index.ts +15 -2
  35. package/src/contracts/log.ts +18 -0
  36. package/src/lib/alertContract.ts +79 -0
  37. package/src/lib/apiClient.ts +39 -0
  38. package/src/lib/export-logs.ts +47 -9
  39. package/src/lib/logImportContract.ts +12 -0
  40. package/src/lib/useAlerts.ts +82 -0
  41. package/src/lib/useGroupEvidence.ts +6 -2
  42. package/src/lib/useGroups.ts +6 -2
  43. package/src/proxy/alerts.ts +664 -0
  44. package/src/proxy/logBodyChunks.ts +91 -0
  45. package/src/proxy/logImporter.ts +491 -0
  46. package/src/proxy/logIndex.ts +63 -2
  47. package/src/proxy/sqliteLogIndex.ts +612 -0
  48. package/src/proxy/store.ts +32 -7
  49. package/src/routes/api/alerts.summary.ts +24 -0
  50. package/src/routes/api/alerts.ts +52 -0
  51. package/src/routes/api/logs.$id.body.ts +44 -0
  52. package/src/routes/api/logs.import.ts +50 -0
  53. package/src/services/alerts.ts +12 -0
  54. package/.output/public/assets/ProxyViewerContainer-YxtP1xBj.js +0 -117
  55. package/.output/public/assets/_sessionId-DGehOTux.js +0 -1
  56. package/.output/public/assets/index-B-MZ9lQM.css +0 -1
  57. package/.output/public/assets/index-DhsjXb_K.js +0 -1
  58. package/.output/server/_tanstack-start-manifest_v-VrssoWVE.mjs +0 -4
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import { JsonValueSchema } from "../contracts";
3
+
4
+ export const AlertSeveritySchema = z.enum(["critical", "warning", "notice"]);
5
+ export const AlertStatusSchema = z.enum(["open"]);
6
+ export const AlertSourceSchema = z.enum(["proxy", "tool-schema", "run", "group"]);
7
+ export const AlertCategorySchema = z.enum([
8
+ "request-failure",
9
+ "request-timeout",
10
+ "slow-response",
11
+ "tool-schema",
12
+ "run-failure",
13
+ "group-failure",
14
+ "group-incomplete",
15
+ ]);
16
+ export const AlertEvidenceKindSchema = z.enum(["log", "session", "run", "group"]);
17
+ export const AlertApiFormatSchema = z.enum(["anthropic", "openai", "unknown"]);
18
+
19
+ export const AlertEvidenceLinkSchema = z.object({
20
+ kind: AlertEvidenceKindSchema,
21
+ id: z.string(),
22
+ label: z.string(),
23
+ href: z.string().nullable(),
24
+ });
25
+
26
+ export const AlertSchema = z.object({
27
+ id: z.string(),
28
+ fingerprint: z.string(),
29
+ severity: AlertSeveritySchema,
30
+ category: AlertCategorySchema,
31
+ source: AlertSourceSchema,
32
+ status: AlertStatusSchema,
33
+ title: z.string(),
34
+ message: z.string(),
35
+ count: z.number().int().positive(),
36
+ firstSeenAt: z.string(),
37
+ lastSeenAt: z.string(),
38
+ logIds: z.array(z.number().int().positive()),
39
+ sessionIds: z.array(z.string()),
40
+ runIds: z.array(z.string()),
41
+ groupIds: z.array(z.string()),
42
+ provider: z.string().nullable(),
43
+ model: z.string().nullable(),
44
+ apiFormat: AlertApiFormatSchema.nullable(),
45
+ evidence: z.array(AlertEvidenceLinkSchema),
46
+ metadata: z.record(z.string(), JsonValueSchema),
47
+ });
48
+
49
+ export const AlertCategoryCountSchema = z.object({
50
+ category: AlertCategorySchema,
51
+ count: z.number().int().nonnegative(),
52
+ });
53
+
54
+ export const AlertSummarySchema = z.object({
55
+ total: z.number().int().nonnegative(),
56
+ open: z.number().int().nonnegative(),
57
+ critical: z.number().int().nonnegative(),
58
+ warning: z.number().int().nonnegative(),
59
+ notice: z.number().int().nonnegative(),
60
+ byCategory: z.array(AlertCategoryCountSchema),
61
+ });
62
+
63
+ export const AlertListResponseSchema = z.object({
64
+ alerts: z.array(AlertSchema),
65
+ total: z.number().int().nonnegative(),
66
+ limit: z.number().int().positive(),
67
+ summary: AlertSummarySchema,
68
+ });
69
+
70
+ export type AlertSeverity = z.infer<typeof AlertSeveritySchema>;
71
+ export type AlertStatus = z.infer<typeof AlertStatusSchema>;
72
+ export type AlertSource = z.infer<typeof AlertSourceSchema>;
73
+ export type AlertCategory = z.infer<typeof AlertCategorySchema>;
74
+ export type AlertEvidenceKind = z.infer<typeof AlertEvidenceKindSchema>;
75
+ export type AlertEvidenceLink = z.infer<typeof AlertEvidenceLinkSchema>;
76
+ export type Alert = z.infer<typeof AlertSchema>;
77
+ export type AlertCategoryCount = z.infer<typeof AlertCategoryCountSchema>;
78
+ export type AlertSummary = z.infer<typeof AlertSummarySchema>;
79
+ export type AlertListResponse = z.infer<typeof AlertListResponseSchema>;
@@ -47,3 +47,42 @@ export async function fetchJson<T>(
47
47
  }
48
48
  return parseJsonResponse(response, schema);
49
49
  }
50
+
51
+ function formatTimeout(timeoutMs: number): string {
52
+ if (timeoutMs < 1000) return `${String(timeoutMs)}ms`;
53
+ return `${String(Math.round(timeoutMs / 1000))}s`;
54
+ }
55
+
56
+ export class ApiTimeoutError extends Error {
57
+ constructor(timeoutMs: number) {
58
+ super(`Request timed out after ${formatTimeout(timeoutMs)}`);
59
+ this.name = "ApiTimeoutError";
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Fetch a JSON resource with a browser-side timeout. This prevents UI loading
65
+ * states from waiting forever when a tab keeps a stale connection across a
66
+ * local server force-restart.
67
+ */
68
+ export async function fetchJsonWithTimeout<T>(
69
+ input: RequestInfo | URL,
70
+ schema: z.ZodType<T>,
71
+ timeoutMs: number,
72
+ init?: RequestInit,
73
+ errorFallback?: (response: Response) => string,
74
+ ): Promise<T> {
75
+ const controller = new AbortController();
76
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
77
+
78
+ try {
79
+ return await fetchJson(input, schema, { ...init, signal: controller.signal }, errorFallback);
80
+ } catch (error) {
81
+ if (error instanceof DOMException && error.name === "AbortError") {
82
+ throw new ApiTimeoutError(timeoutMs);
83
+ }
84
+ throw error;
85
+ } finally {
86
+ clearTimeout(timeout);
87
+ }
88
+ }
@@ -1,5 +1,6 @@
1
1
  import JSZip from "jszip";
2
2
  import type { CapturedLog } from "../contracts";
3
+ import packageJson from "../../package.json";
3
4
 
4
5
  export type ExportMode = "redacted" | "raw";
5
6
  export type ExportLogsResult =
@@ -23,6 +24,17 @@ const MAX_EXPORT_LOGS = 200;
23
24
  const MAX_EXPORT_TEXT_CHARS = 25_000_000;
24
25
  const MAX_STREAMING_CHUNK_EXPORTS = 50;
25
26
 
27
+ export type ExportManifest = {
28
+ version: string;
29
+ exportedAt: string;
30
+ mode: ExportMode;
31
+ redacted: boolean;
32
+ logCount: number;
33
+ logIds: number[];
34
+ streamingChunkExportLimit: number;
35
+ streamingChunkLogIds: number[];
36
+ };
37
+
26
38
  function formatApproxSize(chars: number): string {
27
39
  if (chars < 1024) return `${chars} chars`;
28
40
  if (chars < 1024 * 1024) return `${Math.ceil(chars / 1024)} KB`;
@@ -100,6 +112,36 @@ function exportText(text: string, mode: ExportMode): string {
100
112
  return mode === "redacted" ? redactJsonText(text) : text;
101
113
  }
102
114
 
115
+ export function buildExportLogMetadata(log: CapturedLog, mode: ExportMode): CapturedLog {
116
+ return {
117
+ ...log,
118
+ rawRequestBody: null,
119
+ responseText: null,
120
+ rawHeaders: mode === "raw" ? log.rawHeaders : undefined,
121
+ headers: mode === "raw" ? log.headers : undefined,
122
+ streamingChunks: undefined,
123
+ bodyContentMode: "compact",
124
+ };
125
+ }
126
+
127
+ export function buildExportManifest(
128
+ logs: readonly CapturedLog[],
129
+ mode: ExportMode,
130
+ streamingLogs: readonly CapturedLog[],
131
+ exportedAt: string = new Date().toISOString(),
132
+ ): ExportManifest {
133
+ return {
134
+ version: packageJson.version,
135
+ exportedAt,
136
+ mode,
137
+ redacted: mode === "redacted",
138
+ logCount: logs.length,
139
+ logIds: logs.map((log) => log.id),
140
+ streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
141
+ streamingChunkLogIds: streamingLogs.map((log) => log.id),
142
+ };
143
+ }
144
+
103
145
  export async function exportLogsAsZip(
104
146
  logs: CapturedLog[],
105
147
  mode: ExportMode = "redacted",
@@ -112,16 +154,12 @@ export async function exportLogsAsZip(
112
154
  const streamingLogs = logs.filter((log) => log.streaming).slice(0, MAX_STREAMING_CHUNK_EXPORTS);
113
155
  zip.file(
114
156
  "manifest.json",
157
+ JSON.stringify(buildExportManifest(logs, mode, streamingLogs), null, 2),
158
+ );
159
+ zip.file(
160
+ "logs.json",
115
161
  JSON.stringify(
116
- {
117
- exportedAt: new Date().toISOString(),
118
- mode,
119
- redacted: mode === "redacted",
120
- logCount: logs.length,
121
- logIds: logs.map((log) => log.id),
122
- streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
123
- streamingChunkLogIds: streamingLogs.map((log) => log.id),
124
- },
162
+ logs.map((log) => buildExportLogMetadata(log, mode)),
125
163
  null,
126
164
  2,
127
165
  ),
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { CapturedLogSchema } from "../contracts";
3
+
4
+ export const ImportLogsResponseSchema = z.object({
5
+ sessionId: z.string(),
6
+ imported: z.number().int().nonnegative(),
7
+ skipped: z.number().int().nonnegative(),
8
+ warnings: z.array(z.string()),
9
+ logs: z.array(CapturedLogSchema),
10
+ });
11
+
12
+ export type ImportLogsResponse = z.infer<typeof ImportLogsResponseSchema>;
@@ -0,0 +1,82 @@
1
+ import useSWR, { type SWRResponse } from "swr";
2
+ import {
3
+ AlertListResponseSchema,
4
+ AlertSummarySchema,
5
+ type AlertCategory,
6
+ type AlertListResponse,
7
+ type AlertSeverity,
8
+ type AlertSource,
9
+ type AlertSummary,
10
+ } from "./alertContract";
11
+ import { fetchJsonWithTimeout } from "./apiClient";
12
+
13
+ const ALERT_SUMMARY_FETCH_TIMEOUT_MS = 8_000;
14
+ const ALERT_LIST_FETCH_TIMEOUT_MS = 10_000;
15
+
16
+ export type AlertListFilters = {
17
+ severity?: AlertSeverity;
18
+ category?: AlertCategory;
19
+ source?: AlertSource;
20
+ limit?: number;
21
+ };
22
+
23
+ type UseAlertsResult = {
24
+ alerts: AlertListResponse["alerts"];
25
+ total: number;
26
+ summary: AlertSummary | null;
27
+ isLoading: boolean;
28
+ error: Error | undefined;
29
+ mutate: SWRResponse<AlertListResponse, Error>["mutate"];
30
+ };
31
+
32
+ type UseAlertSummaryResult = {
33
+ summary: AlertSummary | null;
34
+ isLoading: boolean;
35
+ error: Error | undefined;
36
+ mutate: SWRResponse<AlertSummary, Error>["mutate"];
37
+ };
38
+
39
+ function alertsPath(filters: AlertListFilters): string {
40
+ const params = new URLSearchParams();
41
+ if (filters.severity !== undefined) params.set("severity", filters.severity);
42
+ if (filters.category !== undefined) params.set("category", filters.category);
43
+ if (filters.source !== undefined) params.set("source", filters.source);
44
+ if (filters.limit !== undefined) params.set("limit", String(filters.limit));
45
+ const query = params.toString();
46
+ return query.length > 0 ? `/api/alerts?${query}` : "/api/alerts";
47
+ }
48
+
49
+ export function useAlerts(filters: AlertListFilters, enabled: boolean): UseAlertsResult {
50
+ const response: SWRResponse<AlertListResponse, Error> = useSWR<AlertListResponse, Error>(
51
+ enabled ? alertsPath(filters) : null,
52
+ (url: string) =>
53
+ fetchJsonWithTimeout(url, AlertListResponseSchema, ALERT_LIST_FETCH_TIMEOUT_MS),
54
+ );
55
+
56
+ return {
57
+ alerts: response.data?.alerts ?? [],
58
+ total: response.data?.total ?? 0,
59
+ summary: response.data?.summary ?? null,
60
+ isLoading: response.isLoading,
61
+ error: response.error,
62
+ mutate: response.mutate,
63
+ };
64
+ }
65
+
66
+ export function useAlertSummary(): UseAlertSummaryResult {
67
+ const response: SWRResponse<AlertSummary, Error> = useSWR<AlertSummary, Error>(
68
+ "/api/alerts/summary",
69
+ (url: string) => fetchJsonWithTimeout(url, AlertSummarySchema, ALERT_SUMMARY_FETCH_TIMEOUT_MS),
70
+ {
71
+ refreshInterval: 5_000,
72
+ revalidateOnFocus: true,
73
+ },
74
+ );
75
+
76
+ return {
77
+ summary: response.data ?? null,
78
+ isLoading: response.isLoading,
79
+ error: response.error,
80
+ mutate: response.mutate,
81
+ };
82
+ }
@@ -4,7 +4,9 @@ import {
4
4
  type GroupEvidenceReadResponse,
5
5
  type InspectorGroup,
6
6
  } from "./groupContract";
7
- import { fetchJson } from "./apiClient";
7
+ import { fetchJsonWithTimeout } from "./apiClient";
8
+
9
+ const GROUP_EVIDENCE_FETCH_TIMEOUT_MS = 12_000;
8
10
 
9
11
  type UseGroupEvidenceResult = {
10
12
  evidenceResponse: GroupEvidenceReadResponse | null;
@@ -22,7 +24,9 @@ export function useGroupEvidence(group: InspectorGroup | null): UseGroupEvidence
22
24
  const response: SWRResponse<GroupEvidenceReadResponse, Error> = useSWR<
23
25
  GroupEvidenceReadResponse,
24
26
  Error
25
- >(evidenceKey(group), (url: string) => fetchJson(url, GroupEvidenceReadResponseSchema));
27
+ >(evidenceKey(group), (url: string) =>
28
+ fetchJsonWithTimeout(url, GroupEvidenceReadResponseSchema, GROUP_EVIDENCE_FETCH_TIMEOUT_MS),
29
+ );
26
30
 
27
31
  return {
28
32
  evidenceResponse: response.data ?? null,
@@ -4,7 +4,9 @@ import {
4
4
  type InspectorGroup,
5
5
  type InspectorGroupsListResponse,
6
6
  } from "./groupContract";
7
- import { fetchJson } from "./apiClient";
7
+ import { fetchJsonWithTimeout } from "./apiClient";
8
+
9
+ const GROUPS_FETCH_TIMEOUT_MS = 8_000;
8
10
 
9
11
  type UseGroupsResult = {
10
12
  groups: InspectorGroup[];
@@ -17,7 +19,9 @@ export function useGroups(): UseGroupsResult {
17
19
  const response: SWRResponse<InspectorGroupsListResponse, Error> = useSWR<
18
20
  InspectorGroupsListResponse,
19
21
  Error
20
- >("/api/groups", (url: string) => fetchJson(url, InspectorGroupsListResponseSchema));
22
+ >("/api/groups", (url: string) =>
23
+ fetchJsonWithTimeout(url, InspectorGroupsListResponseSchema, GROUPS_FETCH_TIMEOUT_MS),
24
+ );
21
25
 
22
26
  return {
23
27
  groups: response.data?.groups ?? [],