tokentracker-cli 1.5.2 → 1.5.4

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 (134) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-B5kmt0VD.js → AchievementBadge-CKNb_cwg.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-C0FlkOo0.js → AchievementsPage-u9P8DXJI.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-CRcp80Pp.js → AchievementsSection-BSCUJ4DR.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-DAFjGbKW.js → BadgeDetailModal-Bg7cE3Zq.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-CO0bbimB.js → BotAnimated-C7D2WbQA.js} +1 -1
  6. package/dashboard/dist/assets/{Card-Xhr5exz8.js → Card-B4IqAhai.js} +1 -1
  7. package/dashboard/dist/assets/{CloudCheckoutPage-DsQ7FmsI.js → CloudCheckoutPage-P75pH9Ym.js} +1 -1
  8. package/dashboard/dist/assets/{CloudPage-DkSexu5V.js → CloudPage-DDEZfmZg.js} +2 -2
  9. package/dashboard/dist/assets/{CommandPalette-Bm0huQdj.js → CommandPalette-CER_AklA.js} +1 -1
  10. package/dashboard/dist/assets/{CommunityStatsModal-BsL8GN8Q.js → CommunityStatsModal-Cbb74eib.js} +1 -1
  11. package/dashboard/dist/assets/{ConfirmModal-_WPp292g.js → ConfirmModal-Q1ZQdZHt.js} +1 -1
  12. package/dashboard/dist/assets/{Controls-ktzn68KM.js → Controls-DaGOfcoz.js} +1 -1
  13. package/dashboard/dist/assets/DashboardPage-CXI7RmxO.js +1 -0
  14. package/dashboard/dist/assets/{DateRangePopover-DWr4zIzF.js → DateRangePopover-DSS1Ira_.js} +1 -1
  15. package/dashboard/dist/assets/{DevicePage-CBa8Mk47.js → DevicePage-6zPLYydh.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-CZ5jzgtb.js → DialogClose-DdCQ36LK.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-DztHQvj-.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-Dr0N6q15.js → DialogTitle-BjP217Y1.js} +1 -1
  19. package/dashboard/dist/assets/{ElectricLogo-sYqgrO6-.js → ElectricLogo-Dhq8PM4h.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-Qq-hcrZW.js → FadeIn-CGNbED4E.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-B77zSv7i.js → HeaderGithubStar-DwqEFzb6.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-lRDaUmoW.js → HoverTooltip-CQZTgyQy.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-B6AvHZjA.js → IpCheckPage-Bz09HXLn.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-DgzOjf3x.js → LandingPage-DC-lZsYR.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardPage-BtjjtDMC.js → LeaderboardPage-D8MpLM_t.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProBadge-B2wNTUTU.js → LeaderboardProBadge-pdXgokhE.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-DJJckwHh.js → LeaderboardProfileModal-nHZLiE0m.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-DJdxByl2.js → LeaderboardProfilePage-BKv3oIAW.js} +1 -1
  29. package/dashboard/dist/assets/LimitsPage-TmRTgSCb.js +6 -0
  30. package/dashboard/dist/assets/{LocalOnlyNotice-B2lVqf_H.js → LocalOnlyNotice-aF01udyR.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-i0gg1yby.js → LoginCard-BaroD0Ct.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-B-loLNZM.js → LoginModal-0jgyMGVM.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-D_swI75j.js → LoginPage-BUx8U58i.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-DDpKvpjG.js → PetPage-CpOjtJqg.js} +2 -2
  35. package/dashboard/dist/assets/{PopoverPopup-Gja4MyEK.js → PopoverPopup-BYjfer-M.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-DOPj7tAZ.js → ProviderIcon-NtYa2h0u.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-BVADLk89.js → ResetPasswordPage-CYUg7TR1.js} +1 -1
  38. package/dashboard/dist/assets/{Select-D-38tN-E.js → Select-CYP95o5y.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-jN61ukxk.js → SelectItemText-BCDfusOM.js} +1 -1
  40. package/dashboard/dist/assets/{SelfHostPage-k7AkGpl1.js → SelfHostPage-BmVc2rIT.js} +1 -1
  41. package/dashboard/dist/assets/ServiceStatusPage-Dyp8NwIn.js +1 -0
  42. package/dashboard/dist/assets/SessionsPage-tGEj6Ztz.js +3 -0
  43. package/dashboard/dist/assets/{SettingsPage-DfnMCAuA.js → SettingsPage-CCMbq5wc.js} +3 -3
  44. package/dashboard/dist/assets/{SkillsPage-YyIHIaHN.js → SkillsPage-C4O4fbaQ.js} +1 -1
  45. package/dashboard/dist/assets/{TokenGalaxy-BLcvZuQ9.js → TokenGalaxy-D2d738xr.js} +1 -1
  46. package/dashboard/dist/assets/{TooltipPopup-3E8kyVzz.js → TooltipPopup-BJfFYesG.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-ByKAdXSF.js → TrendMonitor-CElakFSU.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-BgEVIIzL.js → WidgetsPage-Cvd1LbJm.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-HjbX8QaA.js → WrappedPage-wRC1tE_a.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-Br-KAXh6.css → __vite-browser-external-CMBWGkOq.css} +1 -1
  51. package/dashboard/dist/assets/__vite-browser-external-V4TPLCJG.js +45 -0
  52. package/dashboard/dist/assets/{agent-logos-BVF2siPQ.js → agent-logos-Ba57HF3w.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-left-CYO24PGP.js → arrow-left-B1bKnxXL.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-right-Xg9PZYhw.js → arrow-right-DrOlKzX5.js} +1 -1
  55. package/dashboard/dist/assets/{arrow-up-right-B7ah6Qv-.js → arrow-up-right-hlYpo3yw.js} +1 -1
  56. package/dashboard/dist/assets/{calendar-days-BV_iG938.js → calendar-days-CSY6SEsI.js} +1 -1
  57. package/dashboard/dist/assets/{check--EZH8RTp.js → check-xrP1gLw5.js} +1 -1
  58. package/dashboard/dist/assets/{chevron-down-BzGTlk0-.js → chevron-down-6-zT5n9d.js} +1 -1
  59. package/dashboard/dist/assets/{cloud-checkout-BgyxPHEh.js → cloud-checkout-BZymZD4K.js} +1 -1
  60. package/dashboard/dist/assets/{composite-C0KrmLEK.js → composite-C9izWafa.js} +1 -1
  61. package/dashboard/dist/assets/{copy-BtucSK2U.js → copy-Dkas5wZX.js} +1 -1
  62. package/dashboard/dist/assets/{download-CWHq03Y2.js → download-CagGRs0_.js} +1 -1
  63. package/dashboard/dist/assets/{external-link-DCTXF5VZ.js → external-link-DRl10hxd.js} +1 -1
  64. package/dashboard/dist/assets/{flame-BE5TsqVF.js → flame-CkvmsTFB.js} +1 -1
  65. package/dashboard/dist/assets/{format-tokens-ThNIMWnZ.js → format-tokens-BtwhO3mV.js} +1 -1
  66. package/dashboard/dist/assets/{gift-DgaN9s4-.js → gift-YzjXkMQx.js} +1 -1
  67. package/dashboard/dist/assets/{icons-B4r5EiDC.js → icons-CHhKYs0u.js} +1 -1
  68. package/dashboard/dist/assets/{index-DqCtgU0G.js → index-B0MQZTrM.js} +1 -1
  69. package/dashboard/dist/assets/{index-fNKH4U7j.js → index-C2DK8av-.js} +1 -1
  70. package/dashboard/dist/assets/{index-CKhcCLNC.js → index-CJknN9Jd.js} +1 -1
  71. package/dashboard/dist/assets/{index-DFF9xo6f.js → index-DojzoMqd.js} +1 -1
  72. package/dashboard/dist/assets/{info-B4YzRR4y.js → info-Torep-Ti.js} +1 -1
  73. package/dashboard/dist/assets/{link-2-DzbUbaBw.js → link-2-DE5Yu-z6.js} +1 -1
  74. package/dashboard/dist/assets/{loader-circle-B9c7D-7v.js → loader-circle-BL8LxD9n.js} +1 -1
  75. package/dashboard/dist/assets/{local-usage-transfer-btuk_b0Y.js → local-usage-transfer-8X1InWix.js} +1 -1
  76. package/dashboard/dist/assets/{modifiers.esm-BjrU-Xz7.js → modifiers.esm-D60-6UQa.js} +1 -1
  77. package/dashboard/dist/assets/{monitor-smartphone-Bwn4tb4q.js → monitor-smartphone-C-IFPjqk.js} +1 -1
  78. package/dashboard/dist/assets/{play-DIFOwGIQ.js → play-n6uWrdH9.js} +1 -1
  79. package/dashboard/dist/assets/{plus-BpBqnwVg.js → plus-B2f6TaNj.js} +1 -1
  80. package/dashboard/dist/assets/{provider-display-DSrs4u7T.js → provider-display-DWqCEcsP.js} +1 -1
  81. package/dashboard/dist/assets/{react-BgG7_MY0.js → react-B-zfi_j6.js} +1 -1
  82. package/dashboard/dist/assets/{safePolygon-BUGWIDOM.js → safePolygon-C2PsvI-v.js} +1 -1
  83. package/dashboard/dist/assets/{search-aHrmBmtZ.js → search-DZOaTLF9.js} +1 -1
  84. package/dashboard/dist/assets/{server-DOEvwlEU.js → server-DIp4Qfsv.js} +1 -1
  85. package/dashboard/dist/assets/{shield-check-BPTnLhsT.js → shield-check-Cs2M8-iT.js} +1 -1
  86. package/dashboard/dist/assets/{skills-api-C052Mwez.js → skills-api-CoYy6Tov.js} +1 -1
  87. package/dashboard/dist/assets/{sortable.esm-DaZKhjhu.js → sortable.esm-B5_hjIfs.js} +1 -1
  88. package/dashboard/dist/assets/{store-Cq30Pofw.js → store-BLXqtJzb.js} +1 -1
  89. package/dashboard/dist/assets/{terminal-CoSDSkE7.js → terminal-CEMKX3ba.js} +1 -1
  90. package/dashboard/dist/assets/{trash-BAmt-kPj.js → trash-DOWYaXjK.js} +1 -1
  91. package/dashboard/dist/assets/{use-community-stats-Bxq0kBhm.js → use-community-stats-BjRI1Bzz.js} +1 -1
  92. package/dashboard/dist/assets/{use-latest-request-guard-DmIQDlvk.js → use-latest-request-guard-T7L5qBU6.js} +1 -1
  93. package/dashboard/dist/assets/use-limits-display-prefs-vNIrCPZA.js +1 -0
  94. package/dashboard/dist/assets/{use-native-settings-s_CIC8Bb.js → use-native-settings-Cg5Zb4q_.js} +1 -1
  95. package/dashboard/dist/assets/{use-ordered-list-BHhykAbt.js → use-ordered-list-BF-wIvmG.js} +1 -1
  96. package/dashboard/dist/assets/use-reduced-motion-B9K5mIx4.js +1 -0
  97. package/dashboard/dist/assets/{use-transform-CVeMFlS6.js → use-transform-CbJ1IcNc.js} +1 -1
  98. package/dashboard/dist/assets/use-usage-limits-Dgg3jpC4.js +1 -0
  99. package/dashboard/dist/assets/{useCurrency-DilqL2Iv.js → useCurrency-3oBHWfSe.js} +1 -1
  100. package/dashboard/dist/assets/{usePositioner-BcnySw8a.js → usePositioner-D0YrIW0A.js} +1 -1
  101. package/dashboard/dist/assets/{useTokenFormat-CvKW46WP.js → useTokenFormat-DZAHWE_R.js} +1 -1
  102. package/dashboard/dist/assets/{zap-D7VRNTwk.js → zap-kLub6Jiv.js} +1 -1
  103. package/dashboard/dist/index.html +2 -2
  104. package/dashboard/dist/ip-check.html +2 -2
  105. package/dashboard/dist/leaderboard.html +2 -2
  106. package/dashboard/dist/share.html +2 -2
  107. package/package.json +1 -1
  108. package/src/commands/serve.js +25 -15
  109. package/src/lib/accept-encoding.js +59 -0
  110. package/src/lib/cursor-config.js +3 -1
  111. package/src/lib/deepseek-limits.js +280 -0
  112. package/src/lib/deepseek-status.js +157 -0
  113. package/src/lib/grok-limits.js +40 -1
  114. package/src/lib/local-api.js +248 -10
  115. package/src/lib/opencode-status.js +88 -0
  116. package/src/lib/pricing/index.js +182 -79
  117. package/src/lib/pricing/seed-snapshot.json +1 -1
  118. package/src/lib/provider-api-key-store.js +2 -0
  119. package/src/lib/provider-status.js +27 -2
  120. package/src/lib/qoder-limits.js +17 -2
  121. package/src/lib/session-analytics.js +194 -29
  122. package/src/lib/static-server.js +74 -2
  123. package/src/lib/usage-limits.js +714 -53
  124. package/src/lib/workbuddy-limits.js +291 -26
  125. package/src/lib/zcode-limits.js +143 -35
  126. package/dashboard/dist/assets/DashboardPage-CdNXi3Im.js +0 -1
  127. package/dashboard/dist/assets/DialogDescription-kCZ4gN09.js +0 -1
  128. package/dashboard/dist/assets/LimitsPage-gSBHSBi_.js +0 -6
  129. package/dashboard/dist/assets/ServiceStatusPage-D_Fa_pYz.js +0 -1
  130. package/dashboard/dist/assets/SessionsPage-BymccEEz.js +0 -3
  131. package/dashboard/dist/assets/__vite-browser-external-D_cMeuqI.js +0 -45
  132. package/dashboard/dist/assets/use-limits-display-prefs-CyAn1fme.js +0 -1
  133. package/dashboard/dist/assets/use-reduced-motion-BmFyFE3S.js +0 -1
  134. package/dashboard/dist/assets/use-usage-limits-DRZMI20u.js +0 -1
