@yansigit/opencodex 2.31.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 (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,973 @@
1
+ #!/usr/bin/env bun
2
+ import { spawn } from "node:child_process";
3
+ import { currentExternalCodexModelProvider, restoreNativeCodex, restoreNativeCodexAsync, shouldInjectApiAuthHeader } from "../codex/inject";
4
+ import { stripGrokConfig } from "../grok/inject";
5
+ import {
6
+ describeHistoryJobFailure,
7
+ resolveCodexHistoryJobTarget,
8
+ runCodexHistoryJob,
9
+ } from "../codex/history-job";
10
+ import { reconcileJournal } from "../codex/journal";
11
+ import {
12
+ codexAutoStartEnabled,
13
+ getConfigDir,
14
+ loadConfig,
15
+ readPid,
16
+ readPidFileValue,
17
+ readRuntimePort,
18
+ removePid,
19
+ removePidIfValueIs,
20
+ removeRuntimePort,
21
+ removeRuntimePortIfPidIs,
22
+ saveConfig,
23
+ writePid,
24
+ writeRuntimePort,
25
+ } from "../config";
26
+ import { collectStatus } from "./status";
27
+
28
+ import {
29
+ discoverStableProxyForRestart,
30
+ isProxyReplacement,
31
+ runProxyRestart,
32
+ runTrayProxyStart,
33
+ type ProxyRestartLive,
34
+ type ProxyRestartResult,
35
+ } from "./tray-proxy";
36
+ import { requestBoundSystemRestart } from "./system-restart-client";
37
+ import { installCrashGuards } from "../lib/crash-guard";
38
+ import { dispatchCommand } from "./dispatch";
39
+ import { findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
40
+ import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
41
+ import { createReadinessGate } from "../server/readiness";
42
+ import { runReady, type ReadyArgs } from "./ready";
43
+ import { runCli } from "./root";
44
+ import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control";
45
+ import { loadServiceTokenFromFile } from "../lib/service-secrets";
46
+ import { diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
47
+ import { startupHealthSummary } from "../codex/autostart-health";
48
+ import { drainAndShutdown, isRecyclingForExit, startServer } from "../server";
49
+ import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
50
+ import { buildDesktop3pRegistry } from "../claude/desktop-3p";
51
+ import { startTokenGuardian } from "../oauth/token-guardian";
52
+ import { startHistoryMigrationGuardian } from "../codex/history-migration-guardian";
53
+ import { maybeShowStarPrompt } from "./star-prompt";
54
+ import { scheduleCatalogPrewarm } from "./catalog-prewarm";
55
+ import { maybeShowUpdatePrompt } from "../update/notify";
56
+ import { PKG } from "../update/index";
57
+ import { syncModelsToCodex } from "../codex/sync";
58
+ import {
59
+ shouldSyncGrokOnStart,
60
+ syncCodexOnStartIfEnabled,
61
+ } from "../codex/desired-state";
62
+ import {
63
+ reconcileClientStartupBeforeReady,
64
+ syncClaudeAgentDefsAtProxyStartup,
65
+ } from "./claude-agent-startup-sync";
66
+ import {
67
+ grokSyncFailureMessage,
68
+ reconcileEnsureDesiredIntegrations,
69
+ } from "./ensure-desired-integrations";
70
+
71
+ /**
72
+ * A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
73
+ * `claude-env.sh` from every new interactive shell, pointing at a proxy or a CLI that may no
74
+ * longer exist. `reconcileShellHook` already reports `state: "failed"`, but both call sites
75
+ * discarded it, so the one outcome the user has to act on was the one they never saw.
76
+ */
77
+ function reportShellHookFailure(result: { state: "installed" | "absent" | "failed"; reason?: string }): void {
78
+ if (result.state !== "failed") return;
79
+ console.warn(` Claude shell hook not reconciled${result.reason ? `: ${result.reason}` : ""}`);
80
+ console.warn(" Check ~/.zshrc for the '# opencodex claude-env hook' block.");
81
+ }
82
+
83
+
84
+ import { removeOwnedConfigState } from "../lib/config-ownership";
85
+ import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
86
+ import { selfLaunchArgv } from "../lib/self-launch-argv";
87
+ import { initializeNodeLauncherContext } from "./launcher-context";
88
+ import { createLocalAttestationSecret } from "../lib/local-management-attestation";
89
+ import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
90
+
91
+ initializeNodeLauncherContext();
92
+
93
+ // Head: version/help early exits, `ready` pre-parse (exit 64 before any
94
+ // preflight), and the bounded Codex-shim auto-restore preflight live in
95
+ // src/cli/root.ts (Phase 1 of the CLI deepening). runCli exits for
96
+ // version/help and returns the dispatchable head otherwise; the switch below
97
+ // owns command dispatch.
98
+ const head = await runCli(process.argv.slice(2));
99
+ const args = head.args;
100
+ const command = head.command;
101
+
102
+ function parsePortOption(): number | undefined {
103
+ if (args.length === 1) return undefined;
104
+ if (args.length !== 3 || args[1] !== "--port") {
105
+ console.error("Usage: ocx start [--port <port>]");
106
+ process.exit(1);
107
+ }
108
+ const portIdx = args.indexOf("--port");
109
+ if (portIdx === -1) return undefined;
110
+ const value = args[portIdx + 1];
111
+ const port = value && /^\d+$/.test(value) ? Number(value) : NaN;
112
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
113
+ console.error("Invalid port number");
114
+ process.exit(1);
115
+ }
116
+ return port;
117
+ }
118
+
119
+ async function waitForProxy(timeoutMs = 8_000): Promise<LiveProxy | null> {
120
+ const deadline = Date.now() + timeoutMs;
121
+ while (Date.now() < deadline) {
122
+ // Runtime-state-first with identity: finds the proxy even when it started on a
123
+ // fallback port, and never mistakes a foreign 200 for our proxy.
124
+ const live = await findLiveProxy();
125
+ if (live) return live;
126
+ await new Promise(resolve => setTimeout(resolve, 150));
127
+ }
128
+ return null;
129
+ }
130
+
131
+ /** Argv for detached `start`, optionally hard-pinning the listen port. */
132
+ function startArgv(port?: number): string[] {
133
+ const args = ["start"];
134
+ if (typeof port === "number" && Number.isFinite(port) && port > 0 && port <= 65535) {
135
+ args.push("--port", String(Math.trunc(port)));
136
+ }
137
+ return selfLaunchArgv(args);
138
+ }
139
+
140
+ async function chooseListenPort(requestedPort?: number): Promise<number> {
141
+ const config = loadConfig();
142
+ const preferred = requestedPort ?? config.port ?? 10100;
143
+ const hardPin = requestedPort !== undefined && requestedPort > 0;
144
+ const reservedLoopbackPort = config.unauthenticatedLoopbackListener?.enabled
145
+ ? config.unauthenticatedLoopbackListener.port
146
+ : undefined;
147
+ // Before the reclaim path, not after (#1102). Asking for the port the loopback listener is
148
+ // configured to bind is a configuration mistake, and reclaim would spend up to 60 seconds
149
+ // waiting for a socket to free before reporting "port is busy" — the wrong diagnosis for a
150
+ // collision the config can state outright.
151
+ if (reservedLoopbackPort !== undefined && preferred === reservedLoopbackPort) {
152
+ throw new Error(
153
+ `Port ${preferred} is reserved for unauthenticatedLoopbackListener; choose a different proxy port.`,
154
+ );
155
+ }
156
+ // Soft start: brief prefer-retry then ephemeral hop.
157
+ // Explicit `--port` (service wrappers / update restart): wait for the pinned port
158
+ // to free without killing any listener (healthy ocx / foreign). Never hop.
159
+ if (hardPin && preferred > 0) {
160
+ const { reclaimListenPort } = await import("../server/port-reclaim");
161
+ await reclaimListenPort(preferred, config.hostname ?? "127.0.0.1", {
162
+ // Ghost LISTEN rows with a dead PID can outlive the process for a while.
163
+ // SetTcpEntry(DELETE_TCB) needs elevation (often returns 317), so the only
164
+ // reliable non-admin recovery is to wait for the OS to release the TCB.
165
+ timeoutMs: 60_000,
166
+ intervalMs: 100,
167
+ scanIntervalMs: 500,
168
+ killOcxHolders: false,
169
+ dropTcpRows: true,
170
+ });
171
+ }
172
+ try {
173
+ const selected = await findAvailablePort(preferred, config.hostname ?? "127.0.0.1", {
174
+ // After reclaim, keep probing briefly — ghost rows sometimes clear between
175
+ // the reclaim deadline and the final listen. Still never hop off `--port`.
176
+ preferRetryMs: hardPin ? 5_000 : 750,
177
+ preferRetryIntervalMs: 50,
178
+ allowEphemeralFallback: !hardPin,
179
+ // Never hand the public listener the port the loopback listener is configured to
180
+ // bind (#1102). Without this, `--port <loopback port>` binds the public listener
181
+ // first and the loopback bind then fails, rolling back a startup that was only
182
+ // ever a config collision.
183
+ ...(reservedLoopbackPort !== undefined ? { reservedPort: reservedLoopbackPort } : {}),
184
+ });
185
+ if (preferred > 0 && selected !== preferred) {
186
+ console.log(`⚠️ Port ${preferred} is busy; starting opencodex on ${selected}.`);
187
+ }
188
+ if (shouldPersistSelectedPort(config.port, selected, preferred)) {
189
+ config.port = selected;
190
+ saveConfig(config);
191
+ }
192
+ return selected;
193
+ } catch (err) {
194
+ if (err instanceof PortUnavailableError) {
195
+ console.error(`❌ ${err.message}`);
196
+ console.error(" Stop whatever holds that port, or change config.port, then retry.");
197
+ process.exit(1);
198
+ }
199
+ throw err;
200
+ }
201
+ }
202
+
203
+ async function findProxyOwnerBeforeJournalRecovery(
204
+ options: { probeConfiguredPort?: boolean } = {},
205
+ ): Promise<{ live: LiveProxy | null; pidSnapshot: number | null }> {
206
+ const pidSnapshot = readPidFileValue();
207
+ const hasRuntimeOwner = readRuntimePort() !== null;
208
+ const shouldProbe = pidSnapshot !== null || hasRuntimeOwner || options.probeConfiguredPort === true;
209
+ const live = shouldProbe ? await findLiveProxy() : null;
210
+ if (live) return { live, pidSnapshot };
211
+
212
+ // The probe established that the snapshotted owner is stale. Compare before
213
+ // deleting so a concurrent start that rewrote the PID file keeps its state.
214
+ removePidIfValueIs(pidSnapshot);
215
+ if (!currentExternalCodexModelProvider()) reconcileJournal();
216
+ return { live: null, pidSnapshot };
217
+ }
218
+
219
+ async function handleStart(options: { block?: boolean } = {}) {
220
+ // Native (WinSW) service mode has no batch wrapper to read the service token file
221
+ // into the environment, so the app loads it here before the server binds. The server
222
+ // auth path reads OPENCODEX_API_AUTH_TOKEN from the environment.
223
+ const serviceToken = loadServiceTokenFromFile(process.env);
224
+ if (serviceToken) process.env.OPENCODEX_API_AUTH_TOKEN = serviceToken;
225
+ const requestedPort = parsePortOption();
226
+ const owner = await findProxyOwnerBeforeJournalRecovery();
227
+ if (owner.live) {
228
+ // Service-wrapper context (opencodex-service.cmd `:loop`): a healthy proxy from
229
+ // ANY source means the requested port is already served. Exit 0 so the wrapper's
230
+ // `if %ERRORLEVEL% NEQ 0` retry loop terminates instead of respawning every 5s
231
+ // against a listener it can never claim (observed as an endless
232
+ // "Proxy already running" service.log loop).
233
+ // Only the exact "1" sentinel takes this path — the same check syncCleanup
234
+ // uses — so an env value like "0" or "false" cannot bypass the conflict error.
235
+ if (process.env.OCX_SERVICE === "1") {
236
+ console.log(`Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}); service wrapper staying out of the way.`);
237
+ process.exit(0);
238
+ }
239
+ console.error(`⚠️ Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}). Use 'ocx stop' first.`);
240
+ process.exit(1);
241
+ }
242
+
243
+ // Interactive-only update prompt. Must run BEFORE we bind a port / write a
244
+ // PID: choosing "Update now" installs globally and exits, so we never want a
245
+ // live daemon holding resources while it overwrites its own binary.
246
+ await maybeShowUpdatePrompt();
247
+
248
+ // Port selection is check-then-bind: a concurrent `ocx start`/`ensure` can win the port
249
+ // between the probe and Bun.serve. Soft starts may re-pick; hard-pinned `--port` retries
250
+ // the same port only (never hop — that was the remaining PR #152 gap).
251
+ let port = await chooseListenPort(requestedPort);
252
+ // One private readiness gate for this startServer invocation, captured by the
253
+ // listener's closure. handleStart owns it and transitions it after the
254
+ // post-startup sync settles. A second startServer in the same process would
255
+ // get its own gate and could never reset/mutate this one.
256
+ const readinessGate = createReadinessGate();
257
+ let server: ReturnType<typeof startServer>;
258
+ const localAttestationSecret = createLocalAttestationSecret();
259
+ for (let attempt = 0; ; attempt++) {
260
+ try {
261
+ server = startServer(port, { localAttestationSecret, readinessGate });
262
+ // Prewarm the live provider model cache as soon as the port is bound so the
263
+ // first GUI /v1/models (and syncModelsToCodex below) share one discovery flight
264
+ // instead of racing duplicate upstream /models fetches.
265
+ scheduleCatalogPrewarm();
266
+ break;
267
+ } catch (err) {
268
+ if (!isAddrInUse(err) || attempt >= 2) throw err;
269
+ if (requestedPort !== undefined) {
270
+ console.log(`⚠️ Port ${port} was taken while starting; waiting to retry the same port...`);
271
+ const hostname = loadConfig().hostname ?? "127.0.0.1";
272
+ const freed = await waitForPortAvailable(port, hostname, { timeoutMs: 3_000, intervalMs: 50 });
273
+ if (!freed) {
274
+ console.error(`❌ Port ${port} stayed busy; refusing to hop to an ephemeral port.`);
275
+ process.exit(1);
276
+ }
277
+ continue;
278
+ }
279
+ console.log(`⚠️ Port ${port} was taken while starting; picking another...`);
280
+ port = await chooseListenPort(requestedPort);
281
+ }
282
+ }
283
+ // A single request's streaming error must never crash the daemon serving every
284
+ // other Codex session — capture the full stack to crash.log and stay up.
285
+ installCrashGuards();
286
+ writePid(process.pid);
287
+
288
+ const config = loadConfig();
289
+ writeRuntimePort({ pid: process.pid, port, hostname: config.hostname, attestationSecret: localAttestationSecret });
290
+ // No pre-emptive snapshot here. `injectCodexConfig` journals the exact bytes it
291
+ // is about to transform; snapshotting earlier only captured a baseline that could
292
+ // already be stale by the time injection ran (#477).
293
+
294
+ // Background proactive token refresh. No-op unless config.tokenGuardian.enabled; timer is unref'd
295
+ // so it never keeps the process alive on its own. Stopped in syncCleanup so no refresh fires mid-drain.
296
+ const guardian = startTokenGuardian();
297
+ // Design B upgrade path: keep retrying the one-time opencodex→openai history migration in the
298
+ // background — the first `ocx start` after an update usually races the Codex app's DB lock.
299
+ // Loopback-only (legacy mode still forward-tags) and respects syncResumeHistory opt-out.
300
+ let historyGuardian: ReturnType<typeof startHistoryMigrationGuardian> | undefined;
301
+
302
+ let cleaned = false;
303
+ let cleanupSucceeded = true;
304
+ const syncCleanup = () => {
305
+ if (cleaned) return cleanupSucceeded;
306
+ cleaned = true;
307
+ try { guardian.stop(); } catch { /* best-effort */ }
308
+ try { historyGuardian?.stop(); } catch { /* best-effort */ }
309
+ // Dashboard drain-and-restart (#563) must not tear down injection: the replacement
310
+ // process expects Codex/Grok/env fences to still be in place.
311
+ const recycling = isRecyclingForExit();
312
+ if (!recycling) {
313
+ try { revertSystemEnv(); } catch { /* best-effort */ }
314
+ }
315
+ removePid(process.pid);
316
+ removeRuntimePort(process.pid);
317
+ const preserveRouting = process.env.OCX_SERVICE === "1";
318
+ if (!recycling && !preserveRouting && !currentExternalCodexModelProvider()) {
319
+ try {
320
+ const restored = restoreNativeCodex();
321
+ if (!restored.success) {
322
+ cleanupSucceeded = false;
323
+ console.error(`⚠️ Native Codex restore failed during shutdown: ${restored.message}`);
324
+ }
325
+ } catch (error) {
326
+ cleanupSucceeded = false;
327
+ console.error(`⚠️ Native Codex restore failed during shutdown: ${error instanceof Error ? error.message : String(error)}`);
328
+ }
329
+ }
330
+ // Same ownership rule as `ocx stop`: if the installed service belongs to another home, the
331
+ // Grok fence is shared state we must not remove — that service keeps running and would be
332
+ // left pointing nowhere. This guard also covers signal-driven exits, which is the path that
333
+ // would otherwise bypass handleStop's gate entirely.
334
+ if (!recycling && !preserveRouting && serviceEnvironmentOwnedHere()) {
335
+ try { stripGrokConfig(); } catch { /* best-effort restore */ }
336
+ }
337
+ return cleanupSucceeded;
338
+ };
339
+
340
+ let shuttingDown = false;
341
+ let shutdownStartedAt = 0;
342
+ // Terminal Ctrl-C delivers SIGINT to the whole foreground group AND the launcher
343
+ // forwards its own — two signals land within milliseconds. Treat a duplicate inside
344
+ // this window as the same Ctrl-C (one graceful drain); a deliberate later press
345
+ // escalates to an immediate force-exit ("gradual kill").
346
+ const FORCE_AFTER_MS = 500;
347
+ const shutdown = () => {
348
+ const now = Date.now();
349
+ if (shuttingDown) {
350
+ if (now - shutdownStartedAt < FORCE_AFTER_MS) return; // near-simultaneous duplicate — ignore
351
+ console.log("\n⏹ Force shutdown (second signal).");
352
+ try { syncCleanup(); } catch { /* best-effort */ }
353
+ process.exit(130);
354
+ }
355
+ shuttingDown = true;
356
+ shutdownStartedAt = now;
357
+ console.log("\n🛑 Shutting down opencodex proxy...");
358
+ void (async () => {
359
+ try {
360
+ await drainAndShutdown(server, config.shutdownTimeoutMs ?? 5000);
361
+ } finally {
362
+ const restored = syncCleanup(); // idempotent (cleaned-guard); also re-run by process.on("exit")
363
+ process.exit(restored ? 0 : 1);
364
+ }
365
+ })();
366
+ };
367
+
368
+ process.on("SIGINT", shutdown);
369
+ process.on("SIGTERM", shutdown);
370
+ // The launcher (bin/ocx.mjs) forwards SIGHUP too (e.g. terminal close); handle it
371
+ // gracefully here so it drains + cleans up instead of a default immediate kill.
372
+ process.on("SIGHUP", shutdown);
373
+ process.on("exit", syncCleanup);
374
+
375
+ // System-wide env injection AFTER signal handlers are registered (crash safety:
376
+ // syncCleanup reverts even if injection itself or subsequent startup steps fail).
377
+ const systemEnv = await injectSystemEnv(port, config).catch(() => ({ injected: false }));
378
+ // The hook is useful only for an installed Claude Code CLI. Reconcile instead of
379
+ // appending unconditionally so stale OpenCodex-owned hooks are removed as well.
380
+ reportShellHookFailure(reconcileShellHook(systemEnv.injected));
381
+ await maybeShowStarPrompt(); // once-only Yes/No GitHub-star prompt on first interactive start
382
+ // Codex sync owns the ready/failed verdict, but its successful transition is
383
+ // deferred until the best-effort Claude roster reconciliation settles. This
384
+ // keeps /readyz closed across both startup writes without making an optional
385
+ // Claude integration failure prevent the proxy from starting.
386
+ const startupSync = await reconcileClientStartupBeforeReady(
387
+ readinessGate,
388
+ gate => syncCodexOnStartIfEnabled(port, config, undefined, gate),
389
+ () => systemEnv.injected
390
+ ? Promise.resolve(null)
391
+ : syncClaudeAgentDefsAtProxyStartup(config, port),
392
+ );
393
+ if (!startupSync.ran) console.log(" Codex integration OFF; startup left Codex native.");
394
+ // #1046: one warning per startup, after BOTH writes. The server's cache
395
+ // invalidation happens first and the catalog sync second, so the mtime is only
396
+ // final here — and neither write site warns on its own, or a boot that hits
397
+ // both would warn twice.
398
+ const { consumeStartupCacheInvalidationWrite } = await import("../server");
399
+ if (consumeStartupCacheInvalidationWrite() || startupSync.catalogWritten || startupSync.cacheSynced) {
400
+ const { warnIfStaleCodexAppServersAfterStartupWrite } = await import("../codex/app-server-processes");
401
+ warnIfStaleCodexAppServersAfterStartupWrite({ log: console });
402
+ }
403
+ if (!currentExternalCodexModelProvider() && !shouldInjectApiAuthHeader(config) && config.syncResumeHistory !== false) {
404
+ historyGuardian = startHistoryMigrationGuardian();
405
+ }
406
+ // Build Desktop 3P alias registry so inbound claude-opus-4-8-{code} aliases (and legacy claude-opus-4-{code}) decode correctly.
407
+ try {
408
+ const { fetchAllModels } = await import("../server/management-api");
409
+ const { visibleNativeSlugs, filterCatalogVisibleModels } = await import("../codex/catalog");
410
+ const models = filterCatalogVisibleModels(await fetchAllModels(config), config);
411
+ buildDesktop3pRegistry(
412
+ [...visibleNativeSlugs(config)],
413
+ models.map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow })),
414
+ config.claudeCode?.desktopProfile,
415
+ );
416
+ } catch { /* best-effort — registry rebuilds on first /v1/models call */ }
417
+ // Grok Build auto-registration: additive fenced block in ~/.grok/config.toml so an installed
418
+ // grok CLI can pick opencodex-routed models without manual config. No-op when ~/.grok is
419
+ // absent or the bind is non-loopback; removed again by stop/eject/uninstall/shutdown.
420
+ // Deliberately a SIBLING of the Desktop-3P block above: nesting it there meant a catalog
421
+ // failure skipped the fence entirely, even though syncGrokConfig handles that case itself.
422
+ //
423
+ // Gated on the persisted switch: without this, turning Grok off lasted exactly
424
+ // one restart, because the toggle removed the fence and start wrote it back.
425
+ if (shouldSyncGrokOnStart(config)) try {
426
+ const { syncGrokConfig } = await import("../grok/sync");
427
+ const r = await syncGrokConfig(port, config, config.hostname ? { hostname: config.hostname } : {});
428
+ if (r.changed) console.log(" + Grok Build config updated (~/.grok/config.toml)");
429
+ else if (!r.ok) console.error(`⚠️ ${r.message}`);
430
+ } catch (err) {
431
+ // Best-effort: grok integration must never block startup. But swallowing the error
432
+ // silently is how a stale fence survives unnoticed — ~/.grok/config.toml keeps
433
+ // pointing at whatever port the LAST successful sync wrote, and if that listener is
434
+ // gone every grok turn retries against a refused connection with nothing in our log
435
+ // to explain it. Name the failure and the one command that repairs it.
436
+ console.error(`⚠️ ${grokSyncFailureMessage(err)}`);
437
+ }
438
+ if (options.block ?? true) {
439
+ setInterval(() => {}, 60_000);
440
+ await new Promise<void>(() => {});
441
+ }
442
+ }
443
+
444
+ function detachedStartEnvironment(): NodeJS.ProcessEnv {
445
+ const env: NodeJS.ProcessEnv = { ...process.env };
446
+ // Only a real service wrapper may claim supervision. A detached ensure/tray child
447
+ // is an ordinary owner: while live it maintains routing, and on exit it restores it.
448
+ delete env.OCX_SERVICE;
449
+ return withProcessRuntimeProvenance(env);
450
+ }
451
+
452
+ async function handleEnsure(options: { existingIsSuccess?: boolean } = {}): Promise<boolean> {
453
+ const owner = await findProxyOwnerBeforeJournalRecovery({ probeConfiguredPort: true });
454
+ const config = loadConfig();
455
+ if (!codexAutoStartEnabled(config)) {
456
+ console.log("Codex autostart is disabled.");
457
+ return false;
458
+ }
459
+ const live = owner.live;
460
+ if (live) {
461
+ if (options.existingIsSuccess === false) {
462
+ console.error("Proxy appeared while restart was confirming absence; no start was attempted.");
463
+ return false;
464
+ }
465
+ const synced = await syncModelsToCodex(live.port).catch(e => {
466
+ console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
467
+ return null;
468
+ });
469
+ if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
470
+ // Ensure env file exists for already-running proxy (may have been deleted or pre-dates this feature).
471
+ const systemEnv = await injectSystemEnv(live.port, config).catch(() => ({ injected: false }));
472
+ reportShellHookFailure(reconcileShellHook(systemEnv.injected));
473
+ if (!systemEnv.injected) await syncClaudeAgentDefsAtProxyStartup(config, live.port);
474
+ // Refresh the Grok Build fence too (same contract as start). live.hostname is the
475
+ // hostname the running proxy actually bound — config.hostname may have drifted.
476
+ // The reconciler re-reads immediately before each client-file mutation; only
477
+ // the live proxy's observed bind host is safe to carry across this boundary.
478
+ await reconcileEnsureDesiredIntegrations(
479
+ live.port,
480
+ { kind: "live", hostname: live.hostname },
481
+ );
482
+ console.log(`✅ Proxy running on port ${live.port}`);
483
+ return true;
484
+ }
485
+
486
+ const pinPort = config.port ?? 10100;
487
+ const child = spawn(process.execPath, startArgv(pinPort > 0 ? pinPort : undefined), {
488
+ detached: true,
489
+ stdio: "ignore",
490
+ windowsHide: true,
491
+ env: detachedStartEnvironment(),
492
+ });
493
+ child.unref();
494
+
495
+ const port = (await waitForProxy())?.port;
496
+ if (!port) {
497
+ console.error("❌ Proxy did not become healthy after starting.");
498
+ process.exitCode = 1;
499
+ return false;
500
+ }
501
+ // Deterministic fence guarantee when the durable switch is ON: the spawned child
502
+ // injects late in its own startup, but this parent returns as soon as /healthz
503
+ // responds — align here too so `ocx ensure` never returns with a stale ON/OFF mismatch.
504
+ // Persisted state is loaded inside each mutation after waitForProxy, so a
505
+ // toggle while the child starts wins over the pre-spawn snapshot.
506
+ await reconcileEnsureDesiredIntegrations(port, { kind: "spawned" });
507
+ // Always sync the LIVE port: after a fallback-port start, config.port still names the
508
+ // busy preferred port — syncing that would point Codex at a dead listener.
509
+ const synced = await syncModelsToCodex(port).catch(e => {
510
+ console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
511
+ return null;
512
+ });
513
+ if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
514
+ // The child opens /healthz before its best-effort roster reconcile. Await the same idempotent
515
+ // operation in the parent so `ocx ensure` cannot report success while stale ocx-*.md files are
516
+ // still observable. Always use the live port, including fallback-port starts.
517
+ await syncClaudeAgentDefsAtProxyStartup(config, port);
518
+ console.log(`✅ Proxy running on port ${port}`);
519
+ return true;
520
+ }
521
+
522
+ /** Fixed tray action: start the proxy without depending on codexAutoStart. */
523
+ async function handleTrayProxyStart(existingIsSuccess = true): Promise<boolean> {
524
+ const ok = await runTrayProxyStart({
525
+ findLive: findLiveProxy,
526
+ existingIsSuccess,
527
+ diagnoseService: () => {
528
+ const service = diagnoseService();
529
+ return { installed: service.installed, startable: serviceStartableFromTray(service), summary: service.summary };
530
+ },
531
+ startService: () => serviceCommand("start"),
532
+ startDirect: () => {
533
+ const config = loadConfig();
534
+ const port = (config.port ?? 10100) > 0 ? (config.port ?? 10100) : 10100;
535
+ const child = spawn(process.execPath, startArgv(port), {
536
+ detached: true,
537
+ stdio: "ignore",
538
+ windowsHide: true,
539
+ env: detachedStartEnvironment(),
540
+ });
541
+ child.unref();
542
+ },
543
+ // serviceCommand("start") already spends up to 20s confirming the supervised
544
+ // child. Slow Windows hosts can still be publishing native-main state after that
545
+ // first window, so keep one shared follow-up budget instead of returning a false
546
+ // failure while Task Scheduler is still starting the approved child.
547
+ waitForProxy: () => waitForProxy(40_000),
548
+ info: message => console.log(message),
549
+ error: message => console.error(message),
550
+ });
551
+ // serviceCommand("start") can set exitCode=1 after its own 20s probe, while
552
+ // the coordinator's bounded follow-up observes the same service become live.
553
+ // The final observed state, not the earlier probe, owns this command result.
554
+ process.exitCode = ok ? 0 : 1;
555
+ return ok;
556
+ }
557
+
558
+ const PROXY_RESTART_OBSERVE_MS = MEMORY_DRAIN_RESTART_MS + REPLACEMENT_READY_TIMEOUT_MS + 15_000;
559
+
560
+ async function waitForProxyReplacement(
561
+ previous: ProxyRestartLive,
562
+ deadlineAt: number,
563
+ ): Promise<ProxyRestartLive | null> {
564
+ while (Date.now() < deadlineAt) {
565
+ const live = await findLiveProxy({ deadlineAt });
566
+ if (Date.now() >= deadlineAt) return null;
567
+ // Modern /healthz publishes a PID. Require a different, identity-verified process;
568
+ // merely seeing the old port online again is not proof that restart completed.
569
+ if (isProxyReplacement(previous, live)) {
570
+ return live;
571
+ }
572
+ const remainingMs = deadlineAt - Date.now();
573
+ if (remainingMs > 0) await Bun.sleep(Math.min(250, remainingMs));
574
+ }
575
+ return null;
576
+ }
577
+
578
+ function reportRestartFailure(result: Extract<ProxyRestartResult, { ok: false }>): void {
579
+ if (result.phase === "identity") {
580
+ console.error("❌ Refusing to restart because the running proxy identity could not be attested.");
581
+ } else if (result.phase === "request") {
582
+ const code = result.error instanceof Error ? result.error.message : "";
583
+ if (code === "restart_capability_unsupported") {
584
+ console.error("❌ The running proxy predates process-bound restart support; no unsafe fallback was attempted.");
585
+ console.error(" After confirming this home owns the proxy, run `ocx stop` and then `ocx start` once.");
586
+ } else {
587
+ console.error("❌ Proxy restart request could not be confirmed; no fallback stop/start was attempted.");
588
+ }
589
+ } else if (result.phase === "replacement") {
590
+ console.error("❌ Proxy restart was accepted, but no identity-verified replacement became healthy in time.");
591
+ } else {
592
+ console.error("❌ Proxy was not running and the fallback start did not become healthy.");
593
+ }
594
+ }
595
+
596
+ async function handleProxyRestart(
597
+ startWhenStopped: () => Promise<boolean | "skipped">,
598
+ ): Promise<boolean> {
599
+ const deadlineAt = Date.now() + PROXY_RESTART_OBSERVE_MS;
600
+ const result = await runProxyRestart({
601
+ findLive: () => discoverStableProxyForRestart({
602
+ findLive: () => findLiveProxy({ deadlineAt, attempts: 2 }),
603
+ expired: () => Date.now() >= deadlineAt,
604
+ }),
605
+ startWhenStopped,
606
+ requestInPlaceRestart: previous => requestBoundSystemRestart(previous, deadlineAt),
607
+ waitForReplacement: previous => waitForProxyReplacement(previous, deadlineAt),
608
+ });
609
+ if (!result.ok) reportRestartFailure(result);
610
+ process.exitCode = result.ok ? 0 : 1;
611
+ return result.ok;
612
+ }
613
+
614
+ async function handleTrayProxyRestart(): Promise<void> {
615
+ await handleProxyRestart(() => handleTrayProxyStart(false));
616
+ }
617
+
618
+ async function handleRestartStartWhenStopped(): Promise<boolean | "skipped"> {
619
+ if (!codexAutoStartEnabled(loadConfig())) {
620
+ console.log("Codex autostart is disabled; no proxy was started.");
621
+ return "skipped";
622
+ }
623
+ return handleEnsure({ existingIsSuccess: false });
624
+ }
625
+
626
+ async function restoreSharedClientStateAfterStop(): Promise<boolean> {
627
+ let restored = true;
628
+ try {
629
+ const result = await restoreNativeCodexAsync();
630
+ if (result.success) console.log(`↩️ ${result.message}`);
631
+ else {
632
+ restored = false;
633
+ console.error(`⚠️ ${result.message}`);
634
+ }
635
+ } catch (error) {
636
+ restored = false;
637
+ console.error(`⚠️ Native Codex restore failed: ${error instanceof Error ? error.message : String(error)}`);
638
+ }
639
+
640
+ // A refused or thrown Grok strip is actionable because it would point Grok at a dead proxy.
641
+ try {
642
+ const grok = stripGrokConfig();
643
+ if (grok.changed) console.log(`↩️ ${grok.message}`);
644
+ else if (!grok.ok) { restored = false; console.error(`⚠️ ${grok.message}`); }
645
+ } catch (error) {
646
+ restored = false;
647
+ console.error(`⚠️ Grok config restore failed: ${error instanceof Error ? error.message : String(error)}`);
648
+ }
649
+ return restored;
650
+ }
651
+
652
+ async function handleStop() {
653
+ let stopFailed = false;
654
+ let stoppedService = false;
655
+ // An ownership mismatch means the service manager was never even contacted: the installed
656
+ // service is still live and will respawn the proxy. Tearing down SHARED state in that
657
+ // situation (native Codex config, the Grok fence) removes config out from under a running
658
+ // service — the exact failure this flag prevents. A plain stop failure is different: we
659
+ // tried, so local teardown still proceeds.
660
+ let ownershipBlocked = false;
661
+ try {
662
+ stoppedService = stopServiceIfInstalled();
663
+ if (stoppedService) console.log("🛑 Service manager stopped (won't respawn).");
664
+ } catch (err) {
665
+ if (isServiceOwnershipError(err)) {
666
+ ownershipBlocked = true;
667
+ stopFailed = true;
668
+ console.error(`❌ ${err.message}`);
669
+ console.error(" Skipping shared teardown (native Codex restore, Grok config): the installed service is still running.");
670
+ } else {
671
+ console.error(`⚠️ Service manager stop failed: ${err instanceof Error ? err.message : String(err)}`);
672
+ }
673
+ }
674
+
675
+ const pid = readPid();
676
+ if (pid) {
677
+ try {
678
+ // Graceful-first (management-API drain) — on Windows this is the only path where
679
+ // the proxy's shutdown handlers actually run; taskkill /F is the fallback inside.
680
+ await stopProxy(pid);
681
+ console.log(`✅ Proxy (PID ${pid}) stopped.`);
682
+ removePid(pid);
683
+ removeRuntimePort(pid);
684
+ } catch (err) {
685
+ stopFailed = true;
686
+ console.error(`❌ Failed to stop proxy (PID ${pid}).`);
687
+ // stopProxy throws with the reason — an ownership refusal (409) carries the
688
+ // remediation ("run the stop from that home"). Swallowing it leaves the operator
689
+ // with a bare failure and a manual `kill` as the obvious next move, which is the
690
+ // exact teardown the refusal exists to prevent.
691
+ const detail = err instanceof Error ? err.message : String(err);
692
+ if (detail) console.error(` ${detail}`);
693
+ if (err instanceof ProxyOwnershipRefusedError) {
694
+ ownershipBlocked = true;
695
+ console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
696
+ }
697
+ }
698
+ } else {
699
+ // Snapshot the stale on-disk state BEFORE the async probe: a concurrent `ocx start`
700
+ // can write fresh records mid-probe, and the purge below must never delete those.
701
+ const stalePidValue = readPidFileValue();
702
+ const staleRuntimePid = readRuntimePort()?.pid ?? null;
703
+ // Orphan recovery: a live proxy can outlive its pid file (crash, manual delete,
704
+ // corrupt file). Identity-checked liveness still finds it via the runtime record.
705
+ const live = await findLiveProxy();
706
+ if (live?.pid) {
707
+ try {
708
+ await stopProxy(live.pid);
709
+ console.log(`✅ Proxy (PID ${live.pid}) stopped.`);
710
+ } catch (err) {
711
+ stopFailed = true;
712
+ console.error(`❌ Failed to stop proxy (PID ${live.pid}).`);
713
+ const detail = err instanceof Error ? err.message : String(err);
714
+ if (detail) console.error(` ${detail}`);
715
+ if (err instanceof ProxyOwnershipRefusedError) {
716
+ ownershipBlocked = true;
717
+ console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
718
+ }
719
+ }
720
+ } else if (!stoppedService) {
721
+ console.log("No running proxy found.");
722
+ }
723
+ if (!stopFailed) {
724
+ // `readPid() === null` means the snapshotted pid file was absent, invalid, dead, or
725
+ // not ours — stale by definition. Purge (guarded by the snapshot) so `ocx update`'s
726
+ // stop gate can't wedge on it.
727
+ removePidIfValueIs(stalePidValue);
728
+ removeRuntimePortIfPidIs(staleRuntimePid);
729
+ }
730
+ }
731
+ // Environment ownership is independent from service ownership. Always roll back
732
+ // current-home variables; the helper refuses foreign markers on its own.
733
+ try { revertSystemEnv(); } catch { /* best-effort */ }
734
+ if (!ownershipBlocked) {
735
+ if (!await restoreSharedClientStateAfterStop()) stopFailed = true;
736
+ }
737
+ // Set the code rather than exiting inline: `restart` and the tray coordinator call this
738
+ // function and need it to RETURN so they can decide what to do next.
739
+ if (stopFailed) process.exitCode = 1;
740
+ return !stopFailed;
741
+ }
742
+
743
+ async function handleUninstall() {
744
+ const failures: string[] = [];
745
+
746
+ const runStep = async (label: string, step: () => void | boolean | Promise<void | boolean>) => {
747
+ try {
748
+ const changed = await step();
749
+ if (changed === false) console.log(`- ${label}: not installed`);
750
+ else console.log(`✅ ${label}`);
751
+ } catch (err) {
752
+ failures.push(label);
753
+ console.error(`⚠️ ${label} failed: ${err instanceof Error ? err.message : String(err)}`);
754
+ }
755
+ };
756
+
757
+ await runStep("service stopped", () => stopServiceIfInstalled());
758
+
759
+ await runStep("proxy stopped", async () => {
760
+ const pid = readPid();
761
+ if (!pid) return false;
762
+ await stopProxy(pid);
763
+ removePid(pid);
764
+ removeRuntimePort(pid);
765
+ return true;
766
+ });
767
+
768
+ await runStep("service removed", () => uninstallServiceIfInstalled());
769
+
770
+ if (process.platform === "win32") {
771
+ await runStep("Windows tray removed", async () => {
772
+ const { getWindowsTrayStatus, uninstallWindowsTray } = await import("../tray/windows");
773
+ const tray = getWindowsTrayStatus();
774
+ if (!tray.installed && !tray.stale && !tray.running) return false;
775
+ uninstallWindowsTray();
776
+ });
777
+ }
778
+
779
+ await runStep("native Codex restored", async () => {
780
+ const r = await restoreNativeCodexAsync();
781
+ if (!r.success) throw new Error(r.message);
782
+ });
783
+
784
+ await runStep("Grok Build config restored", () => {
785
+ const r = stripGrokConfig();
786
+ if (!r.ok) throw new Error(r.message);
787
+ return r.changed;
788
+ });
789
+
790
+ await runStep("system env vars reverted", () => {
791
+ const r = revertSystemEnv();
792
+ if (!r.reverted && r.reason !== "no tracking file" && r.reason !== "not macOS") throw new Error(r.reason ?? "revert failed");
793
+ });
794
+
795
+ await runStep("shell hook removed", () => {
796
+ const r = uninstallShellHook();
797
+ if (!r.removed && r.reason !== "not installed" && r.reason !== "not macOS") throw new Error(r.reason ?? "remove failed");
798
+ });
799
+
800
+ try {
801
+ const { uninstallCodexShim } = await import("../codex/shim");
802
+ const r = uninstallCodexShim();
803
+ console.log(r.removed ? "✅ Codex autostart shim removed" : "- Codex autostart shim removed: not installed");
804
+ } catch (err) {
805
+ failures.push("Codex autostart shim removed");
806
+ console.error(`⚠️ Codex autostart shim removed failed: ${err instanceof Error ? err.message : String(err)}`);
807
+ }
808
+
809
+ if (failures.length === 0) {
810
+ await runStep("opencodex config removed", () => {
811
+ const result = removeOwnedConfigState(getConfigDir());
812
+ if (result.status === "absent") return false;
813
+ if (result.status === "removed") return true;
814
+ const residual = result.residualPaths.length > 0
815
+ ? ` Residual path(s): ${result.residualPaths.join(", ")}`
816
+ : "";
817
+ throw new Error(`${result.status} uninstall: ${result.reason ?? "config state was not removed"}.${residual}`);
818
+ });
819
+ } else {
820
+ console.error("Leaving opencodex config/backups in place so the failed restore step can be retried.");
821
+ }
822
+
823
+ if (failures.length > 0) {
824
+ console.error(`\nUninstall finished with ${failures.length} failed step(s): ${failures.join(", ")}`);
825
+ process.exit(1);
826
+ }
827
+ console.log(`\n✅ opencodex local state removed. Remove the package with: npm uninstall -g ${PKG}`);
828
+ }
829
+
830
+ async function handleStatus() {
831
+ const statusArgs = args.slice(1);
832
+ const wantsJson = statusArgs.length === 1 && statusArgs[0] === "--json";
833
+ if (statusArgs.length > 1 || (statusArgs.length === 1 && !wantsJson)) {
834
+ console.error("Usage: ocx status [--json]");
835
+ process.exit(1);
836
+ }
837
+
838
+ const status = await collectStatus();
839
+ if (wantsJson) {
840
+ console.log(JSON.stringify(status.json, null, 2));
841
+ return;
842
+ }
843
+
844
+ if (status.json.proxy.pid || status.json.proxy.health.ok) {
845
+ console.log(`✅ Proxy: ${status.proxyLabel}`);
846
+ } else {
847
+ console.log(`❌ Proxy: ${status.proxyLabel}`);
848
+ }
849
+ console.log(` Health: ${status.healthLabel}`);
850
+ if (!(status.json.proxy.pid || status.json.proxy.health.ok)) {
851
+ console.log(" ↳ Not running — Codex/Claude requests will fail with connection errors.");
852
+ // The service summary a few lines below already tells a registered-but-not-serving
853
+ // user to repair. Printing "install the persistent service" unconditionally
854
+ // contradicted it in the same report, and install re-registers: UAC on Windows and a
855
+ // possible WinSW-to-scheduler switch for someone who already has a service.
856
+ const installed = status.json.startup.serviceInstalled && !status.json.startup.serviceConflict;
857
+ console.log(installed
858
+ ? " Restart with 'ocx start', or refresh the installed service: 'ocx service repair'."
859
+ : " Restart with 'ocx start', or install the persistent service: 'ocx service install'.");
860
+ }
861
+ console.log(` Dashboard: ${status.json.dashboard.url}`);
862
+ console.log(` Config: ${status.json.paths.config}`);
863
+ console.log(` PID file: ${status.json.paths.pid}`);
864
+ console.log(` Runtime: ${status.json.paths.runtime}`);
865
+ console.log(` Runtime source: ${status.json.runtime.source}${status.json.runtime.overrideEnv ? ` (${status.json.runtime.overrideEnv})` : ""}`);
866
+ console.log(` Default provider: ${status.json.defaultProvider}`);
867
+ console.log(` Codex autostart: ${status.json.codexAutostart ? "enabled" : "disabled"}`);
868
+ console.log(` Restart safety: ${startupHealthSummary(status.json.startup)}`);
869
+ console.log(` Service: ${status.json.service.summary}`);
870
+ console.log(` ${status.json.codexShim.summary}`);
871
+ console.log(` Codex runtime: ${status.json.codexRuntime.path}`);
872
+ console.log(` Codex version: ${status.json.codexRuntime.version ?? "unknown"}`);
873
+ console.log(` Codex source: ${status.json.codexRuntime.source}`);
874
+ console.log(` Codex home: ${status.json.codexHome.effectiveCodexHome}`);
875
+ if (status.json.codexHome.warning) {
876
+ console.log(` ⚠️ ${status.json.codexHome.warning}`);
877
+ console.log(` Action: ${status.json.codexHome.action}`);
878
+ }
879
+ console.log(` Catalog clamp: ${status.json.codexRuntime.catalogClamp.active ? "active" : "inactive"}`);
880
+ if (status.json.codexRuntime.catalogClamp.removedEfforts.length > 0) {
881
+ console.log(` Removed efforts: ${status.json.codexRuntime.catalogClamp.removedEfforts.join(", ")}`);
882
+ }
883
+ if (status.json.codexRuntime.warning) {
884
+ console.log(` ⚠️ ${status.json.codexRuntime.warning}`);
885
+ }
886
+ if (status.json.codexPlugins.applicable) {
887
+ const icon = status.json.codexPlugins.stale ? "⚠️ " : "✅";
888
+ console.log(` ${icon} Codex bundled plugins: ${status.json.codexPlugins.summary}`);
889
+ if (status.json.codexPlugins.suggestedRepair) {
890
+ console.log(` Suggested: ${status.json.codexPlugins.suggestedRepair}`);
891
+ }
892
+ }
893
+ const { collectOAuthHealthEntriesForCli, oauthLoginSummary } = await import("../oauth");
894
+ const { formatOAuthHealthForStatus } = await import("./status-oauth");
895
+ console.log(` OAuth logins:`);
896
+ for (const e of oauthLoginSummary()) {
897
+ console.log(` ${e.provider.padEnd(10)} ${e.loggedIn ? `✓ logged in${e.email ? ` (${e.email})` : ""}` : "✗ not logged in"}`);
898
+ }
899
+ const oauthHealthBlock = formatOAuthHealthForStatus(await collectOAuthHealthEntriesForCli());
900
+ if (oauthHealthBlock) {
901
+ for (const line of oauthHealthBlock.split("\n")) {
902
+ console.log(` ${line}`);
903
+ }
904
+ }
905
+ }
906
+
907
+ async function handleRecoverHistory() {
908
+ if (args[1] !== "--legacy-openai") {
909
+ console.error("Usage: ocx recover-history --legacy-openai");
910
+ console.error("Only use this if an older syncResumeHistory build already remapped OpenAI Codex App history to opencodex before backup support existed.");
911
+ process.exit(1);
912
+ }
913
+ // Manifest-independent legacy ejection, serialized like every other history
914
+ // mutation. It is a separate operation from generic restore precisely because
915
+ // it must not read, consume or replace the backup manifest.
916
+ const outcome = await runCodexHistoryJob({
917
+ ...resolveCodexHistoryJobTarget(),
918
+ operation: "recover-legacy-openai",
919
+ });
920
+ const r = outcome.kind === "converged"
921
+ ? { rows: outcome.rows, files: outcome.files, failed: undefined }
922
+ : { rows: 0, files: 0, failed: true as const };
923
+ if (r.failed) {
924
+ console.error(
925
+ `⚠️ Recovery SKIPPED: ${describeHistoryJobFailure(outcome, "recover-legacy")}`,
926
+ );
927
+ process.exit(1);
928
+ }
929
+ console.log(`Recovered ${r.rows} legacy thread(s) to openai (${r.files} rollout file(s) updated).`);
930
+ }
931
+
932
+ /**
933
+ * `ocx ready` — arguments are pre-parsed above (before
934
+ * maybeAutoRestoreCodexShim) so invalid usage exits 64 before any global
935
+ * preflight. This handler only runs the dependency-injected runner in ./ready
936
+ * and exits with the returned code; it performs no parsing and no I/O of its
937
+ * own. The full behavior is unit-testable without spawning a subprocess.
938
+ */
939
+ async function handleReady(args: ReadyArgs): Promise<number> {
940
+ return runReady(args);
941
+ }
942
+
943
+ process.exit(await dispatchCommand(head, {
944
+ args,
945
+ command,
946
+ head,
947
+ loadConfig,
948
+ findLiveProxy,
949
+ probeHostname,
950
+ waitForProxy,
951
+ startArgv,
952
+ spawnDetached: argv => {
953
+ const child = spawn(process.execPath, argv, {
954
+ detached: true,
955
+ stdio: "ignore",
956
+ windowsHide: true,
957
+ env: withProcessRuntimeProvenance(process.env),
958
+ });
959
+ child.unref();
960
+ },
961
+ handleStart,
962
+ handleStop,
963
+ handleEnsure,
964
+ handleTrayProxyStart,
965
+ handleTrayProxyRestart,
966
+ handleRestartStartWhenStopped,
967
+ handleProxyRestart,
968
+ handleUninstall,
969
+ handleStatus,
970
+ handleRecoverHistory,
971
+ handleReady,
972
+ serviceCommand,
973
+ }));