@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
@@ -2,8 +2,9 @@ import { MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT } from "../../codex/quota-types";
2
2
  import { resolveMainAccountHardLockThresholds } from "../../codex/main-account-hard-lock";
3
3
  import { getMainAccountExternalUsageWarning } from "../../codex/main-account-external-usage";
4
4
  import { getObservedMainQuotaIdentityKey } from "../../codex/main-account-cache";
5
- import { compactionRoutingSchema, memoryModelsSchema } from "../../config/schema/leaf-validators";
5
+ import { compactionRoutingSchema, memoryModelsSchema, reasoningRetentionSchema } from "../../config/schema/leaf-validators";
6
6
  import { compactionRecoverySchema } from "../../config/schema/compaction-recovery";
7
+ import { mainAccountHardLockFallbackSchema } from "../../config/schema/main-account-hard-lock-fallback";
7
8
  import { anthropicSidecarPatchError } from "../../config/schema/anthropic-account-pool";
8
9
  import { isAnthropicInstanceId } from "../../providers/anthropic-instance-id";
9
10
  import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
@@ -121,6 +122,8 @@ import {
121
122
  } from "../../lib/debug-settings";
122
123
  import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
123
124
  import { shadowCallTargetError } from "./shadow-call-validation";
125
+ import { autoReviewOverrideConfigError } from "../../config/provider-validation";
126
+ import { ConfigWritePublishedError } from "../../config/persist-unlocked";
124
127
  import { drainAndShutdown } from "../lifecycle";
125
128
  import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
126
129
  import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
@@ -268,7 +271,6 @@ export async function syncEnabledClientIntegrations(
268
271
  try {
269
272
  const { writeDesktop3pConfig } = await import("../../claude/desktop-3p");
270
273
  const { desktopVisibleNativeSlugs, filterCatalogVisibleModels } = await import("../../codex/catalog");
271
- const { fetchAllModels } = await import("../management-api");
272
274
  const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
273
275
  // Serialized with Desktop mode transitions (picker lock): a first-party switch cannot interleave.
274
276
  const { runPickerTransition } = await import("./claude-desktop-picker-routes");
@@ -329,8 +331,8 @@ export async function syncEnabledClientIntegrations(
329
331
  config,
330
332
  port,
331
333
  }, unattended
332
- ? ["mcode", "pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo"]
333
- : ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"],
334
+ ? ["mcode", "pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo", "qoder"]
335
+ : ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo", "qoder"],
334
336
  ...unattended ? [{ refreshOnly: true, admit }] as const : []));
335
337
 
336
338
  return out;
@@ -353,6 +355,7 @@ function publicVisionSidecarSettings(
353
355
  };
354
356
  }
355
357
 
358
+ /** Serve validated settings and publish catalog-affecting changes after persistence. */
356
359
  export async function handleConfigRoutes(ctx: ManagementContext): Promise<Response | null> {
357
360
  const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
358
361
  const readStartupHealth = deps.getCachedStartupHealth ?? getCachedStartupHealth;
@@ -424,6 +427,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
424
427
  // Absent means on by default: the GUI renders a switch enabled unless explicit false.
425
428
  fastRows: config.fastRows !== false,
426
429
  codexMainAccountHardLock: isMainAccountHardLockEnabled(config),
430
+ // Absent means off: the fallback is an extension of the lock, so the GUI renders
431
+ // the extended fields empty unless an explicit section exists.
432
+ codexMainAccountHardLockFallback: config.codexMainAccountHardLockFallback ?? null,
427
433
  mainAccountHardLock: { ...getMainAccountHardLockStatus(config),
428
434
  externalUsage: getMainAccountExternalUsageWarning(getObservedMainQuotaIdentityKey()) },
429
435
  // Absent means the historical auto-open, so the GUI can render the toggle
@@ -437,6 +443,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
437
443
  codexDesktopSwitches: describeCodexDesktopSwitches(config, await observedCodexDesktopSwitchApply()),
438
444
  compactionRouting: config.compactionRouting ?? null,
439
445
  compactionRecovery: config.compactionRecovery ?? null,
446
+ reasoningRetention: config.reasoningRetention ?? null,
440
447
  // Absent means both phases keep their existing routes; the GUI renders that as "Off".
441
448
  memoryModels: config.memoryModels ?? null,
442
449
  startupHealth: await readStartupHealthSnapshot(config),
@@ -530,11 +537,13 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
530
537
  fastRows?: unknown;
531
538
  codexMainAccountHardLock?: unknown;
532
539
  codexMainAccountHardLockThresholds?: unknown;
540
+ codexMainAccountHardLockFallback?: unknown;
533
541
  codexDesktopAuthless?: unknown;
534
542
  codexClientCompaction?: unknown;
535
543
  compactionRouting?: unknown;
536
544
  compactionRecovery?: unknown;
537
545
  memoryModels?: unknown;
546
+ reasoningRetention?: unknown;
538
547
  };
