@bitkyc08/opencodex 2.83.0 → 2.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -30,6 +30,11 @@ export interface OcxProtocolsConfig {
30
30
  managedMessagesNativeOAuth?: boolean;
31
31
  /** Chat and Messages clients are encoded directly from adapter events. */
32
32
  directEncoders?: boolean;
33
+ /**
34
+ * Chat requests to a concrete Google Antigravity route decode straight into the IR and are
35
+ * answered through the direct encoders, with no internal Responses body in either direction.
36
+ */
37
+ antigravityDirect?: boolean;
33
38
  /** Compare the dispatch plan with the observed path; never sends a second request. */
34
39
  shadowPlan?: boolean;
35
40
  };
@@ -519,6 +524,11 @@ export interface OcxConfig {
519
524
  skills?: OcxSkillsConfig;
520
525
  /** Opt-in process-local aggregate request metrics on the authenticated management plane. */
521
526
  metricsExport?: { enabled?: boolean };
527
+ /**
528
+ * Opt-in recovery of a managed service proxy that stays alive but stops answering /healthz:
529
+ * a separate sentinel process terminates it so its supervisor restarts it (#6671).
530
+ */
531
+ serviceHealthRecovery?: { enabled?: boolean };
522
532
  /** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
523
533
  emptyCompletionRetry?: boolean;
524
534
  /** Suppress allowlisted client-facing Codex transport hints; provider enforcement is unchanged. */
@@ -576,12 +586,13 @@ export interface OcxConfig {
576
586
  /**
577
587
  * Opt-in Ultra Fast service tier, default off.
578
588
  *
579
- * This does NOT synthesize an `ultrafast` row: `src/codex/data/upstream-models.json`
580
- * advertises only `priority`, and PR #2994 was closed precisely because a catalog row
581
- * the wire cannot honor is a picker entry that lies. What the flag turns on is honesty
582
- * about a tier the operator supplies themselves — the catalog stops stripping an
583
- * `ultrafast` the user configured, and the request path names it instead of recording
584
- * "no fast tier was requested".
589
+ * On native GPT-6 Astra and GPT-6.1 Sol rows (bare and account-bound), the flag adds the
590
+ * Ultrafast speed choice OpenAI documents for those two models, so the Codex picker offers
591
+ * it even though `model_catalog_json` replaces ChatGPT's live, plan-aware model list
592
+ * (`src/codex/catalog/native-ultrafast.ts`). On routed rows it still never invents a tier:
593
+ * it keeps an `ultrafast` the operator configured from being stripped on regeneration
594
+ * (PR #2994 was closed for adding one the wire could not honor). Turning it off withdraws
595
+ * the generated native tier on the next catalog write.
585
596
  */
586
597
  ultraFastTier?: boolean;
587
598
  /**
@@ -594,6 +605,13 @@ export interface OcxConfig {
594
605
  codexMainAccountHardLock?: boolean;
595
606
  /** Per-window percentages: short defaults to 90, long to 98; integers 80..100, short <= long. */
596
607
  codexMainAccountHardLockThresholds?: { short?: number; long?: number };
608
+ /**
609
+ * Who refreshes the native main ChatGPT login in CODEX_HOME (#2999). "codex" asks the Codex
610
+ * binary through app-server `getAuthStatus` and never writes auth.json; "opencodex" keeps the
611
+ * guarded writer. Absent means "codex" when a Codex binary at or above the supported version
612
+ * is found, otherwise "opencodex". "codex" also falls back when no such binary exists.
613
+ */
614
+ nativeMainRefresh?: "codex" | "opencodex";
597
615
  /** Explicit top-level deletion intent used by stale whole-config rebases. */
598
616
  configRebaseProvenance?: OcxConfigRebaseProvenance | Record<string, unknown>;
599
617
  /** OpenAI provider-contract migration marker (v2 = single `openai` provider with account mode). */
@@ -765,6 +783,8 @@ export interface OcxConfig {
765
783
  };
766
784
  /** Opt-in failure-only recovery; never replaces the initial compaction model. */
767
785
  compactionRecovery?: { enabled: boolean; model: string; allowDevinInvalidArgument?: boolean };
786
+ /** Explicit opt-in; resumes Observe only using an existing trusted certificate and endpoints. */
787
+ desktopCompatibility?: { startOnProxyStart: boolean };
768
788
  /**
769
789
  * Destination model for Codex's own memory pipeline, per phase
770
790
  * (src/server/responses/memory-models.ts).
@@ -1023,6 +1043,16 @@ export interface OcxConfig {
1023
1043
  cacheRetention?: "none" | "short" | "long";
1024
1044
  /** Web-search sidecar: route web_search for non-OpenAI models through a gpt-mini via ChatGPT passthrough. */
1025
1045
  webSearchSidecar?: OcxWebSearchSidecarConfig;
1046
+ /**
1047
+ * Advisor sidecar: an OpenCodex-owned expert consultation runtime. The proxy injects a synthetic
1048
+ * `advisor` tool into routed worker turns, executes the configured expert model itself (loopback
1049
+ * through the normal routing authority, so the advisor may be ANY routable provider/model), and
1050
+ * reinjects the advice so the original worker continues. `policy: "preflight"` additionally
1051
+ * attempts one automatic consultation per task without worker cooperation, once the task has
1052
+ * produced orientation evidence. Task context is sent only after
1053
+ * `contextSharingConsent` is the current version; `enabled` does not grant that consent.
1054
+ */
1055
+ advisor?: OcxAdvisorConfig;
1026
1056
  /** Vision sidecar: describe images via a gpt vision model so text-only models can "see" them. */
1027
1057
  visionSidecar?: OcxVisionSidecarConfig;
1028
1058
  /** /v1/images relay for codex's built-in image_gen tool. */
@@ -1547,6 +1577,38 @@ export interface OcxVisionSidecarConfig {
1547
1577
  timeoutMs?: number;
1548
1578
  }
1549
1579
 
1580
+ /**
1581
+ * Advisor sidecar configuration. Kept deliberately small for PR1: no adaptive trigger knobs
1582
+ * (failedValidations / noProgressCycles / semanticClassifier) — those belong to a later PR.
1583
+ */
1584
+ export interface OcxAdvisorConfig {
1585
+ /** Master switch. Default: false — the advisor stays off the request path entirely. */
1586
+ enabled?: boolean;
1587
+ /**
1588
+ * The expert model consulted by the sidecar. Any model string the routing authority accepts:
1589
+ * a bare native model ("gpt-6-astra"), an explicit "provider/model" ("anthropic/claude-...",
1590
+ * "xai/grok-..."), or an account-qualified native model ("<namespace>/gpt-6-astra").
1591
+ */
1592
+ model?: string;
1593
+ /** Reasoning effort for the advisor call. Validated against the canonical effort ladder. */
1594
+ effort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
1595
+ /**
1596
+ * When the advisor is consulted. "manual" (default): only when the worker explicitly calls the
1597
+ * synthetic `advisor` tool. "preflight": OpenCodex additionally attempts one automatic
1598
+ * consultation per task once the task has produced orientation evidence (an assistant tool call
1599
+ * or a tool result after the latest user message).
1600
+ */
1601
+ policy?: "manual" | "preflight";
1602
+ /** Advisor fetch timeout (ms). Default 120000. */
1603
+ timeoutMs?: number;
1604
+ /**
1605
+ * Operator consent to send task conversation and tool results to the configured Advisor
1606
+ * provider. Only `"v1"` is current. Absent or any other value means the runtime must not
1607
+ * send task context. `enabled: true` does not grant this, and upgrades do not write it.
1608
+ */
1609
+ contextSharingConsent?: "v1";
1610
+ }
1611
+
1550
1612
  export interface OcxWebSearchSidecarConfig {
1551
1613
  /** Explicit Anthropic account pool; unset inherits the request's instance. */
1552
1614
  anthropicInstance?: AnthropicInstanceId;
@@ -622,6 +622,11 @@ export interface OcxProviderConfig {
622
622
  contextWindow?: number;
623
623
  /** Per-model fallback when context metadata is absent; otherwise caps the reported window. */
624
624
  modelContextWindows?: Record<string, number>;
625
+ /**
626
+ * Custom providers only, default off: fill a missing context window from bundled model-ID
627
+ * metadata before the Context cap / 128k fallback. An estimate, not verified relay capacity.
628
+ */
629
+ inferContextWindow?: boolean;
625
630
  /** Per-model Copilot upstream tier; only the github-copilot route sends it. */
626
631
  modelContextTiers?: Record<string, "default" | "long_context">;
627
632
  /** Model-specific Codex catalog input modalities, e.g. ["text"] or ["text", "image"]. */
@@ -639,6 +644,8 @@ export interface OcxProviderConfig {
639
644
  * Responses `max_output_tokens`. Adapters still let an explicit request win.
640
645
  */
641
646
  defaultMaxOutputTokens?: number;
647
+ /** Opt-in Azure Responses floor for explicit positive output budgets; may raise the caller cap. */
648
+ minMaxOutputTokens?: number;
642
649
  /** Model-specific fallback output token budgets. Exact/model-pattern entries beat the provider default. */
643
650
  modelMaxOutputTokens?: Record<string, number>;
644
651
  /**
@@ -164,6 +164,19 @@ export interface OcxParsedRequest {
164
164
  * provider-private continuation caches again on every later turn.
165
165
  */
166
166
  _contextCompactionBoundary?: boolean;
167
+ /**
168
+ * Core-owned registration slot for the optional advisor subsystem (src/advisor). Set per request
169
+ * by the sidecar planner, never at module load: the core Responses path only sees this
170
+ * structural shape, so an advisor-disabled install imports no advisor module. When present, the
171
+ * adapter delivery wraps the worker's event stream with it — the guard holds synthetic
172
+ * `advisor` tool calls, consults the configured expert model, and re-dispatches the worker.
173
+ */
174
+ _advisorGuard?: (options: {
175
+ parsed: OcxParsedRequest;
176
+ firstEvents: AsyncIterable<AdapterEvent>;
177
+ /** One bounded worker continuation re-dispatch (same machinery as the terminal guard). */
178
+ continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
179
+ }) => AsyncGenerator<AdapterEvent>;
167
180
  }
168
181
 
169
182
  export interface OcxContext {
@@ -441,6 +454,8 @@ export type AdapterEvent =
441
454
  /** Responses error type and code when the adapter has a structured provider failure. */
442
455
  errorType?: string;
443
456
  code?: string;
457
+ /** Proxy-internal permission to consider API-key recovery; never serialized. */
458
+ apiKeyRecovery?: true;
444
459
  retryable?: boolean;
445
460
  /**
446
461
  * Proxy-internal, never serialized: the upstream failed the turn before producing text or a
@@ -25,6 +25,8 @@ export interface OcxTool {
25
25
  imageGeneration?: boolean;
26
26
  /** Synthetic video_gen tool: executed by the xAI video bridge sidecar. */
27
27
  videoGeneration?: boolean;
28
+ /** Synthetic advisor tool: the model's call is intercepted by the advisor sidecar (see src/advisor), never relayed to the client. */
29
+ advisor?: boolean;
28
30
  }
29
31
 
30
32
  /**
@@ -51,7 +51,7 @@ export async function latestVersionAsync(
51
51
  installer: Installer,
52
52
  deps: AsyncLookupDeps = defaultDeps,
53
53
  ): Promise<string | null> {
54
- if (installer === "source" || installer === "mise") return null;
54
+ if (installer === "source" || installer === "standalone" || installer === "mise") return null;
55
55
  let owner: PnpmGlobalOwner | null | undefined;
56
56
  try { owner = installer === "pnpm" ? await deps.ownerFn() : undefined; }
57
57
  catch { return null; }
@@ -44,11 +44,11 @@ export function readUpdateBadge(deps: UpdateBadgeDeps = defaultDeps): UpdateBadg
44
44
  latestVersion: null,
45
45
  channel,
46
46
  installer,
47
- canUpdate: installer !== "source" && installer !== "mise",
47
+ canUpdate: installer !== "source" && installer !== "standalone" && installer !== "mise",
48
48
  unknown: true,
49
49
  };
50
50
  // A source checkout has nothing to compare against, so "unknown" is not useful there.
51
- if (installer === "source" || current === "?" || isSourceBuildVersion(current)) {
51
+ if (installer === "source" || installer === "standalone" || current === "?" || isSourceBuildVersion(current)) {
52
52
  return { ...base, canUpdate: false, unknown: false };
53
53
  }
54
54
 
@@ -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
+ }