@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) 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__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -3,7 +3,7 @@
3
3
  * `provider` + `source` model to the new `provider_connection` model.
4
4
  *
5
5
  * Walks three locations in `llm.*` on every boot:
6
- * - `llm.default` — the base profile every dispatch falls back on
6
+ * - `llm.default` — the legacy raw base blob still present in older configs
7
7
  * - `llm.profiles.*` — named alternate profiles (fast/balanced/...)
8
8
  * - `llm.callSites.*` — per-call-site overrides with bare `provider`
9
9
  *
@@ -17,15 +17,18 @@
17
17
  * default profile and on any legacy bare-`provider` callsite override.
18
18
  */
19
19
 
20
+ import { MANAGED_PROFILE_NAMES } from "../../config/default-profile-catalog.js";
20
21
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
21
22
  import type { DrizzleDb } from "../../persistence/db-connection.js";
22
23
  import { credentialKey } from "../../security/credential-key.js";
23
24
  import { getLogger } from "../../util/logger.js";
25
+ import { isConnectionCompatibleWithModel } from "../connection-model-compat.js";
24
26
  import { MANAGED_ROUTABLE_PROVIDERS } from "../vellum-model-routing.js";
25
27
  import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "./auth.js";
26
28
  import {
27
29
  createConnection,
28
30
  getConnection,
31
+ listConnections,
29
32
  seedCanonicalConnections,
30
33
  VELLUM_MANAGED_CONNECTION_NAME,
31
34
  } from "./connections.js";
