@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -30,11 +30,14 @@ import {
30
30
  composeProjection,
31
31
  computePromptProbeStateFingerprint,
32
32
  findInvalidCharacter,
33
+ importBaseVariant,
33
34
  inspectOwnership,
34
35
  normalizeBody,
35
36
  previewAdopt,
37
+ previewBaseImport,
36
38
  previewSalvage,
37
39
  readPromptLayers,
40
+ recoverPromptJournal,
38
41
  salvageProjection,
39
42
  selectBaseVariant,
40
43
  setToggle,
@@ -76,6 +79,7 @@ const WRITE_ERROR_STATUS: Record<WriteError, number> = {
76
79
  unknown_layer: 400,
77
80
  store_unreadable: 409,
78
81
  invalid_characters: 400,
82
+ body_too_large: 400,
79
83
  write_superseded: 409,
80
84
  // Not the caller's fault and not a race: the filesystem refused the write and the
81
85
  // transaction rolled itself back. 500 rather than 409 — retrying the same request
@@ -84,6 +88,8 @@ const WRITE_ERROR_STATUS: Record<WriteError, number> = {
84
88
  recovery_required: 409,
85
89
  locked: 409,
86
90
  unsafe: 422,
91
+ import_body_changed: 409,
92
+ unsupported_form: 409,
87
93
  };
88
94
 
89
95
  /** Read-only view for the route test that asserts every mapping is a client error. */
@@ -335,7 +341,12 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
335
341
  if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
336
342
  const id = body.id;
337
343
  if (typeof id !== "string") return fail(ctx, "invalid_body", 400, "id must be a string");
338
- if (typeof body.enabled !== "boolean") return fail(ctx, "invalid_body", 400, "enabled must be a boolean");
344
+ // `null` is the restore-the-default verb: it deletes the line instead of
345
+ // writing the documented default back as a literal, so a later upstream
346
+ // change to the default still reaches this file.
347
+ if (typeof body.enabled !== "boolean" && body.enabled !== null) {
348
+ return fail(ctx, "invalid_body", 400, "enabled must be a boolean or null");
349
+ }
339
350
  const revision = revisionOf(body);
340
351
  if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
341
352
 
@@ -388,6 +399,109 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
388
399
  return settle(ctx, selectBaseVariant(selection, revision, paths(ctx)));
389
400
  }
390
401
 
402
+ if (url.pathname === "/api/codex-prompt/base/import" && req.method === "POST") {
403
+ // The adopt flow for `model_instructions_file`: preview first, write only on
404
+ // confirmation. The verb exists because /base/select refuses the `external`
405
+ // state by design — that refusal is about SILENT retargeting, and an
406
+ // explicit, previewed import is precisely the opt-in it protects.
407
+ //
408
+ // The preview carries the SERIALIZED variant file — `# {title}\n{body}` —
409
+ // not just the body: the heading is part of what Codex will read, so the
410
+ // thing confirmed must be the whole stored text, title included. Both byte
411
+ // counts are reported and separately named: `bodyBytes` is the budget the
412
+ // 64 KiB cap applies to, `serializedBytes` is the complete file.
413
+ const body = await readBody(ctx);
414
+ if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
415
+ const readState = readPromptLayers(paths(ctx));
416
+ if (!readState.readable) {
417
+ return fail(ctx, "config_unreadable", 409, "the configuration file exists but could not be read", { path: readState.configPath });
418
+ }
419
+ // The title is validated before anything is previewed or written: a caller
420
+ // that supplies one gets the same contract as a custom-layer title —
421
+ // 1-80 characters on a single line.
422
+ if (body.title !== undefined) {
423
+ const t = body.title;
424
+ if (typeof t !== "string" || t.trim().length === 0
425
+ || t.length > MAX_TITLE_CHARS || /[\r\n]/.test(t)) {
426
+ return fail(ctx, "invalid_title", 400, "title must be 1-80 characters on a single line");
427
+ }
428
+ }
429
+ const preview = previewBaseImport(paths(ctx), typeof body.title === "string" ? body.title : undefined);
430
+ if (preview.reason === "nothing_to_import") {
431
+ return fail(ctx, "nothing_to_import", 409,
432
+ "model_instructions_file is absent or already points at a managed variant");
433
+ }
434
+ if (preview.reason === "unsupported_form") {
435
+ return fail(ctx, "import_unsupported_form", 409, preview.detail, {
436
+ path: preview.rawPath,
437
+ });
438
+ }
439
+ if (preview.reason === "file_unreadable") {
440
+ return fail(ctx, "import_file_unreadable", 409, preview.detail, {
441
+ path: preview.resolvedPath ?? preview.rawPath,
442
+ });
443
+ }
444
+ if (preview.reason === "invalid_characters") {
445
+ return fail(ctx, "invalid_characters", 400, preview.detail, { path: preview.resolvedPath });
446
+ }
447
+ // Same boundary as every other write to this key: the 64 KiB cap applies to
448
+ // the BODY alone — the title heading lives outside that budget, unchanged
449
+ // from the managed-variant contract.
450
+ if (preview.reason === "body_too_large" || utf8Bytes(preview.body!) > MAX_BODY_BYTES) {
451
+ return fail(ctx, "body_too_large", 400, `the file body exceeds ${MAX_BODY_BYTES} bytes`, {
452
+ path: preview.resolvedPath,
453
+ });
454
+ }
455
+ if (preview.reason === "slots_full") {
456
+ return fail(ctx, "variant_slots_full", 409,
457
+ `at most ${MAX_BASE_VARIANTS} base variants; delete one first`, {
458
+ maxBaseVariants: MAX_BASE_VARIANTS,
459
+ serialized: preview.serialized,
460
+ resolvedPath: preview.resolvedPath,
461
+ });
462
+ }
463
+ if (body.confirm !== true) {
464
+ // Preview writes nothing, by construction: previewBaseImport is a pure read.
465
+ return jsonResponse({
466
+ ok: true,
467
+ changed: false,
468
+ preview: {
469
+ rawPath: preview.rawPath,
470
+ resolvedPath: preview.resolvedPath,
471
+ serialized: preview.serialized,
472
+ serializedBytes: utf8Bytes(preview.serialized!),
473
+ bodyBytes: utf8Bytes(preview.body!),
474
+ suggestedTitle: preview.suggestedTitle,
475
+ effectiveTitle: preview.effectiveTitle,
476
+ previewSha256: preview.previewSha256,
477
+ },
478
+ }, 200, req, ctx.config);
479
+ }
480
+ const revision = revisionOf(body);
481
+ if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
482
+ // The preview hash is REQUIRED — a confirm that does not name exactly what
483
+ // was previewed (serialized text, title included) cannot be checked against
484
+ // it and is refused without writing.
485
+ const previewSha256 = body.previewSha256;
486
+ if (typeof previewSha256 !== "string" || !/^[0-9a-f]{64}$/.test(previewSha256)) {
487
+ return fail(ctx, "import_preview_required", 400,
488
+ "confirm requires the previewSha256 returned by a preview of the same title");
489
+ }
490
+ if (previewSha256 !== preview.previewSha256) {
491
+ // The file or the selected title moved between preview and confirm. Refuse
492
+ // rather than import a serialization nobody previewed.
493
+ return fail(ctx, "import_body_changed", 409,
494
+ "the preview no longer matches; preview it again", {
495
+ resolvedPath: preview.resolvedPath,
496
+ });
497
+ }
498
+ return settle(ctx, importBaseVariant(
499
+ { title: typeof body.title === "string" ? body.title : undefined, previewSha256 },
500
+ revision,
501
+ paths(ctx),
502
+ ));
503
+ }
504
+
391
505
  if (url.pathname === "/api/codex-prompt/base" && req.method === "PUT") {
392
506
  const body = await readBody(ctx);
393
507
  if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
@@ -568,11 +682,28 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
568
682
  return settle(ctx, adoptDeveloperInstructions(revision, paths(ctx)));
569
683
  }
570
684
 
571
- // journal-present: recovery lives inside WP1's commit and is not exported.
572
- // Any ordinary mutation replays it on its own path, so the honest answer is
573
- // to name the state rather than duplicate the transaction here.
685
+ if (drift === "journal-present") {
686
+ // Recovery as its own locked operation — never a disguised layers write.
687
+ // The GUI used to replay recovery through a byte-identical custom PUT,
688
+ // which projected custom=[] over a config still carrying instructions
689
+ // whenever the store file was missing.
690
+ if (!confirm) {
691
+ return jsonResponse({
692
+ ok: true,
693
+ changed: false,
694
+ preview: { drift, storePath: snapshot.storePath },
695
+ }, 200, req, ctx.config);
696
+ }
697
+ if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
698
+ if (revision !== snapshot.revision) {
699
+ return fail(ctx, "stale_revision", 409, "the configuration moved since it was read");
700
+ }
701
+ return settle(ctx, recoverPromptJournal(paths(ctx)));
702
+ }
703
+
704
+ // A drift kind this build does not repair: name it rather than guess.
574
705
  return fail(ctx, "repair_unsupported", 409,
575
- "a write journal is present; recovery runs automatically on the next write", {
706
+ "this drift has no repair on this route", {
576
707
  drift, storePath: snapshot.storePath,
577
708
  });
578
709
  }
@@ -209,7 +209,7 @@ export async function proposeDelegationModel(options: {
209
209
  role: DELEGATED_WORK_ROLE,
210
210
  model: options.config.injectionModel ?? null,
211
211
  effort: options.config.injectionEffort ?? null,
212
- }], outcomes, classified, { alwaysProposeEffort: true });
212
+ }], outcomes, classified);
213
213
  return {
214
214
  sizingModel,
215
215
  sizingError,
@@ -27,6 +27,10 @@ function response(): Response {
27
27
  }
28
28
 
29
29
  export async function handleCompanionRoutes(ctx: ManagementContext): Promise<Response | null> {
30
+ if (ctx.url.pathname === "/api/advisor/settings") {
31
+ const { handleAdvisorRoutes } = await import("./advisor-routes");
32
+ return handleAdvisorRoutes(ctx);
33
+ }
30
34
  if (ctx.url.pathname === "/api/companion/open-in-browser" && ctx.req.method === "POST") {
31
35
  let body: unknown;
32
36
  try {
@@ -329,8 +329,8 @@ export async function syncEnabledClientIntegrations(
329
329
  config,
330
330
  port,
331
331
  }, unattended
332
- ? ["mcode", "pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo"]
333
- : ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"],
332
+ ? ["mcode", "pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo", "qoder"]
333
+ : ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo", "qoder"],
334
334
  ...unattended ? [{ refreshOnly: true, admit }] as const : []));
335
335
 
336
336
  return out;
@@ -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. */
@@ -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
+ }
@@ -22,10 +22,12 @@ import {
22
22
  } from "../../grok/reset-coupons";
23
23
  import {
24
24
  getGrokResetCouponOperationAccountId,
25
+ isTerminalGrokCouponReplay,
25
26
  markGrokResetCouponAttempt,
26
27
  openGrokResetCouponOperation,
27
28
  recordGrokResetCouponSettlement,
28
29
  readGrokResetCouponTerminalReplay,
30
+ refuseGrokResetCouponBeforeOpen,
29
31
  settleGrokResetCouponPreflightRefusal,
30
32
  type GrokResetCouponOperationRecord,
31
33
  } from "../../grok/reset-coupon-ledger";
@@ -140,10 +142,6 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
140
142
 
141
143
  const { accountId: rawAccountId, tokenId: requestedTokenId, operationId } = body;
142
144
 
143
- if (requestedTokenId !== undefined && (typeof requestedTokenId !== "string" || requestedTokenId.trim() === "")) {
144
- return jsonResponse({ error: { code: "invalid_token_id", message: "tokenId must be a non-empty string" } }, 400, req, config);
145
- }
146
-
147
145
  if (operationId !== undefined && !isCodexResetCreditOperationId(operationId)) {
148
146
  return jsonResponse(
149
147
  { error: { code: "invalid_operation_id", message: "operationId must be a valid UUIDv4" } },
@@ -153,47 +151,87 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
153
151
  );
154
152
  }
155
153
 
154
+ /**
155
+ * Once a caller supplies an operationId, that id may already name an operation that
156
+ * another request is spending. A refusal from this request must not read as
157
+ * definitive then, or the caller drops its hold and mints a replacement.
158
+ */
159
+ const uncertain = (id: string): Response => jsonResponse({ operationId: id, error: {
160
+ code: "attempt_in_progress",
161
+ message: "This request did not attempt a redemption, but another request may hold this operation; retry the same operationId to read its durable state",
162
+ } }, 409, req, config);
163
+ /** Refuse before open: definitive only when the supplied id is durably reserved as failed. */
164
+ const preOpenRefusal = (definitive: Response, code: string, knownAccountId?: string): Response => {
165
+ if (operationId === undefined) return definitive;
166
+ if (knownAccountId === undefined) return uncertain(operationId);
167
+ try {
168
+ const result = refuseGrokResetCouponBeforeOpen({
169
+ operationId, accountId: knownAccountId, code,
170
+ tokenId: typeof requestedTokenId === "string" ? requestedTokenId : undefined,
171
+ });
172
+ if (result.kind === "recorded") return definitive;
173
+ if (result.kind === "replay") return jsonResponse({
174
+ code: result.code, replayed: true, tokenId: result.tokenId, settledAt: result.settledAt,
175
+ }, 200, req, config);
176
+ if (result.kind === "identity-mismatch") return jsonResponse({ error: {
177
+ code: "operation_id_owned_by_another_account",
178
+ message: "Operation ID was previously registered with a different account or token",
179
+ } }, 409, req, config);
180
+ return uncertain(operationId);
181
+ } catch {
182
+ return uncertain(operationId);
183
+ }
184
+ };
185
+
186
+ if (requestedTokenId !== undefined && (typeof requestedTokenId !== "string" || requestedTokenId.trim() === "")) {
187
+ return preOpenRefusal(jsonResponse({ error: { code: "invalid_token_id", message: "tokenId must be a non-empty string" } }, 400, req, config), "invalid_token_id");
188
+ }
189
+
156
190
  let recordedAccountId: string | undefined;
157
191
  if (rawAccountId === undefined && operationId !== undefined) {
158
192
  try { recordedAccountId = getGrokResetCouponOperationAccountId(operationId); }
159
193
  catch {
160
- return jsonResponse({ error: { code: "ledger_unavailable", message: "Coupon ledger could not be read or locked; no redemption was attempted" } }, 503, req, config);
194
+ return uncertain(operationId);
161
195
  }
162
196
  }
163
197
  let accountId: string;
164
198
  try {
165
199
  accountId = resolveTargetAccountId(rawAccountId ?? recordedAccountId);
166
200
  } catch (err) {
167
- return jsonResponse(
201
+ return preOpenRefusal(jsonResponse(
168
202
  { error: { code: "no_account", message: err instanceof Error ? err.message : String(err) } },
169
203
  400,
170
204
  req,
171
205
  config,
172
- );
206
+ ), "no_account");
173
207
  }
174
208
 
175
209
  let tokenSnapshot;
176
210
  try {
177
211
  tokenSnapshot = await getValidAccessSnapshotForAccount("xai", accountId, { requireUsableAccount: true });
178
212
  } catch (err) {
179
- return jsonResponse(
213
+ return preOpenRefusal(jsonResponse(
180
214
  { error: { code: "auth_failed", message: "Failed to resolve valid xAI credentials for account" } },
181
215
  401,
182
216
  req,
183
217
  config,
184
- );
218
+ ), "auth_failed", accountId);
185
219
  }
186
220
 
187
221
  // Journaling and Idempotency settlement check
188
222
  const effectiveOpId = operationId ?? crypto.randomUUID();
189
- if (grokCouponInFlightAttempts.has(effectiveOpId)) return jsonResponse({ error: {
223
+ if (grokCouponInFlightAttempts.has(effectiveOpId)) return jsonResponse({ operationId: effectiveOpId, error: {
190
224
  code: "attempt_in_progress", message: "This operation still has an active request; retry later",
191
225
  } }, 409, req, config);
192
226
  let opRecord: GrokResetCouponOperationRecord;
193
227
  try {
194
228
  opRecord = openGrokResetCouponOperation({ accountId, tokenId: requestedTokenId, operationId: effectiveOpId });
195
229
  } catch {
196
- return jsonResponse({ error: { code: "ledger_unavailable", message: "Coupon ledger could not be read or locked; no redemption was attempted" } }, 503, req, config);
230
+ // A failed open cannot prove that no earlier request already opened this id.
231
+ return jsonResponse({ operationId: effectiveOpId, error: {
232
+ code: "ledger_unavailable",
233
+ message: "Coupon ledger could not be read or locked; this request did not attempt a redemption. Retry the same operationId",
234
+ } }, 503, req, config);
197
235
  }
198
236
 
199
237
  let resolvedTokenId = requestedTokenId ?? opRecord.tokenId;
@@ -201,6 +239,14 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
201
239
 
202
240
  if (opRecord.kind === "replay") {
203
241
  if (opRecord.code !== undefined) {
242
+ // Replay only a definitive outcome; a recorded code that means "unconfirmed"
243
+ // must keep the caller on this operation.
244
+ if (!isTerminalGrokCouponReplay(opRecord, accountId, requestedTokenId)) {
245
+ return jsonResponse({ operationId: effectiveOpId, error: {
246
+ code: "attempt_unresolved",
247
+ message: "This operation's recorded outcome is unconfirmed; preserve this operationId and do not create a replacement attempt",
248
+ } }, 409, req, config);
249
+ }
204
250
  return jsonResponse(
205
251
  {
206
252
  code: opRecord.code,
@@ -230,6 +276,7 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
230
276
  // retry with the recorded token and preserve the operationId.
231
277
  return jsonResponse(
232
278
  {
279
+ operationId: effectiveOpId,
233
280
  error: {
234
281
  code: "operation_token_mismatch",
235
282
  message: "Operation was attempted with a different coupon; preserve the operationId and retry with the recorded tokenId",
@@ -282,7 +329,7 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
282
329
  } }, 409, req, config);
283
330
  }
284
331
 
285
- if (opRecord.kind === "token-mismatch") return jsonResponse({ error: {
332
+ if (opRecord.kind === "token-mismatch") return jsonResponse({ operationId: effectiveOpId, error: {
286
333
  code: "operation_token_mismatch", message: "Operation ID was previously registered with a different coupon",
287
334
  } }, 409, req, config);
288
335
 
@@ -301,11 +348,14 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
301
348
  }
302
349
 
303
350
  if (opRecord.kind !== "execute") {
351
+ // A capacity refusal cannot reserve the id, so an earlier same-id request still in
352
+ // credential resolution may open it once records expire: keep the caller on the id.
304
353
  return jsonResponse(
305
354
  {
355
+ ...(operationId !== undefined ? { operationId: effectiveOpId } : {}),
306
356
  error: {
307
357
  code: opRecord.kind,
308
- message: "Coupon ledger capacity or unavailable failure",
358
+ message: "Coupon ledger is at capacity; this request did not attempt a redemption. Retry the same operationId later",
309
359
  },
310
360
  },
311
361
  503,
@@ -324,14 +374,13 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
324
374
  if (result.kind === "replay") return jsonResponse({
325
375
  code: result.code, replayed: true, tokenId: result.tokenId, settledAt: result.settledAt,
326
376
  }, 200, req, config);
327
- return jsonResponse({ error: {
377
+ return jsonResponse({ operationId: effectiveOpId, error: {
328
378
  code: "operation_state_changed",
329
379
  message: "Another request changed this operation during inspection; retry the same operationId to read its durable state",
330
380
  } }, 409, req, config);
331
381
  } catch {
332
- return jsonResponse({ error: {
333
- code: "ledger_unavailable", message: "Coupon refusal could not be recorded; no redemption was attempted",
334
- } }, 503, req, config);
382
+ // The refusal could not be recorded, so the operation may still be claimed elsewhere.
383
+ return uncertain(effectiveOpId);
335
384
  }
336
385
  };
337
386
 
@@ -345,6 +394,10 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
345
394
  const remaining = await getGrokRemainingResets({ accessToken: tokenSnapshot.accessToken });
346
395
  tokens = remaining.tokens ?? [];
347
396
  } catch (err) {
397
+ // Close the still-open operation before reporting a definitive failure: once it is
398
+ // recorded as failed it can never be claimed, so the caller may safely start over.
399
+ const changed = settlePreflightRefusal("fetch_resets_failed", resolvedTokenId);
400
+ if (changed) return changed;
348
401
  return jsonResponse(
349
402
  { error: { code: "fetch_resets_failed", message: err instanceof Error ? err.message : String(err) } },
350
403
  502,
@@ -389,6 +442,8 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
389
442
 
390
443
  if (resolvedTokenId === undefined) {
391
444
  // Unreachable: every path above either resolves a token or returns.
445
+ const changed = settlePreflightRefusal("token_unresolved");
446
+ if (changed) return changed;
392
447
  return jsonResponse(
393
448
  { error: { code: "token_unresolved", message: "No reset coupon token could be resolved" } },
394
449
  500,
@@ -403,6 +458,7 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
403
458
  // on this operationId again regardless of how stale the mark looks.
404
459
  return jsonResponse(
405
460
  {
461
+ operationId: effectiveOpId,
406
462
  error: {
407
463
  code: "attempt_in_progress",
408
464
  message: "A redemption attempt for this operation is still running; retry later",
@@ -433,17 +489,12 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
433
489
  if (winner) return jsonResponse({
434
490
  code: winner.code, replayed: true, tokenId: winner.tokenId, settledAt: winner.settledAt,
435
491
  }, 200, req, config);
436
- return jsonResponse({ error: {
437
- code: "attempt_in_progress", message: "Another request already claimed this operation; no redemption was attempted",
438
- } }, 409, req, config);
492
+ return uncertain(effectiveOpId);
439
493
  }
440
- } catch (err) {
441
- return jsonResponse(
442
- { error: { code: "attempt_mark_failed", message: err instanceof Error ? err.message : String(err) } },
443
- 500,
444
- req,
445
- config,
446
- );
494
+ } catch {
495
+ // The claim write failed or was contended: this request dispatched nothing, but a
496
+ // concurrent holder of this operation may be spending, so keep the caller on it.
497
+ return uncertain(effectiveOpId);
447
498
  }
448
499
 
449
500
  try {