539
548
  if (body.codexAutoStart === undefined
540
549
  && body.streamMode === undefined
@@ -547,12 +556,14 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
547
556
  && body.fastRows === undefined
548
557
  && body.codexMainAccountHardLock === undefined
549
558
  && body.codexMainAccountHardLockThresholds === undefined
559
+ && body.codexMainAccountHardLockFallback === undefined
550
560
  && body.codexDesktopAuthless === undefined
551
561
  && body.codexClientCompaction === undefined
552
562
  && body.compactionRouting === undefined
553
563
  && body.compactionRecovery === undefined
564
+ && body.reasoningRetention === undefined
554
565
  && body.memoryModels === undefined) {
555
- return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, codexQuotaAutoRefresh, oauthOpenBrowser, showCodexCredits, ultraFastTier, fastRows, codexMainAccountHardLock, codexMainAccountHardLockThresholds, codexDesktopAuthless, codexClientCompaction, compactionRouting, compactionRecovery, or memoryModels" }, 400);
566
+ return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, codexQuotaAutoRefresh, oauthOpenBrowser, showCodexCredits, ultraFastTier, fastRows, codexMainAccountHardLock, codexMainAccountHardLockThresholds, codexMainAccountHardLockFallback, codexDesktopAuthless, codexClientCompaction, compactionRouting, compactionRecovery, reasoningRetention, or memoryModels" }, 400);
556
567
  }
557
568
  if (body.codexAutoStart !== undefined && typeof body.codexAutoStart !== "boolean") {
558
569
  return jsonResponse({ error: "codexAutoStart boolean is required" }, 400);
@@ -597,6 +608,13 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
597
608
  return jsonResponse({ error: "codexMainAccountHardLockThresholds short must not exceed long" }, 400);
598
609
  }
599
610
  }
611
+ // null clears the section, like compactionRouting; a present section must fully parse.
612
+ const hardLockFallback = body.codexMainAccountHardLockFallback == null
613
+ ? body.codexMainAccountHardLockFallback
614
+ : mainAccountHardLockFallbackSchema.safeParse(body.codexMainAccountHardLockFallback);
615
+ if (hardLockFallback != null && !hardLockFallback.success) {
616
+ return jsonResponse({ error: "codexMainAccountHardLockFallback requires a nonblank model selector and an optional declared reasoningEffort" }, 400);
617
+ }
600
618
  if (body.codexDesktopAuthless !== undefined && typeof body.codexDesktopAuthless !== "boolean") {
601
619
  return jsonResponse({ error: "codexDesktopAuthless boolean is required" }, 400);
602
620
  }
@@ -614,6 +632,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
614
632
  const memoryModels = body.memoryModels == null
615
633
  ? body.memoryModels
616
634
  : memoryModelsSchema.safeParse(body.memoryModels);
635
+ const reasoningRetention = body.reasoningRetention == null ? body.reasoningRetention : reasoningRetentionSchema.safeParse(body.reasoningRetention);
636
+ if (reasoningRetention != null && !reasoningRetention.success) return jsonResponse({ error: "reasoningRetention requires optional boolean enabled, maxContextPercent greater than 0 and at most 100 and/or positive integer maxTokens; no other fields are allowed" }, 400);
617
637
  if (memoryModels != null && !memoryModels.success) {
618
638
  return jsonResponse({ error: "memoryModels requires a nonblank model and an optional declared reasoningEffort per configured phase, and no other fields" }, 400);
619
639
  }
@@ -646,7 +666,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
646
666
  )) {
647
667
  return jsonResponse({ error: `appOwnedMemoryBudgetMb must be an integer from ${MIN_APP_OWNED_MEMORY_BUDGET_MB} to ${MAX_APP_OWNED_MEMORY_BUDGET_MB}` }, 400);
648
668
  }
