tokentracker-cli 0.81.2 → 0.82.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/dashboard/dist/assets/AchievementBadge-D6iPEEbz.js +1 -0
- package/dashboard/dist/assets/AchievementsPage-Jry355YE.js +2 -0
- package/dashboard/dist/assets/AchievementsSection-ah-kG00E.js +1 -0
- package/dashboard/dist/assets/BadgeDetailModal-FyvUrVvF.js +1 -0
- package/dashboard/dist/assets/Card-C3nLScju.js +1 -0
- package/dashboard/dist/assets/ClawdAnimated-CLHhE26z.js +1 -0
- package/dashboard/dist/assets/CommunityStatsModal-DA3PHLV5.js +1 -0
- package/dashboard/dist/assets/ConfirmModal-CTDarLrD.js +1 -0
- package/dashboard/dist/assets/DashboardPage-DZIiWc3Y.js +1 -0
- package/dashboard/dist/assets/DevicePage-CyXjUO8W.js +1 -0
- package/dashboard/dist/assets/DialogDescription-ahxSNUcB.js +1 -0
- package/dashboard/dist/assets/DialogTitle-z86PHHyk.js +1 -0
- package/dashboard/dist/assets/FadeIn-BDWtfIsO.js +1 -0
- package/dashboard/dist/assets/HeaderGithubStar-DE1USQbb.js +1 -0
- package/dashboard/dist/assets/{IpCheckPage-CMf9CHFr.js → IpCheckPage-B2SkgT0P.js} +3 -3
- package/dashboard/dist/assets/{LandingPage-BT61_TqT.js → LandingPage-PU5gxsfY.js} +165 -165
- package/dashboard/dist/assets/LeaderboardAvatar-CaOV_hFE.js +1 -0
- package/dashboard/dist/assets/LeaderboardPage-B0JtEyRP.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-C_kxqOMn.js → LeaderboardProfileModal-DtNEbVH8.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-zgNsE0EQ.js → LeaderboardProfilePage-DRklax3-.js} +3 -3
- package/dashboard/dist/assets/LimitsPage-Dg0cJa40.js +4 -0
- package/dashboard/dist/assets/LocalOnlyNotice-si9PMKya.js +1 -0
- package/dashboard/dist/assets/LoginPage-DgLwhXKo.js +1 -0
- package/dashboard/dist/assets/PetPage-DEZbjk99.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-DX7v1XfE.js +1 -0
- package/dashboard/dist/assets/ResetPasswordPage-C6NvRO-Q.js +1 -0
- package/dashboard/dist/assets/Select-CH2xVU8X.js +1 -0
- package/dashboard/dist/assets/SelectItemText-CCW6JeeN.js +1 -0
- package/dashboard/dist/assets/SettingsPage-Cm3uT11D.js +1 -0
- package/dashboard/dist/assets/SkillsPage-CljRL6yF.js +1 -0
- package/dashboard/dist/assets/TrendMonitor-DM1_WNfw.js +19 -0
- package/dashboard/dist/assets/WidgetsPage-mJ1j6hly.js +1 -0
- package/dashboard/dist/assets/WrappedPage-Dz71ln4t.js +1 -0
- package/dashboard/dist/assets/__vite-browser-external-C7JwykID.css +1 -0
- package/dashboard/dist/assets/__vite-browser-external-Eoyj4eG-.js +1341 -0
- package/dashboard/dist/assets/agent-logos-D3K_ZRUj.js +1 -0
- package/dashboard/dist/assets/arrow-up-right-CT4-gmOS.js +1 -0
- package/dashboard/dist/assets/check-B87_qvAg.js +1 -0
- package/dashboard/dist/assets/copy-Dtr68epm.js +1 -0
- package/dashboard/dist/assets/download-DaWsA_Gj.js +1 -0
- package/dashboard/dist/assets/flame-vLBOq0X0.js +1 -0
- package/dashboard/dist/assets/format-tokens-C6Pidz3s.js +1 -0
- package/dashboard/dist/assets/icons-BaaYJxUN.js +1 -0
- package/dashboard/dist/assets/index-lKnLA6Pd.js +1 -0
- package/dashboard/dist/assets/info-B46Qt16h.js +1 -0
- package/dashboard/dist/assets/limits-providers-13af4PEg.js +1 -0
- package/dashboard/dist/assets/link-2-DeN6MJpT.js +1 -0
- package/dashboard/dist/assets/loader-circle-ByEuaaSw.js +1 -0
- package/dashboard/dist/assets/provider-display-CKbOKeV4.js +42 -0
- package/dashboard/dist/assets/terminal-DyicX7lG.js +1 -0
- package/dashboard/dist/assets/trash-2-CVCC3DtQ.js +1 -0
- package/dashboard/dist/assets/use-community-stats-CExk3D6G.js +1 -0
- package/dashboard/dist/assets/use-limits-display-prefs-CRl4uU95.js +1 -0
- package/dashboard/dist/assets/use-native-settings-Bi6E9dsE.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-DPbiKnPc.js → use-ordered-list-Dk0p2otw.js} +2 -2
- package/dashboard/dist/assets/use-session-efficiency-pref-YAG-OYIh.js +1 -0
- package/dashboard/dist/assets/use-transform-lflyghWN.js +1 -0
- package/dashboard/dist/assets/use-usage-limits-DJXvJ25Q.js +1 -0
- package/dashboard/dist/assets/useCurrency-BtHtcsO5.js +1 -0
- package/dashboard/dist/assets/useScrollLock-C2C-Yazq.js +1 -0
- package/dashboard/dist/assets/useTokenFormat-BdD057ih.js +1 -0
- package/dashboard/dist/assets/zap-DWWma29E.js +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/cli.js +6 -0
- package/src/commands/serve.js +8 -6
- package/src/commands/sessions.js +29 -0
- package/src/commands/status.js +2 -1
- package/src/commands/sync.js +164 -36
- package/src/lib/codex-context-breakdown.js +330 -13
- package/src/lib/codex-rollout-parser.js +323 -42
- package/src/lib/context-health.js +124 -0
- package/src/lib/cursor-store.js +1289 -0
- package/src/lib/diagnostics.js +8 -5
- package/src/lib/fs.js +254 -16
- package/src/lib/git-outcomes.js +147 -0
- package/src/lib/local-api.js +151 -20
- package/src/lib/outcomes-engine.js +23 -0
- package/src/lib/pricing/index.js +8 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +101 -9
- package/src/lib/session-analytics.js +612 -0
- package/src/lib/usage-limits.js +21 -0
- package/dashboard/dist/assets/AchievementBadge-DXWHZrAL.js +0 -1
- package/dashboard/dist/assets/AchievementsPage-BhrQXniL.js +0 -2
- package/dashboard/dist/assets/AchievementsSection-gzP3m1Ml.js +0 -1
- package/dashboard/dist/assets/BadgeDetailModal-DGKPuvDU.js +0 -1
- package/dashboard/dist/assets/Card-BRtYQ-Ot.js +0 -1
- package/dashboard/dist/assets/ClawdAnimated-UsNG-qDv.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-CJQw-P4t.js +0 -1
- package/dashboard/dist/assets/ConfirmModal-ByY4GDsy.js +0 -1
- package/dashboard/dist/assets/DashboardPage-CuLVnW98.js +0 -19
- package/dashboard/dist/assets/DevicePage-D67CxpmR.js +0 -1
- package/dashboard/dist/assets/DialogClose-Ci8hkTw5.js +0 -1
- package/dashboard/dist/assets/DialogDescription-C9vXUZU0.js +0 -1
- package/dashboard/dist/assets/DialogTitle-sLJxKWjv.js +0 -1
- package/dashboard/dist/assets/FadeIn-xVds37w6.js +0 -1
- package/dashboard/dist/assets/HeaderGithubStar-kK3omgmB.js +0 -1
- package/dashboard/dist/assets/LeaderboardAvatar-DyKz6NdT.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-DJ4zihJJ.js +0 -2
- package/dashboard/dist/assets/LimitsPage-BiQpqxj8.js +0 -4
- package/dashboard/dist/assets/LocalOnlyNotice-DTrrUVbZ.js +0 -1
- package/dashboard/dist/assets/LoginPage-BOM9Q2sI.js +0 -1
- package/dashboard/dist/assets/PetPage-BURk7RG-.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-BV_aoNBd.js +0 -1
- package/dashboard/dist/assets/ResetPasswordPage-SBL2LvXC.js +0 -1
- package/dashboard/dist/assets/Select-BsRdHde3.js +0 -1
- package/dashboard/dist/assets/SelectItemText-DgkrZxIq.js +0 -1
- package/dashboard/dist/assets/SettingsPage-DqxVsfnB.js +0 -1
- package/dashboard/dist/assets/SkillsPage-B0VfWkOh.js +0 -1
- package/dashboard/dist/assets/WidgetsPage-4QQ8wI_g.js +0 -1
- package/dashboard/dist/assets/WrappedPage-BK53khVY.js +0 -1
- package/dashboard/dist/assets/agent-logos-DXpW_Wmz.js +0 -1
- package/dashboard/dist/assets/arrow-up-right-DCc--V5G.js +0 -1
- package/dashboard/dist/assets/check-DqxuosV2.js +0 -1
- package/dashboard/dist/assets/copy-qChAT7MY.js +0 -1
- package/dashboard/dist/assets/download-W9R3sBph.js +0 -1
- package/dashboard/dist/assets/flame-0eyhMnfU.js +0 -1
- package/dashboard/dist/assets/format-tokens-CkfBUcoa.js +0 -1
- package/dashboard/dist/assets/info-DY1O85-U.js +0 -1
- package/dashboard/dist/assets/link-2-B_Z66hFT.js +0 -1
- package/dashboard/dist/assets/loader-circle-xH40lon2.js +0 -1
- package/dashboard/dist/assets/main-CeaB5N36.js +0 -1259
- package/dashboard/dist/assets/main-DAc77vwF.css +0 -1
- package/dashboard/dist/assets/provider-display-C_XKZQcF.js +0 -42
- package/dashboard/dist/assets/trash-2-DVhNJ74r.js +0 -1
- package/dashboard/dist/assets/use-community-stats-CO-nr2Vf.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-RRqmyoxt.js +0 -1
- package/dashboard/dist/assets/use-native-settings-79piPeET.js +0 -1
- package/dashboard/dist/assets/use-quality-per-dollar-pref-DyF49QRF.js +0 -1
- package/dashboard/dist/assets/use-transform-BvXyGAtb.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-BiZ_I-KE.js +0 -1
- package/dashboard/dist/assets/useCurrency-D-C1e7eE.js +0 -1
- package/dashboard/dist/assets/useScrollLock-BfU2EMvG.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-Cm4EvrQp.js +0 -1
- package/dashboard/dist/assets/zap-DlAOTwFs.js +0 -1
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"use strict";
|
|
12
12
|
|
|
13
13
|
const crypto = require("node:crypto");
|
|
14
|
+
const fs = require("node:fs");
|
|
14
15
|
const os = require("node:os");
|
|
15
16
|
const path = require("node:path");
|
|
16
17
|
|
|
@@ -31,6 +32,7 @@ const {
|
|
|
31
32
|
finalizeSkillRows,
|
|
32
33
|
finalizeExecRows,
|
|
33
34
|
buildSkillStatsEntry,
|
|
35
|
+
isCodexResumeInvalidated,
|
|
34
36
|
} = require("./codex-rollout-parser");
|
|
35
37
|
|
|
36
38
|
// ---------------------------------------------------------------------------
|
|
@@ -85,6 +87,50 @@ function mergeExecRows(map, rows) {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
function mergeParsedSessionResults(previous, delta) {
|
|
91
|
+
if (!previous) return delta;
|
|
92
|
+
if (!delta) return previous;
|
|
93
|
+
|
|
94
|
+
const totals = emptyTotals();
|
|
95
|
+
mergeRollupTotals(totals, previous.totals || {});
|
|
96
|
+
mergeRollupTotals(totals, delta.totals || {});
|
|
97
|
+
|
|
98
|
+
const tools = new Map();
|
|
99
|
+
mergeRows(tools, previous.toolBreakdown?.tool_rows);
|
|
100
|
+
mergeRows(tools, delta.toolBreakdown?.tool_rows);
|
|
101
|
+
const skills = new Map();
|
|
102
|
+
mergeSkillRows(skills, previous.skillsBreakdown?.skill_rows);
|
|
103
|
+
mergeSkillRows(skills, delta.skillsBreakdown?.skill_rows);
|
|
104
|
+
|
|
105
|
+
const mergeExecGroup = (key) => {
|
|
106
|
+
const rows = new Map();
|
|
107
|
+
mergeExecRows(rows, previous.execCommandBreakdown?.[key]);
|
|
108
|
+
mergeExecRows(rows, delta.execCommandBreakdown?.[key]);
|
|
109
|
+
return finalizeExecRows(rows);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
sessionId: delta.sessionId || previous.sessionId,
|
|
114
|
+
cwd: delta.cwd || previous.cwd,
|
|
115
|
+
model: delta.model || previous.model,
|
|
116
|
+
provider: delta.provider || previous.provider,
|
|
117
|
+
version: delta.version || previous.version,
|
|
118
|
+
filePath: previous.filePath || delta.filePath,
|
|
119
|
+
turnCount: Number(previous.turnCount || 0) + Number(delta.turnCount || 0),
|
|
120
|
+
totals,
|
|
121
|
+
toolBreakdown: { tool_rows: finalizeToolRows(tools) },
|
|
122
|
+
skillsBreakdown: { skill_rows: finalizeSkillRows(skills) },
|
|
123
|
+
execCommandBreakdown: {
|
|
124
|
+
byType: mergeExecGroup("byType"),
|
|
125
|
+
byExit: mergeExecGroup("byExit"),
|
|
126
|
+
byExecutable: mergeExecGroup("byExecutable"),
|
|
127
|
+
byCommand: mergeExecGroup("byCommand"),
|
|
128
|
+
byDuration: mergeExecGroup("byDuration"),
|
|
129
|
+
byOutput: mergeExecGroup("byOutput"),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
// ---------------------------------------------------------------------------
|
|
89
135
|
// Period helpers
|
|
90
136
|
// ---------------------------------------------------------------------------
|
|
@@ -122,8 +168,9 @@ function buildDateRange({ period, date }) {
|
|
|
122
168
|
const CACHE = new Map();
|
|
123
169
|
const PARSED_GROUP_CACHE = new Map();
|
|
124
170
|
const CACHE_TTL_MS = 60_000;
|
|
125
|
-
const CACHE_SCHEMA_VERSION = "codex-context-
|
|
171
|
+
const CACHE_SCHEMA_VERSION = "codex-context-v6";
|
|
126
172
|
const MAX_PARSED_GROUP_CACHE_ENTRIES = 4096;
|
|
173
|
+
const PREFIX_HASH_READ_BYTES = 1024 * 1024;
|
|
127
174
|
|
|
128
175
|
function cacheTimeZoneKey(timeZoneContext) {
|
|
129
176
|
if (!timeZoneContext) return "";
|
|
@@ -131,7 +178,26 @@ function cacheTimeZoneKey(timeZoneContext) {
|
|
|
131
178
|
}
|
|
132
179
|
|
|
133
180
|
function statIdentity(filePath, stat) {
|
|
134
|
-
return JSON.stringify([filePath, stat.
|
|
181
|
+
return JSON.stringify([filePath, stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function contentStatIdentity(filePath, stat) {
|
|
185
|
+
return JSON.stringify([
|
|
186
|
+
filePath,
|
|
187
|
+
stat.dev,
|
|
188
|
+
stat.ino,
|
|
189
|
+
stat.size,
|
|
190
|
+
stat.mtimeMs,
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isUnlinkedResumeSnapshot(filePath, before, after) {
|
|
195
|
+
return Boolean(
|
|
196
|
+
before &&
|
|
197
|
+
after &&
|
|
198
|
+
Number(after.nlink) === 0 &&
|
|
199
|
+
contentStatIdentity(filePath, before) === contentStatIdentity(filePath, after),
|
|
200
|
+
);
|
|
135
201
|
}
|
|
136
202
|
|
|
137
203
|
function inventorySignature(entries) {
|
|
@@ -152,6 +218,15 @@ function parsedGroupCacheKey(entries, { from, to, timeZoneContext }) {
|
|
|
152
218
|
hash.update(to || "");
|
|
153
219
|
hash.update("\0");
|
|
154
220
|
hash.update(cacheTimeZoneKey(timeZoneContext));
|
|
221
|
+
for (const { filePath } of entries) {
|
|
222
|
+
hash.update(filePath);
|
|
223
|
+
hash.update("\0");
|
|
224
|
+
}
|
|
225
|
+
return hash.digest("hex");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parsedGroupSignature(entries) {
|
|
229
|
+
const hash = crypto.createHash("sha256");
|
|
155
230
|
for (const { filePath, stat } of entries) {
|
|
156
231
|
hash.update(statIdentity(filePath, stat));
|
|
157
232
|
hash.update("\0");
|
|
@@ -159,6 +234,123 @@ function parsedGroupCacheKey(entries, { from, to, timeZoneContext }) {
|
|
|
159
234
|
return hash.digest("hex");
|
|
160
235
|
}
|
|
161
236
|
|
|
237
|
+
function selectAppendHashPaths(candidateGroups) {
|
|
238
|
+
// Only the newest single-file group pays the cold hash cost. Any file that
|
|
239
|
+
// later grows becomes newest, takes one conservative full parse, and keeps
|
|
240
|
+
// its hash state for subsequent append-only refreshes.
|
|
241
|
+
let newest = null;
|
|
242
|
+
for (const entries of candidateGroups.values()) {
|
|
243
|
+
if (
|
|
244
|
+
entries.length !== 1 ||
|
|
245
|
+
!rolloutSessionIdFromPath(entries[0].filePath)
|
|
246
|
+
) continue;
|
|
247
|
+
const entry = entries[0];
|
|
248
|
+
if (!newest) {
|
|
249
|
+
newest = entry;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const mtimeDelta = Number(entry.stat?.mtimeMs || 0) -
|
|
253
|
+
Number(newest.stat?.mtimeMs || 0);
|
|
254
|
+
const ctimeDelta = Number(entry.stat?.ctimeMs || 0) -
|
|
255
|
+
Number(newest.stat?.ctimeMs || 0);
|
|
256
|
+
if (
|
|
257
|
+
mtimeDelta > 0 ||
|
|
258
|
+
(mtimeDelta === 0 && ctimeDelta > 0) ||
|
|
259
|
+
(mtimeDelta === 0 && ctimeDelta === 0 &&
|
|
260
|
+
entry.filePath.localeCompare(newest.filePath) < 0)
|
|
261
|
+
) {
|
|
262
|
+
newest = entry;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return newest ? new Set([newest.filePath]) : new Set();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function digestFilePrefix(
|
|
269
|
+
sourceHandle,
|
|
270
|
+
endOffset,
|
|
271
|
+
diagnostics = null,
|
|
272
|
+
) {
|
|
273
|
+
const end = Math.max(0, Number(endOffset) || 0);
|
|
274
|
+
const hash = crypto.createHash("sha256");
|
|
275
|
+
if (end === 0) return hash.digest("hex");
|
|
276
|
+
let bytesRead = 0;
|
|
277
|
+
const buffer = Buffer.allocUnsafe(PREFIX_HASH_READ_BYTES);
|
|
278
|
+
while (bytesRead < end) {
|
|
279
|
+
const length = Math.min(buffer.length, end - bytesRead);
|
|
280
|
+
const result = await sourceHandle.read(buffer, 0, length, bytesRead);
|
|
281
|
+
if (!result.bytesRead) break;
|
|
282
|
+
hash.update(buffer.subarray(0, result.bytesRead));
|
|
283
|
+
bytesRead += result.bytesRead;
|
|
284
|
+
}
|
|
285
|
+
if (diagnostics) {
|
|
286
|
+
diagnostics.prefix_validation_bytes =
|
|
287
|
+
Number(diagnostics.prefix_validation_bytes || 0) + bytesRead;
|
|
288
|
+
}
|
|
289
|
+
return bytesRead === end ? hash.digest("hex") : null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function openValidatedResumeFile(cached, entry, diagnostics = null) {
|
|
293
|
+
if (
|
|
294
|
+
!cached?.resumeState ||
|
|
295
|
+
!cached?.stat ||
|
|
296
|
+
!cached?.contentHashState ||
|
|
297
|
+
typeof cached.contentHashState.copy !== "function" ||
|
|
298
|
+
cached.appendable !== true ||
|
|
299
|
+
cached.filePath !== entry.filePath
|
|
300
|
+
) return null;
|
|
301
|
+
const previous = cached.stat;
|
|
302
|
+
const current = entry.stat;
|
|
303
|
+
if (
|
|
304
|
+
Number(previous.dev) !== Number(current.dev) ||
|
|
305
|
+
Number(previous.ino) !== Number(current.ino) ||
|
|
306
|
+
Number(current.size) <= Number(previous.size) ||
|
|
307
|
+
Number(cached.endOffset) < 0 ||
|
|
308
|
+
Number(cached.endOffset) > Number(previous.size)
|
|
309
|
+
) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
let handle = null;
|
|
313
|
+
let keepOpen = false;
|
|
314
|
+
try {
|
|
315
|
+
// Validation and suffix parsing share this handle so a path replacement
|
|
316
|
+
// cannot splice cached aggregates onto bytes from a different file.
|
|
317
|
+
handle = await fs.promises.open(entry.filePath, "r");
|
|
318
|
+
if (diagnostics) diagnostics.stat_calls += 1;
|
|
319
|
+
const opened = await handle.stat();
|
|
320
|
+
if (
|
|
321
|
+
statIdentity(entry.filePath, opened) !==
|
|
322
|
+
statIdentity(entry.filePath, current)
|
|
323
|
+
) return null;
|
|
324
|
+
|
|
325
|
+
const expected = cached.contentHashState.copy().digest("hex");
|
|
326
|
+
const actual = await digestFilePrefix(
|
|
327
|
+
handle,
|
|
328
|
+
cached.endOffset,
|
|
329
|
+
diagnostics,
|
|
330
|
+
);
|
|
331
|
+
if (actual !== expected) return null;
|
|
332
|
+
if (diagnostics) diagnostics.stat_calls += 1;
|
|
333
|
+
const afterValidation = await handle.stat();
|
|
334
|
+
if (
|
|
335
|
+
statIdentity(entry.filePath, current) !==
|
|
336
|
+
statIdentity(entry.filePath, afterValidation) &&
|
|
337
|
+
!isUnlinkedResumeSnapshot(entry.filePath, current, afterValidation)
|
|
338
|
+
) return null;
|
|
339
|
+
keepOpen = true;
|
|
340
|
+
return handle;
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
} finally {
|
|
344
|
+
if (handle && !keepOpen) {
|
|
345
|
+
try {
|
|
346
|
+
await handle.close();
|
|
347
|
+
} catch {
|
|
348
|
+
// A failed validation already forces the conservative full parse.
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
162
354
|
function rememberParsedGroup(key, value) {
|
|
163
355
|
PARSED_GROUP_CACHE.delete(key);
|
|
164
356
|
PARSED_GROUP_CACHE.set(key, value);
|
|
@@ -169,6 +361,17 @@ function rememberParsedGroup(key, value) {
|
|
|
169
361
|
}
|
|
170
362
|
}
|
|
171
363
|
|
|
364
|
+
function splitCapturedParse(parsed) {
|
|
365
|
+
const {
|
|
366
|
+
resumeState = null,
|
|
367
|
+
endOffset = 0,
|
|
368
|
+
appendable = false,
|
|
369
|
+
contentHashState = null,
|
|
370
|
+
...result
|
|
371
|
+
} = parsed;
|
|
372
|
+
return { parsed: result, resumeState, endOffset, appendable, contentHashState };
|
|
373
|
+
}
|
|
374
|
+
|
|
172
375
|
// ---------------------------------------------------------------------------
|
|
173
376
|
// Main entry
|
|
174
377
|
// ---------------------------------------------------------------------------
|
|
@@ -208,6 +411,10 @@ async function computeCodexContextBreakdown({
|
|
|
208
411
|
opened_files: 0,
|
|
209
412
|
parsed_files: 0,
|
|
210
413
|
parse_cache_hits: 0,
|
|
414
|
+
incremental_parse_hits: 0,
|
|
415
|
+
incremental_parse_fallbacks: 0,
|
|
416
|
+
bytes_read: 0,
|
|
417
|
+
prefix_validation_bytes: 0,
|
|
211
418
|
json_parse_calls: 0,
|
|
212
419
|
};
|
|
213
420
|
const discovered = discoverCodexSessionFiles(roots, {
|
|
@@ -245,6 +452,7 @@ async function computeCodexContextBreakdown({
|
|
|
245
452
|
if (!candidateGroups.has(sessionKey)) candidateGroups.set(sessionKey, []);
|
|
246
453
|
candidateGroups.get(sessionKey).push(entry);
|
|
247
454
|
}
|
|
455
|
+
const appendHashPaths = selectAppendHashPaths(candidateGroups);
|
|
248
456
|
|
|
249
457
|
for (const entries of candidateGroups.values()) {
|
|
250
458
|
const filePaths = entries.map((entry) => entry.filePath);
|
|
@@ -252,22 +460,131 @@ async function computeCodexContextBreakdown({
|
|
|
252
460
|
const parsedCacheKey = cacheable
|
|
253
461
|
? parsedGroupCacheKey(entries, { from: fromKey, to: toKey, timeZoneContext })
|
|
254
462
|
: null;
|
|
255
|
-
|
|
256
|
-
|
|
463
|
+
const signature = parsedGroupSignature(entries);
|
|
464
|
+
const cachedGroup = parsedCacheKey ? PARSED_GROUP_CACHE.get(parsedCacheKey) : null;
|
|
465
|
+
let parsed = null;
|
|
466
|
+
if (cachedGroup?.signature === signature) {
|
|
257
467
|
// Cacheable groups are partitioned by rollout UUID, so their token event
|
|
258
468
|
// keys cannot overlap and cache hits need not backfill seenTokenEvents.
|
|
259
469
|
diagnostics.parse_cache_hits += 1;
|
|
470
|
+
parsed = cachedGroup.parsed;
|
|
260
471
|
PARSED_GROUP_CACHE.delete(parsedCacheKey);
|
|
261
|
-
PARSED_GROUP_CACHE.set(parsedCacheKey,
|
|
472
|
+
PARSED_GROUP_CACHE.set(parsedCacheKey, cachedGroup);
|
|
262
473
|
} else {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
474
|
+
let captured = null;
|
|
475
|
+
const singleEntry = entries.length === 1 ? entries[0] : null;
|
|
476
|
+
const appendHashEligible = Boolean(
|
|
477
|
+
singleEntry && appendHashPaths.has(singleEntry.filePath),
|
|
478
|
+
);
|
|
479
|
+
const resumeHandle = appendHashEligible
|
|
480
|
+
? await openValidatedResumeFile(cachedGroup, singleEntry, diagnostics)
|
|
481
|
+
: null;
|
|
482
|
+
if (resumeHandle) {
|
|
483
|
+
try {
|
|
484
|
+
const resumed = await parseCodexRolloutFile(singleEntry.filePath, {
|
|
485
|
+
from: fromKey,
|
|
486
|
+
to: toKey,
|
|
487
|
+
timeZoneContext,
|
|
488
|
+
diagnostics,
|
|
489
|
+
seenTokenEvents: new Set(),
|
|
490
|
+
startOffset: cachedGroup.endOffset,
|
|
491
|
+
endOffset: singleEntry.stat.size,
|
|
492
|
+
resumeState: cachedGroup.resumeState,
|
|
493
|
+
captureResumeState: true,
|
|
494
|
+
captureContentHash: true,
|
|
495
|
+
contentHashState: cachedGroup.contentHashState,
|
|
496
|
+
sourceHandle: resumeHandle,
|
|
497
|
+
});
|
|
498
|
+
if (diagnostics) diagnostics.stat_calls += 1;
|
|
499
|
+
const afterParse = await resumeHandle.stat().catch(() => null);
|
|
500
|
+
let stableAfterParse = Boolean(
|
|
501
|
+
afterParse &&
|
|
502
|
+
statIdentity(singleEntry.filePath, afterParse) ===
|
|
503
|
+
statIdentity(singleEntry.filePath, singleEntry.stat),
|
|
504
|
+
);
|
|
505
|
+
if (
|
|
506
|
+
!stableAfterParse &&
|
|
507
|
+
isUnlinkedResumeSnapshot(
|
|
508
|
+
singleEntry.filePath,
|
|
509
|
+
singleEntry.stat,
|
|
510
|
+
afterParse,
|
|
511
|
+
)
|
|
512
|
+
) {
|
|
513
|
+
try {
|
|
514
|
+
const expectedSnapshot = resumed.contentHashState.copy().digest("hex");
|
|
515
|
+
const actualSnapshot = await digestFilePrefix(
|
|
516
|
+
resumeHandle,
|
|
517
|
+
singleEntry.stat.size,
|
|
518
|
+
diagnostics,
|
|
519
|
+
);
|
|
520
|
+
stableAfterParse = actualSnapshot === expectedSnapshot;
|
|
521
|
+
} catch {
|
|
522
|
+
stableAfterParse = false;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (stableAfterParse) {
|
|
526
|
+
const delta = splitCapturedParse(resumed);
|
|
527
|
+
captured = {
|
|
528
|
+
parsed: mergeParsedSessionResults(cachedGroup.parsed, delta.parsed),
|
|
529
|
+
resumeState: delta.resumeState,
|
|
530
|
+
endOffset: delta.endOffset,
|
|
531
|
+
appendable: delta.appendable,
|
|
532
|
+
contentHashState: delta.contentHashState,
|
|
533
|
+
};
|
|
534
|
+
diagnostics.incremental_parse_hits += 1;
|
|
535
|
+
} else {
|
|
536
|
+
diagnostics.incremental_parse_fallbacks += 1;
|
|
537
|
+
}
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (!isCodexResumeInvalidated(error)) throw error;
|
|
540
|
+
diagnostics.incremental_parse_fallbacks += 1;
|
|
541
|
+
} finally {
|
|
542
|
+
await resumeHandle.close();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!captured) {
|
|
547
|
+
let freshHandle = null;
|
|
548
|
+
let freshEndOffset = singleEntry?.stat?.size ?? null;
|
|
549
|
+
try {
|
|
550
|
+
if (resumeHandle && singleEntry) {
|
|
551
|
+
freshHandle = await fs.promises.open(singleEntry.filePath, "r");
|
|
552
|
+
if (diagnostics) diagnostics.stat_calls += 1;
|
|
553
|
+
freshEndOffset = (await freshHandle.stat()).size;
|
|
554
|
+
}
|
|
555
|
+
const fresh = await parseCodexRolloutFile(
|
|
556
|
+
filePaths.length === 1 ? filePaths[0] : filePaths,
|
|
557
|
+
{
|
|
558
|
+
from: fromKey,
|
|
559
|
+
to: toKey,
|
|
560
|
+
timeZoneContext,
|
|
561
|
+
diagnostics,
|
|
562
|
+
seenTokenEvents: filePaths.length === 1 ? new Set() : seenTokenEvents,
|
|
563
|
+
endOffset: freshEndOffset,
|
|
564
|
+
captureResumeState: Boolean(singleEntry),
|
|
565
|
+
captureContentHash: appendHashEligible,
|
|
566
|
+
sourceHandle: freshHandle,
|
|
567
|
+
},
|
|
568
|
+
);
|
|
569
|
+
captured = splitCapturedParse(fresh);
|
|
570
|
+
} finally {
|
|
571
|
+
await freshHandle?.close();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
parsed = captured.parsed;
|
|
576
|
+
if (parsedCacheKey) {
|
|
577
|
+
rememberParsedGroup(parsedCacheKey, {
|
|
578
|
+
signature,
|
|
579
|
+
parsed,
|
|
580
|
+
resumeState: captured.resumeState,
|
|
581
|
+
endOffset: captured.endOffset,
|
|
582
|
+
appendable: captured.appendable === true,
|
|
583
|
+
contentHashState: captured.contentHashState,
|
|
584
|
+
filePath: singleEntry?.filePath || null,
|
|
585
|
+
stat: singleEntry?.stat || null,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
271
588
|
}
|
|
272
589
|
if (!parsed || !parsed.totals || !parsed.totals.total_tokens) continue;
|
|
273
590
|
sessions.push(parsed);
|