@bitkyc08/opencodex 2.84.0 → 2.87.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 (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -1,3 +1,4 @@
1
+ import { anthropicProgressEnvelope, isAnthropicProgressMirror } from "../responses/anthropic-progress";
1
2
  /**
2
3
  * Claude Code outbound: internal /v1/responses output -> Anthropic Messages API shapes.
3
4
  *
@@ -11,7 +12,7 @@
11
12
  * - errors: {type:"error", error:{type,message}}; may arrive mid-stream after HTTP 200.
12
13
  */
13
14
  import { createHash } from "node:crypto";
14
- import { httpStatusFromTerminalError } from "../lib/errors";
15
+ import { httpStatusFromTerminalError, UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
15
16
  import { isTransientUpstreamStatus } from "../lib/upstream-retry";
16
17
  import {
17
18
  isTranslatorBudgetExceededError,
@@ -81,12 +82,16 @@ export function anthropicErrorType(status: number): string {
81
82
  * (devlog/_plan/261009_claude_1m_default/010).
82
83
  */
83
84
  const CLAUDE_PROMPT_TOO_LONG_RE = /prompt is too long|input is too long for requested model/i;
84
- const STATED_CONTEXT_COUNTS_RE = /maximum context length is (\d+) tokens[\s\S]*?(?:resulted in|you requested) (\d+) tokens/i;
85
+ const MAX_OVERFLOW_MESSAGE_CHARS = 64 * 1024;
86
+ const STATED_CONTEXT_LIMIT_RE = /maximum context length is (\d+) tokens/i;
87
+ const STATED_REQUEST_COUNT_RE = /(?:resulted in|you requested) (\d+) tokens/i;
85
88
 
86
89
  export function claudePromptTooLongMessage(message: string): string {
90
+ message = message.slice(0, MAX_OVERFLOW_MESSAGE_CHARS);
87
91
  if (CLAUDE_PROMPT_TOO_LONG_RE.test(message)) return message;
88
- const counts = STATED_CONTEXT_COUNTS_RE.exec(message);
89
- if (counts) return `prompt is too long: ${counts[2]} tokens > ${counts[1]} maximum`;
92
+ const limit = STATED_CONTEXT_LIMIT_RE.exec(message);
93
+ const requested = limit && STATED_REQUEST_COUNT_RE.exec(message.slice(limit.index + limit[0].length));
94
+ if (limit && requested) return `prompt is too long: ${requested[1]} tokens > ${limit[1]} maximum`;
90
95
  return `prompt is too long: ${message}`;
91
96
  }
92
97
 
@@ -218,10 +223,20 @@ export function sanitizeWebSearchInput(input: unknown): Rec {
218
223
  /**
219
224
  * Map a Responses `web_search_call` item to its Anthropic pair: the server_tool_use
220
225
  * input (query/queries) and the web_search_tool_result content (hits, or the error
221
- * object when the search failed). Shared by the SSE and JSON translation paths.
226
+ * object when the search failed). An item carrying `fetch_result` is Anthropic's
227
+ * server-side web_fetch and maps to web_fetch + its web_fetch_tool_result, verbatim.
228
+ * Shared by the SSE and JSON translation paths.
222
229
  */
223
- export function webSearchPairFromItem(item: Rec): { id: string; input: Rec; resultContent: unknown; completed: boolean } {
230
+ export function webSearchPairFromItem(item: Rec): {
231
+ id: string; name: "web_search" | "web_fetch"; resultType: string; input: Rec; resultContent: unknown; completed: boolean;
232
+ } {
224
233
  const action = isRec(item.action) ? item.action : {};
234
+ const id = typeof item.id === "string" && item.id.length > 0 ? item.id : `srvtoolu_${uuid()}`;
235
+ const completed = item.status !== "failed";
236
+ if (item.fetch_result !== undefined) {
237
+ const input = { url: typeof action.url === "string" ? action.url : "" };
238
+ return { id, name: "web_fetch", resultType: "web_fetch_tool_result", input, resultContent: item.fetch_result, completed };
239
+ }
225
240
  const queries = Array.isArray(action.queries)
226
241
  ? action.queries.filter((q): q is string => typeof q === "string" && q.length > 0)
227
242
  : [];
@@ -229,7 +244,6 @@ export function webSearchPairFromItem(item: Rec): { id: string; input: Rec; resu
229
244
  const input = sanitizeWebSearchInput(
230
245
  queries.length > 1 ? { queries } : { query: queries[0] ?? query },
231
246
  );
232
- const completed = item.status !== "failed";
233
247
  let resultContent: unknown;
234
248
  if (completed) {
235
249
  const hits: Rec[] = [];
@@ -244,8 +258,7 @@ export function webSearchPairFromItem(item: Rec): { id: string; input: Rec; resu
244
258
  } else {
245
259
  resultContent = { type: "web_search_tool_result_error", error_code: "unavailable" };
246
260
  }
247
- const id = typeof item.id === "string" && item.id.length > 0 ? item.id : `srvtoolu_${uuid()}`;
248
- return { id, input, resultContent, completed };
261
+ return { id, name: "web_search", resultType: "web_search_tool_result", input, resultContent, completed };
249
262
  }
250
263
 
251
264
  /**
@@ -549,7 +562,13 @@ export function responsesSseToAnthropicSse(
549
562
  terminalDelivered = true;
550
563
  };
551
564
 
565
+ let progressMirrorId: string | undefined;
552
566
  const handleFrame = (eventName: string, data: Rec) => {
567
+ const frameItem = isRec(data.item) ? data.item : null;
568
+ if (progressMirrorId && (data.item_id === progressMirrorId || frameItem?.id === progressMirrorId)) {
569
+ if (eventName === "response.output_item.done") progressMirrorId = undefined;
570
+ return;
571
+ }
553
572
  switch (eventName) {
554
573
  case "response.created":
555
574
  case "response.in_progress": {
@@ -701,7 +720,7 @@ export function responsesSseToAnthropicSse(
701
720
  const toolIndex = blockIndex++;
702
721
  emit("content_block_start", {
703
722
  type: "content_block_start", index: toolIndex,
704
- content_block: { type: "server_tool_use", id: pair.id, name: "web_search" },
723
+ content_block: { type: "server_tool_use", id: pair.id, name: pair.name },
705
724
  });
706
725
  emit("content_block_delta", {
707
726
  type: "content_block_delta", index: toolIndex,
@@ -711,10 +730,10 @@ export function responsesSseToAnthropicSse(
711
730
  const resultIndex = blockIndex++;
712
731
  emit("content_block_start", {
713
732
  type: "content_block_start", index: resultIndex,
714
- content_block: { type: "web_search_tool_result", tool_use_id: pair.id, content: pair.resultContent },
733
+ content_block: { type: pair.resultType, tool_use_id: pair.id, content: pair.resultContent },
715
734
  });
716
735
  emit("content_block_stop", { type: "content_block_stop", index: resultIndex });
717
- if (pair.completed) webSearchRequests++;
736
+ if (pair.completed && pair.name === "web_search") webSearchRequests++;
718
737
  break;
719
738
  }
720
739
  // Close the matching open block (message/reasoning items close implicitly on
@@ -768,6 +787,15 @@ export function responsesSseToAnthropicSse(
768
787
  ensureBlock("thinking");
769
788
  }
770
789
  if (open?.kind === "thinking") {
790
+ if (env?.upd && typeof env.txt === "string") {
791
+ // This plaintext was deliberately surfaced as commentary, unlike hidden reasoning.
792
+ progressMirrorId = env.upd;
793
+ const bytes = Buffer.byteLength(env.txt);
794
+ translatorBudget.chargeRetained(bytes, { kind: "reasoning" });
795
+ translatorBudget.releaseRetained(open.thinkingBufBytes ?? 0, { kind: "reasoning" });
796
+ open.thinkingBuf = env.txt;
797
+ open.thinkingBufBytes = bytes;
798
+ }
771
799
  if (env?.sig) open.reasoningSig = env.sig;
772
800
  if (native) open.reasoningNative = typeof item.id === "string" && item.id.length > 0 ? { enc: native, id: item.id } : { enc: native };
773
801
  closeOpenBlock();
@@ -804,11 +832,14 @@ export function responsesSseToAnthropicSse(
804
832
  // Unclassified status-absent response.failed (relaySseWithFailedTail synthetic
805
833
  // tail) still lands on a transient 5xx here — the mid-stream reset shape maps to
806
834
  // overloaded_error by design.
835
+ // The undeclared-tool refusal is deterministic and proxy-owned, not an upstream
836
+ // condition: keep its api_error type and code instead of a retryable overload.
837
+ const deterministic = code === UNDECLARED_TOOL_CALL_ERROR_CODE;
807
838
  fail(
808
839
  status,
809
840
  message,
810
- true,
811
- code === "context_length_exceeded" ? code : undefined,
841
+ !deterministic,
842
+ deterministic || code === "context_length_exceeded" ? code : undefined,
812
843
  );
813
844
  break;
814
845
  }
@@ -969,7 +1000,8 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
969
1000
  let sawToolUse = false;
970
1001
  let webSearchRequests = 0;
971
1002
 
972
- for (const raw of output) {
1003
+ for (const [index, raw] of output.entries()) {
1004
+ if (index > 0 && isAnthropicProgressMirror(raw, anthropicProgressEnvelope(output[index - 1], translatorBudget))) continue;
973
1005
  if (!isRec(raw)) continue;
974
1006
  switch (raw.type) {
975
1007
  case "message": {
@@ -1003,7 +1035,7 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
1003
1035
  const native = nativeEncryptedReasoning(encrypted);
1004
1036
  const tag = native && nativeReasoningTagFor?.(native);
1005
1037
  if (parts.length > 0 || env?.sig || tag) {
1006
- const text = parts.join("\n\n");
1038
+ const text = env?.upd ? env.txt! : parts.join("\n\n");
1007
1039
  const nat = tag ? { enc: native, model, tag, ...(typeof raw.id === "string" && raw.id.length > 0 ? { id: raw.id } : {}) } : undefined;
1008
1040
  content.push({ type: "thinking", thinking: text, signature: env?.sig ?? encodeReasoningEnvelope({ txt: text, ...(nat ? { nat } : {}) }, translatorBudget) });
1009
1041
  }
@@ -1027,9 +1059,9 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string, tr
1027
1059
  case "web_search_call": {
1028
1060
  // Server-side search: emit the Anthropic pair. Does NOT set sawToolUse.
1029
1061
  const pair = webSearchPairFromItem(raw);
1030
- content.push({ type: "server_tool_use", id: pair.id, name: "web_search", input: pair.input });
1031
- content.push({ type: "web_search_tool_result", tool_use_id: pair.id, content: pair.resultContent });
1032
- if (pair.completed) webSearchRequests++;
1062
+ content.push({ type: "server_tool_use", id: pair.id, name: pair.name, input: pair.input });
1063
+ content.push({ type: pair.resultType, tool_use_id: pair.id, content: pair.resultContent });
1064
+ if (pair.completed && pair.name === "web_search") webSearchRequests++;
1033
1065
  break;
1034
1066
  }
1035
1067
  default:
@@ -306,6 +306,7 @@ export interface ProviderQuotaWindowDto {
306
306
  label: string;
307
307
  percent: number;
308
308
  resetAt?: number;
309
+ modelId?: string;
309
310
  }
310
311
 
311
312
  export interface ProviderQuotaDto extends CodexQuotaDto {
@@ -8,6 +8,7 @@ export const threshold = z.number().int().min(0).max(100);
8
8
  const nullableThreshold = threshold.nullable();
9
9
  const row = z.object({
10
10
  id: accountId, alias: text.optional(), autoSwitchThresholdOverride: nullableThreshold.optional(),
11
+ useRemainingQuota: z.boolean().optional(),
11
12
  quota: z.object({ shortWindowSeconds: z.number().finite().optional(), shortResetAt: z.number().finite().optional(),
12
13
  weeklyResetAt: z.number().finite().optional() }).nullable().optional(),
13
14
  quotaAutoRefresh: z.object({ fiveHourAvailable: z.boolean(), weeklyAvailable: z.boolean(),
@@ -39,6 +40,11 @@ export const poolSchema = z.object({
39
40
  export const thresholdReceipt = z.object({ ok: z.literal(true), id: accountId,
40
41
  autoSwitchThresholdOverride: nullableThreshold, autoSwitchThreshold: threshold });
41
42
  export const creditReceipt = z.object({ ok: z.literal(true), id: accountId, creditsAfterLimit: z.boolean() });
43
+ export const remainingQuotaReceipt = z.object({ ok: z.literal(true), id: accountId,
44
+ useRemainingQuota: z.boolean(), autoSwitchThreshold: threshold,
45
+ mainAccountHardLock: z.object({ enabled: z.boolean(), state: z.enum(["off", "unknown", "ready", "blocked"]),
46
+ thresholds: z.object({ short: threshold, long: threshold }) }).optional(),
47
+ });
42
48
  export const creditAllReceipt = z.object({ ok: z.literal(true), all: z.boolean(),
43
49
  ids: z.array(accountId).refine(ids => new Set(ids).size === ids.length) });
44
50
  const window = z.enum(["five_hour", "seven_day", "seven_day_overage_included"]);
@@ -5,14 +5,15 @@ import { resolveCodexAccountTargetFromRows } from "./account-target";
5
5
  import { runCatalogAction } from "./catalog-command-result";
6
6
  import { serializeManagementJson } from "./json-input";
7
7
  import { creditAllReceipt, creditReceipt, grantsSchema, parseDto, poolSchema, record,
8
- rosterSchema, threshold, thresholdReceipt } from "./account-policy-dto";
8
+ rosterSchema, threshold, thresholdReceipt, remainingQuotaReceipt } from "./account-policy-dto";
9
9
  import { CliUsageError, RuntimeApiError, printData, runtimeBaseUrl, runtimeRequest, summaryLines,
10
10
  takeFlag, takeOptionWithSyntax, type RuntimeApiDeps } from "./runtime-api";
11
11
 
12
- type Subcommand = "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants";
12
+ type Subcommand = "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants" | "use-remaining";
13
13
  const USAGE = "Usage: ocx account pool <provider> [--enabled on|off] [--threshold 0-100] [--strategy NAME] [--sticky 1-100] [--quota-window NAME] [--json]\n"
14
14
  + " ocx account auto-switch openai <status|on|off|inherit|threshold N> --account ID [--json]\n"
15
15
  + " ocx account credits openai <ID on|off|--all on|off> [--json]\n"
16
+ + " ocx account use-remaining openai ID <on|off|status> [--json]\n"
16
17
  + " ocx account quota-activation openai ID --window <fiveHour|weekly> <on|off> [--json]\n"
17
18
  + " ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]";
18
19
  const READ_ERRORS = { no_account: "No matching Anthropic OAuth account.", auth_failed: "Sign in to this Anthropic account again.",
@@ -53,6 +54,32 @@ function output(data: Record<string, unknown>, json: boolean, note?: string): nu
53
54
  }
54
55
  function requireEqual(actual: unknown, expected: unknown): void { if (actual !== expected) throw new Error("Unverified account policy result"); }
55
56
 
57
+ async function useRemaining(args: string[], json: boolean, deps: RuntimeApiDeps): Promise<number> {
58
+ if (args.shift() !== "openai") usage();
59
+ const requested = selector(args.shift()), action = args.shift();
60
+ if (!["on", "off", "status"].includes(action ?? "")) usage();
61
+ done(args);
62
+ const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
63
+ const { id } = await target(pinned, requested);
64
+ const path = "/api/codex-auth/accounts/use-remaining";
65
+ const result = parseDto(remainingQuotaReceipt, await request(
66
+ action === "status" ? path + "?id=" + encodeURIComponent(id) : path,
67
+ pinned, action === "status" ? undefined : { id, useRemainingQuota: action === "on" },
68
+ ));
69
+ requireEqual(result.id, id);
70
+ if (action !== "status") requireEqual(result.useRemainingQuota, action === "on");
71
+ if (result.useRemainingQuota) {
72
+ requireEqual(result.autoSwitchThreshold, 0);
73
+ if (id === MAIN_CODEX_ACCOUNT_ID) {
74
+ requireEqual(result.mainAccountHardLock?.thresholds.short, 100);
75
+ requireEqual(result.mainAccountHardLock?.thresholds.long, 100);
76
+ }
77
+ }
78
+ return output(result, json, result.useRemainingQuota
79
+ ? "Use remaining included quota: on. Paid-credit permission is unchanged."
80
+ : "Use remaining included quota: off. Configured usage thresholds apply.");
81
+ }
82
+
56
83
  async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promise<number> {
57
84
  const provider = selector(args.shift()).toLowerCase();
58
85
  if (!isValidProviderName(provider)) usage();
@@ -63,7 +90,8 @@ async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promis
63
90
  if (limit !== undefined) patch.autoSwitchThreshold = integer(limit, 0);
64
91
  if (sticky !== undefined) patch.stickyLimit = integer(sticky, 1);
65
92
  if (strategy !== undefined) {
66
- if (!["quota", "round-robin", "fill-first", ...(provider === "openai" ? ["reset-first"] : []),
93
+ // reset-first is a Codex and generic-pool strategy; the Anthropic pool does not run it.
94
+ if (!["quota", "round-robin", "fill-first", ...(isAnthropicInstanceId(provider) ? [] : ["reset-first"]),
67
95
  ...(provider === "kiro" ? ["least-loaded"] : [])].includes(strategy)) usage();
68
96
  patch.strategy = strategy;
69
97
  }
@@ -122,12 +150,12 @@ async function codex(sub: "auto-switch" | "credits" | "quota-activation", args:
122
150
  const override = selected?.autoSwitchThresholdOverride;
123
151
  if (override === undefined) throw new Error("Missing account threshold evidence");
124
152
  const active = record(await request("/api/codex-auth/active", pinned));
125
- const effective = override ?? parseDto(threshold, active.autoSwitchThreshold);
153
+ const effective = selected?.useRemainingQuota ? 0 : override ?? parseDto(threshold, active.autoSwitchThreshold);
126
154
  return output({ ok: true, id, autoSwitchThresholdOverride: override, autoSwitchThreshold: effective }, json);
127
155
  }
128
156
  const result = parseDto(thresholdReceipt, await request("/api/codex-auth/auto-switch", pinned, { id, threshold: value }));
129
157
  requireEqual(result.id, id); requireEqual(result.autoSwitchThresholdOverride, value);
130
- if (value !== null) requireEqual(result.autoSwitchThreshold, value);
158
+ if (value !== null && !rows.find(row => row.id === id)?.useRemainingQuota) requireEqual(result.autoSwitchThreshold, value);
131
159
  return output(result, json, "Account threshold saved.");
132
160
  }
133
161
  if (sub === "credits") {
@@ -157,6 +185,7 @@ export async function handleAccountPolicyCommand(sub: Subcommand, args: string[]
157
185
  const rest = [...args], json = takeFlag(rest, "--json");
158
186
  if (rest.includes("--json")) usage();
159
187
  if (sub === "pool") return pool(rest, json, deps);
188
+ if (sub === "use-remaining") return useRemaining(rest, json, deps);
160
189
  if (sub !== "anthropic-reset-grants") return codex(sub, rest, json, deps);
161
190
  const selectedProvider = option(rest, "--provider");
162
191
  const provider = selectedProvider ?? "anthropic";
@@ -0,0 +1,44 @@
1
+ import { apiError, apiJson, proxyUnreachable, resolveBaseUrl, type AccountDeps } from "./account-api";
2
+
3
+ /** This operation follows the selected runtime; never read a local account/config roster. */
4
+ export async function cmdPreserveSignedIn(args: string[], deps: AccountDeps): Promise<number> {
5
+ const provider = args.shift();
6
+ let mode = "status";
7
+ let hasMode = false;
8
+ let json = false;
9
+ let applyToBound = false;
10
+ for (const arg of args) {
11
+ if (arg === "--json" && !json) json = true;
12
+ else if (arg === "--apply-to-bound" && !applyToBound) applyToBound = true;
13
+ else if (["on", "off", "status"].includes(arg) && !hasMode) { mode = arg; hasMode = true; }
14
+ else return usage();
15
+ }
16
+ if (provider !== "openai" || (applyToBound && mode !== "on")) return usage();
17
+ const baseUrl = await resolveBaseUrl(deps);
18
+ if (!baseUrl) return proxyUnreachable();
19
+ const result = await apiJson(deps, baseUrl, mode === "status" ? "GET" : "PUT", "/api/codex-auth/preserve-signed-in",
20
+ mode === "status" ? undefined : { enabled: mode === "on", applyToBound });
21
+ if (result.status === 0) return proxyUnreachable(result.transportError);
22
+ if (result.status !== 200) return apiError(result.json, "Signed-in quota policy unavailable", result.status);
23
+ if (typeof result.json.enabled !== "boolean" || !Array.isArray(result.json.affectedAccountIds)
24
+ || !result.json.affectedAccountIds.every(id => typeof id === "string")
25
+ || (result.json.nextAccountId !== null && typeof result.json.nextAccountId !== "string")) {
26
+ return apiError({}, "Invalid signed-in quota policy response", 502);
27
+ }
28
+ if (json) console.log(JSON.stringify(result.json, null, 2));
29
+ else {
30
+ console.log(`Preserve signed-in quota: ${result.json.enabled ? "on" : "off"}`);
31
+ console.log(`Affected pool entries: ${result.json.affectedAccountIds.join(", ")}`);
32
+ console.log(`Next unbound choice: ${result.json.nextAccountId ?? "none"}`);
33
+ console.log(`Bindings: ${result.json.bindings}; manual pin: ${result.json.pinnedAccountId ?? "none"}`);
34
+ if (result.json.priorityFailbackEnabled === true) console.log("Existing priority failback can move bound work when its quota and freshness conditions hold.");
35
+ console.log("Fallback can consume signed-in quota. Exact-account models override this preference.");
36
+ console.log("Use on --apply-to-bound to release affected bindings and pins for the next request.");
37
+ }
38
+ return 0;
39
+ }
40
+
41
+ function usage(): number {
42
+ console.error("Usage: ocx account preserve-signed-in openai [on|off|status] [--apply-to-bound] [--json]");
43
+ return 1;
44
+ }
@@ -54,6 +54,7 @@ const ACCOUNT_USAGE = `Usage:
54
54
  ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
55
55
  ocx account auto-switch <anthropic|anthropic2> <on|off|status|inherit|threshold <0-100>> --account <id> [--json]
56
56
  ocx account alias <provider> <account-or-key-id|alias> <display-name|-> [--json]
57
+ ocx account preserve-signed-in openai [on|off|status] [--apply-to-bound] [--json]
57
58
  ocx account priority <provider> <account-id|alias|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
58
59
  ocx account pause <provider> <account-id|alias|main> [--json]
59
60
  ocx account resume <provider> <account-id|alias|main> [--json]
@@ -69,6 +70,7 @@ const ACCOUNT_USAGE = `Usage:
69
70
  ocx account login <provider> [--id <account-id>] [--reauth] [--open-browser on|off] [--add-account on|off] [--code -] [--no-wait] [--json]
70
71
  ocx account pool <provider> [--enabled on|off] [--threshold N] [--strategy NAME] [--sticky N] [--quota-window W] [--json]
71
72
  ocx account credits openai <ID on|off|--all on|off> [--json]
73
+ ocx account use-remaining openai <ID|main> <on|off|status> [--json]
72
74
  ocx account quota-activation openai ID --window fiveHour|weekly <on|off> [--json]
73
75
  ocx account anthropic-reset-grants [ID] [--provider anthropic|anthropic2] [--json]
74
76
  ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
@@ -459,11 +461,11 @@ async function cmdClear(rest: string[], deps: AccountDeps): Promise<number> {
459
461
  export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promise<number> {
460
462
  const [sub, ...rest] = args;
461
463
  try {
462
- if (["pool", "credits", "quota-activation", "anthropic-reset-grants"].includes(sub ?? "")
464
+ if (["pool", "credits", "quota-activation", "anthropic-reset-grants", "use-remaining"].includes(sub ?? "")
463
465
  || (sub === "auto-switch" && rest[0]?.trim().toLowerCase() === "openai"
464
466
  && rest.some(arg => arg === "--account" || arg.startsWith("--account=")))) {
465
467
  const { handleAccountPolicyCommand } = await import("./account-policy");
466
- return handleAccountPolicyCommand(sub as "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants", rest, deps);
468
+ return handleAccountPolicyCommand(sub as "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants" | "use-remaining", rest, deps);
467
469
  }
468
470
  if (sub === "list") return await cmdList(rest, deps);
469
471
  if (sub === "history") {
@@ -476,6 +478,10 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
476
478
  if (sub === "refresh") return await cmdRefresh(rest, deps);
477
479
  if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps);
478
480
  if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps);
481
+ if (sub === "preserve-signed-in") {
482
+ const { cmdPreserveSignedIn } = await import("./account-preservation");
483
+ return cmdPreserveSignedIn(rest, deps);
484
+ }
479
485
  if (sub === "priority") return await cmdPriority(rest, deps);
480
486
  // #2702: the server routes existed and only the CLI caller was missing, so these were
481
487
  // dashboard-only capabilities.
@@ -0,0 +1,165 @@
1
+ import { ADVISOR_CONTEXT_SHARING_CONSENT_VERSION, ADVISOR_CONTEXT_SHARING_DISCLOSURE } from "../advisor/disclosure";
2
+ import { CliUsageError, printData, rejectArgs, runCliAction, runtimeRequest, takeFlag, type RuntimeApiDeps } from "./runtime-api";
3
+
4
+ const USAGE = `Usage:
5
+ ocx advisor status [--json]
6
+ ocx advisor activity [--json]
7
+ ocx advisor on [--ack-context-sharing] [--json]
8
+ ocx advisor off [--json]
9
+ ocx advisor consent [--revoke] [--json]
10
+ ocx advisor set [--model <model>] [--effort <effort>] [--policy <manual|preflight|adaptive>] [--max-consultations <1-3>] [--timeout-ms <ms>] [--json]`;
11
+
12
+ const VALUED_FLAGS = new Set(["--model", "--effort", "--policy", "--timeout-ms", "--max-consultations"]);
13
+
14
+ interface SetOptions {
15
+ model?: string;
16
+ effort?: string;
17
+ policy?: string;
18
+ timeoutMs?: string;
19
+ maxConsultations?: string;
20
+ }
21
+
22
+ function parseSetArgs(args: string[]): SetOptions {
23
+ const options: SetOptions = {};
24
+ for (let index = 0; index < args.length; index += 1) {
25
+ const arg = args[index]!;
26
+ if (!VALUED_FLAGS.has(arg)) throw new CliUsageError(`unknown advisor set option ${arg}`, USAGE);
27
+ const value = args[++index];
28
+ if (value === undefined || value.startsWith("--")) throw new CliUsageError(`${arg} requires a value`, USAGE);
29
+ // An explicitly supplied empty value is meaningful for --model alone: the settings route
30
+ // treats "" as the supported "clear the model" state. Every other valued flag still needs one.
31
+ if (value === "" && arg !== "--model") throw new CliUsageError(`${arg} requires a value`, USAGE);
32
+ if (arg === "--model") options.model = value;
33
+ else if (arg === "--effort") options.effort = value;
34
+ else if (arg === "--policy") options.policy = value;
35
+ else if (arg === "--max-consultations") options.maxConsultations = value;
36
+ else options.timeoutMs = value;
37
+ }
38
+ if (Object.keys(options).length === 0) {
39
+ throw new CliUsageError("advisor set requires a model, effort, policy, task limit, or timeout option", USAGE);
40
+ }
41
+ return options;
42
+ }
43
+
44
+ async function status(argv: string[], deps: RuntimeApiDeps): Promise<void> {
45
+ const args = [...argv];
46
+ const wantsJson = takeFlag(args, "--json");
47
+ rejectArgs(args, USAGE);
48
+ printData(await runtimeRequest("/api/advisor/settings", {}, deps), wantsJson);
49
+ }
50
+
51
+ function printDisclosure(): void {
52
+ console.error(ADVISOR_CONTEXT_SHARING_DISCLOSURE);
53
+ }
54
+
55
+ async function readConsent(deps: RuntimeApiDeps): Promise<unknown> {
56
+ const current = await runtimeRequest("/api/advisor/settings", {}, deps) as {
57
+ settings?: { contextSharingConsent?: unknown };
58
+ };
59
+ return current.settings?.contextSharingConsent;
60
+ }
61
+
62
+ async function setEnabled(enabled: boolean, argv: string[], deps: RuntimeApiDeps): Promise<void> {
63
+ const args = [...argv];
64
+ const wantsJson = takeFlag(args, "--json");
65
+ const acknowledge = takeFlag(args, "--ack-context-sharing");
66
+ rejectArgs(args, USAGE);
67
+ if (!enabled) {
68
+ if (acknowledge) {
69
+ throw new CliUsageError(
70
+ "--ack-context-sharing has no effect with `ocx advisor off`; it never records consent.",
71
+ USAGE,
72
+ );
73
+ }
74
+ printData(await runtimeRequest("/api/advisor/settings", {
75
+ method: "PUT",
76
+ body: JSON.stringify({ enabled: false }),
77
+ }, deps), wantsJson, ["Advisor disabled."]);
78
+ return;
79
+ }
80
+ const consent = await readConsent(deps);
81
+ if (acknowledge) {
82
+ printDisclosure();
83
+ printData(await runtimeRequest("/api/advisor/settings", {
84
+ method: "PUT",
85
+ body: JSON.stringify({
86
+ enabled: true,
87
+ contextSharingConsent: ADVISOR_CONTEXT_SHARING_CONSENT_VERSION,
88
+ }),
89
+ }, deps), wantsJson, ["Advisor enabled with context-sharing consent v1."]);
90
+ return;
91
+ }
92
+ if (consent !== ADVISOR_CONTEXT_SHARING_CONSENT_VERSION) {
93
+ printDisclosure();
94
+ throw new CliUsageError(
95
+ "Advisor context-sharing consent is required before consultations can send task content. "
96
+ + "Re-run `ocx advisor on --ack-context-sharing` after reading the disclosure, or run `ocx advisor consent`.",
97
+ USAGE,
98
+ );
99
+ }
100
+ printData(await runtimeRequest("/api/advisor/settings", {
101
+ method: "PUT",
102
+ body: JSON.stringify({ enabled: true }),
103
+ }, deps), wantsJson, ["Advisor enabled."]);
104
+ }
105
+
106
+ async function consent(argv: string[], deps: RuntimeApiDeps): Promise<void> {
107
+ const args = [...argv];
108
+ const wantsJson = takeFlag(args, "--json");
109
+ const revoke = takeFlag(args, "--revoke");
110
+ rejectArgs(args, USAGE);
111
+ if (!revoke) printDisclosure();
112
+ printData(await runtimeRequest("/api/advisor/settings", {
113
+ method: "PUT",
114
+ body: JSON.stringify({
115
+ contextSharingConsent: revoke ? null : ADVISOR_CONTEXT_SHARING_CONSENT_VERSION,
116
+ }),
117
+ }, deps), wantsJson, [revoke
118
+ ? "Advisor context-sharing consent removed. Consultations will not send task content."
119
+ : "Advisor context-sharing consent v1 recorded.",
120
+ ]);
121
+ }
122
+
123
+ async function set(argv: string[], deps: RuntimeApiDeps): Promise<void> {
124
+ const args = [...argv];
125
+ const wantsJson = takeFlag(args, "--json");
126
+ const options = parseSetArgs(args);
127
+ const patch: Record<string, unknown> = {};
128
+ if (options.model !== undefined) patch.model = options.model;
129
+ if (options.effort !== undefined) patch.effort = options.effort;
130
+ if (options.policy !== undefined) patch.policy = options.policy;
131
+ if (options.maxConsultations !== undefined) {
132
+ const limit = Number(options.maxConsultations);
133
+ if (!Number.isInteger(limit) || limit < 1 || limit > 3) throw new CliUsageError("--max-consultations must be an integer between 1 and 3", USAGE);
134
+ patch.maxConsultationsPerTask = limit;
135
+ }
136
+ if (options.timeoutMs !== undefined) {
137
+ const parsed = Number(options.timeoutMs);
138
+ if (!Number.isFinite(parsed)) throw new CliUsageError("--timeout-ms must be a number", USAGE);
139
+ patch.timeoutMs = parsed;
140
+ }
141
+ printData(await runtimeRequest("/api/advisor/settings", {
142
+ method: "PUT",
143
+ body: JSON.stringify(patch),
144
+ }, deps), wantsJson, ["Advisor settings saved."]);
145
+ }
146
+
147
+ export async function handleAdvisorCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
148
+ return runCliAction(async () => {
149
+ const [sub = "status", ...rest] = argv;
150
+ if (sub === "status") await status(rest, deps);
151
+ else if (sub === "activity") {
152
+ const args = [...rest];
153
+ const wantsJson = takeFlag(args, "--json");
154
+ rejectArgs(args, USAGE);
155
+ printData(await runtimeRequest("/api/advisor/activity", {}, deps), wantsJson);
156
+ }
157
+ else if (sub === "on") await setEnabled(true, rest, deps);
158
+ else if (sub === "off") await setEnabled(false, rest, deps);
159
+ else if (sub === "consent") await consent(rest, deps);
160
+ else if (sub === "set") await set(rest, deps);
161
+ else throw new CliUsageError(`unknown advisor command ${sub}`, USAGE);
162
+ });
163
+ }
164
+
165
+ export const ADVISOR_USAGE = USAGE;
@@ -0,0 +1,37 @@
1
+ import type { OcxConfig } from "../types";
2
+ import { AuxiliaryListenerBindError } from "../server/ports";
3
+ import { createServer } from "node:net";
4
+
5
+ /**
6
+ * Reject impossible auxiliary binds before startup creates private state. On Windows
7
+ * that publication requires several synchronous ACL subprocesses. This is only an
8
+ * early rejection: startServer still owns the actual bind and transactional rollback
9
+ * if another process takes the port between this probe and startup.
10
+ */
11
+ export async function assertAuxiliaryListenerPortsAvailable(config: OcxConfig): Promise<void> {
12
+ const loopback = config.unauthenticatedLoopbackListener;
13
+ const management = config.runtimeRole === "hub" ? config.hub?.managementIngress : undefined;
14
+ const targets = [
15
+ { listener: "unauthenticatedLoopbackListener", port: loopback?.enabled ? loopback.port : undefined },
16
+ { listener: "hub.managementIngress", port: management?.enabled ? management.port : undefined },
17
+ ] as const;
18
+ // Each probe releases its socket, so two listeners configured for one port would both
19
+ // pass and startup would still fail on the second bind. Reject the collision directly.
20
+ if (targets[0].port !== undefined && targets[0].port === targets[1].port) {
21
+ throw new AuxiliaryListenerBindError(
22
+ "hub.managementIngress", targets[1].port, "127.0.0.1",
23
+ new Error("port is also configured for unauthenticatedLoopbackListener"),
24
+ );
25
+ }
26
+ for (const { listener, port } of targets) {
27
+ // The port-less companion shares the public bind; it has no independent target.
28
+ if (port === undefined) continue;
29
+ await new Promise<void>((resolve, reject) => {
30
+ const probe = createServer(socket => socket.destroy());
31
+ const fail = (cause: unknown) => reject(new AuxiliaryListenerBindError(listener, port, "127.0.0.1", cause));
32
+ probe.once("error", fail);
33
+ probe.once("listening", () => probe.close(error => error ? fail(error) : resolve()));
34
+ try { probe.listen({ port, host: "127.0.0.1" }); } catch (error) { fail(error); }
35
+ });
36
+ }
37
+ }
@@ -2,6 +2,14 @@ import type { Capability } from "./capability-types";
2
2
 
3
3
  /** Account pool operations and physical native-login profiles have separate owners. */
4
4
  export const ACCOUNT_CAPABILITIES: readonly Capability[] = [
5
+ {
6
+ command: ["account", "preserve-signed-in"], summary: "Prefer other Codex identities and reserve the native sign-in as fallback.",
7
+ usage: "ocx account preserve-signed-in openai [on|off|status] [--apply-to-bound] [--json]",
8
+ routes: [{ method: "GET", path: "/api/codex-auth/preserve-signed-in" }, { method: "PUT", path: "/api/codex-auth/preserve-signed-in" }],
9
+ flags: [{ name: "--apply-to-bound", value: "boolean", summary: "With on, release protected bindings and pins for the next request." }, { name: "--json", value: "boolean", summary: "Emit affected entries, next unbound choice and pin/binding semantics." }],
10
+ mutates: true, json: "payload",
11
+ details: ["Default/status reads. Custom priorities are preserved. Bound threads and manual pins remain unless explicitly released. Exact-account models override the preference. Fallback can spend signed-in quota; linked pause excludes it instead."],
12
+ },
5
13
  {
6
14
  command: ["logout"],
7
15
  summary: "Remove the active stored OAuth credential for a provider locally.",
@@ -157,11 +165,22 @@ export const ACCOUNT_CAPABILITIES: readonly Capability[] = [
157
165
  usage: "ocx account pool <provider> [--enabled <on|off>] [--threshold <0-100>] [--strategy <name>] [--sticky <1-100>] [--quota-window <five-hour|weekly|max-utilization>] [--json]",
158
166
  summary: "Read or edit the selected runtime's supported pool policy fields.",
159
167
  routes: [{"method":"GET","path":"/api/pool/settings"},{"method":"PUT","path":"/api/pool/settings"}],
160
- flags: [{"name":"--enabled","value":"string","summary":"Stored pool preference; unsupported for Codex. Not equivalent to threshold0."},{"name":"--threshold","value":"number","summary":"Auto-switch threshold0–100."},{"name":"--strategy","value":"string","summary":"Use a strategy supported by this provider kind; reset-first is Codex, least-loaded is Kiro."},{"name":"--sticky","value":"number","summary":"Sticky limit1–100."},{"name":"--quota-window","value":"string","summary":"Anthropic only: five-hour, weekly or max-utilization."},{"name":"--json","value":"boolean","summary":"Emit the validated task result as one JSON document."}],
168
+ flags: [{"name":"--enabled","value":"string","summary":"Stored pool preference; unsupported for Codex. Not equivalent to threshold0."},{"name":"--threshold","value":"number","summary":"Auto-switch threshold0–100."},{"name":"--strategy","value":"string","summary":"Use a strategy supported by this provider kind; reset-first is Codex or generic OAuth, least-loaded is Kiro."},{"name":"--sticky","value":"number","summary":"Sticky limit1–100."},{"name":"--quota-window","value":"string","summary":"Anthropic only: five-hour, weekly or max-utilization."},{"name":"--json","value":"boolean","summary":"Emit the validated task result as one JSON document."}],
161
169
  mutates: true,
162
170
  json: "envelope",
163
171
  details: ["Reads the target supported-field list before a narrow PUT. Null/unsupported, stored/effective and generic inert policy stay distinct; disabling does not promise that reactive429 rotation stops.","No local classification/defaults are round-tripped into the live target; supported-field reads are not CAS."],
164
172
  },
173
+ {
174
+ command: ["account", "use-remaining"],
175
+ usage: "ocx account use-remaining openai <id|alias|main> <on|off|status> [--json]",
176
+ summary: "Choose whether one Codex account keeps using its remaining included quota.",
177
+ routes: [{ method: "GET", path: "/api/codex-auth/accounts" },
178
+ { method: "GET", path: "/api/codex-auth/accounts/use-remaining" }, { method: "PUT", path: "/api/codex-auth/accounts/use-remaining" }],
179
+ flags: [{ name: "--json", value: "boolean", summary: "Emit the validated account policy." }],
180
+ mutates: true, json: "envelope",
181
+ details: ["On disables proactive usage switching for the selected account and raises main-account protection to 100%. Off restores the configured thresholds.",
182
+ "This is a persistent preference, not account selection. Pause, reauthentication, cooldown, entitlement and paid-credit policy still apply."],
183
+ },
165
184
  {
166
185
  command: ["account","credits"],
167
186
  usage: "ocx account credits openai <id|alias|main> <on|off> [--json]; ocx account credits openai --all <on|off> [--json]",