@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,545 @@
1
+ import { spawnSync, type SpawnSyncReturns } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ import { compareStrictSemver, parseStrictSemver } from "../lib/strict-semver";
8
+ import { npmInvocation } from "../update/npm-invocation.mjs";
9
+ import {
10
+ inspectCodexCliInstall,
11
+ type CodexCliInstallKind,
12
+ type CodexCliInstallProvenanceDeps,
13
+ type CodexCliInstallReport,
14
+ } from "./cli-install-provenance";
15
+ import {
16
+ scanCodexSessionProcesses,
17
+ type CodexAppServerProcessIo,
18
+ type CodexAppServerProcessScan,
19
+ } from "./app-server-processes";
20
+
21
+ /**
22
+ * Phase 2 of the Codex CLI update manager: a deterministic dry-run plan.
23
+ *
24
+ * Phase 1 ({@link inspectCodexCliInstall}) answers who owns the installation. It never
25
+ * queries a registry, never enumerates processes and never writes. This module adds the
26
+ * three inputs it deliberately left out — an exact resolved target with registry
27
+ * integrity, live session blockers, and a decision — and nothing else.
28
+ *
29
+ * Two invariants shape the whole module:
30
+ *
31
+ * - No persisted state. The plan is not stored anywhere; {@link CodexCliUpdatePlan.planId}
32
+ * is a digest over the evidence the decision rests on, so an approved change to any
33
+ * bound field produces a different id.
34
+ * - Nothing is terminated or restarted. A live Codex session refuses the plan; it is
35
+ * never signalled, killed or waited on. The plan performs no application-state
36
+ * mutation: it resolves registry metadata and reads the process table under a
37
+ * temporary isolated npm root (cwd, npmrc, cache and logs) that is removed
38
+ * afterwards; a forcibly terminated run may leave that temp root behind, so the
39
+ * cleanup contract is best-effort rather than absolute.
40
+ */
41
+
42
+ export const CODEX_CLI_PACKAGE = "@openai/codex";
43
+ export const CODEX_CLI_UPDATE_SCHEMA_VERSION = 1 as const;
44
+
45
+ /**
46
+ * The one registry this workflow is allowed to resolve, pack and install from.
47
+ *
48
+ * npm view/pack inherit the operator's npm configuration by default, so a project or
49
+ * user .npmrc — or an npm_config_* env var — can redirect "@openai/codex" to another
50
+ * registry whose version query, integrity query and tarball all agree with each
51
+ * other. Every npm call below pins this registry explicitly AND runs with the
52
+ * registry-affecting configuration isolated, so the pinned origin is the only source
53
+ * the evidence can come from.
54
+ */
55
+ export const CODEX_CLI_REGISTRY = "https://registry.npmjs.org";
56
+
57
+ /** Only the stable channel is exposed; a moving dist-tag is resolved before it is used. */
58
+ export type CodexCliUpdateChannel = "latest";
59
+
60
+ export type CodexCliUpdateRefusal =
61
+ | "windows_inspection_deferred"
62
+ | "not_managed"
63
+ | "installed_version_unverified"
64
+ | "target_unresolved"
65
+ | "already_current"
66
+ | "target_not_newer"
67
+ | "blocked_active_session"
68
+ | "blocked_process_state_unknown";
69
+
70
+ /**
71
+ * `not-evaluated` is not a weaker `unknown`: it records that the plan was already refused
72
+ * on ownership or target grounds, so the process table was never read. Only `unknown`
73
+ * means an enumeration attempt failed.
74
+ */
75
+ export type CodexCliUpdateSessionState = "none" | "active" | "unknown" | "not-evaluated";
76
+
77
+ export interface CodexCliUpdateSession {
78
+ readonly state: CodexCliUpdateSessionState;
79
+ /** Match count only. Command lines and paths never leave the process scanner. */
80
+ readonly matches: number | null;
81
+ }
82
+
83
+ export type CodexCliUpdateTarget =
84
+ | Readonly<{ kind: "resolved"; version: string; integrity: string }>
85
+ | Readonly<{ kind: "unresolved"; reason: string }>;
86
+
87
+ export interface CodexCliUpdatePlan {
88
+ readonly schemaVersion: typeof CODEX_CLI_UPDATE_SCHEMA_VERSION;
89
+ readonly package: typeof CODEX_CLI_PACKAGE;
90
+ readonly channel: CodexCliUpdateChannel;
91
+ readonly applicable: boolean;
92
+ readonly refusal: CodexCliUpdateRefusal | null;
93
+ /** Present only for an applicable plan; there is nothing to quote for a refusal. */
94
+ readonly planId: string | null;
95
+ readonly provenance: CodexCliInstallKind;
96
+ readonly managed: boolean;
97
+ readonly installedVersion: string | null;
98
+ readonly versionEvidence: CodexCliInstallReport["versionEvidence"]["kind"];
99
+ readonly location: string | null;
100
+ readonly targetVersion: string | null;
101
+ readonly targetIntegrity: string | null;
102
+ readonly session: CodexCliUpdateSession;
103
+ /** Indicative install argv for the operator to read; the apply phase this plan precedes verifies a packed tarball against the bound sha512 rather than trusting a moving spec. */
104
+ readonly command: readonly string[] | null;
105
+ }
106
+
107
+ export interface CodexCliUpdatePlanDeps {
108
+ readonly inspect?: (deps: CodexCliInstallProvenanceDeps) => Promise<CodexCliInstallReport>;
109
+ readonly inspectionDeps?: CodexCliInstallProvenanceDeps;
110
+ readonly platform?: NodeJS.Platform;
111
+ readonly channel?: CodexCliUpdateChannel;
112
+ readonly scanProcesses?: (io?: CodexAppServerProcessIo) => CodexAppServerProcessScan;
113
+ readonly processIo?: CodexAppServerProcessIo;
114
+ readonly resolveTarget?: (channel: CodexCliUpdateChannel) => CodexCliUpdateTarget;
115
+ readonly spawnProcess?: typeof spawnSync;
116
+ }
117
+
118
+
119
+ const PLAN_ID_LENGTH = 32;
120
+ const PLAN_ID_RE = /^[0-9a-f]{32}$/;
121
+ const REGISTRY_TIMEOUT_MS = 12_000;
122
+ const SHA512_INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]{86}==$/;
123
+
124
+ function isSha512Integrity(token: string): boolean {
125
+ if (!SHA512_INTEGRITY_RE.test(token)) return false;
126
+ const encoded = token.slice("sha512-".length);
127
+ // Base64 decoders accept truncated input and nonzero unused padding bits.
128
+ // Require exactly 64 bytes and the canonical encoding of those same bytes.
129
+ const digest = Buffer.from(encoded, "base64");
130
+ return digest.byteLength === 64 && digest.toString("base64") === encoded;
131
+ }
132
+
133
+ /** The install argv this plan quotes for the operator to read, in argv form. */
134
+ export function codexCliUpdateCommand(version: string): readonly string[] {
135
+ // The displayed command must reproduce the artifact the plan bound: without the
136
+ // pinned registry the operator's npmrc could silently substitute another origin
137
+ // for the same package@version string.
138
+ return Object.freeze([
139
+ "npm",
140
+ "install",
141
+ "-g",
142
+ "--registry=" + CODEX_CLI_REGISTRY,
143
+ `${CODEX_CLI_PACKAGE}@${version}`,
144
+ ]);
145
+ }
146
+
147
+ /**
148
+ * Digest over exactly the evidence the decision rests on.
149
+ *
150
+ * Session blockers are deliberately absent: a session that starts or ends between dry-run
151
+ * and a later apply must not invalidate an otherwise identical plan; it is re-read where
152
+ * it can only ever refuse. Everything else — ownership, installed version,
153
+ * location, resolved target, integrity — is bound, so any drift produces a different id.
154
+ */
155
+ export function codexCliUpdatePlanId(bound: {
156
+ readonly platform: NodeJS.Platform;
157
+ readonly provenance: CodexCliInstallKind;
158
+ readonly installedVersion: string;
159
+ readonly installDigest: string | null;
160
+ readonly channel: CodexCliUpdateChannel;
161
+ readonly targetVersion: string;
162
+ readonly targetIntegrity: string;
163
+ }): string {
164
+ // Ordered pairs, not object key order: the digest must not depend on how a caller
165
+ // happened to build the record.
166
+ const fields: readonly (readonly [string, string])[] = [
167
+ ["schemaVersion", String(CODEX_CLI_UPDATE_SCHEMA_VERSION)],
168
+ ["package", CODEX_CLI_PACKAGE],
169
+ ["platform", bound.platform],
170
+ ["provenance", bound.provenance],
171
+ ["installedVersion", bound.installedVersion],
172
+ ["installDigest", bound.installDigest ?? ""],
173
+ ["channel", bound.channel],
174
+ ["targetVersion", bound.targetVersion],
175
+ ["targetIntegrity", bound.targetIntegrity],
176
+ ];
177
+ const hash = createHash("sha256");
178
+ for (const [key, value] of fields) hash.update(`${key}=${value}\n`);
179
+ return hash.digest("hex").slice(0, PLAN_ID_LENGTH);
180
+ }
181
+
182
+ interface NpmTarget {
183
+ readonly bin: string;
184
+ readonly args: string[];
185
+ readonly options: {
186
+ readonly windowsVerbatimArguments?: boolean;
187
+ readonly cwd?: string;
188
+ readonly env?: NodeJS.ProcessEnv;
189
+ };
190
+ }
191
+
192
+ function npmTarget(args: readonly string[], env?: NodeJS.ProcessEnv): NpmTarget | null {
193
+ const invocation = npmInvocation(args, process.platform, env ?? process.env);
194
+ if (!invocation) return null;
195
+ return { bin: invocation.file, args: invocation.args, options: invocation.options };
196
+ }
197
+
198
+ /**
199
+ * The npm environment with every registry-affecting config channel removed.
200
+ *
201
+ * npm maps any npm_config_* env var into its configuration, so NPM_CONFIG_REGISTRY or
202
+ * npm_config_@openai:registry would defeat the pinned --registry flag (a scoped
203
+ * registry beats the default for that scope). They are all stripped. The settings
204
+ * this codebase intentionally supports survive untouched: the standard proxy
205
+ * variables npm itself honors (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY, any case)
206
+ * and NODE_EXTRA_CA_CERTS, which npm's own Node runtime reads for TLS.
207
+ *
208
+ * Node's own startup channels are stripped too. npm runs inside Node, so ambient
209
+ * NODE_OPTIONS injects code before the first npm line, NODE_PATH redirects module
210
+ * resolution, and NODE_TLS_REJECT_UNAUTHORIZED would make the pinned-registry TLS
211
+ * check void - each one reaches the evidence-producing process unless removed.
212
+ * NODE_V8_COVERAGE and NODE_REDIRECT_WARNINGS are dropped for the same reason in
213
+ * reverse: they are output paths, so ambient values would write Node artifacts to
214
+ * arbitrary external directories on exit. ELF/Darwin loader controls (LD_* and
215
+ * DYLD_*) are also removed: they can load code or redirect loader output before
216
+ * npm runs. This is a denylist for process-startup/output channels, not a claim
217
+ * that every ambient variable is allowlisted. Ordinary OS environment, proxy
218
+ * settings and the supported NODE_EXTRA_CA_CERTS extension remain available.
219
+ */
220
+ const NPM_ENV_DROP_KEYS: ReadonlySet<string> = new Set([
221
+ "node_options",
222
+ "node_path",
223
+ "node_tls_reject_unauthorized",
224
+ "node_compile_cache",
225
+ // Output-path channels: ambient values would make the Node process write
226
+ // coverage JSON or warning output to an arbitrary external directory on exit,
227
+ // escaping the owned temp root regardless of the isolated --cache.
228
+ "node_v8_coverage",
229
+ "node_redirect_warnings",
230
+ ]);
231
+
232
+ function codexCliUpdateNpmEnv(base: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
233
+ const env: NodeJS.ProcessEnv = { ...base };
234
+ for (const key of Object.keys(env)) {
235
+ const lower = key.toLowerCase();
236
+ if (lower.startsWith("npm_config_") || lower.startsWith("ld_")
237
+ || lower.startsWith("dyld_") || NPM_ENV_DROP_KEYS.has(lower)) delete env[key];
238
+ }
239
+ return env;
240
+ }
241
+
242
+ /**
243
+ * Ambient environment with the executable-selection channels replaced by the proof-bound
244
+ * launcher snapshot. HOME, proxy variables and NODE_EXTRA_CA_CERTS stay ambient because npm
245
+ * itself needs them; PATH/PATHEXT are the channels a project dotenv could use to supply a
246
+ * fake npm, so they come only from the captured snapshot. A snapshot without PATH means no
247
+ * trusted PATH at all — an empty one fails the spawn closed rather than falling back.
248
+ */
249
+ function codexCliUpdateBoundEnv(bound: NodeJS.ProcessEnv | undefined): NodeJS.ProcessEnv {
250
+ const merged = codexCliUpdateNpmEnv();
251
+ if (bound === undefined) return merged;
252
+ for (const key of Object.keys(merged)) {
253
+ const k = key.toLowerCase();
254
+ if (k === "path" || k === "pathext") delete merged[key];
255
+ }
256
+ let hasTrustedPath = false;
257
+ for (const [key, value] of Object.entries(bound)) {
258
+ const k = key.toLowerCase();
259
+ if ((k === "path" || k === "pathext") && value !== undefined) {
260
+ merged[key] = value;
261
+ if (k === "path") hasTrustedPath = true;
262
+ }
263
+ }
264
+ // A trusted snapshot without PATH means "no trusted PATH", not "keep the ambient
265
+ // one". Install the empty value explicitly so executable resolution fails closed
266
+ // instead of silently re-inheriting a PATH the proof never attested.
267
+ if (!hasTrustedPath) merged.PATH = "";
268
+ return merged;
269
+ }
270
+
271
+ interface NpmConfigIsolation {
272
+ /** Controlled cwd: contains the sentinel package.json, so npm's project-config walk stops here. */
273
+ readonly dir: string;
274
+ /** Distinct empty files: npm refuses to load one path at two configuration levels. */
275
+ readonly npmrc: string;
276
+ readonly globalNpmrc: string;
277
+ /** npm cache root under dir: keeps _cacache and _logs inside the owned root so a
278
+ * successful query cannot leave residue in the ambient ~/.npm. */
279
+ readonly cache: string;
280
+ readonly env: NodeJS.ProcessEnv;
281
+ }
282
+
283
+ /**
284
+ * A directory npm cannot read hostile configuration through.
285
+ *
286
+ * npm resolves project config from the nearest ancestor containing package.json (or
287
+ * node_modules/.git), so a bare temp dir is NOT enough — the walk would continue into
288
+ * the operator's real ancestors. The sentinel package.json anchors the walk here,
289
+ * where no .npmrc exists. --userconfig/--globalconfig replace the two file configs,
290
+ * and the env filter removes the env-var channel. What remains is exactly the pinned
291
+ * --registry flag plus deliberately supported proxy/CA env.
292
+ */
293
+ function createNpmConfigIsolation(dir?: string, boundEnv?: NodeJS.ProcessEnv): NpmConfigIsolation {
294
+ const ownsRoot = dir === undefined;
295
+ const root = dir ?? mkdtempSync(join(tmpdir(), "ocx-codex-cli-meta-"));
296
+ try {
297
+ writeFileSync(join(root, "package.json"), "{}\n");
298
+ const npmrc = join(root, "ocx-update.npmrc");
299
+ writeFileSync(npmrc, "");
300
+ const globalNpmrc = join(root, "ocx-update-global.npmrc");
301
+ writeFileSync(globalNpmrc, "");
302
+ const cache = join(root, "npm-cache");
303
+ return { dir: root, npmrc, globalNpmrc, cache, env: codexCliUpdateBoundEnv(boundEnv) };
304
+ } catch (error) {
305
+ // A mid-setup failure must not leak a directory this call created; a
306
+ // caller-supplied directory stays the caller's responsibility.
307
+ if (ownsRoot) rmSync(root, { recursive: true, force: true });
308
+ throw error;
309
+ }
310
+ }
311
+
312
+ /** Registry-pinned, config-isolated argv for one npm call. */
313
+ function isolatedNpmArgs(args: readonly string[], isolation: NpmConfigIsolation): readonly string[] {
314
+ return [
315
+ ...args,
316
+ "--registry=" + CODEX_CLI_REGISTRY,
317
+ "--userconfig=" + isolation.npmrc,
318
+ "--globalconfig=" + isolation.globalNpmrc,
319
+ "--cache=" + isolation.cache,
320
+ ];
321
+ }
322
+
323
+ function isolatedNpmTarget(args: readonly string[], isolation: NpmConfigIsolation): NpmTarget | null {
324
+ const target = npmTarget(isolatedNpmArgs(args, isolation), isolation.env);
325
+ if (!target) return null;
326
+ return {
327
+ bin: target.bin,
328
+ args: target.args,
329
+ options: { ...target.options, cwd: isolation.dir, env: isolation.env },
330
+ };
331
+ }
332
+
333
+ /**
334
+ * Resolve the channel to one exact version plus its sha512 integrity token.
335
+ *
336
+ * OpenCodex's own updater treats a failed integrity query as `skipped` and proceeds
337
+ * best-effort. That is a defensible trade for the package we publish ourselves; it is not
338
+ * acceptable for a foreign package we are about to install on the operator's behalf, so
339
+ * every failure lane here is a refusal.
340
+ */
341
+ export function resolveCodexCliUpdateTarget(
342
+ channel: CodexCliUpdateChannel,
343
+ spawn: typeof spawnSync = spawnSync,
344
+ /** Proof-bound launcher snapshot env; executable resolution reads only its PATH/PATHEXT. */
345
+ boundEnv?: NodeJS.ProcessEnv,
346
+ ): CodexCliUpdateTarget {
347
+ // The pinned registry and the isolation directory together are the boundary: the
348
+ // version, the integrity token and the tarball URL all come from npmjs or the
349
+ // target is unresolved — a redirected answer can never be the evidence.
350
+ let isolation: NpmConfigIsolation;
351
+ try {
352
+ isolation = createNpmConfigIsolation(undefined, boundEnv);
353
+ } catch {
354
+ // Setup failures are a refusal lane like every other resolve failure, not an
355
+ // exception escaping the dry-run.
356
+ return Object.freeze({ kind: "unresolved" as const, reason: "npm configuration isolation setup failed" });
357
+ }
358
+ try {
359
+ const runView = (field: string, spec: string): SpawnSyncReturns<string> | null => {
360
+ const target = isolatedNpmTarget(["view", spec, field], isolation);
361
+ if (!target) return null;
362
+ return spawn(target.bin, target.args, {
363
+ encoding: "utf8",
364
+ timeout: REGISTRY_TIMEOUT_MS,
365
+ windowsHide: true,
366
+ ...target.options,
367
+ });
368
+ };
369
+
370
+ const versionRun = runView("version", `${CODEX_CLI_PACKAGE}@${channel}`);
371
+ if (!versionRun) return Object.freeze({ kind: "unresolved" as const, reason: "npm executable was not found on a trusted PATH entry" });
372
+ // status === null covers a timeout as well as a spawn failure.
373
+ if (versionRun.status !== 0) {
374
+ return Object.freeze({ kind: "unresolved" as const, reason: `registry version query failed (status ${versionRun.status ?? "timeout"})` });
375
+ }
376
+ const version = (versionRun.stdout ?? "").trim();
377
+ if (!parseStrictSemver(version)) {
378
+ return Object.freeze({ kind: "unresolved" as const, reason: "registry returned no exact version" });
379
+ }
380
+
381
+ const integrityRun = runView("dist.integrity", `${CODEX_CLI_PACKAGE}@${version}`);
382
+ if (!integrityRun) return Object.freeze({ kind: "unresolved" as const, reason: "npm executable was not found on a trusted PATH entry" });
383
+ if (integrityRun.status !== 0) {
384
+ return Object.freeze({ kind: "unresolved" as const, reason: `registry integrity query failed (status ${integrityRun.status ?? "timeout"})` });
385
+ }
386
+ // `dist.integrity` may arrive quoted or as a space-separated multi-hash list.
387
+ const tokens = (integrityRun.stdout ?? "").replace(/["']/g, "").trim().split(/\s+/).filter(Boolean);
388
+ const integrity = tokens.find(isSha512Integrity);
389
+ if (!integrity) {
390
+ return Object.freeze({ kind: "unresolved" as const, reason: "registry returned no sha512 integrity token" });
391
+ }
392
+
393
+ // The digest binds the tarball's CONTENT; this binds its ORIGIN. A registry
394
+ // answer whose tarball lives outside the pinned registry is not the artifact
395
+ // the plan approved, even if its sha512 matched.
396
+ const tarballRun = runView("dist.tarball", `${CODEX_CLI_PACKAGE}@${version}`);
397
+ if (!tarballRun) return Object.freeze({ kind: "unresolved" as const, reason: "npm executable was not found on a trusted PATH entry" });
398
+ if (tarballRun.status !== 0) {
399
+ return Object.freeze({ kind: "unresolved" as const, reason: `registry tarball query failed (status ${tarballRun.status ?? "timeout"})` });
400
+ }
401
+ const tarball = (tarballRun.stdout ?? "").replace(/["']/g, "").trim();
402
+ let tarballOrigin: string | null = null;
403
+ try {
404
+ tarballOrigin = new URL(tarball).origin;
405
+ } catch {
406
+ tarballOrigin = null;
407
+ }
408
+ if (tarballOrigin !== CODEX_CLI_REGISTRY) {
409
+ return Object.freeze({ kind: "unresolved" as const, reason: "registry returned a tarball outside the official registry origin" });
410
+ }
411
+ return Object.freeze({ kind: "resolved" as const, version, integrity });
412
+ } finally {
413
+ // Cleanup is best-effort; a locked residue must not replace the plan result.
414
+ try { rmSync(isolation.dir, { recursive: true, force: true }); }
415
+ catch { /* the owned temporary root may remain, as documented */ }
416
+ }
417
+ }
418
+
419
+ function refusedPlan(
420
+ refusal: CodexCliUpdateRefusal,
421
+ report: CodexCliInstallReport,
422
+ channel: CodexCliUpdateChannel,
423
+ target: CodexCliUpdateTarget | null,
424
+ session: CodexCliUpdateSession,
425
+ ): CodexCliUpdatePlan {
426
+ return Object.freeze({
427
+ schemaVersion: CODEX_CLI_UPDATE_SCHEMA_VERSION,
428
+ package: CODEX_CLI_PACKAGE,
429
+ channel,
430
+ applicable: false,
431
+ refusal,
432
+ planId: null,
433
+ provenance: report.provenance,
434
+ managed: report.managed,
435
+ installedVersion: report.packageVersion,
436
+ versionEvidence: report.versionEvidence.kind,
437
+ location: report.location,
438
+ targetVersion: target?.kind === "resolved" ? target.version : null,
439
+ targetIntegrity: target?.kind === "resolved" ? target.integrity : null,
440
+ session,
441
+ command: null,
442
+ });
443
+ }
444
+
445
+ const NOT_EVALUATED: CodexCliUpdateSession = Object.freeze({ state: "not-evaluated" as const, matches: null });
446
+
447
+ /**
448
+ * Build the dry-run plan. Installs nothing and mutates no application state;
449
+ * registry evidence is gathered under a temporary isolated npm root whose cache,
450
+ * logs, npmrc and cwd are all inside it and removed best-effort afterwards.
451
+ *
452
+ * The refusal order is deliberate. Ownership and target questions are settled before the
453
+ * process table is read, so a machine that can never be updated by this workflow does not
454
+ * pay for an enumeration, and the reported refusal is the decisive one rather than
455
+ * whichever check happened to run first.
456
+ */
457
+ export async function createCodexCliUpdatePlan(deps: CodexCliUpdatePlanDeps = {}): Promise<CodexCliUpdatePlan> {
458
+ const channel: CodexCliUpdateChannel = deps.channel ?? "latest";
459
+ const platform = deps.platform ?? process.platform;
460
+ const inspect = deps.inspect ?? inspectCodexCliInstall;
461
+ const report = await inspect(deps.inspectionDeps ?? {});
462
+
463
+ // Phase 1 performs no candidate filesystem I/O at all on Windows, so there is no
464
+ // ownership evidence to build a plan on. This stays dormant until the handle-bound
465
+ // provenance layer exists; pretending otherwise would require exactly the reads phase 1
466
+ // refused to perform.
467
+ if (platform === "win32" || report.reason === "windows_inspection_deferred") {
468
+ return refusedPlan("windows_inspection_deferred", report, channel, null, NOT_EVALUATED);
469
+ }
470
+ if (!report.managed || report.provenance !== "npm-global") {
471
+ return refusedPlan("not_managed", report, channel, null, NOT_EVALUATED);
472
+ }
473
+ // An advisory runtime string is what a candidate binary said about itself. Only
474
+ // package-manifest evidence states what is installed on disk, and only that can be
475
+ // compared with a registry version or read back after an install.
476
+ const installedVersion = report.versionEvidence.kind === "package-manifest" ? report.packageVersion : null;
477
+ const installedSemver = installedVersion ? parseStrictSemver(installedVersion) : null;
478
+ if (!installedVersion || !installedSemver) {
479
+ return refusedPlan("installed_version_unverified", report, channel, null, NOT_EVALUATED);
480
+ }
481
+
482
+ // The npm evidence channels are bound to the same environment snapshot the
483
+ // ownership inspection ran against: PATH/PATHEXT from the proof-bound launcher
484
+ // context, never the ambient ones a project dotenv could point at a fake npm.
485
+ // When the caller had no trusted snapshot, inspectionDeps.env is the fail-closed
486
+ // { PATH: "" } form, so this resolution cannot silently fall back to ambient.
487
+ const resolveTarget = deps.resolveTarget
488
+ ?? (ch => resolveCodexCliUpdateTarget(ch, deps.spawnProcess ?? spawnSync, deps.inspectionDeps?.env));
489
+ const target = resolveTarget(channel);
490
+ if (target.kind !== "resolved") {
491
+ return refusedPlan("target_unresolved", report, channel, target, NOT_EVALUATED);
492
+ }
493
+ // Raw equality is not the gate: a resolved target must advance the installed
494
+ // version by semver precedence. Equal versions are already current and lower
495
+ // ones are refused rather than applied as a silent downgrade.
496
+ const targetSemver = parseStrictSemver(target.version);
497
+ if (!targetSemver) {
498
+ return refusedPlan("target_unresolved", report, channel, target, NOT_EVALUATED);
499
+ }
500
+ const versionOrder = compareStrictSemver(targetSemver, installedSemver);
501
+ if (versionOrder === 0) {
502
+ return refusedPlan("already_current", report, channel, target, NOT_EVALUATED);
503
+ }
504
+ if (versionOrder < 0) {
505
+ return refusedPlan("target_not_newer", report, channel, target, NOT_EVALUATED);
506
+ }
507
+
508
+ const scan = (deps.scanProcesses ?? scanCodexSessionProcesses)(deps.processIo ?? {});
509
+ if (scan.kind !== "observed") {
510
+ // An unreadable process table is not "no sessions". `listCodexAppServerProcesses`
511
+ // maps that failure to an empty list because its kill contract must never signal a
512
+ // process it could not verify; the update contract has to defer instead.
513
+ return refusedPlan("blocked_process_state_unknown", report, channel, target, Object.freeze({ state: "unknown" as const, matches: null }));
514
+ }
515
+ const matches = scan.processes.length;
516
+ if (matches > 0) {
517
+ return refusedPlan("blocked_active_session", report, channel, target, Object.freeze({ state: "active" as const, matches }));
518
+ }
519
+
520
+ return Object.freeze({
521
+ schemaVersion: CODEX_CLI_UPDATE_SCHEMA_VERSION,
522
+ package: CODEX_CLI_PACKAGE,
523
+ channel,
524
+ applicable: true,
525
+ refusal: null,
526
+ planId: codexCliUpdatePlanId({
527
+ platform,
528
+ provenance: report.provenance,
529
+ installedVersion,
530
+ installDigest: report.installDigest,
531
+ channel,
532
+ targetVersion: target.version,
533
+ targetIntegrity: target.integrity,
534
+ }),
535
+ provenance: report.provenance,
536
+ managed: report.managed,
537
+ installedVersion,
538
+ versionEvidence: report.versionEvidence.kind,
539
+ location: report.location,
540
+ targetVersion: target.version,
541
+ targetIntegrity: target.integrity,
542
+ session: Object.freeze({ state: "none" as const, matches: 0 }),
543
+ command: codexCliUpdateCommand(target.version),
544
+ });
545
+ }
@@ -96,8 +96,29 @@ function acquire(configPath: string): ConfigWriteAcquireResult {
96
96
  throw error;
97
97
  }
98
98
  const acquired = tryAcquire(`${target.canonical}.ocx-write.lock`);
99
- if (acquired.ok) destinations.set(acquired.handle, target);
100
- return acquired;
99
+ if (!acquired.ok) return acquired;
100
+ try {
101
+ // A preceding writer may atomically replace the file before releasing the lock.
102
+ // Capture the inode only after acquisition, while retaining the canonical-path gate.
103
+ const current = destination(configPath);
104
+ if (current.canonical !== target.canonical || !stillHeld(acquired.handle)) {
105
+ release(acquired.handle);
106
+ return { ok: false, error: "unsafe", detail: acquired.handle.path };
107
+ }
108
+ // Preserve caller-owned unreadable/non-regular errors, but do not mistake a
109
+ // dangling link (realpath's fallback) or a fresh replacement for proven absence.
110
+ try {
111
+ const entry = lstatSync(current.canonical, { bigint: true });
112
+ if (entry.isSymbolicLink() || `${entry.dev}:${entry.ino}` !== current.inode) throw new ConfigWriteDestinationChanged();
113
+ } catch (error) {
114
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT" || current.inode !== null) throw error;
115
+ }
116
+ destinations.set(acquired.handle, current);
117
+ return acquired;
118
+ } catch (error) {
119
+ release(acquired.handle);
120
+ throw error;
121
+ }
101
122
  }
102
123
  export function withConfigWriteLock<T>(configPath: string, run: (handle: LockHandle) => T): ConfigWriteLockOutcome<T> {
103
124
  const acquired = acquire(configPath);
@@ -166,15 +187,21 @@ function canonicalFileIdentity(canonical: string): string | null {
166
187
  /** Capture before the first write; nested native children share the same recovery scope. */
167
188
  export function withConfigWriteRecovery<T>(
168
189
  configPath: string, held: LockHandle, run: () => T,
169
- options: { refreshBeforeSpawn?: boolean; preimage?: Buffer } = {},
190
+ options: { refreshBeforeSpawn?: boolean; preimage?: Buffer; requireMatchingPreimage?: boolean } = {},
170
191
  ): T {
171
- if (recoveries.has(held)) return run();
192
+ const nested = recoveries.get(held);
193
+ if (nested && !options.requireMatchingPreimage) return run();
172
194
  let canonical: string;
173
195
  try { canonical = assertConfigWriteDestination(configPath, held); }
174
196
  catch { throw new ConfigWriteDestinationChanged(); }
175
197
  const identity = canonicalFileIdentity(canonical);
176
198
  const preimage = identity === null ? null : readFileSync(canonical);
177
199
  if (canonicalFileIdentity(canonical) !== identity) throw new ConfigWriteDestinationChanged();
200
+ if (options.requireMatchingPreimage && (!options.preimage || !preimage?.equals(options.preimage))) {
201
+ throw new ConfigWriteDestinationChanged();
202
+ }
203
+ if (nested && (nested.canonical !== canonical || nested.identity !== identity)) throw new ConfigWriteDestinationChanged();
204
+ if (nested) return run();
178
205
  const recovery: ConfigWriteRecovery = {
179
206
  canonical, identity, preimage: options.preimage ?? preimage,
180
207
  refreshBeforeSpawn: options.refreshBeforeSpawn === true && options.preimage === undefined,
@@ -317,6 +344,38 @@ export function publishConfigWriteTarget<T>(
317
344
  });
318
345
  }
319
346
 
347
+ export function removeConfigWriteEntry(configPath: string, held: LockHandle, path: string): void {
348
+ assertConfigWriteDestination(configPath, held);
349
+ watchConfigWriteTargets(held, [path]);
350
+ const watched = targets.get(held)!;
351
+ const expected = watched.get(path)!;
352
+ const current = destination(path);
353
+ if (expected.canonical !== current.canonical || expected.inode !== current.inode
354
+ || current.canonical !== join(realpathSync.native(dirname(resolve(path))), basename(resolve(path)))) {
355
+ throw new Error("Codex artifact destination changed; files and recovery evidence were preserved.");
356
+ }
357
+ if (expected.inode === null) return;
358
+
359
+ const entry = join(realpathSync.native(dirname(resolve(path))), basename(resolve(path)));
360
+ const identity = (file: { dev: bigint; ino: bigint }) => `${file.dev}:${file.ino}`;
361
+ const before = lstatSync(entry, { bigint: true });
362
+ if (before.isSymbolicLink() || !before.isFile() || identity(before) !== expected.inode) {
363
+ throw new Error("Codex artifact destination changed; files and recovery evidence were preserved.");
364
+ }
365
+ assertConfigWriteDestination(configPath, held);
366
+ const now = lstatSync(entry, { bigint: true });
367
+ if (now.isSymbolicLink() || !now.isFile() || identity(now) !== expected.inode) {
368
+ throw new Error("Codex artifact destination changed; files and recovery evidence were preserved.");
369
+ }
370
+ unlinkSync(entry);
371
+ assertConfigWriteDestination(configPath, held);
372
+ const after = destination(path);
373
+ if (after.inode !== null || after.canonical !== expected.canonical) {
374
+ throw new Error("Codex artifact destination changed; recovery evidence was preserved.");
375
+ }
376
+ watched.set(path, after);
377
+ }
378
+
320
379
  export function configWriteLockFailureMessage(failure: Exclude<ConfigWriteAcquireResult, { ok: true }>): string {
321
380
  if (failure.error === "missing-parent") return `Codex configuration parent is missing or unresolved at ${failure.detail}; no files were changed.`;
322
381
  return failure.error === "unsafe"
@@ -1,4 +1,5 @@
1
1
  import { projectAntigravitySelectedModels } from "../providers/antigravity-effort-families";
2
+ import { configuredUltraFastSlugs } from "./catalog/configured-tiers";
2
3
  import { join } from "node:path";
3
4
  import { sameCatalogHealPath } from "./catalog/heal-observation";
4
5
 
@@ -371,6 +372,7 @@ function prepareCatalog(
371
372
  );
372
373
  const mergedModels = mergeCatalogEntriesFromObservedState({
373
374
  ultraFastTier: ultraFastTierEnabled(config),
375
+ configuredUltraFastSlugs: configuredUltraFastSlugs(ordered),
374
376
  modelPickerOrder,
375
377
  accountSelectors,
376
378
  catalogModels,