@vellumai/assistant 0.10.9 → 0.10.10-dev.202607170046.8482d40

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 (496) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +26 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +16 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +26 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +16 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  14. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  15. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +26 -0
  16. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  17. package/node_modules/@vellumai/service-contracts/src/ingress.ts +16 -0
  18. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  19. package/openapi.yaml +678 -107
  20. package/package.json +1 -1
  21. package/scripts/generate-openapi.ts +8 -0
  22. package/src/__tests__/activation-early-marking.test.ts +6 -5
  23. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  24. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  26. package/src/__tests__/app-bundler.test.ts +4 -12
  27. package/src/__tests__/app-executors.test.ts +2 -49
  28. package/src/__tests__/app-routes-csp.test.ts +114 -146
  29. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  30. package/src/__tests__/build-persisted-content.test.ts +96 -0
  31. package/src/__tests__/bundle-scanner.test.ts +27 -1
  32. package/src/__tests__/call-controller.test.ts +291 -0
  33. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  34. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  35. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  36. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  37. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  38. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  39. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  40. package/src/__tests__/config-schema.test.ts +190 -257
  41. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  42. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  43. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  44. package/src/__tests__/conversation-error.test.ts +31 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  46. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  47. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  48. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  49. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  50. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  51. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  52. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  53. package/src/__tests__/conversation-usage.test.ts +4 -12
  54. package/src/__tests__/credential-prompt-route.test.ts +137 -0
  55. package/src/__tests__/credential-routes.test.ts +355 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +5 -0
  57. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  58. package/src/__tests__/empty-response-hook.test.ts +186 -1
  59. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  60. package/src/__tests__/heartbeat-service.test.ts +0 -28
  61. package/src/__tests__/host-shell-tool.test.ts +2 -0
  62. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  63. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  64. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  65. package/src/__tests__/invite-routes-http.test.ts +12 -16
  66. package/src/__tests__/list-all-apps.test.ts +0 -4
  67. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  68. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  69. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  70. package/src/__tests__/llm-resolver.test.ts +342 -602
  71. package/src/__tests__/llm-schema.test.ts +79 -37
  72. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  73. package/src/__tests__/media-stream-output.test.ts +259 -3
  74. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  75. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  76. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  77. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  78. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  79. package/src/__tests__/mtime-cache.test.ts +61 -0
  80. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  81. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  82. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  83. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  84. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  85. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  86. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  87. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  88. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  89. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  90. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  91. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  92. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  93. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  94. package/src/__tests__/published-app-updater.test.ts +22 -16
  95. package/src/__tests__/registry.test.ts +5 -16
  96. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  97. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  98. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  99. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  100. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  101. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  102. package/src/__tests__/secret-fixtures.ts +9 -0
  103. package/src/__tests__/secret-routes-acp-guard.test.ts +77 -0
  104. package/src/__tests__/server-history-render.test.ts +48 -0
  105. package/src/__tests__/skills.test.ts +9 -4
  106. package/src/__tests__/slack-share-routes.test.ts +0 -1
  107. package/src/__tests__/stt-stream-session.test.ts +6 -5
  108. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  109. package/src/__tests__/subagent-disposal.test.ts +2 -0
  110. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  111. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  112. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  113. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  114. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  115. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  116. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  117. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  118. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  119. package/src/__tests__/telemetry-routes.test.ts +99 -19
  120. package/src/__tests__/tool-audit.test.ts +34 -4
  121. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  122. package/src/__tests__/tool-profiler.test.ts +72 -1
  123. package/src/__tests__/tool-result-spool.test.ts +49 -4
  124. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  125. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  126. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  127. package/src/__tests__/usage-attribution.test.ts +18 -41
  128. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  129. package/src/__tests__/voice-config-update.test.ts +46 -0
  130. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  131. package/src/__tests__/weak-open-model.test.ts +8 -1
  132. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  133. package/src/acp/__tests__/acp-claude-oauth.test.ts +206 -0
  134. package/src/acp/__tests__/acp-connect-card-state.test.ts +23 -0
  135. package/src/acp/__tests__/acp-credentials.test.ts +87 -0
  136. package/src/acp/__tests__/prepare-agent-env.test.ts +161 -1
  137. package/src/acp/acp-claude-oauth.ts +144 -0
  138. package/src/acp/acp-connect-card-state.ts +39 -0
  139. package/src/acp/acp-credentials.ts +67 -0
  140. package/src/acp/prepare-agent-env.ts +103 -6
  141. package/src/agent/loop.ts +8 -3
  142. package/src/api/events/open-conversation.test.ts +64 -0
  143. package/src/api/events/open-conversation.ts +33 -0
  144. package/src/api/events/tool-result.ts +8 -0
  145. package/src/api/index.ts +6 -0
  146. package/src/api/responses/conversation-message.ts +14 -0
  147. package/src/apps/app-store.ts +25 -35
  148. package/src/bundler/app-bundler.ts +34 -48
  149. package/src/bundler/app-compiler.ts +39 -4
  150. package/src/bundler/bundle-scanner.ts +13 -0
  151. package/src/bundler/manifest.ts +1 -1
  152. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  153. package/src/calls/call-constants.ts +5 -0
  154. package/src/calls/call-controller.ts +100 -32
  155. package/src/calls/call-transport.ts +9 -0
  156. package/src/calls/media-stream-output.ts +107 -4
  157. package/src/calls/media-stream-server.ts +29 -9
  158. package/src/calls/media-stream-stt-session.ts +7 -1
  159. package/src/calls/media-turn-detector.ts +11 -1
  160. package/src/calls/voice-session-bridge.ts +84 -63
  161. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  162. package/src/cli/commands/__tests__/inference-send.test.ts +50 -0
  163. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  164. package/src/cli/commands/config.help.ts +6 -6
  165. package/src/cli/commands/credentials.help.ts +13 -0
  166. package/src/cli/commands/credentials.ts +23 -1
  167. package/src/cli/commands/email.help.ts +7 -0
  168. package/src/cli/commands/email.ts +35 -1
  169. package/src/cli/commands/inference-providers.ts +168 -107
  170. package/src/cli/commands/inference.help.ts +118 -46
  171. package/src/cli/commands/inference.ts +6 -0
  172. package/src/cli/commands/memory/index.help.ts +23 -0
  173. package/src/cli/commands/memory/nodes.ts +146 -0
  174. package/src/cli/commands/notifications.help.ts +10 -10
  175. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  176. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  177. package/src/cli/commands/oauth/connect.test.ts +126 -0
  178. package/src/cli/commands/oauth/connect.ts +29 -6
  179. package/src/cli/commands/oauth/index.help.ts +7 -1
  180. package/src/cli/commands/oauth/status.test.ts +69 -3
  181. package/src/cli/commands/oauth/status.ts +50 -12
  182. package/src/cli/commands/plugins.help.ts +13 -2
  183. package/src/cli/commands/plugins.ts +61 -7
  184. package/src/cli/commands/telemetry.help.ts +13 -0
  185. package/src/cli/commands/telemetry.ts +45 -5
  186. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  187. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  188. package/src/cli/lib/bundled-marketplace.json +93 -36
  189. package/src/cli/lib/inspect-plugin.ts +5 -1
  190. package/src/cli/lib/install-from-github.ts +68 -4
  191. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  192. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  193. package/src/cli/lib/upgrade-plugin.ts +236 -35
  194. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  195. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  196. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  197. package/src/config/bundled-skills/AGENTS.md +3 -30
  198. package/src/config/bundled-skills/acp/SKILL.md +11 -3
  199. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  200. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  201. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  202. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  203. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  204. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  205. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  207. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  208. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  209. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  210. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  211. package/src/config/call-site-defaults.ts +3 -3
  212. package/src/config/feature-flag-registry.json +24 -39
  213. package/src/config/llm-resolver.ts +88 -475
  214. package/src/config/profile-materialization.ts +11 -11
  215. package/src/config/schema.ts +93 -127
  216. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  217. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  218. package/src/config/schemas/live-voice.ts +10 -4
  219. package/src/config/schemas/llm.ts +52 -15
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrospective.ts +8 -0
  222. package/src/config/schemas/memory-v2.ts +2 -2
  223. package/src/config/schemas/memory-v3.ts +1 -1
  224. package/src/config/schemas/services.ts +6 -3
  225. package/src/config/schemas/stt.ts +38 -21
  226. package/src/config/schemas/tts.ts +13 -18
  227. package/src/config/skills.ts +31 -21
  228. package/src/context/compactor.ts +44 -7
  229. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  230. package/src/context/tool-result-spool.ts +26 -5
  231. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  232. package/src/conversations/message-consolidation.ts +22 -2
  233. package/src/credential-execution/prompted-credential.ts +18 -0
  234. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  235. package/src/daemon/app-source-watcher.ts +17 -23
  236. package/src/daemon/chat-credential-redaction.ts +1365 -0
  237. package/src/daemon/conversation-agent-loop-handlers.ts +640 -34
  238. package/src/daemon/conversation-agent-loop.ts +71 -31
  239. package/src/daemon/conversation-error.ts +36 -14
  240. package/src/daemon/conversation-process.ts +22 -0
  241. package/src/daemon/conversation-store.ts +35 -0
  242. package/src/daemon/conversation-surfaces.ts +25 -25
  243. package/src/daemon/conversation-tool-setup.ts +33 -7
  244. package/src/daemon/conversation.ts +55 -5
  245. package/src/daemon/handlers/shared.ts +40 -2
  246. package/src/daemon/lifecycle.ts +4 -4
  247. package/src/daemon/message-types/conversations.ts +6 -17
  248. package/src/daemon/providers-setup.ts +8 -0
  249. package/src/daemon/tool-setup-types.ts +7 -1
  250. package/src/daemon/wake-conversation-ops.ts +10 -1
  251. package/src/hooks/types.ts +9 -0
  252. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  253. package/src/ipc/gateway-client.test.ts +59 -0
  254. package/src/ipc/gateway-client.ts +70 -27
  255. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  256. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  257. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  258. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  259. package/src/live-voice/live-voice-session.ts +443 -40
  260. package/src/live-voice/protocol.ts +143 -1
  261. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  262. package/src/monitoring/plugin-source-watch.ts +3 -62
  263. package/src/notifications/README.md +1 -1
  264. package/src/permissions/checker.ts +8 -4
  265. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  266. package/src/persistence/conversation-crud.ts +104 -2
  267. package/src/persistence/db-init.ts +14 -3
  268. package/src/persistence/job-handlers/cleanup.ts +15 -7
  269. package/src/persistence/llm-request-log-store.ts +88 -52
  270. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  271. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  272. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  273. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  274. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  275. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  276. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  277. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  278. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  279. package/src/persistence/migrations/run-migrations.ts +25 -1
  280. package/src/persistence/schema/infrastructure.ts +9 -0
  281. package/src/persistence/schema/memory-core.ts +3 -0
  282. package/src/persistence/schema/memory-injection.ts +2 -0
  283. package/src/persistence/steps.ts +36 -0
  284. package/src/platform/client.test.ts +1 -44
  285. package/src/platform/client.ts +10 -20
  286. package/src/platform/consent-cache.test.ts +89 -23
  287. package/src/platform/consent-cache.ts +71 -33
  288. package/src/plugin-api/constants.ts +12 -0
  289. package/src/plugin-api/conversation-turn.ts +37 -14
  290. package/src/plugin-api/index.ts +11 -1
  291. package/src/plugin-api/resolve-credential.ts +75 -0
  292. package/src/plugin-api/vision-support.test.ts +8 -19
  293. package/src/plugin-api/vision-support.ts +26 -17
  294. package/src/plugins/collect-source-versions.ts +77 -0
  295. package/src/plugins/defaults/compaction/compact.ts +6 -0
  296. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  297. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  298. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  299. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  300. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  301. package/src/plugins/defaults/index.ts +8 -1
  302. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  303. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  304. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  305. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  306. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  307. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  308. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  309. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  310. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  311. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  312. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  313. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  314. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  315. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  316. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  317. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  318. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  319. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  320. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  321. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  322. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  323. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  324. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  325. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  326. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  327. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  328. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  329. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  330. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  331. package/src/plugins/defaults/memory/tools.ts +12 -2
  332. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  333. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  334. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  335. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  336. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  337. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  338. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  339. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  340. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  341. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  342. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  343. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  344. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  345. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  346. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  347. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  348. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  349. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  350. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  351. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  352. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  353. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  354. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  355. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  356. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  357. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  358. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +8 -6
  359. package/src/plugins/external-plugin-loader.ts +15 -15
  360. package/src/plugins/mtime-cache.ts +47 -0
  361. package/src/plugins/pipeline.ts +9 -1
  362. package/src/plugins/plugin-execution-context.ts +44 -0
  363. package/src/plugins/plugin-tree-walk.ts +31 -24
  364. package/src/plugins/source-fingerprint.ts +3 -4
  365. package/src/prompts/normalize-onboarding.ts +12 -0
  366. package/src/prompts/persona-resolver.ts +8 -0
  367. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  368. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  369. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  370. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  371. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  372. package/src/providers/anthropic/client.ts +1 -0
  373. package/src/providers/atlascloud/client.ts +10 -49
  374. package/src/providers/baseten/client.ts +43 -0
  375. package/src/providers/call-site-routing.ts +26 -13
  376. package/src/providers/connection-resolution.ts +9 -11
  377. package/src/providers/fetch-provider-catalog.ts +4 -2
  378. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  379. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  380. package/src/providers/inference/adapter-factory.ts +27 -6
  381. package/src/providers/inference/auth.ts +29 -1
  382. package/src/providers/inference/backfill.ts +61 -10
  383. package/src/providers/inference/connection-availability.ts +3 -2
  384. package/src/providers/inference/resolve-auth.ts +9 -1
  385. package/src/providers/model-catalog.ts +49 -0
  386. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  387. package/src/providers/openai/__tests__/chat-completions-provider-endpoint.test.ts +159 -0
  388. package/src/providers/openai/api-key-validation.ts +70 -0
  389. package/src/providers/openai/chat-completions-provider.ts +1 -0
  390. package/src/providers/openai/responses-provider.ts +1 -0
  391. package/src/providers/retry.ts +2 -0
  392. package/src/providers/types.ts +18 -4
  393. package/src/providers/vellum-model-routing.test.ts +26 -0
  394. package/src/providers/vellum-model-routing.ts +30 -0
  395. package/src/providers/voice-error-copy.ts +47 -0
  396. package/src/providers/weak-open-model.ts +14 -11
  397. package/src/runtime/agent-wake.ts +40 -24
  398. package/src/runtime/for-chat-mint-registry.ts +118 -0
  399. package/src/runtime/reveal-nonce.ts +49 -0
  400. package/src/runtime/reveal-success-registry.ts +306 -0
  401. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +408 -0
  402. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  403. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  404. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +97 -0
  405. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  406. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  407. package/src/runtime/routes/acp-claude-auth-routes.ts +389 -0
  408. package/src/runtime/routes/app-management-routes.ts +152 -45
  409. package/src/runtime/routes/app-routes.ts +16 -77
  410. package/src/runtime/routes/canned-message-complete.ts +16 -16
  411. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  412. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  413. package/src/runtime/routes/conversation-routes.ts +48 -2
  414. package/src/runtime/routes/credential-prompt-routes.ts +45 -0
  415. package/src/runtime/routes/credential-routes.ts +134 -3
  416. package/src/runtime/routes/email-routes.ts +17 -1
  417. package/src/runtime/routes/errors.ts +2 -2
  418. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  419. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  420. package/src/runtime/routes/index.ts +2 -0
  421. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  422. package/src/runtime/routes/inference-send-routes.ts +17 -1
  423. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  424. package/src/runtime/routes/plugins-routes.ts +37 -6
  425. package/src/runtime/routes/publish-routes.ts +15 -18
  426. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  427. package/src/runtime/routes/secret-routes.ts +29 -0
  428. package/src/runtime/routes/telemetry-routes.ts +149 -45
  429. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  430. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  431. package/src/schedule/schedule-store.ts +12 -1
  432. package/src/schedule/schedule-timezone.ts +63 -0
  433. package/src/schedule/scheduler.ts +100 -6
  434. package/src/schedule/worker-control.ts +19 -0
  435. package/src/security/__tests__/oauth2-exchange-body.test.ts +123 -0
  436. package/src/security/auth-fallback-events-store.ts +7 -6
  437. package/src/security/oauth2.ts +37 -9
  438. package/src/security/secret-scanner.ts +26 -1
  439. package/src/services/published-app-updater.ts +6 -11
  440. package/src/stt/stt-stream-session.ts +36 -16
  441. package/src/subagent/manager.ts +34 -8
  442. package/src/telemetry/AGENTS.md +30 -1
  443. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  444. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  445. package/src/telemetry/config-setting-snapshot.ts +44 -10
  446. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  447. package/src/telemetry/telemetry-event-sources.ts +137 -83
  448. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  449. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  450. package/src/telemetry/telemetry-wire-source.json +1 -1
  451. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  452. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  453. package/src/telemetry/tool-audit.ts +15 -9
  454. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  455. package/src/telemetry/turn-events-store.ts +20 -0
  456. package/src/telemetry/types.ts +45 -12
  457. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  458. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  459. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  460. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  461. package/src/tools/acp/spawn.test.ts +17 -0
  462. package/src/tools/acp/spawn.ts +31 -2
  463. package/src/tools/apps/executors.ts +11 -36
  464. package/src/tools/executor.ts +14 -1
  465. package/src/tools/host-terminal/host-shell.ts +6 -2
  466. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  467. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  468. package/src/tools/network/web-fetch.ts +6 -2
  469. package/src/tools/skills/sandbox-runner.ts +5 -2
  470. package/src/tools/subagent/spawn.ts +7 -11
  471. package/src/tools/terminal/shell.ts +5 -2
  472. package/src/tools/tool-manifest.ts +0 -2
  473. package/src/tools/tool-profiler.ts +37 -6
  474. package/src/tools/types.ts +8 -0
  475. package/src/tools/ui-surface/channel-variants.ts +101 -0
  476. package/src/tools/ui-surface/definitions.ts +23 -67
  477. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  478. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  479. package/src/tts/provider-catalog.ts +2 -4
  480. package/src/tts/providers/deepgram-provider.ts +10 -1
  481. package/src/types/onboarding-context.ts +8 -0
  482. package/src/usage/attribution.ts +18 -112
  483. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  484. package/src/util/provider-error-patterns.ts +7 -1
  485. package/src/util/worker-process.ts +105 -0
  486. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  487. package/src/watcher/telemetry.ts +5 -5
  488. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  489. package/src/workspace/migrations/registry.ts +2 -0
  490. package/src/workspace/provider-commit-message-generator.ts +6 -4
  491. package/src/__tests__/app-open-proxy.test.ts +0 -67
  492. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  493. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  494. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  495. package/src/tools/apps/definitions.ts +0 -73
  496. package/src/tools/apps/open-proxy.ts +0 -43
