@bitkyc08/opencodex 2.80.0 → 2.82.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 (376) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +166 -73
  3. package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
  4. package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
  5. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  6. package/gui/dist/assets/index-D3FBCNiP.js +94 -0
  7. package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/gui/dist/provider-icons/claude-green.svg +1 -0
  10. package/package.json +3 -2
  11. package/src/adapters/anthropic-model-contract.ts +8 -2
  12. package/src/adapters/anthropic.ts +8 -3
  13. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  14. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  15. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  16. package/src/adapters/coding-agent/protocol.ts +122 -17
  17. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  18. package/src/adapters/coding-agent/turn.ts +101 -44
  19. package/src/adapters/cursor/catalog.ts +8 -0
  20. package/src/adapters/cursor/effort-map.ts +2 -0
  21. package/src/adapters/devin/live-models.ts +2 -0
  22. package/src/adapters/kiro-retry.ts +9 -4
  23. package/src/adapters/openai-chat/messages.ts +7 -6
  24. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  25. package/src/adapters/openai-responses/passthrough.ts +9 -4
  26. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  27. package/src/adapters/physical-send.ts +8 -5
  28. package/src/adapters/qoder/adapter.ts +41 -6
  29. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  30. package/src/adapters/zed.ts +16 -1
  31. package/src/claude/agents-inject.ts +14 -3
  32. package/src/claude/context-windows.ts +19 -17
  33. package/src/claude/desktop-3p.ts +88 -12
  34. package/src/claude/desktop-profile.ts +2 -1
  35. package/src/claude/gateway-cache.ts +18 -1
  36. package/src/claude/inbound-cache-stabilize.ts +4 -9
  37. package/src/claude/inbound.ts +78 -3
  38. package/src/claude/intercept/cli-catalog.ts +3 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  41. package/src/claude/intercept/picker-listener.ts +71 -4
  42. package/src/claude/intercept/picker-models.ts +14 -6
  43. package/src/claude/intercept/settings.ts +3 -0
  44. package/src/claude/long-context.ts +66 -0
  45. package/src/claude/model-info.ts +19 -16
  46. package/src/claude/outbound.ts +94 -9
  47. package/src/claude/subagent-model.ts +40 -11
  48. package/src/cli/account-anthropic-threshold.ts +14 -5
  49. package/src/cli/account-api.ts +3 -1
  50. package/src/cli/account-extended.ts +13 -9
  51. package/src/cli/account-policy-dto.ts +1 -1
  52. package/src/cli/account-policy.ts +13 -4
  53. package/src/cli/account.ts +3 -3
  54. package/src/cli/agent.ts +9 -5
  55. package/src/cli/aside-profile-recovery.ts +33 -0
  56. package/src/cli/capabilities-accounts.ts +2 -2
  57. package/src/cli/capabilities-agents-routing.ts +24 -0
  58. package/src/cli/capabilities-base.ts +5 -5
  59. package/src/cli/capabilities-integrations.ts +18 -2
  60. package/src/cli/cli-path-diagnostics.ts +132 -0
  61. package/src/cli/codex-shim-autorestore.ts +3 -0
  62. package/src/cli/desktop-runtime-guidance.ts +65 -0
  63. package/src/cli/dispatch.ts +15 -1
  64. package/src/cli/doctor.ts +16 -3
  65. package/src/cli/help.ts +4 -1
  66. package/src/cli/index.ts +25 -14
  67. package/src/cli/integration-aside-sync.ts +2 -1
  68. package/src/cli/integration-plan-dto.ts +1 -0
  69. package/src/cli/integration-preview.ts +8 -1
  70. package/src/cli/integrations.ts +48 -6
  71. package/src/cli/message-args.ts +34 -0
  72. package/src/cli/message-command.ts +43 -0
  73. package/src/cli/message-runtime.ts +6 -0
  74. package/src/cli/observe.ts +3 -1
  75. package/src/cli/provider.ts +29 -6
  76. package/src/cli/registry.ts +24 -2
  77. package/src/cli/resolve.ts +25 -2
  78. package/src/cli/root.ts +4 -2
  79. package/src/cli/status.ts +64 -12
  80. package/src/cli/update-restart-child.ts +4 -2
  81. package/src/cli/update-restart-home.ts +28 -10
  82. package/src/cli/update-restart-service-record.ts +116 -0
  83. package/src/cli/update-restart-supervision.ts +96 -0
  84. package/src/cli/update-restart.ts +59 -13
  85. package/src/cli/v2.ts +27 -20
  86. package/src/cli/version-skew-notice.ts +77 -0
  87. package/src/clients/config-export/commandcode.ts +131 -0
  88. package/src/clients/config-export/contracts.ts +12 -0
  89. package/src/clients/config-export.ts +64 -1
  90. package/src/clients/lazycodex.ts +1 -1
  91. package/src/clients/omo-role-models.ts +32 -7
  92. package/src/codex/auth-api/account-list.ts +11 -3
  93. package/src/codex/auth-api/account-selection.ts +107 -0
  94. package/src/codex/auth-api/main-account-probe.ts +25 -8
  95. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  96. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  97. package/src/codex/auth-api/routes.ts +11 -49
  98. package/src/codex/auth-context.ts +20 -1
  99. package/src/codex/autostart-health.ts +12 -3
  100. package/src/codex/catalog/auto-review.ts +13 -6
  101. package/src/codex/catalog/build-entries.ts +33 -7
  102. package/src/codex/catalog/bundled.ts +155 -26
  103. package/src/codex/catalog/combo-member.ts +3 -2
  104. package/src/codex/catalog/control-plane.ts +45 -0
  105. package/src/codex/catalog/effort.ts +37 -6
  106. package/src/codex/catalog/gather-capture.ts +10 -3
  107. package/src/codex/catalog/model-hints.ts +15 -8
  108. package/src/codex/catalog/model-visibility.ts +3 -1
  109. package/src/codex/catalog/operator-tiers.ts +38 -0
  110. package/src/codex/catalog/parsing.ts +2 -1
  111. package/src/codex/catalog/provider-models.ts +58 -44
  112. package/src/codex/catalog/retained-sync.ts +7 -5
  113. package/src/codex/catalog/routed-gather.ts +15 -1
  114. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  115. package/src/codex/catalog-auto-refresh.ts +19 -0
  116. package/src/codex/catalog.ts +1 -1
  117. package/src/codex/codex-home-owner.ts +3 -0
  118. package/src/codex/config-write-lock.ts +333 -0
  119. package/src/codex/control-plane-models.ts +6 -0
  120. package/src/codex/convergence.ts +2 -1
  121. package/src/codex/features.ts +257 -200
  122. package/src/codex/inject/config-write-section.ts +34 -0
  123. package/src/codex/inject/multi-agent-v2.ts +14 -6
  124. package/src/codex/inject/remove.ts +36 -11
  125. package/src/codex/inject/restore.ts +85 -20
  126. package/src/codex/inject-coordination.ts +15 -6
  127. package/src/codex/inject.ts +62 -20
  128. package/src/codex/journal.ts +62 -17
  129. package/src/codex/main-account-cache.ts +43 -0
  130. package/src/codex/paths.ts +2 -0
  131. package/src/codex/prepared-home.ts +18 -0
  132. package/src/codex/prompt-journal.ts +25 -12
  133. package/src/codex/prompt-layers.ts +49 -26
  134. package/src/codex/prompt-lock-claim.ts +134 -0
  135. package/src/codex/prompt-lock-evidence.ts +26 -0
  136. package/src/codex/prompt-lock-io.ts +16 -0
  137. package/src/codex/prompt-lock-owner.ts +130 -0
  138. package/src/codex/prompt-lock.ts +84 -53
  139. package/src/codex/routing/failure-window.ts +146 -0
  140. package/src/codex/routing/selection.ts +4 -1
  141. package/src/codex/routing.ts +13 -17
  142. package/src/codex/runtime.ts +106 -18
  143. package/src/codex/shim.ts +45 -19
  144. package/src/combos/jev-decision-contract.ts +25 -2
  145. package/src/combos/jev-service-exchange.ts +281 -0
  146. package/src/combos/jev.ts +34 -219
  147. package/src/combos/types.ts +1 -1
  148. package/src/config/atomic-write.ts +1 -1
  149. package/src/config/codex-ws-reuse-setting.ts +10 -0
  150. package/src/config/diagnostics.ts +27 -16
  151. package/src/config/live-reconcile.ts +17 -0
  152. package/src/config/load-degrade.ts +42 -0
  153. package/src/config/rebase-provenance.ts +11 -0
  154. package/src/config/schema/anthropic-account-pool.ts +143 -0
  155. package/src/config/schema/config-schema.ts +11 -1
  156. package/src/config/schema/leaf-validators.ts +4 -0
  157. package/src/config.ts +2 -0
  158. package/src/generated/compatibility-version.json +566 -298
  159. package/src/generated/model-metadata.ts +7 -6
  160. package/src/grok/inject.ts +50 -4
  161. package/src/grok/sync.ts +2 -1
  162. package/src/images/loop.ts +30 -11
  163. package/src/integrations/aside-profiles.ts +10 -3
  164. package/src/integrations/catalog-refresh.ts +19 -3
  165. package/src/integrations/mutation-plan.ts +7 -0
  166. package/src/integrations/registry.ts +8 -0
  167. package/src/integrations/state.ts +9 -2
  168. package/src/integrations/writer.ts +17 -5
  169. package/src/lib/bun-path-runtime.d.mts +32 -0
  170. package/src/lib/bun-path-runtime.mjs +95 -0
  171. package/src/lib/bun-runtime-preflight.ts +140 -0
  172. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  173. package/src/lib/desktop-cli-handoff.mjs +92 -0
  174. package/src/lib/desktop-cli-record.d.mts +29 -0
  175. package/src/lib/desktop-cli-record.mjs +102 -0
  176. package/src/lib/lab-live-route-production.ts +9 -1
  177. package/src/lib/request-execution-budget.ts +244 -58
  178. package/src/lib/spend-pool-alias-validation.ts +79 -0
  179. package/src/lib/spend-pool-continuity.ts +34 -0
  180. package/src/lib/spend-reservation-ledger.ts +412 -50
  181. package/src/lib/state-store-registrations.ts +20 -3
  182. package/src/lib/synced-state-location.ts +46 -6
  183. package/src/lib/test-home-guard.ts +87 -1
  184. package/src/lib/translator-budget.ts +17 -4
  185. package/src/lib/upstream-retry.ts +17 -5
  186. package/src/lib/windows-atomic-replace.ts +38 -16
  187. package/src/lib/windows-elevation.ts +6 -0
  188. package/src/lib/windows-secret-acl.ts +92 -25
  189. package/src/lib/winsw.ts +2 -3
  190. package/src/lib/workflow-budget.ts +53 -11
  191. package/src/messaging/budget.ts +45 -0
  192. package/src/messaging/discovery.ts +57 -0
  193. package/src/messaging/envelope.ts +49 -0
  194. package/src/messaging/input.ts +37 -0
  195. package/src/messaging/rpc.ts +196 -0
  196. package/src/messaging/send.ts +58 -0
  197. package/src/messaging/socket.ts +72 -0
  198. package/src/messaging/types.ts +29 -0
  199. package/src/oauth/anthropic-account-refusal.ts +90 -18
  200. package/src/oauth/anthropic-account-threshold.ts +8 -2
  201. package/src/oauth/anthropic-continuity.ts +15 -1
  202. package/src/oauth/anthropic-model-quota.ts +120 -48
  203. package/src/oauth/anthropic-model-routes.ts +11 -3
  204. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  205. package/src/oauth/anthropic-pool-config.ts +30 -0
  206. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  207. package/src/oauth/anthropic-routing.ts +1202 -866
  208. package/src/oauth/anthropic-send-ownership.ts +41 -0
  209. package/src/oauth/anthropic.ts +4 -1
  210. package/src/oauth/chatgpt.ts +113 -41
  211. package/src/oauth/generic-account-failover.ts +3 -1
  212. package/src/oauth/health.ts +8 -6
  213. package/src/oauth/index.ts +96 -175
  214. package/src/oauth/kiro.ts +22 -9
  215. package/src/oauth/login-cli.ts +2 -0
  216. package/src/oauth/model-discovery-auth.ts +43 -0
  217. package/src/oauth/pool-kernel.ts +11 -5
  218. package/src/oauth/pool-settings-capability.ts +5 -3
  219. package/src/oauth/store-anthropic-instance.ts +87 -0
  220. package/src/oauth/store.ts +68 -10
  221. package/src/oauth/token-guardian.ts +2 -0
  222. package/src/oauth/types.ts +2 -0
  223. package/src/protocols/plan-snapshot.ts +5 -0
  224. package/src/protocols/settings.ts +20 -5
  225. package/src/providers/anthropic-instance-id.ts +47 -0
  226. package/src/providers/anthropic-instance.ts +48 -0
  227. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  228. package/src/providers/azure-model-metadata.ts +115 -0
  229. package/src/providers/derive.ts +23 -0
  230. package/src/providers/kiro-models.ts +3 -0
  231. package/src/providers/label.ts +1 -1
  232. package/src/providers/model-rename-fields.ts +2 -0
  233. package/src/providers/quota/account-cache.ts +37 -11
  234. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  235. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  236. package/src/providers/quota/report-cache.ts +24 -1
  237. package/src/providers/quota/vendor-probes-key.ts +127 -17
  238. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  239. package/src/providers/quota.ts +23 -33
  240. package/src/providers/registry/entries-core.ts +35 -30
  241. package/src/providers/registry/model-ids.ts +1 -0
  242. package/src/providers/registry/model-seeds.ts +7 -4
  243. package/src/providers/registry/types.ts +3 -0
  244. package/src/providers/registry.ts +3 -3
  245. package/src/responses/reasoning-envelope.ts +29 -1
  246. package/src/responses/reasoning-replay-cache.ts +17 -1
  247. package/src/responses/spill-store.ts +20 -3
  248. package/src/responses/state/metrics.ts +95 -0
  249. package/src/responses/state/snapshot-codec.ts +6 -0
  250. package/src/responses/state/spill-failure.ts +12 -0
  251. package/src/responses/state/spill-queue.ts +13 -5
  252. package/src/responses/state/unforced-store-false.ts +55 -0
  253. package/src/responses/state.ts +68 -76
  254. package/src/responses/task-input.ts +17 -0
  255. package/src/router.ts +28 -4
  256. package/src/routing/compatibility/assemble.ts +1 -1
  257. package/src/routing/quota.ts +7 -3
  258. package/src/server/auth-cors.ts +5 -0
  259. package/src/server/background-lifecycle.ts +27 -0
  260. package/src/server/chat-completions.ts +7 -1
  261. package/src/server/chat-native.ts +64 -28
  262. package/src/server/claude-messages.ts +59 -8
  263. package/src/server/gui-static.ts +38 -4
  264. package/src/server/inbound-body-admission.ts +1 -0
  265. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  266. package/src/server/index/serve-options.ts +10 -2
  267. package/src/server/index/spend-ledger-lifecycle.ts +12 -5
  268. package/src/server/index/startup-warnings.ts +19 -0
  269. package/src/server/index.ts +5 -2
  270. package/src/server/inference/context.ts +1 -1
  271. package/src/server/lifecycle.ts +1 -1
  272. package/src/server/management/agent-settings-routes.ts +123 -38
  273. package/src/server/management/anthropic-account-threshold.ts +12 -9
  274. package/src/server/management/anthropic-pool-settings.ts +72 -0
  275. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  276. package/src/server/management/codex-agent-role-routes.ts +4 -2
  277. package/src/server/management/codex-prompt-routes.ts +1 -0
  278. package/src/server/management/codex-role-auto-assign.ts +13 -2
  279. package/src/server/management/config-routes.ts +85 -14
  280. package/src/server/management/context.ts +2 -2
  281. package/src/server/management/decision-routes.ts +2 -1
  282. package/src/server/management/integration-routes.ts +25 -2
  283. package/src/server/management/oauth-account-routes.ts +79 -98
  284. package/src/server/management/provider-instance-ownership.ts +44 -0
  285. package/src/server/management/provider-routes.ts +21 -28
  286. package/src/server/management/route-registry.ts +3 -1
  287. package/src/server/management/routing-profile-routes.ts +4 -4
  288. package/src/server/management/shared.ts +3 -3
  289. package/src/server/management/system-restart.ts +44 -14
  290. package/src/server/management/system-routes.ts +24 -2
  291. package/src/server/management/vision-sidecar-options.ts +9 -6
  292. package/src/server/management/web-search-sidecar-options.ts +51 -2
  293. package/src/server/management-auth.ts +6 -0
  294. package/src/server/messages-native-eligibility.ts +3 -2
  295. package/src/server/messages-native-oauth.ts +91 -45
  296. package/src/server/messages-native-selector.ts +39 -0
  297. package/src/server/messages-native.ts +133 -38
  298. package/src/server/messages-response-headers.ts +14 -0
  299. package/src/server/request-log-conversation.ts +48 -5
  300. package/src/server/request-log.ts +36 -2
  301. package/src/server/responses/adapter-continuation.ts +31 -19
  302. package/src/server/responses/adapter-dispatch.ts +75 -24
  303. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  304. package/src/server/responses/codex-ws-exchange.ts +17 -2
  305. package/src/server/responses/codex-ws-pool.ts +100 -13
  306. package/src/server/responses/codex-ws-wire.ts +9 -0
  307. package/src/server/responses/combo-requested-effort.ts +3 -1
  308. package/src/server/responses/compact.ts +25 -8
  309. package/src/server/responses/compaction-recovery.ts +1 -1
  310. package/src/server/responses/core-codex-account.ts +22 -1
  311. package/src/server/responses/core-combo-native.ts +24 -4
  312. package/src/server/responses/core-combo.ts +38 -25
  313. package/src/server/responses/core-lifetime.ts +3 -36
  314. package/src/server/responses/core-normalize.ts +1 -0
  315. package/src/server/responses/core-options.ts +8 -1
  316. package/src/server/responses/core-replay.ts +78 -0
  317. package/src/server/responses/core.ts +3 -3
  318. package/src/server/responses/fetch-helpers.ts +26 -3
  319. package/src/server/responses/passthrough-delivery.ts +30 -4
  320. package/src/server/responses/passthrough-dispatch.ts +39 -9
  321. package/src/server/responses/request-prepare.ts +41 -5
  322. package/src/server/responses/request-send-budget.ts +150 -50
  323. package/src/server/responses/request-spend.ts +325 -33
  324. package/src/server/responses/request-transport.ts +132 -52
  325. package/src/server/responses/response-effects.ts +1 -0
  326. package/src/server/responses/run-turn-execution.ts +13 -3
  327. package/src/server/responses/sidecar-execution.ts +113 -22
  328. package/src/server/responses/sidecar-send-budget.ts +58 -0
  329. package/src/server/responses/ws-upstream.ts +19 -6
  330. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  331. package/src/server/startup-health-cache.ts +2 -1
  332. package/src/server/workflow-refusal.ts +33 -0
  333. package/src/service/cli.ts +20 -4
  334. package/src/service/desktop-command-guard.ts +32 -0
  335. package/src/service/desktop-startup.ts +66 -40
  336. package/src/service/desktop-supervision.d.mts +26 -0
  337. package/src/service/desktop-supervision.mjs +140 -0
  338. package/src/service/managing-cli.ts +1 -0
  339. package/src/service/orchestration.ts +46 -20
  340. package/src/service/ownership-mutation-lease.d.mts +1 -1
  341. package/src/service/ownership-mutation-lease.mjs +5 -7
  342. package/src/service/repair.ts +25 -9
  343. package/src/service/state.ts +4 -3
  344. package/src/service/systemd.ts +17 -8
  345. package/src/service/windows-ops.ts +61 -22
  346. package/src/sidecar/anthropic-binding.ts +79 -0
  347. package/src/sidecar/auth.ts +76 -2
  348. package/src/storage/policy-job.ts +5 -1
  349. package/src/storage/policy-worker.ts +3 -1
  350. package/src/storage/policy.ts +9 -0
  351. package/src/types/anthropic-account-pool.ts +33 -0
  352. package/src/types/config.ts +33 -28
  353. package/src/types/provider.ts +5 -0
  354. package/src/types/request.ts +5 -0
  355. package/src/types/tools.ts +7 -0
  356. package/src/types/wire.ts +1 -0
  357. package/src/types.ts +4 -0
  358. package/src/update/index.ts +57 -9
  359. package/src/update/restart-ownership.ts +10 -3
  360. package/src/update/runtime-ownership.d.mts +6 -0
  361. package/src/update/runtime-ownership.mjs +23 -7
  362. package/src/usage/cost.ts +3 -2
  363. package/src/usage/expected-prices.ts +66 -14
  364. package/src/vision/anthropic-describe.ts +16 -3
  365. package/src/vision/index.ts +3 -0
  366. package/src/vision/plan.ts +19 -6
  367. package/src/web-search/alpha-search.ts +7 -1
  368. package/src/web-search/anthropic-executor.ts +16 -4
  369. package/src/web-search/index.ts +7 -3
  370. package/src/web-search/loop.ts +32 -7
  371. package/src/web-search/passthrough-bridge.ts +4 -1
  372. package/src/web-search/sidecar-providers.ts +4 -3
  373. package/gui/dist/assets/App-MbDVfUeC.js +0 -54
  374. package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
  375. package/gui/dist/assets/index-UJt6npXk.js +0 -94
  376. package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
