@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/index.ts CHANGED
@@ -1,13 +1,20 @@
1
1
  #!/usr/bin/env bun
2
2
  import { spawn } from "node:child_process";
3
- import { currentExternalCodexModelProvider, restoreNativeCodex, shouldInjectApiAuthHeader } from "../codex/inject";
3
+ import { currentExternalCodexModelProvider, restoreNativeCodex, restoreNativeCodexAsync, shouldInjectApiAuthHeader } from "../codex/inject";
4
4
  import { stripGrokConfig } from "../grok/inject";
5
- import { restoreLegacyOpenaiHistory } from "../codex/history-provider";
5
+ import {
6
+ describeHistoryJobFailure,
7
+ resolveCodexHistoryJobTarget,
8
+ runCodexHistoryJob,
9
+ } from "../codex/history-job";
6
10
  import { reconcileJournal } from "../codex/journal";
7
11
  import {
8
12
  codexAutoStartEnabled,
9
13
  getConfigDir,
10
14
  loadConfig,
15
+ saveConfig,
16
+ } from "../config";
17
+ import {
11
18
  readPid,
12
19
  readPidFileValue,
13
20
  readRuntimePort,
@@ -15,56 +22,84 @@ import {
15
22
  removePidIfValueIs,
16
23
  removeRuntimePort,
17
24
  removeRuntimePortIfPidIs,
18
- saveConfig,
19
25
  writePid,
20
26
  writeRuntimePort,
21
- } from "../config";
22
- import { collectStatus } from "./status";
23
- import { dispatchInternalCliCommand, type InternalCliCommand } from "./internal-dispatch";
24
- import { runTrayProxyRestart, runTrayProxyStart } from "./tray-proxy";
27
+ } from "../config/process-state";
28
+ import { collectStatus, unusedProxyWarningLines } from "./status";
29
+ import { takeFlag } from "./runtime-api";
30
+
31
+ import {
32
+ discoverStableProxyForRestart,
33
+ isProxyReplacement,
34
+ runProxyRestart,
35
+ runTrayProxyStart,
36
+ type ProxyRestartLive,
37
+ type ProxyRestartResult,
38
+ } from "./tray-proxy";
39
+ import { requestBoundSystemRestart } from "./system-restart-client";
25
40
  import { installCrashGuards } from "../lib/crash-guard";