@@ -8,7 +8,6 @@ import {
8
8
  MANAGED_ROUTABLE_PROVIDERS,
9
9
  VELLUM_MANAGED_CONNECTION_NAME,
10
10
  } from "../providers/vellum-model-routing.js";
11
- import { isAssistantFeatureFlagEnabled } from "./assistant-feature-flags.js";
12
11
  import { CALL_SITE_DEFAULTS } from "./call-site-defaults.js";
13
12
  import {
14
13
  getEffectiveProfile,
@@ -23,73 +22,50 @@ import {
23
22
  } from "./schemas/llm.js";
24
23
 
25
24
  /**
26
- * Resolves a fully-specified `LLMConfigBase` for a given call site by layering
27
- * call-site overrides, optional per-call profile, an optional ad-hoc override
28
- * profile, the workspace's active profile, and the required `llm.default`.
25
+ * Resolves a fully-specified `LLMConfigBase` for a given call site.
29
26
  *
30
- * Merge layers (low high precedence; later layers override earlier) for
31
- * non-main-agent call sites:
32
- * 1. `llm.default` fields (required base)
33
- * 2. `llm.profiles[llm.activeProfile]` (workspace-wide active profile)
34
- * folded in ONLY when the call site resolves no profile of its own (a
35
- * profile-less leaf like `vision`/`workflowLeaf`, or a BYOK install whose
36
- * pinned managed profile was stripped). When the call site resolves a
37
- * profile, that profile is the authoritative provider config and the
38
- * active profile does not contribute — otherwise a deep-merge would let
39
- * its orphan fields bleed onto a different provider.
40
- * 3. `llm.profiles[opts.overrideProfile]` (per-call ad-hoc override)
41
- * 4. `llm.profiles[site.profile]` fields (call-site's named profile)
42
- * 5. `llm.callSites[callSite]` fields (call-site override)
27
+ * Selection is a single-winner chain (see `selectWinningProfile`): the
28
+ * highest-precedence rung that names a usable, complete profile wins, and the
29
+ * resolved config is composed as code-owned schema defaults + the winning
30
+ * profile's fragment + the call site's own tuning tweak. No profile ever
31
+ * contributes a field to another profile `deepMerge` here only lets nested
32
+ * tweaks (e.g. `thinking.enabled`) combine leaf-wise with the winner rather
33
+ * than wholesale-replacing sibling fields.
43
34
  *
44
- * For `mainAgent`, the selected active/conversation profile is the direct
45
- * user intent for the chat loop, so profile layers intentionally sit above
46
- * any static `llm.callSites.mainAgent` defaults seeded by migrations or UI
47
- * settings:
48
- * 1. `llm.default`
49
- * 2. `llm.profiles[site.profile]`
50
- * 3. `llm.callSites.mainAgent`
51
- * 4. `llm.profiles[llm.activeProfile]`
52
- * 5. `llm.profiles[opts.overrideProfile]`
35
+ * Precedence of the selection chain (high low):
36
+ * 1. `opts.overrideProfile` (per-turn / per-conversation pin)
37
+ * 2. `llm.activeProfile` `mainAgent` only, since it IS that call site's
38
+ * user-facing chat-model selection
39
+ * 3. `llm.callSites[callSite].profile` (the call site's named profile)
40
+ * 4. `CALL_SITE_DEFAULTS[callSite].profile` intent resolved through
41
+ * `llm.defaultProvider`
42
+ * 5. balanced intent through `llm.defaultProvider` — the code-owned anchor
43
+ * for profileless call sites, or when nothing above is usable
53
44
  *
54
- * `opts.forceOverrideProfile` is an explicit escape hatch for non-main-agent
55
- * call sites: when true (and `opts.overrideProfile` resolves to a defined
56
- * profile), the override profile floats ABOVE the call-site layers — the
57
- * non-main ordering becomes default → activeProfile → site profile →
58
- * call-site override → overrideProfile. This mirrors how `mainAgent` treats
59
- * the user's chat-model selection as authoritative, and exists for callers
60
- * that must run a background call site under a specific conversation's
61
- * inference profile (e.g. fork-based memory retrospectives matching the
62
- * source conversation for provider prompt-cache reuse). When the referenced
63
- * profile is missing, the flag is inert and the normal precedence applies
64
- * (same silent fall-through as any `overrideProfile` reference); for
65
- * `mainAgent` the flag is a no-op because the override already sits on top.
45
+ * A winner must carry its own `provider` AND `model`: the base layer's schema
46
+ * default identity never stands in for a selected profile. The anchor is
47
+ * code-owned and resolved through `llm.defaultProvider`, never through
48
+ * user-mutable state.
66
49
  *
67
- * Nested objects (`thinking`, `contextWindow`, and
68
- * `contextWindow.overflowRecovery`) are deep-merged so partial overrides at
69
- * any nesting level merge into rather than replace the corresponding
70
- * base value.
50
+ * `temperature`/`top_p` come only from the winner (or an explicit call-site
51
+ * tweak); `logitBias` only ever from the winner. These are provider-coupled, so
52
+ * a shadowed profile can never leak its sampling onto a different provider.
71
53
  *
72
- * `temperature` and `top_p` are provider-coupled, so they do NOT deep-merge
73
- * field-by-field with the rest of the config: only the winning profile (the
74
- * highest-precedence profile that determines provider/model) contributes them,
75
- * and an explicit `llm.callSites[callSite]` override still wins. A lower-
76
- * precedence profile whose model is shadowed never leaks its sampling onto a
77
- * different provider (which would trip e.g. Anthropic's "temperature and top_p
78
- * cannot both be specified" constraint). `logitBias` is winning-profile-scoped
79
- * the same way.
54
+ * `opts.forceOverrideProfile` is a no-op here: the override profile already
55
+ * sits at the top of the chain for every call site.
80
56
  *
81
- * `activeProfile` and `overrideProfile` are resolved by name lookup against
82
- * the effective profile catalog (code-defined defaults + workspace
83
- * `llm.profiles`; see `getEffectiveProfile`). Missing references silently
84
- * fall through (no throw) so the
85
- * resolver stays pure; schema validation in `LLMSchema.superRefine` catches
86
- * unknown `activeProfile` references at config-load time.
57
+ * Profile names are resolved against the effective profile catalog
58
+ * (code-defined defaults + workspace `llm.profiles`; see `getEffectiveProfile`).
59
+ * A "mix" profile is expanded to one of its arms by a seeded weighted pick (see
60
+ * `resolveProfileFragment` and `opts.selectionSeed`), uniformly wherever a name
61
+ * is dereferenced. Missing references silently fall through (no throw) so the
62
+ * resolver stays pure; schema validation catches unknown static references at
63
+ * config-load time.
87
64
  *
88
- * A profile reference that points at a "mix" profile is expanded to one of its
89
- * constituent profiles by a seeded weighted pick (see `resolveProfileFragment`
90
- * and `opts.selectionSeed`). Expansion happens uniformly at every dereference
91
- * spot, so a mix works as `activeProfile`, `overrideProfile`, or a call-site
92
- * `profile`.
65
+ * Nested objects (`thinking`, `contextWindow`, and
66
+ * `contextWindow.overflowRecovery`) are deep-merged so partial tweaks at any
67
+ * nesting level merge into rather than replace — the corresponding base
68
+ * value.
93
69
  *
94
70
  * Pure & synchronous: no I/O, no async work. (Random selection only occurs for
95
71
  * mix profiles when no `selectionSeed` is supplied; with a seed the pick is
@@ -98,11 +74,11 @@ import {
98
74
  export interface ResolveCallSiteOpts {
99
75
  overrideProfile?: string;
100
76
  /**
101
- * Float `overrideProfile` above the call-site layers (named site profile +
102
- * call-site override) for non-main-agent call sites. See the
103
- * `resolveCallSiteConfig` docstring for the resulting precedence and the
104
- * use case. Inert when `overrideProfile` is absent or references a missing
105
- * profile, and a no-op for `mainAgent`.
77
+ * Float `overrideProfile` above the call-site layers for non-main-agent call
78
+ * sites. Retained for API compatibility; under single-winner selection the
79
+ * override already sits at the top of the chain, so this is effectively a
80
+ * no-op. Inert when `overrideProfile` is absent or references a missing
81
+ * profile.
106
82
  */
107
83
  forceOverrideProfile?: boolean;
108
84
  /**
@@ -121,167 +97,29 @@ export interface ResolveCallSiteOpts {
121
97
  */
122
98
  onMixSelected?: (info: { mixProfile: string; chosenProfile: string }) => void;
123
99
  /**
124
- * Override-or-default semantics only: invoked once per chain rung that
125
- * named a profile the resolver could not use, before resolution continues
126
- * to the next rung. Fallback is silent to the call but must be visible in
127
- * logs — callers on user-facing paths should log at warn.
100
+ * Invoked once per chain rung that named a profile the resolver could not
101
+ * use, before resolution continues to the next rung. Fallback is silent to
102
+ * the call but must be visible in logs callers on user-facing paths should
103
+ * log at warn.
128
104
  */
129
105
  onResolutionFallback?: (info: {
130
106
  callSite: LLMCallSite;
131
107
  requested: string;
132
108
  reason: ResolutionFallbackReason;
133
109
  }) => void;
134
- /**
135
- * Test/caller override for the resolution semantics. When absent, the
136
- * `override-or-default-resolution` feature flag decides (ships enabled;
137
- * disabling it is the kill switch back to the legacy merge cascade).
138
- */
139
- resolutionSemantics?: "override-or-default" | "legacy-merge";
140
110
  }