@@ -1156,6 +1156,10 @@ function readZcodeLimitsCache({ home, nowMs = Date.now(), providerKeys = [], ide
1156
1156
  configured: true,
1157
1157
  error: null,
1158
1158
  provider_key: providerKey,
1159
+ // Live results carry their provider's region for the other_region
1160
+ // attach; the cached shape mirrors them so a cache-served card keeps
1161
+ // that section too (issue #959).
1162
+ region: zcodeRegionFromProviderId(providerKey),
1159
1163
  plan_kind: typeof raw.plan_kind === "string" ? raw.plan_kind : null,
1160
1164
  plan_id: typeof raw.plan_id === "string" ? raw.plan_id : null,
1161
1165
  plan_label: typeof raw.plan_label === "string" ? raw.plan_label : null,
@@ -1172,27 +1176,23 @@ function readZcodeLimitsCache({ home, nowMs = Date.now(), providerKeys = [], ide
1172
1176
  return null;
1173
1177
  }
1174
1178
 
1175
- /** Merge one provider partition into the disk cache; best-effort, never throws. */
1176
- function writeZcodeLimitsCache(limits, { home, nowMs = Date.now(), providerKey, identity } = {}) {
1177
- if (!providerKey || !identity || !limits?.configured || limits.error) return;
1178
- if (!limits.primary_window && !limits.secondary_window && !limits.tertiary_window) return;
1179
+ /**
1180
+ * Merge partitions into the disk cache atomically; best-effort, never throws.
1181
+ * Cross-process semantics are last-writer-wins on purpose, matching the house
1182
+ * pattern (ark / qoder / codex caches): a concurrent writer can discard a
1183
+ * partition added between its read and rename, which costs one refresh cycle
1184
+ * — the next fetch rewrites it. Snapshots in particular keep getting
1185
+ * re-persisted on every fetch while their region's rows are inside the scan
1186
+ * window, so a lost write self-heals long before the window expires.
1187
+ */
1188
+ function mergeZcodeLimitsCacheEntries(entries, { home } = {}) {
1179
1189
  const cachePath = zcodeLimitsCachePath({ home });
1180
1190
  let payload = {};
1181
1191
  try {
1182
1192
  const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
1183
1193
  if (parsed && typeof parsed === "object") payload = parsed;
1184
1194
  } catch (_error) {}
1185
- payload[providerKey] = {
1186
- identity,
1187
- plan_kind: typeof limits.plan_kind === "string" ? limits.plan_kind : null,
1188
- plan_id: typeof limits.plan_id === "string" ? limits.plan_id : null,
1189
- plan_label: typeof limits.plan_label === "string" ? limits.plan_label : null,
1190
- buckets: Array.isArray(limits.buckets) ? limits.buckets : [],
1191
- primary_window: limits.primary_window || null,
1192
- secondary_window: limits.secondary_window || null,
1193
- tertiary_window: limits.tertiary_window || null,
1194
- cached_at: new Date(nowMs).toISOString(),
1195
- };
1195
+ Object.assign(payload, entries);
1196
1196
  try {
1197
1197
  fs.mkdirSync(path.dirname(cachePath), { recursive: true });
1198
1198
  const tmpPath = `${cachePath}.${process.pid}.tmp`;
@@ -1201,28 +1201,129 @@ function writeZcodeLimitsCache(limits, { home, nowMs = Date.now(), providerKey,
1201
1201
  } catch (_error) {}
1202
1202
  }
1203
1203
 
1204
+ /** Merge one provider partition into the disk cache; best-effort, never throws. */
1205
+ function writeZcodeLimitsCache(limits, { home, nowMs = Date.now(), providerKey, identity } = {}) {
1206
+ if (!providerKey || !identity || !limits?.configured || limits.error) return;
1207
+ if (!limits.primary_window && !limits.secondary_window && !limits.tertiary_window) return;
1208
+ mergeZcodeLimitsCacheEntries({
1209
+ [providerKey]: {
1210
+ identity,
1211
+ plan_kind: typeof limits.plan_kind === "string" ? limits.plan_kind : null,
1212
+ plan_id: typeof limits.plan_id === "string" ? limits.plan_id : null,
1213
+ plan_label: typeof limits.plan_label === "string" ? limits.plan_label : null,
1214
+ buckets: Array.isArray(limits.buckets) ? limits.buckets : [],
1215
+ primary_window: limits.primary_window || null,
1216
+ secondary_window: limits.secondary_window || null,
1217
+ tertiary_window: limits.tertiary_window || null,
1218
+ cached_at: new Date(nowMs).toISOString(),
1219
+ },
1220
+ }, { home });
1221
+ }
1222
+
1223
+ // Issue #959: ZCode only queries the balance of the region currently in use,
1224
+ // so the other region's log rows stop appearing the moment it goes idle and
1225
+ // then age out of the 6h scan window — the other_region section would vanish.
1226
+ // While a region's rows are still fresh, snapshot its latest record into a
1227
+ // per-region cache partition; withZcodeOtherRegion falls back to it after the
1228
+ // window expires. The snapshot shape comes straight from
1229
+ // normalizeZcodeBalanceResponse, so the no-reset_credits / no-start_plan red
1230
+ // line holds structurally. Log records carry no login id, so unlike the
1231
+ // windows partitions there is no identity guard here: staleness is
1232
+ // self-attested by the snapshot's log_timestamp (rendered as the section's
1233
+ // time row), and the section stays collapsed and informational.
1234
+ function zcodeLogSnapshotCacheKey(region) {
1235
+ return `log-snapshot:${region}`;
1236
+ }
1237
+
1238
+ function writeZcodeLogSnapshotCache(byRegion, { home, nowMs = Date.now() } = {}) {
1239
+ if (!(byRegion instanceof Map) || byRegion.size === 0) return;
1240
+ const entries = {};
1241
+ for (const [region, record] of byRegion) {
1242
+ if (region !== "bigmodel" && region !== "zai") continue;
1243
+ let normalized;
1244
+ try {
1245
+ normalized = normalizeZcodeBalanceResponse(record.body);
1246
+ } catch (_error) {
1247
+ continue;
1248
+ }
1249
+ entries[zcodeLogSnapshotCacheKey(region)] = {
1250
+ source: "zcode-log",
1251
+ region,
1252
+ provider_key: record.providerKey,
1253
+ log_timestamp: record.log_timestamp,
1254
+ ...normalized,
1255
+ cached_at: new Date(nowMs).toISOString(),
1256
+ };
1257
+ }
1258
+ if (Object.keys(entries).length > 0) mergeZcodeLimitsCacheEntries(entries, { home });
1259
+ }
1260
+
1261
+ /**
1262
+ * Read one region's persisted log snapshot, shaped exactly like the fresh
1263
+ * withZcodeOtherRegion attach (issue #938). Deliberately no per-window
1264
+ * expiry: clipping aged windows would make the cached section render
1265
+ * differently from the same record served fresh inside the scan window.
1266
+ */
1267
+ function readZcodeLogSnapshotCache(region, { home, nowMs = Date.now() } = {}) {
1268
+ if (region !== "bigmodel" && region !== "zai") return null;
1269
+ let parsed;
1270
+ try {
1271
+ parsed = JSON.parse(fs.readFileSync(zcodeLimitsCachePath({ home }), "utf8"));
1272
+ } catch (_error) {
1273
+ return null;
1274
+ }
1275
+ const raw = parsed?.[zcodeLogSnapshotCacheKey(region)];
1276
+ if (!raw || typeof raw !== "object" || raw.source !== "zcode-log" || raw.region !== region) return null;
1277
+ if (!Number.isFinite(Date.parse(typeof raw.log_timestamp === "string" ? raw.log_timestamp : ""))) return null;
1278
+ const cachedAtMs = Date.parse(typeof raw.cached_at === "string" ? raw.cached_at : "");
1279
+ if (!Number.isFinite(cachedAtMs) || cachedAtMs > nowMs + 60_000) return null;
1280
+ return {
1281
+ source: "zcode-log",
1282
+ region,
1283
+ provider_key: typeof raw.provider_key === "string" ? raw.provider_key : null,
1284
+ log_timestamp: raw.log_timestamp,
1285
+ plan_kind: typeof raw.plan_kind === "string" ? raw.plan_kind : null,
1286
+ plan_id: typeof raw.plan_id === "string" ? raw.plan_id : null,
1287
+ plan_label: typeof raw.plan_label === "string" ? raw.plan_label : null,
1288
+ buckets: Array.isArray(raw.buckets) ? raw.buckets : [],
1289
+ primary_window: raw.primary_window || null,
1290
+ secondary_window: raw.secondary_window || null,
1291
+ tertiary_window: raw.tertiary_window || null,
1292
+ };
1293
+ }
1294
+
1204
1295
  /**
1205
1296
  * Attach the freshest logged balance snapshot from the user's other region
1206
1297
  * (issue #938): ZCode's own usage logs carry both regions' billing data even
1207
- * when only the active region's credentials are on disk. The snapshot is
1298
+ * when only the active region's credentials are on disk. Once those rows age
1299
+ * out of the 6h scan window (ZCode only queries the active region's balance,
1300
+ * issue #959), fall back to the snapshot persisted by
1301
+ * writeZcodeLogSnapshotCache while they were fresh. Either way the section is
1208
1302
  * read-only and deliberately carries no reset_credits / start_plan extras —
1209
1303
  * those are login-scoped and must never ride along across regions.
1210
1304
  */
1211
- function withZcodeOtherRegion(result, byRegion) {
1305
+ function withZcodeOtherRegion(result, byRegion, { home, env = process.env, nowMs = Date.now() } = {}) {
1212
1306
  if (!result || result.error || !result.region) return result;
1213
1307
  const otherRegion = result.region === "bigmodel" ? "zai" : "bigmodel";
1214
1308
  const record = byRegion instanceof Map ? byRegion.get(otherRegion) : null;
1215
- if (!record) return result;
1216
- return {
1217
- ...result,
1218
- other_region: {
1219
- source: "zcode-log",
1220
- region: otherRegion,
1221
- provider_key: record.providerKey,
1222
- log_timestamp: record.log_timestamp,
1223
- ...normalizeZcodeBalanceResponse(record.body),
1224
- },
1225
- };
1309
+ if (record) {
1310
+ return {
1311
+ ...result,
1312
+ other_region: {
1313
+ source: "zcode-log",
1314
+ region: otherRegion,
1315
+ provider_key: record.providerKey,
1316
+ log_timestamp: record.log_timestamp,
1317
+ ...normalizeZcodeBalanceResponse(record.body),
1318
+ },
1319
+ };
1320
+ }
1321
+ // Saved snapshots are log-derived data, so the log-fallback kill switch
1322
+ // silences them along with the scan that feeds them.
1323
+ if (env?.TOKENTRACKER_ZCODE_DISABLE_LOG_FALLBACK === "1") return result;
1324
+ const snapshot = readZcodeLogSnapshotCache(otherRegion, { home, nowMs });
1325
+ if (!snapshot) return result;
1326
+ return { ...result, other_region: snapshot };
1226
1327
  }
1227
1328
 
1228
1329
  /**
@@ -1264,8 +1365,9 @@ function loadZcodeLoginScopedTokens(family, auth, { home, env } = {}) {
1264
1365
  * logs, and finally the last-good disk cache. The result carries the winning
1265
1366
  * provider's `region` ("bigmodel" | "zai") and, when ZCode's own logs hold one,
1266
1367
  * an `other_region` snapshot of the second region's plan (issue #938) — read-only
1267
- * log data, never extended with login-scoped extras. Credentials and internal
1268
- * team routing identifiers are omitted from the result.
1368
+ * log data, never extended with login-scoped extras, served from the logs while
1369
+ * fresh and from the disk cache once the scan window expires (issue #959).
1370
+ * Credentials and internal team routing identifiers are omitted from the result.
1269
1371
  */
1270
1372
  async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now(), providerTimeoutMs } = {}) {
1271
1373
  const startedAt = Date.now();
@@ -1283,11 +1385,15 @@ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now
1283
1385
  providerKeys: authCandidates.map((auth) => auth.providerKey),
1284
1386
  nowMs,
1285
1387
  });
1388
+ // Issue #959: capture each region's freshest log record while it is still
1389
+ // inside the scan window, so the other_region section survives the window
1390
+ // expiring (ZCode stops writing rows for a region the moment it goes idle).
1391
+ writeZcodeLogSnapshotCache(logScan.byRegion, { home, nowMs });
1286
1392
  const requireProviderMatch = authCandidates.some((auth) => auth.planKind === "coding-plan");
1287
1393
  const selectLogFallback = (errors) => {
1288
1394
  const record = logScan.preferred
1289
1395
  || (requireProviderMatch && authCandidates.length > 0 ? null : logScan.fallback);
1290
- return withZcodeOtherRegion(zcodeLogFallbackResult(record, errors), logScan.byRegion);
1396
+ return withZcodeOtherRegion(zcodeLogFallbackResult(record, errors), logScan.byRegion, { home, env, nowMs });
1291
1397
  };
1292
1398
  if (!authCandidates.length) {
1293
1399
  return selectLogFallback([]) || { configured: false };
@@ -1403,13 +1509,13 @@ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now
1403
1509
  }
1404
1510
  }
1405
1511
  }
1406
- return withZcodeOtherRegion(result, logScan.byRegion);
1512
+ return withZcodeOtherRegion(result, logScan.byRegion, { home, env, nowMs });
1407
1513
  } catch (error) {
1408
1514
  errors.push(`${auth.providerKey}: ${error?.message || "Unknown error"}`);
1409
1515
  partitionFailures.push({ providerKey: auth.providerKey, authError: error?.authError === true });
1410
1516
  }
1411
1517
  }
