@iislee/opencodex 2.31.0 → 2.35.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 (261) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +99 -70
  3. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  4. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -1
  7. package/src/adapters/anthropic.ts +104 -15
  8. package/src/adapters/base.ts +29 -1
  9. package/src/adapters/command-code.ts +42 -6
  10. package/src/adapters/cursor/call-id.ts +44 -0
  11. package/src/adapters/cursor/catalog.ts +541 -0
  12. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  13. package/src/adapters/cursor/cursor-errors.ts +15 -0
  14. package/src/adapters/cursor/discovery.ts +69 -18
  15. package/src/adapters/cursor/effort-map.ts +79 -1
  16. package/src/adapters/cursor/envelope-echo.ts +290 -0
  17. package/src/adapters/cursor/live-models.ts +7 -2
  18. package/src/adapters/cursor/live-transport.ts +14 -0
  19. package/src/adapters/cursor/message-mapper.ts +4 -1
  20. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  21. package/src/adapters/cursor/native-exec-fs.ts +2 -2
  22. package/src/adapters/cursor/native-exec-network.ts +1 -1
  23. package/src/adapters/cursor/native-exec-shell.ts +5 -2
  24. package/src/adapters/cursor/native-exec.ts +20 -0
  25. package/src/adapters/cursor/protobuf-request.ts +68 -14
  26. package/src/adapters/cursor/request-builder.ts +40 -10
  27. package/src/adapters/cursor/tool-definitions.ts +14 -4
  28. package/src/adapters/cursor/tool-result-normalize.ts +28 -5
  29. package/src/adapters/cursor/types.ts +20 -0
  30. package/src/adapters/cursor.ts +209 -38
  31. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  32. package/src/adapters/google-antigravity-replay.ts +173 -25
  33. package/src/adapters/google-antigravity-wire.ts +5 -0
  34. package/src/adapters/google-errors.ts +44 -12
  35. package/src/adapters/google.ts +136 -24
  36. package/src/adapters/image.ts +1 -1
  37. package/src/adapters/kiro-constants.ts +27 -0
  38. package/src/adapters/kiro-tools.ts +55 -16
  39. package/src/adapters/kiro.ts +182 -23
  40. package/src/adapters/openai-chat.ts +300 -242
  41. package/src/adapters/openai-responses.ts +353 -27
  42. package/src/adapters/run-turn-queue.ts +36 -1
  43. package/src/adapters/tool-catalog-nudge.ts +4 -3
  44. package/src/adapters/xai-tool-schema.ts +436 -0
  45. package/src/adapters/xai-web-search.ts +17 -16
  46. package/src/bridge.ts +90 -37
  47. package/src/chat/inbound.ts +14 -1
  48. package/src/chat/outbound.ts +15 -7
  49. package/src/claude/agents-inject.ts +8 -1
  50. package/src/claude/context-windows.ts +16 -9
  51. package/src/claude/outbound.ts +24 -11
  52. package/src/cli/access.ts +46 -3
  53. package/src/cli/account-api.ts +116 -19
  54. package/src/cli/account-extended.ts +262 -29
  55. package/src/cli/account-main.ts +12 -12
  56. package/src/cli/account.ts +69 -15
  57. package/src/cli/agent.ts +8 -1
  58. package/src/cli/alias.ts +66 -0
  59. package/src/cli/capabilities-command.ts +94 -0
  60. package/src/cli/capabilities.ts +496 -0
  61. package/src/cli/claude-desktop.ts +31 -11
  62. package/src/cli/claude.ts +26 -1
  63. package/src/cli/dispatch.ts +256 -28
  64. package/src/cli/doctor.ts +126 -14
  65. package/src/cli/help.ts +17 -6
  66. package/src/cli/index.ts +39 -9
  67. package/src/cli/init.ts +1 -0
  68. package/src/cli/inspect.ts +230 -0
  69. package/src/cli/models-runtime.ts +95 -0
  70. package/src/cli/models.ts +26 -10
  71. package/src/cli/observe.ts +31 -8
  72. package/src/cli/provider-runtime.ts +16 -2
  73. package/src/cli/provider.ts +2 -1
  74. package/src/cli/registry.ts +49 -10
  75. package/src/cli/runtime-api.ts +51 -7
  76. package/src/cli/status.ts +41 -1
  77. package/src/cli/storage.ts +234 -0
  78. package/src/cli/system-command.ts +16 -0
  79. package/src/cli/system-restart-client.ts +1 -1
  80. package/src/cli/usage-report.ts +184 -0
  81. package/src/cli/v2.ts +34 -10
  82. package/src/cli/version-skew.ts +46 -0
  83. package/src/clients/config-export.ts +85 -3
  84. package/src/clients/effective-status.ts +30 -2
  85. package/src/clients/probes/cline.ts +81 -0
  86. package/src/codex/account-label.ts +21 -0
  87. package/src/codex/account-pause.ts +2 -1
  88. package/src/codex/account-priority.ts +3 -2
  89. package/src/codex/app-server-processes.ts +83 -7
  90. package/src/codex/auth-api.ts +50 -10
  91. package/src/codex/auth-context.ts +21 -18
  92. package/src/codex/autostart-health.ts +16 -0
  93. package/src/codex/catalog/aggregation.ts +18 -0
  94. package/src/codex/catalog/effort.ts +18 -3
  95. package/src/codex/catalog/metadata.ts +27 -1
  96. package/src/codex/catalog/native-models.ts +5 -2
  97. package/src/codex/catalog/parsing.ts +72 -27
  98. package/src/codex/catalog/provider-fetch.ts +187 -31
  99. package/src/codex/catalog/sync.ts +131 -5
  100. package/src/codex/catalog.ts +1 -1
  101. package/src/codex/codex-write-lock.ts +3 -1
  102. package/src/codex/convergence-types.ts +1 -1
  103. package/src/codex/convergence.ts +27 -2
  104. package/src/codex/desired-state.ts +2 -2
  105. package/src/codex/desktop-app-restart.ts +355 -0
  106. package/src/codex/history-job.ts +32 -3
  107. package/src/codex/history-manifest.ts +112 -0
  108. package/src/codex/history-migration-guardian.ts +5 -5
  109. package/src/codex/history-provider.ts +825 -247
  110. package/src/codex/history-worker.ts +8 -5
  111. package/src/codex/inject-coordination.ts +83 -0
  112. package/src/codex/inject.ts +63 -22
  113. package/src/codex/injected-marker.ts +1 -1
  114. package/src/codex/internal/history-writer.ts +4 -3
  115. package/src/codex/log-guard/inspect.ts +22 -4
  116. package/src/codex/model-entitlements.ts +9 -2
  117. package/src/codex/native-profile-startup.ts +157 -27
  118. package/src/codex/native-residue.ts +26 -33
  119. package/src/codex/prompt-layers.ts +371 -25
  120. package/src/codex/prompt-text-probe.ts +238 -0
  121. package/src/codex/quota.ts +91 -10
  122. package/src/codex/routing.ts +9 -0
  123. package/src/codex/shim.ts +56 -3
  124. package/src/codex/subagent-model-fallback.ts +104 -25
  125. package/src/codex/transition-state.ts +116 -8
  126. package/src/combos/failover.ts +27 -0
  127. package/src/combos/types.ts +10 -0
  128. package/src/compatibility/index.ts +26 -0
  129. package/src/compatibility/manifest.ts +253 -0
  130. package/src/compatibility/openai-responses.ts +121 -0
  131. package/src/config/atomic-write.ts +219 -0
  132. package/src/config/paths.ts +40 -0
  133. package/src/config/process-state.ts +309 -0
  134. package/src/config/provider-validation.ts +177 -0
  135. package/src/config/rebase-provenance.ts +68 -0
  136. package/src/config.ts +206 -824
  137. package/src/generated/compatibility-version.json +373 -213
  138. package/src/images/loop.ts +5 -4
  139. package/src/images/plan.ts +5 -4
  140. package/src/integrations/ownership-policy.ts +160 -0
  141. package/src/integrations/ownership.ts +46 -2
  142. package/src/integrations/registry.ts +7 -0
  143. package/src/integrations/state.ts +79 -7
  144. package/src/integrations/writer.ts +26 -2
  145. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  146. package/src/lab/fabric/producer-child.ts +1 -1
  147. package/src/lib/admin-secrets.ts +24 -0
  148. package/src/lib/bounded-body.ts +14 -2
  149. package/src/lib/config-ownership.ts +20 -0
  150. package/src/lib/errors.ts +36 -3
  151. package/src/lib/package-tree-integrity.ts +101 -0
  152. package/src/lib/process-control.ts +2 -1
  153. package/src/lib/service-secrets.ts +15 -0
  154. package/src/lib/tool-argument-integers.ts +56 -5
  155. package/src/oauth/callback-server.ts +22 -2
  156. package/src/oauth/cline.ts +203 -0
  157. package/src/oauth/generic-account-failover.ts +231 -0
  158. package/src/oauth/health.ts +1 -1
  159. package/src/oauth/index.ts +65 -6
  160. package/src/oauth/key-providers.ts +10 -1
  161. package/src/oauth/kimi.ts +9 -1
  162. package/src/oauth/kiro.ts +45 -0
  163. package/src/oauth/local-token-detect.ts +2 -0
  164. package/src/oauth/open-browser-choice.ts +26 -0
  165. package/src/oauth/store.ts +14 -5
  166. package/src/oauth/types.ts +15 -0
  167. package/src/providers/auto-compact-budget.ts +65 -0
  168. package/src/providers/command-code-efforts.ts +38 -6
  169. package/src/providers/context-cap.ts +4 -3
  170. package/src/providers/default-aliases.ts +65 -0
  171. package/src/providers/derive.ts +28 -0
  172. package/src/providers/fastwire.ts +7 -1
  173. package/src/providers/label.ts +34 -1
  174. package/src/providers/model-presets.ts +119 -0
  175. package/src/providers/new-model-policy.ts +146 -0
  176. package/src/providers/provider-id-rewrite.ts +2 -1
  177. package/src/providers/quota.ts +65 -7
  178. package/src/providers/registry.ts +184 -69
  179. package/src/providers/slug-codec.ts +52 -0
  180. package/src/providers/xai-transport.ts +21 -0
  181. package/src/reasoning-effort.ts +19 -2
  182. package/src/responses/apply-patch-envelope.ts +63 -0
  183. package/src/responses/code-mode-helper-compat.ts +50 -0
  184. package/src/responses/custom-tool-compat.ts +157 -39
  185. package/src/responses/namespace-tool-compat.ts +84 -4
  186. package/src/responses/parser.ts +7 -2
  187. package/src/responses/reasoning-replay-cache.ts +81 -3
  188. package/src/responses/schema.ts +5 -1
  189. package/src/responses/state.ts +94 -4
  190. package/src/responses/thought-signature-replay.ts +17 -0
  191. package/src/responses/turn-termination.ts +107 -0
  192. package/src/router.ts +40 -2
  193. package/src/routing/analytics.ts +1 -0
  194. package/src/routing/quota.ts +10 -0
  195. package/src/server/auth-cors.ts +27 -7
  196. package/src/server/chat-completions.ts +24 -16
  197. package/src/server/chat-native-sse.ts +3 -3
  198. package/src/server/chat-native.ts +30 -11
  199. package/src/server/claude-messages.ts +1 -1
  200. package/src/server/index.ts +179 -32
  201. package/src/server/lifecycle.ts +52 -1
  202. package/src/server/local-management-read-client.ts +1 -1
  203. package/src/server/local-provider-reload-client.ts +1 -1
  204. package/src/server/management/agent-settings-routes.ts +45 -23
  205. package/src/server/management/codex-prompt-routes.ts +570 -0
  206. package/src/server/management/combo-routes.ts +2 -1
  207. package/src/server/management/config-routes.ts +59 -12
  208. package/src/server/management/context.ts +10 -1
  209. package/src/server/management/logs-usage-routes.ts +30 -24
  210. package/src/server/management/model-routes.ts +274 -4
  211. package/src/server/management/model-rows.ts +4 -0
  212. package/src/server/management/native-integration-routes.ts +2 -1
  213. package/src/server/management/oauth-account-routes.ts +10 -4
  214. package/src/server/management/provider-capability-config.ts +1 -1
  215. package/src/server/management/provider-routes.ts +81 -11
  216. package/src/server/management/route-registry.ts +332 -0
  217. package/src/server/management/routing-profile-routes.ts +2 -2
  218. package/src/server/management/system-restart.ts +1 -1
  219. package/src/server/management-api.ts +18 -16
  220. package/src/server/port-reclaim.ts +1 -1
  221. package/src/server/proxy-liveness.ts +29 -5
  222. package/src/server/relay-eager.ts +147 -20
  223. package/src/server/relay.ts +251 -19
  224. package/src/server/request-log-conversation.ts +51 -0
  225. package/src/server/request-log.ts +70 -16
  226. package/src/server/responses/codex-auth-error.ts +55 -0
  227. package/src/server/responses/collaboration.ts +37 -3
  228. package/src/server/responses/combo-stream-preflight.ts +178 -0
  229. package/src/server/responses/compact.ts +6 -21
  230. package/src/server/responses/core.ts +810 -208
  231. package/src/server/responses/empty-completion-guard.ts +63 -6
  232. package/src/server/responses/fetch-helpers.ts +17 -102
  233. package/src/server/responses/input-admission.ts +3 -1
  234. package/src/server/responses/passthrough-error.ts +33 -9
  235. package/src/server/responses/responses-field-backfill.ts +105 -13
  236. package/src/server/responses/ws-upstream.ts +75 -2
  237. package/src/server/responses-custom-tool-repair.ts +87 -6
  238. package/src/server/responses-terminal-repair.ts +25 -4
  239. package/src/server/responses-undeclared-tool-guard.ts +241 -18
  240. package/src/server/sse-frame-buffer.ts +31 -4
  241. package/src/server/ws-bridge.ts +14 -2
  242. package/src/service.ts +42 -4
  243. package/src/storage/policy-job.ts +14 -4
  244. package/src/storage/policy.ts +88 -23
  245. package/src/types/config.ts +66 -0
  246. package/src/types/provider.ts +69 -0
  247. package/src/types/request.ts +17 -4
  248. package/src/types/tools.ts +116 -11
  249. package/src/types.ts +3 -1
  250. package/src/update/index.ts +5 -4
  251. package/src/update/job.ts +3 -1
  252. package/src/update/transactional-install.mjs +8 -1
  253. package/src/usage/expected-prices.ts +43 -24
  254. package/src/usage/log.ts +63 -12
  255. package/src/usage/summary.ts +211 -8
  256. package/src/vision/describe.ts +18 -13
  257. package/src/web-search/exa-executor.ts +40 -9
  258. package/src/web-search/executor.ts +10 -3
  259. package/src/web-search/loop.ts +5 -4
  260. package/gui/dist/assets/index-BF38heuV.js +0 -104
  261. package/gui/dist/assets/index-DMiI18Kv.css +0 -1
