tokentracker-cli 0.86.0 → 0.87.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 (102) hide show
  1. package/README.de.md +2 -0
  2. package/README.ja.md +2 -0
  3. package/README.ko.md +2 -0
  4. package/README.md +2 -0
  5. package/README.zh-CN.md +2 -0
  6. package/dashboard/dist/assets/{AchievementBadge-BgbH_mX4.js → AchievementBadge-CU9m5POo.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-CQqkh1JS.js → AchievementsPage-ESzayVR5.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-Dmkxpn0s.js → AchievementsSection-DUVRqw19.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-7OQf37lk.js → BadgeDetailModal-CxNQcN6_.js} +1 -1
  10. package/dashboard/dist/assets/{Card-1PmWZRTK.js → Card-DXIoSOtO.js} +1 -1
  11. package/dashboard/dist/assets/{ClawdAnimated-CMJFagpX.js → ClawdAnimated-BsuxMkNd.js} +1 -1
  12. package/dashboard/dist/assets/{CommandPalette-D4OrwF1w.js → CommandPalette-C-amBp21.js} +1 -1
  13. package/dashboard/dist/assets/{CommunityStatsModal-BDC9EcwZ.js → CommunityStatsModal-ByFOQube.js} +1 -1
  14. package/dashboard/dist/assets/{ConfirmModal-22DL5C_9.js → ConfirmModal-BnUyidbn.js} +1 -1
  15. package/dashboard/dist/assets/{DashboardPage-CEXWzDtt.js → DashboardPage-GyXkKYW0.js} +1 -1
  16. package/dashboard/dist/assets/{DevicePage-8uhHB7kX.js → DevicePage-B_o-jhku.js} +1 -1
  17. package/dashboard/dist/assets/{DialogClose-BcJx6VK-.js → DialogClose-BSd7Jiag.js} +1 -1
  18. package/dashboard/dist/assets/DialogDescription-BFBVGbUT.js +1 -0
  19. package/dashboard/dist/assets/{DialogTitle-gRzunAqv.js → DialogTitle-XBpneDtQ.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-4rlPdBVe.js → FadeIn-Dg0v4V9p.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-CR003aO3.js → HeaderGithubStar-BIsQD1OK.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-Dgz-dM8Q.js → HoverTooltip-CtmRa8ne.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-B4PJRJP_.js → IpCheckPage-BUoLz5sL.js} +1 -1
  24. package/dashboard/dist/assets/LandingPage-BheA-lw8.js +2 -0
  25. package/dashboard/dist/assets/{LeaderboardAvatar-CL9LxiEf.js → LeaderboardAvatar-DAy9SOdL.js} +1 -1
  26. package/dashboard/dist/assets/{LeaderboardPage-DhIzM2Z2.js → LeaderboardPage-CwLqtEUN.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-bbf5uJR5.js → LeaderboardProfileModal-BZ9yGyjB.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-CzuPxscw.js → LeaderboardProfilePage-Cs-LTfvS.js} +1 -1
  29. package/dashboard/dist/assets/{LimitsPage-B59rb2gg.js → LimitsPage-BQle8e8N.js} +1 -1
  30. package/dashboard/dist/assets/{LocalOnlyNotice-BkK-6n1R.js → LocalOnlyNotice-Bp4CqObQ.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-BFGCzEjE.js → LoginCard-BIXfWkR2.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-yhkyscFn.js → LoginModal-B-ubPXdr.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-DwU5s0F7.js → LoginPage-9arjWT-s.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-BPscYR6z.js → PetPage-BzAVsvh6.js} +1 -1
  35. package/dashboard/dist/assets/{PopoverPopup-E7PyGTTB.js → PopoverPopup-DMg7XE1h.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-CvmnzwKU.js → ProviderIcon-BG3oqeST.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-DD6qX616.js → ResetPasswordPage-LQ9rfcRE.js} +1 -1
  38. package/dashboard/dist/assets/{SegmentedControl-DjGBL_l1.js → SegmentedControl-GY2tRk1u.js} +1 -1
  39. package/dashboard/dist/assets/{Select-B2MGmsi2.js → Select-CCngFwmz.js} +1 -1
  40. package/dashboard/dist/assets/{SelectItemText-BaTwA_ZF.js → SelectItemText-B9Zm8WTR.js} +1 -1
  41. package/dashboard/dist/assets/{ServiceStatusPage-0K0y8A6m.js → ServiceStatusPage-BxPvk1-W.js} +1 -1
  42. package/dashboard/dist/assets/{SessionsPage-CxrtRm3B.js → SessionsPage-DGo6yAwP.js} +1 -1
  43. package/dashboard/dist/assets/SettingsPage-ConPEiwO.js +1 -0
  44. package/dashboard/dist/assets/{SkillsPage-BVOU8t3t.js → SkillsPage-D0PB3WR5.js} +1 -1
  45. package/dashboard/dist/assets/{TokenGalaxy-w7bwGloC.js → TokenGalaxy-Chh_fPIv.js} +1 -1
  46. package/dashboard/dist/assets/{ToolbarRootContext-BAzpDZej.js → ToolbarRootContext-BXacVvrF.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-DyT7Q4xp.js → TrendMonitor-CjiCwNmO.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-eDDtopQT.js → WidgetsPage-CBw9uBCO.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-CKkbdWHF.js → WrappedPage-CEpBAMZR.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-SU8sCTLB.js → __vite-browser-external-Dyag-P6u.js} +20 -13
  51. package/dashboard/dist/assets/{agent-logos-bkh9XZJd.js → agent-logos-CLQGE0AM.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-up-right-D16zCIw8.js → arrow-up-right-CU2HDjoG.js} +1 -1
  53. package/dashboard/dist/assets/{check-BFL17JWQ.js → check-CucGM5lh.js} +1 -1
  54. package/dashboard/dist/assets/{chevron-right-CUJgrtkp.js → chevron-right-DYzUvcID.js} +1 -1
  55. package/dashboard/dist/assets/{copy-CRQiCjgs.js → copy-BFeQ31Tr.js} +1 -1
  56. package/dashboard/dist/assets/{download-Cd12nDrQ.js → download-Coq_llCX.js} +1 -1
  57. package/dashboard/dist/assets/{external-link-CvAfnsG-.js → external-link-BJiDBW3d.js} +1 -1
  58. package/dashboard/dist/assets/{flame-CCi681Yl.js → flame-5stsnhc0.js} +1 -1
  59. package/dashboard/dist/assets/{format-tokens-BAVt8uTZ.js → format-tokens-CpTJbo-2.js} +1 -1
  60. package/dashboard/dist/assets/{icons-CbKC96gR.js → icons-jsuvmnnO.js} +1 -1
  61. package/dashboard/dist/assets/{index-B9LWn-mo.js → index-BU6GsTbx.js} +1 -1
  62. package/dashboard/dist/assets/{index-B5b6PCh5.js → index-Bs97feNM.js} +1 -1
  63. package/dashboard/dist/assets/{index--TDvaL1y.js → index-DSD3RLQ4.js} +1 -1
  64. package/dashboard/dist/assets/{info-BPFpD--M.js → info-DNNUzeV_.js} +1 -1
  65. package/dashboard/dist/assets/{limits-providers-Cyedt-WJ.js → limits-providers-DKTqNO55.js} +1 -1
  66. package/dashboard/dist/assets/{link-2-BkEPPdfC.js → link-2-DC1hnQcE.js} +1 -1
  67. package/dashboard/dist/assets/{loader-circle-CzASiylJ.js → loader-circle-DtInM4Io.js} +1 -1
  68. package/dashboard/dist/assets/{maximize-2-CkwnHUJn.js → maximize-2-DxpsaYXp.js} +1 -1
  69. package/dashboard/dist/assets/{provider-display-BXBBedC-.js → provider-display-hkyvfEJF.js} +1 -1
  70. package/dashboard/dist/assets/{react-BXSctDQj.js → react-TbJoDSP4.js} +1 -1
  71. package/dashboard/dist/assets/{search-_qGma9tM.js → search-C7bnqB9b.js} +1 -1
  72. package/dashboard/dist/assets/{skills-api-DdmJjnJ9.js → skills-api-D7glOWg7.js} +1 -1
  73. package/dashboard/dist/assets/{store-owYCN530.js → store-CCVlzuqv.js} +1 -1
  74. package/dashboard/dist/assets/{terminal-Clzw-KEF.js → terminal-uQlsmzGi.js} +1 -1
  75. package/dashboard/dist/assets/{trash-2-C5Giwp_E.js → trash-2-B1yuX-oD.js} +1 -1
  76. package/dashboard/dist/assets/{use-community-stats-Dk8UjLEr.js → use-community-stats-0Fs5nY1G.js} +1 -1
  77. package/dashboard/dist/assets/{use-limits-display-prefs-CArM595Q.js → use-limits-display-prefs-CrftAn62.js} +1 -1
  78. package/dashboard/dist/assets/{use-native-settings-Cpd69kiF.js → use-native-settings-CVqB9eoO.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list--CzwhBTx.js → use-ordered-list-DAdPjLb5.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-DVGYs3-k.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-DUXO8cCg.js → use-session-efficiency-pref-Cc52HiK0.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-Cg3kuMj_.js → use-transform-D76wHhX0.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-DY9_mV28.js → use-usage-limits-B7Prb8rk.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-BIe86hMn.js → useCurrency-DWoM_A3s.js} +1 -1
  85. package/dashboard/dist/assets/{useScrollLock-7-hejkNC.js → useScrollLock-Bp6ddiQo.js} +1 -1
  86. package/dashboard/dist/assets/{useTokenFormat-DEtLbRgQ.js → useTokenFormat-DZOYz5HT.js} +1 -1
  87. package/dashboard/dist/assets/{zap-DUxY1d-O.js → zap-DyeAeO8l.js} +1 -1
  88. package/dashboard/dist/index.html +1 -1
  89. package/dashboard/dist/ip-check.html +1 -1
  90. package/dashboard/dist/leaderboard.html +1 -1
  91. package/dashboard/dist/share.html +1 -1
  92. package/package.json +1 -1
  93. package/src/commands/sync.js +11 -11
  94. package/src/lib/codex-rollout-parser.js +8 -52
  95. package/src/lib/jsonl-lines.js +104 -0
  96. package/src/lib/pricing/seed-snapshot.json +1 -1
  97. package/src/lib/rollout.js +89 -18
  98. package/src/lib/wsl-probe.js +36 -0
  99. package/dashboard/dist/assets/DialogDescription-D6zxtOM5.js +0 -1
  100. package/dashboard/dist/assets/LandingPage-3JQe-jOZ.js +0 -2
  101. package/dashboard/dist/assets/SettingsPage-CyvSWvDP.js +0 -1
  102. package/dashboard/dist/assets/use-reduced-motion-CcILnpU1.js +0 -1
