@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
@@ -7,6 +7,9 @@ import type { ResponsesEffects } from "./response-effects";
7
7
  import type { ResponsesSendBudget } from "./request-send-budget";
8
8
  import { formatErrorResponse } from "../../bridge";
9
9
  import { planWebSearch, buildWebSearchTool, runWithWebSearch } from "../../web-search";
10
+ import { createRegisteredAdvisorPlan } from "./advisor-plan-slot";
11
+ import { ADVISOR_TOOL_NAME } from "./advisor-slot";
12
+ import { buildToolBridgeMaps } from "./collaboration";
10
13
  import {
11
14
  planImageBridge,
12
15
  planVideoBridge,
@@ -18,7 +21,7 @@ import {
18
21
  clampImageMaxRounds,
19
22
  } from "../../images";
20
23
  import type { ProviderAdapter } from "../../adapters/base";
21
- import type { OcxParsedRequest } from "../../types";
24
+ import type { OcxParsedRequest, OcxUsage } from "../../types";
22
25
  import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
23
26
  import {
24
27
  isGenericOAuthFailoverEnabled,
@@ -30,6 +33,8 @@ import {
30
33
  } from "../../oauth/generic-account-failover";
31
34
  import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
32
35
  import { isNonReplayableResponse } from "../../lib/upstream-retry";
36
+ import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
37
+ import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
33
38
  import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
34
39
  import { persistKiroAccountState } from "../../providers/kiro-account-state-disk";
35
40
  import { anthropicPoolCooldownResponse, readDisplaySafeErrorText } from "./core-errors";
@@ -45,6 +50,7 @@ import { providerFetch } from "./fetch-helpers";
45
50
  import type { AttemptRecoveryKind } from "../../usage/log";
46
51
  import { recordAdapterReasoning, recordAdapterTier } from "../request-log";
47
52
  import { normalizeLogConversationId } from "../request-log-conversation";
53
+ import { attemptAccountChanged } from "../request-log-account-rotation";
48
54
  import { rememberResponseState } from "../../responses/state";
49
55
  import { trackStreamLifetime } from "../lifecycle";
50
56
  import { isAntigravityRawValidationRefusal } from "./antigravity-validation-refusal";
@@ -60,6 +66,8 @@ export async function executeResponsesSidecars(
60
66
  | "inboundWire"
61
67
  | "selectedForwardHeaders"
62
68
  | "translatorBudget"
69
+ | "clientToolCatalogRestriction"
70
+ | "toolBridgeMaps"
63
71
  | "rememberKiroDeliveredFinalAnswer"
64
72
  | "responseStateOptions"
65
73
  >,
@@ -169,6 +177,25 @@ export async function executeResponsesSidecars(
169
177
  }
170
178
  }
171
179
 
180
+ // Advisor sidecar plan (optional subsystem; null when disabled, unconfigured, or when this
181
+ // request IS an advisor loopback consultation — the recursion fence). The plan carries
182
+ // request-scoped state only; preflight keeps fixed policy separate from user-role advice
183
+ // before the worker is dispatched. Registration seam: the core path sees only
184
+ // `parsed._advisorGuard`; only an activated factory can load the optional implementation.
185
+ const advisorPlan = options.advisorInternal === true
186
+ ? null
187
+ : await createRegisteredAdvisorPlan({
188
+ config,
189
+ workerIdentity: `${route.modelId} (provider ${route.providerName})`,
190
+ workerModelId: route.modelId,
191
+ abortSignal: options.abortSignal,
192
+ });
193
+ // Preflight only on real worker turns: a routed-compaction turn is a summarization request,
194
+ // and injecting advice into it would pollute the summary Codex replaces its history with.
195
+ if (advisorPlan && !routedCompaction) {
196
+ await advisorPlan.preflightInject(parsed);
197
+ }
198
+
172
199
  // Image / web-search sidecars: plan once, then dispatch with runTurn-aware priority.
173
200
  // Routed-compaction turns must NOT hit the image bridge: compaction clears tools/_webSearch but
174
201
  // leaves _imageGeneration, so planImageBridge would activate and return a normal Responses
@@ -198,6 +225,20 @@ export async function executeResponsesSidecars(
198
225
  // A single request may rotate once for a verification refusal. Keep this separate from the
199
226
  // broader OAuth failover count: a sidecar turn can also spend that count on rate-limit recovery.
200
227
  let antigravityValidationRotationAttempted = false;
228
+ // A replacement credential whose Copilot Auto negotiation leaves the key's scope ends the turn.
229
+ // The loops only see "no rotation" and fail with the original, replayable 429, so the refusal
230
+ // is held here and answers in place of that failure.
231
+ let sidecarScopeDenial: AdmissionModelDeniedError | undefined;
232
+ const noteSidecarScopeDenial = (error: unknown): void => {
233
+ const refusal = unwrapUpstreamRetryEvidenceError(error);
234
+ if (refusal instanceof AdmissionModelDeniedError) sidecarScopeDenial ??= refusal;
235
+ };
236
+ const sidecarScopeRefusal = (response: Response): Response | undefined => {
237
+ if (!sidecarScopeDenial || response.ok) return undefined;
238
+ void response.body?.cancel().catch(() => {});
239
+ releaseSearchProbeLease();
240
+ return admissionModelDeniedResponse(sidecarScopeDenial);
241
+ };
201
242
  /** Admit a replacement credential, or leave the loop's original refusal authoritative. */
202
243
  const rotateSidecarProviderOn429 = async (
203
244
  retryAfter: string | null,
@@ -264,7 +305,8 @@ export async function executeResponsesSidecars(
264
305
  return null;
265
306
  }
266
307
  transportState.genericFailovers += 1;
267
- } catch {
308
+ } catch (error) {
309
+ noteSidecarScopeDenial(error);
268
310
  hop.permit?.release();
269
311
  return null;
270
312
  }
@@ -284,7 +326,10 @@ export async function executeResponsesSidecars(
284
326
  } else if (rotated) {
285
327
  route.provider = rotated;
286
328
  try { await resolveCopilotSelection(retryParsed ?? parsed); }
287
- catch { return null; } // Keep the sidecar's original refusal when replacement negotiation fails.
329
+ catch (error) { // Keep the sidecar's original refusal when replacement negotiation fails.
330
+ noteSidecarScopeDenial(error);
331
+ return null;
332
+ }
288
333
  } else if (
289
334
  // A POSITIVE gate, not an early return. An early `return null` here made every later arm
290
335
  // unreachable: Anthropic never has a genericFailoverAccountId (isGenericFailoverProvider
@@ -326,7 +371,8 @@ export async function executeResponsesSidecars(
326
371
  return null;
327
372
  }
328
373
  if (route.providerName === "kiro") transportState.genericFailovers += 1;
329
- } catch {
374
+ } catch (error) {
375
+ noteSidecarScopeDenial(error);
330
376
  hop.permit?.release();
331
377
  return null;
332
378
  }
@@ -373,7 +419,8 @@ export async function executeResponsesSidecars(
373
419
  hop.permit?.release();
374
420
  return null;
375
421
  }
376
- recoveryKind = originalResponse?.status === 401 ? "oauth-401" : "anthropic-oauth-429";
422
+ recoveryKind = originalResponse?.status === 401 ? "oauth-401"
423
+ : originalResponse?.status === 403 ? "oauth-account-403" : "anthropic-oauth-429";
377
424
  sidecarBudget.ownCredentialHop(hop.permit);
378
425
  } else {
379
426
  // No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
@@ -407,6 +454,15 @@ export async function executeResponsesSidecars(
407
454
  });
408
455
  return { adapter: rotatedAdapter, recoveryKind };
409
456
  };
457
+ // A loop's hidden rounds before a rotation were spent by the account it left. Bill them to that
458
+ // account's attempt while it is still open; the next send seals it. Declined when that send
459
+ // keeps the account (no new attempt), so the loop's final total still carries them.
460
+ const billRotatedUsage = (usage: OcxUsage): boolean => {
461
+ const attempt = logCtx.activeAttempt;
462
+ if (!attempt || !attemptAccountChanged(attempt.accountLogLabel, logCtx.accountLogLabel, attempt.provider, logCtx.provider)) return false;
463
+ transportState.bindKeyUsageFromBridge(usage);
464
+ return true;
465
+ };
410
466
  if ((imgPlan || vidPlan) && wsPlan) {
411
467
  // Web search takes priority when both are active — the media bridge cannot run
412
468
  // alongside runWithWebSearch. Surface a runtime signal so the user knows their
@@ -464,6 +520,7 @@ export async function executeResponsesSidecars(
464
520
  );
465
521
  const imgResponse = await runWithImageBridge({
466
522
  parsed, adapter: transportState.adapter,
523
+ clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
467
524
  incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget, ...(options.comboInitialSend ? { providerFetch: imageProviderFetch } : {}), ...sidecarBudget.incomingMeta },
468
525
  onProducerStart: sidecarBudget.takeProducerOwnership,
469
526
  onProducerEnd: sidecarBudget.release,
@@ -505,10 +562,12 @@ export async function executeResponsesSidecars(
505
562
  transportState.bindKeyUsageFromBridge(usage);
506
563
  },
507
564
  on429: rotateSidecarProviderOn429,
565
+ onRotatedUsage: billRotatedUsage,
508
566
  retryOn429Policy: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro")
509
567
  ? null : rateLimitRetryPolicyFor(route.provider),
510
568
  onFirstOutput: noteSidecarOutput,
511
569
  ...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
570
+ terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
512
571
  onCompletedResponse: (response, providerState) => {
513
572
  const served = transportState.replayOAuthCredentialSnapshot;
514
573
  if (route.providerName === "kiro" && response.status === "completed" && served
@@ -527,6 +586,8 @@ export async function executeResponsesSidecars(
527
586
  notifyResponseComplete(response);
528
587
  },
529
588
  });
589
+ const imgScopeRefusal = sidecarScopeRefusal(imgResponse);
590
+ if (imgScopeRefusal) return imgScopeRefusal;
530
591
  if (imgResponse.body) {
531
592
  const imgTurnAc = new AbortController();
532
593
  imgTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
@@ -557,6 +618,7 @@ export async function executeResponsesSidecars(
557
618
  })(input, init)) as typeof globalThis.fetch;
558
619
  const loopResponse = await runWithWebSearch({
559
620
  parsed, adapter: transportState.adapter,
621
+ clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
560
622
  onIterationEnd: sidecarBudget.releaseUnsentHop,
561
623
  fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
562
624
  dispatchOverride: oauthDispatch(request, iterParsed),
@@ -587,6 +649,7 @@ export async function executeResponsesSidecars(
587
649
  settings: wsPlan.settings,
588
650
  maxSearches: wsPlan.maxSearches,
589
651
  forceEmptyResponseId: true,
652
+ terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
590
653
  abortSignal: options.abortSignal,
591
654
  onFirstOutput: noteSidecarOutput,
592
655
  onRequestBuilt: request => {
@@ -605,6 +668,7 @@ export async function executeResponsesSidecars(
605
668
  stallTimeoutSec: wsPlan.stallTimeoutSec,
606
669
  streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
607
670
  on429: rotateSidecarProviderOn429,
671
+ onRotatedUsage: billRotatedUsage,
608
672
  ...(!options.comboInitialSend && route.providerName === "google-antigravity" ? { onIterationEnd: () => {
609
673
  // The next Antigravity adapter dispatch claims this permit. If request construction,
610
674
  // admission or cancellation stopped it first, return the unused send allowance.
@@ -622,6 +686,8 @@ export async function executeResponsesSidecars(
622
686
  notifyResponseComplete(response);
623
687
  },
624
688
  });
689
+ const wsScopeRefusal = sidecarScopeRefusal(loopResponse);
690
+ if (wsScopeRefusal) return wsScopeRefusal;
625
691
  // A routed Anthropic model whose last pool account refused on spent quota fails the loop's
626
692
  // first iteration with a generic 429 before any search runs. Answer the usage limit instead,
627
693
  // as the main path does (#6991), so Codex stops rather than retrying the whole turn.
@@ -645,5 +711,32 @@ export async function executeResponsesSidecars(
645
711
  return wsResponse;
646
712
  }
647
713
 
714
+ // Advisor synthetic tool + stream guard: attach ONLY when this function is about to hand the
715
+ // request back to the normal translated exchange (no sidecar loop claimed the turn, not a
716
+ // run-turn adapter, not compaction). The web-search/image loops own their own event handling;
717
+ // run-turn adapters execute their own loop; both would leak the synthetic tool call they
718
+ // cannot intercept. Preflight (above) still applies to those paths — only the tool does not.
719
+ if (advisorPlan && !routedCompaction && !transportState.adapter.runTurn && !wsPlan && !imgPlan && !vidPlan) {
720
+ // A client-declared tool named "advisor" would collide with the synthetic injection (one
721
+ // wire name, two schemas); the synthetic runtime owns the name for this turn.
722
+ parsed.context.tools = [
723
+ ...(parsed.context.tools ?? []).filter(t => !t.advisor && t.name !== ADVISOR_TOOL_NAME),
724
+ advisorPlan.tool,
725
+ ];
726
+ // The advisor tool joined AFTER prepare computed the bridge maps; recompute so the tool is
727
+ // declared (undeclared-tool guard, tool_choice mapping, schema repair) on this turn.
728
+ //
729
+ // Rebuild WITHOUT the budget: prepare already charged every tool in the catalog, and
730
+ // buildToolBridgeMaps charges each name it registers, so re-passing the budget would bill the
731
+ // whole catalog a second time and can trip `translation_buffer_limit` for a large MCP catalog.
732
+ // Charge only the one entry that is genuinely new — the synthetic advisor tool is a bare
733
+ // function tool, so it retains its wire name and its bare name (collaboration.ts:187, :233).
734
+ requestState.toolBridgeMaps = buildToolBridgeMaps(parsed);
735
+ translatorBudget.chargeRetained(new TextEncoder().encode(ADVISOR_TOOL_NAME).byteLength * 2, {
736
+ kind: "retained_collectors",
737
+ });
738
+ advisorPlan.attachGuard(parsed);
739
+ }
740
+
648
741
  return undefined;
649
742
  }
@@ -1,6 +1,6 @@
1
+ import { assistantReplayContent } from "../../responses/assistant-replay";
1
2
  import type {
2
3
  AdapterEvent,
3
- OcxAssistantContentPart,
4
4
  OcxAssistantMessage,
5
5
  OcxParsedRequest,
6
6
  OcxUsage,
@@ -121,21 +121,7 @@ export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly A
121
121
  }
122
122
 
123
123
  function assistantMessageFromEvents(events: readonly AdapterEvent[], timestamp: number): OcxAssistantMessage | undefined {
124
- let text = "";
125
- let thinking = "";
126
- let signature: string | undefined;
127
- const redacted: string[] = [];
128
- for (const event of events) {
129
- if (event.type === "text_delta") text += event.text;
130
- else if (event.type === "thinking_delta") thinking += event.thinking;
131
- else if (event.type === "thinking_signature") signature = event.signature;
132
- else if (event.type === "redacted_thinking") redacted.push(event.data);
133
- }
134
- const content: OcxAssistantContentPart[] = [];
135
- if (thinking || signature || redacted.length > 0) {
136
- content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
137
- }
138
- if (text) content.push({ type: "text", text });
124
+ const content = assistantReplayContent(events);
139
125
  if (content.length === 0) return undefined;
140
126
  return { role: "assistant", content, timestamp };
141
127
  }
@@ -170,7 +156,8 @@ export function isTerminalGuardPassthroughOnly(event: AdapterEvent): boolean {
170
156
  return event.type === "heartbeat" || event.type === "tool_call_delta";
171
157
  }
172
158
 
173
- function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
159
+ /** Merge two reported usages (canonical Responses convention). Shared with the advisor guard. */
160
+ export function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
174
161
  if (!first) return second;
175
162
  if (!second) return first;
176
163
  const sumOptional = (key: keyof OcxUsage): number | undefined => {
@@ -1,3 +1,4 @@
1
+ import { UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
1
2
  import {
2
3
  collectAmbiguousDottedAliases,
3
4
  dottedAliasIsUnambiguous,
@@ -78,7 +79,7 @@ const NAMELESS_TOOL_SPEC_TYPES = new Set([
78
79
  /** An upstream-supplied name reaches the error message; keep it bounded. */
79
80
  const MAX_REPORTED_NAME_CHARS = 100;
80
81
 
81
- export const UNDECLARED_TOOL_CALL_ERROR_CODE = "undeclared_tool_call";
82
+ export { UNDECLARED_TOOL_CALL_ERROR_CODE };
82
83
 
83
84
  function isPlainObject(value: unknown): value is Record<string, unknown> {
84
85
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -370,6 +371,24 @@ export function hasExplicitWireToolCatalog(body: unknown): boolean {
370
371
  );
371
372
  }
372
373
 
374
+ /**
375
+ * Whether the caller declared an explicitly EMPTY catalog — `tools: []` or an
376
+ * `additional_tools` item with `tools: []`. Unlike an absent or partial catalog this is an
377
+ * unambiguous deny-all: no deferred registry can exist under it, so even deferred wires
378
+ * enforce it. A hosted-only catalog is NOT empty — it still defers like an absent one.
379
+ */
380
+ export function hasExplicitEmptyToolCatalog(body: unknown): boolean {
381
+ if (!isPlainObject(body)) return false;
382
+ if (Array.isArray(body.tools) && body.tools.length === 0) return true;
383
+ if (!Array.isArray(body.input)) return false;
384
+ return body.input.some(item =>
385
+ isPlainObject(item)
386
+ && item.type === "additional_tools"
387
+ && Array.isArray(item.tools)
388
+ && item.tools.length === 0
389
+ );
390
+ }
391
+
373
392
  /**
374
393
  * Evaluates whether an individual output item represents an undeclared tool call.
375
394
  *
@@ -703,7 +722,6 @@ function failedBlocks(name: string, newline: string): readonly string[] {
703
722
  * @param providerExecutedCallTypes - Call types executed by the provider.
704
723
  * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
705
724
  * @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
706
- * @param enforceDeclaredToolNames - Refuse undeclared calls; normalization remains active when false.
707
725
  * @returns An SSE block rewrite function.
708
726
  */
709
727
  export function createUndeclaredToolCallGuardBlockRewrite(
@@ -712,8 +730,11 @@ export function createUndeclaredToolCallGuardBlockRewrite(
712
730
  providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
713
731
  declaredBare?: ReadonlySet<string>,
714
732
  declaredCustom?: ReadonlySet<string>,
715
- enforceDeclaredToolNames = true,
733
+ options?: { refuseUndeclared?: boolean },
716
734
  ): SseBlockRewrite {
735
+ // Deferred-catalog wires (Chat/Anthropic) keep the default-namespace normalization — the
736
+ // client can only resolve the bare name it declared — while the refusal itself stays off.
737
+ const refuse = options?.refuseUndeclared !== false;
717
738
  let tripped = false;
718
739
  return (block: string) => {
719
740
  if (tripped) return [];
@@ -725,12 +746,12 @@ export function createUndeclaredToolCallGuardBlockRewrite(
725
746
  } catch {
726
747
  return [block];
727
748
  }
728
- const name = enforceDeclaredToolNames
729
- ? undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom)
730
- : undefined;
731
- if (name !== undefined) {
732
- tripped = true;
733
- return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
749
+ if (refuse) {
750
+ const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
751
+ if (name !== undefined) {
752
+ tripped = true;
753
+ return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
754
+ }
734
755
  }
735
756
  const normalized = normalizeDefaultNamespaceInPayload(parsed, declared, declaredBare);
736
757
  if (normalized.changed) {
@@ -3,6 +3,8 @@ import { execFile } from "node:child_process";
3
3
  import { join } from "node:path";
4
4
  import { durableBunPath } from "../lib/bun-runtime";
5
5
  import { selfLaunchArgv } from "../lib/self-launch-argv";
6
+ import { isDesktopSupervised } from "../lib/system-restart-contract";
7
+ import { withLoginShellPath } from "./login-shell-path";
6
8
  import {
7
9
  WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER,
8
10
  isWindowsSchtasksCreateAccessDenied,
@@ -66,6 +68,8 @@ interface StartupInstallLaunch {
66
68
  execFile?: typeof execFile;
67
69
  /** Overrides Bun's own standalone detection, which a source-mode test cannot flip. */
68
70
  isStandaloneExecutable?: boolean;
71
+ isDesktopSupervised?: () => boolean;
72
+ withLoginShellPath?: (env: NodeJS.ProcessEnv) => Promise<NodeJS.ProcessEnv>;
69
73
  }
70
74
  let installLaunch: StartupInstallLaunch = {};
71
75
 
@@ -169,7 +173,16 @@ export function installFailureDetail(stdout: string, stderr: string, error: Erro
169
173
  return classifyCliInstallFailure(stdout, stderr, error).detail;
170
174
  }
171
175
 
172
- function runCliInstall(
176
+ /**
177
+ * Under OpenCodex Desktop the proxy has the GUI launch PATH, where no user-installed Codex is
178
+ * visible. The shim installer gets the user's login-shell PATH instead (#6971).
179
+ */
180
+ async function installChildEnv(action: StartupInstallAction): Promise<NodeJS.ProcessEnv> {
181
+ if (action !== "install-shim" || !(installLaunch.isDesktopSupervised ?? isDesktopSupervised)()) return process.env;
182
+ return (installLaunch.withLoginShellPath ?? withLoginShellPath)(process.env);
183
+ }
184
+
185
+ async function runCliInstall(
173
186
  action: StartupInstallAction,
174
187
  options?: { repair?: boolean },
175
188
  ): Promise<{ stdout: string; stderr: string }> {
@@ -183,13 +196,14 @@ function runCliInstall(
183
196
  isStandaloneExecutable: installLaunch.isStandaloneExecutable,
184
197
  });
185
198
  const run = installLaunch.execFile ?? execFile;
199
+ const env = await installChildEnv(action);
186
200
  return new Promise((resolve, reject) => {
187
201
  const timeout = process.platform === "win32" && action === "install-service"
188
202
  ? 0
189
203
  : 60_000;
190
204
  run(bun, argv, {
191
205
  encoding: "utf8",
192
- env: process.env,
206
+ env,
193
207
  // A fresh Windows scheduler install now owns its UAC prompt inside this CLI
194
208
  // transaction. Killing only the CLI at 60s can orphan its elevated schtasks child,
195
209
  // which may register the task after the Dashboard has reported failure. Keep the
@@ -117,6 +117,7 @@ function conservativeFallback(config: Pick<OcxConfig, "codexAutoStart">): Startu
117
117
  serviceSupported: process.platform === "win32" || process.platform === "darwin" || process.platform === "linux",
118
118
  shimInstalled: shim.installed,
119
119
  shimHealthy: shim.healthy,
120
+ ...(shim.activationPending ? { shimActivationPending: true } : {}),
120
121
  platform: process.platform,
121
122
  diagnosticStale: true,
122
123
  });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The install/repair preflight over the data-plane credential, plus the retry command it
3
+ * names. Split out of `guards.ts` so the guard module stops importing `diagnostics`.
4
+ *
5
+ * Direction of the remaining edge is deliberate: this module asks `guards` for
6
+ * `assertNotAdminToken` and `diagnostics` for the install verdict, and neither imports it back.
7
+ */
8
+ import { loadConfig } from "../config";
9
+ import { isLoopbackHostname } from "../codex/loopback-target";
10
+ import { readServiceApiTokenState, serviceApiTokenFilePath } from "../lib/service-secrets";
11
+ import { diagnoseService } from "./diagnostics";
12
+ import type { ServiceDiagnostic } from "./diagnostics";
13
+ import { assertNotAdminToken } from "./guards";
14
+
15
+ /**
16
+ * The `ocx` command a user should rerun for the service state they actually have.
17
+ *
18
+ * `installed` alone is not enough: `repairService()` refuses a Task-Scheduler-plus-WinSW
19
+ * conflict outright, so recommending repair there names a command guaranteed to fail.
20
+ * Install IS the valid conflict recovery, because `installWindows` removes the native
21
+ * backend first. Exported so the guard tests the real selector rather than a copy of it.
22
+ */
23
+ export function serviceRetryCommand(
24
+ diag: Pick<ServiceDiagnostic, "installed" | "conflict"> = diagnoseService(),
25
+ ): string {
26
+ return diag.installed && !diag.conflict ? "ocx service repair" : "ocx service install";
27
+ }
28
+
29
+ /**
30
+ * Preflight for `service install` / `service repair` on the data-plane credential.
31
+ *
32
+ * It used to DEMAND `OPENCODEX_API_AUTH_TOKEN` for a non-loopback hostname, and it threw
33
+ * even when `~/.opencodex/service-api-token` already held a perfectly good token. That is
34
+ * the defect behind the incident this unit exists to close (#4236): an operator exported the
35
+ * ADMIN token as OPENCODEX_API_AUTH_TOKEN because `install` asked for a token, the hub then
36
+ * crash-looped on `assertNotAdminToken`, and `service repair` asked for the same env var
37
+ * again — so the only remembered way to make the command proceed was the thing that broke it.
38
+ *
39
+ * Nobody should have to export a token by hand to run a hub. {@link writeServiceApiTokenFile}
40
+ * provisions one, so the only conditions left that install cannot fix are an admin-token
41
+ * collision in the environment and a token file that exists but cannot be used.
42
+ */
43
+ export function assertServiceAuthEnvironment(): void {
44
+ const config = loadConfig();
45
+ // Both collision checks come BEFORE the loopback short-circuit, because the launch wrapper
46
+ // exports the token file unconditionally (`buildServiceShellCommand` cats it whenever it
47
+ // exists, whatever the hostname): a management token in either source fences the whole
48
+ // management plane closed at boot, even on a loopback install that needs no admission
49
+ // secret. Returning early is what let that broken state through.
50
+ const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
51
+ if (present) assertNotAdminToken(present);
52
+ const state = readServiceApiTokenState();
53
+ // An existing FILE holding the admin token is the incident shape itself, and the first round
54
+ // only checked the env var — so install/repair reused it and the hub crash-looped at boot.
55
+ // On a machine connected to a hub this same file holds that hub's issued client key, which
56
+ // is never a management token, so the check is a no-op there.
57
+ if (state.kind === "present") assertNotAdminToken(state.token, process.env, "file");
58
+ if (isLoopbackHostname(config.hostname)) return;
59
+ if (present) return;
60
+ // Absent is fine — install/repair generates one below. `unsafe` is not: the writer refuses
61
+ // to replace a path it cannot vouch for, so say so here, where the operator can still act,
62
+ // instead of failing mid-install. Reached from `service repair` as well as `install`, so
63
+ // name a command that can actually succeed (see serviceRetryCommand).
64
+ if (state.kind !== "unsafe") return;
65
+ const diag = diagnoseService();
66
+ throw new Error(
67
+ `The data-plane token file cannot be used (${state.reason}): ${serviceApiTokenFilePath()}. `
68
+ + `Move it aside, then rerun \`${serviceRetryCommand(diag)}\`; the service provisions a `
69
+ + "fresh owner-only token and needs nothing from the environment.",
70
+ );
71
+ }
@@ -11,8 +11,10 @@ import { statusWinswRaw, type WinswStatus } from "../lib/winsw";
11
11
  import { withWindowsServiceMutationLock } from "../lib/windows-service-mutation-lock";
12
12
  import { maybeShowStarPrompt } from "../cli/star-prompt";
13
13
  import { serviceStatusReport } from "./diagnostics";
14
- import { assertServiceEnvironmentMatchesInstall, assertServiceAuthEnvironment } from "./guards";
15
- import { resolveServiceListenPort, reportServiceServing } from "./health";
14
+ import { assertServiceAuthEnvironment } from "./auth-preflight";
15
+ import { assertServiceEnvironmentMatchesInstall } from "./guards";
16
+ import { resolveServiceListenPort } from "./definition";
17
+ import { reportServiceServing } from "./health";
16
18
  import { platformOps, proxyStillLiveAfterStop, stopTrackedProxyForServiceCommand, installServiceSafely, installFreshWindowsSchedulerSafely, removeServiceInstallState, isServiceInstalled } from "./orchestration";
17
19
  import { repairService } from "./repair";
18
20
  import type { ServiceRepairVerb } from "./repair";
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The shape of a service definition: the shell command both Unix backends bake into
3
+ * their unit/plist, the port they pin into it, and the proxy environment it carries.
4
+ *
5
+ * Leaf module. It reads config and the token-file path, and asks nothing of the
6
+ * backends that consume it — importing it from `launchd.ts`/`systemd.ts` cannot
7
+ * route back through them (see `installedServiceListenPort` in `health.ts`, which
8
+ * intentionally stays behind because it asks every backend).
9
+ */
10
+ import { loadConfig } from "../config";
11
+ import { serviceApiTokenFilePath } from "../lib/service-secrets";
12
+ import { PROXY_ENV_KEYS } from "../lib/proxy-env";
13
+
14
+ export function shellQuote(value: string): string {
15
+ return `'${value.replace(/'/g, "'\\''")}'`;
16
+ }
17
+
18
+ /**
19
+ * Listen port baked into service wrappers / WinSW XML.
20
+ * Priority: explicit override → OCX_BAKE_PORT (update restart) → config.port → 10100.
21
+ * `config.port === 0` means ephemeral for interactive start; services need a stable pin,
22
+ * so treat 0 / invalid like unset (default 10100) instead of baking `--port 0`.
23
+ */
24
+ export function resolveServiceListenPort(override?: number): number {
25
+ if (typeof override === "number" && Number.isFinite(override) && override > 0 && override <= 65535) {
26
+ return Math.trunc(override);
27
+ }
28
+ const baked = process.env.OCX_BAKE_PORT?.trim();
29
+ if (baked && /^\d+$/.test(baked)) {
30
+ const n = Number(baked);
31
+ if (n > 0 && n <= 65535) return n;
32
+ }
33
+ const configured = loadConfig().port;
34
+ if (typeof configured === "number" && configured > 0 && configured <= 65535) return configured;
35
+ return 10100;
36
+ }
37
+
38
+ export function buildServiceShellCommand(bun: string, cli: string | null, port = resolveServiceListenPort()): string {
39
+ const tokenFile = serviceApiTokenFilePath();
40
+ const args = cli ? `${shellQuote(cli)} start` : "start";
41
+ return `if [ -f ${shellQuote(tokenFile)} ]; then OPENCODEX_API_AUTH_TOKEN="$(cat ${shellQuote(tokenFile)})"; export OPENCODEX_API_AUTH_TOKEN; fi; exec ${shellQuote(bun)} ${args} --port ${port}`;
42
+ }
43
+
44
+ /**
45
+ * The same command shape, launched through a stable `ocx` executable instead of an
46
+ * explicit Bun + CLI pair. The token-file preamble is identical and deliberately shared
47
+ * in form: the service still reads the token from disk at start and never carries it in
48
+ * the unit.
49
+ */
50
+ export function buildServiceLauncherShellCommand(launcher: string, port = resolveServiceListenPort()): string {
51
+ const tokenFile = serviceApiTokenFilePath();
52
+ return `if [ -f ${shellQuote(tokenFile)} ]; then OPENCODEX_API_AUTH_TOKEN="$(cat ${shellQuote(tokenFile)})"; export OPENCODEX_API_AUTH_TOKEN; fi; exec ${shellQuote(launcher)} start --port ${port}`;
53
+ }
54
+
55
+ /**
56
+ * Shared tail parser for the baked `--port <n>`.
57
+ *
58
+ * Terminators cover all three artifact shapes: whitespace (batch wrapper, systemd
59
+ * unit), `"` (systemd's quoted ExecStart), `<` (WinSW's `</arguments>`), and `&` (an
60
+ * XML-escaped quote). Matched LAST because every artifact carries the Bun and CLI
61
+ * paths ahead of the argument, and a path containing the literal must not shadow it.
62
+ */
63
+ export function parseBakedListenPort(read: () => string): number | null {
64
+ try {
65
+ const last = [...read().matchAll(/start --port (\d{1,5})(?:\s|"|&|<|$)/gm)].at(-1);
66
+ if (!last) return null;
67
+ const n = Number(last[1]);
68
+ return n > 0 && n <= 65535 ? n : null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Outbound proxy settings the installing shell had, resolved for baking into a service
76
+ * definition.
77
+ *
78
+ * A service manager does not inherit the environment of the shell that installed it, and
79
+ * `ExecStart=/bin/sh -lc` is dash on Ubuntu/WSL — login dash reads `.profile`, not
80
+ * `.bashrc`, which is where proxy exports usually live. So a user who needs a proxy to
81
+ * reach the upstream got a service that dialed direct: the socket was reset, the retry
82
+ * budget drained, and the request surfaced as `502 Provider unreachable` (#2107). The
83
+ * same install driven through `ocx codex-shim` worked, because that path spawns with
84
+ * `{ ...process.env }`.
85
+ *
86
+ * Lower-case variants are honored because curl-style tooling sets them and the runtime's
87
+ * own `applyProxyEnv` already treats both cases as equivalent. Only the canonical
88
+ * upper-case name is baked, so a definition never carries two spellings of one setting.
89
+ */
90
+ export function resolvedProxyEnv(env: NodeJS.ProcessEnv = process.env): { name: string; value: string }[] {
91
+ const resolved: { name: string; value: string }[] = [];
92
+ for (const key of PROXY_ENV_KEYS) {
93
+ const value = env[key]?.trim() || env[key.toLowerCase()]?.trim();
94
+ if (value) resolved.push({ name: key, value });
95
+ }
96
+ return resolved;
97
+ }
@@ -122,11 +122,25 @@ function macLoginRegistration(deps: DesktopStartupDeps) {
122
122
  }
123
123
  const program = /^\s*program = (.+)$/m.exec(loaded)?.[1];
124
124
  const loadedPath = /^\s*path = (.+)$/m.exec(loaded)?.[1];
125
+ // macOS 13+ Background Task Management can take over the user LaunchAgent and resubmit it
126
+ // through smd; launchctl then names the submitter instead of the plist (#7019). The loaded
127
+ // job no longer points at the registration, so credit it like an unloaded job: same program,
128
+ // and the entire registration re-read unchanged.
129
+ if (program !== undefined && loadedPath !== undefined && SMD_SUBMITTED.test(loadedPath)
130
+ && /^\s*managed_by = (.+)$/m.exec(loaded)?.[1] === SERVICE_MANAGEMENT && realpathSync(program) === app) {
131
+ const final = capture();
132
+ if (!final || JSON.stringify(first) !== JSON.stringify(final)) return { app, proxy, loginEnabled: false };
133
+ return { app, proxy, loginEnabled: true, loginRegistration: "loaded" as const };
134
+ }
125
135
  const loginEnabled = program !== undefined && realpathSync(program) === app
126
136
  && loadedPath !== undefined && realpathSync(loadedPath) === first.registrationPath;
127
137
  return { app, proxy, loginEnabled, ...(loginEnabled ? { loginRegistration: "loaded" as const } : {}) };
128
138
  }
129
139
 
140
+ /** launchctl's `path` for a job Background Task Management resubmitted through smd. */
141
+ const SMD_SUBMITTED = /^\(submitted by smd\.\d+\)$/;
142
+ const SERVICE_MANAGEMENT = "com.apple.xpc.ServiceManagement";
143
+
130
144
  type DesktopLoginFacts = Pick<DesktopStartupDiagnostic, "loginEnabled" | "loginRegistration">;
131
145
 
132
146
  /**