@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -5,6 +5,7 @@ import { loadConfig } from "../config";
5
5
  import { resolveCodexHomeDir } from "./home";
6
6
  import { extractAccountId } from "../oauth/chatgpt";
7
7
  import { isSelectableCodexPoolAccount } from "./account-id";
8
+ import { codexPlanKey } from "./plan";
8
9
 
9
10
  export interface CodexTokens {
10
11
  access_token: string;
@@ -78,8 +79,9 @@ function normalizedEmail(email: string | undefined | null): string | null {
78
79
  return trimmed || null;
79
80
  }
80
81
 
81
- function isWorkspacePlan(plan: string | undefined | null): boolean {
82
- return !!plan && /team|business|enterprise|workspace|edu/i.test(plan);
82
+ function isWorkspacePlan(plan: unknown): boolean {
83
+ const key = codexPlanKey(plan);
84
+ return !!key && /team|business|enterprise|workspace|edu/.test(key);
83
85
  }
84
86
 
85
87
  // Main login and managed pool accounts are separate duplicate buckets.
@@ -88,7 +90,7 @@ function isWorkspacePlan(plan: string | undefined | null): boolean {
88
90
  export function checkAccountIdCollision(
89
91
  chatgptAccountId: string,
90
92
  email?: string | null,
91
- plan?: string | null,
93
+ plan?: unknown,
92
94
  excludeAccountId?: string | null,
93
95
  ): { collision: true; reason: string } | { collision: false } {
94
96
  const candidateEmail = normalizedEmail(email);
@@ -1,3 +1,4 @@
1
+ import { createHmac, randomBytes } from "node:crypto";
1
2
  import {
2
3
  CodexCredentialGenerationConflictError,
3
4
  CodexCredentialRefreshLockTimeoutError,
@@ -6,11 +7,14 @@ import {
6
7
  getValidCodexToken,
7
8
  isCodexAccountGenerationLive,
8
9
  } from "./account-store";
10
+ import { isAccountNeedsReauth, markAccountNeedsReauth } from "./account-runtime-state";
11
+ import { isCodexAccountPaused } from "./account-pause";
9
12
  import { ConfigMutationLockError } from "../config";
10
- import { markAccountNeedsReauth } from "./account-runtime-state";
11
13
  import { isCodexAccountUsable } from "./account-usability";
12
14
  import { reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
13
- import { MAIN_CODEX_ACCOUNT_ID, getMainAccountToken } from "./main-account";
15
+ import { MAIN_CODEX_ACCOUNT_ID, getMainAccountToken, isMainAccountTokenLive } from "./main-account";
16
+ import { isNativeMainTrafficBlocked, nativeMainStartupGateSnapshot } from "./native-profile-startup";
17
+ import type { NativeMainStartupBlockReason } from "./native-profile-startup";
14
18
  import {
15
19
  codexQuotaScopeForModel,
16
20
  getCodexQuotaHealthSnapshot,
@@ -21,6 +25,12 @@ import {
21
25
  pickAlternateCodexAccount,
22
26
  resolveCodexAccountForThreadDetailed,
23
27
  } from "./routing";
28
+ import {
29
+ entitledCodexAccountIdsForModel,
30
+ isDirectCallerEntitledToCodexModel,
31
+ resolveCodexModelEntitlements,
32
+ } from "./model-entitlements";
33
+ import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "./catalog/native-models";
24
34
  import type { CodexCooldownSource, CodexQuotaScope } from "./routing";
25
35
  import { maskAccountId } from "../lib/privacy";
26
36
  import { formatErrorResponse } from "../bridge";
@@ -28,6 +38,38 @@ import { getAccountQuota } from "./quota";
28
38
  import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
29
39
  import { FORWARD_HEADERS } from "../adapters/openai-responses";
30
40
  import { captureConfigGeneration } from "../lib/state-store-sweeper";
41
+ import { retainedUtf8Bytes } from "../lib/admission";
42
+
43
+ const CODEX_AFFINITY_COMPONENT_MAX_BYTES = 512;
44
+ const CODEX_APP_AFFINITY_KEY = randomBytes(32);
45
+
46
+ function boundedCodexAffinityComponent(value: string | null): string | undefined {
47
+ const normalized = value?.trim();
48
+ if (!normalized) return undefined;
49
+ if (retainedUtf8Bytes(normalized) > CODEX_AFFINITY_COMPONENT_MAX_BYTES) return undefined;
50
+ return normalized;
51
+ }
52
+
53
+ /**
54
+ * Preserve Codex's parent-thread affinity when present. Desktop App requests can omit that
55
+ * header while retaining a stable session/thread pair, so derive an opaque process-local key
56
+ * only from the complete bounded pair. Raw identifiers and durable hashes never enter Pool state.
57
+ */
58
+ export function codexPoolAffinityKey(headers: Headers): string | undefined {
59
+ const parentThreadId = boundedCodexAffinityComponent(headers.get("x-codex-parent-thread-id"));
60
+ if (parentThreadId) return parentThreadId;
61
+
62
+ const sessionId = boundedCodexAffinityComponent(headers.get("session-id"));
63
+ const threadId = boundedCodexAffinityComponent(headers.get("thread-id"));
64
+ if (!sessionId || !threadId) return undefined;
65
+
66
+ return `app:${createHmac("sha256", CODEX_APP_AFFINITY_KEY)
67
+ .update("opencodex-app-pool-affinity-v1\0")
68
+ .update(sessionId)
69
+ .update("\0")
70
+ .update(threadId)
71
+ .digest("base64url")}`;
72
+ }
31
73
 
32
74
  export type CodexAuthContext =
33
75
  | { kind: "main"; accountId: null }
@@ -38,6 +80,10 @@ export type CodexAuthContext =
38
80
  generation: number;
39
81
  accessToken: string;
40
82
  chatgptAccountId: string;
83
+ /** Bypass Pool selection and suppress quota/transient failover for an exact selector. */
84
+ fixedAccount?: boolean;
85
+ /** Pool binding key; the Desktop fallback is an opaque process-local HMAC. */
86
+ affinityKey?: string;
41
87
  /**
42
88
  * Set when this request was admitted through an active quota cooldown as
43
89
  * the account's single probe. Must be echoed into the upstream outcome so
@@ -57,6 +103,10 @@ export type CodexAuthContext =
57
103
  writerGeneration: number;
58
104
  accessToken: string;
59
105
  chatgptAccountId: string;
106
+ /** Bypass Pool selection and suppress quota/transient failover for an exact selector. */
107
+ fixedAccount?: boolean;
108
+ /** See `pool.affinityKey`. */
109
+ affinityKey?: string;
60
110
  /** See `pool.probeLeaseId`. */
61
111
  probeLeaseId?: string;
62
112
  quotaScope?: CodexQuotaScope;
@@ -100,12 +150,80 @@ export class CodexAuthContextError extends Error {
100
150
  }
101
151
 
102
152
  export class CodexPoolAuthenticationError extends Error {
103
- constructor() {
104
- super("OpenAI account pool has no usable account credential");
153
+ constructor(message = "OpenAI account pool has no usable account credential") {
154
+ super(message);
105
155
  this.name = "CodexPoolAuthenticationError";
106
156
  }
107
157
  }
108
158
 
159
+ export const CODEX_MAIN_PROFILE_MAINTENANCE_MESSAGE =
160
+ "OpenCodex local native-main profile maintenance is active; retry this request";
161
+
162
+ export class CodexMainProfileDrainingError extends Error {
163
+ /**
164
+ * Which startup-gate state fenced this request, when one did. Undefined means the
165
+ * fence came from somewhere other than the startup gate — the turn-drain claim race
166
+ * throws this same error while the gate reads `ready`, and inventing a reason there
167
+ * would point the next report at a gate that never closed.
168
+ *
169
+ * Captured here rather than at the throw sites because this is the last moment it is
170
+ * both in scope and still true: every catch site has already lost it, and re-reading
171
+ * the gate later can observe a recovery that completed in between (#2108).
172
+ */
173
+ readonly reason?: NativeMainStartupBlockReason;
174
+
175
+ constructor() {
176
+ super(CODEX_MAIN_PROFILE_MAINTENANCE_MESSAGE);
177
+ this.name = "CodexMainProfileDrainingError";
178
+ const gate = nativeMainStartupGateSnapshot();
179
+ if (gate.status !== "blocked") return;
180
+ this.reason = gate.reason;
181
+ reportNativeMainFenceReason(gate.reason);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * #2108: a reboot could leave this fence closed until `ocx restart`, and the report was
187
+ * unactionable because the settled reason was never written anywhere. It cannot ride the
188
+ * message (claude-messages.ts matches that string exactly to keep the fence a 503 rather
189
+ * than an Anthropic 529) and it cannot ride a header (/api/logs reads only error.message
190
+ * from the body, and the Claude surface rebuilds its response headers from scratch), so
191
+ * stdout is the one surface that covers every path this fence fires on.
192
+ *
193
+ * Deduped per distinct reason: the original report shows three 503s in eleven seconds and
194
+ * a real client retries harder than that, so a per-request line would bury the signal.
195
+ * Only the reason is emitted; the snapshot's homeId is derived from a profile directory.
196
+ */
197
+ const reportedFenceReasons = new Set<NativeMainStartupBlockReason>();
198
+
199
+ function reportNativeMainFenceReason(reason: NativeMainStartupBlockReason): void {
200
+ if (reportedFenceReasons.has(reason)) return;
201
+ reportedFenceReasons.add(reason);
202
+ console.warn(
203
+ `native-main admission is fenced (reason: ${reason}); native model requests return 503 until it clears`,
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Test-only reset for the dedup set above.
209
+ *
210
+ * The dedup is process-lifetime module state, so it is order-sensitive across test files
211
+ * sharing one Bun process: whichever file constructs this error first consumes the one-shot
212
+ * warn, and a later file asserting on it would see nothing and pass vacuously. Any test that
213
+ * asserts on the warn must call this first — an `afterEach` in the asserting file is not
214
+ * enough on its own, because the consuming file may not be the asserting one.
215
+ */
216
+ export function __resetNativeMainFenceReasonLog(): void {
217
+ reportedFenceReasons.clear();
218
+ }
219
+
220
+ export function codexMainProfileDrainingResponse(): Response {
221
+ const response = formatErrorResponse(503, "server_busy", CODEX_MAIN_PROFILE_MAINTENANCE_MESSAGE);
222
+ const headers = new Headers(response.headers);
223
+ headers.set("Retry-After", "1");
224
+ return new Response(response.body, { status: response.status, headers });
225
+ }
226
+
109
227
  export class CodexDirectAuthenticationError extends Error {
110
228
  constructor() {
111
229
  super("Codex Direct requires a caller Authorization bearer token");
@@ -154,22 +272,31 @@ export function cooldownAccountLabel(accountId: string): string {
154
272
  * as HTTP. The bare "cooling down" string left users with no route but commenting out the
155
273
  * injected `openai_base_url` in config.toml.
156
274
  */
157
- export function cooldownErrorMessage(err: CodexAccountCooldownError): string {
275
+ export function cooldownErrorMessage(err: CodexAccountCooldownError, accountSelector?: string): string {
158
276
  const until = new Date(err.cooldownUntil).toISOString();
159
277
  const scope = err.quotaScope === "spark"
160
278
  ? "Spark quota"
161
279
  : err.quotaScope === "shared"
162
280
  ? "shared native quota"
163
281
  : null;
164
- return `Selected Codex account (${cooldownAccountLabel(err.accountId)})${scope ? ` ${scope} is` : " is"} cooling down until ${until}`
165
- + ` (source: ${err.cooldownSource ?? "default"}).`
166
- + ` Run 'ocx account list openai' to find the id, then`
167
- + ` 'ocx account clear-cooldown openai <id>' to lift it, or switch accounts with 'ocx account use openai <id>'.`;
282
+ const selected = accountSelector
283
+ ? `Selected Codex account selector (${accountSelector})`
284
+ : `Selected Codex account (${cooldownAccountLabel(err.accountId)})`;
285
+ const recovery = accountSelector
286
+ ? " This request is pinned to that selector and will not switch accounts; choose another account-qualified model or retry later."
287
+ : " Run 'ocx account list openai' to find the id, then"
288
+ + " 'ocx account clear-cooldown openai <id>' to lift it, or switch accounts with 'ocx account use openai <id>'.";
289
+ return `${selected}${scope ? ` ${scope} is` : " is"} cooling down until ${until}`
290
+ + ` (source: ${err.cooldownSource ?? "default"}).${recovery}`;
168
291
  }
169
292
 
170
293
  /** HTTP form of {@link cooldownErrorMessage}, carrying Retry-After for well-behaved clients. */
171
- export function cooldownErrorResponse(err: CodexAccountCooldownError, now = Date.now()): Response {
172
- const res = formatErrorResponse(429, "rate_limit_error", cooldownErrorMessage(err));
294
+ export function cooldownErrorResponse(
295
+ err: CodexAccountCooldownError,
296
+ now = Date.now(),
297
+ accountSelector?: string,
298
+ ): Response {
299
+ const res = formatErrorResponse(429, "rate_limit_error", cooldownErrorMessage(err, accountSelector));
173
300
  const headers = new Headers(res.headers);
174
301
  headers.set("Retry-After", String(Math.max(1, Math.ceil((err.cooldownUntil - now) / 1000))));
175
302
  return new Response(res.body, { status: res.status, headers });
@@ -195,8 +322,28 @@ export function shouldMarkAccountNeedsReauthForCodexAuthFailure(cause: unknown):
195
322
 
196
323
  export interface ResolveCodexAuthContextOptions {
197
324
  excludeAccountId?: string;
325
+ /** Resolve exactly this account without consulting or mutating Pool selection. */
326
+ accountId?: string;
198
327
  /** Final native model selected for this request, used to select its quota group. */
199
328
  modelId?: string;
329
+ /** Short reservation converted to turn ownership before native `__main__` token materialization. */
330
+ beginCodexAccountSelection?: () => CodexAccountSelectionAdmission | undefined;
331
+ /** Test-only native credential read seams. */
332
+ isMainAccountTokenLive?: () => boolean;
333
+ getMainAccountToken?: typeof getMainAccountToken;
334
+ primeCodexPoolQuotas?: (config: OcxConfig, reason: string) => Promise<void>;
335
+ /** Test seam for account-gated native model discovery. */
336
+ resolveCodexModelEntitlements?: typeof resolveCodexModelEntitlements;
337
+ /** Direct requests admitted with a proxy bearer substitute the stored native-main credential. */
338
+ substituteMainCredentialForDirect?: boolean;
339
+ /** Test seam for a Direct request's own forwarded ChatGPT credential. */
340
+ isDirectCallerEntitledToCodexModel?: (headers: Headers, modelId: string) => Promise<boolean>;
341
+ }
342
+
343
+ export interface CodexAccountSelectionAdmission {
344
+ readonly mainProfileDraining: boolean;
345
+ claimMainProfile(): boolean;
346
+ release(): void;
200
347
  }
201
348
 
202
349
  export async function resolveCodexAuthContext(
@@ -206,33 +353,150 @@ export async function resolveCodexAuthContext(
206
353
  options: ResolveCodexAuthContextOptions = {},
207
354
  ): Promise<CodexAuthContext> {
208
355
  const writerGeneration = captureConfigGeneration();
209
- if (mode === "direct") {
356
+ const fixedAccountId = options.accountId;
357
+ if (fixedAccountId !== undefined && options.excludeAccountId !== undefined) {
358
+ throw new Error("Codex auth context cannot select and exclude an account simultaneously");
359
+ }
360
+ // An explicit namespace binding is stronger than the provider's default mode. It must use the
361
+ // selected stored credential even while the canonical OpenAI provider is globally Direct.
362
+ if (mode === "direct" && fixedAccountId === undefined) {
210
363
  if (!hasCallerCodexBearer(headers)) throw new CodexDirectAuthenticationError();
364
+ if (options.modelId && ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(options.modelId)) {
365
+ const entitled = options.substituteMainCredentialForDirect
366
+ ? entitledCodexAccountIdsForModel(
367
+ await (options.resolveCodexModelEntitlements ?? resolveCodexModelEntitlements)(config),
368
+ options.modelId,
369
+ )?.has(MAIN_CODEX_ACCOUNT_ID) === true
370
+ : await (options.isDirectCallerEntitledToCodexModel ?? isDirectCallerEntitledToCodexModel)(
371
+ headers,
372
+ options.modelId,
373
+ );
374
+ if (!entitled) {
375
+ throw new CodexPoolAuthenticationError("The selected ChatGPT account does not support this model");
376
+ }
377
+ }
211
378
  return { kind: "main", accountId: null };
212
379
  }
213
- reconcileMainCodexAccountRuntimeState();
214
- const threadId = headers.get("x-codex-parent-thread-id");
380
+ const affinityKey = fixedAccountId === undefined ? codexPoolAffinityKey(headers) : undefined;
381
+ // Retained startup recovery makes the physical main identity ineligible. Routing
382
+ // can still preserve service by selecting a healthy configured pool account.
383
+ const nativeMainTrafficBlocked = isNativeMainTrafficBlocked();
384
+ const selectionAdmission = options.beginCodexAccountSelection?.();
385
+ const nativeMainReadsForbidden = nativeMainTrafficBlocked || selectionAdmission?.mainProfileDraining === true;
386
+ let accountId: string;
215
387
  const quotaScope = codexQuotaScopeForModel(options.modelId);
216
- const resolution = options.excludeAccountId
217
- ? (() => {
218
- const accountId = pickAlternateCodexAccount(config, options.excludeAccountId!, Date.now(), quotaScope);
219
- return accountId
220
- ? { status: "selected" as const, accountId }
221
- : { status: "none" as const };
222
- })()
223
- : resolveCodexAccountForThreadDetailed(threadId, config, Date.now(), quotaScope);
224
- if (resolution.status === "expired") throw new CodexThreadAffinityExpiredError(resolution.accountId);
225
- let accountId = resolution.status === "selected" ? resolution.accountId : null;
226
- if (!accountId) throw new CodexPoolAuthenticationError();
388
+ try {
389
+ const excludeAccountIds = nativeMainReadsForbidden
390
+ ? new Set([MAIN_CODEX_ACCOUNT_ID])
391
+ : undefined;
392
+ const entitlementSnapshot = options.modelId && ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(options.modelId)
393
+ ? await (options.resolveCodexModelEntitlements ?? resolveCodexModelEntitlements)(config, { excludeAccountIds })
394
+ : undefined;
395
+ const entitledAccountIds = entitlementSnapshot
396
+ ? entitledCodexAccountIdsForModel(entitlementSnapshot, options.modelId)
397
+ : undefined;
398
+ const modelEligibleAccountIds = entitledAccountIds
399
+ ? new Set([...entitledAccountIds].filter(candidate => !excludeAccountIds?.has(candidate)))
400
+ : undefined;
401
+ const selectionOptions = {
402
+ // Temporary switch drain keeps the candidate until the atomic claim rejects
403
+ // it. Retained recovery makes main wholly ineligible so pool routing continues.
404
+ nativeMainSelectionOnly: !nativeMainTrafficBlocked
405
+ && selectionAdmission?.mainProfileDraining === true,
406
+ isMainAccountTokenLive: options.isMainAccountTokenLive,
407
+ modelEligibleAccountIds,
408
+ };
409
+ // A pre-drain selector reserves the native identity while reconciliation and
410
+ // routing inspect it. Selectors arriving after the fence skip reconciliation
411
+ // and may still route to non-main pool accounts without touching switch state.
412
+ if (!nativeMainReadsForbidden) reconcileMainCodexAccountRuntimeState();
413
+ const resolution = fixedAccountId !== undefined
414
+ ? { status: "selected" as const, accountId: fixedAccountId }
415
+ : options.excludeAccountId
416
+ ? (() => {
417
+ const selected = pickAlternateCodexAccount(
418
+ config,
419
+ options.excludeAccountId!,
420
+ Date.now(),
421
+ quotaScope,
422
+ selectionOptions,
423
+ );
424
+ return selected
425
+ ? { status: "selected" as const, accountId: selected }
426
+ : { status: "none" as const };
427
+ })()
428
+ : resolveCodexAccountForThreadDetailed(affinityKey ?? null, config, Date.now(), quotaScope, selectionOptions);
429
+ if (resolution.status === "expired") throw new CodexThreadAffinityExpiredError(resolution.accountId);
430
+ const selected = resolution.status === "selected" ? resolution.accountId : null;
431
+ if (!selected) {
432
+ if (fixedAccountId !== undefined) {
433
+ throw new CodexPoolAuthenticationError(
434
+ modelEligibleAccountIds && !modelEligibleAccountIds.has(fixedAccountId)
435
+ ? "Selected Codex account does not support this model"
436
+ : "Selected Codex account is unavailable",
437
+ );
438
+ }
439
+ // Recovery deliberately makes physical main ineligible. If no healthy
440
+ // pool route is configured and main is the intended route, report the
441
+ // temporary fence rather than misclassifying that credential as invalid.
442
+ // A configured pool retry/exclusion that finds no alternate preserves its
443
+ // ordinary pool-auth failure instead of being mislabeled as a main fence.
444
+ if (nativeMainTrafficBlocked && !options.excludeAccountId) {
445
+ throw new CodexMainProfileDrainingError();
446
+ }
447
+ throw new CodexPoolAuthenticationError(
448
+ modelEligibleAccountIds ? "No eligible Codex account supports this model" : undefined,
449
+ );
450
+ }
451
+ accountId = selected;
452
+ if (accountId === MAIN_CODEX_ACCOUNT_ID && nativeMainTrafficBlocked) {
453
+ throw new CodexMainProfileDrainingError();
454
+ }
455
+ if (
456
+ accountId === MAIN_CODEX_ACCOUNT_ID
457
+ && selectionAdmission
458
+ && !selectionAdmission.claimMainProfile()
459
+ ) {
460
+ throw new CodexMainProfileDrainingError();
461
+ }
462
+ // Some legacy Pool fallbacks preserve a configured active account even when it is not
463
+ // currently selectable, so token/cooldown code can produce the historical actionable error.
464
+ // Model entitlement is different: sending the request would spend a turn on an account whose
465
+ // authenticated roster already denied the model. Reassert this boundary after every selector.
466
+ if (modelEligibleAccountIds && !modelEligibleAccountIds.has(accountId)) {
467
+ throw new CodexPoolAuthenticationError(
468
+ fixedAccountId !== undefined
469
+ ? "Selected Codex account does not support this model"
470
+ : "No eligible Codex account supports this model",
471
+ );
472
+ }
473
+ if (fixedAccountId !== undefined) {
474
+ if (isCodexAccountPaused(config, accountId)) {
475
+ throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
476
+ }
477
+ if (isAccountNeedsReauth(accountId)) {
478
+ throw new CodexPoolAuthenticationError("Selected Codex account needs reauthentication");
479
+ }
480
+ if (!isCodexAccountUsable(config, accountId, selectionOptions)) {
481
+ throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
482
+ }
483
+ }
484
+ } finally {
485
+ selectionAdmission?.release();
486
+ }
227
487
  // Lazy prime: if the selected account has no quota yet, the pool is likely
228
488
  // unprimed (dashboard never opened, or startup prime was blocked). Kick a
229
489
  // best-effort prime so the NEXT routing decision has real scores. This never
230
490
  // blocks the current request, and the helper's single-flight guard collapses
231
491
  // repeated triggers into one pass.
232
- if (!getAccountQuota(accountId)) {
233
- import("./auth-api")
234
- .then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "pre-route"))
235
- .catch(() => {});
492
+ if (fixedAccountId === undefined && !nativeMainReadsForbidden && !getAccountQuota(accountId)) {
493
+ if (options.primeCodexPoolQuotas) {
494
+ void options.primeCodexPoolQuotas(config, "pre-route").catch(() => {});
495
+ } else {
496
+ import("./auth-api")
497
+ .then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "pre-route"))
498
+ .catch(() => {});
499
+ }
236
500
  }
237
501
  // Snapshot (not just the deadline) so a refused request can report WHY it is cooled:
238
502
  // a literal Retry-After reads very differently to a user than a reset-derived guess.
@@ -244,6 +508,11 @@ export async function resolveCodexAuthContext(
244
508
  let probeLeaseId: string | undefined;
245
509
  let probeQuotaScope: CodexQuotaScope | undefined;
246
510
  if (cooldownUntil) {
511
+ // Exact bindings are not Pool recovery traffic. Fail closed instead of consuming the Pool's
512
+ // one probe lease or selecting another account.
513
+ if (fixedAccountId !== undefined) {
514
+ throw new CodexAccountCooldownError(accountId, cooldownUntil, cooldown?.cooldownSource, cooldown?.quotaScope);
515
+ }
247
516
  probeQuotaScope = cooldown?.quotaScope;
248
517
  probeLeaseId = probeQuotaScope
249
518
  ? tryAcquireCodexQuotaScopeProbeLease(accountId, probeQuotaScope) ?? undefined
@@ -255,12 +524,14 @@ export async function resolveCodexAuthContext(
255
524
 
256
525
  if (accountId === MAIN_CODEX_ACCOUNT_ID) {
257
526
  // Main account in rotation: inject the read-only auth.json token and fail closed if it vanished.
258
- const token = getMainAccountToken();
527
+ const token = (options.getMainAccountToken ?? getMainAccountToken)();
259
528
  if (!token) {
260
529
  // Nothing will reach upstream, so give the probe back instead of burning it.
261
530
  if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
262
531
  else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
263
- throw new CodexPoolAuthenticationError();
532
+ throw new CodexPoolAuthenticationError(
533
+ fixedAccountId !== undefined ? "Selected Codex account is unavailable" : undefined,
534
+ );
264
535
  }
265
536
  return {
266
537
  kind: "main-pool",
@@ -268,6 +539,8 @@ export async function resolveCodexAuthContext(
268
539
  writerGeneration,
269
540
  accessToken: token.accessToken,
270
541
  chatgptAccountId: token.chatgptAccountId,
542
+ ...(fixedAccountId !== undefined ? { fixedAccount: true } : {}),
543
+ ...(affinityKey ? { affinityKey } : {}),
271
544
  ...(quotaScope ? { quotaScope } : {}),
272
545
  ...(probeLeaseId ? { probeLeaseId } : {}),
273
546
  ...(probeQuotaScope ? { probeQuotaScope } : {}),
@@ -283,6 +556,8 @@ export async function resolveCodexAuthContext(
283
556
  generation: token.generation,
284
557
  accessToken: token.accessToken,
285
558
  chatgptAccountId: token.chatgptAccountId,
559
+ ...(fixedAccountId !== undefined ? { fixedAccount: true } : {}),
560
+ ...(affinityKey ? { affinityKey } : {}),
286
561
  ...(quotaScope ? { quotaScope } : {}),
287
562
  ...(probeLeaseId ? { probeLeaseId } : {}),
288
563
  ...(probeQuotaScope ? { probeQuotaScope } : {}),
@@ -323,7 +598,32 @@ export function applyCodexAuthContextToProvider(
323
598
  };
324
599
  }
325
600
 
326
- export function headersForCodexAuthContext(headers: Headers, ctx: CodexAuthContext): Headers {
601
+ export class CodexMainSubstitutionUnavailableError extends Error {
602
+ constructor() {
603
+ super("No usable Codex main credential to substitute for an admission bearer");
604
+ this.name = "CodexMainSubstitutionUnavailableError";
605
+ }
606
+ }
607
+
608
+ /**
609
+ * Build the upstream auth headers for one Codex turn.
610
+ *
611
+ * The two credential domains meet here, and only here:
612
+ *
613
+ * - `pool` / `main-pool` always OVERWRITE with the stored account credential. Whatever the
614
+ * caller sent is irrelevant to what we send upstream.
615
+ * - `main` with an admission-bearer caller (#1686) must substitute the stored main credential.
616
+ * The caller proved admission with one of OUR secrets, which must never leave the process, so
617
+ * the only two acceptable outcomes are replaced-with-stored-main or fail-before-any-IO.
618
+ * Silently forwarding would be the leak validateForwardAdmissionCredential exists to prevent.
619
+ * - `main` with a dedicated-header caller keeps the existing intentional passthrough: the bearer
620
+ * there is the user's own ChatGPT credential, not ours.
621
+ */
622
+ export function materializeCodexUpstreamAuth(
623
+ headers: Headers,
624
+ ctx: CodexAuthContext,
625
+ options: { substituteMainCredential?: boolean } = {},
626
+ ): Headers {
327
627
  const selected = new Headers();
328
628
  for (const name of FORWARD_HEADERS) {
329
629
  const value = headers.get(name);
@@ -332,10 +632,26 @@ export function headersForCodexAuthContext(headers: Headers, ctx: CodexAuthConte
332
632
  if (ctx.kind === "pool" || ctx.kind === "main-pool") {
333
633
  selected.set("authorization", `Bearer ${ctx.accessToken}`);
334
634
  selected.set("chatgpt-account-id", ctx.chatgptAccountId);
635
+ return selected;
636
+ }
637
+ if (ctx.kind === "main" && options.substituteMainCredential === true) {
638
+ const stored = getMainAccountToken();
639
+ // Fail BEFORE any upstream I/O. Falling through here would send the admission secret.
640
+ if (!stored?.accessToken || !isMainAccountTokenLive()) {
641
+ throw new CodexMainSubstitutionUnavailableError();
642
+ }
643
+ selected.set("authorization", `Bearer ${stored.accessToken}`);
644
+ if (stored.chatgptAccountId) selected.set("chatgpt-account-id", stored.chatgptAccountId);
645
+ return selected;
335
646
  }
336
647
  return selected;
337
648
  }
338
649
 
650
+ /** @deprecated Prefer materializeCodexUpstreamAuth; kept for call sites without admission context. */
651
+ export function headersForCodexAuthContext(headers: Headers, ctx: CodexAuthContext): Headers {
652
+ return materializeCodexUpstreamAuth(headers, ctx);
653
+ }
654
+
339
655
  export function isCodexAuthContextUsable(ctx: CodexAuthContext, config: OcxConfig): boolean {
340
656
  if (ctx.kind === "main") return true;
341
657
  if (ctx.kind === "main-pool") return isCodexAccountUsable(config, ctx.accountId);
@@ -47,6 +47,7 @@ export interface StartupHealth {
47
47
  recommendedCommand: string | null;
48
48
  commands: {
49
49
  installService: string;
50
+ repairService: string;
50
51
  installShim: string;
51
52
  restoreNative: string;
52
53
  };
@@ -54,6 +55,7 @@ export interface StartupHealth {
54
55
 
55
56
  const COMMANDS = {
56
57
  installService: "ocx service install",
58
+ repairService: "ocx service repair",
57
59
  installShim: "ocx codex-shim install",
58
60
  restoreNative: "ocx restore",
59
61
  } as const;
@@ -88,7 +90,12 @@ export function deriveStartupHealth(inputs: StartupHealthInputs): StartupHealth
88
90
  : inputs.routingKind === "custom-local" || inputs.routingKind === "unknown"
89
91
  ? COMMANDS.restoreNative
90
92
  : inputs.serviceSupported
91
- ? COMMANDS.installService
93
+ // An already-registered service is refreshed in place: `repair` rewrites its assets
94
+ // and restarts it without re-registering, so it needs no elevation on Windows and
95
+ // cannot switch a WinSW install to Task Scheduler the way `install` would. Only a
96
+ // genuinely absent (or conflicting, which needs uninstall-then-install) service
97
+ // gets the registering command.
98
+ ? (inputs.serviceInstalled && !inputs.serviceConflict ? COMMANDS.repairService : COMMANDS.installService)
92
99
  : COMMANDS.restoreNative;
93
100
  return {
94
101
  ...inputs,
@@ -147,3 +154,19 @@ export function startupHealthSummary(health: StartupHealth): string {
147
154
  if (health.serviceInstalled && !health.serviceViable) return `AT RISK after restart (installed service is disabled, stopped, or unhealthy; run '${command}')`;
148
155
  return `AT RISK after restart (no viable background service; run '${command}')`;
149
156
  }
157
+
158
+ /**
159
+ * The routing/service/shim token `ocx doctor` prints under restart safety.
160
+ * Extracted so `ocx status` can show the same string rather than growing a
161
+ * second copy that drifts (#2411). Two management routes computing the same
162
+ * thing separately is exactly how #2457 happened.
163
+ */
164
+ export function formatStartupRoutingDetail(health: StartupHealth): string {
165
+ const service = health.serviceViable
166
+ ? "viable"
167
+ : health.serviceInstalled ? "installed-but-unhealthy" : "absent";
168
+ const shim = health.shimHealthy
169
+ ? "healthy"
170
+ : health.shimInstalled ? "stale" : "absent";
171
+ return `routing=${health.routingKind}, service=${service}, shim=${shim}`;
172
+ }
@@ -0,0 +1,67 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import {
3
+ codexAccountNamespaceEntries,
4
+ codexAccountPickerEnabled,
5
+ isMainCodexAccountTarget,
6
+ } from "../account-namespaces";
7
+ import type { RawEntry } from "./parsing";
8
+
9
+ /** Stable nonsemantic marker used to distinguish generated rows from provider-owned rows. */
10
+ export const CODEX_ACCOUNT_BOUND_CATALOG_KIND = "account-selector-v1";
11
+
12
+ /**
13
+ * Public selectors whose configured account row still exists.
14
+ *
15
+ * Stale mappings stay in config so exact routing keeps failing closed, but they must not advertise
16
+ * a deleted account. Credential health, pause, and reauthentication state intentionally do not
17
+ * churn catalog identity. Selector keys have already passed config's nonempty, single-segment
18
+ * namespace validation. Only those public keys leave this boundary; private account ids do not.
19
+ */
20
+ export function visibleCodexAccountSelectors(
21
+ config: Pick<OcxConfig, "codexAccounts" | "codexAccountNamespaces" | "codexAccountPickerEnabled">,
22
+ ): string[] {
23
+ if (!codexAccountPickerEnabled(config)) return [];
24
+ const storedPoolAccounts = new Set(
25
+ (config.codexAccounts ?? [])
26
+ .filter(account => !account.isMain)
27
+ .map(account => account.id),
28
+ );
29
+ return codexAccountNamespaceEntries(config)
30
+ .filter(([, accountId]) =>
31
+ isMainCodexAccountTarget(accountId) || storedPoolAccounts.has(accountId)
32
+ )
33
+ .map(([selector]) => selector);
34
+ }
35
+
36
+ export function accountBoundNativeDisplayName(selector: string, native: RawEntry): string {
37
+ const rawModel = typeof native.display_name === "string"
38
+ ? native.display_name
39
+ : String(native.slug ?? "");
40
+ const model = rawModel
41
+ .replace(/^gpt-/i, "")
42
+ .split("-")
43
+ .filter(Boolean)
44
+ .map(part => /^[a-z]/.test(part) ? part.charAt(0).toUpperCase() + part.slice(1) : part)
45
+ .join(" ");
46
+ return `${selector} / ${model}`;
47
+ }
48
+
49
+ /** Identify current generated rows without changing Codex's semantic model fields. */
50
+ export function trustedAccountBoundNativeCatalogSlug(entry: RawEntry): string | undefined {
51
+ if (entry.opencodex_catalog_kind !== CODEX_ACCOUNT_BOUND_CATALOG_KIND
52
+ || typeof entry.slug !== "string") return undefined;
53
+ const slash = entry.slug.indexOf("/");
54
+ if (slash <= 0 || slash !== entry.slug.lastIndexOf("/") || slash === entry.slug.length - 1) {
55
+ return undefined;
56
+ }
57
+ return entry.slug.slice(slash + 1);
58
+ }
59
+
60
+ export function accountBoundNativeModelSlugs(
61
+ config: Pick<OcxConfig, "codexAccounts" | "codexAccountNamespaces" | "codexAccountPickerEnabled">,
62
+ nativeSlugs: Iterable<string>,
63
+ ): string[] {
64
+ const natives = [...nativeSlugs];
65
+ return visibleCodexAccountSelectors(config)
66
+ .flatMap(selector => natives.map(slug => `${selector}/${slug}`));
67
+ }