@iislee/opencodex 2.11.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 (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Cached "is an update available?" answer for the GUI sidebar badge.
3
+ *
4
+ * `/api/update/check` spawns `npm view` on every call (~1s, network-bound), so a
5
+ * sidebar that polls it would spawn a process per tick on every page of the GUI.
6
+ * The badge instead READS the 20h version cache the CLI update prompt already
7
+ * maintains (`~/.opencodex/version.json`).
8
+ *
9
+ * This is deliberately read-only: it must never trigger a registry refresh. The GUI
10
+ * polls it, so a refresh-on-read would let repeated polls launch repeated `npm view`
11
+ * helpers with no coalescing. Cache warming stays with `ocx start`
12
+ * (`triggerBackgroundRefreshIfStale` in `src/update/notify.ts`) and with the explicit
13
+ * `/api/update/check` the user reaches by clicking the sidebar update button.
14
+ */
15
+ import { currentVersion, defaultUpdateTag, detectInstall, type Channel } from "./index";
16
+ import { isNewer, isSourceBuildVersion, readVersionCache } from "./notify";
17
+
18
+ export interface UpdateBadge {
19
+ /** True only when a newer version exists on the current channel. */
20
+ updateAvailable: boolean;
21
+ currentVersion: string;
22
+ latestVersion: string | null;
23
+ channel: Channel;
24
+ /** False for source checkouts, where the GUI cannot offer a one-click update. */
25
+ canUpdate: boolean;
26
+ /** True when no cached registry answer exists yet, so "no update" is unproven. */
27
+ unknown: boolean;
28
+ }
29
+
30
+ export interface UpdateBadgeDeps {
31
+ currentVersion: () => string;
32
+ detectInstall: () => ReturnType<typeof detectInstall>;
33
+ readCache: (channel: Channel) => ReturnType<typeof readVersionCache>;
34
+ }
35
+
36
+ const defaultDeps: UpdateBadgeDeps = {
37
+ currentVersion,
38
+ detectInstall,
39
+ readCache: readVersionCache,
40
+ };
41
+
42
+ /**
43
+ * Read-only badge state. Source checkouts and unknown versions report no update
44
+ * rather than a dead badge the user cannot act on.
45
+ */
46
+ export function readUpdateBadge(deps: UpdateBadgeDeps = defaultDeps): UpdateBadge {
47
+ const current = deps.currentVersion();
48
+ const installer = deps.detectInstall();
49
+ const channel = defaultUpdateTag(current);
50
+ const base: UpdateBadge = {
51
+ updateAvailable: false,
52
+ currentVersion: current,
53
+ latestVersion: null,
54
+ channel,
55
+ canUpdate: installer !== "source",
56
+ unknown: true,
57
+ };
58
+ // A source checkout has nothing to compare against, so "unknown" is not useful there.
59
+ if (installer === "source" || current === "?" || isSourceBuildVersion(current)) {
60
+ return { ...base, canUpdate: false, unknown: false };
61
+ }
62
+
63
+ const cache = deps.readCache(channel);
64
+ if (!cache) return base;
65
+
66
+ return {
67
+ ...base,
68
+ latestVersion: cache.latest_version,
69
+ updateAvailable: isNewer(cache.latest_version, current, channel),
70
+ unknown: false,
71
+ };
72
+ }
@@ -0,0 +1,407 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { readFileSync, readdirSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ import { getConfigDir, loadConfig, readPid, readRuntimePort } from "../config";
6
+ import { npmInvocation } from "./npm-invocation.mjs";
7
+ import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "./tray-update-plan.mjs";
8
+
9
+ /**
10
+ * A `codex-history-backup-*.json` surviving a stop means the native-history restore was
11
+ * skipped (locked state DB) — routed threads stay hidden in the Codex app until a retry.
12
+ */
13
+ export function historyRestoreIncomplete(configDir = getConfigDir()): boolean {
14
+ try {
15
+ return readdirSync(configDir).some(
16
+ name => name.startsWith("codex-history-backup-") && name.endsWith(".json"),
17
+ );
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ export const PKG = "@iislee/opencodex";
24
+ const HERE = dirname(fileURLToPath(import.meta.url)); // .../opencodex/src/update
25
+
26
+ export type Installer = "bun" | "npm" | "source";
27
+ export type Channel = "latest" | "preview";
28
+
29
+ /** Infer how opencodex is installed from the running module's path. */
30
+ export function detectInstall(): Installer {
31
+ if (!HERE.includes("node_modules")) return "source"; // a git checkout, not a global install
32
+ return HERE.includes(".bun") ? "bun" : "npm";
33
+ }
34
+
35
+ export function currentVersion(): string {
36
+ try {
37
+ return (JSON.parse(readFileSync(join(HERE, "..", "..", "package.json"), "utf8")).version as string) ?? "?";
38
+ } catch {
39
+ return "?";
40
+ }
41
+ }
42
+
43
+ export function defaultUpdateTag(current: string): Channel {
44
+ return current.includes("-preview.") ? "preview" : "latest";
45
+ }
46
+
47
+ export function updateTag(current: string): Channel {
48
+ const tagIndex = process.argv.indexOf("--tag");
49
+ const explicit = tagIndex !== -1 ? process.argv[tagIndex + 1] : undefined;
50
+ if (explicit === "preview" || explicit === "latest") return explicit;
51
+ return defaultUpdateTag(current);
52
+ }
53
+
54
+ function npmSpawnTarget(args: readonly string[]): { bin: string; args: string[]; options: { windowsVerbatimArguments?: boolean } } | null {
55
+ const invocation = npmInvocation(args);
56
+ if (!invocation) return null;
57
+ return { bin: invocation.file, args: invocation.args, options: invocation.options };
58
+ }
59
+
60
+ function updateSpawnTarget(bin: string, args: readonly string[]): { bin: string; args: string[]; options: { windowsVerbatimArguments?: boolean } } | null {
61
+ if (bin === "npm") return npmSpawnTarget(args);
62
+ if (process.platform === "win32" && bin === "bun") {
63
+ return { bin: process.execPath, args: [...args], options: {} };
64
+ }
65
+ return { bin, args: [...args], options: {} };
66
+ }
67
+
68
+ /**
69
+ * The GUI update worker sets OCX_SERVICE=1 and has stdio ignored — inheriting that for
70
+ * Background package-manager children can open stacked visible consoles on Windows.
71
+ * Pipe instead and relay bounded output after the child exits. (Ported from PR #167.)
72
+ */
73
+ function updateChildStdio(): "inherit" | "pipe" {
74
+ if (process.env.OCX_SERVICE === "1") return "pipe";
75
+ if (typeof process.stdout.isTTY === "boolean" && !process.stdout.isTTY) return "pipe";
76
+ return "inherit";
77
+ }
78
+
79
+ function logSpawnOutput(label: string, result: { stdout?: string | Buffer | null; stderr?: string | Buffer | null }): void {
80
+ const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
81
+ const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
82
+ if (stdout) console.log(stdout.length > 4000 ? `${label}${stdout.slice(-4000)}` : stdout);
83
+ if (stderr) console.error(stderr.length > 4000 ? `${label}${stderr.slice(-4000)}` : stderr);
84
+ }
85
+
86
+ /** In-process cache so dashboard polls do not re-hit the registry every few seconds. */
87
+ const LATEST_VERSION_TTL_MS = 15 * 60_000;
88
+ const latestVersionCache = new Map<string, { value: string | null; expiresAt: number }>();
89
+
90
+ /** Latest published version from the registry (best-effort; null if npm isn't available). */
91
+ export function latestVersion(tag: string, now = Date.now()): string | null {
92
+ const cached = latestVersionCache.get(tag);
93
+ if (cached && cached.expiresAt > now) return cached.value;
94
+
95
+ const npm = npmSpawnTarget(["view", `${PKG}@${tag}`, "version"]);
96
+ if (!npm) {
97
+ latestVersionCache.set(tag, { value: null, expiresAt: now + LATEST_VERSION_TTL_MS });
98
+ return null;
99
+ }
100
+ const r = spawnSync(npm.bin, npm.args, {
101
+ encoding: "utf8",
102
+ timeout: 12000,
103
+ windowsHide: true,
104
+ ...npm.options,
105
+ });
106
+ const value = r.status === 0 ? (r.stdout.trim() || null) : null;
107
+ latestVersionCache.set(tag, { value, expiresAt: now + LATEST_VERSION_TTL_MS });
108
+ return value;
109
+ }
110
+
111
+ /** Test helper — clears the registry version cache. */
112
+ export function clearLatestVersionCache(): void {
113
+ latestVersionCache.clear();
114
+ }
115
+
116
+ /** The global-install command opencodex would run to update on this channel. */
117
+ export function updateCommand(installer: Installer, tag: Channel, resolvedVersion?: string | null): { bin: string; args: string[] } {
118
+ const bin = installer === "bun" ? "bun" : "npm";
119
+ // Immutable target: when the registry resolved a concrete version, install exactly
120
+ // that version — the dist-tag can move between resolution and install (TOCTOU).
121
+ const target = resolvedVersion || tag;
122
+ const args = installer === "bun"
123
+ ? ["add", "-g", `${PKG}@${target}`]
124
+ : ["install", "-g", `${PKG}@${target}`];
125
+ return { bin, args };
126
+ }
127
+
128
+ /** Human-readable form of {@link updateCommand}, used in the update prompt label. */
129
+ export function updateCommandStr(installer: Installer, tag: Channel, resolvedVersion?: string | null): string {
130
+ const { bin, args } = updateCommand(installer, tag, resolvedVersion);
131
+ return `${bin} ${args.join(" ")}`;
132
+ }
133
+
134
+ /**
135
+ * Pre-flight integrity metadata check (NOT independent tamper-proofing — the installer
136
+ * verifies tarballs against the same registry metadata). Two failure lanes:
137
+ * - transient registry failure (spawn error/timeout/nonzero exit) → `{ ok: "skipped" }`
138
+ * so registry absence never turns into an unconditional update failure;
139
+ * - successful query with missing/malformed SRI → `{ ok: false }` (anomalous
140
+ * metadata — fail closed BEFORE the running proxy is stopped).
141
+ * `dist.integrity` may be a quoted, space-separated multi-hash list; any sha512 token passes.
142
+ */
143
+ export function checkUpdatePackageIntegrity(
144
+ version: string | null,
145
+ spawn: typeof spawnSync = spawnSync,
146
+ ): { ok: true; integrity: string } | { ok: false; reason: string } | { ok: "skipped"; reason: string } {
147
+ if (!version) return { ok: "skipped", reason: "no resolved version (registry unavailable)" };
148
+ const npm = npmSpawnTarget(["view", `${PKG}@${version}`, "dist.integrity"]);
149
+ if (!npm) return { ok: "skipped", reason: "npm executable was not found on a trusted PATH entry" };
150
+ const r = spawn(
151
+ npm.bin,
152
+ npm.args,
153
+ { encoding: "utf8", timeout: 12000, windowsHide: true, ...npm.options },
154
+ );
155
+ // status !== 0 covers nonzero exits AND timeouts (status === null).
156
+ if (r.status !== 0) return { ok: "skipped", reason: `registry integrity query failed (status ${r.status ?? "timeout"})` };
157
+ const tokens = (r.stdout ?? "").replace(/["']/g, "").trim().split(/\s+/).filter(Boolean);
158
+ const match = tokens.find(token => /^sha512-[A-Za-z0-9+/=]+$/.test(token));
159
+ if (!match) return { ok: false, reason: `registry returned no sha512 integrity for ${PKG}@${version}` };
160
+ return { ok: true, integrity: match };
161
+ }
162
+
163
+ /**
164
+ * `ocx update` fallback for source checkouts and Bun global installs. npm global installs are updated
165
+ * in the Node bin launcher before Bun starts, so Windows does not replace the running Bun binary.
166
+ */
167
+ export async function runUpdate(): Promise<void> {
168
+ const installer = detectInstall();
169
+ const current = currentVersion();
170
+ const tag = updateTag(current);
171
+ console.log(`opencodex v${current} (installed via ${installer}, tag ${tag})`);
172
+
173
+ if (installer === "source") {
174
+ console.log("Running from a source checkout — update with: git pull && bun install");
175
+ return;
176
+ }
177
+
178
+ const latest = latestVersion(tag);
179
+ if (latest && latest === current) {
180
+ console.log(`Already on the latest ${tag} version (v${latest}).`);
181
+ return;
182
+ }
183
+
184
+ // Pre-flight integrity metadata check — runs BEFORE the proxy is stopped so an
185
+ // anomalous registry entry aborts without unloading the running service.
186
+ const integrity = checkUpdatePackageIntegrity(latest);
187
+ if (integrity.ok === false) {
188
+ console.error(`⚠️ ${integrity.reason} — aborting the update before stopping the proxy.`);
189
+ process.exit(1);
190
+ }
191
+ if (integrity.ok === "skipped") {
192
+ console.warn(`⚠️ Integrity pre-flight skipped: ${integrity.reason}. Proceeding best-effort.`);
193
+ } else {
194
+ console.log(`Verified ${PKG}@${latest} integrity metadata ${integrity.integrity.slice(0, 24)}…`);
195
+ }
196
+
197
+ const { bin, args: cmdArgs } = updateCommand(installer, tag, latest);
198
+ const target = updateSpawnTarget(bin, cmdArgs);
199
+ if (!target) {
200
+ console.error("⚠️ Could not resolve npm from a trusted absolute PATH entry; aborting before stopping the proxy.");
201
+ process.exit(1);
202
+ }
203
+
204
+ // Remember whether a background service manages the proxy BEFORE stopping — `ocx stop`
205
+ // unloads it permanently, so a successful update must reinstall/restart it afterwards.
206
+ let serviceWasInstalled = false;
207
+ try {
208
+ const { isServiceInstalled } = await import("../service");
209
+ serviceWasInstalled = isServiceInstalled();
210
+ } catch { /* best-effort */ }
211
+ let trayWasInstalled = false;
212
+ let trayWasRunning = false;
213
+ if (process.platform === "win32") {
214
+ try {
215
+ const { getWindowsTrayStatus, startWindowsTray, stopWindowsTray } = await import("../tray/windows");
216
+ const tray = getWindowsTrayStatus();
217
+ const trayPlan = handoffWindowsTrayForUpdate(tray, {
218
+ stop: () => {
219
+ const stopped = stopWindowsTray();
220
+ return { exitStatus: 0, running: stopped.running };
221
+ },
222
+ start: () => startWindowsTray(),
223
+ });
224
+ trayWasInstalled = trayPlan.refreshAfterReplacement;
225
+ trayWasRunning = trayPlan.restoreOnFailure;
226
+ } catch (error) {
227
+ console.error(`⚠️ Could not stop the Windows tray; aborting before package replacement: ${error instanceof Error ? error.message : String(error)}`);
228
+ process.exit(1);
229
+ }
230
+ }
231
+
232
+ // Capture listen target before stop clears runtime state (same contract as GUI update worker).
233
+ // Prefer a live runtime record; a stale crashed leftover must not override config.port.
234
+ const preUpdateConfig = loadConfig();
235
+ const preUpdateRt = readRuntimePort();
236
+ const livePid = readPid();
237
+ const runtimeTrusted = !!(preUpdateRt && livePid && preUpdateRt.pid === livePid);
238
+ const configPort = typeof preUpdateConfig.port === "number" && preUpdateConfig.port > 0
239
+ ? preUpdateConfig.port
240
+ : 10100;
241
+ const capturedListen = {
242
+ port: runtimeTrusted ? preUpdateRt.port : configPort,
243
+ hostname: (runtimeTrusted ? preUpdateRt.hostname : undefined) ?? preUpdateConfig.hostname ?? "127.0.0.1",
244
+ ...(runtimeTrusted && livePid ? { oldPid: livePid } : {}),
245
+ };
246
+
247
+ // Never replace package files under a live proxy: the running server dynamic-imports
248
+ // modules after startup, so an in-place update leaves it executing mixed old/new code.
249
+ // Gate on the service and the runtime-port record too, not just the pid file — a
250
+ // service-managed or orphaned proxy can be live while ocx.pid is stale/missing.
251
+ // Full `ocx stop` semantics (drain, service stop, restore).
252
+ if (serviceWasInstalled || readPid() || readRuntimePort()) {
253
+ console.log("⏹ Stopping the running proxy before updating...");
254
+ const stopStdio = updateChildStdio();
255
+ const stop = spawnSync(process.execPath, [process.argv[1], "stop"], {
256
+ stdio: stopStdio,
257
+ encoding: stopStdio === "pipe" ? "utf8" : undefined,
258
+ windowsHide: true,
259
+ });
260
+ if (stopStdio === "pipe") logSpawnOutput("", stop);
261
+ if (stop.status !== 0 || readPid() || readRuntimePort()) {
262
+ if (trayWasRunning) {
263
+ try {
264
+ const { startWindowsTray } = await import("../tray/windows");
265
+ startWindowsTray();
266
+ } catch { /* preserve the proxy stop failure */ }
267
+ }
268
+ console.error("⚠️ Could not stop the running proxy; aborting the update. Run 'ocx stop' and retry.");
269
+ process.exit(1);
270
+ }
271
+ if (historyRestoreIncomplete()) {
272
+ console.warn(
273
+ "⚠️ Codex resume history was NOT restored (history DB locked — Codex app/IDE open?).\n" +
274
+ " Your routed threads stay hidden in the native Codex app until restored.\n" +
275
+ " After the update: close the Codex app, then run 'ocx stop' once to restore.",
276
+ );
277
+ }
278
+ }
279
+
280
+ console.log(`Updating${latest ? ` to v${latest}` : ""}…\n$ ${bin} ${cmdArgs.join(" ")}`);
281
+
282
+ const installStdio = updateChildStdio();
283
+ const r = spawnSync(target.bin, target.args, {
284
+ stdio: installStdio,
285
+ encoding: installStdio === "pipe" ? "utf8" : undefined,
286
+ timeout: 180000,
287
+ windowsHide: true,
288
+ ...target.options,
289
+ });
290
+ if (installStdio === "pipe") logSpawnOutput("", r);
291
+ if (r.status === 0) {
292
+ console.log(`\n✅ Updated${latest ? ` to v${latest}` : ""}.`);
293
+ // Re-bake the bundled Bun path into the Codex autostart shim on every
294
+ // platform when one is installed (refresh-only; never installs fresh).
295
+ try {
296
+ const { isCodexShimInstalled, installCodexShim } = await import("../codex/shim");
297
+ if (isCodexShimInstalled()) {
298
+ const result = installCodexShim();
299
+ if (result.installed) console.log(`🔧 ${result.message}`);
300
+ }
301
+ } catch (e) {
302
+ console.warn(`⚠️ Shim repair skipped: ${e instanceof Error ? e.message : e}`);
303
+ }
304
+ if (trayWasInstalled) {
305
+ const trayArgs = [process.argv[1], ...planWindowsTrayUpdate({ installed: trayWasInstalled, running: trayWasRunning }).installArgs];
306
+ const tray = spawnSync(process.execPath, trayArgs, { stdio: "inherit", windowsHide: true });
307
+ if (tray.status === 0) {
308
+ console.log("🔧 Refreshed Windows tray startup paths.");
309
+ } else {
310
+ console.warn("⚠️ Windows tray refresh failed. Run 'ocx tray install'.");
311
+ if (trayWasRunning) spawnSync(process.execPath, [process.argv[1], "tray", "start"], { stdio: "ignore", windowsHide: true });
312
+ }
313
+ }
314
+ // The stop above unloaded any managed service; reinstall it with the NEW files
315
+ // (spawn the fresh cli.ts so updated code writes the baked paths) so a
316
+ // launchd/schtasks/systemd user isn't left with the background proxy down.
317
+ if (serviceWasInstalled) {
318
+ console.log("🔁 Reinstalling the background service with the updated files...");
319
+ const { serviceReinstallArgs } = await import("../service");
320
+ const { reclaimListenPort } = await import("../server/port-reclaim");
321
+ const freed = await reclaimListenPort(capturedListen.port, capturedListen.hostname, {
322
+ timeoutMs: 30_000,
323
+ intervalMs: 100,
324
+ scanIntervalMs: 500,
325
+ killOcxHolders: capturedListen.oldPid != null,
326
+ onlyKillPids: capturedListen.oldPid != null ? [capturedListen.oldPid] : [],
327
+ });
328
+ if (!freed) {
329
+ console.warn(`⚠️ Port ${capturedListen.port} still busy after 30s; reinstalling service with pinned --port ${capturedListen.port} anyway (refusing to hop).`);
330
+ }
331
+ const prevBake = process.env.OCX_BAKE_PORT;
332
+ process.env.OCX_BAKE_PORT = String(capturedListen.port);
333
+ try {
334
+ const svcStdio = updateChildStdio();
335
+ const svc = spawnSync(process.execPath, [process.argv[1], ...serviceReinstallArgs()], {
336
+ stdio: svcStdio,
337
+ encoding: svcStdio === "pipe" ? "utf8" : undefined,
338
+ windowsHide: true,
339
+ });
340
+ if (svcStdio === "pipe") logSpawnOutput("", svc);
341
+ const serviceRefreshed = svc.status === 0;
342
+ let serviceViable = serviceRefreshed;
343
+ if (serviceRefreshed) {
344
+ try {
345
+ const { isServiceViable } = await import("../service");
346
+ serviceViable = isServiceViable();
347
+ } catch {
348
+ serviceViable = false;
349
+ }
350
+ }
351
+ if (!serviceRefreshed || !serviceViable) {
352
+ // On Windows, schtasks /create requires elevation. The CLI inherits the
353
+ // user's (non-admin) token, so the service reinstall can fail with access
354
+ // denied — or exit 0 while leaving stale/missing assets that never start
355
+ // the proxy. Fall back to a direct detached proxy start so the update
356
+ // never leaves the user without a running proxy — but only when the port is free.
357
+ if (!freed) {
358
+ console.warn(
359
+ serviceRefreshed
360
+ ? "⚠️ Service refresh left a non-viable manager and the captured port is still busy; not starting on another port."
361
+ : "⚠️ Service refresh failed and the captured port is still busy; not starting on another port.",
362
+ );
363
+ console.warn(process.platform === "win32"
364
+ ? ` Run 'ocx service install' as administrator, then 'ocx start --port ${capturedListen.port}'.`
365
+ : ` Run 'ocx service install' to see the reason, then 'ocx start --port ${capturedListen.port}'.`);
366
+ } else {
367
+ console.warn(
368
+ serviceRefreshed
369
+ ? "⚠️ Service refresh left a non-viable manager (stale or missing assets) — starting the proxy directly instead."
370
+ : "⚠️ Service refresh failed — starting the proxy directly instead.",
371
+ );
372
+ // Elevation is a Windows-only remedy; elsewhere the refresh fails for
373
+ // reasons `ocx service install` reports directly (since it now verifies
374
+ // the service actually serves).
375
+ console.warn(process.platform === "win32"
376
+ ? " Run 'ocx service install' as administrator to refresh the background service."
377
+ : " Run 'ocx service install' to refresh the background service and see why it failed.");
378
+ const env = { ...process.env };
379
+ delete env.OCX_SERVICE;
380
+ const child = spawn(process.execPath, [process.argv[1], "start", "--port", String(capturedListen.port)], {
381
+ detached: true,
382
+ stdio: "ignore",
383
+ windowsHide: true,
384
+ env,
385
+ });
386
+ child.unref();
387
+ console.log(`✅ Proxy starting on port ${capturedListen.port}.`);
388
+ }
389
+ }
390
+ } finally {
391
+ if (prevBake === undefined) delete process.env.OCX_BAKE_PORT;
392
+ else process.env.OCX_BAKE_PORT = prevBake;
393
+ }
394
+ } else {
395
+ console.log(`Restart the proxy: ocx start --port ${capturedListen.port}`);
396
+ }
397
+ } else {
398
+ if (trayWasRunning) {
399
+ try {
400
+ const { startWindowsTray } = await import("../tray/windows");
401
+ startWindowsTray();
402
+ } catch { /* keep the primary update failure */ }
403
+ }
404
+ console.error(`\n⚠️ Update failed (${bin} exit ${r.status ?? "?"}). Try manually: ${bin} ${cmdArgs.join(" ")}`);
405
+ process.exit(1);
406
+ }
407
+ }