@@ -1,7 +1,7 @@
1
1
  import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
2
2
  import { initializeProviderModelSelection } from "../providers/initial-model-selection";
3
3
  import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
4
- import { ConfigMutationLockError, loadConfig, mutatePersistedConfig, saveConfig } from "../config";
4
+ import { getDefaultConfig, initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig, observeInitialConfigState, saveConfig } from "../config";
5
5
  import { resolveProviderApiKey } from "../providers/key-store";
6
6
  import { projectEmail } from "../lib/privacy";
7
7
  import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro";
@@ -9,15 +9,13 @@ import {
9
9
  OAuthMutationBusyError,
10
10
  OAuthRefreshIntentIOError,
11
11
  clearOAuthRefreshIntent,
12
- clearOAuthRefreshIntentIfMatch,
13
12
  createOAuthRefreshIntentLock,
14
13
  credentialGeneration,
15
14
  getAccountCredential,
16
15
  getAccountCredentialWithStatus,
17
16
  getAccountSet,
18
17
  getCredential,
19
- markAccountNeedsReauthIfGeneration,
20
- markOAuthRefreshIntentCleanupPending,
18
+ markAccountNeedsReauthIfGeneration, accountNeedsReauthForStatus, markAccountRefreshAttentionIfGeneration,
21
19
  markOAuthRefreshIntentStaleOwner,
22
20
  mergeAccountCredential,
23
21
  normalizeAuthStoreBuffer,
@@ -29,14 +27,18 @@ import {
29
27
  writeOAuthRefreshIntent,
30
28
  type OAuthCredentialWriteReceipt,
31
29
  type OAuthRefreshIntent,
32
- type OAuthRefreshIntentCleanupPending,
33
30
  type AuthStore,
34
31
  } from "./store";
35
32
  import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenRequestError } from "./xai";