package/README.md CHANGED
@@ -27,17 +27,17 @@ ocx start # proxy + dashboard on localhost:10100
27
27
  <sub><b>Claude Code, running any model.</b><br>The picker is stock Claude Code. The brain behind it isn't.</sub>
28
28
  </td>
29
29
  <td width="50%" align="center">
30
- <img src="assets/demo.gif" alt="opencodex demo — running a task in the Codex app on a routed non-OpenAI model" width="410"><br>
30
+ <img src="https://raw.githubusercontent.com/lidge-jun/opencodex/main/assets/demo.gif" alt="opencodex demo — running a task in the Codex app on a routed non-OpenAI model" width="410"><br>
31
31
  <sub><b>Codex, running any model.</b><br>Pick a provider and go — same workflow, different brain.</sub>
32
32
  </td>
33
33
  </tr>
34
34
  <tr>
35
35
  <td width="50%" align="center">
36
- <img src="assets/claude-desktop-subagent.gif" alt="Claude Desktop answering as Claude Opus 4.8, then dispatching a GPT-5.6 Sol subagent through opencodex" width="410"><br>
36
+ <img src="https://raw.githubusercontent.com/lidge-jun/opencodex/main/assets/claude-desktop-subagent.gif" alt="Claude Desktop answering as Claude Opus 4.8, then dispatching a GPT-5.6 Sol subagent through opencodex" width="410"><br>
37
37
  <sub><b>Claude Desktop, running any model.</b><br>Opus answers, then hands the task to a GPT-5.6 Sol subagent.</sub>
