tokentracker-cli 0.93.3 → 0.93.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 (100) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dashboard/dist/assets/{AchievementBadge-Bl3bWjZw.js → AchievementBadge-BcNvxrVM.js} +1 -1
  4. package/dashboard/dist/assets/{AchievementsPage-B6FrDjg8.js → AchievementsPage-DhipQygJ.js} +2 -2
  5. package/dashboard/dist/assets/{AchievementsSection-D5TJtcI5.js → AchievementsSection-BJyb_mIr.js} +1 -1
  6. package/dashboard/dist/assets/{BadgeDetailModal-BFlrt34z.js → BadgeDetailModal-CoZv7o6t.js} +1 -1
  7. package/dashboard/dist/assets/{BotAnimated-KONFZbQT.js → BotAnimated-eVBNtvcG.js} +1 -1
  8. package/dashboard/dist/assets/{Card-DDZHhXlQ.js → Card-BEfG2im9.js} +1 -1
  9. package/dashboard/dist/assets/{ClawdAnimated-DMw54QJ_.js → ClawdAnimated-C-30t4dC.js} +2 -2
  10. package/dashboard/dist/assets/{CommandPalette-CxVv0uo_.js → CommandPalette-DF-TGB1C.js} +1 -1
  11. package/dashboard/dist/assets/{CommunityStatsModal-CR2MuFo3.js → CommunityStatsModal-D-0eI3Pz.js} +1 -1
  12. package/dashboard/dist/assets/{ConfirmModal-B-jvLsxn.js → ConfirmModal-BQ7Aor1C.js} +1 -1
  13. package/dashboard/dist/assets/{Controls-DXA3Zxx0.js → Controls-COThRtMS.js} +1 -1
  14. package/dashboard/dist/assets/{DashboardPage-0jQPu59U.js → DashboardPage-Ci_Gb5I1.js} +1 -1
  15. package/dashboard/dist/assets/{DevicePage-CMR7jT8-.js → DevicePage-BmlmimUg.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-B1j73B1g.js → DialogClose-BdALEZzc.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-DAKoQCEv.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-eIgptOU-.js → DialogTitle-F6W1aTiE.js} +1 -1
  19. package/dashboard/dist/assets/{FadeIn-B8aisUg4.js → FadeIn-S2d3D28d.js} +1 -1
  20. package/dashboard/dist/assets/{HeaderGithubStar-B7MEGODn.js → HeaderGithubStar-DBBwOwc_.js} +1 -1
  21. package/dashboard/dist/assets/{HoverTooltip-DfBzMfW7.js → HoverTooltip-rLWQC9Va.js} +1 -1
  22. package/dashboard/dist/assets/{IpCheckPage-BBvSL0Zz.js → IpCheckPage-BpPoAIry.js} +1 -1
  23. package/dashboard/dist/assets/{LandingPage-CcX03ndP.js → LandingPage-BUm_ZMew.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardAvatar-Dk4UJx-j.js → LeaderboardAvatar-DQxbOYZb.js} +1 -1
  25. package/dashboard/dist/assets/{LeaderboardPage-CcIp7sjv.js → LeaderboardPage-DzLrFQm8.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProfileModal-DzXfmNhy.js → LeaderboardProfileModal-Cq4B8Les.js} +3 -3
  27. package/dashboard/dist/assets/{LeaderboardProfilePage-18ijOFN-.js → LeaderboardProfilePage-C-Xj65xf.js} +1 -1
  28. package/dashboard/dist/assets/{LimitsPage-ZeRD1J97.js → LimitsPage-Bjcmr0bd.js} +1 -1
  29. package/dashboard/dist/assets/{LocalOnlyNotice-DQ_wPyho.js → LocalOnlyNotice-9mWINkVj.js} +1 -1
  30. package/dashboard/dist/assets/{LoginCard-DX5-Bxhr.js → LoginCard-BlKleF8M.js} +1 -1
  31. package/dashboard/dist/assets/{LoginModal-_Rt3qUyK.js → LoginModal-BUYa13hj.js} +1 -1
  32. package/dashboard/dist/assets/{LoginPage-CvYcDpeR.js → LoginPage-DbQhApOd.js} +1 -1
  33. package/dashboard/dist/assets/{PetPage-D0D4mRwd.js → PetPage-DuAf3OOf.js} +1 -1
  34. package/dashboard/dist/assets/{PopoverPopup-Bz3yRvv6.js → PopoverPopup-CG76MkbK.js} +1 -1
  35. package/dashboard/dist/assets/{ProviderIcon-PauvxgNb.js → ProviderIcon-N7OTtgte.js} +1 -1
  36. package/dashboard/dist/assets/{ResetPasswordPage-BiC1Us4d.js → ResetPasswordPage-D48--1wC.js} +1 -1
  37. package/dashboard/dist/assets/{SegmentedControl-bW8MsUDI.js → SegmentedControl-zRvbFqyY.js} +1 -1
  38. package/dashboard/dist/assets/{Select-BSpIj6-K.js → Select-Bi3B21Fb.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-taYIkVIw.js → SelectItemText-Bg8I618N.js} +1 -1
  40. package/dashboard/dist/assets/{ServiceStatusPage-GMkqBtrt.js → ServiceStatusPage-CeeYx4XY.js} +1 -1
  41. package/dashboard/dist/assets/{SessionsPage-QydYL4yt.js → SessionsPage-CZF3qLEm.js} +1 -1
  42. package/dashboard/dist/assets/{SettingsPage-BFvCcos0.js → SettingsPage-D2wa8i5u.js} +2 -2
  43. package/dashboard/dist/assets/{SkillsPage-CKci6NM2.js → SkillsPage-OxW2w6up.js} +1 -1
  44. package/dashboard/dist/assets/{TokenGalaxy-DMof07_u.js → TokenGalaxy-DmtP_r86.js} +1 -1
  45. package/dashboard/dist/assets/{ToolbarRootContext-CAsiI0PJ.js → ToolbarRootContext-DC6BLLoO.js} +1 -1
  46. package/dashboard/dist/assets/{TrendMonitor-Dj2Uxs9x.js → TrendMonitor-OvvLCD3K.js} +1 -1
  47. package/dashboard/dist/assets/{WidgetsPage-DOr-nMn1.js → WidgetsPage-DhOC8rae.js} +1 -1
  48. package/dashboard/dist/assets/{WrappedPage-BZUv1rX5.js → WrappedPage-C0boSobK.js} +1 -1
  49. package/dashboard/dist/assets/{__vite-browser-external-WKiEWJ30.js → __vite-browser-external-D_dICEUB.js} +2 -2
  50. package/dashboard/dist/assets/{agent-logos-1FaE1SqE.js → agent-logos-Dzyq5cDh.js} +1 -1
  51. package/dashboard/dist/assets/{arrow-up-right-Bs5oicPH.js → arrow-up-right-DrLfm7Vy.js} +1 -1
  52. package/dashboard/dist/assets/{check-Be2PwUMS.js → check-DAsdH-MF.js} +1 -1
  53. package/dashboard/dist/assets/{chevron-right-DH93vlVo.js → chevron-right-CdSgby9-.js} +1 -1
  54. package/dashboard/dist/assets/{copy-xOLhiaQa.js → copy-TuVoW7qK.js} +1 -1
  55. package/dashboard/dist/assets/{download-qUjPQH7V.js → download-BZ61xuhi.js} +1 -1
  56. package/dashboard/dist/assets/{external-link-D0nLaYYu.js → external-link-DJrRSKF9.js} +1 -1
  57. package/dashboard/dist/assets/{flame-DFEEnwot.js → flame-9JS9yMju.js} +1 -1
  58. package/dashboard/dist/assets/{format-tokens-BPq1moPg.js → format-tokens-Blp6lI_n.js} +1 -1
  59. package/dashboard/dist/assets/{icons-DxM91e9c.js → icons-CbF0cL6i.js} +1 -1
  60. package/dashboard/dist/assets/{index-BEi52jGG.js → index-Bu3I2hmd.js} +1 -1
  61. package/dashboard/dist/assets/{index-CEl88_eC.js → index-FnWiMfqV.js} +1 -1
  62. package/dashboard/dist/assets/{index-C8Sb5l3M.js → index-I81NzV7H.js} +1 -1
  63. package/dashboard/dist/assets/{info-C43iVFmY.js → info-B_iryt84.js} +1 -1
  64. package/dashboard/dist/assets/{limits-providers-xNF55jgX.js → limits-providers-Cli4pkKC.js} +1 -1
  65. package/dashboard/dist/assets/{link-2-CfarpF4W.js → link-2-pB3-7Waa.js} +1 -1
  66. package/dashboard/dist/assets/{loader-circle-CIKyaEoN.js → loader-circle-DivA-LsF.js} +1 -1
  67. package/dashboard/dist/assets/{maximize-2-DlTJ_PyX.js → maximize-2-WNMF5NUU.js} +1 -1
  68. package/dashboard/dist/assets/{modifiers.esm-CXV6l89k.js → modifiers.esm-BaJJxzf2.js} +1 -1
  69. package/dashboard/dist/assets/{monitor-smartphone-6dCO7HCP.js → monitor-smartphone-pAR3IHow.js} +1 -1
  70. package/dashboard/dist/assets/{provider-display-DNEUispM.js → provider-display-CyYhYVar.js} +1 -1
  71. package/dashboard/dist/assets/{react-us685QUG.js → react-BldsIPA1.js} +1 -1
  72. package/dashboard/dist/assets/{search-NcbnjXYq.js → search-xIfdCEo0.js} +1 -1
  73. package/dashboard/dist/assets/{skills-api-CFSZ441M.js → skills-api-DZNsCHPo.js} +1 -1
  74. package/dashboard/dist/assets/{sortable.esm-DiFLdkJ7.js → sortable.esm-DMaj448G.js} +1 -1
  75. package/dashboard/dist/assets/{store-B-d1Nz34.js → store-VTEegajU.js} +1 -1
  76. package/dashboard/dist/assets/{terminal-D3L94ngT.js → terminal-Bj8SotRv.js} +1 -1
  77. package/dashboard/dist/assets/{trash-2-esrIc05L.js → trash-2-CWgY_FBc.js} +1 -1
  78. package/dashboard/dist/assets/{use-community-stats-C4nPlCzJ.js → use-community-stats-BOVvh_Gv.js} +1 -1
  79. package/dashboard/dist/assets/{use-limits-display-prefs-iXm2ziaO.js → use-limits-display-prefs-MtLsQ5kN.js} +1 -1
  80. package/dashboard/dist/assets/{use-ordered-list-DU8WcVsn.js → use-ordered-list-DjCZVinv.js} +1 -1
  81. package/dashboard/dist/assets/use-reduced-motion-DjWNXuSi.js +1 -0
  82. package/dashboard/dist/assets/{use-session-efficiency-pref-CpGicS2y.js → use-session-efficiency-pref-DASihY0W.js} +1 -1
  83. package/dashboard/dist/assets/{use-transform-WxJRtugr.js → use-transform-tHlm2id8.js} +1 -1
  84. package/dashboard/dist/assets/{use-usage-limits-55w4KAqE.js → use-usage-limits-BBEHNJKl.js} +1 -1
  85. package/dashboard/dist/assets/{useCurrency-C6HKlFA6.js → useCurrency-DgTCDMXb.js} +1 -1
  86. package/dashboard/dist/assets/{useScrollLock-CZ1Na38Y.js → useScrollLock-C3TFkbz0.js} +1 -1
  87. package/dashboard/dist/assets/{useTokenFormat-BR7kJQ6x.js → useTokenFormat-BtqPANfZ.js} +1 -1
  88. package/dashboard/dist/assets/{zap-BcwbN9j5.js → zap-CJNTZ87b.js} +1 -1
  89. package/dashboard/dist/index.html +1 -1
  90. package/dashboard/dist/ip-check.html +1 -1
  91. package/dashboard/dist/leaderboard.html +1 -1
  92. package/dashboard/dist/share.html +1 -1
  93. package/package.json +1 -1
  94. package/src/commands/status.js +3 -3
  95. package/src/lib/opencode-go-limits.js +74 -27
  96. package/src/lib/pricing/seed-snapshot.json +1 -1
  97. package/src/lib/rollout.js +187 -28
  98. package/src/lib/trae-cn-config.js +11 -2
  99. package/dashboard/dist/assets/DialogDescription-ZS29ikF4.js +0 -1
  100. package/dashboard/dist/assets/use-reduced-motion-DDhy4yg_.js +0 -1