649
- const restoreCompactionRouting = captureConfigTopLevelRollback(config, ["compactionRouting", "compactionRecovery", "memoryModels"]);
669
+ const restoreCompactionRouting = captureConfigTopLevelRollback(config, ["compactionRouting", "compactionRecovery", "memoryModels", "reasoningRetention"]);
650
670
  const previousSettings = {
651
671
  codexAutoStart: config.codexAutoStart,
652
672
  hasCodexAutoStart: Object.hasOwn(config, "codexAutoStart"),
@@ -672,6 +692,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
672
692
  hasCodexMainAccountHardLockThresholds: Object.hasOwn(config, "codexMainAccountHardLockThresholds"),
673
693
  codexMainAccountHardLock: config.codexMainAccountHardLock,
674
694
  hasCodexMainAccountHardLock: Object.hasOwn(config, "codexMainAccountHardLock"),
695
+ codexMainAccountHardLockFallback: config.codexMainAccountHardLockFallback,
696
+ hasCodexMainAccountHardLockFallback: Object.hasOwn(config, "codexMainAccountHardLockFallback"),
675
697
  codexDesktopAuthless: config.codexDesktopAuthless,
676
698
  hasCodexDesktopAuthless: Object.hasOwn(config, "codexDesktopAuthless"),
677
699
  codexClientCompaction: config.codexClientCompaction,
@@ -717,6 +739,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
717
739
  if (hardLockThresholds !== undefined) config.codexMainAccountHardLockThresholds = { ...hardLockThresholds };
718
740
  if (body.codexMainAccountHardLock === false) config.codexMainAccountHardLock = false;
719
741
  else if (body.codexMainAccountHardLock === true) deleteConfigTopLevelKey(config, "codexMainAccountHardLock");
742
+ if (hardLockFallback === null) deleteConfigTopLevelKey(config, "codexMainAccountHardLockFallback");
743
+ else if (hardLockFallback?.success) config.codexMainAccountHardLockFallback = hardLockFallback.data;
720
744
  if (body.codexDesktopAuthless === true) config.codexDesktopAuthless = true;
721
745
  else if (body.codexDesktopAuthless === false) deleteConfigTopLevelKey(config, "codexDesktopAuthless");
722
746
  if (body.codexClientCompaction === true) config.codexClientCompaction = true;
@@ -725,6 +749,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
725
749
  else if (compactionRouting?.success) config.compactionRouting = compactionRouting.data;
726
750
  if (compactionRecovery === null) deleteConfigTopLevelKey(config, "compactionRecovery");
727
751
  else if (compactionRecovery?.success) config.compactionRecovery = compactionRecovery.data;
752
+ if (reasoningRetention === null) deleteConfigTopLevelKey(config, "reasoningRetention");
753
+ else if (reasoningRetention?.success) config.reasoningRetention = reasoningRetention.data;
728
754
  // Null clears both phases; the GUI sends that when neither row names a model.
729
755
  if (memoryModels === null) deleteConfigTopLevelKey(config, "memoryModels");
730
756
  else if (memoryModels?.success) config.memoryModels = memoryModels.data;
@@ -775,6 +801,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
775
801
  if (previousSettings.hasCodexMainAccountHardLock) {
776
802
  config.codexMainAccountHardLock = previousSettings.codexMainAccountHardLock;
777
803
  } else deleteConfigTopLevelKey(config, "codexMainAccountHardLock");
804
+ if (previousSettings.hasCodexMainAccountHardLockFallback) {
805
+ config.codexMainAccountHardLockFallback = previousSettings.codexMainAccountHardLockFallback;
806
+ } else deleteConfigTopLevelKey(config, "codexMainAccountHardLockFallback");
778
807
  if (previousSettings.hasCodexDesktopAuthless) {
779
808
  config.codexDesktopAuthless = previousSettings.codexDesktopAuthless;
780
809
  } else deleteConfigTopLevelKey(config, "codexDesktopAuthless");
@@ -836,10 +865,12 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
836
865
  codexDesktopSwitches,
837
866
  compactionRouting: config.compactionRouting ?? null,
838
867
  compactionRecovery: config.compactionRecovery ?? null,
868
+ reasoningRetention: config.reasoningRetention ?? null,
839
869
  // The panel re-reads its own save response, so a missing block would render both
840
870
  // phases as "Off" while the server kept them.
841
871
  memoryModels: config.memoryModels ?? null,
842
872
  codexMainAccountHardLock: isMainAccountHardLockEnabled(config),
873
+ codexMainAccountHardLockFallback: config.codexMainAccountHardLockFallback ?? null,
843
874
  mainAccountHardLock: { ...getMainAccountHardLockStatus(config),
844
875
  externalUsage: getMainAccountExternalUsageWarning(getObservedMainQuotaIdentityKey()) },
845
876
  startupHealth: await readStartupHealthSnapshot(config),
@@ -1058,6 +1089,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
1058
1089
  // Read BEFORE the mutation below: the Codex-side key follows the switch, and a flip is the
1059
1090
  // only case that owes a config.toml rewrite.
1060
1091
  const webSearchEnabledBefore = config.webSearchSidecar?.enabled !== false;
1092
+ const visionEnabledBefore = config.visionSidecar?.enabled !== false;
1061
1093
  if (body.webSearch) {
1062
1094
  const pairTouched = body.webSearch.model !== undefined || body.webSearch.backend !== undefined || body.webSearch.anthropicInstance !== undefined;
1063
1095
  // Validate against the backend the caller SUBMITTED, across the whole
@@ -1193,6 +1225,11 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
1193
1225
  }
1194
1226
  }
1195
1227
  saveConfigPreservingClaudeCode(config);
1228
+ // Publish the changed attachment affordance to both the managed catalog and Codex cache.
1229
+ // Use the existing bounded catalog-only path after persistence, outside the config lock.
1230
+ const catalogRefresh = visionEnabledBefore !== (config.visionSidecar?.enabled !== false)
1231
+ ? await convergeCodexCatalog()
1232
+ : undefined;
1196
1233
  // The sidecar's own switch is only half of what "off" means: Codex keeps declaring its native
