tokentracker-cli 0.87.7 → 0.87.9

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 (104) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +3 -2
  3. package/dashboard/dist/assets/{AchievementBadge-4SSFyzNE.js → AchievementBadge-D3_hERkQ.js} +1 -1
  4. package/dashboard/dist/assets/{AchievementsPage-4ASCXja2.js → AchievementsPage-DZGjT92g.js} +2 -2
  5. package/dashboard/dist/assets/{AchievementsSection-CyaMYMol.js → AchievementsSection-CEzq3U3A.js} +1 -1
  6. package/dashboard/dist/assets/{BadgeDetailModal-CUWTCEru.js → BadgeDetailModal-D3_B8E--.js} +1 -1
  7. package/dashboard/dist/assets/{Card-C79yXijL.js → Card-CMXM3-lp.js} +1 -1
  8. package/dashboard/dist/assets/{ClawdAnimated-Cw6J-x_d.js → ClawdAnimated-lQxUKRzw.js} +1 -1
  9. package/dashboard/dist/assets/{CommandPalette-C1RQ58De.js → CommandPalette-BCGdAeFB.js} +1 -1
  10. package/dashboard/dist/assets/{CommunityStatsModal-CycaCGsd.js → CommunityStatsModal-BwE5h2y5.js} +1 -1
  11. package/dashboard/dist/assets/{ConfirmModal-hPuA2aup.js → ConfirmModal-8uhS2ccj.js} +1 -1
  12. package/dashboard/dist/assets/{Controls-DjunxsRS.js → Controls-CBEQTl2X.js} +1 -1
  13. package/dashboard/dist/assets/{DashboardPage-C5H5_EZN.js → DashboardPage-UE_3tGVy.js} +1 -1
  14. package/dashboard/dist/assets/{DevicePage-a1TT-0Bd.js → DevicePage-B06ybHJI.js} +1 -1
  15. package/dashboard/dist/assets/{DialogClose-CCRfnIbO.js → DialogClose-L_t-nrYm.js} +1 -1
  16. package/dashboard/dist/assets/DialogDescription-B7FFtR7A.js +1 -0
  17. package/dashboard/dist/assets/{DialogTitle-2Qa5pApU.js → DialogTitle-Bsft4VvL.js} +1 -1
  18. package/dashboard/dist/assets/{FadeIn-C3mpVy83.js → FadeIn-BNDdLqTu.js} +1 -1
  19. package/dashboard/dist/assets/{HeaderGithubStar-Dn_F7ByX.js → HeaderGithubStar-CvNaKDbl.js} +1 -1
  20. package/dashboard/dist/assets/{HoverTooltip-DaywO0Zz.js → HoverTooltip-D5j5IpNx.js} +1 -1
  21. package/dashboard/dist/assets/{IpCheckPage-oqGfnWb7.js → IpCheckPage-Csf8gMJW.js} +1 -1
  22. package/dashboard/dist/assets/{LandingPage-CX7dYsEX.js → LandingPage-BVSSwM0D.js} +2 -2
  23. package/dashboard/dist/assets/{LeaderboardAvatar-Bl-OMQ2x.js → LeaderboardAvatar-DXxSHqmP.js} +1 -1
  24. package/dashboard/dist/assets/{LeaderboardPage-yeWcgiVo.js → LeaderboardPage-DXj21BBR.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardProfileModal-CDmZtQ_m.js → LeaderboardProfileModal-DbEzZGg4.js} +3 -3
  26. package/dashboard/dist/assets/{LeaderboardProfilePage-BqJYxoN1.js → LeaderboardProfilePage-DoIm3jB_.js} +1 -1
  27. package/dashboard/dist/assets/{LimitsPage-D8NFgXbZ.js → LimitsPage-D_tpQrUM.js} +1 -1
  28. package/dashboard/dist/assets/{LocalOnlyNotice-DjdhDEiq.js → LocalOnlyNotice-DBi5iuPr.js} +1 -1
  29. package/dashboard/dist/assets/{LoginCard-jQXAmSQb.js → LoginCard-qDIJgUX1.js} +1 -1
  30. package/dashboard/dist/assets/{LoginModal-q4G6EN7b.js → LoginModal-D4b5p4u1.js} +1 -1
  31. package/dashboard/dist/assets/{LoginPage-BklWSXzs.js → LoginPage-CsSgItFA.js} +1 -1
  32. package/dashboard/dist/assets/{PetPage-DyWuMUTA.js → PetPage-B-wElg5q.js} +1 -1
  33. package/dashboard/dist/assets/{PopoverPopup-Dbxed_FL.js → PopoverPopup-9C_VarUQ.js} +1 -1
  34. package/dashboard/dist/assets/{ProviderIcon-ChYuSSfj.js → ProviderIcon-CciEdEeR.js} +1 -1
  35. package/dashboard/dist/assets/{ResetPasswordPage-DJww1dxo.js → ResetPasswordPage-lsI8qlzE.js} +1 -1
  36. package/dashboard/dist/assets/{SegmentedControl-Dr73_QVm.js → SegmentedControl-BGonMV45.js} +1 -1
  37. package/dashboard/dist/assets/{Select-BMCy_8oY.js → Select-DhRrGrDT.js} +1 -1
  38. package/dashboard/dist/assets/{SelectItemText-BRU7mlBI.js → SelectItemText-ClMYgZsa.js} +1 -1
  39. package/dashboard/dist/assets/{ServiceStatusPage-DDao-oTH.js → ServiceStatusPage-DuzttgL7.js} +1 -1
  40. package/dashboard/dist/assets/{SessionsPage-BjngkrsY.js → SessionsPage-gZbxx0f_.js} +1 -1
  41. package/dashboard/dist/assets/{SettingsPage-Dsftjg5j.js → SettingsPage-NhxTrVNW.js} +1 -1
  42. package/dashboard/dist/assets/{SkillsPage-igML0DOZ.js → SkillsPage-BeXojJqQ.js} +1 -1
  43. package/dashboard/dist/assets/{TokenGalaxy-BBETSYe7.js → TokenGalaxy-DM49zKzo.js} +1 -1
  44. package/dashboard/dist/assets/{ToolbarRootContext-CzEAxfqj.js → ToolbarRootContext-BfvoVNMW.js} +1 -1
  45. package/dashboard/dist/assets/{TrendMonitor-JV6lkw-s.js → TrendMonitor-Bu-dXcrV.js} +1 -1
  46. package/dashboard/dist/assets/{WidgetsPage-GpUmv0hI.js → WidgetsPage-D2Wo7seE.js} +1 -1
  47. package/dashboard/dist/assets/{WrappedPage-SP2r4wf1.js → WrappedPage-BXAwic4D.js} +1 -1
  48. package/dashboard/dist/assets/{__vite-browser-external-AQgdBxou.js → __vite-browser-external-BDEQk--l.js} +4 -3
  49. package/dashboard/dist/assets/{agent-logos-DxwSc05k.js → agent-logos-DMEEHjG7.js} +1 -1
  50. package/dashboard/dist/assets/{arrow-up-right-CDW5zn0Q.js → arrow-up-right-gNviKtY8.js} +1 -1
  51. package/dashboard/dist/assets/{check-CQfDkdqM.js → check-Cvg5pmYP.js} +1 -1
  52. package/dashboard/dist/assets/{chevron-right-DDFSY4w-.js → chevron-right-DxdaNTo8.js} +1 -1
  53. package/dashboard/dist/assets/{copy-hxnyMdBa.js → copy-BNIJL7bf.js} +1 -1
  54. package/dashboard/dist/assets/{download-DOUBKxcf.js → download-C9McJlPa.js} +1 -1
  55. package/dashboard/dist/assets/{external-link-CVipCZOa.js → external-link-C_IU6zDK.js} +1 -1
  56. package/dashboard/dist/assets/{flame-C-QU6mPf.js → flame-CII7X8Je.js} +1 -1
  57. package/dashboard/dist/assets/{format-tokens-DRX15id1.js → format-tokens-DuQbGpPt.js} +1 -1
  58. package/dashboard/dist/assets/{icons-ajxtn_0O.js → icons-B1YTqnxG.js} +1 -1
  59. package/dashboard/dist/assets/{index-DnVXRsqb.js → index-BRMxraNK.js} +1 -1
  60. package/dashboard/dist/assets/{index-OJC-yROZ.js → index-Dc9xsIu2.js} +1 -1
  61. package/dashboard/dist/assets/{index-Bq5VZMkS.js → index-QtcYir6O.js} +1 -1
  62. package/dashboard/dist/assets/{info-eITfPd17.js → info-CIgVqJoj.js} +1 -1
  63. package/dashboard/dist/assets/{limits-providers-TMq74bRy.js → limits-providers-B31NsGar.js} +1 -1
  64. package/dashboard/dist/assets/{link-2-D778MOmL.js → link-2-DTPXzHuB.js} +1 -1
  65. package/dashboard/dist/assets/{loader-circle-uN5rBNIF.js → loader-circle-CgQSyCz8.js} +1 -1
  66. package/dashboard/dist/assets/{maximize-2-C4FBbMpN.js → maximize-2-BL5J4aHZ.js} +1 -1
  67. package/dashboard/dist/assets/{modifiers.esm-DLeiQDTT.js → modifiers.esm-s_rzcEf2.js} +1 -1
  68. package/dashboard/dist/assets/{monitor-smartphone-C-obu2-u.js → monitor-smartphone-C9ei_xde.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-Bjl8tmYu.js → provider-display-ZgvoJi05.js} +2 -2
  70. package/dashboard/dist/assets/{react-Diah7uAT.js → react-DhfPVGOm.js} +1 -1
  71. package/dashboard/dist/assets/{search-B0bPOquo.js → search-Ba_MWO7q.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-6otf2Sdl.js → skills-api-BkmJmyg1.js} +1 -1
  73. package/dashboard/dist/assets/{sortable.esm-DcrJaOfF.js → sortable.esm-Bmd5t12E.js} +1 -1
  74. package/dashboard/dist/assets/{store-DOs4omJM.js → store-BxRQs9yc.js} +1 -1
  75. package/dashboard/dist/assets/{terminal-CpMZjaGk.js → terminal-BzUwAs4g.js} +1 -1
  76. package/dashboard/dist/assets/{trash-2-BQYnn3zf.js → trash-2-CCprY7cu.js} +1 -1
  77. package/dashboard/dist/assets/{use-community-stats-CdL4zXXV.js → use-community-stats-TCdPJ_ir.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-DUh4f8f4.js → use-limits-display-prefs-CNF9luO6.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-DVs0Ik3O.js → use-ordered-list-BlTuSYJy.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-CfiR91Eg.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-CGggPI6Y.js → use-session-efficiency-pref-iHS0O_Fo.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-DMj91FEL.js → use-transform-CcVfL3mi.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-xJOXzuZG.js → use-usage-limits-Xdy-Y_1Z.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-BQRBx-KO.js → useCurrency-IXccXbmx.js} +1 -1
  85. package/dashboard/dist/assets/{useScrollLock-DuTPOBl8.js → useScrollLock-BEVewfZm.js} +1 -1
  86. package/dashboard/dist/assets/{useTokenFormat-Dd95ph6d.js → useTokenFormat-Cihv3Ghl.js} +1 -1
  87. package/dashboard/dist/assets/{zap-DJMdRI6y.js → zap-zG2Fbb5_.js} +1 -1
  88. package/dashboard/dist/brand-logos/omp.svg +33 -0
  89. package/dashboard/dist/brand-logos/pi.svg +16 -0
  90. package/dashboard/dist/index.html +1 -1
  91. package/dashboard/dist/ip-check.html +1 -1
  92. package/dashboard/dist/leaderboard.html +1 -1
  93. package/dashboard/dist/share.html +1 -1
  94. package/package.json +1 -1
  95. package/src/commands/init.js +49 -8
  96. package/src/commands/status.js +12 -5
  97. package/src/commands/sync.js +245 -1
  98. package/src/commands/uninstall.js +36 -1
  99. package/src/lib/install-resolver.js +22 -6
  100. package/src/lib/omp-hook.js +589 -0
  101. package/src/lib/pricing/seed-snapshot.json +1 -1
  102. package/src/lib/rollout.js +272 -32
  103. package/dashboard/dist/assets/DialogDescription-C99d5K4r.js +0 -1
  104. package/dashboard/dist/assets/use-reduced-motion--_Yc7AWk.js +0 -1
