tokentracker-cli 0.80.0 → 0.80.2
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-D9jOMhlD.js → AchievementBadge-lZzUk8UJ.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DuswZa4g.js → AchievementsPage-Ddht3Szx.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BJPeE1Uq.js → AchievementsSection-Bz9Enx-j.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-nNPIsy6i.js → BadgeDetailModal-C7QiFDH4.js} +1 -1
- package/dashboard/dist/assets/{Card-VENq8eEp.js → Card-Cs_xyoXz.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-Cq7TW0vv.js → ClawdAnimated-C1Y6W_Di.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-y2VftQuq.js → CommunityStatsModal-B1VvGpUY.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CWgnJ4hm.js → ConfirmModal-Cd-fM8lV.js} +1 -1
- package/dashboard/dist/assets/{Controls-BUQ_iZZq.js → Controls-D0ULfwWh.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-DXbecrQR.js +19 -0
- package/dashboard/dist/assets/{DevicePage-Dh3XDtNx.js → DevicePage-BJsSXmi0.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-KTFU_B3F.js → DialogClose-BkG6LVKN.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DDDcU_07.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-bza2pIek.js → DialogTitle-Do2zDkSF.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-Dm3FVNfr.js → FadeIn-BlxFR4Uo.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CbSElzLt.js → HeaderGithubStar-CsLBMMBv.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BDON7VCU.js → IpCheckPage-Bi1QPsiQ.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Ck3CwZrt.js → LandingPage-Ble4Qxzj.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-GVUy0Ynq.js → LeaderboardAvatar-D2Iqj9VP.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-C19Rkt44.js → LeaderboardPage-DImECEuN.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-QcEq_o6E.js → LeaderboardProfileModal-BGkNzlpU.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-pFhI6292.js → LeaderboardProfilePage-f-E74S02.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-ChV3Qezd.js → LimitsPage-BgTxwSrO.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-ALvpjznF.js → LocalOnlyNotice-D91Lp2zA.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CL-QrJxU.js → LoginPage-hbsmEr6V.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DSuvdGgd.js → PetPage-CIj7VSZN.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-jSDQ87Bi.js → PopoverPopup-DMgjzzjH.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-Dk2IDAJq.js → ResetPasswordPage-Cv1mRgno.js} +1 -1
- package/dashboard/dist/assets/{Select-Cz6WTj7D.js → Select-DvneEQak.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BZyRjWCM.js → SelectItemText-BQlJKuRz.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-CANz8HMM.js → SettingsPage-DpnL6Jm6.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-WZkQG_83.js → SkillsPage-Cz7d619N.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BEyJ62NV.js → WidgetsPage-BP1zxfmh.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Ck_dOx_w.js → WrappedPage-2eIams41.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-DpjHiAbZ.js → agent-logos-C0_7FQ0s.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-C4iYPBtK.js → arrow-up-right-DBYYRxgR.js} +1 -1
- package/dashboard/dist/assets/check-B1MZEuWp.js +1 -0
- package/dashboard/dist/assets/{copy-CHJ-38K6.js → copy-BxhpNvJX.js} +1 -1
- package/dashboard/dist/assets/{download-BSeG8u17.js → download-DAvxmQ3t.js} +1 -1
- package/dashboard/dist/assets/{flame-DeHwKcE-.js → flame-DX9uRr7X.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-B-MJYJyp.js → format-tokens-mMtru3Ww.js} +1 -1
- package/dashboard/dist/assets/{info-DA_0xr_o.js → info-BLfeChk0.js} +1 -1
- package/dashboard/dist/assets/{link-2-noUH83Lc.js → link-2-JVAqARKl.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-wL7KZtm0.js → loader-circle--bezqxUZ.js} +1 -1
- package/dashboard/dist/assets/{main-BHfJDFcu.js → main-8AbVGr5W.js} +16 -16
- package/dashboard/dist/assets/{provider-display-DxIClOGM.js → provider-display-DUwBv-X6.js} +1 -1
- package/dashboard/dist/assets/{trash-2-B8TPUmxZ.js → trash-2-Z1EIC4PI.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-CXD3MRRE.js → use-community-stats-Dr1q98cg.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-C2pJVYTn.js → use-limits-display-prefs-DnacsQ0f.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BeOBLEZa.js → use-native-settings-BgBQK2b0.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-kAtW8pJm.js → use-ordered-list-BBs8mEfc.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-CFZUZM3S.js → use-quality-per-dollar-pref-BSagz4Mk.js} +1 -1
- package/dashboard/dist/assets/{use-transform-B9YwpyKi.js → use-transform-CL6RuAA2.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DSX5iKPE.js → use-usage-limits-CzNniz6Z.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CKg5K4Nj.js → useCurrency-ByQkX7uG.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DyyY_Vyt.js → useScrollLock-D5wf8_sp.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-BpdwKlbn.js +1 -0
- package/dashboard/dist/assets/{zap-B69x9NCT.js → zap-v3s0o4Ig.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/init.js +596 -6
- package/src/commands/sync.js +16 -1
- package/src/lib/codex-config.js +135 -40
- package/src/lib/codex-context-breakdown.js +123 -43
- package/src/lib/codex-rollout-parser.js +329 -32
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +167 -8
- package/dashboard/dist/assets/DashboardPage-WkiygMmq.js +0 -19
- package/dashboard/dist/assets/DialogDescription-DIra288n.js +0 -1
- package/dashboard/dist/assets/check-BkPUxPZV.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-FfTjEH_m.js +0 -1
|
@@ -19,6 +19,11 @@ const {
|
|
|
19
19
|
buildExecStatsEntry,
|
|
20
20
|
} = require("./categorizer-utils");
|
|
21
21
|
|
|
22
|
+
const DISCOVERY_FULL_AUDIT_INTERVAL_MS = 60 * 1000;
|
|
23
|
+
const MAX_DISCOVERY_INVENTORIES = 32;
|
|
24
|
+
const DISCOVERY_INVENTORIES = new Map();
|
|
25
|
+
const ZONED_DAY_FORMATTERS = new Map();
|
|
26
|
+
|
|
22
27
|
// ---------------------------------------------------------------------------
|
|
23
28
|
// Timezone helpers
|
|
24
29
|
// ---------------------------------------------------------------------------
|
|
@@ -52,13 +57,18 @@ function getZonedParts(date, timeZoneContext = {}) {
|
|
|
52
57
|
|
|
53
58
|
if (timeZone && typeof Intl !== "undefined" && Intl.DateTimeFormat) {
|
|
54
59
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
let formatter = ZONED_DAY_FORMATTERS.get(timeZone);
|
|
61
|
+
if (!formatter) {
|
|
62
|
+
formatter = new Intl.DateTimeFormat("en-CA", {
|
|
63
|
+
timeZone,
|
|
64
|
+
year: "numeric",
|
|
65
|
+
month: "2-digit",
|
|
66
|
+
day: "2-digit",
|
|
67
|
+
hourCycle: "h23",
|
|
68
|
+
});
|
|
69
|
+
ZONED_DAY_FORMATTERS.set(timeZone, formatter);
|
|
70
|
+
}
|
|
71
|
+
return formatter.formatToParts(dt);
|
|
62
72
|
} catch {
|
|
63
73
|
// Fall through to offset handling.
|
|
64
74
|
}
|
|
@@ -98,8 +108,9 @@ function isTimestampInRequestedDayRange(timestamp, { from, to, timeZoneContext }
|
|
|
98
108
|
// File discovery
|
|
99
109
|
// ---------------------------------------------------------------------------
|
|
100
110
|
|
|
101
|
-
function safeJsonParse(str) {
|
|
111
|
+
function safeJsonParse(str, diagnostics = null) {
|
|
102
112
|
try {
|
|
113
|
+
if (diagnostics) diagnostics.json_parse_calls += 1;
|
|
103
114
|
return JSON.parse(str);
|
|
104
115
|
} catch {
|
|
105
116
|
return null;
|
|
@@ -133,6 +144,210 @@ function listJsonlFiles(rootDir) {
|
|
|
133
144
|
return out;
|
|
134
145
|
}
|
|
135
146
|
|
|
147
|
+
function rolloutPathDay(filePath) {
|
|
148
|
+
const normalized = String(filePath || "").replaceAll("\\", "/");
|
|
149
|
+
const fileMatch = path.basename(normalized).match(/(?:^|rollout-)(\d{4})-(\d{2})-(\d{2})/);
|
|
150
|
+
if (fileMatch) return `${fileMatch[1]}-${fileMatch[2]}-${fileMatch[3]}`;
|
|
151
|
+
const directoryMatch = normalized.match(/.*\/(\d{4})\/(\d{2})\/(\d{2})(?:\/|$)/);
|
|
152
|
+
return directoryMatch
|
|
153
|
+
? `${directoryMatch[1]}-${directoryMatch[2]}-${directoryMatch[3]}`
|
|
154
|
+
: null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function nextDayKey(dayKey) {
|
|
158
|
+
if (!dayKey) return null;
|
|
159
|
+
const date = new Date(`${dayKey}T00:00:00.000Z`);
|
|
160
|
+
if (!Number.isFinite(date.getTime())) return null;
|
|
161
|
+
date.setUTCDate(date.getUTCDate() + 1);
|
|
162
|
+
return date.toISOString().slice(0, 10);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function metadataRangeBounds(from, to, timeZoneContext) {
|
|
166
|
+
const offsetMinutes = timeZoneContext?.offsetMinutes;
|
|
167
|
+
if (Number.isFinite(offsetMinutes)) {
|
|
168
|
+
const dstGuardMs = 2 * 60 * 60 * 1000;
|
|
169
|
+
const fromUtc = from ? Date.parse(`${from}T00:00:00.000Z`) : null;
|
|
170
|
+
const afterTo = to ? nextDayKey(to) : null;
|
|
171
|
+
const toUtcExclusive = afterTo ? Date.parse(`${afterTo}T00:00:00.000Z`) : null;
|
|
172
|
+
return {
|
|
173
|
+
fromMs: Number.isFinite(fromUtc)
|
|
174
|
+
? fromUtc + offsetMinutes * 60_000 - dstGuardMs
|
|
175
|
+
: null,
|
|
176
|
+
toMs: Number.isFinite(toUtcExclusive)
|
|
177
|
+
? toUtcExclusive + offsetMinutes * 60_000 + dstGuardMs
|
|
178
|
+
: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const { fromIso, toIso } = dayKeyToIsoBounds(from, to);
|
|
182
|
+
return {
|
|
183
|
+
fromMs: fromIso ? Date.parse(fromIso) : null,
|
|
184
|
+
toMs: toIso ? Date.parse(toIso) : null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isConservativeRangeCandidate(filePath, stat, {
|
|
189
|
+
from = null,
|
|
190
|
+
to = null,
|
|
191
|
+
timeZoneContext = null,
|
|
192
|
+
metadataBounds = null,
|
|
193
|
+
} = {}) {
|
|
194
|
+
if (!from && !to) return true;
|
|
195
|
+
// With no lower bound, a later-path rewrite can still contain an event at or
|
|
196
|
+
// before `to`. There is no metadata-only exclusion that can prove otherwise.
|
|
197
|
+
if (!from) return true;
|
|
198
|
+
const pathDay = rolloutPathDay(filePath);
|
|
199
|
+
if (!pathDay) return true;
|
|
200
|
+
|
|
201
|
+
const pathOverlaps = (!from || pathDay >= from) && (!to || pathDay <= to);
|
|
202
|
+
if (pathOverlaps) return true;
|
|
203
|
+
|
|
204
|
+
// Old sessions can be appended or rewritten. A post-range mtime or ctime is
|
|
205
|
+
// therefore a reason to parse, never a reason to exclude. ctime also catches
|
|
206
|
+
// cold-process inode replacement/truncation when a writer preserves mtime.
|
|
207
|
+
// Future-dated decoys are kept conservatively; correctness wins over an open.
|
|
208
|
+
const bounds = metadataBounds || metadataRangeBounds(from, to, timeZoneContext);
|
|
209
|
+
const fromMs = bounds.fromMs;
|
|
210
|
+
const toMs = bounds.toMs;
|
|
211
|
+
const mtimeMs = Number(stat?.mtimeMs);
|
|
212
|
+
const ctimeMs = Number(stat?.ctimeMs);
|
|
213
|
+
const changedAfterStart = (value) => (
|
|
214
|
+
Number.isFinite(value) && (!Number.isFinite(fromMs) || value >= fromMs)
|
|
215
|
+
);
|
|
216
|
+
const changedInsideRange = (value) => (
|
|
217
|
+
changedAfterStart(value) && (!Number.isFinite(toMs) || value <= toMs)
|
|
218
|
+
);
|
|
219
|
+
if (pathDay < from) {
|
|
220
|
+
return changedAfterStart(mtimeMs) || changedAfterStart(ctimeMs);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// A path one day ahead can still overlap the requested browser day because
|
|
224
|
+
// rollout folders follow the writer's local date. Its current mtime may be
|
|
225
|
+
// later if the same session kept growing, so metadata alone cannot exclude it.
|
|
226
|
+
if (to && pathDay === nextDayKey(to)) return true;
|
|
227
|
+
|
|
228
|
+
// A host-local path day can be later than the requested browser day when
|
|
229
|
+
// their timezones differ. Keep it only when filesystem metadata maps back
|
|
230
|
+
// into the requested day range; do not pull every genuinely future session
|
|
231
|
+
// into a historical query.
|
|
232
|
+
return changedInsideRange(mtimeMs) || changedInsideRange(ctimeMs);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function isRolloutPathInRequestedRange(filePath, { from = null, to = null } = {}) {
|
|
236
|
+
const pathDay = rolloutPathDay(filePath);
|
|
237
|
+
if (!pathDay) return true;
|
|
238
|
+
if (from && pathDay < from) return false;
|
|
239
|
+
if (to && pathDay > to) return false;
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function discoveryInventoryKey(roots) {
|
|
244
|
+
return roots.map((root) => path.resolve(root)).sort().join("\0");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getDiscoveryInventory(roots, nowMs) {
|
|
248
|
+
const key = discoveryInventoryKey(roots);
|
|
249
|
+
let inventory = DISCOVERY_INVENTORIES.get(key);
|
|
250
|
+
if (!inventory) {
|
|
251
|
+
inventory = { files: new Map(), lastFullAuditAtMs: 0, lastUsedAtMs: nowMs };
|
|
252
|
+
DISCOVERY_INVENTORIES.set(key, inventory);
|
|
253
|
+
}
|
|
254
|
+
inventory.lastUsedAtMs = nowMs;
|
|
255
|
+
while (DISCOVERY_INVENTORIES.size > MAX_DISCOVERY_INVENTORIES) {
|
|
256
|
+
let oldestKey = null;
|
|
257
|
+
let oldestAt = Infinity;
|
|
258
|
+
for (const [candidateKey, candidate] of DISCOVERY_INVENTORIES) {
|
|
259
|
+
if (candidateKey === key) continue;
|
|
260
|
+
if (candidate.lastUsedAtMs < oldestAt) {
|
|
261
|
+
oldestAt = candidate.lastUsedAtMs;
|
|
262
|
+
oldestKey = candidateKey;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!oldestKey) break;
|
|
266
|
+
DISCOVERY_INVENTORIES.delete(oldestKey);
|
|
267
|
+
}
|
|
268
|
+
return inventory;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function discoverCodexSessionFiles(rootDirs, options = {}) {
|
|
272
|
+
const diagnostics = options.diagnostics || null;
|
|
273
|
+
const roots = Array.isArray(rootDirs) ? rootDirs : [rootDirs];
|
|
274
|
+
const nowMs = Number.isFinite(Number(options.nowMs)) ? Number(options.nowMs) : Date.now();
|
|
275
|
+
const inventory = getDiscoveryInventory(roots.filter(Boolean), nowMs);
|
|
276
|
+
const candidateOptions = {
|
|
277
|
+
...options,
|
|
278
|
+
metadataBounds: metadataRangeBounds(options.from, options.to, options.timeZoneContext),
|
|
279
|
+
};
|
|
280
|
+
const bounded = Boolean(options.from || options.to);
|
|
281
|
+
const auditAgeMs = nowMs - Number(inventory.lastFullAuditAtMs || 0);
|
|
282
|
+
const fullMetadataAudit = Boolean(
|
|
283
|
+
options.exhaustive ||
|
|
284
|
+
!bounded ||
|
|
285
|
+
!Number.isFinite(auditAgeMs) ||
|
|
286
|
+
auditAgeMs < 0 ||
|
|
287
|
+
auditAgeMs >= DISCOVERY_FULL_AUDIT_INTERVAL_MS
|
|
288
|
+
);
|
|
289
|
+
if (diagnostics) diagnostics.full_metadata_audit = fullMetadataAudit;
|
|
290
|
+
const entries = [];
|
|
291
|
+
const visited = new Set();
|
|
292
|
+
const stack = roots.filter(Boolean);
|
|
293
|
+
|
|
294
|
+
while (stack.length > 0) {
|
|
295
|
+
const dir = stack.pop();
|
|
296
|
+
let children;
|
|
297
|
+
try {
|
|
298
|
+
children = fs.readdirSync(dir, { withFileTypes: true });
|
|
299
|
+
} catch {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
for (const child of children) {
|
|
303
|
+
const filePath = path.join(dir, child.name);
|
|
304
|
+
if (child.isDirectory()) {
|
|
305
|
+
stack.push(filePath);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (!child.isFile() || !child.name.endsWith(".jsonl") || visited.has(filePath)) continue;
|
|
309
|
+
visited.add(filePath);
|
|
310
|
+
if (diagnostics) diagnostics.discovered_files += 1;
|
|
311
|
+
const cached = inventory.files.get(filePath) || null;
|
|
312
|
+
const cachedCandidate = cached
|
|
313
|
+
? isConservativeRangeCandidate(filePath, cached, candidateOptions)
|
|
314
|
+
: false;
|
|
315
|
+
const shouldStat = Boolean(
|
|
316
|
+
fullMetadataAudit ||
|
|
317
|
+
!cached ||
|
|
318
|
+
cachedCandidate ||
|
|
319
|
+
isRolloutPathInRequestedRange(filePath, options)
|
|
320
|
+
);
|
|
321
|
+
let stat = cached;
|
|
322
|
+
if (shouldStat) {
|
|
323
|
+
try {
|
|
324
|
+
if (diagnostics) diagnostics.stat_calls += 1;
|
|
325
|
+
stat = fs.statSync(filePath);
|
|
326
|
+
inventory.files.set(filePath, stat);
|
|
327
|
+
} catch {
|
|
328
|
+
inventory.files.delete(filePath);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
} else if (diagnostics) {
|
|
332
|
+
diagnostics.metadata_cache_hits += 1;
|
|
333
|
+
}
|
|
334
|
+
const candidate = Boolean(
|
|
335
|
+
options.exhaustive ||
|
|
336
|
+
isConservativeRangeCandidate(filePath, stat, candidateOptions),
|
|
337
|
+
);
|
|
338
|
+
entries.push({ filePath, stat, candidate });
|
|
339
|
+
if (candidate && diagnostics) diagnostics.candidate_files += 1;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (const filePath of inventory.files.keys()) {
|
|
344
|
+
if (!visited.has(filePath)) inventory.files.delete(filePath);
|
|
345
|
+
}
|
|
346
|
+
if (fullMetadataAudit) inventory.lastFullAuditAtMs = nowMs;
|
|
347
|
+
|
|
348
|
+
return entries.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
349
|
+
}
|
|
350
|
+
|
|
136
351
|
async function listCodexSessionFiles(baseDir) {
|
|
137
352
|
const rolloutFiles = await listRolloutFiles(baseDir).catch(() => []);
|
|
138
353
|
const allJsonlFiles = listJsonlFiles(baseDir);
|
|
@@ -172,9 +387,9 @@ function normalizeToolName(payload) {
|
|
|
172
387
|
return name || "unknown";
|
|
173
388
|
}
|
|
174
389
|
|
|
175
|
-
function extractSkillNameFromFunctionCall(payload) {
|
|
390
|
+
function extractSkillNameFromFunctionCall(payload, diagnostics = null) {
|
|
176
391
|
if (!payload || payload.name !== "exec_command") return null;
|
|
177
|
-
const args = safeJsonParse(payload.arguments || "{}") || {};
|
|
392
|
+
const args = safeJsonParse(payload.arguments || "{}", diagnostics) || {};
|
|
178
393
|
const cmd = String(args.cmd || "");
|
|
179
394
|
const match = cmd.match(/(?:^|\/)skills\/(?:\.system\/)?([^/\s]+)\/SKILL\.md\b/);
|
|
180
395
|
return match ? match[1] : null;
|
|
@@ -334,9 +549,15 @@ function finalizeExecRows(map) {
|
|
|
334
549
|
// Main parser
|
|
335
550
|
// ---------------------------------------------------------------------------
|
|
336
551
|
|
|
337
|
-
async function parseCodexRolloutFile(filePath, {
|
|
338
|
-
|
|
339
|
-
|
|
552
|
+
async function parseCodexRolloutFile(filePath, {
|
|
553
|
+
from = null,
|
|
554
|
+
to = null,
|
|
555
|
+
timeZoneContext = null,
|
|
556
|
+
diagnostics = null,
|
|
557
|
+
seenTokenEvents = null,
|
|
558
|
+
} = {}) {
|
|
559
|
+
const filePaths = (Array.isArray(filePath) ? filePath : [filePath]).filter(Boolean);
|
|
560
|
+
const primaryFilePath = filePaths[0] || String(filePath || "");
|
|
340
561
|
|
|
341
562
|
let sessionId = null;
|
|
342
563
|
let cwd = null;
|
|
@@ -430,7 +651,13 @@ async function parseCodexRolloutFile(filePath, { fromIso, toIso, from = null, to
|
|
|
430
651
|
}
|
|
431
652
|
|
|
432
653
|
function attributeTurn(delta) {
|
|
433
|
-
if (!delta || delta.total_tokens <= 0)
|
|
654
|
+
if (!delta || delta.total_tokens <= 0) {
|
|
655
|
+
for (const p of pendingExecEnds) absorbExecEnd(p);
|
|
656
|
+
pendingCalls = [];
|
|
657
|
+
pendingSkills = [];
|
|
658
|
+
pendingExecEnds = [];
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
434
661
|
turnCount += 1;
|
|
435
662
|
|
|
436
663
|
const toolNames = pendingCalls
|
|
@@ -517,19 +744,10 @@ async function parseCodexRolloutFile(filePath, { fromIso, toIso, from = null, to
|
|
|
517
744
|
pendingExecEnds = [];
|
|
518
745
|
}
|
|
519
746
|
|
|
520
|
-
for await (const
|
|
521
|
-
if (!line) continue;
|
|
522
|
-
let obj;
|
|
523
|
-
try {
|
|
524
|
-
obj = JSON.parse(line);
|
|
525
|
-
} catch {
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
747
|
+
for await (const obj of iterateCodexObjects(filePaths, diagnostics)) {
|
|
528
748
|
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
529
749
|
if (!ts) continue;
|
|
530
|
-
|
|
531
|
-
if (toIso && ts > toIso) continue;
|
|
532
|
-
if (!isTimestampInRequestedDayRange(ts, { from, to, timeZoneContext })) continue;
|
|
750
|
+
const inRequestedRange = isTimestampInRequestedDayRange(ts, { from, to, timeZoneContext });
|
|
533
751
|
|
|
534
752
|
if (obj.type === "session_meta") {
|
|
535
753
|
const p = obj.payload || {};
|
|
@@ -548,7 +766,7 @@ async function parseCodexRolloutFile(filePath, { fromIso, toIso, from = null, to
|
|
|
548
766
|
|
|
549
767
|
if (obj.type === "response_item" && obj.payload?.type === "function_call") {
|
|
550
768
|
pendingCalls.push(obj.payload);
|
|
551
|
-
const skill = extractSkillNameFromFunctionCall(obj.payload);
|
|
769
|
+
const skill = extractSkillNameFromFunctionCall(obj.payload, diagnostics);
|
|
552
770
|
if (skill) pendingSkills.push(skill);
|
|
553
771
|
continue;
|
|
554
772
|
}
|
|
@@ -565,21 +783,31 @@ async function parseCodexRolloutFile(filePath, { fromIso, toIso, from = null, to
|
|
|
565
783
|
const totalUsage = info?.total_token_usage;
|
|
566
784
|
const delta = pickDelta(lastUsage, totalUsage, prevTotals);
|
|
567
785
|
if (totalUsage && typeof totalUsage === "object") prevTotals = totalUsage;
|
|
568
|
-
if (
|
|
786
|
+
if (inRequestedRange) {
|
|
787
|
+
const eventSessionId = sessionId || rolloutSessionIdFromPath(primaryFilePath) || primaryFilePath;
|
|
788
|
+
const eventKey = `${eventSessionId}:${ts}:${JSON.stringify({ lastUsage, totalUsage })}`;
|
|
789
|
+
if (seenTokenEvents && seenTokenEvents.has(eventKey)) {
|
|
790
|
+
attributeTurn(null);
|
|
791
|
+
} else {
|
|
792
|
+
if (seenTokenEvents && delta?.total_tokens > 0) seenTokenEvents.add(eventKey);
|
|
793
|
+
attributeTurn(delta);
|
|
794
|
+
}
|
|
795
|
+
} else {
|
|
796
|
+
pendingCalls = [];
|
|
797
|
+
pendingSkills = [];
|
|
798
|
+
pendingExecEnds = [];
|
|
799
|
+
}
|
|
569
800
|
continue;
|
|
570
801
|
}
|
|
571
802
|
}
|
|
572
803
|
|
|
573
|
-
rl.close();
|
|
574
|
-
stream.close?.();
|
|
575
|
-
|
|
576
804
|
return {
|
|
577
|
-
sessionId,
|
|
805
|
+
sessionId: sessionId || rolloutSessionIdFromPath(primaryFilePath) || primaryFilePath,
|
|
578
806
|
cwd,
|
|
579
807
|
model: model || provider,
|
|
580
808
|
provider,
|
|
581
809
|
version: cliVersion,
|
|
582
|
-
filePath,
|
|
810
|
+
filePath: primaryFilePath,
|
|
583
811
|
turnCount,
|
|
584
812
|
totals,
|
|
585
813
|
toolBreakdown: {
|
|
@@ -599,6 +827,71 @@ async function parseCodexRolloutFile(filePath, { fromIso, toIso, from = null, to
|
|
|
599
827
|
};
|
|
600
828
|
}
|
|
601
829
|
|
|
830
|
+
async function* readCodexObjects(filePath, diagnostics, fileIndex) {
|
|
831
|
+
if (diagnostics) diagnostics.opened_files += 1;
|
|
832
|
+
const stream = fs.createReadStream(filePath, { encoding: "utf8" });
|
|
833
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
834
|
+
let lineIndex = 0;
|
|
835
|
+
try {
|
|
836
|
+
for await (const line of rl) {
|
|
837
|
+
if (!line) continue;
|
|
838
|
+
let obj;
|
|
839
|
+
try {
|
|
840
|
+
if (diagnostics) diagnostics.json_parse_calls += 1;
|
|
841
|
+
obj = JSON.parse(line);
|
|
842
|
+
} catch {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
yield { obj, fileIndex, lineIndex };
|
|
846
|
+
lineIndex += 1;
|
|
847
|
+
}
|
|
848
|
+
} finally {
|
|
849
|
+
rl.close();
|
|
850
|
+
stream.close?.();
|
|
851
|
+
if (diagnostics) diagnostics.parsed_files += 1;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function* iterateCodexObjects(filePaths, diagnostics) {
|
|
856
|
+
if (filePaths.length <= 1) {
|
|
857
|
+
for await (const record of readCodexObjects(filePaths[0], diagnostics, 0)) {
|
|
858
|
+
yield record.obj;
|
|
859
|
+
}
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// A live rollout can overlap its archived copy or be split into prefix/suffix
|
|
864
|
+
// fragments. Merge only duplicate-session groups, order by event time, and
|
|
865
|
+
// suppress byte-semantically identical records from different files so the
|
|
866
|
+
// cumulative baseline is established exactly once.
|
|
867
|
+
const records = [];
|
|
868
|
+
for (let fileIndex = 0; fileIndex < filePaths.length; fileIndex += 1) {
|
|
869
|
+
for await (const record of readCodexObjects(filePaths[fileIndex], diagnostics, fileIndex)) {
|
|
870
|
+
records.push(record);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
records.sort((a, b) => {
|
|
874
|
+
const aTimestamp = typeof a.obj?.timestamp === "string" ? a.obj.timestamp : "";
|
|
875
|
+
const bTimestamp = typeof b.obj?.timestamp === "string" ? b.obj.timestamp : "";
|
|
876
|
+
return aTimestamp.localeCompare(bTimestamp) || a.fileIndex - b.fileIndex || a.lineIndex - b.lineIndex;
|
|
877
|
+
});
|
|
878
|
+
const firstFileByRecord = new Map();
|
|
879
|
+
for (const record of records) {
|
|
880
|
+
const semanticKey = JSON.stringify(record.obj);
|
|
881
|
+
const firstFile = firstFileByRecord.get(semanticKey);
|
|
882
|
+
if (firstFile !== undefined && firstFile !== record.fileIndex) continue;
|
|
883
|
+
if (firstFile === undefined) firstFileByRecord.set(semanticKey, record.fileIndex);
|
|
884
|
+
yield record.obj;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function rolloutSessionIdFromPath(filePath) {
|
|
889
|
+
const match = path.basename(String(filePath || "")).match(
|
|
890
|
+
/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i,
|
|
891
|
+
);
|
|
892
|
+
return match ? match[1] : null;
|
|
893
|
+
}
|
|
894
|
+
|
|
602
895
|
module.exports = {
|
|
603
896
|
parseCodexRolloutFile,
|
|
604
897
|
extractTokenCount,
|
|
@@ -610,12 +903,16 @@ module.exports = {
|
|
|
610
903
|
totalsReset,
|
|
611
904
|
listJsonlFiles,
|
|
612
905
|
listCodexSessionFiles,
|
|
906
|
+
discoverCodexSessionFiles,
|
|
907
|
+
rolloutPathDay,
|
|
908
|
+
isConservativeRangeCandidate,
|
|
613
909
|
safeJsonParse,
|
|
614
910
|
dayKeyToIsoBounds,
|
|
615
911
|
formatPartsDayKey,
|
|
616
912
|
getZonedParts,
|
|
617
913
|
timestampDayKey,
|
|
618
914
|
isTimestampInRequestedDayRange,
|
|
915
|
+
rolloutSessionIdFromPath,
|
|
619
916
|
finalizeToolRows,
|
|
620
917
|
finalizeSkillRows,
|
|
621
918
|
finalizeExecRows,
|