@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
@@ -13,12 +13,14 @@ import {
13
13
  type RuntimeApiDeps,
14
14
  } from "./runtime-api";
15
15
  import { clientIntegrationPath, validateAsideProfile } from "./integration-input";
16
+ import { asideProfileRecoveryLines } from "./aside-profile-recovery";
17
+ import type { IntegrationClientId } from "../integrations/registry";
16
18
 
17
19
  const CLAUDE_USAGE = `Usage:
18
20
  ocx claude config [status] [--json]
19
21
  ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>]
20
22
  [--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>]
21
- [--compact-window <tokens|default>] [--inject-agents <on|off>]
23
+ [--compact-window <tokens|default>] [--context-accounting <1m|200k>] [--inject-agents <on|off>]
22
24
  [--small-fast-model <id|->] [--model-map <from=to,from=to|->]
23
25
  [--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|xai|gemini|exa|->]
24
26
  [--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]
@@ -74,6 +76,7 @@ export async function handleClaudeConfigCommand(argv: string[], deps: RuntimeApi
74
76
  const fastMode = takeBooleanOption(args, "--fast-mode");
75
77
  const autoContext = takeBooleanOption(args, "--auto-context");
76
78
  const compact = takeOption(args, "--compact-window");
79
+ const accounting = takeOption(args, "--context-accounting");
77
80
  const injectAgents = takeBooleanOption(args, "--inject-agents");
78
81
  const smallFastModel = takeOption(args, "--small-fast-model");
79
82
  const modelMap = takeOption(args, "--model-map");
@@ -96,6 +99,11 @@ export async function handleClaudeConfigCommand(argv: string[], deps: RuntimeApi
96
99
  body.autoCompactWindow = value;
97
100
  }
98
101
  }
102
+ if (accounting !== undefined) {
103
+ const value = accounting.toLowerCase();
104
+ if (value !== "1m" && value !== "200k") throw new CliUsageError("--context-accounting must be 1m or 200k", CLAUDE_USAGE);
105
+ body.contextAccounting = value;
106
+ }
99
107
  if (injectAgents !== undefined) body.injectAgents = injectAgents;
100
108
  if (smallFastModel !== undefined) body.smallFastModel = smallFastModel === "-" ? "" : smallFastModel;
101
109
  if (modelMap !== undefined) body.modelMap = parseMap(modelMap);
@@ -193,7 +201,10 @@ function raycastBlock(result: unknown): RaycastStatusBlock | null {
193
201
  */
194
202
  function singleClientStatusLines(result: unknown): string[] {
195
203
  const raycast = raycastBlock(result);
196
- if (!raycast) return summaryLines(result);
204
+ if (!raycast) {
205
+ const aside = (result as { clientId?: unknown } | null)?.clientId === "aside";
206
+ return [...summaryLines(result), ...(aside ? asideProfileRecoveryLines([result]) : [])];
207
+ }
197
208
  const rest = Object.fromEntries(Object.entries(result as Record<string, unknown>).filter(([key]) => key !== "raycast"));
198
209
  const lines = [...summaryLines(rest), `plan: ${raycast.plan}`];
199
210
  if (!raycast.aiDirPresent) {
@@ -215,7 +226,11 @@ function singleClientStatusLines(result: unknown): string[] {
215
226
  export async function handleClientIntegrationCommand(
216
227
  argv: string[],
217
228
  deps: RuntimeApiDeps = {},
229
+ expectedClientId?: IntegrationClientId,
218
230
  ): Promise<number> {
231
+ if (expectedClientId !== undefined && argv.some(arg => ["--client", "--profile"].some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
232
+ return runCliAction(async () => { throw new CliUsageError("client-specific restore does not accept --client or --profile", CLIENT_USAGE); });
233
+ }
219
234
  if ((argv[0] === "history" || argv[0] === "journal") && argv[1] === "remove") {
220
235
  const { handleIntegrationJournalRemove } = await import("./integration-journal");
221
236
  return handleIntegrationJournalRemove(argv.slice(2), deps);
@@ -227,7 +242,7 @@ export async function handleClientIntegrationCommand(
227
242
  if (argv[0] === "preview" || argv.some(arg => ["--preview", "--plan-fingerprint", "--reasoning-default", "--clear-reasoning-defaults"]
228
243
  .some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
229
244
  const { handleIntegrationPreviewCommand } = await import("./integration-preview");
230
- return handleIntegrationPreviewCommand(argv, deps);
245
+ return handleIntegrationPreviewCommand(argv, deps, expectedClientId);
231
246
  }
232
247
  return runCliAction(async () => {
233
248
  const args = [...argv];
@@ -247,7 +262,10 @@ export async function handleClientIntegrationCommand(
247
262
  const profiles = (result as { profiles?: Array<Record<string, unknown>> }).profiles;
248
263
  printData(result, wantsJson, profiles
249
264
  ? profiles.length > 0
250
- ? profiles.map(row => `${String(row.profileId)} ${String(row.name ?? "Aside")}: ${row.enabled ? "on" : "off"} (${String(row.state)})${row.current ? " [current]" : ""}`)
265
+ ? [
266
+ ...profiles.map(row => `${String(row.profileId)} ${String(row.name ?? "Aside")}: ${row.enabled ? "on" : "off"} (${String(row.state)})${row.current ? " [current]" : ""}`),
267
+ ...asideProfileRecoveryLines(profiles),
268
+ ]
251
269
  : [String((result as { error?: string }).error ?? "No Aside profiles found.")]
252
270
  : rows
253
271
  /*
@@ -289,9 +307,12 @@ export async function handleClientIntegrationCommand(
289
307
  if (client !== undefined && profile === undefined) throw new CliUsageError("restore --client requires --profile", CLIENT_USAGE);
290
308
  rejectArgs(args, CLIENT_USAGE);
291
309
  if (!opId) throw new CliUsageError("--op <opId> is required", CLIENT_USAGE);
292
- const result = await runtimeRequest(profile === undefined ? "/api/client-integrations/restore" : `${clientIntegrationPath("aside", profile)}/restore`, {
310
+ const restorePath = expectedClientId !== undefined ? `${clientIntegrationPath(expectedClientId)}/restore`
311
+ : profile === undefined ? "/api/client-integrations/restore" : `${clientIntegrationPath("aside", profile)}/restore`;
312
+ const result = await runtimeRequest(restorePath, {
293
313
  method: "POST",
294
- body: JSON.stringify({ opId, confirmDrift }),
314
+ ...(expectedClientId === undefined ? {} : { redirect: "error" as const }),
315
+ body: JSON.stringify({ opId, confirmDrift, ...(expectedClientId === undefined ? {} : { expectedClientId }) }),
295
316
  }, deps);
296
317
  printData(result, wantsJson, [String((result as Record<string, unknown>).message ?? "Restored.")]);
297
318
  return;
@@ -387,3 +408,24 @@ export async function handleClaudeInterceptCommand(argv: string[], deps: Runtime
387
408
  printData(result, wantsJson, summaryLines(result));
388
409
  });
389
410
  }
411
+
412
+ const COMMANDCODE_USAGE = "Usage: ocx commandcode [status|show|list|enable|disable|history|journal|restore] [--json]";
413
+
414
+ export async function handleCommandcodeCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
415
+ const args = [...argv];
416
+ const verbIndex = args.findIndex(arg => !arg.startsWith("-"));
417
+ const action = (verbIndex === -1 ? "status" : args[verbIndex]).toLowerCase();
418
+ const known = ["status", "show", "list", "enable", "disable", "history", "journal", "restore"];
419
+ if (!known.includes(action)) {
420
+ console.error(`unknown commandcode command ${action}`);
421
+ console.error(COMMANDCODE_USAGE);
422
+ return 2;
423
+ }
424
+ const rest = verbIndex === -1 ? args : [...args.slice(0, verbIndex), ...args.slice(verbIndex + 1)];
425
+ const forwarded = action === "restore" ? [action, ...rest] : [action, ...rest, "--client", "commandcode"];
426
+ const code = await handleClientIntegrationCommand(forwarded, deps, action === "restore" ? "commandcode" : undefined);
427
+ if (code === 0 && (action === "enable" || action === "disable")) {
428
+ console.error("Command Code reads providers on startup.");
429
+ }
430
+ return code;
431
+ }
@@ -0,0 +1,34 @@
1
+ import type { MessageKind } from "../messaging/envelope";
2
+ import { isThreadId } from "../messaging/types";
3
+
4
+ export type MessageArgs = { action: "sessions"; json: boolean }
5
+ | { action: "send"; json: boolean; kind: MessageKind; thread?: string; name?: string; inReplyTo?: string };
6
+
7
+ /** Pure pre-parse: unknown/duplicate options and unsupported remote surfaces fail closed. */
8
+ export function parseMessageArgs(argv: readonly string[]): MessageArgs | null {
9
+ const action = argv[0];
10
+ if (action !== "sessions" && action !== "send") return null;
11
+ const seen = new Set<string>();
12
+ const values: Record<string, string> = {};
13
+ for (let index = 1; index < argv.length; index++) {
14
+ const flag = argv[index]!;
15
+ if (seen.has(flag)) return null;
16
+ seen.add(flag);
17
+ if (flag === "--json" || (action === "send" && flag === "--stdin")) continue;
18
+ if (action !== "send" || !["--thread", "--name", "--kind", "--in-reply-to"].includes(flag)) return null;
19
+ const value = argv[++index];
20
+ if (!value || value.startsWith("--")) return null;
21
+ values[flag] = value;
22
+ }
23
+ const json = seen.has("--json");
24
+ if (action === "sessions") return { action, json };
25
+ const thread = values["--thread"], name = values["--name"], inReplyTo = values["--in-reply-to"];
26
+ const kind = values["--kind"] ?? "request";
27
+ if (!seen.has("--stdin") || (thread !== undefined) === (name !== undefined)
28
+ || (thread !== undefined && !isThreadId(thread))
29
+ || (name !== undefined && (!name.trim() || name.length > 4096 || /[\x00-\x1f\x7f-\x9f]/.test(name)))
30
+ || !["request", "response", "notification"].includes(kind)
31
+ || (kind === "response" ? !isThreadId(inReplyTo) : inReplyTo !== undefined)) return null;
32
+ return { action, json, kind: kind as MessageKind, ...(thread ? { thread } : {}),
33
+ ...(name ? { name } : {}), ...(inReplyTo ? { inReplyTo } : {}) };
34
+ }
@@ -0,0 +1,43 @@
1
+ import { MessageBudget } from "../messaging/budget";
2
+ import { readMessageInput } from "../messaging/input";
3
+ import { localSessions, messageFailure, sendLocalMessage } from "../messaging/send";
4
+ import { parseMessageArgs } from "./message-args";
5
+ import { terminalSafeText } from "./runtime-api";
6
+
7
+ /** Run one local messaging operation after pure syntax validation, then release owned resources. */
8
+ export async function runMessageCommand(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): Promise<number> {
9
+ const args = parseMessageArgs(argv);
10
+ if (!args) {
11
+ console.error("Usage: ocx message sessions [--json] | send (--thread <uuid> | --name <exact-name>) --stdin [--kind request|response|notification] [--in-reply-to <uuid>] [--json]");
12
+ return 64;
13
+ }
14
+ const controller = new AbortController();
15
+ const cancel = () => controller.abort();
16
+ process.once("SIGINT", cancel); process.once("SIGTERM", cancel);
17
+ const budget = new MessageBudget(30_000, controller.signal);
18
+ try {
19
+ // Keep home selection on this command path and after syntax validation.
20
+ const { messageCodexHome } = await import("./message-runtime");
21
+ const home = messageCodexHome(env);
22
+ if (args.action === "sessions") {
23
+ const sessions = await localSessions(home, budget);
24
+ if (args.json) console.log(JSON.stringify({ schema: "ocx-message-sessions/1", sessions }));
25
+ else for (const session of sessions) console.log(terminalSafeText(`${session.id}\t${session.status}\t${session.name ?? "(unnamed)"}`));
26
+ return 0;
27
+ }
28
+ const body = await readMessageInput(Bun.stdin.stream(), budget);
29
+ const receipt = await sendLocalMessage({ ...args, body },
30
+ { home, senderId: env.CODEX_THREAD_ID }, budget);
31
+ if (args.json) console.log(JSON.stringify(receipt));
32
+ else console.log(terminalSafeText(`${receipt.status}: ${receipt.messageId}${receipt.error ? ` — ${receipt.error.message}` : " (submitted, not proof of processing)"}`));
33
+ return receipt.status === "queued" ? 0 : receipt.status === "unknown" ? 3 : 1;
34
+ } catch (error) {
35
+ const failure = { schema: "ocx-message-error/1", ...(args.action === "send" ? { status: "not_sent" } : {}), error: messageFailure(error) };
36
+ if (args.json) console.log(JSON.stringify(failure));
37
+ else console.error(failure.error.message);
38
+ return 1;
39
+ } finally {
40
+ budget.dispose();
41
+ process.removeListener("SIGINT", cancel); process.removeListener("SIGTERM", cancel);
42
+ }
43
+ }
@@ -0,0 +1,6 @@
1
+ import { resolveCodexHomeDir } from "../codex/home";
2
+
3
+ /** Resolve the effective existing Codex home using the shared policy, without creating it. */
4
+ export function messageCodexHome(env: NodeJS.ProcessEnv): string {
5
+ return resolveCodexHomeDir({ env });
6
+ }
@@ -129,7 +129,9 @@ async function explain(argv: string[], deps: RuntimeApiDeps): Promise<void> {
129
129
  rejectArgs(args, USAGE);
130
130
  const encoded = encodeURIComponent(requestId);
131
131
  const result = await runtimeRequest(`/api/request-history/${encoded}/route-decision`, {}, deps);
132
- printData(result, wantsJson, wantsJson ? undefined : [JSON.stringify(result, null, 2)]);
132
+ // One entry per line: printData escapes control characters per entry, so a single
133
+ // pretty-printed string would print its line breaks as literal `\x0a`.
134
+ printData(result, wantsJson, wantsJson ? undefined : JSON.stringify(result, null, 2).split("\n"));
133
135
  }
134
136
 
135
137
  async function rebuildIndex(argv: string[], deps: RuntimeApiDeps): Promise<void> {
@@ -8,11 +8,12 @@
8
8
  * show <name> Show provider config details (secrets masked)
9
9
  * set-default <name> Change the default provider
10
10
  */
11
- import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate } from "../config";
11
+ import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate, withConfigMutationLockSync } from "../config";
12
12
  import { apiKeyTransportConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../config/provider-validation";
13
13
  import { hasHelpFlag, printSubcommandUsage } from "./help";
14
14
  import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
15
15
  import { providerConfigSeed } from "../providers/derive";
16
+ import { assertAnthropicInstanceLoginConfig } from "../oauth/store-anthropic-instance";
16
17
  import { dropProviderCustomModels } from "../providers/provider-id-rewrite";
17
18
  import type { OcxProviderConfig } from "../types";
18
19
  import { findLiveProxy } from "../server/proxy-liveness";
@@ -249,6 +250,17 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
249
250
 
250
251
  if (responsesPath !== undefined) provConfig.responsesPath = responsesPath;
251
252
  if (authMode !== undefined) provConfig.authMode = authMode as OcxProviderConfig["authMode"];
253
+ if (name === "anthropic2") {
254
+ if (provConfig.adapter !== "anthropic" || provConfig.authMode !== "oauth") delete provConfig.anthropicOAuthInstance;
255
+ if (provConfig.anthropicOAuthInstance) {
256
+ try { assertAnthropicInstanceLoginConfig(config, name); }
257
+ catch {
258
+ console.error("Error: cannot add Pool 2 over an existing custom or unreadable provider configuration; resolve it first.");
259
+ process.exitCode = 2;
260
+ return;
261
+ }
262
+ }
263
+ }
252
264
  if (name === "openai" && (authMode !== undefined || responsesPath !== undefined)
253
265
  && !isCanonicalOpenAiForwardProvider(provConfig)) {
254
266
  console.error("Error: Canonical OpenAI must keep its built-in forward destination and authentication. Use a separate provider name for a custom endpoint.");
@@ -315,11 +327,22 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
315
327
  return;
316
328
  }
317
329
  const { initializeProviderModelSelection } = await import("../providers/initial-model-selection");
318
- initializeProviderModelSelection(name, provConfig, existingProvider, config);
319
- config.providers[name] = provConfig;
320
- if (setDefault) config.defaultProvider = name;
321
-
322
- validateAndSave(config);
330
+ let markerCollision = false;
331
+ withConfigMutationLockSync(() => {
332
+ if (provConfig.anthropicOAuthInstance) {
333
+ try { assertAnthropicInstanceLoginConfig(config, name); }
334
+ catch { markerCollision = true; return; }
335
+ }
336
+ initializeProviderModelSelection(name, provConfig, existingProvider, config);
337
+ config.providers[name] = provConfig;
338
+ if (setDefault) config.defaultProvider = name;
339
+ validateAndSave(config);
340
+ });
341
+ if (markerCollision) {
342
+ console.error("Error: provider configuration changed while adding Pool 2; resolve the ownership collision first.");
343
+ process.exitCode = 2;
344
+ return;
345
+ }
323
346
 
324
347
  let sync: LocalSyncResult | undefined;
325
348
  if (wantsSync) {
@@ -8,6 +8,12 @@ export interface CliCommandEntry {
8
8
  }
9
9
 
10
10
  export const CLI_COMMANDS: CliCommandEntry[] = [
11
+ {
12
+ name: "message",
13
+ usage: "ocx message <sessions|send> [options]",
14
+ summary: "Discover loaded local Codex sessions and submit one queued peer message.",
15
+ details: ["Local Unix transport only; starts no daemon. queued is submission, not processing; never replay an unknown send."],
16
+ },
11
17
  {
12
18
  name: "chatgpt",
13
19
  usage: "ocx chatgpt <launch|restore|status>",
@@ -467,8 +473,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
467
473
  },
468
474
  {
469
475
  name: "export",
470
- usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
471
- summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid) wired to the running proxy.",
476
+ usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
477
+ summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Command Code, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid) wired to the running proxy.",
472
478
  details: [
473
479
  "--json prints the generated document as JSON on stdout; use --out for the client's native format.",
474
480
  "--out <path> writes the native config there and refuses to replace an existing file without --force.",
@@ -598,6 +604,22 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
598
604
  "Select OpenCodex Proxy/<provider>/<model> from ZCode's model picker.",
599
605
  ],
600
606
  },
607
+ {
608
+ name: "commandcode",
609
+ usage: "ocx commandcode [status|show|list|enable|disable|history|journal|restore --op <opId>] [--json]",
610
+ summary: "Connect Command Code CLI to the proxy via its managed provider.",
611
+ details: [
612
+ "Alias of ocx integration client <sub> --client commandcode.",
613
+ "enable manages the opencodex block under the existing provider or providers root in ~/.commandcode/providers.json; disable removes only that block.",
614
+ "Command Code reads providers on startup.",
615
+ "`restore --op <opId>` replays the exact previous managed block; take the id from `ocx commandcode history`.",
616
+ ],
617
+ },
618
+ {
619
+ name: "cmd",
620
+ usage: "ocx cmd [status|show|list|enable|disable|history|journal|restore --op <opId>] [--json]",
621
+ summary: "Alias of ocx commandcode.",
622
+ },
601
623
  {
602
624
  name: "restart",
603
625
  usage: "ocx restart",
@@ -59,6 +59,7 @@ import {
59
59
  } from "../service/ownership-compatibility";
60
60
  import { observeManagingClis } from "../service/managing-cli";
61
61
  import { SERVICE_OWNERSHIP_MINIMUM_CLI_VERSION } from "../service/install-state-contract.mjs";
62
+ import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
62
63
  import { computeVersionSkew, type VersionSkew } from "./version-skew";
63
64
 
64
65
  /** Wire version of the resolve document. Bump only on an incompatible shape change. */
@@ -123,6 +124,12 @@ export interface ResolveJson {
123
124
  * takeover. Additive within ocx-resolve/1; older shells ignore it and read as unknown.
124
125
  */
125
126
  versionSkew?: VersionSkew;
127
+ supervisor?: {
128
+ kind: "desktop" | "none" | "unknown" | "unsupported";
129
+ supervisorPid?: number;
130
+ runtimePid?: number;
131
+ app?: string;
132
+ };
126
133
  }
127
134
 
128
135
  export type ResolveTakeover =
@@ -160,6 +167,7 @@ export interface ResolveIo {
160
167
  observeManagers?: (
161
168
  state: ServiceInstallState | null,
162
169
  ) => Readonly<Record<ManagingCliRole, ManagingCliObservation>>;
170
+ inspectSupervision?: typeof inspectDesktopSupervision;
163
171
  stdout?: { log: (s: string) => void };
164
172
  stderr?: { error: (s: string) => void };
165
173
  }
@@ -187,6 +195,7 @@ export function buildResolveJson(
187
195
  cliVersion: string,
188
196
  ownership: ServiceOwnershipResolution,
189
197
  takeover: ResolveTakeover,
198
+ supervision?: ReturnType<typeof inspectDesktopSupervision>,
190
199
  ): ResolveJson {
191
200
  const configured = config.port ?? RESOLVE_DEFAULT_PORT;
192
201
  return {
@@ -199,6 +208,9 @@ export function buildResolveJson(
199
208
  source: live ? live.source : "config",
200
209
  },
201
210
  liveness: livenessJson(live),
211
+ ...(live && supervision ? { supervisor: supervision.kind === "desktop"
212
+ ? { kind: supervision.kind, supervisorPid: supervision.supervisorPid, runtimePid: supervision.runtimePid, app: supervision.app }
213
+ : { kind: supervision.kind } } : {}),
202
214
  ownership,
203
215
  takeover,
204
216
  // A proven absence has nothing to skew against; the field is omitted there rather
@@ -216,7 +228,8 @@ function reportHuman(json: ResolveJson, stdout: { log: (s: string) => void }): v
216
228
  if (live.status === "live") {
217
229
  const pidText = live.pid === null ? "unknown" : String(live.pid);
218
230
  const versionText = live.version ?? "unknown version";
219
- stdout.log(`Proxy live on port ${json.port.effective} (PID ${pidText}, ${versionText}); effective port ${json.port.effective}.`);
231
+ const supervisor = json.supervisor?.kind === "desktop" ? `; supervisor: desktop (pid ${json.supervisor.supervisorPid})` : "";
232
+ stdout.log(`Proxy live on port ${json.port.effective} (PID ${pidText}, ${versionText}); effective port ${json.port.effective}${supervisor}.`);
220
233
  } else {
221
234
  stdout.log(`No live proxy (absence proven); effective port ${json.port.effective} (configured).`);
222
235
  }
@@ -353,7 +366,17 @@ export async function runResolve(args: ResolveArgs, io: ResolveIo = {}): Promise
353
366
  };
354
367
  }
355
368
  }
356
- const json = buildResolveJson(diagnostics.config, live, configHome, cliVersion(), ownership, takeover);
369
+ let supervision: ReturnType<typeof inspectDesktopSupervision> | undefined;
370
+ if (live) {
371
+ try {
372
+ supervision = live.pid === null ? { kind: "unknown", reason: "pid-unavailable", desktopSeen: false }
373
+ : (io.inspectSupervision ?? inspectDesktopSupervision)({ targetPid: live.pid });
374
+ } catch {
375
+ // Supervision is independent evidence; its failure cannot invalidate established liveness.
376
+ supervision = { kind: "unknown", reason: "probe-failed", desktopSeen: false };
377
+ }
378
+ }
379
+ const json = buildResolveJson(diagnostics.config, live, configHome, cliVersion(), ownership, takeover, supervision);
357
380
  if (args.json) stdout.log(JSON.stringify(json));
358
381
  else reportHuman(json, stdout);
359
382
  return 0;
package/src/cli/root.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * CLI head: version/help early exits, `ocx ready` pre-parse, and the bounded
3
- * Codex-shim auto-restore preflight, in that order (Phase 1 of the CLI
2
+ * CLI head: version/help early exits, ready/resolve pre-parse, lifecycle version
3
+ * notice, then bounded Codex-shim auto-restore preflight (Phase 1 of the CLI
4
4
  * deepening — moved out of src/cli/index.ts).
5
5
  *
6
6
  * `parseCliHead` is pure (no I/O, no process access) so the ordering and the
@@ -16,6 +16,7 @@ import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
16
16
  import { findCommand } from "./registry";
17
17
  import { printUnknownCommand } from "./help-recovery";
18
18
  import { noteCredentialArgv, redactSecretArgs } from "./secret-args";
19
+ import { maybeNoticeVersionSkew } from "./version-skew-notice";
19
20
 
20
21
  export interface CliHead {
21
22
  kind: "version" | "help" | "ready" | "resolve" | "command";
@@ -141,6 +142,7 @@ export async function runCli(argv: string[]): Promise<CliHead> {
141
142
  console.error(uninstallError);
142
143
  process.exit(2);
143
144
  }
145
+ await maybeNoticeVersionSkew(head.command, head.args);
144
146
  maybeAutoRestoreCodexShim(head.command, head.args);
145
147
  return head;
146
148
  }
package/src/cli/status.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { collectCliPathDiagnostics, formatCliStatusHealthLabel, type CliPathDiagnostics } from "./cli-path-diagnostics";
1
2
  import type { CodexMainAccountPolicyHealth } from "../oauth/health";
2
3
  import { durableBunRuntime } from "../lib/bun-runtime";
3
4
  import { existsSync, readFileSync } from "node:fs";
@@ -7,6 +8,7 @@ import { diagnoseCodexBundledPlugins, type CodexPluginsDiagnostic } from "../cod
7
8
  import { findLiveProxy, probeHostname } from "../server/proxy-liveness";
8
9
  import type { OcxConfig } from "../types";
9
10
  import { diagnoseService, serviceLogPath } from "../service";
11
+ import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
10
12
  import { collectStartupHealth, startupHealthReadBudgetMs, type StartupHealth } from "../codex/autostart-health";
11
13
  import { getCodexRoutingKind } from "../codex/inject";
12
14
  import { missingOwnedCatalogPath } from "../codex/inject/config-toml";
@@ -215,6 +217,8 @@ export type CliStatusJson = {
215
217
  * ignores the key is unaffected, and `proxyVersion` is null when nothing is live.
216
218
  */
217
219
  versionSkew: VersionSkew;
220
+ cliCommand: CliPathDiagnostics;
221
+ startupSource: "live" | "local" | "local-supervision-override";
218
222
  };
219
223
 
220
224
  export type CliStatusView = {
@@ -259,12 +263,28 @@ export async function fetchLiveStartupHealth(
259
263
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
260
264
  const row = payload as Record<string, unknown>;
261
265
  if (row.status !== "native" && row.status !== "protected" && row.status !== "at-risk") return null;
262
- if (row.protection !== "service" && row.protection !== "shim" && row.protection !== "none") return null;
266
+ if (row.protection !== "service" && row.protection !== "desktop" && row.protection !== "shim" && row.protection !== "none") return null;
263
267
  if (row.routingKind !== "native" && row.routingKind !== "opencodex-local"
264
268
  && row.routingKind !== "custom-local" && row.routingKind !== "custom-remote" && row.routingKind !== "unknown") return null;
265
269
  if (row.shimCoverage !== "full" && row.shimCoverage !== "cli-only" && row.shimCoverage !== "none") return null;
266
270
  if (typeof row.platform !== "string") return null;
267
271
  if (row.recommendedCommand !== null && typeof row.recommendedCommand !== "string") return null;
272
+ if (row.recommendedAction !== undefined && row.recommendedAction !== null
273
+ && (typeof row.recommendedAction !== "string" || row.recommendedAction.length > 512)) return null;
274
+ if (row.desktop !== undefined) {
275
+ if (!row.desktop || typeof row.desktop !== "object" || Array.isArray(row.desktop)) return null;
276
+ const desktop = row.desktop as Record<string, unknown>;
277
+ for (const key of ["owned", "loginEnabled", "running", "viable"] as const) {
278
+ if (typeof desktop[key] !== "boolean") return null;
279
+ }
280
+ if (desktop.supervisor !== undefined) {
281
+ if (!desktop.supervisor || typeof desktop.supervisor !== "object" || Array.isArray(desktop.supervisor)) return null;
282
+ const supervisor = desktop.supervisor as Record<string, unknown>;
283
+ if (typeof supervisor.supervisorPid !== "number" || !Number.isFinite(supervisor.supervisorPid)
284
+ || typeof supervisor.runtimePid !== "number" || !Number.isFinite(supervisor.runtimePid)
285
+ || typeof supervisor.app !== "string") return null;
286
+ }
287
+ }
268
288
  if (!row.commands || typeof row.commands !== "object" || Array.isArray(row.commands)) return null;
269
289
  for (const key of ["installService", "repairService", "installShim", "restoreNative"] as const) {
270
290
  if (typeof (row.commands as Record<string, unknown>)[key] !== "string") return null;
@@ -286,12 +306,36 @@ export async function fetchLiveStartupHealth(
286
306
  return payload as StartupHealth;
287
307
  }
288
308
 
289
- /** Prefer an attested live verdict and evaluate the local fallback only when live state is absent. */
309
+ /** Older runtimes cannot report supervision; only that missing capability permits a local override. */
290
310
  export function selectStatusStartupHealth(
291
311
  liveStartup: StartupHealth | null,
292
312
  fallback: () => StartupHealth,
293
- ): StartupHealth {
294
- return liveStartup ?? fallback();
313
+ supervision?: () => ReturnType<typeof inspectDesktopSupervision>,
314
+ livePid?: number | null,
315
+ ): { startup: StartupHealth; startupSource: CliStatusJson["startupSource"] } {
316
+ if (!liveStartup) return { startup: fallback(), startupSource: "local" };
317
+ if (liveStartup.desktop === undefined && supervision) {
318
+ let evidence: ReturnType<typeof inspectDesktopSupervision> | undefined;
319
+ try { evidence = supervision(); }
320
+ catch { /* Failed optional evidence cannot replace an attested live verdict. */ }
321
+ if (evidence?.kind === "desktop" && evidence.runtimePid === livePid) {
322
+ const local = fallback();
323
+ const supervisor = local.desktop?.supervisor;
324
+ // The local probe runs later; it must still describe the same unowned process pair.
325
+ if (local.desktop?.owned === false && supervisor
326
+ && supervisor.runtimePid === evidence.runtimePid && supervisor.supervisorPid === evidence.supervisorPid) {
327
+ return { startup: local, startupSource: "local-supervision-override" };
328
+ }
329
+ }
330
+ }
331
+ return { startup: liveStartup, startupSource: "live" };
332
+ }
333
+
334
+ export function runtimeSupervisorLine(startup: StartupHealth): string | null {
335
+ const supervisor = startup.desktop?.supervisor;
336
+ return supervisor
337
+ ? `Runtime supervisor: OpenCodex Desktop (pid ${supervisor.supervisorPid}, ${supervisor.app}); durable owner: ${startup.desktop?.owned ? "desktop" : "none"}`
338
+ : null;
295
339
  }
296
340
 
297
341
  /** Build the service summary from the same startup source that `ocx status` selected. */
@@ -299,18 +343,23 @@ export function statusServiceSummary(
299
343
  liveStartup: StartupHealth | null,
300
344
  service: Pick<ReturnType<typeof diagnoseService>, "installed" | "summary">,
301
345
  live: boolean,
346
+ // Which verdict was selected. A live proxy can still be described by the local reading when
347
+ // the runtime predates supervision reporting, so the label follows the verdict, not liveness.
348
+ startupSource?: CliStatusJson["startupSource"],
302
349
  ): string {
303
- if (liveStartup) {
350
+ if (live && liveStartup) {
351
+ const supervisor = liveStartup.desktop?.supervisor ? "OpenCodex Desktop supervises the running proxy; " : "";
304
352
  if (liveStartup.protection === "service" && liveStartup.serviceViable) {
305
- return `running under the live managed service (logs: ${serviceLogPath()})`;
353
+ return `${supervisor}running under the live managed service (logs: ${serviceLogPath()})`;
306
354
  }
307
355
  const state = [
308
356
  liveStartup.serviceInstalled ? "installed" : "absent",
309
357
  liveStartup.serviceRunning ? "running" : "not running",
310
358
  liveStartup.serviceViable ? "viable" : "not viable",
311
359
  ].join(", ");
312
- const action = liveStartup.recommendedCommand ? `; run '${liveStartup.recommendedCommand}'` : "";
313
- return `live startup reports service ${state}${action} (logs: ${serviceLogPath()})`;
360
+ const action = !liveStartup.desktop?.supervisor && liveStartup.recommendedCommand ? `; run '${liveStartup.recommendedCommand}'` : "";
361
+ const label = (startupSource ?? (live ? "live" : "local")) === "live" ? "live" : "local";
362
+ return `${supervisor}${label} startup reports service ${state}${action} (logs: ${serviceLogPath()})`;
314
363
  }
315
364
  return service.installed && !live
316
365
  ? `${service.summary} — registered but NOT serving; see ${serviceLogPath()} and re-run 'ocx service repair'`
@@ -680,6 +729,7 @@ export function missingCodexCatalogLines(missingCatalogPath: string | null): str
680
729
 
681
730
  /** `mainAccountPolicy`: only `--json` asks; the human report reads the same accounts once via OAuth health. */
682
731
  export async function collectStatus(options: { mainAccountPolicy?: boolean } = {}): Promise<CliStatusView> {
732
+ const cliCommand = collectCliPathDiagnostics();
683
733
  const configDiagnostics = readConfigDiagnostics();
684
734
  const config = configDiagnostics.config;
685
735
  const claudeDesktop = {
@@ -757,14 +807,14 @@ export async function collectStatus(options: { mainAccountPolicy?: boolean } = {
757
807
  // A service can be registered and still not serve: the manager reports the job
758
808
  // either way. When the identity-probed live proxy provides an attested startup verdict,
759
809
  // prefer it over a shell-local service-manager probe that lacks the service environment.
760
- const serviceSummary = statusServiceSummary(liveStartup, service, Boolean(live));
761
810
  const codexShim = diagnoseCodexShim();
762
811
  const codexShimSummary = codexShim.summary;
763
- const startup = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(config, {
812
+ const { startup, startupSource } = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(config, {
764
813
  service,
765
814
  shim: codexShim,
766
815
  routingKind: getCodexRoutingKind(),
767
- }));
816
+ }), live?.pid != null ? () => inspectDesktopSupervision({ targetPid: live.pid! }) : undefined, live?.pid);
817
+ const serviceSummary = statusServiceSummary(startup, service, Boolean(live), startupSource);
768
818
  const codexPlugins = diagnoseCodexBundledPlugins();
769
819
  const lastClamp = loadLastEffortClamp();
770
820
  const clampActive = effortClampAppliesToRuntime(lastClamp, resolvedRuntime.runtime);
@@ -869,7 +919,7 @@ export async function collectStatus(options: { mainAccountPolicy?: boolean } = {
869
919
 
870
920
  return {
871
921
  proxyLabel,
872
- healthLabel: health.label,
922
+ healthLabel: formatCliStatusHealthLabel(health.label, cliCommand, remoteHub.connected),
873
923
  json: {
874
924
  ...(mainAccountHardLock ? { mainAccountHardLock } : {}),
875
925
  schemaVersion: 1,
@@ -932,6 +982,8 @@ export async function collectStatus(options: { mainAccountPolicy?: boolean } = {
932
982
  // fact about this install, not about the Codex runtime, and filing it there would
933
983
  // print it under the wrong heading (#2701).
934
984
  versionSkew,
985
+ cliCommand,
986
+ startupSource,
935
987
  },
936
988
  };
937
989
  }
@@ -16,7 +16,8 @@ function parseMarker(raw: string): UpdateRestartChildMarker {
16
16
  if (!value || !Number.isSafeInteger(value.deadlineAt) || !Number.isInteger(value.port)
17
17
  || typeof value.hostname !== "string" || value.port < 1 || value.port > 65535 || typeof value.version !== "string"
18
18
  || value.version === "0.0.0" || !parseStrictSemver(value.version) || !value.home
19
- || !Number.isSafeInteger(value.home.revision)) throw new Error("update_restart_child_marker_invalid");
19
+ || !Number.isSafeInteger(value.home.revision) || value.home.serviceRecord?.schema !== 1
20
+ || typeof value.home.serviceRecord.digest !== "string" || !/^[a-f0-9]{64}$/.test(value.home.serviceRecord.digest)) throw new Error("update_restart_child_marker_invalid");
20
21
  for (const directory of [value.home.config, value.home.codex]) {
21
22
  // Windows reports 64-bit NTFS file ids (MFT sequence in the high 16 bits) as doubles above
22
23
  // 2^53. Parent and child read them through the same stat API, so any finite integer is a
@@ -56,7 +57,7 @@ export function admitUpdateRestartChild(argv: string[], io: UpdateRestartChildIo
56
57
  if (now() >= marker.deadlineAt) throw new Error("update_restart_deadline_expired");
57
58
  const bindHost = (host: string) => !host.trim() || /^localhost\.?$/i.test(host.trim()) ? "127.0.0.1" : host.trim();
58
59
  if (port !== marker.port || bindHost(hostname) !== bindHost(marker.hostname) || (io.version ?? packageVersion)() !== marker.version) throw new Error("update_restart_child_identity_changed");
59
- (io.checkHome ?? assertUpdateRestartHome)(marker.home);
60
+ (io.checkHome ?? (home => assertUpdateRestartHome(home, marker.deadlineAt)))(marker.home);
60
61
  (io.checkState ?? (expected => {
61
62
  assertUpdateRestartConfiguration(expected.hostname);
62
63
  const current = readRuntimePort();
@@ -65,6 +66,7 @@ export function admitUpdateRestartChild(argv: string[], io: UpdateRestartChildIo
65
66
  throw new Error("update_restart_competing_runtime");
66
67
  }
67
68
  }))(marker);
69
+ if (now() >= marker.deadlineAt) throw new Error("update_restart_deadline_expired");
68
70
  };
69
71
  check();
70
72
  const lease = (io.acquire ?? (waitMs => acquireOwnershipMutationLease(serviceStatePaths(), { waitMs })))(Math.min(2000, marker.deadlineAt - now()));