@tonyclaw/agent-inspector 2.1.3 → 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 (56) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BLYcm6Dg.js → CompareDrawer-BihEPd3v.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DYqXb6WW.js +117 -0
  4. package/.output/public/assets/{ReplayDialog-_kCy9L9E.js → ReplayDialog-DnX-6kY8.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-DWp5pWsv.js → RequestAnatomy-CIkHAxZO.js} +1 -1
  6. package/.output/public/assets/{ResponseView-Ci92o6mt.js → ResponseView-BFc8jjZM.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-C-1ZQl_b.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-RgLvmIQk.js → main-xlSdu7_I.js} +2 -2
  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-DnOYbKO7.mjs → _sessionId-CwVQgxoK.mjs} +3 -3
  15. package/.output/server/_ssr/{CompareDrawer-D44_F13_.mjs → CompareDrawer-Br9Ec0ah.mjs} +3 -3
  16. package/.output/server/_ssr/{ProxyViewerContainer-Cx4p_awW.mjs → ProxyViewerContainer-Oe5Tr3C2.mjs} +982 -70
  17. package/.output/server/_ssr/{ReplayDialog-wScCF3Oc.mjs → ReplayDialog-C1_QsoA-.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-Cf7R7PXS.mjs → RequestAnatomy-cn08QSoz.mjs} +2 -2
  19. package/.output/server/_ssr/{ResponseView-7pVIZLL-.mjs → ResponseView-CIqUCCTG.mjs} +3 -3
  20. package/.output/server/_ssr/{StreamingChunkSequence-Bs57_GbC.mjs → StreamingChunkSequence-0MhVWD2F.mjs} +3 -3
  21. package/.output/server/_ssr/{index-6eY__la9.mjs → index-jpnI5ghp.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-DRZPhZB6.mjs → router-SSOn7c09.mjs} +1970 -221
  24. package/.output/server/_tanstack-start-manifest_v-BCgy_9er.mjs +4 -0
  25. package/.output/server/index.mjs +64 -64
  26. package/package.json +2 -1
  27. package/src/components/ProxyViewer.tsx +87 -1
  28. package/src/components/ProxyViewerContainer.tsx +26 -0
  29. package/src/components/alerts/AlertsDialog.tsx +610 -0
  30. package/src/components/proxy-viewer/LogEntry.tsx +324 -51
  31. package/src/components/ui/dialog.tsx +1 -0
  32. package/src/contracts/index.ts +15 -2
  33. package/src/contracts/log.ts +18 -0
  34. package/src/lib/alertContract.ts +79 -0
  35. package/src/lib/apiClient.ts +39 -0
  36. package/src/lib/export-logs.ts +47 -9
  37. package/src/lib/logImportContract.ts +12 -0
  38. package/src/lib/useAlerts.ts +82 -0
  39. package/src/lib/useGroupEvidence.ts +6 -2
  40. package/src/lib/useGroups.ts +6 -2
  41. package/src/proxy/alerts.ts +664 -0
  42. package/src/proxy/logBodyChunks.ts +91 -0
  43. package/src/proxy/logImporter.ts +491 -0
  44. package/src/proxy/logIndex.ts +63 -2
  45. package/src/proxy/sqliteLogIndex.ts +612 -0
  46. package/src/proxy/store.ts +32 -7
  47. package/src/routes/api/alerts.summary.ts +24 -0
  48. package/src/routes/api/alerts.ts +52 -0
  49. package/src/routes/api/logs.$id.body.ts +44 -0
  50. package/src/routes/api/logs.import.ts +50 -0
  51. package/src/services/alerts.ts +12 -0
  52. package/.output/public/assets/ProxyViewerContainer-Ba5wFn00.js +0 -117
  53. package/.output/public/assets/_sessionId-DKzAmSNP.js +0 -1
  54. package/.output/public/assets/index-B-MZ9lQM.css +0 -1
  55. package/.output/public/assets/index-ZZHwgkWJ.js +0 -1
  56. package/.output/server/_tanstack-start-manifest_v-DfAVFkZ5.mjs +0 -4