@@ -6,6 +6,7 @@ const readline = require("node:readline");
6
6
 
7
7
  const crypto = require("node:crypto");
8
8
  const { ensureDir, writeJson, chmod600IfPossible } = require("./fs");
9
+ const { physicalJsonlRecords } = require("./jsonl-lines");
9
10
  const { readSqliteJsonRows, readSqliteJsonRowsAsync } = require("./sqlite-reader");
10
11
  const wsl = require("./wsl-probe");
11
12
  const { resolveInstallPaths } = require("./install-resolver");
@@ -180,7 +181,11 @@ async function parseRolloutIncremental({
180
181
  source,
181
182
  publicRepoResolver,
182
183
  diagnostics,
184
+ invalidRecordPolicy = "skip",
183
185
  }) {
186
+ if (invalidRecordPolicy !== "skip" && invalidRecordPolicy !== "throw") {
187
+ throw new TypeError(`unsupported invalidRecordPolicy: ${invalidRecordPolicy}`);
188
+ }
184
189
  await ensureDir(path.dirname(queuePath));
185
190
  let filesProcessed = 0;
186
191
  let eventsAggregated = 0;
@@ -446,6 +451,7 @@ async function parseRolloutIncremental({
446
451
  publicRepoCache,
447
452
  publicRepoResolver,
448
453
  projectContext,
454
+ invalidRecordPolicy,
449
455
  })
450
456
  : await parseRolloutFile({
451
457
  filePath,
@@ -467,6 +473,7 @@ async function parseRolloutIncremental({
467
473
  projectContext,
468
474
  seenCodexEvents: codexEventTracker,
469
475
  sessionId: codexSessionIdFromPath(filePath),
476
+ invalidRecordPolicy,
470
477
  });
471
478
 
472
479
  const nextCursor = {
@@ -760,6 +767,11 @@ async function parseClaudeIncremental({
760
767
  const projectTouchedBuckets = projectEnabled ? new Set() : null;
761
768
  const projectMetaCache = projectEnabled ? new Map() : null;
762
769
  const publicRepoCache = projectEnabled ? new Map() : null;
770
+ // Sessions cluster into few repos, so the per-file freshness checks below
771
+ // keep stat-ing the same handful of .git/config paths. Share one stat per
772
+ // config per run, as the Codex path does — it matters most for WSL installs,
773
+ // where every stat crosses the \\wsl$ bridge (#374).
774
+ const projectFreshnessCache = projectEnabled ? new Map() : null;
763
775
  const touchedBuckets = new Set();
764
776
  // Persist seenMessageHashes across syncs to prevent cross-file duplicates
765
777
  // (e.g. subagent file created after main session was already parsed).
@@ -878,7 +890,9 @@ async function parseClaudeIncremental({
878
890
  const cachedContextAbsent = cachedProjectFileContext?.absent === true;
879
891
  const projectContextFresh =
880
892
  projectEnabled && cachedProjectFileContext && !cachedContextAbsent
881
- ? await isProjectFileContextFresh(cachedProjectFileContext)
893
+ ? await isProjectFileContextFresh(cachedProjectFileContext, {
894
+ freshnessCache: projectFreshnessCache,
895
+ })
882
896
  : false;
883
897
  const projectKnownAbsent = cachedCwd === null || cachedContextAbsent;
884
898
  const projectSettled =
@@ -909,7 +923,7 @@ async function parseClaudeIncremental({
909
923
  }
910
924
  if (nextCwd) {
911
925
  const projectContext = await resolveProjectContextForPath({
912
- startDir: nextCwd,
926
+ startDir: wsl.mapWslCwdToUnc(nextCwd, filePath),
913
927
  projectMetaCache,
914
928
  publicRepoCache,
915
929
  publicRepoResolver,
@@ -1945,6 +1959,7 @@ async function parseRolloutFile({
1945
1959
  projectContext,
1946
1960
  seenCodexEvents,
1947
1961
  sessionId,
1962
+ invalidRecordPolicy,
1948
1963
  }) {
1949
1964
  const st = fileStat || (await fs.stat(filePath));
1950
1965
  const endOffset = st.size;
@@ -1961,8 +1976,10 @@ async function parseRolloutFile({
1961
1976
  };
1962
1977
  }
1963
1978
 
1964
- const stream = fssync.createReadStream(filePath, { encoding: "utf8", start: startOffset });
1965
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
1979
+ const stream = fssync.createReadStream(filePath, {
1980
+ start: startOffset,
1981
+ end: endOffset - 1,
1982
+ });
1966
1983
 
1967
1984
  let model = typeof lastModel === "string" ? lastModel : null;
1968
1985
  const usageDeltaState = createUsageDeltaState({
@@ -1987,8 +2004,19 @@ async function parseRolloutFile({
1987
2004
  let currentProjectRef = projectRef || null;
1988
2005
  let currentProjectKey = projectKey || null;
1989
2006
  let eventsAggregated = 0;
2007
+ let scannedEndOffset = startOffset;
2008
+ let committedEndOffset = startOffset;
2009
+
2010
+ const invalidUtf8 = invalidRecordPolicy === "throw" ? "throw" : "record";
2011
+ for await (const record of physicalJsonlRecords(stream, { invalidUtf8 })) {
2012
+ scannedEndOffset += record.physicalBytes;
2013
+ if (record.terminated) committedEndOffset = scannedEndOffset;
2014
+ if (!record.utf8Valid) {
2015
+ if (!record.terminated) break;
2016
+ continue;
2017
+ }
1990
2018
 
1991
- for await (const line of rl) {
2019
+ const { line } = record;
1992
2020
  if (!line) continue;
1993
2021
  const maybeTokenCount = line.includes('"token_count"');
1994
2022
  const maybeTurnContext =
@@ -1998,14 +2026,28 @@ async function parseRolloutFile({
1998
2026
  line.includes('"cwd"') ||
1999
2027
  line.includes('"current_date"') ||
2000
2028
  line.includes('"forked_from_id"'));
2001
- if (!maybeTokenCount && !maybeTurnContext) continue;
2029
+ if (!maybeTokenCount && !maybeTurnContext) {
2030
+ if (invalidRecordPolicy === "throw" || !record.terminated) {
2031
+ try {
2032
+ JSON.parse(line);
2033
+ committedEndOffset = scannedEndOffset;
2034
+ } catch (error) {
2035
+ if (invalidRecordPolicy === "throw") throw error;
2036
+ break;
2037
+ }
2038
+ }
2039
+ continue;
2040
+ }
2002
2041
 
2003
2042
  let obj;
2004
2043
  try {
2005
2044
  obj = JSON.parse(line);
2006
- } catch (_e) {
2045
+ } catch (error) {
2046
+ if (invalidRecordPolicy === "throw") throw error;
2047
+ if (!record.terminated) break;
2007
2048
  continue;
2008
2049
  }
2050
+ if (!record.terminated) committedEndOffset = scannedEndOffset;
2009
2051
 
2010
2052
  if (
2011
2053
  (obj?.type === "turn_context" || obj?.type === "session_meta") &&
@@ -2025,7 +2067,7 @@ async function parseRolloutFile({
2025
2067
  const nextCwd = obj.payload.cwd.trim();
2026
2068
  if (nextCwd && nextCwd !== currentCwd) {
2027
2069
  const context = await resolveProjectContextForPath({
2028
- startDir: nextCwd,
2070
+ startDir: wsl.mapWslCwdToUnc(nextCwd, filePath),
2029
2071
  projectMetaCache,
2030
2072
  publicRepoCache,
2031
2073
  publicRepoResolver,
@@ -2149,7 +2191,7 @@ async function parseRolloutFile({
2149
2191
  }
2150
2192
 
2151
2193
  return {
2152
- endOffset,
2194
+ endOffset: committedEndOffset,
2153
2195
  lastTotal: latestTotal,
2154
2196
  tokenUsageBaselines: snapshotUsageBaselines(usageDeltaState),
2155
2197
  lastModel: model,
@@ -2169,6 +2211,7 @@ async function scanRolloutProjectFileContexts({
2169
2211
  publicRepoCache,
2170
2212
  publicRepoResolver,
2171
2213
  projectContext,
2214
+ invalidRecordPolicy,
2172
2215
  }) {
2173
2216
  const st = fileStat || (await fs.stat(filePath));
2174
2217
  const endOffset = st.size;
@@ -2185,11 +2228,21 @@ async function scanRolloutProjectFileContexts({
2185
2228
  };
2186
2229
  }
2187
2230
 
2188
- const stream = fssync.createReadStream(filePath, { encoding: "utf8", start: 0 });
2189
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
2231
+ const stream = fssync.createReadStream(filePath, { start: 0, end: endOffset - 1 });
2190
2232
  let currentCwd = null;
2233
+ let scannedEndOffset = 0;
2234
+ let committedEndOffset = 0;
2235
+
2236
+ const invalidUtf8 = invalidRecordPolicy === "throw" ? "throw" : "record";
2237
+ for await (const record of physicalJsonlRecords(stream, { invalidUtf8 })) {
2238
+ scannedEndOffset += record.physicalBytes;
2239
+ if (record.terminated) committedEndOffset = scannedEndOffset;
2240
+ if (!record.utf8Valid) {
2241
+ if (!record.terminated) break;
2242
+ continue;
2243
+ }
2191
2244
 
2192
- for await (const line of rl) {
2245
+ const { line } = record;
2193
2246
  if (
2194
2247
  !line ||
2195
2248
  !(
@@ -2198,15 +2251,27 @@ async function scanRolloutProjectFileContexts({
2198
2251
  ) ||
2199
2252
  !line.includes('"cwd"')
2200
2253
  ) {
2254
+ if (line && (invalidRecordPolicy === "throw" || !record.terminated)) {
2255
+ try {
2256
+ JSON.parse(line);
2257
+ committedEndOffset = scannedEndOffset;
2258
+ } catch (error) {
2259
+ if (invalidRecordPolicy === "throw") throw error;
2260
+ break;
2261
+ }
2262
+ }
2201
2263
  continue;
2202
2264
  }
2203
2265
 
2204
2266
  let obj;
2205
2267
  try {
2206
2268
  obj = JSON.parse(line);
2207
- } catch (_e) {
2269
+ } catch (error) {
2270
+ if (invalidRecordPolicy === "throw") throw error;
2271
+ if (!record.terminated) break;
2208
2272
  continue;
2209
2273
  }
2274
+ if (!record.terminated) committedEndOffset = scannedEndOffset;
2210
2275
  if (
2211
2276
  (obj?.type !== "turn_context" && obj?.type !== "session_meta") ||
2212
2277
  !obj?.payload ||
@@ -2219,7 +2284,7 @@ async function scanRolloutProjectFileContexts({
2219
2284
  const nextCwd = obj.payload.cwd.trim();
2220
2285
  if (!nextCwd || nextCwd === currentCwd) continue;
2221
2286
  const context = await resolveProjectContextForPath({
2222
- startDir: nextCwd,
2287
+ startDir: wsl.mapWslCwdToUnc(nextCwd, filePath),
2223
2288
  projectMetaCache,
2224
2289
  publicRepoCache,
2225
2290
  publicRepoResolver,
@@ -2230,7 +2295,7 @@ async function scanRolloutProjectFileContexts({
2230
2295
  }
2231
2296
 
2232
2297
  return {
2233
- endOffset,
2298
+ endOffset: committedEndOffset,
2234
2299
  lastTotal,
2235
2300
  tokenUsageBaselines,
2236
2301
  lastModel,
@@ -4129,6 +4194,7 @@ function readZcodeDbMessages(dbPath, sqliteOptions = {}) {
4129
4194
 
4130
4195
  async function parseOpencodeDbIncremental({
4131
4196
  dbMessages,
4197
+ dbPath,
4132
4198
  cursors,
4133
4199
  queuePath,
4134
4200
  projectQueuePath,
@@ -4225,6 +4291,7 @@ async function parseOpencodeDbIncremental({
4225
4291
  if (projectEnabled) {
4226
4292
  const projectContext = await resolveProjectContextForDb({
4227
4293
  msg,
4294
+ dbPath,
4228
4295
  projectMetaCache,
4229
4296
  publicRepoCache,
4230
4297
  publicRepoResolver,
@@ -4444,6 +4511,7 @@ function qoderProjectPath(row) {
4444
4511
 
4445
4512
  async function parseQoderDbIncremental({
4446
4513
  dbMessages,
4514
+ dbPath,
4447
4515
  cursors,
4448
4516
  queuePath,
4449
4517
  projectQueuePath,
@@ -4515,7 +4583,7 @@ async function parseQoderDbIncremental({
4515
4583
  const projectPath = qoderProjectPath(row);
4516
4584
  if (projectPath) {
4517
4585
  const context = await resolveProjectContextForPath({
4518
- startDir: projectPath,
4586
+ startDir: wsl.mapWslCwdToUnc(projectPath, dbPath),
4519
4587
  projectMetaCache,
4520
4588
  publicRepoCache,
4521
4589
  publicRepoResolver,
@@ -4975,6 +5043,7 @@ async function parseClaudeScienceIncremental({
4975
5043
  // Resolve project context from DB message (no file path available)
4976
5044
  async function resolveProjectContextForDb({
4977
5045
  msg,
5046
+ dbPath,
4978
5047
  projectMetaCache,
4979
5048
  publicRepoCache,
4980
5049
  publicRepoResolver,
@@ -4983,7 +5052,9 @@ async function resolveProjectContextForDb({
4983
5052
  const cwd = msg?.path?.cwd;
4984
5053
  if (!cwd || typeof cwd !== "string") return null;
4985
5054
  return resolveProjectContextForPath({
4986
- startDir: cwd,
5055
+ // The DB itself is read over the distro's UNC bridge on Windows, so its
5056
+ // path carries the prefix the recorded POSIX cwd needs (#374).
5057
+ startDir: wsl.mapWslCwdToUnc(cwd, dbPath),
4987
5058
  projectMetaCache,
4988
5059
  publicRepoCache,
4989
5060
  publicRepoResolver,
@@ -11141,7 +11212,7 @@ async function parseOmpIncremental({
11141
11212
  const cwd = await resolveOmpFileCwd(filePath);
11142
11213
  const projectContext = cwd
11143
11214
  ? await resolveProjectContextForPath({
11144
- startDir: cwd,
11215
+ startDir: wsl.mapWslCwdToUnc(cwd, filePath),
11145
11216
  projectMetaCache,
11146
11217
  publicRepoCache,
11147
11218
  publicRepoResolver,
@@ -164,6 +164,40 @@ function isUncPath(p) {
164
164
  return typeof p === "string" && (p.startsWith("\\\\") || p.startsWith("//"));
165
165
  }
166
166
 
167
+ const WSL_UNC_ROOT_RE = /^[\\/]{2}(wsl\$|wsl\.localhost)[\\/]([^\\/]+)[\\/]/i;
168
+ const WINDOWS_DRIVE_PREFIX_RE = /^\/[A-Za-z]:[\\/]/;
169
+
170
+ // `\\wsl$\Ubuntu-24.04\home\u\...` -> `\\wsl$\Ubuntu-24.04\`. Returns null for
171
+ // native and non-WSL UNC paths.
172
+ function wslUncRoot(p) {
173
+ if (typeof p !== "string") return null;
174
+ const match = WSL_UNC_ROOT_RE.exec(p);
175
+ return match ? `\\\\${match[1]}\\${match[2]}\\` : null;
176
+ }
177
+
178
+ // A WSL session records its cwd as a POSIX path (`/home/u/dev/app`) while the
179
+ // transcript itself is read over the distro's UNC bridge. Probing that raw
180
+ // POSIX path from Windows resolves it against the current drive
181
+ // (`C:\home\u\dev\app`), which never exists — so project attribution finds no
182
+ // `.git`, and every WSL session ends up with a null projectKey (#374).
183
+ // Re-anchor the cwd onto the UNC prefix the transcript came from.
184
+ //
185
+ // The prefix is taken from the transcript path rather than re-probed, which
186
+ // also picks whichever of `\\wsl$` / `\\wsl.localhost` actually resolves on
187
+ // this machine — they are not interchangeable everywhere. Anything that isn't
188
+ // a POSIX cwd from a WSL-hosted file is returned untouched.
189
+ function mapWslCwdToUnc(cwd, transcriptPath) {
190
+ if (typeof cwd !== "string" || !cwd.startsWith("/")) return cwd;
191
+ // `new URL("file:///C:/dev/app").pathname` is "/C:/dev/app" — a leading
192
+ // slash, but a Windows path rather than a POSIX one. Qoder feeds exactly
193
+ // that shape in via `project_uri`; re-anchoring it would invent
194
+ // `\\wsl$\Distro\C:\dev\app`.
195
+ if (WINDOWS_DRIVE_PREFIX_RE.test(cwd)) return cwd;
196
+ const root = wslUncRoot(transcriptPath);
197
+ if (!root) return cwd;
198
+ return root + cwd.slice(1).replaceAll("/", "\\");
199
+ }
200
+
167
201
  function snapshotSqliteDb(dbPath) {
168
202
  const tmpRoot = fssync.mkdtempSync(
169
203
  path.join(require("node:os").tmpdir(), "tokentracker-wsl-snap-"),
@@ -191,6 +225,8 @@ module.exports = {
191
225
  resetWslProbeCache,
192
226
  discoverWslHome,
193
227
  isUncPath,
228
+ wslUncRoot,
229
+ mapWslCwdToUnc,
194
230
  snapshotSqliteDb,
195
231
  getWslMode,
196
232
  isInvalidWslMode,
@@ -1 +0,0 @@
1
- import{r as n,A as a}from"./__vite-browser-external-SU8sCTLB.js";import{u as p}from"./DialogTitle-gRzunAqv.js";import{u as c}from"./useScrollLock-7-hejkNC.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};