@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -26,6 +26,7 @@ export const FILE_INTEGRATION_CLIENTS = [
26
26
  "commandcode",
27
27
  "kilo",
28
28
  "droid",
29
+ "qoder",
29
30
  ] as const;
30
31
 
31
32
  export const INTEGRATION_REFUSAL_REASONS: ReadonlySet<string> = new Set<IntegrationRefusalReason>([
@@ -65,6 +66,7 @@ const PLAN_SCHEMA_PATHS = new Set([
65
66
  "settings.providers.opencodex",
66
67
  "catalog.providers.opencodex",
67
68
  "customModels.*",
69
+ "modelConfigs.customModels.*",
68
70
  // ZCode reads its providers from a second file; a plan for it publishes that
69
71
  // file's templates, and a path missing here is rejected as an invalid preview.
70
72
  "config.providerConfigRules.providerRules.[providerId=opencodex]",
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Management commands on a Remote Link Child (#6957).
3
+ *
4
+ * A link Child's listener serves no management API, and the Home owns every setting that decides
5
+ * routing. Each management command therefore does one of two things: an allowlisted read goes
6
+ * to the Home through the connection-bound link relay, and anything else is refused with the one
7
+ * message `linkChildManagementRefusal` builds, which names the Home and the link.
8
+ */
9
+ import { readBoundedResponseBytes } from "../lib/bounded-body";
10
+ import { readServiceApiTokenState } from "../lib/service-secrets";
11
+ import { linkChildManagementRefusal, linkManagementReadAllowed } from "../link/management-routes";
12
+ import { readClientConnectionState } from "../client/state";
13
+ import { fetchLinkHomeManagement, linkHomePhrase, type LinkHomeDeps } from "../client/link-home";
14
+ import type { OcxClientConnectionConfig } from "../types";
15
+
16
+ const RESPONSE_MAX_BYTES = 32 * 1024 * 1024;
17
+
18
+ export interface LinkChildContext {
19
+ connection: OcxClientConnectionConfig;
20
+ linkId: string;
21
+ homePhrase: string;
22
+ }
23
+
24
+ /** The link Child this CLI runs on, or null on a standalone, a hub or a hub client. */
25
+ export function readLinkChildContext(read: typeof readClientConnectionState = readClientConnectionState): LinkChildContext | null {
26
+ let state: ReturnType<typeof readClientConnectionState>;
27
+ try { state = read(); } catch { return null; }
28
+ if (state.kind !== "connected" || state.value.transport !== "link" || !state.value.link) return null;
29
+ return { connection: state.value, linkId: state.value.link.linkId, homePhrase: linkHomePhrase(state.value) };
30
+ }
31
+
32
+ export function linkChildRefusal(context: LinkChildContext, operation?: string): string {
33
+ return linkChildManagementRefusal(context.homePhrase, context.linkId, operation);
34
+ }
35
+
36
+ /**
37
+ * The stderr label for a local read on a link Child (`ocx provider list`, `ocx config get`):
38
+ * the values are real, but they are this machine's and the Home's apply.
39
+ */
40
+ export function linkChildLocalNotice(what: string): string | null {
41
+ const link = readLinkChildContext();
42
+ if (!link) return null;
43
+ return `Note: this machine is a Remote Link Child managed by ${link.homePhrase} (link ${link.linkId}). `
44
+ + `${what} below are this machine's local configuration. Its provider, model and routing settings are inactive while it is linked; the Home's apply. Run 'ocx status' for the Home's state.`;
45
+ }
46
+
47
+ export async function printLinkChildLocalNotice(what: string): Promise<void> {
48
+ const notice = linkChildLocalNotice(what);
49
+ if (notice) console.error(notice);
50
+ }
51
+
52
+ /** `ocx config set|unset|import` on a link Child: the write is local and its routing parts inactive. */
53
+ export async function printLinkChildLocalWriteNotice(): Promise<void> {
54
+ const link = readLinkChildContext();
55
+ if (!link) return;
56
+ console.error(`Note: this machine is a Remote Link Child managed by ${link.homePhrase} (link ${link.linkId}). `
57
+ + "This writes this machine's local configuration; its provider, model and routing settings stay inactive while it is linked, and the Home's apply.");
58
+ }
59
+
60
+ /** The refusal for a local provider change on a link Child, or null elsewhere. */
61
+ export function linkChildProviderChangeRefusal(operation: string): string | null {
62
+ const link = readLinkChildContext();
63
+ return link ? linkChildRefusal(link, operation) : null;
64
+ }
65
+
66
+ /** Can this management request be answered by reading the Home? */
67
+ export function linkChildReadable(method: string, path: string): boolean {
68
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, "http://cli.invalid");
69
+ return linkManagementReadAllowed(method, url.pathname, url.searchParams);
70
+ }
71
+
72
+ /** One allowlisted read from the Home. Throws with an operator sentence when it cannot be sent. */
73
+ export async function readLinkHomeJson(
74
+ context: LinkChildContext,
75
+ path: string,
76
+ deps: LinkHomeDeps = {},
77
+ ): Promise<{ status: number; body: unknown }> {
78
+ const token = readServiceApiTokenState();
79
+ if (token.kind !== "present" || token.fingerprint !== context.connection.tokenFingerprint) {
80
+ throw new Error(`The enrolled link key for ${context.homePhrase} is missing or changed; re-link this machine from the Home.`);
81
+ }
82
+ let response: Response;
83
+ try {
84
+ response = await fetchLinkHomeManagement(context.connection, token.token, path.startsWith("/") ? path : `/${path}`, {}, deps);
85
+ } catch (error) {
86
+ const detail = error instanceof Error && error.name === "LinkRelayAuthenticationError" ? error.message : "the link tunnel did not answer";
87
+ throw new Error(`Could not read ${context.homePhrase} over Remote Link (${context.linkId}): ${detail}.`);
88
+ }
89
+ const result = await readBoundedResponseBytes(response, { maxBytes: RESPONSE_MAX_BYTES, signal: AbortSignal.timeout(30_000) });
90
+ if (result.oversized) throw new Error(`${context.homePhrase} returned a response larger than the CLI accepts.`);
91
+ const text = new TextDecoder().decode(result.bytes);
92
+ let body: unknown = null;
93
+ if (text) {
94
+ try { body = JSON.parse(text); } catch { body = text; }
95
+ }
96
+ // A Home that predates the relay answers its listener's generic unknown-endpoint 404.
97
+ const error = body && typeof body === "object" ? (body as Record<string, unknown>).error : undefined;
98
+ const unknownEndpoint = error === "not_found"
99
+ || (!!error && typeof error === "object" && (error as Record<string, unknown>).code === "not_found");
100
+ if (response.status === 404 && unknownEndpoint) {
101
+ return { status: 503, body: { error: `${context.homePhrase} does not serve Remote Link management reads; upgrade the Home.` } };
102
+ }
103
+ return { status: response.status, body };
104
+ }
@@ -0,0 +1,96 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { assertSshAlias } from "../link/ssh-argv";
3
+ import { CliUsageError, RuntimeApiError, runtimeBaseUrl, rejectArgs, takeJsonFlag, takeOption, printData } from "./runtime-api";
4
+ import type { LinkCliDeps } from "./link";
5
+
6
+ const FINGERPRINT = /^SHA256:[A-Za-z0-9+/]{16,88}={0,2}$/;
7
+ const ENROLLMENT_DEADLINE_MS = 360_000;
8
+ const PHASES: Record<string, string> = {
9
+ remote_port_failed: "remote port selection", remote_port_timeout: "remote port selection",
10
+ child_preflight_failed: "Child preflight", child_preflight_timeout: "Child preflight",
11
+ child_restart_unsupported: "Child restart preflight", child_curl_missing: "Child curl preflight",
12
+ home_setup_failed: "Home setup", home_setup_timeout: "Home setup",
13
+ remote_connect_failed: "Child enrollment", remote_connect_timeout: "Child enrollment",
14
+ child_restart_failed: "Child restart", child_restart_timeout: "Child restart",
15
+ child_relay_failed: "Child relay verification", child_relay_timeout: "Child relay verification",
16
+ child_runtime_not_client: "Child runtime role verification",
17
+ remote_platform_unsupported: "remote platform verification", remote_ocx_missing: "remote OpenCodex discovery",
18
+ };
19
+ const RECOVERY = "Run ocx disconnect and ocx restart on the Child before retrying.";
20
+ type LinkRequest = <T>(path: string, init: RequestInit, deps: LinkCliDeps) => Promise<T>;
21
+
22
+ function record(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
23
+ function offered(value: unknown): { alias: string; fingerprint: string; keyType: string; ocxVersion: string } {
24
+ if (!record(value) || Object.keys(value).sort().join(",") !== "alias,fingerprint,keyType,ocxVersion"
25
+ || typeof value.alias !== "string" || typeof value.fingerprint !== "string" || !FINGERPRINT.test(value.fingerprint)
26
+ || typeof value.keyType !== "string" || !/^[A-Za-z0-9@._+-]{1,64}$/.test(value.keyType)) throw new Error("Invalid host probe response");
27
+ if (typeof value.ocxVersion !== "string" || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]{1,128})?$/.test(value.ocxVersion)) throw new Error("Invalid host version response");
28
+ return { alias: value.alias, fingerprint: value.fingerprint, keyType: value.keyType, ocxVersion: value.ocxVersion };
29
+ }
30
+
31
+ export async function runLinkEnrollment(command: "probe" | "add", args: string[], deps: LinkCliDeps, request: LinkRequest, usage: string): Promise<void> {
32
+ const wantsJson = takeJsonFlag(args);
33
+ const expected = takeOption(args, "--expect-fingerprint");
34
+ const alias = args.shift();
35
+ if (!alias) throw new CliUsageError(command + " requires an SSH alias", usage);
36
+ try { assertSshAlias(alias); } catch { throw new CliUsageError("Invalid SSH alias", usage); }
37
+ rejectArgs(args, usage);
38
+ if (command === "probe" && expected !== undefined) throw new CliUsageError("--expect-fingerprint is only for link add", usage);
39
+ if (expected !== undefined && !FINGERPRINT.test(expected)) throw new CliUsageError("Expected a SHA256 SSH fingerprint", usage);
40
+ if (command === "add" && !expected && !(deps.stdinImpl ?? process.stdin).isTTY) {
41
+ throw new CliUsageError("Non-interactive link add requires --expect-fingerprint", usage);
42
+ }
43
+ const timeout = deps.enrollmentTimeoutMs ?? ENROLLMENT_DEADLINE_MS;
44
+ if (!Number.isFinite(timeout) || timeout < 1 || timeout > ENROLLMENT_DEADLINE_MS) throw new Error("Invalid link enrollment deadline");
45
+ const controller = new AbortController();
46
+ let timer: ReturnType<typeof setTimeout> | undefined;
47
+ const expiry = new Promise<never>((_, reject) => {
48
+ timer = setTimeout(() => { controller.abort(); reject(new Error("Link enrollment timed out; its outcome is uncertain. Run ocx link status on the Home and ocx connect status on the Child before retrying.")); }, timeout);
49
+ });
50
+ let boundDeps = deps;
51
+ const post = async <T>(path: string, body: unknown): Promise<T> => {
52
+ controller.signal.throwIfAborted();
53
+ const result = await request<T>(path, { method: "POST", redirect: "error", signal: controller.signal, headers: { "content-type": "application/json" }, body: JSON.stringify(body) }, boundDeps);
54
+ controller.signal.throwIfAborted();
55
+ return result;
56
+ };
57
+ let rl: ReturnType<typeof createInterface> | undefined;
58
+ const work = async () => {
59
+ boundDeps = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
60
+ controller.signal.throwIfAborted();
61
+ const probe = offered(await post<unknown>("/api/link/probe", { alias, includeVersion: true }));
62
+ if (probe.alias !== alias) throw new Error("Host probe alias did not match");
63
+ if (command === "probe") { printData(probe, wantsJson, [probe.alias + " " + probe.keyType + " " + probe.fingerprint + " opencodex " + probe.ocxVersion]); return; }
64
+ if (expected && expected !== probe.fingerprint) throw new CliUsageError("The offered host fingerprint does not match --expect-fingerprint");
65
+ if (!expected) {
66
+ let approved: boolean;
67
+ if (deps.confirmFingerprint) approved = await deps.confirmFingerprint(probe);
68
+ else {
69
+ rl = createInterface({ input: deps.stdinImpl ?? process.stdin, output: process.stderr });
70
+ approved = (await rl.question("Confirm " + alias + " " + probe.keyType + " " + probe.fingerprint + " opencodex " + probe.ocxVersion + " (type yes): ", { signal: controller.signal })).trim() === "yes";
71
+ }
72
+ controller.signal.throwIfAborted();
73
+ if (!approved) throw new CliUsageError("Host fingerprint was not confirmed");
74
+ }
75
+ const confirm = await post<unknown>("/api/link/confirm-host", { alias, fingerprint: probe.fingerprint });
76
+ if (!record(confirm) || Object.keys(confirm).sort().join(",") !== "alias,fingerprint,ocxVersion" || confirm.alias !== alias || confirm.fingerprint !== probe.fingerprint
77
+ || typeof confirm.ocxVersion !== "string" || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]{1,128})?$/.test(confirm.ocxVersion)) throw new Error("Invalid host confirmation response");
78
+ const result = await post<unknown>("/api/link/apply", { alias, fingerprint: probe.fingerprint });
79
+ if (!record(result) || Object.keys(result).join(",") !== "linkId" || typeof result.linkId !== "string" || !/^lnk_[0-9a-f]{16}$/.test(result.linkId)) throw new Error("Invalid link apply response");
80
+ printData({ linkId: result.linkId, alias, state: "connected", childRole: "client" }, wantsJson, ["Connected " + alias + " as a Child (" + result.linkId + ")."]);
81
+ };
82
+ try { await Promise.race([work(), expiry]); }
83
+ catch (error) {
84
+ if (error instanceof RuntimeApiError && record(error.body)) {
85
+ const code = record(error.body.error) ? error.body.error.code : undefined;
86
+ const phase = typeof code === "string" && Object.hasOwn(PHASES, code) ? PHASES[code] : undefined;
87
+ const message = phase ? "Remote Link failed during " + phase : error.message;
88
+ if (phase || error.body.residual === true) throw new Error(message + (error.body.residual === true ? ". " + RECOVERY : ""));
89
+ }
90
+ throw error;
91
+ } finally {
92
+ if (timer) clearTimeout(timer);
93
+ controller.abort();
94
+ rl?.close();
95
+ }
96
+ }
package/src/cli/link.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { clientLinkStatusIdentity } from "../client/link-status";
2
+ import { clientLinkTunnelStatus } from "../client/link-tunnel";
3
+ import { loadConfig } from "../config";
4
+ import { projectLinkStatus, type ClientLinkIdentity, type LinkStatusConfig, type LinkStatusDto } from "../link/status-projection";
5
+ import { runLinkEnrollment } from "./link-enrollment";
1
6
  import { assertSshAlias } from "../link/ssh-argv";
2
7
  import { linkStorePath } from "../link/paths";
3
8
  import { isLinkPort } from "../link/ports";
@@ -19,10 +24,13 @@ import {
19
24
  } from "./runtime-api";
20
25
 
21
26
  export const LINK_USAGE = `Usage:
27
+ ocx link probe <alias> [--json]
28
+ ocx link add <alias> [--expect-fingerprint SHA256:…] [--json]
22
29
  ocx link port [--json]
23
30
  ocx link issue --alias <alias> --tunnel-port <port> [--json]
24
31
  ocx link status [--json]
25
32
  ocx link revoke --link-id <id> [--force --yes] [--json]
33
+ ocx link reconnect --link-id <id> [--json]
26
34
 
27
35
  Forced revoke skips remote cleanup. Run ocx disconnect on the remote client.`;
28
36
 
@@ -57,7 +65,13 @@ export interface LinkIssuePayload {
57
65
  }
58
66
 
59
67
  export interface LinkCliDeps extends RuntimeApiDeps {
68
+ /** Test seam can shorten, never extend, the whole enrollment deadline. */
69
+ enrollmentTimeoutMs?: number;
60
70
  choosePort?: () => Promise<number>;
71
+ readConfig?: () => LinkStatusConfig;
72
+ readClientLinkIdentity?: (linkId: string) => ClientLinkIdentity | null;
73
+ readChildStatus?: () => LinkStatusDto["child"];
74
+ confirmFingerprint?: (host: { alias: string; fingerprint: string; keyType: string }) => Promise<boolean>;
61
75
  readStore?: () => LinkStore;
62
76
  readAdminToken?: () => string | null;
63
77
  }
@@ -172,25 +186,12 @@ function validateRevoke(value: unknown, expectedLinkId: string): void {
172
186
  if (value.linkId !== expectedLinkId) throw new Error("invalid link API response: revoke link id");
173
187
  }
174
188
 
175
- function localStatus(store: LinkStore): LinkStatusPayload {
176
- const hasLinks = store.links.length > 0;
177
- return {
178
- role: hasLinks ? "home" : "standalone",
179
- listener: {
180
- state: hasLinks && store.listenerPort !== null ? "listening" : "off",
181
- port: hasLinks ? store.listenerPort : null,
182
- },
183
- links: store.links.map(link => ({
184
- id: link.id,
185
- alias: link.alias,
186
- direction: link.direction,
187
- state: "idle",
188
- since: link.createdAt,
189
- reason: null,
190
- tunnelPort: link.tunnelPort,
191
- })),
192
- child: null,
193
- };
189
+ function localStatus(store: LinkStore, deps: LinkCliDeps): LinkStatusPayload {
190
+ const config = (deps.readConfig ?? loadConfig)();
191
+ const identity = config.client?.transport === "link" && config.client.link
192
+ ? (deps.readClientLinkIdentity ?? clientLinkStatusIdentity)(config.client.link.linkId) : null;
193
+ return projectLinkStatus(store, [], { state: store.links.length > 0 && store.listenerPort !== null ? "listening" : "off", port: store.links.length > 0 ? store.listenerPort : null },
194
+ config, { version: 1, entries: {} }, (deps.readChildStatus ?? clientLinkTunnelStatus)(), identity);
194
195
  }
195
196
 
196
197
  async function linkRequest<T>(path: string, init: RequestInit, deps: LinkCliDeps): Promise<T> {
@@ -211,10 +212,10 @@ async function linkRequest<T>(path: string, init: RequestInit, deps: LinkCliDeps
211
212
  }
212
213
  }
213
214
 
214
- function errorCodeBody(body: unknown): { error: { code: string } } | null {
215
+ function errorCodeBody(body: unknown): { error: { code: string }; residual?: true } | null {
215
216
  if (!isRecord(body) || !isRecord(body.error)) return null;
216
217
  const code = body.error.code;
217
- return typeof code === "string" && /^[a-z_]{1,64}$/.test(code) ? { error: { code } } : null;
218
+ return typeof code === "string" && /^[a-z_]{1,64}$/.test(code) ? { error: { code }, ...(body.residual === true ? { residual: true as const } : {}) } : null;
218
219
  }
219
220
 
220
221
  async function runPort(args: string[], deps: LinkCliDeps): Promise<void> {
@@ -252,7 +253,7 @@ async function runStatus(args: string[], deps: LinkCliDeps): Promise<void> {
252
253
  } catch (error) {
253
254
  if (!deps.baseUrl && error instanceof RuntimeApiError && error.status === 503) {
254
255
  const store = (deps.readStore ?? (() => readLinkStore(linkStorePath())))();
255
- console.log(JSON.stringify(localStatus(store)));
256
+ console.log(JSON.stringify(localStatus(store, deps)));
256
257
  return;
257
258
  }
258
259
  throw error;
@@ -303,14 +304,28 @@ async function runRevoke(args: string[], deps: LinkCliDeps): Promise<void> {
303
304
  console.log(JSON.stringify({ linkId }));
304
305
  }
305
306
 
307
+ async function runReconnect(args: string[], deps: LinkCliDeps): Promise<void> {
308
+ takeJsonFlag(args);
309
+ const linkId = takeOption(args, "--link-id");
310
+ if (!linkId || !LINK_ID.test(linkId)) throw new CliUsageError("reconnect requires a valid --link-id", LINK_USAGE);
311
+ rejectArgs(args, LINK_USAGE);
312
+ const response = await linkRequest<unknown>(`/api/link/${encodeURIComponent(linkId)}/reconnect`, { method: "POST" }, deps);
313
+ if (!isRecord(response)) throw new Error("invalid link API response: reconnect object");
314
+ assertExactKeys(response, ["linkId"], "reconnect");
315
+ if (response.linkId !== linkId) throw new Error("invalid link API response: reconnect link id");
316
+ console.log(JSON.stringify({ linkId }));
317
+ }
318
+
306
319
  export async function runLinkCommand(rawArgs: string[], deps: LinkCliDeps = {}): Promise<number> {
307
320
  return runCliAction(async () => {
308
321
  const args = [...rawArgs];
309
322
  const command = args.shift();
310
- if (command === "port") await runPort(args, deps);
323
+ if (command === "probe" || command === "add") await runLinkEnrollment(command, args, deps, linkRequest, LINK_USAGE);
324
+ else if (command === "port") await runPort(args, deps);
311
325
  else if (command === "issue") await runIssue(args, deps);
312
326
  else if (command === "status") await runStatus(args, deps);
313
327
  else if (command === "revoke") await runRevoke(args, deps);
314
- else throw new CliUsageError("link requires one of: port, issue, status, revoke", LINK_USAGE);
328
+ else if (command === "reconnect") await runReconnect(args, deps);
329
+ else throw new CliUsageError("link requires one of: probe, add, port, issue, status, revoke, reconnect", LINK_USAGE);
315
330
  });
316
331
  }
@@ -89,7 +89,12 @@ export async function withObserveStream(
89
89
  // Snapshot recovery comes from our observed target, never arbitrary transport text.
90
90
  if (context.kind === "snapshot") {
91
91
  if (!found) throw new ObservationError("Proxy is not running. Start it with: ocx start");
92
- if (found.role === "client") throw new ObservationError("This listener runs in the client role and has no management API. Run this command on its serving hub, or open the hub dashboard.");
92
+ if (found.role === "client") {
93
+ const { readLinkChildContext, linkChildRefusal } = await import("./link-child");
94
+ const link = readLinkChildContext();
95
+ throw new ObservationError(link ? linkChildRefusal(link)
96
+ : "This listener runs in the client role and has no management API. Run this command on its serving hub, or open the hub dashboard.");
97
+ }
93
98
  }
94
99
  return found;
95
100
  } }), signal);
@@ -113,8 +113,12 @@ async function logs(argv: string[], deps: ObserveStreamDeps): Promise<number> {
113
113
  return followLogs({ query: new URLSearchParams(query(params)), limit, jsonl: wantsJsonl, events: wantsEvents, formatRow: formatLog }, deps);
114
114
  }
115
115
  const data = await runtimeRequest(`/api/logs${query(params)}`, {}, deps);
116
+ const rows = logRows(data);
117
+ if (!wantsJson && !wantsJsonl && rows.length === 0 && [provider, model, status, conversationId, account].some(Boolean)) {
118
+ console.log("No log entry matched the filters.");
119
+ }
116
120
  if (wantsJson) printData(data, true);
117
- else for (const row of logRows(data)) {
121
+ else for (const row of rows) {
118
122
  if (wantsJsonl) console.log(JSON.stringify(row));
119
123
  else console.log(formatLog(row));
120
124
  }
@@ -206,8 +210,12 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
206
210
  if (token.kind !== "present" || token.fingerprint !== connection.value.tokenFingerprint) {
207
211
  throw new Error("Client usage unavailable: the enrolled data key is missing or changed; repair the client connection");
208
212
  }
213
+ // A Remote Link Child sends this read through the connection-bound link relay (#6957).
214
+ const link = connection.value.transport === "link" && connection.value.link;
215
+ const peerFetch = deps.fetchImpl
216
+ ?? (link ? (await import("../client/link-home")).linkBoundFetch(connection.value, token.token) : undefined);
209
217
  result = await fetchHubUsage(connection.value.serverUrl, token.token, new URLSearchParams(suffix), {
210
- fetchImpl: deps.fetchImpl, timeoutMs: 60_000,
218
+ fetchImpl: peerFetch, timeoutMs: 60_000, ...(link ? { peer: "Home" as const } : {}),
211
219
  });
212
220
  const current = readClientConnectionState();
213
221
  const currentToken = readServiceApiTokenState();
@@ -584,8 +584,19 @@ export async function handleProviderCommand(args: string[], deps: ProviderComman
584
584
  return;
585
585
  }
586
586
 
587
+ // #6957: a link Child's local providers are inactive; changing them is refused like any other
588
+ // provider change, which belongs to the Home.
589
+ if (sub === "add" || sub === "remove" || sub === "set-default") {
590
+ const refusal = (await import("./link-child")).linkChildProviderChangeRefusal(`ocx provider ${sub}`);
591
+ if (refusal) {
592
+ console.error(refusal);
593
+ process.exitCode = 1;
594
+ return;
595
+ }
596
+ }
587
597
  switch (sub) {
588
598
  case "list":
599
+ await (await import("./link-child")).printLinkChildLocalNotice("The providers");
589
600
  handleList(subArgs);
590
601
  break;
591
602
  case "add":
@@ -595,6 +606,7 @@ export async function handleProviderCommand(args: string[], deps: ProviderComman
595
606
  handleRemove(subArgs);
596
607
  break;
597
608
  case "show":
609
+ await (await import("./link-child")).printLinkChildLocalNotice("The provider settings");
598
610
  handleShow(subArgs);
599
611
  break;
600
612
  case "set-default":
@@ -144,14 +144,15 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
144
144
  },
145
145
  {
146
146
  name: "link",
147
- usage: "ocx link <port|issue|status|revoke>",
147
+ usage: "ocx link <port|issue|status|revoke|reconnect>",
148
148
  summary: "Allocate and manage a loopback remote home link.",
149
149
  details: [
150
150
  "Port: ocx link port [--json]",
151
151
  "Issue: ocx link issue --alias <alias> --tunnel-port <port> [--json]",
152
152
  "Status: ocx link status [--json]",
153
153
  "Revoke: ocx link revoke --link-id <id> [--force --yes] [--json]",
154
- "Issue, status, and revoke use the running proxy's loopback management API and admin token.",
154
+ "Reconnect: ocx link reconnect --link-id <id> [--json]",
155
+ "Issue, status, revoke, and reconnect use the running proxy's loopback management API and admin token.",
155
156
  ],
156
157
  },
157
158
  {
@@ -245,8 +246,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
245
246
  },
246
247
  {
247
248
  name: "hub",
248
- usage: "ocx hub invite [--json] [--data-url <origin>] [--management-url <origin>] [--clients codex,claude]",
249
- summary: "Hub-side commands. `invite` prints a ready-to-run `ocx connect` line for one more machine.",
249
+ usage: "ocx hub invite [--json] [--data-url <origin>] [--management-url <origin>] [--clients codex,claude]\n ocx hub disable [--revoke-clients --client-key <id> ...] [--json]",
250
+ summary: "Invite another machine, or disable hub mode in one validated config write.",
250
251
  details: [
251
252
  "Topology: a hub serves ONE port. Remote machines dial `hostname:port` with their own per-client",
252
253
  "key; the hub's own local processes dial `127.0.0.1:<same port>` with no credential, through the",
@@ -377,6 +378,18 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
377
378
  usage: "ocx model <subcommand>",
378
379
  summary: "Alias of ocx models.",
379
380
  },
381
+ {
382
+ name: "advisor",
383
+ usage: "ocx advisor <status|on|off|consent|set> ...",
384
+ summary: "Inspect and configure the advisor sidecar (expert consultation for routed workers).",
385
+ details: [
386
+ "ocx advisor and ocx advisor status read the resolved settings; use --json for machine-readable output.",
387
+ "ocx advisor on requires current context-sharing consent. Pass --ack-context-sharing to record consent v1 and enable. ocx advisor off disables the sidecar without granting consent.",
388
+ "ocx advisor consent records consent v1 after printing the disclosure. ocx advisor consent --revoke removes it and stops task-context transfer.",
389
+ "ocx advisor set updates --model, --effort, --policy and --timeout-ms; the model may be any routable model string (bare native, provider/model, or account-qualified). set does not grant consent.",
390
+ "policy manual consults only when the worker calls the synthetic advisor tool; policy preflight also attempts one automatic consultation per task with a stable conversation identity, once the task shows orientation evidence. Without a stable identity, each eligible request may trigger another consultation. Both require current consent before any task context is sent.",
391
+ ],
392
+ },
380
393
  {
381
394
  name: "companion",
382
395
  usage: "ocx companion <show|set|reset|usage|timeline> ...",
@@ -468,13 +481,13 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
468
481
  "protocols [--provider <name>] Contract version, API surfaces, protocol settings and feature vocabulary.",
469
482
  "explain --model <id> --inbound <responses|chat|messages> [--feature <key>]... Preview the request path; sends nothing upstream.",
470
483
  "policy Read the protocol policy; with --messages, --unrepresentable or --rollout <switch>=<on|off> it changes config.",
471
- "Every rollout switch defaults off. `ocx api policy` writes only when a setting flag is given.",
484
+ "Every rollout switch defaults off except antigravityDirect. `ocx api policy` writes only when a setting flag is given.",
472
485
  ],
473
486
  },
474
487
  {
475
488
  name: "export",
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.",
489
+ usage: "ocx export --client <qoder|opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
490
+ 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, Qoder) wired to the running proxy.",
478
491
  details: [
479
492
  "--json prints the generated document as JSON on stdout; use --out for the client's native format.",
480
493
  "--out <path> writes the native config there and refuses to replace an existing file without --force.",
@@ -173,6 +173,9 @@ export async function handleDesktopAppRestart(
173
173
  + "Quit and relaunch the desktop app manually to refresh the model picker.",
174
174
  );
175
175
  return result;
176
+ case "relaunch_context_failed":
177
+ log.error("Codex desktop launch options could not be preserved safely, so the app was not stopped. Review its current launch options before retrying.");
178
+ return result;
176
179
  case "no_targets":
177
180
  log.log("Codex desktop app is not running; nothing to restart.");
178
181
  return result;
@@ -189,4 +192,3 @@ export async function handleDesktopAppRestart(
189
192
  return result;
190
193
  }
191
194
  }
192
-
@@ -12,6 +12,7 @@
12
12
  import { findLiveProxy, probeHostname, type LivenessIo, type LiveProxy } from "../server/proxy-liveness";
13
13
  import { runningProxyUpdateHeaders } from "../oauth/login-cli";
14
14
  import { redactSecretArgs } from "./secret-args";
15
+ import type { LinkHomeDeps } from "../client/link-home";
15
16
  export { redactSecretArgs };
16
17
 
17
18
  export type CliStdin = NodeJS.ReadableStream & { isTTY?: boolean; readableEnded?: boolean };
@@ -28,6 +29,8 @@ export interface RuntimeApiDeps {
28
29
  stdinSignal?: AbortSignal;
29
30
  /** Optional proxy liveness probe injection for commands that check or fall back around live runtime state. */
30
31
  findLiveProxy?: (io?: LivenessIo) => Promise<LiveProxy | null>;
32
+ /** Remote Link Child: how allowlisted reads reach the Home (#6957). */
33
+ linkHome?: LinkHomeDeps;
31
34
  }
32
35
 
33
36
  export class CliUsageError extends Error {
@@ -45,7 +48,7 @@ export class RuntimeApiError extends Error {
45
48
  readonly status: number,
46
49
  readonly body: unknown,
47
50
  /** Local discovery refusals occur before the management request is sent. */
48
- readonly code?: "proxy_not_running" | "client_role_management_unavailable",
51
+ readonly code?: "proxy_not_running" | "client_role_management_unavailable" | "link_child_management_unavailable",
49
52
  ) {
50
53
  super(message);
51
54
  this.name = "RuntimeApiError";
@@ -83,6 +86,10 @@ export async function runtimeBaseUrl(deps: RuntimeApiDeps = {}): Promise<string>
83
86
  // this costs no extra request. Only the client role is refused: an absent role is a
84
87
  // standalone or hub proxy (or a legacy body that predates the field), and both serve /api/*.
85
88
  if (live.role === "client") {
89
+ // A Remote Link Child has no relay transport to point at; it names its Home instead (#6957).
90
+ const { readLinkChildContext, linkChildRefusal } = await import("./link-child");
91
+ const link = readLinkChildContext();
92
+ if (link) throw new RuntimeApiError(linkChildRefusal(link), 503, null, "link_child_management_unavailable");
86
93
  throw new RuntimeApiError(clientRoleManagementRefusal(live.port), 503, null, "client_role_management_unavailable");
87
94
  }
88
95
  return `http://${probeHostname(live.hostname)}:${live.port}`;
@@ -180,7 +187,25 @@ export async function runtimeRequest<T = unknown>(
180
187
  init: RequestInit = {},
181
188
  deps: RuntimeApiDeps = {},
182
189
  ): Promise<T> {
183
- const baseUrl = await runtimeBaseUrl(deps);
190
+ let baseUrl: string;
191
+ try {
192
+ baseUrl = await runtimeBaseUrl(deps);
193
+ } catch (error) {
194
+ if (!(error instanceof RuntimeApiError) || error.code !== "link_child_management_unavailable") throw error;
195
+ // #6957: on a Remote Link Child an allowlisted read is answered by the Home over the link;
196
+ // every other request is refused with the operation named, before anything is sent.
197
+ const method = (init.method ?? "GET").toUpperCase();
198
+ const { readLinkChildContext, linkChildReadable, linkChildRefusal, readLinkHomeJson } = await import("./link-child");
199
+ const link = readLinkChildContext();
200
+ if (!link) throw error;
201
+ const pathname = new URL(path.startsWith("/") ? path : `/${path}`, "http://cli.invalid").pathname;
202
+ if (!linkChildReadable(method, path)) {
203
+ throw new RuntimeApiError(linkChildRefusal(link, `${method} ${pathname}`), 503, null, "link_child_management_unavailable");
204
+ }
205
+ const { status, body } = await readLinkHomeJson(link, path, deps.linkHome);
206
+ if (status < 200 || status >= 300) throw new RuntimeApiError(responseMessage(body, status), status, body);
207
+ return body as T;
208
+ }
184
209
  const headers = runningProxyUpdateHeaders();
185
210
  for (const [key, value] of new Headers(init.headers).entries()) headers.set(key, value);
186
211
  const fetchImpl = deps.fetchImpl ?? fetch;