1412
- if (emptySuccess) return withZcodeOtherRegion(emptySuccess, logScan.byRegion);
1518
+ if (emptySuccess) return withZcodeOtherRegion(emptySuccess, logScan.byRegion, { home, env, nowMs });
1413
1519
  // Rank fallback partitions in live candidate priority order, and withhold
1414
1520
  // any partition whose attempts all ended in auth errors: re-login is
1415
1521
  // actionable, so neither a stale log record nor stale cache bars may mask
@@ -1428,7 +1534,7 @@ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now
1428
1534
  // A log record for a provider with no live candidate is untouched by this
1429
1535
  // gate: nothing was attempted for it, so no error is being masked.
1430
1536
  if (logRecord && !authErrorOnlyProviders.has(logRecord.providerKey)) {
1431
- return withZcodeOtherRegion(zcodeLogFallbackResult(logRecord, errors), logScan.byRegion);
1537
+ return withZcodeOtherRegion(zcodeLogFallbackResult(logRecord, errors), logScan.byRegion, { home, env, nowMs });
1432
1538
  }
1433
1539
  // Last stop before the error result: serve the disk cache in live candidate
1434
1540
  // priority order, scoped to the login that produced the windows so an
@@ -1440,7 +1546,7 @@ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now
1440
1546
  identityByKey.get(auth.providerKey).add(zcodeCacheIdentity(loginIdFor(zcodeProviderFamily(auth.providerKey)), auth.teamContext));
1441
1547
  }
1442
1548
  const cached = readZcodeLimitsCache({ home, nowMs, providerKeys: cacheEligible, identityByKey });
1443
- if (cached) return withZcodeOtherRegion(cached, logScan.byRegion);
1549
+ if (cached) return withZcodeOtherRegion(cached, logScan.byRegion, { home, env, nowMs });
1444
1550
  return {
1445
1551
  configured: true,
1446
1552
  error: errors[0] || "Unknown error",
@@ -1474,6 +1580,8 @@ module.exports = {
1474
1580
  fetchZcodeStartPlanGrants,
1475
1581
  readZcodeLimitsCache,
1476
1582
  writeZcodeLimitsCache,
1583
+ writeZcodeLogSnapshotCache,
1584
+ readZcodeLogSnapshotCache,
1477
1585
  zcodeCacheIdentity,
1478
1586
  fetchZcodeLimits,
1479
1587
  };