@@ -7840,9 +7840,45 @@ function resolveCodebuddyDefaultModel(env = process.env) {
7840
7840
  return fallback;
7841
7841
  }
7842
7842
 
7843
+ // Modern CodeBuddy writes token usage into providerData.rawUsage on JSONL
7844
+ // records. Older IDE installs may have no usable JSONL usage and rely on the
7845
+ // extension log fallback below. Keep the sniff bounded: it is used only to
7846
+ // decide whether enabling the legacy log source would create a second copy of
7847
+ // the same round trips.
7848
+ function codebuddyJsonlHasUsage(filePath) {
7849
+ let fd;
7850
+ try {
7851
+ fd = fssync.openSync(filePath, "r");
7852
+ const buffer = Buffer.alloc(64 * 1024);
7853
+ const bytesRead = fssync.readSync(fd, buffer, 0, buffer.length, 0);
7854
+ const lines = buffer.toString("utf8", 0, bytesRead).split("\n");
7855
+ const limit = Math.min(lines.length, 100);
7856
+ for (let i = 0; i < limit; i += 1) {
7857
+ const line = lines[i];
7858
+ if (!line || (!line.includes("rawUsage") && !line.includes("\"usage\""))) continue;
7859
+ let entry;
7860
+ try { entry = JSON.parse(line); } catch { continue; }
7861
+ if (!entry || typeof entry !== "object") continue;
7862
+ const provider = entry.providerData;
7863
+ if (provider && typeof provider === "object" && provider.rawUsage && typeof provider.rawUsage === "object") {
7864
+ return true;
7865
+ }
7866
+ if (entry.usage && typeof entry.usage === "object") return true;
7867
+ }
7868
+ } catch {
7869
+ return false;
7870
+ } finally {
7871
+ if (fd !== undefined) {
7872
+ try { fssync.closeSync(fd); } catch {}
7873
+ }
7874
+ }
7875
+ return false;
7876
+ }
7877
+
7843
7878
  function resolveCodebuddyProjectFiles(env = process.env) {
7844
7879
  const codebuddyHome = resolveCodebuddyHome(env);
7845
- const files = [];
7880
+ const jsonlFiles = [];
7881
+ const logFiles = [];
7846
7882
 
7847
7883
  // 1. Recursive JSONL scan in codebuddyHome/projects
7848
7884
  if (codebuddyHome) {
@@ -7863,7 +7899,7 @@ function resolveCodebuddyProjectFiles(env = process.env) {
7863
7899
  } catch { continue; }
7864
7900
  }
7865
7901
  if (isDir) walkJsonl(full);
7866
- else if (isFile && entry.name.endsWith(".jsonl")) files.push(full);
7902
+ else if (isFile && entry.name.endsWith(".jsonl")) jsonlFiles.push(full);
7867
7903
  }
