tokentracker-cli 1.4.4 → 1.4.6
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/README.de.md +1 -0
- package/README.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dashboard/dist/assets/{AchievementBadge-slL-XBCu.js → AchievementBadge-BmWRsZMx.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-BeFkGc5M.js → AchievementsPage-CyIP2nck.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BrGUIipD.js → AchievementsSection-BeNyJ0LW.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-avmK1ODc.js → BadgeDetailModal-DIFnFzkv.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-DaEPdEZi.js → BotAnimated-CKVeJHit.js} +1 -1
- package/dashboard/dist/assets/{Card-CIZCccTA.js → Card-lQmyKbh4.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-BhRj0SYO.js → CloudCheckoutPage-B_8y6dyX.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-CvaN8a54.js → CloudPage-BOQjUGAt.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-BUNuXmYF.js → CommandPalette-BV9LqokI.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BCI0WHPr.js → CommunityStatsModal-WELxKEFc.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CT7IwtLR.js → ConfirmModal-B8H5o8c_.js} +1 -1
- package/dashboard/dist/assets/{Controls-CtnMyxNH.js → Controls-9ynPx9rF.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BuzyITeL.js → DashboardPage-B8z-U63p.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-1Ps1-1dw.js → DateRangePopover-DEqWsV_8.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-Dt-dVQAG.js → DevicePage-CUOlUySM.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-D-DRTF1u.js → DialogClose-Dlg52bL6.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-vuQFBlM_.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-CiiRTn3_.js → DialogTitle-CduwXjqH.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-k6RFJ2qC.js → ElectricLogo-DFt7_F2f.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CNHg83aK.js → FadeIn-DlyusE6n.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CIYW5dc3.js → HeaderGithubStar-tUtJoFHh.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-Dn80DyPM.js → HoverTooltip-v-zkCqsX.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-B29dYfoI.js → IpCheckPage-C5Y1uVEr.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Dzc8_QyW.js → LandingPage-DzOJOAj7.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-DXPcIhyQ.js → LeaderboardPage-BBZ_6tCw.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-DxEdj2Zq.js → LeaderboardProBadge-DakoXIQn.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-DnKY2223.js → LeaderboardProfileModal-CINpES_x.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CY94ngqy.js → LeaderboardProfilePage-B6ISf-1l.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-AlvnAPCD.js → LimitsPage-UVX730CQ.js} +8 -8
- package/dashboard/dist/assets/{LocalOnlyNotice-EHw7BeYS.js → LocalOnlyNotice-BJSsmXF1.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-E5E3i2kj.js → LoginCard-Dggv2aGv.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DxhQI9kh.js → LoginModal-B8qDd2kY.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DRwJn-Fw.js → LoginPage-Dv66U8yJ.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DIpVTdiC.js → PetPage-CNwrQdKM.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-Bjs7E91g.js → PopoverPopup-CqmvpYOi.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-DqXIjym-.js → ProviderIcon-BF2Ch7wi.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DxSRYQgP.js → ResetPasswordPage-B0R8VUU3.js} +1 -1
- package/dashboard/dist/assets/{Select-BfjrcaiM.js → Select-idAiBnJG.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Dpdlb0cU.js → SelectItemText-DPmmPY_D.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-Cnp_PMaD.js → SelfHostPage-oR9cPK02.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-fBwG-qXq.js → ServiceStatusPage-CdV9gfGF.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-BEk7AHdq.js → SessionsPage-BkD34VlL.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-BgML2jQR.js → SettingsPage-DsthWCyM.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-DcvVZ3Wn.js → SkillsPage-CzMk9s3x.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-D29yX2Hb.js → TokenGalaxy-DQ6o8YBp.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-CjzmNK2l.js → TooltipPopup-CMQjA-HE.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-DfnQrsrx.js → TrendMonitor-DrV1m8E7.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CRYomYfk.js → WidgetsPage-Cc16WM0Y.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-CvQI5zUw.js → WrappedPage-pMwbM4R7.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BZ-i40Kf.js → __vite-browser-external-DCy_YLfe.js} +3 -3
- package/dashboard/dist/assets/{agent-logos-CvOX7WXW.js → agent-logos-Zw7LNkMO.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-BrQrZJtW.js → arrow-left-DycYjE-k.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-fC11gmFY.js → arrow-right-uNxLHoJ0.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CCMEqQQp.js → arrow-up-right-CmdJ7gd7.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-OaIQ06qD.js → calendar-days-CJ33_t5O.js} +1 -1
- package/dashboard/dist/assets/{check-D888uFpx.js → check-Dbubk4Il.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-BJ0BpS_w.js → chevron-down-BwvBVAOs.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-BeOpEJTZ.js → cloud-checkout-IG-IOpeP.js} +1 -1
- package/dashboard/dist/assets/{composite-CUuXvKos.js → composite-IIgJPPIw.js} +1 -1
- package/dashboard/dist/assets/{download-BJ7CKwx5.js → download-DxtH7w6a.js} +1 -1
- package/dashboard/dist/assets/{external-link-CDaZwJVN.js → external-link-evKf3mnD.js} +1 -1
- package/dashboard/dist/assets/{flame-ChyUKs-9.js → flame-D2qOE_W4.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-B6WX7zcH.js → format-tokens-CqDmm-sK.js} +1 -1
- package/dashboard/dist/assets/{gift-CQlAZUJ5.js → gift-Ge11HK1x.js} +1 -1
- package/dashboard/dist/assets/{host-mode-Gw6KD5OJ.js → host-mode-8_TNh7yE.js} +1 -1
- package/dashboard/dist/assets/{icons-C7gGTMuQ.js → icons-OmfWvE5K.js} +1 -1
- package/dashboard/dist/assets/{index-Bm2dDAVL.js → index-BYuEzP9p.js} +1 -1
- package/dashboard/dist/assets/{index-CLb64Tlj.js → index-BsYtyo4U.js} +1 -1
- package/dashboard/dist/assets/{index-C0pbymQa.js → index-tbe1aERA.js} +1 -1
- package/dashboard/dist/assets/{info-CvOmd88O.js → info-xhrso9aO.js} +1 -1
- package/dashboard/dist/assets/{link-2-BNEPxvlF.js → link-2-BgNpTaO4.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-bRHxCgY3.js → loader-circle-DRooZ0e5.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-D-X0-uvt.js → modifiers.esm-D-QAEpOZ.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-Bo7d-q73.js → monitor-smartphone-Bwu7__2B.js} +1 -1
- package/dashboard/dist/assets/{play-DqpGELN5.js → play-CBPvTiWM.js} +1 -1
- package/dashboard/dist/assets/{plus-2fA1tz7S.js → plus-B4lFIyP8.js} +1 -1
- package/dashboard/dist/assets/{provider-display-B1KigDYZ.js → provider-display-CQlWusXH.js} +1 -1
- package/dashboard/dist/assets/{react-DYja9tZG.js → react-DDOtS4Bd.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-BerB8y-A.js → safePolygon-BGfBVKoy.js} +1 -1
- package/dashboard/dist/assets/{search-CU6UVI1L.js → search-DVyNq72J.js} +1 -1
- package/dashboard/dist/assets/{server-CZR4PTdb.js → server-EpUTEsOf.js} +1 -1
- package/dashboard/dist/assets/{shield-check-BGbNmrMP.js → shield-check-DRg8cPKV.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CJLEzxDf.js → skills-api-y42dEF96.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-CJ1NZWER.js → sortable.esm-D2S1gXCK.js} +1 -1
- package/dashboard/dist/assets/{store-CJFdru_6.js → store-DfGPS2IR.js} +1 -1
- package/dashboard/dist/assets/{terminal-ClyQ8tvQ.js → terminal-hXIrrL2b.js} +1 -1
- package/dashboard/dist/assets/{trash-Dg2PWl5-.js → trash-cjaASRQl.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-Ca995kap.js → use-community-stats-B-nipjWt.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BKbMGVji.js → use-latest-request-guard-DIEYrSzn.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CGyd8lvc.js → use-limits-display-prefs-BdHDwBBC.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-Dqeuqm9O.js → use-native-settings-7w71_h44.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Nj7R1iUu.js → use-ordered-list-CSMpK4ar.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-HUGf223R.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-C_H5BaGV.js → use-session-efficiency-pref-BagBVWW6.js} +1 -1
- package/dashboard/dist/assets/{use-transform-CC90hurD.js → use-transform-sjGGXCNn.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-Nml-wUB-.js +1 -0
- package/dashboard/dist/assets/{useCurrency-CMHIL39_.js → useCurrency-DAKIY1nR.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-CRSfrGQq.js → usePositioner-B7vVQ7th.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-B5jhM_Kb.js → useTokenFormat-gtgw8k2Z.js} +1 -1
- package/dashboard/dist/assets/{zap-CKx8ySZs.js → zap-DFWOiTJ1.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/sync.js +546 -31
- package/src/lib/async-queue-reader.js +37 -16
- package/src/lib/codex-context-breakdown.js +8 -1
- package/src/lib/codex-rollout-parser.js +65 -5
- package/src/lib/grok-limits.js +56 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +356 -8
- package/src/lib/session-analytics.js +1 -1
- package/src/lib/usage-limits.js +33 -1
- package/dashboard/dist/assets/DialogDescription-BOYDS1mM.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-CGMbDpHQ.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-DtaaE7D8.js +0 -1
package/src/commands/sync.js
CHANGED
|
@@ -7,6 +7,7 @@ const readline = require("node:readline");
|
|
|
7
7
|
const { functionUrlFor, fetchFunctionResponse } = require("../lib/function-url");
|
|
8
8
|
const { cloudUsageResetBlocksUpload, finishCloudDeviceUsageReset } = require("../lib/cloud-usage-reset");
|
|
9
9
|
const { KIMI_MODEL_REPAIR_KEY, repairKimiModelAttribution } = require("../lib/kimi-model-repair");
|
|
10
|
+
const { readSqliteJsonRows } = require("../lib/sqlite-reader");
|
|
10
11
|
|
|
11
12
|
const { resolveInstallPaths, resolveZcodeNativeDbPath, resolveMimoNativeDbPath, ensureFlatCursor } = require("../lib/install-resolver");
|
|
12
13
|
const { multiInstallParse, mergeBothFileSources } = require("../lib/multi-install-parser");
|
|
@@ -26,6 +27,7 @@ const { countRecordOnlyFiles, formatRecordOnlyWarning } = require("../lib/codex-
|
|
|
26
27
|
const {
|
|
27
28
|
listRolloutFiles,
|
|
28
29
|
listRolloutFilesDeep,
|
|
30
|
+
readCodexRolloutSnapshot,
|
|
29
31
|
codexSessionIdFromPath,
|
|
30
32
|
filterColdCodexRolloutFiles,
|
|
31
33
|
listClaudeProjectFiles,
|
|
@@ -149,6 +151,7 @@ const {
|
|
|
149
151
|
toUtcHalfHourStart,
|
|
150
152
|
totalsKey,
|
|
151
153
|
claudeMessageDedupKey,
|
|
154
|
+
coerceEpochMs,
|
|
152
155
|
hasZcodeCursorState,
|
|
153
156
|
resolveZcodeInstallPaths,
|
|
154
157
|
} = require("../lib/rollout");
|
|
@@ -311,6 +314,13 @@ const MIMO_PROVIDER_REPAIR_KEY = "mimoClaudeMislabelRepair_2026_06";
|
|
|
311
314
|
const DSH_LEGACY_SOURCE_MIGRATION_KEY = "deepseekHarnessSourceMigration_2026_08";
|
|
312
315
|
const ZCODE_NATIVE_USAGE_REPAIR_KEY = "zcodeNativeUsageRepair_2026_08";
|
|
313
316
|
const ZCODE_INCLUSIVE_TOKEN_REPAIR_KEY = "zcodeInclusiveTokenRepair_2026_09";
|
|
317
|
+
// #828: ZCode CLI prunes model_usage/turn_usage to ~30 days. The wipe+rescan
|
|
318
|
+
// repairs above destroyed pre-window history for late upgraders (and anyone
|
|
319
|
+
// whose prune line advanced before the one-shot key fired). This follow-up
|
|
320
|
+
// restores pre-window peaks from queue.jsonl.bak.* when the live ledger is
|
|
321
|
+
// lower, and the wipe helpers below now preserve hours older than the live
|
|
322
|
+
// rebuild floor so a future re-key cannot repeat the loss.
|
|
323
|
+
const ZCODE_PRUNE_WINDOW_RESTORE_KEY = "zcodePruneWindowRestore_2026_10";
|
|
314
324
|
const AUTO_SYNC_SOURCE_ALIASES = new Map([
|
|
315
325
|
["code", "every-code"],
|
|
316
326
|
["commandcode", "command-code"],
|
|
@@ -1769,6 +1779,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
1769
1779
|
const nativeUsageAvailable = [zcodePaths.native, zcodePaths.wsl]
|
|
1770
1780
|
.filter(Boolean)
|
|
1771
1781
|
.some((dbPath) => hasZcodeNativeUsageSchema(dbPath));
|
|
1782
|
+
const zcodeDbPaths = [zcodePaths.native, zcodePaths.wsl].filter(Boolean);
|
|
1783
|
+
const zcodeRebuildFloorMs = resolveZcodeRebuildFloorMs(zcodeDbPaths);
|
|
1772
1784
|
if (nativeUsageAvailable) {
|
|
1773
1785
|
await repairZcodeNativeUsageMigration({
|
|
1774
1786
|
cursors,
|
|
@@ -1776,6 +1788,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
1776
1788
|
queueStatePath,
|
|
1777
1789
|
projectQueuePath,
|
|
1778
1790
|
projectQueueStatePath,
|
|
1791
|
+
rebuildFloorMs: zcodeRebuildFloorMs,
|
|
1779
1792
|
});
|
|
1780
1793
|
}
|
|
1781
1794
|
await repairZcodeInclusiveTokenMigration({
|
|
@@ -1784,6 +1797,13 @@ async function cmdSync(argv, context = {}) {
|
|
|
1784
1797
|
queueStatePath,
|
|
1785
1798
|
projectQueuePath,
|
|
1786
1799
|
projectQueueStatePath,
|
|
1800
|
+
rebuildFloorMs: zcodeRebuildFloorMs,
|
|
1801
|
+
});
|
|
1802
|
+
await repairZcodePruneWindowRestore({
|
|
1803
|
+
cursors,
|
|
1804
|
+
queuePath,
|
|
1805
|
+
queueStatePath,
|
|
1806
|
+
dbPaths: zcodeDbPaths,
|
|
1787
1807
|
});
|
|
1788
1808
|
zcodeResult = await multiInstallParse({
|
|
1789
1809
|
paths: zcodePaths, parserFn: parseOpencodeDbForInstall, providerName: "zcode",
|
|
@@ -3898,6 +3918,9 @@ module.exports = {
|
|
|
3898
3918
|
repairMimoClaudeMislabel,
|
|
3899
3919
|
repairZcodeNativeUsageMigration,
|
|
3900
3920
|
repairZcodeInclusiveTokenMigration,
|
|
3921
|
+
repairZcodePruneWindowRestore,
|
|
3922
|
+
resolveZcodeRebuildFloorMs,
|
|
3923
|
+
ZCODE_PRUNE_WINDOW_RESTORE_KEY,
|
|
3901
3924
|
reincludeClaudeMemObserverFiles,
|
|
3902
3925
|
repairGrokQueueFromSessionSnapshots,
|
|
3903
3926
|
applyCloudConversationsBackfill,
|
|
@@ -4942,17 +4965,194 @@ async function resetUploadOffsetForZcodeRepair(queueStatePath, note) {
|
|
|
4942
4965
|
return true;
|
|
4943
4966
|
}
|
|
4944
4967
|
|
|
4945
|
-
|
|
4968
|
+
function zcodeHourStartMs(hourStart) {
|
|
4969
|
+
if (typeof hourStart !== "string" || !hourStart.trim()) return null;
|
|
4970
|
+
const ms = Date.parse(hourStart);
|
|
4971
|
+
return Number.isFinite(ms) ? ms : null;
|
|
4972
|
+
}
|
|
4973
|
+
|
|
4974
|
+
function zcodeHourFromMainBucketKey(key) {
|
|
4975
|
+
if (typeof key !== "string" || !key.startsWith("zcode|")) return null;
|
|
4976
|
+
const parts = key.split("|");
|
|
4977
|
+
return parts.length >= 3 ? parts.slice(2).join("|") : null;
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4980
|
+
function zcodeHourFromProjectBucketKey(key) {
|
|
4981
|
+
if (typeof key !== "string") return null;
|
|
4982
|
+
const marker = "|zcode|";
|
|
4983
|
+
const idx = key.indexOf(marker);
|
|
4984
|
+
if (idx < 0) return null;
|
|
4985
|
+
const hour = key.slice(idx + marker.length);
|
|
4986
|
+
return hour || null;
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
function zcodeHourFromGroupQueuedKey(key) {
|
|
4990
|
+
if (typeof key !== "string" || !key.startsWith("zcode|")) return null;
|
|
4991
|
+
return key.slice("zcode|".length) || null;
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
function zcodeHourIsPreWindow(hourStart, preserveBeforeMs) {
|
|
4995
|
+
if (!Number.isFinite(preserveBeforeMs)) return false;
|
|
4996
|
+
const hourMs = zcodeHourStartMs(hourStart);
|
|
4997
|
+
return hourMs != null && hourMs < preserveBeforeMs;
|
|
4998
|
+
}
|
|
4999
|
+
|
|
5000
|
+
// Per-install rebuild floor. Prefer native model_usage MIN(started_at) when the
|
|
5001
|
+
// native schema exists — an ancient legacy message does not prove continuous
|
|
5002
|
+
// coverage through pruned native gaps (#828).
|
|
5003
|
+
function resolveZcodeDbRebuildFloorMs(dbPath) {
|
|
5004
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return null;
|
|
5005
|
+
if (hasZcodeNativeUsageSchema(dbPath)) {
|
|
5006
|
+
try {
|
|
5007
|
+
const rows = readSqliteJsonRows(
|
|
5008
|
+
dbPath,
|
|
5009
|
+
`SELECT MIN(started_at) AS floor
|
|
5010
|
+
FROM model_usage
|
|
5011
|
+
WHERE status = 'completed'
|
|
5012
|
+
AND trim(model_id) != ''
|
|
5013
|
+
AND (
|
|
5014
|
+
input_tokens > 0 OR output_tokens > 0 OR reasoning_tokens > 0 OR
|
|
5015
|
+
cache_creation_input_tokens > 0 OR cache_read_input_tokens > 0
|
|
5016
|
+
)`,
|
|
5017
|
+
{ label: "ZCode", timeout: 10_000, maxBuffer: 1024 * 1024, readOnly: true },
|
|
5018
|
+
);
|
|
5019
|
+
const floor = coerceEpochMs(Array.isArray(rows) ? rows[0]?.floor : null);
|
|
5020
|
+
return floor > 0 ? floor : null;
|
|
5021
|
+
} catch (_error) {
|
|
5022
|
+
return null;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
let messages;
|
|
5026
|
+
try {
|
|
5027
|
+
messages = readZcodeDbMessages(dbPath, { readOnly: true });
|
|
5028
|
+
} catch (_error) {
|
|
5029
|
+
return null;
|
|
5030
|
+
}
|
|
5031
|
+
let floor = Number.POSITIVE_INFINITY;
|
|
5032
|
+
for (const message of Array.isArray(messages) ? messages : []) {
|
|
5033
|
+
const ms = coerceEpochMs(
|
|
5034
|
+
message?.timeUpdated ?? message?.data?.time?.completed ?? message?.data?.time?.created,
|
|
5035
|
+
);
|
|
5036
|
+
if (ms > 0) floor = Math.min(floor, ms);
|
|
5037
|
+
}
|
|
5038
|
+
return Number.isFinite(floor) ? floor : null;
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
// Shared wipe window across installs: the LATEST per-install floor (most
|
|
5042
|
+
// conservative). Any listed install that exists but cannot report a floor
|
|
5043
|
+
// defers the wipe — silently taking min() would erase another install's history.
|
|
5044
|
+
function resolveZcodeRebuildFloorMs(dbPaths = []) {
|
|
5045
|
+
const paths = [...new Set((Array.isArray(dbPaths) ? dbPaths : []).filter(Boolean))];
|
|
5046
|
+
if (paths.length === 0) return null;
|
|
5047
|
+
let latestFloor = 0;
|
|
5048
|
+
for (const dbPath of paths) {
|
|
5049
|
+
if (!fssync.existsSync(dbPath)) continue;
|
|
5050
|
+
const floor = resolveZcodeDbRebuildFloorMs(dbPath);
|
|
5051
|
+
if (!Number.isFinite(floor)) return null;
|
|
5052
|
+
latestFloor = Math.max(latestFloor, floor);
|
|
5053
|
+
}
|
|
5054
|
+
return latestFloor > 0 ? latestFloor : null;
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
// Wipe starts at the half-hour bucket that contains the first live message so
|
|
5058
|
+
// that bucket is rebuilt (not preserved) and cannot be double-counted after
|
|
5059
|
+
// cursors.zcode is cleared.
|
|
5060
|
+
function zcodePreserveBeforeMsFromFloor(floorMs) {
|
|
5061
|
+
if (!Number.isFinite(floorMs)) return null;
|
|
5062
|
+
const iso = toUtcHalfHourStart(floorMs);
|
|
5063
|
+
const ms = iso ? Date.parse(iso) : floorMs;
|
|
5064
|
+
return Number.isFinite(ms) ? ms : null;
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
function hasZcodeCursorHistory(cursors) {
|
|
5068
|
+
return hasZcodeCursorState(cursors);
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5071
|
+
function pruneZcodeMessageIndexForRebuild(messages, preserveBeforeMs) {
|
|
5072
|
+
if (!messages || typeof messages !== "object") return 0;
|
|
5073
|
+
let removed = 0;
|
|
5074
|
+
for (const key of Object.keys(messages)) {
|
|
5075
|
+
const entry = messages[key];
|
|
5076
|
+
const attribution = typeof entry?.attribution === "string"
|
|
5077
|
+
? entry.attribution.split(" ")[0]
|
|
5078
|
+
: entry?.attribution?.bucketStart;
|
|
5079
|
+
const bucketStart = typeof attribution === "string" ? attribution : "";
|
|
5080
|
+
// Keep unattributed entries: older cursors omit attribution, and deleting
|
|
5081
|
+
// them re-adds still-present pre-window legacy messages onto preserved
|
|
5082
|
+
// buckets (5000→10000). Prefer a possible in-window under-correction over
|
|
5083
|
+
// permanent double-count (#828).
|
|
5084
|
+
if (!bucketStart) continue;
|
|
5085
|
+
if (zcodeHourIsPreWindow(bucketStart, preserveBeforeMs)) continue;
|
|
5086
|
+
delete messages[key];
|
|
5087
|
+
removed += 1;
|
|
5088
|
+
}
|
|
5089
|
+
return removed;
|
|
5090
|
+
}
|
|
5091
|
+
|
|
5092
|
+
function pruneZcodeMessageCursorForRebuild(cursors, preserveBeforeMs) {
|
|
5093
|
+
if (!cursors || typeof cursors !== "object") return;
|
|
5094
|
+
if (!Number.isFinite(preserveBeforeMs)) {
|
|
5095
|
+
delete cursors.zcode;
|
|
5096
|
+
return;
|
|
5097
|
+
}
|
|
5098
|
+
const root = cursors.zcode;
|
|
5099
|
+
if (!root || typeof root !== "object") {
|
|
5100
|
+
delete cursors.zcode;
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
pruneZcodeMessageIndexForRebuild(root.messages, preserveBeforeMs);
|
|
5104
|
+
for (const nestedKey of ["native", "wsl"]) {
|
|
5105
|
+
const nested = root[nestedKey];
|
|
5106
|
+
if (nested && typeof nested === "object") {
|
|
5107
|
+
pruneZcodeMessageIndexForRebuild(nested.messages, preserveBeforeMs);
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
const hasMessages = Object.keys(root.messages || {}).length > 0
|
|
5111
|
+
|| Object.keys(root.native?.messages || {}).length > 0
|
|
5112
|
+
|| Object.keys(root.wsl?.messages || {}).length > 0;
|
|
5113
|
+
if (!hasMessages) delete cursors.zcode;
|
|
5114
|
+
}
|
|
5115
|
+
|
|
5116
|
+
async function queueFileHasZcodeRows(filePath) {
|
|
5117
|
+
if (typeof filePath !== "string" || !filePath) return false;
|
|
5118
|
+
let raw;
|
|
5119
|
+
try {
|
|
5120
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
5121
|
+
} catch (error) {
|
|
5122
|
+
if (error?.code === "ENOENT") return false;
|
|
5123
|
+
throw error;
|
|
5124
|
+
}
|
|
5125
|
+
for (const line of raw.split("\n")) {
|
|
5126
|
+
if (!line.trim()) continue;
|
|
5127
|
+
try {
|
|
5128
|
+
const row = JSON.parse(line);
|
|
5129
|
+
if (row?.source === "zcode") return true;
|
|
5130
|
+
} catch (_error) {
|
|
5131
|
+
// keep scanning
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
return false;
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
function isCompletedZcodeMigration(entry) {
|
|
5138
|
+
return Boolean(entry) && !(typeof entry === "object" && entry.skipped);
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
async function dropZcodeQueueRows(filePath, {
|
|
5142
|
+
retainRetractions = false,
|
|
5143
|
+
preserveBeforeMs = null,
|
|
5144
|
+
} = {}) {
|
|
4946
5145
|
let raw;
|
|
4947
5146
|
try {
|
|
4948
5147
|
raw = await fs.readFile(filePath, "utf8");
|
|
4949
5148
|
} catch (error) {
|
|
4950
|
-
if (error?.code === "ENOENT") return { removed: 0, retractions: 0 };
|
|
5149
|
+
if (error?.code === "ENOENT") return { removed: 0, retractions: 0, preserved: 0 };
|
|
4951
5150
|
throw error;
|
|
4952
5151
|
}
|
|
4953
5152
|
const kept = [];
|
|
4954
5153
|
const retractions = new Map();
|
|
4955
5154
|
let removed = 0;
|
|
5155
|
+
let preserved = 0;
|
|
4956
5156
|
for (const line of raw.split("\n")) {
|
|
4957
5157
|
if (!line.trim()) continue;
|
|
4958
5158
|
let row;
|
|
@@ -4963,11 +5163,17 @@ async function dropZcodeQueueRows(filePath, { retainRetractions = false } = {})
|
|
|
4963
5163
|
continue;
|
|
4964
5164
|
}
|
|
4965
5165
|
if (row?.source === "zcode") {
|
|
4966
|
-
removed += 1;
|
|
4967
5166
|
const model = typeof row.model === "string" && row.model.trim()
|
|
4968
5167
|
? row.model.trim()
|
|
4969
5168
|
: "unknown";
|
|
4970
5169
|
const hourStart = typeof row.hour_start === "string" ? row.hour_start : "";
|
|
5170
|
+
// #828: keep pre-window rows intact — the live DB cannot rebuild them.
|
|
5171
|
+
if (zcodeHourIsPreWindow(hourStart, preserveBeforeMs)) {
|
|
5172
|
+
kept.push(line);
|
|
5173
|
+
preserved += 1;
|
|
5174
|
+
continue;
|
|
5175
|
+
}
|
|
5176
|
+
removed += 1;
|
|
4971
5177
|
if (retainRetractions && hourStart) {
|
|
4972
5178
|
retractions.set(`${model}|${hourStart}`, {
|
|
4973
5179
|
source: "zcode",
|
|
@@ -4986,7 +5192,7 @@ async function dropZcodeQueueRows(filePath, { retainRetractions = false } = {})
|
|
|
4986
5192
|
}
|
|
4987
5193
|
kept.push(line);
|
|
4988
5194
|
}
|
|
4989
|
-
if (removed === 0) return { removed: 0, retractions: 0 };
|
|
5195
|
+
if (removed === 0) return { removed: 0, retractions: 0, preserved };
|
|
4990
5196
|
await backupExistingFile(filePath);
|
|
4991
5197
|
const tmp = `${filePath}.zcoderepair.${process.pid}.${Date.now()}`;
|
|
4992
5198
|
const rewritten = [
|
|
@@ -4995,7 +5201,7 @@ async function dropZcodeQueueRows(filePath, { retainRetractions = false } = {})
|
|
|
4995
5201
|
];
|
|
4996
5202
|
await fs.writeFile(tmp, rewritten.length ? rewritten.join("\n") + "\n" : "", "utf8");
|
|
4997
5203
|
await fs.rename(tmp, filePath);
|
|
4998
|
-
return { removed, retractions: retractions.size };
|
|
5204
|
+
return { removed, retractions: retractions.size, preserved };
|
|
4999
5205
|
}
|
|
5000
5206
|
|
|
5001
5207
|
async function repairZcodeUsageMigration({
|
|
@@ -5006,25 +5212,52 @@ async function repairZcodeUsageMigration({
|
|
|
5006
5212
|
queueStatePath,
|
|
5007
5213
|
projectQueuePath,
|
|
5008
5214
|
projectQueueStatePath,
|
|
5215
|
+
rebuildFloorMs = null,
|
|
5009
5216
|
} = {}) {
|
|
5010
5217
|
if (!cursors || typeof cursors !== "object") return false;
|
|
5011
5218
|
const migrations = (cursors.migrations ||= {});
|
|
5012
|
-
|
|
5219
|
+
// Mirror Codex: a prior {skipped:true} must retry once the live ledger can
|
|
5220
|
+
// establish a rebuild floor (or the pre-window history is gone).
|
|
5221
|
+
const prior = migrations[migrationKey];
|
|
5222
|
+
if (isCompletedZcodeMigration(prior)) return false;
|
|
5223
|
+
|
|
5224
|
+
const preserveBeforeMs = zcodePreserveBeforeMsFromFloor(rebuildFloorMs);
|
|
5225
|
+
// Empty / unreadable live ledger + existing history => refuse the wipe. The
|
|
5226
|
+
// forward reader still works; only this historical correction defers (#828).
|
|
5227
|
+
if (preserveBeforeMs == null) {
|
|
5228
|
+
const hasQueueHistory = await queueFileHasZcodeRows(queuePath)
|
|
5229
|
+
|| await queueFileHasZcodeRows(projectQueuePath);
|
|
5230
|
+
if (hasZcodeCursorHistory(cursors) || hasQueueHistory) {
|
|
5231
|
+
migrations[migrationKey] = {
|
|
5232
|
+
skipped: true,
|
|
5233
|
+
reason: "zcode_rebuild_floor_unavailable",
|
|
5234
|
+
at: new Date().toISOString(),
|
|
5235
|
+
};
|
|
5236
|
+
return false;
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5013
5239
|
|
|
5014
|
-
const mainRepair = await dropZcodeQueueRows(queuePath, {
|
|
5240
|
+
const mainRepair = await dropZcodeQueueRows(queuePath, {
|
|
5241
|
+
retainRetractions: true,
|
|
5242
|
+
preserveBeforeMs,
|
|
5243
|
+
});
|
|
5015
5244
|
const projectRepair = projectQueuePath
|
|
5016
|
-
? await dropZcodeQueueRows(projectQueuePath)
|
|
5017
|
-
: { removed: 0, retractions: 0 };
|
|
5245
|
+
? await dropZcodeQueueRows(projectQueuePath, { preserveBeforeMs })
|
|
5246
|
+
: { removed: 0, retractions: 0, preserved: 0 };
|
|
5018
5247
|
|
|
5019
5248
|
const hourly = cursors.hourly && typeof cursors.hourly === "object" ? cursors.hourly : null;
|
|
5020
5249
|
if (hourly?.buckets) {
|
|
5021
5250
|
for (const key of Object.keys(hourly.buckets)) {
|
|
5022
|
-
if (key.startsWith("zcode|"))
|
|
5251
|
+
if (!key.startsWith("zcode|")) continue;
|
|
5252
|
+
if (zcodeHourIsPreWindow(zcodeHourFromMainBucketKey(key), preserveBeforeMs)) continue;
|
|
5253
|
+
delete hourly.buckets[key];
|
|
5023
5254
|
}
|
|
5024
5255
|
}
|
|
5025
5256
|
if (hourly?.groupQueued) {
|
|
5026
5257
|
for (const key of Object.keys(hourly.groupQueued)) {
|
|
5027
|
-
if (key.startsWith("zcode|"))
|
|
5258
|
+
if (!key.startsWith("zcode|")) continue;
|
|
5259
|
+
if (zcodeHourIsPreWindow(zcodeHourFromGroupQueuedKey(key), preserveBeforeMs)) continue;
|
|
5260
|
+
delete hourly.groupQueued[key];
|
|
5028
5261
|
}
|
|
5029
5262
|
}
|
|
5030
5263
|
const projectHourly = cursors.projectHourly && typeof cursors.projectHourly === "object"
|
|
@@ -5032,10 +5265,15 @@ async function repairZcodeUsageMigration({
|
|
|
5032
5265
|
: null;
|
|
5033
5266
|
if (projectHourly?.buckets) {
|
|
5034
5267
|
for (const key of Object.keys(projectHourly.buckets)) {
|
|
5035
|
-
if (key.includes("|zcode|"))
|
|
5268
|
+
if (!key.includes("|zcode|")) continue;
|
|
5269
|
+
if (zcodeHourIsPreWindow(zcodeHourFromProjectBucketKey(key), preserveBeforeMs)) continue;
|
|
5270
|
+
delete projectHourly.buckets[key];
|
|
5036
5271
|
}
|
|
5037
5272
|
}
|
|
5038
|
-
|
|
5273
|
+
// Keep pre-window message dedup so preserved buckets are not double-counted
|
|
5274
|
+
// when legacy rows still exist in the live DB. Clear only in-window (and
|
|
5275
|
+
// unattributed) cursor entries so the rescan can rebuild those hours.
|
|
5276
|
+
pruneZcodeMessageCursorForRebuild(cursors, preserveBeforeMs);
|
|
5039
5277
|
|
|
5040
5278
|
if (mainRepair.removed > 0) {
|
|
5041
5279
|
await resetUploadOffsetForZcodeRepair(queueStatePath, queueStateNote);
|
|
@@ -5048,6 +5286,236 @@ async function repairZcodeUsageMigration({
|
|
|
5048
5286
|
removedMain: mainRepair.removed,
|
|
5049
5287
|
removedProject: projectRepair.removed,
|
|
5050
5288
|
retractions: mainRepair.retractions,
|
|
5289
|
+
preservedMain: mainRepair.preserved || 0,
|
|
5290
|
+
preservedProject: projectRepair.preserved || 0,
|
|
5291
|
+
rebuildFloorMs: preserveBeforeMs,
|
|
5292
|
+
};
|
|
5293
|
+
return true;
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
function zcodeRowTotals(row) {
|
|
5297
|
+
return {
|
|
5298
|
+
input_tokens: Number(row?.input_tokens) || 0,
|
|
5299
|
+
cached_input_tokens: Number(row?.cached_input_tokens) || 0,
|
|
5300
|
+
cache_creation_input_tokens: Number(row?.cache_creation_input_tokens) || 0,
|
|
5301
|
+
output_tokens: Number(row?.output_tokens) || 0,
|
|
5302
|
+
reasoning_output_tokens: Number(row?.reasoning_output_tokens) || 0,
|
|
5303
|
+
total_tokens: Number(row?.total_tokens) || 0,
|
|
5304
|
+
conversation_count: Number(row?.conversation_count) || 0,
|
|
5305
|
+
};
|
|
5306
|
+
}
|
|
5307
|
+
|
|
5308
|
+
function zcodeTotalsDominate(candidate, baseline) {
|
|
5309
|
+
if (!candidate) return false;
|
|
5310
|
+
if (!baseline) return (candidate.total_tokens || 0) > 0;
|
|
5311
|
+
return (candidate.total_tokens || 0) > (baseline.total_tokens || 0);
|
|
5312
|
+
}
|
|
5313
|
+
|
|
5314
|
+
async function collectZcodeQueuePeaks(filePath, { beforeMs = null } = {}) {
|
|
5315
|
+
const peaks = new Map();
|
|
5316
|
+
let raw;
|
|
5317
|
+
try {
|
|
5318
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
5319
|
+
} catch (error) {
|
|
5320
|
+
if (error?.code === "ENOENT") return peaks;
|
|
5321
|
+
throw error;
|
|
5322
|
+
}
|
|
5323
|
+
for (const line of raw.split("\n")) {
|
|
5324
|
+
if (!line.trim()) continue;
|
|
5325
|
+
let row;
|
|
5326
|
+
try {
|
|
5327
|
+
row = JSON.parse(line);
|
|
5328
|
+
} catch (_error) {
|
|
5329
|
+
continue;
|
|
5330
|
+
}
|
|
5331
|
+
if (row?.source !== "zcode") continue;
|
|
5332
|
+
const model = typeof row.model === "string" && row.model.trim()
|
|
5333
|
+
? row.model.trim()
|
|
5334
|
+
: "unknown";
|
|
5335
|
+
const hourStart = typeof row.hour_start === "string" ? row.hour_start : "";
|
|
5336
|
+
if (!hourStart) continue;
|
|
5337
|
+
if (beforeMs != null && !zcodeHourIsPreWindow(hourStart, beforeMs)) continue;
|
|
5338
|
+
const key = `${model}|${hourStart}`;
|
|
5339
|
+
const totals = zcodeRowTotals(row);
|
|
5340
|
+
const prev = peaks.get(key);
|
|
5341
|
+
if (!prev || zcodeTotalsDominate(totals, prev.totals)) {
|
|
5342
|
+
peaks.set(key, { model, hour_start: hourStart, totals });
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
return peaks;
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
// Last-wins per key (cloud replay semantics). A historical peak of 9000 followed
|
|
5349
|
+
// by a repair retraction of 0 must report 0 so restore still re-appends.
|
|
5350
|
+
async function collectZcodeQueueLatest(filePath, { beforeMs = null } = {}) {
|
|
5351
|
+
const latest = new Map();
|
|
5352
|
+
let raw;
|
|
5353
|
+
try {
|
|
5354
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
5355
|
+
} catch (error) {
|
|
5356
|
+
if (error?.code === "ENOENT") return latest;
|
|
5357
|
+
throw error;
|
|
5358
|
+
}
|
|
5359
|
+
for (const line of raw.split("\n")) {
|
|
5360
|
+
if (!line.trim()) continue;
|
|
5361
|
+
let row;
|
|
5362
|
+
try {
|
|
5363
|
+
row = JSON.parse(line);
|
|
5364
|
+
} catch (_error) {
|
|
5365
|
+
continue;
|
|
5366
|
+
}
|
|
5367
|
+
if (row?.source !== "zcode") continue;
|
|
5368
|
+
const model = typeof row.model === "string" && row.model.trim()
|
|
5369
|
+
? row.model.trim()
|
|
5370
|
+
: "unknown";
|
|
5371
|
+
const hourStart = typeof row.hour_start === "string" ? row.hour_start : "";
|
|
5372
|
+
if (!hourStart) continue;
|
|
5373
|
+
if (beforeMs != null && !zcodeHourIsPreWindow(hourStart, beforeMs)) continue;
|
|
5374
|
+
latest.set(`${model}|${hourStart}`, {
|
|
5375
|
+
model,
|
|
5376
|
+
hour_start: hourStart,
|
|
5377
|
+
totals: zcodeRowTotals(row),
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
return latest;
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
async function listZcodeQueueBackupPaths(queuePath) {
|
|
5384
|
+
if (typeof queuePath !== "string" || !queuePath) return [];
|
|
5385
|
+
const dir = path.dirname(queuePath);
|
|
5386
|
+
const base = path.basename(queuePath);
|
|
5387
|
+
let names;
|
|
5388
|
+
try {
|
|
5389
|
+
names = await fs.readdir(dir);
|
|
5390
|
+
} catch (error) {
|
|
5391
|
+
if (error?.code === "ENOENT") return [];
|
|
5392
|
+
throw error;
|
|
5393
|
+
}
|
|
5394
|
+
return names
|
|
5395
|
+
.filter((name) => name.startsWith(`${base}.bak.`))
|
|
5396
|
+
.map((name) => path.join(dir, name))
|
|
5397
|
+
.sort();
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5400
|
+
async function repairZcodePruneWindowRestore({
|
|
5401
|
+
cursors,
|
|
5402
|
+
queuePath,
|
|
5403
|
+
queueStatePath,
|
|
5404
|
+
dbPaths = [],
|
|
5405
|
+
rebuildFloorMs: rebuildFloorMsOption = null,
|
|
5406
|
+
} = {}) {
|
|
5407
|
+
if (!cursors || typeof cursors !== "object") return false;
|
|
5408
|
+
const migrations = (cursors.migrations ||= {});
|
|
5409
|
+
const prior = migrations[ZCODE_PRUNE_WINDOW_RESTORE_KEY];
|
|
5410
|
+
if (isCompletedZcodeMigration(prior)) return false;
|
|
5411
|
+
|
|
5412
|
+
const rebuildFloorMs = Number.isFinite(rebuildFloorMsOption)
|
|
5413
|
+
? rebuildFloorMsOption
|
|
5414
|
+
: resolveZcodeRebuildFloorMs(dbPaths);
|
|
5415
|
+
if (!Number.isFinite(rebuildFloorMs)) {
|
|
5416
|
+
migrations[ZCODE_PRUNE_WINDOW_RESTORE_KEY] = {
|
|
5417
|
+
skipped: true,
|
|
5418
|
+
reason: "zcode_rebuild_floor_unavailable",
|
|
5419
|
+
at: new Date().toISOString(),
|
|
5420
|
+
};
|
|
5421
|
+
return false;
|
|
5422
|
+
}
|
|
5423
|
+
|
|
5424
|
+
const backupPaths = await listZcodeQueueBackupPaths(queuePath);
|
|
5425
|
+
if (backupPaths.length === 0) {
|
|
5426
|
+
migrations[ZCODE_PRUNE_WINDOW_RESTORE_KEY] = {
|
|
5427
|
+
skipped: true,
|
|
5428
|
+
reason: "zcode_queue_backup_missing",
|
|
5429
|
+
at: new Date().toISOString(),
|
|
5430
|
+
rebuildFloorMs,
|
|
5431
|
+
};
|
|
5432
|
+
return false;
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
const preserveBeforeMs = zcodePreserveBeforeMsFromFloor(rebuildFloorMs);
|
|
5436
|
+
const backupPeaks = new Map();
|
|
5437
|
+
for (const backupPath of backupPaths) {
|
|
5438
|
+
const peaks = await collectZcodeQueuePeaks(backupPath, { beforeMs: preserveBeforeMs });
|
|
5439
|
+
for (const [key, peak] of peaks) {
|
|
5440
|
+
const prev = backupPeaks.get(key);
|
|
5441
|
+
if (!prev || zcodeTotalsDominate(peak.totals, prev.totals)) {
|
|
5442
|
+
backupPeaks.set(key, peak);
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
if (backupPeaks.size === 0) {
|
|
5447
|
+
migrations[ZCODE_PRUNE_WINDOW_RESTORE_KEY] = {
|
|
5448
|
+
appliedAt: new Date().toISOString(),
|
|
5449
|
+
restored: 0,
|
|
5450
|
+
rebuildFloorMs,
|
|
5451
|
+
preserveBeforeMs,
|
|
5452
|
+
note: "no_pre_window_peaks_in_backups",
|
|
5453
|
+
};
|
|
5454
|
+
return true;
|
|
5455
|
+
}
|
|
5456
|
+
|
|
5457
|
+
const liveLatest = await collectZcodeQueueLatest(queuePath, { beforeMs: preserveBeforeMs });
|
|
5458
|
+
const hourly = (cursors.hourly ||= { buckets: {}, groupQueued: {} });
|
|
5459
|
+
hourly.buckets ||= {};
|
|
5460
|
+
const restorations = [];
|
|
5461
|
+
for (const [key, peak] of backupPeaks) {
|
|
5462
|
+
const bucketKeyName = bucketKey("zcode", peak.model, peak.hour_start);
|
|
5463
|
+
const live = liveLatest.get(key)?.totals || null;
|
|
5464
|
+
// Compare against the latest queue emission (last-wins), not historical peaks
|
|
5465
|
+
// or cursor buckets — a 9000→0 retraction must still restore.
|
|
5466
|
+
if (!zcodeTotalsDominate(peak.totals, live)) {
|
|
5467
|
+
const bucketTotals = hourly.buckets[bucketKeyName]?.totals
|
|
5468
|
+
? zcodeRowTotals(hourly.buckets[bucketKeyName].totals)
|
|
5469
|
+
: null;
|
|
5470
|
+
if (bucketTotals && zcodeTotalsDominate(peak.totals, bucketTotals)) {
|
|
5471
|
+
hourly.buckets[bucketKeyName] = {
|
|
5472
|
+
...(hourly.buckets[bucketKeyName] || {}),
|
|
5473
|
+
totals: { ...peak.totals },
|
|
5474
|
+
hour_start: peak.hour_start,
|
|
5475
|
+
source: "zcode",
|
|
5476
|
+
model: peak.model,
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
continue;
|
|
5480
|
+
}
|
|
5481
|
+
restorations.push(peak);
|
|
5482
|
+
hourly.buckets[bucketKeyName] = {
|
|
5483
|
+
...(hourly.buckets[bucketKeyName] || {}),
|
|
5484
|
+
totals: { ...peak.totals },
|
|
5485
|
+
hour_start: peak.hour_start,
|
|
5486
|
+
source: "zcode",
|
|
5487
|
+
model: peak.model,
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5491
|
+
if (restorations.length === 0) {
|
|
5492
|
+
migrations[ZCODE_PRUNE_WINDOW_RESTORE_KEY] = {
|
|
5493
|
+
appliedAt: new Date().toISOString(),
|
|
5494
|
+
restored: 0,
|
|
5495
|
+
rebuildFloorMs,
|
|
5496
|
+
backupsConsidered: backupPaths.length,
|
|
5497
|
+
note: "backup_peaks_not_higher",
|
|
5498
|
+
};
|
|
5499
|
+
return true;
|
|
5500
|
+
}
|
|
5501
|
+
|
|
5502
|
+
await backupExistingFile(queuePath);
|
|
5503
|
+
const append = restorations.map((peak) => JSON.stringify({
|
|
5504
|
+
source: "zcode",
|
|
5505
|
+
model: peak.model,
|
|
5506
|
+
hour_start: peak.hour_start,
|
|
5507
|
+
...peak.totals,
|
|
5508
|
+
}));
|
|
5509
|
+
await fs.appendFile(queuePath, `${append.join("\n")}\n`, "utf8");
|
|
5510
|
+
await resetUploadOffsetForZcodeRepair(
|
|
5511
|
+
queueStatePath,
|
|
5512
|
+
"reset_after_zcode_prune_window_restore_2026_10",
|
|
5513
|
+
);
|
|
5514
|
+
migrations[ZCODE_PRUNE_WINDOW_RESTORE_KEY] = {
|
|
5515
|
+
appliedAt: new Date().toISOString(),
|
|
5516
|
+
restored: restorations.length,
|
|
5517
|
+
rebuildFloorMs,
|
|
5518
|
+
backupsConsidered: backupPaths.length,
|
|
5051
5519
|
};
|
|
5052
5520
|
return true;
|
|
5053
5521
|
}
|
|
@@ -6306,26 +6774,56 @@ async function scanCodexUsageLineages(filePath, maxBytes = Infinity) {
|
|
|
6306
6774
|
const byteLimit = Number.isFinite(maxBytes) ? Math.max(0, maxBytes) : Infinity;
|
|
6307
6775
|
let bytesRead = 0;
|
|
6308
6776
|
let affected = false;
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
if (bytesRead > byteLimit) return { affected: false, indeterminate: true };
|
|
6316
|
-
if (!line.trim()) continue;
|
|
6317
|
-
let obj;
|
|
6318
|
-
try {
|
|
6319
|
-
obj = JSON.parse(line);
|
|
6320
|
-
} catch (_e) {
|
|
6777
|
+
const isCompressed = typeof filePath === "string" && /\.jsonl\.zstd?$/i.test(filePath);
|
|
6778
|
+
if (isCompressed) {
|
|
6779
|
+
// Honor the background-sync lineage budget against compressed size so a
|
|
6780
|
+
// pending repair with large archives stays bounded and returns
|
|
6781
|
+
// indeterminate until a full sync can finish the scan.
|
|
6782
|
+
if (!(byteLimit > 0) || before.size > byteLimit) {
|
|
6321
6783
|
return { affected: false, indeterminate: true };
|
|
6322
6784
|
}
|
|
6323
|
-
const
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6785
|
+
const snapshot = await readCodexRolloutSnapshot(filePath);
|
|
6786
|
+
if (!snapshot) return { affected: false, indeterminate: true };
|
|
6787
|
+
for (const raw of snapshot.lines) {
|
|
6788
|
+
const line = typeof raw === "string" ? raw : "";
|
|
6789
|
+
if (!line.trim()) continue;
|
|
6790
|
+
let obj;
|
|
6791
|
+
try {
|
|
6792
|
+
obj = JSON.parse(line);
|
|
6793
|
+
} catch (_e) {
|
|
6794
|
+
return { affected: false, indeterminate: true };
|
|
6795
|
+
}
|
|
6796
|
+
const token = extractTokenCount(obj);
|
|
6797
|
+
const info = token?.info;
|
|
6798
|
+
if (!info || typeof info !== "object") continue;
|
|
6799
|
+
consumeUsageDelta(state, info.last_token_usage, info.total_token_usage);
|
|
6800
|
+
if (state.sawInterleaved || state.sawDivergentCumulative) {
|
|
6801
|
+
affected = true;
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
bytesRead = snapshot.size;
|
|
6805
|
+
} else {
|
|
6806
|
+
stream = fssync.createReadStream(filePath, { highWaterMark: 32 * 1024 });
|
|
6807
|
+
for await (const record of physicalJsonlRecords(stream, {
|
|
6808
|
+
maxPhysicalBytes: byteLimit,
|
|
6809
|
+
})) {
|
|
6810
|
+
const { line } = record;
|
|
6811
|
+
bytesRead += record.physicalBytes;
|
|
6812
|
+
if (bytesRead > byteLimit) return { affected: false, indeterminate: true };
|
|
6813
|
+
if (!line.trim()) continue;
|
|
6814
|
+
let obj;
|
|
6815
|
+
try {
|
|
6816
|
+
obj = JSON.parse(line);
|
|
6817
|
+
} catch (_e) {
|
|
6818
|
+
return { affected: false, indeterminate: true };
|
|
6819
|
+
}
|
|
6820
|
+
const token = extractTokenCount(obj);
|
|
6821
|
+
const info = token?.info;
|
|
6822
|
+
if (!info || typeof info !== "object") continue;
|
|
6823
|
+
consumeUsageDelta(state, info.last_token_usage, info.total_token_usage);
|
|
6824
|
+
if (state.sawInterleaved || state.sawDivergentCumulative) {
|
|
6825
|
+
affected = true;
|
|
6826
|
+
}
|
|
6329
6827
|
}
|
|
6330
6828
|
}
|
|
6331
6829
|
const after = await readCodexFileSnapshot(filePath);
|
|
@@ -6430,6 +6928,23 @@ async function scanForForkedCodexRollout(rolloutFiles) {
|
|
|
6430
6928
|
if (!fp || src !== "codex") continue;
|
|
6431
6929
|
let fh = null;
|
|
6432
6930
|
try {
|
|
6931
|
+
if (typeof fp === "string" && /\.jsonl\.zstd?$/i.test(fp)) {
|
|
6932
|
+
const snapshot = await readCodexRolloutSnapshot(fp);
|
|
6933
|
+
if (!snapshot) {
|
|
6934
|
+
indeterminate = true;
|
|
6935
|
+
continue;
|
|
6936
|
+
}
|
|
6937
|
+
// Fork marker is in early session_meta; scan a bounded prefix of lines.
|
|
6938
|
+
let head = "";
|
|
6939
|
+
for (const raw of snapshot.lines) {
|
|
6940
|
+
head += (typeof raw === "string" ? raw : "") + "\n";
|
|
6941
|
+
if (head.length >= HEAD_BYTES) break;
|
|
6942
|
+
}
|
|
6943
|
+
if (head.includes('"forked_from_id"')) {
|
|
6944
|
+
return { forked: true, indeterminate: false };
|
|
6945
|
+
}
|
|
6946
|
+
continue;
|
|
6947
|
+
}
|
|
6433
6948
|
fh = await fs.open(fp, "r");
|
|
6434
6949
|
const buf = Buffer.alloc(HEAD_BYTES);
|
|
6435
6950
|
const { bytesRead } = await fh.read(buf, 0, HEAD_BYTES, 0);
|