@@ -2698,7 +2698,8 @@ async function parseOpencodeMessageFile({
2698
2698
  };
2699
2699
  }
2700
2700
 
2701
- const model = normalizeModelInput(msg?.modelID || msg?.model || msg?.modelId) || DEFAULT_MODEL;
2701
+ const { modelId: fileModelId } = normalizeOpencodeModelFields(msg);
2702
+ const model = fileModelId || DEFAULT_MODEL;
2702
2703
  const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
2703
2704
  addTotals(bucket.totals, delta);
2704
2705
  touchedBuckets.add(bucketKey(source, model, bucketStart));
@@ -3289,8 +3290,11 @@ function deriveOpencodeMessageFingerprint({ msg, totals, source }) {
3289
3290
  const created = coerceEpochMs(msg?.time?.created) || 0;
3290
3291
  const completed = coerceEpochMs(msg?.time?.completed) || 0;
3291
3292
  if (!created && !completed) return null;
3292
- const model = normalizeModelInput(msg?.modelID || msg?.model || msg?.modelId) || DEFAULT_MODEL;
3293
- const provider = normalizeMessageKeyPart(msg?.providerID || msg?.provider || msg?.providerId);
3293
+ // v1 keeps flat modelID/providerID strings; v2 nests them in
3294
+ // `model: { id, providerID }` normalize both (see normalizeOpencodeModelFields).
3295
+ const { modelId, providerId } = normalizeOpencodeModelFields(msg);
3296
+ const model = modelId || DEFAULT_MODEL;
3297
+ const provider = providerId;
3294
3298
  const raw = [
3295
3299
  normalizeSourceInput(source) || "opencode",
3296
3300
  created,
@@ -3402,7 +3406,8 @@ function repairCountedOpencodeForkCopy({
3402
3406
  if (!timestampMs) return false;
3403
3407
  const bucketStart = toUtcHalfHourStart(new Date(timestampMs).toISOString());
3404
3408
  if (!bucketStart) return false;
3405
- const model = normalizeModelInput(msg?.modelID || msg?.model || msg?.modelId) || DEFAULT_MODEL;
3409
+ const { modelId: repairModelId } = normalizeOpencodeModelFields(msg);
3410
+ const model = repairModelId || DEFAULT_MODEL;
3406
3411
  const counted = { ...totals, conversation_count: 1 };
3407
3412
  const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
3408
3413
  subtractTotals(bucket.totals, counted);
@@ -3635,6 +3640,33 @@ function normalizeModelInput(value) {
3635
3640
  return trimmed.length > 0 ? trimmed : null;
3636
3641
  }
3637
3642
 
3643
+ // OpenCode v1 stores the model as flat strings on every assistant message
3644
+ // (`modelID`, `providerID`), while OpenCode v2 (the `opencode2` beta, which
3645
+ // moved messages into the `session_message` table) nests them inside a single
3646
+ // object: `model: { id, providerID, variant }`. Some forks also wrote a plain
3647
+ // string into `model`. Resolve both shapes to { modelId, providerId } so
3648
+ // bucket keys and fork fingerprints stay identical across versions.
3649
+ function normalizeOpencodeModelFields(msg) {
3650
+ const directModel =
3651
+ normalizeModelInput(msg?.modelID) ||
3652
+ normalizeModelInput(typeof msg?.model === "string" ? msg.model : null) ||
3653
+ normalizeModelInput(msg?.modelId);
3654
+ if (directModel) {
3655
+ return {
3656
+ modelId: directModel,
3657
+ providerId: normalizeMessageKeyPart(msg?.providerID || msg?.provider || msg?.providerId),
3658
+ };
3659
+ }
3660
+ const nested = msg?.model;
3661
+ if (nested && typeof nested === "object") {
3662
+ return {
3663
+ modelId: normalizeModelInput(nested.id),
3664
+ providerId: normalizeMessageKeyPart(nested.providerID),
3665
+ };
3666
+ }
3667
+ return { modelId: null, providerId: "" };
3668
+ }
3669
+
3638
3670
  async function resolveProjectMetaForPath(startDir, cache) {
3639
3671
  if (!startDir || typeof startDir !== "string") return null;
3640
3672
  if (cache && cache.has(startDir)) return cache.get(startDir);
@@ -4298,12 +4330,79 @@ async function walkOpencodeMessages(dir, out) {
4298
4330
  }
4299
4331
 
4300
4332
  // ---------------------------------------------------------------------------
4301
- // OpenCode SQLite DB reader (v1.2+ stores messages in opencode.db)
4333
+ // OpenCode SQLite DB reader
4334
+ //
4335
+ // Four real database shapes exist in the wild (see PR #501 / opencode2):
4336
+ // A pure v1: `message`(data) + `session_message`(empty) [+ `session`]
4337
+ // B beta-17887: `session_message`(data) + `session_v2`
4338
+ // C upstream v2: `session_message`(data) + `session`
4339
+ // D transitional: `message`(old data) + `session_message`(new data)
4340
+ //
4341
+ // The reader must serve all four without firing a doomed SQL at type A (an
4342
+ // empty `session_message` table still EXISTS — querying it with a JOIN against
4343
+ // `session_v2` that does not exist throws "no such table" every sync, and the
4344
+ // old code silently swallowed that error). The combined probe below answers
4345
+ // both "is there v2 data?" and "which session table exists?" in one round-trip.
4302
4346
  // ---------------------------------------------------------------------------
4303
4347
 
4348
+ const OPENCODE_DB_V1_MESSAGE_SQL =
4349
+ `SELECT id, session_id, time_updated, data FROM message ` +
4350
+ `WHERE json_extract(data, '$.role') = 'assistant' ORDER BY time_created ASC`;
4351
+
4352
+ // Build the v2 query. When a session table exists the LEFT JOIN restores the
4353
+ // project directory for downstream attribution; when it does not (type B
4354
+ // without session_v2, or an exotic fork) the join and directory column are
4355
+ // omitted and tokens are counted as-is.
4356
+ function buildV2Sql(sessionTable) {
4357
+ const joinClause = sessionTable
4358
+ ? `LEFT JOIN ${sessionTable} s ON s.id = sm.session_id `
4359
+ : "";
4360
+ const directorySelect = sessionTable
4361
+ ? `s.directory AS directory, `
4362
+ : "";
4363
+ return (
4364
+ `SELECT sm.id AS id, sm.session_id AS session_id, sm.time_updated AS time_updated, ` +
4365
+ `${directorySelect}sm.data AS data ` +
4366
+ `FROM session_message sm ${joinClause}` +
4367
+ `WHERE sm.type = 'assistant' ORDER BY sm.time_created ASC`
4368
+ );
4369
+ }
4370
+
4371
+ // One combined probe: hasRows (is there any data in session_message?) plus
4372
+ // sessionTable (which session table exists, session_v2 preferred over session
4373
+ // via DESC). Returns null when the probe itself fails — callers treat that as
4374
+ // v1-only, matching the pre-existing silent-degradation contract.
4375
+ function detectOpencodeMessageLayout(dbPath, sqliteOptions = {}) {
4376
+ let rows;
4377
+ try {
4378
+ rows = readSqliteJsonRows(
4379
+ dbPath,
4380
+ `SELECT (SELECT 1 FROM session_message LIMIT 1) AS hasRows,
4381
+ (SELECT name FROM sqlite_master WHERE name IN ('session','session_v2') ORDER BY name DESC LIMIT 1) AS sessionTable`,
4382
+ { label: "OpenCode", timeout: 10_000, maxBuffer: 1024 * 1024, ...sqliteOptions },
4383
+ );
4384
+ } catch (_e) {
4385
+ return null;
4386
+ }
4387
+ if (!Array.isArray(rows) || rows.length === 0) return null;
4388
+ const row = rows[0];
4389
+ return {
4390
+ hasRows: Boolean(row?.hasRows),
4391
+ sessionTable: typeof row?.sessionTable === "string" && row.sessionTable.trim()
4392
+ ? row.sessionTable.trim()
4393
+ : null,
4394
+ };
4395
+ }
4396
+
4397
+ // Shared provider resolver: v1 rows carry a top-level providerID, v2 rows nest
4398
+ // it under model.providerID. Used by the mimo/zcode discriminators so they work
4399
+ // against both generations without duplicating the resolution logic.
4400
+ function opencodeMessageProvider(data) {
4401
+ return data?.providerID || data?.model?.providerID || "";
4402
+ }
4403
+
4304
4404
  function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4305
4405
  if (!dbPath || !fssync.existsSync(dbPath)) return [];
4306
- const sql = `SELECT id, session_id, time_updated, data FROM message WHERE json_extract(data, '$.role') = 'assistant' ORDER BY time_created ASC`;
4307
4406
 
4308
4407
  let snapshot = null;
4309
4408
  let effectiveDbPath = dbPath;
@@ -4314,13 +4413,16 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4314
4413
  } catch (_e) { }
4315
4414
  }
4316
4415
 
4317
- try {
4318
- const rows = readSqliteJsonRows(effectiveDbPath, sql, {
4319
- label: "OpenCode",
4320
- maxBuffer: 50 * 1024 * 1024,
4321
- timeout: 30_000,
4322
- ...sqliteOptions,
4323
- });
4416
+ // Parse raw SQL rows into the shared { id, sessionID, timeUpdated, data }
4417
+ // shape; optionally restore v2's session-level project directory as the
4418
+ // per-message `path.cwd` the rest of the pipeline expects.
4419
+ //
4420
+ // D-state (transitional) union: both the legacy `message` table and the new
4421
+ // `session_message` table may carry rows. Cross-table duplicates are caught
4422
+ // by the existing cursor-key + #426 fingerprint dedup in
4423
+ // parseOpencodeDbIncremental, so unioning here is safe — the downstream
4424
+ // parser collapses identical sessionID|messageID pairs before bucketing.
4425
+ const collectRows = (rows, isV2) => {
4324
4426
  const out = [];
4325
4427
  for (const row of rows) {
4326
4428
  if (!row || typeof row.data !== "string") continue;
@@ -4338,6 +4440,9 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4338
4440
  toNonNegativeInt(tokens.output) > 0 ||
4339
4441
  toNonNegativeInt(tokens.reasoning) > 0;
4340
4442
  if (!hasTokens) continue;
4443
+ if (isV2 && typeof row.directory === "string" && row.directory.trim()) {
4444
+ data.path = { ...(typeof data.path === "object" && data.path ? data.path : {}), cwd: row.directory };
4445
+ }
4341
4446
  out.push({
4342
4447
  id: row.id || data.id,
4343
4448
  sessionID: row.session_id || data.sessionID,
@@ -4345,6 +4450,40 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4345
4450
  data,
4346
4451
  });
4347
4452
  }
4453
+ return out;
4454
+ };
4455
+
4456
+ const readGeneration = (sql) => {
4457
+ try {
4458
+ const rows = readSqliteJsonRows(effectiveDbPath, sql, {
4459
+ label: "OpenCode",
4460
+ maxBuffer: 50 * 1024 * 1024,
4461
+ timeout: 30_000,
4462
+ ...sqliteOptions,
4463
+ throwOnReadFailure: true,
4464
+ });
4465
+ return Array.isArray(rows) ? rows : [];
4466
+ } catch (_e) {
4467
+ return null; // query-level failure (e.g. wrong generation's table)
4468
+ }
4469
+ };
4470
+
4471
+ try {
4472
+ // Combined probe drives the orchestration: v1 always runs (it is the
4473
+ // baseline every database carries or degrades to), v2 runs only when the
4474
+ // probe confirms session_message has rows — this avoids firing a JOIN
4475
+ // against a non-existent session table on type-A databases.
4476
+ const probe = detectOpencodeMessageLayout(effectiveDbPath, sqliteOptions);
4477
+ const out = [];
4478
+
4479
+ const rows1 = readGeneration(OPENCODE_DB_V1_MESSAGE_SQL);
4480
+ if (rows1) out.push(...collectRows(rows1, false));
4481
+
4482
+ if (probe?.hasRows) {
4483
+ const rows2 = readGeneration(buildV2Sql(probe.sessionTable));
4484
+ if (rows2) out.push(...collectRows(rows2, true));
4485
+ }
4486
+
4348
4487
  return out;
4349
4488
  } finally {
4350
4489
  if (snapshot) snapshot.cleanup();
@@ -4369,7 +4508,7 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
4369
4508
  // subsumes it.
4370
4509
  function isMimoNativeMessage(data) {
4371
4510
  if (!data) return false;
4372
- const provider = String(data.providerID || "").toLowerCase();
4511
+ const provider = String(opencodeMessageProvider(data)).toLowerCase();
4373
4512
  return provider === "mimo" || provider === "xiaomi";
4374
4513
  }
4375
4514
 
@@ -4403,7 +4542,7 @@ function readMimoDbMessages(dbPath, sqliteOptions = {}) {
4403
4542
  // re-count it. Mirrors the Mimo discipline.
4404
4543
  function isZcodeNativeMessage(data) {
4405
4544
  if (!data) return false;
4406
- const provider = String(data.providerID || "").toLowerCase();
4545
+ const provider = String(opencodeMessageProvider(data)).toLowerCase();
4407
4546
  if (!provider) return false;
4408
4547
  return !(
4409
4548
  provider.includes("anthropic") ||
@@ -4562,7 +4701,8 @@ async function parseOpencodeDbIncremental({
4562
4701
  continue;
4563
4702
  }
4564
4703
 
4565
- const model = normalizeModelInput(msg?.modelID || msg?.model || msg?.modelId) || DEFAULT_MODEL;
4704
+ const { modelId: dbModelId } = normalizeOpencodeModelFields(msg);
4705
+ const model = dbModelId || DEFAULT_MODEL;
4566
4706
  const bucket = getHourlyBucket(hourlyState, defaultSource, model, bucketStart);
4567
4707
  addTotals(bucket.totals, delta);
4568
4708
  touchedBuckets.add(bucketKey(defaultSource, model, bucketStart));
@@ -16614,36 +16754,55 @@ function isCjkCodePoint(code) {
16614
16754
  // (session transcripts are SQLCipher-encrypted; memory summaries carry no
16615
16755
  // token counts), so the token-count-only queue is intentionally never written
16616
16756
  // for this provider — the cloud hourly table stays clean.
16617
- function resolveTraePath(env = process.env) {
16757
+ // Ordered candidate app-dir names. The CN IDE build installs as "Trae CN" on
16758
+ // Windows; the international build installs as "TRAE SOLO". Both expose the same
16759
+ // iCubeServerData entitlement key, so either is a valid Trae IDE install.
16760
+ function traeCandidateAppDirs(env = process.env) {
16618
16761
  const override = env.TOKENTRACKER_TRAE_HOME;
16619
16762
  if (typeof override === "string" && override.trim().length > 0) {
16620
- return override.trim();
16763
+ return [override.trim()];
16621
16764
  }
16622
16765
  const home = require("node:os").homedir();
16623
16766
  if (process.platform === "darwin") {
16624
- return path.join(home, "Library", "Application Support", "TRAE SOLO");
16767
+ return [path.join(home, "Library", "Application Support", "TRAE SOLO")];
16625
16768
  }
16626
16769
  if (process.platform === "win32") {
16627
16770
  // A Windows box with no APPDATA still keeps Trae under the standard
16628
16771
  // roaming profile — falling through to the dot-dir below would point at
16629
16772
  // a location Trae never writes.
16630
- const appData = typeof env.APPDATA === "string" ? env.APPDATA.trim() : "";
16631
- return appData
16632
- ? path.join(appData, "TRAE SOLO")
16633
- : path.join(home, "AppData", "Roaming", "TRAE SOLO");
16773
+ const appData =
16774
+ typeof env.APPDATA === "string" && env.APPDATA.trim()
16775
+ ? env.APPDATA.trim()
16776
+ : path.join(home, "AppData", "Roaming");
16777
+ return [
16778
+ path.join(appData, "TRAE SOLO"),
16779
+ path.join(appData, "Trae CN"),
16780
+ ];
16634
16781
  }
16635
16782
  // Linux and friends: TRAE SOLO ships official builds for macOS/Windows
16636
16783
  // only, so there is no verified app-data layout to default to. Fall back to
16637
16784
  // a deterministic home-dir path (best-effort detection);
16638
16785
  // TOKENTRACKER_TRAE_HOME always wins for unusual installs.
16639
- return path.join(home, ".trae-solo");
16786
+ return [path.join(home, ".trae-solo")];
16787
+ }
16788
+
16789
+ // Best-effort: the first candidate that exists on disk (used for display/init
16790
+ // detection). When no install is present, returns the primary candidate so
16791
+ // callers still get a deterministic path to report.
16792
+ function resolveTraePath(env = process.env) {
16793
+ const candidates = traeCandidateAppDirs(env);
16794
+ for (const dir of candidates) {
16795
+ try { if (fssync.existsSync(dir)) return dir; } catch (_error) {}
16796
+ }
16797
+ return candidates.length ? candidates[0] : null;
16640
16798
  }
16641
16799
 
16642
16800
  function resolveTraeStoragePath(env = process.env) {
16643
- const traHome = resolveTraePath(env);
16644
- if (!traHome) return null;
16645
- const p = path.join(traHome, "User", "globalStorage", "storage.json");
16646
- return fssync.existsSync(p) ? p : null;
16801
+ for (const dir of traeCandidateAppDirs(env)) {
16802
+ const p = path.join(dir, "User", "globalStorage", "storage.json");
16803
+ try { if (fssync.existsSync(p)) return p; } catch (_error) {}
16804
+ }
16805
+ return null;
16647
16806
  }
16648
16807
 
16649
16808
  /**
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * TRAE Work CN (国内版) config + usage fetch. Phase A: macOS + test-injected
4
+ * TRAE Work CN (国内版) config + usage fetch. Phase A: macOS + Windows + test-injected
5
5
  * paths only. Resolves the CN home, reads the `iCubeAuthInfo://icube.cloudide`
6
6
  * blob from User/globalStorage/storage.json (plaintext JSON or Base64 tc v5),
7
7
  * and talks to the TRAE CN usage API. JWTs / refresh tokens are never
@@ -85,7 +85,16 @@ function resolveTraeCnHome({ env = process.env, home = os.homedir(), platform =
85
85
  if (platform === "darwin") {
86
86
  return path.join(home, "Library", "Application Support", TRAE_CN_APP_DIR);
87
87
  }
88
- // ponytail: this phase is macOS + test-injected paths only.
88
+ if (platform === "win32") {
89
+ // Windows keeps the CN Work app under the roaming profile, mirroring the
90
+ // macOS Application Support layout (APPDATA/TRAE SOLO CN/User/...).
91
+ const appData =
92
+ typeof env.APPDATA === "string" && env.APPDATA.trim()
93
+ ? env.APPDATA.trim()
94
+ : path.join(home, "AppData", "Roaming");
95
+ return path.join(appData, TRAE_CN_APP_DIR);
96
+ }
97
+ // No verified CN Work app-data layout on other platforms yet.
89
98
  return null;
90
99
  }
91
100
 
@@ -1 +0,0 @@
1
- import{r as n,z as a}from"./__vite-browser-external-WKiEWJ30.js";import{u as p}from"./DialogTitle-eIgptOU-.js";import{u as c}from"./useScrollLock-CZ1Na38Y.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,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-WKiEWJ30.js";import{h as r,i as t,p as s}from"./react-us685QUG.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};