tokentracker-cli 0.88.1 → 0.88.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-Crrws1U1.js → AchievementBadge-BcCrg832.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-C65tiTnu.js → AchievementsPage-C95dF_C-.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-Gc28pe4U.js → AchievementsSection-B-VD8CfN.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-D_jEjBVd.js → BadgeDetailModal-CDTJ3tR6.js} +1 -1
  5. package/dashboard/dist/assets/{Card-DlZueiVg.js → Card-a0RpvhLn.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-DrwVfg1s.js → ClawdAnimated-CaLKUbmC.js} +1 -1
  7. package/dashboard/dist/assets/{CommandPalette-Dj753h1Y.js → CommandPalette-CQpkX7dK.js} +1 -1
  8. package/dashboard/dist/assets/{CommunityStatsModal-PiK4kOib.js → CommunityStatsModal-Cj53g1ky.js} +1 -1
  9. package/dashboard/dist/assets/{ConfirmModal-CJz-TMiD.js → ConfirmModal-FHZmD91i.js} +1 -1
  10. package/dashboard/dist/assets/{Controls-DZ0W7HPO.js → Controls-BSk2Eph4.js} +1 -1
  11. package/dashboard/dist/assets/{DashboardPage-DEDZy960.js → DashboardPage-Ci1jVw-F.js} +1 -1
  12. package/dashboard/dist/assets/{DevicePage-BR60A3uN.js → DevicePage-BnCWVmOI.js} +1 -1
  13. package/dashboard/dist/assets/{DialogClose-qcbYLZ_r.js → DialogClose-BUhPBSeF.js} +1 -1
  14. package/dashboard/dist/assets/DialogDescription-Dp7bBdfJ.js +1 -0
  15. package/dashboard/dist/assets/{DialogTitle-KMA1u6gh.js → DialogTitle-CbNKnmTY.js} +1 -1
  16. package/dashboard/dist/assets/{FadeIn-BtTL9V_I.js → FadeIn-D87p8xfM.js} +1 -1
  17. package/dashboard/dist/assets/{HeaderGithubStar-Ckp60oxi.js → HeaderGithubStar-kE4I1PyE.js} +1 -1
  18. package/dashboard/dist/assets/{HoverTooltip-DaCmGdvh.js → HoverTooltip-miw72aLh.js} +1 -1
  19. package/dashboard/dist/assets/{IpCheckPage-Bs2xgpWT.js → IpCheckPage-D-JoHTz-.js} +1 -1
  20. package/dashboard/dist/assets/{LandingPage-Dj6382lF.js → LandingPage-Dx_MOVjJ.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardAvatar-4LQRczW4.js → LeaderboardAvatar-arnp_cWI.js} +1 -1
  22. package/dashboard/dist/assets/{LeaderboardPage-BKTbW7CC.js → LeaderboardPage-zsoFUX5G.js} +2 -2
  23. package/dashboard/dist/assets/{LeaderboardProfileModal-BP-I9FrE.js → LeaderboardProfileModal-CrmSTl9n.js} +3 -3
  24. package/dashboard/dist/assets/{LeaderboardProfilePage-CWzqXfsO.js → LeaderboardProfilePage-BSE0fPco.js} +1 -1
  25. package/dashboard/dist/assets/{LimitsPage-DJL3rkrZ.js → LimitsPage-BrU9e8AV.js} +1 -1
  26. package/dashboard/dist/assets/{LocalOnlyNotice-CJ8aY8Yg.js → LocalOnlyNotice-BWDZKn00.js} +1 -1
  27. package/dashboard/dist/assets/{LoginCard-RfbqlInv.js → LoginCard-DS8mxxs_.js} +1 -1
  28. package/dashboard/dist/assets/{LoginModal-DEgSV0ho.js → LoginModal-DhINfVDV.js} +1 -1
  29. package/dashboard/dist/assets/{LoginPage-biogQhPJ.js → LoginPage-BKmuhR74.js} +1 -1
  30. package/dashboard/dist/assets/{PetPage-bbmFMllf.js → PetPage-QQk_B1SJ.js} +1 -1
  31. package/dashboard/dist/assets/{PopoverPopup-Bx_a2ATx.js → PopoverPopup-DS6ml7IE.js} +1 -1
  32. package/dashboard/dist/assets/{ProviderIcon-ClMKnRNE.js → ProviderIcon-DkHp1Hrl.js} +1 -1
  33. package/dashboard/dist/assets/{ResetPasswordPage-B0aAisWC.js → ResetPasswordPage-CvJiufPF.js} +1 -1
  34. package/dashboard/dist/assets/{SegmentedControl-85G6K4nz.js → SegmentedControl-DbYmpdj3.js} +1 -1
  35. package/dashboard/dist/assets/{Select-D-PLRmNY.js → Select-DJcVfYm7.js} +1 -1
  36. package/dashboard/dist/assets/{SelectItemText-DOQMVovf.js → SelectItemText-DUQLkHGQ.js} +1 -1
  37. package/dashboard/dist/assets/{ServiceStatusPage-C2fq26Du.js → ServiceStatusPage-DqPQUM1f.js} +1 -1
  38. package/dashboard/dist/assets/{SessionsPage-B-Gzrsxr.js → SessionsPage-CPOSombb.js} +1 -1
  39. package/dashboard/dist/assets/{SettingsPage-DzjURmD5.js → SettingsPage-nIfi1h7D.js} +1 -1
  40. package/dashboard/dist/assets/{SkillsPage-Bmu5fgPF.js → SkillsPage-BnngQwIu.js} +1 -1
  41. package/dashboard/dist/assets/{TokenGalaxy-DFVGDrXI.js → TokenGalaxy-CAVmlIYW.js} +1 -1
  42. package/dashboard/dist/assets/{ToolbarRootContext-CgWa03di.js → ToolbarRootContext-CyG7xQo2.js} +1 -1
  43. package/dashboard/dist/assets/{TrendMonitor-CbLhCRaf.js → TrendMonitor-DyuKVbzV.js} +1 -1
  44. package/dashboard/dist/assets/{WidgetsPage-CClBDKNb.js → WidgetsPage-72n9-HCD.js} +1 -1
  45. package/dashboard/dist/assets/{WrappedPage-B1QPw5JI.js → WrappedPage-Dp1aUOyA.js} +1 -1
  46. package/dashboard/dist/assets/{__vite-browser-external-CV_fKq4F.js → __vite-browser-external-8kU62_i_.js} +2 -2
  47. package/dashboard/dist/assets/{agent-logos-D-hDhb_B.js → agent-logos-ChfjzWPV.js} +1 -1
  48. package/dashboard/dist/assets/{arrow-up-right-DAr-mJ3B.js → arrow-up-right-CiTMPVa_.js} +1 -1
  49. package/dashboard/dist/assets/{check-DPefl5-t.js → check-OQW1qgf_.js} +1 -1
  50. package/dashboard/dist/assets/{chevron-right-MlzWycxC.js → chevron-right-BitImfyV.js} +1 -1
  51. package/dashboard/dist/assets/{copy-B_53rp1c.js → copy-BM76c1Ob.js} +1 -1
  52. package/dashboard/dist/assets/{download-DnlmdACw.js → download-DaKmqoH2.js} +1 -1
  53. package/dashboard/dist/assets/{external-link-DytBHNpR.js → external-link-AL639M-l.js} +1 -1
  54. package/dashboard/dist/assets/{flame-CO4NpDfZ.js → flame-BeYD0c6y.js} +1 -1
  55. package/dashboard/dist/assets/{format-tokens-CwDQe65M.js → format-tokens-DK1Fdxfq.js} +1 -1
  56. package/dashboard/dist/assets/{icons-DvviMgxO.js → icons-BtLj_BDi.js} +1 -1
  57. package/dashboard/dist/assets/{index-Dz9TFADl.js → index-BHBegQjp.js} +1 -1
  58. package/dashboard/dist/assets/{index-B0Lzl2gQ.js → index-BK2Bm5Vp.js} +1 -1
  59. package/dashboard/dist/assets/{index-hGGiJmvs.js → index-D4kdhgAz.js} +1 -1
  60. package/dashboard/dist/assets/{info-BY_SOAny.js → info-CIwDFvrE.js} +1 -1
  61. package/dashboard/dist/assets/{limits-providers-Cpis_64d.js → limits-providers-BFkGzLM4.js} +1 -1
  62. package/dashboard/dist/assets/{link-2-DaIthsuC.js → link-2-xFS-GP5p.js} +1 -1
  63. package/dashboard/dist/assets/{loader-circle-DXLU_CS0.js → loader-circle-83n_EWic.js} +1 -1
  64. package/dashboard/dist/assets/{maximize-2-CnLZ2V-D.js → maximize-2-CevrJZKM.js} +1 -1
  65. package/dashboard/dist/assets/{modifiers.esm-CtoD-5nh.js → modifiers.esm-a1oXNZlE.js} +1 -1
  66. package/dashboard/dist/assets/{monitor-smartphone-CwfUXtG9.js → monitor-smartphone-CSP2FeVP.js} +1 -1
  67. package/dashboard/dist/assets/{provider-display-C0gDapSI.js → provider-display-CamXa0pj.js} +1 -1
  68. package/dashboard/dist/assets/{react-BNEThWJE.js → react-DV1nb3lW.js} +1 -1
  69. package/dashboard/dist/assets/{search-GYLDIV-1.js → search-wj8Kxgr5.js} +1 -1
  70. package/dashboard/dist/assets/{skills-api-noC3sCP_.js → skills-api-BNpGEfgh.js} +1 -1
  71. package/dashboard/dist/assets/{sortable.esm-Bm4LZh1T.js → sortable.esm-Xh3nfzFR.js} +1 -1
  72. package/dashboard/dist/assets/{store-DWhiUbOS.js → store-lBXh9-l6.js} +1 -1
  73. package/dashboard/dist/assets/{terminal-BEQDw7-4.js → terminal-uNBlGBuA.js} +1 -1
  74. package/dashboard/dist/assets/{trash-2-Cm7eF7SC.js → trash-2-08YOSvJh.js} +1 -1
  75. package/dashboard/dist/assets/{use-community-stats-9a-td9J_.js → use-community-stats-CMyCuzcM.js} +1 -1
  76. package/dashboard/dist/assets/{use-limits-display-prefs-XY6qQJai.js → use-limits-display-prefs-BkqvWQ4X.js} +1 -1
  77. package/dashboard/dist/assets/{use-ordered-list-BEaiYX4S.js → use-ordered-list-CJ9F2mdG.js} +1 -1
  78. package/dashboard/dist/assets/use-reduced-motion-CD_DKLbl.js +1 -0
  79. package/dashboard/dist/assets/{use-session-efficiency-pref-oDZEyqyO.js → use-session-efficiency-pref-Hf4gvuAl.js} +1 -1
  80. package/dashboard/dist/assets/{use-transform-LKaVzl1V.js → use-transform-BBP_cds4.js} +1 -1
  81. package/dashboard/dist/assets/{use-usage-limits-DY3e5o6f.js → use-usage-limits-DsLD0OEm.js} +1 -1
  82. package/dashboard/dist/assets/{useCurrency-CSRY4U-t.js → useCurrency-CXngtOdH.js} +1 -1
  83. package/dashboard/dist/assets/{useScrollLock-CSZXl0Pl.js → useScrollLock-Q65zPe5d.js} +1 -1
  84. package/dashboard/dist/assets/{useTokenFormat-f1kcdHaP.js → useTokenFormat-CE5zbI_h.js} +1 -1
  85. package/dashboard/dist/assets/{zap-BMZr9Qc1.js → zap-CHYAc2Be.js} +1 -1
  86. package/dashboard/dist/index.html +1 -1
  87. package/dashboard/dist/ip-check.html +1 -1
  88. package/dashboard/dist/leaderboard.html +1 -1
  89. package/dashboard/dist/share.html +1 -1
  90. package/package.json +1 -1
  91. package/src/commands/sync.js +4 -2
  92. package/src/lib/pricing/seed-snapshot.json +1 -1
  93. package/src/lib/rollout.js +136 -14
  94. package/src/lib/session-analytics.js +220 -145
  95. package/dashboard/dist/assets/DialogDescription-DMUlPgjH.js +0 -1
  96. package/dashboard/dist/assets/use-reduced-motion-C9_Q8rAl.js +0 -1