@@ -63,7 +66,9 @@ export function runProviderConnectionsBackfill(db: DrizzleDb): void {
63
66
  function backfillConfigProfiles(db: DrizzleDb): void {
64
67
  const raw = loadRawConfig();
65
68
  const llm = raw.llm as Record<string, unknown> | undefined;
66
- if (!llm) return;
69
+ if (!llm) {
70
+ return;
71
+ }
67
72
 
68
73
  const isPlatform =
69
74
  process.env.IS_PLATFORM === "true" || process.env.IS_PLATFORM === "1";
@@ -87,13 +92,17 @@ function backfillConfigProfiles(db: DrizzleDb): void {
87
92
  if (profiles && typeof profiles === "object") {
88
93
  for (const [profileName, profileVal] of Object.entries(profiles)) {
89
94
  const profile = profileVal as Record<string, unknown>;
90
- if (!profile || typeof profile !== "object") continue;
95
+ if (!profile || typeof profile !== "object") {
96
+ continue;
97
+ }
91
98
  if (ensureProviderConnection(profile, profileName, db, globalMode)) {
92
99
  profiles[profileName] = profile;
93
100
  changed = true;
94
101
  }
95
102
  }
96
- if (changed) llm.profiles = profiles;
103
+ if (changed) {
104
+ llm.profiles = profiles;
105
+ }
97
106
  }
98
107
 
99
108
  // 3. Per-call-site overrides. Only legacy entries with a bare `provider`
@@ -103,11 +112,15 @@ function backfillConfigProfiles(db: DrizzleDb): void {
103
112
  if (callSites && typeof callSites === "object") {
104
113
  for (const [callSiteName, callSiteVal] of Object.entries(callSites)) {
105
114
  const callSite = callSiteVal as Record<string, unknown>;
106
- if (!callSite || typeof callSite !== "object") continue;
115
+ if (!callSite || typeof callSite !== "object") {
116
+ continue;
117
+ }
107
118
  // Only touch overrides that explicitly set `provider` — the typical
108
119
  // case is `{profile: "fast"}`, which has no provider and inherits
109
120
  // through `resolveCallSiteConfig` deep-merge.
110
- if (callSite.provider == null) continue;
121
+ if (callSite.provider == null) {
122
+ continue;
123
+ }
111
124
  if (
112
125
  ensureProviderConnection(
113
126
  callSite,
@@ -120,7 +133,9 @@ function backfillConfigProfiles(db: DrizzleDb): void {
120
133
  changed = true;
121
134
  }
122
135
  }
123
- if (changed) llm.callSites = callSites;
136
+ if (changed) {
137
+ llm.callSites = callSites;
138
+ }
124
139
  }
125
140
 
126
141
  if (changed) {
@@ -152,10 +167,14 @@ function ensureProviderConnection(
152
167
  // at runtime. Self-heal those alongside null/undefined.
153
168
  const existing = entry.provider_connection;
154
169
  const hasValid = typeof existing === "string" && existing.trim() !== "";
155
- if (hasValid) return false;
170
+ if (hasValid) {
171
+ return false;
172
+ }
156
173
 
157
174
  const provider = entry.provider as string | undefined;
158
- if (!provider) return false;
175
+ if (!provider) {
176
+ return false;
177
+ }
159
178
 
160
179
  if (PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
161
180
  log.warn(
@@ -167,7 +186,39 @@ function ensureProviderConnection(
167
186
 
168
187
  let connectionName: string;
169
188
 
170
- if (globalMode === "managed" && MANAGED_ROUTABLE_PROVIDERS.has(provider)) {
189
+ // For user-owned entries, an existing connection for the entry's provider
190
+ // wins over the mode-derived default. Only user-brought connections can
191
+ // match — the canonical `vellum` row carries the `vellum` sentinel
192
+ // provider, never a concrete upstream. Without this, the managed branch
193
+ // would silently switch a connection-less BYOK-intent profile onto the
194
+ // billed managed connection, and the your-own branch would create a
195
+ // parallel `-personal` row (pointing at an empty credential slot) when a
196
+ // custom-named connection already exists.
197
+ //
198
+ // Managed-owned entries are excluded: a managed preset must stay on the
199
+ // platform-managed route, not start dispatching against a key the user
200
+ // brought for their own profiles. Managed-owned means `source: "managed"`
201
+ // or a canonical managed name without an explicit `source: "user"` —
202
+ // legacy seeders wrote canonical entries source-less, and only an explicit
203
+ // user source marks a shadow the user took ownership of (mirrors
204
+ // workspace migration 109). `entryLabel` is the profile name for the
205
+ // `llm.profiles.*` walk; the other walks pass bracketed labels that never
206
+ // collide with canonical names.
207
+ const isManagedOwned =
208
+ entry.source === "managed" ||
209
+ (entry.source !== "user" && MANAGED_PROFILE_NAMES.has(entryLabel));
210
+ const entryModel = typeof entry.model === "string" ? entry.model : undefined;
211
+ const existingForProvider = isManagedOwned
212
+ ? undefined
213
+ : listConnections(db, { provider }).find((c) =>
214
+ isConnectionCompatibleWithModel(c, entryModel),
215
+ );
216
+ if (existingForProvider) {
217
+ connectionName = existingForProvider.name;
218
+ } else if (
219
+ globalMode === "managed" &&
220
+ MANAGED_ROUTABLE_PROVIDERS.has(provider)
221
+ ) {
171
222
  // All managed-routable providers share the single provider-agnostic
172
223
  // `vellum` connection; the upstream is recovered per-request from the
173
224
  // profile's `provider` field.
@@ -174,14 +174,15 @@ export async function computeConnectionAvailability(
174
174
  message: `Connection "${resolvedConnectionName}" uses Vellum platform auth, which cannot serve provider "${provider}". Add an API-key connection for "${provider}" ${SETTINGS_HINT}.`,
175
175
  };
176
176
  case "none": {
177
- // Keyless providers (catalog setupMode "keyless", e.g. ollama)
177
+ // Keyless providers (catalog setupMode "keyless", e.g. ollama) and
178
+ // openai-compatible endpoints (dual-mode: local servers are keyless)
178
179
  // legitimately dispatch with none-auth — mirror
179
180
  // `createAdapterFromConnection`, which only rejects none-auth for
180
181
  // keyed catalog entries.
181
182
  const isKeyless =
182
183
  PROVIDER_CATALOG.find((entry) => entry.id === provider)?.setupMode ===
183
184
  "keyless";
184
- if (isKeyless) {
185
+ if (isKeyless || provider === "openai-compatible") {
185
186
  return { status: "ok" };
186
187
  }
187
188
  return {
@@ -84,7 +84,15 @@ export async function resolveAuth(
84
84
  }
85
85
 
86
86
  case "none":
87
- return { ok: true, resolved: { kind: "none" } };
87
+ // Keyless endpoints still need their custom baseUrl threaded through
88
+ // (LM Studio / vLLM openai-compatible servers).
89
+ return {
90
+ ok: true,
91
+ resolved: {
92
+ kind: "none",
93
+ ...(safeBaseUrl ? { baseUrl: safeBaseUrl } : {}),
94
+ },
95
+ };
88
96
 
89
97
  case "oauth_subscription": {
90
98
  // Extract the credential prefix from the credential key.
@@ -1945,6 +1945,43 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
1945
1945
  apiKeyUrl: "https://www.atlascloud.ai/console",
1946
1946
  apiKeyPlaceholder: "apikey-...",
1947
1947
  },
1948
+ {
1949
+ id: "baseten",
1950
+ displayName: "Baseten",
1951
+ subtitle: "Open models served by Baseten. Requires a Baseten API key.",
1952
+ setupMode: "api-key",
1953
+ setupHint: "Enter your Baseten API key to enable Baseten models.",
1954
+ envVar: "BASETEN_API_KEY",
1955
+ credentialsGuide: {
1956
+ description: "Sign in to the Baseten dashboard and create an API key.",
1957
+ url: "https://app.baseten.co/settings/api_keys",
1958
+ linkLabel: "Open Baseten Dashboard",
1959
+ },
1960
+ models: [
1961
+ {
1962
+ id: "thinkingmachines/inkling",
1963
+ displayName: "Inkling",
1964
+ // Baseten serves Inkling with a 1,048K-token input window.
1965
+ contextWindowTokens: 1048576,
1966
+ maxOutputTokens: 32768,
1967
+ supportsThinking: true,
1968
+ supportsCaching: true,
1969
+ supportsVision: true,
1970
+ supportsToolUse: true,
1971
+ // Baseten's reasoning_effort for Inkling tops out at "xhigh" (no
1972
+ // "max"), matching the chat-completions client's default ceiling.
1973
+ maxEffort: "xhigh",
1974
+ pricing: {
1975
+ inputPer1mTokens: 1.0,
1976
+ outputPer1mTokens: 4.05,
1977
+ cacheReadPer1mTokens: 0.17,
1978
+ },
1979
+ },
1980
+ ],
1981
+ defaultModel: "thinkingmachines/inkling",
1982
+ apiKeyUrl: "https://app.baseten.co/settings/api_keys",
1983
+ apiKeyPlaceholder: "Your Baseten API key",
1984
+ },
1948
1985
  ];
1949
1986
 
1950
1987
  export const PROVIDER_CATALOG: ProviderCatalogEntry[] =
@@ -322,7 +322,9 @@ describe("captureRawErrorBodyFetch", () => {
322
322
  });
323
323
  });
324
324
 
325
- function n(over: Partial<NormalizedOpenAIAPIError> = {}): NormalizedOpenAIAPIError {
325
+ function n(
326
+ over: Partial<NormalizedOpenAIAPIError> = {},
327
+ ): NormalizedOpenAIAPIError {
326
328
  return { message: "boom", ...over };
327
329
  }
328
330
 
@@ -389,6 +391,24 @@ describe("deriveReason", () => {
389
391
  );
390
392
  });
391
393
 
394
+ test("403 spend-cap 'Key limit exceeded' → insufficient_credits", () => {
395
+ // OpenRouter returns 403 with this body when a key's configured credit
396
+ // limit is reached — a billing condition, not a rejected key. The billing
397
+ // gate precedes the 401/403 credential branch, so it routes to credits.
398
+ expect(deriveReason(n({ message: "Key limit exceeded" }), 403)).toBe(
399
+ "insufficient_credits",
400
+ );
401
+ expect(
402
+ deriveReason(
403
+ n({
404
+ message: "Forbidden",
405
+ rawBody: '{"error":{"code":403,"message":"Key limit exceeded"}}',
406
+ }),
407
+ 403,
408
+ ),
409
+ ).toBe("insufficient_credits");
410
+ });
411
+
392
412
  test("429 → rate_limited", () => {
393
413
  expect(deriveReason(n(), 429)).toBe("rate_limited");
394
414
  });
@@ -414,6 +434,8 @@ describe("deriveReason", () => {
414
434
  });
415
435
 
416
436
  test("no status, no signal → unknown", () => {
417
- expect(deriveReason(n({ message: "who knows" }), undefined)).toBe("unknown");
437
+ expect(deriveReason(n({ message: "who knows" }), undefined)).toBe(
438
+ "unknown",
439
+ );
418
440
  });
419
441
  });
@@ -0,0 +1,70 @@
1
+ import OpenAI from "openai";
2
+
3
+ import { getLogger } from "../../util/logger.js";
4
+
5
+ const log = getLogger("provider-key-validation");
6
+
7
+ /** Validation-specific timeout (10s) so a stalled network doesn't block key submission. */
8
+ const VALIDATION_TIMEOUT_MS = 10_000;
9
+
10
+ export type ApiKeyValidationResult =
11
+ | { valid: true }
12
+ | { valid: false; reason: string };
13
+
14
+ export interface OpenAICompatibleKeyValidation {
15
+ apiKey: string;
16
+ /** The provider's OpenAI-compatible endpoint to list models against. */
17
+ baseURL: string;
18
+ /** Human-readable provider name used in error messages and logs. */
19
+ providerLabel: string;
20
+ }
21
+
22
+ /**
23
+ * Validate an API key by listing models against an OpenAI-compatible
24
+ * endpoint. Definitive auth failures (401/403) reject the key; transient
25
+ * errors (429, 5xx, network) allow the key to be stored so a flaky network
26
+ * doesn't block setup.
27
+ */
28
+ export async function validateOpenAICompatibleApiKey({
29
+ apiKey,
30
+ baseURL,
31
+ providerLabel,
32
+ }: OpenAICompatibleKeyValidation): Promise<ApiKeyValidationResult> {
33
+ try {
34
+ const client = new OpenAI({
35
+ apiKey,
36
+ baseURL,
37
+ timeout: VALIDATION_TIMEOUT_MS,
38
+ maxRetries: 0,
39
+ });
40
+ await client.models.list();
41
+ return { valid: true };
42
+ } catch (error) {
43
+ if (error instanceof OpenAI.APIError) {
44
+ if (error.status === 401) {
45
+ return { valid: false, reason: "API key is invalid or expired." };
46
+ }
47
+ if (error.status === 403) {
48
+ return {
49
+ valid: false,
50
+ reason: `${providerLabel} API error (${error.status}): ${error.message}`,
51
+ };
52
+ }
53
+ // Transient errors (429, 5xx, etc.) — allow the key to be stored.
54
+ log.warn(
55
+ { provider: providerLabel, status: error.status },
56
+ "API returned a transient error during key validation — allowing key",
57
+ );
58
+ return { valid: true };
59
+ }
60
+ // Network errors — allow the key to be stored.
61
+ log.warn(
62
+ {
63
+ provider: providerLabel,
64
+ error: error instanceof Error ? error.message : String(error),
65
+ },
66
+ "Network error during key validation — allowing key",
67
+ );
68
+ return { valid: true };
69
+ }
70
+ }
@@ -55,6 +55,7 @@ const EFFORT_SUPPORTED_PROVIDERS = new Set([
55
55
  "vercel-ai-gateway",
56
56
  "fireworks",
57
57
  "together",
58
+ "baseten",
58
59
  ]);
59
60
 
60
61
  // For these providers, disabling reasoning is encoded through the same effort
@@ -66,6 +67,7 @@ const DISABLED_THINKING_USES_EFFORT_PROVIDERS = new Set([
66
67
  "together",
67
68
  "openrouter",
68
69
  "vercel-ai-gateway",
70
+ "baseten",
69
71
  ]);
70
72
 
71
73
  // Whether a disabled `thinking` config must be encoded as `effort: "none"`
@@ -215,7 +215,8 @@ export interface SendMessageConfig {
215
215
  * LLM call-site identifier. `RetryProvider` resolves
216
216
  * provider/model/maxTokens/effort/speed/verbosity/temperature/thinking/
217
217
  * contextWindow via `resolveCallSiteConfig(callSite, config.llm)`, falling
218
- * back to `llm.default` when no callSite-specific entry is present.
218
+ * back to the shipped call-site defaults when no callSite-specific entry
219
+ * is present.
219
220
  */
220
221
  callSite?: LLMCallSite;
221
222
  /**
@@ -433,11 +434,17 @@ export function extractOverflowTokensFromMessage(message: string): {
433
434
  maxTokens?: number;
434
435
  } {
435
436
  const match = message.match(/(\d[\d,]*)\s*(?:tokens?\s*)?[>≥]\s*(\d[\d,]*)/i);
436
- if (!match) return {};
437
+ if (!match) {
438
+ return {};
439
+ }
437
440
  const actual = parseInt(match[1].replace(/,/g, ""), 10);
438
441
  const max = parseInt(match[2].replace(/,/g, ""), 10);
439
442
  const out: { actualTokens?: number; maxTokens?: number } = {};
440
- if (!isNaN(actual) && actual > 0) out.actualTokens = actual;
441
- if (!isNaN(max) && max > 0) out.maxTokens = max;
443
+ if (!isNaN(actual) && actual > 0) {
444
+ out.actualTokens = actual;
445
+ }
446
+ if (!isNaN(max) && max > 0) {
447
+ out.maxTokens = max;
448
+ }
442
449
  return out;
443
450
  }
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  formatVellumModel,
5
+ getManagedUpstream,
5
6
  MANAGED_ROUTABLE_PROVIDERS,
6
7
  parseVellumModel,
7
8
  } from "./vellum-model-routing.js";
@@ -38,6 +39,31 @@ describe("vellum-model-routing", () => {
38
39
  expect(parseVellumModel("/foo")).toBeNull(); // empty provider
39
40
  });
40
41
 
42
+ test("getManagedUpstream resolves a bare catalog id to its owner", () => {
43
+ expect(getManagedUpstream("claude-fable-5")).toBe("anthropic");
44
+ });
45
+
46
+ test("getManagedUpstream resolves a routing string by its prefix", () => {
47
+ expect(
48
+ getManagedUpstream("fireworks/accounts/fireworks/models/minimax-m3"),
49
+ ).toBe("fireworks");
50
+ });
51
+
52
+ test("getManagedUpstream returns null for non-managed owners", () => {
53
+ expect(getManagedUpstream("MiniMax-M3")).toBeNull(); // minimax, not managed
54
+ expect(getManagedUpstream("openrouter/whatever")).toBeNull();
55
+ });
56
+
57
+ test("getManagedUpstream returns null for unknown models", () => {
58
+ expect(getManagedUpstream("not-a-real-model")).toBeNull();
59
+ expect(getManagedUpstream("")).toBeNull();
60
+ });
61
+
62
+ test("getManagedUpstream rejects managed-prefixed strings whose model is not cataloged under that provider", () => {
63
+ expect(getManagedUpstream("anthropic/definitely-not-a-model")).toBeNull();
64
+ expect(getManagedUpstream("openai/accounts/foo/bar")).toBeNull();
65
+ });
66
+
41
67
  test("managed set matches the platform proxy table", () => {
42
68
  // Guards against drift if PLATFORM_PROVIDER_META changes.
43
69
  expect([...MANAGED_ROUTABLE_PROVIDERS].sort()).toEqual([
@@ -14,6 +14,10 @@
14
14
  * codec — no I/O, no wiring — so it can be adopted incrementally.
15
15
  */
16
16
 
17
+ import {
18
+ getCatalogProviderForModel,
19
+ isModelInCatalog,
20
+ } from "./model-catalog.js";
17
21
  import { PLATFORM_PROVIDER_META } from "./platform-proxy/constants.js";
18
22
 
19
23
  /**
@@ -117,3 +121,29 @@ export function parseVellumModel(
117
121
  }
118
122
  return { provider, model };
119
123
  }
124
+
125
+ /**
126
+ * Resolve the managed upstream provider for a model referenced by a
127
+ * `provider: "vellum"` profile. Such a profile carries no upstream of its own,
128
+ * so the model alone determines it: a `<provider>/<model>` routing string
129
+ * names the upstream directly, and a bare id resolves to its catalog owner.
130
+ * Returns null when the model has no managed-routable upstream (unknown id,
131
+ * or owned by a non-managed provider like openrouter).
132
+ *
133
+ * A decoded routing string must also name a real catalog model under its
134
+ * prefix: OpenRouter/Vercel native ids share the `<provider>/<model>` shape
135
+ * (e.g. `anthropic/…`), so an unvalidated prefix match could route an
136
+ * arbitrary non-managed id through the managed proxy.
137
+ */
138
+ export function getManagedUpstream(model: string): string | null {
139
+ const route = parseVellumModel(model);
140
+ if (route) {
141
+ return isModelInCatalog(route.provider, route.model)
142
+ ? route.provider
143
+ : null;
144
+ }
145
+ const owner = getCatalogProviderForModel(model);
146
+ return owner !== undefined && MANAGED_ROUTABLE_PROVIDERS.has(owner)
147
+ ? owner
148
+ : null;
149
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Single source of truth for user- and model-facing voice (STT/TTS) failure
3
+ * copy. Provider adapters reject with raw upstream bodies (kept in logs only);
4
+ * these helpers turn a normalized error into friendly, actionable text that
5
+ * never leaks raw JSON or HTTP status noise into a conversation or the UI.
6
+ */
7
+
8
+ import type { SttError, SttProviderId } from "../stt/types.js";
9
+ import { getProviderEntry } from "./speech-to-text/provider-catalog.js";
10
+
11
+ function sttProviderLabel(providerId?: SttProviderId): string {
12
+ const displayName = providerId
13
+ ? getProviderEntry(providerId)?.displayName
14
+ : undefined;
15
+ return displayName ?? "the speech-to-text provider";
16
+ }
17
+
18
+ /**
19
+ * Friendly, model-actionable copy for a normalized STT failure. Names the
20
+ * provider (when known) so the model can point the user at the right fix.
21
+ */
22
+ export function describeSttFailure(
23
+ err: SttError,
24
+ providerId?: SttProviderId,
25
+ ): string {
26
+ const provider = sttProviderLabel(providerId);
27
+ switch (err.category) {
28
+ case "auth":
29
+ return `${provider} rejected the configured API key — it may be expired or invalid. Check the speech-to-text API key in Settings → Voice.`;
30
+ case "rate-limit":
31
+ return `${provider} is rate-limiting transcription requests right now. Try again shortly.`;
32
+ case "timeout":
33
+ return "Transcription timed out.";
34
+ case "invalid-audio":
35
+ return "The audio could not be transcribed (unsupported or corrupted format).";
36
+ default:
37
+ return `${provider} returned an error while transcribing.`;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Friendly copy for a TTS authentication failure (HTTP 401/403). Shared with
43
+ * the STT auth copy's "check the API key in Settings → Voice" guidance.
44
+ */
45
+ export function describeTtsAuthFailure(providerDisplayName: string): string {
46
+ return `${providerDisplayName} rejected the configured API key (authentication failed) — check the ${providerDisplayName} API key in Settings → Voice.`;
47
+ }
@@ -70,8 +70,6 @@ import type {
70
70
  import type { InterfaceId } from "../channels/types.js";
71
71
  import { resolveEffectiveContextWindow } from "../config/llm-context-resolution.js";
72
72
  import {
73
- isOverrideOrDefaultResolutionEnabled,
74
- resolveDefaultProfileKey,
75
73
  resolveProfilelessModelKey,
76
74
  selectWinningProfile,
77
75
  } from "../config/llm-resolver.js";
@@ -293,6 +291,14 @@ export interface WakeOptions {
293
291
  * absent.
294
292
  */
295
293
  toolContextPin?: WakeToolContextPin;
294
+ /**
295
+ * Skill IDs to preactivate for the wake so their bundled tools join the
296
+ * turn's active set (`allowedToolNames`) without a prior `skill_load`.
297
+ * Applied and restored alongside `allowedTools`; ignored when `allowedTools`
298
+ * is absent. Used by fork-based memory retrospectives to make the
299
+ * skill-management authoring tools callable directly.
300
+ */
301
+ preactivateSkillIds?: readonly string[];
296
302
  /**
297
303
  * Explicit persona/channel slugs for the wake's system-prompt build,
298
304
  * applied to the conversation for the duration of the run and restored
@@ -439,7 +445,9 @@ async function defaultResolveTarget(
439
445
  await import("../daemon/conversation-store.js");
440
446
  try {
441
447
  const existing = getConversation(conversationId);
442
- if (!existing) return null;
448
+ if (!existing) {
449
+ return null;
450
+ }
443
451
  if (existing.archivedAt != null) {
444
452
  log.info(
445
453
  { conversationId },
@@ -589,7 +597,9 @@ function inspectWakeOutput(
589
597
  let hasVisibleText = false;
590
598
  const toolUseNames: string[] = [];
591
599
  for (const msg of tailMessages) {
592
- if (msg.role !== "assistant") continue;
600
+ if (msg.role !== "assistant") {
601
+ continue;
602
+ }
593
603
  const blocks = Array.isArray(msg.content) ? msg.content : [];
594
604
  for (const block of blocks) {
595
605
  if (block.type === "text" && typeof block.text === "string") {
@@ -749,21 +759,14 @@ export async function wakeAgentForOpportunity(
749
759
  overrideProfile,
750
760
  forceOverrideProfile,
751
761
  });
752
- // Same winner-selection sourcing as the agent loop's key: under
753
- // override-or-default semantics a hand-mirrored chain would disagree
754
- // with dispatch (a non-forced override now wins on every call site).
762
+ // Same winner-selection sourcing as the agent loop's key: a hand-mirrored
763
+ // chain would disagree with dispatch (a non-forced override wins on every
764
+ // call site).
755
765
  const modelProfileKey =
756
- (isOverrideOrDefaultResolutionEnabled()
757
- ? selectWinningProfile(callSite, config.llm, {
758
- ...(overrideProfile != null ? { overrideProfile } : {}),
759
- selectionSeed: conversationId,
760
- }).profileName
761
- : ((forceOverrideProfile || callSite === "mainAgent"
762
- ? overrideProfile
763
- : undefined) ??
764
- resolveDefaultProfileKey(callSite, config.llm) ??
765
- overrideProfile ??
766
- resolveDefaultProfileKey("mainAgent", config.llm))) ??
766
+ selectWinningProfile(callSite, config.llm, {
767
+ ...(overrideProfile != null ? { overrideProfile } : {}),
768
+ selectionSeed: conversationId,
769
+ }).profileName ??
767
770
  resolveProfilelessModelKey(callSite, config.llm, {
768
771
  ...(overrideProfile != null ? { overrideProfile } : {}),
769
772
  ...(forceOverrideProfile ? { forceOverrideProfile: true } : {}),
@@ -1123,7 +1126,9 @@ export async function wakeAgentForOpportunity(
1123
1126
  // and renames `text_delta` → `assistant_text_delta`; bypassing it
1124
1127
  // would ship malformed wire frames.
1125
1128
  const goLive = (currentHistory: Message[]): void => {
1126
- if (mode === "live") return;
1129
+ if (mode === "live") {
1130
+ return;
1131
+ }
1127
1132
  if (!surfaceInjected) {
1128
1133
  if (!opts.suppressWakeSurface) {
1129
1134
  const tailStart = baselineLength + wakeHintMessageCount;
@@ -1186,7 +1191,9 @@ export async function wakeAgentForOpportunity(
1186
1191
  currentHistory: Message[],
1187
1192
  ): Promise<void> => {
1188
1193
  const start = baselineLength + wakeHintMessageCount + persistedTailIndex;
1189
- if (start >= currentHistory.length) return;
1194
+ if (start >= currentHistory.length) {
1195
+ return;
1196
+ }
1190
1197
  const newMessages = currentHistory.slice(start);
1191
1198
  for (const msg of newMessages) {
1192
1199
  conversation.messages.push(msg);
@@ -1207,9 +1214,13 @@ export async function wakeAgentForOpportunity(
1207
1214
  let wakeToolScopeRestored = false;
1208
1215
  let restoreWakeToolScope: (() => void) | null = null;
1209
1216
  const restoreWakeAllowedTools = (): void => {
1210
- if (wakeToolScopeRestored) return;
1217
+ if (wakeToolScopeRestored) {
1218
+ return;
1219
+ }
1211
1220
  wakeToolScopeRestored = true;
1212
- if (!restoreWakeToolScope) return;
1221
+ if (!restoreWakeToolScope) {
1222
+ return;
1223
+ }
1213
1224
  try {
1214
1225
  restoreWakeToolScope();
1215
1226
  } catch (err) {
@@ -1220,13 +1231,16 @@ export async function wakeAgentForOpportunity(
1220
1231
  }
1221
1232
  };
1222
1233
  const applyWakeAllowedTools = (): boolean => {
1223
- if (!opts.allowedTools) return true;
1234
+ if (!opts.allowedTools) {
1235
+ return true;
1236
+ }
1224
1237
  try {
1225
1238
  restoreWakeToolScope = scopeWakeAllowedTools(
1226
1239
  conversation,
1227
1240
  new Set(opts.allowedTools),
1228
1241
  opts.toolGateMode,
1229
1242
  opts.toolContextPin,
1243
+ opts.preactivateSkillIds,
1230
1244
  );
1231
1245
  return true;
1232
1246
  } catch (err) {
@@ -1342,7 +1356,9 @@ export async function wakeAgentForOpportunity(
1342
1356
  const priorTurnTrust = conversation.currentTurnTrustContext;
1343
1357
  conversation.currentCallSite = callSite;
1344
1358
  conversation.currentTurnOverrideProfile = overrideProfile;
1345
- if (opts.clientless) conversation.hasNoClient = true;
1359
+ if (opts.clientless) {
1360
+ conversation.hasNoClient = true;
1361
+ }
1346
1362
  // Per-turn guardian elevation for the wake's tools, set after the pre-run
1347
1363
  // reads so a pre-run failure can't leak it; restored in the finally.
1348
1364
  if (opts.trustContext) {