@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
@@ -0,0 +1,369 @@
1
+ import { createHash } from "node:crypto";
2
+ import { advisorResultIsAdvice } from "./context";
3
+
4
+ /**
5
+ * Conversation- and task-scoped advisor state.
6
+ *
7
+ * Three scopes, deliberately separate:
8
+ *
9
+ * 1. REQUEST-scoped state lives in the per-request plan closure (see runtime.ts) — consultation
10
+ * count, dedup fingerprints, the preflight flag. Born and dies with one request.
11
+ *
12
+ * 2. TASK-scoped preflight ledger (this file): a bounded, process-local claim table that makes
13
+ * "at most one automatic consultation per task" atomic across concurrent requests. A claim
14
+ * requires a STABLE conversation identity plus the current task boundary; a client that sends
15
+ * no identity never enters this ledger (see `advisorLedgerKey`) and therefore fails open —
16
+ * it may be consulted once per request rather than risk two independent tasks suppressing
17
+ * each other through a shared guess.
18
+ *
19
+ * 3. PROVENANCE, split by authority:
20
+ * - MANUAL advice is verifiable history: a `toolResult` whose `toolName` is the synthetic
21
+ * advisor tool and whose content parses as a runtime-written advice object. Ordinary tool
22
+ * output, developer text, user text, and failure notices can never match it. Bytes inside
23
+ * the quoted advice field cannot change the runtime-owned status.
24
+ * - AUTOMATIC preflight dedup is NOT decided from history at all. The developer transport
25
+ * envelope labels the payload for the worker; any client could echo or forge such a message,
26
+ * so the ledger below is the authoritative source for "this task was already consulted" —
27
+ * `success`, `inflight` and `cooldown` states. A conversation with no stable identity gets
28
+ * no ledger and therefore fails open (at most one extra attempt). A forged marker or a
29
+ * forged developer message cannot suppress the policy.
30
+ *
31
+ * Ledger entries are plain state records — no message bodies, no credentials. Every state is
32
+ * bounded by entry count and its own TTL.
33
+ */
34
+
35
+ /** Long-lived success suppression: the task lifetime approximation (also the ledger TTL cap). */
36
+ export const ADVISOR_SUCCESS_TTL_MS = 24 * 60 * 60 * 1000;
37
+ /**
38
+ * Failure cooldown. One minute is the repository's standing minute-scale unit (tray polling,
39
+ * subagent availability polling); it turns a transient 503 into a short pause instead of
40
+ * silencing the policy for the rest of the coding session.
41
+ */
42
+ export const ADVISOR_FAILURE_COOLDOWN_MS = 60 * 1000;
43
+ /**
44
+ * In-flight claim expiry. Longer than the largest configurable consultation timeout (600s
45
+ * upper bound on `advisor.timeoutMs`, default 120s) so a slow-but-alive consultation is never
46
+ * mistaken for a wedged one, while a crashed claim cannot block a task forever.
47
+ */
48
+ export const ADVISOR_INFLIGHT_TTL_MS = 10 * 60 * 1000;
49
+
50
+ const MAX_ENTRIES = 512;
51
+
52
+ export type AdvisorClaimState =
53
+ /** The caller now owns the consultation for this task. */
54
+ | "claimed"
55
+ /** Another request for the same task is consulting right now. */
56
+ | "inflight"
57
+ /** This task already received advice; suppression holds until the success TTL expires. */
58
+ | "complete"
59
+ /** A recent consultation failed; suppression holds for the short failure cooldown. */
60
+ | "cooldown"
61
+ /**
62
+ * The ledger is full of live claims and granted nothing. Fail-open: the worker continues
63
+ * without a new automatic consultation rather than evicting a claim that is still running.
64
+ */
65
+ | "saturated";
66
+
67
+ /**
68
+ * The result of a claim attempt. `token` identifies THIS claim and is present only on
69
+ * `"claimed"`; settlement must present it, so a slow consultation whose in-flight entry expired
70
+ * cannot settle the successor claim that took its place.
71
+ */
72
+ export interface AdvisorClaim {
73
+ state: AdvisorClaimState;
74
+ token?: string;
75
+ }
76
+
77
+ interface LedgerEntry {
78
+ state: "inflight" | "success" | "failed";
79
+ at: number;
80
+ /** Present on in-flight entries: which claim owns this entry. */
81
+ token?: string;
82
+ }
83
+
84
+ export interface AdvisorPreflightLedger {
85
+ /**
86
+ * Atomically try to own the consultation for a task key. Returns `claimed` exactly once per
87
+ * task (with the ownership token) until a matching settlement releases it, so two concurrent
88
+ * requests cannot both consult.
89
+ */
90
+ claim(key: string, now?: number): AdvisorClaim;
91
+ /**
92
+ * Settle a claim the caller owns. A settlement whose token does not match the current entry
93
+ * is a no-op: the entry now belongs to a successor claim (the caller's in-flight window
94
+ * expired), and it must not be able to erase or overwrite that successor's state.
95
+ */
96
+ complete(key: string, token: string, now?: number): void;
97
+ fail(key: string, token: string, now?: number): void;
98
+ release(key: string, token: string, now?: number): void;
99
+ /**
100
+ * Fact, not settlement: this task received advice (used by a successful MANUAL consultation,
101
+ * which owns no preflight claim). Records success regardless of the current entry, because
102
+ * "the task was advised" is true whichever consultation produced it.
103
+ */
104
+ markAdvised(key: string, now?: number): void;
105
+ /** Test/observability seam: current entry count. */
106
+ size(): number;
107
+ }
108
+
109
+ export function createAdvisorPreflightLedger(): AdvisorPreflightLedger {
110
+ const entries = new Map<string, LedgerEntry>();
111
+ let claimSequence = 0;
112
+
113
+ const isExpired = (entry: LedgerEntry, now: number): boolean => {
114
+ const ttl = entry.state === "success"
115
+ ? ADVISOR_SUCCESS_TTL_MS
116
+ : entry.state === "failed"
117
+ ? ADVISOR_FAILURE_COOLDOWN_MS
118
+ : ADVISOR_INFLIGHT_TTL_MS;
119
+ return now - entry.at > ttl;
120
+ };
121
+
122
+ /**
123
+ * Make room for ONE new claim without ever evicting a live in-flight entry, because that entry
124
+ * is the only thing preventing a second automatic consultation for its task. Order: expired
125
+ * entries first, then settled ones (success before failure), oldest first. Returns false when
126
+ * every entry is a live claim — the caller then reports `saturated` instead of breaking the
127
+ * "at most one in-flight consultation per task" guarantee.
128
+ */
129
+ const makeRoom = (now: number): boolean => {
130
+ if (entries.size < MAX_ENTRIES) return true;
131
+ for (const [key, entry] of entries) {
132
+ if (isExpired(entry, now)) entries.delete(key);
133
+ }
134
+ if (entries.size < MAX_ENTRIES) return true;
135
+ for (const state of ["success", "failed"] as const) {
136
+ for (const [key, entry] of entries) {
137
+ if (entry.state === state) {
138
+ entries.delete(key);
139
+ return true;
140
+ }
141
+ }
142
+ }
143
+ return false;
144
+ };
145
+
146
+ const liveEntry = (key: string, now: number): LedgerEntry | undefined => {
147
+ const entry = entries.get(key);
148
+ if (!entry) return undefined;
149
+ if (isExpired(entry, now)) {
150
+ entries.delete(key);
151
+ return undefined;
152
+ }
153
+ return entry;
154
+ };
155
+
156
+ // Replacing an existing key never grows the map, and every NEW key is admitted only through
157
+ // claim()'s makeRoom gate, so the table cannot exceed MAX_ENTRIES.
158
+ const set = (key: string, state: LedgerEntry["state"], now: number, token?: string): void => {
159
+ if (entries.has(key)) entries.delete(key);
160
+ entries.set(key, { state, at: now, ...(token !== undefined ? { token } : {}) });
161
+ };
162
+
163
+ /** True when the caller's token still owns the current in-flight entry for this key. */
164
+ const owns = (key: string, token: string, now: number): boolean => {
165
+ const entry = liveEntry(key, now);
166
+ return entry?.state === "inflight" && entry.token === token;
167
+ };
168
+
169
+ return {
170
+ claim(key, now = Date.now()) {
171
+ const entry = liveEntry(key, now);
172
+ if (entry?.state === "success") return { state: "complete" };
173
+ if (entry?.state === "failed") return { state: "cooldown" };
174
+ if (entry?.state === "inflight") return { state: "inflight" };
175
+ if (!makeRoom(now)) return { state: "saturated" };
176
+ claimSequence += 1;
177
+ const token = `claim-${claimSequence.toString(36)}`;
178
+ set(key, "inflight", now, token);
179
+ return { state: "claimed", token };
180
+ },
181
+ complete(key, token, now = Date.now()) {
182
+ // A settlement that no longer owns the entry is a no-op: the claim it belonged to expired
183
+ // and a successor now owns the state.
184
+ if (owns(key, token, now)) set(key, "success", now);
185
+ },
186
+ fail(key, token, now = Date.now()) {
187
+ if (owns(key, token, now)) set(key, "failed", now);
188
+ },
189
+ release(key, token, now = Date.now()) {
190
+ if (owns(key, token, now)) entries.delete(key);
191
+ },
192
+ markAdvised(key, now = Date.now()) {
193
+ // Recording the FACT must respect the same cap as claiming: a key that is not present and a
194
+ // table holding nothing but live claims means no room, so the record is skipped rather than
195
+ // evicting a claim that is still running. Fail-open: at worst one extra automatic attempt.
196
+ const present = liveEntry(key, now) !== undefined;
197
+ if (!present && !makeRoom(now)) return;
198
+ set(key, "success", now);
199
+ },
200
+ size() {
201
+ return entries.size;
202
+ },
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Stable conversation identity for the ledger, reusing the repository's existing request
208
+ * identities in specificity order: the client's own thread, the shared parent thread, the
209
+ * Cursor conversation, the Cursor client thread, then the reasoning-replay scope's thread.
210
+ * Returns undefined for a client that sends no identity at all — such a caller stays out of the
211
+ * process-global ledger on purpose (fail-open, see the module header).
212
+ */
213
+ export function advisorConversationIdentity(parsed: {
214
+ _codexOwnThreadId?: string;
215
+ _clientThreadId?: string;
216
+ _cursorConversationId?: string;
217
+ _cursorClientThreadId?: string;
218
+ _reasoningReplayScope?: { clientThreadId?: string };
219
+ }): string | undefined {
220
+ return parsed._codexOwnThreadId
221
+ ?? parsed._clientThreadId
222
+ ?? parsed._cursorConversationId
223
+ ?? parsed._cursorClientThreadId
224
+ ?? parsed._reasoningReplayScope?.clientThreadId
225
+ ?? undefined;
226
+ }
227
+
228
+ /**
229
+ * Domain-separated SHA-256 digest. Task identity and suppression are CORRECTNESS boundaries, so a
230
+ * 32-bit non-cryptographic hash is not an acceptable primary digest: distinct tasks must not
231
+ * collide because of a short fold. The ledger stores only this digest, never the raw text, so a
232
+ * captured key reveals nothing about the conversation. The domain prefix keeps digests from
233
+ * different purposes apart even if their inputs coincide.
234
+ */
235
+ function sha256Hex(domain: string, value: string, hexChars: number): string {
236
+ return createHash("sha256").update(`${domain}\0${value}`, "utf8").digest("hex").slice(0, hexChars);
237
+ }
238
+
239
+ /**
240
+ * The current task boundary inside a conversation: how many user turns the history carries and a
241
+ * digest of the FULL latest user text. A new user message moves the boundary (a new task gets its
242
+ * own claim); the same turn re-sent by a stateless full-history client keeps the same boundary,
243
+ * and a `previous_response_id` expansion replays the same user turns, so continuations dedup.
244
+ * The whole text participates — no truncation — so two tasks that share an opening prefix still
245
+ * get different boundaries.
246
+ */
247
+ export function advisorTaskBoundary(parsed: {
248
+ context: { messages: readonly { role: string; content: unknown }[] };
249
+ }): string {
250
+ let userTurns = 0;
251
+ let lastUserText = "";
252
+ for (const message of parsed.context.messages) {
253
+ if (message.role !== "user") continue;
254
+ userTurns += 1;
255
+ lastUserText = contentText(message.content);
256
+ }
257
+ return `t${userTurns}:${sha256Hex("advisor-task-boundary", lastUserText, 32)}`;
258
+ }
259
+
260
+ /**
261
+ * The ledger key: one domain-separated SHA-256 digest over conversation identity + task boundary
262
+ * + worker model. Returns undefined when the caller has no stable conversation identity — the
263
+ * caller then relies on request-scoped dedup and genuine in-history provenance instead of a
264
+ * shared guess (documented fail-open).
265
+ */
266
+ export function advisorLedgerKey(
267
+ parsed: {
268
+ context: { messages: readonly { role: string; content: unknown }[] };
269
+ _codexOwnThreadId?: string;
270
+ _clientThreadId?: string;
271
+ _cursorConversationId?: string;
272
+ _cursorClientThreadId?: string;
273
+ _reasoningReplayScope?: { clientThreadId?: string };
274
+ },
275
+ workerModelId: string,
276
+ ): string | undefined {
277
+ const identity = advisorConversationIdentity(parsed);
278
+ if (!identity) return undefined;
279
+ const material = `${identity}\0${advisorTaskBoundary(parsed)}\0${workerModelId}`;
280
+ return `ak-${sha256Hex("advisor-task-key", material, 40)}`;
281
+ }
282
+
283
+ /** Text projection for string-or-parts content; used only for hashing, never transmitted. */
284
+ export function contentText(content: unknown): string {
285
+ if (typeof content === "string") return content;
286
+ if (!Array.isArray(content)) return "";
287
+ return content
288
+ .filter((part): part is { type: "text"; text: string } =>
289
+ !!part && typeof part === "object" && (part as { type?: unknown }).type === "text"
290
+ && typeof (part as { text?: unknown }).text === "string")
291
+ .map(part => part.text)
292
+ .join("");
293
+ }
294
+
295
+ /**
296
+ * Legacy marker spellings. Failure text still neutralizes them so an upstream error cannot
297
+ * look like an old wrapper. They are not suppression authority and are not emitted.
298
+ */
299
+ export const ADVISOR_PREFLIGHT_MARKER = "<opencodex_advisor_preflight>";
300
+
301
+ /** The synthetic advisor tool's wire name; kept in sync with the tool definition by test. */
302
+ export const ADVISOR_RESULT_TOOL_NAME = "advisor";
303
+
304
+ /** Legacy manual wrapper spelling. Detection does not search for this substring. */
305
+ export const ADVISOR_ADVICE_MARKER = "<opencodex_advisor>";
306
+
307
+ /**
308
+ * Detect an ALREADY-PRESENT MANUAL advisor result — by provenance, never by a bare string.
309
+ * The message must be a `toolResult` whose `toolName` is the synthetic advisor tool, and its
310
+ * content must parse as a runtime-written advice object (`status === "advice"` on the sibling
311
+ * field the runtime sets). Text inside `advice` cannot flip that field.
312
+ *
313
+ * Only messages after the latest user turn count. Manual advice from an earlier task in the
314
+ * same thread does not suppress preflight for a later task; the ledger keys those separately.
315
+ * Developer messages are deliberately NOT inspected. Automatic preflight dedup lives in the
316
+ * ledger. A client-echoed developer envelope, a shell result, or a failure notice matches nothing.
317
+ */
318
+ export function historyHasManualAdvisorResult(parsed: {
319
+ context: { messages: readonly { role: string; content?: unknown; toolName?: string }[] };
320
+ }): boolean {
321
+ for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
322
+ const message = parsed.context.messages[i]!;
323
+ if (message.role === "user") break;
324
+ if (message.role !== "toolResult") continue;
325
+ if (message.toolName !== ADVISOR_RESULT_TOOL_NAME) continue;
326
+ if (advisorResultIsAdvice(contentText(message.content))) return true;
327
+ }
328
+ return false;
329
+ }
330
+
331
+ /** Kept for callers that only need the first user text (payload building, tests). */
332
+ export function firstUserText(parsed: { context: { messages: readonly { role: string; content: unknown }[] } }): string {
333
+ for (const message of parsed.context.messages) {
334
+ if (message.role !== "user") continue;
335
+ const text = contentText(message.content);
336
+ if (text.trim() !== "") return text;
337
+ }
338
+ return "";
339
+ }
340
+
341
+ /**
342
+ * Deterministic preflight trigger: has this conversation already produced orientation evidence
343
+ * since the latest user message? The documented approximation for "before the first substantive
344
+ * implementation" — the protocol layer offers no safe pre-mutation checkpoint, so OpenCodex fires
345
+ * the automatic attempt on the first worker reasoning turn that arrives with that evidence.
346
+ * Evidence is an assistant tool call OR a tool result after the latest user message (both forms
347
+ * are genuinely accepted; the docs say so). Only text/toolResult content is inspected — never
348
+ * reasoning, never encrypted items.
349
+ */
350
+ export function hasOrientationEvidence(parsed: { context: { messages: readonly { role: string; content: unknown }[] } }): boolean {
351
+ let latestUserIndex = -1;
352
+ for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
353
+ if (parsed.context.messages[i]?.role === "user") {
354
+ latestUserIndex = i;
355
+ break;
356
+ }
357
+ }
358
+ if (latestUserIndex < 0) return false;
359
+ for (let i = latestUserIndex + 1; i < parsed.context.messages.length; i += 1) {
360
+ const message = parsed.context.messages[i];
361
+ if (message.role === "toolResult") return true;
362
+ if (message.role === "assistant" && Array.isArray(message.content)
363
+ && message.content.some(part =>
364
+ !!part && typeof part === "object" && (part as { type?: unknown }).type === "toolCall")) {
365
+ return true;
366
+ }
367
+ }
368
+ return false;
369
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The synthetic `advisor` tool injected into routed worker turns.
3
+ *
4
+ * Mirrors src/web-search/synthetic-tool.ts: the proxy OWNS this tool — the worker only expresses
5
+ * "I want to consult the expert" (optionally with a focus), and OpenCodex builds everything else
6
+ * (task context, conversation, tool catalog, identities) itself. The worker never passes a
7
+ * transcript, provider, model, or files.
8
+ */
9
+ import type { OcxTool } from "../types";
10
+
11
+ export const ADVISOR_TOOL_NAME = "advisor";
12
+
13
+ export function buildAdvisorTool(): OcxTool {
14
+ return {
15
+ name: ADVISOR_TOOL_NAME,
16
+ description:
17
+ "Consult an independent expert advisor model about the current task. The advisor receives a "
18
+ + "summary of what you have done so far (task, conversation, tool results) and returns "
19
+ + "strategic advice, critique, root-cause reasoning, or alternative approaches. Use it when "
20
+ + "you are stuck, before starting substantive implementation on a hard task, or when you want "
21
+ + "a second opinion on a plan or diagnosis. The advisor cannot execute tools or edit files; "
22
+ + "you remain responsible for all execution. Optionally pass a short `question` to focus the "
23
+ + "consultation.",
24
+ parameters: {
25
+ type: "object",
26
+ properties: {
27
+ question: { type: "string", description: "Optional short focus question for the advisor." },
28
+ },
29
+ required: [],
30
+ },
31
+ advisor: true,
32
+ };
33
+ }
@@ -13,16 +13,35 @@ import {
13
13
  CYBER_POLICY_ERROR_CODE,
14
14
  isCyberPolicyCode,
15
15
  SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE,
16
+ UNDECLARED_TOOL_CALL_ERROR_CODE,
16
17
  type OcxErrorPayload,
17
18
  } from "../lib/errors";
18
19
 
20
+ const PINNED_VERDICT_CODES = new Set([CYBER_POLICY_ERROR_CODE, "client_closed_request"]);
21
+
19
22
  export function formatErrorResponse(
20
23
  status: number,
21
24
  type: string,
22
25
  message: string,
23
- options?: { code?: string | null; retryAfter?: string | null },
26
+ options?: { code?: string | null; retryAfter?: string | null; classified?: boolean },
24
27
  ): Response {
25
28
  const error = classifyError(status, type, message);
29
+ // A combo child holding an adapterFailureFromEvent verdict has already classified this
30
+ // failure. Re-deriving it from the status folded insufficient_quota into rate_limit_exceeded
31
+ // and a 400 modality refusal into invalid_request_error, so the combo loop cooled and hopped
32
+ // on the wrong evidence. The child body reaches only that loop, whose consumer re-applies
33
+ // this formatter's allowlist before anything reaches a client. The overrides below still win.
34
+ // The caller's code fills in only where its type and message add nothing to what the status
35
+ // alone says. A verdict read from the full message (quota, credentials, cyber policy, context
36
+ // overflow, catalog size) outranks a generic adapter code: the consumer sees only the first
37
+ // 500 characters, so replacing it turned a late cyber refusal into a hop and spent quota into a
38
+ // target cooldown. The baseline uses a neutral type because an untyped adapter event arrives
39
+ // with a type adapterFailureFromEvent already inferred from that same message.
40
+ const messageVerdict = error.code !== classifyError(status, "upstream_error", "").code;
41
+ if (options?.classified && options.code && !messageVerdict && !PINNED_VERDICT_CODES.has(error.code ?? "")) {
42
+ error.type = type;
43
+ error.code = options.code;
44
+ }
26
45
  if (options?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE) {
27
46
  error.type = "server_error";
28
47
  error.code = SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
@@ -31,6 +50,10 @@ export function formatErrorResponse(
31
50
  error.code = CYBER_POLICY_ERROR_CODE;
32
51
  error.type = cyberPolicyErrorType(type);
33
52
  }
53
+ // The undeclared-tool refusal is deterministic, not a transient upstream failure: its code
54
+ // survives so the Anthropic translator can classify it instead of folding it into a
55
+ // retryable overload (and so clients see the SSE path's rejection identity).
56
+ if (options?.code === UNDECLARED_TOOL_CALL_ERROR_CODE) error.code = options.code;
34
57
  // Only the allowlisted transport verdicts survive this formatter. Do not forward
35
58
  // arbitrary provider codes, and preserve the existing cyber-policy precedence.
36
59
  const replayBlocked = error.code !== CYBER_POLICY_ERROR_CODE
@@ -186,7 +186,9 @@ export function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "er
186
186
  * This fixes items created from here on. History recorded before it is repaired at the
187
187
  * replay boundary by `backfillWebSearchQueries()` in the Responses adapter.
188
188
  */
189
- export function webSearchAction(queries: string[]): Record<string, unknown> {
189
+ export function webSearchAction(queries: string[], fetch?: { url: string }): Record<string, unknown> {
190
+ // Anthropic's server-side web_fetch opens one page instead of searching.
191
+ if (fetch) return { type: "open_page", url: fetch.url };
190
192
  const first = queries[0] ?? "";
191
193
  return { type: "search", query: first, queries: queries.length > 0 ? queries : [first] };
192
194
  }
@@ -1,3 +1,4 @@
1
+ import { anthropicProgressItems } from "../responses/anthropic-progress";
1
2
  import type {
2
3
  AdapterEvent,
3
4
  OcxMessagePhase,
@@ -67,6 +68,8 @@ export function buildResponseJSON(
67
68
  if (options?.translatorBudget && options.recordBufferedDelivery !== false) {
68
69
  attemptDeliveryRecorder(budget)?.noteBufferedDelivery(body);
69
70
  }
71
+ if (body.status === "completed" && options?.compaction
72
+ && !events.some(event => event.type === "done" && event.compactionEncryptedContent)) options.compactionRetention?.commit();
70
73
  return body;
71
74
  } finally {
72
75
  for (const event of events) {
@@ -98,6 +101,7 @@ function buildResponseJSONWithBudget(
98
101
  toolSearchToolNames?: Set<string>;
99
102
  /** Remote compaction v2 turn — append one synthetic compaction output item (see bridgeToResponsesSSE). */
100
103
  compaction?: boolean;
104
+ compactionRetention?: { encode(summary: string): string; commit(): void };
101
105
  onProviderState?: (state: OcxProviderContinuationState) => void;
102
106
  /** Raw adapter-reported usage before wire normalization (see bridgeToResponsesSSE onUsage). */
103
107
  onUsage?: (usage: OcxUsage | undefined) => void;
@@ -386,6 +390,16 @@ function buildResponseJSONWithBudget(
386
390
  flushToolCall();
387
391
  break;
388
392
  case "text_delta":
393
+ if (options?.compaction && e.anthropicThinkingSignature) break;
394
+ if (e.anthropicThinkingSignature) {
395
+ rawReasoningForNextToolCall = "";
396
+ flushText("commentary");
397
+ flushSummaryReasoning();
398
+ flushRawReasoning();
399
+ flushToolCall();
400
+ for (const item of anthropicProgressItems(e.text, e.anthropicThinkingSignature, budget)) pushOutput(item as OutputItem);
401
+ break;
402
+ }
389
403
  // Only flush on an explicit phase change. A later delta that omits `phase` must keep
390
404
  // appending under the previously established phase.
391
405
  if (currentText.bytes > 0 && e.phase !== undefined && currentTextPhase !== e.phase) flushText("commentary");
@@ -533,8 +547,9 @@ function buildResponseJSONWithBudget(
533
547
  const safeSources = safeWebSearchSources(e.sources);
534
548
  pushOutput({
535
549
  type: "web_search_call", id: `ws_${uuid()}`, status: e.status ?? "completed",
536
- action: webSearchAction(e.queries),
550
+ action: webSearchAction(e.queries, e.fetch),
537
551
  ...(safeSources.length > 0 ? { sources: safeSources } : {}),
552
+ ...(e.fetch ? { fetch_result: e.fetch.result } : {}),
538
553
  });
539
554
  if (safeSources.length > 0) {
540
555
  for (const source of safeSources) {
@@ -616,7 +631,7 @@ function buildResponseJSONWithBudget(
616
631
  ) {
617
632
  const item = {
618
633
  type: "compaction", id: `cmp_${uuid()}`,
619
- encrypted_content: compactionEncryptedContent ?? encodeCompactionSummary(joinChunks(batchCompaction)),
634
+ encrypted_content: compactionEncryptedContent ?? (options?.compactionRetention?.encode(joinChunks(batchCompaction)) ?? encodeCompactionSummary(joinChunks(batchCompaction))),
620
635
  };
621
636
  pushOutput(item, compactionEncryptedContent ? 0 : batchCompaction.bytes);
622
637
  }
@@ -646,7 +661,7 @@ function buildResponseJSONWithBudget(
646
661
  : errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
647
662
  ...(incompleteEvent ? {
648
663
  incomplete_details: {
649
- reason: incompleteEvent.reason,
664
+ reason: truncationReasonFor(incompleteEvent.reason) ?? incompleteEvent.reason,
650
665
  ...(incompleteEvent.message ? { message: incompleteEvent.message } : {}),
651
666
  ...(incompleteEvent.retryable !== undefined ? { retryable: incompleteEvent.retryable } : {}),
652
667
  },