tokentracker-cli 0.95.2 → 0.96.1

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 (118) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +1 -1
  3. package/dashboard/dist/assets/{AchievementBadge-DtB1MwFe.js → AchievementBadge-DKyElm0T.js} +1 -1
  4. package/dashboard/dist/assets/{AchievementsPage--vddu0dC.js → AchievementsPage-BvCx3wND.js} +2 -2
  5. package/dashboard/dist/assets/{AchievementsSection-CDl9hsbj.js → AchievementsSection-DN69LzXX.js} +1 -1
  6. package/dashboard/dist/assets/{BadgeDetailModal-C7WsIRy9.js → BadgeDetailModal-DGiKdZfz.js} +1 -1
  7. package/dashboard/dist/assets/{BotAnimated-CmXyzIhX.js → BotAnimated-zN8rH7_K.js} +1 -1
  8. package/dashboard/dist/assets/{Card-SUbC3VNS.js → Card-COWo-ClV.js} +1 -1
  9. package/dashboard/dist/assets/{ClawdAnimated-BdEbs8Me.js → ClawdAnimated-BWsB7Eat.js} +2 -2
  10. package/dashboard/dist/assets/{CommandPalette-D1RLLeHB.js → CommandPalette-BfvIwuoJ.js} +1 -1
  11. package/dashboard/dist/assets/{CommunityStatsModal-5ESGJgZQ.js → CommunityStatsModal-BQWg7R_y.js} +1 -1
  12. package/dashboard/dist/assets/{ConfirmModal-DsCd51b2.js → ConfirmModal-C7ABSI-O.js} +1 -1
  13. package/dashboard/dist/assets/{Controls-BVrLC_QD.js → Controls-vMzEZzej.js} +1 -1
  14. package/dashboard/dist/assets/DashboardPage-QBhbCMZF.js +1 -0
  15. package/dashboard/dist/assets/{DevicePage-zYjj-m5V.js → DevicePage-3ejUBLWM.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-D66yBqkd.js → DialogClose-D_dvtfGV.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-ryGYdQjX.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-C4ia00lc.js → DialogTitle-_ahf0z8b.js} +1 -1
  19. package/dashboard/dist/assets/{FadeIn-DF2c8XaR.js → FadeIn-D7Jcfz1N.js} +1 -1
  20. package/dashboard/dist/assets/{HeaderGithubStar-Cnr3aqcX.js → HeaderGithubStar-D--Rx5BS.js} +1 -1
  21. package/dashboard/dist/assets/{HoverTooltip-DBOpxlH7.js → HoverTooltip-n1W0z3Wr.js} +1 -1
  22. package/dashboard/dist/assets/{IpCheckPage-CUY6KBaV.js → IpCheckPage-D202tl9B.js} +1 -1
  23. package/dashboard/dist/assets/{LandingPage-DKL8hZEq.js → LandingPage-D6yL2EZo.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardAvatar-DRYeMNlq.js → LeaderboardAvatar-ChFg2mtA.js} +1 -1
  25. package/dashboard/dist/assets/{LeaderboardPage-DpD9Az3k.js → LeaderboardPage-BXHwGs8r.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProfileModal-Dfg0V13F.js → LeaderboardProfileModal-B5LoOpCk.js} +3 -3
  27. package/dashboard/dist/assets/{LeaderboardProfilePage-DH-jcVDu.js → LeaderboardProfilePage-jsukhRz8.js} +1 -1
  28. package/dashboard/dist/assets/LimitsPage-PvqkOOwp.js +6 -0
  29. package/dashboard/dist/assets/{LocalOnlyNotice-Bj_m0hw7.js → LocalOnlyNotice-Du0lz657.js} +1 -1
  30. package/dashboard/dist/assets/{LoginCard-BVKvtx60.js → LoginCard-BFEX9x7-.js} +1 -1
  31. package/dashboard/dist/assets/{LoginModal-mUGiSIJN.js → LoginModal-DqC7_d35.js} +1 -1
  32. package/dashboard/dist/assets/{LoginPage-BORCuCUt.js → LoginPage-Dzx8N7HE.js} +1 -1
  33. package/dashboard/dist/assets/{PetPage-D1JfEOVA.js → PetPage-CNlWHF4z.js} +1 -1
  34. package/dashboard/dist/assets/{PopoverPopup-DK3et-HG.js → PopoverPopup-D9jadD3j.js} +1 -1
  35. package/dashboard/dist/assets/{ProviderIcon-By8FAjxR.js → ProviderIcon-Caz5PuAm.js} +1 -1
  36. package/dashboard/dist/assets/{ResetPasswordPage-DPa81jqa.js → ResetPasswordPage-x_GR79sR.js} +1 -1
  37. package/dashboard/dist/assets/{SegmentedControl-DLzS-2eZ.js → SegmentedControl-BxDiDV4R.js} +1 -1
  38. package/dashboard/dist/assets/{Select-C05W6XEM.js → Select-B5o-1sEl.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-CcfB3blP.js → SelectItemText-CiA6L7Wk.js} +1 -1
  40. package/dashboard/dist/assets/{ServiceStatusPage-ByqLbHv1.js → ServiceStatusPage-BVQuVdjg.js} +1 -1
  41. package/dashboard/dist/assets/SessionsPage-DE39wNIO.js +2 -0
  42. package/dashboard/dist/assets/SettingsPage-DJrb_fsK.js +2 -0
  43. package/dashboard/dist/assets/{SkillsPage-BuuqQuNU.js → SkillsPage-COk-YQQ5.js} +1 -1
  44. package/dashboard/dist/assets/{TokenGalaxy-DzccxkXW.js → TokenGalaxy-BPM779Wb.js} +1 -1
  45. package/dashboard/dist/assets/{ToolbarRootContext-XUDMp5uy.js → ToolbarRootContext-CozJT6Vs.js} +1 -1
  46. package/dashboard/dist/assets/{TrendMonitor-B-yYabbz.js → TrendMonitor-BkNy-o1D.js} +1 -1
  47. package/dashboard/dist/assets/{WidgetsPage-CwUDjEFo.js → WidgetsPage-Bpg6H-ZB.js} +1 -1
  48. package/dashboard/dist/assets/{WrappedPage-Dr9ow3gH.js → WrappedPage-C6-NM-RO.js} +1 -1
  49. package/dashboard/dist/assets/__vite-browser-external-BGnq8Ebm.js +45 -0
  50. package/dashboard/dist/assets/__vite-browser-external-CvMMIjhi.css +1 -0
  51. package/dashboard/dist/assets/{agent-logos-Cqc_suYk.js → agent-logos-DpbkRqXT.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-up-right-DSsR3Ntr.js → arrow-up-right-BjOtWUVs.js} +1 -1
  53. package/dashboard/dist/assets/{check-Cy35lAKh.js → check-BlxnX5jZ.js} +1 -1
  54. package/dashboard/dist/assets/{composite-BDwOlhhX.js → composite-Dxmivlk4.js} +1 -1
  55. package/dashboard/dist/assets/{copy-4szu5cYh.js → copy-CIsWjLY5.js} +1 -1
  56. package/dashboard/dist/assets/{download-DdGW6Krz.js → download-QDaQbQdd.js} +1 -1
  57. package/dashboard/dist/assets/{external-link-DwSt83cq.js → external-link-DPV4VU3m.js} +1 -1
  58. package/dashboard/dist/assets/{flame-DubukWqK.js → flame-B_X-7VNp.js} +1 -1
  59. package/dashboard/dist/assets/{format-tokens-B70EdQcN.js → format-tokens-DEx2TJj8.js} +1 -1
  60. package/dashboard/dist/assets/{icons-B86TLdAj.js → icons-983RyGSl.js} +1 -1
  61. package/dashboard/dist/assets/{index-B_KMoBeK.js → index-BONYA8HZ.js} +1 -1
  62. package/dashboard/dist/assets/{index-DI-W-pun.js → index-Cjs5stC0.js} +1 -1
  63. package/dashboard/dist/assets/{index-DzEMcUwI.js → index-qtonw_x4.js} +1 -1
  64. package/dashboard/dist/assets/{info-BzFpLbl6.js → info-DpVOhXZI.js} +1 -1
  65. package/dashboard/dist/assets/limits-providers-BUoquSA-.js +1 -0
  66. package/dashboard/dist/assets/{link-2-BQp7-3T7.js → link-2-CBro-upz.js} +1 -1
  67. package/dashboard/dist/assets/{loader-circle-lrDmbIg5.js → loader-circle-wYFdSyOd.js} +1 -1
  68. package/dashboard/dist/assets/{maximize-2-Cnd_Oth8.js → maximize-2-DhTyQ4ly.js} +1 -1
  69. package/dashboard/dist/assets/{modifiers.esm-CPTpZdHU.js → modifiers.esm-CPljwVhN.js} +1 -1
  70. package/dashboard/dist/assets/{monitor-smartphone-C7UY1zRx.js → monitor-smartphone-D_wr-HOR.js} +1 -1
  71. package/dashboard/dist/assets/{plus-Bglp9N_7.js → plus-BNjfkBEF.js} +1 -1
  72. package/dashboard/dist/assets/{provider-display-CGsFySs6.js → provider-display-BOBtUyFi.js} +1 -1
  73. package/dashboard/dist/assets/{react-DKXriBSF.js → react-yHv9MTUA.js} +1 -1
  74. package/dashboard/dist/assets/{search-D_LLDRdU.js → search-Dfak92ZA.js} +1 -1
  75. package/dashboard/dist/assets/{skills-api-CJWjKIco.js → skills-api-By2PdNa1.js} +1 -1
  76. package/dashboard/dist/assets/{sortable.esm-zlY09tye.js → sortable.esm-B9xy66_v.js} +1 -1
  77. package/dashboard/dist/assets/{store-8e16Ac2h.js → store-oQZR4d9M.js} +1 -1
  78. package/dashboard/dist/assets/{terminal-CR228ekj.js → terminal-pbXWBmZM.js} +1 -1
  79. package/dashboard/dist/assets/{trash-2-BjLWewOp.js → trash-2-CboDPwbW.js} +1 -1
  80. package/dashboard/dist/assets/{use-community-stats-DhOJPaVg.js → use-community-stats-D6HTdnHq.js} +1 -1
  81. package/dashboard/dist/assets/{use-latest-request-guard-lcGhb-NC.js → use-latest-request-guard-hwSr9Cam.js} +1 -1
  82. package/dashboard/dist/assets/{use-limits-display-prefs-DJIImtLN.js → use-limits-display-prefs-B-pbwUS8.js} +1 -1
  83. package/dashboard/dist/assets/{use-ordered-list-BJZfZuoJ.js → use-ordered-list-OtfOqrNb.js} +1 -1
  84. package/dashboard/dist/assets/use-reduced-motion-DGXcZBX3.js +1 -0
  85. package/dashboard/dist/assets/{use-session-efficiency-pref-C-l4b1cD.js → use-session-efficiency-pref-BScvtS8F.js} +1 -1
  86. package/dashboard/dist/assets/{use-transform-UjIt477N.js → use-transform-Cy1ykjEs.js} +1 -1
  87. package/dashboard/dist/assets/{use-usage-limits-DqoTZrqT.js → use-usage-limits-CSMb2zYW.js} +1 -1
  88. package/dashboard/dist/assets/{useCurrency-CnM8aB4v.js → useCurrency-VqF3cy1C.js} +1 -1
  89. package/dashboard/dist/assets/{useTokenFormat-D4pAK2ld.js → useTokenFormat-Cgub4MXu.js} +1 -1
  90. package/dashboard/dist/assets/{zap-Du1IyEtj.js → zap-DStzvQJI.js} +1 -1
  91. package/dashboard/dist/index.html +2 -2
  92. package/dashboard/dist/ip-check.html +2 -2
  93. package/dashboard/dist/leaderboard.html +2 -2
  94. package/dashboard/dist/share.html +2 -2
  95. package/package.json +1 -1
  96. package/src/commands/status.js +4 -4
  97. package/src/commands/sync.js +5 -3
  98. package/src/lib/ark-agent-plan-limits.js +438 -0
  99. package/src/lib/ark-coding-plan-limits.js +103 -28
  100. package/src/lib/codex-model-attribution.js +128 -0
  101. package/src/lib/codex-rollout-parser.js +98 -4
  102. package/src/lib/command-runner.js +13 -0
  103. package/src/lib/local-api.js +3 -0
  104. package/src/lib/pricing/curated-overrides.json +6 -6
  105. package/src/lib/pricing/index.js +38 -1
  106. package/src/lib/pricing/seed-snapshot.json +1 -1
  107. package/src/lib/rollout.js +67 -14
  108. package/src/lib/session-analytics.js +374 -110
  109. package/src/lib/usage-limits.js +130 -18
  110. package/dashboard/dist/assets/DashboardPage-DtUOG6og.js +0 -1
  111. package/dashboard/dist/assets/DialogDescription-BVlaoajd.js +0 -1
  112. package/dashboard/dist/assets/LimitsPage-CVCKPScf.js +0 -5
  113. package/dashboard/dist/assets/SessionsPage-CEDv1thj.js +0 -2
  114. package/dashboard/dist/assets/SettingsPage-DAcndJZQ.js +0 -2
  115. package/dashboard/dist/assets/__vite-browser-external-CCMdjCX2.css +0 -1
  116. package/dashboard/dist/assets/__vite-browser-external-DPOlUkcY.js +0 -45
  117. package/dashboard/dist/assets/limits-providers-DxZOjiXQ.js +0 -1
  118. package/dashboard/dist/assets/use-reduced-motion-DdpxZQoh.js +0 -1
