@tonyclaw/agent-inspector 2.1.6 → 2.1.7
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-Djazxhew.js → CompareDrawer-BLS7CVvA.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-BC-z9RPh.js → ProxyViewerContainer-BFZ1WG07.js} +33 -33
- package/.output/public/assets/{ReplayDialog-BaD4R6ac.js → ReplayDialog-DIGF807X.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-w6uGdqYp.js → RequestAnatomy-C3Nlvp9P.js} +1 -1
- package/.output/public/assets/{ResponseView-VPed-Yif.js → ResponseView-BbZywqHe.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-CPB8FgHI.js → StreamingChunkSequence-Dk27PfPn.js} +1 -1
- package/.output/public/assets/_sessionId-BHpX8AVf.js +1 -0
- package/.output/public/assets/index-BXHM9OMb.js +1 -0
- package/.output/public/assets/{main-CIV3Chb5.js → main-BxHXMs6B.js} +2 -2
- package/.output/server/{_sessionId-CIXQGim9.mjs → _sessionId-D5HNwb9A.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-_uFgjkB2.mjs → CompareDrawer-QNk1twf8.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-D8YjQDNj.mjs → ProxyViewerContainer-DGhc2le_.mjs} +37 -10
- package/.output/server/_ssr/{ReplayDialog-CFwKM7jc.mjs → ReplayDialog-D8wg4VNL.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-2Vr84GxT.mjs → RequestAnatomy-BtJHKVl2.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-CSBWBHRJ.mjs → ResponseView-CndDItiU.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-Dr4JApqs.mjs → StreamingChunkSequence-D5Ywpbr7.mjs} +3 -3
- package/.output/server/_ssr/{index-WnsY1fh_.mjs → index-DtcUTHAX.mjs} +3 -3
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-sy38X5AX.mjs → router-C0apgqfC.mjs} +541 -120
- package/.output/server/{_tanstack-start-manifest_v-Dde-eACV.mjs → _tanstack-start-manifest_v-BoTPaB-V.mjs} +1 -1
- package/.output/server/index.mjs +62 -62
- package/package.json +1 -1
- package/src/components/groups/GroupsDialog.tsx +9 -4
- package/src/components/providers/SettingsDialog.tsx +20 -0
- package/src/proxy/sessionArchive.ts +410 -0
- package/src/proxy/store.ts +121 -6
- package/src/routes/api/logs.ts +2 -0
- package/src/routes/api/sessions.ts +2 -2
- package/.output/public/assets/_sessionId-mnr0O0Ri.js +0 -1
- package/.output/public/assets/index-DH1DjJOl.js +0 -1
package/src/proxy/store.ts
CHANGED
|
@@ -40,6 +40,15 @@ import type { CapturedLog } from "./schemas";
|
|
|
40
40
|
import { CapturedLogSchema } from "./schemas";
|
|
41
41
|
import { apiFormatForPath } from "./formats/registry";
|
|
42
42
|
import type { ImportedLogDraft } from "./logImporter";
|
|
43
|
+
import {
|
|
44
|
+
archiveSessionLog,
|
|
45
|
+
clearSessionArchives,
|
|
46
|
+
deleteArchivedLogsByIds,
|
|
47
|
+
findArchivedLogById,
|
|
48
|
+
getSessionArchiveRoot,
|
|
49
|
+
listArchivedSessionLogs,
|
|
50
|
+
listArchivedSessionIds,
|
|
51
|
+
} from "./sessionArchive";
|
|
43
52
|
import {
|
|
44
53
|
clearSessionRegistry,
|
|
45
54
|
getLogSessionId,
|
|
@@ -68,18 +77,23 @@ export type LogStorageStats = {
|
|
|
68
77
|
chunkDir: string;
|
|
69
78
|
chunkFileCount: number;
|
|
70
79
|
chunkBytes: number;
|
|
80
|
+
sessionArchiveDir: string;
|
|
81
|
+
sessionArchiveFileCount: number;
|
|
82
|
+
sessionArchiveBytes: number;
|
|
71
83
|
};
|
|
72
84
|
|
|
73
85
|
export type ClearPersistedLogStorageResult = {
|
|
74
86
|
cleared: number;
|
|
75
87
|
logFilesDeleted: number;
|
|
76
88
|
chunkFilesDeleted: number;
|
|
89
|
+
sessionArchiveFilesDeleted: number;
|
|
77
90
|
};
|
|
78
91
|
|
|
79
92
|
export type ClearPersistedLogsByIdsResult = {
|
|
80
93
|
cleared: number;
|
|
81
94
|
logFilesRewritten: number;
|
|
82
95
|
chunkFilesDeleted: number;
|
|
96
|
+
sessionArchiveLogsDeleted: number;
|
|
83
97
|
};
|
|
84
98
|
|
|
85
99
|
export type ListLogsPageOptions = {
|
|
@@ -138,6 +152,7 @@ type LogCursorPageIndex = {
|
|
|
138
152
|
|
|
139
153
|
const logCursorPageIndexes: Map<string, LogCursorPageIndex> = new Map();
|
|
140
154
|
let logCursorPageIndexBuildCount = 0;
|
|
155
|
+
let sessionArchiveQueue: Promise<void> = Promise.resolve();
|
|
141
156
|
|
|
142
157
|
function cursorPageIndexKey(
|
|
143
158
|
options: Pick<ListLogsCursorPageOptions, "sessionId" | "model">,
|
|
@@ -222,6 +237,22 @@ function addToCache(log: CapturedLog): void {
|
|
|
222
237
|
evictOldestIfNeeded();
|
|
223
238
|
}
|
|
224
239
|
|
|
240
|
+
function archiveLogSnapshot(log: CapturedLog): void {
|
|
241
|
+
const archivedLog = normalizeLog(log);
|
|
242
|
+
sessionArchiveQueue = sessionArchiveQueue
|
|
243
|
+
.catch(() => undefined)
|
|
244
|
+
.then(async () => {
|
|
245
|
+
await archiveSessionLog(archivedLog);
|
|
246
|
+
})
|
|
247
|
+
.catch((err: unknown) => {
|
|
248
|
+
logger.warn("[store] Failed to archive session log:", String(err));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function flushSessionArchiveWrites(): Promise<void> {
|
|
253
|
+
await sessionArchiveQueue;
|
|
254
|
+
}
|
|
255
|
+
|
|
225
256
|
function removeFromCache(id: number): void {
|
|
226
257
|
memoryCache.delete(id);
|
|
227
258
|
removeLogsFromCursorPageIndexes(new Set([id]));
|
|
@@ -325,6 +356,7 @@ export async function addTestLogEntry(entry: Omit<CapturedLog, "id">): Promise<C
|
|
|
325
356
|
});
|
|
326
357
|
|
|
327
358
|
addToCache(log);
|
|
359
|
+
archiveLogSnapshot(log);
|
|
328
360
|
observeSessionLog(log, session.source);
|
|
329
361
|
emitLogUpdate(log);
|
|
330
362
|
return log;
|
|
@@ -414,6 +446,7 @@ export async function createLog(
|
|
|
414
446
|
|
|
415
447
|
// Add to memory cache
|
|
416
448
|
addToCache(log);
|
|
449
|
+
archiveLogSnapshot(log);
|
|
417
450
|
markSessionStarted(log, session.source);
|
|
418
451
|
emitLogUpdate(log);
|
|
419
452
|
|
|
@@ -422,10 +455,21 @@ export async function createLog(
|
|
|
422
455
|
|
|
423
456
|
export function finalizeLogUpdate(log: CapturedLog): void {
|
|
424
457
|
addToCache(log);
|
|
458
|
+
archiveLogSnapshot(log);
|
|
425
459
|
markSessionFinished(log);
|
|
426
460
|
emitLogUpdate(log);
|
|
427
461
|
}
|
|
428
462
|
|
|
463
|
+
async function getArchivedLogByIdForStore(id: number): Promise<CapturedLog | null> {
|
|
464
|
+
await flushSessionArchiveWrites();
|
|
465
|
+
const archivedLog = await findArchivedLogById(id);
|
|
466
|
+
if (archivedLog === null) return null;
|
|
467
|
+
const normalized = normalizeLog(archivedLog);
|
|
468
|
+
addToCache(normalized);
|
|
469
|
+
observeSessionLog(normalized);
|
|
470
|
+
return normalized;
|
|
471
|
+
}
|
|
472
|
+
|
|
429
473
|
/**
|
|
430
474
|
* Get a log by ID, checking memory cache first then disk.
|
|
431
475
|
* Uses byte-offset index for efficient single-line reads.
|
|
@@ -440,14 +484,14 @@ export async function getLogById(id: number): Promise<CapturedLog | null> {
|
|
|
440
484
|
// Look up in index and load from disk
|
|
441
485
|
const entry = await findInIndex(id);
|
|
442
486
|
if (entry === null) {
|
|
443
|
-
return
|
|
487
|
+
return await getArchivedLogByIdForStore(id);
|
|
444
488
|
}
|
|
445
489
|
|
|
446
490
|
// Load from disk using byte-offset index
|
|
447
491
|
try {
|
|
448
492
|
const filePath = join(resolveLogDir(), entry.file);
|
|
449
493
|
if (!existsSync(filePath)) {
|
|
450
|
-
return
|
|
494
|
+
return await getArchivedLogByIdForStore(id);
|
|
451
495
|
}
|
|
452
496
|
|
|
453
497
|
// Use byte offset for direct read when available
|
|
@@ -483,7 +527,7 @@ export async function getLogById(id: number): Promise<CapturedLog | null> {
|
|
|
483
527
|
logger.error("[store] Failed to read log from disk:", String(err));
|
|
484
528
|
}
|
|
485
529
|
|
|
486
|
-
return
|
|
530
|
+
return await getArchivedLogByIdForStore(id);
|
|
487
531
|
}
|
|
488
532
|
|
|
489
533
|
async function scanLogFileForId(filePath: string, id: number): Promise<CapturedLog | null> {
|
|
@@ -578,6 +622,20 @@ async function visitPersistedLogFile(
|
|
|
578
622
|
}
|
|
579
623
|
}
|
|
580
624
|
|
|
625
|
+
async function visitArchivedSessionLogs(
|
|
626
|
+
sessionId: string | undefined,
|
|
627
|
+
visitor: PersistedLogVisitor,
|
|
628
|
+
): Promise<void> {
|
|
629
|
+
if (sessionId === undefined) return;
|
|
630
|
+
await flushSessionArchiveWrites();
|
|
631
|
+
const logs = await listArchivedSessionLogs(sessionId);
|
|
632
|
+
for (const archivedLog of logs) {
|
|
633
|
+
const log = normalizeLog(archivedLog);
|
|
634
|
+
observeSessionLog(log);
|
|
635
|
+
visitor(log);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
581
639
|
function canUseIndexedListPage(options: ListLogsPageOptions): boolean {
|
|
582
640
|
return options.sessionId === undefined && options.model === undefined;
|
|
583
641
|
}
|
|
@@ -659,6 +717,8 @@ export async function listLogsPage(options: ListLogsPageOptions): Promise<ListLo
|
|
|
659
717
|
visitLog(log);
|
|
660
718
|
});
|
|
661
719
|
|
|
720
|
+
await visitArchivedSessionLogs(options.sessionId, visitLog);
|
|
721
|
+
|
|
662
722
|
for (const log of memoryCache.values()) {
|
|
663
723
|
visitLog(log);
|
|
664
724
|
}
|
|
@@ -773,6 +833,18 @@ async function buildLogCursorPageIndexFromLogIndex(
|
|
|
773
833
|
logsById.set(log.id, log);
|
|
774
834
|
}
|
|
775
835
|
|
|
836
|
+
if (options.sessionId !== undefined) {
|
|
837
|
+
await flushSessionArchiveWrites();
|
|
838
|
+
const archivedLogs = await listArchivedSessionLogs(options.sessionId);
|
|
839
|
+
for (const archivedLog of archivedLogs) {
|
|
840
|
+
const log = normalizeLog(archivedLog);
|
|
841
|
+
if (!matchesLogFilters(log, options.sessionId, options.model)) continue;
|
|
842
|
+
observeSessionLog(log);
|
|
843
|
+
idsById.add(log.id);
|
|
844
|
+
logsById.set(log.id, log);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
776
848
|
return {
|
|
777
849
|
key: cursorPageIndexKey(options),
|
|
778
850
|
sessionId: options.sessionId,
|
|
@@ -803,6 +875,8 @@ async function buildLogCursorPageIndex(
|
|
|
803
875
|
visitLog(log);
|
|
804
876
|
});
|
|
805
877
|
|
|
878
|
+
await visitArchivedSessionLogs(options.sessionId, visitLog);
|
|
879
|
+
|
|
806
880
|
for (const log of memoryCache.values()) {
|
|
807
881
|
visitLog(log);
|
|
808
882
|
}
|
|
@@ -923,6 +997,16 @@ async function collectSessionLogSummaries(
|
|
|
923
997
|
summariesById.set(summary.id, summary);
|
|
924
998
|
}
|
|
925
999
|
|
|
1000
|
+
await flushSessionArchiveWrites();
|
|
1001
|
+
const archivedLogs = await listArchivedSessionLogs(sessionId);
|
|
1002
|
+
for (const archivedLog of archivedLogs) {
|
|
1003
|
+
const log = normalizeLog(archivedLog);
|
|
1004
|
+
observeSessionLog(log);
|
|
1005
|
+
if (!summariesById.has(log.id)) {
|
|
1006
|
+
summariesById.set(log.id, buildSessionLogSummary(log));
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
926
1010
|
if (includeHistory && summariesById.size === 0) {
|
|
927
1011
|
await visitPersistedLogs((log) => {
|
|
928
1012
|
observeSessionLog(log);
|
|
@@ -1001,6 +1085,15 @@ export function getSessions(): string[] {
|
|
|
1001
1085
|
return getSessionIds();
|
|
1002
1086
|
}
|
|
1003
1087
|
|
|
1088
|
+
export async function listSessions(): Promise<string[]> {
|
|
1089
|
+
await flushSessionArchiveWrites();
|
|
1090
|
+
const sessionIds = new Set<string>(getSessionIds());
|
|
1091
|
+
for (const sessionId of await listArchivedSessionIds()) {
|
|
1092
|
+
sessionIds.add(sessionId);
|
|
1093
|
+
}
|
|
1094
|
+
return [...sessionIds].sort();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1004
1097
|
export function getModels(): string[] {
|
|
1005
1098
|
const set = new Set<string>();
|
|
1006
1099
|
for (const l of memoryCache.values()) {
|
|
@@ -1050,8 +1143,13 @@ function collectDirectoryStats(
|
|
|
1050
1143
|
export function getLogStorageStats(): LogStorageStats {
|
|
1051
1144
|
const logDir = resolveLogDir();
|
|
1052
1145
|
const chunkDir = getChunksDir();
|
|
1146
|
+
const sessionArchiveDir = getSessionArchiveRoot();
|
|
1053
1147
|
const logStats = collectDirectoryStats(logDir, (fileName) => fileName.endsWith(".jsonl"));
|
|
1054
1148
|
const chunkStats = collectDirectoryStats(chunkDir, (fileName) => fileName.endsWith(".json"));
|
|
1149
|
+
const sessionArchiveStats = collectDirectoryStats(
|
|
1150
|
+
sessionArchiveDir,
|
|
1151
|
+
(fileName) => fileName === "session.sqlite" || fileName.startsWith("session.sqlite-"),
|
|
1152
|
+
);
|
|
1055
1153
|
return {
|
|
1056
1154
|
memoryCount: memoryCache.size,
|
|
1057
1155
|
logDir,
|
|
@@ -1060,6 +1158,9 @@ export function getLogStorageStats(): LogStorageStats {
|
|
|
1060
1158
|
chunkDir,
|
|
1061
1159
|
chunkFileCount: chunkStats.fileCount,
|
|
1062
1160
|
chunkBytes: chunkStats.bytes,
|
|
1161
|
+
sessionArchiveDir,
|
|
1162
|
+
sessionArchiveFileCount: sessionArchiveStats.fileCount,
|
|
1163
|
+
sessionArchiveBytes: sessionArchiveStats.bytes,
|
|
1063
1164
|
};
|
|
1064
1165
|
}
|
|
1065
1166
|
|
|
@@ -1151,9 +1252,10 @@ async function deleteMatchingFiles(
|
|
|
1151
1252
|
export async function clearPersistedLogStorage(): Promise<ClearPersistedLogStorageResult> {
|
|
1152
1253
|
return await runWithFlushedLogWriteLock(async () => {
|
|
1153
1254
|
await flushIndex();
|
|
1255
|
+
await flushSessionArchiveWrites();
|
|
1154
1256
|
|
|
1155
1257
|
const result = clearAllLogs();
|
|
1156
|
-
const [logFilesDeleted, chunkFilesDeleted] = await Promise.all([
|
|
1258
|
+
const [logFilesDeleted, chunkFilesDeleted, sessionArchiveFilesDeleted] = await Promise.all([
|
|
1157
1259
|
deleteMatchingFiles(
|
|
1158
1260
|
resolveLogDir(),
|
|
1159
1261
|
(fileName) =>
|
|
@@ -1167,6 +1269,7 @@ export async function clearPersistedLogStorage(): Promise<ClearPersistedLogStora
|
|
|
1167
1269
|
(fileName) =>
|
|
1168
1270
|
fileName.endsWith(".json") || (fileName.startsWith(".") && fileName.endsWith(".tmp")),
|
|
1169
1271
|
),
|
|
1272
|
+
clearSessionArchives(),
|
|
1170
1273
|
]);
|
|
1171
1274
|
|
|
1172
1275
|
await rebuildIndex();
|
|
@@ -1175,6 +1278,7 @@ export async function clearPersistedLogStorage(): Promise<ClearPersistedLogStora
|
|
|
1175
1278
|
cleared: result.cleared,
|
|
1176
1279
|
logFilesDeleted,
|
|
1177
1280
|
chunkFilesDeleted,
|
|
1281
|
+
sessionArchiveFilesDeleted,
|
|
1178
1282
|
};
|
|
1179
1283
|
});
|
|
1180
1284
|
}
|
|
@@ -1365,20 +1469,27 @@ export async function clearPersistedLogsByIds(
|
|
|
1365
1469
|
): Promise<ClearPersistedLogsByIdsResult> {
|
|
1366
1470
|
const uniqueIds = new Set(ids);
|
|
1367
1471
|
if (uniqueIds.size === 0) {
|
|
1368
|
-
return {
|
|
1472
|
+
return {
|
|
1473
|
+
cleared: 0,
|
|
1474
|
+
logFilesRewritten: 0,
|
|
1475
|
+
chunkFilesDeleted: 0,
|
|
1476
|
+
sessionArchiveLogsDeleted: 0,
|
|
1477
|
+
};
|
|
1369
1478
|
}
|
|
1370
1479
|
|
|
1371
1480
|
return await runWithFlushedLogWriteLock(async () => {
|
|
1372
1481
|
await flushIndex();
|
|
1482
|
+
await flushSessionArchiveWrites();
|
|
1373
1483
|
|
|
1374
1484
|
const memoryRemovedIds = new Set<number>();
|
|
1375
1485
|
for (const id of uniqueIds) {
|
|
1376
1486
|
if (memoryCache.has(id)) memoryRemovedIds.add(id);
|
|
1377
1487
|
}
|
|
1378
1488
|
const result = clearLogsByIds([...uniqueIds]);
|
|
1379
|
-
const [rewriteResult, chunkFilesDeleted] = await Promise.all([
|
|
1489
|
+
const [rewriteResult, chunkFilesDeleted, archivedRemovedIds] = await Promise.all([
|
|
1380
1490
|
rewriteLogFilesWithoutIds(uniqueIds),
|
|
1381
1491
|
deleteChunkFilesByIds(uniqueIds),
|
|
1492
|
+
deleteArchivedLogsByIds(uniqueIds),
|
|
1382
1493
|
]);
|
|
1383
1494
|
|
|
1384
1495
|
await rebuildIndex();
|
|
@@ -1390,11 +1501,15 @@ export async function clearPersistedLogsByIds(
|
|
|
1390
1501
|
clearedIds.add(id);
|
|
1391
1502
|
}
|
|
1392
1503
|
}
|
|
1504
|
+
for (const id of archivedRemovedIds) {
|
|
1505
|
+
clearedIds.add(id);
|
|
1506
|
+
}
|
|
1393
1507
|
|
|
1394
1508
|
return {
|
|
1395
1509
|
cleared: clearedIds.size,
|
|
1396
1510
|
logFilesRewritten: rewriteResult.logFilesRewritten,
|
|
1397
1511
|
chunkFilesDeleted,
|
|
1512
|
+
sessionArchiveLogsDeleted: archivedRemovedIds.size,
|
|
1398
1513
|
};
|
|
1399
1514
|
});
|
|
1400
1515
|
}
|
package/src/routes/api/logs.ts
CHANGED
|
@@ -133,6 +133,7 @@ export const Route = createFileRoute("/api/logs")({
|
|
|
133
133
|
cleared: result.cleared,
|
|
134
134
|
logFilesRewritten: result.logFilesRewritten,
|
|
135
135
|
chunkFilesDeleted: result.chunkFilesDeleted,
|
|
136
|
+
sessionArchiveLogsDeleted: result.sessionArchiveLogsDeleted,
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
const result = await clearPersistedLogStorage();
|
|
@@ -141,6 +142,7 @@ export const Route = createFileRoute("/api/logs")({
|
|
|
141
142
|
cleared: result.cleared,
|
|
142
143
|
logFilesDeleted: result.logFilesDeleted,
|
|
143
144
|
chunkFilesDeleted: result.chunkFilesDeleted,
|
|
145
|
+
sessionArchiveFilesDeleted: result.sessionArchiveFilesDeleted,
|
|
144
146
|
});
|
|
145
147
|
},
|
|
146
148
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import { getSessionInfo, getSessionSnapshots,
|
|
2
|
+
import { getSessionInfo, getSessionSnapshots, listSessions } from "../../proxy/store";
|
|
3
3
|
|
|
4
4
|
function parsePositiveInt(value: string | null): number | undefined {
|
|
5
5
|
if (value === null) return undefined;
|
|
@@ -37,7 +37,7 @@ export const Route = createFileRoute("/api/sessions")({
|
|
|
37
37
|
if (details === "true" || details === "1") {
|
|
38
38
|
return Response.json(getSessionSnapshots());
|
|
39
39
|
}
|
|
40
|
-
return Response.json(
|
|
40
|
+
return Response.json(await listSessions());
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as s,j as e}from"./main-CIV3Chb5.js";import{P as i}from"./ProxyViewerContainer-BC-z9RPh.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{P as o}from"./ProxyViewerContainer-BC-z9RPh.js";import"./main-CIV3Chb5.js";const r=o;export{r as component};
|