7868
7904
  };
7869
7905
  walkJsonl(projectsDir);
@@ -7916,10 +7952,10 @@ function resolveCodebuddyProjectFiles(env = process.env) {
7916
7952
  walkLogs(full);
7917
7953
  } else if (isFile && entry.name.endsWith(".log")) {
7918
7954
  if (root.pattern === "*.log") {
7919
- files.push(full);
7955
+ logFiles.push(full);
7920
7956
  } else if (root.pattern === "codebuddy-extension-log") {
7921
7957
  if (full.toLowerCase().includes("tencent-cloud.coding-copilot")) {
7922
- files.push(full);
7958
+ logFiles.push(full);
7923
7959
  }
7924
7960
  }
7925
7961
  }
@@ -7928,6 +7964,11 @@ function resolveCodebuddyProjectFiles(env = process.env) {
7928
7964
  walkLogs(root.dir);
7929
7965
  }
7930
7966
 
7967
+ const fallbackMode = String(env.TOKENTRACKER_CODEBUDDY_LOG_FALLBACK || "").trim();
7968
+ const includeLogs =
7969
+ fallbackMode === "1" ||
7970
+ (fallbackMode !== "0" && !jsonlFiles.some((filePath) => codebuddyJsonlHasUsage(filePath)));
7971
+ const files = [...jsonlFiles, ...(includeLogs ? logFiles : [])];
7931
7972
  files.sort((a, b) => a.localeCompare(b));
