@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
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Credentials this installation is configured with, read when an upstream diagnostic is checked.
3
+ *
4
+ * A gateway can echo a credential it learned before this process ever sent it: an inactive pool
5
+ * key, another provider's key, an OAuth token from an earlier session. Learning only from sends
6
+ * would let such an echo be warned and finalized before the value is first dispatched, and a warning
7
+ * cannot be retracted. So the request log also refuses any diagnostic that repeats a configured
8
+ * value: every provider's apiKey and apiKeyPool keys (keychain and env references resolved), every
9
+ * configured header value, every credential-named provider field, and every token in the OAuth
10
+ * store. The set is rebuilt when the bytes of config.json or auth.json change, a stored keychain
11
+ * secret is replaced in this process, or a referenced environment variable changes.
12
+ *
13
+ * Freshness is decided by content, never by file metadata. Every check reads both stores and
14
+ * compares a digest of the bytes it read; the rebuild parses those same bytes. An inode/size/mtime
15
+ * key missed a same-length rewrite inside one timestamp tick (coarse on Windows) and any writer that
16
+ * preserves or backdates mtime, and then kept serving the previous, complete-looking view (#6923).
17
+ * The cost is two small reads per check; checks run only for retained upstream diagnostics.
18
+ *
19
+ * Reads are observe-only: no hardening, backup, repair or warning. A file that exists but cannot be
20
+ * read, parsed or recognized as the expected shape, or a keychain reference that cannot be resolved,
21
+ * makes coverage incomplete and callers fail closed; an incomplete set is retried after a short delay
22
+ * so a keychain that becomes available restores diagnostics. Values are held in memory only and
23
+ * never serialized or exposed.
24
+ *
25
+ * Not covered: keychain secrets are cached per process (`readKeychain` in
26
+ * src/providers/api-key-resolve.ts), so a secret replaced by another process is seen only after
27
+ * this process re-resolves it.
28
+ */
29
+ import { createHash } from "node:crypto";
30
+ import { readFileSync, statSync } from "node:fs";
31
+ import { join } from "node:path";
32
+ import { getConfigDir } from "../config/paths";
33
+ import { resolveEnvValue } from "../config/proxy-env";
34
+ import { isKeychainReference, providerKeyResolutionGeneration, resolveProviderApiKeyQuietly } from "../providers/api-key-resolve";
35
+ import { credentialComponents, credentialSetMatches } from "../lib/outbound-credential-registry";
36
+ import { SENSITIVE_KEY_PATTERN } from "../lib/redact";
37
+
38
+ const CREDENTIAL_FIELD = /(?:key|token|secret|password|passwd|credential|cookie|authorization)$/i;
39
+ /** How long an incomplete set is trusted before its sources are read again. */
40
+ export const INCOMPLETE_RETRY_MS = 30_000;
41
+
42
+ export interface ConfiguredCredentials {
43
+ readonly complete: boolean;
44
+ matches(value: string): boolean;
45
+ }
46
+
47
+ interface Snapshot {
48
+ /** The only object handed out: cache metadata (including env values) stays private here. */
49
+ readonly view: ConfiguredCredentials;
50
+ readonly signature: string;
51
+ readonly envNames: readonly string[];
52
+ readonly envFingerprint: string;
53
+ readonly builtAt: number;
54
+ }
55
+
56
+ let cached: Snapshot | undefined;
57
+
58
+ /** One read of a store: its state for the signature and, for a readable file, the bytes read. */
59
+ interface StoreRead {
60
+ /** "absent", "not-a-file", "unreadable", or "sha256:<hex>" of exactly the bytes in `text`. */
61
+ readonly state: string;
62
+ readonly text?: string;
63
+ }
64
+
65
+ function readStore(path: string): StoreRead {
66
+ try {
67
+ if (!statSync(path).isFile()) return { state: "not-a-file" };
68
+ } catch (error) {
69
+ // Only a store that is not there at all counts as intentionally absent.
70
+ return { state: (error as NodeJS.ErrnoException)?.code === "ENOENT" ? "absent" : "unreadable" };
71
+ }
72
+ let bytes: Buffer;
73
+ try {
74
+ bytes = readFileSync(path);
75
+ } catch {
76
+ // Present a moment ago, gone or unreadable now (a replacement in progress): fail closed.
77
+ return { state: "unreadable" };
78
+ }
79
+ return { state: `sha256:${createHash("sha256").update(bytes).digest("hex")}`, text: bytes.toString("utf8") };
80
+ }
81
+
82
+ function parseStore(read: StoreRead): { ok: boolean; value?: unknown } {
83
+ if (read.state === "absent") return { ok: true };
84
+ if (read.text === undefined) return { ok: false };
85
+ try {
86
+ return { ok: true, value: JSON.parse(read.text.replace(/^\uFEFF/, "")) };
87
+ } catch {
88
+ return { ok: false };
89
+ }
90
+ }
91
+
92
+ function isRecord(value: unknown): value is Record<string, unknown> {
93
+ return typeof value === "object" && value !== null && !Array.isArray(value);
94
+ }
95
+
96
+ function envFingerprint(names: readonly string[]): string {
97
+ return names.map(name => process.env[name] ?? "\u0000unset").join("\u0000");
98
+ }
99
+
100
+ /** Mirrors resolveEnvValue: a braced or bare dollar reference reads the environment. */
101
+ function envReference(raw: string): string | undefined {
102
+ if (!raw.startsWith("$")) return undefined;
103
+ return /^\$\{(\w+)\}$/.exec(raw)?.[1] ?? raw.slice(1);
104
+ }
105
+
106
+ function build(configRead: StoreRead, authRead: StoreRead, signature: string, now: number): Snapshot {
107
+ const values = new Set<string>();
108
+ const envNames = new Set<string>();
109
+ let complete = true;
110
+ const add = (raw: string | undefined) => {
111
+ if (raw) for (const part of credentialComponents(raw)) values.add(part);
112
+ };
113
+ const noteEnv = (raw: string) => {
114
+ const name = envReference(raw);
115
+ if (name) envNames.add(name);
116
+ };
117
+ const addReference = (raw: unknown) => {
118
+ if (typeof raw !== "string" || !raw) return;
119
+ noteEnv(raw);
120
+ const resolved = resolveProviderApiKeyQuietly(raw);
121
+ if (resolved === undefined && isKeychainReference(raw)) complete = false;
122
+ add(resolved);
123
+ };
124
+ const isCredentialField = (field: string) => CREDENTIAL_FIELD.test(field) || SENSITIVE_KEY_PATTERN.test(field);
125
+ const walkProvider = (node: unknown, depth: number) => {
126
+ if (depth > 8) return;
127
+ if (Array.isArray(node)) { for (const item of node) walkProvider(item, depth + 1); return; }
128
+ if (!isRecord(node)) return;
129
+ for (const [field, value] of Object.entries(node)) {
130
+ if (field === "headers" && isRecord(value)) {
131
+ for (const header of Object.values(value)) {
132
+ if (typeof header !== "string") continue;
133
+ noteEnv(header);
134
+ add(resolveEnvValue(header) ?? header);
135
+ }
136
+ } else if (typeof value === "string") {
137
+ if (isCredentialField(field)) addReference(value);
138
+ } else {
139
+ walkProvider(value, depth + 1);
140
+ }
141
+ }
142
+ };
143
+ const walkAuth = (node: unknown, depth: number) => {
144
+ if (depth > 8) return;
145
+ if (Array.isArray(node)) { for (const item of node) walkAuth(item, depth + 1); return; }
146
+ if (!isRecord(node)) return;
147
+ for (const [field, value] of Object.entries(node)) {
148
+ if (typeof value === "string") {
149
+ if (field === "access" || field === "refresh" || isCredentialField(field)) add(value);
150
+ } else {
151
+ walkAuth(value, depth + 1);
152
+ }
153
+ }
154
+ };
155
+
156
+ const configSig = configRead.state;
157
+ const authSig = authRead.state;
158
+ const config = parseStore(configRead);
159
+ const auth = parseStore(authRead);
160
+ if (!config.ok || !auth.ok) complete = false;
161
+ // An existing file must have the shape its loader expects; anything else hides credentials.
162
+ if (configSig !== "absent" && config.ok && (!isRecord(config.value)
163
+ || (config.value.providers !== undefined && !isRecord(config.value.providers)))) complete = false;
164
+ if (authSig !== "absent" && auth.ok && !isRecord(auth.value)) complete = false;
165
+ if (isRecord(config.value) && isRecord(config.value.providers)) walkProvider(config.value.providers, 0);
166
+ if (isRecord(auth.value)) walkAuth(auth.value, 0);
167
+
168
+ const names = [...envNames].sort();
169
+ const view: ConfiguredCredentials = Object.freeze({
170
+ complete,
171
+ matches: (value: string) => credentialSetMatches(values, candidate => values.has(candidate), value),
172
+ });
173
+ return {
174
+ view,
175
+ signature,
176
+ envNames: names,
177
+ envFingerprint: envFingerprint(names),
178
+ builtAt: now,
179
+ };
180
+ }
181
+
182
+ /** The configured credential set for the active config home, rebuilt when any input changes. */
183
+ export function configuredCredentials(now = Date.now()): ConfiguredCredentials {
184
+ const dir = getConfigDir();
185
+ const configRead = readStore(join(dir, "config.json"));
186
+ const authRead = readStore(join(dir, "auth.json"));
187
+ const signature = `${configRead.state}|${authRead.state}|${providerKeyResolutionGeneration()}|${dir}`;
188
+ const stale = !cached
189
+ || cached.signature !== signature
190
+ || cached.envFingerprint !== envFingerprint(cached.envNames)
191
+ || (!cached.view.complete && now - cached.builtAt >= INCOMPLETE_RETRY_MS);
192
+ if (stale) cached = build(configRead, authRead, signature, now);
193
+ return cached!.view;
194
+ }
@@ -0,0 +1,56 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import { siblingOfLivePort } from "../../codex/sibling-start";
3
+ import { isTestHomeGuardArmed } from "../../lib/test-home-guard";
4
+ import type { DesktopCompatibilityRuntime } from "../../codex/desktop-compatibility/runtime";
5
+ import { bindNativeCompatibilityOwner } from "../../codex/desktop-compatibility/routing-binding";
6
+ import type { ReadinessGate } from "../readiness";
7
+
8
+ type RuntimeModule = { getDesktopCompatibilityRuntime(): DesktopCompatibilityRuntime; shutdownDesktopCompatibility(): Promise<void> };
9
+ interface StartupIo {
10
+ platform?: string;
11
+ testGuard?: boolean;
12
+ sibling?: boolean;
13
+ load?: () => Promise<RuntimeModule>;
14
+ warn?: (message: string) => void;
15
+ boundPort?: number;
16
+ boundHostname?: string;
17
+ loopbackPort?: number;
18
+ readiness?: Pick<ReadinessGate, "getStatus">;
19
+ readinessTimeoutMs?: number;
20
+ }
21
+ /** Core-safe gate: off installs do not load the optional runtime, read credentials or start timers. */
22
+ export function scheduleDesktopCompatibilityStartup(config: OcxConfig, io: StartupIo = {}): { shutdown(): Promise<void> } {
23
+ let stopped = false, module: RuntimeModule | undefined;
24
+ let cancelReadiness: (() => void) | undefined;
25
+ const waitForReadiness = () => !io.readiness ? Promise.resolve(true) : new Promise<boolean>(resolve => {
26
+ const deadline = Date.now() + (io.readinessTimeoutMs ?? 120000);
27
+ let timer: ReturnType<typeof setTimeout> | undefined;
28
+ const finish = (ready: boolean) => { if (timer) clearTimeout(timer); cancelReadiness = undefined; resolve(ready); };
29
+ cancelReadiness = () => finish(false);
30
+ const check = () => {
31
+ const status = io.readiness!.getStatus();
32
+ if (stopped || status !== "pending" || Date.now() >= deadline) { finish(!stopped && status === "ready"); return; }
33
+ timer = setTimeout(check, 100); timer.unref();
34
+ };
35
+ check();
36
+ });
37
+ const unbind = io.boundPort === undefined || io.boundHostname === undefined ? () => {} : bindNativeCompatibilityOwner({ config, hostname: io.boundHostname, port: io.boundPort, loopbackPort: io.loopbackPort });
38
+ const enabled = config.desktopCompatibility?.startOnProxyStart === true && (io.platform ?? process.platform) === "win32"
39
+ && !(io.testGuard ?? isTestHomeGuardArmed()) && !(io.sibling ?? siblingOfLivePort() !== null)
40
+ && config.runtimeRole !== "client";
41
+ const pending = enabled ? Promise.resolve().then(async () => {
42
+ if (stopped) return;
43
+ if (io.readiness) {
44
+ const ready = await waitForReadiness();
45
+ if (stopped) return;
46
+ if (!ready) throw new Error("desktop_compatibility_startup_not_ready");
47
+ }
48
+ module = await (io.load?.() ?? import("../../codex/desktop-compatibility/service"));
49
+ if (stopped) return;
50
+ await module.getDesktopCompatibilityRuntime().start();
51
+ }).catch(() => { (io.warn ?? console.warn)("Desktop compatibility observation did not start. Inspect Desktop compatibility status; no automatic trust or correction was applied."); }) : Promise.resolve();
52
+ return { async shutdown() {
53
+ stopped = true; cancelReadiness?.(); unbind(); await pending;
54
+ if (module) await module.shutdownDesktopCompatibility();
55
+ } };
56
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The Home end of the Remote Link management relay (#6957).
3
+ *
4
+ * Reached only on the hub-link listener, after the request has passed its link-key admission
5
+ * (`resolveApiAuth` restricted to recorded link keys) and the shared route table in
6
+ * `src/link/management-routes.ts`. The management handler sees a fresh request built here: no
7
+ * caller header, cookie, Origin or credential crosses from the link, and the principal is
8
+ * absent, so no consent- or capability-gated branch can be reached. Only allowlisted reads run.
9
+ * They run as passive reads (`src/lib/passive-read.ts`): no discovery, config write or diagnostic (#6959).
10
+ */
11
+ import type { OcxConfig } from "../../types";
12
+ import { handleManagementAPI, type ManagementApiDeps } from "../management-api";
13
+ import type { ManagementSessionControl } from "../management-auth";
14
+ import { runPassiveRead } from "../../lib/passive-read";
15
+ import { LINK_CODEX_SETTINGS_PATH, linkManagementRouteAllowed, linkManagementTarget } from "../../link/management-routes";
16
+ import { projectHomeCodexSettings } from "../../link/codex-settings";
17
+ import { packageVersion } from "../../lib/package-version";
18
+
19
+ const VERSION = packageVersion("0.0.0");
20
+ const KEPT_RESPONSE_HEADERS = ["content-type", "content-language", "etag", "last-modified"];
21
+
22
+ function notFound(): Response {
23
+ return Response.json({ error: "not_found" }, { status: 404, headers: { "cache-control": "no-store" } });
24
+ }
25
+
26
+ export async function handleLinkManagementRequest(
27
+ req: Request,
28
+ url: URL,
29
+ config: OcxConfig,
30
+ deps: ManagementApiDeps,
31
+ sessionControl?: ManagementSessionControl,
32
+ ): Promise<Response> {
33
+ if (!linkManagementRouteAllowed(url, req)) return notFound();
34
+ if (url.pathname === LINK_CODEX_SETTINGS_PATH) {
35
+ return Response.json(projectHomeCodexSettings(config, VERSION), { headers: { "cache-control": "no-store" } });
36
+ }
37
+ const target = linkManagementTarget(url);
38
+ if (!target) return notFound();
39
+ const local = new URL(`${target.pathname}${target.search}`, "http://127.0.0.1");
40
+ // The handlers branch on GET; an admitted HEAD runs as GET and its body is dropped below.
41
+ const inner = new Request(local.href, {
42
+ method: "GET",
43
+ headers: { accept: "application/json", host: "127.0.0.1" },
44
+ });
45
+ const response = await runPassiveRead(() =>
46
+ handleManagementAPI(inner, local, config, deps, undefined, sessionControl, { trustedLoopback: false }));
47
+ if (!response) return notFound();
48
+ const headers = new Headers({ "cache-control": "no-store" });
49
+ for (const name of KEPT_RESPONSE_HEADERS) {
50
+ const value = response.headers.get(name);
51
+ if (value) headers.set(name, value);
52
+ }
53
+ if (req.method === "HEAD") {
54
+ try { await response.body?.cancel(); } catch { /* best effort */ }
55
+ return new Response(null, { status: response.status, headers });
56
+ }
57
+ return new Response(response.body, { status: response.status, headers });
58
+ }
@@ -14,6 +14,7 @@ import {
14
14
  type LinkListenerStatus,
15
15
  } from "./link-listener";
16
16
  import { createLinkSupervisor, type LinkSupervisor } from "../../link/supervisor";
17
+ import { linkManagementRouteAllowed } from "../../link/management-routes";
17
18
  import { saveConfigPreservingClaudeCode } from "../../config/live-reconcile";
18
19
  import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
19
20
  export { LINK_INGRESS_HOSTNAME } from "./link-listener";
@@ -77,7 +78,10 @@ export function createOptionalListenerSet<T>(linkDeps: LinkListenerDeps = {}): O
77
78
  if (claudeIntercept.ownsListener(server)) return "claude-intercept";
78
79
  return undefined;
79
80
  },
80
- linkRouteAllowed,
81
+ // The Home's link listener admits the data plane plus the read-only management relay
82
+ // (#6957). The Child's own ingress keeps the data-only table, so a local caller on the Child
83
+ // cannot reach the Home's management reads without the Child's dashboard session.
84
+ linkRouteAllowed: (url, req) => linkRouteAllowed(url, req) || linkManagementRouteAllowed(url, req),
81
85
  linkAdmissionKeyIds: () => linkListener.linkAdmissionKeyIds(),
82
86
  onAuthenticatedCatalog: listener => linkListener.onAuthenticatedCatalog(listener),
83
87
  notifyAuthenticatedCatalog: apiKeyId => linkListener.notifyAuthenticatedCatalog(apiKeyId),
@@ -1,5 +1,6 @@
1
1
  import type { Server, ServerWebSocket } from "bun";
2
2
  import type { StartServerDeps } from "./startup-warnings";
3
+ import { isLinkManagementPath } from "../../link/management-routes";
3
4
  import {
4
5
  GUI_PAIRING_EXCHANGE_BODY_LIMIT,
5
6
  REMOTE_WORKSPACE_PAIRING_BODY_LIMIT,
@@ -96,6 +97,7 @@ import {
96
97
  isAllowedManagementOrigin,
97
98
  isApiAuthRequired,
98
99
  isLoopbackHostname,
100
+ markTrustedHubManagementRequest,
99
101
  jsonResponse,
100
102
  admissionFields,
101
103
  resolveApiAuth,
@@ -313,6 +315,11 @@ export function createServeOptions(ctx: ServeOptionsContext) {
313
315
  maxRequestBodySize: inboundBodyLimitBytes,
314
316
  async fetch(req: Request, requestServer: Server<WsData>): Promise<Response> {
315
317
  const ingress = ingressForServer(requestServer);
318
+ // Bind management-origin trust to the actual Bun listener object resolved above.
319
+ // Neither Host nor any X-Forwarded-* header can manufacture this process-local mark.
320
+ if (ingress === "hub-management" && config.runtimeRole === "hub") {
321
+ markTrustedHubManagementRequest(req);
322
+ }
316
323
  const requestUrl = codexCompatibleUrl(req.url);
317
324
  const linkPolicyView = ingress === "hub-link" ? linkPolicy() : undefined;
318
325
  // The unauthenticated loopback listener (#1102) serves a fixed allowlist and nothing
@@ -367,6 +374,12 @@ export function createServeOptions(ctx: ServeOptionsContext) {
367
374
  if (requestUrl.pathname === "/v1/catalog" && requestUrl.search === ""
368
375
  && (req.method === "GET" || req.method === "HEAD")
369
376
  && admission.kind === "configured") onAuthenticatedCatalog(admission.keyId);
377
+ // #6957: the Child's read-only view of this Home. Admitted by the link key above and by
378
+ // the shared table in src/link/management-routes.ts; never reachable on another ingress.
379
+ if (isLinkManagementPath(requestUrl.pathname)) {
380
+ const { handleLinkManagementRequest } = await import("./link-management");
381
+ return withCors(await handleLinkManagementRequest(req, requestUrl, config, requestManagementApiDeps, managementSessionControl), req, policy);
382
+ }
370
383
  }
371
384
  const url = requestUrl;
372
385
  const admissionOptions = { linkIngress: policy.linkIngress?.allowedKeyIds };
@@ -1042,7 +1055,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1042
1055
  );
1043
1056
  const { buildAnthropicModelInfos } = await import("../../claude/model-info");
1044
1057
  const { resolveAutoContext } = await import("../../claude/context-windows");
1045
- const { activeDesktop3pAlias } = await import("../../claude/desktop-3p");
1058
+ const { resolvableDesktop3pAlias } = await import("../../claude/desktop-3p");
1046
1059
  // Per-surface id family (devlog 050): explicit ?ids= wins; otherwise the
1047
1060
  // Claude Code CLI discovery UA (`claude-code/<version>`, binary n_()) gets
1048
1061
  // readable claude-ocx ids and every other client (Desktop 3P) keeps the
@@ -1058,7 +1071,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1058
1071
  goOrdered,
1059
1072
  resolveAutoContext(config.claudeCode),
1060
1073
  idStyle,
1061
- activeDesktop3pAlias,
1074
+ resolvableDesktop3pAlias,
1062
1075
  desktopInputs.nativeContextCap,
1063
1076
  config.fastMode,
1064
1077
  // Explicit opt-out omits the Fast predicate.
@@ -277,3 +277,12 @@ export function warnPlaintextV2AgentMessagesStartup(config: { plaintextV2AgentMe
277
277
  console.warn(" Eligible ChatGPT collaboration calls may carry plaintext message arguments. HTTPS remains encrypted, but task text may be retained in Codex history, selected providers, and local response/debug state.");
278
278
  console.warn(" This depends on undocumented ChatGPT and Codex behavior; it does not decrypt existing tasks.");
279
279
  }
280
+
281
+ export function logProxyEndpoints(actualPort: number): void {
282
+ console.log(`🚀 opencodex proxy running on http://localhost:${actualPort}`);
283
+ console.log(` POST /v1/responses → provider translation`);
284
+ console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
285
+ console.log(` GET /healthz → health check`);
286
+ console.log(` GET /api/* → management API`);
287
+ console.log(` GET / → GUI dashboard`);
288
+ }
@@ -30,7 +30,9 @@ import {
30
30
  } from "../ws-bridge";
31
31
  import {
32
32
  CodexAccountCooldownError,
33
+ CodexMainAccountHardLockError,
33
34
  cooldownErrorMessage,
35
+ cooldownErrorResponse,
34
36
  } from "../../codex/auth-context";
35
37
  import { codexAccountNamespaceForModel } from "../../codex/account-namespace-match";
36
38
  import {
@@ -368,6 +370,13 @@ export function createWebsocketHandler(
368
370
  const accountSelector = typeof payload.model === "string"
369
371
  ? codexAccountNamespaceForModel(config.codexAccountNamespaces, payload.model)
370
372
  : undefined;
373
+ if (err instanceof CodexMainAccountHardLockError) {
374
+ // Same terminal usage-limit body and Retry-After as HTTP (#7046).
375
+ const refusal = cooldownErrorResponse(err, Date.now(), accountSelector);
376
+ const body = await refusal.json() as { error: Record<string, unknown> };
377
+ sendJsonFrame(ws, buildWsErrorFrame(429, body.error, refusal.headers));
378
+ return;
379
+ }
371
380
  sendJsonFrame(ws, buildWsErrorFrame(429, {
372
381
  type: "rate_limit_error",
373
382
  message: cooldownErrorMessage(err, accountSelector),
@@ -1,4 +1,5 @@
1
1
  import { remoteWorkspaceEnabled } from "../remote-control/workspace-activation";
2
+ import { scheduleDesktopCompatibilityStartup } from "./index/desktop-compatibility-startup";
2
3
  import { AuxiliaryListenerBindError } from "./ports";
3
4
  import { runAdmittedBodyWork } from "./inbound-body-admission";
4
5
  import {
@@ -24,6 +25,7 @@ import {
24
25
  websocketsEnabled,
25
26
  } from "../config";
26
27
  import { flushConfigDirHardeningAndReaps } from "../config/paths";
28
+ import { trackBackgroundWork } from "../lib/background-work";
27
29
  import { migrateStartupSubagentModels } from "./subagent-models-startup";
28
30
  import { migrateStartupXaiResponses } from "./xai-responses-startup";
29
31
  import { migrateStartupZaiResponses } from "./zai-responses-startup";
@@ -62,6 +64,7 @@ import {
62
64
  } from "../lib/app-owned-memory-stores";
63
65
  import { acquireServerBackgroundLifecycle } from "./background-lifecycle";
64
66
  import { startPackageRefresh, stopPackageRefresh } from "../update/refresh-scheduler";
67
+ import { activateAdvisor } from "../lib/advisor-activation";
65
68
  import { activateLab, labActivationRequired } from "../lib/lab-activation";
66
69
  import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
67
70
  import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
@@ -198,7 +201,7 @@ import { createReadinessGate, type ReadinessGate } from "./readiness";
198
201
  import { createServeOptions, type ServerIngress } from "./index/serve-options";
199
202
  import { createOptionalListenerSet, LINK_INGRESS_HOSTNAME } from "./index/optional-listeners";
200
203
  import { createPackageTreeIntegrityGuardForServer } from "./index/package-tree-guard";
201
- import { inspectStartupOwnership, resolveInboundBodyLimitWithWarning, setStartupCacheInvalidationWrite, warnAgentTaskRecoveryStartup, warnPlaintextV2AgentMessagesStartup, type StartServerDeps } from "./index/startup-warnings";
204
+ import { inspectStartupOwnership, logProxyEndpoints, resolveInboundBodyLimitWithWarning, setStartupCacheInvalidationWrite, warnAgentTaskRecoveryStartup, warnPlaintextV2AgentMessagesStartup, type StartServerDeps } from "./index/startup-warnings";
202
205
  import { acquireSpendLedgerServerLifecycle, recordFailedStartRollback, type SpendLedgerServerLifecycle } from "./index/spend-ledger-lifecycle";
203
206
  export { waitForFailedStartRollback } from "./index/spend-ledger-lifecycle";
204
207
 
@@ -638,11 +641,14 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
638
641
  let unregisterQuotaAutoRefresh: (() => void) | null = null;
639
642
  let remoteWorkspaceStopping = false;
640
643
  let remoteWorkspaceShutdown: (() => Promise<void>) | undefined;
644
+ let desktopCompatibilityStartup: ReturnType<typeof scheduleDesktopCompatibilityStartup> | undefined;
645
+ let desktopCompatibilityShutdown: (() => Promise<void>) | undefined;
641
646
  const managementApiDeps: ManagementApiDeps = {
642
647
  ...deps.managementApi,
643
648
  listLowQuotaEvents: limit => backgroundLifecycle?.listLowQuotaEvents(limit) ?? [],
644
649
  remoteWorkspaceStopping: () => remoteWorkspaceStopping,
645
650
  onRemoteWorkspaceShutdown: shutdown => { remoteWorkspaceShutdown = shutdown; }, linkSupervisor: () => optionalListeners.linkSupervisor(), linkListener: () => optionalListeners,
651
+ onDesktopCompatibilityShutdown: shutdown => { desktopCompatibilityShutdown = shutdown; },
646
652
  };
647
653
  let workspaceRuntimeFlight: Promise<typeof import("../remote-control/workspace-runtime")> | undefined;
648
654
  const loadRemoteWorkspaceRuntime = () => {
@@ -772,6 +778,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
772
778
  : []),
773
779
  () => optionalListeners.stop(),
774
780
  async () => { await remoteWorkspaceShutdown?.(); },
781
+ async () => { await desktopCompatibilityStartup?.shutdown(); await desktopCompatibilityShutdown?.(); },
775
782
  async () => {
776
783
  try {
777
784
  userCostOverlayReconciler?.stop();
@@ -801,13 +808,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
801
808
  boundPort = actualPort;
802
809
  setCorsOrigin(actualPort);
803
810
 
804
- console.log(`🚀 opencodex proxy running on http://localhost:${actualPort}`);
805
- console.log(` POST /v1/responses → provider translation`);
806
- console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
807
- console.log(` GET /healthz → health check`);
808
- console.log(` GET /api/* → management API`);
809
- console.log(` GET / → GUI dashboard`);
810
-
811
+ logProxyEndpoints(actualPort);
811
812
  if (loopbackServer) {
812
813
  // Loud on every start, not once at enable time. An operator who inherits a config, or
813
814
  // who forgot, has to be able to see that an unauthenticated surface is live without
@@ -843,7 +844,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
843
844
  && isCanonicalOpenAiForwardProvider(openAiProvider)
844
845
  && providerCodexAccountMode("openai", openAiProvider) === "pool"
845
846
  ) {
846
- import("../codex/plan-from-token")
847
+ void trackBackgroundWork("codex-pool-quota-prime-startup", import("../codex/plan-from-token")
847
848
  .then(({ reconcileCodexPlansFromTokens }) => {
848
849
  try {
849
850
  reconcileCodexPlansFromTokens(config);
@@ -853,7 +854,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
853
854
  return import("../codex/auth-api");
854
855
  })
855
856
  .then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "startup"))
856
- .catch(() => {});
857
+ .catch(() => {}));
857
858
  }
858
859
 
859
860
  // Opt-in storage policy (default OFF). Never blocks listen; cancellable on shutdown.
@@ -864,6 +865,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
864
865
  // startServer returns, in the same turn as Bun.serve, so a policy route can never be
865
866
  // evaluated before its evidence provider is registered. That ordering is load-bearing:
866
867
  // the subagent-fallback chain routes synchronously and has nowhere to await.
868
+ activateAdvisor(config);
867
869
  const labConfigDir = getConfigDir();
868
870
  if (labActivationRequired(config, labConfigDir)) {
869
871
  activateLab(config, labConfigDir);
@@ -879,6 +881,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
879
881
  }
880
882
 
881
883
  startPackageRefresh();
884
+ desktopCompatibilityStartup = scheduleDesktopCompatibilityStartup(config, { boundHostname: bindHost, boundPort: actualPort, loopbackPort: loopbackServer?.port ?? undefined, readiness: deps.readinessGate });
882
885
  return server;
883
886
  }
884
887
 
@@ -111,17 +111,18 @@ export interface ClientEncodedDelivery {
111
111
  }
112
112
 
113
113
  /**
114
- * Record the attempt's observed path: the request still goes through the internal Responses
115
- * bridge until the codecs decode to IR directly, so the request path and mode stay the bridge's;
116
- * the response now reaches the client from the IR without the internal Responses hop.
114
+ * Record the attempt's observed path. The response reaches the client from the IR without the
115
+ * internal Responses hop. The request side is the bridge's unless the ingress also decoded the
116
+ * conversation straight into the IR (the Antigravity direct lane, `requestLane: "direct"`).
117
117
  */
118
118
  function markDirectEncoderPath(logCtx: RequestLogContext, encoder: ClientEncoderOption, adapter: string): void {
119
119
  const attempt = logCtx.activeAttempt;
120
120
  if (!attempt) return;
121
121
  const upstream = upstreamWireForAdapter(attempt.adapter || adapter);
122
+ const lane = encoder.requestLane ?? "bridge";
122
123
  markAttemptProtocolPath(attempt, {
123
- mode: deliveryModeForLane(encoder.protocol, "bridge", upstream),
124
- requestPath: requestPathForLane(encoder.protocol, "bridge", upstream),
124
+ mode: deliveryModeForLane(encoder.protocol, lane, upstream),
125
+ requestPath: requestPathForLane(encoder.protocol, lane, upstream),
125
126
  responsePath: [upstream, "ir", encoder.protocol],
126
127
  });
127
128
  }
@@ -8,7 +8,7 @@ import {
8
8
  } from "../storage/policy-job";
9
9
  import { abortRestoreTrashJobAsync } from "../storage/restore-job";
10
10
  import { stopStorageCleanupScheduler } from "../storage/policy-scheduler";
11
- import { runOptionalShutdownHooks } from "../lib/optional-shutdown-hooks";
11
+ import { runDrainStartHooks, runOptionalShutdownHooks } from "../lib/optional-shutdown-hooks";
12
12
  import { stopStateStoreSweeper } from "../lib/state-store-sweeper";
13
13
  import {
14
14
  cancelQueuedStorageWorkerSpawns,
@@ -499,6 +499,7 @@ export async function drainAndShutdown(
499
499
  // ordinary in-flight turns. A stuck scoped owner must not pin shutdown forever.
500
500
  const deadline = Date.now() + Math.max(0, timeoutMs);
501
501
  beginShutdownDrain();
502
+ runDrainStartHooks();
502
503
  const temporaryDrainsSettled = await waitForTemporaryDrainsUntil(deadline);
503
504
  if (!temporaryDrainsSettled) {
504
505
  console.warn("Temporary drain lease did not settle before the shutdown deadline; forcing shutdown");