@@ -26,17 +26,20 @@ import {
26
26
  flushIndex,
27
27
  getNextLogId,
28
28
  getCurrentLogFile,
29
+ listFilteredIndexEntries,
29
30
  listIndexEntries,
30
31
  rebuildIndex,
31
32
  type LogIndexEntry,
32
33
  type LogIndexSummary,
33
34
  } from "./logIndex";
34
35
  import { writeChunks } from "./chunkStorage";
36
+ import { closeSqliteLogIndex } from "./sqliteLogIndex";
35
37
  import type { SessionInfo, SessionLogSummary } from "../lib/sessionInfoContract";
36
38
  import { buildSessionInfo, buildSessionLogSummary } from "./sessionInfo";
37
39
  import type { CapturedLog } from "./schemas";
38
40
  import { CapturedLogSchema } from "./schemas";
39
41
  import { apiFormatForPath } from "./formats/registry";
42
+ import type { ImportedLogDraft } from "./logImporter";
40
43
  import {
41
44
  clearSessionRegistry,
42
45
  getLogSessionId,
@@ -237,8 +240,8 @@ export function compactLogForList(log: CapturedLog): CapturedLog {
237
240
  rawHeaders: undefined,
238
241
  headers: undefined,
239
242
  streamingChunks: undefined,
240
- rawRequestBodyBytes: textByteLength(log.rawRequestBody),
241
- responseTextBytes: textByteLength(log.responseText),
243
+ rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
244
+ responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
242
245
  bodyContentMode: "compact",
243
246
  };
244
247
  }
@@ -327,6 +330,23 @@ export async function addTestLogEntry(entry: Omit<CapturedLog, "id">): Promise<C
327
330
  return log;
328
331
  }
329
332
 
333
+ export async function importCapturedLogs(
334
+ entries: readonly ImportedLogDraft[],
335
+ sessionId: string,
336
+ ): Promise<CapturedLog[]> {
337
+ const imported: CapturedLog[] = [];
338
+ for (const entry of entries) {
339
+ const log = await addTestLogEntry({
340
+ ...entry,
341
+ sessionId,
342
+ isTest: false,
343
+ replayOfLogId: null,
344
+ });
345
+ imported.push(log);
346
+ }
347
+ return imported;
348
+ }
349
+
330
350
  type ClientInfo = SessionClientInfo;
331
351
 
