session-steward 0.6.0 → 0.7.0
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/CHANGELOG.md +12 -0
- package/dist/assets/index-BOACkzUI.js +9 -0
- package/dist/assets/{index-Cn_JrqDr.css → index-DFAWGcgb.css} +1 -1
- package/dist/index.html +2 -2
- package/lib/cli.mjs +17 -0
- package/lib/providers/claude-code/events.mjs +49 -0
- package/lib/providers/claude-code/store.mjs +2 -2
- package/lib/providers/codex/database-families.mjs +18 -2
- package/lib/providers/codex/events.mjs +37 -0
- package/lib/providers/codex/store.mjs +101 -1
- package/lib/session-events.mjs +72 -0
- package/lib/storage/jsonl.mjs +10 -1
- package/package.json +1 -1
- package/dist/assets/index-6OPqaZRp.js +0 -9
|
@@ -440,11 +440,13 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
|
|
|
440
440
|
const archivedSessionsDirectory = path.join(codexHome, "archived_sessions");
|
|
441
441
|
const sessionsDirectory = path.join(codexHome, "sessions");
|
|
442
442
|
const resolution = resolveCodexDatabases(codexHome, { refresh });
|
|
443
|
-
const { goals, logs, memories, state } = resolution.families;
|
|
443
|
+
const { goals, logs, memories, queue, state, threadHistory } = resolution.families;
|
|
444
444
|
const stateDatabasePath = state.primary?.path ?? path.join(codexHome, "state_5.sqlite");
|
|
445
445
|
const logsDatabasePath = logs.primary?.path ?? path.join(codexHome, "logs_2.sqlite");
|
|
446
446
|
const memoryDatabasePath = memories.primary?.path ?? path.join(codexHome, "memories_1.sqlite");
|
|
447
447
|
const goalsDatabasePath = goals.primary?.path ?? path.join(codexHome, "goals_1.sqlite");
|
|
448
|
+
const queueDatabasePath = queue.primary?.path ?? path.join(codexHome, "queue_1.sqlite");
|
|
449
|
+
const threadHistoryDatabasePath = threadHistory.primary?.path ?? path.join(codexHome, "thread_history_1.sqlite");
|
|
448
450
|
|
|
449
451
|
return {
|
|
450
452
|
archivedSessionsDirectory,
|
|
@@ -460,6 +462,8 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
|
|
|
460
462
|
logsDatabasePaths: allValidDatabases(logs).map((database) => database.path),
|
|
461
463
|
memoryDatabasePath,
|
|
462
464
|
memoryDatabasePaths: allValidDatabases(memories).map((database) => database.path),
|
|
465
|
+
queueDatabasePath,
|
|
466
|
+
queueDatabasePaths: allValidDatabases(queue).map((database) => database.path),
|
|
463
467
|
resolvedDatabases: databaseFamilySummary(resolution),
|
|
464
468
|
sessionIndexPath: path.join(codexHome, "session_index.jsonl"),
|
|
465
469
|
sessionsDirectory,
|
|
@@ -467,6 +471,8 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
|
|
|
467
471
|
stateDatabases: allValidDatabases(state),
|
|
468
472
|
stateDatabasePaths: allValidDatabases(state).map((database) => database.path),
|
|
469
473
|
threadWriterLocksDirectory: path.join(codexHome, "thread-writer-locks"),
|
|
474
|
+
threadHistoryDatabasePath,
|
|
475
|
+
threadHistoryDatabasePaths: allValidDatabases(threadHistory).map((database) => database.path),
|
|
470
476
|
transcriptDirectories: [sessionsDirectory, archivedSessionsDirectory],
|
|
471
477
|
};
|
|
472
478
|
}
|
|
@@ -569,12 +575,16 @@ export async function loadSessionStore({ codexHome }) {
|
|
|
569
575
|
hasGoalsDatabase,
|
|
570
576
|
hasLogsDatabase,
|
|
571
577
|
hasMemoryDatabase,
|
|
578
|
+
hasQueueDatabase,
|
|
579
|
+
hasThreadHistoryDatabase,
|
|
572
580
|
] = await Promise.all([
|
|
573
581
|
pathExists(paths.desktopStatePath),
|
|
574
582
|
pathExists(paths.desktopStateBackupPath),
|
|
575
583
|
pathExists(paths.goalsDatabasePath),
|
|
576
584
|
pathExists(paths.logsDatabasePath),
|
|
577
585
|
pathExists(paths.memoryDatabasePath),
|
|
586
|
+
pathExists(paths.queueDatabasePath),
|
|
587
|
+
pathExists(paths.threadHistoryDatabasePath),
|
|
578
588
|
]);
|
|
579
589
|
const threadRowsById = new Map();
|
|
580
590
|
const spawnEdgesByKey = new Map();
|
|
@@ -768,9 +778,12 @@ export async function loadSessionStore({ codexHome }) {
|
|
|
768
778
|
hasGoalsDatabase,
|
|
769
779
|
hasLogsDatabase,
|
|
770
780
|
hasMemoryDatabase,
|
|
781
|
+
hasQueueDatabase,
|
|
782
|
+
hasThreadHistoryDatabase,
|
|
771
783
|
goalsDatabasePath: paths.goalsDatabasePath,
|
|
772
784
|
logsDatabasePath: paths.logsDatabasePath,
|
|
773
785
|
memoryDatabasePath: paths.memoryDatabasePath,
|
|
786
|
+
queueDatabasePath: paths.queueDatabasePath,
|
|
774
787
|
records,
|
|
775
788
|
recordsById: new Map(records.map((record) => [record.id, record])),
|
|
776
789
|
sessionIndexPath: paths.sessionIndexPath,
|
|
@@ -782,6 +795,9 @@ export async function loadSessionStore({ codexHome }) {
|
|
|
782
795
|
resolvedDatabases: paths.resolvedDatabases,
|
|
783
796
|
logsDatabasePaths: paths.logsDatabasePaths,
|
|
784
797
|
memoryDatabasePaths: paths.memoryDatabasePaths,
|
|
798
|
+
queueDatabasePaths: paths.queueDatabasePaths,
|
|
799
|
+
threadHistoryDatabasePath: paths.threadHistoryDatabasePath,
|
|
800
|
+
threadHistoryDatabasePaths: paths.threadHistoryDatabasePaths,
|
|
785
801
|
goalsDatabasePaths: paths.goalsDatabasePaths,
|
|
786
802
|
transcriptHeaders,
|
|
787
803
|
historyPath: paths.historyPath,
|
|
@@ -1217,12 +1233,16 @@ async function getStoreAvailability(paths) {
|
|
|
1217
1233
|
hasGoalsDatabase,
|
|
1218
1234
|
hasLogsDatabase,
|
|
1219
1235
|
hasMemoryDatabase,
|
|
1236
|
+
hasQueueDatabase,
|
|
1237
|
+
hasThreadHistoryDatabase,
|
|
1220
1238
|
] = await Promise.all([
|
|
1221
1239
|
pathExists(paths.desktopStatePath),
|
|
1222
1240
|
pathExists(paths.desktopStateBackupPath),
|
|
1223
1241
|
Promise.resolve(paths.goalsDatabasePaths.length > 0),
|
|
1224
1242
|
Promise.resolve(paths.logsDatabasePaths.length > 0),
|
|
1225
1243
|
Promise.resolve(paths.memoryDatabasePaths.length > 0),
|
|
1244
|
+
Promise.resolve(paths.queueDatabasePaths.length > 0),
|
|
1245
|
+
Promise.resolve(paths.threadHistoryDatabasePaths.length > 0),
|
|
1226
1246
|
]);
|
|
1227
1247
|
|
|
1228
1248
|
return {
|
|
@@ -1231,6 +1251,8 @@ async function getStoreAvailability(paths) {
|
|
|
1231
1251
|
hasGoalsDatabase,
|
|
1232
1252
|
hasLogsDatabase,
|
|
1233
1253
|
hasMemoryDatabase,
|
|
1254
|
+
hasQueueDatabase,
|
|
1255
|
+
hasThreadHistoryDatabase,
|
|
1234
1256
|
};
|
|
1235
1257
|
}
|
|
1236
1258
|
|
|
@@ -1485,6 +1507,7 @@ export async function loadDeletionStore({ codexHome, recordIds }) {
|
|
|
1485
1507
|
historyPath: paths.historyPath,
|
|
1486
1508
|
logsDatabasePath: paths.logsDatabasePath,
|
|
1487
1509
|
memoryDatabasePath: paths.memoryDatabasePath,
|
|
1510
|
+
queueDatabasePath: paths.queueDatabasePath,
|
|
1488
1511
|
records,
|
|
1489
1512
|
recordsById: new Map(records.map((record) => [record.id, record])),
|
|
1490
1513
|
sessionIndexPath: paths.sessionIndexPath,
|
|
@@ -1495,6 +1518,9 @@ export async function loadDeletionStore({ codexHome, recordIds }) {
|
|
|
1495
1518
|
resolvedDatabases: paths.resolvedDatabases,
|
|
1496
1519
|
logsDatabasePaths: paths.logsDatabasePaths,
|
|
1497
1520
|
memoryDatabasePaths: paths.memoryDatabasePaths,
|
|
1521
|
+
queueDatabasePaths: paths.queueDatabasePaths,
|
|
1522
|
+
threadHistoryDatabasePath: paths.threadHistoryDatabasePath,
|
|
1523
|
+
threadHistoryDatabasePaths: paths.threadHistoryDatabasePaths,
|
|
1498
1524
|
goalsDatabasePaths: paths.goalsDatabasePaths,
|
|
1499
1525
|
transcriptHeaders: new Map(
|
|
1500
1526
|
[...transcriptHeaders].filter(([id]) => relevantIds.has(id)),
|
|
@@ -1954,6 +1980,19 @@ export async function planSessionDeletion({ recordIds, store }) {
|
|
|
1954
1980
|
.reduce((total, databasePath) => total + countRowsForIds(databasePath, "stage1_outputs", "thread_id", deletionIds), 0);
|
|
1955
1981
|
const goalRowCount = (store.goalsDatabasePaths ?? [store.goalsDatabasePath].filter(Boolean))
|
|
1956
1982
|
.reduce((total, databasePath) => total + countRowsForIds(databasePath, "thread_goals", "thread_id", deletionIds), 0);
|
|
1983
|
+
const queueRowCount = (store.queueDatabasePaths ?? [store.queueDatabasePath].filter(Boolean))
|
|
1984
|
+
.reduce((total, databasePath) => total + countRowsForIds(databasePath, "queued_items", "thread_id", deletionIds), 0);
|
|
1985
|
+
const queueRevisionRowCount = (store.queueDatabasePaths ?? [store.queueDatabasePath].filter(Boolean))
|
|
1986
|
+
.reduce((total, databasePath) => total + (inspectSqliteTable(databasePath, "queued_thread_revisions").exists
|
|
1987
|
+
? countRowsForIds(databasePath, "queued_thread_revisions", "thread_id", deletionIds)
|
|
1988
|
+
: 0), 0);
|
|
1989
|
+
const threadHistoryRowCount = (store.threadHistoryDatabasePaths ?? [store.threadHistoryDatabasePath].filter(Boolean))
|
|
1990
|
+
.reduce((total, databasePath) => total + [
|
|
1991
|
+
"thread_items",
|
|
1992
|
+
"thread_turns",
|
|
1993
|
+
"thread_history_projection_state",
|
|
1994
|
+
].reduce((databaseTotal, tableName) =>
|
|
1995
|
+
databaseTotal + countRowsForIds(databasePath, tableName, "thread_id", deletionIds), 0), 0);
|
|
1957
1996
|
const stateTargets = (store.stateDatabases ?? [{ path: store.stateDatabasePath }]).map((database) => ({
|
|
1958
1997
|
database,
|
|
1959
1998
|
ids: findRowsForIds(database.path, "threads", "id", deletionIds).map((row) => String(row.id)),
|
|
@@ -1972,6 +2011,8 @@ export async function planSessionDeletion({ recordIds, store }) {
|
|
|
1972
2011
|
ids: deletionIds,
|
|
1973
2012
|
logRowCount,
|
|
1974
2013
|
memoryRowCount,
|
|
2014
|
+
queueRowCount,
|
|
2015
|
+
queueRevisionRowCount,
|
|
1975
2016
|
missingTranscriptPaths,
|
|
1976
2017
|
newestLinkedActivityAtMs,
|
|
1977
2018
|
records: selectedRecords,
|
|
@@ -1980,6 +2021,7 @@ export async function planSessionDeletion({ recordIds, store }) {
|
|
|
1980
2021
|
stateTargets,
|
|
1981
2022
|
transcriptBytes,
|
|
1982
2023
|
transcriptFileCount,
|
|
2024
|
+
threadHistoryRowCount,
|
|
1983
2025
|
transcriptPaths,
|
|
1984
2026
|
};
|
|
1985
2027
|
}
|
|
@@ -1991,6 +2033,8 @@ function getBackupSourcePaths({ plan, scope, store }) {
|
|
|
1991
2033
|
const databasePaths = [
|
|
1992
2034
|
...(store.stateDatabasePaths ?? [store.stateDatabasePath]),
|
|
1993
2035
|
...(store.logsDatabasePaths ?? [store.logsDatabasePath].filter(Boolean)),
|
|
2036
|
+
...(store.queueDatabasePaths ?? [store.queueDatabasePath].filter(Boolean)),
|
|
2037
|
+
...(store.threadHistoryDatabasePaths ?? [store.threadHistoryDatabasePath].filter(Boolean)),
|
|
1994
2038
|
...(scope === "deep" ? (store.memoryDatabasePaths ?? [store.memoryDatabasePath].filter(Boolean)) : []),
|
|
1995
2039
|
...(scope === "deep" ? (store.goalsDatabasePaths ?? [store.goalsDatabasePath].filter(Boolean)) : []),
|
|
1996
2040
|
].filter(Boolean);
|
|
@@ -2033,6 +2077,9 @@ export async function fingerprintSessionDeletion({ plan, scope, store }) {
|
|
|
2033
2077
|
plan.dynamicToolRowCount,
|
|
2034
2078
|
plan.historyMatchCount,
|
|
2035
2079
|
plan.logRowCount,
|
|
2080
|
+
plan.queueRowCount,
|
|
2081
|
+
plan.queueRevisionRowCount,
|
|
2082
|
+
plan.threadHistoryRowCount,
|
|
2036
2083
|
plan.sessionIndexMatchCount,
|
|
2037
2084
|
plan.spawnEdgeCount,
|
|
2038
2085
|
...(scope === "deep" ? [plan.goalRowCount, plan.memoryRowCount] : []),
|
|
@@ -2040,6 +2087,8 @@ export async function fingerprintSessionDeletion({ plan, scope, store }) {
|
|
|
2040
2087
|
hash.update(`counts\0${counts.join("\0")}\0`);
|
|
2041
2088
|
hash.update(`stores\0${[
|
|
2042
2089
|
store.hasLogsDatabase,
|
|
2090
|
+
store.hasQueueDatabase,
|
|
2091
|
+
store.hasThreadHistoryDatabase,
|
|
2043
2092
|
...(scope === "deep" ? [
|
|
2044
2093
|
store.hasDesktopState,
|
|
2045
2094
|
store.hasDesktopStateBackup,
|
|
@@ -2079,6 +2128,17 @@ export async function fingerprintSessionDeletion({ plan, scope, store }) {
|
|
|
2079
2128
|
for (const databasePath of store.logsDatabasePaths ?? [store.logsDatabasePath].filter(Boolean)) {
|
|
2080
2129
|
fingerprintRowsForIds(hash, databasePath, "logs", "thread_id", plan.ids);
|
|
2081
2130
|
}
|
|
2131
|
+
for (const databasePath of store.queueDatabasePaths ?? [store.queueDatabasePath].filter(Boolean)) {
|
|
2132
|
+
fingerprintRowsForIds(hash, databasePath, "queued_items", "thread_id", plan.ids);
|
|
2133
|
+
if (inspectSqliteTable(databasePath, "queued_thread_revisions").exists) {
|
|
2134
|
+
fingerprintRowsForIds(hash, databasePath, "queued_thread_revisions", "thread_id", plan.ids);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
for (const databasePath of store.threadHistoryDatabasePaths ?? [store.threadHistoryDatabasePath].filter(Boolean)) {
|
|
2138
|
+
fingerprintRowsForIds(hash, databasePath, "thread_items", "thread_id", plan.ids);
|
|
2139
|
+
fingerprintRowsForIds(hash, databasePath, "thread_turns", "thread_id", plan.ids);
|
|
2140
|
+
fingerprintRowsForIds(hash, databasePath, "thread_history_projection_state", "thread_id", plan.ids);
|
|
2141
|
+
}
|
|
2082
2142
|
if (scope === "deep") {
|
|
2083
2143
|
for (const databasePath of store.memoryDatabasePaths ?? [store.memoryDatabasePath].filter(Boolean)) {
|
|
2084
2144
|
fingerprintRowsForIds(hash, databasePath, "stage1_outputs", "thread_id", plan.ids);
|
|
@@ -2292,6 +2352,8 @@ async function createOperationBackup({ onProgress, plan, scope, store }) {
|
|
|
2292
2352
|
const snapshotCandidates = [
|
|
2293
2353
|
...(store.stateDatabasePaths ?? [store.stateDatabasePath]).map((databasePath) => [databasePath, path.basename(databasePath), true]),
|
|
2294
2354
|
...(store.logsDatabasePaths ?? [store.logsDatabasePath].filter(Boolean)).map((databasePath) => [databasePath, path.basename(databasePath), true]),
|
|
2355
|
+
...(store.queueDatabasePaths ?? [store.queueDatabasePath].filter(Boolean)).map((databasePath) => [databasePath, path.basename(databasePath), true]),
|
|
2356
|
+
...(store.threadHistoryDatabasePaths ?? [store.threadHistoryDatabasePath].filter(Boolean)).map((databasePath) => [databasePath, path.basename(databasePath), true]),
|
|
2295
2357
|
...(scope === "deep" ? (store.memoryDatabasePaths ?? [store.memoryDatabasePath].filter(Boolean)) : []).map((databasePath) => [databasePath, path.basename(databasePath), true]),
|
|
2296
2358
|
...(scope === "deep" ? (store.goalsDatabasePaths ?? [store.goalsDatabasePath].filter(Boolean)) : []).map((databasePath) => [databasePath, path.basename(databasePath), true]),
|
|
2297
2359
|
];
|
|
@@ -2467,6 +2529,25 @@ export async function executeSessionDeletion({
|
|
|
2467
2529
|
executeTransaction(databasePath, deleteStatements("logs", "thread_id", plan.ids));
|
|
2468
2530
|
}
|
|
2469
2531
|
}
|
|
2532
|
+
if (store.hasQueueDatabase) {
|
|
2533
|
+
for (const databasePath of store.queueDatabasePaths ?? [store.queueDatabasePath]) {
|
|
2534
|
+
executeTransaction(databasePath, [
|
|
2535
|
+
...deleteStatements("queued_items", "thread_id", plan.ids),
|
|
2536
|
+
...(inspectSqliteTable(databasePath, "queued_thread_revisions").exists
|
|
2537
|
+
? deleteStatements("queued_thread_revisions", "thread_id", plan.ids)
|
|
2538
|
+
: []),
|
|
2539
|
+
]);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
if (store.hasThreadHistoryDatabase) {
|
|
2543
|
+
for (const databasePath of store.threadHistoryDatabasePaths ?? [store.threadHistoryDatabasePath]) {
|
|
2544
|
+
executeTransaction(databasePath, [
|
|
2545
|
+
...deleteStatements("thread_items", "thread_id", plan.ids),
|
|
2546
|
+
...deleteStatements("thread_turns", "thread_id", plan.ids),
|
|
2547
|
+
...deleteStatements("thread_history_projection_state", "thread_id", plan.ids),
|
|
2548
|
+
]);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2470
2551
|
for (const { database, ids } of plan.stateTargets ?? (store.stateDatabases ?? [{ path: store.stateDatabasePath }]).map((database) => ({ database, ids: plan.ids }))) {
|
|
2471
2552
|
const stateStatements = [];
|
|
2472
2553
|
const schema = stateSchema(database);
|
|
@@ -2828,6 +2909,21 @@ export async function verifySessionDeletion({ plan, scope = "deep", store }) {
|
|
|
2828
2909
|
? (store.logsDatabasePaths ?? [store.logsDatabasePath]).flatMap((databasePath) =>
|
|
2829
2910
|
findRowsForIds(databasePath, "logs", "thread_id", plan.ids, { limitOne: true }))
|
|
2830
2911
|
: [];
|
|
2912
|
+
const remainingQueueRecords = store.hasQueueDatabase
|
|
2913
|
+
? (store.queueDatabasePaths ?? [store.queueDatabasePath]).flatMap((databasePath) => [
|
|
2914
|
+
...findRowsForIds(databasePath, "queued_items", "thread_id", plan.ids, { limitOne: true }),
|
|
2915
|
+
...(inspectSqliteTable(databasePath, "queued_thread_revisions").exists
|
|
2916
|
+
? findRowsForIds(databasePath, "queued_thread_revisions", "thread_id", plan.ids, { limitOne: true })
|
|
2917
|
+
: []),
|
|
2918
|
+
])
|
|
2919
|
+
: [];
|
|
2920
|
+
const remainingThreadHistoryRecords = store.hasThreadHistoryDatabase
|
|
2921
|
+
? (store.threadHistoryDatabasePaths ?? [store.threadHistoryDatabasePath]).flatMap((databasePath) => [
|
|
2922
|
+
...findRowsForIds(databasePath, "thread_items", "thread_id", plan.ids, { limitOne: true }),
|
|
2923
|
+
...findRowsForIds(databasePath, "thread_turns", "thread_id", plan.ids, { limitOne: true }),
|
|
2924
|
+
...findRowsForIds(databasePath, "thread_history_projection_state", "thread_id", plan.ids, { limitOne: true }),
|
|
2925
|
+
])
|
|
2926
|
+
: [];
|
|
2831
2927
|
const [sessionIndexMatches, historyMatches] = await Promise.all([
|
|
2832
2928
|
inspectJsonlMatches(
|
|
2833
2929
|
store.sessionIndexPath,
|
|
@@ -2872,6 +2968,8 @@ export async function verifySessionDeletion({ plan, scope = "deep", store }) {
|
|
|
2872
2968
|
remainingMemoryRecords.length === 0 &&
|
|
2873
2969
|
remainingGoalRecords.length === 0 &&
|
|
2874
2970
|
remainingLogRecords.length === 0 &&
|
|
2971
|
+
remainingQueueRecords.length === 0 &&
|
|
2972
|
+
remainingThreadHistoryRecords.length === 0 &&
|
|
2875
2973
|
sessionIndexMatches.count === 0 &&
|
|
2876
2974
|
historyMatches.count === 0 &&
|
|
2877
2975
|
remainingTranscriptPaths.length === 0 &&
|
|
@@ -2883,6 +2981,8 @@ export async function verifySessionDeletion({ plan, scope = "deep", store }) {
|
|
|
2883
2981
|
remainingHistoryEntryCount: historyMatches.count,
|
|
2884
2982
|
remainingLogRecords,
|
|
2885
2983
|
remainingMemoryRecords,
|
|
2984
|
+
remainingQueueRecords,
|
|
2985
|
+
remainingThreadHistoryRecords,
|
|
2886
2986
|
remainingSessionIndexEntries,
|
|
2887
2987
|
remainingSessionIndexEntryCount: sessionIndexMatches.count,
|
|
2888
2988
|
remainingThreads,
|
package/lib/session-events.mjs
CHANGED
|
@@ -206,6 +206,72 @@ export function createSessionEventCoverage({
|
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
export function createSessionEventSummary({ asks = 0, commands = 0, edits = 0 } = {}) {
|
|
210
|
+
return {
|
|
211
|
+
asks: requiredCount(asks, "summary.asks"),
|
|
212
|
+
commands: requiredCount(commands, "summary.commands"),
|
|
213
|
+
edits: requiredCount(edits, "summary.edits"),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Ordered largest-concept-first so the bar and its legend agree
|
|
218
|
+
export const SESSION_EVENT_COMPOSITION_SEGMENTS = Object.freeze([
|
|
219
|
+
"toolOutput",
|
|
220
|
+
"largeRecords",
|
|
221
|
+
"compaction",
|
|
222
|
+
"attachments",
|
|
223
|
+
"messages",
|
|
224
|
+
"edits",
|
|
225
|
+
"reasoning",
|
|
226
|
+
"other",
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
export function createSessionEventComposition({
|
|
230
|
+
attachments = 0,
|
|
231
|
+
compaction = 0,
|
|
232
|
+
edits = 0,
|
|
233
|
+
largeRecords = 0,
|
|
234
|
+
messages = 0,
|
|
235
|
+
other = 0,
|
|
236
|
+
reasoning = 0,
|
|
237
|
+
toolOutput = 0,
|
|
238
|
+
total = 0,
|
|
239
|
+
} = {}) {
|
|
240
|
+
const composition = {
|
|
241
|
+
attachments: requiredCount(attachments, "composition.attachments"),
|
|
242
|
+
compaction: requiredCount(compaction, "composition.compaction"),
|
|
243
|
+
edits: requiredCount(edits, "composition.edits"),
|
|
244
|
+
largeRecords: requiredCount(largeRecords, "composition.largeRecords"),
|
|
245
|
+
messages: requiredCount(messages, "composition.messages"),
|
|
246
|
+
other: requiredCount(other, "composition.other"),
|
|
247
|
+
reasoning: requiredCount(reasoning, "composition.reasoning"),
|
|
248
|
+
toolOutput: requiredCount(toolOutput, "composition.toolOutput"),
|
|
249
|
+
total: requiredCount(total, "composition.total"),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const segments = SESSION_EVENT_COMPOSITION_SEGMENTS
|
|
253
|
+
.reduce((sum, segment) => sum + composition[segment], 0);
|
|
254
|
+
|
|
255
|
+
if (segments !== composition.total) {
|
|
256
|
+
throw new TypeError("Session event composition segments must add up to the transcript size.");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return composition;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function finalizeSessionEventComposition(composition, transcriptBytes) {
|
|
263
|
+
const attributed = SESSION_EVENT_COMPOSITION_SEGMENTS
|
|
264
|
+
.filter((segment) => segment !== "other")
|
|
265
|
+
.reduce((sum, segment) => sum + (composition[segment] ?? 0), 0);
|
|
266
|
+
const total = Math.max(transcriptBytes ?? 0, attributed);
|
|
267
|
+
|
|
268
|
+
return createSessionEventComposition({
|
|
269
|
+
...composition,
|
|
270
|
+
other: total - attributed,
|
|
271
|
+
total,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
209
275
|
export function sessionEventCoveragePercent(coverage) {
|
|
210
276
|
const normalizedCoverage = createSessionEventCoverage(coverage);
|
|
211
277
|
const considered = normalizedCoverage.total - normalizedCoverage.skipped;
|
|
@@ -246,13 +312,17 @@ export function createSessionEventHeader({
|
|
|
246
312
|
}
|
|
247
313
|
|
|
248
314
|
export function createSessionEventsResult({
|
|
315
|
+
composition,
|
|
249
316
|
coverage,
|
|
250
317
|
events = [],
|
|
251
318
|
header,
|
|
252
319
|
reason = null,
|
|
320
|
+
summary,
|
|
253
321
|
window = {},
|
|
254
322
|
} = {}) {
|
|
255
323
|
const normalizedCoverage = createSessionEventCoverage(coverage);
|
|
324
|
+
const normalizedSummary = createSessionEventSummary(summary);
|
|
325
|
+
const normalizedComposition = createSessionEventComposition(composition);
|
|
256
326
|
|
|
257
327
|
if (
|
|
258
328
|
normalizedCoverage.recognized
|
|
@@ -298,6 +368,8 @@ export function createSessionEventsResult({
|
|
|
298
368
|
events: [...events],
|
|
299
369
|
header: createSessionEventHeader(header),
|
|
300
370
|
reason,
|
|
371
|
+
composition: normalizedComposition,
|
|
372
|
+
summary: normalizedSummary,
|
|
301
373
|
window: {
|
|
302
374
|
complete,
|
|
303
375
|
end,
|
package/lib/storage/jsonl.mjs
CHANGED
|
@@ -41,7 +41,12 @@ export async function* readJsonlEntries(filePath) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function appendLineBytes(state, bytes, maxLineBytes) {
|
|
44
|
-
if (
|
|
44
|
+
if (bytes.length === 0) return;
|
|
45
|
+
|
|
46
|
+
// Counted even once a line is oversized, because the largest records in a
|
|
47
|
+
// transcript are exactly the ones that stop being buffered.
|
|
48
|
+
state.totalBytes += bytes.length;
|
|
49
|
+
if (state.oversized) return;
|
|
45
50
|
|
|
46
51
|
if (state.length + bytes.length > maxLineBytes) {
|
|
47
52
|
state.length = 0;
|
|
@@ -69,17 +74,20 @@ function createLineState(maxLineBytes) {
|
|
|
69
74
|
buffer: Buffer.allocUnsafe(Math.min(JSONL_STARTING_LINE_BYTES, maxLineBytes)),
|
|
70
75
|
length: 0,
|
|
71
76
|
oversized: false,
|
|
77
|
+
totalBytes: 0,
|
|
72
78
|
};
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
function resetLineState(state) {
|
|
76
82
|
state.length = 0;
|
|
77
83
|
state.oversized = false;
|
|
84
|
+
state.totalBytes = 0;
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
function snapshotEntry(state, index) {
|
|
81
88
|
if (state.oversized) {
|
|
82
89
|
return {
|
|
90
|
+
bytes: state.totalBytes,
|
|
83
91
|
index,
|
|
84
92
|
oversized: true,
|
|
85
93
|
parsed: null,
|
|
@@ -93,6 +101,7 @@ function snapshotEntry(state, index) {
|
|
|
93
101
|
|
|
94
102
|
const entry = parseLine(bytes.toString("utf8"), index);
|
|
95
103
|
return {
|
|
104
|
+
bytes: state.totalBytes,
|
|
96
105
|
index: entry.index,
|
|
97
106
|
oversized: false,
|
|
98
107
|
parsed: entry.parsed,
|
package/package.json
CHANGED