@bitkyc08/opencodex 2.86.0 → 2.88.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 (400) hide show
  1. package/gui/dist/assets/App-CHC89EXQ.js +58 -0
  2. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  3. package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
  4. package/gui/dist/assets/index-B5bsvxor.js +94 -0
  5. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  6. package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic/beta-allowlist.ts +9 -8
  10. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  11. package/src/adapters/anthropic/passthrough.ts +7 -23
  12. package/src/adapters/anthropic-progress.ts +84 -0
  13. package/src/adapters/anthropic-rejection-memo.ts +71 -0
  14. package/src/adapters/anthropic-replay.ts +135 -0
  15. package/src/adapters/anthropic-tool-catalog.ts +11 -0
  16. package/src/adapters/anthropic.ts +151 -33
  17. package/src/adapters/base.ts +2 -0
  18. package/src/adapters/claude-cli/adapter.ts +111 -23
  19. package/src/adapters/claude-cli/profiles.ts +2 -1
  20. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  21. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  22. package/src/adapters/coding-agent/protocol.ts +1 -1
  23. package/src/adapters/coding-agent/turn.ts +18 -2
  24. package/src/adapters/dsh-account.ts +97 -0
  25. package/src/adapters/google-progress-history.ts +53 -0
  26. package/src/adapters/google.ts +52 -9
  27. package/src/adapters/openai-chat/messages.ts +4 -3
  28. package/src/adapters/openai-chat/passthrough.ts +8 -2
  29. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  30. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  31. package/src/adapters/openai-responses/passthrough.ts +57 -125
  32. package/src/adapters/openai-responses/request-strips.ts +25 -3
  33. package/src/adapters/openai-responses/transport.ts +81 -0
  34. package/src/adapters/qoder/adapter.ts +4 -2
  35. package/src/adapters/registry.ts +5 -0
  36. package/src/adapters/zed.ts +26 -1
  37. package/src/advisor/activity.ts +55 -0
  38. package/src/advisor/budget.ts +62 -0
  39. package/src/advisor/consult.ts +4 -1
  40. package/src/advisor/context.ts +16 -4
  41. package/src/advisor/evidence-tool.ts +21 -0
  42. package/src/advisor/evidence.ts +117 -0
  43. package/src/advisor/runtime.ts +79 -31
  44. package/src/advisor/settings.ts +11 -2
  45. package/src/bridge/errors.ts +19 -1
  46. package/src/bridge/internal.ts +7 -1
  47. package/src/bridge/response-json.ts +18 -3
  48. package/src/bridge/sse.ts +59 -8
  49. package/src/bridge/terminal-refusal.ts +59 -0
  50. package/src/claude/desktop-3p-library.ts +32 -7
  51. package/src/claude/desktop-3p.ts +55 -13
  52. package/src/claude/desktop-first-party.ts +1 -1
  53. package/src/claude/desktop-picker-platform.ts +4 -0
  54. package/src/claude/desktop-picker.ts +15 -8
  55. package/src/claude/desktop-system-proxy.ts +50 -4
  56. package/src/claude/inbound-content-options.ts +77 -0
  57. package/src/claude/inbound.ts +33 -0
  58. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  59. package/src/claude/intercept/picker-runtime.ts +6 -5
  60. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  61. package/src/claude/intercept/picker-trust.ts +19 -0
  62. package/src/claude/intercept/port-policy.ts +30 -0
  63. package/src/claude/intercept/runtime.ts +97 -94
  64. package/src/claude/outbound.ts +46 -13
  65. package/src/cli/account-api.ts +1 -0
  66. package/src/cli/account-policy-dto.ts +6 -0
  67. package/src/cli/account-policy.ts +34 -5
  68. package/src/cli/account-preservation.ts +44 -0
  69. package/src/cli/account.ts +8 -2
  70. package/src/cli/advisor.ts +17 -3
  71. package/src/cli/capabilities-accounts.ts +20 -1
  72. package/src/cli/capabilities-agents-routing.ts +5 -2
  73. package/src/cli/capabilities-base.ts +38 -10
  74. package/src/cli/capabilities-observe-system.ts +63 -3
  75. package/src/cli/capabilities-provider-models.ts +11 -0
  76. package/src/cli/codex-cli-update.ts +151 -41
  77. package/src/cli/codex-provider.ts +44 -0
  78. package/src/cli/combo-input.ts +10 -1
  79. package/src/cli/combo-result.ts +1 -1
  80. package/src/cli/combo.ts +2 -1
  81. package/src/cli/cross-home-owner.ts +33 -16
  82. package/src/cli/dispatch.ts +25 -4
  83. package/src/cli/doctor.ts +6 -1
  84. package/src/cli/ensure-desktop-cold-start.ts +60 -7
  85. package/src/cli/help.ts +3 -2
  86. package/src/cli/index.ts +1 -1
  87. package/src/cli/integration-plan-dto.ts +1 -0
  88. package/src/cli/log-history.ts +172 -0
  89. package/src/cli/loopback-family.ts +31 -0
  90. package/src/cli/models-runtime-subcommands.ts +1 -0
  91. package/src/cli/models-runtime.ts +72 -8
  92. package/src/cli/observe-stream.ts +2 -0
  93. package/src/cli/observe.ts +16 -4
  94. package/src/cli/provider.ts +27 -5
  95. package/src/cli/registry.ts +28 -8
  96. package/src/cli/runtime-api.ts +5 -3
  97. package/src/cli/system-command.ts +40 -8
  98. package/src/cli/uninstall-client-state.ts +32 -0
  99. package/src/cli/usage-compare.ts +309 -0
  100. package/src/cli/usage-model-search.ts +4 -4
  101. package/src/cli/usage-report.ts +2 -2
  102. package/src/cli/v2.ts +1 -1
  103. package/src/client/machine-listener.ts +1 -3
  104. package/src/client/runtime.ts +1 -0
  105. package/src/clients/client-path.ts +51 -0
  106. package/src/clients/config-export/contracts.ts +2 -1
  107. package/src/clients/config-export.ts +18 -44
  108. package/src/clients/gentle-shell-path.ts +87 -0
  109. package/src/codex/account-auto-switch.ts +2 -0
  110. package/src/codex/account-identity.ts +29 -0
  111. package/src/codex/account-label.ts +1 -1
  112. package/src/codex/account-lifecycle.ts +21 -5
  113. package/src/codex/account-preservation.ts +28 -0
  114. package/src/codex/account-use-remaining.ts +23 -0
  115. package/src/codex/app-server-processes.ts +97 -7
  116. package/src/codex/app-server-restart-service.ts +55 -4
  117. package/src/codex/auth-api/account-list.ts +4 -0
  118. package/src/codex/auth-api/account-pause-group.ts +6 -50
  119. package/src/codex/auth-api/account-preservation.ts +57 -0
  120. package/src/codex/auth-api/routes.ts +8 -0
  121. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  122. package/src/codex/auth-api.ts +0 -1
  123. package/src/codex/auth-context.ts +2 -2
  124. package/src/codex/catalog/auto-review.ts +20 -3
  125. package/src/codex/catalog/build-entries.ts +9 -1
  126. package/src/codex/catalog/combo-member.ts +14 -5
  127. package/src/codex/catalog/configured-tiers.ts +1 -1
  128. package/src/codex/catalog/gather-capture.ts +2 -0
  129. package/src/codex/catalog/metadata.ts +11 -9
  130. package/src/codex/catalog/model-hints.ts +21 -16
  131. package/src/codex/catalog/model-visibility.ts +3 -1
  132. package/src/codex/catalog/parsing.ts +30 -86
  133. package/src/codex/catalog/provider-models.ts +36 -34
  134. package/src/codex/catalog/routed-gather.ts +14 -13
  135. package/src/codex/cli-install-provenance.ts +105 -17
  136. package/src/codex/cli-update-plan.ts +545 -0
  137. package/src/codex/config-write-lock.ts +23 -2
  138. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  139. package/src/codex/desktop-app-restart.ts +21 -0
  140. package/src/codex/desktop-compatibility/installed-build.ts +24 -6
  141. package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
  142. package/src/codex/desktop-compatibility/runtime.ts +12 -3
  143. package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
  144. package/src/codex/features.ts +85 -45
  145. package/src/codex/inject/history-failure.ts +27 -0
  146. package/src/codex/inject/restore.ts +24 -2
  147. package/src/codex/inject/routing-classify.ts +4 -3
  148. package/src/codex/inject.ts +18 -27
  149. package/src/codex/main-account-hard-lock.ts +30 -1
  150. package/src/codex/native-profile-startup.ts +1 -1
  151. package/src/codex/ocx-compaction-history.ts +12 -10
  152. package/src/codex/paths.ts +86 -1
  153. package/src/codex/provider-routing.ts +174 -0
  154. package/src/codex/refresh.ts +2 -2
  155. package/src/codex/routing/health-store.ts +1 -1
  156. package/src/codex/routing/selection.ts +4 -3
  157. package/src/codex/routing/thread-affinity.ts +1 -1
  158. package/src/codex/signed-in-identity.ts +43 -0
  159. package/src/codex/subagent-model-fallback.ts +1 -1
  160. package/src/codex/sync.ts +6 -1
  161. package/src/codex/user-identity.ts +16 -5
  162. package/src/codex/websocket-activity.ts +161 -0
  163. package/src/codex/websocket-registry.ts +25 -1
  164. package/src/codex/windows-local-appdata.ts +84 -0
  165. package/src/combos/failover.ts +6 -1
  166. package/src/combos/index.ts +1 -0
  167. package/src/combos/resolve.ts +46 -10
  168. package/src/combos/types.ts +15 -0
  169. package/src/config/diagnostics.ts +33 -6
  170. package/src/config/live-reconcile.ts +38 -2
  171. package/src/config/load-degrade.ts +17 -0
  172. package/src/config/opencode-wire-upgrade.ts +54 -0
  173. package/src/config/opencode-zen-upgrade.ts +53 -0
  174. package/src/config/owner-registry.ts +22 -17
  175. package/src/config/provider-validation.ts +45 -2
  176. package/src/config/schema/config-schema.ts +22 -0
  177. package/src/config/schema/leaf-validators.ts +38 -9
  178. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  179. package/src/config/schema/native-reasoning-retention.ts +26 -0
  180. package/src/config.ts +2 -0
  181. package/src/generated/compatibility-version.json +657 -297
  182. package/src/grok/sync.ts +1 -1
  183. package/src/images/loop.ts +116 -27
  184. package/src/integrations/catalog-refresh.ts +1 -1
  185. package/src/integrations/mutation-plan.ts +1 -0
  186. package/src/integrations/registry.ts +8 -0
  187. package/src/lib/codex-restart-contract.ts +15 -0
  188. package/src/lib/desktop-upstream-tunnel.ts +18 -53
  189. package/src/lib/empty-tool-catalog.ts +62 -0
  190. package/src/lib/errors.ts +19 -3
  191. package/src/lib/gcp-adc.ts +96 -9
  192. package/src/lib/gcp-credential-input.ts +12 -0
  193. package/src/lib/lab-live-route-production.ts +5 -0
  194. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  195. package/src/lib/redact.ts +2 -2
  196. package/src/lib/request-failure-model.ts +2 -0
  197. package/src/oauth/account-quota-rank.ts +69 -2
  198. package/src/oauth/anthropic-account-refusal.ts +20 -2
  199. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  200. package/src/oauth/anthropic-routing.ts +1 -21
  201. package/src/oauth/anthropic.ts +38 -7
  202. package/src/oauth/callback-server.ts +27 -1
  203. package/src/oauth/chatgpt-device.ts +1 -1
  204. package/src/oauth/chatgpt-shared.ts +83 -0
  205. package/src/oauth/chatgpt.ts +8 -72
  206. package/src/oauth/dsh.ts +203 -0
  207. package/src/oauth/generic-account-failover.ts +40 -11
  208. package/src/oauth/generic-session-affinity.ts +125 -0
  209. package/src/oauth/index.ts +184 -109
  210. package/src/oauth/local-token-detect.ts +21 -1
  211. package/src/oauth/login-cli-errors.ts +44 -0
  212. package/src/oauth/login-cli.ts +69 -7
  213. package/src/oauth/login-flow-state.ts +371 -23
  214. package/src/oauth/meta-muse-errors.ts +41 -0
  215. package/src/oauth/meta-muse-keychain.ts +52 -0
  216. package/src/oauth/meta-muse.ts +51 -84
  217. package/src/oauth/model-discovery-auth.ts +3 -0
  218. package/src/oauth/pool-settings-capability.ts +4 -2
  219. package/src/oauth/refresh-flight-retirement.ts +48 -0
  220. package/src/oauth/session-affinity-key.ts +23 -0
  221. package/src/oauth/store-anthropic-instance.ts +9 -2
  222. package/src/oauth/store.ts +12 -5
  223. package/src/protocols/encoders/adapter-events.ts +6 -5
  224. package/src/protocols/encoders/messages.ts +9 -6
  225. package/src/protocols/provider-summary.ts +2 -1
  226. package/src/providers/api-key-cooldowns.ts +49 -0
  227. package/src/providers/api-key-resolve.ts +11 -6
  228. package/src/providers/api-key-response-ownership.ts +57 -0
  229. package/src/providers/api-key-selection.ts +31 -6
  230. package/src/providers/api-keys.ts +214 -3
  231. package/src/providers/claude-cli-usage.ts +96 -0
  232. package/src/providers/dsh-account-destination.ts +31 -0
  233. package/src/providers/dsh-account-models.ts +41 -0
  234. package/src/providers/gcp-credential-cleanup.ts +59 -0
  235. package/src/providers/key-failover.ts +54 -50
  236. package/src/providers/key-store.ts +7 -2
  237. package/src/providers/model-rename-fields.ts +3 -0
  238. package/src/providers/model-rename-migration.ts +2 -2
  239. package/src/providers/openai-tiers-destination.ts +11 -2
  240. package/src/providers/openai-tiers.ts +5 -12
  241. package/src/providers/opencode-go-transport.ts +3 -1
  242. package/src/providers/opencode-zen-rate-limit.ts +7 -2
  243. package/src/providers/quota/account-cache.ts +6 -5
  244. package/src/providers/quota/account-row.ts +34 -0
  245. package/src/providers/quota/antigravity.ts +28 -7
  246. package/src/providers/quota/dsh-account.ts +70 -0
  247. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  248. package/src/providers/quota-types.ts +2 -0
  249. package/src/providers/quota.ts +14 -20
  250. package/src/providers/registry/entries-core.ts +37 -8
  251. package/src/providers/registry/types.ts +1 -1
  252. package/src/providers/request-pacing.ts +77 -19
  253. package/src/providers/routed-config.ts +339 -0
  254. package/src/providers/service-tier.ts +2 -1
  255. package/src/quota/window-mapping.ts +5 -2
  256. package/src/responses/anthropic-progress.ts +34 -0
  257. package/src/responses/assistant-replay.ts +35 -0
  258. package/src/responses/compaction-history-framing.ts +65 -0
  259. package/src/responses/compaction.ts +6 -1
  260. package/src/responses/incomplete-reason.ts +31 -0
  261. package/src/responses/parser.ts +50 -5
  262. package/src/responses/reasoning-envelope.ts +4 -0
  263. package/src/responses/reasoning-replay-cache.ts +74 -6
  264. package/src/responses/reasoning-retention.ts +173 -0
  265. package/src/responses/retained-compaction.ts +46 -0
  266. package/src/responses/schema.ts +23 -0
  267. package/src/router.ts +8 -321
  268. package/src/routing/analytics.ts +7 -15
  269. package/src/routing/health.ts +37 -22
  270. package/src/routing/history/indexer.ts +6 -5
  271. package/src/routing/history/ingest.ts +6 -2
  272. package/src/routing/profile.ts +1 -1
  273. package/src/server/admission-model-scope.ts +17 -0
  274. package/src/server/auth-cors.ts +11 -0
  275. package/src/server/chat-completions.ts +7 -1
  276. package/src/server/chat-native-reasoning.ts +123 -0
  277. package/src/server/chat-native-sse.ts +110 -7
  278. package/src/server/chat-native.ts +76 -17
  279. package/src/server/claude-messages.ts +56 -414
  280. package/src/server/effort-policy.ts +1 -1
  281. package/src/server/images.ts +15 -2
  282. package/src/server/index/live-sideband.ts +3 -2
  283. package/src/server/index/model-alias-id.ts +36 -0
  284. package/src/server/index/optional-listeners.ts +2 -0
  285. package/src/server/index/serve-options.ts +20 -6
  286. package/src/server/index/startup-warnings.ts +2 -0
  287. package/src/server/index/websocket-handler.ts +18 -4
  288. package/src/server/index.ts +8 -8
  289. package/src/server/inference/advisor-diagnostics.ts +31 -0
  290. package/src/server/lifecycle.ts +13 -1
  291. package/src/server/management/advisor-routes.ts +15 -4
  292. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  293. package/src/server/management/combo-routes.ts +176 -50
  294. package/src/server/management/companion-routes.ts +1 -1
  295. package/src/server/management/config-routes.ts +75 -4
  296. package/src/server/management/cursor-integration-routes.ts +1 -1
  297. package/src/server/management/model-routes.ts +11 -6
  298. package/src/server/management/model-rows.ts +14 -0
  299. package/src/server/management/native-integration-routes.ts +2 -7
  300. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  301. package/src/server/management/oauth-account-routes.ts +87 -14
  302. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  303. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  304. package/src/server/management/provider-overwrite-carry.ts +7 -0
  305. package/src/server/management/provider-routes.ts +25 -30
  306. package/src/server/management/route-registry.ts +10 -1
  307. package/src/server/management-api.ts +3 -1
  308. package/src/server/messages-native-tool-guard.ts +185 -0
  309. package/src/server/messages-native.ts +117 -24
  310. package/src/server/messages-shared.ts +431 -0
  311. package/src/server/relay.ts +1 -1
  312. package/src/server/request-log-terminal-status.ts +1 -1
  313. package/src/server/request-log.ts +6 -1
  314. package/src/server/responses/account-change-state.ts +8 -2
  315. package/src/server/responses/adapter-continuation.ts +47 -19
  316. package/src/server/responses/adapter-delivery.ts +72 -12
  317. package/src/server/responses/adapter-dispatch.ts +46 -3
  318. package/src/server/responses/advisor-retention.ts +117 -0
  319. package/src/server/responses/advisor-slot.ts +73 -41
  320. package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
  321. package/src/server/responses/codex-ws-wire.ts +15 -8
  322. package/src/server/responses/combo-stream-preflight.ts +3 -2
  323. package/src/server/responses/compact.ts +303 -125
  324. package/src/server/responses/compaction-recovery.ts +20 -10
  325. package/src/server/responses/compaction-retention.ts +61 -0
  326. package/src/server/responses/context-overflow.ts +11 -6
  327. package/src/server/responses/core-auth.ts +6 -0
  328. package/src/server/responses/core-codex-account.ts +11 -1
  329. package/src/server/responses/core-combo-failure.ts +2 -0
  330. package/src/server/responses/core-combo.ts +121 -36
  331. package/src/server/responses/core-errors.ts +3 -3
  332. package/src/server/responses/core-lifetime.ts +8 -4
  333. package/src/server/responses/core-opaque-recovery.ts +35 -0
  334. package/src/server/responses/core-options.ts +7 -0
  335. package/src/server/responses/core-pipeline.ts +141 -0
  336. package/src/server/responses/core-replay.ts +23 -3
  337. package/src/server/responses/core.ts +2 -115
  338. package/src/server/responses/fetch-helpers.ts +73 -19
  339. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  340. package/src/server/responses/native-response-control.ts +4 -2
  341. package/src/server/responses/passthrough-delivery.ts +3 -1
  342. package/src/server/responses/passthrough-dispatch.ts +15 -13
  343. package/src/server/responses/passthrough-execution.ts +3 -2
  344. package/src/server/responses/request-prepare.ts +77 -12
  345. package/src/server/responses/request-sidecar-auth.ts +2 -2
  346. package/src/server/responses/request-transport.ts +104 -27
  347. package/src/server/responses/response-effects.ts +2 -0
  348. package/src/server/responses/response-tool-ownership.ts +21 -0
  349. package/src/server/responses/run-turn-execution.ts +96 -30
  350. package/src/server/responses/sidecar-execution.ts +63 -9
  351. package/src/server/responses/sidecar-send-budget.ts +2 -2
  352. package/src/server/responses/terminal-guard.ts +2 -16
  353. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  354. package/src/server/ws-bridge.ts +2 -0
  355. package/src/service/auth-preflight.ts +71 -0
  356. package/src/service/cli.ts +4 -2
  357. package/src/service/definition.ts +97 -0
  358. package/src/service/guards.ts +1 -61
  359. package/src/service/health-sentinel-linux.ts +112 -0
  360. package/src/service/health-sentinel-main.ts +76 -57
  361. package/src/service/health.ts +5 -89
  362. package/src/service/launchd.ts +27 -7
  363. package/src/service/logging.ts +72 -9
  364. package/src/service/repair.ts +2 -1
  365. package/src/service/state.ts +7 -8
  366. package/src/service/systemd.ts +1 -1
  367. package/src/service/windows-guarded-manager.ts +2 -1
  368. package/src/service/windows-ops.ts +1 -1
  369. package/src/service/windows-scheduler.ts +2 -7
  370. package/src/service/windows-taskxml.ts +2 -2
  371. package/src/service/windows-wscript.ts +14 -0
  372. package/src/service.ts +4 -2
  373. package/src/sidecar/auth.ts +2 -0
  374. package/src/types/config.ts +64 -3
  375. package/src/types/opencode-go.ts +61 -0
  376. package/src/types/opencode-zen.ts +35 -0
  377. package/src/types/provider.ts +32 -6
  378. package/src/types/request.ts +31 -2
  379. package/src/types/wire.ts +31 -37
  380. package/src/types.ts +2 -0
  381. package/src/update/npm-invocation.mjs +12 -2
  382. package/src/usage/cost.ts +48 -12
  383. package/src/usage/expected-prices.ts +3 -0
  384. package/src/usage/ledger-retention.ts +2 -1
  385. package/src/usage/ledger-scanner.ts +6 -5
  386. package/src/usage/log.ts +17 -8
  387. package/src/usage/summary.ts +30 -11
  388. package/src/usage/telemetry-contract.ts +1 -0
  389. package/src/usage/user-cost-overlays.ts +14 -19
  390. package/src/usage/user-cost-validation.ts +38 -0
  391. package/src/vision/eligibility.ts +13 -0
  392. package/src/web-search/anthropic-history.ts +59 -0
  393. package/src/web-search/loop.ts +236 -73
  394. package/src/web-search/progress-stream.ts +15 -2
  395. package/src/web-search/run-turn-loop.ts +9 -1
  396. package/gui/dist/assets/App-BjlttVLy.js +0 -58
  397. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  398. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  399. package/gui/dist/assets/index-BMssP4k3.js +0 -94
  400. package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
