codex-multi-auth 2.3.2 → 2.4.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 (327) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +4 -1
  3. package/dist/lib/accounts.d.ts +14 -0
  4. package/dist/lib/accounts.d.ts.map +1 -1
  5. package/dist/lib/accounts.js +59 -4
  6. package/dist/lib/constants.d.ts +7 -0
  7. package/dist/lib/constants.d.ts.map +1 -1
  8. package/dist/lib/constants.js +7 -0
  9. package/dist/lib/forecast.d.ts.map +1 -1
  10. package/dist/lib/forecast.js +15 -6
  11. package/dist/lib/local-client-tokens.d.ts.map +1 -1
  12. package/dist/lib/local-client-tokens.js +11 -4
  13. package/dist/lib/logger.d.ts.map +1 -1
  14. package/dist/lib/logger.js +5 -0
  15. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
  16. package/dist/lib/preemptive-quota-scheduler.js +29 -3
  17. package/dist/lib/refresh-lease.d.ts +1 -0
  18. package/dist/lib/refresh-lease.d.ts.map +1 -1
  19. package/dist/lib/refresh-lease.js +35 -4
  20. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  21. package/dist/lib/request/fetch-helpers.js +3 -2
  22. package/dist/lib/request/rate-limit-decision.d.ts.map +1 -1
  23. package/dist/lib/request/rate-limit-decision.js +7 -2
  24. package/dist/lib/request/response-handler.d.ts.map +1 -1
  25. package/dist/lib/request/response-handler.js +63 -11
  26. package/dist/lib/runtime/rotation-proxy-state.d.ts +7 -0
  27. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  28. package/dist/lib/runtime/rotation-server-types.d.ts +9 -0
  29. package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
  30. package/dist/lib/runtime-rotation-proxy.d.ts +10 -0
  31. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  32. package/dist/lib/runtime-rotation-proxy.js +38 -1
  33. package/dist/lib/schemas.js +2 -2
  34. package/dist/lib/storage.d.ts.map +1 -1
  35. package/dist/lib/storage.js +6 -1
  36. package/package.json +1 -1
  37. package/scripts/codex.js +189 -8
  38. package/dist/index.js.map +0 -1
  39. package/dist/lib/account-policy.js.map +0 -1
  40. package/dist/lib/accounts/rate-limits.js.map +0 -1
  41. package/dist/lib/accounts.js.map +0 -1
  42. package/dist/lib/audit.js.map +0 -1
  43. package/dist/lib/auth/auth.js.map +0 -1
  44. package/dist/lib/auth/browser.js.map +0 -1
  45. package/dist/lib/auth/device-auth.js.map +0 -1
  46. package/dist/lib/auth/index.js.map +0 -1
  47. package/dist/lib/auth/org-override.js.map +0 -1
  48. package/dist/lib/auth/server.js.map +0 -1
  49. package/dist/lib/auth/token-utils.js.map +0 -1
  50. package/dist/lib/auth-rate-limit.js.map +0 -1
  51. package/dist/lib/budget-guard.js.map +0 -1
  52. package/dist/lib/capability-policy.js.map +0 -1
  53. package/dist/lib/circuit-breaker.js.map +0 -1
  54. package/dist/lib/cli.js.map +0 -1
  55. package/dist/lib/codex-cli/index.js.map +0 -1
  56. package/dist/lib/codex-cli/observability.js.map +0 -1
  57. package/dist/lib/codex-cli/state.js.map +0 -1
  58. package/dist/lib/codex-cli/sync.js.map +0 -1
  59. package/dist/lib/codex-cli/writer.js.map +0 -1
  60. package/dist/lib/codex-manager/account-manager-commands.js.map +0 -1
  61. package/dist/lib/codex-manager/account-pool-write.js.map +0 -1
  62. package/dist/lib/codex-manager/backend-category-entry.js.map +0 -1
  63. package/dist/lib/codex-manager/backend-category-helpers.js.map +0 -1
  64. package/dist/lib/codex-manager/backend-category-prompt.js.map +0 -1
  65. package/dist/lib/codex-manager/backend-settings-controller.js.map +0 -1
  66. package/dist/lib/codex-manager/backend-settings-entry.js.map +0 -1
  67. package/dist/lib/codex-manager/backend-settings-helpers.js.map +0 -1
  68. package/dist/lib/codex-manager/backend-settings-prompt.js.map +0 -1
  69. package/dist/lib/codex-manager/backend-settings-schema.js.map +0 -1
  70. package/dist/lib/codex-manager/behavior-settings-panel.js.map +0 -1
  71. package/dist/lib/codex-manager/commands/account.js.map +0 -1
  72. package/dist/lib/codex-manager/commands/best.js.map +0 -1
  73. package/dist/lib/codex-manager/commands/bridge.js.map +0 -1
  74. package/dist/lib/codex-manager/commands/budget.js.map +0 -1
  75. package/dist/lib/codex-manager/commands/check.js.map +0 -1
  76. package/dist/lib/codex-manager/commands/config-explain.js.map +0 -1
  77. package/dist/lib/codex-manager/commands/debug-bundle.js.map +0 -1
  78. package/dist/lib/codex-manager/commands/forecast.js.map +0 -1
  79. package/dist/lib/codex-manager/commands/init-config.js.map +0 -1
  80. package/dist/lib/codex-manager/commands/integrations.js.map +0 -1
  81. package/dist/lib/codex-manager/commands/models.js.map +0 -1
  82. package/dist/lib/codex-manager/commands/monitor.js.map +0 -1
  83. package/dist/lib/codex-manager/commands/report.js.map +0 -1
  84. package/dist/lib/codex-manager/commands/rotation.js.map +0 -1
  85. package/dist/lib/codex-manager/commands/status.js.map +0 -1
  86. package/dist/lib/codex-manager/commands/switch.js.map +0 -1
  87. package/dist/lib/codex-manager/commands/uninstall.js.map +0 -1
  88. package/dist/lib/codex-manager/commands/unpin.js.map +0 -1
  89. package/dist/lib/codex-manager/commands/usage.js.map +0 -1
  90. package/dist/lib/codex-manager/commands/verify.js.map +0 -1
  91. package/dist/lib/codex-manager/commands/why-selected.js.map +0 -1
  92. package/dist/lib/codex-manager/commands/workspace.js.map +0 -1
  93. package/dist/lib/codex-manager/dashboard-display-panel.js.map +0 -1
  94. package/dist/lib/codex-manager/dashboard-formatters.js.map +0 -1
  95. package/dist/lib/codex-manager/dashboard-settings-controller.js.map +0 -1
  96. package/dist/lib/codex-manager/dashboard-settings-data.js.map +0 -1
  97. package/dist/lib/codex-manager/dashboard-settings-entry.js.map +0 -1
  98. package/dist/lib/codex-manager/experimental-settings-entry.js.map +0 -1
  99. package/dist/lib/codex-manager/experimental-settings-prompt.js.map +0 -1
  100. package/dist/lib/codex-manager/experimental-settings-schema.js.map +0 -1
  101. package/dist/lib/codex-manager/experimental-sync-target-entry.js.map +0 -1
  102. package/dist/lib/codex-manager/experimental-sync-target.js.map +0 -1
  103. package/dist/lib/codex-manager/forecast-report-commands.d.ts +0 -54
  104. package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +0 -1
  105. package/dist/lib/codex-manager/forecast-report-commands.js +0 -443
  106. package/dist/lib/codex-manager/forecast-report-commands.js.map +0 -1
  107. package/dist/lib/codex-manager/forecast-report-shared.js.map +0 -1
  108. package/dist/lib/codex-manager/help.js.map +0 -1
  109. package/dist/lib/codex-manager/manual-callback.js.map +0 -1
  110. package/dist/lib/codex-manager/rate-limit-markers.js.map +0 -1
  111. package/dist/lib/codex-manager/repair-commands.js.map +0 -1
  112. package/dist/lib/codex-manager/settings-hub/backend.js.map +0 -1
  113. package/dist/lib/codex-manager/settings-hub/dashboard.js.map +0 -1
  114. package/dist/lib/codex-manager/settings-hub/experimental.js.map +0 -1
  115. package/dist/lib/codex-manager/settings-hub/index.js.map +0 -1
  116. package/dist/lib/codex-manager/settings-hub/shared.js.map +0 -1
  117. package/dist/lib/codex-manager/settings-hub-entry.js.map +0 -1
  118. package/dist/lib/codex-manager/settings-hub-menu.js.map +0 -1
  119. package/dist/lib/codex-manager/settings-hub-prompt.js.map +0 -1
  120. package/dist/lib/codex-manager/settings-hub.js.map +0 -1
  121. package/dist/lib/codex-manager/settings-panels.js.map +0 -1
  122. package/dist/lib/codex-manager/settings-persist-utils.js.map +0 -1
  123. package/dist/lib/codex-manager/settings-preview.js.map +0 -1
  124. package/dist/lib/codex-manager/settings-write-queue.js.map +0 -1
  125. package/dist/lib/codex-manager/statusline-order.d.ts +0 -3
  126. package/dist/lib/codex-manager/statusline-order.d.ts.map +0 -1
  127. package/dist/lib/codex-manager/statusline-order.js +0 -16
  128. package/dist/lib/codex-manager/statusline-order.js.map +0 -1
  129. package/dist/lib/codex-manager/statusline-settings-panel.js.map +0 -1
  130. package/dist/lib/codex-manager/theme-settings-panel.js.map +0 -1
  131. package/dist/lib/codex-manager/unified-settings-controller.js.map +0 -1
  132. package/dist/lib/codex-manager/unified-settings-entry.js.map +0 -1
  133. package/dist/lib/codex-manager.js.map +0 -1
  134. package/dist/lib/config.js.map +0 -1
  135. package/dist/lib/constants.js.map +0 -1
  136. package/dist/lib/context-overflow.js.map +0 -1
  137. package/dist/lib/dashboard-settings.js.map +0 -1
  138. package/dist/lib/entitlement-cache.js.map +0 -1
  139. package/dist/lib/env-parsing.js.map +0 -1
  140. package/dist/lib/errors.js.map +0 -1
  141. package/dist/lib/forecast.js.map +0 -1
  142. package/dist/lib/fs-retry.js.map +0 -1
  143. package/dist/lib/health.js.map +0 -1
  144. package/dist/lib/index.js.map +0 -1
  145. package/dist/lib/integration-generators.js.map +0 -1
  146. package/dist/lib/live-account-sync.js.map +0 -1
  147. package/dist/lib/local-bridge.js.map +0 -1
  148. package/dist/lib/local-client-tokens.js.map +0 -1
  149. package/dist/lib/logger.js.map +0 -1
  150. package/dist/lib/model-capability-matrix.js.map +0 -1
  151. package/dist/lib/named-backup-export.js.map +0 -1
  152. package/dist/lib/oc-chatgpt-import-adapter.js.map +0 -1
  153. package/dist/lib/oc-chatgpt-orchestrator.js.map +0 -1
  154. package/dist/lib/oc-chatgpt-target-detection.js.map +0 -1
  155. package/dist/lib/parallel-probe.js.map +0 -1
  156. package/dist/lib/policy/runtime-policy.js.map +0 -1
  157. package/dist/lib/preemptive-quota-scheduler.js.map +0 -1
  158. package/dist/lib/proactive-refresh.js.map +0 -1
  159. package/dist/lib/prompts/codex-host-bridge.js.map +0 -1
  160. package/dist/lib/prompts/codex.js.map +0 -1
  161. package/dist/lib/prompts/fetch-utils.js.map +0 -1
  162. package/dist/lib/prompts/host-codex-prompt.js.map +0 -1
  163. package/dist/lib/quota-cache.js.map +0 -1
  164. package/dist/lib/quota-probe.js.map +0 -1
  165. package/dist/lib/quota-readiness.js.map +0 -1
  166. package/dist/lib/recovery/constants.js.map +0 -1
  167. package/dist/lib/recovery/index.d.ts +0 -12
  168. package/dist/lib/recovery/index.d.ts.map +0 -1
  169. package/dist/lib/recovery/index.js +0 -11
  170. package/dist/lib/recovery/index.js.map +0 -1
  171. package/dist/lib/recovery/storage.js.map +0 -1
  172. package/dist/lib/recovery/types.js.map +0 -1
  173. package/dist/lib/recovery.js.map +0 -1
  174. package/dist/lib/refresh-guardian.js.map +0 -1
  175. package/dist/lib/refresh-lease.js.map +0 -1
  176. package/dist/lib/refresh-queue.js.map +0 -1
  177. package/dist/lib/request/failover-config.js.map +0 -1
  178. package/dist/lib/request/failure-policy.js.map +0 -1
  179. package/dist/lib/request/fetch-helpers.js.map +0 -1
  180. package/dist/lib/request/helpers/input-utils.js.map +0 -1
  181. package/dist/lib/request/helpers/model-map.js.map +0 -1
  182. package/dist/lib/request/helpers/tool-utils.js.map +0 -1
  183. package/dist/lib/request/index.js.map +0 -1
  184. package/dist/lib/request/rate-limit-backoff.js.map +0 -1
  185. package/dist/lib/request/request-attempt-budget.js.map +0 -1
  186. package/dist/lib/request/request-init.js.map +0 -1
  187. package/dist/lib/request/request-resilience.js.map +0 -1
  188. package/dist/lib/request/request-transformer.js.map +0 -1
  189. package/dist/lib/request/response-compaction.js.map +0 -1
  190. package/dist/lib/request/response-handler.js.map +0 -1
  191. package/dist/lib/request/response-metadata.js.map +0 -1
  192. package/dist/lib/request/stream-failover.js.map +0 -1
  193. package/dist/lib/request/wait-utils.js.map +0 -1
  194. package/dist/lib/rotation.js.map +0 -1
  195. package/dist/lib/routing-mutex.js.map +0 -1
  196. package/dist/lib/routing-profiles.js.map +0 -1
  197. package/dist/lib/runtime/account-check-helpers.js.map +0 -1
  198. package/dist/lib/runtime/account-check-types.js.map +0 -1
  199. package/dist/lib/runtime/account-check.js.map +0 -1
  200. package/dist/lib/runtime/account-health-check.d.ts +0 -8
  201. package/dist/lib/runtime/account-health-check.d.ts.map +0 -1
  202. package/dist/lib/runtime/account-health-check.js +0 -29
  203. package/dist/lib/runtime/account-health-check.js.map +0 -1
  204. package/dist/lib/runtime/account-manager-cache-entry.js.map +0 -1
  205. package/dist/lib/runtime/account-manager-cache.js.map +0 -1
  206. package/dist/lib/runtime/account-pool.js.map +0 -1
  207. package/dist/lib/runtime/account-scope.js.map +0 -1
  208. package/dist/lib/runtime/account-select-event.js.map +0 -1
  209. package/dist/lib/runtime/account-selection.js.map +0 -1
  210. package/dist/lib/runtime/account-state.js.map +0 -1
  211. package/dist/lib/runtime/account-status.js.map +0 -1
  212. package/dist/lib/runtime/account-storage-scope-entry.js.map +0 -1
  213. package/dist/lib/runtime/app-bind.js.map +0 -1
  214. package/dist/lib/runtime/auth-facade.js.map +0 -1
  215. package/dist/lib/runtime/browser-oauth-flow.js.map +0 -1
  216. package/dist/lib/runtime/capability-boost.js.map +0 -1
  217. package/dist/lib/runtime/config-toml.js.map +0 -1
  218. package/dist/lib/runtime/event-handler.js.map +0 -1
  219. package/dist/lib/runtime/flagged-verify-types.js.map +0 -1
  220. package/dist/lib/runtime/hydrate-emails.js.map +0 -1
  221. package/dist/lib/runtime/live-sync-entry.js.map +0 -1
  222. package/dist/lib/runtime/live-sync.js.map +0 -1
  223. package/dist/lib/runtime/loader-setup.js.map +0 -1
  224. package/dist/lib/runtime/login-menu-accounts.js.map +0 -1
  225. package/dist/lib/runtime/manual-oauth-flow.js.map +0 -1
  226. package/dist/lib/runtime/oauth-browser-flow.d.ts +0 -9
  227. package/dist/lib/runtime/oauth-browser-flow.d.ts.map +0 -1
  228. package/dist/lib/runtime/oauth-browser-flow.js +0 -33
  229. package/dist/lib/runtime/oauth-browser-flow.js.map +0 -1
  230. package/dist/lib/runtime/preemptive-quota.js.map +0 -1
  231. package/dist/lib/runtime/quota-headers.js.map +0 -1
  232. package/dist/lib/runtime/quota-probe.js.map +0 -1
  233. package/dist/lib/runtime/quota-settings.js.map +0 -1
  234. package/dist/lib/runtime/refresh-guardian-entry.js.map +0 -1
  235. package/dist/lib/runtime/refresh-guardian.js.map +0 -1
  236. package/dist/lib/runtime/request-init.js.map +0 -1
  237. package/dist/lib/runtime/runtime-current-account.js.map +0 -1
  238. package/dist/lib/runtime/runtime-observability.js.map +0 -1
  239. package/dist/lib/runtime/runtime-services.js.map +0 -1
  240. package/dist/lib/runtime/session-affinity-entry.js.map +0 -1
  241. package/dist/lib/runtime/session-affinity.d.ts +0 -13
  242. package/dist/lib/runtime/session-affinity.d.ts.map +0 -1
  243. package/dist/lib/runtime/session-affinity.js +0 -16
  244. package/dist/lib/runtime/session-affinity.js.map +0 -1
  245. package/dist/lib/runtime/session-recovery.js.map +0 -1
  246. package/dist/lib/runtime/status-marker.js.map +0 -1
  247. package/dist/lib/runtime/storage-scope.js.map +0 -1
  248. package/dist/lib/runtime/toast.js.map +0 -1
  249. package/dist/lib/runtime/ui-runtime-entry.js.map +0 -1
  250. package/dist/lib/runtime/ui-runtime.js.map +0 -1
  251. package/dist/lib/runtime/verify-flagged.js.map +0 -1
  252. package/dist/lib/runtime-constants.js.map +0 -1
  253. package/dist/lib/runtime-paths.js.map +0 -1
  254. package/dist/lib/runtime-rotation-proxy.js.map +0 -1
  255. package/dist/lib/schemas.js.map +0 -1
  256. package/dist/lib/session-affinity.js.map +0 -1
  257. package/dist/lib/shutdown.js.map +0 -1
  258. package/dist/lib/storage/account-clear-entry.js.map +0 -1
  259. package/dist/lib/storage/account-clear.js.map +0 -1
  260. package/dist/lib/storage/account-match-utils.js.map +0 -1
  261. package/dist/lib/storage/account-persistence.js.map +0 -1
  262. package/dist/lib/storage/account-port.js.map +0 -1
  263. package/dist/lib/storage/account-save-entry.js.map +0 -1
  264. package/dist/lib/storage/account-save.js.map +0 -1
  265. package/dist/lib/storage/account-snapshot.js.map +0 -1
  266. package/dist/lib/storage/backup-metadata-builder.js.map +0 -1
  267. package/dist/lib/storage/backup-metadata.js.map +0 -1
  268. package/dist/lib/storage/backup-paths.js.map +0 -1
  269. package/dist/lib/storage/backup-restore.js.map +0 -1
  270. package/dist/lib/storage/cache-artifacts.js.map +0 -1
  271. package/dist/lib/storage/error-hints.js.map +0 -1
  272. package/dist/lib/storage/file-paths.js.map +0 -1
  273. package/dist/lib/storage/fixture-guards.js.map +0 -1
  274. package/dist/lib/storage/flagged-entry.js.map +0 -1
  275. package/dist/lib/storage/flagged-load-entry.js.map +0 -1
  276. package/dist/lib/storage/flagged-save-entry.js.map +0 -1
  277. package/dist/lib/storage/flagged-storage-file.js.map +0 -1
  278. package/dist/lib/storage/flagged-storage-io.js.map +0 -1
  279. package/dist/lib/storage/flagged-storage.js.map +0 -1
  280. package/dist/lib/storage/gitignore.js.map +0 -1
  281. package/dist/lib/storage/hash.js.map +0 -1
  282. package/dist/lib/storage/health.js.map +0 -1
  283. package/dist/lib/storage/identity.js.map +0 -1
  284. package/dist/lib/storage/import-export.js.map +0 -1
  285. package/dist/lib/storage/metadata-section.js.map +0 -1
  286. package/dist/lib/storage/migrations.js.map +0 -1
  287. package/dist/lib/storage/named-backup-entry.js.map +0 -1
  288. package/dist/lib/storage/named-backups-entry.js.map +0 -1
  289. package/dist/lib/storage/named-backups.js.map +0 -1
  290. package/dist/lib/storage/path-state.js.map +0 -1
  291. package/dist/lib/storage/paths.js.map +0 -1
  292. package/dist/lib/storage/project-migration.js.map +0 -1
  293. package/dist/lib/storage/record-utils.js.map +0 -1
  294. package/dist/lib/storage/restore-assessment.js.map +0 -1
  295. package/dist/lib/storage/restore-backup-entry.js.map +0 -1
  296. package/dist/lib/storage/restore-metadata.js.map +0 -1
  297. package/dist/lib/storage/restore.d.ts +0 -14
  298. package/dist/lib/storage/restore.d.ts.map +0 -1
  299. package/dist/lib/storage/restore.js +0 -52
  300. package/dist/lib/storage/restore.js.map +0 -1
  301. package/dist/lib/storage/snapshot-inspectors.js.map +0 -1
  302. package/dist/lib/storage/storage-parser.js.map +0 -1
  303. package/dist/lib/storage/transactions.js.map +0 -1
  304. package/dist/lib/storage.js.map +0 -1
  305. package/dist/lib/table-formatter.js.map +0 -1
  306. package/dist/lib/tools/hashline-tools.js.map +0 -1
  307. package/dist/lib/types.js.map +0 -1
  308. package/dist/lib/ui/ansi.js.map +0 -1
  309. package/dist/lib/ui/auth-menu.js.map +0 -1
  310. package/dist/lib/ui/confirm.js.map +0 -1
  311. package/dist/lib/ui/copy.d.ts +0 -159
  312. package/dist/lib/ui/copy.d.ts.map +0 -1
  313. package/dist/lib/ui/copy.js +0 -164
  314. package/dist/lib/ui/copy.js.map +0 -1
  315. package/dist/lib/ui/display-width.js.map +0 -1
  316. package/dist/lib/ui/format.js.map +0 -1
  317. package/dist/lib/ui/runtime.js.map +0 -1
  318. package/dist/lib/ui/select.js.map +0 -1
  319. package/dist/lib/ui/theme.js.map +0 -1
  320. package/dist/lib/unified-settings.js.map +0 -1
  321. package/dist/lib/update-notice.js.map +0 -1
  322. package/dist/lib/usage/index.js.map +0 -1
  323. package/dist/lib/usage/ledger.js.map +0 -1
  324. package/dist/lib/usage/pricing.js.map +0 -1
  325. package/dist/lib/usage/redaction.js.map +0 -1
  326. package/dist/lib/usage/types.js.map +0 -1
  327. package/dist/lib/utils.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { createLogger, logRequest, LOGGING_ENABLED } from "../logger.js";
