tokentracker-cli 1.1.9 → 1.1.10

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/dashboard/dist/assets/{AchievementBadge-BeNHapqp.js → AchievementBadge-CH97d6cg.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-vNH31cFv.js → AchievementsPage-Dwvj09sF.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-ChGSmCUP.js → AchievementsSection-D6HHK1f-.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-CN0tcpEE.js → BadgeDetailModal-Bpr6rY9l.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-CKKYEJt-.js → BotAnimated-srF64s63.js} +1 -1
  6. package/dashboard/dist/assets/{Card-Dx3Pj7SE.js → Card-38v0-L6e.js} +1 -1
  7. package/dashboard/dist/assets/{ClawdAnimated-ChJEskg4.js → ClawdAnimated-C9a1u9lq.js} +2 -2
  8. package/dashboard/dist/assets/{CommandPalette-D4-sgyxP.js → CommandPalette-Bj0e0ye0.js} +1 -1
  9. package/dashboard/dist/assets/{CommunityStatsModal-NUDCSEve.js → CommunityStatsModal-IoQLJCe2.js} +1 -1
  10. package/dashboard/dist/assets/{ConfirmModal-q4EXjOda.js → ConfirmModal-B7uK0Wjr.js} +1 -1
  11. package/dashboard/dist/assets/{Controls-OjbHepPz.js → Controls-D5XuFAlI.js} +1 -1
  12. package/dashboard/dist/assets/{DashboardPage-F3swcQnu.js → DashboardPage-Ddb8k4DR.js} +1 -1
  13. package/dashboard/dist/assets/DevicePage-DE-NSyZg.js +1 -0
  14. package/dashboard/dist/assets/{DialogClose-DSp5s5nL.js → DialogClose-DB6ZRDR3.js} +1 -1
  15. package/dashboard/dist/assets/DialogDescription-CaRzgdvP.js +1 -0
  16. package/dashboard/dist/assets/{DialogTitle-M5ojVj4-.js → DialogTitle-CgwmTJf3.js} +1 -1
  17. package/dashboard/dist/assets/{FadeIn-CWDRIc_C.js → FadeIn-CMjPtMSB.js} +1 -1
  18. package/dashboard/dist/assets/{HeaderGithubStar-DR4Px_eh.js → HeaderGithubStar-D5cSnbcg.js} +1 -1
  19. package/dashboard/dist/assets/{HoverTooltip-CsfynzUO.js → HoverTooltip-CFV8yLxb.js} +1 -1
  20. package/dashboard/dist/assets/{IpCheckPage-C8Y1OFOn.js → IpCheckPage-Ctu-GWJQ.js} +1 -1
  21. package/dashboard/dist/assets/{LandingPage-BnmP1NUm.js → LandingPage-PAIF034o.js} +2 -2
  22. package/dashboard/dist/assets/{LeaderboardAvatar-DprHPy4v.js → LeaderboardAvatar-D3CsY-nP.js} +1 -1
  23. package/dashboard/dist/assets/{LeaderboardPage-BI-ryUMj.js → LeaderboardPage-qE8DD-Ia.js} +2 -2
  24. package/dashboard/dist/assets/{LeaderboardProfileModal-r-wgLtCo.js → LeaderboardProfileModal-BY3uaIdF.js} +3 -3
  25. package/dashboard/dist/assets/{LeaderboardProfilePage-qBvdPj6z.js → LeaderboardProfilePage-CwX77U5Z.js} +1 -1
  26. package/dashboard/dist/assets/{LimitsPage--ObDuhSD.js → LimitsPage-BlJgvFay.js} +1 -1
  27. package/dashboard/dist/assets/{LocalOnlyNotice-Cvg81MIm.js → LocalOnlyNotice-CLFKSu0V.js} +1 -1
  28. package/dashboard/dist/assets/{LoginCard-T1vufQym.js → LoginCard-Bdp2gihl.js} +1 -1
  29. package/dashboard/dist/assets/{LoginModal-CElUuzhg.js → LoginModal-o_t6uvYP.js} +1 -1
  30. package/dashboard/dist/assets/{LoginPage-Decz8Qyn.js → LoginPage-Cc3ZHUU8.js} +1 -1
  31. package/dashboard/dist/assets/{PetPage-CitsUTsW.js → PetPage-DFaWnKsI.js} +1 -1
  32. package/dashboard/dist/assets/{PopoverPopup-wx5xna8c.js → PopoverPopup-CGJBBnIf.js} +1 -1
  33. package/dashboard/dist/assets/{ProviderIcon-DeCQ9ytY.js → ProviderIcon-1OG4Kbkl.js} +1 -1
  34. package/dashboard/dist/assets/{ResetPasswordPage-BklHQ_i8.js → ResetPasswordPage-DbfS1gfH.js} +1 -1
  35. package/dashboard/dist/assets/{SegmentedControl-DqbXKZfk.js → SegmentedControl-DPKl-28R.js} +1 -1
  36. package/dashboard/dist/assets/{Select-BPhSzg5T.js → Select-BTA6mSut.js} +1 -1
  37. package/dashboard/dist/assets/{SelectItemText-BEqonLIf.js → SelectItemText-CX5Br8hd.js} +1 -1
  38. package/dashboard/dist/assets/{ServiceStatusPage-DnAv3J6d.js → ServiceStatusPage-CaTOLkT7.js} +1 -1
  39. package/dashboard/dist/assets/{SessionsPage-Fr5rFC4h.js → SessionsPage-Ixp6Fseo.js} +1 -1
  40. package/dashboard/dist/assets/{SettingsPage-DYB-pyw2.js → SettingsPage-BmeQse01.js} +2 -2
  41. package/dashboard/dist/assets/{SkillsPage-B9aU8WEc.js → SkillsPage-BUUfMFpU.js} +1 -1
  42. package/dashboard/dist/assets/{TokenGalaxy-ybV8JSOT.js → TokenGalaxy-DnfKnQhr.js} +1 -1
  43. package/dashboard/dist/assets/{ToolbarRootContext-DTAiJvnu.js → ToolbarRootContext-D0wMtL6Z.js} +1 -1
  44. package/dashboard/dist/assets/{TrendMonitor-BCpOG6Vh.js → TrendMonitor-D3WafvUS.js} +1 -1
  45. package/dashboard/dist/assets/{WidgetsPage-Ypvi7zXN.js → WidgetsPage-Bfod5aSH.js} +1 -1
  46. package/dashboard/dist/assets/{WrappedPage-ZetHSH8Z.js → WrappedPage-fYftPA8s.js} +1 -1
  47. package/dashboard/dist/assets/{__vite-browser-external-C_FI6biS.js → __vite-browser-external-B98Yok9C.js} +2 -2
  48. package/dashboard/dist/assets/{agent-logos-BP-j-Uhx.js → agent-logos-C0O8Lnmv.js} +1 -1
  49. package/dashboard/dist/assets/{arrow-up-right-BDfZ3gqx.js → arrow-up-right-Bs0RpChC.js} +1 -1
  50. package/dashboard/dist/assets/{check-DMSqoEnk.js → check-Dj5Wi1I7.js} +1 -1
  51. package/dashboard/dist/assets/{composite-BdUpN78Q.js → composite-BeHO1nUS.js} +1 -1
  52. package/dashboard/dist/assets/{copy-CSp3uU1K.js → copy-Dgp1NqhD.js} +1 -1
  53. package/dashboard/dist/assets/{download-5KK5dq2E.js → download-D1JugY5p.js} +1 -1
  54. package/dashboard/dist/assets/{external-link-CetdvgSB.js → external-link-ow0tAluE.js} +1 -1
  55. package/dashboard/dist/assets/{flame-CkGhWtYT.js → flame-Obus_5g4.js} +1 -1
  56. package/dashboard/dist/assets/{format-tokens-CnlGC8B4.js → format-tokens-yToZ6yvw.js} +1 -1
  57. package/dashboard/dist/assets/{icons-BwUwzIE1.js → icons-Da1IxY6v.js} +1 -1
  58. package/dashboard/dist/assets/{index-DQV-HPM-.js → index-0hD-aFe2.js} +1 -1
  59. package/dashboard/dist/assets/{index-BLiO8Wso.js → index-BtmxTdj8.js} +1 -1
  60. package/dashboard/dist/assets/{index-D_-EkG8M.js → index-DduY8rpi.js} +1 -1
  61. package/dashboard/dist/assets/{info-KSAnlMrp.js → info-CQjQ1whr.js} +1 -1
  62. package/dashboard/dist/assets/{link-2-BGXe__2Q.js → link-2-BGYfWbPb.js} +1 -1
  63. package/dashboard/dist/assets/{loader-circle-D7ZxwMME.js → loader-circle-WArW9cmn.js} +1 -1
  64. package/dashboard/dist/assets/{maximize-2-D80lpJ50.js → maximize-2-Bhde1l0n.js} +1 -1
  65. package/dashboard/dist/assets/{modifiers.esm-DStyqxn4.js → modifiers.esm-BRxUZvoy.js} +1 -1
  66. package/dashboard/dist/assets/{monitor-smartphone-D282iI2t.js → monitor-smartphone-2ZT8ACJS.js} +1 -1
  67. package/dashboard/dist/assets/{plus-Cn55gGq1.js → plus-C_cg6gJE.js} +1 -1
  68. package/dashboard/dist/assets/{provider-display-DvH8Y5lo.js → provider-display-CM8dzzcI.js} +1 -1
  69. package/dashboard/dist/assets/{react-B-73Uvu6.js → react-C3vDx5Sr.js} +1 -1
  70. package/dashboard/dist/assets/{search-DpOq2L2S.js → search-Vd9we7KU.js} +1 -1
  71. package/dashboard/dist/assets/{skills-api-CCIV2ZIq.js → skills-api-CHxubvN1.js} +1 -1
  72. package/dashboard/dist/assets/{sortable.esm-BjIamCRg.js → sortable.esm-rLR8Wj9r.js} +1 -1
  73. package/dashboard/dist/assets/{store-CmFrvUue.js → store-rdi5mnme.js} +1 -1
  74. package/dashboard/dist/assets/{terminal-CqeGAXgs.js → terminal-xEPZhi6v.js} +1 -1
  75. package/dashboard/dist/assets/{trash-CMLW5F5L.js → trash-Cad8nQc4.js} +1 -1
  76. package/dashboard/dist/assets/{use-community-stats-BKS7Gd-z.js → use-community-stats-SMq01IVW.js} +1 -1
  77. package/dashboard/dist/assets/{use-latest-request-guard-I80QDt_F.js → use-latest-request-guard-CSQ3Ypu3.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-CPLY7mBi.js → use-limits-display-prefs-DaaT6TL9.js} +1 -1
  79. package/dashboard/dist/assets/{use-ordered-list-wlw260-2.js → use-ordered-list-g58JllvT.js} +1 -1
  80. package/dashboard/dist/assets/use-reduced-motion-Dgdv5SSP.js +1 -0
  81. package/dashboard/dist/assets/{use-session-efficiency-pref-BdVVDyew.js → use-session-efficiency-pref-DyATLZ-L.js} +1 -1
  82. package/dashboard/dist/assets/{use-transform-Daiv5LsX.js → use-transform-XYEzPgA-.js} +1 -1
  83. package/dashboard/dist/assets/{use-usage-limits-CK1zK4GT.js → use-usage-limits-DL260plD.js} +1 -1
  84. package/dashboard/dist/assets/{useCurrency-yxDqAL1Q.js → useCurrency-4xq0xUFx.js} +1 -1
  85. package/dashboard/dist/assets/{useTokenFormat-BTYDgiyJ.js → useTokenFormat-B6fHArH6.js} +1 -1
  86. package/dashboard/dist/assets/{zap-CfNiBLFJ.js → zap-InLQedtD.js} +1 -1
  87. package/dashboard/dist/index.html +1 -1
  88. package/dashboard/dist/ip-check.html +1 -1
  89. package/dashboard/dist/leaderboard.html +1 -1
  90. package/dashboard/dist/share.html +1 -1
  91. package/package.json +1 -1
  92. package/src/commands/sync.js +14 -1
  93. package/src/lib/cloud-account.js +23 -12
  94. package/src/lib/cloud-device-token.js +74 -0
  95. package/src/lib/local-api.js +87 -35
  96. package/src/lib/pricing/seed-snapshot.json +1 -1
  97. package/src/lib/upload-throttle.js +4 -1
  98. package/dashboard/dist/assets/DevicePage-CghTfQLN.js +0 -1
  99. package/dashboard/dist/assets/DialogDescription-Ln30UAVH.js +0 -1
  100. package/dashboard/dist/assets/use-reduced-motion-ClrSsBXW.js +0 -1