36
- import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthropicToken } from "./anthropic";
33
+ import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError } from "./anthropic";
34
+ import { isAnthropicOAuthInstance, anthropicInstanceRowShapeMatches } from "../providers/anthropic-instance";
35
+ import { captureModelsOAuthTarget, guardModelsOAuthRequest, mayResolveModelsOAuth } from "./model-discovery-auth";
36
+ import { anthropicOAuthDefinition, assertAnthropicInstanceLoginConfig, AnthropicInstanceCollisionError,
37
+ clearAnthropicRefreshIntentBestEffort, clearAnthropicRefreshIntentForKnownFailure,
38
+ resumeAnthropicRefreshIntentCleanup, clearObservedAnthropicRefreshIntent } from "./anthropic-oauth-definitions";
37
39
  import { loginKimi, refreshKimiToken } from "./kimi";
38
40
  import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous";
39
- import { loginChatGPT, refreshChatGPTToken, type ChatGPTLoginFlow } from "./chatgpt";
41
+ import { ChatGptTokenRequestError, ChatGptTokenError, loginChatGPT, refreshChatGPTToken, type ChatGPTLoginFlow } from "./chatgpt";
40
42
  import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
41
43
  import { loginCursor, refreshCursorToken } from "./cursor";
42
44
  import { loginZed, refreshZedToken } from "./zed";