1197
1234
  // hosted `web_search` tool until its own mode says otherwise, and the tool a client advertises
1198
1235
  // is the one the model reaches for — which is why an MCP search server needs the client-side
@@ -1218,6 +1255,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
1218
1255
  ...(ws.xSearch ? { xSearch: ws.xSearch } : {}),
1219
1256
  },
1220
1257
  codexWebSearch,
1258
+ catalogRefreshPending: catalogRefresh ? catalogRefreshIsPending(catalogRefresh) : false,
1221
1259
  vision: publicVisionSidecarSettings(config, vision),
1222
1260
  visionModels: vision.models,
1223
1261
  // Echoed for the same reason GET always carries it: the dashboard rebuilds
@@ -1269,5 +1307,38 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
1269
1307
  sourceModels: shadowSourceModels(sci.sourceModels),
1270
1308
  });
1271
1309
  }
1310
+
1311
+ if (url.pathname === "/api/auto-review-settings" && req.method === "GET") {
1312
+ const override = config.autoReviewOverride ?? {};
1313
+ return jsonResponse({ enabled: override.enabled === true, model: override.model?.trim() ?? "" });
1314
+ }
1315
+
1316
+ if (url.pathname === "/api/auto-review-settings" && req.method === "PUT") {
1317
+ let raw: unknown;
1318
+ try { raw = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
1319
+ if (!isPlainRecord(raw)) return jsonResponse({ error: "body must be a JSON object" }, 400);
1320
+ const body = raw as { enabled?: unknown; model?: unknown };
1321
+ const shapeError = autoReviewOverrideConfigError(body, "body");
1322
+ if (shapeError) return jsonResponse({ error: shapeError }, 400);
1323
+ const rollback = captureConfigTopLevelRollback(config, ["autoReviewOverride"]);
1324
+ try {
1325
+ config.autoReviewOverride = { ...config.autoReviewOverride };
1326
+ if (typeof body.enabled === "boolean") config.autoReviewOverride.enabled = body.enabled;
1327
+ if (typeof body.model === "string") {
1328
+ const trimmed = body.model.trim();
1329
+ if (trimmed === "") delete config.autoReviewOverride.model;
1330
+ else config.autoReviewOverride.model = trimmed;
1331
+ }
1332
+ (deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode)(config);
1333
+ } catch (error) {
1334
+ if (!(error instanceof ConfigWritePublishedError)) rollback();
1335
+ throw error;
1336
+ }
1337
+ // The catalog carries the reviewer stamps, so the new selector has to reach it now; the
1338
+ // disposition travels in the body like every other catalog-affecting route.
1339
+ const catalogRefresh = await convergeCodexCatalog();
1340
+ const override = config.autoReviewOverride;
1341
+ return jsonResponse({ ok: true, enabled: override.enabled === true, model: override.model ?? "", catalogRefresh });
1342
+ }
1272
1343
  return null;
1273
1344
  }
@@ -45,6 +45,10 @@ export interface ManagementRequestIngress {
45
45
  }
46
46
 