332
352
  export async function createLog(
@@ -711,10 +731,10 @@ async function buildLogCursorPageIndexFromLogIndex(
711
731
  options: Pick<ListLogsCursorPageOptions, "sessionId" | "model">,
712
732
  allowRebuild: boolean,
713
733
  ): Promise<LogCursorPageIndex | null> {
714
- let entries = await listIndexEntries();
734
+ let entries = await listFilteredIndexEntries(options);
715
735
  if (entries.length === 0 && allowRebuild) {
716
736
  await rebuildIndex();
717
- entries = await listIndexEntries();
737
+ entries = await listFilteredIndexEntries(options);
718
738
  }
719
739
 
720
740
  if (entries.some((entry) => !hasLogIndexFilterMetadata(entry))) {
@@ -918,7 +938,7 @@ async function collectSessionLogSummaries(
918
938
  }
919
939
 
920
940
  async function collectIndexedSessionLogSummaries(sessionId: string): Promise<SessionLogSummary[]> {
921
- let entries = await listIndexEntries();
941
+ let entries = await listFilteredIndexEntries({ sessionId });
922
942
  if (entries.length === 0) return [];
923
943
 
924
944
  const needsMetadataRebuild = entries.some((entry) => !hasLogIndexFilterMetadata(entry));
@@ -929,7 +949,7 @@ async function collectIndexedSessionLogSummaries(sessionId: string): Promise<Ses
929
949
 
930
950
  if (needsMetadataRebuild || needsSummaryRebuild) {
931
951
  await rebuildIndex();
932
- entries = await listIndexEntries();
952
+ entries = await listFilteredIndexEntries({ sessionId });
933
953
  }
934
954
 
935
955
  const summaries: SessionLogSummary[] = [];
@@ -1097,6 +1117,7 @@ export function clearAllLogs(): { cleared: number } {
1097
1117
  memoryCache.clear();
1098
1118
  clearLogCursorPageIndexes();
1099
1119
  clearSessionRegistry();
1120
+ closeSqliteLogIndex();
1100
1121
  return { cleared: count };
1101
1122
  }
1102
1123
 
@@ -1135,7 +1156,11 @@ export async function clearPersistedLogStorage(): Promise<ClearPersistedLogStora
1135
1156
  const [logFilesDeleted, chunkFilesDeleted] = await Promise.all([
1136
1157
  deleteMatchingFiles(
1137
1158
  resolveLogDir(),
1138
- (fileName) => fileName.endsWith(".jsonl") || fileName === "logs.idx",
1159
+ (fileName) =>
1160
+ fileName.endsWith(".jsonl") ||
1161
+ fileName === "logs.idx" ||
1162
+ fileName === "inspector.sqlite" ||
1163
+ fileName.startsWith("inspector.sqlite-"),
1139
1164
  ),
1140
1165
  deleteMatchingFiles(
1141
1166
  getChunksDir(),
@@ -0,0 +1,24 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { getInspectorAlertSummary } from "../../services/alerts";
3
+
4
+ function parsePositiveInt(value: string | null): number | undefined {
5
+ if (value === null) return undefined;
6
+ const parsed = Number(value);
7
+ if (!Number.isInteger(parsed) || parsed < 1) return undefined;
8
+ return parsed;
9
+ }
10
+
11
+ export const Route = createFileRoute("/api/alerts/summary")({
12
+ server: {
13
+ handlers: {
14
+ GET: async ({ request }: { request: Request }) => {
15
+ const url = new URL(request.url);
16
+ return Response.json(
17
+ await getInspectorAlertSummary({
18
+ scanLimit: parsePositiveInt(url.searchParams.get("scanLimit")),
19
+ }),
20
+ );
21
+ },
22
+ },
23
+ },
24
+ });
@@ -0,0 +1,52 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import {
3
+ AlertCategorySchema,
4
+ AlertSeveritySchema,
5
+ AlertSourceSchema,
6
+ type AlertCategory,
7
+ type AlertSeverity,
8
+ type AlertSource,
9
+ } from "../../lib/alertContract";
10
+ import { listInspectorAlerts } from "../../services/alerts";
11
+
12
+ type ParsedFilters = {
13
+ severity?: AlertSeverity;
14
+ category?: AlertCategory;
15
+ source?: AlertSource;
16
+ };
17
+
18
+ function parsePositiveInt(value: string | null): number | undefined {
19
+ if (value === null) return undefined;
20
+ const parsed = Number(value);
21
+ if (!Number.isInteger(parsed) || parsed < 1) return undefined;
22
+ return parsed;
23
+ }
24
+
25
+ function parseFilters(url: URL): ParsedFilters {
26
+ const severityResult = AlertSeveritySchema.safeParse(url.searchParams.get("severity"));
27
+ const categoryResult = AlertCategorySchema.safeParse(url.searchParams.get("category"));
28
+ const sourceResult = AlertSourceSchema.safeParse(url.searchParams.get("source"));
29
+
30
+ return {
31
+ severity: severityResult.success ? severityResult.data : undefined,
32
+ category: categoryResult.success ? categoryResult.data : undefined,
33
+ source: sourceResult.success ? sourceResult.data : undefined,
34
+ };
35
+ }
36
+
37
+ export const Route = createFileRoute("/api/alerts")({
38
+ server: {
39
+ handlers: {
40
+ GET: async ({ request }: { request: Request }) => {
41
+ const url = new URL(request.url);
42
+ return Response.json(
43
+ await listInspectorAlerts({
44
+ ...parseFilters(url),
45
+ limit: parsePositiveInt(url.searchParams.get("limit")),
46
+ scanLimit: parsePositiveInt(url.searchParams.get("scanLimit")),
47
+ }),
48
+ );
49
+ },
50
+ },
51
+ },
52
+ });
@@ -0,0 +1,44 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { LogBodyPartSchema } from "../../contracts";
3
+ import { createLogBodyChunk } from "../../proxy/logBodyChunks";
4
+ import { getLogById } from "../../proxy/store";
5
+
6
+ function parseIntegerParam(value: string | null, fallback: number): number {
7
+ if (value === null) return fallback;
8
+ const parsed = Number(value);
9
+ if (!Number.isInteger(parsed)) return fallback;
10
+ return parsed;
11
+ }
12
+
13
+ export const Route = createFileRoute("/api/logs/$id/body")({
14
+ server: {
15
+ handlers: {
16
+ GET: async ({ params, request }: { params: { id: string }; request: Request }) => {
17
+ const id = Number(params.id);
18
+ if (!Number.isInteger(id) || id <= 0) {
19
+ return Response.json({ error: "Invalid log ID" }, { status: 400 });
20
+ }
21
+
22
+ const url = new URL(request.url);
23
+ const partResult = LogBodyPartSchema.safeParse(url.searchParams.get("part"));
24
+ if (!partResult.success) {
25
+ return Response.json({ error: "Invalid body part" }, { status: 400 });
26
+ }
27
+
28
+ const log = await getLogById(id);
29
+ if (log === null) {
30
+ return Response.json({ error: "Log not found" }, { status: 404 });
31
+ }
32
+
33
+ return Response.json(
34
+ createLogBodyChunk({
35
+ log,
36
+ part: partResult.data,
37
+ offset: parseIntegerParam(url.searchParams.get("offset"), 0),
38
+ limit: parseIntegerParam(url.searchParams.get("limit"), 0),
39
+ }),
40
+ );
41
+ },
42
+ },
43
+ },
44
+ });
@@ -0,0 +1,50 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createFileRoute } from "@tanstack/react-router";
3
+ import { parseImportedLogFile, sessionNameFromImportFileName } from "../../proxy/logImporter";
4
+ import { compactLogForList, importCapturedLogs } from "../../proxy/store";
5
+
6
+ function isUploadedFile(value: FormDataEntryValue | null): value is File {
7
+ return value instanceof File;
8
+ }
9
+
10
+ export const Route = createFileRoute("/api/logs/import")({
11
+ server: {
12
+ handlers: {
13
+ POST: async ({ request }: { request: Request }) => {
14
+ let formData: FormData;
15
+ try {
16
+ formData = await request.formData();
17
+ } catch {
18
+ return Response.json({ error: "Expected multipart form data" }, { status: 400 });
19
+ }
20
+
21
+ const upload = formData.get("file");
22
+ if (!isUploadedFile(upload)) {
23
+ return Response.json({ error: "Missing import file" }, { status: 400 });
24
+ }
25
+
26
+ const sessionId = sessionNameFromImportFileName(upload.name);
27
+ const parsed = await parseImportedLogFile({
28
+ fileName: upload.name,
29
+ data: Buffer.from(await upload.arrayBuffer()),
30
+ });
31
+
32
+ if (!parsed.ok) {
33
+ return Response.json(
34
+ { error: parsed.message, warnings: parsed.warnings },
35
+ { status: 400 },
36
+ );
37
+ }
38
+
39
+ const imported = await importCapturedLogs(parsed.logs, sessionId);
40
+ return Response.json({
41
+ sessionId,
42
+ imported: imported.length,
43
+ skipped: parsed.skipped,
44
+ warnings: parsed.warnings,
45
+ logs: imported.map((log) => compactLogForList(log)),
46
+ });
47
+ },
48
+ },
49
+ },
50
+ });
@@ -0,0 +1,12 @@
1
+ import type { AlertListResponse, AlertSummary } from "../lib/alertContract";
2
+ import { getAlertSummary, listAlerts, type ListAlertsOptions } from "../proxy/alerts";
3
+
4
+ export function listInspectorAlerts(options: ListAlertsOptions): Promise<AlertListResponse> {
5
+ return listAlerts(options);
6
+ }
7
+
8
+ export function getInspectorAlertSummary(
9
+ options: Pick<ListAlertsOptions, "scanLimit">,
10
+ ): Promise<AlertSummary> {
11
+ return getAlertSummary(options);
12
+ }