@@ -83,8 +85,9 @@ export { OAUTH_REFRESH_LOCK_WAIT_MS, peekAuthStore, peekOAuthRefreshIntent } fro
83
85
  import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
84
86
 
85
87
  import { REFRESH_SKEW_MS } from "./refresh-policy";
86
- import { newerClaudeCredential, captureAnthropicCredentialOwner } from "./anthropic-continuity";
88
+ import { newerClaudeCredentialForInstance, captureAnthropicCredentialOwnerForInstance } from "./anthropic-continuity";
87
89
  import { mergeAnthropicIdentity, type AnthropicIdentityResolver } from "./anthropic-identity";
90
+ import { AnthropicCrossInstanceDuplicateError, AnthropicLocalCliImportError, assertAnthropicCredentialSource } from "./store-anthropic-instance";
88
91
  export interface OAuthAccessSnapshot {
89
92
  provider: string;
90
93
  accountId: string;
@@ -195,7 +198,7 @@ export interface LoginFlowLifecycle {
195
198
  onSettled?: () => void | Promise<void>;
196
199
  }
197
200
 
198
- interface OAuthProviderDef {
201
+ export interface OAuthProviderDef {
199
202
  login(ctrl: OAuthController, opts?: LoginOpts, providerConfig?: OcxProviderConfig): Promise<OAuthCredentials>;
200
203
  refresh(
201
204
  refreshToken: string,
@@ -266,15 +269,8 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
266
269
  providerConfig: oauthConfig("xai"),
267
270
  defaultModel: oauthDefaultModel("xai"),
268
271
  },
269
- anthropic: {
270
- login: (ctrl, opts) => loginAnthropic(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
271
- refresh: refreshAnthropicToken,
272
- providerConfig: oauthConfig("anthropic"),
273
- defaultModel: oauthDefaultModel("anthropic"),
274
- // Anthropic actively server-side-blocks subscription OAuth outside its own clients (Feb 2026).
275
- // Never generate background refresh traffic for it — grade 20, highest ToS risk.
276
- defaultRefreshPolicy: "disabled",
277
- },
272
+ anthropic: anthropicOAuthDefinition("anthropic"),
273
+ anthropic2: anthropicOAuthDefinition("anthropic2"),
278
274
  kimi: {
279
275
  login: (ctrl) => loginKimi(ctrl),
280
276
  refresh: refreshKimiToken,
@@ -354,7 +350,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
354
350
  },
355
351
  chatgpt: {
356
352
  login: (ctrl, opts) => loginChatGPT(ctrl, { forceLogin: opts?.forceLogin, flow: opts?.flow }),
357
- refresh: (rt) => refreshChatGPTToken(rt),
353
+ refresh: (rt, signal) => refreshChatGPTToken(rt, { signal }),
358
354
  providerConfig: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex", authMode: "forward" as const },
359
355
  defaultModel: "gpt-5.6-luna",
360
356
  },
@@ -446,8 +442,10 @@ export class OAuthAccountPausedError extends Error {
446
442
  }
447
443
 
448
444
  export class OAuthProviderPublicationError extends Error {
449
- constructor() {
450
- super("OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login.");
445
+ constructor(provider?: string, credentialWritten = true) {
446
+ super(provider !== "anthropic2" ? "OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login."
447
+ : credentialWritten ? "Anthropic Pool 2 credential was saved as an orphan auth row, but the provider entry was not written. Repair the persisted config or resolve the provider/account namespace collision, then retry login."
448
+ : "Anthropic Pool 2 login did not start because the config file is missing or could not be written; no credential was saved. Repair the persisted config, then retry login.");
451
449
  this.name = "OAuthProviderPublicationError";
452
450
  }
453
451
  }
@@ -490,6 +488,9 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
490
488
  || error instanceof OAuthReauthIdentityUnverifiedError
491
489
  || error instanceof OAuthTokenRefreshBusyError
492
490
  || error instanceof OAuthTokenRefreshStaleError
491
+ || error instanceof AnthropicInstanceCollisionError
492
+ || error instanceof AnthropicCrossInstanceDuplicateError
493
+ || error instanceof AnthropicLocalCliImportError
493
494
  ) return error.message;
494
495
  return "OAuth authentication failed. Check the OpenCodex account status and retry.";
495
496
  }
@@ -711,10 +712,14 @@ function isTerminalRefreshError(err: unknown): boolean {
711
712
  || msg.includes("expired_token");
712
713
  }
713
714
  function terminal(error:unknown):boolean{
715
+ if (error instanceof ChatGptTokenRequestError) return false;
714
716
  if(error instanceof XaiTokenRequestError)return ["invalid_grant","refresh_token_reused","revoked_token"].includes(error.oauthError??"");
715
717
  if(error instanceof AnthropicTokenError)return (error.httpStatus===400||error.httpStatus===401)&&["invalid_grant","refresh_token_reused","revoked","revoked_token","refresh_token_revoked"].includes(error.oauthError??"");
716
718
  if(error instanceof KiroTokenRefreshError)return (error.httpStatus===400||error.httpStatus===401)&&error.oauthError!==undefined;
717
719
  if(error instanceof NousTokenError)return error.terminal===true||["invalid_grant","refresh_token_reused","revoked","revoked_token","expired_token"].includes(error.oauthError??"");
720
+ // ChatGPT carries the pool classifier's verdict on the error itself: revoked and
721
+ // expired grants are terminal; unknown/transient failures stay retryable.
722
+ if(error instanceof ChatGptTokenError)return error.terminal;
718
723
  // Local durable-write/read/cleanup failures are operational, not credential
719
724
  // death: the provider credential was never rejected or consumed. Never mark
720
725
  // the account needsReauth for broken local persistence infrastructure.
@@ -739,131 +744,6 @@ function definitivelyAnswered(error: unknown): boolean {
739
744
  return false;
740
745
  }
741
746
 
742
- /**
743
- * Intent cleanup is secondary to the refresh outcome it protects.
744
- *
745
- * Once a credential is already durable, cleanup remains secondary and best-effort. A known
746
- * failed attempt takes the stricter path below: its retry-safe marker must become durable before
747
- * the original provider error can be returned.
748
- */
749
- function clearAnthropicRefreshIntentBestEffort(
750
- provider: string,
751
- accountId: string,
752
- expected: OAuthRefreshIntent,
753
- ): boolean {
754
- try {
755
- return expected.attemptId
756
- ? clearOAuthRefreshIntentIfMatch(provider, accountId, expected)
757
- : clearOAuthRefreshIntent(provider, accountId, expected.generation);
758
- } catch {
759
- console.warn(
760
- "[opencodex] Anthropic refresh intent cleanup failed; preserving the durable replay guard.",
761
- );
762
- return false;
763
- }
764
- }
765
-
766
- const ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS = [10, 25, 50] as const;
767
-
768
- function isConfigMutationLockContention(error: unknown): boolean {
769
- if (!(error instanceof ConfigMutationLockError)) return false;
770
- const cause = error.cause;
771
- const code = cause && typeof cause === "object" && "code" in cause
772
- ? String((cause as { code?: unknown }).code)
773
- : "";
774
- return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED";
775
- }
776
-
777
- async function clearAnthropicRefreshIntentForKnownFailure(
778
- provider: string,
779
- accountId: string,
780
- expected: OAuthRefreshIntent,
781
- cleanupPending: OAuthRefreshIntentCleanupPending,
782
- refreshError: unknown,
783
- ): Promise<boolean> {
784
- let marked: OAuthRefreshIntent | undefined;
785
- for (let attempt = 0; attempt <= ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS.length; attempt += 1) {
786
- try {
787
- marked = markOAuthRefreshIntentCleanupPending(
788
- provider,
789
- accountId,
790
- expected,
791
- cleanupPending,
792
- );
793
- break;
794
- } catch (cause) {
795
- const retryDelay = ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS[attempt];
796
- if (!isConfigMutationLockContention(cause) || retryDelay === undefined) {
797
- throw new OAuthRefreshIntentIOError(
798
- "mark-cleanup-pending",
799
- cause,
800
- refreshError,
801
- );
802
- }
803
- // The provider has definitively answered, so caller cancellation no longer changes the
804
- // settlement obligation. Yield briefly while retaining the per-account refresh lock, then
805
- // rerun the existing compare-and-swap marker against current disk state.
806
- await Bun.sleep(retryDelay);
807
- }
808
- }
809
- if (!marked) {
810
- throw new OAuthRefreshIntentIOError(
811
- "mark-cleanup-pending",
812
- new Error("Anthropic refresh intent changed before safe cleanup"),
813
- refreshError,
814
- );
815
- }
816
-
817
- let cleared: boolean;
818
- try {
819
- cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, marked);
820
- } catch {
821
- console.warn(
822
- "[opencodex] Anthropic refresh intent cleanup failed; retry-safe cleanup remains pending.",
823
- );
824
- return false;
825
- }
826
- if (!cleared) {
827
- throw new OAuthRefreshIntentIOError(
828
- "clear-cleanup-pending",
829
- new Error("Anthropic refresh intent changed during safe cleanup"),
830
- refreshError,
831
- );
832
- }
833
- return true;
834
- }
835
-
836
- function resumeAnthropicRefreshIntentCleanup(
837
- provider: string,
838
- accountId: string,
839
- pendingIntent: OAuthRefreshIntent,
840
- ): void {
841
- let cleared: boolean;
842
- try {
843
- cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent);
844
- } catch (cause) {
845
- throw new OAuthRefreshIntentIOError(
846
- "resume-cleanup",
847
- cause,
848
- );
849
- }
850
- if (!cleared) {
851
- throw new OAuthRefreshIntentIOError(
852
- "resume-cleanup",
853
- new Error("Pending Anthropic refresh intent changed before cleanup"),
854
- );
855
- }
856
- }
857
-
858
- function clearObservedAnthropicRefreshIntent(
859
- provider: string,
860
- accountId: string,
861
- pendingIntent: OAuthRefreshIntent,
862
- ): boolean {
863
- return pendingIntent.attemptId
864
- ? clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent)
865
- : clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation);
866
- }
867
747
  function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;}
