@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
@@ -54,12 +54,14 @@ mock.module("../security/credential-key.js", () => ({
54
54
  let mockConsultationTimeoutMs = 90_000;
55
55
  let mockSilenceTimeoutMs = 30_000;
56
56
  let mockEndCallListenWindowMs = 0;
57
+ let mockEndCallDrainMaxWaitMs = 15_000;
57
58
 
58
59
  mock.module("../calls/call-constants.js", () => ({
59
60
  getMaxCallDurationMs: () => 12 * 60 * 1000,
60
61
  getUserConsultationTimeoutMs: () => mockConsultationTimeoutMs,
61
62
  getSilenceTimeoutMs: () => mockSilenceTimeoutMs,
62
63
  getEndCallListenWindowMs: () => mockEndCallListenWindowMs,
64
+ getEndCallDrainMaxWaitMs: () => mockEndCallDrainMaxWaitMs,
63
65
  }));
64
66
 
65
67
  // ── Voice session bridge mock ────────────────────────────────────────
@@ -246,6 +248,7 @@ interface MockTransport extends CallTransport {
246
248
  sentPlayUrls: string[];
247
249
  endCalled: boolean;
248
250
  endReason: string | undefined;
251
+ cancelPendingSpeechCount: number;
249
252
  }
250
253
 
251
254
  function createMockTransport(): MockTransport {
@@ -254,6 +257,7 @@ function createMockTransport(): MockTransport {
254
257
  sentPlayUrls: [] as string[],
255
258
  _endCalled: false,
256
259
  _endReason: undefined as string | undefined,
260
+ _cancelPendingSpeechCount: 0,
257
261
  };
258
262
 
259
263
  return {
@@ -269,6 +273,9 @@ function createMockTransport(): MockTransport {
269
273
  get endReason() {
270
274
  return state._endReason;
271
275
  },
276
+ get cancelPendingSpeechCount() {
277
+ return state._cancelPendingSpeechCount;
278
+ },
272
279
  sendTextToken(token: string, last: boolean) {
273
280
  state.sentTokens.push({ token, last });
274
281
  },
@@ -279,6 +286,9 @@ function createMockTransport(): MockTransport {
279
286
  state._endCalled = true;
280
287
  state._endReason = reason;
281
288
  },
289
+ cancelPendingSpeech() {
290
+ state._cancelPendingSpeechCount++;
291
+ },
282
292
  } as MockTransport;
283
293
  }
284
294
 
@@ -365,6 +375,8 @@ function setupController(
365
375
  trustContext?: import("../daemon/trust-context-types.js").TrustContext;
366
376
  /** Simulate the media-stream transport's PCM requirement. */
367
377
  requiresPcmAudio?: boolean;
378
+ /** Simulate a transport that gates teardown on playback drain. */
379
+ awaitPlaybackDrained?: () => Promise<void>;
368
380
  },
369
381
  ) {
370
382
  ensureConversation("conv-ctrl-test");
@@ -380,6 +392,9 @@ function setupController(
380
392
  if (opts?.requiresPcmAudio) {
381
393
  Object.assign(transport, { requiresPcmAudio: true });
382
394
  }
395
+ if (opts?.awaitPlaybackDrained) {
396
+ Object.assign(transport, { awaitPlaybackDrained: opts.awaitPlaybackDrained });
397
+ }
383
398
  const controller = new CallController(session.id, transport, task ?? null, {
384
399
  assistantId: opts?.assistantId,
385
400
  trustContext: opts?.trustContext,
@@ -440,6 +455,7 @@ describe("call-controller", () => {
440
455
  mockConsultationTimeoutMs = 90_000;
441
456
  mockSilenceTimeoutMs = 30_000;
442
457
  mockEndCallListenWindowMs = 0;
458
+ mockEndCallDrainMaxWaitMs = 15_000;
443
459
  // Reset TTS config to defaults so per-test mutations don't leak.
444
460
  const cfg = loadConfig();
445
461
  cfg.services.tts.provider = "elevenlabs";
@@ -897,6 +913,281 @@ describe("call-controller", () => {
897
913
  controller.destroy();
898
914
  });
899
915
 
916
+ // ── END_CALL playback-drain gating ───────────────────────────────
917
+
918
+ test("END_CALL does not end the session until playback drain resolves", async () => {
919
+ mockEndCallListenWindowMs = 0;
920
+ let resolveDrain!: () => void;
921
+ const drainPromise = new Promise<void>((r) => {
922
+ resolveDrain = r;
923
+ });
924
+ mockStartVoiceTurn.mockImplementation(
925
+ createMockVoiceTurn(["Thank you for calling, goodbye! ", "[END_CALL]"]),
926
+ );
927
+ const { session, relay, controller } = setupController(undefined, {
928
+ awaitPlaybackDrained: () => drainPromise,
929
+ });
930
+
931
+ await controller.handleCallerUtterance("That is all, thanks");
932
+
933
+ // Drain has not resolved — teardown must be blocked.
934
+ await new Promise((r) => setTimeout(r, 20));
935
+ expect(relay.endCalled).toBe(false);
936
+ expect(getCallSession(session.id)!.status).toBe("in_progress");
937
+
938
+ // Drain completes — teardown proceeds and ends the session.
939
+ resolveDrain();
940
+ await pollUntil(() => relay.endCalled);
941
+ expect(getCallSession(session.id)!.status).toBe("completed");
942
+
943
+ controller.destroy();
944
+ });
945
+
946
+ test("END_CALL honors the listen window after drain resolves", async () => {
947
+ mockEndCallListenWindowMs = 30;
948
+ let resolveDrain!: () => void;
949
+ const drainPromise = new Promise<void>((r) => {
950
+ resolveDrain = r;
951
+ });
952
+ mockStartVoiceTurn.mockImplementation(
953
+ createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
954
+ );
955
+ const { session, relay, controller } = setupController(undefined, {
956
+ awaitPlaybackDrained: () => drainPromise,
957
+ });
958
+
959
+ await controller.handleCallerUtterance("That is all, thanks");
960
+ resolveDrain();
961
+
962
+ // Listen window still gates completion after drain.
963
+ await new Promise((r) => setTimeout(r, 10));
964
+ expect(relay.endCalled).toBe(false);
965
+
966
+ await pollUntil(() => relay.endCalled);
967
+ expect(getCallSession(session.id)!.status).toBe("completed");
968
+
969
+ controller.destroy();
970
+ });
971
+
972
+ test("END_CALL forces hangup after the drain safety cap when drain never resolves", async () => {
973
+ mockEndCallListenWindowMs = 0;
974
+ mockEndCallDrainMaxWaitMs = 25;
975
+ mockStartVoiceTurn.mockImplementation(
976
+ createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
977
+ );
978
+ // Drain that never resolves — only the safety cap can release teardown.
979
+ const { session, relay, controller } = setupController(undefined, {
980
+ awaitPlaybackDrained: () => new Promise<void>(() => {}),
981
+ });
982
+
983
+ await controller.handleCallerUtterance("That is all, thanks");
984
+ expect(relay.endCalled).toBe(false);
985
+
986
+ await pollUntil(() => relay.endCalled);
987
+ expect(getCallSession(session.id)!.status).toBe("completed");
988
+
989
+ controller.destroy();
990
+ });
991
+
992
+ test("a second END_CALL teardown keeps its own safety cap after cancelling the first", async () => {
993
+ // Cancelling a mid-drain teardown then immediately starting another must
994
+ // not let the old teardown's continuation clear the new one's cap.
995
+ mockEndCallListenWindowMs = 30;
996
+ mockEndCallDrainMaxWaitMs = 40;
997
+ const turnContents: string[] = [];
998
+ mockStartVoiceTurn.mockImplementation(
999
+ async (opts: {
1000
+ content: string;
1001
+ onTextDelta: (t: string) => void;
1002
+ onComplete: () => void;
1003
+ }) => {
1004
+ turnContents.push(opts.content);
1005
+ opts.onTextDelta(
1006
+ turnContents.length === 2 ? "Okay, bye. [END_CALL]" : "Goodbye! [END_CALL]",
1007
+ );
1008
+ opts.onComplete();
1009
+ return { turnId: `run-${turnContents.length}`, abort: () => {} };
1010
+ },
1011
+ );
1012
+ // Both goodbyes' drains never echo — only each teardown's own safety cap
1013
+ // can release it.
1014
+ const { session, relay, controller } = setupController(undefined, {
1015
+ awaitPlaybackDrained: () => new Promise<void>(() => {}),
1016
+ });
1017
+
1018
+ await controller.handleCallerUtterance("That is all"); // teardown A (mid-drain)
1019
+ // Re-engage: cancels A and starts turn 2, which emits END_CALL → teardown B.
1020
+ await controller.handleCallerUtterance("Actually, bye");
1021
+
1022
+ // B must still hang up via its own cap despite A's cancelled continuation.
1023
+ await pollUntil(() => relay.endCalled);
1024
+ expect(getCallSession(session.id)!.status).toBe("completed");
1025
+
1026
+ controller.destroy();
1027
+ });
1028
+
1029
+ test("caller re-engagement during the drain phase cancels teardown and defers", async () => {
1030
+ mockEndCallListenWindowMs = 30;
1031
+ const turnContents: string[] = [];
1032
+ mockStartVoiceTurn.mockImplementation(
1033
+ async (opts: {
1034
+ content: string;
1035
+ onTextDelta: (t: string) => void;
1036
+ onComplete: () => void;
1037
+ }) => {
1038
+ turnContents.push(opts.content);
1039
+ if (turnContents.length === 1) {
1040
+ opts.onTextDelta("Goodbye! [END_CALL]");
1041
+ } else {
1042
+ opts.onTextDelta("Sure, I'm still here.");
1043
+ }
1044
+ opts.onComplete();
1045
+ return { turnId: `run-${turnContents.length}`, abort: () => {} };
1046
+ },
1047
+ );
1048
+ // Drain never resolves during the test window; re-engagement must still
1049
+ // cancel the pending teardown mid-drain.
1050
+ const { session, relay, controller } = setupController(undefined, {
1051
+ awaitPlaybackDrained: () => new Promise<void>(() => {}),
1052
+ });
1053
+
1054
+ await controller.handleCallerUtterance("That is all, thanks");
1055
+ expect(relay.endCalled).toBe(false);
1056
+
1057
+ // Caller re-engages while teardown is still waiting on drain.
1058
+ await controller.handleCallerUtterance("Wait, one more thing");
1059
+ await new Promise((r) => setTimeout(r, 10));
1060
+
1061
+ expect(relay.endCalled).toBe(false);
1062
+ expect(getCallSession(session.id)!.status).toBe("in_progress");
1063
+ expect(turnContents).toContain("Wait, one more thing");
1064
+ const allText = relay.sentTokens.map((t) => t.token).join("");
1065
+ expect(allText).toContain("I'm still here.");
1066
+ // The abandoned goodbye's queued speech must be cancelled so it can't
1067
+ // play over the caller's follow-up.
1068
+ expect(relay.cancelPendingSpeechCount).toBeGreaterThan(0);
1069
+
1070
+ controller.destroy();
1071
+ });
1072
+
1073
+ test("repeat END_CALL still waits for drain but skips the listen window", async () => {
1074
+ mockEndCallListenWindowMs = 30;
1075
+ let resolveSecondDrain!: () => void;
1076
+ let drainCalls = 0;
1077
+ const turnContents: string[] = [];
1078
+ mockStartVoiceTurn.mockImplementation(
1079
+ async (opts: {
1080
+ content: string;
1081
+ onTextDelta: (t: string) => void;
1082
+ onComplete: () => void;
1083
+ }) => {
1084
+ turnContents.push(opts.content);
1085
+ if (turnContents.length === 1) {
1086
+ opts.onTextDelta("Goodbye! [END_CALL]");
1087
+ } else if (turnContents.length === 2) {
1088
+ opts.onTextDelta("Okay, one quick thing.");
1089
+ } else {
1090
+ opts.onTextDelta("I have to go now. Goodbye! [END_CALL]");
1091
+ }
1092
+ opts.onComplete();
1093
+ return { turnId: `run-${turnContents.length}`, abort: () => {} };
1094
+ },
1095
+ );
1096
+ const { session, relay, controller } = setupController(undefined, {
1097
+ awaitPlaybackDrained: () => {
1098
+ drainCalls++;
1099
+ // First END_CALL drain resolves immediately; the second one is held
1100
+ // so we can assert teardown is gated on it, not on a 0ms timer.
1101
+ if (drainCalls === 1) return Promise.resolve();
1102
+ return new Promise<void>((r) => {
1103
+ resolveSecondDrain = r;
1104
+ });
1105
+ },
1106
+ });
1107
+
1108
+ // First END_CALL — listen window is armed; caller re-engages (deferral #1).
1109
+ await controller.handleCallerUtterance("That is all, thanks");
1110
+ await controller.handleCallerUtterance("Wait, one more thing");
1111
+ await new Promise((r) => setTimeout(r, 40));
1112
+ expect(relay.endCalled).toBe(false);
1113
+
1114
+ // Second END_CALL after the deferral — must still block on drain.
1115
+ await controller.handleCallerUtterance("Just kidding, keep going");
1116
+ await new Promise((r) => setTimeout(r, 20));
1117
+ expect(relay.endCalled).toBe(false);
1118
+
1119
+ // Drain resolves — teardown completes without any listen window.
1120
+ resolveSecondDrain();
1121
+ await pollUntil(() => relay.endCalled);
1122
+ expect(getCallSession(session.id)!.status).toBe("completed");
1123
+
1124
+ controller.destroy();
1125
+ });
1126
+
1127
+ test("transport without awaitPlaybackDrained ends after listen window as before", async () => {
1128
+ mockEndCallListenWindowMs = 25;
1129
+ mockStartVoiceTurn.mockImplementation(
1130
+ createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
1131
+ );
1132
+ // Default mock transport does not implement awaitPlaybackDrained.
1133
+ const { session, relay, controller } = setupController();
1134
+
1135
+ await controller.handleCallerUtterance("That is all, thanks");
1136
+ expect(relay.endCalled).toBe(false);
1137
+
1138
+ await pollUntil(() => relay.endCalled);
1139
+ expect(getCallSession(session.id)!.status).toBe("completed");
1140
+
1141
+ controller.destroy();
1142
+ });
1143
+
1144
+ test("destroy cancels a pending end-call teardown mid-drain", async () => {
1145
+ mockEndCallListenWindowMs = 0;
1146
+ let resolveDrain!: () => void;
1147
+ const drainPromise = new Promise<void>((r) => {
1148
+ resolveDrain = r;
1149
+ });
1150
+ mockStartVoiceTurn.mockImplementation(
1151
+ createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
1152
+ );
1153
+ const { session, relay, controller } = setupController(undefined, {
1154
+ awaitPlaybackDrained: () => drainPromise,
1155
+ });
1156
+
1157
+ await controller.handleCallerUtterance("That is all, thanks");
1158
+ expect(relay.endCalled).toBe(false);
1159
+
1160
+ controller.destroy();
1161
+
1162
+ // Drain resolves after destroy — teardown must not fire endSession.
1163
+ resolveDrain();
1164
+ await new Promise((r) => setTimeout(r, 20));
1165
+ expect(relay.endCalled).toBe(false);
1166
+ expect(getCallSession(session.id)!.status).toBe("in_progress");
1167
+ });
1168
+
1169
+ test("destroy during the listen window settles the wait without hanging up", async () => {
1170
+ // Long window so destroy lands while the listen-window wait is armed;
1171
+ // the wait must settle (not leak) and teardown must not fire endSession.
1172
+ mockEndCallListenWindowMs = 10_000;
1173
+ const drainPromise = Promise.resolve();
1174
+ mockStartVoiceTurn.mockImplementation(
1175
+ createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
1176
+ );
1177
+ const { relay, controller } = setupController(undefined, {
1178
+ awaitPlaybackDrained: () => drainPromise,
1179
+ });
1180
+
1181
+ await controller.handleCallerUtterance("That is all, thanks");
1182
+ // Let the drain phase resolve and the listen-window timer arm.
1183
+ await new Promise((r) => setTimeout(r, 20));
1184
+ expect(relay.endCalled).toBe(false);
1185
+
1186
+ controller.destroy();
1187
+ await new Promise((r) => setTimeout(r, 20));
1188
+ expect(relay.endCalled).toBe(false);
1189
+ });
1190
+
900
1191
  // ── handleUserAnswer ──────────────────────────────────────────────
901
1192
 
902
1193
  test("handleUserAnswer: returns true immediately and fires LLM asynchronously", async () => {
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Verifies `CallSiteRoutingProvider` auto-resolves a connection for
3
+ * connection-less profiles even when the resolved provider matches the
4
+ * default provider's name.
5
+ *
6
+ * On platform-hosted installs the default transport rides the managed
7
+ * (platform-billed) `vellum` connection while presenting the upstream
8
+ * provider's name (e.g. "anthropic"). A BYOK profile `{provider:
9
+ * "anthropic"}` with no explicit `provider_connection` must route through
10
+ * the user's own connection when one exists — reusing the default on a bare
11
+ * name match silently bills managed credits for a BYOK-intent profile.
12
+ */
13
+
14
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
15
+
16
+ import * as realDb from "../persistence/db-connection.js";
17
+ import * as realConnections from "../providers/inference/connections.js";
18
+ import type {
19
+ Message,
20
+ Provider,
21
+ ProviderResponse,
22
+ SendMessageOptions,
23
+ } from "../providers/types.js";
24
+ import { setConfig } from "./helpers/set-config.js";
25
+
26
+ let connectionRows: Array<Record<string, unknown>> = [];
27
+
28
+ mock.module("../providers/inference/connections.js", () => ({
29
+ ...realConnections,
30
+ listConnections: () => connectionRows,
31
+ }));
32
+
33
+ mock.module("../persistence/db-connection.js", () => ({
34
+ ...realDb,
35
+ getDb: () => ({}),
36
+ }));
37
+
38
+ const { CallSiteRoutingProvider } =
39
+ await import("../providers/call-site-routing.js");
40
+
41
+ const DUMMY_MESSAGES: Message[] = [
42
+ { role: "user", content: [{ type: "text", text: "hi" }] },
43
+ ];
44
+
45
+ function makeProvider(name: string, onCall: () => void): Provider {
46
+ return {
47
+ name,
48
+ async sendMessage(
49
+ _messages: Message[],
50
+ _options?: SendMessageOptions,
51
+ ): Promise<ProviderResponse> {
52
+ onCall();
53
+ return {
54
+ content: [{ type: "text", text: "ok" }],
55
+ model: name,
56
+ usage: { inputTokens: 1, outputTokens: 1 },
57
+ stopReason: "end_turn",
58
+ };
59
+ },
60
+ };
61
+ }
62
+
63
+ const PERSONAL_CONNECTION = {
64
+ name: "anthropic-personal",
65
+ provider: "anthropic",
66
+ auth: { type: "api_key", credential: "credential/anthropic/api_key" },
67
+ };
68
+
69
+ beforeEach(() => {
70
+ connectionRows = [];
71
+ // Same provider name as the default transport; no provider_connection on
72
+ // the profile — the managed-install shape that must not silently reuse
73
+ // the default transport.
74
+ setConfig("llm", {
75
+ default: {
76
+ provider: "anthropic",
77
+ model: "claude-opus-4-8",
78
+ provider_connection: "vellum",
79
+ },
80
+ profiles: {
81
+ byok: { provider: "anthropic", model: "claude-fable-5" },
82
+ },
83
+ callSites: {
84
+ memoryRetrieval: { profile: "byok" },
85
+ },
86
+ });
87
+ });
88
+
89
+ describe("CallSiteRoutingProvider connection auto-resolve", () => {
90
+ test("routes a connection-less same-provider profile through the user's own connection", async () => {
91
+ connectionRows = [PERSONAL_CONNECTION];
92
+
93
+ const calls = { default: 0, personal: 0 };
94
+ const defaultProvider = makeProvider("anthropic", () => {
95
+ calls.default++;
96
+ });
97
+ const personalProvider = makeProvider("anthropic", () => {
98
+ calls.personal++;
99
+ });
100
+
101
+ const resolvedNames: string[] = [];
102
+ const wrapped = new CallSiteRoutingProvider(
103
+ defaultProvider,
104
+ async (connectionName: string) => {
105
+ resolvedNames.push(connectionName);
106
+ return connectionName === "anthropic-personal"
107
+ ? personalProvider
108
+ : null;
109
+ },
110
+ );
111
+
112
+ await wrapped.sendMessage(DUMMY_MESSAGES, {
113
+ config: { callSite: "memoryRetrieval" },
114
+ });
115
+
116
+ expect(resolvedNames).toEqual(["anthropic-personal"]);
117
+ expect(calls.personal).toBe(1);
118
+ expect(calls.default).toBe(0);
119
+ });
120
+
121
+ test("reuses the default transport when no connection exists for the provider", async () => {
122
+ connectionRows = [];
123
+
124
+ const calls = { default: 0 };
125
+ const defaultProvider = makeProvider("anthropic", () => {
126
+ calls.default++;
127
+ });
128
+
129
+ const resolvedNames: string[] = [];
130
+ const wrapped = new CallSiteRoutingProvider(
131
+ defaultProvider,
132
+ async (connectionName: string) => {
133
+ resolvedNames.push(connectionName);
134
+ return null;
135
+ },
136
+ );
137
+
138
+ await wrapped.sendMessage(DUMMY_MESSAGES, {
139
+ config: { callSite: "memoryRetrieval" },
140
+ });
141
+
142
+ expect(resolvedNames).toEqual([]);
143
+ expect(calls.default).toBe(1);
144
+ });
145
+
146
+ test("falls back to the default transport when the auto-resolved connection soft-fails", async () => {
147
+ connectionRows = [PERSONAL_CONNECTION];
148
+
149
+ const calls = { default: 0 };
150
+ const defaultProvider = makeProvider("anthropic", () => {
151
+ calls.default++;
152
+ });
153
+
154
+ const wrapped = new CallSiteRoutingProvider(
155
+ defaultProvider,
156
+ async () => null,
157
+ );
158
+
159
+ await wrapped.sendMessage(DUMMY_MESSAGES, {
160
+ config: { callSite: "memoryRetrieval" },
161
+ });
162
+
163
+ expect(calls.default).toBe(1);
164
+ });
165
+ });