@@ -1233,7 +1233,11 @@ async function parseOpencodeIncremental({
1233
1233
  const size = Number.isFinite(st.size) ? st.size : 0;
1234
1234
  const mtimeMs = Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
1235
1235
  const unchanged =
1236
- prev && prev.inode === inode && prev.size === size && prev.mtimeMs === mtimeMs;
1236
+ prev &&
1237
+ prev.inode === inode &&
1238
+ prev.size === size &&
1239
+ prev.mtimeMs === mtimeMs &&
1240
+ prev.opencodeForkRepairVersion === 1;
1237
1241
  if (unchanged) {
1238
1242
  filesProcessed += 1;
1239
1243
  if (cb) {
@@ -1287,6 +1291,7 @@ async function parseOpencodeIncremental({
1287
1291
  mtimeMs,
1288
1292
  lastTotals: result.lastTotals,
1289
1293
  messageKey: result.messageKey || null,
1294
+ opencodeForkRepairVersion: 1,
1290
1295
  updatedAt: new Date().toISOString(),
1291
1296
  };
1292
1297
 
@@ -1300,6 +1305,7 @@ async function parseOpencodeIncremental({
1300
1305
  messageKey: result.messageKey,
1301
1306
  totals: result.lastTotals,
1302
1307
  fingerprint: result.fingerprint || null,
1308
+ dedupedForkCopy: result.dedupedForkCopy === true,
1303
1309
  });
1304
1310
  }
1305
1311
 
@@ -2630,16 +2636,41 @@ async function parseOpencodeMessageFile({
2630
2636
  // `Session.fork` re-materialises the parent's prefix under new message ids in
2631
2637
  // a new session — count it once (issue #426, see deriveOpencodeMessageFingerprint).
2632
2638
  const fingerprint = deriveOpencodeMessageFingerprint({ msg, totals: currentTotals, source });
2633
- if (!lastTotals && isOpencodeForkCopy(fingerprintIndex, fingerprint, messageKey)) {
2634
- return { messageKey, lastTotals: null, fingerprint, eventsAggregated: 0, shouldUpdate: false };
2639
+ if (isOpencodeForkCopy(fingerprintIndex, fingerprint, messageKey)) {
2640
+ if (lastTotals && prev?.dedupedForkCopy !== true) {
2641
+ repairCountedOpencodeForkCopy({
2642
+ msg,
2643
+ totals: lastTotals,
2644
+ source,
2645
+ hourlyState,
2646
+ touchedBuckets,
2647
+ projectState,
2648
+ projectTouchedBuckets,
2649
+ projectRef,
2650
+ projectKey,
2651
+ });
2652
+ }
2653
+ return {
2654
+ messageKey,
2655
+ lastTotals: currentTotals,
2656
+ fingerprint,
2657
+ dedupedForkCopy: true,
2658
+ eventsAggregated: 0,
2659
+ shouldUpdate: prev?.dedupedForkCopy !== true || !lastTotals,
2660
+ };
2635
2661
  }
2636
2662
 
2637
- const delta = diffGeminiTotals(currentTotals, lastTotals);
2663
+ // A formerly deduped copy can become a genuinely distinct in-place update.
2664
+ // Its prior totals were removed from the buckets, so re-add the full current
2665
+ // snapshot instead of only the delta from the suppressed value.
2666
+ const effectiveLastTotals = prev?.dedupedForkCopy === true ? null : lastTotals;
2667
+ const delta = diffGeminiTotals(currentTotals, effectiveLastTotals);
2638
2668
  if (!delta || isAllZeroUsage(delta)) {
2639
2669
  return {
2640
2670
  messageKey,
2641
2671
  lastTotals: currentTotals,
2642
2672
  fingerprint,
2673
+ dedupedForkCopy: false,
2643
2674
  eventsAggregated: 0,
2644
2675
  shouldUpdate: true,
2645
2676
  };
@@ -2686,6 +2717,7 @@ async function parseOpencodeMessageFile({
2686
2717
  messageKey,
2687
2718
  lastTotals: currentTotals,
2688
2719
  fingerprint,
2720
+ dedupedForkCopy: false,
2689
2721
  eventsAggregated: 1,
2690
2722
  shouldUpdate: true,
2691
2723
  };
@@ -3276,13 +3308,15 @@ function deriveOpencodeMessageFingerprint({ msg, totals, source }) {
3276
3308
  return crypto.createHash("sha256").update(raw).digest("base64url").slice(0, 22);
3277
3309
  }
3278
3310
 
3279
- // `fingerprint -> messageKey` for the messages already counted in this cursor
3280
- // namespace. Rebuilt per parse run; entries written before this version carry no
3281
- // fingerprint and simply do not participate (no retroactive rewrite of history).
3311
+ // `fingerprint -> messageKey` for counted messages in this cursor namespace.
3312
+ // Rebuilt per parse run. Pre-#426 entries are fingerprinted as they are read;
3313
+ // the first claims ownership and later cross-session matches are retracted from
3314
+ // persisted buckets once. Tombstoned copies never claim ownership themselves.
3282
3315
  function buildOpencodeFingerprintIndex(messageIndex) {
3283
3316
  const byFingerprint = new Map();
3284
3317
  if (!messageIndex || typeof messageIndex !== "object") return byFingerprint;
3285
3318
  for (const [key, entry] of Object.entries(messageIndex)) {
3319
+ if (entry?.dedupedForkCopy === true) continue;
3286
3320
  const fingerprint = entry && typeof entry.fingerprint === "string" ? entry.fingerprint : null;
3287
3321
  if (fingerprint && !byFingerprint.has(fingerprint)) byFingerprint.set(fingerprint, key);
3288
3322
  }
@@ -3314,13 +3348,25 @@ function isOpencodeForkCopy(fingerprintIndex, fingerprint, messageKey) {
3314
3348
  // cursor untouched. A falsy `fingerprint` means "unknown" and preserves whatever
3315
3349
  // the entry already carried; a new one releases the old claim so a stale
3316
3350
  // mid-stream snapshot cannot shadow an unrelated message later.
3317
- function recordOpencodeMessage({ messageIndex, fingerprintIndex, messageKey, totals, fingerprint }) {
3351
+ function recordOpencodeMessage({
3352
+ messageIndex,
3353
+ fingerprintIndex,
3354
+ messageKey,
3355
+ totals,
3356
+ fingerprint,
3357
+ dedupedForkCopy = false,
3358
+ }) {
3318
3359
  if (!messageIndex || !messageKey) return;
3319
3360
  const prev = messageIndex[messageKey];
3320
3361
  const prevTotals = prev && typeof prev.lastTotals === "object" ? prev.lastTotals : null;
3321
3362
  const prevFingerprint = prev && typeof prev.fingerprint === "string" ? prev.fingerprint : null;
3322
3363
  const nextFingerprint = fingerprint || prevFingerprint;
3323
- if (sameGeminiTotals(totals, prevTotals) && prevFingerprint === nextFingerprint) return;
3364
+ const prevDeduped = prev?.dedupedForkCopy === true;
3365
+ if (
3366
+ sameGeminiTotals(totals, prevTotals) &&
3367
+ prevFingerprint === nextFingerprint &&
3368
+ prevDeduped === Boolean(dedupedForkCopy)
3369
+ ) return;
3324
3370
 
3325
3371
  if (fingerprintIndex && prevFingerprint && prevFingerprint !== nextFingerprint) {
3326
3372
  if (fingerprintIndex.get(prevFingerprint) === messageKey) {
@@ -3329,12 +3375,52 @@ function recordOpencodeMessage({ messageIndex, fingerprintIndex, messageKey, tot
3329
3375
  }
3330
3376
  const entry = { lastTotals: totals, updatedAt: new Date().toISOString() };
3331
3377
  if (nextFingerprint) entry.fingerprint = nextFingerprint;
3378
+ if (dedupedForkCopy) entry.dedupedForkCopy = true;
3332
3379
  messageIndex[messageKey] = entry;
3333
- if (fingerprintIndex && nextFingerprint && !fingerprintIndex.has(nextFingerprint)) {
3380
+ if (
3381
+ fingerprintIndex &&
3382
+ nextFingerprint &&
3383
+ !dedupedForkCopy &&
3384
+ !fingerprintIndex.has(nextFingerprint)
3385
+ ) {
3334
3386
  fingerprintIndex.set(nextFingerprint, messageKey);
3335
3387
  }
3336
3388
  }
3337
3389
 
3390
+ function repairCountedOpencodeForkCopy({
3391
+ msg,
3392
+ totals,
3393
+ source,
3394
+ hourlyState,
3395
+ touchedBuckets,
3396
+ projectState,
3397
+ projectTouchedBuckets,
3398
+ projectRef,
3399
+ projectKey,
3400
+ }) {
3401
+ const timestampMs = coerceEpochMs(msg?.time?.completed) || coerceEpochMs(msg?.time?.created);
3402
+ if (!timestampMs) return false;
3403
+ const bucketStart = toUtcHalfHourStart(new Date(timestampMs).toISOString());
3404
+ if (!bucketStart) return false;
3405
+ const model = normalizeModelInput(msg?.modelID || msg?.model || msg?.modelId) || DEFAULT_MODEL;
3406
+ const counted = { ...totals, conversation_count: 1 };
3407
+ const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
3408
+ subtractTotals(bucket.totals, counted);
3409
+ touchedBuckets.add(bucketKey(source, model, bucketStart));
3410
+ if (projectKey && projectState && projectTouchedBuckets) {
3411
+ const projectBucket = getProjectBucket(
3412
+ projectState,
3413
+ projectKey,
3414
+ source,
3415
+ bucketStart,
3416
+ projectRef,
3417
+ );
3418
+ subtractTotals(projectBucket.totals, counted);
3419
+ projectTouchedBuckets.add(projectBucketKey(projectKey, source, bucketStart));
3420
+ }
3421
+ return true;
3422
+ }
3423
+
3338
3424
  function getHourlyBucket(state, source, model, hourStart) {
3339
3425
  const buckets = state.buckets;
3340
3426
  const normalizedSource = normalizeSourceInput(source) || DEFAULT_SOURCE;
@@ -4390,19 +4476,53 @@ async function parseOpencodeDbIncremental({
4390
4476
  continue;
4391
4477
  }
4392
4478
 
4393
- // A fork copy of an already-counted turn contributes nothing — skip it
4394
- // outright so it never claims an index entry of its own (issue #426).
4479
+ // A fork copy of an already-counted turn contributes nothing. Existing
4480
+ // pre-#426 cursor entries were already added to hourly/project buckets;
4481
+ // retract those once, then persist a tombstone so a later sync is a no-op.
4395
4482
  const fingerprint = deriveOpencodeMessageFingerprint({
4396
4483
  msg,
4397
4484
  totals: currentTotals,
4398
4485
  source: defaultSource,
4399
4486
  });
4400
- if (!lastTotals && isOpencodeForkCopy(fingerprintIndex, fingerprint, messageKey)) {
4487
+ if (isOpencodeForkCopy(fingerprintIndex, fingerprint, messageKey)) {
4488
+ let projectContext = null;
4489
+ if (lastTotals && prev?.dedupedForkCopy !== true && projectEnabled) {
4490
+ projectContext = await resolveProjectContextForDb({
4491
+ msg,
4492
+ dbPath,
4493
+ projectMetaCache,
4494
+ publicRepoCache,
4495
+ publicRepoResolver,
4496
+ projectState,
4497
+ });
4498
+ }
4499
+ if (lastTotals && prev?.dedupedForkCopy !== true) {
4500
+ repairCountedOpencodeForkCopy({
4501
+ msg,
4502
+ totals: lastTotals,
4503
+ source: defaultSource,
4504
+ hourlyState,
4505
+ touchedBuckets,
4506
+ projectState,
4507
+ projectTouchedBuckets,
4508
+ projectRef: projectContext?.projectRef || null,
4509
+ projectKey: projectContext?.projectKey || null,
4510
+ });
4511
+ }
4512
+ recordOpencodeMessage({
4513
+ messageIndex,
4514
+ fingerprintIndex,
4515
+ messageKey,
4516
+ totals: currentTotals,
4517
+ fingerprint,
4518
+ dedupedForkCopy: true,
4519
+ });
4401
4520
  messagesProcessed += 1;
4402
4521
  continue;
4403
4522
  }
4404
4523
 
4405
- const delta = diffGeminiTotals(currentTotals, lastTotals);
4524
+ const effectiveLastTotals = prev?.dedupedForkCopy === true ? null : lastTotals;
4525
+ const delta = diffGeminiTotals(currentTotals, effectiveLastTotals);
4406
4526
  if (!delta || isAllZeroUsage(delta)) {
4407
4527
  // Refresh the index even without a delta: normalization may have changed,
4408
4528
  // and pre-#426 entries need their fingerprint backfilled so a fork taken
@@ -4413,6 +4533,7 @@ async function parseOpencodeDbIncremental({
4413
4533
  messageKey,
4414
4534
  totals: currentTotals,
4415
4535
  fingerprint,
4536
+ dedupedForkCopy: false,
4416
4537
  });
4417
4538
  messagesProcessed += 1;
4418
4539
  if (cb) {
@@ -4476,6 +4597,7 @@ async function parseOpencodeDbIncremental({
4476
4597
  messageKey,
4477
4598
  totals: currentTotals,
4478
4599
  fingerprint,
4600
+ dedupedForkCopy: false,
4479
4601
  });
4480
4602
  messagesProcessed += 1;
4481
4603
  eventsAggregated += 1;