@bitkyc08/opencodex 2.84.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -15,7 +15,7 @@
15
15
  import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
16
16
  import { modelInList } from "../types";
17
17
  import { codexEffortRank, configuredReasoningEfforts, isCodexReasoningEffort, isDeclaredReasoningEffort, modelRecordValue } from "../reasoning-effort";
18
- import { catalogModelEfforts } from "../codex/catalog";
18
+ import { catalogModelEfforts } from "../codex/catalog/effort";
19
19
 
20
20
  /**
21
21
  * True when the request carries codex-rs's spawned-child markers, matched EXACTLY.
@@ -27,9 +27,11 @@ import { formatCodexProviderForLog } from "../codex/routing";
27
27
  import { signalWithTimeout } from "../lib/abort";
28
28
  import { readBoundedResponseBytes, type BoundedBytesResult } from "../lib/bounded-body";
29
29
  import { sidecarEnter } from "../lib/sidecar-tracker";
30
+ import { isNonReplayableResponse } from "../lib/upstream-retry";
30
31
  import type { OcxConfig } from "../types";
31
32
  import { resolveFirstUsableOpenAiSidecar, selectImagesProvider } from "../providers/openai-sidecar";
32
- import { selectProactiveApiKeyTransport } from "../providers/key-failover";
33
+ import { recordDistributedKeyFailure, selectProactiveApiKeyTransport } from "../providers/key-failover";
34
+ import { requestPacingOverloadResponse } from "./responses/pacing-overload";
33
35
  import { getProviderRegistryEntry } from "../providers/registry";
34
36
  import { readJsonRequestBody, resolveInboundBodyLimitBytes } from "./request-decompress";
35
37
  import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "./auth-cors";
@@ -756,6 +758,7 @@ export async function handleImages(
756
758
  catch { console.error("[images] Failed to release probe lease"); }
757
759
  };
758
760
  let url: string;
761
+ let recordKeyFailure = (_status: 401 | 429, _retryAfter: string | null) => {};
759
762
  // Each branch is re-checked as it is entered: the forward check is defence in
760
763
  // depth for the pre-filter above, and the keyed check runs before a key
761
764
  // rotation is committed.
@@ -805,8 +808,15 @@ export async function handleImages(
805
808
  //
806
809
  // Transport variant: this branch reads `provider.baseUrl` and `provider.headers` to build
807
810
  // the URL and the request, and it comes back with the credential already resolved.
808
- const warmKeyProvider = selectProactiveApiKeyTransport(config, providerName, candidates.keyed.provider);
811
+ let warmKeyProvider;
812
+ try { warmKeyProvider = selectProactiveApiKeyTransport(config, providerName, candidates.keyed.provider); }
813
+ catch (error) {
814
+ const refusal = requestPacingOverloadResponse(error);
815
+ if (refusal) return refusal;
816
+ throw error;
817
+ }
809
818
  const provider = warmKeyProvider ?? candidates.keyed.provider;
819
+ recordKeyFailure = (status, retryAfter) => { recordDistributedKeyFailure(config, providerName, provider._apiKeyAttempt, status, Date.now(), retryAfter); };
810
820
  // No fall back to the earlier snapshot once a pick has happened. The picker COMMITS its
811
821
  // choice before returning, so if the chosen reference will not resolve -- revoked keychain
812
822
  // entry, unset env var -- sending the previous key would authenticate a non-idempotent
@@ -865,6 +875,9 @@ export async function handleImages(
865
875
  // compact already set this; the credential-bearing sidecars did not.
866
876
  redirect: "manual",
867
877
  });
878
+ if ((upstreamResponse.status === 401 || upstreamResponse.status === 429) && !isNonReplayableResponse(upstreamResponse)) {
879
+ recordKeyFailure(upstreamResponse.status, upstreamResponse.headers.get("retry-after"));
880
+ }
868
881
  const observed = await readImageResponseBytes(upstreamResponse, {
869
882
  maxBytes: IMAGES_RESPONSE_MAX_BYTES,
870
883
  signal: linkedSignal.signal,
@@ -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,36 @@
1
+ import { fastRowBases } from "../fast-row";
2
+ import { previewRouteModel } from "../../router";
3
+ import type { OcxConfig } from "../../types";
4
+
5
+ /**
6
+ * Build the alias promoter for one model listing. The Fast-base inventory walks
7
+ * the configured catalog, so it is computed at most once per listing instead of
8
+ * once per aliased row.
9
+ */
10
+ export function modelAliasIdPromoter(config: OcxConfig) {
11
+ let isFastBase: ((id: string) => boolean) | undefined;
12
+ const fastBases = () => (isFastBase ??= fastRowBases(config));
13
+ return (provider: string, model: string, alias: string, fastEligible: boolean) =>
14
+ preferredModelAliasId(config, provider, model, alias, fastEligible, fastBases);
15
+ }
16
+
17
+ /** Promote only a short alias whose ordinary route is this exact upstream model. */
18
+ export function preferredModelAliasId(
19
+ config: OcxConfig, provider: string, model: string, alias: string, fastEligible: boolean,
20
+ fastBases: () => (id: string) => boolean = () => fastRowBases(config),
21
+ ): string | undefined {
22
+ if (config.preferModelAliasIds !== true) return undefined;
23
+ // Built-in bare aliases are not stable Fast bases. Preserve the qualified ID
24
+ // when promotion would publish a synthetic sibling that ingress cannot parse.
25
+ if (config.fastRows !== false && fastEligible && !fastBases()(alias)) return undefined;
26
+ try {
27
+ // Preview cannot consume a combo's round-robin selection state. A policy/combo,
28
+ // reserved native name or another provider's native id must keep its own meaning.
29
+ const route = previewRouteModel(config, alias);
30
+ return route.routeReason === "model-alias" && route.providerName === provider && route.modelId === model
31
+ ? alias : undefined;
32
+ } catch {
33
+ // An ambiguous alias stays available through the existing qualified public id.
34
+ return undefined;
35
+ }
36
+ }
@@ -21,6 +21,7 @@ export { LINK_INGRESS_HOSTNAME } from "./link-listener";
21
21
  import type { ServerIngress } from "./serve-options";
