@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
@@ -16,6 +16,7 @@ import {
16
16
  isOwnedDesktopGatewayEntry,
17
17
  parseMetadata,
18
18
  profilePath,
19
+ readDesktopLibraryText,
19
20
  resolveDesktop3pConfigLibraryPath,
20
21
  SAFE_DESKTOP_PROFILE_ID,
21
22
  type Desktop3pConfigLibraryOptions,
@@ -73,6 +74,12 @@ function readFileSnapshot(path: string): { exists: boolean; content?: string } {
73
74
  return existsSync(path) ? { exists: true, content: readFileSync(path, "utf8") } : { exists: false };
74
75
  }
75
76
 
77
+ // Desktop library paths go through the verified regular-file reader: a swapped FIFO or symlink
78
+ // must surface as a refusal, never as a blocking open. Our own picker state file keeps the plain read.
79
+ function readLibrarySnapshot(path: string): { exists: boolean; content?: string } {
80
+ return existsSync(path) ? { exists: true, content: readDesktopLibraryText(path) } : { exists: false };
81
+ }
82
+
76
83
  function restoreFile(path: string, snapshot: { exists: boolean; content?: string }): void {
77
84
  if (snapshot.exists) {
78
85
  rollbackAtomicWriteFile(path, snapshot.content ?? "");
@@ -98,7 +105,7 @@ function pickerEntry(metadata: Desktop3pMetadata): Desktop3pMetadataEntry | unde
98
105
  }
99
106
 
100
107
  function profileObject(path: string): Record<string, unknown> {
101
- const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
108
+ const parsed = JSON.parse(readDesktopLibraryText(path)) as unknown;
102
109
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
103
110
  throw new Error("picker_profile_unreadable");
104
111
  }
@@ -175,10 +182,10 @@ function applyDesktopPickerProfileLocked(options: { proxyPort: number } & Deskto
175
182
  const alreadySelected = selectedId === entryId;
176
183
  const priorState = readPickerState(previousStatePath);
177
184
  const previousAppliedId = alreadySelected ? priorState?.previousAppliedId ?? null : selectedId;
178
- const oldMeta = readFileSnapshot(metaPath);
179
- const oldProfile = readFileSnapshot(profile);
185
+ const oldMeta = readLibrarySnapshot(metaPath);
186
+ const oldProfile = readLibrarySnapshot(profile);
180
187
  const backupPath = `${profile}.bak`;
181
- const oldBackup = readFileSnapshot(backupPath);
188
+ const oldBackup = readLibrarySnapshot(backupPath);
182
189
  const oldState = readFileSnapshot(previousStatePath);
183
190
  const changed = !alreadySelected || !oldProfile.exists || oldProfile.content !== target;
184
191
  if (!changed) return { ok: true, changed: false, path: profile };
@@ -23,6 +23,7 @@ import {
23
23
  type DesktopPickerProfileOptions,
24
24
  } from "./desktop-picker-profile";
25
25
  import { resolveClaudeDesktopMode, observeClaudeDesktopMode } from "./desktop-first-party";
26
+ import { supportsDesktopPicker } from "./desktop-picker-platform";
26
27
 
27
28
  /** Desktop no longer selects the picker profile, so removing the CA's trust cannot strand it. */
28
29
  function profileReleased(profile: DesktopPickerProfileInspection): boolean {
@@ -77,6 +78,8 @@ export interface DesktopPickerControllerDeps {
77
78
  persistPreference: (value: boolean) => boolean;
78
79
  /** Bound picker CONNECT proxy port (Desktop's egress), or null when it is not running. */
79
80
  proxyPort: () => number | null;
81
+ /** Bind a dormant relay on explicit enable, under the controller's mutation lock. */
82
+ ensureProxy?: () => Promise<void>;
80
83
  configDir: string;
81
84
  persistentAuthority?: { store?: PickerCaStore };
82
85
  security?: SecurityRunner;
@@ -91,7 +94,7 @@ function profileOptions(deps: DesktopPickerControllerDeps): DesktopPickerProfile
91
94
  }
92
95
 
93
96
  function statusReasonForConfig(config: OcxConfig, platform: NodeJS.Platform, proxyBound: boolean): DesktopPickerReason {
94
- if (platform !== "darwin") return "unsupported_platform";
97
+ if (!supportsDesktopPicker(platform)) return "unsupported_platform";
95
98
  if (resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) !== "first-party") return "not_first_party";
96
99
  if (!claudeDesktopIntegrationEnabled(config)) return "integration_off";
97
100
  if (config.claudeCode?.intercept?.picker === false) return "disabled";
@@ -101,11 +104,11 @@ function statusReasonForConfig(config: OcxConfig, platform: NodeJS.Platform, pro
101
104
 
102
105
  function emptyStatus(config: OcxConfig, platform: NodeJS.Platform, reason: DesktopPickerReason): DesktopPickerStatus {
103
106
  return {
104
- desired: platform === "darwin"
107
+ desired: supportsDesktopPicker(platform)
105
108
  && claudeDesktopIntegrationEnabled(config)
106
109
  && resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) === "first-party"
107
110
  && config.claudeCode?.intercept?.picker !== false,
108
- supported: platform === "darwin",
111
+ supported: supportsDesktopPicker(platform),
109
112
  trust: "unknown",
110
113
  profile: "absent",
111
114
  listenerReady: false,
@@ -142,7 +145,7 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
142
145
  const trust = trustOverride ?? runtime.trust;
143
146
  const effective = runtime.effective && profileCurrent && trust === "trusted";
144
147
  let reason: DesktopPickerReason;
145
- if (platform !== "darwin") reason = "unsupported_platform";
148
+ if (!supportsDesktopPicker(platform)) reason = "unsupported_platform";
146
149
  else if (!runtime.desired) reason = statusReasonForConfig(deps.readConfig(), platform, deps.proxyPort() !== null);
147
150
  else if (deps.proxyPort() === null) reason = "proxy_unavailable";
148
151
  else if (trust !== "trusted") reason = "trust_pending";
@@ -178,7 +181,7 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
178
181
 
179
182
  async function untrustCurrentCa(): Promise<boolean> {
180
183
  const caPath = pickerCaCertPath(deps.configDir);
181
- if (platform !== "darwin" || !existsSync(caPath)) return true;
184
+ if (!supportsDesktopPicker(platform) || !existsSync(caPath)) return true;
182
185
  try {
183
186
  const sha1 = pickerCaFingerprints(readFileSync(caPath, "utf8")).sha1;
184
187
  return (await untrustPickerCa(caPath, sha1, deps.security, platform)).ok;
@@ -207,11 +210,11 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
207
210
  return withTrustFailure({ ...runtimeStatus(observedTrust), reason }, trustFailed);
208
211
  };
209
212
  const check = (config: OcxConfig, includePreference: boolean): DesktopPickerReason | null => {
210
- if (platform !== "darwin") return "unsupported_platform";
213
+ if (!supportsDesktopPicker(platform)) return "unsupported_platform";
211
214
  if (resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) !== "first-party") return "mode_not_committed";
212
215
  if (!claudeDesktopIntegrationEnabled(config)) return "integration_off";
213
216
  if (includePreference && config.claudeCode?.intercept?.picker === false) return "disabled";
214
- if (deps.proxyPort() === null) return "proxy_unavailable";
217
+ if (deps.proxyPort() === null && !deps.ensureProxy) return "proxy_unavailable";
215
218
  return null;
216
219
  };
217
220
 
@@ -224,6 +227,10 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
224
227
  }
225
228
  const second = check(deps.readConfig(), true);
226
229
  if (second) return refuse(second);
230
+ if (deps.proxyPort() === null) {
231
+ try { await deps.ensureProxy?.(); } catch { return refuse("proxy_unavailable"); }
232
+ }
233
+ if (deps.proxyPort() === null) return refuse("proxy_unavailable");
227
234
 
228
235
  let caPath: string;
229
236
  let caSha1: string;
@@ -336,7 +343,7 @@ export async function removeDesktopPickerArtifacts(options: { configDir?: string
336
343
  return { ok: false, residual };
337
344
  }
338
345
  const caPath = pickerCaCertPath(configDir);
339
- if (platform === "darwin" && existsSync(caPath)) {
346
+ if (supportsDesktopPicker(platform) && existsSync(caPath)) {
340
347
  try {
341
348
  const sha1 = pickerCaFingerprints(readFileSync(caPath, "utf8")).sha1;
342
349
  if (!(await untrustPickerCa(caPath, sha1, options.security, platform)).ok) residual.push("trust");
@@ -33,7 +33,9 @@ export interface RenderedDesktopModel extends DesktopProfileModel {
33
33
  // renderDesktopProfile() converts it to a letter-first wire id because current
34
34
  // Claude Desktop strips terminal dates before comparing model identities.
35
35
  const DATE_ALIAS = /^claude-opus-4-8-(202[6-9]\d{4}|203[0-5]\d{4})$/;
36
- const PROFILE_WIRE_PREFIX = "claude-opus-4-8-";
36
+ // Desktop 2.31226's effort fallback recognizes fable prefixes, while Opus
37
+ // requires an exact canonical id. Keep the suffix so set_model stays distinct.
38
+ const PROFILE_WIRE_PREFIX = "claude-fable-5-";
37
39
  const LEGACY_YEAR = 2026;
38
40
  const LEGACY_DAY_COUNT = 365;
39
41
  const ALIAS_FIRST_YEAR = 2026;
@@ -144,6 +146,13 @@ export function desktopProfileWireAlias(alias: string): string {
144
146
  return `${PROFILE_WIRE_PREFIX}p${slot.toString(36).padStart(3, "0")}`;
145
147
  }
146
148
 
149
+ /** Decode-only profile namespace emitted before the Desktop effort compatibility fix. */
150
+ export function legacyDesktopProfileWireAlias(alias: string): string {
151
+ return validDateAlias(alias)
152
+ ? desktopProfileWireAlias(alias).replace(PROFILE_WIRE_PREFIX, "claude-opus-4-8-")
153
+ : alias;
154
+ }
155
+
147
156
  export function parseDesktopProfile(value: unknown): DesktopProfile {
148
157
  if (!isPlainObject(value)) throw new DesktopProfileError("must be an object");
149
158
  // `appliedFingerprint`/`appliedAt` are written back by the apply route once a profile
@@ -1,8 +1,8 @@
1
- import { chmodSync, lstatSync, mkdirSync, readFileSync, realpathSync, unlinkSync } from "node:fs";
1
+ import { chmodSync, lstatSync, mkdirSync, realpathSync, unlinkSync } from "node:fs";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { atomicWriteFile, getConfigDir } from "../config";
4
4
  import { hardenSecretDir } from "../lib/windows-secret-acl";
5
- import { isRecord, resolveDesktop3pConfigLibraryPath, SAFE_DESKTOP_PROFILE_ID, type Desktop3pMetadata } from "./desktop-3p-library";
5
+ import { isRecord, readDesktopLibraryBytes, resolveDesktop3pConfigLibraryPath, SAFE_DESKTOP_PROFILE_ID, type Desktop3pMetadata } from "./desktop-3p-library";
6
6
  import { DesktopStoreError, digest } from "./desktop-remote-store-state";
7
7
 
8
8
  export const MAX_DESKTOP_METADATA_ENTRIES = 256;
@@ -43,7 +43,9 @@ export class StoreIO {
43
43
  this.seenSizes.set(path, Math.max(counted, stat.size));
44
44
  if (this.bytes > 8 * 1024 * 1024) throw new DesktopStoreError("unsafe");
45
45
  try {
46
- const bytes = readFileSync(path);
46
+ // The shared verified regular-file read keeps a swapped FIFO from parking this
47
+ // open before the post-read identity recheck below can run.
48
+ const bytes = readDesktopLibraryBytes(path);
47
49
  if (bytes.byteLength > maxBytes || identity(lstatSync(path)) !== identity(stat)) throw new DesktopStoreError("conflict");
48
50
  const value: unknown = JSON.parse(bytes.toString("utf8"));
49
51
  if (!isRecord(value)) throw new DesktopStoreError("unsafe");
@@ -1,4 +1,6 @@
1
1
  import type { OcxConfig } from "../types";
2
+ import { connect } from "node:net";
3
+ import { directLocalHttpFetch } from "../server/direct-local-http";
2
4
  import {
3
5
  readWindowsProxyBypassRegistry,
4
6
  readWindowsSystemProxy,
@@ -8,6 +10,7 @@ import {
8
10
  } from "../lib/windows-system-proxy";
9
11
  import { inspectDesktopFirstParty, observeClaudeDesktopMode } from "./desktop-first-party";
10
12
  import { desktopFirstPartyDesired } from "./first-party-settings";
13
+ import { inspectDesktopPickerProfile, type DesktopPickerProfileInspection } from "./desktop-picker-profile";
11
14
 
12
15
  /**
13
16
  * Whether the Windows system proxy silently bypasses Desktop first-party mode.
@@ -17,7 +20,8 @@ import { desktopFirstPartyDesired } from "./first-party-settings";
17
20
  * as `HTTPS_PROXY`/`HTTP_PROXY`. That inherited value takes precedence over the `env` block
18
21
  * OpenCodex writes into the user's `~/.claude/settings.json` (only Claude Code managed settings
19
22
  * override it), so Code-tab traffic goes to the system proxy and never reaches the intercept.
20
- * Routed subagent models then fail, and nothing else reports why. A system proxy such as Clash or
23
+ * A selected picker profile with an available fixed egress relay takes precedence instead.
24
+ * Otherwise routed subagent models fail. A system proxy such as Clash or
21
25
  * v2rayN, with no bypass for the API host, is the common case.
22
26
  *
23
27
  * Observe-only: this reads the registry and never changes it. A PAC script and WPAD automatic
@@ -123,6 +127,39 @@ export interface DesktopSystemProxyDeps {
123
127
  firstPartyState?: () => DesktopFirstPartySettingsState;
124
128
  readSystemProxy?: () => WindowsSystemProxyResult;
125
129
  readBypass?: () => WindowsProxyBypassValues | null;
130
+ inspectPickerProfile?: () => DesktopPickerProfileInspection;
131
+ pickerRelayAvailable?: (proxyUrl: string) => boolean | Promise<boolean>;
132
+ }
133
+
134
+ /** Observe the selected endpoint even in a separate doctor process or a blind-only recovery relay. */
135
+ async function livePickerRelayAvailable(proxyUrl: string): Promise<boolean> {
136
+ const port = /^http:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(proxyUrl)?.[1];
137
+ if (!port || Number(port) > 65535) return false;
138
+ try {
139
+ // A listening TCP socket alone is not evidence of an OpenCodex CONNECT relay.
140
+ const response = await directLocalHttpFetch(proxyUrl, { signal: AbortSignal.timeout(1_000) });
141
+ if (response.status !== 405 || response.headers.get("allow") !== "CONNECT"
142
+ || !(await response.text()).startsWith("OpenCodex Claude intercept proxy accepts only CONNECT tunnels.")) return false;
143
+ // The main intercept proxy has the same HTTP response but requires credentials the Desktop
144
+ // profile cannot send. A loopback CONNECT is denied before dialing or choosing any tunnel;
145
+ // 403 proves unauthenticated admission, whereas the credential-bearing proxy returns 407.
146
+ return await new Promise<boolean>(resolve => {
147
+ const socket = connect({ host: "127.0.0.1", port: Number(port) });
148
+ const finish = (available: boolean) => { clearTimeout(timer); socket.destroy(); resolve(available); };
149
+ const timer = setTimeout(() => finish(false), 1_000);
150
+ let head = "";
151
+ socket.once("error", () => finish(false));
152
+ socket.once("close", () => finish(false));
153
+ socket.once("connect", () => socket.write("CONNECT 127.0.0.1:1 HTTP/1.1\r\nHost: 127.0.0.1:1\r\n\r\n"));
154
+ socket.on("data", bytes => {
155
+ head += bytes.toString("latin1");
156
+ if (head.includes("\r\n\r\n")) finish(/^HTTP\/1\.[01] 403 Forbidden\r\n/.test(head));
157
+ else if (head.length > 8 * 1024) finish(false);
158
+ });
159
+ });
160
+ } catch { // no-excuse-ok: catch -- failed relay probes retain the registry diagnostic.
161
+ return false;
162
+ }
126
163
  }
127
164
 
128
165
  function liveFirstPartyState(
@@ -137,15 +174,24 @@ function liveFirstPartyState(
137
174
  }
138
175
  }
139
176
 
140
- /** Reads the live state. Registry reads run only on Windows with a Desktop first-party env. */
141
- export function collectDesktopSystemProxy(
177
+ /** Registry reads run only on Windows with a Desktop first-party env and no verified picker relay. */
178
+ export async function collectDesktopSystemProxy(
142
179
  config: Pick<OcxConfig, "claudeCode" | "clientIntegrations" | "port" | "runtimeRole">,
143
180
  deps: DesktopSystemProxyDeps = {},
144
- ): DesktopSystemProxyAssessment {
181
+ ): Promise<DesktopSystemProxyAssessment> {
145
182
  const platform = deps.platform ?? process.platform;
146
183
  if (platform !== "win32") return { kind: "not-applicable" };
147
184
  const firstParty = (deps.firstPartyState ?? (() => liveFirstPartyState(config)))();
148
185
  if (firstParty === "off") return { kind: "not-applicable" };
186
+ if (firstParty === "applied" && config.claudeCode?.intercept?.picker !== false) {
187
+ try {
188
+ const profile = (deps.inspectPickerProfile ?? (() => inspectDesktopPickerProfile({ platform })))();
189
+ if (profile.kind === "applied" && await (deps.pickerRelayAvailable ?? livePickerRelayAvailable)(profile.proxyUrl)) {
190
+ return { kind: "not-applicable" };
191
+ }
192
+ } catch { // no-excuse-ok: catch -- missing profile/relay evidence must not hide a registry conflict.
193
+ }
194
+ }
149
195
  return assessDesktopSystemProxy({
150
196
  platform,
151
197
  firstParty,
@@ -76,6 +76,17 @@ export function toolsToResponses(tools: unknown): Rec[] | undefined {
76
76
  return out.length > 0 ? out : undefined;
77
77
  }
78
78
 
79
+ /**
80
+ * The caller's typed Anthropic web tools (`web_search_*`, `web_fetch_*`), as declared. The
81
+ * Responses body has no slot for them — web_search becomes the hosted sidecar tool above and
82
+ * web_fetch is dropped — so an Anthropic destination receives them out of body, unchanged.
83
+ */
84
+ export function declaredAnthropicServerTools(raw: unknown): Rec[] | undefined {
85
+ if (!isRec(raw) || !Array.isArray(raw.tools)) return undefined;
86
+ const tools = raw.tools.filter((t): t is Rec => isRec(t) && typeof t.type === "string" && /^web_(search|fetch)_\d+$/.test(t.type));
87
+ return tools.length > 0 ? tools : undefined;
88
+ }
89
+
79
90
  export function toolChoiceToResponses(choice: unknown, body: Rec): void {
80
91
  if (!isRec(choice)) return;
81
92
  if (choice.disable_parallel_tool_use === true) body.parallel_tool_calls = false;
@@ -24,7 +24,12 @@ import { createTranslatorBudget, type TranslatorBudget } from "../lib/translator
24
24
 
25
25
 
26
26
 
27
- function imageBlockToInputImage(block: Rec): Rec | null {
27
+ /**
28
+ * The reference an Anthropic image block carries, as the Responses `image_url` string the bridge
29
+ * builds from it, or `null` for a source this route does not read. A file-backed source refuses.
30
+ * Shared with the direct Messages decoder (`src/protocols/codecs/messages-ir.ts`).
31
+ */
32
+ export function anthropicImageBlockUrl(block: Rec): string | null {
28
33
  const source = block.source;
29
34
  if (!isRec(source)) return null;
30
35
  if (source.type === "file") {
@@ -34,20 +39,25 @@ function imageBlockToInputImage(block: Rec): Rec | null {
34
39
  }
35
40
  if (source.type === "base64" && typeof source.data === "string") {
36
41
  const media = typeof source.media_type === "string" ? source.media_type : "image/png";
37
- return { type: "input_image", image_url: `data:${media};base64,${source.data}` };
42
+ return `data:${media};base64,${source.data}`;
38
43
  }
39
44
  if (source.type === "url" && typeof source.url === "string") {
40
- return { type: "input_image", image_url: source.url };
45
+ return source.url;
41
46
  }
42
47
  return null;
43
48
  }
44
49
 
45
- function documentTitle(block: Rec): string | undefined {
50
+ function imageBlockToInputImage(block: Rec): Rec | null {
51
+ const url = anthropicImageBlockUrl(block);
52
+ return url === null ? null : { type: "input_image", image_url: url };
53
+ }
54
+
55
+ export function documentTitle(block: Rec): string | undefined {
46
56
  return typeof block.title === "string" && block.title.length > 0 ? block.title : undefined;
47
57
  }
48
58
 
49
- /** An Anthropic base64 document as the Responses `input_file` block that carries its bytes. */
50
- function documentBlockToInputFile(block: Rec): Rec | null {
59
+ /** A base64 document's bytes as a `data:` URL plus its title, or `null` for any other source. */
60
+ export function anthropicDocumentData(block: Rec): { fileData: string; title?: string } | null {
51
61
  const source = block.source;
52
62
  if (!isRec(source) || source.type !== "base64") return null;
53
63
  const mediaType = typeof source.media_type === "string" && source.media_type.length > 0
@@ -55,10 +65,17 @@ function documentBlockToInputFile(block: Rec): Rec | null {
55
65
  : "application/octet-stream";
56
66
  if (typeof source.data !== "string" || source.data.length === 0) return null;
57
67
  const title = documentTitle(block);
68
+ return { fileData: `data:${mediaType};base64,${source.data}`, ...(title !== undefined ? { title } : {}) };
69
+ }
70
+
71
+ /** An Anthropic base64 document as the Responses `input_file` block that carries its bytes. */
72
+ function documentBlockToInputFile(block: Rec): Rec | null {
73
+ const document = anthropicDocumentData(block);
74
+ if (!document) return null;
58
75
  return {
59
76
  type: "input_file",
60
- file_data: `data:${mediaType};base64,${source.data}`,
61
- ...(title !== undefined ? { filename: title } : {}),
77
+ file_data: document.fileData,
78
+ ...(document.title !== undefined ? { filename: document.title } : {}),
62
79
  };
63
80
  }
64
81
 
@@ -145,7 +162,7 @@ const NO_ELISION: SkillElisionContext = { callIds: new Set(), names: [] };
145
162
  * `Base directory for this skill: <dir>/<skill-name>`. Stub that block when the
146
163
  * directory basename matches a blocked skill.
147
164
  */
148
- function maybeElideSkillText(text: string, names: readonly string[]): string {
165
+ export function maybeElideSkillText(text: string, names: readonly string[]): string {
149
166
  if (names.length === 0 || text.length < SKILL_ELISION_MIN_CHARS) return text;
150
167
  if (!text.startsWith(SKILL_TEXT_MARKER)) return text;
151
168
  const pathStart = SKILL_TEXT_MARKER.length;
@@ -162,14 +179,14 @@ function maybeElideSkillText(text: string, names: readonly string[]): string {
162
179
  + "(claudeCode.blockedSkills). The skill is loaded; answer from general knowledge instead of citing the bundle.";
163
180
  }
164
181
 
165
- function skillElisionStub(callId: string): string {
182
+ export function skillElisionStub(callId: string): string {
166
183
  return "[opencodex] Skill document bundle elided for routed models (claudeCode.blockedSkills). "
167
184
  + `The skill loaded, but its reference documents were removed to save context (call ${callId}). `
168
185
  + "Answer from general knowledge instead of citing the bundle.";
169
186
  }
170
187
 
171
188
  /** Collect Skill-tool call ids whose input names a blocked skill. */
172
- function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly string[]): Set<string> {
189
+ export function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly string[]): Set<string> {
173
190
  const ids = new Set<string>();
174
191
  if (blocked.length === 0) return ids;
175
192
  const needles = blocked.map(name => name.toLowerCase()).filter(name => name.length > 0);
@@ -222,7 +239,7 @@ export function anthropicBodyElidesBlockedSkill(body: unknown, cc?: Pick<OcxClau
222
239
  * prefix and rotating the Desktop `prompt_cache_key` fallback below on every turn
223
240
  * (#4148).
224
241
  */
225
- function systemMessageText(content: unknown): string {
242
+ export function systemMessageText(content: unknown): string {
226
243
  if (typeof content === "string") return content;
227
244
  if (!Array.isArray(content)) return "";
228
245
  const parts: string[] = [];
@@ -370,6 +387,39 @@ function canonicalJson(value: unknown): string {
370
387
  /** Provenance of the generated prompt_cache_key (never serialized into the wire body). */
371
388
  export type ClaudeCacheKeySource = "metadata" | "system" | null;
372
389
 
390
+ /**
391
+ * The translated lane places its own prompt-cache breakpoints, so the caller's are dropped;
392
+ * their TTL is the one thing worth keeping. When every caller breakpoint asks for `ttl: "1h"`
393
+ * (Claude Code on a subscription marks them so) this maps to `cacheRetention: "long"`. A mixed
394
+ * caller (1h prefix, 5m tail) stays on the default: "long" also makes the moving per-turn
395
+ * breakpoint 1h, which bills writes the caller asked to keep at the 5m rate at the 1h rate.
396
+ * Read out of body: the Responses body has no field for it.
397
+ */
398
+ export function callerCacheRetention(raw: unknown): "long" | undefined {
399
+ if (!isRec(raw)) return undefined;
400
+ // Top-level automatic caching is a breakpoint too, and content blocks nest (tool_result,
401
+ // search_result, document sources), so every block container is walked. Tool inputs and
402
+ // schemas are caller data, not blocks: a `cache_control` key there is not a breakpoint.
403
+ const ttls: unknown[] = [];
404
+ const visit = (node: unknown, depth: number): void => {
405
+ if (depth > 16) return;
406
+ if (Array.isArray(node)) { for (const item of node) visit(item, depth + 1); return; }
407
+ if (!isRec(node)) return;
408
+ if (isRec(node.cache_control)) ttls.push(node.cache_control.ttl);
409
+ for (const [key, value] of Object.entries(node)) {
410
+ if (key !== "cache_control" && key !== "input" && key !== "input_schema"
411
+ && (Array.isArray(value) || isRec(value))) visit(value, depth + 1);
412
+ }
413
+ };
414
+ if (isRec(raw.cache_control)) ttls.push(raw.cache_control.ttl);
415
+ visit(raw.system, 0);
416
+ visit(raw.tools, 0);
417
+ visit(raw.messages, 0);
418
+ return ttls.length > 0 && ttls.every(ttl => ttl === "1h")
419
+ ? "long"
420
+ : undefined;
421
+ }
422
+
373
423
  export interface ClaudeInboundTranslation {
374
424
  body: Rec;
375
425
  cacheKeySource: ClaudeCacheKeySource;
@@ -410,19 +460,10 @@ function translateAnthropicRequest(
410
460
  cc: OcxClaudeCodeConfig | undefined,
411
461
  budget: TranslatorBudget,
412
462
  ): ClaudeInboundTranslation {
413
- if (!isRec(raw)) throw new AnthropicRequestError("request body must be a JSON object");
414
- if (typeof raw.model !== "string" || raw.model.length === 0) {
415
- throw new AnthropicRequestError("model is required");
416
- }
417
- if (!Array.isArray(raw.messages) || raw.messages.length === 0) {
418
- throw new AnthropicRequestError("messages must be a non-empty array");
419
- }
463
+ assertAnthropicRequestShape(raw);
420
464
 
421
465
  const input: Rec[] = [];
422
466
  const nativeReasoningReplay = new Map<string, string>();
423
- const systemParts: string[] = [];
424
- const topLevelSystem = systemToInstructions(raw.system);
425
- if (topLevelSystem !== undefined) systemParts.push(topLevelSystem);
426
467
  const blockedNames = effectiveBlockedSkillNames(cc);
427
468
  const elide: SkillElisionContext = {
428
469
  callIds: blockedSkillCallIds(raw.messages, blockedNames),
@@ -451,36 +492,99 @@ function translateAnthropicRequest(
451
492
  stream: raw.stream === true,
452
493
  };
453
494
 
495
+ const system = anthropicSystemLayout(raw.system, cc);
496
+ if (system.instructions !== undefined) body.instructions = system.instructions;
497
+ if (system.dynamicNotice !== undefined) {
498
+ input.push({
499
+ type: "message",
500
+ role: "user",
501
+ content: [{ type: "input_text", text: system.dynamicNotice }],
502
+ });
503
+ }
504
+
505
+ const tools = toolsToResponses(raw.tools);
506
+ if (tools) body.tools = tools;
507
+ const cacheKeySource = applyAnthropicSettings(raw, body, system.cacheSystem, tools);
508
+ return { body, cacheKeySource, nativeReasoningReplay };
509
+ }
510
+
511
+ function assertAnthropicRequestShape(raw: unknown): asserts raw is Rec & { model: string; messages: unknown[] } {
512
+ if (!isRec(raw)) throw new AnthropicRequestError("request body must be a JSON object");
513
+ if (typeof raw.model !== "string" || raw.model.length === 0) {
514
+ throw new AnthropicRequestError("model is required");
515
+ }
516
+ if (!Array.isArray(raw.messages) || raw.messages.length === 0) {
517
+ throw new AnthropicRequestError("messages must be a non-empty array");
518
+ }
519
+ }
520
+
521
+ /** Where the top-level `system` lands on the bridge body, and what the cache-cohort key hashes. */
522
+ export interface AnthropicSystemLayout {
523
+ /** The Responses `instructions`, when there are any. */
524
+ instructions?: string;
525
+ /** A dynamic notice `stabilizePromptCache` moved out of the system text into a trailing user turn. */
526
+ dynamicNotice?: string;
527
+ cacheSystem: string | string[];
528
+ }
529
+
530
+ /** The system text placement `claudeCode.stabilizePromptCache` decides; shared with the direct decoder. */
531
+ export function anthropicSystemLayout(system: unknown, cc: OcxClaudeCodeConfig | undefined): AnthropicSystemLayout {
532
+ const systemParts: string[] = [];
533
+ const topLevelSystem = systemToInstructions(system);
534
+ if (topLevelSystem !== undefined) systemParts.push(topLevelSystem);
454
535
  const joinedSystem = systemParts.length > 0 ? systemParts.join("\n\n") : "";
455
536
  const stabilizePromptCache = cc?.stabilizePromptCache === true;
456
537
  // Desktop fallback hashes raw systemParts unless the caller opted into
457
538
  // harness cleanup. Opt-in then hashes the same string as body.instructions.
458
- let cacheSystem: string | string[] = systemParts;
459
- if (joinedSystem) {
460
- if (stabilizePromptCache) {
461
- // Claude Code appends growing <total_tokens>N tokens left</total_tokens>
462
- // footers (and occasional TaskCreate nudges) into system text. That churn
463
- // breaks Muse/Go prefix cache on the Responses instructions prefix even
464
- // when tools stay stable. Relocation is caller-opted, not inferred from
465
- // a matching suffix or metadata.user_id.
466
- const stabilized = stabilizeClaudeInstructionsForPromptCache(joinedSystem);
467
- if (stabilized.instructions) body.instructions = stabilized.instructions;
468
- // Token footers are dropped; append nothing if no other notice remains.
469
- if (stabilized.dynamicNotice) {
470
- input.push({
471
- type: "message",
472
- role: "user",
473
- content: [{ type: "input_text", text: stabilized.dynamicNotice }],
474
- });
475
- }
476
- cacheSystem = stabilized.instructions;
477
- } else {
478
- body.instructions = joinedSystem;
479
- }
480
- }
539
+ if (!joinedSystem) return { cacheSystem: systemParts };
540
+ if (!stabilizePromptCache) return { instructions: joinedSystem, cacheSystem: systemParts };
541
+ // Claude Code appends growing <total_tokens>N tokens left</total_tokens>
542
+ // footers (and occasional TaskCreate nudges) into system text. That churn
543
+ // breaks Muse/Go prefix cache on the Responses instructions prefix even
544
+ // when tools stay stable. Relocation is caller-opted, not inferred from
545
+ // a matching suffix or metadata.user_id.
546
+ const stabilized = stabilizeClaudeInstructionsForPromptCache(joinedSystem);
547
+ return {
548
+ ...(stabilized.instructions ? { instructions: stabilized.instructions } : {}),
549
+ // Token footers are dropped; append nothing if no other notice remains.
550
+ ...(stabilized.dynamicNotice ? { dynamicNotice: stabilized.dynamicNotice } : {}),
551
+ cacheSystem: stabilized.instructions,
552
+ };
553
+ }
481
554
 
482
- const tools = toolsToResponses(raw.tools);
483
- if (tools) body.tools = tools;
555
+ /**
556
+ * The request settings of a Messages body as a Responses body with no conversation: `input` is
557
+ * empty and there are no `instructions` or `tools`, but the prompt-cache key is still derived
558
+ * from the system text and translated tool catalog exactly as the bridge derives it. The
559
+ * Antigravity direct lane hands this to the Responses core while the conversation is decoded
560
+ * straight into the IR (`src/protocols/codecs/messages-ir.ts`); both bodies are completed by
561
+ * `applyAnthropicSettings`. It validates the same request shape, but a conversation error the
562
+ * bridge would report first is not looked for here: a caller that gets a throw must translate
563
+ * the full body to learn the bridge's answer.
564
+ */
565
+ export function anthropicSettingsTranslation(
566
+ raw: unknown,
567
+ cc?: OcxClaudeCodeConfig,
568
+ ): { body: Rec; cacheKeySource: ClaudeCacheKeySource } {
569
+ assertAnthropicRequestShape(raw);
570
+ const body: Rec = {
571
+ model: resolveInboundModel(raw.model, cc),
572
+ input: [],
573
+ store: false,
574
+ stream: raw.stream === true,
575
+ };
576
+ const system = anthropicSystemLayout(raw.system, cc);
577
+ const cacheKeySource = applyAnthropicSettings(raw, body, system.cacheSystem, toolsToResponses(raw.tools));
578
+ return { body, cacheKeySource };
579
+ }
580
+
581
+ /** Everything after the conversation and tool catalog: choice, limits, format, cache key, reasoning. */
582
+ function applyAnthropicSettings(
583
+ raw: Rec,
584
+ body: Rec,
585
+ cacheSystem: string | string[],
586
+ tools: Rec[] | undefined,
587
+ ): ClaudeCacheKeySource {
484
588
  toolChoiceToResponses(raw.tool_choice, body);
485
589
 
486
590
  if (typeof raw.max_tokens === "number") body.max_output_tokens = raw.max_tokens;
@@ -505,7 +609,7 @@ function translateAnthropicRequest(
505
609
  // per-session key with a bounded length/charset.
506
610
  body.prompt_cache_key = userIdHash.slice(0, 32);
507
611
  cacheKeySource = "metadata";
508
- } else if (systemParts.length > 0) {
612
+ } else if (typeof cacheSystem === "string" || cacheSystem.length > 0) {
509
613
  // Claude Desktop sends no metadata.user_id (H1, devlog 130): without any key the
510
614
  // ChatGPT/OpenAI backends reported cached_tokens:0 on every turn. Fall back to a
511
615
  // cache-cohort hash (devlog 260712 B4 + Pro review 012): fingerprint what the
@@ -526,7 +630,7 @@ function translateAnthropicRequest(
526
630
  version: 2,
527
631
  model: body.model,
528
632
  system: cacheSystem,
529
- tools: Array.isArray(body.tools) ? body.tools : [],
633
+ tools: tools ?? [],
530
634
  }))
531
635
  .digest("hex").slice(0, 32);
532
636
  cacheKeySource = "system";
@@ -556,7 +660,7 @@ function translateAnthropicRequest(
556
660
  body.include = ["reasoning.encrypted_content"];
557
661
  }
558
662
 
559
- return { body, cacheKeySource, nativeReasoningReplay };
663
+ return cacheKeySource;
560
664
  }
561
665
 
562
666
  /**
@@ -197,7 +197,9 @@ export interface AuthorityOptions {
197
197
  permittedDnsNames?: readonly string[];
198
198
  /** With permittedDnsNames: also exclude every IP address (default true). */
199
199
  excludeAllIpAddresses?: boolean;
200
- }
200
+ /** Restrict this authority to TLS server authentication; legacy callers keep their existing scope. */
201
+ serverAuthOnly?: boolean;
202
+ }
201
203
 
202
204
  export function createCertificateAuthority(options: AuthorityOptions): LocalInterceptCa {
203
205
  const validityDays = options.validityDays ?? CA_VALIDITY_DAYS;
@@ -217,6 +219,7 @@ export function createCertificateAuthority(options: AuthorityOptions): LocalInte
217
219
  // keyCertSign | cRLSign
218
220
  extension(OID.keyUsage, true, bitString(Uint8Array.of(0x06), 1)),
219
221
  extension(OID.subjectKeyIdentifier, false, octetString(keyIdentifier(publicKey))),
222
+ ...(options.serverAuthOnly ? [extension(OID.extendedKeyUsage, true, sequence(objectIdentifier(OID.serverAuth)))] : []),
220
223
  ...(options.permittedDnsNames?.length
221
224
  ? [extension(OID.nameConstraints, true, nameConstraints(options.permittedDnsNames, options.excludeAllIpAddresses !== false))]
222
225
  : []),
@@ -234,6 +237,10 @@ export function createLocalInterceptCa(): LocalInterceptCa {
234
237
  return createCertificateAuthority({ commonName: CLAUDE_INTERCEPT_CA_COMMON_NAME });
235
238
  }
236
239
 
240
+ export function isServerAuthOnlyCertificate(certificate: X509Certificate): boolean {
241
+ return certificate.keyUsage?.length === 1 && certificate.keyUsage[0] === OID.serverAuth;
242
+ }
243
+
237
244
  /**
238
245
  * Test hook for trust-boundary suites: mint a self-signed authority carrying an arbitrary list of
239
246
  * DER-encoded Extension items so adversarial profiles still bear a valid signature. Production
@@ -2,12 +2,13 @@ import { ensurePickerCa, type PickerCa } from "./picker-ca";
2
2
  import { drainPendingPickerCaUntrust } from "./picker-ca-cleanup";
3
3
  import type { PickerCaStore } from "./picker-ca-store";
4
4
  import type { SecurityRunner } from "./picker-trust";
5
+ import { supportsDesktopPicker } from "../desktop-picker-platform";
5
6
 
6
7
  /** Internal activation seam: finish legacy cleanup before a persistent issuer is usable. */
7
8
  export async function preparePersistentPickerAuthority(options: {
8
9
  configDir: string; store?: PickerCaStore; security?: SecurityRunner; platform: NodeJS.Platform;
9
10
  }): Promise<PickerCa> {
10
- if (options.platform !== "darwin") throw new Error("picker_ca_unsupported_platform");
11
+ if (!supportsDesktopPicker(options.platform)) throw new Error("picker_ca_unsupported_platform");
11
12
  const drain = () => drainPendingPickerCaUntrust(options.configDir, options.security, options.platform);
12
13
  if (!await drain()) throw new Error("picker_ca_pending_untrust");
13
14
  const ca = ensurePickerCa(options.configDir, { rotation: "startup", persistent: true, store: options.store });