2
- import { PLUGIN_NAME } from "../constants.js";
2
+ import { HTTP_STATUS, PLUGIN_NAME } from "../constants.js";
3
3
  import { isRecord } from "../utils.js";
4
4
  const log = createLogger("response-handler");
5
5
  const MAX_SSE_SIZE = 10 * 1024 * 1024; // 10MB limit to prevent memory exhaustion
@@ -456,7 +456,23 @@ function maybeCaptureResponseEvent(state, data, onResponseId) {
456
456
  state.encounteredError = true;
457
457
  return;
458
458
  }
459
- if (data.type === "response.done" || data.type === "response.completed") {
459
+ // response.failed is a genuine terminal failure: the turn did not produce a
460
+ // usable result even though HTTP opened 200. Treat it as an error so the
461
+ // stream is not misclassified as a successful response and misattributed as
462
+ // an account success (stress audit H7).
463
+ if (data.type === "response.failed") {
464
+ log.warn("SSE terminal failure event received", { type: data.type });
465
+ state.encounteredError = true;
466
+ return;
467
+ }
468
+ // response.completed / response.done / response.incomplete are all terminal
469
+ // envelopes that carry a final response object. response.incomplete (e.g.
470
+ // hitting max_output_tokens or a content filter) is a NORMAL early stop whose
471
+ // partial output is the answer — it must be delivered to the client and
472
+ // counts as a healthy account, not a failure (re-audit correction to H7).
473
+ if (data.type === "response.done" ||
474
+ data.type === "response.completed" ||
475
+ data.type === "response.incomplete") {
460
476
  if (isRecord(data.response)) {
461
477
  state.finalResponse = { ...data.response };
462
478
  }
@@ -518,15 +534,19 @@ function parseSseStream(sseText, onResponseId) {
518
534
  let firstMalformedSample = null;
519
535
  for (const line of lines) {
520
536
  const trimmedLine = line.trim();
521
- if (trimmedLine.startsWith("data: ")) {
522
- const payload = trimmedLine.substring(6).trim();
537
+ // Accept "data:" with or without the optional trailing space. The SSE
538
+ // spec allows "data:value"; requiring the space silently dropped every
539
+ // event on an upstream/proxy formatting change (stress audit M1).
540
+ if (trimmedLine.startsWith("data:")) {
541
+ const payload = trimmedLine.substring(5).trim();
523
542
  if (!payload || payload === "[DONE]")
524
543
  continue;
525
544
  try {
526
545
  const data = JSON.parse(payload);
527
546
  maybeCaptureResponseEvent(state, data, onResponseId);
528
- if (state.encounteredError)
529
- return null;
547
+ if (state.encounteredError) {
548
+ return { finalResponse: null, encounteredError: true };
549
+ }
530
550
  }
531
551
  catch (error) {
532
552
  // AUDIT-H9 / H-03: previously these malformed chunks were
@@ -553,7 +573,10 @@ function parseSseStream(sseText, onResponseId) {
553
573
  firstSample: firstMalformedSample,
554
574
  });
555
575
  }
556
- return finalizeParsedResponse(state);
576
+ return {
577
+ finalResponse: finalizeParsedResponse(state),
578
+ encounteredError: state.encounteredError,
579
+ };
557
580
  }
558
581
  /**
559
582
  * Convert SSE stream response to JSON for generateText()
@@ -597,13 +620,42 @@ export async function convertSseToJson(response, headers, options) {
597
620
  logRequest("stream-full", { fullContent: fullText });
598
621
  }
599
622
  // Parse SSE events to extract the final response
600
- const finalResponse = parseSseStream(fullText, options?.onResponseId);
623
+ const { finalResponse, encounteredError } = parseSseStream(fullText, options?.onResponseId);
624
+ // A terminal failure event (mid-stream `error`, `response.failed`, or
625
+ // `response.incomplete`) means the upstream turn failed even though HTTP
626
+ // opened 200. Returning the raw SSE at 200 here makes the proxy record an
627
+ // account success and skip rotation/retry (stress audit H6+H7). Synthesize
628
+ // a non-2xx so the caller routes to failure. (A stream that simply yields
629
+ // no final response WITHOUT an error — empty/truncated — is left as the
630
+ // original passthrough below so the empty-response retry path still runs.)
631
+ if (encounteredError) {
632
+ log.warn("SSE stream ended with a terminal failure event");
633
+ logRequest("stream-error", {
634
+ error: "SSE stream terminated with an error/failed/incomplete event",
635
+ });
636
+ const upstreamFailed = typeof response.status === "number" && response.status >= 400;
637
+ const status = upstreamFailed ? response.status : HTTP_STATUS.BAD_GATEWAY;
638
+ const errorHeaders = new Headers(headers);
639
+ errorHeaders.set("content-type", "application/json; charset=utf-8");
640
+ return new Response(JSON.stringify({
641
+ error: {
642
+ message: "Upstream SSE stream terminated with a failure event",
643
+ type: "upstream_stream_error",
644
+ code: "sse_terminal_error",
645
+ },
646
+ }), {
647
+ status,
648
+ statusText: upstreamFailed ? response.statusText : "Bad Gateway",
649
+ headers: errorHeaders,
650
+ });
651
+ }
601
652
  if (!finalResponse) {
602
653
  log.warn("Could not find final response in SSE stream");
603
654
  logRequest("stream-error", {
604
655
  error: "No terminal response event found in SSE stream",
605
656
  });
606
- // Return original stream if we can't parse
657
+ // Return original stream if we can't parse (no error event, just no
658
+ // terminal response). Preserves the empty-response retry path.
607
659
  return new Response(fullText, {
608
660
  status: response.status,
609
661
  statusText: response.statusText,
@@ -650,9 +702,9 @@ function createResponseIdCapturingStream(body, onResponseId) {
650
702
  }
651
703
  for (const rawLine of lines) {
652
704
  const trimmedLine = rawLine.trim();
653
- if (!trimmedLine.startsWith("data: "))
705
+ if (!trimmedLine.startsWith("data:"))
654
706
  continue;
655
- const payload = trimmedLine.slice(6).trim();
707
+ const payload = trimmedLine.slice(5).trim();
656
708
  if (!payload || payload === "[DONE]")
657
709
  continue;
658
710
  try {
@@ -29,6 +29,13 @@ export interface RotationProxyStateInit {
29
29
  quotaRemainingPercentThreshold: number;
30
30
  sessionAffinityStore: SessionAffinityStore | null;
31
31
  lastObservedAffinityGeneration: number;
32
+ /**
33
+ * Ephemeral per-invocation account pin (0-based) or null. When a number,
34
+ * the request handler treats it exactly like a manual `switch` pin — a
35
+ * deterministic, fail-hard selection — but it is never written to disk, so
36
+ * concurrent invocations with different pins never interfere (issue #623).
37
+ */
38
+ forcedAccountIndex: number | null;
32
39
  }
33
40
  /**
34
41
  * Mutable rotation-loop state shared by every request of one proxy instance.
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
1
+ {"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
@@ -32,6 +32,15 @@ export interface RuntimeRotationProxyOptions {
32
32
  maxRequestBodyBytes?: number;
33
33
  fetchTimeoutMs?: number;
34
34
  streamStallTimeoutMs?: number;
35
+ /**
36
+ * Ephemeral, per-instance account pin (0-based) for a single invocation
37
+ * (issue #623: `codex-multi-auth-codex --account`). When set, this proxy
38
+ * routes every request to exactly this account and never rotates, without
39
+ * touching the persisted `switch` pin on disk. Falls back to
40
+ * `CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX` in the environment when omitted so
41
+ * the value survives the launcher -> detached app-helper process boundary.
42
+ */
43
+ forcedAccountIndex?: number | null;
35
44
  }
36
45
  export interface RequestContext {
37
46
  body: Buffer;
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;CAC5C;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;CAC5C;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
@@ -5,5 +5,15 @@ export type { PinnedUnavailableErrorBody } from "./request/rate-limit-decision.j
5
5
  export { chooseAccount } from "./runtime/rotation-account-selection.js";
6
6
  export { maybeInvalidateAffinityFromDisk, readPinnedAccountIndexFromDisk, readStorageMetaFromDisk, resetPinCacheForTesting, } from "./runtime/rotation-storage-meta.js";
7
7
  export type { StorageMeta } from "./runtime/rotation-storage-meta.js";
8
+ /**
9
+ * Coerce a forced-account pin from an option (number) or the launcher's env
10
+ * string into a normalized 0-based index, or null when absent/invalid. Invalid
11
+ * or negative values collapse to null so an out-of-range env value can never
12
+ * throw at proxy startup; chooseAccount reports the deterministic
13
+ * pinned-account failure per-request instead. Exported for unit tests.
14
+ *
15
+ * @internal
16
+ */
17
+ export declare function normalizeForcedAccountIndex(value: number | string | null | undefined): number | null;
8
18
  export declare function startRuntimeRotationProxy(options: RuntimeRotationProxyOptions): Promise<RuntimeRotationProxyServer>;
9
19
  //# sourceMappingURL=runtime-rotation-proxy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAwEA,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AA6etE,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA+IrC"}
1
+ {"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAwEA,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AA6etE;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA6JrC"}
@@ -420,6 +420,25 @@ function writePoolExhausted(params) {
420
420
  },
421
421
  });
422
422
  }