22
22
 
23
23
  export interface OptionalListenerStartContext<T> {
24
+ startProxy?: typeof import("../../claude/intercept/connect-proxy").startConnectProxy;
24
25
  config: OcxConfig;
25
26
  publicPort: number;
26
27
  requestedPort?: number;
@@ -106,6 +107,7 @@ export function createOptionalListenerSet<T>(linkDeps: LinkListenerDeps = {}): O
106
107
  }
107
108
  supervisorStop = () => supervisor.stop();
108
109
  claudeIntercept.start({
110
+ startProxy: ctx.startProxy,
109
111
  config: ctx.config,
110
112
  publicPort: ctx.publicPort,
111
113
  requestedPort: ctx.requestedPort,
@@ -1,4 +1,5 @@
1
1
  import type { Server, ServerWebSocket } from "bun";
2
+ import { modelAliasIdPromoter } from "./model-alias-id";
2
3
  import type { StartServerDeps } from "./startup-warnings";
3
4
  import { isLinkManagementPath } from "../../link/management-routes";
4
5
  import {
@@ -1055,7 +1056,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1055
1056
  );
1056
1057
  const { buildAnthropicModelInfos } = await import("../../claude/model-info");
1057
1058
  const { resolveAutoContext } = await import("../../claude/context-windows");
1058
- const { activeDesktop3pAlias } = await import("../../claude/desktop-3p");
1059
+ const { resolvableDesktop3pAlias } = await import("../../claude/desktop-3p");
1059
1060
  // Per-surface id family (devlog 050): explicit ?ids= wins; otherwise the
1060
1061
  // Claude Code CLI discovery UA (`claude-code/<version>`, binary n_()) gets
1061
1062
  // readable claude-ocx ids and every other client (Desktop 3P) keeps the
@@ -1071,7 +1072,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1071
1072
  goOrdered,
1072
1073
  resolveAutoContext(config.claudeCode),
1073
1074
  idStyle,
1074
- activeDesktop3pAlias,
1075
+ resolvableDesktop3pAlias,
1075
1076
  desktopInputs.nativeContextCap,
1076
1077
  config.fastMode,
1077
1078
  // Explicit opt-out omits the Fast predicate.
@@ -1252,6 +1253,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1252
1253
  effortRowKnownIds,
1253
1254
  ));
1254
1255
  };
