codex-multi-auth 2.17.2 → 2.17.3

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 (394) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +83 -428
  3. package/dist/index.d.ts +0 -1
  4. package/dist/lib/account-policy.d.ts +18 -2
  5. package/dist/lib/account-policy.js +97 -40
  6. package/dist/lib/accounts/rate-limits.d.ts +0 -1
  7. package/dist/lib/accounts.d.ts +0 -1
  8. package/dist/lib/accounts.js +4 -5
  9. package/dist/lib/api-route-store.d.ts +0 -1
  10. package/dist/lib/audit.d.ts +0 -1
  11. package/dist/lib/audit.js +43 -12
  12. package/dist/lib/auth/account-access.d.ts +0 -1
  13. package/dist/lib/auth/account-access.js +5 -1
  14. package/dist/lib/auth/auth.d.ts +4 -3
  15. package/dist/lib/auth/auth.js +220 -28
  16. package/dist/lib/auth/browser.d.ts +0 -1
  17. package/dist/lib/auth/callback-guidance.d.ts +0 -1
  18. package/dist/lib/auth/device-auth.d.ts +0 -1
  19. package/dist/lib/auth/index.d.ts +0 -1
  20. package/dist/lib/auth/org-override.d.ts +0 -1
  21. package/dist/lib/auth/server.d.ts +1 -2
  22. package/dist/lib/auth/server.js +100 -53
  23. package/dist/lib/auth/token-utils.d.ts +0 -1
  24. package/dist/lib/auth-rate-limit.d.ts +0 -1
  25. package/dist/lib/budget-guard.d.ts +16 -2
  26. package/dist/lib/budget-guard.js +123 -43
  27. package/dist/lib/capability-policy.d.ts +0 -1
  28. package/dist/lib/circuit-breaker.d.ts +20 -1
  29. package/dist/lib/circuit-breaker.js +24 -5
  30. package/dist/lib/cli.d.ts +0 -1
  31. package/dist/lib/codex-cli/index.d.ts +0 -1
  32. package/dist/lib/codex-cli/observability.d.ts +0 -1
  33. package/dist/lib/codex-cli/state.d.ts +0 -1
  34. package/dist/lib/codex-cli/sync.d.ts +0 -1
  35. package/dist/lib/codex-cli/writer.d.ts +0 -1
  36. package/dist/lib/codex-manager/account-credentials.d.ts +0 -1
  37. package/dist/lib/codex-manager/account-manager-commands.d.ts +0 -1
  38. package/dist/lib/codex-manager/account-pool-write.d.ts +0 -1
  39. package/dist/lib/codex-manager/active-account-sync.d.ts +6 -0
  40. package/dist/lib/codex-manager/active-account-sync.js +336 -0
  41. package/dist/lib/codex-manager/api-login-menu.d.ts +0 -1
  42. package/dist/lib/codex-manager/backend-category-entry.d.ts +0 -1
  43. package/dist/lib/codex-manager/backend-category-helpers.d.ts +0 -1
  44. package/dist/lib/codex-manager/backend-category-prompt.d.ts +0 -1
  45. package/dist/lib/codex-manager/backend-settings-controller.d.ts +0 -1
  46. package/dist/lib/codex-manager/backend-settings-entry.d.ts +0 -1
  47. package/dist/lib/codex-manager/backend-settings-helpers.d.ts +0 -1
  48. package/dist/lib/codex-manager/backend-settings-prompt.d.ts +0 -1
  49. package/dist/lib/codex-manager/backend-settings-schema.d.ts +0 -1
  50. package/dist/lib/codex-manager/behavior-settings-panel.d.ts +0 -1
  51. package/dist/lib/codex-manager/commands/account.d.ts +8 -3
  52. package/dist/lib/codex-manager/commands/account.js +46 -34
  53. package/dist/lib/codex-manager/commands/best.d.ts +0 -1
  54. package/dist/lib/codex-manager/commands/bridge.d.ts +0 -1
  55. package/dist/lib/codex-manager/commands/budget.d.ts +8 -3
  56. package/dist/lib/codex-manager/commands/budget.js +6 -4
  57. package/dist/lib/codex-manager/commands/check.d.ts +0 -1
  58. package/dist/lib/codex-manager/commands/config-explain.d.ts +0 -1
  59. package/dist/lib/codex-manager/commands/debug-bundle.d.ts +0 -1
  60. package/dist/lib/codex-manager/commands/forecast.d.ts +0 -1
  61. package/dist/lib/codex-manager/commands/history.d.ts +0 -1
  62. package/dist/lib/codex-manager/commands/init-config.d.ts +0 -1
  63. package/dist/lib/codex-manager/commands/integrations.d.ts +0 -1
  64. package/dist/lib/codex-manager/commands/limits.d.ts +0 -1
  65. package/dist/lib/codex-manager/commands/models.d.ts +0 -1
  66. package/dist/lib/codex-manager/commands/monitor.d.ts +0 -1
  67. package/dist/lib/codex-manager/commands/report.d.ts +0 -1
  68. package/dist/lib/codex-manager/commands/resets.d.ts +0 -1
  69. package/dist/lib/codex-manager/commands/resets.js +9 -1
  70. package/dist/lib/codex-manager/commands/rotation.d.ts +0 -1
  71. package/dist/lib/codex-manager/commands/status.d.ts +0 -1
  72. package/dist/lib/codex-manager/commands/switch.d.ts +0 -1
  73. package/dist/lib/codex-manager/commands/uninstall.d.ts +0 -1
  74. package/dist/lib/codex-manager/commands/unpin.d.ts +0 -1
  75. package/dist/lib/codex-manager/commands/usage.d.ts +0 -1
  76. package/dist/lib/codex-manager/commands/verify.d.ts +0 -1
  77. package/dist/lib/codex-manager/commands/why-selected.d.ts +0 -1
  78. package/dist/lib/codex-manager/commands/workspace.d.ts +0 -1
  79. package/dist/lib/codex-manager/dashboard-display-panel.d.ts +0 -1
  80. package/dist/lib/codex-manager/dashboard-formatters.d.ts +0 -1
  81. package/dist/lib/codex-manager/dashboard-settings-controller.d.ts +0 -1
  82. package/dist/lib/codex-manager/dashboard-settings-data.d.ts +0 -1
  83. package/dist/lib/codex-manager/dashboard-settings-entry.d.ts +0 -1
  84. package/dist/lib/codex-manager/experimental-settings-entry.d.ts +0 -1
  85. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +0 -1
  86. package/dist/lib/codex-manager/experimental-settings-schema.d.ts +0 -1
  87. package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts +0 -1
  88. package/dist/lib/codex-manager/experimental-sync-target.d.ts +0 -1
  89. package/dist/lib/codex-manager/forecast-report-shared.d.ts +0 -1
  90. package/dist/lib/codex-manager/formatters/account-formatters.d.ts +0 -1
  91. package/dist/lib/codex-manager/formatters/index.d.ts +0 -1
  92. package/dist/lib/codex-manager/formatters/model-formatters.d.ts +0 -1
  93. package/dist/lib/codex-manager/formatters/quota-formatters.d.ts +0 -1
  94. package/dist/lib/codex-manager/formatters/text-style.d.ts +0 -1
  95. package/dist/lib/codex-manager/health-check.d.ts +0 -1
  96. package/dist/lib/codex-manager/help.d.ts +0 -1
  97. package/dist/lib/codex-manager/login-action-panel.d.ts +0 -1
  98. package/dist/lib/codex-manager/login-flow.d.ts +0 -1
  99. package/dist/lib/codex-manager/login-menu-actions.d.ts +0 -1
  100. package/dist/lib/codex-manager/login-menu-data.d.ts +0 -1
  101. package/dist/lib/codex-manager/login-oauth.d.ts +0 -1
  102. package/dist/lib/codex-manager/login-workspace-choice.d.ts +0 -1
  103. package/dist/lib/codex-manager/manual-callback.d.ts +0 -1
  104. package/dist/lib/codex-manager/persist-selected-account.d.ts +0 -1
  105. package/dist/lib/codex-manager/quota-cache-helpers.d.ts +0 -1
  106. package/dist/lib/codex-manager/rate-limit-markers.d.ts +0 -1
  107. package/dist/lib/codex-manager/repair-commands.d.ts +0 -1
  108. package/dist/lib/codex-manager/settings-hub/backend.d.ts +0 -1
  109. package/dist/lib/codex-manager/settings-hub/dashboard.d.ts +0 -1
  110. package/dist/lib/codex-manager/settings-hub/experimental.d.ts +0 -1
  111. package/dist/lib/codex-manager/settings-hub/index.d.ts +0 -1
  112. package/dist/lib/codex-manager/settings-hub/shared.d.ts +0 -1
  113. package/dist/lib/codex-manager/settings-hub-entry.d.ts +0 -1
  114. package/dist/lib/codex-manager/settings-hub-menu.d.ts +0 -1
  115. package/dist/lib/codex-manager/settings-hub-prompt.d.ts +0 -1
  116. package/dist/lib/codex-manager/settings-hub.d.ts +0 -1
  117. package/dist/lib/codex-manager/settings-panels.d.ts +0 -1
  118. package/dist/lib/codex-manager/settings-persist-utils.d.ts +0 -1
  119. package/dist/lib/codex-manager/settings-preview.d.ts +0 -1
  120. package/dist/lib/codex-manager/settings-write-queue.d.ts +0 -1
  121. package/dist/lib/codex-manager/statusline-settings-panel.d.ts +0 -1
  122. package/dist/lib/codex-manager/theme-settings-panel.d.ts +0 -1
  123. package/dist/lib/codex-manager/unified-settings-controller.d.ts +0 -1
  124. package/dist/lib/codex-manager/unified-settings-entry.d.ts +0 -1
  125. package/dist/lib/codex-manager.d.ts +1 -2
  126. package/dist/lib/codex-manager.js +5 -95
  127. package/dist/lib/concurrency.d.ts +0 -1
  128. package/dist/lib/config-schema.d.ts +0 -1
  129. package/dist/lib/config.d.ts +0 -1
  130. package/dist/lib/config.js +46 -241
  131. package/dist/lib/constants.d.ts +0 -1
  132. package/dist/lib/context-budget/model-context-windows.d.ts +0 -1
  133. package/dist/lib/context-budget-guard.d.ts +0 -1
  134. package/dist/lib/context-budget-response.d.ts +0 -1
  135. package/dist/lib/context-overflow.d.ts +0 -1
  136. package/dist/lib/dashboard-settings.d.ts +0 -1
  137. package/dist/lib/entitlement-cache.d.ts +0 -1
  138. package/dist/lib/env-parsing.d.ts +13 -1
  139. package/dist/lib/env-parsing.js +21 -0
  140. package/dist/lib/errors.d.ts +0 -1
  141. package/dist/lib/forecast.d.ts +0 -1
  142. package/dist/lib/fs-retry.d.ts +0 -1
  143. package/dist/lib/health.d.ts +0 -1
  144. package/dist/lib/index.d.ts +0 -1
  145. package/dist/lib/integration-generators.d.ts +0 -1
  146. package/dist/lib/live-account-sync.d.ts +0 -1
  147. package/dist/lib/local-bridge.d.ts +0 -1
  148. package/dist/lib/local-bridge.js +4 -1
  149. package/dist/lib/local-client-tokens.d.ts +0 -1
  150. package/dist/lib/local-client-tokens.js +125 -30
  151. package/dist/lib/logger.d.ts +0 -1
  152. package/dist/lib/logger.js +11 -1
  153. package/dist/lib/model-capability-matrix.d.ts +0 -1
  154. package/dist/lib/model-route-policy.d.ts +1 -2
  155. package/dist/lib/named-backup-export.d.ts +0 -1
  156. package/dist/lib/oc-chatgpt-import-adapter.d.ts +0 -1
  157. package/dist/lib/oc-chatgpt-orchestrator.d.ts +0 -1
  158. package/dist/lib/oc-chatgpt-target-detection.d.ts +0 -1
  159. package/dist/lib/parallel-probe.d.ts +0 -1
  160. package/dist/lib/policy/runtime-policy-cache.d.ts +80 -0
  161. package/dist/lib/policy/runtime-policy-cache.js +310 -0
  162. package/dist/lib/policy/runtime-policy.d.ts +1 -2
  163. package/dist/lib/policy/runtime-policy.js +46 -19
  164. package/dist/lib/preemptive-quota-scheduler.d.ts +0 -1
  165. package/dist/lib/proactive-refresh.d.ts +0 -1
  166. package/dist/lib/prompts/codex-host-bridge.d.ts +0 -1
  167. package/dist/lib/prompts/codex.d.ts +0 -1
  168. package/dist/lib/prompts/fetch-utils.d.ts +0 -1
  169. package/dist/lib/prompts/host-codex-prompt.d.ts +0 -1
  170. package/dist/lib/quota-cache.d.ts +0 -1
  171. package/dist/lib/quota-cache.js +60 -36
  172. package/dist/lib/quota-probe.d.ts +0 -1
  173. package/dist/lib/quota-probe.js +5 -2
  174. package/dist/lib/quota-readiness.d.ts +0 -1
  175. package/dist/lib/recovery/constants.d.ts +0 -1
  176. package/dist/lib/recovery/storage.d.ts +0 -1
  177. package/dist/lib/recovery/types.d.ts +0 -1
  178. package/dist/lib/recovery.d.ts +0 -1
  179. package/dist/lib/redaction.d.ts +0 -1
  180. package/dist/lib/refresh-guardian.d.ts +0 -1
  181. package/dist/lib/refresh-lease.d.ts +0 -1
  182. package/dist/lib/refresh-queue.d.ts +0 -1
  183. package/dist/lib/request/client-cancellation.d.ts +0 -1
  184. package/dist/lib/request/error-classification.d.ts +0 -1
  185. package/dist/lib/request/failover-config.d.ts +0 -1
  186. package/dist/lib/request/failure-policy.d.ts +0 -1
  187. package/dist/lib/request/fetch-helpers.d.ts +0 -1
  188. package/dist/lib/request/headers.d.ts +0 -1
  189. package/dist/lib/request/helpers/input-utils.d.ts +0 -1
  190. package/dist/lib/request/helpers/input-utils.js +88 -34
  191. package/dist/lib/request/helpers/model-map.d.ts +0 -1
  192. package/dist/lib/request/helpers/tool-utils.d.ts +0 -1
  193. package/dist/lib/request/helpers/tool-utils.js +421 -4
  194. package/dist/lib/request/index.d.ts +0 -1
  195. package/dist/lib/request/rate-limit-backoff.d.ts +0 -1
  196. package/dist/lib/request/rate-limit-decision.d.ts +0 -1
  197. package/dist/lib/request/request-attempt-budget.d.ts +0 -1
  198. package/dist/lib/request/request-init.d.ts +0 -1
  199. package/dist/lib/request/request-resilience.d.ts +0 -1
  200. package/dist/lib/request/request-transformer.d.ts +0 -1
  201. package/dist/lib/request/request-transformer.js +50 -25
  202. package/dist/lib/request/response-compaction.d.ts +0 -1
  203. package/dist/lib/request/response-handler.d.ts +0 -1
  204. package/dist/lib/request/response-metadata.d.ts +0 -1
  205. package/dist/lib/request/response-outcome.d.ts +0 -1
  206. package/dist/lib/request/stream-failover-runtime.d.ts +0 -1
  207. package/dist/lib/request/stream-failover.d.ts +0 -1
  208. package/dist/lib/request/stream-failover.js +28 -4
  209. package/dist/lib/request/token-refresh.d.ts +0 -1
  210. package/dist/lib/request/url-rewriting.d.ts +0 -1
  211. package/dist/lib/request/wait-utils.d.ts +0 -1
  212. package/dist/lib/rotation.d.ts +0 -1
  213. package/dist/lib/routing-mutex.d.ts +0 -1
  214. package/dist/lib/routing-profiles.d.ts +16 -2
  215. package/dist/lib/routing-profiles.js +119 -44
  216. package/dist/lib/runtime/account-check-helpers.d.ts +0 -1
  217. package/dist/lib/runtime/account-check-types.d.ts +0 -1
  218. package/dist/lib/runtime/account-check.d.ts +0 -1
  219. package/dist/lib/runtime/account-manager-cache-entry.d.ts +0 -1
  220. package/dist/lib/runtime/account-manager-cache.d.ts +0 -1
  221. package/dist/lib/runtime/account-model-catalog.d.ts +1 -2
  222. package/dist/lib/runtime/account-pool.d.ts +0 -1
  223. package/dist/lib/runtime/account-reset-credits.d.ts +0 -1
  224. package/dist/lib/runtime/account-reset-credits.js +8 -2
  225. package/dist/lib/runtime/account-scope.d.ts +0 -1
  226. package/dist/lib/runtime/account-select-event.d.ts +0 -1
  227. package/dist/lib/runtime/account-selection.d.ts +0 -1
  228. package/dist/lib/runtime/account-state.d.ts +0 -1
  229. package/dist/lib/runtime/account-status.d.ts +0 -1
  230. package/dist/lib/runtime/account-storage-scope-entry.d.ts +0 -1
  231. package/dist/lib/runtime/api-model-capabilities.d.ts +0 -1
  232. package/dist/lib/runtime/api-model-runtime.d.ts +0 -1
  233. package/dist/lib/runtime/app-bind.d.ts +0 -1
  234. package/dist/lib/runtime/app-helper-selection.d.ts +0 -1
  235. package/dist/lib/runtime/auth-facade.d.ts +0 -1
  236. package/dist/lib/runtime/automatic-account-checks.d.ts +0 -1
  237. package/dist/lib/runtime/automatic-subscription-checks.d.ts +0 -1
  238. package/dist/lib/runtime/browser-oauth-flow.d.ts +0 -1
  239. package/dist/lib/runtime/capability-boost.d.ts +0 -1
  240. package/dist/lib/runtime/catalog-capabilities.d.ts +0 -1
  241. package/dist/lib/runtime/config-toml.d.ts +0 -1
  242. package/dist/lib/runtime/context-budget-settings.d.ts +0 -1
  243. package/dist/lib/runtime/event-handler.d.ts +0 -1
  244. package/dist/lib/runtime/first-run.d.ts +0 -1
  245. package/dist/lib/runtime/flagged-verify-types.d.ts +0 -1
  246. package/dist/lib/runtime/hydrate-emails.d.ts +0 -1
  247. package/dist/lib/runtime/inference-activity.d.ts +0 -1
  248. package/dist/lib/runtime/live-sync-entry.d.ts +0 -1
  249. package/dist/lib/runtime/live-sync.d.ts +0 -1
  250. package/dist/lib/runtime/loader-setup.d.ts +0 -1
  251. package/dist/lib/runtime/login-menu-accounts.d.ts +0 -1
  252. package/dist/lib/runtime/manual-oauth-flow.d.ts +0 -1
  253. package/dist/lib/runtime/model-discovery-status.d.ts +2 -3
  254. package/dist/lib/runtime/native-account-storage.d.ts +0 -1
  255. package/dist/lib/runtime/native-account-sync.d.ts +0 -1
  256. package/dist/lib/runtime/native-binding-lock.d.ts +0 -1
  257. package/dist/lib/runtime/native-client-auth.d.ts +0 -1
  258. package/dist/lib/runtime/native-provider-config.d.ts +0 -1
  259. package/dist/lib/runtime/native-rate-limits.d.ts +2 -1
  260. package/dist/lib/runtime/native-rate-limits.js +23 -6
  261. package/dist/lib/runtime/preemptive-quota.d.ts +0 -1
  262. package/dist/lib/runtime/quota-headers.d.ts +0 -1
  263. package/dist/lib/runtime/quota-probe.d.ts +0 -1
  264. package/dist/lib/runtime/quota-settings.d.ts +0 -1
  265. package/dist/lib/runtime/refresh-guardian-entry.d.ts +0 -1
  266. package/dist/lib/runtime/refresh-guardian.d.ts +0 -1
  267. package/dist/lib/runtime/request-init.d.ts +0 -1
  268. package/dist/lib/runtime/reset-credit-routing.d.ts +0 -1
  269. package/dist/lib/runtime/reset-credits.d.ts +0 -1
  270. package/dist/lib/runtime/response-output-history.d.ts +0 -1
  271. package/dist/lib/runtime/responses-websocket.d.ts +0 -1
  272. package/dist/lib/runtime/resume-picker.d.ts +0 -1
  273. package/dist/lib/runtime/rotation-account-selection.d.ts +0 -1
  274. package/dist/lib/runtime/rotation-proxy-state.d.ts +0 -1
  275. package/dist/lib/runtime/rotation-server-types.d.ts +0 -1
  276. package/dist/lib/runtime/rotation-storage-meta.d.ts +0 -1
  277. package/dist/lib/runtime/rotation-token-refresh.d.ts +0 -1
  278. package/dist/lib/runtime/runtime-capability-failures.d.ts +0 -1
  279. package/dist/lib/runtime/runtime-current-account.d.ts +0 -1
  280. package/dist/lib/runtime/runtime-observability.d.ts +0 -1
  281. package/dist/lib/runtime/runtime-services.d.ts +0 -1
  282. package/dist/lib/runtime/session-affinity-entry.d.ts +0 -1
  283. package/dist/lib/runtime/session-recovery.d.ts +0 -1
  284. package/dist/lib/runtime/status-marker.d.ts +0 -1
  285. package/dist/lib/runtime/storage-scope.d.ts +0 -1
  286. package/dist/lib/runtime/subscription-first-use.d.ts +0 -1
  287. package/dist/lib/runtime/subscription-quota-event.d.ts +0 -1
  288. package/dist/lib/runtime/subscription-quota-order.d.ts +0 -1
  289. package/dist/lib/runtime/toast.d.ts +0 -1
  290. package/dist/lib/runtime/ui-runtime-entry.d.ts +0 -1
  291. package/dist/lib/runtime/ui-runtime.d.ts +0 -1
  292. package/dist/lib/runtime/verify-flagged.d.ts +0 -1
  293. package/dist/lib/runtime/workspace-model-scopes.d.ts +0 -1
  294. package/dist/lib/runtime-constants.d.ts +0 -1
  295. package/dist/lib/runtime-paths.d.ts +0 -1
  296. package/dist/lib/runtime-rotation-proxy.d.ts +1 -1
  297. package/dist/lib/runtime-rotation-proxy.js +133 -0
  298. package/dist/lib/schemas.d.ts +35 -13
  299. package/dist/lib/schemas.js +19 -2
  300. package/dist/lib/session-affinity.d.ts +0 -1
  301. package/dist/lib/shutdown.d.ts +0 -1
  302. package/dist/lib/storage/account-clear-entry.d.ts +0 -1
  303. package/dist/lib/storage/account-clear.d.ts +0 -1
  304. package/dist/lib/storage/account-match-utils.d.ts +0 -1
  305. package/dist/lib/storage/account-persistence.d.ts +0 -1
  306. package/dist/lib/storage/account-port.d.ts +0 -1
  307. package/dist/lib/storage/account-save-entry.d.ts +0 -1
  308. package/dist/lib/storage/account-save.d.ts +9 -2
  309. package/dist/lib/storage/account-save.js +4 -1
  310. package/dist/lib/storage/account-snapshot.d.ts +0 -1
  311. package/dist/lib/storage/backup-metadata-builder.d.ts +0 -1
  312. package/dist/lib/storage/backup-metadata.d.ts +0 -1
  313. package/dist/lib/storage/backup-paths.d.ts +0 -1
  314. package/dist/lib/storage/backup-restore.d.ts +0 -1
  315. package/dist/lib/storage/cache-artifacts.d.ts +0 -1
  316. package/dist/lib/storage/credential-sidecars.d.ts +0 -1
  317. package/dist/lib/storage/error-hints.d.ts +0 -1
  318. package/dist/lib/storage/file-lock.d.ts +0 -1
  319. package/dist/lib/storage/file-lock.js +33 -1
  320. package/dist/lib/storage/file-paths.d.ts +0 -1
  321. package/dist/lib/storage/fixture-guards.d.ts +0 -1
  322. package/dist/lib/storage/flagged-entry.d.ts +0 -1
  323. package/dist/lib/storage/flagged-load-entry.d.ts +0 -1
  324. package/dist/lib/storage/flagged-save-entry.d.ts +0 -1
  325. package/dist/lib/storage/flagged-storage-file.d.ts +0 -1
  326. package/dist/lib/storage/flagged-storage-io.d.ts +0 -1
  327. package/dist/lib/storage/flagged-storage.d.ts +0 -1
  328. package/dist/lib/storage/gitignore.d.ts +0 -1
  329. package/dist/lib/storage/hash.d.ts +0 -1
  330. package/dist/lib/storage/health.d.ts +0 -1
  331. package/dist/lib/storage/identity.d.ts +0 -1
  332. package/dist/lib/storage/import-export.d.ts +0 -1
  333. package/dist/lib/storage/json-store-lock.d.ts +105 -0
  334. package/dist/lib/storage/json-store-lock.js +259 -0
  335. package/dist/lib/storage/metadata-section.d.ts +0 -1
  336. package/dist/lib/storage/migrations.d.ts +0 -1
  337. package/dist/lib/storage/migrations.js +31 -18
  338. package/dist/lib/storage/named-backup-entry.d.ts +0 -1
  339. package/dist/lib/storage/named-backups-entry.d.ts +0 -1
  340. package/dist/lib/storage/named-backups.d.ts +0 -1
  341. package/dist/lib/storage/path-state.d.ts +0 -1
  342. package/dist/lib/storage/paths.d.ts +0 -1
  343. package/dist/lib/storage/pending-auth.d.ts +9 -3
  344. package/dist/lib/storage/pending-auth.js +30 -8
  345. package/dist/lib/storage/project-migration.d.ts +0 -1
  346. package/dist/lib/storage/public-types.d.ts +0 -1
  347. package/dist/lib/storage/record-identity.d.ts +0 -1
  348. package/dist/lib/storage/record-utils.d.ts +0 -1
  349. package/dist/lib/storage/restore-assessment.d.ts +0 -1
  350. package/dist/lib/storage/restore-backup-entry.d.ts +0 -1
  351. package/dist/lib/storage/restore-metadata.d.ts +0 -1
  352. package/dist/lib/storage/save-retry.d.ts +0 -1
  353. package/dist/lib/storage/snapshot-inspectors.d.ts +0 -1
  354. package/dist/lib/storage/snapshot-merge.d.ts +0 -1
  355. package/dist/lib/storage/storage-parser.d.ts +2 -1
  356. package/dist/lib/storage/storage-parser.js +10 -2
  357. package/dist/lib/storage/transactions.d.ts +0 -1
  358. package/dist/lib/storage.d.ts +15 -2
  359. package/dist/lib/storage.js +612 -25
  360. package/dist/lib/synthetic-response.d.ts +0 -1
  361. package/dist/lib/table-formatter.d.ts +0 -1
  362. package/dist/lib/temp-path.d.ts +0 -1
  363. package/dist/lib/tools/hashline-tools.d.ts +0 -1
  364. package/dist/lib/types.d.ts +0 -1
  365. package/dist/lib/ui/ansi.d.ts +0 -1
  366. package/dist/lib/ui/auth-menu-builder.d.ts +0 -1
  367. package/dist/lib/ui/auth-menu.d.ts +0 -1
  368. package/dist/lib/ui/check-progress.d.ts +0 -1
  369. package/dist/lib/ui/confirm.d.ts +0 -1
  370. package/dist/lib/ui/display-width.d.ts +0 -1
  371. package/dist/lib/ui/format.d.ts +0 -1
  372. package/dist/lib/ui/index.d.ts +0 -1
  373. package/dist/lib/ui/runtime.d.ts +0 -1
  374. package/dist/lib/ui/select.d.ts +0 -1
  375. package/dist/lib/ui/theme.d.ts +0 -1
  376. package/dist/lib/ui/ui-copy.d.ts +0 -1
  377. package/dist/lib/unified-settings.d.ts +13 -11
  378. package/dist/lib/unified-settings.js +22 -14
  379. package/dist/lib/update-notice.d.ts +0 -1
  380. package/dist/lib/usage/index.d.ts +0 -1
  381. package/dist/lib/usage/ledger.d.ts +0 -1
  382. package/dist/lib/usage/pricing.d.ts +0 -1
  383. package/dist/lib/usage/redaction.d.ts +0 -1
  384. package/dist/lib/usage/stream-usage-deferral.d.ts +0 -1
  385. package/dist/lib/usage/types.d.ts +0 -1
  386. package/dist/lib/usage/usage-extraction.d.ts +0 -1
  387. package/dist/lib/utils.d.ts +21 -1
  388. package/dist/lib/utils.js +31 -0
  389. package/dist/lib/wsl.d.ts +0 -1
  390. package/package.json +11 -5
  391. package/scripts/codex-bin-resolver.js +20 -1
  392. package/scripts/codex-multi-auth.js +7 -1
  393. package/scripts/codex.js +288 -7
  394. package/skills/codex-auth-setup/SKILL.md +73 -0