423
+ /**
424
+ * Coerce a forced-account pin from an option (number) or the launcher's env
425
+ * string into a normalized 0-based index, or null when absent/invalid. Invalid
426
+ * or negative values collapse to null so an out-of-range env value can never
427
+ * throw at proxy startup; chooseAccount reports the deterministic
428
+ * pinned-account failure per-request instead. Exported for unit tests.
429
+ *
430
+ * @internal
431
+ */
432
+ export function normalizeForcedAccountIndex(value) {
433
+ if (value === null || value === undefined) {
434
+ return null;
435
+ }
436
+ const parsed = typeof value === "number" ? value : Number.parseInt(value.trim(), 10);
437
+ if (!Number.isInteger(parsed) || parsed < 0) {
438
+ return null;
439
+ }
440
+ return parsed;
441
+ }
423
442
  export async function startRuntimeRotationProxy(options) {
424
443
  const pluginConfig = loadPluginConfig();
425
444
  const activeAccountManager = options.accountManager ?? (await AccountManager.loadFromDisk());
@@ -455,6 +474,17 @@ export async function startRuntimeRotationProxy(options) {
455
474
  throw new CodexValidationError("Runtime rotation proxy requires a clientApiKey.", { field: "clientApiKey", expected: "a non-empty string" });
456
475
  }
457
476
  const now = options.now ?? Date.now;
477
+ // Ephemeral per-invocation pin (issue #623). Prefer an explicit option (used by
478
+ // tests and the in-process inline proxy); otherwise fall back to the numeric env
479
+ // the launcher publishes, which is how the value reaches a detached app-helper
480
+ // process. `??` means both `undefined` and an explicit `null` option defer to the
481
+ // env, so "no option" and "no pin" behave identically. Invalid/negative values
482
+ // are ignored (treated as "no forced pin") rather than throwing — the launcher
483
+ // already validated the selector, and an out-of-range index is surfaced
484
+ // per-request as a deterministic pinned-account failure by chooseAccount rather
485
+ // than crashing proxy startup.
486
+ const forcedAccountIndex = normalizeForcedAccountIndex(options.forcedAccountIndex ??
487
+ process.env.CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX);
458
488
  const tokenRefreshSkewMs = getTokenRefreshSkewMs(pluginConfig);
459
489
  const networkErrorCooldownMs = getNetworkErrorCooldownMs(pluginConfig);
460
490
  const serverErrorCooldownMs = getServerErrorCooldownMs(pluginConfig);
@@ -500,6 +530,7 @@ export async function startRuntimeRotationProxy(options) {
500
530
  quotaRemainingPercentThreshold,
501
531
  sessionAffinityStore,
502
532
  lastObservedAffinityGeneration,
533
+ forcedAccountIndex,
503
534
  });