1256
+ const promoteAliasId = modelAliasIdPromoter(config);
1255
1257
  const routedRows = await Promise.all(uniqueCatalogModelsForRawPublicList(goOrdered)
1256
1258
  .filter(m => listAllows(m.provider, m.id))
1257
1259
  .map(async m => {
@@ -1269,8 +1271,11 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1269
1271
  knownModelIdsForProvider(m.provider, provider, config),
1270
1272
  ).get(m.id)
1271
1273
  : undefined;
1274
+ const effectiveId = !isCombo && !fastModelId && effective
1275
+ ? promoteAliasId(m.provider, m.id, effective.alias, catalogRowFastEligible(m)) ?? publicId
1276
+ : publicId;
1272
1277
  const row = {
1273
- id: publicId,
1278
+ id: effectiveId,
1274
1279
  object: "model",
1275
1280
  created: 0,
1276
1281
  // This endpoint is an OpenAI-compatible inbound contract. Some clients use
@@ -1279,6 +1284,9 @@ export function createServeOptions(ctx: ServeOptionsContext) {
1279
1284
  owned_by: isCombo ? "openai" : (m.owned_by ?? m.provider),
1280
1285
  ...(isCombo ? { is_combo: true } : {}),
1281
1286
  ...(effective ? { alias_of: `${provider?.alias || m.provider}/${effective.alias}` } : {}),
1287
+ // When preferModelAliasIds promotes the alias to `id`, retain the original
1288
+ // provider/nativeId so clients that need the upstream identity can read it.
1289
+ ...(effectiveId !== publicId ? { native_id: publicId } : {}),
1282
1290
  ...grokEffortFields(m.reasoningEfforts ?? [], m.defaultReasoningEffort),
1283
1291
  ...modelCapabilityFields({
1284
1292
  reasoningEfforts: m.reasoningEfforts,
@@ -149,6 +149,8 @@ export function withRequestLogId(response: Response, requestId: string): Respons
149
149
  }
150
150
 
151
151
  export interface StartServerDeps {
152
+ /** Test-only CONNECT binder; permits kernel-assigned ports without probe-and-release races. */
153
+ startClaudeInterceptProxy?: typeof import("../../claude/intercept/connect-proxy").startConnectProxy;
152
154
  /** Test-only seam; production always initializes its own management credential state. */
153
155
  managementAuthState?: ManagementAuthState;
154
156
  /** Test-only route dependencies, forwarded only after management admission succeeds. */
@@ -277,3 +279,12 @@ export function warnPlaintextV2AgentMessagesStartup(config: { plaintextV2AgentMe
277
279
  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
280
  console.warn(" This depends on undocumented ChatGPT and Codex behavior; it does not decrypt existing tasks.");
279
281
  }
282
+
283
+ export function logProxyEndpoints(actualPort: number): void {
284
+ console.log(`🚀 opencodex proxy running on http://localhost:${actualPort}`);
285
+ console.log(` POST /v1/responses → provider translation`);
286
+ console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
287
+ console.log(` GET /healthz → health check`);
288
+ console.log(` GET /api/* → management API`);
289
+ console.log(` GET / → GUI dashboard`);
290
+ }
@@ -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 = () => {
@@ -732,7 +738,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
732
738
  throw new AuxiliaryListenerBindError("hub.managementIngress", managementIngressPort, "127.0.0.1", error);
733
739
  }
734
740
  }
735
- optionalListeners.start({ config, publicPort: server.port ?? listenPort, requestedPort: listenPort,
741
+ optionalListeners.start({ config, publicPort: server.port ?? listenPort, requestedPort: listenPort, startProxy: deps.startClaudeInterceptProxy,
736
742
  maxRequestBodySize: inboundBodyLimitBytes, dispatch: (req, requestServer) => serveOptions.fetch(req, requestServer) });
737
743
  } catch (error) {
738
744
  unregisterQuotaAutoRefresh?.();
@@ -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
 
@@ -0,0 +1,31 @@
1
+ import type { RequestLogContext } from "../request-log";
2
+ import { ADVISOR_INTERNAL_CAPABILITY_HEADER, isInternalCallCapability } from "../../lib/local-internal-call-capability";
3
+
4
+ export const ADVISOR_REASONING_HEADER = "x-opencodex-advisor-reasoning";
5
+ const EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
6
+
7
+ /** Native Chat sends the finalized body directly instead of the translating adapter builder. */
8
+ export function observeAdvisorNativeReasoning(headers: Headers, log: RequestLogContext, body: string): void {
9
+ if (!isInternalCallCapability(headers.get(ADVISOR_INTERNAL_CAPABILITY_HEADER))) return;
10
+ delete log.effectiveEffort;
11
+ try {
12
+ const effort = JSON.parse(body)?.reasoning_effort;
13
+ if (typeof effort === "string" && EFFORTS.has(effort)) log.effectiveEffort = effort;
14
+ } catch { /* absence of evidence stays unreported */ }
15
+ }
16
+
17
+ /** Only the authenticated internal caller receives adapter-owned serialization evidence. */
18
+ export function attachAdvisorReasoning(response: Response, log: RequestLogContext): Response {
19
+ const headers = new Headers(response.headers);
20
+ headers.set(ADVISOR_REASONING_HEADER, JSON.stringify({
21
+ effectiveEffort: log.effectiveEffort && EFFORTS.has(log.effectiveEffort) ? log.effectiveEffort : null,
22
+ }));
23
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
24
+ }
25
+
26
+ export function readAdvisorReasoning(headers: Headers): string | null {
27
+ try {
28
+ const value = JSON.parse(headers.get(ADVISOR_REASONING_HEADER) ?? "null")?.effectiveEffort;
29
+ return typeof value === "string" && EFFORTS.has(value) ? value : null;
30
+ } catch { return null; }
31
+ }
@@ -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
  }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The user's login-shell PATH, for installers the dashboard runs under OpenCodex Desktop (#6971).
3
+ *
4
+ * A Finder or login launch gives the Desktop app, and so its proxy sidecar, only the system PATH
5
+ * (`/usr/bin:/bin:/usr/sbin:/sbin`). `ocx codex-shim install` scans PATH for the native Codex
6
+ * launcher, so from the dashboard it reported "Native Codex launcher not found" for Homebrew, npm,
7
+ * bun, nvm, fnm, volta and mise installs, and an npm launcher had no `node` for its probe.
8
+ *
9
+ * This asks the user's shell once, as an interactive login shell would start, and keeps only PATH:
10
+ * the value is framed by markers so startup-file output cannot leak into it. The read has a
11
+ * deadline, runs in its own process group so a hung startup file is killed with all of its
12
+ * children, and its result, including failure, is cached so repeated dashboard actions do not
13
+ * spawn shells. It is scoped to the shim installer's child environment; the service definition's
14
+ * PATH has its own policy.
15
+ */
16
+ import { spawn as nodeSpawn, type ChildProcess } from "node:child_process";
17
+ import { userInfo } from "node:os";
18
+ import { basename, isAbsolute } from "node:path";
19
+
20
+ const BEGIN = "__OCX_LOGIN_PATH_BEGIN__";
21
+ const END = "__OCX_LOGIN_PATH_END__";
22
+ export const LOGIN_SHELL_PATH_TIMEOUT_MS = 5_000;
23
+ export const LOGIN_SHELL_PATH_CACHE_MS = 5 * 60_000;
24
+ const MAX_OUTPUT_BYTES = 64 * 1024;
25
+ /** After the shell exits, how long its stdout may stay open before a background job is assumed to hold it. */
26
+ const STDOUT_DRAIN_MS = 500;
27
+
28
+ export function loginShellPathArgs(shell: string): string[] {
29
+ const print = basename(shell) === "fish"
30
+ ? `printf '%s%s%s\\n' ${BEGIN} (string join : $PATH) ${END}`
31
+ : `printf '%s%s%s\\n' ${BEGIN} "$PATH" ${END}`;
32
+ return ["-l", "-i", "-c", print];
33
+ }
34
+
35
+ /** The framed PATH, only when every entry is absolute. */
36
+ export function parseLoginShellPath(output: string): string | null {
37
+ const start = output.lastIndexOf(BEGIN);
38
+ if (start < 0) return null;
39
+ const end = output.indexOf(END, start + BEGIN.length);
40
+ if (end < 0) return null;
41
+ const value = output.slice(start + BEGIN.length, end);
42
+ if (!value || /[\r\n\0]/.test(value)) return null;
43
+ const entries = value.split(":").filter(Boolean);
44
+ return entries.length > 0 && entries.every(entry => isAbsolute(entry)) ? entries.join(":") : null;
45
+ }
46
+
47
+ /** Ordered union: the shell's entries first, then any the process had that the shell did not. */
48
+ export function mergePathLists(preferred: string, fallback: string | undefined): string {
49
+ // POSIX only: the login shell runs on macOS and Linux, where PATH is colon-separated.
50
+ return [...new Set([...preferred.split(":"), ...(fallback ?? "").split(":")].filter(Boolean))].join(":");
51
+ }
52
+
53
+ export interface LoginShellPathDeps {
54
+ platform?: NodeJS.Platform;
55
+ shell?: string | null;
56
+ env?: NodeJS.ProcessEnv;
57
+ spawn?: (command: string, args: string[], options: Parameters<typeof nodeSpawn>[2]) => ChildProcess;
58
+ killGroup?: (pid: number) => void;
59
+ timeoutMs?: number;
60
+ now?: () => number;
61
+ }
62
+
63
+ let cached: { at: number; value: string | null } | null = null;
64
+
65
+ export function resetLoginShellPathCacheForTests(): void {
66
+ cached = null;
67
+ }
68
+
69
+ function defaultShell(env: NodeJS.ProcessEnv): string | null {
70
+ const fromEnv = env.SHELL?.trim();
71
+ if (fromEnv && isAbsolute(fromEnv)) return fromEnv;
72
+ try {
73
+ const shell = userInfo().shell;
74
+ return shell && isAbsolute(shell) ? shell : null;
75
+ } catch { return null; }
76
+ }
77
+
78
+ function killProcessGroup(pid: number): void {
79
+ try { process.kill(-pid, "SIGKILL"); } catch { /* already gone */ }
80
+ }
81
+
82
+ export async function readLoginShellPath(deps: LoginShellPathDeps = {}): Promise<string | null> {
83
+ const platform = deps.platform ?? process.platform;
84
+ if (platform === "win32") return null;
85
+ const now = deps.now ?? Date.now;
86
+ if (cached && now() - cached.at < LOGIN_SHELL_PATH_CACHE_MS) return cached.value;
87
+ const env = deps.env ?? process.env;
88
+ const shell = deps.shell === undefined ? defaultShell(env) : deps.shell;
89
+ const value = shell ? await probe(shell, env, deps) : null;
90
+ cached = { at: now(), value };
91
+ return value;
92
+ }
93
+
94
+ function probe(shell: string, env: NodeJS.ProcessEnv, deps: LoginShellPathDeps): Promise<string | null> {
95
+ const spawn = deps.spawn ?? nodeSpawn;
96
+ const killGroup = deps.killGroup ?? killProcessGroup;
97
+ return new Promise(resolve => {
98
+ let child: ChildProcess;
99
+ try {
100
+ child = spawn(shell, loginShellPathArgs(shell), {
101
+ detached: true,
102
+ stdio: ["ignore", "pipe", "ignore"],
103
+ // Only what a login shell needs to find its startup files; no proxy or credential state.
104
+ env: Object.fromEntries(Object.entries({
105
+ HOME: env.HOME, USER: env.USER, LOGNAME: env.LOGNAME, SHELL: shell, LANG: env.LANG,
106
+ PATH: env.PATH, TERM: "dumb",
107
+ }).filter(([, value]) => value !== undefined)) as NodeJS.ProcessEnv,
108
+ });
109
+ } catch { resolve(null); return; }
110
+ let output = "";
111
+ let settled = false;
112
+ let exitCode: number | null = null;
113
+ let drain: ReturnType<typeof setTimeout> | undefined;
114
+ const finish = (value: string | null, killGroupMembers: boolean) => {
115
+ if (settled) return;
116
+ settled = true;
117
+ clearTimeout(timer);
118
+ clearTimeout(drain);
119
+ // A hung startup file, or a background job still holding the pipe, is killed with its group.
120
+ // A shell that exited and closed its output is left alone: its group id may already be free.
121
+ if (killGroupMembers && child.pid) killGroup(child.pid);
122
+ resolve(value);
123
+ };
124
+ const timer = setTimeout(() => finish(null, true), deps.timeoutMs ?? LOGIN_SHELL_PATH_TIMEOUT_MS);
125
+ child.stdout?.setEncoding("utf8");
126
+ child.stdout?.on("data", (chunk: string) => {
127
+ output += chunk;
128
+ if (output.length > MAX_OUTPUT_BYTES) finish(null, true);
129
+ });
130
+ const parsed = () => exitCode === 0 ? parseLoginShellPath(output) : null;
131
+ child.once("error", () => finish(null, true));
132
+ // Parse only after stdout closes: `exit` can arrive before the last chunk.
133
+ child.once("close", code => { exitCode ??= code; finish(parsed(), false); });
134
+ child.once("exit", code => {
135
+ exitCode = code;
136
+ drain = setTimeout(() => finish(parsed(), true), STDOUT_DRAIN_MS);
137
+ });
138
+ });
139
+ }
140
+
141
+ /** `env` with the login-shell PATH in front, or unchanged when it cannot be read. */
142
+ export async function withLoginShellPath(env: NodeJS.ProcessEnv, deps: LoginShellPathDeps = {}): Promise<NodeJS.ProcessEnv> {
143
+ const loginPath = await readLoginShellPath({ ...deps, env });
144
+ return loginPath ? { ...env, PATH: mergePathLists(loginPath, env.PATH) } : env;
145
+ }
146
+