@@ -13,6 +13,7 @@ const {
13
13
  const { accountSlugFor, fetchAccountUsage, mintAccessToken, invalidateCloudAccountPayloadCache, getCloudAccountSessionGeneration } = require("./cloud-account");
14
14
  const { getOrCreateMachineId, computeStableMachineId } = require("./machine-id");
15
15
  const { functionUrlFor, fetchFunctionResponse } = require("./function-url");
16
+ const { createCloudDeviceTokenStore } = require("./cloud-device-token");
16
17
 
17
18
  const SYNC_TIMEOUT_MS = 120_000;
18
19
  // A failed account-view request should not stall every dashboard refresh
@@ -896,7 +897,14 @@ function runSyncCommand(extraEnv = {}, opts = {}) {
896
897
  if (opts.drain === true) args.push("--drain");
897
898
  if (opts.waitForLock === true) args.push("--wait-for-lock");
898
899
  const child = spawn(process.execPath, args, {
899
- env: { ...process.env, ...extraEnv },
900
+ env: {
901
+ ...process.env, ...extraEnv,
902
+ ...(extraEnv.TOKENTRACKER_DEVICE_TOKEN ? {
903
+ TOKENTRACKER_INSFORGE_BASE_URL: extraEnv.TOKENTRACKER_INSFORGE_BASE_URL || resolveRuntimeConfig().baseUrl,
904
+ TOKENTRACKER_INSFORGE_ANON_KEY: resolveRuntimeConfig().anonKey,
905
+ } : {}),
906
+ TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN: extraEnv.TOKENTRACKER_DEVICE_TOKEN || "",
907
+ },
900
908
  stdio: ["ignore", "pipe", "pipe"],
901
909
  });
902
910
  let stdout = "";
@@ -1144,19 +1152,29 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1144
1152
  const localAuthToken = crypto.randomBytes(24).toString("hex");
1145
1153
  const trackerDataDir = path.join(os.homedir(), ".tokentracker", "tracker");
1146
1154
  const cookiePath = path.join(trackerDataDir, "relay-cookies.json");
1155
+ const localSyncDeviceTokenStore = createCloudDeviceTokenStore(path.join(trackerDataDir, "cloud-device-token.json"));
1147
1156
  const localSyncDeviceTokenCache = new Map();
1148
1157
  const localSyncDeviceTokenInflight = new Map();
1149
1158
  let localSyncDeviceTokenUserId = "";
1150
1159
  function clearLocalSyncDeviceTokenCache() {
1151
1160
  localSyncDeviceTokenCache.clear();
1152
1161
  localSyncDeviceTokenInflight.clear();
1162
+ localSyncDeviceTokenStore.clear();
1153
1163
  localSyncDeviceTokenUserId = "";
1154
1164
  }
1155
- function currentCloudUploadFailure(syncStartedAt, { backoff = false } = {}) {
1165
+ function invalidateLocalSyncDeviceToken(token) {
1166
+ if (!token) return;
1167
+ for (const [key, value] of localSyncDeviceTokenCache) {
1168
+ if (value === token) localSyncDeviceTokenCache.delete(key);
1169
+ }
1170
+ localSyncDeviceTokenStore.invalidate(token);
1171
+ }
1172
+ function currentCloudUploadFailure(syncStartedAt, { backoff = false, deviceToken, attemptId } = {}) {
1156
1173
  try {
1157
1174
  const state = JSON.parse(fs.readFileSync(path.join(path.dirname(qp), "upload.throttle.json"), "utf8"));
1158
- if (Date.parse(state.lastErrorAt || "") >= syncStartedAt && Number(state.lastErrorStatus) > 0) {
1159
- if (state.lastErrorCode === "CLOUD_DEVICE_TOKEN_REJECTED") clearLocalSyncDeviceTokenCache();
1175
+ if (attemptId && state.lastErrorAttemptId === attemptId &&
1176
+ Date.parse(state.lastErrorAt || "") >= syncStartedAt && Number(state.lastErrorStatus) > 0) {
1177
+ if (state.lastErrorCode === "CLOUD_DEVICE_TOKEN_REJECTED") invalidateLocalSyncDeviceToken(deviceToken);
1160
1178
  return { code: state.lastErrorCode || "CLOUD_UPLOAD_FAILED", status: Number(state.lastErrorStatus) };
1161
1179
  }
1162
1180
  if (backoff && Number(state.backoffUntilMs || 0) > syncStartedAt) {
@@ -1170,7 +1188,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1170
1188
  // request for one account must not clear another account's failure), and
1171
1189
  // suppress the duplicate fan-out while the first cloud probe is pending.
1172
1190
  const accountViewFailures = new Map();
1173
- const accountViewInFlight = new Set();
1191
+ const accountViewInFlight = new Map();
1174
1192
  let accountUploadState = readAccountUploadState();
1175
1193
 
1176
1194
  function readAccountUploadState() {
@@ -1417,6 +1435,10 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1417
1435
  anonKey: runtime.anonKey,
1418
1436
  refreshToken,
1419
1437
  timeoutMs: runtime.httpTimeoutMs,
1438
+ throwOnFailure: true,
1439
+ }).catch((error) => {
1440
+ if (error.status === 401 && sessionAtStart === getCloudAccountSessionGeneration()) clearLocalSyncDeviceTokenCache();
1441
+ throw error;
1420
1442
  });
1421
1443
  if (!minted?.accessToken || sessionAtStart !== getCloudAccountSessionGeneration()) return null;
1422
1444
  const rotatedRefreshToken =
@@ -1439,8 +1461,14 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1439
1461
  if (userId) localSyncDeviceTokenUserId = userId;
1440
1462
  const identity = userId || `refresh:${rotatedRefreshToken || refreshToken}`;
1441
1463
  const cacheKey = localSyncDeviceTokenCacheKey(identity, machineId, baseUrl);
1442
- const cachedToken = localSyncDeviceTokenCache.get(cacheKey);
1443
- if (cachedToken) return cachedToken;
1464
+ const binding = { userId, baseUrl, machineId };
1465
+ const cachedToken = localSyncDeviceTokenCache.get(cacheKey) ||
1466
+ (userId ? localSyncDeviceTokenStore.get(binding) : null);
1467
+ if (cachedToken) {
1468
+ localSyncDeviceTokenCache.set(cacheKey, cachedToken);
1469
+ if (userId) localSyncDeviceTokenStore.set(binding, cachedToken);
1470
+ return cachedToken;
1471
+ }
1444
1472
  const inflightToken = localSyncDeviceTokenInflight.get(cacheKey);
1445
1473
  if (inflightToken?.sessionGeneration === sessionAtStart) return inflightToken.promise;
1446
1474
 
@@ -1482,13 +1510,19 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1482
1510
  });
1483
1511
  if (sessionAtStart !== getCloudAccountSessionGeneration() || !getCloudSyncPref() || cloudPrefAtStart !== cloudSyncPrefGeneration) return null;
1484
1512
  if (!res.ok) {
1485
- if (res.status === 401) clearLocalSyncDeviceTokenCache();
1513
+ if (res.status === 401 && sessionAtStart === getCloudAccountSessionGeneration()) {
1514
+ localSyncDeviceTokenCache.delete(cacheKey);
1515
+ localSyncDeviceTokenStore.clear(binding);
1516
+ }
1486
1517
  return null;
1487
1518
  }
1488
1519
  const data = await res.json().catch(() => null);
1489
1520
  const token = typeof data?.token === "string" ? data.token.trim() : "";
1490
1521
  if (sessionAtStart !== getCloudAccountSessionGeneration() || !getCloudSyncPref() || cloudPrefAtStart !== cloudSyncPrefGeneration) return null;
1491
- if (token) localSyncDeviceTokenCache.set(cacheKey, token);
1522
+ if (token) {
1523
+ localSyncDeviceTokenCache.set(cacheKey, token);
1524
+ if (userId) localSyncDeviceTokenStore.set(binding, token);
1525
+ }
1492
1526
  return token || null;
1493
1527
  })();
1494
1528
  localSyncDeviceTokenInflight.set(cacheKey, { promise: issuePromise, sessionGeneration: sessionAtStart });
@@ -1535,32 +1569,46 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1535
1569
  invalidateAfterAccountUpload();
1536
1570
  const runtime = resolveRuntimeConfig();
1537
1571
  const failureKey = `${runtime.baseUrl}\0${refreshToken}`;
1538
- const requestKey = `${failureKey}\0${usageSlug}\0${url.searchParams.toString()}`;
1572
+ const sessionAtStart = getCloudAccountSessionGeneration();
1573
+ const cloudPrefAtStart = cloudSyncPrefGeneration;
1574
+ const params = new URLSearchParams(url.searchParams);
1575
+ params.sort();
1576
+ const requestKey = `${failureKey}\0${sessionAtStart}\0${cloudPrefAtStart}\0${accountUploadState}\0${usageSlug}\0${params}`;
1539
1577
  if (url.searchParams.get("refresh") !== "1" && accountViewFailureUntil(failureKey) > Date.now()) {
1540
1578
  return "transient-backoff";
1541
1579
  }
1542
- // Six native dashboard requests normally arrive together. If the cloud is
1543
- // already being probed for this session, let the remaining requests use
1544
- // their local fallback instead of opening more timeout-bound sockets.
1545
- if (accountViewInFlight.has(requestKey)) {
1546
- return "transient-inflight";
1547
- }
1548
- accountViewInFlight.add(requestKey);
1549
1580
  const envTimeout = process.env.TOKENTRACKER_HTTP_TIMEOUT_MS
1550
1581
  ? parseInt(process.env.TOKENTRACKER_HTTP_TIMEOUT_MS, 10)
1551
1582
  : 0;
1552
1583
  const timeoutMs = envTimeout > 0 ? envTimeout : 4000;
1553
- const sessionAtStart = getCloudAccountSessionGeneration();
1554
- const cloudPrefAtStart = cloudSyncPrefGeneration;
1555
1584
  try {
1556
- const out = await fetchAccountUsage({
1557
- usageSlug,
1558
- searchParams: url.searchParams,
1559
- baseUrl: runtime.baseUrl || DEFAULT_BASE_URL,
1560
- anonKey: runtime.anonKey,
1561
- refreshToken,
1562
- timeoutMs,
1563
- });
1585
+ // An identical overlapping read shares the entire probe, including its
1586
+ // token mint. A healthy pending read must not look like a cloud outage
1587
+ // and trigger the native client's account recovery loop.
1588
+ let pending = accountViewInFlight.get(requestKey);
1589
+ if (!pending) {
1590
+ pending = fetchAccountUsage({
1591
+ usageSlug,
1592
+ searchParams: url.searchParams,
1593
+ baseUrl: runtime.baseUrl || DEFAULT_BASE_URL,
1594
+ anonKey: runtime.anonKey,
1595
+ refreshToken,
1596
+ timeoutMs,
1597
+ onSessionRefreshed: (session) => {
1598
+ if (sessionAtStart !== getCloudAccountSessionGeneration()) {
1599
+ throw Object.assign(new Error("Account session changed"), { code: "auth_session_changed" });
1600
+ }
1601
+ if (session.refreshToken) setRelayRefreshToken(session.refreshToken);
1602
+ if (session.csrfToken) setRelayCsrfToken(session.csrfToken);
1603
+ },
1604
+ });
1605
+ accountViewInFlight.set(requestKey, pending);
1606
+ const cleanup = () => {
1607
+ if (accountViewInFlight.get(requestKey) === pending) accountViewInFlight.delete(requestKey);
1608
+ };
1609
+ pending.then(cleanup, cleanup);
1610
+ }
1611
+ const out = await pending;
1564
1612
  // `null` here means the slug has no cloud equivalent, or the session
1565
1613
  // vanished mid-request — a routing/session fact, not an outage.
1566
1614
  if (!out) return ACCOUNT_FALLBACK_SIGNED_OUT;
@@ -1571,8 +1619,6 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1571
1619
  rememberAccountViewFailure(failureKey);
1572
1620
  return "transient-upstream";
1573
1621
  }
1574
- if (out.rotatedRefreshToken) setRelayRefreshToken(out.rotatedRefreshToken);
1575
- if (out.rotatedCsrfToken) setRelayCsrfToken(out.rotatedCsrfToken);
1576
1622
  if (!getCloudSyncPref()) return ACCOUNT_FALLBACK_CLOUD_SYNC_OFF;
1577
1623
  if (cloudPrefAtStart !== cloudSyncPrefGeneration) {
1578
1624
  json(res, { error: "Cloud sync preference changed", code: "CLOUD_SYNC_CHANGED" }, 409);
@@ -1587,8 +1633,15 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1587
1633
  res.end(JSON.stringify(out.data));
1588
1634
  return "served";
1589
1635
  } catch (e) {
1590
- if (e.code === "auth_session_changed") {
1591
- json(res, { error: "Account session changed", code: e.code }, 409);
1636
+ const currentSession = getCloudAccountSessionGeneration();
1637
+ if (e.code === "auth_session_changed" ||
1638
+ (sessionAtStart !== currentSession && e.invalidatedSessionGeneration !== currentSession)) {
1639
+ json(res, { error: "Account session changed", code: "auth_session_changed" }, 409);
1640
+ return "served";
1641
+ }
1642
+ if (!getCloudSyncPref()) return ACCOUNT_FALLBACK_CLOUD_SYNC_OFF;
1643
+ if (cloudPrefAtStart !== cloudSyncPrefGeneration) {
1644
+ json(res, { error: "Cloud sync preference changed", code: "CLOUD_SYNC_CHANGED" }, 409);
1592
1645
  return "served";
1593
1646
  }
1594
1647
  // Signed in + cloud sync on, but the cloud read failed (offline, token
@@ -1601,8 +1654,6 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
1601
1654
  }
1602
1655
  rememberAccountViewFailure(failureKey);
1603
1656
  return reason;
1604
- } finally {
1605
- accountViewInFlight.delete(requestKey);
1606
1657
  }
1607
1658
  }
1608
1659
 
@@ -2162,6 +2213,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
2162
2213
  }
