@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,117 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { OcxParsedRequest } from "../types";
3
+ import { contentText } from "./state";
4
+ import { advisorEvidenceTool } from "./evidence-tool";
5
+
6
+ export interface AdvisorRepairEvidence {
7
+ id: string;
8
+ command: string;
9
+ firstFailure: { message: number; exitCode: number; output: string };
10
+ repair: { message: number; tool: string };
11
+ repeatedFailure: { message: number; exitCode: number; output: string };
12
+ }
13
+ interface Validation { command: string; fingerprint: string; invokedAt: number }
14
+ const digest = (value: string) => createHash("sha256").update(value).digest("hex");
15
+ const excerpt = (text: string) => text.length <= 1600 ? text : `${text.slice(0, 800)}\n[output truncated]\n${text.slice(-800)}`;
16
+
17
+ /** A deliberately small command grammar. No pipelines, substitutions or arbitrary scripts. */
18
+ function validation(args: Record<string, unknown>, invokedAt: number): Validation | undefined {
19
+ const input = args.cmd ?? args.command;
20
+ const raw = Array.isArray(input) && input.every(value => typeof value === "string")
21
+ ? input.map(value => JSON.stringify(value)).join(" ") : input;
22
+ if (typeof raw !== "string" || raw.length > 4096 || /[\n\r;|<>`$]/.test(raw)) return;
23
+ let command = raw.trim();
24
+ let cwd = typeof (args.workdir ?? args.cwd) === "string" ? String(args.workdir ?? args.cwd) : "";
25
+ const cd = /^cd\s+("[^"]+"|'[^']+'|[^\s&]+)\s*&&\s*(.+)$/.exec(command);
26
+ if (cd) { cwd += `/${cd[1]!.replace(/^["']|["']$/g, "")}`; command = cd[2]!; }
27
+ if (command.includes("&")) return;
28
+ // Quoted arguments stay intact; metacharacters above are rejected even inside quotes.
29
+ const tokens = command.match(/"[^"]*"|'[^']*'|[^\s"']+/g);
30
+ if (!tokens || tokens.join(" ").replace(/\s+/g, " ") !== command.replace(/\s+/g, " ")) return;
31
+ const words = tokens.map(token => token.replace(/^["']|["']$/g, ""));
32
+ const executable = words[0]!.split(/[\\/]/).at(-1)!;
33
+ const rest = words.slice(1).join(" ");
34
+ const isValidation = /^(?:pytest|tsc|eslint)$/.test(executable)
35
+ || (/^(?:bun|npm|pnpm|yarn)$/.test(executable) && /^(?:(?:run\s+)?(?:test|typecheck|lint|check|build)(?:[\s:]|$)|(?:x|exec)\s+(?:tsc|eslint)(?:\s|$))/.test(rest))
36
+ || (/^python(?:3)?$/.test(executable) && /^-m pytest(?:\s|$)/.test(rest))
37
+ || (executable === "cargo" && /^(?:test|check|clippy|build)(?:\s|$)/.test(rest))
38
+ || (executable === "go" && /^(?:test|vet)(?:\s|$)/.test(rest));
39
+ if (!isValidation) return;
40
+ return { command: raw, fingerprint: digest(JSON.stringify([cwd, words])), invokedAt };
41
+ }
42
+
43
+ /** Shell envelope status only. Test prose such as "FAIL" is not an exit status. */
44
+ function exitCode(text: string): number | undefined {
45
+ try {
46
+ const wrapped = text.indexOf("\nOutput:\n");
47
+ const raw: unknown = JSON.parse(wrapped >= 0 ? text.slice(wrapped + "\nOutput:\n".length) : text);
48
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
49
+ const record = raw as Record<string, unknown>;
50
+ const code = record.exit_code ?? record.exitCode;
51
+ if (typeof code === "number" && Number.isSafeInteger(code)) return code;
52
+ }
53
+ } catch { /* textual shell envelope */ }
54
+ const code = /^(?:Process exited with code|Process exit code:|Exit code:)\s*(-?\d+)\s*$/m.exec(text);
55
+ return code ? Number(code[1]) : undefined;
56
+ }
57
+
58
+ /** Reconstructed from paired tool calls/results after the latest user turn; no LLM or timers. */
59
+ export function collectAdvisorRepairEvidence(parsed: OcxParsedRequest): AdvisorRepairEvidence | undefined {
60
+ const calls = new Map<string, { tool: string; args: Record<string, unknown>; invokedAt: number }>();
61
+ const sessions = new Map<string, Validation>();
62
+ const failures = new Map<string, { failure: AdvisorRepairEvidence["firstFailure"]; callId: string; repair?: AdvisorRepairEvidence["repair"] & { callId: string } }>();
63
+ let candidate: AdvisorRepairEvidence | undefined;
64
+ let candidateFingerprint: string | undefined;
65
+ const messages = parsed.context.messages;
66
+ let start = messages.findLastIndex(message => message.role === "user") + 1;
67
+ // Bounded work; a clipped prefix cannot establish a failure/repair sequence.
68
+ start = Math.max(start, messages.length - 512);
69
+ for (let i = start; i < messages.length; i++) {
70
+ const message = messages[i]!;
71
+ if (message.role === "assistant") {
72
+ for (const part of message.content) if (part.type === "toolCall") {
73
+ calls.set(part.id, { ...advisorEvidenceTool(part.name, part.arguments ?? {}), invokedAt: i + 1 });
74
+ }
75
+ continue;
76
+ }
77
+ if (message.role !== "toolResult") continue;
78
+ const call = calls.get(message.toolCallId);
79
+ if (!call) continue;
80
+ calls.delete(message.toolCallId); // A replayed result cannot stand in for another execution.
81
+ const output = contentText(message.content);
82
+ if (["apply_patch", "Write", "Edit", "write_file", "edit_file"].includes(call.tool) && !message.isError
83
+ && output.trim() && !/^(?:error|failed|failure)\b/i.test(output.trim())) {
84
+ for (const failure of failures.values()) if (call.invokedAt > failure.failure.message) failure.repair = { message: i + 1, tool: call.tool, callId: message.toolCallId };
85
+ continue;
86
+ }
87
+ let check: Validation | undefined;
88
+ if (["exec_command", "shell_command", "shell", "Bash", "bash"].includes(call.tool)) check = validation(call.args, call.invokedAt);
89
+ else if (call.tool === "write_stdin") check = sessions.get(String(call.args.session_id));
90
+ if (!check) continue;
91
+ const code = exitCode(output);
92
+ if (code === undefined) {
93
+ const session = /(?:Process running with session ID\s+|"session_id"\s*:\s*)(\d+)/.exec(output);
94
+ if (session) sessions.set(session[1]!, check);
95
+ continue;
96
+ }
97
+ if (call.tool === "write_stdin") sessions.delete(String(call.args.session_id));
98
+ if (code === 0) {
99
+ failures.delete(check.fingerprint);
100
+ if (candidateFingerprint === check.fingerprint) { candidate = undefined; candidateFingerprint = undefined; }
101
+ continue;
102
+ }
103
+ const failure = { message: i + 1, exitCode: code, output: excerpt(output) };
104
+ const earlier = failures.get(check.fingerprint);
105
+ if (earlier?.repair && check.invokedAt > earlier.repair.message) {
106
+ candidate = {
107
+ // Positions are prompt references only: history pruning must not mint a new event.
108
+ id: digest(JSON.stringify([check.fingerprint, earlier.callId, earlier.repair.callId, message.toolCallId])),
109
+ command: check.command,
110
+ firstFailure: earlier.failure, repair: { message: earlier.repair.message, tool: earlier.repair.tool }, repeatedFailure: failure,
111
+ };
112
+ candidateFingerprint = check.fingerprint;
113
+ }
114
+ failures.set(check.fingerprint, { failure, callId: message.toolCallId });
115
+ }
116
+ return candidate;
117
+ }
@@ -0,0 +1,353 @@
1
+ /**
2
+ * The advisor request plan: what the optional subsystem registers into the core Responses path.
3
+ *
4
+ * Created PER REQUEST through the activated factory called by the sidecar planner — never
5
+ * at module load and never globally. All mutable state is request-scoped except the bounded
6
+ * task-scoped preflight ledger (src/advisor/state.ts).
7
+ *
8
+ * Responsibilities:
9
+ * - decide whether the advisor applies to this request (settings + capability of the path);
10
+ * - preflight: one automatic consultation ATTEMPT per task when the orientation evidence exists,
11
+ * claimed atomically in the ledger and injected as user-role advisory data;
12
+ * - manual: back the synthetic `advisor` tool guard with real consultations through the routing
13
+ * authority (loopback chat completion);
14
+ * - observability: one structured log line per consultation — proof that the advisor actually
15
+ * ran (worker model, advisor model, trigger, duration, status, usage).
16
+ *
17
+ * Preflight injection transport: the runtime-owned instruction is fixed developer policy;
18
+ * all Advisor-generated bytes are a separate JSON-quoted user-role advisory message. Manual
19
+ * consultations remain paired tool results. No automatic path fabricates a tool call or
20
+ * places Advisor output in developer/system content. User-role advice can still contain
21
+ * hostile recommendations; the fixed policy tells the worker how to interpret that data.
22
+ */
23
+ import type { OcxConfig, OcxParsedRequest } from "../types";
24
+ import type { AdvisorPlan, AdvisorConsultOutcome } from "../server/responses/advisor-slot";
25
+ import { createAdvisorGuard, MAX_ADVISOR_CONSULTATIONS_PER_REQUEST } from "../server/responses/advisor-slot";
26
+ import { resolveAdvisorSettings, type AdvisorPolicy } from "./settings";
27
+ import { consultAdvisor } from "./consult";
28
+ import { buildAdvisorTool } from "./synthetic-tool";
29
+ import { sanitizeLogMetadataString } from "../lib/redact";
30
+ import {
31
+ advisorLedgerKey,
32
+ createAdvisorPreflightLedger,
33
+ hasOrientationEvidence,
34
+ historyHasManualAdvisorResult,
35
+ type AdvisorPreflightLedger,
36
+ } from "./state";
37
+ import { formatAdvisorAdvice, advisorPreflightMessages, formatAdvisorUnavailable } from "./context";
38
+ import { advisorActivityStore, type AdvisorActivityStore, type AdvisorTrigger } from "./activity";
39
+ import { createAdvisorTaskBudget, type AdvisorTaskBudget } from "./budget";
40
+ import { collectAdvisorRepairEvidence, type AdvisorRepairEvidence } from "./evidence";
41
+
42
+ /**
43
+ * Process-local task ledger. Bounded (entries + per-state TTL) in src/advisor/state.ts; one
44
+ * instance per process so the claim is atomic across concurrent requests. Not durable by design
45
+ * — see the documented restart limitation.
46
+ */
47
+ const sharedPreflightLedger = createAdvisorPreflightLedger();
48
+
49
+ export const ADVISOR_SHARED_LEDGER = sharedPreflightLedger;
50
+
51
+ export interface AdvisorRuntimeDeps {
52
+ config: Pick<OcxConfig, "advisor" | "port" | "hostname" | "apiKeys" | "unauthenticatedLoopbackListener">;
53
+ /** Routed worker identity for logs and the advisor payload, e.g. "deepseek-chat (provider deepseek)". */
54
+ workerIdentity: string;
55
+ workerModelId: string;
56
+ abortSignal?: AbortSignal;
57
+ /** Test seam; production always self-fetches the resolved local destination. */
58
+ baseUrlOverride?: string;
59
+ /** Test seam; production uses the process-wide ledger. */
60
+ ledger?: AdvisorPreflightLedger;
61
+ budget?: AdvisorTaskBudget;
62
+ activities?: AdvisorActivityStore;
63
+ /** Deterministic clock seam for the ledger's TTL/cooldown arithmetic (tests only). */
64
+ now?: () => number;
65
+ /**
66
+ * Test seam: runs after a preflight claim is taken and before the consultation
67
+ * dispatch, so a test can revoke consent in the claim-to-outbound window.
68
+ */
69
+ afterPreflightClaim?: () => void;
70
+ }
71
+
72
+ export interface AdvisorRuntimePlan extends AdvisorPlan {
73
+ readonly policy: AdvisorPolicy;
74
+ readonly toolEnabled: boolean;
75
+ readonly tool: import("../types").OcxTool;
76
+ /**
77
+ * The automatic preflight pass. Returns true when advice was injected. A cancelled
78
+ * consultation injects nothing and leaves the task eligible for a later attempt.
79
+ */
80
+ preflightInject(parsed: OcxParsedRequest): Promise<boolean>;
81
+ /** Attach the stream guard for the synthetic tool to the parsed request. */
82
+ attachGuard(parsed: OcxParsedRequest): void;
83
+ }
84
+
85
+ export function createAdvisorRuntimePlan(deps: AdvisorRuntimeDeps): AdvisorRuntimePlan | null {
86
+ const initial = resolveAdvisorSettings(deps.config);
87
+ // A model is required before the synthetic tool exists. Consent is checked at every
88
+ // consultation against the live config, so an enabled advisor without current consent can
89
+ // still refuse a manual call in-band without sending task context. Disabled, or enabled
90
+ // with no model, stays off the path.
91
+ if (!initial.enabled || initial.model.trim() === "") return null;
92
+ const ledger = deps.ledger ?? sharedPreflightLedger;
93
+ const now = deps.now ?? (() => Date.now());
94
+ const budget = deps.budget ?? createAdvisorTaskBudget();
95
+ const activities = deps.activities ?? advisorActivityStore(deps.config);
96
+ // Management PUT mutates this same config object (`config.advisor = ...`). Re-resolving
97
+ // at dispatch is what makes a mid-request consent revocation stop outbound transfer.
98
+ const liveSettings = () => resolveAdvisorSettings(deps.config);
99
+
100
+ // Request-scoped state: born here, dies with the request. Never global.
101
+ const fingerprints = new Set<string>();
102
+ let preflightUsed = false;
103
+ let consultations = 0;
104
+ // Capture before automatic user-role advisory injection. Continuations belong to this
105
+ // original task; an injected advisory must never mint another spending allowance.
106
+ let identity: { preflight?: string; budget?: string } | undefined;
107
+ const identify = (parsed: OcxParsedRequest) => identity ??= {
108
+ preflight: advisorLedgerKey(parsed, deps.workerModelId),
109
+ budget: advisorLedgerKey(parsed, ""),
110
+ };
111
+
112
+ const taskKey = (parsed: OcxParsedRequest): string | undefined =>
113
+ identify(parsed).preflight;
114
+
115
+ const logConsultation = (
116
+ trigger: AdvisorTrigger,
117
+ outcome: { ok: boolean; cancelled?: boolean; durationMs: number; error?: string; usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number } },
118
+ advisorModel: string,
119
+ ): void => {
120
+ const usage = outcome.usage
121
+ ? ` usage=in=${outcome.usage.inputTokens ?? "?"} out=${outcome.usage.outputTokens ?? "?"}`
122
+ : "";
123
+ const status = outcome.ok ? "ok" : outcome.cancelled ? "cancelled" : "failed";
124
+ const errorNote = outcome.ok || outcome.cancelled
125
+ ? ""
126
+ : ` error=${sanitizeLogMetadataString(outcome.error ?? "unknown", 160) ?? "unknown"}`;
127
+ // One structured line per consultation: model ids, timing, and a bounded status. No prompt,
128
+ // no tool output, no capability.
129
+ console.warn(
130
+ `[advisor] consultation ${status} trigger=${trigger} worker=${deps.workerModelId}`
131
+ + ` advisor=${advisorModel} durationMs=${outcome.durationMs}${usage}${errorNote}`,
132
+ );
133
+ };
134
+
135
+ const runConsultation = async (
136
+ parsed: OcxParsedRequest,
137
+ reason: AdvisorTrigger,
138
+ question: string | undefined,
139
+ evidence?: AdvisorRepairEvidence,
140
+ ): Promise<AdvisorConsultOutcome> => {
141
+ const current = liveSettings();
142
+ if (!current.enabled || current.model.trim() === "") {
143
+ return {
144
+ ok: false,
145
+ isError: true,
146
+ blocked: "settings",
147
+ content: formatAdvisorUnavailable(
148
+ reason === "manual" ? "manual" : "preflight",
149
+ "advisor is not configured",
150
+ ),
151
+ };
152
+ }
153
+ // Consent is operator config. Task text, the worker, and the advisor cannot grant it.
154
+ // The live object is re-read here so a revocation after plan creation still blocks
155
+ // outbound transfer. No fingerprint, no ledger write, no fetch.
156
+ if (current.contextSharingConsent === null) {
157
+ console.warn(
158
+ `[advisor] consultation blocked trigger=${reason} reason=advisor_context_sharing_consent_required`,
159
+ );
160
+ return {
161
+ ok: false,
162
+ isError: true,
163
+ blocked: "consent",
164
+ content: formatAdvisorUnavailable(
165
+ "consent",
166
+ "advisor_context_sharing_consent_required",
167
+ ),
168
+ };
169
+ }
170
+ // Same-consultation dedup within this request: identical trigger + focus returns a
171
+ // non-advice outcome instead of a second expert call.
172
+ const fingerprint = `${reason}|${question ?? ""}|${evidence?.id ?? ""}`;
173
+ let result;
174
+ if (fingerprints.has(fingerprint)) {
175
+ result = {
176
+ ok: false,
177
+ advice: "",
178
+ advisorModel: current.model,
179
+ error: "duplicate consultation request (already consulted with this focus in this request)",
180
+ durationMs: 0,
181
+ };
182
+ return {
183
+ ok: false,
184
+ isError: true,
185
+ content: formatAdvisorUnavailable(reason === "manual" ? "manual" : "preflight", result.error),
186
+ };
187
+ }
188
+ if (deps.abortSignal?.aborted) return { ok: false, isError: true, cancelled: true, content: "" };
189
+ const budgetKey = identify(parsed).budget;
190
+ const requestCapped = consultations >= MAX_ADVISOR_CONSULTATIONS_PER_REQUEST;
191
+ if (requestCapped
192
+ || (current.policy === "adaptive" && budgetKey && !budget.reserve(budgetKey, current.maxConsultationsPerTask, now()).ok)) {
193
+ const id = activities.start({ trigger: reason, workerModel: deps.workerModelId, advisorModel: current.model, requestedEffort: current.effort }, now());
194
+ const blocked = requestCapped ? "limit" : "budget";
195
+ activities.finish(id, { status: "blocked", durationMs: 0, blockReason: blocked });
196
+ return { ok: false, isError: true, blocked, content: formatAdvisorUnavailable(blocked, requestCapped
197
+ ? "per-request consultation limit reached; no consultation sent"
198
+ : "task budget exhausted or unavailable; no consultation sent") };
199
+ }
200
+ consultations += 1;
201
+ fingerprints.add(fingerprint);
202
+ const startedAt = now();
203
+ const activityId = activities.start({ trigger: reason, workerModel: deps.workerModelId, advisorModel: current.model, requestedEffort: current.effort }, startedAt);
204
+ try {
205
+ result = await consultAdvisor(
206
+ {
207
+ parsed,
208
+ workerIdentity: deps.workerIdentity,
209
+ advisorModel: current.model,
210
+ reason,
211
+ ...(evidence ? { evidence } : {}),
212
+ ...(question !== undefined ? { question } : {}),
213
+ },
214
+ deps.config,
215
+ current.effort,
216
+ current.timeoutMs,
217
+ deps.abortSignal,
218
+ deps.baseUrlOverride,
219
+ );
220
+ } catch (error) {
221
+ activities.finish(activityId, { status: "failed", durationMs: Math.max(0, now() - startedAt) });
222
+ throw error;
223
+ }
224
+ activities.finish(activityId, {
225
+ status: result.ok ? "ok" : result.cancelled ? "cancelled" : "failed",
226
+ durationMs: result.durationMs, effectiveEffort: result.effectiveEffort ?? null,
227
+ ...(result.usage ? { usage: result.usage } : {}),
228
+ });
229
+ logConsultation(reason, result, current.model);
230
+
231
+ if (result.ok) {
232
+ // A genuine result suppresses further automatic consultation for the task. Manual success
233
+ // settles it too: a task the worker already had advised does not need a preflight attempt.
234
+ if (reason === "manual") {
235
+ const key = taskKey(parsed);
236
+ // A manual consultation owns no preflight claim; this records the FACT that the task was
237
+ // advised, which is true whichever consultation produced the advice.
238
+ if (key) ledger.markAdvised(key, now());
239
+ }
240
+ return {
241
+ ok: true,
242
+ isError: false,
243
+ content: formatAdvisorAdvice({
244
+ advisorModel: result.advisorModel,
245
+ reason,
246
+ advice: result.advice,
247
+ channel: reason === "manual" ? "manual" : "preflight",
248
+ }),
249
+ };
250
+ }
251
+ return {
252
+ ok: false,
253
+ isError: true,
254
+ ...(result.cancelled ? { cancelled: true } : {}),
255
+ content: formatAdvisorUnavailable(reason === "manual" ? "manual" : "preflight", result.error ?? "unavailable"),
256
+ };
257
+ };
258
+
259
+ const preflightInject = async (parsed: OcxParsedRequest): Promise<boolean> => {
260
+ const task = identify(parsed);
261
+ const current = liveSettings();
262
+ if (current.policy === "manual" || preflightUsed || deps.abortSignal?.aborted) return false;
263
+ // No consent, disabled, or no model: do not claim, do not inject, do not send task context.
264
+ if (!current.enabled || current.model.trim() === "" || current.contextSharingConsent === null) return false;
265
+ // A genuine MANUAL consultation already advised this task (verifiable tool-result
266
+ // provenance), or the task has no orientation evidence yet: skip.
267
+ // Adaptive automation needs a stable identity for its durable task budget. Legacy
268
+ // preflight keeps its documented identity-less, request-scoped behavior.
269
+ if (current.policy === "adaptive" && !task.budget) return false;
270
+ const evidence = current.policy === "adaptive" ? collectAdvisorRepairEvidence(parsed) : undefined;
271
+ const reason = evidence ? "repair_failure" : "preflight";
272
+ if (!evidence && (historyHasManualAdvisorResult(parsed) || !hasOrientationEvidence(parsed))) return false;
273
+
274
+ // Atomic claim. A client with a stable conversation identity participates in the
275
+ // process-global ledger, so concurrent requests for one task consult at most once and a
276
+ // settled task is not re-consulted. A client with NO stable identity stays out of the
277
+ // ledger on purpose: request-scoped dedup plus genuine in-history provenance are the only
278
+ // suppression it gets — fail-open, so two independent identity-less conversations can never
279
+ // suppress each other through a shared guess.
280
+ const baseKey = taskKey(parsed);
281
+ const key = evidence && baseKey ? `${baseKey}:repair:${evidence.id}` : baseKey;
282
+ let claimToken: string | undefined;
283
+ if (key) {
284
+ const claim = ledger.claim(key, now());
285
+ if (claim.state !== "claimed") return false;
286
+ claimToken = claim.token;
287
+ }
288
+ preflightUsed = true;
289
+ deps.afterPreflightClaim?.();
290
+
291
+ let outcome: AdvisorConsultOutcome;
292
+ try {
293
+ outcome = await runConsultation(parsed, reason, undefined, evidence);
294
+ } catch (error) {
295
+ if (key && claimToken) ledger.fail(key, claimToken, now());
296
+ console.warn(`[advisor] consultation failed trigger=${reason} worker=${deps.workerModelId} error=plan_threw`);
297
+ parsed.context.messages = [
298
+ ...parsed.context.messages,
299
+ {
300
+ role: "user",
301
+ content: "An automatic advisor consultation could not be completed. Continue with your own judgment.",
302
+ timestamp: Date.now(),
303
+ },
304
+ ];
305
+ void error;
306
+ return false;
307
+ }
308
+
309
+ if (outcome.blocked) {
310
+ // Operator revoked consent (or disabled the sidecar) after the claim: not a provider
311
+ // failure. Release so the task is not put on cooldown and nothing is injected.
312
+ if (key && claimToken) ledger.release(key, claimToken, now());
313
+ return false;
314
+ }
315
+ if (outcome.ok) {
316
+ if (key && claimToken) ledger.complete(key, claimToken, now());
317
+ } else if (outcome.cancelled) {
318
+ // Client cancellation is not a provider failure: no cooldown, the task may retry later.
319
+ if (key && claimToken) ledger.release(key, claimToken, now());
320
+ // Nothing to inject — the caller is gone or aborting; do not add noise to a live turn.
321
+ return false;
322
+ } else {
323
+ if (key && claimToken) ledger.fail(key, claimToken, now());
324
+ }
325
+
326
+ parsed.context.messages = [
327
+ ...parsed.context.messages,
328
+ ...advisorPreflightMessages(outcome.content),
329
+ ];
330
+ return outcome.ok;
331
+ };
332
+
333
+ const plan: AdvisorPlan = {
334
+ consult: (parsed, reason, question) => runConsultation(parsed, reason, question),
335
+ // The guard's own failure/limit text goes through the same runtime-owned, marker-neutralized
336
+ // formatter so no guard path can emit text that looks like a genuine advice wrapper.
337
+ formatUnavailable: (kind, error) => formatAdvisorUnavailable(kind, error),
338
+ };
339
+
340
+ return {
341
+ policy: initial.policy,
342
+ // The synthetic tool is only safe where the guard can intercept: run-turn adapters own their
343
+ // own loops, so they get preflight support but never the tool (documented limitation).
344
+ toolEnabled: initial.enabled,
345
+ tool: buildAdvisorTool(),
346
+ consult: plan.consult,
347
+ formatUnavailable: plan.formatUnavailable,
348
+ preflightInject,
349
+ attachGuard: parsed => {
350
+ parsed._advisorGuard = createAdvisorGuard(plan);
351
+ },
352
+ };
353
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The single reader of `advisor` config.
3
+ *
4
+ * Nothing else reads the `advisor` key directly: the sidecar planner, the management API and the
5
+ * CLI all resolve through here, so they cannot disagree about defaults or validity. Type-only
6
+ * config import keeps this module free of runtime edges.
7
+ */
8
+ import type { OcxConfig } from "../types";
9
+ import { ADVISOR_CONTEXT_SHARING_CONSENT_VERSION } from "./disclosure";
10
+
11
+ export { ADVISOR_CONTEXT_SHARING_CONSENT_VERSION };
12
+
13
+ export type AdvisorPolicy = "manual" | "preflight" | "adaptive";
14
+
15
+ export const ADVISOR_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"] as const;
16
+ export type AdvisorEffort = (typeof ADVISOR_EFFORTS)[number];
17
+
18
+ export const ADVISOR_POLICIES = ["manual", "preflight", "adaptive"] as const;
19
+
20
+ export interface AdvisorSettings {
21
+ enabled: boolean;
22
+ model: string;
23
+ effort: AdvisorEffort;
24
+ policy: AdvisorPolicy;
25
+ timeoutMs: number;
26
+ /** Adaptive task budget, including manual calls. Stable task identity is required. */
27
+ maxConsultationsPerTask: number;
28
+ /**
29
+ * Current context-sharing consent, or null when absent, stale, or malformed.
30
+ * `enabled` does not imply this. Only the current version allows data transfer.
31
+ */
32
+ contextSharingConsent: typeof ADVISOR_CONTEXT_SHARING_CONSENT_VERSION | null;
33
+ /** Where each resolved value came from, so the GUI/CLI can show real runtime state. */
34
+ sources: {
35
+ enabled: "default" | "configured";
36
+ model: "default" | "configured";
37
+ effort: "default" | "configured";
38
+ policy: "default" | "configured";
39
+ contextSharingConsent: "default" | "configured";
40
+ };
41
+ }
42
+
43
+ export const DEFAULT_ADVISOR_SETTINGS: Readonly<AdvisorSettings> = Object.freeze({
44
+ enabled: false,
45
+ model: "",
46
+ effort: "max",
47
+ policy: "manual",
48
+ timeoutMs: 120_000,
49
+ maxConsultationsPerTask: 3,
50
+ contextSharingConsent: null,
51
+ sources: Object.freeze({
52
+ enabled: "default",
53
+ model: "default",
54
+ effort: "default",
55
+ policy: "default",
56
+ contextSharingConsent: "default",
57
+ }),
58
+ });
59
+
60
+ type Rec = Record<string, unknown>;
61
+ function isRec(value: unknown): value is Rec {
62
+ return !!value && typeof value === "object" && !Array.isArray(value);
63
+ }
64
+
65
+ export function isValidAdvisorEffort(value: unknown): value is AdvisorEffort {
66
+ return typeof value === "string" && (ADVISOR_EFFORTS as readonly string[]).includes(value);
67
+ }
68
+
69
+ export function isValidAdvisorPolicy(value: unknown): value is AdvisorPolicy {
70
+ return typeof value === "string" && (ADVISOR_POLICIES as readonly string[]).includes(value);
71
+ }
72
+
73
+ export function isValidAdvisorTaskLimit(value: unknown): value is number {
74
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 3;
75
+ }
76
+
77
+ /** True only for the current context-sharing consent version. Anything else is not a grant. */
78
+ export function isCurrentAdvisorContextSharingConsent(
79
+ value: unknown,
80
+ ): value is typeof ADVISOR_CONTEXT_SHARING_CONSENT_VERSION {
81
+ return value === ADVISOR_CONTEXT_SHARING_CONSENT_VERSION;
82
+ }
83
+
84
+ /**
85
+ * Resolve advisor settings with conservative defaults for every absent or malformed field.
86
+ * A malformed block resolves to fully disabled defaults rather than throwing: the advisor is
87
+ * optional and must never take the request path down with it.
88
+ */
89
+ export function resolveAdvisorSettings(config: Pick<OcxConfig, "advisor">): AdvisorSettings {
90
+ const raw: unknown = config.advisor;
91
+ if (!isRec(raw)) return { ...DEFAULT_ADVISOR_SETTINGS, sources: { ...DEFAULT_ADVISOR_SETTINGS.sources } };
92
+ const enabled = typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_ADVISOR_SETTINGS.enabled;
93
+ const model = typeof raw.model === "string" ? raw.model.trim() : DEFAULT_ADVISOR_SETTINGS.model;
94
+ const effort = isValidAdvisorEffort(raw.effort) ? raw.effort : DEFAULT_ADVISOR_SETTINGS.effort;
95
+ const policy = isValidAdvisorPolicy(raw.policy) ? raw.policy : DEFAULT_ADVISOR_SETTINGS.policy;
96
+ const timeoutRaw = raw.timeoutMs;
97
+ const timeoutMs = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw) && timeoutRaw >= 1_000
98
+ ? Math.min(Math.floor(timeoutRaw), 600_000)
99
+ : DEFAULT_ADVISOR_SETTINGS.timeoutMs;
100
+ // A present but non-current value (stale version, wrong type) resolves as no consent.
101
+ // The stored bytes are left untouched; this reader never writes an upgrade.
102
+ const consentConfigured = Object.prototype.hasOwnProperty.call(raw, "contextSharingConsent");
103
+ const contextSharingConsent = isCurrentAdvisorContextSharingConsent(raw.contextSharingConsent)
104
+ ? raw.contextSharingConsent
105
+ : null;
106
+ return {
107
+ enabled,
108
+ model,
109
+ effort,
110
+ policy,
111
+ timeoutMs,
112
+ maxConsultationsPerTask: isValidAdvisorTaskLimit(raw.maxConsultationsPerTask)
113
+ ? raw.maxConsultationsPerTask : DEFAULT_ADVISOR_SETTINGS.maxConsultationsPerTask,
114
+ contextSharingConsent,
115
+ sources: {
116
+ enabled: typeof raw.enabled === "boolean" ? "configured" : "default",
117
+ model: typeof raw.model === "string" && raw.model.trim() !== "" ? "configured" : "default",
118
+ effort: isValidAdvisorEffort(raw.effort) ? "configured" : "default",
119
+ policy: isValidAdvisorPolicy(raw.policy) ? "configured" : "default",
120
+ contextSharingConsent: consentConfigured ? "configured" : "default",
121
+ },
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Whether a consultation may send task context. Requires the switch, a model, and the
127
+ * current context-sharing consent. `enabled` is not consent. A miss fails closed for the
128
+ * data transfer and leaves the worker request itself running.
129
+ */
130
+ export function advisorRunnable(settings: AdvisorSettings): boolean {
131
+ return settings.enabled
132
+ && settings.model.trim() !== ""
133
+ && settings.contextSharingConsent === ADVISOR_CONTEXT_SHARING_CONSENT_VERSION;
134
+ }
135
+
136
+ /**
137
+ * Enabled, with a model, but without current consent. The management surface reports
138
+ * `advisor_context_sharing_consent_required` and the runtime sends no task context.
139
+ */
140
+ export function advisorContextSharingBlocked(settings: AdvisorSettings): boolean {
141
+ return settings.enabled
142
+ && settings.model.trim() !== ""
143
+ && settings.contextSharingConsent !== ADVISOR_CONTEXT_SHARING_CONSENT_VERSION;
144
+ }