@@ -1,13 +1,16 @@
1
1
  import { readExistingSpendSalt, spendPoolAliasesError, validatePoolAliasOwners } from "../lib/spend-pool-alias-validation";
2
2
  import { isSubagentModelEntry, rawSubagentModelForce } from "./subagent-models";
3
+ import { validRemainingQuotaAccountIds } from "../codex/account-use-remaining";
3
4
  import { protocolConfigSchema } from "./schema/config-schema";
4
5
  import { createHash } from "node:crypto";
5
6
  import { lstatSync, readFileSync } from "node:fs";
6
7
  import { join } from "node:path";
7
8
  import * as z from "zod/v4";
8
9
  import { compactionRecoveryConfigError } from "./schema/compaction-recovery";
10
+ import { nativeReasoningRetentionConfigError } from "./schema/native-reasoning-retention";
9
11
  import { desktopCompatibilityConfigError } from "./schema/desktop-compatibility";
10
12
  import { blockedModelRedirectsError } from "./schema/blocked-model-redirects";
13
+ import { mainAccountHardLockFallbackError } from "./schema/main-account-hard-lock-fallback";
11
14
  import type { OcxConfig } from "../types";
12
15
  import { anthropicAccountPoolConfigError, anthropicOAuthInstanceConfigError, anthropicSidecarConfigError } from "./schema/anthropic-account-pool";
