@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,4 +1,5 @@
1
1
  import type { Server } from "bun";
2
+ import { supportsDesktopPicker } from "../desktop-picker-platform";
2
3
  import type { OcxConfig } from "../../types";
3
4
  import { getConfigDir } from "../../config/paths";
4
5
  import type { DesktopPickerController } from "../desktop-picker";
@@ -12,6 +13,7 @@ import { discardPickerCaKey } from "./picker-ca";
12
13
  import { preparePersistentPickerAuthority } from "./picker-ca-startup";
13
14
  import type { PickerCaStore } from "./picker-ca-store";
14
15
  import type { PickerRouteInput } from "./picker-models";
16
+ import { CLAUDE_INTERCEPT_PORT_OFFSET, claudeInterceptEnabled, claudeInterceptProxyPort, claudePickerProxyPort } from "./port-policy";
15
17
  import { createPickerRuntime, pickerDesired, type CreatePickerRuntimeOptions, type PickerRuntime } from "./picker-runtime";
16
18
  import type { SecurityRunner } from "./picker-trust";
17
19
  import { ensureClaudeInterceptProxyToken, readClaudeInterceptProxyToken } from "./proxy-auth";
@@ -28,7 +30,7 @@ import { buildClaudeInterceptEnv, migrateClaudeInterceptSettings } from "./setti
28
30
  * proxy. Desktop also hands that proxy to the Claude Code processes it spawns, so the choice is per
29
31
  * client: Claude Code (no User-Agent on CONNECT) trusts only the intercept CA and gets the
30
32
  * api.anthropic.com intercept and nothing else; the app itself (a browser User-Agent) trusts only
31
- * the login keychain and never meets the api.anthropic.com intercept, and only its claude.ai
33
+ * the OS root store and never meets the api.anthropic.com intercept, and only its claude.ai
32
34
  * tunnels may be terminated by the picker runtime (src/claude/intercept/picker-runtime.ts).
33
35
  */
34
36
 
@@ -41,25 +43,7 @@ const PICKER_AUTHORITY_ERROR_CODES = new Set([
41
43
  "picker_ca_unsupported_platform",
42
44
  ]);
43
45
 
44
- export const CLAUDE_INTERCEPT_PORT_OFFSET = 100;
45
-
46
- export function claudeInterceptEnabled(config: Pick<OcxConfig, "claudeCode" | "runtimeRole">): boolean {
47
- if (config.runtimeRole === "client") return false;
48
- if (config.claudeCode?.enabled === false) return false;
49
- return config.claudeCode?.intercept?.enabled !== false;
50
- }
51
-
52
- export function claudeInterceptProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
53
- const configured = config.claudeCode?.intercept?.port;
54
- if (typeof configured === "number" && Number.isInteger(configured) && configured >= 1 && configured <= 65535) return configured;
55
- return publicPort + CLAUDE_INTERCEPT_PORT_OFFSET;
56
- }
57
-
58
- /** Desktop's egress proxy for picker mode: the port after the intercept proxy (before it at 65535). */
59
- export function claudePickerProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
60
- const interceptPort = claudeInterceptProxyPort(config, publicPort);
61
- return interceptPort < 65535 ? interceptPort + 1 : interceptPort - 1;
62
- }
46
+ export { CLAUDE_INTERCEPT_PORT_OFFSET, claudeInterceptEnabled, claudeInterceptProxyPort, claudePickerProxyPort } from "./port-policy";
63
47
 
64
48
  export type ClaudeInterceptOutcome = { ok: true; state: ClaudeInterceptState } | { ok: false; reason: "disabled" | "client_role" | "ephemeral_port" | "port_in_use" | "stopped" | "failed"; port?: number; message?: string };
65
49
 