38
38
  </td>
39
39
  <td width="50%" align="center">
40
- <img src="assets/grok-build-subagent.gif" alt="Grok Build running GPT-5.6 Sol through opencodex and calling a Kimi K3 subagent" width="410"><br>
40
+ <img src="https://raw.githubusercontent.com/lidge-jun/opencodex/main/assets/grok-build-subagent.gif" alt="Grok Build running GPT-5.6 Sol through opencodex and calling a Kimi K3 subagent" width="410"><br>
41
41
  <sub><b>Grok Build, running any model.</b><br>Sol drives the session and calls a Kimi K3 subagent.</sub>
42
42
  </td>
43
43
  </tr>
package/bin/ocx.mjs CHANGED
@@ -227,11 +227,90 @@ function runNpmSelfUpdate() {
227
227
  } catch { /* keep default */ }
228
228
  }
229
229
 
230
+ const launcher = fileURLToPath(import.meta.url);
231
+
232
+ function startProxyDirectly() {
233
+ if (!existsSync(launcher)) {
234
+ console.error("opencodex: cannot restart the proxy because the launcher is missing; reinstall opencodex manually.");
235
+ return;
236
+ }
237
+ const env = { ...process.env };
238
+ delete env.OCX_SERVICE;
239
+ console.log(`Attempting to restart the proxy on port ${bakePort}.`);
240
+ const child = spawn(process.execPath, [launcher, "start", "--port", String(bakePort)], {
241
+ detached: true,
242
+ stdio: "ignore",
243
+ windowsHide: true,
244
+ env,
245
+ });
246
+ child.on("error", error => {
247
+ console.error(`opencodex: direct proxy restart failed: ${error.message}`);
248
+ });
249
+ child.unref();
250
+ }
251
+
252
+ function refreshBackgroundServiceOrStartDirect() {
253
+ const prevBake = process.env.OCX_BAKE_PORT;
254
+ process.env.OCX_BAKE_PORT = String(bakePort);
255
+ try {
256
+ let svc = spawnSync(process.execPath, serviceRefreshArgs(), { stdio: "inherit", windowsHide: true });
257
+ // `serviceWasInstalled` is inferred from service-state.json alone, which can be
258
+ // STALE — present while the registration is gone. Repair refuses that case by
259
+ // design, and its thrown Error is indistinguishable from any other failure at
260
+ // this layer (plain Error, inherited stdio, generic exit status). So ask for
261
+ // structured state instead of parsing the failure: install only when the
262
+ // diagnostic says the service is genuinely absent. Installing after ANY repair
263
+ // failure would resurrect the elevation prompt this change exists to avoid, and
264
+ // could re-register a service the user just uninstalled.
265
+ if (svc.status !== 0 && readServiceInstalledFromStatus(launcher) === false) {
266
+ console.log("No registered service found — installing it instead.");
267
+ svc = spawnSync(process.execPath, serviceInstallArgs(), { stdio: "inherit", windowsHide: true });
268
+ }
269
+ let needDirectStart = svc.status !== 0;
270
+ if (!needDirectStart) {
271
+ // Exit 0 can still leave stale/missing assets that never bring the proxy
272
+ // back — match the GUI/CLI fallthrough so /healthz is not left dead.
273
+ try {
274
+ const st = spawnSync(process.execPath, [launcher, "status", "--json"], {
275
+ encoding: "utf8",
276
+ timeout: 20_000,
277
+ windowsHide: true,
278
+ });
279
+ if (st.status === 0 && typeof st.stdout === "string" && st.stdout.trim()) {
280
+ const parsed = JSON.parse(st.stdout);
281
+ const proxyUp = parsed?.proxy?.running === true || parsed?.proxy?.health?.ok === true;
282
+ const viable = parsed?.startup?.serviceViable === true;
283
+ if (!proxyUp && !viable) needDirectStart = true;
284
+ } else {
285
+ // status failed or empty — fail closed to direct start (match CLI).
286
+ needDirectStart = true;
287
+ }
288
+ } catch {
289
+ needDirectStart = true;
290
+ }
291
+ }
292
+ if (needDirectStart) {
293
+ // A repair needs no elevation, but it can still fail — or exit 0 while leaving
294
+ // a non-viable manager. Fall back to a direct detached proxy start so the
295
+ // update never leaves the user without a running proxy.
296
+ console.warn(
297
+ svc.status === 0
298
+ ? "opencodex: service refresh left a non-viable manager — starting the proxy directly instead."
299
+ : "opencodex: service refresh failed — starting the proxy directly instead.",
300
+ );
301
+ console.warn(" Run 'ocx service repair' to see why the background service could not restart.");
302
+ startProxyDirectly();
303
+ }
304
+ } finally {
305
+ if (prevBake === undefined) delete process.env.OCX_BAKE_PORT;
306
+ else process.env.OCX_BAKE_PORT = prevBake;
307
+ }
308
+ }
309
+
230
310
  // Never replace package files under a live proxy — stop it first (full `ocx stop`
