tokentracker-cli 0.85.0 → 0.86.0

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 (100) hide show
  1. package/README.de.md +5 -4
  2. package/README.ja.md +5 -4
  3. package/README.ko.md +5 -4
  4. package/README.md +8 -7
  5. package/README.zh-CN.md +5 -4
  6. package/dashboard/dist/assets/{AchievementBadge-DEqbHlmn.js → AchievementBadge-BgbH_mX4.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-BOaEu1AQ.js → AchievementsPage-CQqkh1JS.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-C370ngO3.js → AchievementsSection-Dmkxpn0s.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-D7Wkef0K.js → BadgeDetailModal-7OQf37lk.js} +1 -1
  10. package/dashboard/dist/assets/{Card-mDEVjNdM.js → Card-1PmWZRTK.js} +1 -1
  11. package/dashboard/dist/assets/{ClawdAnimated-DdsX-z8n.js → ClawdAnimated-CMJFagpX.js} +1 -1
  12. package/dashboard/dist/assets/{CommandPalette-DhxRIuHC.js → CommandPalette-D4OrwF1w.js} +1 -1
  13. package/dashboard/dist/assets/{CommunityStatsModal-CG-9JPS6.js → CommunityStatsModal-BDC9EcwZ.js} +1 -1
  14. package/dashboard/dist/assets/{ConfirmModal-COhxErLm.js → ConfirmModal-22DL5C_9.js} +1 -1
  15. package/dashboard/dist/assets/{DashboardPage-tX0DKgFE.js → DashboardPage-CEXWzDtt.js} +1 -1
  16. package/dashboard/dist/assets/{DevicePage-BsSeHx84.js → DevicePage-8uhHB7kX.js} +1 -1
  17. package/dashboard/dist/assets/{DialogClose-eyOW2cbx.js → DialogClose-BcJx6VK-.js} +1 -1
  18. package/dashboard/dist/assets/DialogDescription-D6zxtOM5.js +1 -0
  19. package/dashboard/dist/assets/{DialogTitle-H8sGiH7c.js → DialogTitle-gRzunAqv.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-pq2ja50M.js → FadeIn-4rlPdBVe.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-DWgcpDHr.js → HeaderGithubStar-CR003aO3.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-CcJ_VR65.js → HoverTooltip-Dgz-dM8Q.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-CNUN346Q.js → IpCheckPage-B4PJRJP_.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-yaoRGsNI.js → LandingPage-3JQe-jOZ.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardAvatar-Ddai4I12.js → LeaderboardAvatar-CL9LxiEf.js} +1 -1
  26. package/dashboard/dist/assets/{LeaderboardPage-Bajkxf63.js → LeaderboardPage-DhIzM2Z2.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-BGYKDgIi.js → LeaderboardProfileModal-bbf5uJR5.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-Ck5vPaeN.js → LeaderboardProfilePage-CzuPxscw.js} +1 -1
  29. package/dashboard/dist/assets/{LimitsPage-Bqobtsw2.js → LimitsPage-B59rb2gg.js} +1 -1
  30. package/dashboard/dist/assets/{LocalOnlyNotice-e9KSE3wj.js → LocalOnlyNotice-BkK-6n1R.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-BhTSm2tb.js → LoginCard-BFGCzEjE.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-CRwuThde.js → LoginModal-yhkyscFn.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-j1i74lcc.js → LoginPage-DwU5s0F7.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-r62ViU1X.js → PetPage-BPscYR6z.js} +1 -1
  35. package/dashboard/dist/assets/{PopoverPopup-DopNTyD4.js → PopoverPopup-E7PyGTTB.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-c1LkPhYM.js → ProviderIcon-CvmnzwKU.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-DJdDNvtq.js → ResetPasswordPage-DD6qX616.js} +1 -1
  38. package/dashboard/dist/assets/{SegmentedControl-DhWVOTPN.js → SegmentedControl-DjGBL_l1.js} +1 -1
  39. package/dashboard/dist/assets/{Select-DeUmTmsJ.js → Select-B2MGmsi2.js} +1 -1
  40. package/dashboard/dist/assets/{SelectItemText-pX0B5f7h.js → SelectItemText-BaTwA_ZF.js} +1 -1
  41. package/dashboard/dist/assets/{ServiceStatusPage-ejIPz6oF.js → ServiceStatusPage-0K0y8A6m.js} +1 -1
  42. package/dashboard/dist/assets/{SessionsPage-WEbn90GW.js → SessionsPage-CxrtRm3B.js} +1 -1
  43. package/dashboard/dist/assets/{SettingsPage-B8t5gCeI.js → SettingsPage-CyvSWvDP.js} +1 -1
  44. package/dashboard/dist/assets/{SkillsPage-BWMlzeAp.js → SkillsPage-BVOU8t3t.js} +1 -1
  45. package/dashboard/dist/assets/{TokenGalaxy-DIBRBvMj.js → TokenGalaxy-w7bwGloC.js} +1 -1
  46. package/dashboard/dist/assets/{ToolbarRootContext-C6KxLvnI.js → ToolbarRootContext-BAzpDZej.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-BaMNh67w.js → TrendMonitor-DyT7Q4xp.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-DDsy4M3x.js → WidgetsPage-eDDtopQT.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-CiNnUFRI.js → WrappedPage-CKkbdWHF.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-CM-H8-LW.js → __vite-browser-external-SU8sCTLB.js} +3 -3
  51. package/dashboard/dist/assets/{agent-logos-BjVa6oe7.js → agent-logos-bkh9XZJd.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-up-right-CCum1mqX.js → arrow-up-right-D16zCIw8.js} +1 -1
  53. package/dashboard/dist/assets/{check-ySRQc1YP.js → check-BFL17JWQ.js} +1 -1
  54. package/dashboard/dist/assets/{chevron-right-5vFloHew.js → chevron-right-CUJgrtkp.js} +1 -1
  55. package/dashboard/dist/assets/{copy-mhfKVMKo.js → copy-CRQiCjgs.js} +1 -1
  56. package/dashboard/dist/assets/{download-GdrXglSx.js → download-Cd12nDrQ.js} +1 -1
  57. package/dashboard/dist/assets/{external-link-KGFaKc8s.js → external-link-CvAfnsG-.js} +1 -1
  58. package/dashboard/dist/assets/{flame-BmNtNoS1.js → flame-CCi681Yl.js} +1 -1
  59. package/dashboard/dist/assets/{format-tokens-CCpCNw3d.js → format-tokens-BAVt8uTZ.js} +1 -1
  60. package/dashboard/dist/assets/{icons-XLKZavTf.js → icons-CbKC96gR.js} +1 -1
  61. package/dashboard/dist/assets/{index-_xU5U5Ct.js → index--TDvaL1y.js} +1 -1
  62. package/dashboard/dist/assets/{index-CsywTeCQ.js → index-B5b6PCh5.js} +1 -1
  63. package/dashboard/dist/assets/{index-IKpmeZte.js → index-B9LWn-mo.js} +1 -1
  64. package/dashboard/dist/assets/{info-DUV5UpmT.js → info-BPFpD--M.js} +1 -1
  65. package/dashboard/dist/assets/{limits-providers-DFkCZCZj.js → limits-providers-Cyedt-WJ.js} +1 -1
  66. package/dashboard/dist/assets/{link-2-D7c64yLY.js → link-2-BkEPPdfC.js} +1 -1
  67. package/dashboard/dist/assets/{loader-circle-sMONRK2M.js → loader-circle-CzASiylJ.js} +1 -1
  68. package/dashboard/dist/assets/{maximize-2-CVWRgrk9.js → maximize-2-CkwnHUJn.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-DSKqVjgm.js → provider-display-BXBBedC-.js} +2 -2
  70. package/dashboard/dist/assets/{react-c1q4mGkS.js → react-BXSctDQj.js} +1 -1
  71. package/dashboard/dist/assets/{search-CwD3s-_C.js → search-_qGma9tM.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-BRXj8kjT.js → skills-api-DdmJjnJ9.js} +1 -1
  73. package/dashboard/dist/assets/{store-lqTW2gfK.js → store-owYCN530.js} +1 -1
  74. package/dashboard/dist/assets/{terminal-DU1_KbKo.js → terminal-Clzw-KEF.js} +1 -1
  75. package/dashboard/dist/assets/{trash-2-B8Y5Xc-K.js → trash-2-C5Giwp_E.js} +1 -1
  76. package/dashboard/dist/assets/{use-community-stats-DYwwimvp.js → use-community-stats-Dk8UjLEr.js} +1 -1
  77. package/dashboard/dist/assets/{use-limits-display-prefs-Bst6ybK3.js → use-limits-display-prefs-CArM595Q.js} +1 -1
  78. package/dashboard/dist/assets/{use-native-settings-Cqle4lgw.js → use-native-settings-Cpd69kiF.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-Dtze0ahb.js → use-ordered-list--CzwhBTx.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-CcILnpU1.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-gwbDMHXa.js → use-session-efficiency-pref-DUXO8cCg.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-CU0yxXpX.js → use-transform-Cg3kuMj_.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-5y1I1T5_.js → use-usage-limits-DY9_mV28.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-BfVNrywe.js → useCurrency-BIe86hMn.js} +1 -1
  85. package/dashboard/dist/assets/{useScrollLock-CF1m7AMJ.js → useScrollLock-7-hejkNC.js} +1 -1
  86. package/dashboard/dist/assets/{useTokenFormat-DiRwVvRr.js → useTokenFormat-DEtLbRgQ.js} +1 -1
  87. package/dashboard/dist/assets/{zap-BZtlWtuN.js → zap-DUxY1d-O.js} +1 -1
  88. package/dashboard/dist/index.html +12 -10
  89. package/dashboard/dist/ip-check.html +1 -1
  90. package/dashboard/dist/leaderboard.html +1 -1
  91. package/dashboard/dist/llms.txt +3 -2
  92. package/dashboard/dist/share.html +1 -1
  93. package/package.json +2 -2
  94. package/src/commands/init.js +1 -0
  95. package/src/commands/status.js +15 -0
  96. package/src/commands/sync.js +114 -8
  97. package/src/lib/pricing/seed-snapshot.json +1 -1
  98. package/src/lib/rollout.js +451 -0
  99. package/dashboard/dist/assets/DialogDescription-BwtE4lKl.js +0 -1
  100. package/dashboard/dist/assets/use-reduced-motion-CbZDR54N.js +0 -1
