@bitkyc08/opencodex 2.80.0 → 2.82.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 (376) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +166 -73
  3. package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
  4. package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
  5. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  6. package/gui/dist/assets/index-D3FBCNiP.js +94 -0
  7. package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/gui/dist/provider-icons/claude-green.svg +1 -0
  10. package/package.json +3 -2
  11. package/src/adapters/anthropic-model-contract.ts +8 -2
  12. package/src/adapters/anthropic.ts +8 -3
  13. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  14. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  15. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  16. package/src/adapters/coding-agent/protocol.ts +122 -17
  17. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  18. package/src/adapters/coding-agent/turn.ts +101 -44
  19. package/src/adapters/cursor/catalog.ts +8 -0
  20. package/src/adapters/cursor/effort-map.ts +2 -0
  21. package/src/adapters/devin/live-models.ts +2 -0
  22. package/src/adapters/kiro-retry.ts +9 -4
  23. package/src/adapters/openai-chat/messages.ts +7 -6
  24. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  25. package/src/adapters/openai-responses/passthrough.ts +9 -4
  26. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  27. package/src/adapters/physical-send.ts +8 -5
  28. package/src/adapters/qoder/adapter.ts +41 -6
  29. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  30. package/src/adapters/zed.ts +16 -1
  31. package/src/claude/agents-inject.ts +14 -3
  32. package/src/claude/context-windows.ts +19 -17
  33. package/src/claude/desktop-3p.ts +88 -12
  34. package/src/claude/desktop-profile.ts +2 -1
  35. package/src/claude/gateway-cache.ts +18 -1
  36. package/src/claude/inbound-cache-stabilize.ts +4 -9
  37. package/src/claude/inbound.ts +78 -3
  38. package/src/claude/intercept/cli-catalog.ts +3 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  41. package/src/claude/intercept/picker-listener.ts +71 -4
  42. package/src/claude/intercept/picker-models.ts +14 -6
  43. package/src/claude/intercept/settings.ts +3 -0
  44. package/src/claude/long-context.ts +66 -0
  45. package/src/claude/model-info.ts +19 -16
  46. package/src/claude/outbound.ts +94 -9
  47. package/src/claude/subagent-model.ts +40 -11
  48. package/src/cli/account-anthropic-threshold.ts +14 -5
  49. package/src/cli/account-api.ts +3 -1
  50. package/src/cli/account-extended.ts +13 -9
  51. package/src/cli/account-policy-dto.ts +1 -1
  52. package/src/cli/account-policy.ts +13 -4
  53. package/src/cli/account.ts +3 -3
  54. package/src/cli/agent.ts +9 -5
  55. package/src/cli/aside-profile-recovery.ts +33 -0
  56. package/src/cli/capabilities-accounts.ts +2 -2
  57. package/src/cli/capabilities-agents-routing.ts +24 -0
  58. package/src/cli/capabilities-base.ts +5 -5
  59. package/src/cli/capabilities-integrations.ts +18 -2
  60. package/src/cli/cli-path-diagnostics.ts +132 -0
  61. package/src/cli/codex-shim-autorestore.ts +3 -0
  62. package/src/cli/desktop-runtime-guidance.ts +65 -0
  63. package/src/cli/dispatch.ts +15 -1
  64. package/src/cli/doctor.ts +16 -3
  65. package/src/cli/help.ts +4 -1
  66. package/src/cli/index.ts +25 -14
  67. package/src/cli/integration-aside-sync.ts +2 -1
  68. package/src/cli/integration-plan-dto.ts +1 -0
  69. package/src/cli/integration-preview.ts +8 -1
  70. package/src/cli/integrations.ts +48 -6
  71. package/src/cli/message-args.ts +34 -0
  72. package/src/cli/message-command.ts +43 -0
  73. package/src/cli/message-runtime.ts +6 -0
  74. package/src/cli/observe.ts +3 -1
  75. package/src/cli/provider.ts +29 -6
  76. package/src/cli/registry.ts +24 -2
  77. package/src/cli/resolve.ts +25 -2
  78. package/src/cli/root.ts +4 -2
  79. package/src/cli/status.ts +64 -12
  80. package/src/cli/update-restart-child.ts +4 -2
  81. package/src/cli/update-restart-home.ts +28 -10
  82. package/src/cli/update-restart-service-record.ts +116 -0
  83. package/src/cli/update-restart-supervision.ts +96 -0
  84. package/src/cli/update-restart.ts +59 -13
  85. package/src/cli/v2.ts +27 -20
  86. package/src/cli/version-skew-notice.ts +77 -0
  87. package/src/clients/config-export/commandcode.ts +131 -0
  88. package/src/clients/config-export/contracts.ts +12 -0
  89. package/src/clients/config-export.ts +64 -1
  90. package/src/clients/lazycodex.ts +1 -1
  91. package/src/clients/omo-role-models.ts +32 -7
  92. package/src/codex/auth-api/account-list.ts +11 -3
  93. package/src/codex/auth-api/account-selection.ts +107 -0
  94. package/src/codex/auth-api/main-account-probe.ts +25 -8
  95. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  96. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  97. package/src/codex/auth-api/routes.ts +11 -49
  98. package/src/codex/auth-context.ts +20 -1
  99. package/src/codex/autostart-health.ts +12 -3
  100. package/src/codex/catalog/auto-review.ts +13 -6
  101. package/src/codex/catalog/build-entries.ts +33 -7
  102. package/src/codex/catalog/bundled.ts +155 -26
  103. package/src/codex/catalog/combo-member.ts +3 -2
  104. package/src/codex/catalog/control-plane.ts +45 -0
  105. package/src/codex/catalog/effort.ts +37 -6
  106. package/src/codex/catalog/gather-capture.ts +10 -3
  107. package/src/codex/catalog/model-hints.ts +15 -8
  108. package/src/codex/catalog/model-visibility.ts +3 -1
  109. package/src/codex/catalog/operator-tiers.ts +38 -0
  110. package/src/codex/catalog/parsing.ts +2 -1
  111. package/src/codex/catalog/provider-models.ts +58 -44
  112. package/src/codex/catalog/retained-sync.ts +7 -5
  113. package/src/codex/catalog/routed-gather.ts +15 -1
  114. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  115. package/src/codex/catalog-auto-refresh.ts +19 -0
  116. package/src/codex/catalog.ts +1 -1
  117. package/src/codex/codex-home-owner.ts +3 -0
  118. package/src/codex/config-write-lock.ts +333 -0
  119. package/src/codex/control-plane-models.ts +6 -0
  120. package/src/codex/convergence.ts +2 -1
  121. package/src/codex/features.ts +257 -200
  122. package/src/codex/inject/config-write-section.ts +34 -0
  123. package/src/codex/inject/multi-agent-v2.ts +14 -6
  124. package/src/codex/inject/remove.ts +36 -11
  125. package/src/codex/inject/restore.ts +85 -20
  126. package/src/codex/inject-coordination.ts +15 -6
  127. package/src/codex/inject.ts +62 -20
  128. package/src/codex/journal.ts +62 -17
  129. package/src/codex/main-account-cache.ts +43 -0
  130. package/src/codex/paths.ts +2 -0
  131. package/src/codex/prepared-home.ts +18 -0
  132. package/src/codex/prompt-journal.ts +25 -12
  133. package/src/codex/prompt-layers.ts +49 -26
  134. package/src/codex/prompt-lock-claim.ts +134 -0
  135. package/src/codex/prompt-lock-evidence.ts +26 -0
  136. package/src/codex/prompt-lock-io.ts +16 -0
  137. package/src/codex/prompt-lock-owner.ts +130 -0
  138. package/src/codex/prompt-lock.ts +84 -53
  139. package/src/codex/routing/failure-window.ts +146 -0
  140. package/src/codex/routing/selection.ts +4 -1
  141. package/src/codex/routing.ts +13 -17
  142. package/src/codex/runtime.ts +106 -18
  143. package/src/codex/shim.ts +45 -19
  144. package/src/combos/jev-decision-contract.ts +25 -2
  145. package/src/combos/jev-service-exchange.ts +281 -0
  146. package/src/combos/jev.ts +34 -219
  147. package/src/combos/types.ts +1 -1
  148. package/src/config/atomic-write.ts +1 -1
  149. package/src/config/codex-ws-reuse-setting.ts +10 -0
  150. package/src/config/diagnostics.ts +27 -16
  151. package/src/config/live-reconcile.ts +17 -0
  152. package/src/config/load-degrade.ts +42 -0
  153. package/src/config/rebase-provenance.ts +11 -0
  154. package/src/config/schema/anthropic-account-pool.ts +143 -0
  155. package/src/config/schema/config-schema.ts +11 -1
  156. package/src/config/schema/leaf-validators.ts +4 -0
  157. package/src/config.ts +2 -0
  158. package/src/generated/compatibility-version.json +566 -298
  159. package/src/generated/model-metadata.ts +7 -6
  160. package/src/grok/inject.ts +50 -4
  161. package/src/grok/sync.ts +2 -1
  162. package/src/images/loop.ts +30 -11
  163. package/src/integrations/aside-profiles.ts +10 -3
  164. package/src/integrations/catalog-refresh.ts +19 -3
  165. package/src/integrations/mutation-plan.ts +7 -0
  166. package/src/integrations/registry.ts +8 -0
  167. package/src/integrations/state.ts +9 -2
  168. package/src/integrations/writer.ts +17 -5
  169. package/src/lib/bun-path-runtime.d.mts +32 -0
  170. package/src/lib/bun-path-runtime.mjs +95 -0
  171. package/src/lib/bun-runtime-preflight.ts +140 -0
  172. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  173. package/src/lib/desktop-cli-handoff.mjs +92 -0
  174. package/src/lib/desktop-cli-record.d.mts +29 -0
  175. package/src/lib/desktop-cli-record.mjs +102 -0
  176. package/src/lib/lab-live-route-production.ts +9 -1
  177. package/src/lib/request-execution-budget.ts +244 -58
  178. package/src/lib/spend-pool-alias-validation.ts +79 -0
  179. package/src/lib/spend-pool-continuity.ts +34 -0
  180. package/src/lib/spend-reservation-ledger.ts +412 -50
  181. package/src/lib/state-store-registrations.ts +20 -3
  182. package/src/lib/synced-state-location.ts +46 -6
  183. package/src/lib/test-home-guard.ts +87 -1
  184. package/src/lib/translator-budget.ts +17 -4
  185. package/src/lib/upstream-retry.ts +17 -5
  186. package/src/lib/windows-atomic-replace.ts +38 -16
  187. package/src/lib/windows-elevation.ts +6 -0
  188. package/src/lib/windows-secret-acl.ts +92 -25
  189. package/src/lib/winsw.ts +2 -3
  190. package/src/lib/workflow-budget.ts +53 -11
  191. package/src/messaging/budget.ts +45 -0
  192. package/src/messaging/discovery.ts +57 -0
  193. package/src/messaging/envelope.ts +49 -0
  194. package/src/messaging/input.ts +37 -0
  195. package/src/messaging/rpc.ts +196 -0
  196. package/src/messaging/send.ts +58 -0
  197. package/src/messaging/socket.ts +72 -0
  198. package/src/messaging/types.ts +29 -0
  199. package/src/oauth/anthropic-account-refusal.ts +90 -18
  200. package/src/oauth/anthropic-account-threshold.ts +8 -2
  201. package/src/oauth/anthropic-continuity.ts +15 -1
  202. package/src/oauth/anthropic-model-quota.ts +120 -48
  203. package/src/oauth/anthropic-model-routes.ts +11 -3
  204. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  205. package/src/oauth/anthropic-pool-config.ts +30 -0
  206. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  207. package/src/oauth/anthropic-routing.ts +1202 -866
  208. package/src/oauth/anthropic-send-ownership.ts +41 -0
  209. package/src/oauth/anthropic.ts +4 -1
  210. package/src/oauth/chatgpt.ts +113 -41
  211. package/src/oauth/generic-account-failover.ts +3 -1
  212. package/src/oauth/health.ts +8 -6
  213. package/src/oauth/index.ts +96 -175
  214. package/src/oauth/kiro.ts +22 -9
  215. package/src/oauth/login-cli.ts +2 -0
  216. package/src/oauth/model-discovery-auth.ts +43 -0
  217. package/src/oauth/pool-kernel.ts +11 -5
  218. package/src/oauth/pool-settings-capability.ts +5 -3
  219. package/src/oauth/store-anthropic-instance.ts +87 -0
  220. package/src/oauth/store.ts +68 -10
  221. package/src/oauth/token-guardian.ts +2 -0
  222. package/src/oauth/types.ts +2 -0
  223. package/src/protocols/plan-snapshot.ts +5 -0
  224. package/src/protocols/settings.ts +20 -5
  225. package/src/providers/anthropic-instance-id.ts +47 -0
  226. package/src/providers/anthropic-instance.ts +48 -0
  227. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  228. package/src/providers/azure-model-metadata.ts +115 -0
  229. package/src/providers/derive.ts +23 -0
  230. package/src/providers/kiro-models.ts +3 -0
  231. package/src/providers/label.ts +1 -1
  232. package/src/providers/model-rename-fields.ts +2 -0
  233. package/src/providers/quota/account-cache.ts +37 -11
  234. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  235. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  236. package/src/providers/quota/report-cache.ts +24 -1
  237. package/src/providers/quota/vendor-probes-key.ts +127 -17
  238. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  239. package/src/providers/quota.ts +23 -33
  240. package/src/providers/registry/entries-core.ts +35 -30
  241. package/src/providers/registry/model-ids.ts +1 -0
  242. package/src/providers/registry/model-seeds.ts +7 -4
  243. package/src/providers/registry/types.ts +3 -0
  244. package/src/providers/registry.ts +3 -3
  245. package/src/responses/reasoning-envelope.ts +29 -1
  246. package/src/responses/reasoning-replay-cache.ts +17 -1
  247. package/src/responses/spill-store.ts +20 -3
  248. package/src/responses/state/metrics.ts +95 -0
  249. package/src/responses/state/snapshot-codec.ts +6 -0
  250. package/src/responses/state/spill-failure.ts +12 -0
  251. package/src/responses/state/spill-queue.ts +13 -5
  252. package/src/responses/state/unforced-store-false.ts +55 -0
  253. package/src/responses/state.ts +68 -76
  254. package/src/responses/task-input.ts +17 -0
  255. package/src/router.ts +28 -4
  256. package/src/routing/compatibility/assemble.ts +1 -1
  257. package/src/routing/quota.ts +7 -3
  258. package/src/server/auth-cors.ts +5 -0
  259. package/src/server/background-lifecycle.ts +27 -0
  260. package/src/server/chat-completions.ts +7 -1
  261. package/src/server/chat-native.ts +64 -28
  262. package/src/server/claude-messages.ts +59 -8
  263. package/src/server/gui-static.ts +38 -4
  264. package/src/server/inbound-body-admission.ts +1 -0
  265. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  266. package/src/server/index/serve-options.ts +10 -2
  267. package/src/server/index/spend-ledger-lifecycle.ts +12 -5
  268. package/src/server/index/startup-warnings.ts +19 -0
  269. package/src/server/index.ts +5 -2
  270. package/src/server/inference/context.ts +1 -1
  271. package/src/server/lifecycle.ts +1 -1
  272. package/src/server/management/agent-settings-routes.ts +123 -38
  273. package/src/server/management/anthropic-account-threshold.ts +12 -9
  274. package/src/server/management/anthropic-pool-settings.ts +72 -0
  275. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  276. package/src/server/management/codex-agent-role-routes.ts +4 -2
  277. package/src/server/management/codex-prompt-routes.ts +1 -0
  278. package/src/server/management/codex-role-auto-assign.ts +13 -2
  279. package/src/server/management/config-routes.ts +85 -14
  280. package/src/server/management/context.ts +2 -2
  281. package/src/server/management/decision-routes.ts +2 -1
  282. package/src/server/management/integration-routes.ts +25 -2
  283. package/src/server/management/oauth-account-routes.ts +79 -98
  284. package/src/server/management/provider-instance-ownership.ts +44 -0
  285. package/src/server/management/provider-routes.ts +21 -28
  286. package/src/server/management/route-registry.ts +3 -1
  287. package/src/server/management/routing-profile-routes.ts +4 -4
  288. package/src/server/management/shared.ts +3 -3
  289. package/src/server/management/system-restart.ts +44 -14
  290. package/src/server/management/system-routes.ts +24 -2
  291. package/src/server/management/vision-sidecar-options.ts +9 -6
  292. package/src/server/management/web-search-sidecar-options.ts +51 -2
  293. package/src/server/management-auth.ts +6 -0
  294. package/src/server/messages-native-eligibility.ts +3 -2
  295. package/src/server/messages-native-oauth.ts +91 -45
  296. package/src/server/messages-native-selector.ts +39 -0
  297. package/src/server/messages-native.ts +133 -38
  298. package/src/server/messages-response-headers.ts +14 -0
  299. package/src/server/request-log-conversation.ts +48 -5
  300. package/src/server/request-log.ts +36 -2
  301. package/src/server/responses/adapter-continuation.ts +31 -19
  302. package/src/server/responses/adapter-dispatch.ts +75 -24
  303. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  304. package/src/server/responses/codex-ws-exchange.ts +17 -2
  305. package/src/server/responses/codex-ws-pool.ts +100 -13
  306. package/src/server/responses/codex-ws-wire.ts +9 -0
  307. package/src/server/responses/combo-requested-effort.ts +3 -1
  308. package/src/server/responses/compact.ts +25 -8
  309. package/src/server/responses/compaction-recovery.ts +1 -1
  310. package/src/server/responses/core-codex-account.ts +22 -1
  311. package/src/server/responses/core-combo-native.ts +24 -4
  312. package/src/server/responses/core-combo.ts +38 -25
  313. package/src/server/responses/core-lifetime.ts +3 -36
  314. package/src/server/responses/core-normalize.ts +1 -0
  315. package/src/server/responses/core-options.ts +8 -1
  316. package/src/server/responses/core-replay.ts +78 -0
  317. package/src/server/responses/core.ts +3 -3
  318. package/src/server/responses/fetch-helpers.ts +26 -3
  319. package/src/server/responses/passthrough-delivery.ts +30 -4
  320. package/src/server/responses/passthrough-dispatch.ts +39 -9
  321. package/src/server/responses/request-prepare.ts +41 -5
  322. package/src/server/responses/request-send-budget.ts +150 -50
  323. package/src/server/responses/request-spend.ts +325 -33
  324. package/src/server/responses/request-transport.ts +132 -52
  325. package/src/server/responses/response-effects.ts +1 -0
  326. package/src/server/responses/run-turn-execution.ts +13 -3
  327. package/src/server/responses/sidecar-execution.ts +113 -22
  328. package/src/server/responses/sidecar-send-budget.ts +58 -0
  329. package/src/server/responses/ws-upstream.ts +19 -6
  330. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  331. package/src/server/startup-health-cache.ts +2 -1
  332. package/src/server/workflow-refusal.ts +33 -0
  333. package/src/service/cli.ts +20 -4
  334. package/src/service/desktop-command-guard.ts +32 -0
  335. package/src/service/desktop-startup.ts +66 -40
  336. package/src/service/desktop-supervision.d.mts +26 -0
  337. package/src/service/desktop-supervision.mjs +140 -0
  338. package/src/service/managing-cli.ts +1 -0
  339. package/src/service/orchestration.ts +46 -20
  340. package/src/service/ownership-mutation-lease.d.mts +1 -1
  341. package/src/service/ownership-mutation-lease.mjs +5 -7
  342. package/src/service/repair.ts +25 -9
  343. package/src/service/state.ts +4 -3
  344. package/src/service/systemd.ts +17 -8
  345. package/src/service/windows-ops.ts +61 -22
  346. package/src/sidecar/anthropic-binding.ts +79 -0
  347. package/src/sidecar/auth.ts +76 -2
  348. package/src/storage/policy-job.ts +5 -1
  349. package/src/storage/policy-worker.ts +3 -1
  350. package/src/storage/policy.ts +9 -0
  351. package/src/types/anthropic-account-pool.ts +33 -0
  352. package/src/types/config.ts +33 -28
  353. package/src/types/provider.ts +5 -0
  354. package/src/types/request.ts +5 -0
  355. package/src/types/tools.ts +7 -0
  356. package/src/types/wire.ts +1 -0
  357. package/src/types.ts +4 -0
  358. package/src/update/index.ts +57 -9
  359. package/src/update/restart-ownership.ts +10 -3
  360. package/src/update/runtime-ownership.d.mts +6 -0
  361. package/src/update/runtime-ownership.mjs +23 -7
  362. package/src/usage/cost.ts +3 -2
  363. package/src/usage/expected-prices.ts +66 -14
  364. package/src/vision/anthropic-describe.ts +16 -3
  365. package/src/vision/index.ts +3 -0
  366. package/src/vision/plan.ts +19 -6
  367. package/src/web-search/alpha-search.ts +7 -1
  368. package/src/web-search/anthropic-executor.ts +16 -4
  369. package/src/web-search/index.ts +7 -3
  370. package/src/web-search/loop.ts +32 -7
  371. package/src/web-search/passthrough-bridge.ts +4 -1
  372. package/src/web-search/sidecar-providers.ts +4 -3
  373. package/gui/dist/assets/App-MbDVfUeC.js +0 -54
  374. package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
  375. package/gui/dist/assets/index-UJt6npXk.js +0 -94
  376. package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
