@bitkyc08/opencodex 2.84.0 → 2.87.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 (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -1,3 +1,4 @@
1
+ import { restoreApprovedCodexProviders } from "../provider-routing";
1
2
  import { beginCodexWriteSection, cleanExternalProviderJournal, publishCodexArtifact } from "./config-write-section";
2
3
  import { assertCodexHomeOwner, codexHomeIsAbsent, codexHomeOwnerBlocksCompensation, CodexHomeOwnerRefusal, type CodexHomeOwnerRefusalReason } from "../codex-home-owner";
3
4
  import { loadConfig } from "../../config";
@@ -115,6 +116,7 @@ export interface CodexRestoreConfigResult {
115
116
  | "owned-fields-stripped"
116
117
  | "routing-restored-provider-retained"
117
118
  | "external-provider-preserved"
119
+ | "approved-provider-restored"
118
120
  | "failed";
119
121
  message: string;
120
122
  retained?: RetainedCodexProviderTable;
@@ -236,6 +238,26 @@ export function failedHistoryRestoreFromOutcome(
236
238
  return failedHistoryRestore(undefined, describeHistoryJobFailure(outcome, "restore"));
237
239
  }
238
240
 
241
+ export function finishApprovedProviderRestore(result: CodexNativeRestoreResult, revalidateDesiredState = false): CodexNativeRestoreResult {
242
+ if (!result.success || revalidateDesiredState && shouldSyncCodexOnStart(loadConfig())) return result;
243
+ try {
244
+ if (!restoreApprovedCodexProviders()) return result;
245
+ } catch (error) {
246
+ if (error instanceof CodexHomeOwnerRefusal) throw error;
247
+ const message = `Approved Codex provider URL restoration failed: ${error instanceof Error ? error.message : String(error)}`;
248
+ return { ...result, success: false, message: `${result.message} ${message}`, artifacts: {
249
+ ...result.artifacts, config: { ...result.artifacts.config, state: "failed", action: "failed", message },
250
+ } };
251
+ }
252
+ const message = "Approved Codex provider URLs restored; identity, other settings and history preserved.";
253
+ return { ...result, message: result.externalProvider ? message : `${result.message} ${message}`, artifacts: {
254
+ ...result.artifacts,
255
+ config: { ...result.artifacts.config, state: result.artifacts.config.state === "partial" ? "partial" : "ok", changed: true,
256
+ action: result.artifacts.config.changed ? result.artifacts.config.action : "approved-provider-restored",
257
+ message: result.artifacts.config.state === "partial" ? `${result.artifacts.config.message} ${message}` : message },
258
+ } };
259
+ }
260
+
239
261
  function externalProviderRestoreResult(activeProvider: string): CodexNativeRestoreResult {
240
262
  const message = `External Codex provider ${tomlString(activeProvider)} preserved; no native restore was needed.`;
241
263
  return {
@@ -553,7 +575,7 @@ export async function restoreNativeCodexAsync(
553
575
  if (sibling) return sibling;
554
576
  try {
555
577
  assertCodexHomeOwner(getCodexHome());
556
- return await restoreNativeCodexAsyncImpl(options);
578
+ return finishApprovedProviderRestore(await restoreNativeCodexAsyncImpl(options), options.revalidateDesiredState);
557
579
  } catch (error) {
558
580
  if (error instanceof CodexHomeOwnerRefusal) return homeOwnerRestoreRefusal(error);
559
581
  if (!(error instanceof CodexRestoreRefusal)) throw error;
@@ -782,7 +804,7 @@ export function restoreNativeCodex(
782
804
  if (sibling) return sibling;
783
805
  try {
784
806
  assertCodexHomeOwner(getCodexHome());
785
- return restoreNativeCodexImpl(options);
807
+ return finishApprovedProviderRestore(restoreNativeCodexImpl(options), options.revalidateDesiredState);
786
808
  } catch (error) {
787
809
  if (error instanceof CodexHomeOwnerRefusal) return homeOwnerRestoreRefusal(error);
788
810
  throw error;
@@ -1,3 +1,4 @@
1
+ import { isApprovedCodexProviderUrl } from "../provider-routing";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import {
3
4
  hasInjectedCodexRouting,
@@ -54,7 +55,7 @@ export function classifyRoutingEndpoint(value: string): RoutingEndpointKind {
54
55
  }
55
56
 
56
57
  /** Classify actual routing dependency separately from opencodex ownership. */
57
- export function classifyCodexRouting(content: string): CodexRoutingKind {
58
+ export function classifyCodexRouting(content: string, approvedProviderUrl?: (id: string, url: string) => boolean): CodexRoutingKind {
58
59
  const rootBaseUrl = rootTomlString(content, "openai_base_url");
59
60
  if (rootBaseUrl) {
60
61
  const endpoint = classifyRoutingEndpoint(rootBaseUrl);
@@ -74,7 +75,7 @@ export function classifyCodexRouting(content: string): CodexRoutingKind {
74
75
  if (providerBaseUrl) {
75
76
  const endpoint = classifyRoutingEndpoint(providerBaseUrl);
76
77
  if (endpoint === "unknown") return "unknown";
77
- if (rootProvider === "opencodex") return "opencodex-local";
78
+ if (rootProvider === "opencodex" || endpoint === "local" && approvedProviderUrl?.(rootProvider, providerBaseUrl)) return "opencodex-local";
78
79
  return endpoint === "local" ? "custom-local" : "custom-remote";
79
80
  }
80
81
  if (
@@ -102,7 +103,7 @@ export function getCodexRoutingKind(): CodexRoutingKind {
102
103
  const path = CODEX_CONFIG_PATH;
103
104
  if (!existsSync(path)) return "native";
104
105
  try {
105
- return classifyCodexRouting(readFileSync(path, "utf8"));
106
+ return classifyCodexRouting(readFileSync(path, "utf8"), isApprovedCodexProviderUrl);
106
107
  } catch {
107
108
  return "unknown";
108
109
  }
@@ -1,3 +1,4 @@
1
+ import { injectApprovedCodexProvider } from "./provider-routing";
1
2
  import { beginCodexWriteSection, cleanExternalProviderJournal, publishCodexArtifact } from "./inject/config-write-section";
2
3
  import { closeSync, existsSync, openSync, readFileSync, statSync } from "node:fs";
3
4
  import { dirname } from "node:path";
@@ -46,8 +47,8 @@ import {
46
47
  writeJournal,
47
48
  } from "./journal";
48
49
  import { HISTORY_RELABEL_STANDS_DOWN } from "./history-provider";
50
+ import { formatApplyHistoryFailure } from "./inject/history-failure";
49
51
  import {
50
- describeHistoryJobFailure,
51
52
  deriveCodexHistoryOperation,
52
53
  resolveCodexHistoryJobTarget,
53
54
  runCodexHistoryJob,
@@ -142,6 +143,8 @@ export interface CodexInjectResult {
142
143
  message: string;
143
144
  /** False when injection intentionally preserves configuration owned by another provider. */
144
145
  configApplied?: false;
146
+ /** True only after a successful configuration transaction changes config.toml bytes. */
147
+ configChanged?: boolean;
145
148
  /**
146
149
  * Structured read-only history preflight refusal; never parsed from display text.
147
150
  *
@@ -240,6 +243,10 @@ async function injectCodexConfigImpl(
240
243
  const rawContent = missingConfig ? "" : readFileSync(CODEX_CONFIG_PATH, "utf-8");
241
244
  const activeProvider = externalCodexModelProvider(rawContent);
242
245
  if (activeProvider) {
246
+ const approved = injectApprovedCodexProvider(activeProvider, routingTarget, options.beforeClientWrite ? () => runClientWriteGuard(options.beforeClientWrite) : undefined, options.validateOnly);
247
+ if (approved) {
248
+ return { success: true, configChanged: approved.configChanged, ...(options.validateOnly ? { configApplied: false as const } : {}), message: `Approved Codex provider ${tomlString(activeProvider)} routes through OpenCodex; provider identity and transport settings preserved.` };
249
+ }
243
250
  // A launcher may have journaled before the provider manager took ownership. Never let shutdown
244
251
  // replay that stale snapshot over externally managed config.
245
252
  if (!options.validateOnly) {
@@ -258,7 +265,7 @@ async function injectCodexConfigImpl(
258
265
  : {}),
259
266
  message:
260
267
  `⚠️ Codex routing NOT injected: config.toml selects the external model_provider ${tomlString(activeProvider)}.\n` +
261
- ` OpenCodex preserves external provider configuration so existing ${tomlString(activeProvider)} session history stays visible.\n` +
268
+ ` OpenCodex preserves external provider configuration so existing ${tomlString(activeProvider)} session history stays visible.\n To authorize URL-only management, run ocx codex-provider allow ${tomlString(activeProvider)} and confirm.\n` +
262
269
  ` Configure that provider for Responses passthrough at ${routingTarget.baseUrl}` +
263
270
  `${routingTarget.requiresAdmissionToken ? ` with x-opencodex-api-key from ${routingTarget.tokenEnv}` : ""}.\n` +
264
271
  ` For direct injection, switch to the built-in openai provider, remove any user-owned root openai_base_url, and rerun 'ocx start'.`,
@@ -717,6 +724,11 @@ async function injectCodexConfigImpl(
717
724
  releaseConfigWriteLock(configLock.handle);
718
725
  heldConfigWriteLock = undefined;
719
726
  }
727
+ // Both write paths have committed, including the coordinator's post-callback checks.
728
+ // Compare against the admitted original, not post-v1-reconcile input: the native
729
+ // feature transition is part of this transaction too. Refusals/rollbacks return
730
+ // above and must never publish this receipt. No filesystem reread after unlock.
731
+ const configChanged = Boolean(missingConfig) || effectivePlan.content !== rawContent;
720
732
  // Legacy mode still forward-tags history so re-tagged threads stay listable. Design B needs
721
733
  // the opposite: a one-time migration of previously re-tagged threads BACK to openai (restore
722
734
  // machinery; cheap no-op when there is nothing to migrate). The client-compaction opt-in keeps
@@ -799,6 +811,7 @@ async function injectCodexConfigImpl(
799
811
  if (effectivePlan.keptUserBaseUrl && effectivePlan.keepRootOverrideAlongsideTable) {
800
812
  return {
801
813
  success: true,
814
+ configChanged,
802
815
  ...(effectivePlan.nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning: effectivePlan.nativeSubagentDefaultsWarning } : {}),
803
816
  ...(effectivePlan.historyRelabelRefusal ? { historyPreflightFailureReason: effectivePlan.historyRelabelRefusal } : {}),
804
817
  message:
@@ -817,6 +830,7 @@ async function injectCodexConfigImpl(
817
830
  if (effectivePlan.keptUserBaseUrl) {
818
831
  return {
819
832
  success: true,
833
+ configChanged,
820
834
  ...(effectivePlan.nativeSubagentDefaultsWarning
821
835
  ? { nativeSubagentDefaultsWarning: effectivePlan.nativeSubagentDefaultsWarning }
822
836
  : {}),
@@ -839,6 +853,7 @@ async function injectCodexConfigImpl(
839
853
  : `Pointed Codex's built-in openai provider at the opencodex proxy (openai_base_url + realtime sideband override).\n`;
840
854
  return {
841
855
  success: true,
856
+ configChanged,
842
857
  ...(effectivePlan.nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning: effectivePlan.nativeSubagentDefaultsWarning } : {}),
843
858
  ...(effectivePlan.historyRelabelRefusal ? { historyPreflightFailureReason: effectivePlan.historyRelabelRefusal } : {}),
844
859
  message:
@@ -860,31 +875,7 @@ export function getCodexConfigPath(): string {
860
875
  return CODEX_CONFIG_PATH;
861
876
  }
862
877
 
863
- /**
864
- * Frame one failed apply history job honestly.
865
- *
866
- * A genuine lock keeps the established deferred/SKIPPED wording; any other
867
- * reason names itself instead of blaming the Codex app/IDE.
868
- */
869
- export function formatApplyHistoryFailure(outcome: CodexHistoryJobOutcome, legacyMode: boolean): string {
870
- // A busy database is a deferral no matter which half observed it: the lock
871
- // contended (blocked/busy), or the worker acquired the lock and then found
872
- // SQLite busy (failed with a busy history reason). Only those keep the
873
- // deferred headline; every other failure is a real "NOT changed".
874
- const busy =
875
- (outcome.kind === "blocked" && outcome.reason === "busy") ||
876
- (outcome.kind === "failed" && outcome.historyFailureReason === "busy");
877
- const partiallyChanged = outcome.kind === "failed"
878
- && ((outcome.rows ?? 0) > 0 || (outcome.files ?? 0) > 0);
879
- const headline = partiallyChanged
880
- ? "Codex resume history changed but did not converge"
881
- : legacyMode
882
- ? "Codex resume history sync SKIPPED"
883
- : busy
884
- ? "Codex resume history metadata restore deferred"
885
- : "Codex resume history NOT changed";
886
- return ` ⚠️ ${headline}: ${describeHistoryJobFailure(outcome, "apply", legacyMode)}\n`;
887
- }
878
+ export { formatApplyHistoryFailure };
888
879
 
889
880
  export {
890
881
  providerBaseHost,
@@ -1,7 +1,7 @@
1
1
  import { beginCodexWriteSection, publishCodexArtifact } from "./inject/config-write-section";
2
2
  import { createHash } from "node:crypto";
3
3
  import type { AtomicWriteHooks } from "../config/atomic-write";
4
- import { existsSync, readFileSync, unlinkSync } from "node:fs";
4
+ import { existsSync, lstatSync, readFileSync, unlinkSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { atomicWriteFile } from "../config";
7
7
  import { assertCodexHomeOwner, codexHomeIsAbsent, CODEX_HOME_JOURNAL_FILE, CodexHomeOwnerRefusal, opencodexHomeForInjection, readCodexHomeJournal, type CodexHomeOwnerRefusalReason } from "./codex-home-owner";
@@ -347,6 +347,21 @@ export function restoreJournalState(
347
347
  };
348
348
  }
349
349
 
350
+ // A matching byte hash does not authorize following a newly substituted profile link.
351
+ // Refuse the complete pair before restoring config or deleting its recovery journal.
352
+ let profileIsSymlink = false;
353
+ if (profileUnchanged && (!comparison.profileAlreadyOriginal || comparison.originalProfile === null)) {
354
+ try { profileIsSymlink = lstatSync(CODEX_PROFILE_PATH).isSymbolicLink(); }
355
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
356
+ }
357
+ if (profileIsSymlink) {
358
+ return {
359
+ configRestored: comparison.configAlreadyOriginal, profileRestored: false,
360
+ configRewritten: false, profileRewritten: false,
361
+ configChanged: !configUnchanged, profileChanged: false,
362
+ complete: false, unverified: false, profileRestoreFailed: true as const,
363
+ };
364
+ }
350
365
  assertCodexHomeOwner(CODEX_HOME);
351
366
  let configRestored = comparison.configAlreadyOriginal;
352
367
  let profileRestored = comparison.profileAlreadyOriginal;
@@ -1,4 +1,7 @@
1
1
  import type { OcxConfig } from "../types";
2
+ import { MAIN_CODEX_ACCOUNT_ID } from "./account-id";
3
+ import { codexAccountUsesRemainingQuota } from "./account-use-remaining";
4
+ import { isDeclaredReasoningEffort } from "../reasoning-effort";
2
5
  import { getMainPolicyQuota } from "./quota";
3
6
  import { MAIN_ACCOUNT_HARD_LOCK_PERCENT, MAIN_ACCOUNT_HARD_LOCK_SHORT_PERCENT, MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT } from "./quota-types";
4
7
 
@@ -13,9 +16,35 @@ export interface MainAccountHardLockStatus {
13
16
  window?: "short" | "long";
14
17
  }
15
18
 
16
- type PolicyConfig = Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds">;
19
+ type PolicyConfig = Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "codexMainAccountHardLockFallback" | "codexUseRemainingQuotaAccountIds">;
20
+
21
+ /** The reroute destination for requests the lock blocks, when one is configured and readable. */
22
+ export interface MainAccountHardLockFallback {
23
+ model: string;
24
+ reasoningEffort?: string;
25
+ }
26
+
27
+ /**
28
+ * The configured fallback, or `undefined` when nothing qualifies. The dashboard row keeps
29
+ * the configured model while its switch is off, so `enabled: false` pauses the reroute
30
+ * without forgetting the destination. A malformed hand edit — blank or padded model,
31
+ * undeclared effort — disables the reroute entirely rather than degrading into a
32
+ * half-applied one: the request path must never guess which part of a broken setting to
33
+ * keep. This is the same disable-don't-widen reading the compaction override takes, and
34
+ * the load/write boundaries report the malformation separately.
35
+ */
36
+ export function resolveMainAccountHardLockFallback(config: PolicyConfig | undefined): MainAccountHardLockFallback | undefined {
37
+ const raw = config?.codexMainAccountHardLockFallback;
38
+ if (!raw || raw.enabled === false) return undefined;
39
+ const model = typeof raw.model === "string" ? raw.model : "";
40
+ if (model.length === 0 || model !== model.trim() || /\s/.test(model)) return undefined;
41
+ if (raw.reasoningEffort !== undefined
42
+ && (typeof raw.reasoningEffort !== "string" || !isDeclaredReasoningEffort(raw.reasoningEffort))) return undefined;
43
+ return raw.reasoningEffort !== undefined ? { model, reasoningEffort: raw.reasoningEffort } : { model };
44
+ }
17
45
 
18
46
  export function resolveMainAccountHardLockThresholds(config: PolicyConfig | undefined): { short: number; long: number } {
47
+ if (codexAccountUsesRemainingQuota(config, MAIN_CODEX_ACCOUNT_ID)) return { short: 100, long: 100 };
19
48
  const valid = (value: unknown, fallback: number): number => typeof value === "number"
20
49
  && Number.isInteger(value) && value >= MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT && value <= 100 ? value : fallback;
21
50
  const long = valid(config?.codexMainAccountHardLockThresholds?.long, MAIN_ACCOUNT_HARD_LOCK_PERCENT);
@@ -31,6 +31,12 @@ import { advanceCodexCredentialMutationEpoch } from "./credential-mutation-epoch
31
31
  import { withNativeMainExclusiveClaim } from "./native-main-claim";
32
32
  import { resolveNativeProfileContext } from "./native-profile-store";
33
33
  import { isNativeMainTrafficBlocked } from "./native-profile-startup";
34
+ import { loadConfig } from "../config";
35
+ import {
36
+ requestCodexAuthStatus,
37
+ resolveNativeMainRefreshMode,
38
+ type CodexAuthStatus,
39
+ } from "./native-main-refresh-mode";
34
40
 
35
41
  export { MAIN_CODEX_ACCOUNT_ID } from "./account-id";
36
42
 
@@ -48,13 +54,11 @@ type MainAuthJsonCredential = {
48
54
  path: string;
49
55
  rawSha256: string;
50
56
  /**
51
- * Filesystem identity of the file the hash was taken from (#2999).
52
- *
53
- * A content hash cannot tell "unchanged" from "replaced with a file that happens to
54
- * hash the same", and more importantly it is read at a different instant than the
55
- * rename. Carrying dev+ino lets the pre-rename guard ask the sharper question: is this
56
- * still the same file, not merely one with the same bytes. `null` when the target could
57
- * not be stat'ed, which is treated as "cannot prove identity" rather than "matches".
57
+ * Filesystem identity of the file the hash was taken from. It catches a writer that
58
+ * replaces auth.json by rename with identical bytes. It does not see Codex's own writes:
59
+ * Codex rewrites auth.json in place (truncate + write), which keeps dev and ino, so only
60
+ * the content hash notices those, and only once the new bytes are visible (#2999).
61
+ * `null` when the target could not be stat'ed, which fails closed.
58
62
  */
59
63
  identity: { dev: number; ino: number } | null;
60
64
  root: Record<string, unknown>;
@@ -66,6 +70,8 @@ type MainAuthJsonCredential = {
66
70
 
67
71
  export interface NativeMainRefreshDependencies {
68
72
  refreshToken?: (refreshToken: string, options: { signal: AbortSignal }) => Promise<OAuthCredentials>;
73
+ /** Codex-mode seam: one app-server `getAuthStatus` call. Used only when Codex owns the refresh. */
74
+ codexAuthStatus?: (options: { refresh: boolean; signal: AbortSignal }) => Promise<CodexAuthStatus>;
69
75
  signal?: AbortSignal;
70
76
  /** Metadata-only refresh must not retract a concurrent traffic reauth quarantine. */
71
77
  preserveReauth?: boolean;
@@ -264,10 +270,9 @@ function assertMainAuthJsonSnapshotUnchanged(expected: MainAuthJsonCredential):
264
270
  if (!current || current.path !== expected.path || current.rawSha256 !== expected.rawSha256) {
265
271
  throw new MainAuthJsonChangedDuringRefreshError();
266
272
  }
267
- // Identity, not just content (#2999). A writer can land between this check and the
268
- // rename, and rename(2) replaces unconditionally - so the narrower the question asked
269
- // here, the smaller the window where a Codex login gets silently overwritten. An
270
- // unreadable identity on either side fails closed: unprovable is not the same as equal.
273
+ // Also refuse a rename-based replacement with identical bytes. An unreadable identity on
274
+ // either side fails closed. This narrows the window; rename(2) still replaces
275
+ // unconditionally after this check returns.
271
276
  assertMainAuthJsonIdentityUnchanged(expected);
272
277
  }
273
278
 
@@ -288,9 +293,7 @@ function persistRefreshedMainAuthJson(
288
293
  assertNotRealCodexHomeUnderTest(resolveCodexHomeDir());
289
294
  const accessToken = refreshed.access;
290
295
  const refreshToken = refreshed.refresh || expected.refreshToken!;
291
- const chatgptAccountId = refreshed.accountId
292
- ?? extractAccountId(undefined, accessToken)
293
- ?? expected.chatgptAccountId;
296
+ const { chatgptAccountId } = inFlightMainAccountToken(expected, refreshed);
294
297
  const tokens = {
295
298
  ...expected.tokens,
296
299
  access_token: accessToken,
@@ -475,6 +478,12 @@ async function resolveMainAccountToken(
475
478
  const signal = dependencies.signal
476
479
  ? AbortSignal.any([dependencies.signal, refreshTimeout])
477
480
  : refreshTimeout;
481
+ // An injected exchange is the guarded writer by definition. Otherwise Codex refreshes its own
482
+ // login whenever a supported binary exists, unless the operator chose "opencodex" (#2999).
483
+ const refreshMode = dependencies.refreshToken
484
+ ? null
485
+ : await resolveNativeMainRefreshMode(loadConfig());
486
+ if (signal.aborted) throw signal.reason;
478
487
  const lockKey = refreshGrantFingerprintForToken(initial.refreshToken);
479
488
  // Two locks, because they guard two different things that live in two different
480
489
  // homes. `withCodexRefreshFileLock` is keyed on the grant fingerprint and lives
@@ -508,6 +517,11 @@ async function resolveMainAccountToken(
508
517
  && mainAccessTokenFresh(locked.accessToken, Date.now(), MAIN_TOKEN_REFRESH_SKEW_MS)) {
509
518
  return { accessToken: locked.accessToken!, chatgptAccountId: locked.chatgptAccountId };
510
519
  }
520
+ if (refreshMode?.mode === "codex") {
521
+ const command = refreshMode.command!;
522
+ return refreshThroughCodex(locked, rejectedAccessToken, dependencies, refreshTimeout,
523
+ dependencies.codexAuthStatus ?? (options => requestCodexAuthStatus(command, options)));
524
+ }
511
525
  const refresh = dependencies.refreshToken ?? refreshNativeMainGrant;
512
526
  let refreshed: OAuthCredentials;
513
527
  if (signal.aborted) throw signal.reason;
@@ -539,7 +553,19 @@ async function resolveMainAccountToken(
539
553
  // Once the exchange starts, only its own timeout may abort it: a successful
540
554
  // rotating grant retires the old refresh token. Save the only live grant before
541
555
  // honoring caller cancellation, while retaining the external-writer guards.
542
- const result = persistRefreshedMainAuthJson(locked, refreshed);
556
+ let result: { accessToken: string; chatgptAccountId: string };
557
+ try {
558
+ result = persistRefreshedMainAuthJson(locked, refreshed);
559
+ } catch (cause) {
560
+ if (!(cause instanceof MainAuthJsonChangedDuringRefreshError)) throw cause;
561
+ // Another writer owns auth.json now (#2999). The exchange already succeeded, so
562
+ // failing the request would waste it. Serve the new access token to this request
563
+ // only: leave the file as the other writer left it, keep nothing as the main
564
+ // credential, and do not retire the grant.
565
+ noteNativeMainRefresh("publish-refused-served-in-flight");
566
+ if (dependencies.signal?.aborted) throw dependencies.signal.reason;
567
+ return inFlightMainAccountToken(locked, refreshed);
568
+ }
543
569
  // A permitted in-flight success speaks only for its own profile and grant.
544
570
  clearMainRefreshGrantRejection(mainRefreshGrantKey(locked)!);
545
571
  if (dependencies.preserveReauth !== true) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
@@ -556,6 +582,64 @@ async function resolveMainAccountToken(
556
582
  }
557
583
  }
558
584
 
585
+ /** The refreshed bearer for one request whose save was refused; never stored anywhere. */
586
+ function inFlightMainAccountToken(
587
+ expected: MainAuthJsonCredential,
588
+ refreshed: OAuthCredentials,
589
+ ): { accessToken: string; chatgptAccountId: string } {
590
+ return {
591
+ accessToken: refreshed.access,
592
+ chatgptAccountId: refreshed.accountId
593
+ ?? extractAccountId(undefined, refreshed.access)
594
+ ?? expected.chatgptAccountId,
595
+ };
596
+ }
597
+
598
+ /**
599
+ * Codex mode (#2999): Codex refreshes and stores its own login; OpenCodex only uses the bearer.
600
+ *
601
+ * auth.json is not read again after the call. In keyring mode Codex deletes the file once the
602
+ * keyring save succeeds, so the RPC answer is the only place the new token exists for us.
603
+ * The call runs on the refresh timeout alone: killing app-server mid-exchange could retire
604
+ * the grant before Codex saves its successor.
605
+ */
606
+ async function refreshThroughCodex(
607
+ locked: MainAuthJsonCredential,
608
+ rejectedAccessToken: string | undefined,
609
+ dependencies: NativeMainRefreshDependencies,
610
+ refreshTimeout: AbortSignal,
611
+ authStatus: NonNullable<NativeMainRefreshDependencies["codexAuthStatus"]>,
612
+ ): Promise<{ accessToken: string; chatgptAccountId: string }> {
613
+ let status: CodexAuthStatus;
614
+ try {
615
+ status = await authStatus({ refresh: true, signal: refreshTimeout });
616
+ } catch (cause) {
617
+ noteNativeMainRefresh("codex-unavailable");
618
+ throw new MainAccountTokenRefreshError("transient", { cause });
619
+ }
620
+ if (status.authMethod === "chatgpt" && !status.authToken) {
621
+ // The only permanent-failure signal the response carries: Codex withholds the token once
622
+ // its refresh was refused for good. The specific OAuth code is not on the wire.
623
+ noteNativeMainRefresh("codex-reauth");
624
+ markMainRefreshGrantRejected(mainRefreshGrantKey(locked)!);
625
+ throw new MainAccountTokenRefreshError("reauth");
626
+ }
627
+ const token = status.authToken;
628
+ if (status.authMethod !== "chatgpt" || !token) {
629
+ noteNativeMainRefresh("codex-no-chatgpt-login");
630
+ throw new MainAccountTokenRefreshError("transient");
631
+ }
632
+ // A transient Codex refresh failure still answers with its cached bearer.
633
+ if (token === rejectedAccessToken || !mainAccessTokenFresh(token, Date.now(), 0)) {
634
+ noteNativeMainRefresh("codex-transient");
635
+ throw new MainAccountTokenRefreshError("transient");
636
+ }
637
+ noteNativeMainRefresh("codex-ok");
638
+ if (dependencies.preserveReauth !== true) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
639
+ if (dependencies.signal?.aborted) throw dependencies.signal.reason;
640
+ return { accessToken: token, chatgptAccountId: extractAccountId(undefined, token) ?? locked.chatgptAccountId };
641
+ }
642
+
559
643
  /** Refresh the CLI-owned native credential before upstream I/O and publish it atomically. */
560
644
  export function getValidMainAccountToken(
561
645
  dependencies: NativeMainRefreshDependencies = {},