141
111
 
142
112
  export type ResolutionFallbackReason = "missing" | "disabled" | "incomplete";
143
113
 
144
- export const OVERRIDE_OR_DEFAULT_RESOLUTION_FLAG =
145
- "override-or-default-resolution";
146
-
147
- /**
148
- * The flag read ignores its config argument (the override cache and registry
149
- * are module state), so the resolver stays call-site-compatible without
150
- * threading `AssistantConfig` through every consumer.
151
- */
152
- export function isOverrideOrDefaultResolutionEnabled(): boolean {
153
- return isAssistantFeatureFlagEnabled(
154
- OVERRIDE_OR_DEFAULT_RESOLUTION_FLAG,
155
- undefined as never,
156
- );
157
- }
158
-
159
- function useOverrideOrDefaultSemantics(opts: ResolveCallSiteOpts): boolean {
160
- if (opts.resolutionSemantics != null) {
161
- return opts.resolutionSemantics === "override-or-default";
162
- }
163
- return isOverrideOrDefaultResolutionEnabled();
164
- }
165
-
166
114
  export function resolveCallSiteConfig(
167
115
  callSite: LLMCallSite,
168
116
  llm: z.infer<typeof LLMSchema>,
169
117
  opts: ResolveCallSiteOpts = {},
170
118
  ): z.infer<typeof LLMConfigBase> {
171
- if (useOverrideOrDefaultSemantics(opts)) {
172
- return resolveOverrideOrDefault(callSite, llm, opts);
173
- }
174
- const layers: Mergeable[] = [llm.default as Mergeable];
175
-
176
- // Effective logit-bias preset, tracked outside the deep-merge so it ties to
177
- // the single highest-precedence *profile* that wins resolution rather than
178
- // inheriting from a lower one. Profile layers are appended low→high, and each
179
- // one fully determines the preset (a profile that omits `logitBias` clears
180
- // any value a lower-precedence profile set), so the last profile appended
181
- // wins — matching the merge's own precedence and including the implicit
182
- // call-site default selected by `effectiveDefault`.
183
- const biasRef: LogitBiasRef = { preset: undefined };
184
-
185
- // Effective sampling params, tracked outside the deep-merge for the same
186
- // reason as `logitBias`: `temperature`/`top_p` are provider-coupled, so only
187
- // the winning profile may contribute them. A profile clears what a lower
188
- // PROFILE set where it is silent (so a shadowed profile's sampling can't
189
- // leak), while an explicit call-site override is sticky and survives a later
190
- // silent profile (see `applyProfileSampling` / `appendCallSiteLayers`).
191
- const samplingRef: SamplingRef = {
192
- temperature: undefined,
193
- topP: undefined,
194
- temperatureFromCallSite: false,
195
- topPFromCallSite: false,
196
- };
197
-
198
- const activeFragment = resolveProfileFragment(llm.activeProfile, llm, opts);
199
- const overrideFragment = resolveProfileFragment(
200
- opts.overrideProfile,
201
- llm,
202
- opts,
203
- );
204
- const site =
205
- llm.callSites?.[callSite] ??
206
- effectiveDefault(callSite, llm, opts.overrideProfile != null);
207
-
208
- if (callSite === "mainAgent") {
209
- appendCallSiteLayers(
210
- layers,
211
- callSite,
212
- llm,
213
- site,
214
- opts,
215
- biasRef,
216
- samplingRef,
217
- );
218
- appendProfileLayer(layers, activeFragment, biasRef, samplingRef);
219
- appendProfileLayer(layers, overrideFragment, biasRef, samplingRef);
220
- } else if (opts.forceOverrideProfile === true && overrideFragment != null) {
221
- // Escape hatch: float the override profile above the call-site layers,
222
- // mirroring mainAgent's treatment of the user's chat-model selection.
223
- // Guarded on a resolved fragment so a missing profile reference degrades
224
- // to the normal precedence below instead of silently dropping the
225
- // call-site layers' standing. The active profile stays the bottom fallback
226
- // (its sampling can't leak — a higher profile's REPLACE clears it).
227
- appendProfileLayer(layers, activeFragment, biasRef, samplingRef);
228
- appendCallSiteLayers(
229
- layers,
230
- callSite,
231
- llm,
232
- site,
233
- opts,
234
- biasRef,
235
- samplingRef,
236
- );
237
- appendProfileLayer(layers, overrideFragment, biasRef, samplingRef);
238
- } else {
239
- // The active profile is a low-precedence FALLBACK for call sites that
240
- // resolve no profile of their own — profile-less leaves (`vision`,
241
- // `workflowLeaf`) and BYOK installs where the pinned managed profile was
242
- // stripped. When the call site DOES resolve its own profile, that profile
243
- // is the authoritative provider config, so the active profile must not
244
- // contribute its orphan fields to a different provider.
245
- if (site?.profile == null) {
246
- appendProfileLayer(layers, activeFragment, biasRef, samplingRef);
247
- }
248
- appendProfileLayer(layers, overrideFragment, biasRef, samplingRef);
249
- appendCallSiteLayers(
250
- layers,
251
- callSite,
252
- llm,
253
- site,
254
- opts,
255
- biasRef,
256
- samplingRef,
257
- );
258
- }
259
-
260
- const resolved = finalize(deepMerge(...withImpliedProviders(layers)));
261
- // `logitBias` is profile-scoped: the winning profile is its only source.
262
- // Overwrite — or clear — whatever the deep-merge may have copied from a
263
- // non-profile layer (`llm.default` or a call-site fragment), so a preset set
264
- // outside a profile can't apply to a profile that didn't opt in.
265
- if (biasRef.preset !== undefined) {
266
- resolved.logitBias = biasRef.preset;
267
- } else {
268
- delete (resolved as { logitBias?: unknown }).logitBias;
269
- }
270
- // `temperature`/`top_p` are winning-profile-scoped like `logitBias`, but an
271
- // explicit call-site override may also set them. Apply the tracked value,
272
- // overriding whatever a shadowed profile may have left in the merge. An
273
- // `undefined` ref means no profile or override opted in, so the `llm.default`
274
- // base already in `resolved` stands.
275
- if (samplingRef.temperature !== undefined) {
276
- resolved.temperature = samplingRef.temperature;
277
- }
278
- if (samplingRef.topP !== undefined) {
279
- resolved.topP = samplingRef.topP;
280
- }
281
- return resolved;
119
+ return resolveOverrideOrDefault(callSite, llm, opts);
282
120
  }
