@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
@@ -17,13 +17,10 @@ import { unprivilegedOwnershipMutationEnvironment } from "../service/ownership-m
17
17
  import { withUpdateOwnershipLease, readUpdateRuntimeTarget } from "./ownership-transaction";
18
18
  import { npmInvocation } from "./npm-invocation.mjs";
19
19
  import { pnpmInvocation, pnpmInvocationForPath, resolvePnpmCommands } from "./pnpm-invocation.mjs";
20
- import {
21
- detectInstallFromPath,
22
- detectInstallOwnershipFromPath,
23
- } from "./install-detection.mjs";
20
+ import { detectInstallOwnershipFromPath } from "./install-detection.mjs";
24
21
  import type {
25
22
  DetectedInstall,
26
- InstallOwnership,
23
+ InstallOwnership as PackageInstallOwnership,
27
24
  MiseInstallOwner,
28
25
  } from "./install-detection.d.mts";
29
26
  import {
@@ -42,8 +39,13 @@ import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "./tray-updat
42
39
  import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
43
40
  import { withoutSiblingMarker } from "../codex/sibling-start";
44
41
  import { packageVersion } from "../lib/package-version";
42
+ import { isStandaloneModuleUrl } from "../lib/standalone";
45
43
  import { selfLaunchArgv } from "../lib/self-launch-argv";
46
44
  import { PNPM_READ_CWD, withPnpmCommandCwd, pnpmReadEnvironment } from "./pnpm-read-policy.mjs";
45
+ import {
46
+ freshServingPid, hasRestartableServiceManager, readParentPid, readServiceManagerPid, restartServiceManagerJob,
47
+ supervisionVerdict, unsettledManagerWarning, unsupervisedRuntimeWarning,
48
+ } from "./supervised-recovery.mjs";
47
49
 
48
50
  /**
49
51
  * A `codex-history-backup-*.json` surviving a stop means the native-history restore was
@@ -62,18 +64,21 @@ export function historyRestoreIncomplete(configDir = getConfigDir()): boolean {
62
64
  export const PKG = "@bitkyc08/opencodex";
63
65
  const HERE = dirname(fileURLToPath(import.meta.url)); // .../opencodex/src/update
64
66
 
65
- export type Installer = DetectedInstall;
67
+ export type Installer = DetectedInstall | "standalone";
68
+ export const STANDALONE_UPDATE_HINT = "Update via OpenCodex Desktop (Check for Updates…) or reinstall the standalone binary from GitHub Releases.";
66
69
  export type Channel = "latest" | "preview";
67
- export type { InstallOwnership, MiseInstallOwner };
70
+ export type InstallOwnership = PackageInstallOwnership | { installer: "standalone" };
71
+ export type { MiseInstallOwner };
68
72
 
69
73
  /** Infer how opencodex is installed from the running module's path. */
70
- export function detectInstall(): Installer {
71
- return detectInstallFromPath(HERE, { exists: existsSync });
74
+ export function detectInstall(moduleUrl = import.meta.url): Installer {
75
+ return detectInstallOwnership(moduleUrl).installer;
72
76
  }
73
77
 
74
78
  /** Resolve installer ownership and verified mise update guidance for this package. */
75
- export function detectInstallOwnership(): InstallOwnership {
76
- return detectInstallOwnershipFromPath(HERE, { exists: existsSync });
79
+ export function detectInstallOwnership(moduleUrl = import.meta.url): InstallOwnership {
80
+ if (isStandaloneModuleUrl(moduleUrl)) return { installer: "standalone" };
81
+ return detectInstallOwnershipFromPath(dirname(fileURLToPath(moduleUrl)), { exists: existsSync });
77
82
  }
78
83
 
79
84
  export function miseUpdateCommand(
@@ -234,6 +239,7 @@ export function registrySpawnTarget(
234
239
  args: readonly string[],
235
240
  owner?: PnpmGlobalOwner,
236
241
  ): SpawnTarget | null {
242
+ if (installer === "standalone") return null;
237
243
  // A pnpm command without an owner would silently fall back to the first PATH
238
244
  // candidate. That is unsafe when multiple PNPM_HOME installations expose the
239
245
  // same version, so registry queries use the same hard binding as mutation.
@@ -310,6 +316,7 @@ export function latestVersion(
310
316
 
311
317
  /** The global-install command opencodex would run to update on this channel. */
312
318
  export function updateCommand(installer: Installer, tag: Channel, resolvedVersion?: string | null): { bin: string; args: string[] } {
319
+ if (installer === "standalone") throw new Error(STANDALONE_UPDATE_HINT);
313
320
  if (installer === "mise") {
314
321
  throw new Error("mise-owned installations must be upgraded through mise");
315
322
  }
@@ -427,6 +434,12 @@ export async function runUpdate(): Promise<void> {
427
434
  return;
428
435
  }
429
436
 
437
+ if (installer === "standalone") {
438
+ console.error(STANDALONE_UPDATE_HINT);
439
+ process.exitCode = 1;
440
+ return;
441
+ }
442
+
430
443
  if (installer === "source") {
431
444
  console.log("Running from a source checkout — update with: git pull && bun install");
432
445
  return;
@@ -551,7 +564,8 @@ export async function runUpdate(): Promise<void> {
551
564
  return before?.owner === after?.owner && before?.installId === after?.installId
552
565
  && before?.consentGeneration === after?.consentGeneration;
553
566
  };
554
- const startProxyDirectly = async (): Promise<boolean> => {
567
+ /** `"vetoed"`: another owner holds the runtime, so not starting is the correct outcome. */
568
+ const startProxyDirectly = async (): Promise<boolean | "vetoed"> => {
555
569
  const ownership = await resolvedRuntimeOwnership();
556
570
  const supervision = observeSupervision();
557
571
  const plan = planUpdateRuntimeHandling({ ...ownership, serviceInstalled: false, supervision });
@@ -559,7 +573,7 @@ export async function runUpdate(): Promise<void> {
559
573
  console.warn(supervision
560
574
  ? "OpenCodex Desktop supervises the proxy; no CLI runtime was restored."
561
575
  : plan.notice);
562
- return false;
576
+ return "vetoed";
563
577
  }
564
578
  if (!postUpdateLauncherUsable || !existsSync(postUpdateLauncher)) return false;
565
579
  // An ordinary owner: a stray sibling marker would otherwise mark it before any probe.
@@ -582,6 +596,51 @@ export async function runUpdate(): Promise<void> {
582
596
  console.warn("⚠️ Recovery did not report a healthy proxy before its deadline; run 'ocx service repair' or 'ocx start'.");
583
597
  return false;
584
598
  };
599
+ // #7043: before any detached start, ask launchd/systemd to restart the job it already holds,
600
+ // and count it only when the serving proxy is that job. Runs unleased: the manager's child
601
+ // cannot join this lease (#5760). `"unsettled"` means the manager may still finish the restart,
602
+ // so no competing proxy may be started.
603
+ const restartThroughServiceManager = async (): Promise<"supervised" | "failed" | "unsettled"> => {
604
+ if (!hasRestartableServiceManager()) return "failed";
605
+ console.log("🔁 Asking the service manager to restart the background service...");
606
+ const before = readUpdateRuntimeTarget(getRuntimePortPath(), capturedListen.hostname);
607
+ const staleRecordPid = before.kind === "target" ? before.target.pid : null;
608
+ const restarted = restartServiceManagerJob();
609
+ const unsettled = !restarted.ok && restarted.unsettled === true;
610
+ let verdict: ReturnType<typeof supervisionVerdict> | { kind: "failed"; reason: string };
611
+ if (!restarted.ok && !unsettled) {
612
+ verdict = { kind: "failed", reason: restarted.reason };
613
+ } else {
614
+ let servingPid: number | null = null;
615
+ const deadline = Date.now() + 30_000;
616
+ while (servingPid === null && Date.now() < deadline) {
617
+ const current = readUpdateRuntimeTarget(getRuntimePortPath(), capturedListen.hostname);
618
+ if (current.kind === "target" && current.target.port === capturedListen.port
619
+ && freshServingPid(current.target.pid, staleRecordPid) !== null
620
+ && await proxyIdentityAt(current.target.port, { hostname: current.target.hostname, expectedPid: current.target.pid },
621
+ { timeoutMs: 1500, deadlineAt: deadline, attempts: 1 })) servingPid = current.target.pid;
622
+ else await Bun.sleep(Math.min(100, Math.max(0, deadline - Date.now())));
623
+ }
624
+ verdict = supervisionVerdict({ servingPid, managerPid: readServiceManagerPid(), parentPid: pid => readParentPid(pid) });
625
+ }
626
+ if (verdict.kind === "supervised") {
627
+ console.log(`✅ Proxy is healthy on port ${capturedListen.port} under the service manager (pid ${verdict.managerPid}).`);
628
+ return "supervised";
629
+ }
630
+ if (unsettled) {
631
+ console.warn(unsettledManagerWarning(capturedListen.port));
632
+ return "unsettled";
633
+ }
634
+ console.warn(`⚠️ The service manager did not recover a supervised proxy: ${verdict.reason}.`);
635
+ return "failed";
636
+ };
637
+ /** Direct start as the last resort for a managed install: never reported as full recovery. */
638
+ const startUnsupervisedFallback = async (): Promise<"vetoed" | "started" | "failed"> => {
639
+ const started = await startProxyDirectly();
640
+ if (started === "vetoed") return "vetoed";
641
+ if (started) console.warn(unsupervisedRuntimeWarning(capturedListen.port));
642
+ return started ? "started" : "failed";
643
+ };
585
644
  const recoverStoppedRuntime = async (reason: string): Promise<void> => {
586
645
  try {
587
646
  const planRecovery = async () => {
@@ -617,6 +676,12 @@ export async function runUpdate(): Promise<void> {
617
676
  env: mutation.controlEnvironment({ ...process.env, OCX_BAKE_PORT: String(capturedListen.port) }),
618
677
  });
619
678
  if (service.status !== 0 || !isServiceViable()) {
679
+ // Ownership may have moved while the refresh ran unleased; only a recovery plan that
680
+ // still names the service may restart the manager's job.
681
+ if ((await planRecovery()).action === "service") {
682
+ const managed = await restartThroughServiceManager();
683
+ if (managed !== "failed") return;
684
+ }
620
685
  // The refresh ran unleased; a claim could have landed meanwhile, so take
621
686
  // the lease back before re-reading ownership and authorizing a direct start.
622
687
  mutation.reacquire();
@@ -626,7 +691,7 @@ export async function runUpdate(): Promise<void> {
626
691
  liveness: currentPackageRuntimeLiveness(), serviceInstalled: false,
627
692
  launcherUsable: postUpdateLauncherUsable && existsSync(postUpdateLauncher), hadRuntimeState,
628
693
  });
629
- if (fallback.action === "direct") await startProxyDirectly();
694
+ if (fallback.action === "direct") await startUnsupervisedFallback();
630
695
  else console.warn("⚠️ Service recovery was not confirmed; no second proxy was started.");
631
696
  }
632
697
  } else if (recovery.action === "direct") {
@@ -902,6 +967,12 @@ export async function runUpdate(): Promise<void> {
902
967
  }
903
968
  }
904
969
  if (!serviceRefreshed || !serviceViable) {
970
+ // #7043: the manager first. Only a proxy it owns counts as recovered; everything
971
+ // below this line leaves an unsupervised runtime and exits non-zero.
972
+ if (!await mayRefresh()) return;
973
+ const managed = await restartThroughServiceManager();
974
+ if (managed === "supervised") return;
975
+ if (managed === "unsettled") return 1;
905
976
  // Repair normally avoids elevation for a healthy scheduler task, but a stale
906
977
  // definition may require guarded create/elevation. It can also fail — or exit 0
907
978
  // while leaving stale/missing assets that never start
@@ -956,7 +1027,9 @@ export async function runUpdate(): Promise<void> {
956
1027
  console.warn(process.platform === "win32"
957
1028
  ? " Run 'ocx service repair' to refresh the background service."
958
1029
  : " Run 'ocx service repair' to refresh the background service and see why it failed.");
959
- if (await startProxyDirectly()) console.log(`✅ Proxy is healthy on port ${capturedListen.port}.`);
1030
+ // An ownership veto is the correct outcome, not a degraded one.
1031
+ if (await startUnsupervisedFallback() === "vetoed") return;
1032
+ return 1;
960
1033
  }
961
1034
  }
962
1035
  } finally {
@@ -0,0 +1,107 @@
1
+ import { readRuntimePort } from "../config/process-state";
2
+ import { proxyIdentityAt } from "../server/proxy-liveness";
3
+ import {
4
+ freshServingPid, hasRestartableServiceManager, readParentPid, readServiceManagerPid, restartServiceManagerJob,
5
+ supervisionVerdict, unsettledManagerWarning, unsupervisedRuntimeWarning,
6
+ } from "./supervised-recovery.mjs";
7
+
8
+ /**
9
+ * Manager-first recovery for the dashboard update worker (#7043), the `src/update/job.ts` side of
10
+ * what #7074 did for `ocx update`. When the post-update service refresh fails, is not viable, or
11
+ * never serves, the worker asks launchd/systemd to restart the job it already holds before any
12
+ * detached `ocx start`, and counts it only when the serving proxy is that job. It lives beside the
13
+ * worker because `src/update/job.ts` sits at the repository's size cap.
14
+ */
15
+ export type ManagerRestartOutcome = "supervised" | "failed" | "unsettled";
16
+
17
+ export interface ManagerRestartDeps {
18
+ hasManager?: () => boolean;
19
+ restartJob?: () => { ok: true } | { ok: false; reason: string; unsettled?: true };
20
+ managerPid?: () => number | null;
21
+ parentPid?: (pid: number) => number | null;
22
+ readRecord?: () => { pid: number; port: number; hostname?: string } | null;
23
+ probeServing?: (port: number, hostname: string, pid: number, deadlineAt: number) => Promise<boolean>;
24
+ now?: () => number;
25
+ sleepMs?: (ms: number) => Promise<void>;
26
+ deadlineMs?: number;
27
+ }
28
+
29
+ export type ManagerRestartFn = (
30
+ target: { port: number; hostname: string; release?: () => void },
31
+ log: (line: string) => void,
32
+ ) => Promise<ManagerRestartOutcome>;
33
+
34
+ /**
35
+ * Ask the manager to restart its job and prove the serving proxy is that job. Never throws.
36
+ * `release` frees the update lease first: the manager's child cannot join it (#5760), and the
37
+ * worker reaches this point without a release when it skipped the service repair.
38
+ */
39
+ export async function restartServiceManagerAfterUpdate(
40
+ target: { port: number; hostname: string; release?: () => void },
41
+ log: (line: string) => void,
42
+ deps: ManagerRestartDeps = {},
43
+ ): Promise<ManagerRestartOutcome> {
44
+ if (!(deps.hasManager ?? hasRestartableServiceManager)()) return "failed";
45
+ target.release?.();
46
+ log("Asking the service manager to restart the background service...");
47
+ const readRecord = deps.readRecord ?? (() => readRuntimePort());
48
+ const probe = deps.probeServing ?? (async (port: number, hostname: string, pid: number, deadlineAt: number) => (
49
+ !!(await proxyIdentityAt(port, { hostname, expectedPid: pid }, { timeoutMs: 1500, deadlineAt, attempts: 1 }))
50
+ ));
51
+ const now = deps.now ?? (() => Date.now());
52
+ const sleep = deps.sleepMs ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
53
+ const staleRecordPid = readRecord()?.pid ?? null;
54
+ const restarted = (deps.restartJob ?? restartServiceManagerJob)();
55
+ const unsettled = !restarted.ok && restarted.unsettled === true;
56
+ let verdict: ReturnType<typeof supervisionVerdict>;
57
+ if (!restarted.ok && !unsettled) {
58
+ // The refusal reason is the manager's stderr: external text, so it never reaches the persisted job.
59
+ verdict = { kind: "failed", reason: "the service manager refused the restart" };
60
+ } else {
61
+ let servingPid: number | null = null;
62
+ const deadline = now() + (deps.deadlineMs ?? 30_000);
63
+ while (servingPid === null && now() < deadline) {
64
+ const current = readRecord();
65
+ if (current && current.port === target.port && freshServingPid(current.pid, staleRecordPid) !== null
66
+ && await probe(current.port, current.hostname ?? target.hostname, current.pid, deadline)) servingPid = current.pid;
67
+ else await sleep(Math.min(100, Math.max(0, deadline - now())));
68
+ }
69
+ verdict = supervisionVerdict({
70
+ servingPid,
71
+ managerPid: (deps.managerPid ?? readServiceManagerPid)(),
72
+ parentPid: deps.parentPid ?? (pid => readParentPid(pid)),
73
+ });
74
+ }
75
+ if (verdict.kind === "supervised") {
76
+ log(`Proxy is healthy on port ${target.port} under the service manager (pid ${verdict.managerPid}).`);
77
+ return "supervised";
78
+ }
79
+ if (unsettled) return "unsettled";
80
+ log(`The service manager did not recover a supervised proxy: ${verdict.reason}.`);
81
+ return "failed";
82
+ }
83
+
84
+ /** Job patch and log line when the manager may still finish its restart: no second proxy is started. */
85
+ export function unsettledManagerRestartFailure(port: number) {
86
+ const patch = {
87
+ status: "failed" as const,
88
+ restarted: false,
89
+ error: "the service manager did not finish restarting the proxy; no second proxy was started",
90
+ };
91
+ return [patch, oneLine(unsettledManagerWarning(port))] as const;
92
+ }
93
+
94
+ /** Job patch and log line when only a detached fallback ran for a service-managed install. */
95
+ export function unsupervisedRestartFailure(port: number) {
96
+ const patch = {
97
+ status: "failed" as const,
98
+ restarted: false,
99
+ error: `the proxy on port ${port} is running UNSUPERVISED after the update; run 'ocx service repair'`,
100
+ };
101
+ return [patch, oneLine(unsupervisedRuntimeWarning(port))] as const;
102
+ }
103
+
104
+ /** The job store withholds multi-line text as possible vendor output, so join the shared warning. */
105
+ function oneLine(text: string): string {
106
+ return text.split("\n").map(line => line.trim()).join(" ");
107
+ }
package/src/update/job.ts CHANGED
@@ -2,18 +2,8 @@ import { spawn, spawnSync, type ChildProcess } from "node:child_process";
2
2
  import { existsSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
3
3
  import { dirname, join, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import {
6
- atomicWriteFile,
7
- getConfigDir,
8
- loadConfig,
9
- } from "../config";
10
- import {
11
- readPid,
12
- readRuntimePort,
13
- removePid,
14
- removeRuntimePort,
15
- verifyPidIdentity,
16
- } from "../config/process-state";
5
+ import { atomicWriteFile, getConfigDir, loadConfig } from "../config";
6
+ import { readPid, readRuntimePort, removePid, removeRuntimePort, verifyPidIdentity } from "../config/process-state";
17
7
  import { isProcessAlive, killProxy } from "../lib/process-control";
18
8
  import { selfLaunchArgv } from "../lib/self-launch-argv";
19
9
  import { killWindowsSchedulerWrappers } from "../lib/windows-service-wrappers";
@@ -33,9 +23,10 @@ import {
33
23
  } from "../server/proxy-liveness";
34
24
  import { isServiceInstalled, isServiceViable, readServiceBackend, stopWindows } from "../service";
35
25
  import { runUpdateRestartWithOwnershipLease, type ServiceOwnershipResolution } from "./restart-ownership";
26
+ import { restartServiceManagerAfterUpdate, unsettledManagerRestartFailure, unsupervisedRestartFailure, type ManagerRestartFn } from "./job-supervised-restart";
36
27
  import {
37
28
  type Channel, type Installer,
38
- PKG,
29
+ PKG, STANDALONE_UPDATE_HINT,
39
30
  checkUpdatePackageIntegrity,
40
31
  currentVersion,
41
32
  defaultUpdateTag,
@@ -492,17 +483,17 @@ export function checkForUpdate(
492
483
  const ownership = deps.detectInstallOwnership?.();
493
484
  const installer = ownership?.installer ?? deps.detectInstall();
494
485
  const channel = requestedChannel ?? normalizeUpdateChannel(null, current);
495
- const latest = installer === "source" ? null : deps.latestVersion(channel);
486
+ const latest = installer === "source" || installer === "standalone" ? null : deps.latestVersion(channel);
496
487
  const updateAvailable = !!latest && isNewer(latest, current, channel);
497
488
  let reason: string | undefined;
498
- let command = installer === "source"
489
+ let command = installer === "standalone" ? STANDALONE_UPDATE_HINT : installer === "source"
499
490
  ? manualSourceCommand()
500
491
  : installer === "mise"
501
492
  ? (ownership && deps.miseUpdateCommand?.(ownership)) ?? ""
502
493
  : updateExecutionCommand(installer, channel).display;
503
494
 
504
- if (installer === "source") {
505
- reason = "source_checkout";
495
+ if (installer === "source" || installer === "standalone") {
496
+ reason = installer === "source" ? "source_checkout" : "standalone_install";
506
497
  } else if (installer === "mise") {
507
498
  reason = command ? "externally_managed" : "external_ownership_invalid";
508
499
  } else if (!latest) {
@@ -517,7 +508,7 @@ export function checkForUpdate(
517
508
  channel,
518
509
  installer,
519
510
  updateAvailable,
520
- canUpdate: installer !== "source" && installer !== "mise" && updateAvailable,
511
+ canUpdate: installer !== "source" && installer !== "standalone" && installer !== "mise" && updateAvailable,
521
512
  command,
522
513
  releaseNotesUrl: RELEASE_NOTES_URL,
523
514
  ...(reason ? { reason } : {}),
@@ -1005,6 +996,7 @@ export interface RestartIo {
1005
996
  releaseForServiceManagerFn?: () => void;
1006
997
  /** Re-acquire the lease for the direct-start fallthrough, then re-run the veto; a notice stops the restart. */
1007
998
  reacquireForDirectStartFn?: () => { readonly notice: string; readonly failed: boolean } | null;
999
+ restartServiceManagerFn?: ManagerRestartFn; // launchd/systemd restart before a managed direct fallback (#7043)
1008
1000
  probeProxy?: (port: number, hostname?: string) => Promise<boolean>;
1009
1001
  /** Richer /healthz read for update-correlated restart evidence (pid + version). */
1010
1002
  probeProxyIdentity?: (port: number, hostname?: string) => Promise<RestartProxyIdentity | null>;
@@ -1035,7 +1027,7 @@ export interface RestartIo {
1035
1027
  job: UpdateJobState,
1036
1028
  captured?: { port: number; hostname: string; oldPid?: number },
1037
1029
  io?: RestartIo,
1038
- ) => Promise<false | void>;
1030
+ ) => Promise<false | void | "unsupervised">;
1039
1031
  /**
1040
1032
  * PIDs currently LISTENing on the captured port. Used to widen the post-update
1041
1033
  * kill allowlist beyond the pre-update PID (Windows often leaves a respawned
@@ -1098,7 +1090,7 @@ async function restartAfterUpdate(
1098
1090
  job: UpdateJobState,
1099
1091
  captured?: { port: number; hostname: string; oldPid?: number },
1100
1092
  io: RestartIo = {},
1101
- ): Promise<false | void> {
1093
+ ): Promise<false | void | "unsupervised"> {
1102
1094
  const serviceInstalled = (io.serviceInstalledFn ?? isServiceInstalled)();
1103
1095
  const config = loadConfig();
1104
1096
  // Stop-first update already cleared pid/runtime state, so the pre-update capture is
@@ -1250,6 +1242,12 @@ async function restartAfterUpdate(
1250
1242
  }
1251
1243
  }
1252
1244
 
1245
+ // #7043: a managed install asks launchd/systemd first; only the manager's own job counts as recovered.
1246
+ const managed = !serviceInstalled ? "failed" : await (io.restartServiceManagerFn ?? restartServiceManagerAfterUpdate)(
1247
+ { port, hostname, release: io.releaseForServiceManagerFn }, line => updateJob(job, {}, line));
1248
+ if (managed === "supervised") return;
1249
+ if (managed === "unsettled") { updateJob(job, ...unsettledManagerRestartFailure(port)); return false; }
1250
+ const fallback = serviceInstalled ? "unsupervised" as const : undefined; // every exit below is unproven ownership
1253
1251
  // The service refresh ran outside the lease; take it back so the veto and the kills/start below stay serialized.
1254
1252
  const restartVeto = io.reacquireForDirectStartFn?.();
1255
1253
  if (restartVeto) {
@@ -1282,7 +1280,7 @@ async function restartAfterUpdate(
1282
1280
  );
1283
1281
  if (liveHolders.length > 0) {
1284
1282
  updateJob(job, {}, `Live holder(s) remain on port ${port}; not starting on another port. Retry 'ocx start --port ${port}'.`);
1285
- return;
1283
+ return fallback;
1286
1284
  }
1287
1285
  // Dead PIDs can still own LISTEN rows. SetTcpEntry needs elevation (rc 317 on a
1288
1286
  // normal update worker), so poll until netstat is empty and the start runtime can bind.
@@ -1306,7 +1304,7 @@ async function restartAfterUpdate(
1306
1304
  + `${formatPortHolders(port, listPids, verifyOcx, directAllow)} `
1307
1305
  + `Retry 'ocx start --port ${port}'.`,
1308
1306
  );
1309
- return;
1307
+ return fallback;
1310
1308
  }
1311
1309
  }
1312
1310
  }
@@ -1314,7 +1312,7 @@ async function restartAfterUpdate(
1314
1312
  // retries on missing /healthz after prepare + ghost-LISTEN clear.
1315
1313
  if (io.spawnStart) {
1316
1314
  io.spawnStart(job, job.installer, port, launcher);
1317
- return;
1315
+ return fallback;
1318
1316
  }
1319
1317
  const sleep = io.sleepMs ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
1320
1318
  const probe = io.probeProxy ?? (async (p: number, host?: string) => (
@@ -1334,7 +1332,7 @@ async function restartAfterUpdate(
1334
1332
  {},
1335
1333
  `Proxy already healthy on ${hostname}:${port} at ${expectedVersion}; skipping pinned start.`,
1336
1334
  );
1337
- return;
1335
+ return fallback;
1338
1336
  }
1339
1337
  }
1340
1338
  const attempts = 3;
@@ -1393,12 +1391,13 @@ async function restartAfterUpdate(
1393
1391
  child.once("close", retireChild);
1394
1392
  const healthDeadline = now() + perAttemptHealthMs;
1395
1393
  while (now() < healthDeadline) {
1396
- if (await probe(port, hostname)) return;
1394
+ if (await probe(port, hostname)) return fallback;
1397
1395
  await sleep(500);
1398
1396
  }
1399
1397
  }
1400
1398
  // Exhausted retries: do not leave a hung pinned-start child owning the port.
1401
1399
  killSpawnAttempt(lastChild);
1400
+ return fallback;
1402
1401
  }
1403
1402
 
1404
1403
  /** Compact listen-holder summary for update-job logs when reclaim fails. */
@@ -1454,7 +1453,7 @@ export function restartAfterUpdateForTests(
1454
1453
  job: UpdateJobState,
1455
1454
  captured: { port: number; hostname: string; oldPid?: number },
1456
1455
  io: RestartIo,
1457
- ): Promise<false | void> {
1456
+ ): Promise<false | void | "unsupervised"> {
1458
1457
  return restartAfterUpdate(job, captured, io);
1459
1458
  }
1460
1459
 
@@ -1729,12 +1728,13 @@ export async function finishGuiUpdateRestart(
1729
1728
  }
1730
1729
  }
1731
1730
  const restartFn = io.restartAfterUpdateFn ?? restartAfterUpdate;
1732
- if ((await restartFn(job, captured, io)) === false) return false;
1733
- if (!usesNodeLauncher(installer)) {
1734
- // Bun/source: health alone remains enough unless a richer identity probe is supplied.
1735
- if (!io.probeProxyIdentity) return confirmRestartedProxy(job, captured, io);
1736
- }
1737
- return confirmPackageManagerExplicitRestart(job, captured, io);
1731
+ const restarted = await restartFn(job, captured, io);
1732
+ if (restarted === false) return false;
1733
+ // Bun/source: health alone remains enough unless a richer identity probe is supplied.
1734
+ const confirm = !usesNodeLauncher(installer) && !io.probeProxyIdentity ? confirmRestartedProxy : confirmPackageManagerExplicitRestart;
1735
+ const confirmed = await confirm(job, captured, io);
1736
+ if (confirmed && restarted === "unsupervised") updateJob(job, ...unsupervisedRestartFailure(captured.port)); // #7043
1737
+ return confirmed && restarted !== "unsupervised";
1738
1738
  }
1739
1739
 
1740
1740
  /**
@@ -152,7 +152,7 @@ export function interactiveGuardOk(): boolean {
152
152
  */
153
153
  export function shouldConsider(): { channel: Channel; current: string } | null {
154
154
  const installer = detectInstall();
155
- if (installer === "source" || installer === "mise") return null;
155
+ if (installer === "source" || installer === "standalone" || installer === "mise") return null;
156
156
  const current = currentVersion();
157
157
  if (current === "?" || isSourceBuildVersion(current)) return null;
158
158
  if (!interactiveGuardOk()) return null;
@@ -95,12 +95,22 @@ export function resolveNpmCommand(
95
95
  return null;
96
96
  }
97
97
 
98
+ function shellEnvValue(env, name) {
99
+ // Copying process.env loses Windows' case-insensitive property access. Keep
100
+ // the existing exact-key precedence; do not choose between conflicting aliases.
101
+ if (env[name] !== undefined) return env[name];
102
+ const values = new Set(Object.entries(env)
103
+ .filter(([key, value]) => key.toLowerCase() === name.toLowerCase() && value !== undefined)
104
+ .map(([, value]) => value));
105
+ return values.size === 1 ? values.values().next().value : undefined;
106
+ }
107
+
98
108
  function systemCommandProcessor(env) {
99
- const systemRoot = env.SystemRoot ?? env.windir;
109
+ const systemRoot = shellEnvValue(env, "SystemRoot") ?? shellEnvValue(env, "windir");
100
110
  if (systemRoot && win32.isAbsolute(systemRoot)) {
101
111
  return win32.join(systemRoot, "System32", "cmd.exe");
102
112
  }
103
- const comSpec = env.ComSpec;
113
+ const comSpec = shellEnvValue(env, "ComSpec");
104
114
  return comSpec && win32.isAbsolute(comSpec) ? win32.resolve(comSpec) : null;
105
115
  }
106
116
 
@@ -53,7 +53,7 @@ export function createRefreshScheduler(deps: RefreshDeps = defaults) {
53
53
  const version = deps.current();
54
54
  const installer = deps.install();
55
55
  return !deps.disabled() && version !== "?" && !isSourceBuildVersion(version)
56
- && installer !== "source" && installer !== "mise";
56
+ && installer !== "source" && installer !== "standalone" && installer !== "mise";
57
57
  };
58
58
  const channel = () => defaultUpdateTag(deps.current());
59
59
  const stale = (tag: Channel) => {
@@ -123,7 +123,7 @@ export function createRefreshScheduler(deps: RefreshDeps = defaults) {
123
123
  },
124
124
  async check(tag: Channel): Promise<UpdateCheckResult> {
125
125
  const installer = deps.install();
126
- const latest = installer === "source" || installer === "mise" ? null : await lookup(tag, false);
126
+ const latest = installer === "source" || installer === "standalone" || installer === "mise" ? null : await lookup(tag, false);
127
127
  return checkForUpdate(tag, {
128
128
  currentVersion: deps.current,
129
129
  detectInstall: deps.install,
@@ -0,0 +1,27 @@
1
+ /** Declarations for the plain-ESM manager recovery shared with `bin/ocx.mjs` (#7043). */
2
+ export type ServiceManagerRun = (file: string, args: string[]) => { ok: boolean; stdout: string; stderr: string; timedOut?: boolean };
3
+ export interface ServiceManagerDeps {
4
+ platform?: NodeJS.Platform;
5
+ uid?: number;
6
+ run?: ServiceManagerRun;
7
+ }
8
+ export type SupervisionVerdict =
9
+ | { kind: "supervised"; managerPid: number; servingPid: number }
10
+ | { kind: "failed"; reason: string };
11
+ export declare const LAUNCHD_SERVICE_LABEL: string;
12
+ export declare const SYSTEMD_SERVICE_UNIT: string;
13
+ export declare function parseLaunchdJobPid(printOutput: string): number | null;
14
+ export declare function parseSystemdMainPid(showOutput: string): number | null;
15
+ export declare function hasRestartableServiceManager(deps?: ServiceManagerDeps): boolean;
16
+ export declare function restartServiceManagerJob(deps?: ServiceManagerDeps):
17
+ { ok: true } | { ok: false; reason: string; unsettled?: true };
18
+ export declare function freshServingPid(recordPid: number, staleRecordPid: number | null): number | null;
19
+ export declare function unsettledManagerWarning(port: number): string;
20
+ export declare function readServiceManagerPid(deps?: ServiceManagerDeps): number | null;
21
+ export declare function readParentPid(pid: number, deps?: ServiceManagerDeps): number | null;
22
+ export declare function supervisionVerdict(input: {
23
+ servingPid: number | null;
24
+ managerPid: number | null;
25
+ parentPid: (pid: number) => number | null;
26
+ }): SupervisionVerdict;
27
+ export declare function unsupervisedRuntimeWarning(port: number): string;