7932
7973
  return files;
7933
7974
  }
@@ -8179,9 +8220,15 @@ async function parseCodebuddyIncremental({
8179
8220
  } else {
8180
8221
  let entry;
8181
8222
  try { entry = JSON.parse(line); } catch { continue; }
8182
-
8183
- if (!entry || entry.type !== "message" || entry.role !== "assistant") continue;
8184
-
8223
+ if (!entry || typeof entry !== "object") continue;
8224
+
8225
+ // Usage is carried on ANY record with providerData.rawUsage — assistant
8226
+ // messages AND function_call records. Each LLM round-trip (whether it
8227
+ // ends in a text reply or a tool call) carries its own usage; on real
8228
+ // installs function_call records are ~93% of round-trips and ~14x the
8229
+ // assistant-message token volume, so filtering by record type drops
8230
+ // the majority of usage. Aggregate them all; dedup per round-trip.
8231
+ // (Same convention as the WorkBuddy reader — same transcript format.)
8185
8232
  const provider = entry.providerData;
8186
8233
  const rawUsage = provider && typeof provider === "object" ? provider.rawUsage : null;
8187
8234
  if (!rawUsage || typeof rawUsage !== "object") continue;
@@ -8194,38 +8241,65 @@ async function parseCodebuddyIncremental({
8194
8241
  Number.isFinite(Number(entry.timestamp)) && Number(entry.timestamp) > 0
8195
8242
  ? Number(entry.timestamp)
8196
8243
  : null;
8244
+ // One usage record per LLM round-trip; providerData.messageId is the
8245
+ // response-level id shared by the function_call/message pair of the
8246
+ // same round-trip, so it is the most stable dedup key. entry.id is the
8247
+ // per-record append id (unique per line, NOT per round-trip) and must
8248
+ // NOT be preferred — preferring it would count a round-trip once per
8249
+ // record type instead of once total.
8197
8250
  const messageId =
8198
- typeof entry.uuid === "string" && entry.uuid
8199
- ? entry.uuid
8200
- : typeof entry.id === "string" && entry.id
8201
- ? entry.id
8202
- : tsMs != null
8203
- ? `${sessionId}:${tsMs}`
8204
- : null;
8251
+ typeof provider?.messageId === "string" && provider?.messageId
8252
+ ? provider.messageId
8253
+ : typeof entry.uuid === "string" && entry.uuid
8254
+ ? entry.uuid
8255
+ : typeof entry.id === "string" && entry.id
8256
+ ? entry.id
8257
+ : tsMs != null
8258
+ ? `${sessionId}:${tsMs}`
8259
+ : null;
8205
8260
  if (!messageId) continue;
8206
8261
  if (seenIds.has(messageId)) continue;
8207
8262
 
8208
8263
  recordsProcessed++;
8209
8264
 
8210
8265
  const promptTokens = toNonNegativeInt(rawUsage.prompt_tokens);
8211
- const completionTokens = toNonNegativeInt(rawUsage.completion_tokens);
8266
+ const completionTokensRaw = toNonNegativeInt(rawUsage.completion_tokens);
8212
8267
  const details =
8213
8268
  rawUsage.prompt_tokens_details && typeof rawUsage.prompt_tokens_details === "object"
8214
8269
  ? rawUsage.prompt_tokens_details
8215
8270
  : {};
8216
- const cachedTokens = toNonNegativeInt(details.cached_tokens);
8271
+ const completionDetails =
8272
+ rawUsage.completion_tokens_details && typeof rawUsage.completion_tokens_details === "object"
8273
+ ? rawUsage.completion_tokens_details
8274
+ : {};
8275
+ // Cache-read mirrors three ways depending on upstream: Anthropic-style
8276
+ // cache_read_input_tokens, OpenAI-style prompt_tokens_details.cached_tokens,
8277
+ // DeepSeek-style prompt_cache_hit_tokens. Take the max (they mirror the
8278
+ // same quantity; on real data exactly one is non-zero).
8279
+ const cachedTokens = Math.max(
8280
+ toNonNegativeInt(details.cached_tokens),
8281
+ toNonNegativeInt(rawUsage.prompt_cache_hit_tokens),
8282
+ );
8217
8283
  const cacheReadAlt = toNonNegativeInt(rawUsage.cache_read_input_tokens);
8218
- const cacheCreation = toNonNegativeInt(rawUsage.cache_creation_input_tokens);
8219
- const reasoningTokens = toNonNegativeInt(details.reasoning_tokens);
8284
+ const cacheCreation = Math.max(
8285
+ toNonNegativeInt(rawUsage.cache_creation_input_tokens),
8286
+ toNonNegativeInt(rawUsage.prompt_cache_write_tokens),
8287
+ );
8288
+ // reasoning_tokens lives in completion_tokens_details (verified on real
8289
+ // CodeBuddy data: prompt_tokens_details.reasoning_tokens is always 0).
8290
+ // completion_tokens INCLUDES reasoning, so subtract to avoid double-count.
8291
+ const reasoningTokens = Math.min(completionTokensRaw, toNonNegativeInt(completionDetails.reasoning_tokens));
8292
+ const completionTokens = Math.max(0, completionTokensRaw - reasoningTokens);
8220
8293
 
8221
8294
  const cacheRead = Math.max(cachedTokens, cacheReadAlt);
8222
- const inputTokens = Math.max(0, promptTokens - cacheRead);
8295
+ const inputTokens = Math.max(0, promptTokens - cacheRead - cacheCreation);
8223
8296
 
8224
8297
  if (
8225
8298
  inputTokens === 0 &&
8226
8299
  completionTokens === 0 &&
8227
8300
  cacheRead === 0 &&
8228
- cacheCreation === 0
8301
+ cacheCreation === 0 &&
8302
+ reasoningTokens === 0
8229
8303
  ) {
8230
8304
  seenIds.add(messageId);
8231
8305
  continue;
@@ -8356,6 +8430,12 @@ async function parseCodebuddyIncremental({
8356
8430
  //
8357
8431
  // model is the auto-router placeholder ("auto") — WorkBuddy does not expose
8358
8432
  // the underlying model in the log, so we emit it verbatim.
8433
+ //
8434
+ // 4. Completed ~/.workbuddy/traces/**/trace_*.json summaries expose aggregate
8435
+ // modelInfo input/output/cache totals. They are a fallback only when the
8436
+ // session has no detailed JSONL usage; SQLite's session_usage table has no
8437
+ // output/cache/reasoning columns, so SQLite-only rows intentionally remain
8438
+ // input/total-only instead of fabricating a token split.
8359
8439
  // ─────────────────────────────────────────────────────────────────────────────
8360
8440
 
8361
8441
  function resolveWorkbuddyHome(env = process.env) {
@@ -8390,14 +8470,16 @@ function resolveWorkbuddyDefaultModel(env = process.env) {
8390
8470
 
8391
8471
  // Recursively collect every *.jsonl under ~/.workbuddy/projects so that
8392
8472
  // per-session conversation logs AND their nested subagents/agent-*.jsonl files
8393
- // are both discovered. Non-.jsonl artefacts (tool-results/*.txt) are ignored.
8473
+ // are both discovered. WorkBuddy also writes completed trace summaries under
8474
+ // ~/.workbuddy/traces/<pid>/trace_*.json. Those summaries are useful only when
8475
+ // the detailed JSONL for a session is absent, so they are returned as typed
8476
+ // entries and parsed after JSONL (JSONL remains authoritative when both exist).
8394
8477
  function resolveWorkbuddyProjectFiles(env = process.env) {
8395
8478
  const workbuddyHome = resolveWorkbuddyHome(env);
8396
8479
  if (!workbuddyHome) return [];
8397
8480
  const projectsDir = path.join(workbuddyHome, "projects");
8398
- if (!fssync.existsSync(projectsDir)) return [];
8399
8481
  const files = [];
8400
- const walk = (dir) => {
8482
+ const walkJsonl = (dir) => {
8401
8483
  let entries;
8402
8484
  try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
8403
8485
  for (const entry of entries) {
@@ -8412,12 +8494,43 @@ function resolveWorkbuddyProjectFiles(env = process.env) {
8412
8494
  isFile = st.isFile();
8413
8495
  } catch { continue; }
8414
8496
  }
8415
- if (isDir) walk(full);
8497
+ if (isDir) walkJsonl(full);
8416
8498
  else if (isFile && entry.name.endsWith(".jsonl")) files.push(full);
8417
8499
  }
8418
8500
  };
8419
- walk(projectsDir);
8420
- files.sort((a, b) => a.localeCompare(b));
8501
+ if (fssync.existsSync(projectsDir)) walkJsonl(projectsDir);
8502
+
8503
+ const tracesDir = path.join(workbuddyHome, "traces");
8504
+ const walkTraces = (dir) => {
8505
+ let entries;
8506
+ try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
8507
+ for (const entry of entries) {
8508
+ const full = path.join(dir, entry.name);
8509
+ let isDir = entry.isDirectory();
8510
+ let isFile = entry.isFile();
8511
+ if (!isDir && !isFile) {
8512
+ try {
8513
+ const st = fssync.statSync(full);
8514
+ isDir = st.isDirectory();
8515
+ isFile = st.isFile();
8516
+ } catch { continue; }
8517
+ }
8518
+ if (isDir) walkTraces(full);
8519
+ else if (isFile && /^trace_[^/]+\.json$/i.test(entry.name)) {
8520
+ files.push({ path: full, kind: "trace" });
8521
+ }
8522
+ }
8523
+ };
8524
+ if (fssync.existsSync(tracesDir)) walkTraces(tracesDir);
8525
+ files.sort((a, b) => {
8526
+ const aPath = typeof a === "string" ? a : a?.path || "";
8527
+ const bPath = typeof b === "string" ? b : b?.path || "";
8528
+ // JSONL first is intentional: trace summaries are a lossy fallback and
8529
+ // must not win over a detailed rawUsage record on the same session.
8530
+ const aKind = typeof a === "string" ? 0 : 1;
8531
+ const bKind = typeof b === "string" ? 0 : 1;
8532
+ return aKind - bKind || aPath.localeCompare(bPath);
8533
+ });
8421
8534
  return files;
8422
8535
  }
8423
8536
 
@@ -8447,11 +8560,22 @@ async function parseWorkbuddyIncremental({
8447
8560
  workbuddyState.detailedSessions && typeof workbuddyState.detailedSessions === "object"
8448
8561
  ? { ...workbuddyState.detailedSessions }
8449
8562
  : {};
8563
+ const seenTraceIds = new Set(
8564
+ Array.isArray(workbuddyState.seenTraceIds) ? workbuddyState.seenTraceIds : [],
8565
+ );
8566
+ const tracedSessionIds = new Set(
8567
+ Array.isArray(workbuddyState.tracedSessionIds) ? workbuddyState.tracedSessionIds : [],
8568
+ );
8450
8569
  const detailedSessionsWithUsage = new Set();
8451
8570
 
8452
- const files = Array.isArray(projectFiles)
8571
+ const allFiles = Array.isArray(projectFiles)
8453
8572
  ? projectFiles
8454
8573
  : resolveWorkbuddyProjectFiles(env || process.env);
8574
+ const jsonlFiles = allFiles.filter((entry) => typeof entry === "string");
8575
+ const traceFiles = allFiles.filter(
8576
+ (entry) => entry && typeof entry === "object" && typeof entry.path === "string" && entry.kind === "trace",
8577
+ );
8578
+ const files = [...jsonlFiles, ...traceFiles];
8455
8579
  const fallbackModel = defaultModel || resolveWorkbuddyDefaultModel(env || process.env);
8456
8580
 
8457
8581
  const workbuddyHome = resolveWorkbuddyHome(env || process.env);
@@ -8462,6 +8586,8 @@ async function parseWorkbuddyIncremental({
8462
8586
  cursors.workbuddy = {
8463
8587
  ...workbuddyState,
8464
8588
  seenIds: Array.from(seenIds),
8589
+ seenTraceIds: Array.from(seenTraceIds),
8590
+ tracedSessionIds: Array.from(tracedSessionIds),
8465
8591
  fileOffsets,
8466
8592
  sqliteSessions,
8467
8593
  detailedSessions,
@@ -8476,8 +8602,8 @@ async function parseWorkbuddyIncremental({
8476
8602
  let recordsProcessed = 0;
8477
8603
  let eventsAggregated = 0;
8478
8604
 
8479
- for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
8480
- const filePath = files[fileIdx];
8605
+ for (let fileIdx = 0; fileIdx < jsonlFiles.length; fileIdx++) {
8606
+ const filePath = jsonlFiles[fileIdx];
8481
8607
  let stat;
8482
8608
  try { stat = fssync.statSync(filePath); } catch { continue; }
8483
8609
 
@@ -8515,7 +8641,10 @@ async function parseWorkbuddyIncremental({
8515
8641
  typeof entry.sessionId === "string" && entry.sessionId
8516
8642
  ? entry.sessionId
8517
8643
  : path.basename(filePath, ".jsonl");
8518
- if (Object.prototype.hasOwnProperty.call(sqliteSessions, sessionId)) {
8644
+ if (
8645
+ Object.prototype.hasOwnProperty.call(sqliteSessions, sessionId) ||
8646
+ tracedSessionIds.has(sessionId)
8647
+ ) {
8519
8648
  continue;
8520
8649
  }
8521
8650
  const tsMs =
@@ -8627,6 +8756,99 @@ async function parseWorkbuddyIncremental({
8627
8756
  };
8628
8757
  }
8629
8758
 
8759
+ // Trace summaries are a fallback for sessions whose JSONL has no detailed
8760
+ // providerData.rawUsage. A trace's modelInfo totals include the cache read
8761
+ // portion of input, so split input into uncached input + cached input. Do
8762
+ // not merge a trace with the same session's JSONL (or SQLite) data: the
8763
+ // JSONL path has per-round detail and the trace summary can be partial on
8764
+ // older WorkBuddy versions.
8765
+ for (let fileIdx = 0; fileIdx < traceFiles.length; fileIdx++) {
8766
+ const entry = traceFiles[fileIdx];
8767
+ const filePath = entry.path;
8768
+ let stat;
8769
+ try { stat = fssync.statSync(filePath); } catch { continue; }
8770
+ const prevEntry = fileOffsets[filePath] || {};
8771
+ const prevSize = Number(prevEntry.size) || 0;
8772
+ const prevIno = prevEntry.ino;
8773
+ const inodeChanged = typeof prevIno === "number" && prevIno !== stat.ino;
8774
+ const startOffset = stat.size < prevSize || inodeChanged ? 0 : prevSize;
8775
+ if (stat.size <= startOffset) continue;
8776
+
8777
+ let traceDoc;
8778
+ try {
8779
+ traceDoc = JSON.parse(fssync.readFileSync(filePath, "utf8"));
8780
+ } catch {
8781
+ continue;
8782
+ }
8783
+ const trace = traceDoc && typeof traceDoc === "object" && traceDoc.trace && typeof traceDoc.trace === "object"
8784
+ ? traceDoc.trace
8785
+ : null;
8786
+ const traceId = typeof trace?.traceId === "string" && trace.traceId
8787
+ ? trace.traceId
8788
+ : path.basename(filePath, ".json");
8789
+ const metadata = trace?.metadata && typeof trace.metadata === "object" ? trace.metadata : {};
8790
+ const modelInfo = trace?.modelInfo && typeof trace.modelInfo === "object"
8791
+ ? trace.modelInfo
8792
+ : metadata.modelInfo && typeof metadata.modelInfo === "object"
8793
+ ? metadata.modelInfo
8794
+ : {};
8795
+ const sessionId =
8796
+ (typeof trace?.sessionId === "string" && trace.sessionId) ||
8797
+ (typeof metadata.sessionId === "string" && metadata.sessionId) ||
8798
+ traceId;
8799
+ const startedAtRaw = trace?.startedAt || metadata.startedAt;
8800
+ const tsMs = typeof startedAtRaw === "number" && Number.isFinite(startedAtRaw)
8801
+ ? (startedAtRaw > 10000000000 ? startedAtRaw : startedAtRaw * 1000)
8802
+ : Date.parse(String(startedAtRaw || ""));
8803
+ const totalInput = toNonNegativeInt(modelInfo.totalInputTokens);
8804
+ const totalOutput = toNonNegativeInt(modelInfo.totalOutputTokens);
8805
+ const totalCached = Math.min(totalInput, toNonNegativeInt(modelInfo.totalCachedTokens));
8806
+ if (seenTraceIds.has(traceId) || detailedSessions[sessionId] || detailedSessionsWithUsage.has(sessionId) || tracedSessionIds.has(sessionId)) {
8807
+ seenTraceIds.add(traceId);
8808
+ fileOffsets[filePath] = { size: stat.size, mtimeMs: stat.mtimeMs, ino: stat.ino };
8809
+ continue;
8810
+ }
8811
+ if (!Number.isFinite(tsMs) || tsMs <= 0 || (totalInput <= 0 && totalOutput <= 0)) {
8812
+ // Keep the file offset but do not permanently suppress a trace that may
8813
+ // still be finalized in place by WorkBuddy.
8814
+ fileOffsets[filePath] = { size: stat.size, mtimeMs: stat.mtimeMs, ino: stat.ino };
8815
+ continue;
8816
+ }
8817
+
8818
+ const bucketStart = toUtcHalfHourStart(new Date(tsMs).toISOString());
8819
+ if (!bucketStart) continue;
8820
+ const model =
8821
+ normalizeModelInput(Array.isArray(modelInfo.models) ? modelInfo.models[0] : modelInfo.model) ||
8822
+ fallbackModel;
8823
+ const inputTokens = Math.max(0, totalInput - totalCached);
8824
+ const delta = {
8825
+ input_tokens: inputTokens,
8826
+ cached_input_tokens: totalCached,
8827
+ cache_creation_input_tokens: 0,
8828
+ output_tokens: totalOutput,
8829
+ reasoning_output_tokens: 0,
8830
+ total_tokens: inputTokens + totalCached + totalOutput,
8831
+ conversation_count: 1,
8832
+ };
8833
+ const bucket = getHourlyBucket(hourlyState, "workbuddy", model, bucketStart);
8834
+ addTotals(bucket.totals, delta);
8835
+ touchedBuckets.add(bucketKey("workbuddy", model, bucketStart));
8836
+ seenTraceIds.add(traceId);
8837
+ tracedSessionIds.add(sessionId);
8838
+ recordsProcessed++;
8839
+ eventsAggregated++;
8840
+ fileOffsets[filePath] = { size: stat.size, mtimeMs: stat.mtimeMs, ino: stat.ino };
8841
+ if (cb) {
8842
+ cb({
8843
+ index: jsonlFiles.length + fileIdx + 1,
8844
+ total: files.length,
8845
+ recordsProcessed,
8846
+ eventsAggregated,
8847
+ bucketsQueued: touchedBuckets.size,
8848
+ });
8849
+ }
8850
+ }
8851
+
8630
8852
  if (dbExists) {
8631
8853
  const query = `
8632
8854
  SELECT
@@ -8659,7 +8881,11 @@ async function parseWorkbuddyIncremental({
8659
8881
  if (!row || typeof row !== "object") continue;
8660
8882
  const sessionId = typeof row.session_id === "string" ? row.session_id.trim() : "";
8661
8883
  if (!sessionId) continue;
8662
- if (detailedSessions[sessionId] || detailedSessionsWithUsage.has(sessionId)) continue;
8884
+ if (
8885
+ detailedSessions[sessionId] ||
8886
+ detailedSessionsWithUsage.has(sessionId) ||
8887
+ tracedSessionIds.has(sessionId)
8888
+ ) continue;
8663
8889
 
8664
8890
  const usedNow = toNonNegativeInt(row.used);
8665
8891
  const updatedAtRaw = toNonNegativeInt(row.updated_at);
@@ -8696,13 +8922,16 @@ async function parseWorkbuddyIncremental({
8696
8922
 
8697
8923
  const model = normalizeModelInput(rawModel) || fallbackModel;
8698
8924
 
8925
+ // session_usage only exposes a cumulative `used` value. It has no
8926
+ // output/cache/reasoning columns, so SQLite-only history can safely
8927
+ // contribute input/total but must not invent a token split.
8699
8928
  const delta = {
8700
8929
  input_tokens: inputTokens,
8701
8930
  cached_input_tokens: cacheRead,
8702
8931
  cache_creation_input_tokens: cacheCreation,
8703
8932
  output_tokens: completionTokens,
8704
8933
  reasoning_output_tokens: reasoningTokens,
8705
- total_tokens: inputTokens,
8934
+ total_tokens: inputTokens + completionTokens + cacheRead + cacheCreation + reasoningTokens,
8706
8935
  conversation_count: prevUsed === 0 || isReset ? 1 : 0,
8707
8936
  };
8708
8937
 
@@ -8740,6 +8969,14 @@ async function parseWorkbuddyIncremental({
8740
8969
  detailedSessionEntries.length > 10_000
8741
8970
  ? Object.fromEntries(detailedSessionEntries.slice(detailedSessionEntries.length - 10_000))
8742
8971
  : detailedSessions;
8972
+ const traceArr = Array.from(seenTraceIds);
8973
+ const cappedSeenTraceIds =
8974
+ traceArr.length > 10_000 ? traceArr.slice(traceArr.length - 10_000) : traceArr;
8975
+ const tracedSessionArr = Array.from(tracedSessionIds);
8976
+ const cappedTracedSessionIds =
8977
+ tracedSessionArr.length > 10_000
8978
+ ? tracedSessionArr.slice(tracedSessionArr.length - 10_000)
8979
+ : tracedSessionArr;
8743
8980
 
8744
8981
  const bucketsQueued = await enqueueTouchedBuckets({
8745
8982
  queuePath,
@@ -8752,6 +8989,8 @@ async function parseWorkbuddyIncremental({
8752
8989
  cursors.workbuddy = {
8753
8990
  ...workbuddyState,
8754
8991
  seenIds: cappedSeen,
8992
+ seenTraceIds: cappedSeenTraceIds,
8993
+ tracedSessionIds: cappedTracedSessionIds,
8755
8994
  fileOffsets,
8756
8995
  sqliteSessions: cappedSqliteSessions,
8757
8996
  detailedSessions: cappedDetailedSessions,
@@ -15448,6 +15687,7 @@ module.exports = {
15448
15687
  resolveKimiCodeDefaultModel,
15449
15688
  parseKimiCodeIncremental,
15450
15689
  resolveCodebuddyHome,
15690
+ codebuddyJsonlHasUsage,
15451
15691
  resolveCodebuddyProjectFiles,
15452
15692
  resolveCodebuddyDefaultModel,
15453
15693
  parseCodebuddyIncremental,
@@ -1 +0,0 @@
1
- import{r as n,z as a}from"./__vite-browser-external-AQgdBxou.js";import{u as p}from"./DialogTitle-2Qa5pApU.js";import{u as c}from"./useScrollLock-DuTPOBl8.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,{store:i}=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
@@ -1 +0,0 @@
1
- import{r as o}from"./__vite-browser-external-AQgdBxou.js";import{h as r,i as t,p as s}from"./react-Diah7uAT.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};