47
47
  export interface ManagementApiDeps {
48
+ desktopStartupSettings?: import("../../codex/desktop-compatibility/startup-settings").DesktopStartupSettingsService;
49
+ desktopCertificateService?: import("../../codex/desktop-compatibility/certificate-service").DesktopCertificateService;
50
+ desktopCompatibilityRuntime?: import("../../codex/desktop-compatibility/runtime").DesktopCompatibilityRuntime;
51
+ onDesktopCompatibilityShutdown?: (shutdown: () => Promise<void>) => void;
48
52
  /** Bound to this server's lifecycle owner; absent in direct route tests. */
49
53
  listLowQuotaEvents?: (limit?: number) => LowQuotaEvent[];
50
54
  /** Bound Claude intercept state, injectable for isolated management-route tests. */
@@ -16,7 +16,7 @@ import { buildCursorLocalInstallerHint, type CursorLocalInstallerHint } from "..
16
16
  import { loadCursorEffortTable } from "../../integrations/cursor-effort-table";
17
17
  import { configuredApiAuthToken, isApiAuthRequired, jsonResponse } from "../auth-cors";
18
18
  import { localInferenceDestination } from "../../lib/local-destinations";
19
- import { fetchAllModels } from "../management-api";
19
+ import { fetchAllModels } from "./shared";
20
20
  import { predictCursorEffort } from "../models-capabilities";
21
21
  import { expandCursorEffortRow, knownEffortRowIds } from "../effort-row";
22
22
  import type { ManagementContext } from "./context";
@@ -0,0 +1,46 @@
1
+ import { jsonResponse } from "../auth-cors";
2
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
3
+ import type { ManagementContext } from "./context";
4
+ import type { DesktopCertificateService } from "../../codex/desktop-compatibility/certificate-service";
5
+
6
+ const PATH = "/api/codex/desktop-compatibility/certificate";
7
+ let service: DesktopCertificateService | undefined;
8
+
9
+ export async function handleDesktopCompatibilityRoutes(ctx: ManagementContext): Promise<Response | null> {
10
+ if (ctx.url.pathname !== PATH) return null;
11
+ if (ctx.req.method !== "GET" && ctx.req.method !== "POST") return jsonResponse({ error: "method_not_allowed" }, 405);
12
+ // A local dashboard confirmation precedes any persisted key or CurrentUser Root mutation.
13
+ // This is caller provenance, not protection against an arbitrary same-user process.
14
+ if (ctx.req.method === "POST" && (ctx.principal !== "gui-session" || !ctx.trustedLoopbackIngress)) {
15
+ return jsonResponse({ error: "local_dashboard_confirmation_required" }, 403);
16
+ }
17
+ let action: "prepare" | "trust" | "remove-trust" | "renew" | undefined, fingerprint: string | undefined;
18
+ if (ctx.req.method === "POST") {
19
+ let body: unknown;
20
+ try { body = await readManagementJsonBody(ctx.req); }
21
+ catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid_json" }, 400); }
22
+ if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "invalid_request" }, 400);
23
+ const value = body as Record<string, unknown>;
24
+ if (Object.keys(value).some(key => !["action", "fingerprint", "confirmed"].includes(key)) || value.confirmed !== true
25
+ || typeof value.action !== "string" || !["prepare", "trust", "remove-trust", "renew"].includes(value.action)) return jsonResponse({ error: "invalid_request" }, 400);
26
+ action = value.action as typeof action;
27
+ if (action !== "prepare" && (typeof value.fingerprint !== "string" || !/^[A-F0-9]{64}$/.test(value.fingerprint))) {
28
+ return jsonResponse({ error: "certificate_fingerprint_required" }, 400);
29
+ }
30
+ if (action === "prepare" && value.fingerprint !== undefined) return jsonResponse({ error: "unexpected_fingerprint" }, 400);
31
+ fingerprint = value.fingerprint as string | undefined;
32
+ }
33
+ const controller = ctx.deps.desktopCertificateService ?? (service ??= (await import("../../codex/desktop-compatibility/certificate-service")).createDesktopCertificateService());
34
+ try {
35
+ const status = action === "prepare" ? await controller.prepare()
36
+ : action === "trust" ? await controller.trust(fingerprint!)
37
+ : action === "renew" ? await controller.renew(fingerprint!)
38
+ : action === "remove-trust" ? await controller.removeTrust(fingerprint!) : await controller.status();
39
+ return jsonResponse({ ok: true, certificate: status });
40
+ } catch (error) {
41
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "operation_failed";
42
+ const allowed = new Set(["unsupported", "busy", "not_prepared", "fingerprint_changed", "app_running", "runtime_running", "app_state_unknown", "trust_unknown", "trust_not_applied",
43
+ "unsafe_path", "unreadable", "expired", "renewal_required", "protection_failed"]);
44
+ return jsonResponse({ ok: false, error: allowed.has(code) ? code : "operation_failed" }, 409);
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ import { jsonResponse } from "../auth-cors";
2
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
3
+ import type { ManagementContext } from "./context";
4
+ import type { DesktopCompatibilityRuntime } from "../../codex/desktop-compatibility/runtime";
5
+
6
+ const PATH = "/api/codex/desktop-compatibility/runtime";
7
+ const ERROR_CODES = new Set(["busy", "unsupported", "test_environment", "stopping", "build_unverified", "egress_proxy_invalid",
8
+ "trust_required", "certificate_expiring", "certificate_not_prepared", "certificate_invalid", "certificate_expired",
9
+ "native_identity_unverified", "native_routing_unverified", "cleanup_incomplete", "not_running", "connection_invalid", "connection_changed", "connection_cleanup_required", "connection_unavailable"]);
10
+
11
+ export async function handleDesktopCompatibilityRuntimeRoutes(ctx: ManagementContext): Promise<Response | null> {
12
+ if (ctx.url.pathname !== PATH) return null;
13
+ if (ctx.req.method !== "GET" && ctx.req.method !== "POST") return jsonResponse({ error: "method_not_allowed" }, 405);
14
+ if (ctx.req.method === "POST" && (ctx.principal !== "gui-session" || !ctx.trustedLoopbackIngress)) return jsonResponse({ error: "local_dashboard_confirmation_required" }, 403);
15
+ let action: "start" | "stop" | "observe" | "apply" | "launch" | undefined;
16
+ if (ctx.req.method === "POST") {
17
+ let body: unknown;
18
+ try { body = await readManagementJsonBody(ctx.req); }
19
+ catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid_json" }, 400); }
20
+ if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "invalid_request" }, 400);
21
+ const value = body as Record<string, unknown>;
22
+ if (Object.keys(value).some(key => !["action", "confirmed", "accountWideConsent"].includes(key)) || value.confirmed !== true
23
+ || typeof value.action !== "string" || !["start", "stop", "observe", "apply", "launch"].includes(value.action)) return jsonResponse({ error: "invalid_request" }, 400);
24
+ if (value.action === "apply" ? value.accountWideConsent !== true : value.accountWideConsent !== undefined) return jsonResponse({ error: "invalid_consent_scope" }, 400);
25
+ action = value.action as typeof action;
26
+ }
27
+ const module = ctx.deps.desktopCompatibilityRuntime ? null : await import("../../codex/desktop-compatibility/service");
28
+ const service: DesktopCompatibilityRuntime = ctx.deps.desktopCompatibilityRuntime ?? module!.getDesktopCompatibilityRuntime();
29
+ if (module) ctx.deps.onDesktopCompatibilityShutdown?.(module.shutdownDesktopCompatibility);
30
+ try {
31
+ if (action === "apply") {
32
+ const result = await service.apply(true);
33
+ return jsonResponse({ ok: result.accepted, activation: result, runtime: service.status() }, result.accepted ? 202 : 409);
34
+ }
35
+ if (action === "launch") {
36
+ const result = await service.launch();
37
+ return jsonResponse({ ok: result.status === "started", launch: result, runtime: service.status() }, result.status === "started" ? 200 : 409);
38
+ }
39
+ const result = action === "start" ? await service.start() : action === "stop" ? await service.stop()
40
+ : action === "observe" ? await service.observe() : service.status();
41
+ return jsonResponse({ ok: true, runtime: result });
42
+ } catch (error) {
43
+ const code = error instanceof Error ? error.message.replace(/^desktop_compatibility_/, "") : "operation_failed";
44
+ return jsonResponse({ ok: false, error: ERROR_CODES.has(code) ? code : "operation_failed", runtime: service.status() }, 409);
45
+ }
46
+ }
@@ -0,0 +1,27 @@
1
+ import { jsonResponse } from "../auth-cors";
2
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
3
+ import type { ManagementContext } from "./context";
4
+
5
+ const PATH = "/api/codex/desktop-compatibility/settings";
6
+ export async function handleDesktopCompatibilitySettingsRoutes(ctx: ManagementContext): Promise<Response | null> {
7
+ if (ctx.url.pathname !== PATH) return null;
8
+ if (ctx.req.method !== "GET" && ctx.req.method !== "POST") return jsonResponse({ error: "method_not_allowed" }, 405);
9
+ if (ctx.req.method === "POST" && (ctx.principal !== "gui-session" || !ctx.trustedLoopbackIngress)) return jsonResponse({ error: "local_dashboard_confirmation_required" }, 403);
10
+ let update: { startOnProxyStart: boolean; revision: string } | undefined;
11
+ if (ctx.req.method === "POST") {
12
+ let body: unknown;
13
+ try { body = await readManagementJsonBody(ctx.req); }
14
+ catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid_json" }, 400); }
15
+ if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "invalid_request" }, 400);
16
+ const value = body as Record<string, unknown>;
17
+ if (Object.keys(value).some(key => !["startOnProxyStart", "revision", "confirmed"].includes(key)) || value.confirmed !== true
18
+ || typeof value.startOnProxyStart !== "boolean" || typeof value.revision !== "string" || !/^[a-f0-9]{64}$/.test(value.revision)) return jsonResponse({ error: "invalid_request" }, 400);
19
+ update = { startOnProxyStart: value.startOnProxyStart, revision: value.revision };
20
+ }
21
+ const service = ctx.deps.desktopStartupSettings ?? (await import("../../codex/desktop-compatibility/startup-settings")).createDesktopStartupSettings({ liveConfig: ctx.config });
22
+ try { return jsonResponse({ ok: true, settings: update ? service.set(update.startOnProxyStart, update.revision) : service.status() }); }
23
+ catch (error) {
24
+ const code = error instanceof Error ? error.message.replace(/^desktop_compatibility_settings_/, "") : "operation_unconfirmed";
25
+ return jsonResponse({ ok: false, error: ["unavailable", "invalid", "changed", "invalid_request"].includes(code) ? `settings_${code}` : "settings_operation_unconfirmed" }, 409);
26
+ }
27
+ }
@@ -109,6 +109,7 @@ import {
109
109
  import { removeCredential } from "../../oauth/store";
110
110
  import { providerDestinationResolvedError } from "../../lib/destination-policy";
111
111
  import { redactSecretString } from "../../lib/redact";
112
+ import { withModelCostRowReplacement } from "../../config/live-reconcile";
112
113
  import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
113
114
  import { deriveProviderPresets } from "../../providers/derive";
114
115
  import { providerCodexAccountMode } from "../../providers/registry";
@@ -408,7 +409,7 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
408
409
  const submitted = { [modelId]: body.cost };
409
410
  const validationError = body.cost === null ? null : providerModelCostsConfigError(submitted);
410
411
  if (validationError) return jsonResponse({ error: validationError }, 400, req, config);
411
- // Copy only validated rate fields; never echo a secret-shaped model key that the
412
+ // Copy validated rates and policy; never echo a secret-shaped model key that the
412
413
  // shared display boundary suppresses. Model IDs remain exact, including slashes.
413
414
  const cost = body.cost === null ? null : sanitizeModelCostsForDisplay(submitted)?.[modelId];
414
415
  if (cost === undefined) return jsonResponse({ error: "modelId cannot be displayed safely" }, 400, req, config);
@@ -423,10 +424,10 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
423
424
  const previousModelCosts = provider.modelCosts;
424
425
  const nextModelCosts = Object.assign(
425
426
  Object.create(null) as Record<string, ProviderCostOverlay>,
426
- previousModelCosts ?? {},
427
+ structuredClone(previousModelCosts ?? {}),
427
428
  );
428
429
  if (cost === null) delete nextModelCosts[modelId];
429
- else nextModelCosts[modelId] = cost;
430
+ else nextModelCosts[modelId] = structuredClone(cost);
430
431
  const mergedError = providerModelCostsConfigError(nextModelCosts);
431
432
  if (mergedError) return jsonResponse({ error: mergedError }, 400, req, config);
432
433
  // Keep even an empty map until persistence reconciles individual model keys.
@@ -435,10 +436,14 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
435
436
  try {
436
437
  // The persistence owner refreshes usage overlays after its atomic write.
437
438
  // Price-only edits do not change routing or require catalog convergence.
438
- persistConfig(config);
439
+ withModelCostRowReplacement(config, name, modelId, cost, () => persistConfig(config));
439
440
  } catch (error) {
440
- if (hadModelCosts) provider.modelCosts = previousModelCosts;
441
- else delete provider.modelCosts;
441
+ // A published write is durable even if bookkeeping failed: recovery GET
442
+ // must not serve old prices or let a later save undo the committed policy.
443
+ if (!(error instanceof ConfigWritePublishedError)) {
444
+ if (hadModelCosts) provider.modelCosts = previousModelCosts;
445
+ else delete provider.modelCosts;
446
+ }
442
447
  throw error;
443
448
  }
444
449
  return jsonResponse({ ok: true, provider: name, modelId, cost }, 200, req, config);
@@ -556,9 +561,9 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
556
561
  if (!(error instanceof ClientPathError)) throw error;
557
562
  return jsonResponse({ error: error.message }, 400, req, config);
558
563
  }