@@ -571,11 +571,11 @@ export const CAPABILITIES: readonly Capability[] = [
571
571
  command: ["claude","config"],
572
572
  summary: "Read or update Claude Code settings, including independent CLI first-party routing.",
573
573
  routes: [{"method":"GET","path":"/api/claude-code"},{"method":"PUT","path":"/api/claude-code"}],
574
- flags: [{"name":"--enabled","value":"string","summary":"on or off."},{"name":"--auth-mode","value":"string","summary":"auto, proxy or subscription."},{"name":"--system-env","value":"string","summary":"on or off."},{"name":"--fast-mode","value":"string","summary":"on or off."},{"name":"--auto-context","value":"string","summary":"on or off."},{"name":"--compact-window","value":"string","summary":"Positive token count or default to clear."},{"name":"--inject-agents","value":"string","summary":"on or off."},{"name":"--small-fast-model","value":"string","summary":"Model ID; - clears."},{"name":"--model-map","value":"string","summary":"Comma-separated from=to mappings; - clears."},{"name":"--blocked-skills","value":"string","summary":"Comma-separated names; - clears."},{"name":"--web-model","value":"string","summary":"Model ID; - clears."},{"name":"--web-backend","value":"string","summary":"openai, anthropic, xai, gemini, exa or -."},{"name":"--vision-model","value":"string","summary":"Model ID; - clears."},{"name":"--vision-backend","value":"string","summary":"openai, anthropic or -."},{"name":"--first-party","value":"string","summary":"on or off; must be the only setting."},{"name":"--json","value":"boolean","summary":"Emit the result as JSON."}],
574
+ flags: [{"name":"--enabled","value":"string","summary":"on or off."},{"name":"--auth-mode","value":"string","summary":"auto, proxy or subscription."},{"name":"--system-env","value":"string","summary":"on or off."},{"name":"--fast-mode","value":"string","summary":"on or off."},{"name":"--auto-context","value":"string","summary":"on or off."},{"name":"--compact-window","value":"string","summary":"Positive token count or default to clear."},{"name":"--context-accounting","value":"string","summary":"1m (default) or 200k."},{"name":"--inject-agents","value":"string","summary":"on or off."},{"name":"--small-fast-model","value":"string","summary":"Model ID; - clears."},{"name":"--model-map","value":"string","summary":"Comma-separated from=to mappings; - clears."},{"name":"--blocked-skills","value":"string","summary":"Comma-separated names; - clears."},{"name":"--web-model","value":"string","summary":"Model ID; - clears."},{"name":"--web-backend","value":"string","summary":"openai, anthropic, xai, gemini, exa or -."},{"name":"--vision-model","value":"string","summary":"Model ID; - clears."},{"name":"--vision-backend","value":"string","summary":"openai, anthropic or -."},{"name":"--first-party","value":"string","summary":"on or off; must be the only setting."},{"name":"--json","value":"boolean","summary":"Emit the result as JSON."}],
575
575
  mutates: true,
576
576
  json: "payload",
577
577
  details: ["At least one setting is required. --first-party must be set alone and writes Claude settings immediately.","Clear both helper model and backend with - to restore inheritance. Preserve shared_proxy_retained warnings."],
578
- usage: "ocx claude config status [--json]; ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>] [--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>] [--compact-window <tokens|default>] [--inject-agents <on|off>] [--small-fast-model <id|->] [--model-map <from=to,...|->] [--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|xai|gemini|exa|->] [--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]; or ocx claude config set --first-party <on|off> [--json]",
578
+ usage: "ocx claude config status [--json]; ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>] [--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>] [--compact-window <tokens|default>] [--context-accounting <1m|200k>] [--inject-agents <on|off>] [--small-fast-model <id|->] [--model-map <from=to,...|->] [--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|xai|gemini|exa|->] [--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]; or ocx claude config set --first-party <on|off> [--json]",
579
579
  },