231
311
  // semantics: graceful drain, service stop, native Codex restore). Gate on the service
232
312
  // and the runtime-port record too: a service-managed or orphaned proxy can be live
233
313
  // while ocx.pid is stale/missing.
234
- const launcher = fileURLToPath(import.meta.url);
235
314
  if (trayBeforeUpdate.stopBeforeReplacement) {
236
315
  console.log("⏹ Handing off the Windows tray before updating...");
237
316
  try {
@@ -249,6 +328,17 @@ function runNpmSelfUpdate() {
249
328
  }
250
329
  const hasRuntimeState =
251
330
  existsSync(join(configDir(), "ocx.pid")) || existsSync(join(configDir(), "runtime-port.json"));
331
+
332
+ function recoverStoppedRuntimeAfterFailure() {
333
+ if (serviceWasInstalled) {
334
+ console.warn("opencodex: update failed after stopping the proxy — restoring the previous background service.");
335
+ refreshBackgroundServiceOrStartDirect();
336
+ } else if (hasRuntimeState) {
337
+ console.warn("opencodex: update failed after stopping the proxy — restarting the previous version directly.");
338
+ startProxyDirectly();
339
+ }
340
+ }
341
+
252
342
  if (serviceWasInstalled || hasRuntimeState) {
253
343
  console.log("⏹ Stopping the running proxy before updating...");
254
344
  const stopRes = spawnSync(process.execPath, [launcher, "stop"], { stdio: "inherit", windowsHide: true });
@@ -261,9 +351,9 @@ function runNpmSelfUpdate() {
261
351
  }
262
352
  if (historyRestoreIncomplete()) {
263
353
  console.warn(
264
- "opencodex: WARNING — Codex resume history was NOT restored (history DB locked; Codex app/IDE open?).\n" +
265
- " Routed threads stay hidden in the native Codex app until restored.\n" +
266
- " After the update: close the Codex app, then run 'ocx stop' once to restore.",
354
+ "opencodex: WARNING — Codex resume-history metadata restore is incomplete (a backup manifest remains).\n" +
355
+ " The DB may be busy or the manifest/target may need review; untracked routed history is intentionally unchanged.\n" +
356
+ " After the update: close the Codex app, run 'ocx doctor', then run 'ocx stop' once to retry.",
267
357
  );
268
358
  }
269
359
  }
@@ -309,7 +399,8 @@ function runNpmSelfUpdate() {
309
399
  // it recreates the #1849 destruction path. Report and stop; the boot probe and the
310
400
  // recovery marker cover the swap-window states.
311
401
  console.error(`opencodex: transactional update failed unexpectedly (${error?.message ?? error}). ` +
312
- "The live install was not knowingly modified; run 'ocx update' again or reinstall with npm install -g.");
402
+ `The live install was not knowingly modified; run 'ocx update' again or reinstall with ` +
403
+ `npm install -g --allow-scripts=bun ${PKG}@${tag}.`);
313
404
  res = { status: 1 };
314
405
  }
315
406
  if (res.status === 0) {
@@ -329,77 +420,15 @@ function runNpmSelfUpdate() {
329
420
  // launcher so the new files write the baked paths and the service restarts.
330
421
  if (serviceWasInstalled) {
331
422
  console.log("Refreshing the background service with the updated files...");
332
- const prevBake = process.env.OCX_BAKE_PORT;
333
- process.env.OCX_BAKE_PORT = String(bakePort);
334
- try {
335
- let svc = spawnSync(process.execPath, serviceRefreshArgs(), { stdio: "inherit", windowsHide: true });
336
- // `serviceWasInstalled` is inferred from service-state.json alone, which can be
337
- // STALE — present while the registration is gone. Repair refuses that case by
338
- // design, and its thrown Error is indistinguishable from any other failure at
339
- // this layer (plain Error, inherited stdio, generic exit status). So ask for
340
- // structured state instead of parsing the failure: install only when the
341
- // diagnostic says the service is genuinely absent. Installing after ANY repair
342
- // failure would resurrect the elevation prompt this change exists to avoid, and
343
- // could re-register a service the user just uninstalled.
344
- if (svc.status !== 0 && readServiceInstalledFromStatus(launcher) === false) {
345
- console.log("No registered service found — installing it instead.");
346
- svc = spawnSync(process.execPath, serviceInstallArgs(), { stdio: "inherit", windowsHide: true });
347
- }
348
- let needDirectStart = svc.status !== 0;
349
- if (!needDirectStart) {
350
- // Exit 0 can still leave stale/missing assets that never bring the proxy
351
- // back — match the GUI/CLI fallthrough so /healthz is not left dead.
352
- try {
353
- const st = spawnSync(process.execPath, [launcher, "status", "--json"], {
354
- encoding: "utf8",
355
- timeout: 20_000,
356
- windowsHide: true,
357
- });
358
- if (st.status === 0 && typeof st.stdout === "string" && st.stdout.trim()) {
359
- const parsed = JSON.parse(st.stdout);
360
- const proxyUp = parsed?.proxy?.running === true || parsed?.proxy?.health?.ok === true;
361
- const viable = parsed?.startup?.serviceViable === true;
362
- if (!proxyUp && !viable) needDirectStart = true;
363
- } else {
364
- // status failed or empty — fail closed to direct start (match CLI).
365
- needDirectStart = true;
366
- }
367
- } catch {
368
- needDirectStart = true;
369
- }
370
- }
371
- if (needDirectStart) {
372
- // A repair needs no elevation, but it can still fail — or exit 0 while leaving
373
- // a non-viable manager. Fall back to a direct detached proxy start so the
374
- // update never leaves the user without a running proxy.
375
- console.warn(
376
- svc.status === 0
377
- ? "opencodex: service refresh left a non-viable manager — starting the proxy directly instead."
378
- : "opencodex: service refresh failed — starting the proxy directly instead.",
379
- );
380
- console.warn(" Run 'ocx service repair' to see why the background service could not restart.");
381
- const env = { ...process.env };
382
- delete env.OCX_SERVICE;
383
- const child = spawn(process.execPath, [launcher, "start", "--port", String(bakePort)], {
384
- detached: true,
385
- stdio: "ignore",
386
- windowsHide: true,
387
- env,
388
- });
389
- child.unref();
390
- console.log(`Proxy starting on port ${bakePort}.`);
391
- }
392
- } finally {
393
- if (prevBake === undefined) delete process.env.OCX_BAKE_PORT;
394
- else process.env.OCX_BAKE_PORT = prevBake;
395
- }
423
+ refreshBackgroundServiceOrStartDirect();
396
424
  } else {
397
425
  console.log("Restart the proxy: ocx start");
398
426
  }
399
427
  process.exit(0);
400
428
  }
401
429
  if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(launcher, "start");
402
- console.error(`\nUpdate failed (npm exit ${res.status ?? "?"}). Try manually: npm install -g ${PKG}@${tag}`);
430
+ recoverStoppedRuntimeAfterFailure();
431
+ console.error(`\nUpdate failed (npm exit ${res.status ?? "?"}). Try manually: npm install -g --allow-scripts=bun ${PKG}@${tag}`);
403
432
  process.exit(1);
404
433
  }
405
434