504
535
  const server = createServer((req, res) => {
505
536
  void handleRequest(state, req, res);
@@ -669,7 +700,13 @@ async function handleRequestInner(state, req, res, traceId) {
669
700
  // account. The proxy itself never bumps the generation, so its own
670
701
  // debounced disk writes do not clear affinity. See issue #474.
671
702
  const storageMeta = readStorageMetaFromDisk();
672
- const pinnedIndex = storageMeta.pinnedAccountIndex;
703
+ // The ephemeral --account pin (issue #623) takes precedence over the
704
+ // persisted `switch` pin for this invocation, without ever mutating disk
705
+ // state. Use `??` (not `||`) so a forced index of 0 is honored. When set,
706
+ // everything downstream — deterministic pick, no cursor advance, no
707
+ // stale-state recovery, the `codex_pinned_account_unavailable` failure —
708
+ // applies unchanged because it all keys off `pinnedIndex` / `isPinned`.
709
+ const pinnedIndex = state.forcedAccountIndex ?? storageMeta.pinnedAccountIndex;
673
710
  const isPinned = typeof pinnedIndex === "number";
674
711
  if (storageMeta.affinityGeneration > state.lastObservedAffinityGeneration) {
675
712
  state.sessionAffinityStore?.clearAll();
@@ -274,7 +274,7 @@ export const TokenSuccessSchema = z.object({
274
274
  type: z.literal("success"),
275
275
  access: z.string().min(1),
276
276
  refresh: z.string().min(1),
277
- expires: z.number(),
277
+ expires: z.number().int().positive(),
278
278
  idToken: z.string().optional(),
279
279
  multiAccount: z.boolean().optional(),
280
280
  });
@@ -303,7 +303,7 @@ export const TokenResultSchema = z.discriminatedUnion("type", [
303
303
  export const OAuthTokenResponseSchema = z.object({
304
304
  access_token: z.string().min(1),
305
305
  refresh_token: z.string().optional(),
306
- expires_in: z.number(),
306
+ expires_in: z.number().int().positive(),
307
307
  id_token: z.string().optional(),
308
308
  token_type: z.string().optional(),
309
309
  scope: z.string().optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../lib/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../lib/storage.ts"],"names":[],"mappings":"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"}
@@ -814,7 +814,12 @@ export function normalizeAccountStorage(data) {
814
814
  const baseStorage = fromVersion === 1
815
815
  ? migrateV1ToV3(data)
816
816
  : data;
817
- const validAccounts = rawAccounts.filter((account) => isRecord(account) &&
817
+ // Build from baseStorage.accounts (the migrated V3 shape), NOT the raw V1
818
+ // objects. Otherwise a V1->V3 upgrade discards migrateV1ToV3's per-account
819
+ // transforms — most importantly the scalar `rateLimitResetTime` -> map
820
+ // `rateLimitResetTimes` conversion — so a rate-limited V1 account would be
821
+ // treated as immediately available on upgrade and burst 429s (stress audit M3).
822
+ const validAccounts = baseStorage.accounts.filter((account) => isRecord(account) &&
818
823
  typeof account.refreshToken === "string" &&
819
824
  !!account.refreshToken.trim());
820
825
  const deduplicatedAccounts = deduplicateAccounts(validAccounts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-multi-auth",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/scripts/codex.js CHANGED
@@ -307,6 +307,172 @@ function findAccountIndexByIdOrEmail(accounts, id, email) {
307
307
  return -1;
308
308
  }
309
309
 
310
+ // --- Forced-account selection (`--account`, issue #623) ---------------------
311
+ //
312
+ // `codex-multi-auth-codex --account <selector>` (or the CODEX_MULTI_AUTH_FORCE_ACCOUNT
313
+ // env var) pins ONE account for a single invocation. The selector is resolved to a
314
+ // 0-based index here in the launcher and published as CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX,
315
+ // which the runtime rotation proxy consumes as an ephemeral pin — never touching the
316
+ // persisted `switch` pin on disk. The flag wins over the env var.
317
+ const FORCE_ACCOUNT_ENV = "CODEX_MULTI_AUTH_FORCE_ACCOUNT";
318
+ const FORCE_ACCOUNT_INDEX_ENV = "CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX";
319
+
320
+ /**
321
+ * Extract and strip `--account <sel>` / `--account=<sel>` from a forwarded arg
322
+ * list. The flag is a launcher concept; real Codex has no `--account`, so it must
323
+ * never be forwarded. Returns { selector, strippedArgs, error }; selector is null
324
+ * when the flag is absent. The last occurrence wins if repeated.
325
+ */
326
+ function extractForcedAccountFlag(args) {
327
+ const strippedArgs = [];
328
+ let selector = null;
329
+ let sawFlag = false;
330
+ for (let index = 0; index < args.length; index += 1) {
331
+ const arg = args[index];
332
+ if (arg === "--account") {
333
+ sawFlag = true;
334
+ const next = args[index + 1];
335
+ if (typeof next !== "string") {
336
+ return {
337
+ selector: null,
338
+ strippedArgs: args,
339
+ error:
340
+ "codex-multi-auth: --account requires a value (account index, email, or account id).",
341
+ };
342
+ }
343
+ selector = next;
344
+ index += 1;
345
+ continue;
346
+ }
347
+ if (typeof arg === "string" && arg.startsWith("--account=")) {
348
+ sawFlag = true;
349
+ selector = arg.slice("--account=".length);
350
+ continue;
351
+ }
352
+ strippedArgs.push(arg);
353
+ }
354
+ return { selector, strippedArgs, error: null, sawFlag };
355
+ }
356
+
357
+ /**
358
+ * Resolve the effective forced-account selector from args (flag) or environment,
359
+ * with the flag taking precedence. Returns { selector, strippedArgs, error };
360
+ * selector is null when neither source requests a forced account.
361
+ */
362
+ function resolveForcedAccountSelector(rawArgs, env = process.env) {
363
+ const { selector: flagSelector, strippedArgs, error, sawFlag } =
364
+ extractForcedAccountFlag(rawArgs);
365
+ if (error) {
366
+ return { selector: null, strippedArgs, error };
367
+ }
368
+ if (sawFlag) {
369
+ const trimmed = flagSelector.trim();
370
+ if (trimmed.length === 0) {
371
+ return {
372
+ selector: null,
373
+ strippedArgs,
374
+ error:
375
+ "codex-multi-auth: --account requires a non-empty value (account index, email, or account id).",
376
+ };
377
+ }
378
+ return { selector: trimmed, strippedArgs, error: null };
379
+ }
380
+ const envSelector = (env[FORCE_ACCOUNT_ENV] ?? "").trim();
381
+ if (envSelector.length > 0) {
382
+ return { selector: envSelector, strippedArgs, error: null };
383
+ }
384
+ return { selector: null, strippedArgs, error: null };
385
+ }
386
+
387
+ function formatForcedAccountList(accounts) {
388
+ const lines = accounts.map((account, index) => {
389
+ const email =
390
+ typeof account?.email === "string" && account.email.trim().length > 0
391
+ ? account.email.trim()
392
+ : `account ${index + 1}`;
393
+ return ` ${index + 1}. ${email}`;
394
+ });
395
+ return `Available accounts:\n${lines.join("\n")}`;
396
+ }
397
+
398
+ /**
399
+ * Resolve a forced-account selector to a 0-based index against the same scoped
400
+ * accounts pool the runtime rotation proxy will load. Selector forms: a 1-based
401
+ * integer index, an email, or an account id. Returns { ok, index } or
402
+ * { ok: false, error }.
403
+ */
404
+ async function resolveForcedAccountIndex(selector, env = process.env) {
405
+ const accountsDir = await resolveStatusAccountsDir(env);
406
+ const storage = readJsonFileQuiet(resolveAccountsPath(env, accountsDir));
407
+ const accounts = Array.isArray(storage?.accounts) ? storage.accounts : [];
408
+ if (accounts.length === 0) {
409
+ return {
410
+ ok: false,
411
+ error:
412
+ "codex-multi-auth: --account was set but no Codex accounts are configured. Run `codex-multi-auth login` first.",
413
+ };
414
+ }
415
+ if (/^\d+$/.test(selector)) {
416
+ const oneBased = Number.parseInt(selector, 10);
417
+ if (oneBased < 1 || oneBased > accounts.length) {
418
+ return {
419
+ ok: false,
420
+ error: `codex-multi-auth: --account ${selector} is out of range (have ${accounts.length} account${
421
+ accounts.length === 1 ? "" : "s"
422
+ }).\n${formatForcedAccountList(accounts)}`,
423
+ };
424
+ }
425
+ return { ok: true, index: oneBased - 1 };
426
+ }
427
+ const index = findAccountIndexByIdOrEmail(accounts, selector, selector);
428
+ if (index < 0) {
429
+ return {
430
+ ok: false,
431
+ error: `codex-multi-auth: --account "${selector}" did not match any configured account.\n${formatForcedAccountList(accounts)}`,
432
+ };
433
+ }
434
+ return { ok: true, index };
435
+ }
436
+
437
+ /**
438
+ * Preflight the `--account` flag / env var before forwarding to Codex. On success
439
+ * returns { forwardArgs } with the flag stripped and (when a forced account was
440
+ * requested) CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX published on the environment for
441
+ * the proxy to consume. On failure returns { error } so the caller can exit
442
+ * non-zero WITHOUT launching Codex — a forced account must never silently fall
443
+ * back to a different one.
444
+ */
445
+ async function applyForcedAccountSelection(rawArgs, env = process.env) {
446
+ const forced = resolveForcedAccountSelector(rawArgs, env);
447
+ if (forced.error) {
448
+ return { error: forced.error };
449
+ }
450
+ if (forced.selector === null) {
451
+ // Defensive: the resolved-index var is internal and only ever set below.
452
+ // Clear any stray value from the ambient environment so a request without
453
+ // --account can never inherit an unintended pin.
454
+ delete env[FORCE_ACCOUNT_INDEX_ENV];
455
+ return { forwardArgs: rawArgs };
456
+ }
457
+ // The pin can only be honored when the runtime rotation proxy is active for
458
+ // this command; otherwise the run would silently use a non-forced account,
459
+ // so fail hard instead of ignoring the flag.
460
+ if (!(await isRuntimeRotationProxyEnabled(forced.strippedArgs, env))) {
461
+ return {
462
+ error: [
463
+ "codex-multi-auth: --account requires the runtime rotation proxy, which is not active for this command.",
464
+ "Enable it with `codex-multi-auth rotation enable`, and make sure CODEX_MULTI_AUTH_BYPASS is not set.",
465
+ ].join("\n"),
466
+ };
467
+ }
468
+ const resolved = await resolveForcedAccountIndex(forced.selector, env);
469
+ if (!resolved.ok) {
470
+ return { error: resolved.error };
471
+ }
472
+ env[FORCE_ACCOUNT_INDEX_ENV] = String(resolved.index);
473
+ return { forwardArgs: forced.strippedArgs };
474
+ }
475
+
310
476
  function resolveModelFamilyForStatus(model) {
311
477
  const normalized = typeof model === "string" ? model.trim().toLowerCase() : "";
312
478
  if (normalized.startsWith("gpt-5.2")) return "gpt-5.2";
@@ -657,15 +823,20 @@ function maybeRefreshQuotaCacheInBackground(env = process.env) {
657
823
  // process. The close/error handlers below remove the lock dir, but if the parent
658
824
  // exits before the child settles they will NOT fire — in that case the lock is
659
825
  // reclaimed by the 10-minute stale-lock recovery in acquireStatusRefreshLock.
826
+ const refreshChildEnv = {
827
+ ...env,
828
+ CODEX_MULTI_AUTH_STATUS_REFRESH_CHILD: "1",
829
+ CODEX_MULTI_AUTH_STATUSLINE: "0",
830
+ };
831
+ // A forced-account pin is scoped to a single forwarded Codex run; this
832
+ // management child (`forecast`) must never inherit it, so it can never be
833
+ // coupled to a specific account if a future change routes it through the proxy.
834
+ delete refreshChildEnv[FORCE_ACCOUNT_INDEX_ENV];
660
835
  const child = spawn(
661
836
  process.execPath,
662
837
  [scriptPath, "forecast", "--live", "--json"],
663
838
  {
664
- env: {
665
- ...env,
666
- CODEX_MULTI_AUTH_STATUS_REFRESH_CHILD: "1",
667
- CODEX_MULTI_AUTH_STATUSLINE: "0",
668
- },
839
+ env: refreshChildEnv,
669
840
  stdio: "ignore",
670
841
  detached: true,
671
842
  },
@@ -4766,12 +4937,22 @@ async function main() {
4766
4937
  return 1;
4767
4938
  }
4768
4939
 
4940
+ // Resolve `--account` / CODEX_MULTI_AUTH_FORCE_ACCOUNT before forwarding: strip
4941
+ // the launcher-only flag from the Codex args and publish the resolved pin, or
4942
+ // fail hard so a forced account can never silently fall back to another one.
4943
+ const forcedAccount = await applyForcedAccountSelection(rawArgs, process.env);
4944
+ if (forcedAccount.error) {
4945
+ console.error(forcedAccount.error);
4946
+ return 1;
4947
+ }
4948
+ const forwardArgs = forcedAccount.forwardArgs;
4949
+
4769
4950
  await autoSyncManagerActiveSelectionIfEnabled();
4770
- await maybePrintForwardStatusLine(rawArgs);
4951
+ await maybePrintForwardStatusLine(forwardArgs);
4771
4952
  maybeRefreshQuotaCacheInBackground();
4772
4953
  try {
4773
- return await withForwardedRuntimeObservability(rawArgs, () =>
4774
- forwardToRealCodex(realCodexBin, rawArgs),
4954
+ return await withForwardedRuntimeObservability(forwardArgs, () =>
4955
+ forwardToRealCodex(realCodexBin, forwardArgs),
4775
4956
  );
4776
4957
  } finally {
4777
4958
  await autoSyncManagerActiveSelectionIfEnabled();