580
580
  {
581
581
  command: ["claude","intercept","start"],
@@ -714,11 +714,11 @@ export const CAPABILITIES: readonly Capability[] = [
714
714
  command: ["agent","roles"],
715
715
  summary: "omo (Codex / LazyCodex): show each Codex agent role's model pin, set one role's model in its TOML and in omo.jsonc, or suggest a model for every role.",
716
716
  routes: [{"method":"GET","path":"/api/codex-agent-roles"},{"method":"PUT","path":"/api/codex-agent-roles/{role}"},{"method":"POST","path":"/api/codex-agent-roles/auto-assign"}],
717
- flags: [{"name":"--json","value":"boolean","summary":"Emit the role list, the write result or the proposals as JSON."},{"name":"--model","value":"string","summary":"suggest: size the roles with this model instead of the Codex default model."},{"name":"--apply","value":"boolean","summary":"suggest: write every proposal through the role model write."}],
717
+ flags: [{"name":"--json","value":"boolean","summary":"Emit the role list, the write result or the proposals as JSON."},{"name":"--effort","value":"string","summary":"set: also write the role's reasoning effort."},{"name":"--model","value":"string","summary":"suggest: size the roles with this model instead of the Codex default model."},{"name":"--apply","value":"boolean","summary":"suggest: write every proposal through the role model write."}],
718
718
  mutates: true,
719
719
  json: "payload",
720
- details: ["A bare invocation reads and never writes.","Requires Codex-based omo (LazyCodex): the omo@sisyphuslabs Codex plugin enabled in config.toml and installed; otherwise status lists no roles, and set and suggest are refused.","set rewrites only the root model value of $CODEX_HOME/agents/<role>.toml; omo.jsonc is skipped when absent or when it contains comments.","suggest sizes every role with one model call and prints proposals without writing; --apply writes each proposed model, and its effort when the role file already sets model_reasoning_effort."],
721
- usage: "ocx agent roles [status] [--json]; ocx agent roles set <role> <model> [--json]; ocx agent roles suggest [--model <id>] [--apply] [--json]",
720
+ details: ["A bare invocation reads and never writes.","Requires Codex-based omo (LazyCodex): the omo@sisyphuslabs Codex plugin enabled in config.toml and installed; otherwise status lists no roles, and set and suggest are refused.","set rewrites only the root model value of $CODEX_HOME/agents/<role>.toml, plus model_reasoning_effort with --effort; omo.jsonc gets the same model and, when LazyCodex has that level, the effort as reasoning; it is skipped when absent or when it contains comments.","suggest sizes every role with one model call and prints proposals without writing; --apply writes each proposed model, and its effort when the role file already sets model_reasoning_effort."],
721
+ usage: "ocx agent roles [status] [--json]; ocx agent roles set <role> <model> [--effort <level>] [--json]; ocx agent roles suggest [--model <id>] [--apply] [--json]",
722
722
  },
723
723
  {
724
724
  command: ["agent","injection"],
@@ -111,6 +111,22 @@ export const INTEGRATION_CAPABILITIES: readonly Capability[] = [
111
111
  json: "payload",
112
112
  details: ["With --client, restore requires --profile; only Aside profiles accept that selector. No automatic drift confirmation or retry.","Preview preserves exact opId/profile/confirm-drift intent. Generic restore derives the client on the server; the returned plan does not embed opId or complete command input.","A refused or no-op preview is completed inspection, not applied work. A stale bound mutation exits5 with a fixed re-preview hint; no replacement token is automatically adopted."],
113
113
  },
114
+ {
115
+ command: ["commandcode", "restore"],
116
+ usage: "ocx commandcode restore --op <opId> [--confirm-drift] [--preview | --plan-fingerprint <token>] [--json]",
117
+ summary: "Restore a Command Code rollback operation, retaining client ownership and drift checks.",
118
+ routes: [{ method: "POST", path: "/api/client-integrations/commandcode/restore" }, { method: "POST", path: "/api/client-integrations/commandcode/restore/preview" }],
119
+ flags: [
120
+ { name: "--op", value: "string", summary: "Command Code operation ID; --op-id is an alias.", required: true },
121
+ { name: "--confirm-drift", value: "boolean", summary: "Explicitly allow replacing edits made after the snapshot." },
122
+ { name: "--preview", value: "boolean", summary: "Inspect restoration without mutating; exclusive with a commit fingerprint." },
123
+ { name: "--plan-fingerprint", value: "string", summary: "Optional versioned token from a matching preview; stale intent is refused without retry." },
124
+ { name: "--json", value: "boolean", summary: "Emit the result as JSON." },
125
+ ],
126
+ mutates: true,
127
+ json: "payload",
128
+ details: ["cmd restore is an alias. --client and --profile are rejected; the dedicated route binds the operation to Command Code.", "Older proxies without the dedicated route fail without a generic restore fallback. Redirects are refused.", "Preview preserves the operation and drift intent. A stale bound mutation requires a new explicit preview."],
129
+ },
114
130
  {
115
131
  command: ["claude", "config", "status"],
116
132
  usage: "ocx claude config status [--json]",
@@ -123,10 +139,10 @@ export const INTEGRATION_CAPABILITIES: readonly Capability[] = [
123
139
  },
124
140
  {
125
141
  command: ["claude", "config", "set"],
126
- usage: "ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>] [--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>] [--compact-window <tokens|default>] [--inject-agents <on|off>] [--small-fast-model <id|->] [--model-map <from=to,...|->] [--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|xai|gemini|exa|->] [--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]; or ocx claude config set --first-party <on|off> [--json]",
142
+ usage: "ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>] [--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>] [--compact-window <tokens|default>] [--context-accounting <1m|200k>] [--inject-agents <on|off>] [--small-fast-model <id|->] [--model-map <from=to,...|->] [--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|xai|gemini|exa|->] [--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]; or ocx claude config set --first-party <on|off> [--json]",
127
143
  summary: "Change Claude Code settings through the runtime owner.",
128
144
  routes: [{"method": "PUT", "path": "/api/claude-code"}],
129
- flags: [{"name": "--enabled", "value": "string", "summary": "on or off."}, {"name": "--auth-mode", "value": "string", "summary": "auto, proxy or subscription."}, {"name": "--system-env", "value": "string", "summary": "on or off."}, {"name": "--fast-mode", "value": "string", "summary": "on or off."}, {"name": "--auto-context", "value": "string", "summary": "on or off."}, {"name": "--compact-window", "value": "string", "summary": "Positive token count or default to clear."}, {"name": "--inject-agents", "value": "string", "summary": "on or off."}, {"name": "--small-fast-model", "value": "string", "summary": "Model ID; - clears."}, {"name": "--model-map", "value": "string", "summary": "Comma-separated from=to mappings; - clears."}, {"name": "--blocked-skills", "value": "string", "summary": "Comma-separated names; - clears."}, {"name": "--web-model", "value": "string", "summary": "Model ID; - clears."}, {"name": "--web-backend", "value": "string", "summary": "openai, anthropic, xai, gemini, exa or -."}, {"name": "--vision-model", "value": "string", "summary": "Model ID; - clears."}, {"name": "--vision-backend", "value": "string", "summary": "openai, anthropic or -."}, {"name": "--first-party", "value": "string", "summary": "on or off; must be the only setting."}, {"name": "--json", "value": "boolean", "summary": "Emit the result as JSON."}],
145
+ flags: [{"name": "--enabled", "value": "string", "summary": "on or off."}, {"name": "--auth-mode", "value": "string", "summary": "auto, proxy or subscription."}, {"name": "--system-env", "value": "string", "summary": "on or off."}, {"name": "--fast-mode", "value": "string", "summary": "on or off."}, {"name": "--auto-context", "value": "string", "summary": "on or off."}, {"name": "--compact-window", "value": "string", "summary": "Positive token count or default to clear."}, {"name": "--context-accounting", "value": "string", "summary": "1m (default) or 200k."}, {"name": "--inject-agents", "value": "string", "summary": "on or off."}, {"name": "--small-fast-model", "value": "string", "summary": "Model ID; - clears."}, {"name": "--model-map", "value": "string", "summary": "Comma-separated from=to mappings; - clears."}, {"name": "--blocked-skills", "value": "string", "summary": "Comma-separated names; - clears."}, {"name": "--web-model", "value": "string", "summary": "Model ID; - clears."}, {"name": "--web-backend", "value": "string", "summary": "openai, anthropic, xai, gemini, exa or -."}, {"name": "--vision-model", "value": "string", "summary": "Model ID; - clears."}, {"name": "--vision-backend", "value": "string", "summary": "openai, anthropic or -."}, {"name": "--first-party", "value": "string", "summary": "on or off; must be the only setting."}, {"name": "--json", "value": "boolean", "summary": "Emit the result as JSON."}],
130
146
  mutates: true,
131
147
  json: "payload",
132
148
  details: ["At least one setting is required. --first-party must be set alone and writes Claude settings immediately.", "Clear both helper model and backend with - to restore inheritance. Preserve shared_proxy_retained warnings."],
@@ -0,0 +1,132 @@
1
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { posix, win32 } from "node:path";
4
+ import { readDesktopCliRecord, type DesktopCliRecordRead, desktopCliRecordPath, DESKTOP_CLI_RECORD_MAX_BYTES } from "../lib/desktop-cli-record.mjs";
5
+ import { redactUserPath } from "../lib/redact";
6
+
7
+ export type CliCommandIssue = Extract<DesktopCliRecordRead, { issue: string }>["issue"]
8
+ | "cleanup-pending"
9
+ | "desktop-target-missing" | "desktop-target-unusable"
10
+ | "expected-command-missing" | "expected-command-unusable"
11
+ | "path-missing" | "path-first-not-desktop" | "path-unreadable"
12
+ | "path-scan-truncated" | "windows-current-directory-shadow";
13
+ export type CliPathCandidate = {
14
+ path: string; realpath: string; pathIndex: number; extension: string;
15
+ };
16
+ export type CliPathDiagnostics = {
17
+ configured: boolean;
18
+ recordPath: string;
19
+ packageHandoff: "enabled" | "disabled-on-windows";
20
+ recordState: DesktopCliRecordRead["state"];
21
+ expectedExecutable: string | null;
22
+ handoffTarget: string | null;
23
+ candidates: CliPathCandidate[];
24
+ pathFirst: CliPathCandidate | null;
25
+ desktopFirstOnPath: boolean | null;
26
+ currentDirectoryCandidate: CliPathCandidate | null;
27
+ shellResolution: "unobserved";
28
+ issues: CliCommandIssue[];
29
+ };
30
+ export type CliPathObservation =
31
+ | { kind: "usable"; realpath: string }
32
+ | { kind: "missing" | "unusable" | "unreadable" };
33
+ export type CliPathDiagnosticOptions = {
34
+ platform?: NodeJS.Platform; env?: NodeJS.ProcessEnv; cwd?: string; home?: string;
35
+ recordRead?: DesktopCliRecordRead;
36
+ observe?: (path: string) => CliPathObservation;
37
+ };
38
+
39
+ export function collectCliPathDiagnostics(options: CliPathDiagnosticOptions = {}): CliPathDiagnostics {
40
+ const platform = options.platform ?? process.platform;
41
+ const env = options.env ?? process.env;
42
+ const api = platform === "win32" ? win32 : posix;
43
+ const cwd = options.cwd ?? process.cwd();
44
+ const home = options.home ?? (platform === "win32" ? env.USERPROFILE || homedir() : homedir());
45
+ const read = options.recordRead ?? readDesktopCliRecord({ platform, env, home, recordPath: desktopCliRecordPath({ platform, env, home }) });
46
+ const configured = read.state === "ready";
47
+ const target = configured && read.state === "ready" ? read.record.cliExecutable : null;
48
+ const expected = configured ? (platform === "win32" ? target : api.join(home, ".opencodex-desktop", "bin", "ocx")) : null;
49
+ const issues: CliCommandIssue[] = [];
50
+ const add = (issue: CliCommandIssue): void => { if (!issues.includes(issue)) issues.push(issue); };
51
+ if (read.state === "invalid" || read.state === "unreadable") add(read.issue);
52
+ if (read.state === "disabled" && read.cleanupPending) add("cleanup-pending");
53
+ const observe = options.observe ?? ((path: string): CliPathObservation => {
54
+ try {
55
+ if (!statSync(path).isFile()) return { kind: "unusable" };
56
+ accessSync(path, platform === "win32" ? constants.F_OK : constants.X_OK);
57
+ return { kind: "usable", realpath: realpathSync(path) };
58
+ } catch (error) {
59
+ const code = (error as NodeJS.ErrnoException).code;
60
+ return { kind: code === "ENOENT" || code === "ENOTDIR" ? "missing" : code === "EACCES" ? "unusable" : "unreadable" };
61
+ }
62
+ });
63
+ const key = (path: string): string => platform === "win32" ? path.toLowerCase() : path;
64
+ const same = (a: string, b: string): boolean => key(a) === key(b);
65
+ const envValue = (name: string): string | undefined => platform === "win32"
66
+ ? env[Object.keys(env).find(key => key.toLowerCase() === name.toLowerCase()) ?? name] : env[name];
67
+ const path = envValue("PATH");
68
+ const rawExtensions = platform === "win32"
69
+ ? (envValue("PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(ext => /^\.[A-Za-z0-9]+$/.test(ext)) : [];
70
+ const uniqueExtensions = [...new Set(rawExtensions.map(ext => ext.toLowerCase()))];
71
+ const extensions = platform === "win32" ? [...uniqueExtensions.slice(0, 32), ""] : [""];
72
+ if (uniqueExtensions.length > 32) add("path-scan-truncated");
73
+ const directories = path === undefined ? [] : path.split(platform === "win32" ? ";" : ":");
74
+ if (directories.length > 256) add("path-scan-truncated");
75
+ const candidates: CliPathCandidate[] = [];
76
+ const scan = (directory: string, pathIndex: number): CliPathCandidate[] => {
77
+ const result: CliPathCandidate[] = [];
78
+ const unquoted = platform === "win32" && directory.startsWith('"') && directory.endsWith('"') ? directory.slice(1, -1) : directory;
79
+ for (const extension of extensions) {
80
+ const candidate = api.resolve(cwd, unquoted || ".", `ocx${extension}`);
81
+ const found = observe(candidate);
82
+ if (found.kind === "unreadable") add("path-unreadable");
83
+ if (found.kind === "usable") result.push({ path: candidate, realpath: found.realpath, pathIndex, extension });
84
+ }
85
+ return result;
86
+ };
87
+ for (const [index, directory] of directories.slice(0, 256).entries()) candidates.push(...scan(directory, index));
88
+ const pathFirst = candidates[0] ?? null;
89
+ const currentDirectoryCandidate = platform === "win32" ? scan(cwd, -1)[0] ?? null : null;
90
+ let expectedRealpath: string | null = null;
91
+ if (target) {
92
+ const found = observe(target);
93
+ if (found.kind === "missing") add("desktop-target-missing");
94
+ else if (found.kind !== "usable") add("desktop-target-unusable");
95
+ }
96
+ if (expected) {
97
+ const found = observe(expected);
98
+ if (found.kind === "missing") add("expected-command-missing");
99
+ else if (found.kind !== "usable") add("expected-command-unusable");
100
+ else expectedRealpath = found.realpath;
101
+ if (!pathFirst) add("path-missing");
102
+ else if (!same(pathFirst.path, expected) && (!expectedRealpath || !same(pathFirst.realpath, expectedRealpath))) add("path-first-not-desktop");
103
+ if (currentDirectoryCandidate && !same(currentDirectoryCandidate.path, expected)
104
+ && (!expectedRealpath || !same(currentDirectoryCandidate.realpath, expectedRealpath))) add("windows-current-directory-shadow");
105
+ }
106
+ return {
107
+ configured, recordPath: read.path, packageHandoff: platform === "win32" ? "disabled-on-windows" : "enabled",
108
+ recordState: read.state, expectedExecutable: expected, handoffTarget: platform === "win32" ? null : target,
109
+ candidates, pathFirst,
110
+ desktopFirstOnPath: configured && !issues.includes("path-unreadable") && !issues.includes("path-scan-truncated")
111
+ ? Boolean(pathFirst && expected && expectedRealpath
112
+ && (same(pathFirst.path, expected) || same(pathFirst.realpath, expectedRealpath))) : null,
113
+ currentDirectoryCandidate, shellResolution: "unobserved", issues,
114
+ };
115
+ }
116
+
117
+ export function formatCliCommandLine(value: CliPathDiagnostics): string {
118
+ const first = value.pathFirst ? redactUserPath(value.pathFirst.path).replace(/[\x00-\x1f\x7f]/g, "?") : "none observed";
119
+ return `ocx command: PATH first=${first}; Desktop=${!value.configured ? "not configured" : value.desktopFirstOnPath === null ? "unobserved" : value.desktopFirstOnPath ? "first" : "not first"}; ${value.packageHandoff === "disabled-on-windows" ? "package handoff=disabled on Windows (user Path selects Desktop); " : ""}shell=unobserved${value.issues.length ? `; issues=${value.issues.join(",")}` : ""}`;
120
+ }
121
+
122
+ export function formatCliStatusHealthLabel(health: string, value: CliPathDiagnostics, local: boolean): string {
123
+ return `${health}${local ? " (local)" : ""}\n ${formatCliCommandLine(value)}`;
124
+ }
125
+
126
+ export function cliCommandDoctorChecks(value: CliPathDiagnostics): { level: "OK" | "WARN" | "FAIL"; message: string }[] {
127
+ const hard = value.issues.some(issue => issue.startsWith("record-") || issue.startsWith("desktop-target-") || issue.startsWith("expected-command-"));
128
+ const recovery = value.issues.some(issue => issue.startsWith("record-"))
129
+ ? ` The terminal-command record at ${redactUserPath(value.recordPath).replace(/[\x00-\x1f\x7f]/g, "?")} could not be used. Open OpenCodex Desktop to repair the terminal command.${value.packageHandoff === "disabled-on-windows" ? "" : " Or run with OCX_NO_DESKTOP_HANDOFF=1."}${value.issues.includes("record-too-large") ? ` The record limit is ${DESKTOP_CLI_RECORD_MAX_BYTES} bytes.` : ""}`
130
+ : value.issues.includes("cleanup-pending") ? " Open OpenCodex Desktop to finish terminal-command cleanup." : "";
131
+ return [{ level: hard ? "FAIL" : value.issues.length ? "WARN" : "OK", message: formatCliCommandLine(value) + recovery }];
132
+ }
@@ -15,11 +15,14 @@ const DEFAULT_DEPS: CodexShimAutoRestoreCliDeps = {
15
15
  readConfig: readConfigDiagnostics,
16
16
  };
17
17
 
18
+ /** Identify command namespaces whose execution must not trigger automatic shim repair. */
18
19
  export function skipsCodexShimAutoRestore(command: string | undefined, args: string[]): boolean {
19
20
  if (command === "uninstall" || command === "remove") return true;
20
21
  if (command === "status" || command === "doctor") return true;
21
22
  // `lab` is read-only inspection; it must not trigger shim side effects.
22
23
  if (command === "lab") return true;
24
+ // Local messaging owns its helpers; even malformed usage must not repair/start anything.
25
+ if (command === "message") return true;
23
26
  // `resolve` is read-only inspection for embedding shells: a lookup made to populate
24
27
  // a consent surface must not trigger a shim repair side effect first.
25
28
  if (command === "resolve") return true;
@@ -0,0 +1,65 @@
1
+ import { createSupervisionLatch, inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
2
+ import type { DesktopSupervision, SupervisionInspector } from "../service/desktop-command-guard";
3
+ import { UpdateRestartRequired } from "./update-restart-candidate";
4
+ import { runProxyRestart, type ProxyRestartIo } from "./tray-proxy";
5
+
6
+ function evidenceForTarget(evidence: DesktopSupervision, pid: number | null | undefined): DesktopSupervision {
7
+ // Injected inspectors must obey the same target binding as the production inspector.
8
+ return evidence.kind === "desktop" && pid != null && evidence.runtimePid !== pid
9
+ ? { kind: "unknown", reason: "unrelated-target", desktopSeen: false } : evidence;
10
+ }
11
+
12
+ export function duplicateRuntimeMessage(
13
+ pid: number | null | undefined,
14
+ port: number,
15
+ inspect: SupervisionInspector = inspectDesktopSupervision,
16
+ ): string {
17
+ const latch = createSupervisionLatch();
18
+ const evidence = evidenceForTarget(inspect({ targetPid: pid ?? undefined }), pid);
19
+ if (pid != null && latch.observe(evidence)) {
20
+ const supervisor = evidence.kind === "desktop" ? `, ${evidence.app}` : "";
21
+ return `OpenCodex Desktop supervises the running proxy (pid ${pid}${supervisor}, port ${port}). `
22
+ + "Use the running proxy, or quit OpenCodex before starting it from this CLI.";
23
+ }
24
+ return `⚠️ Proxy already running (PID ${pid ?? "unknown"}, port ${port}). Use 'ocx stop' first.`;
25
+ }
26
+
27
+ export function desktopStopNotice(evidence: DesktopSupervision): string | null {
28
+ const latch = createSupervisionLatch();
29
+ return latch.observe(evidence)
30
+ ? "OpenCodex Desktop may start this proxy again after a short backoff. Use Stop Proxy or Quit in the OpenCodex menu to keep it stopped."
31
+ : null;
32
+ }
33
+
34
+ export function runDesktopAwareProxyRestart(
35
+ io: ProxyRestartIo,
36
+ inspect: SupervisionInspector = inspectDesktopSupervision,
37
+ info: (line: string) => void = console.log,
38
+ ) {
39
+ const latch = createSupervisionLatch();
40
+ let targetPid: number | undefined;
41
+ let supervisedRequest = false;
42
+ return runProxyRestart({
43
+ ...io,
44
+ requestInPlaceRestart: async previous => {
45
+ targetPid = previous.pid ?? undefined;
46
+ const evidence = evidenceForTarget(inspect({ targetPid }), targetPid);
47
+ supervisedRequest = latch.observe(evidence);
48
+ const result = await io.requestInPlaceRestart(previous);
49
+ if (evidence.kind === "desktop" && result.accepted) {
50
+ info("Restart requested; OpenCodex Desktop starts the replacement.");
51
+ }
52
+ if (supervisedRequest && !result.accepted && !result.uncertain && result.error instanceof UpdateRestartRequired) {
53
+ info("OpenCodex Desktop supervises this proxy. Use the app's updater (tray → Check for Updates), or quit OpenCodex before updating and restarting from this CLI.");
54
+ return { accepted: false as const, uncertain: false, error: new Error("restart_desktop_update_required") };
55
+ }
56
+ return result;
57
+ },
58
+ startWhenStopped: async recovering => {
59
+ const blocked = latch.observe(evidenceForTarget(inspect({ targetPid }), targetPid));
60
+ // An accepted supervised handoff keeps its replacement intent through temporary absence.
61
+ if (supervisedRequest || blocked) return { status: "skipped" as const };
62
+ return io.startWhenStopped(recovering);
63
+ },
64
+ });
65
+ }
@@ -37,6 +37,8 @@ import { parseStopApproval, type StopApproval } from "./stop-approval";
37
37
  import type { ResolveArgs } from "./resolve";
38
38
  import type { ClientConnectionState } from "../client/state";
39
39
  import { OCX_NATIVE_REPLAY_RECOVERY_NOTE } from "../responses/compaction";
40
+ import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
41
+ import { desktopStopNotice } from "./desktop-runtime-guidance";
40
42
 
41
43
  export interface CliDispatchDeps {
42
44
  args: string[];
@@ -51,6 +53,7 @@ export interface CliDispatchDeps {
51
53
  spawnDetached: (argv: readonly string[]) => void;
52
54
  handleStart: () => Promise<void>;
53
55
  handleStop: (approval?: StopApproval) => Promise<StopOutcome>;
56
+ inspectDesktopSupervision?: typeof inspectDesktopSupervision;
54
57
  handleEnsure: (options?: { existingIsSuccess?: boolean }) => Promise<boolean>;
55
58
  handleResolve: (args: ResolveArgs) => Promise<number>;
56
59
  handleTrayProxyStart: (existingIsSuccess?: boolean) => Promise<boolean>;
@@ -112,6 +115,8 @@ const commandRunners: Record<string, CommandRunner> = {
112
115
  // re-start the proxy immediately, so warning there would contradict the next line.
113
116
  const warning = "⚠️ Codex/Claude requests through the proxy will fail until it is restarted ('ocx start' or 'ocx service start').";
114
117
  if (!parsed.json) {
118
+ const notice = desktopStopNotice((deps.inspectDesktopSupervision ?? inspectDesktopSupervision)());
119
+ if (notice) console.error(notice);
115
120
  // handleStop returns the structured outcome now; an object is always truthy, so
116
121
  // the warning must key on .ok — otherwise a failed stop would still claim downtime.
117
122
  if ((await deps.handleStop()).ok) console.log(warning);
@@ -145,6 +150,7 @@ const commandRunners: Record<string, CommandRunner> = {
145
150
  if (!deps.head.resolveArgs) return 64;
146
151
  return await deps.handleResolve(deps.head.resolveArgs);
147
152
  },
153
+ message: async deps => (await import("./message-command")).runMessageCommand(deps.args.slice(1)),
148
154
  restore: async deps => {
149
155
  const restoreArgs = deps.args.slice(1);
150
156
  const restoreJson = takeFlag(restoreArgs, "--json");
@@ -465,7 +471,7 @@ const commandRunners: Record<string, CommandRunner> = {
465
471
  },
466
472
  config,
467
473
  port: live.port,
468
- }, ["mcode", "pi", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
474
+ }, ["mcode", "pi", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"]));
469
475
  } catch (error) {
470
476
  console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
471
477
  }
@@ -961,6 +967,14 @@ const commandRunners: Record<string, CommandRunner> = {
961
967
  const { handleZcodeCommand } = await import("./integrations");
962
968
  return await handleZcodeCommand(deps.args.slice(1));
963
969
  },
970
+ commandcode: async deps => {
971
+ const { handleCommandcodeCommand } = await import("./integrations");
972
+ return await handleCommandcodeCommand(deps.args.slice(1));
973
+ },
974
+ cmd: async deps => {
975
+ const { handleCommandcodeCommand } = await import("./integrations");
976
+ return await handleCommandcodeCommand(deps.args.slice(1));
977
+ },
964
978
  help: async () => {
965
979
  printUsage();
966
980
  return 0;
package/src/cli/doctor.ts CHANGED
@@ -7,11 +7,13 @@
7
7
  * it never sets proxy env, relocates state dirs, mutates quota, or changes
8
8
  * networking. See devlog/_plan/260630_wsl-account-autoswitch/30_*.
9
9
  */
10
+ import { cliCommandDoctorChecks, collectCliPathDiagnostics } from "./cli-path-diagnostics";
10
11
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
11
12
  import { homedir } from "node:os";
12
13
  import { dirname, join } from "node:path";
13
14
  import { getConfigDir, getConfigPath, readConfigDiagnostics } from "../config";
14
15
  import { readPid } from "../config/process-state";
16
+ import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
15
17
  import { fetchLiveStartupHealth, probeUncleanExitState, selectStatusStartupHealth } from "./status";
16
18
  import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
17
19
  import { directLocalHttpFetch } from "../server/direct-local-http";
@@ -1324,6 +1326,12 @@ export async function runDoctor(args: string[] = []): Promise<void> {
1324
1326
  console.log(` ${row.exists ? "ok " : "-- "} ${row.label}: ${row.path}${flags ? ` (${flags})` : ""}`);
1325
1327
  }
1326
1328
 
1329
+ console.log("\nocx command selection");
1330
+ for (const check of cliCommandDoctorChecks(collectCliPathDiagnostics())) {
1331
+ console.log(` ${check.level === "OK" ? "ok " : "!! "} ${check.message}`);
1332
+ if (check.level === "FAIL") recordDoctorFailure();
1333
+ }
1334
+
1327
1335
  // Runs without the proxy on purpose: the worst accumulation happens when the proxy will
1328
1336
  // not start, which is exactly when the in-process periodic reclaim never ticks.
1329
1337
  const reclaimTemps = args.includes(RECLAIM_RESPONSE_TEMPS_FLAG);
@@ -1379,7 +1387,8 @@ export async function runDoctor(args: string[] = []): Promise<void> {
1379
1387
  configFn: () => ({ port: doctorConfig.port, hostname: doctorConfig.hostname }),
1380
1388
  });
1381
1389
  const liveStartup = live ? await fetchLiveStartupHealth(live) : null;
1382
- const startup = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(doctorConfig));
1390
+ const { startup } = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(doctorConfig),
1391
+ live?.pid != null ? () => inspectDesktopSupervision({ targetPid: live.pid! }) : undefined, live?.pid);
1383
1392
  console.log("\nCodex restart safety");
1384
1393
  console.log(` ${startup.rebootSafe ? "ok " : "!! "} ${startupHealthSummary(startup)}`);
1385
1394
  console.log(` ${formatStartupRoutingDetail(startup)}`);
@@ -1685,8 +1694,12 @@ export async function runDoctor(args: string[] = []): Promise<void> {
1685
1694
  const anyDrvfs = paths.some(p => detectFsType(p.path, mounts).isDrvfs || detectFsType(p.path, mounts).isMntDrive);
1686
1695
  const noProxy = currentProxyEnv.every(p => !p.present) && !configuredProxy.present;
1687
1696
  if (!startup.rebootSafe) {
1688
- const command = startup.recommendedCommand ?? startup.commands.restoreNative;
1689
- hints.push(`Codex is pinned to the local proxy without persistent startup protection. After restart, requests can reconnect indefinitely. Run '${command}'.`);
1697
+ if (startup.recommendedAction || startup.desktop?.supervisor) {
1698
+ hints.push(`Codex is pinned to the local proxy without persistent startup protection. ${startup.recommendedAction ?? "Reopen OpenCodex and check Start at Login."}`);
1699
+ } else {
1700
+ const command = startup.recommendedCommand ?? startup.commands.restoreNative;
1701
+ hints.push(`Codex is pinned to the local proxy without persistent startup protection. After restart, requests can reconnect indefinitely. Run '${command}'.`);
1702
+ }
1690
1703
  }
1691
1704
  if (anyDrvfs) {
1692
1705
  hints.push("State dir is on a Windows-mounted (/mnt) drive. Prefer the Linux home (~) under WSL for token/lock reliability.");
package/src/cli/help.ts CHANGED
@@ -79,6 +79,7 @@ Usage:
79
79
  ocx alias <sub> Short names for providers and models (list, set, rm, defaults)
80
80
  ocx combo <sub> Combo routing strategies and failover
81
81
  ocx agent <sub> Subagents, injection, effort caps, and sidecars
82
+ ocx message <sub> Loaded local Codex sessions and queued peer messages (sessions|send)
82
83
  ocx effort [sub] Inspect and configure reasoning effort caps and defaults
83
84
  ocx observe <sub> Logs, usage, storage, memory, and debug data
84
85
  ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
@@ -91,7 +92,7 @@ Usage:
91
92
  ocx api-key <sub> Alias of ocx access key
92
93
  ocx access <sub> External API keys and endpoint information
93
94
  ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
94
- ocx export --client <id> Print a client config wired to the running proxy (17 clients)
95
+ ocx export --client <id> Print a client config wired to the running proxy (18 clients)
95
96
  ocx integration client <sub> Enable, disable, inspect or roll back a client integration
96
97
  ocx grok <sub> Grok Build model selection and apply
97
98
  ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
@@ -105,6 +106,8 @@ Usage:
105
106
  ocx mcode [args...] Launch MiniMax Code through its managed provider
106
107
  ocx mmx text <sub> [args] Launch MiniMax CLI text through the proxy
107
108
  ocx zcode [sub] Connect ZCode to the proxy (managed provider)
109
+ ocx commandcode [sub] Connect Command Code CLI to the proxy (managed provider)
110
+ ocx cmd [sub] Alias of ocx commandcode
108
111
  ocx help [command] Show help
109
112
  ocx --version | -v Print version
110
113
 
package/src/cli/index.ts CHANGED
@@ -8,6 +8,8 @@ import { homedir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { currentServingCommand, deferServiceChildToNewerRuntime, markDelegatedServiceReady, recordServingRuntime } from "../config/serving-runtimes";
10
10
  import { packageVersion } from "../lib/package-version";
11
+ import { codexHomeIsAbsent } from "../codex/codex-home-owner";
12
+ import { getCodexHome } from "../codex/paths";
11
13
  import { admitUpdateRestartChild } from "./update-restart-child";
12
14
  import { UpdateRestartRequired } from "./update-restart-candidate";
13
15
  import { describeUpdateRestartFailure, restartFromCurrentInstallation } from "./update-restart";
@@ -75,7 +77,7 @@ import {
75
77
  pendingTeardownsAreExactly,
76
78
  quarantinePendingTeardown,
77
79
  } from "../config/pending-teardown";
78
- import { collectStatus, deadProxyRoutingAdviceLines, detectMissingCodexCatalogPath, hubStatusLines, missingCodexCatalogLines, remoteHubBannerLine, remoteHubStatusLines, unusedProxyWarningLines } from "./status";
80
+ import { collectStatus, deadProxyRoutingAdviceLines, detectMissingCodexCatalogPath, hubStatusLines, missingCodexCatalogLines, remoteHubBannerLine, remoteHubStatusLines, runtimeSupervisorLine, unusedProxyWarningLines } from "./status";
79
81
  import { endpointsToProve, everyEndpointProvenDownAsync, sharedTeardownAuthorized, type UninstallObservation } from "./uninstall-plan";
80
82
  import { takeFlag } from "./runtime-api";
81
83
  import { parseStartOptions, StartArgsError } from "./start-args";
@@ -85,11 +87,11 @@ import {
85
87
  recheckRestartFailedStart, reobserveRestartReplacement,
86
88
  restartStartOutcome,
87
89
  waitForProxyReplacement,
88
- runProxyRestart,
89
90
  runTrayProxyStart,
90
91
  type ProxyRestartLive,
91
92
  type ProxyRestartStartOutcome,
92
93
  } from "./tray-proxy";
94
+ import { duplicateRuntimeMessage, runDesktopAwareProxyRestart } from "./desktop-runtime-guidance";
93
95
  import { reportRestartFailure } from "./restart-failure";
94
96
  import { requestBoundSystemRestart } from "./system-restart-client";
95
97
  import { installCrashGuards } from "../lib/crash-guard";
@@ -210,11 +212,17 @@ initializeNodeLauncherContext();
210
212
  // The compiled executable is also the capture-only MCP server's launcher.
211
213
  // Handle this private entrypoint before CLI preflight or command dispatch.
212
214
  if (process.argv[2] === "__keyring-load-check") { console.log(JSON.stringify((await import("../lib/keyring-native")).inspectKeyringBinding())); process.exit(0); }
213
- if (process.argv[2] === "__codebuddy-mcp") {
214
- const { runCodeBuddyMcpServer } = await import("../adapters/codebuddy/mcp-server");
215
- await runCodeBuddyMcpServer(process.argv[3] ?? "");
216
- // The MCP stdio loop owns this process until stdin closes; do not fall through
217
- // to ordinary CLI dispatch or exit after the handshake completes.
215
+
216
+ // The compiled executable also launches the isolated MCP servers.
217
+ // Handle these private entrypoints before CLI preflight or command dispatch.
218
+ if (process.argv[2] === "__codebuddy-mcp" || process.argv[2] === "__qoder-mcp") {
219
+ if (process.argv[2] === "__codebuddy-mcp") {
220
+ const { runCodeBuddyMcpServer } = await import("../adapters/codebuddy/mcp-server");
221
+ await runCodeBuddyMcpServer(process.argv[3] ?? "");
222
+ } else {
223
+ const { runCodingAgentMcpServer } = await import("../adapters/coding-agent/mcp-server");
224
+ await runCodingAgentMcpServer(process.argv[3] ?? "");
225
+ }
218
226
  await new Promise<never>(() => {});
219
227
  }
220
228
 
@@ -323,7 +331,7 @@ async function chooseListenPort(
323
331
  throw new StartCommandExit(serviceStayOutExitCode());
324
332
  }
325
333
  if (decision === "refuse-live-proxy") {
326
- console.error(`⚠️ Proxy already running (PID ${holder?.pid ?? "unknown"}, port ${preferred}). Use 'ocx stop' first.`);
334
+ console.error(duplicateRuntimeMessage(holder?.pid, preferred));
327
335
  throw new StartCommandExit(1);
328
336
  }
329
337
  if (decision === "refuse-unidentified-holder") {
@@ -438,7 +446,7 @@ async function handleStart(options: { block?: boolean } = {}) {
438
446
  process.exit(serviceStayOutExitCode());
439
447
  }
440
448
  if (decision === "refuse" || decision === "await-parent") {
441
- console.error(`⚠️ Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}). Use 'ocx stop' first.`);
449
+ console.error(duplicateRuntimeMessage(owner.live.pid ?? owner.pidSnapshot, owner.live.port));
442
450
  process.exit(1);
443
451
  }
444
452
  // Sibling path. The new instance takes over this home's ocx.pid / runtime-port.json while
@@ -648,10 +656,12 @@ async function handleStart(options: { block?: boolean } = {}) {
648
656
  removeRuntimePort(process.pid);
649
657
  if (teardown.restoreNativeCodex && !currentExternalCodexModelProvider()) {
650
658
  try {
651
- const restored = restoreNativeCodex();
652
- if (!restored.success) {
653
- cleanupSucceeded = false;
654
- console.error(`⚠️ Native Codex restore failed during shutdown: ${restored.message}`);
659
+ if (!codexHomeIsAbsent(getCodexHome())) {
660
+ const restored = restoreNativeCodex();
661
+ if (!restored.success) {
662
+ cleanupSucceeded = false;
663
+ console.error(`⚠️ Native Codex restore failed during shutdown: ${restored.message}`);
664
+ }
655
665
  }
656
666
  } catch (error) {
657
667
  cleanupSucceeded = false;
@@ -922,7 +932,7 @@ async function handleProxyRestart(
922
932
  startWhenStopped: (recoveringLiveRestart: boolean) => Promise<ProxyRestartStartOutcome>,
923
933
  ): Promise<boolean> {
924
934
  const deadlineAt = Date.now() + PROXY_RESTART_OBSERVE_MS;
925
- const result = await runProxyRestart({
935
+ const result = await runDesktopAwareProxyRestart({
926
936
  findLive: () => discoverStableProxyForRestart({
927
937
  findLive: () => findLiveProxy({ deadlineAt, attempts: 2, acceptPackageTreeFenced: true }),
928
938
  expired: () => Date.now() >= deadlineAt,
@@ -1779,6 +1789,7 @@ async function handleStatus() {
1779
1789
  console.log(` Config: ${status.json.paths.config}${local}`);
1780
1790
  console.log(` PID file: ${status.json.paths.pid}${local}`);
1781
1791
  console.log(` Runtime: ${status.json.paths.runtime}${local}`);
1792
+ if (runtimeSupervisorLine(status.json.startup)) console.log(` ${runtimeSupervisorLine(status.json.startup)}`);
1782
1793
  console.log(` Runtime source: ${status.json.runtime.source}${status.json.runtime.overrideEnv ? ` (${status.json.runtime.overrideEnv})` : ""}${local}`);
1783
1794
  // On a client this is the local default, which routing does not use — the hub applies its own.
1784
1795
  console.log(` Default provider: ${status.json.defaultProvider}${local}`);
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { redactUserPath } from "../lib/redact";
3
3
  import type { RefusalReason } from "../integrations/mutation-plan";
4
+ import { ASIDE_SYNC_EMPTY_LINES } from "./aside-profile-recovery";
4
5
  import { refreshAsideProfilesThroughServer } from "./aside-profiles";
5
6
  import { runCatalogAction } from "./catalog-command-result";
6
7
  import { CliUsageError, RuntimeApiError, printData, takeFlag, takeOption, type RuntimeApiDeps } from "./runtime-api";
@@ -56,7 +57,7 @@ export function handleIntegrationAsideSync(argv: string[], deps: AsideSyncCliDep
56
57
  const parsed = resultsSchema.safeParse(outcomes);
57
58
  if (!parsed.success) throw new Error("Invalid Aside synchronization outcome");
58
59
  const results = parsed.data;
59
- const lines = results.length === 0 ? ["No eligible Aside profiles to synchronize. Check integration status and profile sync preferences."]
60
+ const lines = results.length === 0 ? [...ASIDE_SYNC_EMPTY_LINES]
60
61
  : results.flatMap(row => [
61
62
  `Aside profile ${row.profileId}: ${row.ok ? row.changed ? "updated" : "unchanged" : "failed"}.`,
62
63
  ...(row.reason ? [row.reason] : []),
@@ -23,6 +23,7 @@ export const FILE_INTEGRATION_CLIENTS = [
23
23
  "raycast",
24
24
  "omo",
25
25
  "cline",
26
+ "commandcode",
26
27
  "kilo",
27
28
  "droid",
28
29
  ] as const;
@@ -1,5 +1,6 @@
1
1
  /** Preview and explicitly bound mutations use the existing management authority. */
2
2
  import type { IntegrationMutationPlan, IntegrationPlanOperation } from "../integrations/mutation-plan";
3
+ import type { IntegrationClientId } from "../integrations/registry";
3
4
  import { redactSecretString, redactUserPath } from "../lib/redact";
4
5
  import {
5
6
  CliUsageError, RuntimeApiError, printData, runtimeRequest, terminalSafeText,
@@ -186,9 +187,15 @@ function reportError(error: unknown): number {
186
187
  return 1;
187
188
  }
188
189
 
189
- export async function handleIntegrationPreviewCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
190
+ export async function handleIntegrationPreviewCommand(argv: string[], deps: RuntimeApiDeps = {}, expectedClientId?: IntegrationClientId): Promise<number> {
190
191
  try {
191
192
  const intent = parseIntent(argv);
193
+ if (expectedClientId !== undefined && intent.operation === "restore") {
194
+ if (intent.client !== undefined || intent.profile !== undefined) throw new CliUsageError("client-specific restore does not accept --client or --profile", USAGE);
195
+ intent.client = expectedClientId;
196
+ intent.path = `${clientIntegrationPath(expectedClientId)}/restore${intent.preview ? "/preview" : ""}`;
197
+ intent.body.expectedClientId = expectedClientId;
198
+ }
192
199
  const result = await runtimeRequest(intent.path, {
193
200
  method: intent.preview || intent.operation === "restore" ? "POST" : "PUT",
194
201
  redirect: "error", body: JSON.stringify(intent.body),