@@ -144,7 +128,7 @@ export interface StartClaudeInterceptOptions<T> {
144
128
  createPicker?: (options: CreatePickerRuntimeOptions) => PickerRuntime;
145
129
  /** Test seam: bind real CONNECT handlers on kernel-assigned ports without probe-and-release races. */
146
130
  startProxy?: typeof startConnectProxy;
147
- /** Test seams: the macOS `security` runner and platform for the picker runtime and controller. */
131
+ /** Test seams: the platform trust runner and platform for the picker runtime and controller. */
148
132
  pickerCaStore?: PickerCaStore;
149
133
  pickerSecurity?: SecurityRunner;
150
134
  pickerPlatform?: NodeJS.Platform;
@@ -167,18 +151,6 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
167
151
  const startProxy = options.startProxy ?? startConnectProxy;
168
152
  const authToken = ensureClaudeInterceptProxyToken(configDir);
169
153
  const leaf = issueLocalInterceptLeaf(ca, CLAUDE_INTERCEPT_HOSTS);
170
- // Refresh an env we already own (e.g. a pre-auth proxy URL left by an upgrade) before the
171
- // authenticated proxy takes over the port — a plain `ocx start` after an update would
172
- // otherwise 407 every CONNECT until the next `ocx ensure` or apply. Only `stale` state is
173
- // rewritten, so installs that never applied first-party are untouched.
174
- try {
175
- const proxyPort = claudeInterceptProxyPort(options.config, options.publicPort);
176
- migrateClaudeInterceptSettings(buildClaudeInterceptEnv(proxyPort, claudeInterceptCaCertPath(configDir), authToken));
177
- } catch (error) {
178
- // A skipped rewrite degrades to the pre-migration behaviour and ensure/apply retries it,
179
- // but silence here leaves upgraded clients hitting 407 with no recorded cause.
180
- console.warn(`[claude-intercept] settings migration skipped: ${error instanceof Error ? error.message : String(error)}`);
181
- }
182
154
  const listener = startClaudeInterceptListener<T>({
183
155
  leaf,
184
156
  dispatch: options.dispatch,
@@ -203,15 +175,25 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
203
175
  await listener.stop(true);
204
176
  throw new ClaudeInterceptProxyBindError(error, claudeInterceptProxyPort(options.config, options.publicPort));
205
177
  }
178
+ // Publish only a successfully bound endpoint, using the handle's actual port. A failed
179
+ // fixed-port bind leaves settings untouched; an ephemeral test binder never publishes :0
180
+ // or a probed port another process can steal. Only stale, owned env blocks are rewritten.
181
+ try {
182
+ migrateClaudeInterceptSettings(buildClaudeInterceptEnv(proxy.port, claudeInterceptCaCertPath(configDir), authToken));
183
+ } catch (error) {
184
+ // Ensure/apply retries a skipped migration; upgraded clients otherwise keep hitting 407.
185
+ console.warn(`[claude-intercept] settings migration skipped: ${error instanceof Error ? error.message : String(error)}`);
186
+ }
206
187
  // Widened on purpose: assignments happen in nested awaits the catch below must still see.
207
188
  let picker = null as PickerRuntime | null;
208
189
  let pickerProxy = null as ConnectProxyHandle | null;
209
190
  let controller = null as DesktopPickerController | null;
210
191
  let pickerProxyLive = false;
192
+ let stopped = false;
211
193
  let pickerReason: ClaudeInterceptState["pickerReason"] = null;
212
194
  let pickerFailurePort: number | undefined;
213
195
  try {
214
- if (options.loadPickerRoutes && (options.pickerPlatform ?? process.platform) === "darwin") {
196
+ if (options.loadPickerRoutes && supportsDesktopPicker(options.pickerPlatform ?? process.platform)) {
215
197
  // Remove any legacy plaintext key before inspecting or activating picker state.
216
198
  discardPickerCaKey(configDir);
217
199
  const { inspectDesktopPickerProfile } = await import("../desktop-picker-profile");
@@ -225,11 +207,15 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
225
207
  });
226
208
  const pickerProfileApplied = pickerProfile.kind === "applied";
227
209
  let pickerBlocked = false;
210
+ let pickerWired = false;
228
211
  try {
229
212
  const { observeClaudeDesktopMode, resolveClaudeDesktopMode } = await import("../desktop-first-party");
230
213
  const fresh = options.config;
231
214
  const platform = options.pickerPlatform ?? process.platform;
232
- if (pickerProfileApplied || pickerDesired(fresh, resolveClaudeDesktopMode(fresh, observeClaudeDesktopMode(fresh)), platform)) {
215
+ const pickerRequired = pickerProfileApplied || pickerDesired(fresh, resolveClaudeDesktopMode(fresh, observeClaudeDesktopMode(fresh)), platform);
216
+ // Preserve macOS's dormant relay; Windows defers binding until enable or profile recovery.
217
+ pickerWired = platform !== "win32" || pickerRequired;
218
+ if (pickerRequired) {
233
219
  await preparePersistentPickerAuthority({ configDir, store: options.pickerCaStore, security: options.pickerSecurity, platform });
234
220
  }
235
221
  } catch (error) {
@@ -276,63 +262,76 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
276
262
  const runtime = picker;
277
263
  const interceptPort = listener.port!;
278
264
  const pickerPort = claudePickerProxyPort(options.config, options.publicPort);
279
- try {
280
- pickerProxy = await startProxy(pickerPort, {
281
- interceptPort,
282
- // No authToken: Desktop's egressProxyUrl cannot present proxy credentials, so this
283
- // listener stays an unauthenticated loopback relay until the profile format can carry
284
- // one. The intercept proxy above is the credential-bearing hop.
285
- // No host list here: the choice below depends on which client opened the tunnel.
286
- interceptHosts: [],
287
- selectTunnel: (host, port, request) => {
288
- // Desktop hands its pinned egress proxy to the Claude Code processes it spawns, so their
289
- // api.anthropic.com traffic arrives here too and gets the same intercept as on the Claude
290
- // Code proxy. Those processes trust only the intercept CA, so the picker never terminates
291
- // their claude.ai tunnels; only the app's own (browser) CONNECTs reach the picker.
292
- if (!isBrowserConnect(request)) {
293
- return port === 443 && (CLAUDE_INTERCEPT_HOSTS as readonly string[]).includes(host.toLowerCase())
294
- ? { kind: "intercept", port: interceptPort }
295
- : { kind: "blind" };
296
- }
297
- return runtime.selectTunnel(host, port);
265
+ const bindPickerProxy = async (): Promise<void> => {
266
+ if (stopped || pickerProxyLive) return;
267
+ try {
268
+ pickerProxy = await startProxy(pickerPort, {
269
+ interceptPort,
270
+ // No authToken: Desktop's egressProxyUrl cannot present proxy credentials, so this
271
+ // listener stays an unauthenticated loopback relay until the profile format can carry
272
+ // one. The intercept proxy above is the credential-bearing hop.
273
+ // No host list here: the choice below depends on which client opened the tunnel.
274
+ interceptHosts: [],
275
+ selectTunnel: (host, port, request) => {
276
+ // Desktop hands its pinned egress proxy to the Claude Code processes it spawns, so their
277
+ // api.anthropic.com traffic arrives here too and gets the same intercept as on the Claude
278
+ // Code proxy. Those processes trust only the intercept CA, so the picker never terminates
279
+ // their claude.ai tunnels; only the app's own (browser) CONNECTs reach the picker.
280
+ if (!isBrowserConnect(request)) {
281
+ return port === 443 && (CLAUDE_INTERCEPT_HOSTS as readonly string[]).includes(host.toLowerCase())
282
+ ? { kind: "intercept", port: interceptPort }
283
+ : { kind: "blind" };
284
+ }
285
+ return runtime.selectTunnel(host, port);
286
+ },
287
+ });
288
+ if (stopped) { await pickerProxy.close(); return; }
289
+ pickerProxyLive = true;
290
+ pickerReason = null;
291
+ pickerFailurePort = undefined;
292
+ } catch (error) {
293
+ // Picker mode is optional: a busy port leaves the intercept pair running without it.
294
+ // The selected profile row survives; a dormant enable can retry after the port is free.
295
+ pickerReason = error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE" ? "port_in_use" : "failed";
296
+ if (pickerReason === "port_in_use") pickerFailurePort = pickerPort;
297
+ console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
298
+ throw error;
299
+ }
300
+ };
301
+ if (pickerWired) {
302
+ try { await bindPickerProxy(); }
303
+ catch { await runtime.stop(); picker = null; }
304
+ }
305
+ if (picker) {
306
+ // Dynamic: the controller reaches desktop-first-party, which imports this module.
307
+ const [{ createDesktopPickerController }, { loadConfig }, persistPreference] = await Promise.all([
308
+ import("../desktop-picker"),
309
+ import("../../config"),
310
+ createPickerPreferenceWriter(options.config),
311
+ ]);
312
+ controller = createDesktopPickerController({
313
+ runtime: picker,
314
+ readConfig: loadConfig,
315
+ persistPreference,
316
+ proxyPort: () => (pickerProxyLive ? pickerProxy?.port ?? null : null),
317
+ ensureProxy: async () => {
318
+ await bindPickerProxy();
319
+ if (!pickerProxyLive) return;
320
+ await runtime.start();
321
+ if (!stopped && activeController === controller) activePicker = runtime;
298
322
  },
323
+ configDir,
324
+ persistentAuthority: { store: options.pickerCaStore },
325
+ ...(options.pickerSecurity ? { security: options.pickerSecurity } : {}),
326
+ ...(options.pickerPlatform ? { platform: options.pickerPlatform } : {}),
299
327
  });
300
- pickerProxyLive = true;
301
- } catch (error) {
302
- // Picker mode is optional: a busy port leaves the intercept pair running without it.
303
- // The selected profile row survives, so the next startup retries the restore once the
304
- // port is free again.
305
- pickerReason = error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE" ? "port_in_use" : "failed";
306
- if (pickerReason === "port_in_use") pickerFailurePort = pickerPort;
307
- console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
308
- await runtime.stop();
309
- picker = null;
310
- }
311
- }
312
- if (picker) {
313
- // Dynamic: the controller reaches desktop-first-party, which imports this module.
314
- const [{ createDesktopPickerController }, { loadConfig }, persistPreference] = await Promise.all([
315
- import("../desktop-picker"),
316
- import("../../config"),
317
- createPickerPreferenceWriter(options.config),
318
- ]);
319
- const boundProxy = pickerProxy;
320
- controller = createDesktopPickerController({
321
- runtime: picker,
322
- readConfig: loadConfig,
323
- persistPreference,
324
- proxyPort: () => (pickerProxyLive && boundProxy ? boundProxy.port : null),
325
- configDir,
326
- persistentAuthority: { store: options.pickerCaStore },
327
- ...(options.pickerSecurity ? { security: options.pickerSecurity } : {}),
328
- ...(options.pickerPlatform ? { platform: options.pickerPlatform } : {}),
329
- });
330
- await picker.start();
331
- // Restore inspects the retained identity without installing trust. Explicit enable/trust
332
- // owns consent when trust was revoked or legacy migration introduced a new authority.
333
- if (pickerProfileApplied && controller) {
334
- void controller.enable({ persist: false, context: "server", allowTrustPrompt: false })
335
- .catch(error => console.warn(`⚠ Claude Desktop picker restore failed: ${error instanceof Error ? error.message : String(error)}`));
328
+ if (pickerWired) await picker.start();
329
+ // Restore inspects the retained identity without installing trust. Explicit enable/trust
330
+ // owns consent when trust was revoked or legacy migration introduced a new authority.
331
+ if (pickerProfileApplied && controller) {
332
+ void controller.enable({ persist: false, context: "server", allowTrustPrompt: false })
333
+ .catch(error => console.warn(`⚠ Claude Desktop picker restore failed: ${error instanceof Error ? error.message : String(error)}`));
334
+ }
336
335
  }
337
336
  }
338
337
  }
@@ -348,21 +347,25 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
348
347
  throw error;
349
348
  }
350
349
  const state: ClaudeInterceptState = {
351
- pickerReason,
352
- pickerFailurePort,
350
+ get pickerReason() { return pickerReason; },
351
+ get pickerFailurePort() { return pickerFailurePort; },
353
352
  proxyPort: proxy.port,
354
353
  caCertPath: claudeInterceptCaCertPath(configDir),
355
- pickerProxyPort: pickerProxy?.port ?? null,
354
+ get pickerProxyPort() { return pickerProxyLive ? pickerProxy?.port ?? null : null; },
356
355
  };
357
356
  activeState = state;
358
- activePicker = picker;
357
+ activePicker = pickerProxyLive ? picker : null;
359
358
  activeController = controller;
360
359
  const ownPicker = picker;
361
360
  const ownController = controller;
362
361
  return {
363
362
  ...state,
363
+ get pickerProxyPort() { return state.pickerProxyPort; },
364
+ get pickerReason() { return state.pickerReason; },
365
+ get pickerFailurePort() { return state.pickerFailurePort; },
364
366
  listener,
365
367
  stop: async () => {
368
+ stopped = true;
366
369
  if (activeState === state) activeState = null;
367
370
  if (activePicker === ownPicker) activePicker = null;
368
371
  if (activeController === ownController) activeController = null;
@@ -0,0 +1,154 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { linkSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, unlinkSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { atomicWriteFileNoFollow } from "../config/atomic-write";
5
+ import { hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
6
+ import { assertNotRealClaudeConfigUnderTest } from "../lib/test-home-guard";
7
+ import { claudeConfigDir } from "./auth-detect";
8
+ import { claudeTierDisplayName, type ClaudeTierModels } from "./context-windows";
9
+
10
+ const tiers = ["opus", "sonnet", "haiku", "fable"] as const;
11
+
12
+ function readSettings(path: string): { raw: string; exists: boolean; doc: Record<string, unknown>; env: Record<string, unknown> } {
13
+ let raw = "";
14
+ let exists = false;
15
+ try {
16
+ const stat = lstatSync(path);
17
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024) throw new Error("settings_unreadable");
18
+ raw = readFileSync(path, "utf8");
19
+ exists = true;
20
+ } catch (error) {
21
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw new Error("settings_unreadable");
22
+ }
23
+ let doc: Record<string, unknown>;
24
+ try {
25
+ const parsed: unknown = exists ? JSON.parse(raw) : {};
26
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
27
+ doc = parsed as Record<string, unknown>;
28
+ if (doc.env !== undefined && (!doc.env || typeof doc.env !== "object" || Array.isArray(doc.env))) throw new Error();
29
+ } catch { throw new Error("settings_unreadable"); }
30
+ return { raw, exists, doc, env: (doc.env ?? {}) as Record<string, unknown> };
31
+ }
32
+
33
+ function snapshotFingerprint(current: ReturnType<typeof readSettings>, models: ClaudeTierModels | undefined) {
34
+ const target = selectedEnv(models, current.env);
35
+ return createHash("sha256").update(JSON.stringify([current.exists, current.raw, target])).digest("hex");
36
+ }
37
+
38
+ /** Test seams for edits at publication boundaries; never configured through the API. */
39
+ export interface ModelDefaultWriteHooks {
40
+ beforeCapture?: () => void;
41
+ beforePublish?: () => void;
42
+ }
43
+
44
+ export class ModelDefaultsRecoveryError extends Error {
45
+ constructor(readonly backupPath: string, options?: ErrorOptions) {
46
+ super("settings_recovery_required", options);
47
+ }
48
+ }
49
+
50
+ /** Unset tiers stay untouched; only a label matching its current alias is managed. */
51
+ function selectedEnv(models: ClaudeTierModels | undefined, current: Record<string, unknown>): Record<string, string | null> {
52
+ const env: Record<string, string | null> = {};
53
+ for (const tier of tiers) {
54
+ const slot = `ANTHROPIC_DEFAULT_${tier.toUpperCase()}_MODEL`;
55
+ const name = `${slot}_NAME`;
56
+ const value = models?.[tier]?.trim();
57
+ if (!value) continue;
58
+ env[slot] = value;
59
+ const previous = typeof current[slot] === "string" ? claudeTierDisplayName(current[slot]) : undefined;
60
+ const managed = previous !== undefined && current[name] === previous;
61
+ // Custom (including empty/non-string) labels are user-owned. Exact pairing
62
+ // follows the launcher label convention; the name alone proves no ownership.
63
+ if (!Object.hasOwn(current, name) || managed) {
64
+ const label = claudeTierDisplayName(value);
65
+ if (label !== undefined) env[name] = label;
66
+ else if (managed) env[name] = null;
67
+ }
68
+ }
69
+ return env;
70
+ }
71
+
72
+ export function previewClaudeModelDefaults(models: ClaudeTierModels | undefined, configDir = claudeConfigDir()) {
73
+ const path = join(configDir, "settings.json");
74
+ const current = readSettings(path);
75
+ const target = selectedEnv(models, current.env);
76
+ const changes = Object.entries(target).filter(([key, value]) => current.env[key] !== value)
77
+ // The existing preview wire shape uses an empty replacement for label removal.
78
+ .map(([key, value]) => ({ key, before: typeof current.env[key] === "string" ? current.env[key] as string : null, after: value ?? "" }));
79
+ const fingerprint = snapshotFingerprint(current, models);
80
+ return { path, fingerprint, changes };
81
+ }
82
+
83
+ export function applyClaudeModelDefaults(
84
+ models: ClaudeTierModels | undefined,
85
+ fingerprint: string,
86
+ configDir = claudeConfigDir(),
87
+ backupDir = join(configDir, "model-default-backups"),
88
+ hooks: ModelDefaultWriteHooks = {},
89
+ ) {
90
+ const preview = previewClaudeModelDefaults(models, configDir);
91
+ if (preview.fingerprint !== fingerprint) throw new Error("settings_changed");
92
+ if (!preview.changes.length) return { changed: false, path: preview.path };
93
+ assertNotRealClaudeConfigUnderTest(configDir, preview.path);
94
+ const current = readSettings(preview.path);
95
+ const check = snapshotFingerprint(current, models);
96
+ if (check !== fingerprint) throw new Error("settings_changed");
97
+ mkdirSync(configDir, { recursive: true });
98
+ mkdirSync(backupDir, { recursive: true, mode: 0o700 });
99
+ const recoveryDir = mkdtempSync(join(backupDir, "apply-"));
100
+ if (process.platform === "win32") hardenSecretDir(recoveryDir, { required: true });
101
+ const backupPath = join(recoveryDir, "settings.json");
102
+ const staged = join(configDir, `.model-defaults-${randomUUID()}.tmp`);
103
+ let captured = false;
104
+ let published = false;
105
+ try {
106
+ const env = { ...current.env };
107
+ for (const [key, value] of Object.entries(selectedEnv(models, current.env))) {
108
+ if (value === null) delete env[key];
109
+ else env[key] = value;
110
+ }
111
+ current.doc.env = env;
112
+ atomicWriteFileNoFollow(staged, `${JSON.stringify(current.doc, null, 2)}\n`);
113
+ // Verify same-volume exclusive publication before moving the shared file.
114
+ const probe = join(recoveryDir, "link-probe");
115
+ linkSync(staged, probe);
116
+ unlinkSync(probe);
117
+ hooks.beforeCapture?.();
118
+ if (current.exists) {
119
+ // Move the actual destination, not a stale in-memory copy. A same-filesystem
120
+ // move captures late edits; cross-device failures leave the original alone.
121
+ try { renameSync(preview.path, backupPath); } catch (error) {
122
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") throw new Error("settings_changed");
123
+ throw error;
124
+ }
125
+ captured = true;
126
+ const capturedEntry = lstatSync(backupPath);
127
+ if (!capturedEntry.isFile() || capturedEntry.isSymbolicLink()) throw new Error("settings_changed");
128
+ if (process.platform === "win32") hardenSecretPath(backupPath, { required: true });
129
+ if (snapshotFingerprint(readSettings(backupPath), models) !== fingerprint) throw new Error("settings_changed");
130
+ }
131
+ hooks.beforePublish?.();
132
+ // A hard-link publish is exclusive on POSIX and Windows: never replace a
133
+ // document created by another writer during the brief move-aside interval.
134
+ try { linkSync(staged, preview.path); } catch (error) {
135
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") throw new Error("settings_changed");
136
+ throw error;
137
+ }
138
+ published = true;
139
+ return { changed: true, path: preview.path, ...(captured ? { backupPath } : {}) };
140
+ } catch (error) {
141
+ if (captured && !published) {
142
+ try { linkSync(backupPath, preview.path); } catch (restoreError) {
143
+ // A newer destination wins. Retain the captured document privately;
144
+ // never delete the winner or overwrite it as an automatic rollback.
145
+ throw new ModelDefaultsRecoveryError(backupPath, { cause: restoreError });
146
+ }
147
+ }
148
+ throw error;
149
+ } finally {
150
+ // The private stage is also present at the destination after successful
151
+ // linking; cleanup failure must not turn a committed apply into failure.
152
+ try { unlinkSync(staged); } catch { /* private residue, no shared-file cleanup */ }
153
+ }
154
+ }
@@ -103,19 +103,19 @@ function modelInfo(id: string, displayName: string, ladder: readonly string[], i
103
103
 
104
104
  /**
105
105
  * Which id family the discovery list carries (devlog 050): Claude Code (CLI)
106
- * gets readable `claude-ocx-*` ids; Claude Desktop keeps the hashed
107
- * `claude-opus-4-8-<code>` family its 3P config was written with. Both families
106
+ * gets readable `ocx-claude-*` ids; Claude Desktop keeps its profile's
107
+ * `claude-fable-5-p<code>` ids (legacy no-profile callers use hash aliases). Both families
108
108
  * decode in resolveInboundModel regardless of the style served here.
109
109
  */
110
110
  export type AnthropicIdStyle = "desktop3p" | "readable";
111
111
 
112
- /** Build the full anthropic-flavor discovery list (ids are Desktop 3P aliases). */
112
+ /** Build discovery rows; a Desktop resolver can return undefined to withhold an unbound ID. */
113
113
  export function buildAnthropicModelInfos(
114
114
  nativeSlugs: readonly string[],
115
115
  routedModels: readonly CatalogModel[],
116
116
  auto: AutoContextMode = AUTO_CONTEXT_OFF,
117
117
  idStyle: AnthropicIdStyle = "desktop3p",
118
- aliasForRoute: (provider: string, modelId: string) => string = desktop3pAlias,
118
+ aliasForRoute: (provider: string, modelId: string) => string | undefined = desktop3pAlias,
119
119
  nativeContextCap?: NativeContextLimitsInput,
120
120
  fastMode?: boolean,
121
121
  // Presence is the feature gate: the caller passes undefined when `fastRows` is off, so a
@@ -144,7 +144,7 @@ export function buildAnthropicModelInfos(
144
144
  ? claudeCodeAlias(m.provider, listed)
145
145
  : aliasForRoute(m.provider, m.id);
146
146
  }),
147
- ]);
147
+ ].filter((id): id is string => typeof id === "string" && id.length > 0));
148
148
  // [1m] picker variant (devlog 260712 B1; widened in devlog/_plan/261009_claude_1m_default/020):
149
149
  // Claude Code accounts exactly 1M for ids carrying the marker (2.1.207 binary: /\[1m\]/i -> 1e6,
150
150
  // compaction preserved). A discovery client may lack CLAUDE_CODE_AUTO_COMPACT_WINDOW, so the
@@ -201,7 +201,7 @@ export function buildAnthropicModelInfos(
201
201
  };
202
202
  for (const slug of nativeSlugs) {
203
203
  const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug);
204
- if (seen.has(id)) continue;
204
+ if (!id || seen.has(id)) continue;
205
205
  seen.add(id);
206
206
  const nativeWindow = nativeOpenAiContextWindow(slug, nativeContextCap);
207
207
  const nativeMaxInput = nativeOpenAiMaxInputTokens(slug, nativeContextCap);
@@ -228,7 +228,7 @@ export function buildAnthropicModelInfos(
228
228
  const id = idStyle === "readable"
229
229
  ? claudeCodeAlias(m.provider, listedModelId)
230
230
  : aliasForRoute(m.provider, m.id);
231
- if (seen.has(id)) continue;
231
+ if (!id || seen.has(id)) continue;
232
232
  seen.add(id);
233
233
  const groupStart = out.length;
234
234
  const ladder = Array.isArray(m.reasoningEfforts) ? m.reasoningEfforts : [];