@@ -0,0 +1,259 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { promises as fs } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { withRetry } from "../fs-retry.js";
5
+ import { tempPathFor } from "../temp-path.js";
6
+ import { withFileTransactionLock } from "./file-lock.js";
7
+ /**
8
+ * Shared concurrency machinery for the file-backed governance JSON stores
9
+ * (config, unified settings, budget guards, routing profiles, account
10
+ * policies, local client tokens, quota cache).
11
+ *
12
+ * Three cooperating guards, extracted from lib/config.ts's save path so every
13
+ * store gets the same semantics instead of each re-deriving a subset:
14
+ *
15
+ * 1. {@link withJsonStoreWriteQueue} — a per-path in-process promise queue
16
+ * that serializes writers inside this process (replaces each store's
17
+ * hand-rolled `writeQueue` module variable).
18
+ * 2. {@link withJsonStoreFileLock} — a cross-process lock directory
19
+ * (`<path>.write-lock`) delegated to {@link withFileTransactionLock}: a
20
+ * pre-populated candidate directory is published by atomic rename, and a
21
+ * crashed holder's lock is recovered only when its owner PID is dead —
22
+ * live holders are NEVER evicted, so a takeover can never unlink a newly
23
+ * acquired lock (the stale-check→unlink TOCTOU that a single lockfile
24
+ * cannot close: POSIX has no delete-if-unchanged primitive, and renaming
25
+ * the file aside leaves an empty path another `wx` create can claim).
26
+ * The promise queue only serializes THIS process; the lock directory
27
+ * closes the read→check→merge→rename TOCTOU window against OTHER
28
+ * processes. Re-entrant within one async context so a nested write on the
29
+ * same path cannot deadlock itself.
30
+ * 3. mtime compare-and-swap — {@link getJsonStoreFileMtimeMs} /
31
+ * {@link assertJsonStoreFileMtimeUnchanged} /
32
+ * {@link writeJsonStoreFileAtomicWithRetry} plus
33
+ * {@link withJsonStoreCasRetry}: each attempt re-stats, re-reads, and
34
+ * re-merges against the latest on-disk state before writing, aborting with
35
+ * `ESTALE` when another writer moved the file under us. This stays on as a
36
+ * second-line guard for writers that do not take the lockfile.
37
+ *
38
+ * Storage layer: this module sits below the governance stores (which live in
39
+ * lib/ root) and below lib/config.ts; it must only depend on leaf utilities.
40
+ */
41
+ const JSON_STORE_WRITE_RETRY_CODES = new Set(["EBUSY", "EPERM"]);
42
+ const JSON_STORE_READ_RETRY_CODES = new Set(["EBUSY", "EPERM", "EAGAIN"]);
43
+ const JSON_STORE_CAS_MAX_ATTEMPTS = 3;
44
+ /**
45
+ * Stamp `updatedAt` for a per-key governance entry with a hybrid logical
46
+ * floor: when the wall clock regressed below the timestamp the entry being
47
+ * overwritten already carries, stamp `prior + 1` instead of `now`. Every
48
+ * upsert-only store merges with `updatedAt >=`, so a backward clock jump
49
+ * would otherwise stamp a semantically-newer write below its own
50
+ * predecessor and the merge would silently drop it. Equal or forward wall
51
+ * times pass through unchanged — ties already resolve to the caller's
52
+ * write under `>=`.
53
+ *
54
+ * `prior` is the entry the writer saw, which can trail the freshest on-disk
55
+ * entry when a concurrent writer lands between the caller's load and its
56
+ * save; baseline-aware save merges re-apply this same floor against the
57
+ * on-disk entry at merge time so the edit still cannot be lost.
58
+ */
59
+ export function stampUpdatedAt(now, prior) {
60
+ return typeof prior === "number" && Number.isFinite(prior) && now < prior
61
+ ? prior + 1
62
+ : now;
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // 1. Per-path in-process write queue
66
+ // ---------------------------------------------------------------------------
67
+ const jsonStoreWriteQueues = new Map();
68
+ // Async-local re-entrancy map for the write queue (keyed by the raw path
69
+ // string, matching the queue key): a nested withJsonStoreWriteQueue on the
70
+ // same path inside one async context runs inside the outer critical section
71
+ // instead of deadlocking on its own queue slot — e.g. config.ts wraps
72
+ // saveUnifiedPluginConfig (which queues on settings.json) in an outer queue
73
+ // for the same path.
74
+ const jsonStoreQueueLeases = new AsyncLocalStorage();
75
+ /**
76
+ * Serialize `task` behind the previous write queued for `path`, so same-file
77
+ * writers inside this process run one at a time in submission order. A failed
78
+ * predecessor never blocks or fails successors. Re-entrant for the same path
79
+ * within one async context.
80
+ *
81
+ * This is the per-path generalization of the module-local `writeQueue`
82
+ * variables the governance stores used to keep by hand.
83
+ */
84
+ export async function withJsonStoreWriteQueue(path, task) {
85
+ const held = jsonStoreQueueLeases.getStore();
86
+ if (held?.get(path)?.active) {
87
+ return task();
88
+ }
89
+ const previous = jsonStoreWriteQueues.get(path) ?? Promise.resolve();
90
+ const lease = { active: true };
91
+ const context = new Map(held);
92
+ context.set(path, lease);
93
+ const queued = previous.catch(() => { }).then(async () => {
94
+ try {
95
+ return await jsonStoreQueueLeases.run(context, task);
96
+ }
97
+ finally {
98
+ lease.active = false;
99
+ }
100
+ });
101
+ // Store the settled (never-rejecting) wrapper so successors chain cleanly;
102
+ // keep the reference for the same-object check below.
103
+ const stored = queued.then(() => undefined, () => undefined);
104
+ jsonStoreWriteQueues.set(path, stored);
105
+ try {
106
+ return await queued;
107
+ }
108
+ finally {
109
+ if (jsonStoreWriteQueues.get(path) === stored) {
110
+ jsonStoreWriteQueues.delete(path);
111
+ }
112
+ }
113
+ }
114
+ /** Test helper: drop queued-write bookkeeping (all paths, or one when given). */
115
+ export function resetJsonStoreWriteQueuesForTests(path) {
116
+ if (path !== undefined) {
117
+ jsonStoreWriteQueues.delete(path);
118
+ return;
119
+ }
120
+ jsonStoreWriteQueues.clear();
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // 2. mtime compare-and-swap + atomic JSON write
124
+ // ---------------------------------------------------------------------------
125
+ /**
126
+ * Stat `filePath` for its mtime with bounded transient-FS retry (the save
127
+ * path is Windows-sensitive: a single transient EBUSY/EPERM/EAGAIN from an
128
+ * AV/indexer lock must not abort the whole save). ENOENT returns `null`
129
+ * immediately; on exhaustion the last failure surfaces so the caller treats
130
+ * it as a real save error rather than a phantom missing file.
131
+ */
132
+ export async function getJsonStoreFileMtimeMs(filePath) {
133
+ return withRetry(async () => {
134
+ try {
135
+ return (await fs.stat(filePath)).mtimeMs;
136
+ }
137
+ catch (error) {
138
+ if (error?.code === "ENOENT") {
139
+ return null;
140
+ }
141
+ throw error;
142
+ }
143
+ }, {
144
+ maxAttempts: 5,
145
+ backoffMs: (attempt) => 10 * 2 ** (attempt - 1),
146
+ retryableCodes: JSON_STORE_READ_RETRY_CODES,
147
+ });
148
+ }
149
+ /**
150
+ * The `ESTALE` signal used by the CAS retry loop: the file's mtime moved
151
+ * between the caller's stat and its write, so the attempt must re-read and
152
+ * re-merge against the latest on-disk state instead of clobbering it.
153
+ */
154
+ export function createJsonStoreStaleError(filePath) {
155
+ const staleError = new Error(`JSON store at ${filePath} changed on disk during save; retrying with latest state.`);
156
+ staleError.code = "ESTALE";
157
+ return staleError;
158
+ }
159
+ /**
160
+ * Throw `ESTALE` when `filePath`'s current mtime differs from
161
+ * `expectedMtimeMs` (`null` means "file must still be absent"). Writers that
162
+ * keep their own temp+rename flow call this just before the rename, matching
163
+ * {@link writeJsonStoreFileAtomicWithRetry}'s inline check.
164
+ */
165
+ export async function assertJsonStoreFileMtimeUnchanged(filePath, expectedMtimeMs) {
166
+ const currentMtimeMs = await getJsonStoreFileMtimeMs(filePath);
167
+ if (currentMtimeMs !== expectedMtimeMs) {
168
+ throw createJsonStoreStaleError(filePath);
169
+ }
170
+ }
171
+ /**
172
+ * Atomic JSON write: serialize `payload` pretty-printed with a trailing
173
+ * newline to a crypto-random sibling temp file, then rename it over
174
+ * `filePath` with bounded transient-FS retry. Optionally CAS-checks the
175
+ * target mtime first and fsyncs the temp file; on failure the temp is
176
+ * removed best-effort.
177
+ */
178
+ export async function writeJsonStoreFileAtomicWithRetry(filePath, payload, options) {
179
+ const tempPath = tempPathFor(filePath);
180
+ await fs.mkdir(dirname(filePath), { recursive: true });
181
+ const handle = await fs.open(tempPath, "w", options?.mode);
182
+ try {
183
+ await handle.writeFile(`${JSON.stringify(payload, null, 2)}\n`, "utf8");
184
+ if (options?.fsync) {
185
+ await handle.sync();
186
+ }
187
+ }
188
+ finally {
189
+ await handle.close();
190
+ }
191
+ let renamed = false;
192
+ try {
193
+ if (options && "expectedMtimeMs" in options) {
194
+ await assertJsonStoreFileMtimeUnchanged(filePath, options.expectedMtimeMs ?? null);
195
+ }
196
+ await withRetry(() => fs.rename(tempPath, filePath), {
197
+ maxAttempts: 5,
198
+ backoffMs: (attempt) => 10 * 2 ** (attempt - 1),
199
+ retryableCodes: options?.retryableCodes ?? JSON_STORE_WRITE_RETRY_CODES,
200
+ });
201
+ renamed = true;
202
+ }
203
+ finally {
204
+ if (!renamed) {
205
+ try {
206
+ await fs.unlink(tempPath);
207
+ }
208
+ catch {
209
+ // Best-effort temp cleanup.
210
+ }
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Run `task`, retrying `ESTALE` CAS aborts so each attempt can re-read and
216
+ * re-merge against the latest on-disk state (matches the config save loop:
217
+ * 3 attempts, immediate retry, ESTALE-only).
218
+ */
219
+ export async function withJsonStoreCasRetry(task) {
220
+ return withRetry(task, {
221
+ maxAttempts: JSON_STORE_CAS_MAX_ATTEMPTS,
222
+ backoffMs: 0,
223
+ retryableCodes: ["ESTALE"],
224
+ });
225
+ }
226
+ // ---------------------------------------------------------------------------
227
+ // 3. Cross-process lockfile
228
+ // ---------------------------------------------------------------------------
229
+ const JSON_STORE_LOCK_WAIT_TIMEOUT_MS = 10_000;
230
+ /**
231
+ * Cross-process mutex for a JSON store file, delegated to the proven
232
+ * candidate-directory transaction lock in lib/storage/file-lock.ts: each
233
+ * acquirer stages `<path>.write-lock.candidate-*` containing its unique
234
+ * `<host>.<pid>.<uuid>` owner file, then publishes it with a single atomic
235
+ * `rename` (which only succeeds while the lock directory is absent or empty,
236
+ * so there is no ownerless acquisition window). A crashed holder's lock is
237
+ * recovered by unlinking exactly that dead owner's unique filename and
238
+ * removing the directory only while it is still empty — an `rmdir` can never
239
+ * erase a lock that was republished in the meantime, so takeover can never
240
+ * delete another owner's live lock. Waiters on a live holder retry inside
241
+ * `waitTimeoutMs` (default 10s), then this wrapper surfaces the exhaustion
242
+ * as `ELOCKTIMEOUT`. Re-entrant for the same resolved path within one async
243
+ * context.
244
+ */
245
+ export async function withJsonStoreFileLock(targetPath, task, options) {
246
+ try {
247
+ return await withFileTransactionLock(targetPath, task, {
248
+ waitMs: options?.waitTimeoutMs ?? JSON_STORE_LOCK_WAIT_TIMEOUT_MS,
249
+ });
250
+ }
251
+ catch (error) {
252
+ if (error?.code === "ELOCKED") {
253
+ const timeoutError = new Error(`Timed out acquiring JSON store lock at ${targetPath}.`);
254
+ timeoutError.code = "ELOCKTIMEOUT";
255
+ throw timeoutError;
256
+ }
257
+ throw error;
258
+ }
259
+ }
@@ -13,4 +13,3 @@ export declare function buildMetadataSection<TSnapshot extends {
13
13
  validSnapshotCount: number;
14
14
  snapshots: TSnapshot[];
15
15
  };
16
- //# sourceMappingURL=metadata-section.d.ts.map
@@ -33,4 +33,3 @@ export interface AccountStorageV1 {
33
33
  }
34
34
  export declare function migrateV1ToV3(v1: AccountStorageV1): AccountStorageV3;
35
35
  export {};
36
- //# sourceMappingURL=migrations.d.ts.map
@@ -7,38 +7,51 @@
7
7
  * ./public-types.ts.
8
8
  */
9
9
  import { MODEL_FAMILIES } from "../request/helpers/model-map.js";
10
+ import { isRecord } from "./record-utils.js";
10
11
  function nowMs() {
11
12
  return Date.now();
12
13
  }
13
14
  export function migrateV1ToV3(v1) {
14
15
  const now = nowMs();
16
+ const rawActiveIndexByFamily = isRecord(v1.activeIndexByFamily)
17
+ ? v1
18
+ .activeIndexByFamily
19
+ : {};
15
20
  return {
16
21
  version: 3,
17
- accounts: v1.accounts.map((account) => {
22
+ accounts: v1.accounts
23
+ .filter((account) => account !== null && typeof account === "object")
24
+ .map((account) => {
25
+ const { rateLimitResetTime, rateLimitResetTimes: existingResets, ...passthrough } = account;
26
+ // A pre-existing V3 map wins per key; the legacy scalar only fills
27
+ // families the map does not already cover.
18
28
  const rateLimitResetTimes = {};
19
- if (typeof account.rateLimitResetTime === "number" && account.rateLimitResetTime > now) {
29
+ if (isRecord(existingResets)) {
30
+ for (const [family, value] of Object.entries(existingResets)) {
31
+ if (typeof value === "number") {
32
+ rateLimitResetTimes[family] = value;
33
+ }
34
+ }
35
+ }
36
+ if (typeof rateLimitResetTime === "number" && rateLimitResetTime > now) {
20
37
  for (const family of MODEL_FAMILIES) {
21
- rateLimitResetTimes[family] = account.rateLimitResetTime;
38
+ rateLimitResetTimes[family] ??= rateLimitResetTime;
22
39
  }
23
40
  }
24
41
  return {
25
- accountId: account.accountId,
26
- accountIdSource: account.accountIdSource,
27
- accountLabel: account.accountLabel,
28
- email: account.email,
29
- refreshToken: account.refreshToken,
30
- accessToken: account.accessToken,
31
- expiresAt: account.expiresAt,
32
- enabled: account.enabled,
33
- addedAt: account.addedAt,
34
- lastUsed: account.lastUsed,
35
- lastSwitchReason: account.lastSwitchReason,
36
- rateLimitResetTimes: Object.keys(rateLimitResetTimes).length > 0 ? rateLimitResetTimes : undefined,
37
- coolingDownUntil: account.coolingDownUntil,
38
- cooldownReason: account.cooldownReason,
42
+ ...passthrough,
43
+ rateLimitResetTimes: Object.keys(rateLimitResetTimes).length > 0
44
+ ? rateLimitResetTimes
45
+ : undefined,
39
46
  };
40
47
  }),
41
48
  activeIndex: v1.activeIndex,
42
- activeIndexByFamily: Object.fromEntries(MODEL_FAMILIES.map((family) => [family, v1.activeIndex])),
49
+ activeIndexByFamily: Object.fromEntries(MODEL_FAMILIES.map((family) => {
50
+ const perFamily = rawActiveIndexByFamily[family];
51
+ return [
52
+ family,
53
+ typeof perFamily === "number" ? perFamily : v1.activeIndex,
54
+ ];
55
+ })),
43
56
  };
44
57
  }
@@ -12,4 +12,3 @@ export declare function exportNamedBackupEntry(params: {
12
12
  getStoragePath: () => string;
13
13
  exportAccounts: (filePath: string, force?: boolean, beforeCommit?: (resolvedPath: string) => Promise<void> | void) => Promise<void>;
14
14
  }): Promise<string>;
15
- //# sourceMappingURL=named-backup-entry.d.ts.map
@@ -16,4 +16,3 @@ export declare function getNamedBackupsEntry(params: {
16
16
  }>;
17
17
  logDebug: (message: string, details: Record<string, unknown>) => void;
18
18
  }): Promise<NamedBackupSummary[]>;
19
- //# sourceMappingURL=named-backups-entry.d.ts.map
@@ -12,4 +12,3 @@ export declare function collectNamedBackups(storagePath: string, deps: {
12
12
  }>;
13
13
  logDebug: (message: string, details: Record<string, unknown>) => void;
14
14
  }): Promise<NamedBackupSummary[]>;
15
- //# sourceMappingURL=named-backups.d.ts.map
@@ -10,4 +10,3 @@ export declare function setStoragePathDirectState(state: StoragePathState): void
10
10
  export declare function runWithStoragePathState<T>(state: StoragePathState, fn: () => T | Promise<T>): Promise<T>;
11
11
  /** Temporarily use the shared pool without mutating the caller or sibling async scopes. */
12
12
  export declare function runWithGlobalStoragePath<T>(fn: () => T | Promise<T>): Promise<T>;
13
- //# sourceMappingURL=path-state.d.ts.map
@@ -59,4 +59,3 @@ export declare function resolveProjectStorageIdentityRoot(projectRoot: string):
59
59
  export declare function isProjectDirectory(dir: string): boolean;
60
60
  export declare function findProjectRoot(startDir: string): string | null;
61
61
  export declare function resolvePath(filePath: string): string;
62
- //# sourceMappingURL=paths.d.ts.map
@@ -11,8 +11,14 @@ export declare function recordPendingAuth(storagePath: string, auth: {
11
11
  }): Promise<void>;
12
12
  /** Overlay pending rotated credentials on rows that still hold the spent token. */
13
13
  export declare function applyPendingAuth(storagePath: string, storage: AccountStorageV3 | null): Promise<AccountStorageV3 | null>;
14
- /** After a save, drop entries whose spent token is no longer on disk (persisted or superseded). */
15
- export declare function prunePendingAuth(storagePath: string, saved: AccountStorageV3): Promise<void>;
14
+ /**
15
+ * After a save, drop entries whose spent token is no longer in any restorable
16
+ * state. `saved` is the primary content just persisted; callers may also
17
+ * supply `restorableRefreshTokens` — refresh tokens still present in recovery
18
+ * sources (rotating/discovered backups, a WAL payload) — since a restore can
19
+ * resurface a spent token, its entry must survive until no source holds it.
20
+ * A scan failure keeps every entry: retention is the safe direction.
21
+ */
22
+ export declare function prunePendingAuth(storagePath: string, saved: AccountStorageV3, restorableRefreshTokens?: () => Promise<Iterable<string>>): Promise<void>;
16
23
  /** Coordinate resets with pending credential writers, including Windows rename locks. */
17
24
  export declare function clearPendingAuth(storagePath: string): Promise<void>;
18
- //# sourceMappingURL=pending-auth.d.ts.map
@@ -79,15 +79,17 @@ export async function recordPendingAuth(storagePath, auth) {
79
79
  }
80
80
  const prior = hash(auth.priorRefreshToken);
81
81
  const entries = (await read(path)).filter((entry) => entry.prior !== prior && entry.prior !== hash(auth.refreshToken));
82
- // Rotating a token that is itself only journaled: the row on disk still holds
83
- // the original spent token, so extend that entry rather than add one that
84
- // no disk row can match.
82
+ // Rotating a token that is itself only journaled: extend that entry to the
83
+ // newest credential, but ALSO keep an entry keyed by the token this
84
+ // rotation just spent. A backup slot or WAL can still hold that
85
+ // intermediate token, and pruning now keeps entries whose spent token any
86
+ // recovery source can surface — a stale-snapshot restore then self-heals
87
+ // straight to the newest credential instead of reviving a spent token.
85
88
  const chained = entries.find((entry) => entry.refreshToken === auth.priorRefreshToken);
86
- const current = chained ?? { prior, recordId: auth.recordId, refreshToken: auth.refreshToken, accessToken: auth.accessToken, expiresAt: auth.expiresAt, at: auth.at };
87
89
  if (chained)
88
90
  Object.assign(chained, { recordId: chained.recordId ?? auth.recordId, refreshToken: auth.refreshToken, accessToken: auth.accessToken, expiresAt: auth.expiresAt, at: auth.at });
89
- else
90
- entries.push(current);
91
+ const current = { prior, recordId: auth.recordId, refreshToken: auth.refreshToken, accessToken: auth.accessToken, expiresAt: auth.expiresAt, at: auth.at };
92
+ entries.push(current);
91
93
  const excess = entries.length - MAX_ENTRIES;
92
94
  if (excess > 0) {
93
95
  // Drop the oldest other entries, never the credential being recorded now.
@@ -134,14 +136,34 @@ export async function applyPendingAuth(storagePath, storage) {
134
136
  }
135
137
  return storage;
136
138
  }
137
- /** After a save, drop entries whose spent token is no longer on disk (persisted or superseded). */
138
- export async function prunePendingAuth(storagePath, saved) {
139
+ /**
140
+ * After a save, drop entries whose spent token is no longer in any restorable
141
+ * state. `saved` is the primary content just persisted; callers may also
142
+ * supply `restorableRefreshTokens` — refresh tokens still present in recovery
143
+ * sources (rotating/discovered backups, a WAL payload) — since a restore can
144
+ * resurface a spent token, its entry must survive until no source holds it.
145
+ * A scan failure keeps every entry: retention is the safe direction.
146
+ */
147
+ export async function prunePendingAuth(storagePath, saved, restorableRefreshTokens) {
139
148
  const path = getPendingAuthPath(storagePath);
140
149
  const current = await read(path);
141
150
  if (!current.length)
142
151
  return;
143
152
  await withFileTransactionLock(path, async () => {
144
153
  const spent = new Set(saved.accounts.map((account) => hash(account.refreshToken)));
154
+ if (restorableRefreshTokens) {
155
+ try {
156
+ for (const token of await restorableRefreshTokens()) {
157
+ spent.add(hash(token));
158
+ }
159
+ }
160
+ catch (error) {
161
+ log.warn("Recovery-source scan failed; keeping all pending rotated credentials", {
162
+ code: typeof error.code === "string" && /^[A-Z_]{1,40}$/.test(error.code ?? "") ? error.code : "UNKNOWN",
163
+ });
164
+ return;
165
+ }
166
+ }
145
167
  const entries = await read(path);
146
168
  const remaining = entries.filter((entry) => spent.has(entry.prior));
147
169
  if (remaining.length !== entries.length)
@@ -7,4 +7,3 @@ export declare function loadNormalizedStorageFromPath(path: string, label: strin
7
7
  logWarn: (message: string, details: Record<string, unknown>) => void;
8
8
  }): Promise<AccountStorageV3 | null>;
9
9
  export declare function mergeStorageForMigration(current: AccountStorageV3 | null, incoming: AccountStorageV3, normalizeAccountStorage: (value: unknown) => AccountStorageV3 | null, findMatchingAccountIndex: (accounts: AccountStorageV3["accounts"], candidate: AccountStorageV3["accounts"][number]) => number | undefined): AccountStorageV3;
10
- //# sourceMappingURL=project-migration.d.ts.map
@@ -86,4 +86,3 @@ export interface FlaggedAccountStorageV1 {
86
86
  version: 1;
87
87
  accounts: FlaggedAccountMetadataV1[];
88
88
  }
89
- //# sourceMappingURL=public-types.d.ts.map
@@ -13,4 +13,3 @@ export declare function resolveAccountRecordId(account: {
13
13
  refreshToken: string;
14
14
  addedAt: number;
15
15
  }): string;
16
- //# sourceMappingURL=record-identity.d.ts.map
@@ -1,3 +1,2 @@
1
1
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
2
  export declare function clampIndex(index: number, length: number): number;
3
- //# sourceMappingURL=record-utils.d.ts.map
@@ -4,4 +4,3 @@ export declare function buildRestoreAssessment(params: {
4
4
  backupMetadata: BackupMetadata;
5
5
  hasResetMarker: boolean;
6
6
  }): RestoreAssessment;
7
- //# sourceMappingURL=restore-assessment.d.ts.map
@@ -21,4 +21,3 @@ export declare function restoreAccountsFromBackupEntry(params: {
21
21
  }>;
22
22
  saveAccounts: (storage: AccountStorageV3) => Promise<void>;
23
23
  }): Promise<AccountStorageV3>;
24
- //# sourceMappingURL=restore-backup-entry.d.ts.map
@@ -7,4 +7,3 @@ type AccountStorageWithMetadata = AccountStorageV3 & {
7
7
  export declare function createEmptyStorageWithRestoreMetadata(restoreEligible: boolean, restoreReason: RestoreReason): AccountStorageWithMetadata;
8
8
  export declare function withRestoreMetadata(storage: AccountStorageV3, restoreEligible: boolean, restoreReason: RestoreReason): AccountStorageWithMetadata;
9
9
  export {};
10
- //# sourceMappingURL=restore-metadata.d.ts.map
@@ -7,4 +7,3 @@
7
7
  import type { AccountStorageV3 } from "./public-types.js";
8
8
  export declare function isRetryableStorageWriteError(error: unknown): boolean;
9
9
  export declare function saveAccountsWithRetry(storage: AccountStorageV3, saveAccounts: (storage: AccountStorageV3) => Promise<void>): Promise<void>;
10
- //# sourceMappingURL=save-retry.d.ts.map
@@ -19,4 +19,3 @@ export declare function describeFlaggedSnapshot(path: string, kind: BackupSnapsh
19
19
  loadFlaggedAccountsFromPath: (path: string) => Promise<FlaggedAccountStorageV1>;
20
20
  logWarn?: (message: string, meta: Record<string, unknown>) => void;
21
21
  }): Promise<BackupSnapshotMetadata>;
22
- //# sourceMappingURL=snapshot-inspectors.d.ts.map
@@ -13,4 +13,3 @@ export declare function mergeAccountSnapshot(base: AccountStorageV3 | null, curr
13
13
  /** Save observations independently of unresolved user-edit conflicts. Inventory stays disk-owned. */
14
14
  export declare function mergeAccountRuntimeObservations(base: AccountStorageV3, current: AccountStorageV3, local: AccountStorageV3): AccountStorageV3;
15
15
  export {};
16
- //# sourceMappingURL=snapshot-merge.d.ts.map
@@ -28,5 +28,6 @@ export declare function loadAccountsFromPath(path: string, deps: {
28
28
  normalized: AccountStorageV3 | null;
29
29
  storedVersion: unknown;
30
30
  schemaErrors: string[];
31
+ /** sha256 of the exact bytes read, for cheap unchanged-content checks. */
32
+ contentSha256: string;
31
33
  }>;
32
- //# sourceMappingURL=storage-parser.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { AnyAccountStorageSchema, getValidationErrors, safeParseJson, } from "../schemas.js";
3
3
  import { withFileOperationRetry } from "../fs-retry.js";
4
+ import { computeSha256 } from "./hash.js";
4
5
  export function parseAndNormalizeStorage(data, normalizeAccountStorage, isRecord) {
5
6
  const schemaErrors = getValidationErrors(AnyAccountStorageSchema, data);
6
7
  const normalized = normalizeAccountStorage(data);
@@ -33,18 +34,25 @@ export async function loadAccountsFromPath(path, deps) {
33
34
  // unchanged; JSON.parse runs outside the retry, so the SyntaxError → recovery
34
35
  // contract documented above is also preserved.
35
36
  const content = await withFileOperationRetry(() => fs.readFile(path, "utf-8"));
37
+ const contentSha256 = computeSha256(content);
36
38
  // Run the Zod-guarded JSON boundary first. Returns null on either a
37
39
  // `SyntaxError` or a schema mismatch; we disambiguate below so the
38
40
  // `SyntaxError` contract keeps propagating to callers that rely on it
39
41
  // for WAL / backup recovery.
40
42
  const validated = safeParseJson(content, AnyAccountStorageSchema, "storage-parser.loadAccountsFromPath");
41
43
  if (validated !== null) {
42
- return parseAndNormalizeStorage(validated, deps.normalizeAccountStorage, deps.isRecord);
44
+ return {
45
+ ...parseAndNormalizeStorage(validated, deps.normalizeAccountStorage, deps.isRecord),
46
+ contentSha256,
47
+ };
43
48
  }
44
49
  // `validated === null`: either SyntaxError or schema mismatch.
45
50
  // A raw JSON.parse distinguishes them: SyntaxError propagates to preserve
46
51
  // existing recovery semantics; otherwise fall through to the TS normalizer
47
52
  // (legacy unknown-shape path, surfaced via `schemaErrors`).
48
53
  const data = JSON.parse(content);
49
- return parseAndNormalizeStorage(data, deps.normalizeAccountStorage, deps.isRecord);
54
+ return {
55
+ ...parseAndNormalizeStorage(data, deps.normalizeAccountStorage, deps.isRecord),
56
+ contentSha256,
57
+ };
50
58
  }
@@ -29,4 +29,3 @@ export declare function withAccountAndFlaggedStorageTransaction<T>(handler: (cur
29
29
  cloneAccountStorageForPersistence: (storage: AccountStorageV3 | null | undefined) => AccountStorageV3;
30
30
  logRollbackError: (error: unknown, rollbackError: unknown) => void;
31
31
  }): Promise<T>;
32
- //# sourceMappingURL=transactions.d.ts.map
@@ -156,7 +156,21 @@ export declare function withFlaggedStorageTransaction<T>(handler: (current: Flag
156
156
  * @param storage - Account storage data to save
157
157
  * @throws StorageError with platform-aware hints on failure
158
158
  */
159
- /** Clone a loaded working snapshot without losing its optimistic-concurrency baseline. */
159
+ /**
160
+ * Clone a loaded working snapshot without losing its optimistic-concurrency baseline.
161
+ *
162
+ * Baseline contract: `saveAccounts` only runs the three-way merge
163
+ * (`mergeAccountSnapshot` against a fresh `loadPrimaryAccountsForMerge` read)
164
+ * when the saved object is registered in `loadedAccountSnapshots` — and that
165
+ * WeakMap is keyed by OBJECT IDENTITY, so registration does not survive
166
+ * `structuredClone`, `JSON.parse(JSON.stringify(...))`, spread copies, or
167
+ * manual reconstruction. A baseline-less save writes the object as-is
168
+ * (last-writer-wins): concurrent changes are neither merged nor flagged with
169
+ * ESTALE, and when the primary on disk differs from the state this process
170
+ * last read or wrote the save additionally emits a one-time baseline-less
171
+ * overwrite warning. Use this helper — or mutate the loaded object in place —
172
+ * to keep the merge armed.
173
+ */
160
174
  export declare function cloneTrackedAccountStorage(storage: AccountStorageV3): AccountStorageV3;
161
175
  export declare function saveAccounts(storage: AccountStorageV3): Promise<void>;
162
176
  /**
@@ -185,4 +199,3 @@ export declare function importAccounts(filePath: string): Promise<{
185
199
  total: number;
186
200
  skipped: number;
187
201
  }>;
188
- //# sourceMappingURL=storage.d.ts.map