26
- import { hasHelpFlag, printSubcommandUsage, printUsage, printVersion } from "./help";
41
+ import { dispatchCommand } from "./dispatch";
27
42
  import { findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
28
43
  import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
29
- import { stopProxy } from "../lib/process-control";
44
+ import { createReadinessGate } from "../server/readiness";
45
+ import { runReady, type ReadyArgs } from "./ready";
46
+ import { runCli } from "./root";
47
+ import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control";
30
48
  import { loadServiceTokenFromFile } from "../lib/service-secrets";
31
- import { diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
32
- import { startupHealthSummary } from "../codex/autostart-health";
49
+ import { assertNotAdminToken, diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
50
+ import { formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
33
51
  import { drainAndShutdown, isRecyclingForExit, startServer } from "../server";
34
- import { injectSystemEnv, revertSystemEnv } from "../server/system-env";
52
+ import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
35
53
  import { buildDesktop3pRegistry } from "../claude/desktop-3p";
36
- import { installShellHook, uninstallShellHook } from "../server/system-env";
37
54
  import { startTokenGuardian } from "../oauth/token-guardian";
38
55
  import { startHistoryMigrationGuardian } from "../codex/history-migration-guardian";
39
- import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
40
56
  import { maybeShowStarPrompt } from "./star-prompt";
41
57
  import { scheduleCatalogPrewarm } from "./catalog-prewarm";
42
58
  import { maybeShowUpdatePrompt } from "../update/notify";
43
59
  import { syncModelsToCodex } from "../codex/sync";
44
- import { normalizeUpdateChannel, runGuiUpdateWorker } from "../update/job";
45
- import { collectOrcaCodexHomeDiagnostic } from "../codex/home";
46
- import { removeOwnedConfigState } from "../lib/config-ownership";
47
-
48
- const args = process.argv.slice(2);
49
- const command = args[0];
50
-
51
- if (command === "--version" || command === "-v" || command === "version") {
52
- printVersion();
53
- process.exit(0);
54
- }
60
+ import {
61
+ shouldSyncGrokOnStart,
62
+ syncCodexOnStartIfEnabled,
63
+ } from "../codex/desired-state";
64
+ import {
65
+ reconcileClientStartupBeforeReady,
66
+ syncClaudeAgentDefsAtProxyStartup,
67
+ } from "./claude-agent-startup-sync";
68
+ import {
69
+ grokSyncFailureMessage,
70
+ reconcileEnsureDesiredIntegrations,
71
+ } from "./ensure-desired-integrations";
55
72
 
56
- if (command === undefined || command === "help" || command === "--help" || command === "-h") {
57
- if (command === "help" && args[1]) printSubcommandUsage(args[1]);
58
- else printUsage();
59
- process.exit(0);
73
+ /**
74
+ * A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
75
+ * `claude-env.sh` from every new interactive shell, pointing at a proxy or a CLI that may no
76
+ * longer exist. `reconcileShellHook` already reports `state: "failed"`, but both call sites
77
+ * discarded it, so the one outcome the user has to act on was the one they never saw.
78
+ */
79
+ function reportShellHookFailure(result: { state: "installed" | "absent" | "failed"; reason?: string }): void {
80
+ if (result.state !== "failed") return;
81
+ console.warn(` Claude shell hook not reconciled${result.reason ? `: ${result.reason}` : ""}`);
82
+ console.warn(" Check ~/.zshrc for the '# opencodex claude-env hook' block.");
60
83
  }
61
84
 
62
- if (command !== undefined && command !== "help" && hasHelpFlag(args.slice(1))) {
63
- printSubcommandUsage(command);
64
- process.exit(0);
65
- }
66
85
 
67
- maybeAutoRestoreCodexShim(command, args);
86
+ import { removeOwnedConfigState } from "../lib/config-ownership";
87
+ import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
88
+ import { selfLaunchArgv } from "../lib/self-launch-argv";
89
+ import { initializeNodeLauncherContext } from "./launcher-context";
90
+ import { createLocalAttestationSecret } from "../lib/local-management-attestation";
91
+ import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
92
+
93
+ initializeNodeLauncherContext();
94
+
95
+ // Head: version/help early exits, `ready` pre-parse (exit 64 before any
96
+ // preflight), and the bounded Codex-shim auto-restore preflight live in
97
+ // src/cli/root.ts (Phase 1 of the CLI deepening). runCli exits for
98
+ // version/help and returns the dispatchable head otherwise; the switch below
99
+ // owns command dispatch.
100
+ const head = await runCli(process.argv.slice(2));
101
+ const args = head.args;
102
+ const command = head.command;
68
103
 
69
104
  function parsePortOption(): number | undefined {
70
105
  if (args.length === 1) return undefined;
@@ -95,34 +130,31 @@ async function waitForProxy(timeoutMs = 8_000): Promise<LiveProxy | null> {
95
130
  return null;
96
131
  }
97
132
 
98
- /**
99
- * A Grok fence sync that throws is best-effort by design — it must never block startup.
100
- * Reporting nothing, however, is what lets a STALE fence survive: `~/.grok/config.toml`
101
- * keeps naming whatever port the last successful sync wrote, and once that listener is
102
- * gone every grok turn retries against a refused connection while our own log stays
103
- * silent (2026-07-27 field report: 8 entries pinned to a dead 127.0.0.1:4179).
104
- * So say what failed and name the single command that repairs it.
105
- */
106
- function grokSyncFailureMessage(err: unknown): string {
107
- const detail = err instanceof Error ? err.message : String(err);
108
- return `Grok Build config sync failed: ${detail}. `
109
- + "~/.grok/config.toml may still point at a previous proxy port — "
110
- + "run 'ocx ensure' (or apply from the dashboard's Grok page) to repoint it.";
111
- }
112
-
113
133
  /** Argv for detached `start`, optionally hard-pinning the listen port. */
114
134
  function startArgv(port?: number): string[] {
115
- const args = [process.argv[1], "start"];
135
+ const args = ["start"];
116
136
  if (typeof port === "number" && Number.isFinite(port) && port > 0 && port <= 65535) {
117
137
  args.push("--port", String(Math.trunc(port)));
118
138
  }
119
- return args;
139
+ return selfLaunchArgv(args);
120
140
  }
121
141
 
122
142
  async function chooseListenPort(requestedPort?: number): Promise<number> {
123
143
  const config = loadConfig();
124
144
  const preferred = requestedPort ?? config.port ?? 10100;
125
145
  const hardPin = requestedPort !== undefined && requestedPort > 0;
146
+ const reservedLoopbackPort = config.unauthenticatedLoopbackListener?.enabled
147
+ ? config.unauthenticatedLoopbackListener.port
148
+ : undefined;
149
+ // Before the reclaim path, not after (#1102). Asking for the port the loopback listener is
150
+ // configured to bind is a configuration mistake, and reclaim would spend up to 60 seconds
151
+ // waiting for a socket to free before reporting "port is busy" — the wrong diagnosis for a
152
+ // collision the config can state outright.
153
+ if (reservedLoopbackPort !== undefined && preferred === reservedLoopbackPort) {
154
+ throw new Error(
155
+ `Port ${preferred} is reserved for unauthenticatedLoopbackListener; choose a different proxy port.`,
156
+ );
157
+ }
126
158
  // Soft start: brief prefer-retry then ephemeral hop.
127
159
  // Explicit `--port` (service wrappers / update restart): wait for the pinned port
128
160
  // to free without killing any listener (healthy ocx / foreign). Never hop.
@@ -146,6 +178,11 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
146
178
  preferRetryMs: hardPin ? 5_000 : 750,
147
179
  preferRetryIntervalMs: 50,
148
180
  allowEphemeralFallback: !hardPin,
181
+ // Never hand the public listener the port the loopback listener is configured to
182
+ // bind (#1102). Without this, `--port <loopback port>` binds the public listener
183
+ // first and the loopback bind then fails, rolling back a startup that was only
184
+ // ever a config collision.
185
+ ...(reservedLoopbackPort !== undefined ? { reservedPort: reservedLoopbackPort } : {}),
149
186
  });
150
187
  if (preferred > 0 && selected !== preferred) {
151
188
  console.log(`⚠️ Port ${preferred} is busy; starting opencodex on ${selected}.`);
@@ -165,22 +202,49 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
165
202
  }
166
203
  }
167
204
 
205
+ async function findProxyOwnerBeforeJournalRecovery(
206
+ options: { probeConfiguredPort?: boolean } = {},
207
+ ): Promise<{ live: LiveProxy | null; pidSnapshot: number | null }> {
208
+ const pidSnapshot = readPidFileValue();
209
+ const hasRuntimeOwner = readRuntimePort() !== null;
210
+ const shouldProbe = pidSnapshot !== null || hasRuntimeOwner || options.probeConfiguredPort === true;
211
+ const live = shouldProbe ? await findLiveProxy() : null;
212
+ if (live) return { live, pidSnapshot };
213
+
214
+ // The probe established that the snapshotted owner is stale. Compare before
215
+ // deleting so a concurrent start that rewrote the PID file keeps its state.
216
+ removePidIfValueIs(pidSnapshot);
217
+ if (!currentExternalCodexModelProvider()) reconcileJournal();
218
+ return { live: null, pidSnapshot };
219
+ }
220
+
168
221
  async function handleStart(options: { block?: boolean } = {}) {
169
222
  // Native (WinSW) service mode has no batch wrapper to read the service token file
170
223
  // into the environment, so the app loads it here before the server binds. The server
171
224
  // auth path reads OPENCODEX_API_AUTH_TOKEN from the environment.
172
225
  const serviceToken = loadServiceTokenFromFile(process.env);
173
226
  if (serviceToken) process.env.OPENCODEX_API_AUTH_TOKEN = serviceToken;
227
+ // The service wrapper (and WinSW via OCX_API_TOKEN_FILE) can still export a colliding
228
+ // token that install now refuses to write. Refuse it here too, before bind, so an
229
+ // already-broken file cannot fence /api/* closed at boot (#2696).
230
+ const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
231
+ if (present) assertNotAdminToken(present);
174
232
  const requestedPort = parsePortOption();
175
- if (!currentExternalCodexModelProvider()) reconcileJournal();
176
- const existingPid = readPid();
177
- if (existingPid) {
178
- const live = await findLiveProxy();
179
- if (live) {
180
- console.error(`⚠️ Proxy already running (PID ${live.pid ?? existingPid}, port ${live.port}). Use 'ocx stop' first.`);
181
- process.exit(1);
182
- }
183
- removePid(existingPid);
233
+ const owner = await findProxyOwnerBeforeJournalRecovery();
234
+ if (owner.live) {
235
+ // Service-wrapper context (opencodex-service.cmd `:loop`): a healthy proxy from
236
+ // ANY source means the requested port is already served. Exit 0 so the wrapper's
237
+ // `if %ERRORLEVEL% NEQ 0` retry loop terminates instead of respawning every 5s
238
+ // against a listener it can never claim (observed as an endless
239
+ // "Proxy already running" service.log loop).
240
+ // Only the exact "1" sentinel takes this path — the same check syncCleanup
241
+ // uses — so an env value like "0" or "false" cannot bypass the conflict error.
242
+ if (process.env.OCX_SERVICE === "1") {
243
+ console.log(`Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}); service wrapper staying out of the way.`);
244
+ process.exit(0);
245
+ }
246
+ console.error(`⚠️ Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}). Use 'ocx stop' first.`);
247
+ process.exit(1);
184
248
  }
185
249
 
186
250
  // Interactive-only update prompt. Must run BEFORE we bind a port / write a
@@ -192,10 +256,16 @@ async function handleStart(options: { block?: boolean } = {}) {
192
256
  // between the probe and Bun.serve. Soft starts may re-pick; hard-pinned `--port` retries
193
257
  // the same port only (never hop — that was the remaining PR #152 gap).
194
258
  let port = await chooseListenPort(requestedPort);
259
+ // One private readiness gate for this startServer invocation, captured by the
260
+ // listener's closure. handleStart owns it and transitions it after the
261
+ // post-startup sync settles. A second startServer in the same process would
262
+ // get its own gate and could never reset/mutate this one.
263
+ const readinessGate = createReadinessGate();
195
264
  let server: ReturnType<typeof startServer>;
265
+ const localAttestationSecret = createLocalAttestationSecret();
196
266
  for (let attempt = 0; ; attempt++) {
197
267
  try {
198
- server = startServer(port);
268
+ server = startServer(port, { localAttestationSecret, readinessGate });
199
269
  // Prewarm the live provider model cache as soon as the port is bound so the
200
270
  // first GUI /v1/models (and syncModelsToCodex below) share one discovery flight
201
271
  // instead of racing duplicate upstream /models fetches.
@@ -223,7 +293,7 @@ async function handleStart(options: { block?: boolean } = {}) {
223
293
  writePid(process.pid);
224
294
 
225
295
  const config = loadConfig();
226
- writeRuntimePort({ pid: process.pid, port, hostname: config.hostname });
296
+ writeRuntimePort({ pid: process.pid, port, hostname: config.hostname, attestationSecret: localAttestationSecret });
227
297
  // No pre-emptive snapshot here. `injectCodexConfig` journals the exact bytes it
228
298
  // is about to transform; snapshotting earlier only captured a baseline that could
229
299
  // already be stale by the time injection ran (#477).
@@ -251,7 +321,8 @@ async function handleStart(options: { block?: boolean } = {}) {
251
321
  }
252
322
  removePid(process.pid);
253
323
  removeRuntimePort(process.pid);
254
- if (!recycling && !process.env.OCX_SERVICE && !currentExternalCodexModelProvider()) {
324
+ const preserveRouting = process.env.OCX_SERVICE === "1";
325
+ if (!recycling && !preserveRouting && !currentExternalCodexModelProvider()) {
255
326
  try {
256
327
  const restored = restoreNativeCodex();
257
328
  if (!restored.success) {
@@ -267,7 +338,7 @@ async function handleStart(options: { block?: boolean } = {}) {
267
338
  // Grok fence is shared state we must not remove — that service keeps running and would be
268
339
  // left pointing nowhere. This guard also covers signal-driven exits, which is the path that
269
340
  // would otherwise bypass handleStop's gate entirely.
270
- if (!recycling && !process.env.OCX_SERVICE && serviceEnvironmentOwnedHere()) {
341
+ if (!recycling && !preserveRouting && serviceEnvironmentOwnedHere()) {
271
342
  try { stripGrokConfig(); } catch { /* best-effort restore */ }
272
343
  }
273
344
  return cleanupSucceeded;
@@ -310,12 +381,32 @@ async function handleStart(options: { block?: boolean } = {}) {
310
381
 
311
382
  // System-wide env injection AFTER signal handlers are registered (crash safety:
312
383
  // syncCleanup reverts even if injection itself or subsequent startup steps fail).
313
- await injectSystemEnv(port, config).catch(() => {});
314
- // Auto-install .zshrc hook (idempotent skips if already present).
315
- installShellHook();
316
-
384
+ const systemEnv = await injectSystemEnv(port, config).catch(() => ({ injected: false }));
385
+ // The hook is useful only for an installed Claude Code CLI. Reconcile instead of
386
+ // appending unconditionally so stale OpenCodex-owned hooks are removed as well.
387
+ reportShellHookFailure(reconcileShellHook(systemEnv.injected));
317
388
  await maybeShowStarPrompt(); // once-only Yes/No GitHub-star prompt on first interactive start
318
- await syncModelsToCodex(port).catch(() => {});
389
+ // Codex sync owns the ready/failed verdict, but its successful transition is
390
+ // deferred until the best-effort Claude roster reconciliation settles. This
391
+ // keeps /readyz closed across both startup writes without making an optional
392
+ // Claude integration failure prevent the proxy from starting.
393
+ const startupSync = await reconcileClientStartupBeforeReady(
394
+ readinessGate,
395
+ gate => syncCodexOnStartIfEnabled(port, config, undefined, gate),
396
+ () => systemEnv.injected
397
+ ? Promise.resolve(null)
398
+ : syncClaudeAgentDefsAtProxyStartup(config, port),
399
+ );
400
+ if (!startupSync.ran) console.log(" Codex integration OFF; startup left Codex native.");
401
+ // #1046: one warning per startup, after BOTH writes. The server's cache
402
+ // invalidation happens first and the catalog sync second, so the mtime is only
403
+ // final here — and neither write site warns on its own, or a boot that hits
404
+ // both would warn twice.
405
+ const { consumeStartupCacheInvalidationWrite } = await import("../server");
406
+ if (consumeStartupCacheInvalidationWrite() || startupSync.catalogWritten || startupSync.cacheSynced) {
407
+ const { warnIfStaleCodexAppServersAfterStartupWrite } = await import("../codex/app-server-processes");
408
+ warnIfStaleCodexAppServersAfterStartupWrite({ log: console });
409
+ }
319
410
  if (!currentExternalCodexModelProvider() && !shouldInjectApiAuthHeader(config) && config.syncResumeHistory !== false) {
320
411
  historyGuardian = startHistoryMigrationGuardian();
321
412
  }
@@ -335,7 +426,10 @@ async function handleStart(options: { block?: boolean } = {}) {
335
426
  // absent or the bind is non-loopback; removed again by stop/eject/uninstall/shutdown.
336
427
  // Deliberately a SIBLING of the Desktop-3P block above: nesting it there meant a catalog
337
428
  // failure skipped the fence entirely, even though syncGrokConfig handles that case itself.
338
- try {
429
+ //
430
+ // Gated on the persisted switch: without this, turning Grok off lasted exactly
431
+ // one restart, because the toggle removed the fence and start wrote it back.
432
+ if (shouldSyncGrokOnStart(config)) try {
339
433
  const { syncGrokConfig } = await import("../grok/sync");
340
434
  const r = await syncGrokConfig(port, config, config.hostname ? { hostname: config.hostname } : {});
341
435
  if (r.changed) console.log(" + Grok Build config updated (~/.grok/config.toml)");
@@ -354,30 +448,46 @@ async function handleStart(options: { block?: boolean } = {}) {
354
448
  }
355
449
  }
356
450
 
357
- async function handleEnsure() {
358
- if (!currentExternalCodexModelProvider()) reconcileJournal();
451
+ function detachedStartEnvironment(): NodeJS.ProcessEnv {
452
+ const env: NodeJS.ProcessEnv = { ...process.env };
453
+ // Only a real service wrapper may claim supervision. A detached ensure/tray child
454
+ // is an ordinary owner: while live it maintains routing, and on exit it restores it.
455
+ delete env.OCX_SERVICE;
456
+ return withProcessRuntimeProvenance(env);
457
+ }
458
+
459
+ async function handleEnsure(options: { existingIsSuccess?: boolean } = {}): Promise<boolean> {
460
+ const owner = await findProxyOwnerBeforeJournalRecovery({ probeConfiguredPort: true });
359
461
  const config = loadConfig();
360
462
  if (!codexAutoStartEnabled(config)) {
361
463
  console.log("Codex autostart is disabled.");
362
- return;
464
+ return false;
363
465
  }
364
- const live = await findLiveProxy();
365
- if (live) {
366
- await syncModelsToCodex(live.port).catch(e => {
466
+ const live = owner.live;
467
+ if (live) {
468
+ if (options.existingIsSuccess === false) {
469
+ console.error("Proxy appeared while restart was confirming absence; no start was attempted.");
470
+ return false;
471
+ }
472
+ const synced = await syncModelsToCodex(live.port).catch(e => {
367
473
  console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
474
+ return null;
368
475
  });
476
+ if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
369
477
  // Ensure env file exists for already-running proxy (may have been deleted or pre-dates this feature).
370
- await injectSystemEnv(live.port, config).catch(() => {});
478
+ const systemEnv = await injectSystemEnv(live.port, config).catch(() => ({ injected: false }));
479
+ reportShellHookFailure(reconcileShellHook(systemEnv.injected));
480
+ if (!systemEnv.injected) await syncClaudeAgentDefsAtProxyStartup(config, live.port);
371
481
  // Refresh the Grok Build fence too (same contract as start). live.hostname is the
372
482
  // hostname the running proxy actually bound — config.hostname may have drifted.
373
- try {
374
- const { syncGrokConfig } = await import("../grok/sync");
375
- const g = await syncGrokConfig(live.port, config, live.hostname ? { hostname: live.hostname } : {});
376
- if (g.changed) console.log(" + Grok Build config updated (~/.grok/config.toml)");
377
- else if (!g.ok) console.error(`⚠️ ${g.message}`);
378
- } catch (err) { console.error(`⚠️ ${grokSyncFailureMessage(err)}`); }
483
+ // The reconciler re-reads immediately before each client-file mutation; only
484
+ // the live proxy's observed bind host is safe to carry across this boundary.
485
+ await reconcileEnsureDesiredIntegrations(
486
+ live.port,
487
+ { kind: "live", hostname: live.hostname },
488
+ );
379
489
  console.log(`✅ Proxy running on port ${live.port}`);
380
- return;
490
+ return true;
381
491
  }
382
492
 
383
493
  const pinPort = config.port ?? 10100;
@@ -385,36 +495,42 @@ async function handleEnsure() {
385
495
  detached: true,
386
496
  stdio: "ignore",
387
497
  windowsHide: true,
388
- env: { ...process.env, OCX_SERVICE: "1" },
498
+ env: detachedStartEnvironment(),
389
499
  });
390
500
  child.unref();
391
501
 
392
502
  const port = (await waitForProxy())?.port;
393
503
  if (!port) {
394
504
  console.error("❌ Proxy did not become healthy after starting.");
395
- process.exit(1);
396
- }
397
- // Deterministic fence guarantee: the spawned child injects late in its own startup, but
398
- // this parent returns as soon as /healthz responds inject here too (idempotent block
399
- // replace) so `ocx ensure` never returns without the Grok fence in place.
400
- try {
401
- const { syncGrokConfig } = await import("../grok/sync");
402
- const g = await syncGrokConfig(port, config, config.hostname ? { hostname: config.hostname } : {});
403
- if (g.changed) console.log(" + Grok Build config updated (~/.grok/config.toml)");
404
- else if (!g.ok) console.error(`⚠️ ${g.message}`);
405
- } catch (err) { console.error(`⚠️ ${grokSyncFailureMessage(err)}`); }
505
+ process.exitCode = 1;
506
+ return false;
507
+ }
508
+ // Deterministic fence guarantee when the durable switch is ON: the spawned child
509
+ // injects late in its own startup, but this parent returns as soon as /healthz
510
+ // responds — align here too so `ocx ensure` never returns with a stale ON/OFF mismatch.
511
+ // Persisted state is loaded inside each mutation after waitForProxy, so a
512
+ // toggle while the child starts wins over the pre-spawn snapshot.
513
+ await reconcileEnsureDesiredIntegrations(port, { kind: "spawned" });
406
514
  // Always sync the LIVE port: after a fallback-port start, config.port still names the
407
515
  // busy preferred port — syncing that would point Codex at a dead listener.
408
- await syncModelsToCodex(port).catch(e => {
516
+ const synced = await syncModelsToCodex(port).catch(e => {
409
517
  console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
518
+ return null;
410
519
  });
520
+ if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
521
+ // The child opens /healthz before its best-effort roster reconcile. Await the same idempotent
522
+ // operation in the parent so `ocx ensure` cannot report success while stale ocx-*.md files are
523
+ // still observable. Always use the live port, including fallback-port starts.
524
+ await syncClaudeAgentDefsAtProxyStartup(config, port);
411
525
  console.log(`✅ Proxy running on port ${port}`);
526
+ return true;
412
527
  }
413
528
 
414
529
  /** Fixed tray action: start the proxy without depending on codexAutoStart. */
415
- async function handleTrayProxyStart(): Promise<void> {
530
+ async function handleTrayProxyStart(existingIsSuccess = true): Promise<boolean> {
416
531
  const ok = await runTrayProxyStart({
417
532
  findLive: findLiveProxy,
533
+ existingIsSuccess,
418
534
  diagnoseService: () => {
419
535
  const service = diagnoseService();
420
536
  return { installed: service.installed, startable: serviceStartableFromTray(service), summary: service.summary };
@@ -427,29 +543,117 @@ async function handleTrayProxyStart(): Promise<void> {
427
543
  detached: true,
428
544
  stdio: "ignore",
429
545
  windowsHide: true,
430
- env: { ...process.env, OCX_SERVICE: "1" },
546
+ env: detachedStartEnvironment(),
431
547
  });
432
548
  child.unref();
433
549
  },
434
- waitForProxy,
550
+ // serviceCommand("start") already spends up to 20s confirming the supervised
551
+ // child. Slow Windows hosts can still be publishing native-main state after that
552
+ // first window, so keep one shared follow-up budget instead of returning a false
553
+ // failure while Task Scheduler is still starting the approved child.
554
+ waitForProxy: () => waitForProxy(40_000),
435
555
  info: message => console.log(message),
436
556
  error: message => console.error(message),
437
557
  });
438
- if (!ok) process.exitCode = 1;
558
+ // serviceCommand("start") can set exitCode=1 after its own 20s probe, while
559
+ // the coordinator's bounded follow-up observes the same service become live.
560
+ // The final observed state, not the earlier probe, owns this command result.
561
+ process.exitCode = ok ? 0 : 1;
562
+ return ok;
439
563
  }
440
564
 
441
- async function handleTrayProxyRestart(): Promise<void> {
442
- const ok = await runTrayProxyRestart({
443
- stop: async () => {
444
- await handleStop();
445
- return !process.exitCode || process.exitCode === 0;
446
- },
447
- start: async () => {
448
- await handleTrayProxyStart();
449
- return !process.exitCode || process.exitCode === 0;
450
- },
565
+ const PROXY_RESTART_OBSERVE_MS = MEMORY_DRAIN_RESTART_MS + REPLACEMENT_READY_TIMEOUT_MS + 15_000;
566
+
567
+ async function waitForProxyReplacement(
568
+ previous: ProxyRestartLive,
569
+ deadlineAt: number,
570
+ ): Promise<ProxyRestartLive | null> {
571
+ while (Date.now() < deadlineAt) {
572
+ const live = await findLiveProxy({ deadlineAt });
573
+ if (Date.now() >= deadlineAt) return null;
574
+ // Modern /healthz publishes a PID. Require a different, identity-verified process;
575
+ // merely seeing the old port online again is not proof that restart completed.
576
+ if (isProxyReplacement(previous, live)) {
577
+ return live;
578
+ }
579
+ const remainingMs = deadlineAt - Date.now();
580
+ if (remainingMs > 0) await Bun.sleep(Math.min(250, remainingMs));
581
+ }
582
+ return null;
583
+ }
584
+
585
+ function reportRestartFailure(result: Extract<ProxyRestartResult, { ok: false }>): void {
586
+ if (result.phase === "identity") {
587
+ console.error("❌ Refusing to restart because the running proxy identity could not be attested.");
588
+ } else if (result.phase === "request") {
589
+ const code = result.error instanceof Error ? result.error.message : "";
590
+ if (code === "restart_capability_unsupported") {
591
+ console.error("❌ The running proxy predates process-bound restart support; no unsafe fallback was attempted.");
592
+ console.error(" After confirming this home owns the proxy, run `ocx stop` and then `ocx start` once.");
593
+ } else {
594
+ console.error("❌ Proxy restart request could not be confirmed; no fallback stop/start was attempted.");
595
+ }
596
+ } else if (result.phase === "replacement") {
597
+ console.error("❌ Proxy restart was accepted, but no identity-verified replacement became healthy in time.");
598
+ } else {
599
+ console.error("❌ Proxy was not running and the fallback start did not become healthy.");
600
+ }
601
+ }
602
+
603
+ async function handleProxyRestart(
604
+ startWhenStopped: () => Promise<boolean | "skipped">,
605
+ ): Promise<boolean> {
606
+ const deadlineAt = Date.now() + PROXY_RESTART_OBSERVE_MS;
607
+ const result = await runProxyRestart({
608
+ findLive: () => discoverStableProxyForRestart({
609
+ findLive: () => findLiveProxy({ deadlineAt, attempts: 2 }),
610
+ expired: () => Date.now() >= deadlineAt,
611
+ }),
612
+ startWhenStopped,
613
+ requestInPlaceRestart: previous => requestBoundSystemRestart(previous, deadlineAt),
614
+ waitForReplacement: previous => waitForProxyReplacement(previous, deadlineAt),
451
615
  });
452
- if (!ok) process.exitCode = 1;
616
+ if (!result.ok) reportRestartFailure(result);
617
+ process.exitCode = result.ok ? 0 : 1;
618
+ return result.ok;
619
+ }
620
+
621
+ async function handleTrayProxyRestart(): Promise<void> {
622
+ await handleProxyRestart(() => handleTrayProxyStart(false));
623
+ }
624
+
625
+ async function handleRestartStartWhenStopped(): Promise<boolean | "skipped"> {
626
+ if (!codexAutoStartEnabled(loadConfig())) {
627
+ console.log("Codex autostart is disabled; no proxy was started.");
628
+ return "skipped";
629
+ }
630
+ return handleEnsure({ existingIsSuccess: false });
631
+ }
632
+
633
+ async function restoreSharedClientStateAfterStop(): Promise<boolean> {
634
+ let restored = true;
635
+ try {
636
+ const result = await restoreNativeCodexAsync();
637
+ if (result.success) console.log(`↩️ ${result.message}`);
638
+ else {
639
+ restored = false;
640
+ console.error(`⚠️ ${result.message}`);
641
+ }
642
+ } catch (error) {
643
+ restored = false;
644
+ console.error(`⚠️ Native Codex restore failed: ${error instanceof Error ? error.message : String(error)}`);
645
+ }
646
+
647
+ // A refused or thrown Grok strip is actionable because it would point Grok at a dead proxy.
648
+ try {
649
+ const grok = stripGrokConfig();
650
+ if (grok.changed) console.log(`↩️ ${grok.message}`);
651
+ else if (!grok.ok) { restored = false; console.error(`⚠️ ${grok.message}`); }
652
+ } catch (error) {
653
+ restored = false;
654
+ console.error(`⚠️ Grok config restore failed: ${error instanceof Error ? error.message : String(error)}`);
655
+ }
656
+ return restored;
453
657
  }
454
658
 
455
659
  async function handleStop() {
@@ -493,6 +697,10 @@ async function handleStop() {
493
697
  // exact teardown the refusal exists to prevent.
494
698
  const detail = err instanceof Error ? err.message : String(err);
495
699
  if (detail) console.error(` ${detail}`);
700
+ if (err instanceof ProxyOwnershipRefusedError) {
701
+ ownershipBlocked = true;
702
+ console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
703
+ }
496
704
  }
497
705
  } else {
498
706
  // Snapshot the stale on-disk state BEFORE the async probe: a concurrent `ocx start`
@@ -511,6 +719,10 @@ async function handleStop() {
511
719
  console.error(`❌ Failed to stop proxy (PID ${live.pid}).`);
512
720
  const detail = err instanceof Error ? err.message : String(err);
513
721
  if (detail) console.error(` ${detail}`);
722
+ if (err instanceof ProxyOwnershipRefusedError) {
723
+ ownershipBlocked = true;
724
+ console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
725
+ }
514
726
  }
515
727
  } else if (!stoppedService) {
516
728
  console.log("No running proxy found.");
@@ -523,26 +735,11 @@ async function handleStop() {
523
735
  removeRuntimePortIfPidIs(staleRuntimePid);
524
736
  }
525
737
  }
526
- if (!ownershipBlocked) {
527
- const r = restoreNativeCodex();
528
- if (r.success) console.log(`↩️ ${r.message}`);
529
- else {
530
- stopFailed = true;
531
- console.error(`⚠️ ${r.message}`);
532
- }
533
- }
534
- // revertSystemEnv is NOT gated: it carries its own ownership check and concerns launchctl
535
- // user env, not CODEX_HOME. Safety net for when the daemon's syncCleanup didn't run (SIGKILL).
738
+ // Environment ownership is independent from service ownership. Always roll back
739
+ // current-home variables; the helper refuses foreign markers on its own.
536
740
  try { revertSystemEnv(); } catch { /* best-effort */ }
537
741
  if (!ownershipBlocked) {
538
- // Same safety net for the Grok Build managed block (marker-owned, idempotent).
539
- try {
540
- const g = stripGrokConfig();
541
- if (g.changed) console.log(`↩️ ${g.message}`);
542
- // A refused strip (e.g. orphaned marker) leaves the fence pointing at a dead proxy —
543
- // reporting success there hides a broken end state.
544
- else if (!g.ok) { stopFailed = true; console.error(`⚠️ ${g.message}`); }
545
- } catch { /* best-effort */ }
742
+ if (!await restoreSharedClientStateAfterStop()) stopFailed = true;
546
743
  }
547
744
  // Set the code rather than exiting inline: `restart` and the tray coordinator call this
548
745
  // function and need it to RETURN so they can decide what to do next.
@@ -586,8 +783,8 @@ async function handleUninstall() {
586
783
  });
587
784
  }
588
785
 
589
- await runStep("native Codex restored", () => {
590
- const r = restoreNativeCodex();
786
+ await runStep("native Codex restored", async () => {
787
+ const r = await restoreNativeCodexAsync();
591
788
  if (!r.success) throw new Error(r.message);
592
789
  });
593
790
 
@@ -634,13 +831,17 @@ async function handleUninstall() {
634
831
  console.error(`\nUninstall finished with ${failures.length} failed step(s): ${failures.join(", ")}`);
635
832
  process.exit(1);
636
833
  }
637
- console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @iislee/opencodex");
834
+ console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @bitkyc08/opencodex");
638
835
  }
639
836
 
640
837
  async function handleStatus() {
641
838
  const statusArgs = args.slice(1);
642
- const wantsJson = statusArgs.length === 1 && statusArgs[0] === "--json";
643
- if (statusArgs.length > 1 || (statusArgs.length === 1 && !wantsJson)) {
839
+ // Order-independent: the previous form only honoured `--json` as the LONE argument, so
840
+ // `ocx status --json --anything` silently printed human output to a caller that asked
841
+ // for JSON. Take the flag out of argv, then reject whatever is left over -- which keeps
842
+ // the strict unknown-argument behaviour rather than trading one defect for another.
843
+ const wantsJson = takeFlag(statusArgs, "--json");
844
+ if (statusArgs.length > 0) {
644
845
  console.error("Usage: ocx status [--json]");
645
846
  process.exit(1);
646
847
  }
@@ -657,9 +858,28 @@ async function handleStatus() {
657
858
  console.log(`❌ Proxy: ${status.proxyLabel}`);
658
859
  }
659
860
  console.log(` Health: ${status.healthLabel}`);
861
+ // Printed here, not only in --json: a stale ocx on PATH is exactly the situation where
862
+ // the operator is reading human output and wondering why the CLI disagrees with the
863
+ // dashboard. Adding the JSON field alone would satisfy a test and help nobody (#2701).
864
+ if (status.json.versionSkew.warning) {
865
+ console.log(` ⚠️ ${status.json.versionSkew.warning}`);
866
+ }
867
+ for (const line of unusedProxyWarningLines({
868
+ proxyUp: Boolean(status.json.proxy.pid || status.json.proxy.health.ok),
869
+ routingKind: status.json.startup.routingKind,
870
+ })) {
871
+ console.log(` ${line}`);
872
+ }
660
873
  if (!(status.json.proxy.pid || status.json.proxy.health.ok)) {
661
874
  console.log(" ↳ Not running — Codex/Claude requests will fail with connection errors.");
662
- console.log(" Restart with 'ocx start', or install the persistent service: 'ocx service install'.");
875
+ // The service summary a few lines below already tells a registered-but-not-serving
876
+ // user to repair. Printing "install the persistent service" unconditionally
877
+ // contradicted it in the same report, and install re-registers: UAC on Windows and a
878
+ // possible WinSW-to-scheduler switch for someone who already has a service.
879
+ const installed = status.json.startup.serviceInstalled && !status.json.startup.serviceConflict;
880
+ console.log(installed
881
+ ? " Restart with 'ocx start', or refresh the installed service: 'ocx service repair'."
882
+ : " Restart with 'ocx start', or install the persistent service: 'ocx service install'.");
663
883
  }
664
884
  console.log(` Dashboard: ${status.json.dashboard.url}`);
665
885
  console.log(` Config: ${status.json.paths.config}`);
@@ -669,6 +889,7 @@ async function handleStatus() {
669
889
  console.log(` Default provider: ${status.json.defaultProvider}`);
670
890
  console.log(` Codex autostart: ${status.json.codexAutostart ? "enabled" : "disabled"}`);
671
891
  console.log(` Restart safety: ${startupHealthSummary(status.json.startup)}`);
892
+ console.log(` ${formatStartupRoutingDetail(status.json.startup)}`);
672
893
  console.log(` Service: ${status.json.service.summary}`);
673
894
  console.log(` ${status.json.codexShim.summary}`);
674
895
  console.log(` Codex runtime: ${status.json.codexRuntime.path}`);
@@ -707,407 +928,75 @@ async function handleStatus() {
707
928
  }
708
929
  }
709
930
 
710
- function handleRecoverHistory() {
931
+ async function handleRecoverHistory() {
711
932
  if (args[1] !== "--legacy-openai") {
712
- console.error("Usage: ocx recover-history --legacy-openai");
713
- console.error("Only use this if an older syncResumeHistory build already remapped OpenAI Codex App history to opencodex before backup support existed.");
933
+ console.error("Usage: ocx recover-history --legacy-openai --yes");
934
+ console.error("This force-relabels every user-message opencodex row to OpenAI, including legitimate dedicated-provider history. Back up first and use it only for pre-backup legacy recovery.");
935
+ process.exit(1);
936
+ }
937
+ console.error("WARNING: this force-relabels every user-message opencodex row to OpenAI, normalizes exec to cli, and includes legitimate dedicated-provider history.");
938
+ if (args.length !== 3 || args[2] !== "--yes") {
939
+ console.error("Re-run with explicit confirmation: ocx recover-history --legacy-openai --yes");
714
940
  process.exit(1);
715
941
  }
716
- const r = restoreLegacyOpenaiHistory();
942
+ // Manifest-independent legacy ejection, serialized like every other history
943
+ // mutation. It is a separate operation from generic restore precisely because
944
+ // it must not read, consume or replace the backup manifest.
945
+ const outcome = await runCodexHistoryJob({
946
+ ...resolveCodexHistoryJobTarget(),
947
+ operation: "recover-legacy-openai",
948
+ });
949
+ const r = outcome.kind === "converged"
950
+ ? { rows: outcome.rows, files: outcome.files, failed: undefined }
951
+ : { rows: 0, files: 0, failed: true as const };
717
952
  if (r.failed) {
718
953
  console.error(
719
- "⚠️ Recovery SKIPPED: the Codex history DB is locked (Codex app/IDE open?). Close it and rerun this command.",
954
+ `⚠️ Recovery SKIPPED: ${describeHistoryJobFailure(outcome, "recover-legacy")}`,
720
955
  );
721
956
  process.exit(1);
722
957
  }
723
958
  console.log(`Recovered ${r.rows} legacy thread(s) to openai (${r.files} rollout file(s) updated).`);
724
959
  }
725
960
 
726
- switch (command) {
727
- case "init":
728
- case "setup": {
729
- const { runInit } = await import("./init");
730
- await runInit();
731
- break;
732
- }
733
- case "start":
734
- await handleStart();
735
- break;
736
- case "stop": {
737
- // Downtime warning lives HERE, not in handleStop: `restart`/tray-restart callers
738
- // re-start the proxy immediately, so warning there would contradict the next line.
739
- if (await handleStop()) {
740
- console.log("⚠️ Codex/Claude requests through the proxy will fail until it is restarted ('ocx start' or 'ocx service start').");
741
- }
742
- break;
743
- }
744
- case "restore":
745
- case "eject": {
746
- if (args[1] === "back") {
747
- // Reverse switch: re-point plain `codex` at the RUNNING proxy without touching its
748
- // lifecycle — the counterpart of `ocx restore`. Start/stop triggers are unchanged;
749
- // this only re-runs the same inject (config + catalog + history) `ocx start` does.
750
- const live = await findLiveProxy();
751
- if (!live) {
752
- console.error("No running proxy found. Run 'ocx start' — it injects opencodex automatically.");
753
- process.exit(1);
754
- }
755
- const synced = await syncModelsToCodex(live.port);
756
- if (!synced.ok) {
757
- process.exitCode = 1;
758
- console.error("Plain `codex` was not switched back to opencodex. Fix the reported Codex config issue and retry.");
759
- break;
760
- }
761
- const target = collectOrcaCodexHomeDiagnostic();
762
- console.log(`Plain \`codex\` now routes through opencodex in ${target.effectiveCodexHome} (undo with: ocx restore).`);
763
- break;
764
- }
765
- let r: { success: boolean; message: string };
766
- try {
767
- r = restoreNativeCodex();
768
- } catch (err) {
769
- r = { success: false, message: err instanceof Error ? err.message : String(err) };
770
- }
771
- if (r.success) console.log(`✅ ${r.message}`);
772
- else {
773
- console.error(`⚠️ ${r.message}`);
774
- process.exitCode = 1;
775
- }
776
- try {
777
- const g = stripGrokConfig();
778
- if (g.changed) console.log(`✅ ${g.message}`);
779
- else if (!g.ok) {
780
- console.error(`⚠️ ${g.message}`);
781
- process.exitCode = 1;
782
- }
783
- } catch { /* best-effort */ }
784
- if (r.success) {
785
- console.log("Plain `codex` now runs natively (no proxy). Switch back with: ocx restore back");
786
- } else {
787
- console.error("Plain `codex` was not fully restored. Inspect $CODEX_HOME/config.toml before using native Codex.");
788
- }
789
- break;
790
- }
791
- case "recover-history":
792
- handleRecoverHistory();
793
- break;
794
- case "uninstall":
795
- case "remove":
796
- await handleUninstall();
797
- break;
798
- case "status":
799
- await handleStatus();
800
- break;
801
- case "doctor": {
802
- const { runDoctor } = await import("./doctor");
803
- await runDoctor(args.slice(1));
804
- break;
805
- }
806
- case "debug": {
807
- const { handleDebugCommand } = await import("./debug");
808
- await handleDebugCommand(args.slice(1));
809
- break;
810
- }
811
- case "ensure":
812
- await handleEnsure();
813
- break;
814
- case "login": {
815
- const { handleLogin } = await import("../oauth/login-cli");
816
- await handleLogin(args[1]);
817
- break;
818
- }
819
- case "logout": {
820
- const { removeCredential } = await import("../oauth/store");
821
- const name = (args[1] ?? "").trim().toLowerCase();
822
- await removeCredential(name);
823
- console.log(`Logged out of ${name || "(none)"}.`);
824
- break;
825
- }
826
- case "sync": {
827
- const restartCodex = args.slice(1).includes("--restart-codex");
828
- const synced = await syncModelsToCodex((await findLiveProxy())?.port);
829
- if (!synced.ok) {
830
- process.exitCode = 1;
831
- console.error("Codex sync did not complete. Fix the reported Codex config issue and retry.");
832
- }
833
- // Only warn/restart when a catalog or models_cache write actually happened. This is
834
- // deliberately not an `else`: refreshCodexModelCatalog runs before injectCodexConfig,
835
- // so a sync can fail (`ok: false`) after the catalog was already rewritten — which is
836
- // exactly when a long-lived app-server is holding the stale list.
837
- if (synced.catalogWritten || synced.cacheSynced) {
838
- const { afterCatalogWriteHandleAppServers } = await import("../codex/app-server-processes");
839
- afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
840
- }
841
- break;
842
- }
843
- case "v2": {
844
- const { cmdV2 } = await import("./v2");
845
- process.exitCode = await cmdV2(args.slice(1), {}, async () => (await findLiveProxy())?.port);
846
- break;
847
- }
848
- case "sync-cache": {
849
- const restartCodex = args.slice(1).includes("--restart-codex");
850
- const { invalidateCodexModelsCache } = await import("../codex/catalog");
851
- // Only warn/restart when models_cache was actually rewritten from a readable catalog.
852
- if (invalidateCodexModelsCache()) {
853
- const { afterCatalogWriteHandleAppServers } = await import("../codex/app-server-processes");
854
- afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
855
- }
856
- break;
857
- }
858
- case "gui": {
859
- const cfg = await import("../config");
860
- const config = cfg.loadConfig();
861
- // Identity-checked liveness (not the pid file + a fixed sleep): finds a fallback-port
862
- // proxy and waits until the spawned one actually answers before opening the browser.
863
- let live = await findLiveProxy();
864
- if (!live) {
865
- console.log("Proxy not running. Starting...");
866
- const child = spawn(process.execPath, startArgv((config.port ?? 10100) > 0 ? (config.port ?? 10100) : undefined), {
867
- detached: true,
868
- stdio: "ignore",
869
- windowsHide: true,
870
- env: process.env,
871
- });
872
- child.unref();
873
- live = await waitForProxy();
874
- if (!live) {
875
- console.error("❌ Proxy did not become healthy after starting. Not opening the GUI.");
876
- process.exit(1);
877
- }
878
- }
879
- // Open the host the proxy actually binds — `localhost` only answers for
880
- // loopback/wildcard binds, not a concrete LAN/IPv6 hostname.
881
- const guiHost = probeHostname(live?.hostname ?? config.hostname);
882
- const guiUrl = `http://${guiHost === "127.0.0.1" ? "localhost" : guiHost}:${live?.port ?? config.port}`;
883
- console.log(`Opening ${guiUrl}`);
884
- const { openUrl } = await import("../lib/open-url");
885
- openUrl(guiUrl);
886
- break;
887
- }
888
- case "service":
889
- await serviceCommand(...args.slice(1));
890
- break;
891
- case "tray": {
892
- const { windowsTrayCommand } = await import("../tray/windows");
893
- await windowsTrayCommand(args.slice(1));
894
- break;
895
- }
896
- case "codex-shim": {
897
- const { codexShimStatus, installCodexShim, uninstallCodexShim } = await import("../codex/shim");
898
- switch (args[1]) {
899
- case "install": {
900
- const r = installCodexShim();
901
- console.log(r.installed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
902
- break;
903
- }
904
- case "status":
905
- console.log(codexShimStatus());
906
- break;
907
- case "uninstall":
908
- case "remove": {
909
- const r = uninstallCodexShim();
910
- console.log(r.removed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
911
- break;
912
- }
913
- default:
914
- console.error("Usage: ocx codex-shim <install|status|uninstall|remove>");
915
- process.exit(1);
916
- }
917
- break;
918
- }
919
- case "update": {
920
- // `ocx update --help` must print usage and exit WITHOUT side effects — running the
921
- // real self-update stops the proxy and drops in-flight routed streams (issue #168).
922
- if (hasHelpFlag(args.slice(1))) {
923
- printSubcommandUsage("update");
924
- break;
925
- }
926
- const { runUpdate } = await import("../update");
927
- await runUpdate();
928
- break;
929
- }
930
- case "__refresh-version": {
931
- // Hidden, detached helper spawned by the update prompt to refresh the
932
- // cached latest version without blocking the foreground start. Not in help.
933
- const { refreshVersionCache } = await import("../update/notify");
934
- const channel = args[1] === "preview" ? "preview" : "latest";
935
- await refreshVersionCache(channel);
936
- break;
937
- }
938
- case "__tray-start":
939
- case "__tray-restart":
940
- case "__startup-health":
941
- await dispatchInternalCliCommand(command as InternalCliCommand, {
942
- trayStart: handleTrayProxyStart,
943
- trayRestart: handleTrayProxyRestart,
944
- startupHealth: async () => {
945
- const { collectStartupHealth } = await import("../codex/autostart-health");
946
- console.log(JSON.stringify(collectStartupHealth(loadConfig())));
947
- },
948
- });
949
- break;
950
- case "__tray-host": {
951
- const { runWindowsTrayHost } = await import("../tray/windows");
952
- await runWindowsTrayHost();
953
- break;
954
- }
955
- case "__gui-update-worker": {
956
- const jobId = args[1];
957
- if (!jobId) process.exit(1);
958
- const channel = normalizeUpdateChannel(args[2]);
959
- await runGuiUpdateWorker(jobId, channel, args[3] === "restart");
960
- break;
961
- }
962
- case "restart": {
963
- // A failed stop must not be followed by a re-inject: with a foreign service still running
964
- // (ownership mismatch) we would rewrite shared config we just declined to touch.
965
- if (await handleStop()) await handleEnsure();
966
- else console.error("↩️ Restart aborted: the proxy was not stopped cleanly.");
967
- break;
968
- }
969
- case "health": {
970
- const healthArgs = args.slice(1);
971
- const wantsHealthJson = healthArgs.includes("--json");
972
- const live = await findLiveProxy();
973
- if (wantsHealthJson) {
974
- console.log(JSON.stringify({ ok: !!live, pid: live?.pid ?? null, port: live?.port ?? null }));
975
- } else {
976
- console.log(live ? `Proxy healthy (PID ${live.pid}, port ${live.port})` : "Proxy not healthy");
977
- }
978
- process.exit(live ? 0 : 1);
979
- }
980
- case "provider": {
981
- const { handleProviderCommand } = await import("./provider");
982
- await handleProviderCommand(args.slice(1));
983
- break;
984
- }
985
- case "account": {
986
- const { cmdAccount } = await import("./account");
987
- process.exitCode = await cmdAccount(args.slice(1));
988
- break;
989
- }
990
- case "models":
991
- case "model": {
992
- const { handleModels } = await import("./models");
993
- await handleModels(args.slice(1));
994
- break;
995
- }
996
- case "combo": {
997
- const { handleComboCommand } = await import("./combo");
998
- process.exitCode = await handleComboCommand(args.slice(1));
999
- break;
1000
- }
1001
- case "route": {
1002
- if (args[1] !== "combo") {
1003
- console.error("Usage: ocx route combo <subcommand>");
1004
- process.exitCode = 2;
1005
- break;
1006
- }
1007
- const { handleComboCommand } = await import("./combo");
1008
- process.exitCode = await handleComboCommand(args.slice(2));
1009
- break;
1010
- }
1011
- case "agent": {
1012
- const { handleAgentCommand } = await import("./agent");
1013
- process.exitCode = await handleAgentCommand(args.slice(1));
1014
- break;
1015
- }
1016
- case "observe": {
1017
- const { handleObserveCommand } = await import("./observe");
1018
- process.exitCode = await handleObserveCommand(args.slice(1));
1019
- break;
1020
- }
1021
- case "logs":
1022
- case "usage":
1023
- case "storage":
1024
- case "memory": {
1025
- const { handleObserveCommand } = await import("./observe");
1026
- process.exitCode = await handleObserveCommand([command, ...args.slice(1)]);
1027
- break;
1028
- }
1029
- case "access": {
1030
- const { handleAccessCommand } = await import("./access");
1031
- process.exitCode = await handleAccessCommand(args.slice(1));
1032
- break;
1033
- }
1034
- case "api-key": {
1035
- const { handleAccessCommand } = await import("./access");
1036
- process.exitCode = await handleAccessCommand(["key", ...args.slice(1)]);
1037
- break;
1038
- }
1039
- case "export": {
1040
- const { handleExportCommand } = await import("./export-command");
1041
- process.exitCode = await handleExportCommand(args.slice(1));
1042
- break;
1043
- }
1044
- case "grok": {
1045
- const { handleGrokCommand } = await import("./integrations");
1046
- process.exitCode = await handleGrokCommand(args.slice(1));
1047
- break;
1048
- }
1049
- case "integration": {
1050
- const integration = args[1];
1051
- if (integration === "grok") {
1052
- const { handleGrokCommand } = await import("./integrations");
1053
- process.exitCode = await handleGrokCommand(args.slice(2));
1054
- } else if (integration === "claude") {
1055
- const { handleClaudeConfigCommand } = await import("./integrations");
1056
- process.exitCode = await handleClaudeConfigCommand(args.slice(2));
1057
- } else {
1058
- console.error("Usage: ocx integration <claude|grok> <subcommand>");
1059
- process.exitCode = 2;
1060
- }
1061
- break;
1062
- }
1063
- case "system": {
1064
- const { handleSystemCommand } = await import("./system-command");
1065
- process.exitCode = await handleSystemCommand(args.slice(1));
1066
- break;
1067
- }
1068
- case "config": {
1069
- const { handleConfigCommand } = await import("./config-command");
1070
- process.exitCode = await handleConfigCommand(args.slice(1));
1071
- break;
1072
- }
1073
- case "sync-cloud": {
1074
- const { handleSyncCloudCommand } = await import("./sync-cloud");
1075
- process.exitCode = await handleSyncCloudCommand(args.slice(1));
1076
- break;
1077
- }
1078
- case "claude": {
1079
- const { cmdClaude } = await import("./claude");
1080
- // "ocx claude desktop" → write Desktop 3P config
1081
- if (args[1] === "desktop") {
1082
- const { handleClaudeDesktopCommand } = await import("./claude-desktop");
1083
- const exitCode = await handleClaudeDesktopCommand(args.slice(2));
1084
- if (exitCode !== 0) process.exit(exitCode);
1085
- break;
1086
- }
1087
- if (args[1] === "config") {
1088
- const { handleClaudeConfigCommand } = await import("./integrations");
1089
- process.exitCode = await handleClaudeConfigCommand(args.slice(2));
1090
- break;
1091
- }
1092
- process.exit(await cmdClaude(args.slice(1)));
1093
- }
1094
- case "opencode": {
1095
- const { cmdOpencode } = await import("./opencode");
1096
- process.exit(await cmdOpencode(args.slice(1)));
1097
- }
1098
- case "pi": {
1099
- const { handlePiCommand } = await import("./pi");
1100
- process.exitCode = await handlePiCommand(args.slice(1));
1101
- break;
1102
- }
1103
- case "help":
1104
- case "--help":
1105
- case "-h":
1106
- case undefined:
1107
- printUsage();
1108
- break;
1109
- default:
1110
- console.error(`Unknown command: ${command}`);
1111
- printUsage();
1112
- process.exit(1);
961
+ /**
962
+ * `ocx ready` — arguments are pre-parsed above (before
963
+ * maybeAutoRestoreCodexShim) so invalid usage exits 64 before any global
964
+ * preflight. This handler only runs the dependency-injected runner in ./ready
965
+ * and exits with the returned code; it performs no parsing and no I/O of its
966
+ * own. The full behavior is unit-testable without spawning a subprocess.
967
+ */
968
+ async function handleReady(args: ReadyArgs): Promise<number> {
969
+ return runReady(args);
1113
970
  }
971
+
972
+ process.exit(await dispatchCommand(head, {
973
+ args,
974
+ command,
975
+ head,
976
+ loadConfig,
977
+ findLiveProxy,
978
+ probeHostname,
979
+ waitForProxy,
980
+ startArgv,
981
+ spawnDetached: argv => {
982
+ const child = spawn(process.execPath, argv, {
983
+ detached: true,
984
+ stdio: "ignore",
985
+ windowsHide: true,
986
+ env: withProcessRuntimeProvenance(process.env),
987
+ });
988
+ child.unref();
989
+ },
990
+ handleStart,
991
+ handleStop,
992
+ handleEnsure,
993
+ handleTrayProxyStart,
994
+ handleTrayProxyRestart,
995
+ handleRestartStartWhenStopped,
996
+ handleProxyRestart,
997
+ handleUninstall,
998
+ handleStatus,
999
+ handleRecoverHistory,
1000
+ handleReady,
1001
+ serviceCommand,
1002
+ }));