283
121
 
284
- // ─── Override-or-default resolution (flag-on path) ──────────────────────────
122
+ // ─── Single-winner profile resolution ───────────────────────────────────────
285
123
  //
286
124
  // Selection is a single-winner chain — each rung is either a complete
287
125
  // explicit choice or skipped with a reported reason — bottoming out on the
@@ -294,9 +132,8 @@ export function resolveCallSiteConfig(
294
132
  // above usable)
295
133
  // A winner must carry its own `provider` AND `model`: the base layer's
296
134
  // schema-default identity must never stand in for a selected profile (that
297
- // would be merge inheritance by the back door). The legacy `custom-${intent}`
298
- // hop is deliberately absent — a fallback anchor must be code-owned, never
299
- // user-mutable state.
135
+ // would be merge inheritance by the back door). A fallback anchor must be
136
+ // code-owned, never user-mutable state.
300
137
  //
301
138
  // The request config is the base + winner + site-tweak composition:
302
139
  // code-owned schema defaults, then the single winning profile, then the call
@@ -508,8 +345,8 @@ function usableDefaultIntent(
508
345
 
509
346
  /**
510
347
  * Schema-default base for composition. Non-identity fields a winner omits
511
- * fall to these code-owned defaults (never to `llm.default`); the winner's
512
- * identity fields are guaranteed present by `selectWinningProfile`.
348
+ * fall to these code-owned defaults; the winner's identity fields are
349
+ * guaranteed present by `selectWinningProfile`.
513
350
  */
514
351
  const CODE_DEFAULT_BASE: z.infer<typeof LLMConfigBase> = LLMConfigBase.parse(
515
352
  {},
@@ -525,8 +362,7 @@ function resolveOverrideOrDefault(
525
362
  selection.entry == null ? {} : winnerConfigFragment(selection.entry);
526
363
 
527
364
  // The call site's own tweak fragment: the workspace override replaces the
528
- // code default wholesale (matching legacy `llm.callSites[site] ??
529
- // CALL_SITE_DEFAULTS[site]`). `profile` is the selection discriminator and
365
+ // code default wholesale. `profile` is the selection discriminator and
530
366
  // `logitBias` is winner-owned, so neither enters the merge.
531
367
  const site = llm.callSites?.[callSite] ?? CALL_SITE_DEFAULTS[callSite];
532
368
  const {
@@ -585,20 +421,6 @@ function winnerConfigFragment(entry: ProfileEntry): Mergeable {
585
421
  return config as Mergeable;
586
422
  }
587
423
 
588
- type LogitBiasRef = { preset: ProfileEntry["logitBias"] };
589
-
590
- type SamplingRef = {
591
- temperature: ProfileEntry["temperature"];
592
- topP: ProfileEntry["topP"];
593
- // Provenance of the current pair: `true` when a field came from an explicit
594
- // call-site override (deliberate, sticky), `false` when it came from a profile
595
- // (clearable by a higher-precedence profile that determines the model). Lets a
596
- // silent higher profile clear a lower profile's sampling without discarding a
597
- // deliberate call-site override.
598
- temperatureFromCallSite: boolean;
599
- topPFromCallSite: boolean;
600
- };
601
-
602
424
  // ---------------------------------------------------------------------------
603
425
  // Internal helpers
604
426
  // ---------------------------------------------------------------------------
@@ -634,11 +456,15 @@ function weightedPick<T extends { weight: number }>(
634
456
  ): T {
635
457
  const total = entries.reduce((sum, e) => sum + e.weight, 0);
636
458
  // Defensive: a degenerate total (unreachable post-schema) → first arm.
637
- if (!(total > 0)) return entries[0];
459
+ if (!(total > 0)) {
460
+ return entries[0];
461
+ }
638
462
  let threshold = unit * total;
639
463
  for (const entry of entries) {
640
464
  threshold -= entry.weight;
641
- if (threshold < 0) return entry;
465
+ if (threshold < 0) {
466
+ return entry;
467
+ }
642
468
  }
643
469
  // Floating-point fall-through (unit ≈ 1): return the last arm.
644
470
  return entries[entries.length - 1];
@@ -664,9 +490,13 @@ function resolveProfileFragment(
664
490
  lookupEntry: (name: string) => ProfileEntry | undefined = (n) =>
665
491
  getEffectiveProfile(llm.profiles, n),
666
492
  ): ProfileEntry | undefined {
667
- if (name == null) return undefined;
493
+ if (name == null) {
494
+ return undefined;
495
+ }
668
496
  const entry = lookupEntry(name);
669
- if (entry?.mix == null) return entry;
497
+ if (entry?.mix == null) {
498
+ return entry;
499
+ }
670
500
 
671
501
  // Mix: pick one constituent. Seed by per-conversation seed + the mix's own
672
502
  // name so two different mixes in the same conversation pick independently,
@@ -683,71 +513,33 @@ function resolveProfileFragment(
683
513
  }
684
514
 
685
515
  /**
686
- * Returns the effective default profile key the resolver would actually
687
- * select for a call site when no per-turn `overrideProfile` is supplied.
688
- *
689
- * Mirrors the layering in `resolveCallSiteConfig`:
690
- * - For `mainAgent`, the workspace's `activeProfile` sits ABOVE the
691
- * call-site catalog default (and above any static `llm.callSites.mainAgent`
692
- * override), so a non-disabled `activeProfile` wins.
693
- * - For other call sites, the catalog default sits ABOVE `activeProfile`,
694
- * so the catalog default (with `custom-*` fallback) wins.
516
+ * Returns the profile key `resolveCallSiteConfig` selects as the winner for a
517
+ * call site when no per-turn `overrideProfile` is supplied — the highest-
518
+ * precedence rung that resolves to a usable, complete profile (see
519
+ * `selectWinningProfile`). Returns `undefined` when the winner is the
520
+ * code-owned anchor rather than a named profile.
695
521
  */
696
522
  export function resolveDefaultProfileKey(
697
523
  callSite: LLMCallSite,
698
524
  llm: z.infer<typeof LLMSchema>,
699
525
  ): string | undefined {
700
- if (useOverrideOrDefaultSemantics({})) {
701
- return selectWinningProfile(callSite, llm, {}).profileName ?? undefined;
702
- }
703
- if (callSite === "mainAgent" && llm.activeProfile != null) {
704
- const active = getEffectiveProfile(llm.profiles, llm.activeProfile);
705
- if (active != null && active.status !== "disabled") {
706
- return llm.activeProfile;
707
- }
708
- }
709
-
710
- const dflt = CALL_SITE_DEFAULTS[callSite];
711
- if (dflt?.profile == null) return undefined;
712
- const target = getEffectiveProfile(llm.profiles, dflt.profile);
713
- if (target != null && target.status !== "disabled") return dflt.profile;
714
- const customKey = `custom-${dflt.profile}`;
715
- const customTarget = getEffectiveProfile(llm.profiles, customKey);
716
- if (customTarget != null && customTarget.status !== "disabled")
717
- return customKey;
718
- return undefined;
526
+ return selectWinningProfile(callSite, llm, {}).profileName ?? undefined;
719
527
  }
720
528
 
721
529
  /**
722
- * Returns the profile key that `resolveCallSiteConfig` would actually treat as
723
- * the winning (highest-precedence) profile for a turn — the profile whose
724
- * fragment supplies the resolved provider/model. Unlike `resolveDefaultProfileKey`
725
- * this accounts for the per-turn `overrideProfile`/`forceOverrideProfile` and
726
- * for `effectiveDefault` stripping the catalog default when an override is
727
- * present, so error attribution names the slot the resolver really used:
728
- * - `mainAgent`: override → active → catalog default.
729
- * - forced override: the override.
730
- * - other sites: the call-site's own profile (explicit or catalog default) when
731
- * one survives; otherwise override → active. A pinned override on a bare call
732
- * site therefore attributes to the override, not the stripped catalog default.
530
+ * Returns the profile key that `resolveCallSiteConfig` treats as the winning
531
+ * (highest-precedence) profile for a turn — the profile whose fragment supplies
532
+ * the resolved provider/model. Accounts for the per-turn `overrideProfile`, so
533
+ * error attribution names the slot the resolver really used. Returns
534
+ * `undefined` when the winner is the code-owned anchor rather than a named
535
+ * profile.
733
536
  */
734
537
  export function resolveEffectiveProfileKey(
735
538
  callSite: LLMCallSite,
736
539
  llm: z.infer<typeof LLMSchema>,
737
540
  opts: ResolveCallSiteOpts = {},
738
541
  ): string | undefined {
739
- const override = opts.overrideProfile ?? undefined;
740
- if (callSite === "mainAgent") {
741
- return override ?? resolveDefaultProfileKey(callSite, llm);
742
- }
743
- if (opts.forceOverrideProfile === true && override != null) {
744
- return override;
745
- }
746
- const site =
747
- llm.callSites?.[callSite] ??
748
- effectiveDefault(callSite, llm, override != null);
749
- if (site?.profile != null) return site.profile;
750
- return override ?? llm.activeProfile ?? undefined;
542
+ return selectWinningProfile(callSite, llm, opts).profileName ?? undefined;
751
543
  }
752
544
 
753
545
  /**
@@ -763,191 +555,10 @@ export function resolveProfilelessModelKey(
763
555
  try {
764
556
  return resolveCallSiteConfig(callSite, llm, opts).model;
765
557
  } catch {
766
- return llm.default?.model ?? "default";
767
- }
768
- }
769
-
770
- function effectiveDefault(
771
- callSite: LLMCallSite,
772
- llm: z.infer<typeof LLMSchema>,
773
- hasOverrideProfile = false,
774
- ): z.infer<typeof LLMSchema>["callSites"][LLMCallSite] | undefined {
775
- const dflt = CALL_SITE_DEFAULTS[callSite];
776
- if (dflt == null) return undefined;
777
- const targetProfile =
778
- dflt.profile != null
779
- ? getEffectiveProfile(llm.profiles, dflt.profile)
780
- : undefined;
781
- const profileUnavailable =
782
- dflt.profile != null &&
783
- (targetProfile == null || targetProfile.status === "disabled");
784
-
785
- if (profileUnavailable && !hasOverrideProfile) {
786
- const customKey = `custom-${dflt.profile}`;
787
- const customProfile = getEffectiveProfile(llm.profiles, customKey);
788
- if (customProfile != null && customProfile.status !== "disabled") {
789
- return { ...dflt, profile: customKey };
790
- }
791
- }
792
-
793
- const stripProfile = hasOverrideProfile || profileUnavailable;
794
- if (stripProfile) {
795
- const { profile: _profile, ...rest } = dflt;
796
- return Object.keys(rest).length > 0 ? rest : undefined;
797
- }
798
- return dflt;
799
- }
800
-
801
- /**
802
- * Stamp a catalog-implied provider onto model-only layers whose model the
803
- * provider applicable at that point in the merge does not serve. Layers are
804
- * scanned in merge order (low → high precedence), tracking the provider the
805
- * merged config would carry at each layer: when that provider already lists
806
- * the layer's model in its own catalog, no implication is needed and the
807
- * layer stays provider-less so the lower-precedence provider wins. Models
808
- * unknown to the catalog never imply a provider.
809
- */
810
- function withImpliedProviders(layers: Mergeable[]): Mergeable[] {
811
- let applicableProvider: string | undefined;
812
- return layers.map((layer) => {
813
- if (layer.provider !== undefined) {
814
- if (typeof layer.provider === "string") {
815
- applicableProvider = layer.provider;
816
- }
817
- return layer;
818
- }
819
- const model = layer.model;
820
- if (typeof model !== "string" || model.length === 0) {
821
- return layer;
822
- }
823
- if (
824
- applicableProvider !== undefined &&
825
- isModelInCatalog(applicableProvider, model)
826
- ) {
827
- return layer;
828
- }
829
- const provider = getCatalogProviderForModel(model);
830
- if (provider === undefined) {
831
- return layer;
832
- }
833
- applicableProvider = provider;
834
- return { ...layer, provider };
835
- });
836
- }
837
-
838
- /**
839
- * Fold a profile's sampling into `samplingRef`. A profile determines
840
- * provider/model, so its pair supersedes any LOWER PROFILE's: set each field the
841
- * profile specifies, and clear a lower profile's value where the profile is
842
- * silent. A deliberate call-site override is NOT a profile and outranks a silent
843
- * profile — it survives until a profile EXPLICITLY sets the field. (The mirror
844
- * COALESCE for call-site overrides lives in `appendCallSiteLayers`.)
845
- */
846
- function applyProfileSampling(
847
- samplingRef: SamplingRef,
848
- profile: ProfileEntry,
849
- ): void {
850
- if (profile.temperature !== undefined) {
851
- samplingRef.temperature = profile.temperature;
852
- samplingRef.temperatureFromCallSite = false;
853
- } else if (!samplingRef.temperatureFromCallSite) {
854
- samplingRef.temperature = undefined;
855
- }
856
- if (profile.topP !== undefined) {
857
- samplingRef.topP = profile.topP;
858
- samplingRef.topPFromCallSite = false;
859
- } else if (!samplingRef.topPFromCallSite) {
860
- samplingRef.topP = undefined;
861
- }
862
- }
863
-
864
- function appendProfileLayer(
865
- layers: Mergeable[],
866
- profile: ProfileEntry | undefined,
867
- biasRef: LogitBiasRef,
868
- samplingRef: SamplingRef,
869
- ): void {
870
- if (profile != null) {
871
- biasRef.preset = profile.logitBias;
872
- applyProfileSampling(samplingRef, profile);
873
- layers.push(profileConfigFragment(profile));
558
+ return "default";
874
559
  }
875
560
  }
876
561
 
877
- function appendCallSiteLayers(
878
- layers: Mergeable[],
879
- callSite: LLMCallSite,
880
- llm: z.infer<typeof LLMSchema>,
881
- site: z.infer<typeof LLMSchema>["callSites"][LLMCallSite] | undefined,
882
- opts: ResolveCallSiteOpts,
883
- biasRef: LogitBiasRef,
884
- samplingRef: SamplingRef,
885
- ): void {
886
- if (site != null) {
887
- if (site.profile != null) {
888
- const profileFragment = resolveProfileFragment(site.profile, llm, opts);
889
- if (profileFragment == null) {
890
- // Defensive: `LLMSchema.superRefine` already rejects unknown profile
891
- // references (and unknown mix arms) at config load, so this branch is
892
- // unreachable for any config that survived schema validation. Throw a
893
- // clear error in case a hand-crafted (un-parsed) config slips through.
894
- throw new Error(
895
- `LLM call site "${callSite}" references undefined profile "${site.profile}"`,
896
- );
897
- }
898
- biasRef.preset = profileFragment.logitBias;
899
- applyProfileSampling(samplingRef, profileFragment);
900
- layers.push(profileConfigFragment(profileFragment));
901
- }
902
- // Strip the `profile` discriminator (not a `LLMConfigBase` field) and the
903
- // sampling params before merging. An explicit call-site `temperature` /
904
- // `topP` is a deliberate per-site choice, so it COALESCES over the winning
905
- // profile's pair (only overriding the fields it sets) and is marked sticky
906
- // so a later silent profile can't clear it — routed through `samplingRef` so
907
- // it never inherits a shadowed profile's value via merge.
908
- const {
909
- profile: _profile,
910
- temperature: siteTemperature,
911
- topP: siteTopP,
912
- ...siteFragment
913
- } = site;
914
- if (siteTemperature !== undefined) {
915
- samplingRef.temperature = siteTemperature;
916
- samplingRef.temperatureFromCallSite = true;
917
- }
918
- if (siteTopP !== undefined) {
919
- samplingRef.topP = siteTopP;
920
- samplingRef.topPFromCallSite = true;
921
- }
922
- layers.push(siteFragment as Mergeable);
923
- }
924
- }
925
-
926
- function profileConfigFragment(profile: ProfileEntry): Mergeable {
927
- const {
928
- source: _source,
929
- label: _label,
930
- description: _description,
931
- // `mix` never reaches here in practice (a mix expands to a standard
932
- // profile before this point), but strip it defensively so it can never
933
- // leak into the merged `LLMConfigBase`.
934
- mix: _mix,
935
- // `logitBias` is profile-identity metadata, not inheritable config: a
936
- // preset must apply only to the profile that opted in, never bleed from a
937
- // lower-precedence (e.g. active) profile into one that merely inherited it.
938
- // `RetryProvider` resolves it from the applied profile, not the merge.
939
- logitBias: _logitBias,
940
- // `temperature`/`top_p` are provider-coupled: only the winning profile
941
- // contributes them (tracked via `samplingRef`, applied post-merge), so a
942
- // shadowed profile's sampling can never reach a different provider through
943
- // the deep-merge. Strip here so no profile's sampling enters the merge.
944
- temperature: _temperature,
945
- topP: _topP,
946
- ...config
947
- } = profile;
948
- return config as Mergeable;
949
- }
950
-
951
562
  /**
952
563
  * Returns true for objects we should recurse into during deep merge. We
953
564
  * deliberately exclude arrays so that array-valued fields (e.g.
@@ -972,15 +583,17 @@ function isPlainObject(value: unknown): value is Mergeable {
972
583
  *
973
584
  * Plain-object values are always cloned (via recursion) rather than aliased,
974
585
  * so the returned config is an isolated snapshot — mutating any nested object
975
- * on the result cannot affect `llm.default`, named profiles, or other call
976
- * sites' resolutions. Arrays and primitives are copied by reference; the
977
- * resolver does not return arrays, and primitives are immutable.
586
+ * on the result cannot affect named profiles or other call sites' resolutions.
587
+ * Arrays and primitives are copied by reference; the resolver does not return
588
+ * arrays, and primitives are immutable.
978
589
  */
979
590
  function deepMerge(...sources: Mergeable[]): Mergeable {
980
591
  const out: Mergeable = {};
981
592
  for (const source of sources) {
982
593
  for (const [key, value] of Object.entries(source)) {
983
- if (value === undefined) continue;
594
+ if (value === undefined) {
595
+ continue;
596
+ }
984
597
  const existing = out[key];
985
598
  if (isPlainObject(value)) {
986
599
  // Recurse for any plain-object source. Using `existing` as the base
@@ -998,9 +611,9 @@ function deepMerge(...sources: Mergeable[]): Mergeable {
998
611
  }
999
612
 
1000
613
  /**
1001
- * Cast helper that documents the intent: after merging `llm.default` (which
1002
- * is `LLMConfigBase`) with optional fragments, every required field is still
1003
- * present, so the result satisfies `LLMConfigBase`.
614
+ * Cast helper that documents the intent: after merging the code-owned base
615
+ * (which is `LLMConfigBase`) with optional fragments, every required field is
616
+ * still present, so the result satisfies `LLMConfigBase`.
1004
617
  */
1005
618
  function finalize(merged: Mergeable): z.infer<typeof LLMConfigBase> {
1006
619
  return merged as unknown as z.infer<typeof LLMConfigBase>;