tokentracker-cli 1.1.11 → 1.1.13
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.md +2 -1
- package/dashboard/dist/assets/{AchievementBadge-DuEuFaXg.js → AchievementBadge-DHCKPAgO.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-BTTFtk_Z.js → AchievementsPage-BqlGMD7S.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BZppKteZ.js → AchievementsSection-CTTTR9CN.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-TzmxXy8k.js → BadgeDetailModal-e0Iz8ct4.js} +1 -1
- package/dashboard/dist/assets/BotAnimated-BLYZyT1O.js +1 -0
- package/dashboard/dist/assets/{Card-HigkIm_b.js → Card-EHkn9ktD.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-Bx_11gJk.js → CommandPalette-CuSVstX9.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BjDPCBol.js → CommunityStatsModal-Cb1vefLE.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-DTo93RpG.js → ConfirmModal-pIUQJ7QM.js} +1 -1
- package/dashboard/dist/assets/Controls-CX4Di57j.js +1 -0
- package/dashboard/dist/assets/DashboardPage-D1Ox1y3E.js +1 -0
- package/dashboard/dist/assets/{DevicePage-BVupGt9K.js → DevicePage-Q57gBE8A.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BlQJpi7n.js → DialogClose-Dn-nxOSp.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-GXslTq2z.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-CDmxp9ap.js → DialogTitle-DuAY9sfZ.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DPercU5n.js → FadeIn-25jNO9JV.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DeVzeBIo.js → HeaderGithubStar-0-ne32-S.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-Duo2Icus.js → HoverTooltip-C2Qop8B3.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DgYiaRrT.js → IpCheckPage-Q9lwiFdt.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CYTR4PbQ.js → LandingPage-iX6E8Flr.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-B67pMaIl.js → LeaderboardAvatar-CiQrgneF.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-2wIMkBgq.js → LeaderboardPage-BIhIA28A.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-D3VxkFlx.js → LeaderboardProfileModal-CF-MI8rL.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-BS01jN5M.js → LeaderboardProfilePage-DRbLb-fO.js} +3 -3
- package/dashboard/dist/assets/{LimitsPage-BGRZW-aH.js → LimitsPage-lvYidBkz.js} +2 -2
- package/dashboard/dist/assets/{LocalOnlyNotice-Do9KUdi_.js → LocalOnlyNotice-CAQTnlaI.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-CzAXLXfZ.js → LoginCard-Ci2Rrsfl.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-D1J8zG0C.js → LoginModal-DhyQPl-V.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DeZJNMLe.js → LoginPage-B6q8P6hE.js} +1 -1
- package/dashboard/dist/assets/PetPage-82Cldgg6.js +1 -0
- package/dashboard/dist/assets/{PopoverPopup-BYzzQJTD.js → PopoverPopup-Btcu7vU0.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-BfFkS-OF.js → ProviderIcon-aLwclDsD.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BFyYvG24.js → ResetPasswordPage-BlRl_aJr.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-D5USR3JT.js → SegmentedControl-2O_XVL8u.js} +1 -1
- package/dashboard/dist/assets/{Select-DmFQdVA1.js → Select-D5uuRQwh.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-B5MXGLN-.js → SelectItemText-Bq_ZdWU0.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-Dvm5eUJK.js → ServiceStatusPage-Pd6dgJua.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-DQKezzUW.js → SessionsPage-DjHudcn6.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-DOLhZiBE.js +2 -0
- package/dashboard/dist/assets/{SkillsPage-DyX_-dcr.js → SkillsPage-D9-ZorB5.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-aK3jhWW0.js → TokenGalaxy-BN71jQQ8.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-CY_SULMC.js → ToolbarRootContext-B0cqyD1i.js} +1 -1
- package/dashboard/dist/assets/TrendMonitor-DWxtbzyQ.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-D8tzK-7m.js → WidgetsPage-BwmJ2Z9d.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BjZPa_al.js → WrappedPage-Cq8DrzLm.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-CPMSmJgP.js +45 -0
- package/dashboard/dist/assets/__vite-browser-external-DrXtuPP9.css +1 -0
- package/dashboard/dist/assets/{agent-logos-QSoXRwwJ.js → agent-logos-X7FQq7rz.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-C3WUmTcI.js → arrow-up-right-CPxuidzy.js} +1 -1
- package/dashboard/dist/assets/{check-Gz5mnj6f.js → check-D2K19ObI.js} +1 -1
- package/dashboard/dist/assets/{composite-DJhoahpv.js → composite-BN1UKLj0.js} +1 -1
- package/dashboard/dist/assets/{copy-C5RiL5JQ.js → copy-CSN17qFv.js} +1 -1
- package/dashboard/dist/assets/{download-CccMGCc1.js → download-CpW-TB2h.js} +1 -1
- package/dashboard/dist/assets/{external-link-fdU7Lj6p.js → external-link-ChBchK6G.js} +1 -1
- package/dashboard/dist/assets/{flame-CYEjossP.js → flame-f7119ZMd.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-xXCznLkf.js → format-tokens-COrRnk53.js} +1 -1
- package/dashboard/dist/assets/{icons-CYNYszil.js → icons-Dn9vA9Qu.js} +1 -1
- package/dashboard/dist/assets/{index-CQ5GHXiW.js → index-BCNOKYSO.js} +1 -1
- package/dashboard/dist/assets/{index-Czz7x_MZ.js → index-CA-94DFW.js} +1 -1
- package/dashboard/dist/assets/{index-Cgfb050k.js → index-CElyhNzh.js} +1 -1
- package/dashboard/dist/assets/{info-Zae50fSC.js → info-BHsPBczD.js} +1 -1
- package/dashboard/dist/assets/{link-2-DW_mHLzI.js → link-2-BHfEWv22.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-Bl7dBXh1.js → loader-circle-DTIT1Uk1.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-DTP25Fl_.js → maximize-2-BmtIBzLu.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-CYXpOF3i.js → modifiers.esm-CPBla3OK.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone--TLNLuLe.js → monitor-smartphone-CbsYB-S2.js} +1 -1
- package/dashboard/dist/assets/{plus-DgeFv3Xc.js → plus-CZiNoMsy.js} +1 -1
- package/dashboard/dist/assets/{provider-display-crAVn3qa.js → provider-display-CXKRt9q1.js} +2 -2
- package/dashboard/dist/assets/{react-QPulAuJP.js → react-CZdcuJ28.js} +1 -1
- package/dashboard/dist/assets/{search-DIUmGfar.js → search-q2ARMGca.js} +1 -1
- package/dashboard/dist/assets/{skills-api-09qiTRNb.js → skills-api-CIyqyk-w.js} +1 -1
- package/dashboard/dist/assets/skins-Mc0sI20C.js +1 -0
- package/dashboard/dist/assets/{sortable.esm-BbsbmOc2.js → sortable.esm-BztIEZTk.js} +1 -1
- package/dashboard/dist/assets/{store-DdyzwTsy.js → store-DvdXwk-7.js} +1 -1
- package/dashboard/dist/assets/{terminal-JpDnkjVA.js → terminal-CFQOW2Ey.js} +1 -1
- package/dashboard/dist/assets/{trash-CRsyysiP.js → trash-D0ptaWPk.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-D3PwUyvR.js → use-community-stats-DYibzhAl.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-DTCniVRw.js → use-latest-request-guard-viIhH7H5.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-BbYcvoh_.js → use-limits-display-prefs-CbXApWU1.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-ypqFhcs4.js → use-ordered-list-BBP8hr8y.js} +1 -1
- package/dashboard/dist/assets/use-pet-settings-CIrPZ9pB.js +2 -0
- package/dashboard/dist/assets/use-reduced-motion-Dpq-cKyM.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-C5BZmpkY.js → use-session-efficiency-pref-2Vb3tJrw.js} +1 -1
- package/dashboard/dist/assets/{use-transform-C3bGaC_U.js → use-transform-CECRFELz.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-DEnbjOVG.js +1 -0
- package/dashboard/dist/assets/{useCurrency-BianRgdW.js → useCurrency-Dh9pUzDO.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-CoGcVhPP.js +1 -0
- package/dashboard/dist/assets/{zap-BJTChg1t.js → zap-BdeAxq64.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +3 -2
- package/src/commands/sync.js +57 -29
- package/src/lib/claude-categorizer.js +4 -7
- package/src/lib/claude-user-identity.js +56 -0
- package/src/lib/cursor-store.js +636 -57
- package/src/lib/grok-limits.js +17 -4
- package/src/lib/kimi-profile.js +61 -0
- package/src/lib/multi-install-parser.js +3 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +91 -39
- package/src/lib/usage-limits.js +128 -79
- package/dashboard/dist/assets/BotAnimated-4hWTFjwp.js +0 -1
- package/dashboard/dist/assets/ClawdAnimated-BRoihVSd.js +0 -2
- package/dashboard/dist/assets/Controls-BvWTXyfI.js +0 -1
- package/dashboard/dist/assets/DashboardPage--1_GuEr-.js +0 -1
- package/dashboard/dist/assets/DialogDescription-DE2BF-HX.js +0 -1
- package/dashboard/dist/assets/PetPage-D2oqJ05F.js +0 -1
- package/dashboard/dist/assets/SettingsPage-DbUDye5r.js +0 -2
- package/dashboard/dist/assets/TrendMonitor-BH6Jx8nS.js +0 -19
- package/dashboard/dist/assets/__vite-browser-external-Bg0J-a5P.css +0 -1
- package/dashboard/dist/assets/__vite-browser-external-VmN0WaC3.js +0 -45
- package/dashboard/dist/assets/skins-C8EyB85e.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-Cdn1VrG0.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-BRLNaZEv.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-DO4vxqhW.js +0 -1
package/src/lib/cursor-store.js
CHANGED
|
@@ -15,6 +15,9 @@ const STORE_DIRNAME = "cursor-store-v2";
|
|
|
15
15
|
const MANIFEST_FILENAME = "manifest.json";
|
|
16
16
|
const GENERATIONS_DIRNAME = "generations";
|
|
17
17
|
const DEFERRED_CODEX_AUDIT_FILENAME = "deferred-codex-audit.json";
|
|
18
|
+
const SHARDED_CORE_FILENAME = "core-v3.json";
|
|
19
|
+
const OPENCODE_MESSAGES_DIRNAME = "opencode-messages";
|
|
20
|
+
const OPENCODE_FINGERPRINTS_DIRNAME = "opencode-fingerprints";
|
|
18
21
|
const DEFAULT_ACTIVATION_BYTES = 16 * 1024 * 1024;
|
|
19
22
|
const PROJECT_ABSENT_CONTEXT_RESCAN_MS = 24 * 60 * 60 * 1000;
|
|
20
23
|
const CURSOR_STORE_RETRY_CODE = "TOKENTRACKER_CURSOR_STORE_RETRY";
|
|
@@ -66,26 +69,9 @@ async function openCursorStore({
|
|
|
66
69
|
return opened;
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
legacyFingerprint,
|
|
73
|
-
previousManifest: manifest,
|
|
74
|
-
codexRoots: normalizedCodexRoots,
|
|
75
|
-
failureInjector,
|
|
76
|
-
});
|
|
77
|
-
migratedDuringOpen = true;
|
|
78
|
-
const recovered = await openManifestGeneration({
|
|
79
|
-
cursorsPath,
|
|
80
|
-
storeRoot,
|
|
81
|
-
manifest,
|
|
82
|
-
codexRoots: normalizedCodexRoots,
|
|
83
|
-
failureInjector,
|
|
84
|
-
});
|
|
85
|
-
if (recovered) {
|
|
86
|
-
recovered.requiresCommit = true;
|
|
87
|
-
return recovered;
|
|
88
|
-
}
|
|
72
|
+
throw cursorStoreCorruption(
|
|
73
|
+
`No valid cursor store generation in ${storeRoot}`,
|
|
74
|
+
);
|
|
89
75
|
}
|
|
90
76
|
|
|
91
77
|
const legacySize = Number(legacyFingerprint?.size || 0);
|
|
@@ -136,7 +122,10 @@ async function readCursorStateSummary({ trackerDir, cursorsPath } = {}) {
|
|
|
136
122
|
);
|
|
137
123
|
}
|
|
138
124
|
const opened = await readGeneration({ storeRoot, generationId: manifest.current }) ||
|
|
139
|
-
await readGeneration({
|
|
125
|
+
await readGeneration({
|
|
126
|
+
storeRoot,
|
|
127
|
+
generationId: manifest.rollback || manifest.previous,
|
|
128
|
+
});
|
|
140
129
|
if (opened) {
|
|
141
130
|
return {
|
|
142
131
|
cursors: opened.core,
|
|
@@ -145,6 +134,7 @@ async function readCursorStateSummary({ trackerDir, cursorsPath } = {}) {
|
|
|
145
134
|
fileCount: Number(opened.metadata?.counts?.totalFiles || 0),
|
|
146
135
|
codexFileCount: Number(opened.metadata?.counts?.codexFiles || 0),
|
|
147
136
|
codexEventCount: Number(opened.metadata?.counts?.codexEvents || 0),
|
|
137
|
+
opencodeMessageCount: Number(opened.metadata?.counts?.opencodeMessages || 0),
|
|
148
138
|
};
|
|
149
139
|
}
|
|
150
140
|
}
|
|
@@ -188,6 +178,12 @@ class LegacyCursorStore {
|
|
|
188
178
|
|
|
189
179
|
async materializeAllCodexState() {}
|
|
190
180
|
|
|
181
|
+
async loadOpencodeMessagesForBatch() {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async materializeAllOpencodeState() {}
|
|
186
|
+
|
|
191
187
|
async canSkipCodexDay() {
|
|
192
188
|
return false;
|
|
193
189
|
}
|
|
@@ -211,7 +207,7 @@ class V2CursorStore {
|
|
|
211
207
|
storeRoot,
|
|
212
208
|
manifest,
|
|
213
209
|
generation,
|
|
214
|
-
|
|
210
|
+
fallbackGenerationIds,
|
|
215
211
|
codexRoots,
|
|
216
212
|
failureInjector,
|
|
217
213
|
}) {
|
|
@@ -221,7 +217,9 @@ class V2CursorStore {
|
|
|
221
217
|
this.manifest = manifest;
|
|
222
218
|
this.generation = generation.metadata;
|
|
223
219
|
this.generationDir = generation.directory;
|
|
224
|
-
this.
|
|
220
|
+
this.fallbackGenerationIds = Array.isArray(fallbackGenerationIds)
|
|
221
|
+
? [...fallbackGenerationIds]
|
|
222
|
+
: [];
|
|
225
223
|
this.codexRoots = codexRoots;
|
|
226
224
|
this.cursors = generation.core;
|
|
227
225
|
this.failureInjector = failureInjector;
|
|
@@ -233,10 +231,19 @@ class V2CursorStore {
|
|
|
233
231
|
this.loadedEventSets = new Map();
|
|
234
232
|
this.pendingEventKeys = new Map();
|
|
235
233
|
this.skipValidationCache = new Map();
|
|
234
|
+
this.loadedOpencodeMessageShards = new Map();
|
|
235
|
+
this.loadedOpencodeFingerprintShards = new Map();
|
|
236
|
+
this.opencodeFingerprintIndexes = new Map();
|
|
237
|
+
this.materializedOpencodeNamespaces = new Set();
|
|
236
238
|
this.fileShardLoadCount = 0;
|
|
239
|
+
this.opencodeMessageShardLoadCount = 0;
|
|
240
|
+
this.opencodeFingerprintShardLoadCount = 0;
|
|
237
241
|
this.materializedCodexHashes = false;
|
|
238
242
|
this.requiresCommit = generation.metadata.id !== manifest.current;
|
|
239
243
|
|
|
244
|
+
this.normalizeGenerationMetadata();
|
|
245
|
+
this.stageInlineOpencodeMessages();
|
|
246
|
+
|
|
240
247
|
if (!this.cursors.files || typeof this.cursors.files !== "object") {
|
|
241
248
|
this.cursors.files = {};
|
|
242
249
|
}
|
|
@@ -265,6 +272,30 @@ class V2CursorStore {
|
|
|
265
272
|
return path.join(this.generationDir, this.generation?.coreFile || "core.json");
|
|
266
273
|
}
|
|
267
274
|
|
|
275
|
+
normalizeGenerationMetadata() {
|
|
276
|
+
if (!this.generation.opencodeMessages) this.generation.opencodeMessages = {};
|
|
277
|
+
if (!this.generation.opencodeFingerprints) this.generation.opencodeFingerprints = {};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
stageInlineOpencodeMessages() {
|
|
281
|
+
const states = opencodeNamespaceStates(this.cursors.opencode);
|
|
282
|
+
const messageCount = Array.from(states.values()).reduce(
|
|
283
|
+
(sum, state) => sum + Object.keys(state.messages || {}).length,
|
|
284
|
+
0,
|
|
285
|
+
);
|
|
286
|
+
if (messageCount === 0) return;
|
|
287
|
+
if (
|
|
288
|
+
Object.keys(this.generation.opencodeMessages).length > 0 ||
|
|
289
|
+
Object.keys(this.generation.opencodeFingerprints).length > 0
|
|
290
|
+
) {
|
|
291
|
+
throw cursorStoreCorruption("OpenCode messages exist in both core and shards");
|
|
292
|
+
}
|
|
293
|
+
for (const namespace of states.keys()) {
|
|
294
|
+
this.materializedOpencodeNamespaces.add(namespace);
|
|
295
|
+
}
|
|
296
|
+
this.requiresCommit = true;
|
|
297
|
+
}
|
|
298
|
+
|
|
268
299
|
async loadCodexFilesForPaths(paths, cursors = this.cursors) {
|
|
269
300
|
const shardKeys = new Set();
|
|
270
301
|
for (const entry of Array.isArray(paths) ? paths : []) {
|
|
@@ -371,6 +402,210 @@ class V2CursorStore {
|
|
|
371
402
|
return { restarted: false };
|
|
372
403
|
}
|
|
373
404
|
|
|
405
|
+
async loadOpencodeMessagesForBatch({
|
|
406
|
+
namespace = "flat",
|
|
407
|
+
messageKeys = [],
|
|
408
|
+
fingerprints = [],
|
|
409
|
+
} = {}) {
|
|
410
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
411
|
+
try {
|
|
412
|
+
const loaded = await this.loadOpencodeMessagesForBatchUnchecked({
|
|
413
|
+
namespace,
|
|
414
|
+
messageKeys,
|
|
415
|
+
fingerprints,
|
|
416
|
+
});
|
|
417
|
+
if (loaded) loaded.restarted = attempt > 0;
|
|
418
|
+
return loaded;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
if (
|
|
421
|
+
!isCursorStoreRetry(error) ||
|
|
422
|
+
normalizeOpencodeNamespace(namespace) !== "flat" ||
|
|
423
|
+
attempt > 0
|
|
424
|
+
) {
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async loadOpencodeMessagesForBatchUnchecked({
|
|
433
|
+
namespace,
|
|
434
|
+
messageKeys,
|
|
435
|
+
fingerprints,
|
|
436
|
+
}) {
|
|
437
|
+
const normalizedNamespace = normalizeOpencodeNamespace(namespace);
|
|
438
|
+
const activeRoot = this.cursors.opencode;
|
|
439
|
+
const activeRootIsFlat = Boolean(
|
|
440
|
+
activeRoot &&
|
|
441
|
+
typeof activeRoot === "object" &&
|
|
442
|
+
activeRoot.native === undefined &&
|
|
443
|
+
activeRoot.wsl === undefined,
|
|
444
|
+
);
|
|
445
|
+
const state = normalizedNamespace !== "flat" && activeRootIsFlat
|
|
446
|
+
? activeRoot
|
|
447
|
+
: opencodeStateForNamespace(this.cursors, normalizedNamespace, true);
|
|
448
|
+
if (!state || this.materializedOpencodeNamespaces.has(normalizedNamespace)) return null;
|
|
449
|
+
const messages = state.messages && typeof state.messages === "object"
|
|
450
|
+
? state.messages
|
|
451
|
+
: (state.messages = {});
|
|
452
|
+
const managed = hasOpencodeNamespaceShards({
|
|
453
|
+
namespace: normalizedNamespace,
|
|
454
|
+
metadata: this.generation,
|
|
455
|
+
loadedMessages: this.loadedOpencodeMessageShards,
|
|
456
|
+
loadedFingerprints: this.loadedOpencodeFingerprintShards,
|
|
457
|
+
});
|
|
458
|
+
if (!managed && Object.keys(messages).length > 0) return null;
|
|
459
|
+
if (!managed && this.generation.coreFile !== SHARDED_CORE_FILENAME) return null;
|
|
460
|
+
|
|
461
|
+
const wantedMessageKeys = Array.from(new Set(
|
|
462
|
+
Array.from(messageKeys || []).filter((key) => typeof key === "string" && key.length > 0),
|
|
463
|
+
));
|
|
464
|
+
for (const shardKey of new Set(wantedMessageKeys.map((key) => (
|
|
465
|
+
opencodeMessageShardKey(key, normalizedNamespace)
|
|
466
|
+
)))) {
|
|
467
|
+
const alreadyLoaded = this.loadedOpencodeMessageShards.has(shardKey);
|
|
468
|
+
const data = await this.loadOpencodeMessageShard(shardKey);
|
|
469
|
+
if (!alreadyLoaded) Object.assign(messages, data);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const wantedFingerprints = new Set(
|
|
473
|
+
Array.from(fingerprints || []).filter((value) => (
|
|
474
|
+
typeof value === "string" && value.length > 0
|
|
475
|
+
)),
|
|
476
|
+
);
|
|
477
|
+
for (const messageKey of wantedMessageKeys) {
|
|
478
|
+
const previousFingerprint = messages[messageKey]?.fingerprint;
|
|
479
|
+
if (typeof previousFingerprint === "string" && previousFingerprint.length > 0) {
|
|
480
|
+
wantedFingerprints.add(previousFingerprint);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
for (const shardKey of new Set(Array.from(wantedFingerprints, (fingerprint) => (
|
|
484
|
+
opencodeFingerprintShardKey(fingerprint, normalizedNamespace)
|
|
485
|
+
)))) {
|
|
486
|
+
await this.loadOpencodeFingerprintShard(shardKey, normalizedNamespace);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!this.opencodeFingerprintIndexes.has(normalizedNamespace)) {
|
|
490
|
+
this.opencodeFingerprintIndexes.set(normalizedNamespace, new Map());
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
fingerprintIndex: this.opencodeFingerprintIndexes.get(normalizedNamespace),
|
|
494
|
+
restarted: false,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async loadOpencodeMessageShard(shardKey) {
|
|
499
|
+
try {
|
|
500
|
+
return await this.loadOpencodeMessageShardUnchecked(shardKey);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
if (
|
|
503
|
+
error?.code !== "TOKENTRACKER_CURSOR_STORE_CORRUPT" ||
|
|
504
|
+
!this.activateFallbackGeneration(this.cursors)
|
|
505
|
+
) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
throw cursorStoreRetry(error);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async loadOpencodeMessageShardUnchecked(shardKey) {
|
|
513
|
+
if (this.loadedOpencodeMessageShards.has(shardKey)) {
|
|
514
|
+
return this.loadedOpencodeMessageShards.get(shardKey).data;
|
|
515
|
+
}
|
|
516
|
+
const metadata = this.generation.opencodeMessages[shardKey] || null;
|
|
517
|
+
const data = await readJsonObjectShard({
|
|
518
|
+
generationDir: this.generationDir,
|
|
519
|
+
metadata,
|
|
520
|
+
label: `OpenCode message shard ${metadata?.file || shardKey}`,
|
|
521
|
+
});
|
|
522
|
+
this.loadedOpencodeMessageShards.set(shardKey, { data });
|
|
523
|
+
this.opencodeMessageShardLoadCount += 1;
|
|
524
|
+
return data;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async loadOpencodeFingerprintShard(shardKey, namespace) {
|
|
528
|
+
try {
|
|
529
|
+
return await this.loadOpencodeFingerprintShardUnchecked(shardKey, namespace);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (
|
|
532
|
+
error?.code !== "TOKENTRACKER_CURSOR_STORE_CORRUPT" ||
|
|
533
|
+
!this.activateFallbackGeneration(this.cursors)
|
|
534
|
+
) {
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
throw cursorStoreRetry(error);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async loadOpencodeFingerprintShardUnchecked(shardKey, namespace) {
|
|
542
|
+
const alreadyLoaded = this.loadedOpencodeFingerprintShards.has(shardKey);
|
|
543
|
+
if (!alreadyLoaded) {
|
|
544
|
+
const metadata = this.generation.opencodeFingerprints[shardKey] || null;
|
|
545
|
+
const data = await readJsonObjectShard({
|
|
546
|
+
generationDir: this.generationDir,
|
|
547
|
+
metadata,
|
|
548
|
+
label: `OpenCode fingerprint shard ${metadata?.file || shardKey}`,
|
|
549
|
+
validate: (value) => Array.isArray(value) && value.every((item) => (
|
|
550
|
+
typeof item === "string" && item.length > 0
|
|
551
|
+
)),
|
|
552
|
+
});
|
|
553
|
+
this.loadedOpencodeFingerprintShards.set(shardKey, { data });
|
|
554
|
+
this.opencodeFingerprintShardLoadCount += 1;
|
|
555
|
+
}
|
|
556
|
+
if (!this.opencodeFingerprintIndexes.has(namespace)) {
|
|
557
|
+
this.opencodeFingerprintIndexes.set(namespace, new Map());
|
|
558
|
+
}
|
|
559
|
+
const index = this.opencodeFingerprintIndexes.get(namespace);
|
|
560
|
+
if (!alreadyLoaded) {
|
|
561
|
+
for (const [fingerprint, owners] of Object.entries(
|
|
562
|
+
this.loadedOpencodeFingerprintShards.get(shardKey).data,
|
|
563
|
+
)) {
|
|
564
|
+
index.set(fingerprint, new Set(owners));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return index;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async materializeAllOpencodeState() {
|
|
571
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
572
|
+
try {
|
|
573
|
+
const namespaces = opencodeNamespacesForStore({
|
|
574
|
+
state: this.cursors.opencode,
|
|
575
|
+
metadata: this.generation,
|
|
576
|
+
loadedMessages: this.loadedOpencodeMessageShards,
|
|
577
|
+
loadedFingerprints: this.loadedOpencodeFingerprintShards,
|
|
578
|
+
});
|
|
579
|
+
for (const namespace of namespaces) {
|
|
580
|
+
const state = opencodeStateForNamespace(this.cursors, namespace, true);
|
|
581
|
+
if (!state.messages || typeof state.messages !== "object") state.messages = {};
|
|
582
|
+
for (const shardKey of opencodeShardKeysForNamespace(
|
|
583
|
+
namespace,
|
|
584
|
+
this.generation.opencodeMessages,
|
|
585
|
+
this.loadedOpencodeMessageShards,
|
|
586
|
+
)) {
|
|
587
|
+
Object.assign(
|
|
588
|
+
state.messages,
|
|
589
|
+
await this.loadOpencodeMessageShard(shardKey),
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
for (const shardKey of opencodeShardKeysForNamespace(
|
|
593
|
+
namespace,
|
|
594
|
+
this.generation.opencodeFingerprints,
|
|
595
|
+
this.loadedOpencodeFingerprintShards,
|
|
596
|
+
)) {
|
|
597
|
+
await this.loadOpencodeFingerprintShard(shardKey, namespace);
|
|
598
|
+
}
|
|
599
|
+
this.materializedOpencodeNamespaces.add(namespace);
|
|
600
|
+
}
|
|
601
|
+
return { restarted: attempt > 0 };
|
|
602
|
+
} catch (error) {
|
|
603
|
+
if (!isCursorStoreRetry(error) || attempt > 0) throw error;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return { restarted: false };
|
|
607
|
+
}
|
|
608
|
+
|
|
374
609
|
async canSkipCodexDay({
|
|
375
610
|
filePath,
|
|
376
611
|
dayInventoryCache,
|
|
@@ -490,14 +725,13 @@ class V2CursorStore {
|
|
|
490
725
|
}
|
|
491
726
|
|
|
492
727
|
activateFallbackGeneration(cursors = this.cursors) {
|
|
493
|
-
|
|
494
|
-
this.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
});
|
|
728
|
+
let fallback = null;
|
|
729
|
+
while (!fallback && this.fallbackGenerationIds.length > 0) {
|
|
730
|
+
fallback = readGenerationSync({
|
|
731
|
+
storeRoot: this.storeRoot,
|
|
732
|
+
generationId: this.fallbackGenerationIds.shift(),
|
|
733
|
+
});
|
|
734
|
+
}
|
|
501
735
|
if (!fallback) return false;
|
|
502
736
|
|
|
503
737
|
const fallbackCore = cloneJson(fallback.core);
|
|
@@ -511,11 +745,113 @@ class V2CursorStore {
|
|
|
511
745
|
this.loadedEventSets.clear();
|
|
512
746
|
this.pendingEventKeys.clear();
|
|
513
747
|
this.skipValidationCache.clear();
|
|
748
|
+
this.loadedOpencodeMessageShards.clear();
|
|
749
|
+
this.loadedOpencodeFingerprintShards.clear();
|
|
750
|
+
this.opencodeFingerprintIndexes.clear();
|
|
751
|
+
this.materializedOpencodeNamespaces.clear();
|
|
514
752
|
this.materializedCodexHashes = false;
|
|
515
753
|
this.requiresCommit = true;
|
|
754
|
+
this.normalizeGenerationMetadata();
|
|
755
|
+
this.stageInlineOpencodeMessages();
|
|
516
756
|
return true;
|
|
517
757
|
}
|
|
518
758
|
|
|
759
|
+
async commitOpencodeShards({ cursors, generationDir, metadata }) {
|
|
760
|
+
const states = opencodeNamespaceStates(cursors.opencode);
|
|
761
|
+
const currentNamespaces = new Set(states.keys());
|
|
762
|
+
|
|
763
|
+
for (const shardMap of [metadata.opencodeMessages, metadata.opencodeFingerprints]) {
|
|
764
|
+
for (const [shardKey, entry] of Object.entries(shardMap)) {
|
|
765
|
+
if (currentNamespaces.has(opencodeShardNamespace(shardKey))) continue;
|
|
766
|
+
if (entry?.file) await fs.unlink(path.join(generationDir, entry.file)).catch(() => {});
|
|
767
|
+
delete shardMap[shardKey];
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
for (const [namespace, state] of states) {
|
|
772
|
+
const messages = state.messages && typeof state.messages === "object"
|
|
773
|
+
? state.messages
|
|
774
|
+
: {};
|
|
775
|
+
const managed = hasOpencodeNamespaceShards({
|
|
776
|
+
namespace,
|
|
777
|
+
metadata: this.generation,
|
|
778
|
+
loadedMessages: this.loadedOpencodeMessageShards,
|
|
779
|
+
loadedFingerprints: this.loadedOpencodeFingerprintShards,
|
|
780
|
+
});
|
|
781
|
+
const materialized = this.materializedOpencodeNamespaces.has(namespace) ||
|
|
782
|
+
(!managed && Object.keys(messages).length > 0);
|
|
783
|
+
|
|
784
|
+
if (materialized) {
|
|
785
|
+
await removeOpencodeNamespaceShards({ generationDir, metadata, namespace });
|
|
786
|
+
const partitioned = partitionOpencodeMessages(messages, namespace);
|
|
787
|
+
for (const [shardKey, data] of partitioned.messageShards) {
|
|
788
|
+
await writeOpencodeShard({
|
|
789
|
+
generationDir,
|
|
790
|
+
metadataMap: metadata.opencodeMessages,
|
|
791
|
+
directory: OPENCODE_MESSAGES_DIRNAME,
|
|
792
|
+
shardKey,
|
|
793
|
+
data,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
for (const [shardKey, data] of partitioned.fingerprintShards) {
|
|
797
|
+
await writeOpencodeShard({
|
|
798
|
+
generationDir,
|
|
799
|
+
metadataMap: metadata.opencodeFingerprints,
|
|
800
|
+
directory: OPENCODE_FINGERPRINTS_DIRNAME,
|
|
801
|
+
shardKey,
|
|
802
|
+
data,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const dirtyMessageShards = new Set([
|
|
809
|
+
...opencodeShardKeysForNamespace(
|
|
810
|
+
namespace,
|
|
811
|
+
{},
|
|
812
|
+
this.loadedOpencodeMessageShards,
|
|
813
|
+
),
|
|
814
|
+
...Object.keys(messages).map((key) => opencodeMessageShardKey(key, namespace)),
|
|
815
|
+
]);
|
|
816
|
+
for (const shardKey of dirtyMessageShards) {
|
|
817
|
+
const data = {};
|
|
818
|
+
for (const [messageKey, entry] of Object.entries(messages)) {
|
|
819
|
+
if (opencodeMessageShardKey(messageKey, namespace) === shardKey) {
|
|
820
|
+
data[messageKey] = entry;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
await writeOpencodeShard({
|
|
824
|
+
generationDir,
|
|
825
|
+
metadataMap: metadata.opencodeMessages,
|
|
826
|
+
directory: OPENCODE_MESSAGES_DIRNAME,
|
|
827
|
+
shardKey,
|
|
828
|
+
data,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const fingerprintIndex = this.opencodeFingerprintIndexes.get(namespace) || new Map();
|
|
833
|
+
for (const shardKey of opencodeShardKeysForNamespace(
|
|
834
|
+
namespace,
|
|
835
|
+
{},
|
|
836
|
+
this.loadedOpencodeFingerprintShards,
|
|
837
|
+
)) {
|
|
838
|
+
const data = {};
|
|
839
|
+
for (const [fingerprint, owners] of fingerprintIndex) {
|
|
840
|
+
if (opencodeFingerprintShardKey(fingerprint, namespace) === shardKey) {
|
|
841
|
+
data[fingerprint] = Array.from(owners);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
await writeOpencodeShard({
|
|
845
|
+
generationDir,
|
|
846
|
+
metadataMap: metadata.opencodeFingerprints,
|
|
847
|
+
directory: OPENCODE_FINGERPRINTS_DIRNAME,
|
|
848
|
+
shardKey,
|
|
849
|
+
data,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
519
855
|
async commit(cursors = this.cursors) {
|
|
520
856
|
const generationId = newGenerationId();
|
|
521
857
|
const generationDir = generationDirectory(this.storeRoot, generationId);
|
|
@@ -523,15 +859,19 @@ class V2CursorStore {
|
|
|
523
859
|
try {
|
|
524
860
|
await ensureDir(path.join(generationDir, "codex-files"));
|
|
525
861
|
await ensureDir(path.join(generationDir, "codex-events"));
|
|
862
|
+
await ensureDir(path.join(generationDir, OPENCODE_MESSAGES_DIRNAME));
|
|
863
|
+
await ensureDir(path.join(generationDir, OPENCODE_FINGERPRINTS_DIRNAME));
|
|
526
864
|
|
|
527
865
|
const nextMetadata = {
|
|
528
866
|
version: STORE_VERSION,
|
|
529
867
|
id: generationId,
|
|
530
868
|
createdAt: new Date().toISOString(),
|
|
531
|
-
coreFile:
|
|
869
|
+
coreFile: SHARDED_CORE_FILENAME,
|
|
532
870
|
codexFiles: cloneJson(this.generation?.codexFiles || {}),
|
|
533
871
|
codexEvents: cloneJson(this.generation?.codexEvents || {}),
|
|
534
|
-
|
|
872
|
+
opencodeMessages: cloneJson(this.generation?.opencodeMessages || {}),
|
|
873
|
+
opencodeFingerprints: cloneJson(this.generation?.opencodeFingerprints || {}),
|
|
874
|
+
counts: {},
|
|
535
875
|
};
|
|
536
876
|
|
|
537
877
|
await cloneGenerationFiles({
|
|
@@ -604,9 +944,16 @@ class V2CursorStore {
|
|
|
604
944
|
}
|
|
605
945
|
}
|
|
606
946
|
|
|
947
|
+
await this.commitOpencodeShards({
|
|
948
|
+
cursors,
|
|
949
|
+
generationDir,
|
|
950
|
+
metadata: nextMetadata,
|
|
951
|
+
});
|
|
952
|
+
|
|
607
953
|
const core = {
|
|
608
954
|
...cursors,
|
|
609
955
|
files: coreFiles,
|
|
956
|
+
opencode: cloneOpencodeCoreState(cursors.opencode),
|
|
610
957
|
};
|
|
611
958
|
delete core.codexHashes;
|
|
612
959
|
await fs.writeFile(
|
|
@@ -624,10 +971,16 @@ class V2CursorStore {
|
|
|
624
971
|
|
|
625
972
|
await maybeInjectFailure(this.failureInjector, "beforeManifestSwap");
|
|
626
973
|
|
|
974
|
+
const compatibleGeneration = this.generation?.coreFile === "core.json"
|
|
975
|
+
? this.generation.id
|
|
976
|
+
: this.manifest?.previous || null;
|
|
627
977
|
const nextManifest = {
|
|
628
978
|
version: STORE_VERSION,
|
|
629
979
|
current: generationId,
|
|
630
|
-
|
|
980
|
+
rollback: this.generation?.id || null,
|
|
981
|
+
// Older v2 readers reject core-v3.json and fall back to this frozen,
|
|
982
|
+
// fully-inline checkpoint instead of opening an empty message index.
|
|
983
|
+
previous: compatibleGeneration,
|
|
631
984
|
legacyFingerprint: this.manifest?.legacyFingerprint || null,
|
|
632
985
|
updatedAt: new Date().toISOString(),
|
|
633
986
|
};
|
|
@@ -642,10 +995,16 @@ class V2CursorStore {
|
|
|
642
995
|
this.loadedEventSets.clear();
|
|
643
996
|
this.pendingEventKeys.clear();
|
|
644
997
|
this.skipValidationCache.clear();
|
|
998
|
+
this.loadedOpencodeMessageShards.clear();
|
|
999
|
+
this.loadedOpencodeFingerprintShards.clear();
|
|
1000
|
+
this.opencodeFingerprintIndexes.clear();
|
|
1001
|
+
this.materializedOpencodeNamespaces.clear();
|
|
645
1002
|
this.materializedCodexHashes = false;
|
|
1003
|
+
this.fallbackGenerationIds = [nextManifest.rollback].filter(Boolean);
|
|
646
1004
|
this.requiresCommit = false;
|
|
647
1005
|
await cleanupGenerations(this.storeRoot, new Set([
|
|
648
1006
|
nextManifest.current,
|
|
1007
|
+
nextManifest.rollback,
|
|
649
1008
|
nextManifest.previous,
|
|
650
1009
|
].filter(Boolean)));
|
|
651
1010
|
} catch (error) {
|
|
@@ -779,9 +1138,10 @@ async function openManifestGeneration({
|
|
|
779
1138
|
}) {
|
|
780
1139
|
const generationIds = Array.from(new Set([
|
|
781
1140
|
manifest.current,
|
|
782
|
-
manifest.previous,
|
|
1141
|
+
manifest.rollback || manifest.previous,
|
|
783
1142
|
].filter(Boolean)));
|
|
784
|
-
for (
|
|
1143
|
+
for (let index = 0; index < generationIds.length; index += 1) {
|
|
1144
|
+
const generationId = generationIds[index];
|
|
785
1145
|
const generation = await readGeneration({ storeRoot, generationId });
|
|
786
1146
|
if (!generation) continue;
|
|
787
1147
|
return new V2CursorStore({
|
|
@@ -789,10 +1149,7 @@ async function openManifestGeneration({
|
|
|
789
1149
|
storeRoot,
|
|
790
1150
|
manifest,
|
|
791
1151
|
generation,
|
|
792
|
-
|
|
793
|
-
generationId === manifest.current && manifest.previous !== generationId
|
|
794
|
-
? manifest.previous
|
|
795
|
-
: null,
|
|
1152
|
+
fallbackGenerationIds: generationIds.slice(index + 1),
|
|
796
1153
|
codexRoots,
|
|
797
1154
|
failureInjector,
|
|
798
1155
|
});
|
|
@@ -848,13 +1205,14 @@ function isCursorStateRoot(value) {
|
|
|
848
1205
|
}
|
|
849
1206
|
|
|
850
1207
|
function isGenerationMetadata(metadata, generationId) {
|
|
1208
|
+
const shardedOpencode = metadata?.coreFile === SHARDED_CORE_FILENAME;
|
|
851
1209
|
if (
|
|
852
1210
|
!metadata ||
|
|
853
1211
|
typeof metadata !== "object" ||
|
|
854
1212
|
Array.isArray(metadata) ||
|
|
855
1213
|
metadata.version !== STORE_VERSION ||
|
|
856
1214
|
metadata.id !== generationId ||
|
|
857
|
-
metadata.coreFile !== "core.json" ||
|
|
1215
|
+
(metadata.coreFile !== "core.json" && !shardedOpencode) ||
|
|
858
1216
|
!metadata.codexFiles ||
|
|
859
1217
|
typeof metadata.codexFiles !== "object" ||
|
|
860
1218
|
Array.isArray(metadata.codexFiles) ||
|
|
@@ -867,10 +1225,25 @@ function isGenerationMetadata(metadata, generationId) {
|
|
|
867
1225
|
) {
|
|
868
1226
|
return false;
|
|
869
1227
|
}
|
|
1228
|
+
if (
|
|
1229
|
+
shardedOpencode &&
|
|
1230
|
+
(!metadata.opencodeMessages ||
|
|
1231
|
+
typeof metadata.opencodeMessages !== "object" ||
|
|
1232
|
+
Array.isArray(metadata.opencodeMessages) ||
|
|
1233
|
+
!metadata.opencodeFingerprints ||
|
|
1234
|
+
typeof metadata.opencodeFingerprints !== "object" ||
|
|
1235
|
+
Array.isArray(metadata.opencodeFingerprints))
|
|
1236
|
+
) {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
870
1239
|
|
|
871
1240
|
return (
|
|
872
1241
|
validShardMetadataMap(metadata.codexFiles, "codex-files") &&
|
|
873
|
-
validShardMetadataMap(metadata.codexEvents, "codex-events")
|
|
1242
|
+
validShardMetadataMap(metadata.codexEvents, "codex-events") &&
|
|
1243
|
+
(!shardedOpencode || (
|
|
1244
|
+
validShardMetadataMap(metadata.opencodeMessages, OPENCODE_MESSAGES_DIRNAME) &&
|
|
1245
|
+
validShardMetadataMap(metadata.opencodeFingerprints, OPENCODE_FINGERPRINTS_DIRNAME)
|
|
1246
|
+
))
|
|
874
1247
|
);
|
|
875
1248
|
}
|
|
876
1249
|
|
|
@@ -898,10 +1271,7 @@ function validShardMetadataMap(shards, expectedDirectory) {
|
|
|
898
1271
|
}
|
|
899
1272
|
|
|
900
1273
|
async function generationReferencesExist(directory, metadata) {
|
|
901
|
-
for (const [expectedDirectory, shards] of
|
|
902
|
-
["codex-files", metadata.codexFiles],
|
|
903
|
-
["codex-events", metadata.codexEvents],
|
|
904
|
-
]) {
|
|
1274
|
+
for (const [expectedDirectory, shards] of generationShardMaps(metadata)) {
|
|
905
1275
|
const expectedNames = Object.values(shards)
|
|
906
1276
|
.map((entry) => normalizeShardReference(entry.file, expectedDirectory).name);
|
|
907
1277
|
if (expectedNames.length === 0) continue;
|
|
@@ -921,10 +1291,7 @@ async function generationReferencesExist(directory, metadata) {
|
|
|
921
1291
|
}
|
|
922
1292
|
|
|
923
1293
|
function generationReferencesExistSync(directory, metadata) {
|
|
924
|
-
for (const [expectedDirectory, shards] of
|
|
925
|
-
["codex-files", metadata.codexFiles],
|
|
926
|
-
["codex-events", metadata.codexEvents],
|
|
927
|
-
]) {
|
|
1294
|
+
for (const [expectedDirectory, shards] of generationShardMaps(metadata)) {
|
|
928
1295
|
const expectedNames = Object.values(shards)
|
|
929
1296
|
.map((entry) => normalizeShardReference(entry.file, expectedDirectory).name);
|
|
930
1297
|
if (expectedNames.length === 0) continue;
|
|
@@ -959,17 +1326,31 @@ function normalizeShardReference(relativeFile, expectedDirectory) {
|
|
|
959
1326
|
return { directory: expectedDirectory, name: parts[1] };
|
|
960
1327
|
}
|
|
961
1328
|
|
|
1329
|
+
function generationShardMaps(metadata) {
|
|
1330
|
+
return [
|
|
1331
|
+
["codex-files", metadata.codexFiles || {}],
|
|
1332
|
+
["codex-events", metadata.codexEvents || {}],
|
|
1333
|
+
[OPENCODE_MESSAGES_DIRNAME, metadata.opencodeMessages || {}],
|
|
1334
|
+
[OPENCODE_FINGERPRINTS_DIRNAME, metadata.opencodeFingerprints || {}],
|
|
1335
|
+
];
|
|
1336
|
+
}
|
|
1337
|
+
|
|
962
1338
|
function sameGenerationCounts(left, right) {
|
|
963
|
-
return [
|
|
964
|
-
|
|
1339
|
+
return [
|
|
1340
|
+
"nonCodexFiles",
|
|
1341
|
+
"codexFiles",
|
|
1342
|
+
"totalFiles",
|
|
1343
|
+
"codexEvents",
|
|
1344
|
+
"opencodeMessages",
|
|
1345
|
+
"opencodeFingerprints",
|
|
1346
|
+
].every((key) => Number(left?.[key] || 0) === Number(right?.[key] || 0));
|
|
965
1347
|
}
|
|
966
1348
|
|
|
967
1349
|
async function cloneGenerationFiles({ fromDirectory, toDirectory, metadata }) {
|
|
968
|
-
for (const
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
if (entry?.file) await cloneOrCopy(fromDirectory, toDirectory, entry.file);
|
|
1350
|
+
for (const [, shards] of generationShardMaps(metadata)) {
|
|
1351
|
+
for (const entry of Object.values(shards)) {
|
|
1352
|
+
if (entry?.file) await cloneOrCopy(fromDirectory, toDirectory, entry.file);
|
|
1353
|
+
}
|
|
973
1354
|
}
|
|
974
1355
|
}
|
|
975
1356
|
|
|
@@ -1099,6 +1480,196 @@ function assertShardIntegrity(raw, metadata, label) {
|
|
|
1099
1480
|
}
|
|
1100
1481
|
}
|
|
1101
1482
|
|
|
1483
|
+
async function readJsonObjectShard({
|
|
1484
|
+
generationDir,
|
|
1485
|
+
metadata,
|
|
1486
|
+
label,
|
|
1487
|
+
validate = (value) => value && typeof value === "object" && !Array.isArray(value),
|
|
1488
|
+
}) {
|
|
1489
|
+
if (!metadata?.file) return {};
|
|
1490
|
+
let raw;
|
|
1491
|
+
try {
|
|
1492
|
+
raw = await fs.readFile(path.join(generationDir, metadata.file), "utf8");
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
throw cursorStoreCorruption(`Unable to read ${label}`, error);
|
|
1495
|
+
}
|
|
1496
|
+
assertShardIntegrity(raw, metadata, label);
|
|
1497
|
+
let data;
|
|
1498
|
+
try {
|
|
1499
|
+
data = JSON.parse(raw);
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
throw cursorStoreCorruption(`Invalid ${label}`, error);
|
|
1502
|
+
}
|
|
1503
|
+
if (
|
|
1504
|
+
!data ||
|
|
1505
|
+
typeof data !== "object" ||
|
|
1506
|
+
Array.isArray(data) ||
|
|
1507
|
+
Object.keys(data).length !== metadata.count ||
|
|
1508
|
+
Object.values(data).some((value) => !validate(value))
|
|
1509
|
+
) {
|
|
1510
|
+
throw cursorStoreCorruption(`Invalid ${label}`);
|
|
1511
|
+
}
|
|
1512
|
+
return data;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
async function writeOpencodeShard({
|
|
1516
|
+
generationDir,
|
|
1517
|
+
metadataMap,
|
|
1518
|
+
directory,
|
|
1519
|
+
shardKey,
|
|
1520
|
+
data,
|
|
1521
|
+
}) {
|
|
1522
|
+
const previousFile = metadataMap[shardKey]?.file;
|
|
1523
|
+
if (previousFile) await fs.unlink(path.join(generationDir, previousFile)).catch(() => {});
|
|
1524
|
+
if (Object.keys(data).length === 0) {
|
|
1525
|
+
delete metadataMap[shardKey];
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
const relativeFile = path.join(directory, `${shardKey}.json`);
|
|
1529
|
+
const serialized = `${JSON.stringify(data)}\n`;
|
|
1530
|
+
await fs.writeFile(path.join(generationDir, relativeFile), serialized, "utf8");
|
|
1531
|
+
metadataMap[shardKey] = {
|
|
1532
|
+
file: relativeFile,
|
|
1533
|
+
count: Object.keys(data).length,
|
|
1534
|
+
...buildShardIntegrity(serialized),
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
async function removeOpencodeNamespaceShards({ generationDir, metadata, namespace }) {
|
|
1539
|
+
for (const shardMap of [metadata.opencodeMessages, metadata.opencodeFingerprints]) {
|
|
1540
|
+
for (const [shardKey, entry] of Object.entries(shardMap)) {
|
|
1541
|
+
if (opencodeShardNamespace(shardKey) !== namespace) continue;
|
|
1542
|
+
if (entry?.file) await fs.unlink(path.join(generationDir, entry.file)).catch(() => {});
|
|
1543
|
+
delete shardMap[shardKey];
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
function normalizeOpencodeNamespace(value) {
|
|
1549
|
+
return value === "native" || value === "wsl" ? value : "flat";
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function opencodeNamespaceStates(state) {
|
|
1553
|
+
const out = new Map();
|
|
1554
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) return out;
|
|
1555
|
+
if (state.native !== undefined || state.wsl !== undefined) {
|
|
1556
|
+
for (const namespace of ["native", "wsl"]) {
|
|
1557
|
+
const child = state[namespace];
|
|
1558
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
1559
|
+
out.set(namespace, child);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
} else {
|
|
1563
|
+
out.set("flat", state);
|
|
1564
|
+
}
|
|
1565
|
+
return out;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function opencodeStateForNamespace(cursors, namespace, create = false) {
|
|
1569
|
+
if (!cursors.opencode || typeof cursors.opencode !== "object" || Array.isArray(cursors.opencode)) {
|
|
1570
|
+
if (!create) return null;
|
|
1571
|
+
cursors.opencode = namespace === "flat" ? {} : { native: {}, wsl: {} };
|
|
1572
|
+
}
|
|
1573
|
+
const root = cursors.opencode;
|
|
1574
|
+
const namespaced = root.native !== undefined || root.wsl !== undefined;
|
|
1575
|
+
if (namespace === "flat") return namespaced ? null : root;
|
|
1576
|
+
if (!namespaced) return null;
|
|
1577
|
+
if (!root[namespace] || typeof root[namespace] !== "object" || Array.isArray(root[namespace])) {
|
|
1578
|
+
if (!create) return null;
|
|
1579
|
+
root[namespace] = {};
|
|
1580
|
+
}
|
|
1581
|
+
return root[namespace];
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function opencodeShardHash(value) {
|
|
1585
|
+
return crypto.createHash("sha256").update(value).digest("hex").slice(0, 2);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function opencodeMessageShardKey(messageKey, namespace = "flat") {
|
|
1589
|
+
return `${normalizeOpencodeNamespace(namespace)}-${opencodeShardHash(messageKey)}`;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function opencodeFingerprintShardKey(fingerprint, namespace = "flat") {
|
|
1593
|
+
return `${normalizeOpencodeNamespace(namespace)}-${opencodeShardHash(fingerprint)}`;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function opencodeShardNamespace(shardKey) {
|
|
1597
|
+
const namespace = typeof shardKey === "string" ? shardKey.split("-", 1)[0] : "";
|
|
1598
|
+
return namespace === "native" || namespace === "wsl" ? namespace : "flat";
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
function opencodeShardKeysForNamespace(namespace, metadataMap, loadedMap) {
|
|
1602
|
+
return Array.from(new Set([
|
|
1603
|
+
...Object.keys(metadataMap || {}),
|
|
1604
|
+
...Array.from(loadedMap?.keys?.() || []),
|
|
1605
|
+
].filter((shardKey) => opencodeShardNamespace(shardKey) === namespace)));
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function hasOpencodeNamespaceShards({
|
|
1609
|
+
namespace,
|
|
1610
|
+
metadata,
|
|
1611
|
+
loadedMessages,
|
|
1612
|
+
loadedFingerprints,
|
|
1613
|
+
}) {
|
|
1614
|
+
return (
|
|
1615
|
+
opencodeShardKeysForNamespace(namespace, metadata.opencodeMessages, loadedMessages).length > 0 ||
|
|
1616
|
+
opencodeShardKeysForNamespace(namespace, metadata.opencodeFingerprints, loadedFingerprints).length > 0
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
function opencodeNamespacesForStore({
|
|
1621
|
+
state,
|
|
1622
|
+
metadata,
|
|
1623
|
+
loadedMessages,
|
|
1624
|
+
loadedFingerprints,
|
|
1625
|
+
}) {
|
|
1626
|
+
const namespaces = new Set(opencodeNamespaceStates(state).keys());
|
|
1627
|
+
for (const shardKey of [
|
|
1628
|
+
...Object.keys(metadata.opencodeMessages || {}),
|
|
1629
|
+
...Object.keys(metadata.opencodeFingerprints || {}),
|
|
1630
|
+
...Array.from(loadedMessages.keys()),
|
|
1631
|
+
...Array.from(loadedFingerprints.keys()),
|
|
1632
|
+
]) {
|
|
1633
|
+
namespaces.add(opencodeShardNamespace(shardKey));
|
|
1634
|
+
}
|
|
1635
|
+
return namespaces;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function partitionOpencodeMessages(messages, namespace) {
|
|
1639
|
+
const messageShards = new Map();
|
|
1640
|
+
const fingerprintShards = new Map();
|
|
1641
|
+
for (const messageKey in messages || {}) {
|
|
1642
|
+
const entry = messages[messageKey];
|
|
1643
|
+
const messageShard = opencodeMessageShardKey(messageKey, namespace);
|
|
1644
|
+
if (!messageShards.has(messageShard)) messageShards.set(messageShard, {});
|
|
1645
|
+
messageShards.get(messageShard)[messageKey] = entry;
|
|
1646
|
+
const fingerprint = typeof entry?.fingerprint === "string" ? entry.fingerprint : null;
|
|
1647
|
+
if (!fingerprint || entry?.dedupedForkCopy === true) continue;
|
|
1648
|
+
const fingerprintShard = opencodeFingerprintShardKey(fingerprint, namespace);
|
|
1649
|
+
if (!fingerprintShards.has(fingerprintShard)) fingerprintShards.set(fingerprintShard, {});
|
|
1650
|
+
const shard = fingerprintShards.get(fingerprintShard);
|
|
1651
|
+
if (!shard[fingerprint]) shard[fingerprint] = [];
|
|
1652
|
+
shard[fingerprint].push(messageKey);
|
|
1653
|
+
}
|
|
1654
|
+
return { messageShards, fingerprintShards };
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function cloneOpencodeCoreState(state) {
|
|
1658
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) return state;
|
|
1659
|
+
if (state.native !== undefined || state.wsl !== undefined) {
|
|
1660
|
+
const clone = { ...state };
|
|
1661
|
+
for (const namespace of ["native", "wsl"]) {
|
|
1662
|
+
if (!state[namespace] || typeof state[namespace] !== "object") continue;
|
|
1663
|
+
clone[namespace] = { ...state[namespace] };
|
|
1664
|
+
delete clone[namespace].messages;
|
|
1665
|
+
}
|
|
1666
|
+
return clone;
|
|
1667
|
+
}
|
|
1668
|
+
const clone = { ...state };
|
|
1669
|
+
delete clone.messages;
|
|
1670
|
+
return clone;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1102
1673
|
async function validateProjectSummary(summary, nowMs) {
|
|
1103
1674
|
if (!summary || typeof summary !== "object") return false;
|
|
1104
1675
|
const absentFreshUntil = Number(summary.absentFreshUntil);
|
|
@@ -1176,11 +1747,17 @@ function calculateGenerationCounts(metadata, coreFiles) {
|
|
|
1176
1747
|
.reduce((sum, entry) => sum + Number(entry?.count || 0), 0);
|
|
1177
1748
|
const codexEvents = Object.values(metadata.codexEvents || {})
|
|
1178
1749
|
.reduce((sum, entry) => sum + Number(entry?.count || 0), 0);
|
|
1750
|
+
const opencodeMessages = Object.values(metadata.opencodeMessages || {})
|
|
1751
|
+
.reduce((sum, entry) => sum + Number(entry?.count || 0), 0);
|
|
1752
|
+
const opencodeFingerprints = Object.values(metadata.opencodeFingerprints || {})
|
|
1753
|
+
.reduce((sum, entry) => sum + Number(entry?.count || 0), 0);
|
|
1179
1754
|
return {
|
|
1180
1755
|
nonCodexFiles,
|
|
1181
1756
|
codexFiles,
|
|
1182
1757
|
totalFiles: nonCodexFiles + codexFiles,
|
|
1183
1758
|
codexEvents,
|
|
1759
|
+
opencodeMessages,
|
|
1760
|
+
opencodeFingerprints,
|
|
1184
1761
|
};
|
|
1185
1762
|
}
|
|
1186
1763
|
|
|
@@ -1332,6 +1909,8 @@ module.exports = {
|
|
|
1332
1909
|
STORE_VERSION,
|
|
1333
1910
|
codexEventShardKey,
|
|
1334
1911
|
codexFileShardKey,
|
|
1912
|
+
opencodeFingerprintShardKey,
|
|
1913
|
+
opencodeMessageShardKey,
|
|
1335
1914
|
isCodexSessionCursorPath,
|
|
1336
1915
|
isCursorStoreRetry,
|
|
1337
1916
|
openCursorStore,
|