559
- if ((requested === "raycast" || requested === "droid") && shouldInjectApiAuthHeader(config)) {
564
+ if ((requested === "raycast" || requested === "droid" || requested === "qoder") && shouldInjectApiAuthHeader(config)) {
560
565
  return jsonResponse({
561
- error: `${requested === "droid" ? "Factory Droid" : "Raycast"} export requires an unauthenticated loopback destination; this listener requires an admission header the client cannot supply.`,
566
+ error: `${requested === "droid" ? "Factory Droid" : requested === "qoder" ? "Qoder" : "Raycast"} export requires an unauthenticated loopback destination; this listener requires an admission header the client cannot supply.`,
562
567
  reason: "non_loopback",
563
568
  }, 400, req, config);
564
569
  }
@@ -52,6 +52,7 @@ import { reconcileSuccessfulModelDiscoveries } from "../../providers/new-model-p
52
52
  import { initialModelSelectionPending, pendingModelSelectionProviders } from "../../providers/initial-model-selection";
53
53
  import { catalogFastRowEligible, fastRowId } from "../fast-row";
54
54
  import { knownEffortRowIds } from "../effort-row";
55
+ import { visionSidecarConsumerClassifier } from "../../vision/eligibility";
55
56
 
56
57
  /**
57
58
  * One row of the `/api/models` list. Routed rows spread a `CatalogModel`, so the shape is
@@ -78,6 +79,15 @@ export type ManagementModelRow = Partial<CatalogModel> & {
78
79
  * one thing and saves another, so its own writes never appear to take.
79
80
  */
80
81
  inputModalitiesDeclared?: string[];
82
+ /**
83
+ * The runtime's verdict on whether the Vision Sidecar describes this model's images, from
84
+ * the same predicate the request path consults (legacy `noVisionModels`, registry
85
+ * enrichment, explicit declarations, custom rows). `inputModalitiesDeclared` cannot answer
86
+ * this: a model covered ONLY by legacy `noVisionModels` has no declaration while its
87
+ * advertised modalities are widened to image, so a client inferring consumer status from
88
+ * declared-vs-advertised modalities misclassifies the row after reload.
89
+ */
90
+ visionSidecarConsumer?: boolean;
81
91
  /** Exact stored context override, distinct from the effective catalog window. */
82
92
  contextWindowDeclared?: number;
83
93
  /**
@@ -496,6 +506,7 @@ export async function listManagementModelRows(
496
506
  // a hidden real `x--fast` must never become a synthetic selector for another model.
497
507
  const knownIds = config.fastRows === false ? new Set<string>() : knownEffortRowIds(config);
498
508
  for (const row of rows) knownIds.add(row.namespaced);
509
+ const isVisionSidecarConsumer = visionSidecarConsumerClassifier(config);
499
510
  return rows.map(row => {
500
511
  const pending = initialModelSelectionPending(config.providers[row.provider]);
501
512
  const modelCosts = Object.hasOwn(config.providers, row.provider)
@@ -519,6 +530,9 @@ export async function listManagementModelRows(
519
530
  });
520
531
  return {
521
532
  ...row,
533
+ // One shared definition with the request path: the GUI must not re-derive consumer
534
+ // status from declared-vs-advertised modalities (see the field doc above).
535
+ visionSidecarConsumer: isVisionSidecarConsumer(row.provider, row.id),
522
536
  ...(!row.native && modelCosts !== undefined && Object.hasOwn(modelCosts, row.id)
523
537
  ? { manualPricing: true } : {}),
524
538
  ...(pending ? { disabled: true, initialSelectionPending: true } : {}),
@@ -289,13 +289,8 @@ function isConfigLockError(error: unknown): boolean {
289
289
  */
290
290
  let grokToggleFlight: Promise<Response> | null = null;
291
291
 
292
- /**
293
- * Dynamic import, never static: management-api.ts statically imports THIS
294
- * module, so a static import of fetchAllModels back from it would close a
295
- * cycle. sync.ts:18-21 dodges the same cycle the same way.
296
- */
297
292
  async function defaultFetchAllModels(config: OcxConfig) {
298
- const { fetchAllModels } = await import("../management-api");
293
+ const { fetchAllModels } = await import("./shared");
299
294
  return fetchAllModels(config);
300
295
  }
301
296
 
@@ -665,7 +660,7 @@ function pickerStateNote(picker: DesktopPickerStatus): string {
665
660
  return "Picker mode is on: fully quit and reopen Claude Desktop to see OpenCodex models in the Code tab.";
666
661
  }
667
662
  if (picker.reason === "trust_pending" || picker.reason === "trust_declined") {
668
- return `Picker mode is waiting for the keychain step: run ${picker.hint ?? "ocx claude desktop picker trust"}.`;
663
+ return `Picker mode is waiting for certificate trust: run ${picker.hint ?? "ocx claude desktop picker trust"}.`;
669
664
  }
670
665
  return `Picker mode is off (${picker.reason}).`;
671
666
  }
