@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
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Cold start for `ocx ensure` when OpenCodex Desktop owns or runs the proxy (#6971).
3
+ *
4
+ * The Codex launcher shim runs `ocx ensure` before every Codex launch. Without this policy,
5
+ * ensure spawned a detached CLI proxy whenever nothing answered on the port. That is wrong in two
6
+ * Desktop situations: during the Desktop supervisor's restart backoff, and after Desktop quit
7
+ * while its durable ownership claim remains. In both, the CLI proxy took the port and Desktop came
8
+ * back as a guest without supervision. That is why the dashboard used to disable the shim under
9
+ * Desktop.
10
+ *
11
+ * Rules, in order:
12
+ * - A running Desktop app is the supervisor. Wait for its proxy; never start a competing one.
13
+ * - A durable Desktop claim with no running app: start Desktop (hidden, by the path its own
14
+ * login registration recorded — LaunchServices never picks the bundle) where that is
15
+ * verifiable, then wait. Elsewhere, fail with guidance. A missing Desktop process
16
+ * never authorizes a CLI proxy by itself; handing ownership to the CLI stays an explicit
17
+ * `ocx service install` consent step.
18
+ * - Ownership or the process list that cannot be read is not evidence of absence: refuse.
19
+ * - No claim and no running app: the ordinary CLI cold start.
20
+ *
21
+ * Concurrent shim launches share one Desktop launch through a lease file held by a live process;
22
+ * every caller then waits on the same readiness probe.
23
+ */
24
+ import { execFileSync } from "node:child_process";
25
+ import { randomUUID } from "node:crypto";
26
+ import { accessSync, constants, existsSync, linkSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
27
+ import { homedir } from "node:os";
28
+ import { basename, dirname, join, posix, win32 } from "node:path";
29
+ import { getConfigDir } from "../config/paths";
30
+ import { resolveServiceOwnership } from "../service/state";
31
+
32
+ export type DesktopProcessState = "running" | "absent" | "unknown";
33
+ export type DesktopOwnershipState = "desktop" | "other" | "unknown";
34
+
35
+ export type EnsureColdStartPlan =
36
+ | { kind: "spawn-cli" }
37
+ | { kind: "wait-desktop" }
38
+ | { kind: "launch-desktop" }
39
+ | { kind: "refuse"; reason: "desktop-unlaunchable" | "unverified" };
40
+
41
+ export function planEnsureColdStart(facts: {
42
+ ownership: DesktopOwnershipState;
43
+ desktopProcess: DesktopProcessState;
44
+ platform: NodeJS.Platform;
45
+ }): EnsureColdStartPlan {
46
+ if (facts.desktopProcess === "running") return { kind: "wait-desktop" };
47
+ if (facts.ownership === "unknown" || facts.desktopProcess === "unknown") return { kind: "refuse", reason: "unverified" };
48
+ if (facts.ownership === "other") return { kind: "spawn-cli" };
49
+ return facts.platform === "darwin" ? { kind: "launch-desktop" } : { kind: "refuse", reason: "desktop-unlaunchable" };
50
+ }
51
+
52
+ /** Desktop restart backoff tops out at 30 s; the budget covers it plus a cold sidecar start. */
53
+ export const DESKTOP_COLD_START_TIMEOUT_MS = 40_000;
54
+
55
+ export type DesktopColdStartResult<T> =
56
+ | { kind: "cli" }
57
+ | { kind: "live"; live: T }
58
+ | { kind: "failed"; message: string };
59
+
60
+ export interface DesktopColdStartDeps<T> {
61
+ platform: NodeJS.Platform;
62
+ ownership: () => DesktopOwnershipState;
63
+ desktopProcess: () => DesktopProcessState;
64
+ /** Start the Desktop app hidden. Returns false when the request could not be made. */
65
+ launchDesktop: () => boolean;
66
+ /** Returns a release function when this caller should launch, or null when another caller already did. */
67
+ acquireLaunchLease: () => (() => void) | null;
68
+ waitForProxy: (timeoutMs: number) => Promise<T | null>;
69
+ info?: (line: string) => void;
70
+ }
71
+
72
+ const QUIT_HINT = "To run the proxy from this CLI instead, quit OpenCodex and run `ocx service install` or `ocx start`.";
73
+
74
+ export async function runDesktopColdStart<T>(deps: DesktopColdStartDeps<T>): Promise<DesktopColdStartResult<T>> {
75
+ const plan = planEnsureColdStart({
76
+ ownership: deps.ownership(),
77
+ desktopProcess: deps.desktopProcess(),
78
+ platform: deps.platform,
79
+ });
80
+ if (plan.kind === "spawn-cli") return { kind: "cli" };
81
+ if (plan.kind === "refuse") {
82
+ return { kind: "failed", message: plan.reason === "unverified"
83
+ ? "Could not verify whether OpenCodex Desktop manages this proxy, so no CLI proxy was started. Run `ocx doctor`, or start the proxy explicitly with `ocx start`."
84
+ : `OpenCodex Desktop manages this proxy but is not running. Open OpenCodex to start it. ${QUIT_HINT}` };
85
+ }
86
+ if (plan.kind === "launch-desktop") {
87
+ const release = deps.acquireLaunchLease();
88
+ try {
89
+ if (release) {
90
+ deps.info?.("Starting OpenCodex Desktop, which manages this proxy...");
91
+ if (!deps.launchDesktop()) {
92
+ return { kind: "failed", message: `OpenCodex Desktop manages this proxy, but it could not be started. Open OpenCodex manually. ${QUIT_HINT}` };
93
+ }
94
+ }
95
+ const live = await deps.waitForProxy(DESKTOP_COLD_START_TIMEOUT_MS);
96
+ if (live) return { kind: "live", live };
97
+ return { kind: "failed", message: `OpenCodex Desktop was started, but its proxy did not answer in time. Check the OpenCodex menu. ${QUIT_HINT}` };
98
+ } finally { release?.(); }
99
+ }
100
+ deps.info?.("Waiting for OpenCodex Desktop to restart its proxy...");
101
+ const live = await deps.waitForProxy(DESKTOP_COLD_START_TIMEOUT_MS);
102
+ if (live) return { kind: "live", live };
103
+ return {
104
+ kind: "failed",
105
+ message: `OpenCodex Desktop is running but its proxy is not answering. Use Start Proxy in the OpenCodex menu. ${QUIT_HINT}`,
106
+ };
107
+ }
108
+
109
+ type Execute = (command: string, args: string[], timeoutMs: number) => string;
110
+
111
+ function execute(command: string, args: string[], timeoutMs: number): string {
112
+ return execFileSync(command, args, {
113
+ encoding: "utf8", timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
114
+ });
115
+ }
116
+
117
+ /** A Desktop app executable: `opencodex-desktop`, inside an app bundle's Contents/MacOS on macOS. */
118
+ export function isDesktopExecutable(path: string, platform: NodeJS.Platform): boolean {
119
+ if (basename(path) !== "opencodex-desktop") return false;
120
+ return platform !== "darwin" || (basename(dirname(path)) === "MacOS" && basename(dirname(dirname(path))) === "Contents");
121
+ }
122
+
123
+ export interface DesktopProcessOptions {
124
+ platform?: NodeJS.Platform;
125
+ uid?: number;
126
+ env?: NodeJS.ProcessEnv;
127
+ run?: Execute;
128
+ /** Linux: executable paths of this user's processes, or \`unverified\`. */
129
+ linuxExecutables?: (uid: number) => string[] | "unverified";
130
+ /** Linux: whether procfs hides processes (hidepid=2/4). */
131
+ procfsHidesProcesses?: () => boolean;
132
+ /** Linux: whether this machine shows evidence of an OpenCodex Desktop install. */
133
+ desktopInstalled?: () => boolean;
134
+ }
135
+
136
+ /**
137
+ * Linux: hidepid=2 (invisible) or 4 (ptraceable) can hide a same-uid process whose credentials
138
+ * differ from the caller's (for example after newgrp), so an empty scan proves nothing there.
139
+ * An unreadable mount table is treated as filtering.
140
+ */
141
+ export function procfsHidesProcesses(mountinfo = "/proc/self/mountinfo"): boolean {
142
+ let text: string;
143
+ try { text = readFileSync(mountinfo, "utf8"); } catch { return true; }
144
+ return text.split("\n").some(line => {
145
+ const [mount, fs] = line.split(" - ");
146
+ if (mount?.split(" ")[4] !== "/proc" || !fs) return false;
147
+ const [type, , options = ""] = fs.split(" ");
148
+ return type === "proc" && options.split(",").some(option => /^hidepid=(2|4|invisible|ptraceable)$/.test(option));
149
+ });
150
+ }
151
+
152
+ function linuxDesktopInstalled(): boolean {
153
+ return existsSync(join(homedir(), ".opencodex-desktop")) || existsSync("/usr/bin/opencodex-desktop");
154
+ }
155
+
156
+ /** Linux truncates comm to 15 bytes. */
157
+ const LINUX_DESKTOP_COMM = "opencodex-desktop".slice(0, 15);
158
+
159
+ /**
160
+ * Linux: executables of this user's processes. A process that exited mid-scan or belongs to someone
161
+ * else is skipped. A non-dumpable process (ssh-agent, gpg-agent) hides its executable but not its
162
+ * comm, so it is ruled out by name; one whose comm could be Desktop makes the answer unverifiable.
163
+ * The scan must also see this process, or procfs is filtered and absence proves nothing.
164
+ */
165
+ export function scanLinuxExecutables(uid: number, selfPid: number = process.pid, proc = "/proc"): string[] | "unverified" {
166
+ const paths: string[] = [];
167
+ let sawSelf = false;
168
+ const code = (error: unknown) => (error as { code?: unknown } | null)?.code;
169
+ for (const entry of readdirSync(proc)) {
170
+ if (!/^\d+$/.test(entry)) continue;
171
+ if (Number(entry) === selfPid) sawSelf = true;
172
+ try {
173
+ if (statSync(`${proc}/${entry}`).uid !== uid) continue;
174
+ paths.push(readlinkSync(`${proc}/${entry}/exe`).replace(/ \(deleted\)$/, ""));
175
+ } catch (error) {
176
+ if (code(error) === "ENOENT" || code(error) === "ESRCH") continue;
177
+ try {
178
+ if (readFileSync(`${proc}/${entry}/comm`, "utf8").trim() !== LINUX_DESKTOP_COMM) continue;
179
+ } catch (commError) {
180
+ if (code(commError) === "ENOENT" || code(commError) === "ESRCH") continue;
181
+ }
182
+ return "unverified";
183
+ }
184
+ }
185
+ return sawSelf ? paths : "unverified";
186
+ }
187
+
188
+ /** Whether this user runs an OpenCodex Desktop app, judged by executable identity, never arguments. */
189
+ export function inspectDesktopProcess(options: DesktopProcessOptions = {}): DesktopProcessState {
190
+ const platform = options.platform ?? process.platform;
191
+ const run = options.run ?? execute;
192
+ try {
193
+ const uid = options.uid ?? process.getuid?.();
194
+ if (platform === "linux") {
195
+ if (uid === undefined) return "unknown";
196
+ const executables = (options.linuxExecutables ?? scanLinuxExecutables)(uid);
197
+ if (executables === "unverified") return "unknown";
198
+ if (executables.some(path => isDesktopExecutable(path, platform))) return "running";
199
+ // A filtered procfs cannot prove absence where Desktop may be installed. Without an install
200
+ // there is nothing to hide, so hardened hosts keep the plain CLI cold start.
201
+ return (options.procfsHidesProcesses ?? procfsHidesProcesses)() && (options.desktopInstalled ?? linuxDesktopInstalled)()
202
+ ? "unknown" : "absent";
203
+ }
204
+ if (platform === "darwin") {
205
+ if (uid === undefined) return "unknown";
206
+ // macOS reports the full executable path as comm.
207
+ const output = run("/bin/ps", ["-U", String(uid), "-o", "comm="], 3_000);
208
+ return output.split("\n").some(line => isDesktopExecutable(line.trim(), platform)) ? "running" : "absent";
209
+ }
210
+ if (platform === "win32") {
211
+ const env = options.env ?? process.env;
212
+ const root = env.SystemRoot ?? env.windir ?? "C:\\Windows";
213
+ const user = env.USERNAME?.trim();
214
+ const args = ["/FI", "IMAGENAME eq opencodex-desktop.exe", ...(user ? ["/FI", `USERNAME eq ${user}`] : []), "/FO", "CSV", "/NH"];
215
+ const output = run(win32.join(root, "System32", "tasklist.exe"), args, 3_000);
216
+ return /^"opencodex-desktop\.exe",/im.test(output) ? "running" : "absent";
217
+ }
218
+ return "absent";
219
+ } catch {
220
+ return "unknown";
221
+ }
222
+ }
223
+
224
+ const DESKTOP_BUNDLE_ID = "com.opencodex.desktop";
225
+ const DESKTOP_PLIST_LABEL = "OpenCodex";
226
+
227
+ /**
228
+ * The app bundle the consented installation itself registered as its Start-at-Login program —
229
+ * not whichever bundle LaunchServices would resolve for our identifier. `open -b` answers a
230
+ * mutable per-user registry: any planted bundle claiming {@link DESKTOP_BUNDLE_ID} could be
231
+ * selected and executed in the real app's place. The recorded claim, this app's own install-id
232
+ * store, and the login registration it wrote are the durable evidence of which installation
233
+ * that is; a claim whose registration is absent, malformed, stale or points off a Desktop
234
+ * executable refuses the launch instead of guessing.
235
+ */
236
+ function consentedDesktopBundle(home: string, installId: string, run: Execute): string | null {
237
+ const localId = readFileSync(
238
+ join(home, "Library", "Application Support", DESKTOP_BUNDLE_ID, "install-id"), "utf8",
239
+ ).trim();
240
+ if (!localId || localId !== installId) return null;
241
+ const plistPath = realpathSync(join(home, "Library", "LaunchAgents", `${DESKTOP_PLIST_LABEL}.plist`));
242
+ const registration = JSON.parse(
243
+ run("/usr/bin/plutil", ["-convert", "json", "-o", "-", plistPath], 3_000),
244
+ ) as { Label?: unknown; ProgramArguments?: unknown; Program?: unknown };
245
+ const args = registration.ProgramArguments;
246
+ // The shapes auto-launch writes: [program] or [program, "--autostart"]; see the migration
247
+ // rules in desktop/src-tauri/src/login_registration.rs, which classify anything else Leave.
248
+ // This is a macOS plist path, regardless of the host running an injected launch probe.
249
+ if (registration.Label !== DESKTOP_PLIST_LABEL || !Array.isArray(args)
250
+ || (args.length !== 1 && args.length !== 2) || typeof args[0] !== "string" || !posix.isAbsolute(args[0])
251
+ || (args.length === 2 && args[1] !== "--autostart")
252
+ || (registration.Program !== undefined && registration.Program !== args[0])) return null;
253
+ const executable = realpathSync(args[0]);
254
+ if (!isDesktopExecutable(executable, "darwin") || !statSync(executable).isFile()) return null;
255
+ accessSync(executable, constants.X_OK);
256
+ const bundle = dirname(dirname(dirname(executable)));
257
+ if (!bundle.endsWith(".app")) return null;
258
+ const info = JSON.parse(
259
+ run("/usr/bin/plutil", ["-convert", "json", "-o", "-", join(bundle, "Contents", "Info.plist")], 3_000),
260
+ ) as { CFBundleIdentifier?: unknown; CFBundleExecutable?: unknown };
261
+ // `open` uses this selector, so checking only the registration and identifier would
262
+ // validate one binary while launching another. The registered path is already canonical.
263
+ if (info.CFBundleIdentifier !== DESKTOP_BUNDLE_ID || info.CFBundleExecutable !== "opencodex-desktop") return null;
264
+ return realpathSync(join(bundle, "Contents", "MacOS", info.CFBundleExecutable)) === executable ? bundle : null;
265
+ }
266
+
267
+ /** Start the consented macOS app hidden, by the path its own login registration recorded. */
268
+ export function launchDesktopApp(options: {
269
+ platform?: NodeJS.Platform;
270
+ run?: Execute;
271
+ home?: string;
272
+ ownership?: () => ReturnType<typeof resolveServiceOwnership>;
273
+ } = {}): boolean {
274
+ if ((options.platform ?? process.platform) !== "darwin") return false;
275
+ try {
276
+ const owner = (options.ownership ?? resolveServiceOwnership)();
277
+ if (owner.kind !== "owned" || owner.ownership.owner !== "desktop") return false;
278
+ const bundle = consentedDesktopBundle(options.home ?? homedir(), owner.ownership.installId, options.run ?? execute);
279
+ if (bundle === null) return false;
280
+ (options.run ?? execute)("/usr/bin/open", ["-g", bundle, "--args", "--autostart"], 10_000);
281
+ return true;
282
+ } catch { return false; }
283
+ }
284
+
285
+ function processAlive(pid: number): boolean {
286
+ try { process.kill(pid, 0); return true; }
287
+ catch (error) { return (error as NodeJS.ErrnoException | null)?.code === "EPERM"; }
288
+ }
289
+
290
+ function readHolder(path: string): number | null {
291
+ const pid = Number(readFileSync(path, "utf8").trim());
292
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
293
+ }
294
+
295
+ const LEASE_FILE = /^desktop-launch\.(\d+)\.lease$/;
296
+
297
+ /**
298
+ * One Desktop launch per cold start. Leases are numbered generations, each published whole with
299
+ * link(2), which fails if the name exists, so exactly one contender can create generation N+1. A
300
+ * caller launches only after creating the next generation while the newest one belongs to a dead
301
+ * process (or none exists). A lease stays valid while its holder lives, so a slow launch never
302
+ * expires under its owner. Nothing ever moves or overwrites another holder's lease.
303
+ */
304
+ export function acquireDesktopLaunchLease(
305
+ configDir: string,
306
+ io: { pid?: number; alive?: (pid: number) => boolean } = {},
307
+ ): (() => void) | null {
308
+ const pid = io.pid ?? process.pid;
309
+ const alive = io.alive ?? processAlive;
310
+ const leasePath = (generation: number) => join(configDir, `desktop-launch.${generation}.lease`);
311
+ for (let attempt = 0; attempt < 4; attempt++) {
312
+ let generations: number[];
313
+ try {
314
+ generations = readdirSync(configDir).flatMap(name => {
315
+ const match = LEASE_FILE.exec(name);
316
+ return match ? [Number(match[1])] : [];
317
+ }).sort((a, b) => b - a);
318
+ } catch {
319
+ // An unreadable lease directory must not suppress the launch; it only loses deduplication.
320
+ return () => {};
321
+ }
322
+ const newest = generations[0];
323
+ if (newest !== undefined) {
324
+ try {
325
+ const holder = readHolder(leasePath(newest));
326
+ if (holder !== null && alive(holder)) return null;
327
+ } catch (error) {
328
+ if ((error as { code?: unknown })?.code === "ENOENT") continue;
329
+ return null;
330
+ }
331
+ }
332
+ const next = (newest ?? 0) + 1;
333
+ const staged = join(configDir, `desktop-launch.${randomUUID()}.tmp`);
334
+ try {
335
+ writeFileSync(staged, String(pid), { flag: "wx", mode: 0o600 });
336
+ linkSync(staged, leasePath(next));
337
+ } catch (error) {
338
+ if ((error as { code?: unknown })?.code === "EEXIST") continue;
339
+ return () => {};
340
+ } finally {
341
+ try { unlinkSync(staged); } catch { /* not created */ }
342
+ }
343
+ const withdraw = () => {
344
+ try { if (readHolder(leasePath(next)) === pid) unlinkSync(leasePath(next)); } catch { /* already gone */ }
345
+ };
346
+ // A contender with a stale directory read can publish a generation a winner already cleaned
347
+ // up. The highest generation is the one launcher; anyone below it stands down.
348
+ try {
349
+ if (readdirSync(configDir).some(name => Number(LEASE_FILE.exec(name)?.[1] ?? 0) > next)) {
350
+ withdraw();
351
+ return null;
352
+ }
353
+ } catch { /* keep the lease: the directory was readable a moment ago */ }
354
+ // Older generations belong to dead holders: the newest was checked before publishing above it.
355
+ for (const generation of generations) {
356
+ try { unlinkSync(leasePath(generation)); } catch { /* already gone */ }
357
+ }
358
+ // The newest generation is never deleted on release: removing it would let numbering restart
359
+ // below a stale contender. It stops counting once this process exits.
360
+ return () => {};
361
+ }
362
+ return null;
363
+ }
364
+
365
+ /** Production wiring; `waitForProxy` is ensure's identity-checked readiness probe. */
366
+ export function productionDesktopColdStartDeps<T>(waitForProxy: (timeoutMs: number) => Promise<T | null>): DesktopColdStartDeps<T> {
367
+ return {
368
+ platform: process.platform,
369
+ ownership: () => {
370
+ try {
371
+ const owner = resolveServiceOwnership();
372
+ if (owner.kind === "unknown") return "unknown";
373
+ return owner.kind === "owned" && owner.ownership.owner === "desktop" ? "desktop" : "other";
374
+ } catch { return "unknown"; }
375
+ },
376
+ desktopProcess: () => inspectDesktopProcess(),
377
+ launchDesktop: () => launchDesktopApp(),
378
+ acquireLaunchLease: () => acquireDesktopLaunchLease(getConfigDir()),
379
+ waitForProxy,
380
+ info: line => console.log(line),
381
+ };
382
+ }
@@ -169,7 +169,7 @@ export async function handleExportCommand(argv: string[], deps: ExportCommandDep
169
169
  const spec = EXPORT_CLIENTS[client];
170
170
  const root = await runtimeBaseUrl(deps);
171
171
  let built: { document: unknown; text: string };
172
- if (client === "raycast" || client === "droid") {
172
+ if (client === "raycast" || client === "droid" || client === "qoder") {
173
173
  // The dial address alone cannot distinguish a wildcard authenticated bind
174
174
  // from loopback. Let the live server resolve its admission/listener policy;
175
175
  // saved config can differ from the process serving this request.
package/src/cli/help.ts CHANGED
@@ -85,20 +85,22 @@ Usage:
85
85
  ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
86
86
  ocx route <sub> Routing features (combo, policy)
87
87
  ocx logs [filters] Alias of ocx observe logs
88
- ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>]
88
+ ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>] [--top <1-1000>]
89
89
  Token and estimated-cost report (alias of ocx observe usage)
90
90
  ocx storage <sub> Storage report, cleanup, trash, and the cleanup policy
91
91
  ocx memory [--json] Alias of ocx observe memory
92
92
  ocx api-key <sub> Alias of ocx access key
93
93
  ocx access <sub> External API keys and endpoint information
94
94
  ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
95
- ocx export --client <id> Print a client config wired to the running proxy (18 clients)
95
+ ocx export --client <id> Print a client config wired to the running proxy (20 clients)
96
96
  ocx integration client <sub> Enable, disable, inspect or roll back a client integration
97
97
  ocx grok <sub> Grok Build model selection and apply
98
98
  ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
99
99
  ocx config [sub] Validated configuration show/get/set/import/export
100
+ ocx advisor <status|on|off|set|consent> Advisor sidecar settings and context-sharing consent
100
101
  ocx companion <show|set|reset> Menu-bar and widget companion usage settings
101
102
  ocx lab <sub> Inspect Lab evidence and control local automation
103
+ ocx codex-provider <sub> Confirm custom Codex provider routing: status|allow|revoke
102
104
  ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)
103
105
  ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
104
106
  ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
package/src/cli/index.ts CHANGED
@@ -16,7 +16,6 @@ import { UpdateRestartRequired } from "./update-restart-candidate";
16
16
  import { describeUpdateRestartFailure, restartFromCurrentInstallation } from "./update-restart";
17
17
  import { findGuiDist } from "../server/gui-static";
18
18
  import { inspectGuiBundleFreshness, staleGuiBundleLines } from "../server/gui-freshness";
19
-
20
19
  // Best-effort recovery for runtime execution and spawned children if launched
21
20
  // from an unlinked/deleted working directory (runs after hoisted ESM module imports).
22
21
  try {
@@ -156,9 +155,9 @@ import {
156
155
  reconcileEnsureDesiredIntegrations,
157
156
  } from "./ensure-desired-integrations";
158
157
  import { ENSURE_READY_TIMEOUT_MS, ensureKeepWaiting, waitForLiveProxy } from "./ensure-readiness";
158
+ import { productionDesktopColdStartDeps, runDesktopColdStart } from "./ensure-desktop-cold-start";
159
159
  import { refreshOwnedCatalogIntegrations } from "../integrations/catalog-refresh";
160
160
  import { loadExportModels } from "../server/management/model-rows";
161
-
162
161
  import { removeOwnedConfigAfterDesktopCleanup } from "./uninstall-client-state";
163
162
  import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
164
163
  import { startArgv } from "../lib/self-launch-argv";
@@ -169,6 +168,7 @@ import { recoverStartStateUnderOwnershipLease } from "./start-owner-fence";
169
168
  import { handledSignalExitCode } from "../lib/handled-signal-exit";
170
169
  import { createLocalAttestationSecret } from "../lib/local-management-attestation";
171
170
  import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
171
+ import { startServiceLogging } from "../service/logging";
172
172
 
173
173
  /**
174
174
  * A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
@@ -208,6 +208,7 @@ async function refreshOwnedRaycastCatalog(
208
208
  }
209
209
  }
210
210
 
211
+ if (process.argv[2] === "start" && process.env.OCX_SERVICE) startServiceLogging(join(getConfigDir(), "service.log"));
211
212
  const updateRestartChild = admitUpdateRestartChild(process.argv.slice(2));
212
213
  initializeNodeLauncherContext();
213
214
  // The compiled executable is also the capture-only MCP server's launcher.
@@ -282,6 +283,7 @@ async function chooseListenPort(
282
283
  `Port ${preferred} is reserved for unauthenticatedLoopbackListener; choose a different proxy port.`,
283
284
  );
284
285
  }
286
+ await (await import("./auxiliary-listener-preflight")).assertAuxiliaryListenerPortsAvailable(config);
285
287
  // Soft start: brief prefer-retry then ephemeral hop.
286
288
  // Explicit `--port` (service wrappers / update restart): wait for the pinned port
287
289
  // to free without killing any listener (healthy ocx / foreign). Never hop.
@@ -658,7 +660,7 @@ async function handleStart(options: { block?: boolean } = {}) {
658
660
  }
659
661
  removePid(process.pid);
660
662
  removeRuntimePort(process.pid);
661
- if (teardown.restoreNativeCodex && !currentExternalCodexModelProvider()) {
663
+ if (teardown.restoreNativeCodex) {
662
664
  try {
663
665
  if (!codexHomeIsAbsent(getCodexHome())) {
664
666
  const restored = restoreNativeCodex();
@@ -816,9 +818,16 @@ async function handleEnsure(options: { existingIsSuccess?: boolean; forceStart?:
816
818
  console.log("Codex autostart is disabled.");
817
819
  return false;
818
820
  }
819
- const live = owner.live;
821
+ let live = owner.live;
822
+ if (!live) {
823
+ // OpenCodex Desktop owns or runs this proxy: wait for it or start it, never a competing CLI proxy (#6971).
824
+ const desktopStart = await runDesktopColdStart(productionDesktopColdStartDeps(timeoutMs => waitForProxy(timeoutMs)));
825
+ if (desktopStart.kind === "failed") { console.error(`❌ ${desktopStart.message}`); process.exitCode = 1; return false; }
826
+ // Also for restart: the proxy was absent and Desktop started it, so the live tail below applies.
827
+ if (desktopStart.kind === "live") live = desktopStart.live;
828
+ }
820
829
  if (live) {
821
- if (options.existingIsSuccess === false) {
830
+ if (options.existingIsSuccess === false && live === owner.live) {
822
831
  console.error("Proxy appeared while restart was confirming absence; no start was attempted.");
823
832
  return false;
824
833
  }
@@ -26,6 +26,8 @@ export const FILE_INTEGRATION_CLIENTS = [
26
26
  "commandcode",
27
27
  "kilo",
28
28
  "droid",
29
+ "qoder",
30
+ "gentle-shell",
29
31
  ] as const;
30
32
 
31
33
  export const INTEGRATION_REFUSAL_REASONS: ReadonlySet<string> = new Set<IntegrationRefusalReason>([
@@ -65,6 +67,7 @@ const PLAN_SCHEMA_PATHS = new Set([
65
67
  "settings.providers.opencodex",
66
68
  "catalog.providers.opencodex",
67
69
  "customModels.*",
70
+ "modelConfigs.customModels.*",
68
71
  // ZCode reads its providers from a second file; a plan for it publishes that
69
72
  // file's templates, and a path missing here is rejected as an invalid preview.
70
73
  "config.providerConfigRules.providerRules.[providerId=opencodex]",