868
748
  function merged(fresh: OAuthCredentials, previous: OAuthCredentials): OAuthCredentials {
869
749
  return {
@@ -932,6 +812,7 @@ export async function refreshAnthropicAccountWithLock(
932
812
  callerCredential: OAuthCredentials,
933
813
  deps: AnthropicRefreshDeps = {},
934
814
  ): Promise<string> {
815
+ if (!isAnthropicOAuthInstance(provider)) throw new UnsupportedOAuthProviderError(provider);
935
816
  const writerGeneration = captureConfigGeneration();
936
817
  const now = deps.now ?? Date.now;
937
818
  const guard = await (deps.intentLock ?? createOAuthRefreshIntentLock(provider, accountId)).acquire();
@@ -941,10 +822,10 @@ export async function refreshAnthropicAccountWithLock(
941
822
  if (!accountSet || !account) throw new OAuthLoginRequiredError(provider);
942
823
  if (account.paused) throw new OAuthAccountPausedError();
943
824
  const stored = account.credential;
944
- const owns = captureAnthropicCredentialOwner(accountSet, accountId);
825
+ const owns = captureAnthropicCredentialOwnerForInstance(provider, accountSet, accountId);
945
826
  const generation = credentialGeneration(stored);
946
827
  let pendingIntent = readOAuthRefreshIntent(provider, accountId);
947
- const observed = await newerClaudeCredential(stored, now(), deps.signal, deps.resolveIdentity);
828
+ const observed = await newerClaudeCredentialForInstance(provider, stored, now(), deps.signal, deps.resolveIdentity);
948
829
  const assertOwner = (store: AuthStore, checkDisk = true) => {
949
830
  if (observed.kind !== "absent" && deps.signal?.aborted) throw new OAuthTokenRefreshStaleError();
950
831
  if (!owns(store, checkDisk && observed.kind !== "absent" ? observed.diskGeneration : undefined)) {
@@ -1116,6 +997,10 @@ export async function refreshGenericAccountWithLock(
1116
997
  return fresh.access;
1117
998
  } catch (error) {
1118
999
  if (error instanceof OAuthMutationBusyError) throw error;
1000
+ if (provider === "kiro" && error instanceof KiroTokenRefreshError && error.refreshAttention) {
1001
+ await markAccountRefreshAttentionIfGeneration(provider, accountId, generation, writerGeneration);
1002
+ throw error;
1003
+ }
1119
1004
  if (!terminal(error)) throw error;
1120
1005
  // Nous-specific failure-atomicity: a terminal refresh error that carries
1121
1006
  // an already-issued rotated refresh token (e.g. the access JWT lacked the
@@ -1177,20 +1062,22 @@ async function refreshAndPersistAccessToken(
1177
1062
  replacedStaleFlight?: OAuthRefreshFlightEvidence,
1178
1063
  ): Promise<string> {
1179
1064
  if (provider === "xai") return refreshXaiAccountWithLock(provider, accountId, def, cred, { signal });
1180
- if (provider === "anthropic") return refreshAnthropicAccountWithLock(provider, accountId, def, cred, { signal, flight, replacedStaleFlight });
1065
+ if (isAnthropicOAuthInstance(provider)) return refreshAnthropicAccountWithLock(provider, accountId, def, cred, { signal, flight, replacedStaleFlight });
1181
1066
  return refreshGenericAccountWithLock(provider, accountId, def, cred, { signal });
1182
1067
  }
1183
1068
 
1184
- /**
1185
- * Shared bearer-token resolver for /models listing — used by BOTH server.ts:fetchAllModels and
1186
- * codex-catalog.ts:fetchProviderModels so OAuth providers' models are listed once logged in.
1187
- * Returns undefined for forward-mode or oauth-not-logged-in (caller skips).
1188
- */
1069
+ /** Resolve discovery credentials only for the caller-owned configured OAuth row. */
1070
+ export async function getModelsOAuthAccessSnapshot(name: string, prov: OcxProviderConfig | undefined): Promise<OAuthAccessSnapshot> {
1071
+ if (!mayResolveModelsOAuth(name, prov)) throw new OAuthLoginRequiredError(name);
1072
+ return getValidAccessTokenSnapshot(name);
1073
+ }
1074
+
1075
+ /** Shared /models auth resolver; custom key rows keep their own key, including omitted authMode. */
1189
1076
  export async function resolveModelsAuthToken(name: string, prov: OcxProviderConfig): Promise<string | undefined> {
1190
1077
  if (prov.authMode === "forward") return undefined;
1191
1078
  if (prov.authMode === "oauth") {
1192
1079
  try {
1193
- return await getValidAccessToken(name);
1080
+ return (await getModelsOAuthAccessSnapshot(name, prov)).accessToken;
1194
1081
  } catch {
1195
1082
  return undefined;
1196
1083
  }
@@ -1235,6 +1122,8 @@ export function buildModelsRequest(
1235
1122
  providerName = "",
1236
1123
  observedAuth?: ModelsRequestObservedAuth,
1237
1124
  ): { method?: "POST"; url: string; headers: Record<string, string> } {
1125
+ const authorizedTarget = captureModelsOAuthTarget(providerName, prov);
1126
+ if (prov.authMode === "oauth" && !mayResolveModelsOAuth(providerName, prov)) apiKey = undefined;
1238
1127
  const transportSeed = modelDiscoveryTransportSeed(providerName, prov);
1239
1128
  const copilotApiBaseUrl = observedAuth === undefined
1240
1129
  ? (providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(providerName) : undefined)
@@ -1261,16 +1150,18 @@ export function buildModelsRequest(
1261
1150
  effectiveProvider.baseUrl,
1262
1151
  defaultUrl,
1263
1152
  );
1153
+ const guarded = <T extends { url: string; headers: Record<string, string> }>(request: T): T =>
1154
+ guardModelsOAuthRequest(providerName, prov, request, authorizedTarget);
1264
1155
  if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
1265
1156
  headers.Accept = "application/json";
1266
1157
  headers["Content-Type"] = "application/json";
1267
1158
  headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
1268
1159
  if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
1269
- return {
1270
- method: "POST",
1160
+ return guarded({
1161
+ method: "POST" as const,
1271
1162
  url: discoveryUrl(`${effectiveProvider.baseUrl.replace(/\/+$/, "")}/v1internal:fetchAvailableModels`),
1272
1163
  headers,
1273
- };
1164
+ });
1274
1165
  }
1275
1166
  if (effectiveGoogleMode(providerName, effectiveProvider) === "ai-studio") {
1276
1167
  // Generative Language API: API key goes in x-goog-api-key (never Authorization: Bearer),
@@ -1278,7 +1169,7 @@ export function buildModelsRequest(
1278
1169
  // enough to list everything without a pageToken loop. Vertex/antigravity keep the
1279
1170
  // generic branch (they fall back to their static model lists).
1280
1171
  if (apiKey) headers["x-goog-api-key"] = apiKey;
1281
- return { url: discoveryUrl(`${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`), headers };
1172
+ return guarded({ url: discoveryUrl(`${effectiveProvider.baseUrl}/v1beta/models?pageSize=1000`), headers });
1282
1173
  }
1283
1174
  if (effectiveProvider.adapter === "anthropic") {
1284
1175
  const base = effectiveProvider.baseUrl.replace(/\/v1\/?$/, "");
@@ -1290,10 +1181,10 @@ export function buildModelsRequest(
1290
1181
  if (effectiveProvider.apiKeyTransport === "bearer") headers["Authorization"] = `Bearer ${apiKey}`;
1291
1182
  else headers["x-api-key"] = apiKey;
1292
1183
  }
1293
- return { url: discoveryUrl(`${base}/v1/models?limit=1000`), headers };
1184
+ return guarded({ url: discoveryUrl(`${base}/v1/models?limit=1000`), headers });
1294
1185
  }
1295
1186
  if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
1296
- return { url: discoveryUrl(providerModelsUrl(effectiveProvider.baseUrl)), headers };
1187
+ return guarded({ url: discoveryUrl(providerModelsUrl(effectiveProvider.baseUrl)), headers });
1297
1188
  }
1298
1189
 
1299
1190
  /**
@@ -1452,6 +1343,7 @@ function projectOAuthProviderReconciliation(config: OcxConfig): OAuthReconcilePr
1452
1343
  const touchedAntigravityVersion = projected.googleAntigravityStaticCatalogVersion !== beforeAntigravityVersion;
1453
1344
 
1454
1345
  for (const [name, prov] of Object.entries(projected.providers)) {
1346
+ if (name === "anthropic2" && !anthropicInstanceRowShapeMatches(name, prov)) continue;
1455
1347
  const beforeProvider = JSON.stringify(prov);
1456
1348
  const def = OAUTH_PROVIDERS[name];
1457
1349
  if (name === "command-code" && isLegacyCommandCodeStaticCatalog(prov)) {
@@ -1588,6 +1480,7 @@ const OAUTH_LOGIN_OWNED_PROVIDER_FIELDS = [
1588
1480
 
1589
1481
  /** Add/refresh only an OAuth provider's login-owned config fields (does not persist). */
1590
1482
  export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
1483
+ assertAnthropicInstanceLoginConfig(config, provider);
1591
1484
  if (provider === "chatgpt") return;
1592
1485
  const def = OAUTH_PROVIDERS[provider];
1593
1486
  if (!def) return;
@@ -1656,6 +1549,7 @@ interface RunLoginDeps {
1656
1549
  saveAccountCredential?: typeof saveAccountCredential;
1657
1550
  loadConfig?: typeof loadConfig;
1658
1551
  saveConfig?: typeof saveConfig;
1552
+ mutatePersistedConfig?: typeof mutatePersistedConfig;
1659
1553
  settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
1660
1554
  rollbackCredentialWrite?: typeof rollbackCredentialWriteIfMatch;
1661
1555
  assertCurrentOwner?: () => void;
@@ -1672,14 +1566,28 @@ export async function runLogin(
1672
1566
  if (!def) throw new UnsupportedOAuthProviderError(provider);
1673
1567
  const loadLatestConfig = deps.loadConfig ?? loadConfig;
1674
1568
  const saveLatestConfig = deps.saveConfig ?? saveConfig;
1675
- const preflightConfig = provider !== "chatgpt" ? loadLatestConfig() : undefined;
1569
+ const initialBConfigState = provider === "anthropic2" ? observeInitialConfigState() : undefined;
1570
+ let preflightConfig = provider !== "chatgpt" ? loadLatestConfig() : undefined;
1571
+ if (provider === "anthropic2" && preflightConfig && initialBConfigState === "missing") {
1572
+ // Explicit first-login onboarding may create the ordinary defaults, without a B row.
1573
+ // The create-only owner never replaces a concurrent file. Reload its winner before login;
1574
+ // once the browser starts, later deletion remains a publication refusal, not recreation.
1575
+ if (initializePersistedConfigIfMissing(getDefaultConfig()) === "invalid") {
1576
+ throw new OAuthProviderPublicationError(provider, false);
1577
+ }
1578
+ preflightConfig = loadLatestConfig();
1579
+ }
1676
1580
  if (preflightConfig) {
1581
+ assertAnthropicInstanceLoginConfig(preflightConfig, provider);
1677
1582
  const namespaceCollision = codexAccountNamespaceProviderCollisionError(
1678
1583
  preflightConfig.codexAccountNamespaces,
1679
1584
  provider,
1680
1585
  );
1681
1586
  if (namespaceCollision) throw new Error(namespaceCollision);
1682
1587
  }
1588
+ if (provider === "anthropic2" && observeInitialConfigState() !== "exists") {
1589
+ throw new OAuthProviderPublicationError(provider, false);
1590
+ }
1683
1591
  // loginKiro keys its pending CLI-session transaction by object identity. Keep this exact object
1684
1592
  // for settlement even when source normalization below creates a derived credential object.
1685
1593
  const shouldRollbackKiroAccounts = provider === "kiro" && opts?.forceLogin === true;
@@ -1693,6 +1601,7 @@ export async function runLogin(
1693
1601
  }
1694
1602
  const rawCred = await def.login(ctrl, opts, loginProviderConfig);
1695
1603
  const cred: OAuthCredentials = rawCred.source ? rawCred : { ...rawCred, source: "oauth" };
1604
+ if (isAnthropicOAuthInstance(provider)) assertAnthropicCredentialSource(provider, cred);
1696
1605
  const settleKiroTransaction = deps.settleKiroLoginTransaction ?? settleKiroLoginTransaction;
1697
1606
  try {
1698
1607
  deps.assertCurrentOwner?.();
@@ -1738,16 +1647,28 @@ export async function runLogin(
1738
1647
  if (provider !== "chatgpt") {
1739
1648
  // Re-run against post-credential state so same-provider API-key additions, removals,
1740
1649
  // and active-key switches survive. A late namespace claim wins over provider creation.
1741
- const latestConfig = loadLatestConfig();
1742
- const lateCollision = codexAccountNamespaceProviderCollisionError(
1743
- latestConfig.codexAccountNamespaces,
1744
- provider,
1745
- );
1746
- if (lateCollision) {
1747
- throw new OAuthProviderPublicationError();
1650
+ if (provider === "anthropic2") {
1651
+ const publication = (deps.mutatePersistedConfig ?? mutatePersistedConfig)(latestConfig => {
1652
+ assertAnthropicInstanceLoginConfig(latestConfig, provider, true);
1653
+ if (codexAccountNamespaceProviderCollisionError(latestConfig.codexAccountNamespaces, provider)) {
1654
+ throw new OAuthProviderPublicationError(provider);
1655
+ }
1656
+ upsertOAuthProvider(latestConfig, provider);
1657
+ return { changed: true, value: undefined };
1658
+ });
1659
+ if (publication.status === "unavailable") throw new OAuthProviderPublicationError(provider);
1660
+ } else {
1661
+ const latestConfig = loadLatestConfig();
1662
+ const lateCollision = codexAccountNamespaceProviderCollisionError(
1663
+ latestConfig.codexAccountNamespaces,
1664
+ provider,
1665
+ );
1666
+ if (lateCollision) {
1667
+ throw new OAuthProviderPublicationError();
1668
+ }
1669
+ upsertOAuthProvider(latestConfig, provider);
1670
+ saveLatestConfig(latestConfig);
1748
1671
  }
1749
- upsertOAuthProvider(latestConfig, provider);
1750
- saveLatestConfig(latestConfig);
1751
1672
  }
1752
1673
  } catch (error) {
1753
1674
  const errors: unknown[] = [error];
@@ -1840,7 +1761,7 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
1840
1761
  ...(a.alias ? { alias: a.alias } : {}),
1841
1762
  email: projectEmail(a.credential.email, maskEmails) ?? undefined,
1842
1763
  active: a.id === set.activeAccountId,
1843
- ...(a.needsReauth ? { needsReauth: true } : {}),
1764
+ ...(accountNeedsReauthForStatus(provider, a) ? { needsReauth: true } : {}),
1844
1765
  ...(a.needsReauth && a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
1845
1766
  expiresAt: a.credential.expires,
1846
1767
  // Explicitly null rather than omitted — see OAuthAccountSummary.plan. No OAuth provider
@@ -1852,11 +1773,11 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
1852
1773
 
1853
1774
  // A stored credential counts as "logged in" when it exists and is not marked for
1854
1775
  // re-authentication. An expired access token with a valid refresh token is still
1855
- // logged in: request resolution refreshes expired/near-expiry credentials lazily.
1776
+ // logged in unless refresh-attention evidence applies; refresh eligibility stays internal.
1856
1777
  // Invalid/unknown local-import expiries are handled at parse/adoption time
1857
1778
  // (local-token-detect.ts), never by over-reporting login state here.
1858
- const activeNeedsReauth = set?.accounts
1859
- .find(a => a.id === set.activeAccountId)?.needsReauth === true;
1779
+ const activeAccount = set?.accounts.find(a => a.id === set.activeAccountId);
1780
+ const activeNeedsReauth = activeAccount && accountNeedsReauthForStatus(provider, activeAccount);
1860
1781
  return {
1861
1782
  loggedIn: !!cred && !activeNeedsReauth,
1862
1783
  email: projectEmail(cred?.email, maskEmails) ?? undefined,
package/src/oauth/kiro.ts CHANGED
@@ -60,6 +60,7 @@ export class KiroTokenRefreshError extends Error {
60
60
  constructor(
61
61
  readonly httpStatus: number,
62
62
  readonly oauthError?: string,
63
+ readonly refreshAttention = false,
63
64
  ) {
64
65
  super(`Kiro token refresh failed: ${httpStatus}${oauthError ? ` (${oauthError})` : ""}`);
65
66
  this.name = "KiroTokenRefreshError";
@@ -530,17 +531,21 @@ export function resolveKiroRequestProfile(
530
531
  : { profileArn: undefined, builderIdFallback: false };
531
532
  }
532
533
 
533
- async function kiroTokenRefreshError(response: Response): Promise<KiroTokenRefreshError> {
534
+ async function kiroTokenRefreshError(response: Response, awsSsoRefresh = false): Promise<KiroTokenRefreshError> {
534
535
  let oauthError: string | undefined;
536
+ let refreshAttention = false;
535
537
  try {
536
538
  const payload = await response.json() as { error?: unknown };
539
+ // This input-error code is attention evidence only for our AWS SSO refresh request,
540
+ // never a terminal grant verdict. Do not surface arbitrary upstream descriptions.
541
+ refreshAttention = awsSsoRefresh && response.status === 400 && payload.error === "invalid_request";
537
542
  if (typeof payload.error === "string" && KIRO_TERMINAL_REFRESH_ERRORS.has(payload.error)) {
538
543
  oauthError = payload.error;
539
544
  }
540
545
  } catch {
541
546
  // Error bodies are untrusted and intentionally excluded from the surfaced message.
542
547
  }
543
- return new KiroTokenRefreshError(response.status, oauthError);
548
+ return new KiroTokenRefreshError(response.status, oauthError, refreshAttention);
544
549
  }
545
550
 
546
551
  async function readTokenResponse(res: Response, oldRefresh: string): Promise<OAuthCredentials> {
@@ -608,7 +613,7 @@ async function refreshAwsSsoOidcToken(
608
613
  signal: kiroRefreshSignal(signal),
609
614
  });
610
615
  const res = await run(refresh);
611
- if (!res.ok) throw await kiroTokenRefreshError(res);
616
+ if (!res.ok) throw await kiroTokenRefreshError(res, true);
612
617
  return readTokenResponse(res, refresh);
613
618
  }
614
619
 
@@ -668,11 +673,19 @@ export async function refreshKiroToken(
668
673
  const local = matchingRotatedKiroCliCredential(refresh, credential);
669
674
  if (!local) throw error;
670
675
  const retryMetadata = metadataForRotatedKiroCliCredential(credential, local);
671
- const fresh = await refreshAwsSsoOidcToken(local.refresh, signal, {
672
- ...credential,
673
- refresh: local.refresh,
674
- kiro: retryMetadata,
675
- });
676
- return { ...fresh, kiro: retryMetadata };
676
+ try {
677
+ const fresh = await refreshAwsSsoOidcToken(local.refresh, signal, {
678
+ ...credential,
679
+ refresh: local.refresh,
680
+ kiro: retryMetadata,
681
+ });
682
+ return { ...fresh, kiro: retryMetadata };
683
+ } catch (recoveryError) {
684
+ // A failed CLI fallback may use the desktop endpoint. Retain the original AWS SSO
685
+ // attention evidence on another input rejection; transient recovery stays retryable.
686
+ if (error.refreshAttention && recoveryError instanceof KiroTokenRefreshError
687
+ && recoveryError.httpStatus === 400 && recoveryError.oauthError === undefined) throw error;
688
+ throw recoveryError;
689
+ }
677
690
  }
678
691
  }
@@ -14,6 +14,7 @@ import { KEY_LOGIN_PROVIDERS, isKeyLoginProvider, validateApiKey, type KeyLoginP
14
14
  import type { OcxConfig, OcxProviderConfig } from "../types";
15
15
  import { configuredAdminToken } from "../lib/admin-secrets";
16
16
  import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
17
+ import { assertAnthropicInstanceLoginConfig } from "./store-anthropic-instance";
17
18
 
18
19
  /**
19
20
  * Seams a test drives in place of a browser, a terminal and a real provider. Production passes
@@ -136,6 +137,7 @@ export async function handleLogin(provider?: string, deps: LoginCliDeps = {}): P
136
137
  }
137
138
 
138
139
  export async function handleOAuthLogin(name: string, deps: LoginCliDeps = {}): Promise<void> {
140
+ if (name === "anthropic2") assertAnthropicInstanceLoginConfig(loadConfig(), name);
139
141
  const login = deps.runLogin ?? runLogin;
140
142
  const launch = deps.openUrl ?? openUrl;
141
143
  const browser = createBrowserLaunchReport(deps.warn);
@@ -0,0 +1,43 @@
1
+ import type { OcxProviderConfig } from "../types";
2
+ import { isBuiltinAnthropicInstanceRow } from "../providers/anthropic-instance";
3
+ import { resolveProviderModelDiscoveryUrl } from "../providers/model-discovery";
4
+ import { resolveProviderTransport } from "../providers/xai-transport";
5
+
6
+ /** Discovery owns its configured row (a frozen capture for catalog gathers), not live config. */
7
+ export function mayResolveModelsOAuth(name: string, provider: OcxProviderConfig | undefined): boolean {
8
+ return name !== "anthropic2" || (provider !== undefined && provider.disabled !== true
9
+ && isBuiltinAnthropicInstanceRow(name, provider));
10
+ }
11
+
12
+ /** Derive authority from configured transport/discovery policy, never from the outgoing request. */
13
+ export function captureModelsOAuthTarget(name: string, provider: OcxProviderConfig | undefined): string | undefined {
14
+ if (name !== "anthropic2" || !mayResolveModelsOAuth(name, provider) || !provider) return undefined;
15
+ try {
16
+ const effective = resolveProviderTransport(name, provider);
17
+ const base = effective.baseUrl.replace(/\/v1\/?$/, "");
18
+ const target = new URL(resolveProviderModelDiscoveryUrl(name, provider, effective.baseUrl, `${base}/v1/models?limit=1000`));
19
+ return !target.username && !target.password && !target.hash ? target.href : undefined;
20
+ } catch { return undefined; }
21
+ }
22
+
23
+ export function modelsOAuthTargetMatches(name: string, provider: OcxProviderConfig | undefined, target: string | undefined): boolean {
24
+ return name !== "anthropic2" || (target !== undefined && captureModelsOAuthTarget(name, provider) === target);
25
+ }
26
+
27
+ /** A supplied snapshot cannot authorize sending Pool 2's bearer to a custom destination. */
28
+ export function guardModelsOAuthRequest<T extends { url: string; headers: Record<string, string> }>(
29
+ name: string,
30
+ provider: OcxProviderConfig,
31
+ request: T,
32
+ authorizedTarget = captureModelsOAuthTarget(name, provider),
33
+ ): T {
34
+ if (name !== "anthropic2" || provider.authMode !== "oauth") return request;
35
+ try {
36
+ const destination = new URL(request.url);
37
+ if (mayResolveModelsOAuth(name, provider) && authorizedTarget !== undefined
38
+ && authorizedTarget === captureModelsOAuthTarget(name, provider) && destination.href === authorizedTarget
39
+ && !destination.username && !destination.password && !destination.hash) return request;
40
+ } catch { /* Malformed destinations carry no credential authority. */ }
41
+ return { ...request, headers: Object.fromEntries(Object.entries(request.headers)
42
+ .filter(([header]) => header.toLowerCase() !== "authorization")) };
43
+ }