@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BLYcm6Dg.js → CompareDrawer-BihEPd3v.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DYqXb6WW.js +117 -0
- package/.output/public/assets/{ReplayDialog-_kCy9L9E.js → ReplayDialog-DnX-6kY8.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DWp5pWsv.js → RequestAnatomy-CIkHAxZO.js} +1 -1
- package/.output/public/assets/{ResponseView-Ci92o6mt.js → ResponseView-BFc8jjZM.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-C-1ZQl_b.js → StreamingChunkSequence-p8qkpF6K.js} +1 -1
- package/.output/public/assets/_sessionId-Csx4T_y6.js +1 -0
- package/.output/public/assets/index-Bcq8bZoK.css +1 -0
- package/.output/public/assets/index-KMuh-31x.js +1 -0
- package/.output/public/assets/{main-RgLvmIQk.js → main-xlSdu7_I.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +204 -173
- package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -0
- package/.output/server/{_sessionId-DnOYbKO7.mjs → _sessionId-CwVQgxoK.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-D44_F13_.mjs → CompareDrawer-Br9Ec0ah.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-Cx4p_awW.mjs → ProxyViewerContainer-Oe5Tr3C2.mjs} +982 -70
- package/.output/server/_ssr/{ReplayDialog-wScCF3Oc.mjs → ReplayDialog-C1_QsoA-.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-Cf7R7PXS.mjs → RequestAnatomy-cn08QSoz.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-7pVIZLL-.mjs → ResponseView-CIqUCCTG.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-Bs57_GbC.mjs → StreamingChunkSequence-0MhVWD2F.mjs} +3 -3
- package/.output/server/_ssr/{index-6eY__la9.mjs → index-jpnI5ghp.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DRZPhZB6.mjs → router-SSOn7c09.mjs} +1970 -221
- package/.output/server/_tanstack-start-manifest_v-BCgy_9er.mjs +4 -0
- package/.output/server/index.mjs +64 -64
- package/package.json +2 -1
- package/src/components/ProxyViewer.tsx +87 -1
- package/src/components/ProxyViewerContainer.tsx +26 -0
- package/src/components/alerts/AlertsDialog.tsx +610 -0
- package/src/components/proxy-viewer/LogEntry.tsx +324 -51
- package/src/components/ui/dialog.tsx +1 -0
- package/src/contracts/index.ts +15 -2
- package/src/contracts/log.ts +18 -0
- package/src/lib/alertContract.ts +79 -0
- package/src/lib/apiClient.ts +39 -0
- package/src/lib/export-logs.ts +47 -9
- package/src/lib/logImportContract.ts +12 -0
- package/src/lib/useAlerts.ts +82 -0
- package/src/lib/useGroupEvidence.ts +6 -2
- package/src/lib/useGroups.ts +6 -2
- package/src/proxy/alerts.ts +664 -0
- package/src/proxy/logBodyChunks.ts +91 -0
- package/src/proxy/logImporter.ts +491 -0
- package/src/proxy/logIndex.ts +63 -2
- package/src/proxy/sqliteLogIndex.ts +612 -0
- package/src/proxy/store.ts +32 -7
- package/src/routes/api/alerts.summary.ts +24 -0
- package/src/routes/api/alerts.ts +52 -0
- package/src/routes/api/logs.$id.body.ts +44 -0
- package/src/routes/api/logs.import.ts +50 -0
- package/src/services/alerts.ts +12 -0
- package/.output/public/assets/ProxyViewerContainer-Ba5wFn00.js +0 -117
- package/.output/public/assets/_sessionId-DKzAmSNP.js +0 -1
- package/.output/public/assets/index-B-MZ9lQM.css +0 -1
- package/.output/public/assets/index-ZZHwgkWJ.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-DfAVFkZ5.mjs +0 -4
package/src/proxy/store.ts
CHANGED
|
@@ -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
|
|
734
|
+
let entries = await listFilteredIndexEntries(options);
|
|
715
735
|
if (entries.length === 0 && allowRebuild) {
|
|
716
736
|
await rebuildIndex();
|
|
717
|
-
entries = await
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
+
}
|