@@ -0,0 +1,41 @@
1
+ import {
2
+ nativeReasoningRetentionSchema,
3
+ resolveNativeReasoningRetention,
4
+ } from "../../config/schema/native-reasoning-retention";
5
+ import { captureConfigTopLevelRollback, deleteConfigTopLevelKey } from "../../config/rebase-provenance";
6
+ import { ConfigWritePublishedError } from "../../config/persist-unlocked";
7
+ import { jsonResponse } from "../auth-cors";
8
+ import { readManagementJsonBodyOr } from "./body";
9
+ import type { ManagementContext } from "./context";
10
+
11
+ const INVALID_BODY = Symbol("invalid-body");
12
+
13
+ /** Partial PUTs preserve omitted allowances; null resets the entire block to defaults. */
14
+ export async function handleNativeReasoningRetentionRoutes(ctx: ManagementContext): Promise<Response | null> {
15
+ const { req, url, config } = ctx;
16
+ if (url.pathname === "/api/native-reasoning-retention" && req.method === "GET") {
17
+ return jsonResponse(resolveNativeReasoningRetention(config), 200, req, config);
18
+ }
19
+ if (url.pathname === "/api/native-reasoning-retention" && req.method === "PUT") {
20
+ const body = await readManagementJsonBodyOr(req, INVALID_BODY);
21
+ const parsed = body === null ? null : nativeReasoningRetentionSchema.safeParse(body);
22
+ if (parsed !== null && !parsed.success) {
23
+ return jsonResponse({ error: "body must be null or an object containing only boolean modelSwitch and accountSwitch fields" }, 400, req, config);
24
+ }
25
+ // Resolve the saver before changing the live object: no await may interleave mutation/save.
26
+ const persist = ctx.deps.saveConfigPreservingClaudeCode
27
+ ?? (await import("../../config")).saveConfigPreservingClaudeCode;
28
+ const restore = captureConfigTopLevelRollback(config, ["nativeReasoningRetention"]);
29
+ try {
30
+ if (parsed === null) deleteConfigTopLevelKey(config, "nativeReasoningRetention");
31
+ else config.nativeReasoningRetention = { ...config.nativeReasoningRetention, ...parsed.data };
32
+ persist(config);
33
+ } catch (error) {
34
+ // Published bytes remain authoritative even if later bookkeeping failed.
35
+ if (!(error instanceof ConfigWritePublishedError)) restore();
36
+ throw error;
37
+ }
38
+ return jsonResponse(resolveNativeReasoningRetention(config), 200, req, config);
39
+ }
40
+ return null;
41
+ }