2163
2214
  }
2164
2215
  const syncStartedAt = Date.now();
2216
+ extraEnv.TOKENTRACKER_LOCAL_SYNC_ATTEMPT_ID = crypto.randomUUID();
2165
2217
  const result = await runSyncCommand(extraEnv, {
2166
2218
  drain,
2167
2219
  auto,
@@ -2171,11 +2223,11 @@ function createLocalApiHandler({ queuePath, serverVersion = null }) {
2171
2223
  waitForLock:
2172
2224
  !drain && !background && Boolean(extraEnv.TOKENTRACKER_DEVICE_TOKEN),
2173
2225
  }).catch((error) => {
2174
- const failure = currentCloudUploadFailure(syncStartedAt, { backoff: auto && drain });
2226
+ const failure = currentCloudUploadFailure(syncStartedAt, { backoff: auto && drain, deviceToken: extraEnv.TOKENTRACKER_DEVICE_TOKEN, attemptId: extraEnv.TOKENTRACKER_LOCAL_SYNC_ATTEMPT_ID });
2175
2227
  if (failure) Object.assign(error, failure);
2176
2228
  throw error;
2177
2229
  });
2178
- const failure = currentCloudUploadFailure(syncStartedAt);
2230
+ const failure = currentCloudUploadFailure(syncStartedAt, { deviceToken: extraEnv.TOKENTRACKER_DEVICE_TOKEN, attemptId: extraEnv.TOKENTRACKER_LOCAL_SYNC_ATTEMPT_ID });
2179
2231
  if (failure) throw Object.assign(new Error("Cloud upload failed"), failure);
2180
2232
  invalidateAfterAccountUpload();
2181
2233
  if (drain) invalidateCloudAccountPayloadCache();