@@ -15,6 +15,12 @@ const {
15
15
  createUsageDeltaState,
16
16
  snapshotUsageBaselines,
17
17
  } = require("./codex-token-usage");
18
+ const {
19
+ applyCodexModelEvent,
20
+ createCodexModelAttributionState,
21
+ currentCodexModel,
22
+ snapshotCodexModelAttributionState,
23
+ } = require("./codex-model-attribution");
18
24
  const { USD_TICKS_PER_USD, normalizeGrokUsage } = require("./grok-usage");
19
25
 
20
26
  const DEFAULT_SOURCE = "codex";
@@ -435,6 +441,9 @@ async function parseRolloutIncremental({
435
441
  ? prev.tokenUsageBaselines || null
436
442
  : null;
437
443
  const lastModel = sameInode && !truncated ? prev.lastModel || null : null;
444
+ const modelAttributionState = sameInode && !truncated
445
+ ? prev.modelAttributionState || null
446
+ : null;
438
447
 
439
448
  const codexProjectFastPath = projectEnabled && fileSource === DEFAULT_SOURCE;
440
449
  const projectOffset = sameInode && !truncated ? Number(prev.projectOffset || 0) : 0;
@@ -506,6 +515,7 @@ async function parseRolloutIncremental({
506
515
  lastTotal,
507
516
  tokenUsageBaselines,
508
517
  lastModel,
518
+ modelAttributionState,
509
519
  projectState,
510
520
  projectMetaCache,
511
521
  publicRepoCache,
@@ -520,6 +530,7 @@ async function parseRolloutIncremental({
520
530
  lastTotal,
521
531
  tokenUsageBaselines,
522
532
  lastModel,
533
+ modelAttributionState,
523
534
  hourlyState,
524
535
  touchedBuckets,
525
536
  source: fileSource,
@@ -542,6 +553,7 @@ async function parseRolloutIncremental({
542
553
  lastTotal: result.lastTotal,
543
554
  tokenUsageBaselines: result.tokenUsageBaselines,
544
555
  lastModel: result.lastModel,
556
+ modelAttributionState: result.modelAttributionState,
545
557
  updatedAt: new Date().toISOString(),
546
558
  };
547
559
  if (codexProjectFastPath) {
@@ -2017,6 +2029,7 @@ async function parseRolloutFile({
2017
2029
  lastTotal,
2018
2030
  tokenUsageBaselines,
2019
2031
  lastModel,
2032
+ modelAttributionState: previousModelAttributionState,
2020
2033
  hourlyState,
2021
2034
  touchedBuckets,
2022
2035
  source,
@@ -2042,6 +2055,7 @@ async function parseRolloutFile({
2042
2055
  lastTotal,
2043
2056
  tokenUsageBaselines,
2044
2057
  lastModel,
2058
+ modelAttributionState: previousModelAttributionState,
2045
2059
  eventsAggregated: 0,
2046
2060
  projectFileContexts,
2047
2061
  };
@@ -2053,6 +2067,9 @@ async function parseRolloutFile({
2053
2067
  });
2054
2068
 
2055
2069
  let model = typeof lastModel === "string" ? lastModel : null;
2070
+ const modelAttributionState = createCodexModelAttributionState(
2071
+ previousModelAttributionState || { model },
2072
+ );
2056
2073
  const usageDeltaState = createUsageDeltaState({
2057
2074
  lastTotal,
2058
2075
  baselines: tokenUsageBaselines,
@@ -2090,14 +2107,17 @@ async function parseRolloutFile({
2090
2107
  const { line } = record;
2091
2108
  if (!line) continue;
2092
2109
  const maybeTokenCount = line.includes('"token_count"');
2093
- const maybeTurnContext =
2110
+ const maybeModelReroute =
2094
2111
  !maybeTokenCount &&
2112
+ (line.includes('"model/rerouted"') || line.includes('"model_rerouted"'));
2113
+ const maybeTurnContext =
2114
+ !maybeTokenCount && !maybeModelReroute &&
2095
2115
  (line.includes('"turn_context"') || line.includes('"session_meta"')) &&
2096
2116
  (line.includes('"model"') ||
2097
2117
  line.includes('"cwd"') ||
2098
2118
  line.includes('"current_date"') ||
2099
2119
  line.includes('"forked_from_id"'));
2100
- if (!maybeTokenCount && !maybeTurnContext) {
2120
+ if (!maybeTokenCount && !maybeTurnContext && !maybeModelReroute) {
2101
2121
  if (invalidRecordPolicy === "throw" || !record.terminated) {
2102
2122
  try {
2103
2123
  JSON.parse(line);
@@ -2120,6 +2140,9 @@ async function parseRolloutFile({
2120
2140
  }
2121
2141
  if (!record.terminated) committedEndOffset = scannedEndOffset;
2122
2142
 
2143
+ applyCodexModelEvent(modelAttributionState, obj);
2144
+ model = currentCodexModel(modelAttributionState) || model;
2145
+
2123
2146
  if (
2124
2147
  (obj?.type === "turn_context" || obj?.type === "session_meta") &&
2125
2148
  obj?.payload &&
@@ -2131,9 +2154,6 @@ async function parseRolloutFile({
2131
2154
  if (obj.type === "turn_context" && typeof obj.payload.current_date === "string") {
2132
2155
  currentDate = normalizeIsoDate(obj.payload.current_date);
2133
2156
  }
2134
- if (typeof obj.payload.model === "string") {
2135
- model = obj.payload.model;
2136
- }
2137
2157
  if (projectState && typeof obj.payload.cwd === "string") {
2138
2158
  const nextCwd = obj.payload.cwd.trim();
2139
2159
  if (nextCwd && nextCwd !== currentCwd) {
@@ -2167,7 +2187,9 @@ async function parseRolloutFile({
2167
2187
  if (totalUsage && typeof totalUsage === "object") latestTotal = totalUsage;
2168
2188
 
2169
2189
  const rawDelta = consumeUsageDelta(usageDeltaState, lastUsage, totalUsage);
2170
- const delta = rawDelta ? normalizeUsage(rawDelta) : null;
2190
+ const totalOnlyResetSentinel = source === DEFAULT_SOURCE
2191
+ && isCodexTotalOnlyResetSentinel(lastUsage, totalUsage);
2192
+ const delta = rawDelta && !totalOnlyResetSentinel ? normalizeUsage(rawDelta) : null;
2171
2193
  if (!delta || isAllZeroUsage(delta)) continue;
2172
2194
  delta.conversation_count = 1;
2173
2195
 
@@ -2266,6 +2288,7 @@ async function parseRolloutFile({
2266
2288
  lastTotal: latestTotal,
2267
2289
  tokenUsageBaselines: snapshotUsageBaselines(usageDeltaState),
2268
2290
  lastModel: model,
2291
+ modelAttributionState: snapshotCodexModelAttributionState(modelAttributionState),
2269
2292
  eventsAggregated,
2270
2293
  projectFileContexts,
2271
2294
  };
@@ -2277,6 +2300,7 @@ async function scanRolloutProjectFileContexts({
2277
2300
  lastTotal,
2278
2301
  tokenUsageBaselines,
2279
2302
  lastModel,
2303
+ modelAttributionState,
2280
2304
  projectState,
2281
2305
  projectMetaCache,
2282
2306
  publicRepoCache,
@@ -2294,6 +2318,7 @@ async function scanRolloutProjectFileContexts({
2294
2318
  lastTotal,
2295
2319
  tokenUsageBaselines,
2296
2320
  lastModel,
2321
+ modelAttributionState,
2297
2322
  eventsAggregated: 0,
2298
2323
  projectFileContexts,
2299
2324
  };
@@ -2370,6 +2395,7 @@ async function scanRolloutProjectFileContexts({
2370
2395
  lastTotal,
2371
2396
  tokenUsageBaselines,
2372
2397
  lastModel,
2398
+ modelAttributionState,
2373
2399
  eventsAggregated: 0,
2374
2400
  projectFileContexts,
2375
2401
  };
@@ -4389,13 +4415,28 @@ function normalizeUsage(u) {
4389
4415
  // bytes twice: once at the full input rate and again at the cache_read
4390
4416
  // rate, producing ~6–7x cost inflation on cache-heavy Codex sessions
4391
4417
  // (verified against ccusage's per-day numbers on the same rollouts).
4392
- // We intentionally leave `total_tokens` unchanged: Codex reports
4393
- // total = input(inclusive of cached) + output, which numerically equals
4394
- // our schema's non_cached + cached + output + 0 (cache_creation=0 here).
4418
+ // Preserve the reported total for compatibility with older Codex / Every
4419
+ // Code shapes where output_tokens can exclude reasoning or some component
4420
+ // fields are absent. The exact all-zero reset sentinel is rejected before
4421
+ // normalization by isCodexTotalOnlyResetSentinel().
4395
4422
  out.input_tokens = Math.max(0, out.input_tokens - out.cached_input_tokens);
4396
4423
  return out;
4397
4424
  }
4398
4425
 
4426
+ function isCodexTotalOnlyResetSentinel(lastUsage, totalUsage) {
4427
+ const componentTotal = (usage) => [
4428
+ usage?.input_tokens,
4429
+ usage?.cached_input_tokens,
4430
+ usage?.cache_creation_input_tokens ?? usage?.cache_write_input_tokens,
4431
+ usage?.output_tokens,
4432
+ usage?.reasoning_output_tokens,
4433
+ ].reduce((sum, value) => sum + Math.max(0, Number(value) || 0), 0);
4434
+ return Number(lastUsage?.total_tokens || 0) > 0
4435
+ && Number(totalUsage?.total_tokens || 0) === 0
4436
+ && componentTotal(lastUsage) === 0
4437
+ && componentTotal(totalUsage) === 0;
4438
+ }
4439
+
4399
4440
  // Stable dedup key for one Claude jsonl entry. Anthropic's official protocol
4400
4441
  // guarantees `message.id` is globally unique per response, so msgId alone is a
4401
4442
  // valid dedup key. Older code required both msgId AND requestId, which short-
@@ -5800,17 +5841,21 @@ function resolveQoderCnProjectsDir({ home = os.homedir(), env = process.env, pla
5800
5841
  ? path.resolve(env.QODER_CN_PROJECTS_DIR.trim())
5801
5842
  : null;
5802
5843
  if (override) return override;
5803
- // CN and international currently share ~/.qoder; keep a distinct override for
5804
- // future split without double-counting by default.
5844
+ // The new CN app (com.qodercn.app.stable, 2026-08+) keeps its sessions in
5845
+ // ~/.qoder-cn/projects a sibling of the international ~/.qoder, not a
5846
+ // shared directory. Pointing CN at ~/.qoder/projects made the "CN dir
5847
+ // diverges from international" guards in sync.js/status.js always false,
5848
+ // so new-version CN JSONL usage was silently never parsed (and on
5849
+ // international-only installs would have double-counted under qoder-cn).
5805
5850
  if (platform === "win32" && !env.QODER_CN_PROJECTS_DIR) {
5806
5851
  const discoverWslHome = deps.discoverWslHome || wsl.discoverWslHome;
5807
- const wslRoot = wsl.shouldProbeWsl(env) ? discoverWslHome(".qoder", { ...deps, env }) : null;
5852
+ const wslRoot = wsl.shouldProbeWsl(env) ? discoverWslHome(".qoder-cn", { ...deps, env }) : null;
5808
5853
  if (wslRoot) {
5809
5854
  const wslProjects = path.join(wslRoot, "projects");
5810
5855
  if ((deps.existsSync || fssync.existsSync)(wslProjects)) return wslProjects;
5811
5856
  }
5812
5857
  }
5813
- return path.join(home, ".qoder", "projects");
5858
+ return path.join(home, ".qoder-cn", "projects");
5814
5859
  }
5815
5860
 
5816
5861
  async function listQoderNewSessionFiles(projectsDir) {
@@ -5835,7 +5880,15 @@ async function listQoderNewSessionFiles(projectsDir) {
5835
5880
  function qoderNewModelFromRecord(record) {
5836
5881
  const msgModel = record?.message?.model;
5837
5882
  const direct = typeof msgModel === "string" ? msgModel.trim() : "";
5838
- return normalizeModelInput(direct) || "qoder-agent";
5883
+ // CN BYOK routes embed an install-local provider UUID in the model id
5884
+ // ("qoder-custom-<uuid>/glm-5.3-flash"). Keep the bare model id so bucket
5885
+ // keys stay stable across reinstalls and don't fragment per user; official
5886
+ // ids (e.g. "qmodel_38max") have no prefix and pass through unchanged.
5887
+ const stripped = direct.replace(
5888
+ /^qoder-custom-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\//i,
5889
+ "",
5890
+ );
5891
+ return normalizeModelInput(stripped) || "qoder-agent";
5839
5892
  }
5840
5893
 
5841
5894
  function qoderNewMessageKey(record, filePath, lineIndex = 0) {