@@ -66,6 +66,7 @@ const {
66
66
  resolveRoocodeTaskFiles,
67
67
  resolveZedDbPath,
68
68
  resolveQoderDbPaths,
69
+ resolveClaudeScienceDbPaths,
69
70
  resolveAnythingllmDbPath,
70
71
  resolveGooseDbPath,
71
72
  listDroidSettingsFiles,
@@ -425,6 +426,14 @@ async function cmdStatus(argv = []) {
425
426
  const qoderInstalled = qoderActive.length > 0;
426
427
  const qoderDbPath = qoderActive.join(" | ");
427
428
 
429
+ // Claude Science — token usage lives on the `frames` table of operon-cli.db.
430
+ // Unlike the native/WSL pair other providers resolve to, this is an open-ended
431
+ // list: multi-org installs keep one DB per org (and on Windows they all sit
432
+ // inside WSL), so the resolver already returns only paths that exist.
433
+ const claudeScienceActive = resolveClaudeScienceDbPaths({ home, env: process.env });
434
+ const claudeScienceInstalled = claudeScienceActive.length > 0;
435
+ const claudeScienceDbPath = claudeScienceActive.join(" | ");
436
+
428
437
  // OpenCode (JSON files + SQLite DB) — passive scan of storage/message/ and opencode.db.
429
438
  const opencodeStorageNativeValue = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
430
439
  const wslOpencodeStorageDir = process.platform === "win32" && shouldProbeWsl(process.env)
@@ -786,6 +795,9 @@ async function cmdStatus(argv = []) {
786
795
  qoder: qoderInstalled
787
796
  ? { installed: true, detail: qoderDbPath }
788
797
  : { installed: false },
798
+ "claude-science": claudeScienceInstalled
799
+ ? { installed: true, detail: claudeScienceDbPath }
800
+ : { installed: false },
789
801
  kilocode: kilocodeInstalled
790
802
  ? { installed: true, files: kilocodeTaskFiles.length }
791
803
  : { installed: false },
@@ -907,6 +919,9 @@ async function cmdStatus(argv = []) {
907
919
  qoderInstalled
908
920
  ? `- Qoder: passive reader (${qoderDbPath})`
909
921
  : null,
922
+ claudeScienceInstalled
923
+ ? `- Claude Science: passive reader (${claudeScienceDbPath})`
924
+ : null,
910
925
  opencodeInstalled
911
926
  ? `- OpenCode: passive reader (storage: ${opencodeStorageActive.join(" | ") || "not found"}, DB: ${opencodeDbActive.join(" | ") || "not found"})`
912
927
  : null,
@@ -40,6 +40,12 @@ const {
40
40
  parseOpencodeDbIncremental,
41
41
  parseQoderDbIncremental,
42
42
  parseOpenclawIncremental,
43
+ resolveOpenclawSessionFiles,
44
+ resolveOpenclawHome,
45
+ openclawCursorKey,
46
+ resolveClaudeScienceDbPaths,
47
+ readClaudeScienceFrames,
48
+ parseClaudeScienceIncremental,
43
49
  parseCursorApiIncremental,
44
50
  parseKiroIncremental,
45
51
  parseHermesIncremental,
@@ -237,6 +243,7 @@ const AUTO_SYNC_SOURCES = new Set([
237
243
  "antigravity",
238
244
  "anythingllm",
239
245
  "claude",
246
+ "claude-science",
240
247
  "codebuddy",
241
248
  "codex",
242
249
  "copilot",
@@ -473,7 +480,7 @@ async function cmdSync(argv, context = {}) {
473
480
  // OpenClaw session plugin integration: lifecycle hooks request an
474
481
  // OpenClaw-only auto sync so unrelated providers do not get walked.
475
482
  const openclawSignal = opts.fromOpenclaw
476
- ? resolveOpenclawSignal({ home, env: process.env })
483
+ ? resolveOpenclawSignal({ env: process.env })
477
484
  : null;
478
485
 
479
486
  const autoSourceScope = resolveAutoSourceScope(opts);
@@ -668,9 +675,38 @@ async function cmdSync(argv, context = {}) {
668
675
  codexCursorLoadRestarted = Boolean(loadResult?.restarted);
669
676
  }
670
677
 
671
- const openclawFiles = openclawSignal?.sessionFile
672
- ? [{ path: openclawSignal.sessionFile, source: "openclaw" }]
673
- : [];
678
+ // Plugin-triggered sync points at one specific session file; a normal full
679
+ // sync also passively scans every on-disk OpenClaw transcript so usage is
680
+ // captured even when the session plugin never fires (issue #264). The scan
681
+ // is gated to full syncs so a scoped `--from-openclaw` hook still only
682
+ // touches its own file and the 5-minute background tick stays cheap. The
683
+ // event-identity dedup makes the plugin and passive paths idempotent, so
684
+ // overlap on the same file is safe.
685
+ //
686
+ // Keyed by openclawCursorKey, not the raw path: that is the same identity
687
+ // the parser assigns cursors by, so a plugin-supplied path and a scanned
688
+ // path that differ only in Windows casing collapse to one entry instead of
689
+ // being parsed twice.
690
+ const openclawSessionFiles = new Map();
691
+ if (openclawSignal?.sessionFile) {
692
+ openclawSessionFiles.set(openclawCursorKey(openclawSignal.sessionFile), {
693
+ path: openclawSignal.sessionFile,
694
+ source: "openclaw",
695
+ });
696
+ }
697
+ if (isFullSourceScan && sourceAllowed("openclaw")) {
698
+ try {
699
+ for (const f of await resolveOpenclawSessionFiles(process.env)) {
700
+ const key = openclawCursorKey(f);
701
+ if (!openclawSessionFiles.has(key)) {
702
+ openclawSessionFiles.set(key, { path: f, source: "openclaw" });
703
+ }
704
+ }
705
+ } catch (err) {
706
+ warnProviderParseFailure("OpenClaw", err, opts);
707
+ }
708
+ }
709
+ const openclawFiles = Array.from(openclawSessionFiles.values());
674
710
 
675
711
  if (progress?.enabled) {
676
712
  progress.start(
@@ -740,7 +776,7 @@ async function cmdSync(argv, context = {}) {
740
776
 
741
777
  let openclawResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
742
778
  if (sourceAllowed("openclaw") && openclawFiles.length > 0) {
743
- // Only runs when explicitly triggered by the OpenClaw session plugin.
779
+ // Parses plugin-triggered and/or passively discovered session files.
744
780
  try {
745
781
  openclawResult = await parseOpenclawIncremental({
746
782
  sessionFiles: openclawFiles,
@@ -1055,6 +1091,45 @@ async function cmdSync(argv, context = {}) {
1055
1091
  }
1056
1092
  }
1057
1093
 
1094
+ // ── Claude Science (Anthropic's local research workbench, issue #246) ──
1095
+ // Per-frame token usage lives on the `frames` table of operon-cli.db.
1096
+ // Passive, incremental, subtract-on-change. There can be more than one DB:
1097
+ // multi-org installs keep one per org, and on Windows the app runs inside
1098
+ // WSL so the DB is on the distro home.
1099
+ let claudeScienceResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
1100
+ if (sourceAllowed("claude-science")) {
1101
+ // Resolution is inside the error isolation too: it touches the filesystem
1102
+ // and (on Windows) probes WSL, and a throw here would abort cmdSync before
1103
+ // any provider's buckets are written, not just this one's.
1104
+ let claudeScienceDbPaths = [];
1105
+ try {
1106
+ claudeScienceDbPaths = resolveClaudeScienceDbPaths({ home, env: process.env });
1107
+ } catch (err) {
1108
+ warnProviderParseFailure("Claude Science", err, opts);
1109
+ }
1110
+ if (claudeScienceDbPaths.length > 0 && progress?.enabled) {
1111
+ progress.start(`Parsing Claude Science ${renderBar(0)} | buckets 0`);
1112
+ }
1113
+ for (const claudeScienceDbPath of claudeScienceDbPaths) {
1114
+ try {
1115
+ const dbRows = await readClaudeScienceFrames(claudeScienceDbPath);
1116
+ const parsed = await parseClaudeScienceIncremental({
1117
+ dbRows,
1118
+ cursors,
1119
+ queuePath,
1120
+ onProgress: makeProviderProgress("Claude Science"),
1121
+ });
1122
+ claudeScienceResult = mergeParseResult(claudeScienceResult, {
1123
+ recordsProcessed: parsed.recordsProcessed || 0,
1124
+ eventsAggregated: parsed.eventsAggregated || 0,
1125
+ bucketsQueued: parsed.bucketsQueued || 0,
1126
+ });
1127
+ } catch (err) {
1128
+ warnProviderParseFailure("Claude Science", err, opts);
1129
+ }
1130
+ }
1131
+ }
1132
+
1058
1133
  async function parseOpencodeDbForInstall({ dbPath, readFn, source, cursorKey, ...rest }) {
1059
1134
  if (!dbPath || !fssync.existsSync(dbPath)) {
1060
1135
  return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
@@ -2215,6 +2290,7 @@ async function cmdSync(argv, context = {}) {
2215
2290
  antigravityResult.filesProcessed +
2216
2291
  opencodeResult.filesProcessed +
2217
2292
  qoderResult.recordsProcessed +
2293
+ claudeScienceResult.recordsProcessed +
2218
2294
  cursorResult.recordsProcessed +
2219
2295
  kiroResult.recordsProcessed +
2220
2296
  kiroCliResult.recordsProcessed +
@@ -2245,6 +2321,7 @@ async function cmdSync(argv, context = {}) {
2245
2321
  antigravityResult.bucketsQueued +
2246
2322
  opencodeResult.bucketsQueued +
2247
2323
  qoderResult.bucketsQueued +
2324
+ claudeScienceResult.bucketsQueued +
2248
2325
  cursorResult.bucketsQueued +
2249
2326
  kiroResult.bucketsQueued +
2250
2327
  kiroCliResult.bucketsQueued +
@@ -2586,15 +2663,18 @@ function normalizeString(value) {
2586
2663
  return trimmed.length > 0 ? trimmed : null;
2587
2664
  }
2588
2665
 
2589
- function resolveOpenclawSignal({ home, env } = {}) {
2666
+ function resolveOpenclawSignal({ env } = {}) {
2590
2667
  if (!env) return null;
2591
2668
 
2592
2669
  const agentId = normalizeString(env.TOKENTRACKER_OPENCLAW_AGENT_ID);
2593
2670
  const sessionId = normalizeString(env.TOKENTRACKER_OPENCLAW_PREV_SESSION_ID);
2594
2671
  if (!agentId || !sessionId) return null;
2595
2672
 
2596
- const openclawHome =
2597
- normalizeString(env.TOKENTRACKER_OPENCLAW_HOME) || path.join(home || os.homedir(), ".openclaw");
2673
+ // Resolve the OpenClaw home identically to the passive scanner so the
2674
+ // plugin-triggered file and passively discovered files share one path
2675
+ // spelling — otherwise the same transcript could get two cursors and be
2676
+ // counted twice (issue #264 review).
2677
+ const openclawHome = resolveOpenclawHome(env);
2598
2678
  const sessionFile = path.join(openclawHome, "agents", agentId, "sessions", `${sessionId}.jsonl`);
2599
2679
 
2600
2680
  const prevTotals = {
@@ -2627,6 +2707,16 @@ async function applyOpenclawTotalsFallback({
2627
2707
  return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
2628
2708
  }
2629
2709
 
2710
+ // The passive/plugin transcript parse is authoritative: if this session's
2711
+ // transcript has ever yielded real per-event usage, synthesizing sessions.json
2712
+ // totals on top would count the same tokens twice (issue #264 review). Defer
2713
+ // to the real events and advance the fallback baseline so no stale delta lingers.
2714
+ const transcriptCursor =
2715
+ signal.sessionFile && cursors?.files
2716
+ ? cursors.files[openclawCursorKey(signal.sessionFile)]
2717
+ : null;
2718
+ const transcriptHasRealUsage = Boolean(transcriptCursor?.hasRealUsage);
2719
+
2630
2720
  const sessionKey = `${signal.agentId}:${signal.sessionId}`;
2631
2721
  const statePath = path.join(trackerDir, "openclaw.fallback.state.json");
2632
2722
  const fallbackFilePath = path.join(trackerDir, "openclaw.fallback.jsonl");
@@ -2635,6 +2725,22 @@ async function applyOpenclawTotalsFallback({
2635
2725
  const prev =
2636
2726
  sessions[sessionKey] && typeof sessions[sessionKey] === "object" ? sessions[sessionKey] : null;
2637
2727
 
2728
+ if (transcriptHasRealUsage) {
2729
+ sessions[sessionKey] = {
2730
+ totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
2731
+ inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,
2732
+ outputTokens: normalizeNonNegativeInt(signal?.prevTotals?.outputTokens) || 0,
2733
+ model: normalizeString(signal?.prevTotals?.model) || "unknown",
2734
+ updatedAt: normalizeIsoOrEpoch(signal?.prevTotals?.updatedAt) || new Date().toISOString(),
2735
+ seenAt: new Date().toISOString(),
2736
+ coveredByEvents: true,
2737
+ };
2738
+ state.version = 1;
2739
+ state.sessions = sessions;
2740
+ await writeJson(statePath, state);
2741
+ return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
2742
+ }
2743
+
2638
2744
  const current = {
2639
2745
  totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
2640
2746
  inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,