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.
Files changed (118) hide show
  1. package/README.md +2 -1
  2. package/dashboard/dist/assets/{AchievementBadge-DuEuFaXg.js → AchievementBadge-DHCKPAgO.js} +1 -1
  3. package/dashboard/dist/assets/{AchievementsPage-BTTFtk_Z.js → AchievementsPage-BqlGMD7S.js} +2 -2
  4. package/dashboard/dist/assets/{AchievementsSection-BZppKteZ.js → AchievementsSection-CTTTR9CN.js} +1 -1
  5. package/dashboard/dist/assets/{BadgeDetailModal-TzmxXy8k.js → BadgeDetailModal-e0Iz8ct4.js} +1 -1
  6. package/dashboard/dist/assets/BotAnimated-BLYZyT1O.js +1 -0
  7. package/dashboard/dist/assets/{Card-HigkIm_b.js → Card-EHkn9ktD.js} +1 -1
  8. package/dashboard/dist/assets/{CommandPalette-Bx_11gJk.js → CommandPalette-CuSVstX9.js} +1 -1
  9. package/dashboard/dist/assets/{CommunityStatsModal-BjDPCBol.js → CommunityStatsModal-Cb1vefLE.js} +1 -1
  10. package/dashboard/dist/assets/{ConfirmModal-DTo93RpG.js → ConfirmModal-pIUQJ7QM.js} +1 -1
  11. package/dashboard/dist/assets/Controls-CX4Di57j.js +1 -0
  12. package/dashboard/dist/assets/DashboardPage-D1Ox1y3E.js +1 -0
  13. package/dashboard/dist/assets/{DevicePage-BVupGt9K.js → DevicePage-Q57gBE8A.js} +1 -1
  14. package/dashboard/dist/assets/{DialogClose-BlQJpi7n.js → DialogClose-Dn-nxOSp.js} +1 -1
  15. package/dashboard/dist/assets/DialogDescription-GXslTq2z.js +1 -0
  16. package/dashboard/dist/assets/{DialogTitle-CDmxp9ap.js → DialogTitle-DuAY9sfZ.js} +1 -1
  17. package/dashboard/dist/assets/{FadeIn-DPercU5n.js → FadeIn-25jNO9JV.js} +1 -1
  18. package/dashboard/dist/assets/{HeaderGithubStar-DeVzeBIo.js → HeaderGithubStar-0-ne32-S.js} +1 -1
  19. package/dashboard/dist/assets/{HoverTooltip-Duo2Icus.js → HoverTooltip-C2Qop8B3.js} +1 -1
  20. package/dashboard/dist/assets/{IpCheckPage-DgYiaRrT.js → IpCheckPage-Q9lwiFdt.js} +1 -1
  21. package/dashboard/dist/assets/{LandingPage-CYTR4PbQ.js → LandingPage-iX6E8Flr.js} +2 -2
  22. package/dashboard/dist/assets/{LeaderboardAvatar-B67pMaIl.js → LeaderboardAvatar-CiQrgneF.js} +1 -1
  23. package/dashboard/dist/assets/{LeaderboardPage-2wIMkBgq.js → LeaderboardPage-BIhIA28A.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardProfileModal-D3VxkFlx.js → LeaderboardProfileModal-CF-MI8rL.js} +4 -4
  25. package/dashboard/dist/assets/{LeaderboardProfilePage-BS01jN5M.js → LeaderboardProfilePage-DRbLb-fO.js} +3 -3
  26. package/dashboard/dist/assets/{LimitsPage-BGRZW-aH.js → LimitsPage-lvYidBkz.js} +2 -2
  27. package/dashboard/dist/assets/{LocalOnlyNotice-Do9KUdi_.js → LocalOnlyNotice-CAQTnlaI.js} +1 -1
  28. package/dashboard/dist/assets/{LoginCard-CzAXLXfZ.js → LoginCard-Ci2Rrsfl.js} +1 -1
  29. package/dashboard/dist/assets/{LoginModal-D1J8zG0C.js → LoginModal-DhyQPl-V.js} +1 -1
  30. package/dashboard/dist/assets/{LoginPage-DeZJNMLe.js → LoginPage-B6q8P6hE.js} +1 -1
  31. package/dashboard/dist/assets/PetPage-82Cldgg6.js +1 -0
  32. package/dashboard/dist/assets/{PopoverPopup-BYzzQJTD.js → PopoverPopup-Btcu7vU0.js} +1 -1
  33. package/dashboard/dist/assets/{ProviderIcon-BfFkS-OF.js → ProviderIcon-aLwclDsD.js} +1 -1
  34. package/dashboard/dist/assets/{ResetPasswordPage-BFyYvG24.js → ResetPasswordPage-BlRl_aJr.js} +1 -1
  35. package/dashboard/dist/assets/{SegmentedControl-D5USR3JT.js → SegmentedControl-2O_XVL8u.js} +1 -1
  36. package/dashboard/dist/assets/{Select-DmFQdVA1.js → Select-D5uuRQwh.js} +1 -1
  37. package/dashboard/dist/assets/{SelectItemText-B5MXGLN-.js → SelectItemText-Bq_ZdWU0.js} +1 -1
  38. package/dashboard/dist/assets/{ServiceStatusPage-Dvm5eUJK.js → ServiceStatusPage-Pd6dgJua.js} +1 -1
  39. package/dashboard/dist/assets/{SessionsPage-DQKezzUW.js → SessionsPage-DjHudcn6.js} +2 -2
  40. package/dashboard/dist/assets/SettingsPage-DOLhZiBE.js +2 -0
  41. package/dashboard/dist/assets/{SkillsPage-DyX_-dcr.js → SkillsPage-D9-ZorB5.js} +1 -1
  42. package/dashboard/dist/assets/{TokenGalaxy-aK3jhWW0.js → TokenGalaxy-BN71jQQ8.js} +1 -1
  43. package/dashboard/dist/assets/{ToolbarRootContext-CY_SULMC.js → ToolbarRootContext-B0cqyD1i.js} +1 -1
  44. package/dashboard/dist/assets/TrendMonitor-DWxtbzyQ.js +19 -0
  45. package/dashboard/dist/assets/{WidgetsPage-D8tzK-7m.js → WidgetsPage-BwmJ2Z9d.js} +1 -1
  46. package/dashboard/dist/assets/{WrappedPage-BjZPa_al.js → WrappedPage-Cq8DrzLm.js} +1 -1
  47. package/dashboard/dist/assets/__vite-browser-external-CPMSmJgP.js +45 -0
  48. package/dashboard/dist/assets/__vite-browser-external-DrXtuPP9.css +1 -0
  49. package/dashboard/dist/assets/{agent-logos-QSoXRwwJ.js → agent-logos-X7FQq7rz.js} +1 -1
  50. package/dashboard/dist/assets/{arrow-up-right-C3WUmTcI.js → arrow-up-right-CPxuidzy.js} +1 -1
  51. package/dashboard/dist/assets/{check-Gz5mnj6f.js → check-D2K19ObI.js} +1 -1
  52. package/dashboard/dist/assets/{composite-DJhoahpv.js → composite-BN1UKLj0.js} +1 -1
  53. package/dashboard/dist/assets/{copy-C5RiL5JQ.js → copy-CSN17qFv.js} +1 -1
  54. package/dashboard/dist/assets/{download-CccMGCc1.js → download-CpW-TB2h.js} +1 -1
  55. package/dashboard/dist/assets/{external-link-fdU7Lj6p.js → external-link-ChBchK6G.js} +1 -1
  56. package/dashboard/dist/assets/{flame-CYEjossP.js → flame-f7119ZMd.js} +1 -1
  57. package/dashboard/dist/assets/{format-tokens-xXCznLkf.js → format-tokens-COrRnk53.js} +1 -1
  58. package/dashboard/dist/assets/{icons-CYNYszil.js → icons-Dn9vA9Qu.js} +1 -1
  59. package/dashboard/dist/assets/{index-CQ5GHXiW.js → index-BCNOKYSO.js} +1 -1
  60. package/dashboard/dist/assets/{index-Czz7x_MZ.js → index-CA-94DFW.js} +1 -1
  61. package/dashboard/dist/assets/{index-Cgfb050k.js → index-CElyhNzh.js} +1 -1
  62. package/dashboard/dist/assets/{info-Zae50fSC.js → info-BHsPBczD.js} +1 -1
  63. package/dashboard/dist/assets/{link-2-DW_mHLzI.js → link-2-BHfEWv22.js} +1 -1
  64. package/dashboard/dist/assets/{loader-circle-Bl7dBXh1.js → loader-circle-DTIT1Uk1.js} +1 -1
  65. package/dashboard/dist/assets/{maximize-2-DTP25Fl_.js → maximize-2-BmtIBzLu.js} +1 -1
  66. package/dashboard/dist/assets/{modifiers.esm-CYXpOF3i.js → modifiers.esm-CPBla3OK.js} +1 -1
  67. package/dashboard/dist/assets/{monitor-smartphone--TLNLuLe.js → monitor-smartphone-CbsYB-S2.js} +1 -1
  68. package/dashboard/dist/assets/{plus-DgeFv3Xc.js → plus-CZiNoMsy.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-crAVn3qa.js → provider-display-CXKRt9q1.js} +2 -2
  70. package/dashboard/dist/assets/{react-QPulAuJP.js → react-CZdcuJ28.js} +1 -1
  71. package/dashboard/dist/assets/{search-DIUmGfar.js → search-q2ARMGca.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-09qiTRNb.js → skills-api-CIyqyk-w.js} +1 -1
  73. package/dashboard/dist/assets/skins-Mc0sI20C.js +1 -0
  74. package/dashboard/dist/assets/{sortable.esm-BbsbmOc2.js → sortable.esm-BztIEZTk.js} +1 -1
  75. package/dashboard/dist/assets/{store-DdyzwTsy.js → store-DvdXwk-7.js} +1 -1
  76. package/dashboard/dist/assets/{terminal-JpDnkjVA.js → terminal-CFQOW2Ey.js} +1 -1
  77. package/dashboard/dist/assets/{trash-CRsyysiP.js → trash-D0ptaWPk.js} +1 -1
  78. package/dashboard/dist/assets/{use-community-stats-D3PwUyvR.js → use-community-stats-DYibzhAl.js} +1 -1
  79. package/dashboard/dist/assets/{use-latest-request-guard-DTCniVRw.js → use-latest-request-guard-viIhH7H5.js} +1 -1
  80. package/dashboard/dist/assets/{use-limits-display-prefs-BbYcvoh_.js → use-limits-display-prefs-CbXApWU1.js} +1 -1
  81. package/dashboard/dist/assets/{use-ordered-list-ypqFhcs4.js → use-ordered-list-BBP8hr8y.js} +1 -1
  82. package/dashboard/dist/assets/use-pet-settings-CIrPZ9pB.js +2 -0
  83. package/dashboard/dist/assets/use-reduced-motion-Dpq-cKyM.js +1 -0
  84. package/dashboard/dist/assets/{use-session-efficiency-pref-C5BZmpkY.js → use-session-efficiency-pref-2Vb3tJrw.js} +1 -1
  85. package/dashboard/dist/assets/{use-transform-C3bGaC_U.js → use-transform-CECRFELz.js} +1 -1
  86. package/dashboard/dist/assets/use-usage-limits-DEnbjOVG.js +1 -0
  87. package/dashboard/dist/assets/{useCurrency-BianRgdW.js → useCurrency-Dh9pUzDO.js} +1 -1
  88. package/dashboard/dist/assets/useTokenFormat-CoGcVhPP.js +1 -0
  89. package/dashboard/dist/assets/{zap-BJTChg1t.js → zap-BdeAxq64.js} +1 -1
  90. package/dashboard/dist/index.html +2 -2
  91. package/dashboard/dist/ip-check.html +2 -2
  92. package/dashboard/dist/leaderboard.html +2 -2
  93. package/dashboard/dist/share.html +2 -2
  94. package/package.json +3 -2
  95. package/src/commands/sync.js +57 -29
  96. package/src/lib/claude-categorizer.js +4 -7
  97. package/src/lib/claude-user-identity.js +56 -0
  98. package/src/lib/cursor-store.js +636 -57
  99. package/src/lib/grok-limits.js +17 -4
  100. package/src/lib/kimi-profile.js +61 -0
  101. package/src/lib/multi-install-parser.js +3 -1
  102. package/src/lib/pricing/seed-snapshot.json +1 -1
  103. package/src/lib/rollout.js +91 -39
  104. package/src/lib/usage-limits.js +128 -79
  105. package/dashboard/dist/assets/BotAnimated-4hWTFjwp.js +0 -1
  106. package/dashboard/dist/assets/ClawdAnimated-BRoihVSd.js +0 -2
  107. package/dashboard/dist/assets/Controls-BvWTXyfI.js +0 -1
  108. package/dashboard/dist/assets/DashboardPage--1_GuEr-.js +0 -1
  109. package/dashboard/dist/assets/DialogDescription-DE2BF-HX.js +0 -1
  110. package/dashboard/dist/assets/PetPage-D2oqJ05F.js +0 -1
  111. package/dashboard/dist/assets/SettingsPage-DbUDye5r.js +0 -2
  112. package/dashboard/dist/assets/TrendMonitor-BH6Jx8nS.js +0 -19
  113. package/dashboard/dist/assets/__vite-browser-external-Bg0J-a5P.css +0 -1
  114. package/dashboard/dist/assets/__vite-browser-external-VmN0WaC3.js +0 -45
  115. package/dashboard/dist/assets/skins-C8EyB85e.js +0 -1
  116. package/dashboard/dist/assets/use-reduced-motion-Cdn1VrG0.js +0 -1
  117. package/dashboard/dist/assets/use-usage-limits-BRLNaZEv.js +0 -1
  118. package/dashboard/dist/assets/useTokenFormat-DO4vxqhW.js +0 -1
