@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
@@ -1,4 +1,8 @@
1
- import type { ResponsesRequestContext, ResponsesAdmissionState, ResponsesDispatchers } from "./core-options";
1
+ import type { HandleResponsesOptions, ResponsesRequestContext, ResponsesAdmissionState, ResponsesDispatchers } from "./core-options";
2
+ import type { PolicyRequestEvidence } from "../../routing/evaluator";
3
+ import type { RequestLogContext } from "../request-log";
4
+ import { markProtocolEntry, protocolMarkFeatures } from "../../protocols/trace";
5
+ import { isAntigravityRoute } from "../antigravity-direct-eligibility";
2
6
  import { capturePolicyRequestRoute, assertPolicyNormalizedRoute, recordPolicyPreparedDestination, policyCandidateRefusalResponse } from "./policy-request-scope";
3
7
  import {
4
8
  agentTaskRecoveryConfig,
@@ -33,6 +37,7 @@ import { resolveContextPrincipal } from "../auth-cors";
33
37
  import { resolveSkillsSnapshotScopeKey, snapshotSkillsCatalogInBody } from "./skills-snapshot";
34
38
  import {
35
39
  isShadowSourceModel,
40
+ shadowCallPreservesSelectedModel,
36
41
  shadowSourceModelPrefix,
37
42
  shouldInterceptShadowCall,
38
43
  } from "../../lib/shadow-call";
@@ -154,6 +159,48 @@ import {
154
159
 
155
160
  import { redactHostedImageDisplayPaths } from "../responses-hosted-image-display";
156
161
 
162
+ /** Policy-routing evidence. The direct lane's settings-only body has no conversation to scan. */
163
+ function routeEvidence(options: Pick<HandleResponsesOptions, "directRequest">, parsed: OcxParsedRequest): PolicyRequestEvidence {
164
+ return options.directRequest?.evidence ?? evidenceFromBody(parsed._rawBody);
165
+ }
166
+
167
+ /**
168
+ * Leave the Antigravity direct lane for this request: put the bridge conversation back on the
169
+ * Responses body so every `_rawBody` reader sees what the bridge would have sent, and trace the
170
+ * request as the bridge. The IR conversation already decoded stays: it equals what
171
+ * `parseRequest` builds from the restored body (`tests/protocols/chat-ir-parity.test.ts`).
172
+ */
173
+ function restoreDirectRequestConversation(options: HandleResponsesOptions, body: unknown, logCtx: RequestLogContext): void {
174
+ const direct = options.directRequest;
175
+ if (!direct) return;
176
+ options.directRequest = undefined;
177
+ if (body && typeof body === "object" && !Array.isArray(body)) Object.assign(body, direct.bridgeConversation());
178
+ if (options.clientEncoder) options.clientEncoder = { ...options.clientEncoder, requestLane: "bridge" };
179
+ markProtocolEntry(logCtx, {
180
+ inbound: direct.protocol,
181
+ lane: "bridge",
182
+ reasonCodes: ["not-migrated"],
183
+ features: protocolMarkFeatures(logCtx) ?? [],
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Leave the direct lane as soon as the route stops being the Antigravity route it was decided
189
+ * for. Called after every route assignment, so readers that run between a reroute and dispatch
190
+ * (canonical sanitization, ciphertext stripping, final normalization) see the bridge body.
191
+ */
192
+ function leaveDirectLaneIfRerouted(
193
+ options: HandleResponsesOptions,
194
+ route: Pick<RouteResult, "providerName" | "modelId" | "provider">,
195
+ parsed: OcxParsedRequest,
196
+ logCtx: RequestLogContext,
197
+ ): void {
198
+ const direct = options.directRequest;
199
+ if (!direct) return;
200
+ if (route.providerName === direct.providerName && route.modelId === direct.modelId && isAntigravityRoute(route)) return;
201
+ restoreDirectRequestConversation(options, parsed._rawBody, logCtx);
202
+ }
203
+
157
204
  /** Parses, selects, and admits one request without changing the dispatch policy. */
158
205
  export async function prepareResponsesRequest(
159
206
  requestContext: Pick<ResponsesRequestContext, "options" | "config" | "req" | "logCtx">,
@@ -275,10 +322,11 @@ export async function prepareResponsesRequest(
275
322
  }
276
323
  }
277
324
  let shadowCallIntercepted = false;
278
- // A spawned sub-agent turn names its model on purpose; gpt-6-luna is both the helper
279
- // slug and a default sub-agent model, so neither intercept site may rewrite that turn.
325
+ // Luna is also selectable by users/children. Both intercept sites use the same
326
+ // per-turn provenance; WS frames must not inherit the handshake's classification.
280
327
  const threadSpawn = isThreadSpawnRequest(req.headers);
281
- if (!concreteSelection && !options.compactionRoutingOverride && !options.internalDecisionCall && !threadSpawn && !memoryModelApplies && body && typeof body === "object" && !Array.isArray(body)) {
328
+ const preserveShadowModel = shadowCallPreservesSelectedModel(body, req.headers, options.inboundTransport);
329
+ if (!concreteSelection && !options.compactionRoutingOverride && !options.internalDecisionCall && !preserveShadowModel && !memoryModelApplies && body && typeof body === "object" && !Array.isArray(body)) {
282
330
  const shadowIntercept = config.shadowCallIntercept;
283
331
  const rawShadowModel = (body as { model?: unknown }).model;
284
332
  if (shadowIntercept?.enabled && shadowIntercept.model && typeof rawShadowModel === "string"
@@ -304,6 +352,9 @@ export async function prepareResponsesRequest(
304
352
  }
305
353
  if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) {
306
354
  options.onRequestBodyRead?.();
355
+ // The Antigravity direct lane is decided for one concrete route; a body that reached a combo
356
+ // goes through the combo's own child loop with the full bridge conversation.
357
+ restoreDirectRequestConversation(options, body, logCtx);
307
358
  return requestDispatchers.handleComboResponses(req, body, comboId, config, logCtx, {
308
359
  ...options,
309
360
  // Concrete combo child selectors no longer match the shadow source model. Carry the
@@ -381,6 +432,10 @@ export async function prepareResponsesRequest(
381
432
  let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
382
433
  try {
383
434
  parsed = parseRequest(body);
435
+ // Antigravity direct lane: the body carried only the request settings, so the parse above
436
+ // produced them with an empty conversation; the conversation itself comes straight from the
437
+ // client body. Inside this try so a decode error answers exactly like a parse error.
438
+ if (options.directRequest) parsed = options.directRequest.decode(parsed);
384
439
  if (options.inboundWire === "anthropic") {
385
440
  parsed._nativeReasoningReplay = options.nativeReasoningReplay;
386
441
  parsed._nativeReasoningMint = options.nativeReasoningMint;
@@ -580,8 +635,8 @@ export async function prepareResponsesRequest(
580
635
  const resolved = concreteSelection
581
636
  ? routeConcreteModel(config, modelId)
582
637
  : parsed._compactionRequest === true
583
- ? routeCompactionModel(config, modelId, evidenceFromBody(parsed._rawBody))
584
- : routeModel(config, modelId, evidenceFromBody(parsed._rawBody));
638
+ ? routeCompactionModel(config, modelId, routeEvidence(options, parsed))
639
+ : routeModel(config, modelId, routeEvidence(options, parsed));
585
640
  // A Pool 2 selector may leave Pool 2 only through an operator-configured cross-provider
586
641
  // blocked-model redirect, which the router marks as a credential-domain rewrite. Any other
587
642
  // destination means the selector fell through to default resolution, which stays refused.
@@ -613,7 +668,7 @@ export async function prepareResponsesRequest(
613
668
  }
614
669
  const _sci = config.shadowCallIntercept;
615
670
  let shadowRoute: RouteResult | undefined;
616
- if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.internalDecisionCall && !threadSpawn && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
671
+ if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.internalDecisionCall && !preserveShadowModel && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
617
672
  const sourcePrefix = shadowSourceModelPrefix(parsed.modelId, _sci.sourceModels)!;
618
673
  let sourceIdentity = { providerName: OPENAI_CODEX_PROVIDER_ID, modelId: sourcePrefix };
619
674
  try {
@@ -652,6 +707,7 @@ export async function prepareResponsesRequest(
652
707
  if (parsed._compactionRequest === true || options.compactionRoutingOverride) parsed._cursorIsolateConversation = true;
653
708
  route = memoryRoute ?? shadowRoute ?? resolveRoute(options.policyFallbackCandidate
654
709
  ? `${options.policyFallbackCandidate.provider}/${options.policyFallbackCandidate.model}` : parsed.modelId);
710
+ leaveDirectLaneIfRerouted(options, route, parsed, logCtx);
655
711
  // Name the phase in the persisted route decision, so the request log says why this turn went to
656
712
  // the memory destination instead of leaving it looking like a plain user selection. Set here, on
657
713
  // the resolved route, so a combo child's own route carries it too.
@@ -891,8 +947,9 @@ export async function prepareResponsesRequest(
891
947
  if (fallback?.to && !slugsEquivalent(fallback.to, route.modelId)) {
892
948
  try {
893
949
  route = captureInboundRoutePolicy(
894
- routeModel(config, fallback.to, evidenceFromBody(parsed._rawBody)),
950
+ routeModel(config, fallback.to, routeEvidence(options, parsed)),
895
951
  );
952
+ leaveDirectLaneIfRerouted(options, route, parsed, logCtx);
896
953
  credentialDomainWasRewritten = true;
897
954
  logCtx.routeDecision = route.routeDecision;
898
955
  logCtx.policyEligibility = route.policyEligibility;
@@ -1107,8 +1164,9 @@ export async function prepareResponsesRequest(
1107
1164
  if (fallback?.to && !slugsEquivalent(fallback.to, route.modelId)) {
1108
1165
  try {
1109
1166
  route = captureInboundRoutePolicy(
1110
- routeModel(config, fallback.to, evidenceFromBody(parsed._rawBody)),
1167
+ routeModel(config, fallback.to, routeEvidence(options, parsed)),
1111
1168
  );
1169
+ leaveDirectLaneIfRerouted(options, route, parsed, logCtx);
1112
1170
  credentialDomainWasRewritten = true;
1113
1171
  logCtx.routeDecision = route.routeDecision;
1114
1172
  logCtx.policyEligibility = route.policyEligibility;
@@ -1255,6 +1313,8 @@ export async function prepareResponsesRequest(
1255
1313
  // for streaming clients.
1256
1314
  const clientRequestedStream = parsed.stream;
1257
1315
  const admittedPolicyRoute = { providerName: route.providerName, modelId: route.modelId };
1316
+ // Final guard before normalization may inject guidance into the raw body.
1317
+ leaveDirectLaneIfRerouted(options, route, parsed, logCtx);
1258
1318
  await applyFinalRouteRequestNormalization({
1259
1319
  parsed,
1260
1320
  route,
@@ -1450,6 +1510,11 @@ export async function prepareResponsesRequest(
1450
1510
  commitSkillsSnapshot?.();
1451
1511
  return {
1452
1512
  inboundWire,
1513
+ clientDeclaredEmptyToolCatalog: options.clientDeclaredEmptyToolCatalog === true,
1514
+ // Admission facts later route rewrites must not re-derive: the selector the caller sent and
1515
+ // the key scope as it stood when this request was admitted.
1516
+ inboundSelector,
1517
+ admissionScope,
1453
1518
  translatorBudget,
1454
1519
  parsed,
1455
1520
  toolBridgeMaps,
@@ -1,4 +1,5 @@
1
1
  import { SendBudgetExhaustedError } from "../../lib/upstream-retry";
2
+ import { anthropicBillingPhysicalFetch } from "../../providers/anthropic-billing-ownership";
2
3
  import { rebindPhysicalSend } from "../../lib/request-execution-budget";
3
4
  import { anthropicModelQuotaFor } from "../../oauth/anthropic-model-quota";
4
5
  import { anthropicRatePolicyFor } from "../../oauth/anthropic-rate-limit-policy";
@@ -55,6 +56,11 @@ import { stampOAuthAccountLabel, usesApiKeyAccount } from "../../providers/label
55
56
  import { resolveProviderTransport } from "../../providers/xai-transport";
56
57
  import { captureRouteStaticPolicy } from "../../router";
57
58
  import { CopilotAutoHttpError, resolveCopilotAuto } from "../../providers/github-copilot-auto";
59
+ import {
60
+ AdmissionModelDeniedError,
61
+ admissionModelDeniedResponse,
62
+ assertRouteAllowedByScope,
63
+ } from "../admission-model-scope";
58
64
  import { renameRoutedIdentityInContext } from "../../adapters/identity";
59
65
  import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
60
66
  import {
@@ -87,7 +93,7 @@ import {
87
93
  import type { AttemptRecoveryKind } from "../../usage/log";
88
94
  import { bindAttemptDeliveryRecorder } from "../../usage/attempt-delivery";
89
95
  import { resolvePassiveRouteSubjectId } from "../passive-route-linker";
90
- import { clientCancelledResponse } from "./core-errors";
96
+ import { anthropicPoolCooldownResponse, clientCancelledResponse } from "./core-errors";
91
97
  import { acquireAccountLease, KIRO_ACCOUNT_WAIT_MS } from "../../oauth/kiro-account-load";
92
98
 
93
99
  /** Return a renewed Chat wire to the native owner before sending inference. */
@@ -108,6 +114,8 @@ export async function prepareResponsesTransport(
108
114
  | "inboundWire"
109
115
  | "selectedForwardHeaders"
110
116
  | "translatorBudget"
117
+ | "inboundSelector"
118
+ | "admissionScope"
111
119
  >,
112
120
  ) {
113
121
  const { config, logCtx, options, req } = requestContext;
@@ -296,6 +304,7 @@ export async function prepareResponsesTransport(
296
304
  const copilotRequestedModel = route.modelId;
297
305
  /** Project only bounded Copilot refusal metadata; upstream bodies and session tokens never reach the client. */
298
306
  const copilotRefusalResponse = (error: unknown): Response | undefined => {
307
+ if (error instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(error);
299
308
  if (!(error instanceof CopilotAutoHttpError) || ![401, 403, 429].includes(error.status)) return;
300
309
  const response = formatErrorResponse(error.status, error.status === 429 ? "rate_limit_error" : "authentication_error",
301
310
  error.message);
@@ -356,6 +365,13 @@ export async function prepareResponsesTransport(
356
365
  replayOAuthCredentialSnapshot = { accountId: refreshed.accountId, generation: refreshed.generation };
357
366
  selected = await negotiate();
358
367
  }
368
+ assertRouteAllowedByScope(
369
+ // The scope captured at admission: a key edited or removed while negotiation was in
370
+ // flight must not widen this request. The refusal names the caller's own selector.
371
+ requestState.admissionScope,
372
+ requestState.inboundSelector,
373
+ { providerName: route.providerName, modelId: selected.modelId },
374
+ );
359
375
  copilotSessionExpiresAt = selected.expiresAt ?? Infinity;
360
376
  route.provider = selected.provider;
361
377
  route.modelId = selected.modelId;
@@ -697,7 +713,7 @@ export async function prepareResponsesTransport(
697
713
  }
698
714
  commitKeyAttemptSend();
699
715
  response = await sendWithConnectionPolicy(
700
- fetchImpl,
716
+ anthropicBillingPhysicalFetch(fetchImpl, route.providerName, route.provider),
701
717
  destination,
702
718
  { ...dispatchInit, redirect: "manual" },
703
719
  { providerName: route.providerName, provider: route.provider },
@@ -724,6 +740,7 @@ export async function prepareResponsesTransport(
724
740
  let nextAdapter: ProviderAdapter;
725
741
  try { nextAdapter = await refreshDispatchAdapter(requestParsed); }
726
742
  catch (error) {
743
+ if (error instanceof AdmissionModelDeniedError) throw error;
727
744
  const refusal = copilotRefusalResponse(error);
728
745
  if (refusal) return refusal;
729
746
  throw error;
@@ -784,12 +801,9 @@ export async function prepareResponsesTransport(
784
801
  // Route names may resemble account IDs; log only the matched rule position.
785
802
  if (anthropicRouteDecision) console.warn(`[${anthropicInstance}-pool] route:#${anthropicRouteDecision.position} ${selection.reason}; answering locally`);
786
803
  if (selection.reason === "all-cooled") {
787
- const retryAfterSec = anthropicRouting!.getAnthropicPoolRetryAfterSeconds(Date.now(), anthropicRouteDecision, route.modelId);
788
- return formatErrorResponse(
789
- 429,
790
- "rate_limit_error",
791
- anthropicRouteDecision ? "Anthropic OAuth accounts for this model route are temporarily rate-limited" : "All Anthropic OAuth accounts are temporarily rate-limited",
792
- retryAfterSec !== null ? { retryAfter: String(retryAfterSec) } : undefined,
804
+ return anthropicPoolCooldownResponse(
805
+ anthropicRouting!.anthropicPoolCooldownError(Date.now(), anthropicRouteDecision, route.modelId),
806
+ { comboAttempt: options.comboAttempt, logCtx },
793
807
  );
794
808
  }
795
809
  return formatErrorResponse(401, "authentication_error", anthropicRouteDecision ? "No eligible Anthropic OAuth account for this model route" : "No eligible Anthropic OAuth account available");
@@ -23,6 +23,7 @@ import { normalizeLogConversationId } from "../request-log-conversation";
23
23
  import { normalizeDeclaredToolName, type AdapterEvent, type OcxProviderContinuationState } from "../../types";
24
24
  import { adapterFailureFromMessage, SEND_BUDGET_EXHAUSTED_CODE, SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE, type OcxErrorPayload } from "../../lib/errors";
25
25
  import { SendBudgetExhaustedError, markResponseNonReplayable } from "../../lib/upstream-retry";
26
+ import { hasKeyPoolFailover, rotateProviderTransportOn401, rotateProviderTransportOn429 } from "../../providers/key-failover";
26
27
  import {
27
28
  hasEligibleGenericOAuthFailoverTarget,
28
29
  isGenericOAuthFailoverEnabled,
@@ -224,6 +225,11 @@ export async function executeResponsesRunTurn(
224
225
  // in-flight; only the latest dispatch may merge adapter-written route
225
226
  // state back onto `parsed`, or the superseded attempt would restore the
226
227
  // failed account's cursor/continuation over the rotation's rebind.
228
+ type KeyBinding = Extract<NonNullable<ReturnType<typeof adapterBindings.get>>, { kind: "api-key" }>;
229
+ const failedKeyBindings = new WeakMap<Extract<AdapterEvent, { type: "error" }>, KeyBinding>();
230
+ const failedKeys = new Set<string>();
231
+ const keyAttemptLimit = route.provider.apiKeyPool?.length ?? 0;
232
+ let lastKeyFailure: Extract<AdapterEvent, { type: "error" }> | undefined;
227
233
  let runTurnAttemptSeq = 0;
228
234
  const runTurnAttempt = async (
229
235
  targetQueue: AdapterEventQueue,
@@ -238,7 +244,9 @@ export async function executeResponsesRunTurn(
238
244
  const attemptSeq = ++runTurnAttemptSeq;
239
245
  if (attemptSeq === 1 && options.comboInitialSend) options.comboInitialSend.producerOwned = true;
240
246
  let pacingSlot = preacquiredSlot;
247
+ let sentKeyBinding: KeyBinding | undefined;
241
248
  const emit = (event: AdapterEvent) => {
249
+ if (event.type === "error" && sentKeyBinding) failedKeyBindings.set(event, sentKeyBinding);
242
250
  options.onCompactionRecoveryAdapterEvent?.(event);
243
251
  targetQueue.push(event);
244
252
  };
@@ -249,6 +257,12 @@ export async function executeResponsesRunTurn(
249
257
  pacingSlot = await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
250
258
  }
251
259
  await refreshRunTurnSelection();
260
+ const selectedBinding = adapterBindings.get(transportState.runTurnAdapter);
261
+ if (lastKeyFailure && selectedBinding?.kind === "api-key"
262
+ && failedKeys.has(selectedBinding.provider.apiKey ?? "")) {
263
+ emit(lastKeyFailure);
264
+ return;
265
+ }
252
266
  // LOCAL PATCH (runturn-websearch): refreshRunTurnSelection binds route
253
267
  // state onto `parsed`; mirror it onto the iteration-local copy the
254
268
  // adapter actually receives.
@@ -293,13 +307,14 @@ export async function executeResponsesRunTurn(
293
307
  // The only way the request budget reaches a transport the adapter owns. Without it
294
308
  // a Cursor turn's inner ladder was three physical sends the cap read as one.
295
309
  ...(adapterDispatchBudget ? { sendBudget: adapterDispatchBudget } : {}),
296
- onPhysicalSend: send => noteAdapterPhysicalSend(
297
- logCtx.usageLogInputTokens,
298
- // The attempt's own recovery kind still labels its first send when the adapter
299
- // does not supply one of its own.
300
- { ...send, ...(send.recovery ?? recovery ? { recovery: send.recovery ?? recovery } : {}) },
301
- { includeFirst: reportsOwnSends },
302
- ),
310
+ onPhysicalSend: send => {
311
+ // Capture once at this attempt's send, before later selection or recovery changes it.
312
+ const binding = adapterBindings.get(transportState.runTurnAdapter);
313
+ if (!sentKeyBinding && binding?.kind === "api-key") sentKeyBinding = binding;
314
+ noteAdapterPhysicalSend(logCtx.usageLogInputTokens,
315
+ { ...send, ...(send.recovery ?? recovery ? { recovery: send.recovery ?? recovery } : {}) },
316
+ { includeFirst: reportsOwnSends });
317
+ },
303
318
  onRecoveryWithheld: noteAdapterRecoveryWithheld,
304
319
  },
305
320
  emit,
@@ -361,6 +376,7 @@ export async function executeResponsesRunTurn(
361
376
  const runTurn = async (): Promise<void> => runTurnAttempt(queue, undefined, true, wsFirstParsed, initialPacingSlot);
362
377
  const runTurnFailoverArmed = () =>
363
378
  route.provider.authMode === "oauth"
379
+ || (transportState.runTurnAdapter.name === "qoder" && hasKeyPoolFailover(route.provider))
364
380
  || !!(transportState.genericFailoverAccountId
365
381
  && isGenericOAuthFailoverEnabled(config, route.providerName));
366
382
  const dispatchSearchIteration = (iterParsed: PreparedResponsesRequest["parsed"]): AsyncIterable<AdapterEvent> => {
@@ -464,9 +480,51 @@ export async function executeResponsesRunTurn(
464
480
  return false;
465
481
  }
466
482
  };
483
+ const recoverRunTurnKey = async (
484
+ error: Extract<AdapterEvent, { type: "error" }>,
485
+ ): Promise<AttemptRecoveryKind | undefined> => {
486
+ const binding = failedKeyBindings.get(error);
487
+ if (error.apiKeyRecovery !== true || (error.status !== 401 && error.status !== 429)
488
+ || !binding || runTurnAbort.signal.aborted || !hasKeyPoolFailover(route.provider)
489
+ || failedKeys.size >= keyAttemptLimit) return undefined;
490
+ const failed = binding.provider;
491
+ if (!failed.apiKey || !failed._apiKeyAttempt || failedKeys.has(failed.apiKey)) return undefined;
492
+ const hop = reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|runturn-key-${error.status}`);
493
+ if (!hop.allowed) return undefined;
494
+ failedKeys.add(failed.apiKey);
495
+ lastKeyFailure = error;
496
+ try {
497
+ const rotate = error.status === 401 ? rotateProviderTransportOn401 : rotateProviderTransportOn429;
498
+ const rotated = rotate(config, route.providerName, failed, {
499
+ attemptedKey: failed.apiKey, attemptedSelection: failed._apiKeyAttempt,
500
+ promptCacheKey: parsed.options.promptCacheKey,
501
+ });
502
+ if (!rotated || failedKeys.size >= keyAttemptLimit || failedKeys.has(rotated.apiKey ?? "") || runTurnAbort.signal.aborted) {
503
+ hop.permit?.release();
504
+ return undefined;
505
+ }
506
+ route.provider = rotated;
507
+ const adapter = await refreshRunTurnAdapter(parsed);
508
+ if (!adapter.runTurn || failedKeys.has(route.provider.apiKey ?? "") || runTurnAbort.signal.aborted) {
509
+ hop.permit?.release();
510
+ return undefined;
511
+ }
512
+ bindRouteReasoningReplayScope({ parsed, providerName: route.providerName, provider: route.provider,
513
+ adapterName: adapter.name, forwardHeaders: requestState.selectedForwardHeaders });
514
+ sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel);
515
+ recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, adapter.name);
516
+ sendBudgetState.pendingHopPermit = hop.permit;
517
+ return error.status === 401 ? "key-401" : "key-429";
518
+ } catch {
519
+ hop.permit?.release();
520
+ return undefined;
521
+ }
522
+ };
467
523
  const recoverRunTurnAdapterOnPreflightError = async (
468
524
  error: Extract<AdapterEvent, { type: "error" }>,
469
525
  ): Promise<AttemptRecoveryKind | undefined> => {
526
+ const keyRecovery = await recoverRunTurnKey(error);
527
+ if (keyRecovery) return keyRecovery;
470
528
  if (await recoverRunTurnAdapterOnPreflight401(error)) return "oauth-401";
471
529
  if (await rotateRunTurnAdapterOnPreflight429(error)) return "oauth-account-429";
472
530
  return undefined;
@@ -683,7 +741,11 @@ export async function executeResponsesRunTurn(
683
741
  const stallTimeoutSec = wsPlan?.stallTimeoutSec ?? config.stallTimeoutSec;
684
742
  // A disabled watchdog (0) is not a zero preflight: it would commit SSE before Devin's first
685
743
  // event and deliver a pre-output 429 in-stream. Fall back to the default finite bound.
686
- const preflightDeadlineAt = grokDevinPreflight
744
+ // A key-pool preflight must not outlast the stall budget, or a silent CLI holds the SSE
745
+ // Response (heartbeats, stall watchdog) hostage; the OAuth preflight wait is unchanged.
746
+ const keyPoolPreflight = transportState.runTurnAdapter.name === "qoder"
747
+ && route.provider.authMode !== "oauth" && hasKeyPoolFailover(route.provider);
748
+ const preflightDeadlineAt = grokDevinPreflight || keyPoolPreflight
687
749
  ? Date.now() + (resolveStallTimeoutSec(stallTimeoutSec) || DEFAULT_STALL_TIMEOUT_SEC) * 1_000
688
750
  : undefined;
689
751
  if (runTurnFailoverArmed()) {
@@ -7,6 +7,9 @@ import type { ResponsesEffects } from "./response-effects";
7
7
  import type { ResponsesSendBudget } from "./request-send-budget";
8
8
  import { formatErrorResponse } from "../../bridge";
9
9
  import { planWebSearch, buildWebSearchTool, runWithWebSearch } from "../../web-search";
10
+ import { createRegisteredAdvisorPlan } from "./advisor-plan-slot";
11
+ import { ADVISOR_TOOL_NAME } from "./advisor-slot";
12
+ import { buildToolBridgeMaps } from "./collaboration";
10
13
  import {
11
14
  planImageBridge,
12
15
  planVideoBridge,
@@ -30,9 +33,11 @@ import {
30
33
  } from "../../oauth/generic-account-failover";
31
34
  import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
32
35
  import { isNonReplayableResponse } from "../../lib/upstream-retry";
36
+ import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
37
+ import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
33
38
  import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
34
39
  import { persistKiroAccountState } from "../../providers/kiro-account-state-disk";
35
- import { readDisplaySafeErrorText } from "./core-errors";
40
+ import { anthropicPoolCooldownResponse, readDisplaySafeErrorText } from "./core-errors";
36
41
  import {
37
42
  ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
38
43
  anthropicRoutingFor,
@@ -60,6 +65,7 @@ export async function executeResponsesSidecars(
60
65
  | "inboundWire"
61
66
  | "selectedForwardHeaders"
62
67
  | "translatorBudget"
68
+ | "toolBridgeMaps"
63
69
  | "rememberKiroDeliveredFinalAnswer"
64
70
  | "responseStateOptions"
65
71
  >,
@@ -169,6 +175,25 @@ export async function executeResponsesSidecars(
169
175
  }
170
176
  }
171
177
 
178
+ // Advisor sidecar plan (optional subsystem; null when disabled, unconfigured, or when this
179
+ // request IS an advisor loopback consultation — the recursion fence). The plan carries
180
+ // request-scoped state only; preflight keeps fixed policy separate from user-role advice
181
+ // before the worker is dispatched. Registration seam: the core path sees only
182
+ // `parsed._advisorGuard`; only an activated factory can load the optional implementation.
183
+ const advisorPlan = options.advisorInternal === true
184
+ ? null
185
+ : await createRegisteredAdvisorPlan({
186
+ config,
187
+ workerIdentity: `${route.modelId} (provider ${route.providerName})`,
188
+ workerModelId: route.modelId,
189
+ abortSignal: options.abortSignal,
190
+ });
191
+ // Preflight only on real worker turns: a routed-compaction turn is a summarization request,
192
+ // and injecting advice into it would pollute the summary Codex replaces its history with.
193
+ if (advisorPlan && !routedCompaction) {
194
+ await advisorPlan.preflightInject(parsed);
195
+ }
196
+
172
197
  // Image / web-search sidecars: plan once, then dispatch with runTurn-aware priority.
173
198
  // Routed-compaction turns must NOT hit the image bridge: compaction clears tools/_webSearch but
174
199
  // leaves _imageGeneration, so planImageBridge would activate and return a normal Responses
@@ -198,6 +223,20 @@ export async function executeResponsesSidecars(
198
223
  // A single request may rotate once for a verification refusal. Keep this separate from the
199
224
  // broader OAuth failover count: a sidecar turn can also spend that count on rate-limit recovery.
200
225
  let antigravityValidationRotationAttempted = false;
226
+ // A replacement credential whose Copilot Auto negotiation leaves the key's scope ends the turn.
227
+ // The loops only see "no rotation" and fail with the original, replayable 429, so the refusal
228
+ // is held here and answers in place of that failure.
229
+ let sidecarScopeDenial: AdmissionModelDeniedError | undefined;
230
+ const noteSidecarScopeDenial = (error: unknown): void => {
231
+ const refusal = unwrapUpstreamRetryEvidenceError(error);
232
+ if (refusal instanceof AdmissionModelDeniedError) sidecarScopeDenial ??= refusal;
233
+ };
234
+ const sidecarScopeRefusal = (response: Response): Response | undefined => {
235
+ if (!sidecarScopeDenial || response.ok) return undefined;
236
+ void response.body?.cancel().catch(() => {});
237
+ releaseSearchProbeLease();
238
+ return admissionModelDeniedResponse(sidecarScopeDenial);
239
+ };
201
240
  /** Admit a replacement credential, or leave the loop's original refusal authoritative. */
202
241
  const rotateSidecarProviderOn429 = async (
203
242
  retryAfter: string | null,
@@ -264,7 +303,8 @@ export async function executeResponsesSidecars(
264
303
  return null;
265
304
  }
266
305
  transportState.genericFailovers += 1;
267
- } catch {
306
+ } catch (error) {
307
+ noteSidecarScopeDenial(error);
268
308
  hop.permit?.release();
269
309
  return null;
270
310
  }
@@ -284,7 +324,10 @@ export async function executeResponsesSidecars(
284
324
  } else if (rotated) {
285
325
  route.provider = rotated;
286
326
  try { await resolveCopilotSelection(retryParsed ?? parsed); }
287
- catch { return null; } // Keep the sidecar's original refusal when replacement negotiation fails.
327
+ catch (error) { // Keep the sidecar's original refusal when replacement negotiation fails.
328
+ noteSidecarScopeDenial(error);
329
+ return null;
330
+ }
288
331
  } else if (
289
332
  // A POSITIVE gate, not an early return. An early `return null` here made every later arm
290
333
  // unreachable: Anthropic never has a genericFailoverAccountId (isGenericFailoverProvider
@@ -326,7 +369,8 @@ export async function executeResponsesSidecars(
326
369
  return null;
327
370
  }
328
371
  if (route.providerName === "kiro") transportState.genericFailovers += 1;
329
- } catch {
372
+ } catch (error) {
373
+ noteSidecarScopeDenial(error);
330
374
  hop.permit?.release();
331
375
  return null;
332
376
  }
@@ -527,6 +571,8 @@ export async function executeResponsesSidecars(
527
571
  notifyResponseComplete(response);
528
572
  },
529
573
  });
574
+ const imgScopeRefusal = sidecarScopeRefusal(imgResponse);
575
+ if (imgScopeRefusal) return imgScopeRefusal;
530
576
  if (imgResponse.body) {
531
577
  const imgTurnAc = new AbortController();
532
578
  imgTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
@@ -555,7 +601,7 @@ export async function executeResponsesSidecars(
555
601
  modelId: route.modelId,
556
602
  ...sidecarBudget.fetchOptions(transportState.adapter),
557
603
  })(input, init)) as typeof globalThis.fetch;
558
- const wsResponse = await runWithWebSearch({
604
+ const loopResponse = await runWithWebSearch({
559
605
  parsed, adapter: transportState.adapter,
560
606
  onIterationEnd: sidecarBudget.releaseUnsentHop,
561
607
  fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
@@ -622,6 +668,17 @@ export async function executeResponsesSidecars(
622
668
  notifyResponseComplete(response);
623
669
  },
624
670
  });
671
+ const wsScopeRefusal = sidecarScopeRefusal(loopResponse);
672
+ if (wsScopeRefusal) return wsScopeRefusal;
673
+ // A routed Anthropic model whose last pool account refused on spent quota fails the loop's
674
+ // first iteration with a generic 429 before any search runs. Answer the usage limit instead,
675
+ // as the main path does (#6991), so Codex stops rather than retrying the whole turn.
676
+ const poolUsageLimit = loopResponse.status === 429 && anthropicInstance && route.provider.authMode === "oauth"
677
+ && !options.comboAttempt
678
+ ? anthropicRoutingFor(anthropicInstance).anthropicPoolUsageLimitAfterRefusal(Date.now(), transportState.anthropicRouteDecision, route.modelId)
679
+ : null;
680
+ if (poolUsageLimit) void loopResponse.body?.cancel().catch(() => {});
681
+ const wsResponse = poolUsageLimit ? anthropicPoolCooldownResponse(poolUsageLimit, { logCtx }) : loopResponse;
625
682
  // Register the sidecar stream as an active turn so drainAndShutdown waits for (or aborts)
626
683
  // in-flight web-search turns instead of skipping them during graceful shutdown.
627
684
  if (wsResponse.body) {
@@ -636,5 +693,32 @@ export async function executeResponsesSidecars(
636
693
  return wsResponse;
637
694
  }
638
695
 
696
+ // Advisor synthetic tool + stream guard: attach ONLY when this function is about to hand the
697
+ // request back to the normal translated exchange (no sidecar loop claimed the turn, not a
698
+ // run-turn adapter, not compaction). The web-search/image loops own their own event handling;
699
+ // run-turn adapters execute their own loop; both would leak the synthetic tool call they
700
+ // cannot intercept. Preflight (above) still applies to those paths — only the tool does not.
701
+ if (advisorPlan && !routedCompaction && !transportState.adapter.runTurn && !wsPlan && !imgPlan && !vidPlan) {
702
+ // A client-declared tool named "advisor" would collide with the synthetic injection (one
703
+ // wire name, two schemas); the synthetic runtime owns the name for this turn.
704
+ parsed.context.tools = [
705
+ ...(parsed.context.tools ?? []).filter(t => !t.advisor && t.name !== ADVISOR_TOOL_NAME),
706
+ advisorPlan.tool,
707
+ ];
708
+ // The advisor tool joined AFTER prepare computed the bridge maps; recompute so the tool is
709
+ // declared (undeclared-tool guard, tool_choice mapping, schema repair) on this turn.
710
+ //
711
+ // Rebuild WITHOUT the budget: prepare already charged every tool in the catalog, and
712
+ // buildToolBridgeMaps charges each name it registers, so re-passing the budget would bill the
713
+ // whole catalog a second time and can trip `translation_buffer_limit` for a large MCP catalog.
714
+ // Charge only the one entry that is genuinely new — the synthetic advisor tool is a bare
715
+ // function tool, so it retains its wire name and its bare name (collaboration.ts:187, :233).
716
+ requestState.toolBridgeMaps = buildToolBridgeMaps(parsed);
717
+ translatorBudget.chargeRetained(new TextEncoder().encode(ADVISOR_TOOL_NAME).byteLength * 2, {
718
+ kind: "retained_collectors",
719
+ });
720
+ advisorPlan.attachGuard(parsed);
721
+ }
722
+
639
723
  return undefined;
640
724
  }
@@ -170,7 +170,8 @@ export function isTerminalGuardPassthroughOnly(event: AdapterEvent): boolean {
170
170
  return event.type === "heartbeat" || event.type === "tool_call_delta";
171
171
  }
172
172
 
173
- function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
173
+ /** Merge two reported usages (canonical Responses convention). Shared with the advisor guard. */
174
+ export function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
174
175
  if (!first) return second;
175
176
  if (!second) return first;
176
177
  const sumOptional = (key: keyof OcxUsage): number | undefined => {
@@ -23,8 +23,9 @@ import { CODEX_RESPONSES_HTTP_URL, CODEX_RESPONSES_WS_URL, CODEX_WS_FRAME_HEADER
23
23
  import { codexWsExchange } from "./codex-ws-exchange";
24
24
  import { CodexWsSession } from "./codex-ws-session";
25
25
  import { codexWsPool, codexWsReuseIdentity } from "./codex-ws-pool";
26
- import { codexWsCreateFrameExceedsLimit } from "./codex-ws-wire";
26
+ import { codexWsCreateFrameExceedsLimit, type CodexWsPreOutputRefusalHold } from "./codex-ws-wire";
27
27
  import { isLoopbackUrl, rewriteWebSocketDial } from "../../plugins/upstream-hooks";
28
+ import { outboundCredentials } from "../../lib/outbound-credential-registry";
28
29
  export { CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES, MAX_CODEX_WS_QUEUE_BYTES,
29
30
  MAX_CODEX_WS_CREATE_FRAME_BYTES, CODEX_WS_CREATE_FRAME_LIMIT_BYTES, codexWsCreateFrameExceedsLimit,
30
31
  isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./codex-ws-wire";
@@ -162,6 +163,8 @@ export async function codexWsUpstreamFetch(
162
163
  nativeControl?: NativeResponseControl,
163
164
  beforeContinuation?: () => Promise<void>,
164
165
  onPhysicalDispatch?: () => void,
166
+ /** Opt-in pre-output refusal hold (#6969); see codexWsExchange. */
167
+ preOutputRefusalHold?: CodexWsPreOutputRefusalHold,
165
168
  ): Promise<Response> {
166
169
  const prepared = prepareCodexWsRequest(url, init);
167
170
  if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
@@ -221,6 +224,10 @@ export async function codexWsUpstreamFetch(
221
224
  // reused for a different destination or with stale plugin headers.
222
225
  const dial = planCodexWsDial(wsUrl, headers, proxy);
223
226
  if (!dial) return sseFallback(url, init);
227
+ // The final dial headers, after plugin rewrites, are what the WebSocket upstream receives; the
228
+ // per-turn headers planning drops from the upgrade still reach it inside the frame metadata.
229
+ outboundCredentials().remember(headers);
230
+ outboundCredentials().remember(dial.headers);
224
231
  const identity = control ? null : codexWsReuseIdentity(url, dial.headers, frameText, dial.proxy, dial.url);
225
232
  const reuseAcrossTurns = identity !== null && codexWsReuseAcrossTurnsEnabled();
226
233
  const pooled = identity
@@ -243,6 +250,7 @@ export async function codexWsUpstreamFetch(
243
250
  session, url, init, prepared, sseFallback, onQuota, beforeDispatch,
244
251
  nativeControl: control,
245
252
  beforeContinuation, onPhysicalDispatch,
253
+ preOutputRefusalHold,
246
254
  bunVersion: typeof runtime === "string" ? runtime : runtime.version,
247
255
  });
248
256
  }