13
16
  import { configReasoningPinsConfigError } from "./provider-validation";
@@ -18,6 +21,7 @@ import { isMissingPathError } from "./atomic-write";
18
21
  import { getConfigDir, getConfigPath } from "./paths";
19
22
  import { getDefaultConfig } from "./proxy-env";
20
23
  import { salvageConfigCandidate } from "./salvage";
24
+ import { sanitizeOpenCodeOverridesForLoad } from "./opencode-wire-upgrade";
21
25
  import {
22
26
  sanitizeReasoningPinsForLoad,
23
27
  sanitizeRetryOn429ForLoad,
@@ -25,6 +29,7 @@ import {
25
29
  sanitizeModelCostsForLoad,
26
30
  sanitizeAutoReviewForLoad,
27
31
  degradedListenerWarnings,
32
+ degradedReasoningRetentionWarning,
28
33
  degradedCodexAccountPriorityWarnings,
29
34
  degradedCodexQuotaAutoRefreshWarning,
30
35
  normalizeApiKeyIds,
@@ -71,6 +76,7 @@ import {
71
76
  chatgptDesktopSchema,
72
77
  chatgptDesktopConfigIssue,
73
78
  compactionRoutingSchema,
79
+ reasoningRetentionSchema,
74
80
  skillsConfigSchema,
75
81
  memoryModelsSchema,
76
82
  } from "./schema/leaf-validators";
@@ -136,6 +142,8 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf
136
142
  warnings.push(...degradedCodexAccountPriorityWarnings(rawParsed, normalized));
137
143
  warnings.push(...degradedListenerWarnings(rawParsed, normalized));
138
144
  if (blockedModelRedirectsError(rawParsed)) warnings.push("blockedModelRedirects ignored: expected a map of nonempty model keys to nonempty string targets");
145
+ if (nativeReasoningRetentionConfigError(rawParsed)) warnings.push("nativeReasoningRetention ignored: expected only optional boolean modelSwitch and accountSwitch fields; both remain off");
146
+ if (mainAccountHardLockFallbackError(rawParsed)) warnings.push("codexMainAccountHardLockFallback ignored: expected a nonblank model selector and an optional declared reasoningEffort");
139
147
  const quotaAutoRefreshWarning = degradedCodexQuotaAutoRefreshWarning(rawParsed, normalized);
140
148
  if (quotaAutoRefreshWarning) warnings.push(quotaAutoRefreshWarning);
141
149
  if (rawEffort !== undefined && !isClaudeSubagentEffort(rawEffort)) {
@@ -164,6 +172,8 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf
164
172
  if (codexPoolWarning) warnings.push(codexPoolWarning);
165
173
  const spendWarning = malformedSpendWarning(rawParsed);
166
174
  if (spendWarning) warnings.push(spendWarning);
175
+ const retentionWarning = degradedReasoningRetentionWarning(rawParsed, normalized);
176
+ if (retentionWarning) warnings.push(retentionWarning);
167
177
  if (rawPoolAliasesError(rawParsed)) {
168
178
  warnings.push("spendPoolAliases invalid: configured pool admission is refused until the mapping is corrected");
169
179
  }
@@ -404,12 +414,15 @@ function codexAccountPrioritiesError(value: unknown): string | null {
404
414
  return null;
405
415
  }
406
416
 
407
- function codexAccountPriorityFailbackError(value: unknown): string | null {
417
+ function codexAccountSelectionPreferencesError(value: unknown): string | null {
408
418
  const raw = rawConfigRecord(value);
409
- if (!raw || !Object.hasOwn(raw, "codexAccountPriorityFailback")) return null;
410
- const enabled = raw.codexAccountPriorityFailback;
411
- if (enabled === undefined || typeof enabled === "boolean") return null;
412
- return "schema_invalid: codexAccountPriorityFailback: must be a boolean or omitted";
419
+ if (!raw) return null;
420
+ for (const key of ["codexAccountPriorityFailback", "codexPreserveSignedInAccount"]) {
421
+ if (raw[key] !== undefined && typeof raw[key] !== "boolean") {
422
+ return `schema_invalid: ${key}: must be a boolean or omitted`;
423
+ }
424
+ }
425
+ return null;
413
426
  }
414
427
 
415
428
  /**
@@ -505,6 +518,12 @@ function creditCodexAccountIdsError(value: unknown): string | null {
505
518
  return "schema_invalid: creditCodexAccountIds: must be an array of account ids or omitted";
506
519
  }
507
520
 
521
+ function remainingQuotaAccountIdsError(value: unknown): string | null {
522
+ const ids = rawConfigRecord(value)?.codexUseRemainingQuotaAccountIds;
523
+ return ids === undefined || validRemainingQuotaAccountIds(ids) ? null
524
+ : "schema_invalid: codexUseRemainingQuotaAccountIds: must be an array of Codex account ids or omitted";
525
+ }
526
+
508
527
  function oauthOpenBrowserError(value: unknown): string | null {
509
528
  const raw = rawConfigRecord(value);
510
529
  if (!raw || !Object.hasOwn(raw, "oauthOpenBrowser")) return null;
@@ -681,6 +700,10 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
681
700
  return { ok: false, error: "schema_invalid: chatgptDesktop: requires an optional boolean appServerShim and no other fields" };
682
701
  }
683
702
  const compactionRouting = rawConfigRecord(value)?.compactionRouting;
703
+ const reasoningRetention = rawConfigRecord(value)?.reasoningRetention;
704
+ if (reasoningRetention !== undefined && !reasoningRetentionSchema.safeParse(reasoningRetention).success) {
705
+ return { ok: false, error: "schema_invalid: reasoningRetention: requires optional boolean enabled, maxContextPercent greater than 0 and at most 100 and/or positive integer maxTokens, with no other fields" };
706
+ }
684
707
  if (compactionRouting !== undefined && !compactionRoutingSchema.safeParse(compactionRouting).success) {
685
708
  return { ok: false, error: "schema_invalid: compactionRouting: requires a nonblank model, an optional valid reasoningEffort, and optional non-repeating triggers drawn from \"manual\" and \"auto\"" };
686
709
  }
@@ -695,6 +718,8 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
695
718
  const anthropicError = anthropicOAuthInstanceConfigError(value) ?? anthropicAccountPoolConfigError(value) ?? anthropicSidecarConfigError(value);
696
719
  if (anthropicError) return { ok: false, error: anthropicError };
697
720
  const boundaryError = blockedModelRedirectsError(value)
721
+ ?? nativeReasoningRetentionConfigError(value)
722
+ ?? mainAccountHardLockFallbackError(value)
698
723
  ?? compactionRecoveryConfigError(value) ?? desktopCompatibilityConfigError(value) ?? configReasoningPinsConfigError(value)
699
724
  ?? blankHostnameError(value)
700
725
  ?? (rawSubagentModelForce(value) !== undefined && !isSubagentModelEntry(rawSubagentModelForce(value)) ? "schema_invalid: claudeCode.subagentModelForce: expected a safe roster-style model id" : null)
@@ -710,7 +735,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
710
735
  ?? codexPoolError(value)
711
736
  ?? googleAntigravityStaticCatalogVersionError(value)
712
737
  ?? codexAccountPrioritiesError(value)
713
- ?? codexAccountPriorityFailbackError(value)
738
+ ?? codexAccountSelectionPreferencesError(value)
714
739
  ?? poolCredentialGroupsError(value)
715
740
  ?? codexQuotaAutoRefreshError(value)
716
741
  ?? codexAccountPickerEnabledError(value)
@@ -719,6 +744,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
719
744
  ?? oauthOpenBrowserError(value)
720
745
  ?? showCodexCreditsError(value)
721
746
  ?? creditCodexAccountIdsError(value)
747
+ ?? remainingQuotaAccountIdsError(value)
722
748
  ?? runtimeRoleError(value)
723
749
  ?? remoteGuiConfigError(value)
724
750
  ?? clientConnectionConfigError(value)
@@ -740,6 +766,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
740
766
  export function configDiagnosticsFromRaw(raw: string): ConfigDiagnostics {
741
767
  try {
742
768
  const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
769
+ sanitizeOpenCodeOverridesForLoad(parsed);
743
770
  sanitizeReasoningPinsForLoad(parsed);
744
771
  // Same degradation as loadConfig: a hand-edited invalid retryOn429 must not trip the
745
772
  // schema and send the caller a default-config fallback (the config command could then
@@ -1,4 +1,4 @@
1
- import type { OcxConfig, OcxProviderConfig } from "../types";
1
+ import type { OcxConfig, OcxProviderConfig, ProviderCostOverlay } from "../types";
2
2
  import { configReasoningPinsConfigError } from "./provider-validation";
3
3
  import { adoptCustomModelCatalogMigration, projectCustomModelCatalogMigration } from "../codex/custom-model-catalog-migration";
4
4
  import { refreshPreservedProviderOwner } from "../usage/user-cost-overlays";
@@ -16,7 +16,7 @@ import {
16
16
  } from "./rebase-provenance";
17
17
  import { withConfigMutationLockSync, bumpGenerationForCooperatingConfigWrite } from "./mutation-lock";
18
18
  import { ConfigWritePublishedError, persistConfigUnlocked, readRawConfigJson } from "./persist-unlocked";
19
- import { configDiagnosticsFromRaw, readConfigDiagnostics } from "./diagnostics";
19
+ import { configDiagnosticsFromRaw, readConfigDiagnostics, validateConfigCandidate } from "./diagnostics";
20
20
  import { normalizePersistedClaudeCode } from "./load-degrade";
21
21
  import { reconcileApiKeys } from "./api-key-reconcile";
22
22
  import { captureLiveTopologyBaseline, LIVE_TOPOLOGY_KEYS, projectLiveTopology } from "./live-topology";
@@ -61,6 +61,35 @@ export function adoptPersistedCodexAccountSelection(
61
61
  if (baseline) clearConfigTopLevelDeletionIntent(baseline, keys);
62
62
  }
63
63
 
64
+ type ModelCostReplacement = { provider: string; modelId: string; cost: ProviderCostOverlay | null };
65
+ const modelCostReplacements = new WeakMap<OcxConfig, ModelCostReplacement>();
66
+
67
+ /** Explicit pricing PUT owns one whole row, even when its base tuple equals the baseline. */
68
+ export function withModelCostRowReplacement(
69
+ config: OcxConfig, provider: string, modelId: string, cost: ProviderCostOverlay | null, save: () => void,
70
+ ): void {
71
+ const previous = modelCostReplacements.get(config);
72
+ const replacementCost = structuredClone(cost);
73
+ if (replacementCost?.promptLengthPricing?.mode === "automatic") delete replacementCost.promptLengthPricing;
74
+ modelCostReplacements.set(config, { provider, modelId, cost: replacementCost });
75
+ try { save(); }
76
+ finally {
77
+ if (previous) modelCostReplacements.set(config, previous);
78
+ else modelCostReplacements.delete(config);
79
+ }
80
+ }
81
+
82
+ function applyModelCostRowReplacement(config: OcxConfig): void {
83
+ const replacement = modelCostReplacements.get(config);
84
+ if (!replacement) return;
85
+ if (!Object.hasOwn(config.providers, replacement.provider)) throw new Error("pricing provider was removed before persistence");
86
+ const provider = config.providers[replacement.provider]!;
87
+ const costs = Object.assign(Object.create(null) as Record<string, ProviderCostOverlay>, provider.modelCosts ?? {});
88
+ if (replacement.cost === null) delete costs[replacement.modelId];
89
+ else costs[replacement.modelId] = structuredClone(replacement.cost);
90
+ provider.modelCosts = costs;
91
+ }
92
+
64
93
  /**
65
94
  * Adopt a committed discovery decision and its merge baseline as one synchronous step.
66
95
  * Visibility writers assign and save synchronously; any future writer yielding between
@@ -483,6 +512,10 @@ export function saveConfigPreservingClaudeCode(config: OcxConfig): void {
483
512
  if (pinError) throw new Error(pinError);
484
513
  let published = false;
485
514
  const persist = (candidate: OcxConfig): void => {
515
+ if (modelCostReplacements.has(config)) {
516
+ const validation = validateConfigCandidate(candidate);
517
+ if (!validation.ok) throw new Error(validation.error);
518
+ }
486
519
  const changed = persistConfigUnlocked(candidate);
487
520
  published = true;
488
521
  if (changed) bumpGenerationForCooperatingConfigWrite();
@@ -544,6 +577,9 @@ export function saveConfigPreservingClaudeCode(config: OcxConfig): void {
544
577
  }
545
578
  }
546
579
  applyConfigObjectChildDeletions(config, childDeletions);
580
+ // Recursive three-way merging preserves sibling rows; explicit PUT/reset
581
+ // replaces only its model after merging, including absent policy fields.
582
+ applyModelCostRowReplacement(config);
547
583
  if (claudeCodeBaseline.has(config)) {
548
584
  if (onDisk !== undefined) {
549
585
  const baseline = claudeCodeBaseline.get(config);
@@ -3,12 +3,15 @@ import { isSubagentModelEntry, rawSubagentModelForce } from "./subagent-models";
3
3
  import { chmodSync, existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { compactionRecoveryConfigError } from "./schema/compaction-recovery";
6
+ import { nativeReasoningRetentionConfigError } from "./schema/native-reasoning-retention";
6
7
  import { desktopCompatibilityConfigError } from "./schema/desktop-compatibility";
7
8
  import { blockedModelRedirectsError } from "./schema/blocked-model-redirects";
9
+ import { mainAccountHardLockFallbackError } from "./schema/main-account-hard-lock-fallback";
8
10
  import { anthropicSidecarConfigError } from "./schema/anthropic-account-pool";
9
11
  import {
10
12
  modelPinnedEffortsConfigError,
11
13
  pinnedReasoningEffortConfigError,
14
+ autoReviewOverrideConfigError,
12
15
  autoReviewModelOverridesConfigError,
13
16
  autoReviewModelTargetConfigError,
14
17
  modelCapabilitiesConfigError,
@@ -131,10 +134,14 @@ export function warnDegradedCompactionRouting(rawParsed: unknown, validated: Ocx
131
134
  * the ratchet only ever moves down: a per-block call there costs a line the file does not have.
132
135
  */
133
136
  export function warnDegradedTopLevelOptIns(rawParsed: unknown, validated: OcxConfig): void {
137
+ if (nativeReasoningRetentionConfigError(rawParsed)) console.warn("⚠️ invalid nativeReasoningRetention disabled; model and account switch allowances remain off");
134
138
  setCodexWsReuseAcrossTurns(validated.codexWsReuseAcrossTurns === true);
139
+ const retentionWarning = degradedReasoningRetentionWarning(rawParsed, validated);
140
+ if (retentionWarning) console.warn(retentionWarning);
135
141
  if (compactionRecoveryConfigError(rawParsed)) console.warn("⚠️ invalid compactionRecovery disabled; the original compaction failure is preserved");
136
142
  if (desktopCompatibilityConfigError(rawParsed)) console.warn("Invalid desktopCompatibility startup preference ignored; no desktop compatibility service will start automatically.");
137
143
  if (blockedModelRedirectsError(rawParsed)) console.warn("⚠️ invalid blockedModelRedirects ignored; provider routing remains available");
144
+ if (mainAccountHardLockFallbackError(rawParsed)) console.warn("⚠️ invalid codexMainAccountHardLockFallback ignored; the main-account hard lock still refuses while blocked");
138
145
  const chatgptDesktop = chatgptDesktopConfigIssue(rawParsed);
139
146
  if (chatgptDesktop) console.warn(`⚠️ config.json ${chatgptDesktop} — the whole chatgptDesktop block is ignored, so the ChatGPT desktop integration reads as off`);
140
147
  warnDegradedStreamMode(rawParsed, validated);
@@ -147,6 +154,11 @@ export function warnDegradedTopLevelOptIns(rawParsed: unknown, validated: OcxCon
147
154
  }
148
155
  }
149
156
 
157
+ export function degradedReasoningRetentionWarning(rawParsed: unknown, validated: OcxConfig): string | null {
158
+ if (rawConfigRecord(rawParsed)?.reasoningRetention === undefined || validated.reasoningRetention !== undefined) return null;
159
+ return "config.json reasoningRetention is invalid — reasoning retention is disabled";
160
+ }
161
+
150
162
  /**
151
163
  * A malformed `memoryModels` phase disables that phase rather than failing the whole schema, so
152
164
  * say so once: silently keeping whatever route the phase already had — which may be the shadow
@@ -411,6 +423,11 @@ export function sanitizeModelCostsForLoad(parsed: unknown): void {
411
423
  export function sanitizeAutoReviewForLoad(parsed: unknown): void {
412
424
  if (!parsed || typeof parsed !== "object") return;
413
425
  const root = parsed as Record<string, unknown>;
426
+ if (root.autoReviewOverride !== undefined
427
+ && autoReviewOverrideConfigError(root.autoReviewOverride) !== null) {
428
+ console.warn("⚠️ config.json autoReviewOverride is invalid — ignoring the override");
429
+ delete root.autoReviewOverride;
430
+ }
414
431
  const providers = root.providers;
415
432
  if (!providers || typeof providers !== "object" || Array.isArray(providers)) return;
416
433
  for (const [name, providerValue] of Object.entries(providers as Record<string, unknown>)) {
@@ -0,0 +1,54 @@
1
+ import { OPENCODE_GO_LEGACY_NAME_PINS } from "../types/opencode-go";
2
+ import { captureWireAdapterHardPins } from "../types/wire";
3
+ import { declaredUnsupportedHostedTools, isHostedToolUnsupportedForModel } from "../responses/hosted-tool-policy";
4
+ import { sanitizeOpenCodeZenOverridesForLoad } from "./opencode-zen-upgrade";
5
+
6
+ function record(value: unknown): Record<string, unknown> | undefined {
7
+ return value !== null && typeof value === "object" && !Array.isArray(value)
8
+ ? value as Record<string, unknown> : undefined;
9
+ }
10
+
11
+ /** File reads alone repair overrides admitted before OpenCode's native-wire upgrade. */
12
+ export function sanitizeOpenCodeOverridesForLoad(parsed: unknown): void {
13
+ sanitizeOpenCodeZenOverridesForLoad(parsed);
14
+ const providers = record(record(parsed)?.providers);
15
+ const provider = providers && Object.hasOwn(providers, "opencode-go")
16
+ ? record(providers["opencode-go"]) : undefined;
17
+ if (!provider) return;
18
+ // Use validation's destination/auth rules, including the Go preset's fixed URL.
19
+ const pins = captureWireAdapterHardPins("opencode-go", provider);
20
+ const newlyPinned = (model: string): boolean => Object.hasOwn(pins, model)
21
+ && !OPENCODE_GO_LEGACY_NAME_PINS.has(model);
22
+ const overrides = record(provider.modelAdapters) ?? {};
23
+ const preferences = record(provider.modelPreferHostedTools);
24
+ let preferencesChanged = false;
25
+ if (preferences) {
26
+ const unsupported = declaredUnsupportedHostedTools(provider as { unsupportedHostedTools?: readonly string[] });
27
+ for (const [model, tools] of Object.entries(preferences)) {
28
+ // Go's registry owns the old provider-wide Chat wire. Only an explicit
29
+ // Responses override could previously make these native models eligible.
30
+ if (!newlyPinned(model.trim()) || overrides[model] !== "openai-responses"
31
+ || !Array.isArray(tools) || tools.length === 0 || tools.some(tool => tool !== "image_generation")
32
+ || isHostedToolUnsupportedForModel(model, "image_generation", undefined, unsupported)) continue;
33
+ delete preferences[model];
34
+ preferencesChanged = true;
35
+ }
36
+ if (preferencesChanged && Object.keys(preferences).length === 0) delete provider.modelPreferHostedTools;
37
+ }
38
+ let overridesChanged = false;
39
+ for (const [model, adapter] of Object.entries(overrides)) {
40
+ // Leave malformed or previously incompatible preferences visible on every pass.
41
+ if (preferences && Object.hasOwn(preferences, model)) continue;
42
+ if (newlyPinned(model.trim()) && (adapter === "openai-chat" || adapter === "openai-responses")) {
43
+ delete overrides[model];
44
+ overridesChanged = true;
45
+ }
46
+ }
47
+ if (overridesChanged) {
48
+ if (Object.keys(overrides).length === 0) delete provider.modelAdapters;
49
+ console.warn("OpenCode Go native models now use their required APIs; obsolete Chat/Responses overrides were ignored while loading config.json.");
50
+ }
51
+ if (preferencesChanged) {
52
+ console.warn("OpenCode Go native models now use their required APIs; legacy Responses-only hosted-tool preferences were ignored while loading config.json.");
53
+ }
54
+ }
@@ -0,0 +1,53 @@
1
+ import { openCodeZenBaseUrl, openCodeZenKeyBaseUrl, type OpenCodeZenDestination } from "../types/opencode-zen";
2
+ import { declaredUnsupportedHostedTools, isHostedToolUnsupportedForModel } from "../responses/hosted-tool-policy";
3
+
4
+ function record(value: unknown): Record<string, unknown> | undefined {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value)
6
+ ? value as Record<string, unknown> : undefined;
7
+ }
8
+
9
+ function isNativeZenModel(model: string, provider: OpenCodeZenDestination): boolean {
10
+ const folded = model.trim().toLowerCase();
11
+ return folded.startsWith("claude-") || (folded.startsWith("gemini-") && openCodeZenBaseUrl(provider) !== undefined);
12
+ }
13
+
14
+ /** Repair legacy Zen wire overrides and dependent preferences in file-backed reads only. */
15
+ export function sanitizeOpenCodeZenOverridesForLoad(parsed: unknown): void {
16
+ const providers = record(record(parsed)?.providers);
17
+ const provider = providers && Object.hasOwn(providers, "opencode-zen")
18
+ ? record(providers["opencode-zen"]) : undefined;
19
+ if (!provider || !openCodeZenKeyBaseUrl(provider)) return;
20
+ const overrides = record(provider.modelAdapters) ?? {};
21
+ const preferences = record(provider.modelPreferHostedTools);
22
+ let preferencesChanged = false;
23
+ if (preferences) {
24
+ const unsupported = declaredUnsupportedHostedTools(provider as { unsupportedHostedTools?: readonly string[] });
25
+ for (const [model, tools] of Object.entries(preferences)) {
26
+ // Capture the old wire before deleting overrides. An explicit non-Responses entry
27
+ // must not inherit a provider-wide Responses default for this migration.
28
+ const oldAdapter = Object.hasOwn(overrides, model) ? overrides[model] : provider.adapter;
29
+ if (!isNativeZenModel(model, provider) || oldAdapter !== "openai-responses"
30
+ || !Array.isArray(tools) || tools.length === 0 || tools.some(tool => tool !== "image_generation")
31
+ || isHostedToolUnsupportedForModel(model, "image_generation", undefined, unsupported)) continue;
32
+ delete preferences[model];
33
+ preferencesChanged = true;
34
+ }
35
+ if (preferencesChanged && Object.keys(preferences).length === 0) delete provider.modelPreferHostedTools;
36
+ }
37
+ let overridesChanged = false;
38
+ for (const [model, adapter] of Object.entries(overrides)) {
39
+ // Retain invalid preference/wire pairs so repeated normalization cannot admit them.
40
+ if (preferences && Object.hasOwn(preferences, model)) continue;
41
+ if (isNativeZenModel(model, provider) && (adapter === "openai-chat" || adapter === "openai-responses")) {
42
+ delete overrides[model];
43
+ overridesChanged = true;
44
+ }
45
+ }
46
+ if (overridesChanged) {
47
+ if (Object.keys(overrides).length === 0) delete provider.modelAdapters;
48
+ console.warn("OpenCode Zen Gemini and Claude models now use their native APIs; obsolete Chat/Responses overrides were ignored while loading config.json.");
49
+ }
50
+ if (preferencesChanged) {
51
+ console.warn("OpenCode Zen Gemini and Claude models now use their native APIs; legacy Responses-only hosted-tool preferences were ignored while loading config.json.");
52
+ }
53
+ }
@@ -24,21 +24,19 @@
24
24
  */
25
25
 
26
26
  import { createHash } from "node:crypto";
27
- import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
27
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync } from "node:fs";
28
28
  import { homedir } from "node:os";
29
29
  import { dirname, join, resolve } from "node:path";
30
30
  import { atomicWriteFileNoFollow } from "./atomic-write";
31
31
  import { getConfigDir } from "./paths";
32
- import { assertNotRealHomeUnderTest } from "../lib/test-home-guard";
32
+ import { assertNotRealHomeUnderTest, isTestHomeGuardArmed } from "../lib/test-home-guard";
33
33
  import { hardenSecretDir } from "../lib/windows-secret-acl";
34
34
 
35
35
  const REGISTRY_DIR_NAME = "ocx-homes";
36
36
  /**
37
- * Test seam, not a user knob: os.homedir() resolves the passwd database on POSIX,
38
- * so rewriting HOME inside a test process cannot move the anchor and the armed
39
- * test-home guard would rightly refuse the write. Suites point this at their own
40
- * fixture home; production always leaves it unset so the default home stays the
41
- * one locator every sibling can find.
37
+ * Test seam, not a user knob. The sandbox pins this for inherited child environments;
38
+ * guarded callers without it use their selected config home. Production leaves it
39
+ * unset so the default home stays the one locator every sibling can find.
42
40
  */
43
41
  const REGISTRY_DIR_ENV = "OCX_OWNER_REGISTRY_DIR";
44
42
  const REGISTRY_ENTRY_SUFFIX = ".json";
@@ -60,8 +58,23 @@ function registryBaseDir(): string {
60
58
  /** The shared directory the registry lives under. */
61
59
  export function ownerRegistryDir(): string {
62
60
  const override = process.env[REGISTRY_DIR_ENV]?.trim();
63
- if (override) return resolve(override);
64
- return join(registryBaseDir(), REGISTRY_DIR_NAME);
61
+ const dir = override ? resolve(override)
62
+ : join(isTestHomeGuardArmed() ? getConfigDir() : registryBaseDir(), REGISTRY_DIR_NAME);
63
+ // Bun caches the startup home, so HOME rewrites cannot isolate this anchor.
64
+ // Check reads and removals too, including an inherited override or symlink that
65
+ // points back into the protected home. An unpinned guarded caller fails closed.
66
+ if (isTestHomeGuardArmed()) {
67
+ for (let ancestor = dir; ;) {
68
+ assertNotRealHomeUnderTest(ancestor);
69
+ // Follow an existing alias before walking parents; missing children reach
70
+ // their nearest existing ancestor on the next iterations.
71
+ try { ancestor = realpathSync.native(ancestor); } catch { /* not created yet */ }
72
+ const parent = dirname(ancestor);
73
+ if (parent === ancestor) break;
74
+ ancestor = parent;
75
+ }
76
+ }
77
+ return dir;
65
78
  }
66
79
 
67
80
  function registryEntryPath(dir: string, home: string): string {
@@ -76,14 +89,6 @@ function registryEntryPath(dir: string, home: string): string {
76
89
  export function registerOwnerRegistryHome(home: string): void {
77
90
  try {
78
91
  const dir = ownerRegistryDir();
79
- // Guard every ancestor of the write target: an override that still resolves
80
- // under the protected ~/.opencodex must not slip past the test home guard.
81
- for (let ancestor = dir; ;) {
82
- assertNotRealHomeUnderTest(ancestor);
83
- const parent = dirname(ancestor);
84
- if (parent === ancestor) break;
85
- ancestor = parent;
86
- }
87
92
  mkdirSync(dir, { recursive: true, mode: 0o700 });
88
93
  // This locator contains no secret, but its deterministic entry names must not
89
94
  // give another local user a place to plant a destination symlink. Refuse a
@@ -1,4 +1,5 @@
1
1
  import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
2
+ import { resolveDshAccountMessagesUrl } from "../providers/dsh-account-destination";
2
3
  import { redactSecretString } from "../lib/redact";
3
4
  import {
4
5
  isValidModelDiscoveryModelId,
@@ -256,10 +257,11 @@ export function autoReviewModelTargetConfigError(
256
257
  field = "autoReviewModel",
257
258
  allowClear = false,
258
259
  ): string | null {
259
- if (value === undefined || (allowClear && (value === null || value === ""))) return null;
260
+ if (value === undefined || (allowClear && value === null)) return null;
260
261
  if (typeof value !== "string") return `${field} must be a string`;
261
262
  const trimmed = value.trim();
262
- if (!trimmed) return `${field} must be nonblank`;
263
+ // `allowClear` callers trim then delete, so whitespace-only is a clear, not a blank selector.
264
+ if (!trimmed) return allowClear ? null : `${field} must be nonblank`;
263
265
  if (trimmed.length > 1024 || AUTO_REVIEW_MODEL_CONTROL_CHARS.test(trimmed)) {
264
266
  return `${field} must be a catalog selector without whitespace or control characters`;
265
267
  }
@@ -271,6 +273,26 @@ export function isValidAutoReviewModel(value: unknown): value is string {
271
273
  return typeof value === "string" && autoReviewModelTargetConfigError(value) === null;
272
274
  }
273
275
 
276
+ /**
277
+ * Validate the global auto-review override block the Models settings panel writes.
278
+ *
279
+ * `enabled` is optional so a save may carry only the model during an edit; `model` follows the
280
+ * same selector rules as the provider-scoped field, and an empty value clears the override.
281
+ */
282
+ export function autoReviewOverrideConfigError(value: unknown, field = "autoReviewOverride"): string | null {
283
+ if (value === undefined || value === null) return null;
284
+ if (!value || typeof value !== "object" || Array.isArray(value)) return `${field} must be a plain object`;
285
+ const prototype = Object.getPrototypeOf(value);
286
+ if (prototype !== Object.prototype && prototype !== null) {
287
+ return `${field} must be a plain object with own properties`;
288
+ }
289
+ const block = value as Record<string, unknown>;
290
+ if (block.enabled !== undefined && typeof block.enabled !== "boolean") {
291
+ return `${field}.enabled must be a boolean`;
292
+ }
293
+ return autoReviewModelTargetConfigError(block.model, `${field}.model`, true);
294
+ }
295
+
274
296
  /** Canonical model key used for map matching, duplicate detection, and route tombstones. */
275
297
  export function canonicalAutoReviewModelKey(modelId: string): string {
276
298
  return encodeRoutedModelId(modelId.trim());
@@ -390,6 +412,27 @@ export function modelAdapterRecordConfigError(
390
412
  }
391
413
 
392
414
 
415
+ /**
416
+ * The `dsh-account` row owns the DeepSeek Harness account token, which every helper resolves by this
417
+ * provider name. Keeping the row on its own adapter and DeepSeek's Anthropic host means no generic
418
+ * helper (Anthropic sidecars, discovery, probes) can be pointed at another destination with it.
419
+ */
420
+ export function credentialPinnedProviderConfigError(
421
+ providerName: string,
422
+ provider: { adapter?: unknown; baseUrl?: unknown },
423
+ ): string | null {
424
+ if (providerName !== "dsh-account") return null;
425
+ if (provider.adapter !== undefined && provider.adapter !== "dsh-account") {
426
+ return 'adapter must stay "dsh-account": its account token is pinned to that adapter';
427
+ }
428
+ if (typeof provider.baseUrl === "string") {
429
+ try { resolveDshAccountMessagesUrl(provider.baseUrl); } catch {
430
+ return "baseUrl must stay on https://api.deepseek.com/anthropic: its account token is pinned to that host";
431
+ }
432
+ }
433
+ return null;
434
+ }
435
+
393
436
  function capabilityRecord(value: unknown): value is Record<string, unknown> {
394
437
  return value !== null && typeof value === "object" && !Array.isArray(value)
395
438
  && [Object.prototype, null].includes(Object.getPrototypeOf(value));
@@ -1,9 +1,12 @@
1
1
  import { MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT } from "../../codex/quota-types";
2
+ import { validRemainingQuotaAccountIds } from "../../codex/account-use-remaining";
2
3
  import * as z from "zod/v4";
3
4
  import { anthropicAccountPoolSchema } from "./anthropic-account-pool";
4
5
  import { compactionRecoverySchema } from "./compaction-recovery";
6
+ import { nativeReasoningRetentionSchema } from "./native-reasoning-retention";
5
7
  import { desktopCompatibilitySchema } from "./desktop-compatibility";
6
8
  import { blockedModelRedirectsSchema } from "./blocked-model-redirects";
9
+ import { mainAccountHardLockFallbackSchema } from "./main-account-hard-lock-fallback";
7
10
  import {
8
11
  agentTaskRecoverySchema,
9
12
  catalogAutoRefreshSchema,
@@ -29,7 +32,9 @@ import {
29
32
  CODEX_ACCOUNT_NAMESPACE_ACCOUNT_ID_COLLISION_ERROR,
30
33
  codexAccountNamespacesSchema,
31
34
  modelPinnedEffortsSchema,
35
+ autoReviewOverrideSchema,
32
36
  compactionRoutingSchema,
37
+ reasoningRetentionSchema,
33
38
  memoryModelSettingSchema,
34
39
  memoryModelsSchema,
35
40
  modelPreferHostedToolsConfigError,
@@ -49,6 +54,7 @@ import {
49
54
  providerHeadersConfigError,
50
55
  providerForwardClientHeadersConfigError,
51
56
  reasoningSummaryDeliveryRecordConfigError,
57
+ credentialPinnedProviderConfigError,
52
58
  } from "../provider-validation";
53
59
  import {
54
60
  CODEX_ACCOUNT_NAMESPACE_COMBO_ALIAS_COLLISION_ERROR,
@@ -190,9 +196,15 @@ export const configSchema = z.object({
190
196
  z.object({ enabled: z.literal(true), port: z.number().int().min(1).max(65535).optional() }),
191
197
  ]).optional().catch(undefined),
192
198
  providers: z.record(z.string(), providerConfigSchema),
199
+ // Models settings panel writes the global reviewer override; an invalid hand edit is
200
+ // stripped by load degradation so it cannot reject the providers that carry it.
201
+ autoReviewOverride: autoReviewOverrideSchema.optional(),
193
202
  modelPinnedEfforts: modelPinnedEffortsSchema.optional(),
194
203
  compactionRouting: compactionRoutingSchema.optional().catch(undefined),
195
204
  compactionRecovery: compactionRecoverySchema.optional().catch(undefined),
205
+ // A bad hand edit disables only this block; writes validate the catch-free leaf first.
206
+ reasoningRetention: reasoningRetentionSchema.optional().catch(undefined),
207
+ nativeReasoningRetention: nativeReasoningRetentionSchema.optional().catch(undefined),
196
208
  desktopCompatibility: desktopCompatibilitySchema.optional().catch(undefined),
197
209
  // A hand-edited malformed phase disables only that phase instead of rejecting
198
210
  // providers/apiKeys, matching the load-time degradation notice; the management write
@@ -207,6 +219,7 @@ export const configSchema = z.object({
207
219
  .catch(undefined),
208
220
  defaultProvider: z.string().min(1).default("openai"),
209
221
  defaultModelAliases: z.boolean().optional(),
222
+ preferModelAliasIds: z.boolean().optional().catch(false),
210
223
  // Malformed hand edits disable this opt-in projection without rejecting providers.
211
224
  cursorEffortRows: z.boolean().optional().catch(false),
212
225
  // Fast selectors default on; malformed hand edits disable them without rejecting providers.
@@ -221,6 +234,9 @@ export const configSchema = z.object({
221
234
  short: z.number().int().min(MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT).max(100).optional().catch(undefined),
222
235
  long: z.number().int().min(MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT).max(100).optional().catch(undefined),
223
236
  }).optional().catch(undefined),
237
+ // A malformed fallback section disables only the reroute, never the lock's refusal
238
+ // behavior; the management write boundary still rejects the bad value.
239
+ codexMainAccountHardLockFallback: mainAccountHardLockFallbackSchema.optional().catch(undefined),
224
240
  // #2999: a malformed hand edit falls back to auto detection rather than rejecting the config.
225
241
  nativeMainRefresh: z.enum(["codex", "opencodex"]).optional().catch(undefined),
226
242
  // Future versions remain opaque through passthrough-compatible whole-config saves.
@@ -292,6 +308,7 @@ export const configSchema = z.object({
292
308
  // so a hand-edited typo cannot trip the backup-and-defaults repair path; the write path rejects
293
309
  // it (creditCodexAccountIdsError in diagnostics).
294
310
  creditCodexAccountIds: z.array(z.string().regex(/^[a-zA-Z0-9._-]{1,64}$/)).optional().catch(undefined),
311
+ codexUseRemainingQuotaAccountIds: z.custom<string[]>(validRemainingQuotaAccountIds).optional().catch(undefined),
295
312
  // A malformed policy degrades to "no policy" rather than failing the parse, so a hand-edited
296
313
  // typo cannot trip the backup-and-defaults repair path and wipe providers or pool accounts.
297
314
  // Silently ignoring it would be its own trap, so the write path rejects it and loadConfig warns.
@@ -307,6 +324,7 @@ export const configSchema = z.object({
307
324
  codexAccountAutoSwitchThresholds: z.unknown().optional().transform(salvageCodexAccountAutoSwitchThresholds),
308
325
  // An invalid optional preference must not discard providers or credential rows.
309
326
  codexAccountPriorityFailback: z.boolean().optional().catch(false),
327
+ codexPreserveSignedInAccount: z.boolean().optional().catch(false),
310
328
  // Absent or invalid keeps the sliding window on. Explicit false restores consecutive-only steering.
311
329
  codexFailureWindow: z.boolean().optional().catch(undefined),
312
330
  codexPinnedTransientPolicy: z.enum(["hold", "detour-new-threads"]).optional().catch("hold"),
@@ -594,6 +612,10 @@ export const configSchema = z.object({
594
612
  message: modelAdaptersError,
595
613
  });
596
614
  }
615
+ const credentialPinError = credentialPinnedProviderConfigError(name, provider);
616
+ if (credentialPinError) {
617
+ ctx.addIssue({ code: "custom", path: ["providers", redactSecretString(name)], message: credentialPinError });
618
+ }
597
619
  const preferHostedToolsError = modelPreferHostedToolsConfigError(
598
620
  (provider as { modelPreferHostedTools?: unknown }).modelPreferHostedTools,
599
621
  "modelPreferHostedTools",