@@ -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
- manifest = await migrateLegacyCursorState({
70
- cursorsPath,
71
- storeRoot,
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({ storeRoot, generationId: manifest.previous });
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
- fallbackGenerationId,
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.fallbackGenerationId = fallbackGenerationId;
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
- const generationId = this.fallbackGenerationId;
494
- this.fallbackGenerationId = null;
495
- if (!generationId) return false;
496
-
497
- const fallback = readGenerationSync({
498
- storeRoot: this.storeRoot,
499
- generationId,
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: "core.json",
869
+ coreFile: SHARDED_CORE_FILENAME,
532
870
  codexFiles: cloneJson(this.generation?.codexFiles || {}),
533
871
  codexEvents: cloneJson(this.generation?.codexEvents || {}),
534
- counts: { nonCodexFiles: 0, codexFiles: 0, totalFiles: 0, codexEvents: 0 },
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
- previous: this.generation?.id || null,
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 (const generationId of generationIds) {
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
- fallbackGenerationId:
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 ["nonCodexFiles", "codexFiles", "totalFiles", "codexEvents"]
964
- .every((key) => Number(left?.[key]) === Number(right?.[key]));
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 entry of Object.values(metadata.codexFiles || {})) {
969
- if (entry?.file) await cloneOrCopy(fromDirectory, toDirectory, entry.file);
970
- }
971
- for (const entry of Object.values(metadata.codexEvents || {})) {
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,