@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f

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 (866) hide show
  1. package/ARCHITECTURE.md +94 -91
  2. package/Dockerfile +5 -16
  3. package/README.md +7 -10
  4. package/bun.lock +0 -4
  5. package/docs/architecture/security.md +20 -20
  6. package/docs/stt-provider-onboarding.md +4 -5
  7. package/docs/trusted-contact-access.md +7 -7
  8. package/eslint-rules/cli-no-daemon-internals.js +4 -0
  9. package/eslint.config.mjs +7 -1
  10. package/knip.json +0 -1
  11. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
  13. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
  14. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
  15. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
  17. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
  19. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
  21. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
  22. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
  23. package/openapi.yaml +1073 -225
  24. package/package.json +1 -4
  25. package/scripts/test.sh +35 -13
  26. package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
  27. package/src/__tests__/acp-session.test.ts +4 -1
  28. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
  29. package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
  30. package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
  31. package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
  32. package/src/__tests__/app-source-watcher.test.ts +11 -0
  33. package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
  34. package/src/__tests__/assistant-attachments.test.ts +62 -4
  35. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
  36. package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
  37. package/src/__tests__/attachments-store.test.ts +3 -0
  38. package/src/__tests__/background-shell-bash.test.ts +34 -0
  39. package/src/__tests__/background-shell-host-bash.test.ts +12 -0
  40. package/src/__tests__/background-tool-registry.test.ts +97 -0
  41. package/src/__tests__/background-tool-routes.test.ts +72 -1
  42. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  43. package/src/__tests__/btw-routes.test.ts +57 -3
  44. package/src/__tests__/call-controller.test.ts +474 -84
  45. package/src/__tests__/call-domain.test.ts +48 -1
  46. package/src/__tests__/call-pointer-messages.test.ts +57 -19
  47. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
  48. package/src/__tests__/call-routes-http.test.ts +15 -14
  49. package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
  50. package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
  51. package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
  52. package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
  53. package/src/__tests__/call-setup-flow.test.ts +311 -0
  54. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
  55. package/src/__tests__/channel-guardian.test.ts +4 -7
  56. package/src/__tests__/channel-readiness-service.test.ts +0 -6
  57. package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
  58. package/src/__tests__/channel-retry-sweep.test.ts +76 -64
  59. package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
  60. package/src/__tests__/clear-all-lexical.test.ts +90 -0
  61. package/src/__tests__/compactor-media-strip.test.ts +177 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +5 -5
  63. package/src/__tests__/config-schema.test.ts +17 -0
  64. package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
  65. package/src/__tests__/contacts-relay-reads.test.ts +188 -23
  66. package/src/__tests__/contacts-tools.test.ts +42 -1
  67. package/src/__tests__/contacts-write.test.ts +181 -0
  68. package/src/__tests__/context-search-conversations-source.test.ts +220 -81
  69. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  71. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  72. package/src/__tests__/conversation-agent-loop.test.ts +39 -0
  73. package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
  74. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
  75. package/src/__tests__/conversation-clear-safety.test.ts +0 -1
  76. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
  77. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
  78. package/src/__tests__/conversation-disk-view.test.ts +1 -0
  79. package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
  80. package/src/__tests__/conversation-error.test.ts +0 -18
  81. package/src/__tests__/conversation-fork-crud.test.ts +67 -0
  82. package/src/__tests__/conversation-history-web-search.test.ts +7 -137
  83. package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
  84. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
  85. package/src/__tests__/conversation-process-callsite.test.ts +37 -0
  86. package/src/__tests__/conversation-queue.test.ts +149 -0
  87. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
  88. package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
  89. package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
  90. package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
  91. package/src/__tests__/conversation-skill-tools.test.ts +73 -0
  92. package/src/__tests__/conversation-wipe.test.ts +20 -5
  93. package/src/__tests__/credential-prompt-route.test.ts +69 -0
  94. package/src/__tests__/credential-security-invariants.test.ts +1 -3
  95. package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
  96. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
  97. package/src/__tests__/edit-propagation.test.ts +56 -2
  98. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
  99. package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
  100. package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
  101. package/src/__tests__/guardian-question-mode.test.ts +57 -0
  102. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  103. package/src/__tests__/guardian-wait-controller.test.ts +619 -0
  104. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
  105. package/src/__tests__/heic-fixture.ts +55 -0
  106. package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
  107. package/src/__tests__/http-conversation-lineage.test.ts +6 -1
  108. package/src/__tests__/image-conversion.test.ts +170 -0
  109. package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
  110. package/src/__tests__/introduction-card-resolver.test.ts +354 -0
  111. package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
  112. package/src/__tests__/invite-routes-http.test.ts +246 -420
  113. package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
  114. package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
  115. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
  116. package/src/__tests__/list-messages-attachments.test.ts +159 -0
  117. package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
  118. package/src/__tests__/list-messages-queued.test.ts +25 -0
  119. package/src/__tests__/managed-store.test.ts +129 -1
  120. package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
  121. package/src/__tests__/media-stream-output.test.ts +350 -19
  122. package/src/__tests__/media-stream-server-integration.test.ts +943 -169
  123. package/src/__tests__/media-stream-stt-session.test.ts +498 -1
  124. package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
  125. package/src/__tests__/model-intents.test.ts +1 -1
  126. package/src/__tests__/mtime-cache.test.ts +19 -0
  127. package/src/__tests__/non-member-access-request.test.ts +349 -3
  128. package/src/__tests__/notification-decision-fallback.test.ts +104 -8
  129. package/src/__tests__/notification-decision-strategy.test.ts +57 -8
  130. package/src/__tests__/onboarding-template-contract.test.ts +27 -43
  131. package/src/__tests__/openai-provider.test.ts +33 -0
  132. package/src/__tests__/openai-responses-provider.test.ts +40 -0
  133. package/src/__tests__/persistence-layering-guard.test.ts +29 -20
  134. package/src/__tests__/persona-resolver.test.ts +56 -2
  135. package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
  136. package/src/__tests__/phone.test.ts +1 -15
  137. package/src/__tests__/plugin-bootstrap.test.ts +25 -196
  138. package/src/__tests__/plugin-disabled-state.test.ts +33 -0
  139. package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
  140. package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
  141. package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
  142. package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
  143. package/src/__tests__/plugin-pipeline.test.ts +63 -1
  144. package/src/__tests__/plugin-route-contribution.test.ts +62 -115
  145. package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
  146. package/src/__tests__/plugin-types.test.ts +0 -1
  147. package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
  148. package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
  149. package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
  150. package/src/__tests__/public-ingress-urls.test.ts +0 -48
  151. package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
  152. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
  153. package/src/__tests__/resolve-trust-class.test.ts +28 -10
  154. package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
  155. package/src/__tests__/schedule-retry.test.ts +38 -14
  156. package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
  157. package/src/__tests__/schedule-routes.test.ts +14 -6
  158. package/src/__tests__/schedule-store.test.ts +3 -1
  159. package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
  160. package/src/__tests__/scheduler-recurrence.test.ts +57 -107
  161. package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
  162. package/src/__tests__/scheduler-wake.test.ts +13 -23
  163. package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
  164. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  165. package/src/__tests__/shell-observability.test.ts +1 -0
  166. package/src/__tests__/skill-boundary-guard.test.ts +3 -4
  167. package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
  168. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
  169. package/src/__tests__/skill-script-runner-host.test.ts +53 -0
  170. package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
  171. package/src/__tests__/skill-tool-factory.test.ts +73 -1
  172. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
  173. package/src/__tests__/slack-inbound-verification.test.ts +36 -0
  174. package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
  175. package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
  176. package/src/__tests__/stt-hints.test.ts +2 -256
  177. package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
  178. package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
  179. package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
  180. package/src/__tests__/subagent-persistence.test.ts +122 -0
  181. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  182. package/src/__tests__/subagent-terminal-message.test.ts +99 -0
  183. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
  184. package/src/__tests__/system-prompt.test.ts +17 -118
  185. package/src/__tests__/task-scheduler.test.ts +68 -89
  186. package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
  187. package/src/__tests__/telephony-tts-guard.test.ts +538 -0
  188. package/src/__tests__/title-generate-hook.test.ts +16 -0
  189. package/src/__tests__/tool-approval-handler.test.ts +87 -1
  190. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
  191. package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
  192. package/src/__tests__/trust-context-guards.test.ts +21 -10
  193. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
  194. package/src/__tests__/twilio-provider.test.ts +17 -17
  195. package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
  196. package/src/__tests__/twilio-routes.test.ts +257 -387
  197. package/src/__tests__/twilio-validation.test.ts +1 -1
  198. package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
  199. package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
  200. package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
  201. package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
  202. package/src/__tests__/voice-quality.test.ts +2 -2
  203. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
  204. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
  205. package/src/__tests__/voice-session-bridge.test.ts +1 -10
  206. package/src/__tests__/workspace-git-service.test.ts +1 -1
  207. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
  208. package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
  209. package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
  210. package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
  211. package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
  212. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
  213. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
  214. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
  215. package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
  216. package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
  217. package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
  218. package/src/acp/__tests__/session-manager.test.ts +6 -0
  219. package/src/acp/agent-process.test.ts +116 -0
  220. package/src/acp/agent-process.ts +71 -0
  221. package/src/acp/auto-install.ts +12 -8
  222. package/src/acp/failure-error.test.ts +109 -0
  223. package/src/acp/failure-error.ts +111 -0
  224. package/src/acp/prepare-agent-env.ts +0 -12
  225. package/src/acp/resolve-agent.test.ts +6 -45
  226. package/src/acp/resolve-agent.ts +2 -4
  227. package/src/acp/session-manager.test.ts +312 -0
  228. package/src/acp/session-manager.ts +102 -46
  229. package/src/agent/ax-tree-compaction.test.ts +4 -1
  230. package/src/agent/image-optimize.ts +17 -127
  231. package/src/agent/loop.ts +45 -192
  232. package/src/api/events/background-tool-completed.test.ts +51 -0
  233. package/src/api/events/background-tool-completed.ts +29 -0
  234. package/src/api/events/background-tool-started.test.ts +36 -0
  235. package/src/api/events/background-tool-started.ts +28 -0
  236. package/src/api/events/conversation-error.ts +0 -1
  237. package/src/api/events/open-panel.ts +33 -0
  238. package/src/api/events/subagent-status-changed.ts +4 -1
  239. package/src/api/index.ts +19 -0
  240. package/src/api/responses/conversation-message.ts +34 -0
  241. package/src/approvals/guardian-decision-primitive.ts +74 -24
  242. package/src/approvals/guardian-request-resolvers.ts +450 -122
  243. package/src/apps/app-store.ts +1 -0
  244. package/src/apps/shared-app-links-store.ts +1 -0
  245. package/src/avatar/identity-avatar.ts +3 -7
  246. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  247. package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
  248. package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
  249. package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
  250. package/src/calls/call-controller.ts +134 -48
  251. package/src/calls/call-domain.ts +18 -42
  252. package/src/calls/call-pointer-messages.ts +51 -67
  253. package/src/calls/call-setup-flow-types.ts +76 -0
  254. package/src/calls/call-setup-flow.ts +1743 -0
  255. package/src/calls/call-speech-output.ts +87 -23
  256. package/src/calls/call-store.ts +4 -0
  257. package/src/calls/call-transport.ts +22 -36
  258. package/src/calls/fish-audio-client.ts +3 -0
  259. package/src/calls/gateway-invite-reader.ts +98 -0
  260. package/src/calls/guardian-wait-controller.ts +451 -0
  261. package/src/calls/media-stream-audio-transcode.ts +82 -2
  262. package/src/calls/media-stream-output.ts +142 -36
  263. package/src/calls/media-stream-server.ts +297 -178
  264. package/src/calls/media-stream-stt-session.ts +393 -50
  265. package/src/calls/media-turn-detector.ts +4 -6
  266. package/src/calls/relay-access-wait.ts +3 -4
  267. package/src/calls/relay-setup-router.ts +22 -43
  268. package/src/calls/relay-verification.ts +17 -24
  269. package/src/calls/resolve-call-tts-provider.ts +96 -29
  270. package/src/calls/stt-hints.ts +24 -87
  271. package/src/calls/telephony-credential-preflight.ts +159 -0
  272. package/src/calls/telephony-tts-capability.ts +183 -0
  273. package/src/calls/tts-call-strategy.ts +18 -20
  274. package/src/calls/twilio-provider.ts +3 -2
  275. package/src/calls/twilio-routes.ts +47 -351
  276. package/src/calls/types.ts +5 -3
  277. package/src/calls/voice-ingress-preflight.ts +9 -0
  278. package/src/calls/voice-quality.ts +13 -18
  279. package/src/calls/voice-session-bridge.ts +25 -35
  280. package/src/channels/__tests__/types.test.ts +28 -0
  281. package/src/channels/channel-verification-sessions.ts +7 -2
  282. package/src/channels/config.ts +0 -42
  283. package/src/channels/slack-thread-store.ts +12 -0
  284. package/src/channels/types.ts +23 -0
  285. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
  286. package/src/cli/commands/__tests__/schedules.test.ts +83 -34
  287. package/src/cli/commands/__tests__/status.test.ts +42 -2
  288. package/src/cli/commands/contacts.ts +2 -2
  289. package/src/cli/commands/credentials.ts +17 -0
  290. package/src/cli/commands/monitoring.ts +191 -0
  291. package/src/cli/commands/schedules-worker.ts +175 -0
  292. package/src/cli/commands/schedules.ts +169 -43
  293. package/src/cli/commands/status.ts +12 -1
  294. package/src/cli/commands/tools.ts +5 -1
  295. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
  296. package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
  297. package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
  298. package/src/cli/lib/search-plugins.ts +6 -0
  299. package/src/cli/lib/toggle-plugin.ts +21 -2
  300. package/src/cli/program.ts +3 -1
  301. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
  302. package/src/config/acp-defaults.test.ts +0 -10
  303. package/src/config/acp-defaults.ts +0 -6
  304. package/src/config/bundled-skills/acp/SKILL.md +13 -45
  305. package/src/config/bundled-skills/acp/TOOLS.json +3 -3
  306. package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
  307. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
  308. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
  309. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
  310. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  311. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
  312. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
  313. package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
  314. package/src/config/call-site-defaults.ts +0 -2
  315. package/src/config/feature-flag-registry.json +16 -8
  316. package/src/config/loader.ts +11 -0
  317. package/src/config/schema.ts +9 -1
  318. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
  319. package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
  320. package/src/config/schemas/call-site-catalog.ts +0 -12
  321. package/src/config/schemas/calls.ts +8 -0
  322. package/src/config/schemas/elevenlabs.ts +1 -1
  323. package/src/config/schemas/llm.ts +0 -2
  324. package/src/config/schemas/memory-v2.ts +1 -1
  325. package/src/config/schemas/memory-v3.ts +82 -0
  326. package/src/config/schemas/monitoring.ts +61 -0
  327. package/src/config/schemas/schedules.ts +35 -0
  328. package/src/config/seed-inference-profiles.ts +2 -2
  329. package/src/config/skills-plugin-filter.test.ts +96 -0
  330. package/src/config/skills.ts +26 -0
  331. package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
  332. package/src/contacts/canonical-guardian-store.ts +5 -0
  333. package/src/contacts/contact-store.ts +81 -35
  334. package/src/contacts/contacts-write.ts +50 -25
  335. package/src/contacts/member-write-relay.ts +115 -31
  336. package/src/contacts/types.ts +0 -8
  337. package/src/context/compactor.ts +29 -15
  338. package/src/context/outbound-sanitize.ts +202 -0
  339. package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
  340. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
  341. package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
  342. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
  343. package/src/daemon/abort-watchdog.ts +7 -0
  344. package/src/daemon/app-source-watcher.ts +9 -0
  345. package/src/daemon/assistant-attachments.ts +42 -0
  346. package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
  347. package/src/daemon/conversation-agent-loop.ts +61 -68
  348. package/src/daemon/conversation-error.ts +1 -15
  349. package/src/daemon/conversation-history.ts +32 -236
  350. package/src/daemon/conversation-initial-prompt.ts +77 -0
  351. package/src/daemon/conversation-lifecycle.ts +25 -1
  352. package/src/daemon/conversation-messaging.ts +2 -2
  353. package/src/daemon/conversation-plugin-scope.ts +38 -0
  354. package/src/daemon/conversation-process.ts +50 -14
  355. package/src/daemon/conversation-runtime-assembly.ts +36 -6
  356. package/src/daemon/conversation-skill-tools.ts +19 -3
  357. package/src/daemon/conversation-store.ts +3 -3
  358. package/src/daemon/conversation-surfaces.ts +171 -0
  359. package/src/daemon/conversation-tool-setup.test.ts +95 -0
  360. package/src/daemon/conversation-tool-setup.ts +116 -1
  361. package/src/daemon/conversation-turn-finalize.ts +211 -0
  362. package/src/daemon/conversation.ts +92 -1
  363. package/src/daemon/disk-pressure-policy.ts +2 -1
  364. package/src/daemon/event-loop-watchdog.ts +22 -6
  365. package/src/daemon/external-plugins-bootstrap.ts +47 -134
  366. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  367. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
  368. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
  369. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
  370. package/src/daemon/handlers/config-a2a.ts +7 -14
  371. package/src/daemon/handlers/config-channels.ts +2 -10
  372. package/src/daemon/handlers/conversation-history.ts +3 -1
  373. package/src/daemon/handlers/conversations.ts +0 -52
  374. package/src/daemon/handlers/shared.ts +4 -24
  375. package/src/daemon/handlers/skills.ts +4 -1
  376. package/src/daemon/host-cu-proxy.ts +1 -1
  377. package/src/daemon/lifecycle.ts +59 -472
  378. package/src/daemon/message-protocol.ts +3 -3
  379. package/src/daemon/message-provenance.ts +12 -14
  380. package/src/daemon/message-types/background-tools.ts +28 -0
  381. package/src/daemon/message-types/contacts.ts +2 -10
  382. package/src/daemon/message-types/conversations.ts +10 -0
  383. package/src/daemon/message-types/integrations.ts +2 -0
  384. package/src/daemon/message-types/surfaces.ts +1 -0
  385. package/src/daemon/message-types/sync.ts +1 -0
  386. package/src/daemon/pointer-conversation-trust.ts +23 -9
  387. package/src/daemon/pointer-turn-runner.ts +174 -0
  388. package/src/daemon/providers-setup.ts +16 -0
  389. package/src/daemon/shutdown-handlers.ts +139 -47
  390. package/src/daemon/skill-memory-refresh.ts +2 -2
  391. package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
  392. package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
  393. package/src/daemon/tool-setup-types.ts +8 -0
  394. package/src/daemon/trust-context.ts +21 -6
  395. package/src/daemon/turn-latency-tracker.test.ts +93 -0
  396. package/src/daemon/turn-latency-tracker.ts +38 -0
  397. package/src/daemon/wake-conversation-ops.ts +10 -8
  398. package/src/documents/document-comments-store.test.ts +7 -1
  399. package/src/documents/document-comments-store.ts +8 -0
  400. package/src/documents/document-store.ts +15 -0
  401. package/src/export/__tests__/transcript-formatter.test.ts +11 -0
  402. package/src/export/transcript-formatter.ts +49 -67
  403. package/src/home/__tests__/feed-writer.test.ts +247 -1
  404. package/src/home/conversation-starter-checkpoints.ts +1 -0
  405. package/src/home/feed-writer.ts +61 -0
  406. package/src/home/job-handlers/conversation-starters.ts +1 -0
  407. package/src/hooks/hook-loader.ts +12 -0
  408. package/src/hooks/registry.ts +32 -4
  409. package/src/inbound/public-ingress-urls.ts +0 -17
  410. package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
  411. package/src/ipc/assistant-server.ts +28 -7
  412. package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
  413. package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
  414. package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
  415. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
  416. package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
  417. package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
  418. package/src/ipc/routes/db-proxy.ts +5 -2
  419. package/src/ipc/routes/invite-ipc-routes.ts +53 -39
  420. package/src/jobs/worker.ts +9 -2
  421. package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
  422. package/src/live-voice/live-voice-archive.ts +5 -0
  423. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
  424. package/src/messaging/providers/channel-transport.ts +6 -0
  425. package/src/messaging/providers/index.ts +3 -0
  426. package/src/messaging/providers/slack/api.test.ts +49 -1
  427. package/src/messaging/providers/slack/api.ts +110 -0
  428. package/src/messaging/providers/slack/send.test.ts +85 -0
  429. package/src/messaging/providers/slack/send.ts +100 -28
  430. package/src/messaging/providers/slack/transport.ts +7 -13
  431. package/src/messaging/providers/whatsapp/send.ts +29 -9
  432. package/src/monitoring/__tests__/control.test.ts +235 -0
  433. package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
  434. package/src/monitoring/control.ts +105 -0
  435. package/src/monitoring/resource-sampler.ts +258 -0
  436. package/src/monitoring/sample-ring-buffer.ts +108 -0
  437. package/src/monitoring/worker.ts +94 -0
  438. package/src/notifications/README.md +1 -1
  439. package/src/notifications/access-request-copy.ts +94 -23
  440. package/src/notifications/adapters/slack.ts +89 -21
  441. package/src/notifications/approval-card-builder.ts +33 -18
  442. package/src/notifications/approval-card-data.ts +48 -15
  443. package/src/notifications/broadcaster.ts +25 -16
  444. package/src/notifications/decision-engine.ts +131 -32
  445. package/src/notifications/guardian-question-mode.ts +58 -10
  446. package/src/permissions/approval-policy.ts +8 -1
  447. package/src/permissions/checker.ts +2 -0
  448. package/src/permissions/gateway-threshold-reader.ts +7 -6
  449. package/src/permissions/ipc-risk-types.ts +1 -0
  450. package/src/permissions/secret-prompter.ts +11 -3
  451. package/src/permissions/types.ts +2 -2
  452. package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
  453. package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
  454. package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
  455. package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
  456. package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
  457. package/src/persistence/a2a-invite-store.ts +205 -0
  458. package/src/persistence/attachments-store.ts +94 -10
  459. package/src/persistence/checkpoints.ts +45 -0
  460. package/src/persistence/conversation-crud.ts +453 -260
  461. package/src/persistence/conversation-display-order-migration.ts +8 -2
  462. package/src/persistence/conversation-group-migration.test.ts +8 -0
  463. package/src/persistence/conversation-group-migration.ts +9 -0
  464. package/src/persistence/conversation-queries.ts +244 -125
  465. package/src/persistence/conversation-row-batch-delete.ts +9 -5
  466. package/src/persistence/conversation-search-lexical.ts +49 -0
  467. package/src/persistence/db-async-query.ts +42 -0
  468. package/src/persistence/db-connection.ts +13 -0
  469. package/src/persistence/delivery-channels.ts +0 -82
  470. package/src/persistence/delivery-crud.ts +27 -6
  471. package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
  472. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
  473. package/src/persistence/embeddings/embedding-identity.ts +17 -5
  474. package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
  475. package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
  476. package/src/persistence/embeddings/qdrant-client.ts +81 -5
  477. package/src/persistence/group-crud.ts +13 -1
  478. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
  479. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
  480. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
  481. package/src/persistence/job-handlers/cleanup.ts +57 -9
  482. package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
  483. package/src/persistence/job-handlers/message-lexical.ts +251 -0
  484. package/src/persistence/jobs-store.ts +102 -8
  485. package/src/persistence/jobs-worker.ts +68 -28
  486. package/src/persistence/llm-request-log-store.ts +35 -19
  487. package/src/persistence/llm-usage-store.ts +11 -0
  488. package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
  489. package/src/persistence/memory-lifecycle-hooks.ts +55 -1
  490. package/src/persistence/migrations/028-call-session-mode.ts +1 -1
  491. package/src/persistence/migrations/116-messages-fts.ts +8 -1
  492. package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
  493. package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
  494. package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
  495. package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
  496. package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
  497. package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
  498. package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
  499. package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
  500. package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
  501. package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
  502. package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
  503. package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
  504. package/src/persistence/raw-query.ts +50 -11
  505. package/src/persistence/schema/a2a.ts +14 -0
  506. package/src/persistence/schema/contacts.ts +0 -62
  507. package/src/persistence/schema/conversation-groups.ts +12 -0
  508. package/src/persistence/schema/conversations.ts +2 -0
  509. package/src/persistence/schema/documents.ts +65 -0
  510. package/src/persistence/schema/guardian.ts +4 -0
  511. package/src/persistence/schema/index.ts +4 -0
  512. package/src/persistence/schema/memory-injection.ts +62 -0
  513. package/src/persistence/schema/workflows.ts +55 -0
  514. package/src/persistence/slow-query-log.ts +437 -0
  515. package/src/persistence/slow-sync-log.ts +204 -0
  516. package/src/persistence/steps.ts +16 -0
  517. package/src/persistence/subagent-store.ts +125 -0
  518. package/src/persistence/worker-control.ts +45 -238
  519. package/src/plugin-api/event-hub-facade.ts +172 -0
  520. package/src/plugin-api/index.ts +9 -1
  521. package/src/plugin-api/types.ts +40 -9
  522. package/src/plugins/defaults/default-plugin-names.ts +60 -0
  523. package/src/plugins/defaults/index.ts +19 -29
  524. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
  525. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
  526. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
  527. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
  528. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
  529. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
  530. package/src/plugins/defaults/memory/config.ts +11 -0
  531. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
  532. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
  533. package/src/plugins/defaults/memory/embeddings.ts +77 -0
  534. package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
  535. package/src/plugins/defaults/memory/frontmatter.ts +50 -0
  536. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
  537. package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
  538. package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
  539. package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
  540. package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
  541. package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
  542. package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
  543. package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
  544. package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
  545. package/src/plugins/defaults/memory/graph/tools.ts +1 -1
  546. package/src/plugins/defaults/memory/hooks/init.ts +31 -5
  547. package/src/plugins/defaults/memory/indexer.ts +2 -3
  548. package/src/plugins/defaults/memory/injectors.ts +2 -2
  549. package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
  550. package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
  551. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
  552. package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
  553. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
  554. package/src/plugins/defaults/memory/job-handlers.ts +13 -12
  555. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
  556. package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
  557. package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
  558. package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
  559. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
  560. package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
  561. package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
  562. package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
  563. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
  564. package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
  565. package/src/plugins/defaults/memory/search/semantic.ts +2 -2
  566. package/src/plugins/defaults/memory/startup.ts +244 -0
  567. package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
  568. package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
  569. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
  570. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
  571. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
  572. package/src/plugins/defaults/memory/v2/activation.ts +2 -4
  573. package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
  574. package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
  575. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
  576. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
  577. package/src/plugins/defaults/memory/v2/migration.ts +2 -5
  578. package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
  579. package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
  580. package/src/plugins/defaults/memory/v2/router.ts +5 -5
  581. package/src/plugins/defaults/memory/v2/sim.ts +2 -4
  582. package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
  583. package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
  584. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
  585. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
  586. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
  587. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
  588. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
  589. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
  590. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
  591. package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
  592. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
  593. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
  594. package/src/plugins/defaults/memory/v3/card.ts +1 -4
  595. package/src/plugins/defaults/memory/v3/dense.ts +60 -25
  596. package/src/plugins/defaults/memory/v3/edge.ts +1 -1
  597. package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
  598. package/src/plugins/defaults/memory/v3/gate.ts +158 -0
  599. package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
  600. package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
  601. package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
  602. package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
  603. package/src/plugins/defaults/memory/v3/prune.ts +2 -2
  604. package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
  605. package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
  606. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
  607. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
  608. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
  609. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  610. package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
  611. package/src/plugins/injector-registry.ts +13 -2
  612. package/src/plugins/mtime-cache.ts +36 -47
  613. package/src/plugins/pipeline.ts +15 -1
  614. package/src/plugins/types.ts +14 -23
  615. package/src/prompts/__tests__/system-prompt.test.ts +170 -32
  616. package/src/prompts/persona-resolver.ts +13 -9
  617. package/src/prompts/system-prompt.ts +30 -9
  618. package/src/prompts/templates/BOOTSTRAP.md +16 -119
  619. package/src/prompts/templates/SOUL.md +43 -13
  620. package/src/prompts/templates/system-sections.ts +32 -27
  621. package/src/prompts/templates/users/default.md +19 -0
  622. package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
  623. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
  624. package/src/providers/model-intents.ts +2 -2
  625. package/src/providers/openai/chat-completions-provider.ts +14 -1
  626. package/src/providers/openai/responses-provider.ts +18 -0
  627. package/src/providers/placeholder-sentinels.ts +18 -8
  628. package/src/providers/retry.ts +1 -0
  629. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
  630. package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
  631. package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
  632. package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
  633. package/src/providers/speech-to-text/provider-catalog.ts +0 -89
  634. package/src/providers/speech-to-text/resolve.ts +55 -0
  635. package/src/providers/tool-progress-events.test.ts +50 -0
  636. package/src/providers/tool-progress-events.ts +99 -0
  637. package/src/providers/types.ts +1 -1
  638. package/src/providers/vellum-model-routing.test.ts +51 -0
  639. package/src/providers/vellum-model-routing.ts +84 -0
  640. package/src/runtime/AGENTS.md +5 -4
  641. package/src/runtime/__tests__/agent-wake.test.ts +158 -1
  642. package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
  643. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
  644. package/src/runtime/access-request-helper.ts +154 -3
  645. package/src/runtime/actor-trust-resolver.ts +7 -21
  646. package/src/runtime/agent-wake.ts +41 -1
  647. package/src/runtime/assistant-event.ts +86 -12
  648. package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
  649. package/src/runtime/channel-approval-types.ts +52 -2
  650. package/src/runtime/channel-approvals.ts +23 -8
  651. package/src/runtime/channel-invite-transport.ts +2 -2
  652. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  653. package/src/runtime/channel-readiness-service.ts +3 -5
  654. package/src/runtime/channel-retry-sweep.ts +18 -24
  655. package/src/runtime/channel-verification-service.ts +9 -11
  656. package/src/runtime/finalize-event-delivery.ts +30 -16
  657. package/src/runtime/guardian-action-service.ts +10 -11
  658. package/src/runtime/guardian-reply-router.ts +122 -21
  659. package/src/runtime/http-server.ts +44 -101
  660. package/src/runtime/introduction-policy.test.ts +214 -0
  661. package/src/runtime/introduction-policy.ts +284 -0
  662. package/src/runtime/invite-service.ts +104 -361
  663. package/src/runtime/local-actor-identity.test.ts +38 -0
  664. package/src/runtime/local-actor-identity.ts +14 -0
  665. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
  666. package/src/runtime/middleware/rate-limiter.ts +30 -0
  667. package/src/runtime/middleware/twilio-validation.ts +5 -7
  668. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
  669. package/src/runtime/migrations/origin-mode.ts +6 -6
  670. package/src/runtime/pre-first-message-gate.ts +1 -0
  671. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
  672. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
  673. package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
  674. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
  675. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
  676. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
  677. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
  678. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
  679. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
  680. package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
  681. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
  682. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
  683. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
  684. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  686. package/src/runtime/routes/acp-routes.ts +15 -3
  687. package/src/runtime/routes/app-management-routes.ts +4 -7
  688. package/src/runtime/routes/approval-routes.ts +4 -0
  689. package/src/runtime/routes/attachment-routes.ts +54 -3
  690. package/src/runtime/routes/avatar-routes.ts +0 -4
  691. package/src/runtime/routes/background-tool-routes.ts +34 -5
  692. package/src/runtime/routes/btw-routes.ts +32 -4
  693. package/src/runtime/routes/channel-route-shared.ts +18 -14
  694. package/src/runtime/routes/channel-verification-routes.ts +0 -3
  695. package/src/runtime/routes/contact-routes.ts +298 -117
  696. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  697. package/src/runtime/routes/conversation-management-routes.ts +74 -37
  698. package/src/runtime/routes/conversation-query-routes.ts +5 -2
  699. package/src/runtime/routes/conversation-routes.ts +222 -82
  700. package/src/runtime/routes/conversations-import-routes.ts +8 -1
  701. package/src/runtime/routes/credential-prompt-routes.ts +20 -5
  702. package/src/runtime/routes/debug-routes.ts +1 -0
  703. package/src/runtime/routes/documents-routes.ts +5 -2
  704. package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
  705. package/src/runtime/routes/global-search-routes.ts +2 -1
  706. package/src/runtime/routes/guardian-approval-interception.ts +51 -0
  707. package/src/runtime/routes/home-feed-routes.ts +60 -1
  708. package/src/runtime/routes/identity-routes.ts +4 -87
  709. package/src/runtime/routes/inbound-message-handler.ts +82 -41
  710. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
  711. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
  712. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
  713. package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
  714. package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
  715. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
  716. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
  717. package/src/runtime/routes/index.ts +6 -0
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
  719. package/src/runtime/routes/internal-twilio-routes.ts +0 -15
  720. package/src/runtime/routes/messages-lexical-routes.ts +75 -0
  721. package/src/runtime/routes/migration-routes.ts +69 -6
  722. package/src/runtime/routes/monitoring-routes.ts +237 -0
  723. package/src/runtime/routes/plugins-routes.ts +329 -8
  724. package/src/runtime/routes/schedule-routes.ts +49 -8
  725. package/src/runtime/routes/schedule-worker-routes.ts +210 -0
  726. package/src/runtime/routes/secret-routes.ts +1 -1
  727. package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
  728. package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
  729. package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
  730. package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
  731. package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
  732. package/src/runtime/services/analyze-conversation.ts +14 -4
  733. package/src/runtime/services/conversation-serializer.ts +5 -0
  734. package/src/runtime/slack-no-response.ts +47 -0
  735. package/src/runtime/slack-reply-session.test.ts +633 -0
  736. package/src/runtime/slack-reply-session.ts +382 -0
  737. package/src/runtime/slack-task-progress.ts +95 -0
  738. package/src/runtime/sync/resource-sync-events.ts +25 -0
  739. package/src/runtime/tool-grant-request-helper.ts +17 -1
  740. package/src/runtime/trust-class.test.ts +141 -0
  741. package/src/runtime/trust-class.ts +121 -0
  742. package/src/runtime/trust-verdict-consumer.ts +7 -6
  743. package/src/schedule/__tests__/run-script.test.ts +21 -0
  744. package/src/schedule/__tests__/worker-control.test.ts +241 -0
  745. package/src/schedule/run-script.ts +13 -6
  746. package/src/schedule/schedule-usage-store.ts +1 -0
  747. package/src/schedule/scheduler.ts +178 -127
  748. package/src/schedule/worker-control.ts +118 -0
  749. package/src/schedule/worker.ts +131 -0
  750. package/src/skills/__tests__/categories-cache.test.ts +38 -0
  751. package/src/skills/categories-cache.ts +40 -5
  752. package/src/skills/managed-store.ts +41 -6
  753. package/src/skills/validate-input.ts +47 -0
  754. package/src/stt/types.ts +5 -9
  755. package/src/subagent/manager.ts +267 -39
  756. package/src/subagent/types.ts +6 -2
  757. package/src/subagent/validate-allowlists.ts +65 -0
  758. package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
  759. package/src/telemetry/watchdog-direct-emit.ts +89 -0
  760. package/src/tools/acp/list-agents.test.ts +0 -6
  761. package/src/tools/background-tool-registry.ts +61 -0
  762. package/src/tools/document/document-comment-tool.test.ts +4 -0
  763. package/src/tools/host-terminal/host-shell.test.ts +397 -0
  764. package/src/tools/host-terminal/host-shell.ts +203 -14
  765. package/src/tools/skills/find-similar-skills.test.ts +152 -1
  766. package/src/tools/skills/find-similar-skills.ts +30 -4
  767. package/src/tools/skills/load.ts +36 -0
  768. package/src/tools/skills/scaffold-managed.ts +66 -34
  769. package/src/tools/skills/skill-script-runner.ts +17 -0
  770. package/src/tools/skills/skill-tool-factory.ts +5 -1
  771. package/src/tools/terminal/safe-env.ts +3 -0
  772. package/src/tools/terminal/shell.test.ts +177 -0
  773. package/src/tools/terminal/shell.ts +98 -4
  774. package/src/tools/tool-approval-handler.ts +106 -29
  775. package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
  776. package/src/tools/types.ts +29 -17
  777. package/src/tools/ui-surface/definitions.ts +3 -1
  778. package/src/tts/__tests__/provider-adapters.test.ts +37 -0
  779. package/src/tts/__tests__/provider-catalog.test.ts +21 -0
  780. package/src/tts/provider-catalog.ts +40 -9
  781. package/src/tts/providers/fish-audio-provider.ts +19 -10
  782. package/src/tts/types.ts +3 -4
  783. package/src/util/__tests__/process-tree.test.ts +10 -2
  784. package/src/util/__tests__/text-spacing.test.ts +53 -0
  785. package/src/util/cgroup-memory.ts +142 -0
  786. package/src/util/image-conversion.ts +281 -0
  787. package/src/util/phone.ts +1 -8
  788. package/src/util/platform.ts +33 -0
  789. package/src/util/process-tree.ts +10 -2
  790. package/src/util/text-spacing.ts +53 -0
  791. package/src/util/worker-process.ts +271 -0
  792. package/src/workflows/journal-store.ts +16 -1
  793. package/src/workflows/library.test.ts +35 -20
  794. package/src/workflows/library.ts +105 -66
  795. package/src/workspace/git-service.ts +1 -0
  796. package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
  797. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
  798. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
  799. package/src/workspace/migrations/118-seed-now-md.ts +64 -0
  800. package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
  801. package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
  802. package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
  803. package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
  804. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
  805. package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
  806. package/src/workspace/migrations/registry.ts +12 -0
  807. package/drizzle/0000_dizzy_maggott.sql +0 -301
  808. package/drizzle/meta/0000_snapshot.json +0 -1933
  809. package/drizzle/meta/_journal.json +0 -13
  810. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
  811. package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
  812. package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
  813. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
  814. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
  815. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
  816. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
  817. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
  818. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
  819. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
  820. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
  821. package/src/__tests__/invite-redemption-service.test.ts +0 -1180
  822. package/src/__tests__/invite-service-ipc.test.ts +0 -350
  823. package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
  824. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
  825. package/src/__tests__/relay-server.test.ts +0 -6153
  826. package/src/__tests__/telephony-stt-routing.test.ts +0 -329
  827. package/src/__tests__/voice-invite-redemption.test.ts +0 -702
  828. package/src/calls/relay-server.ts +0 -2463
  829. package/src/calls/telephony-stt-routing.ts +0 -145
  830. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
  831. package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
  832. package/src/daemon/daemon-skill-host.ts +0 -262
  833. package/src/daemon/message-types/meet.ts +0 -143
  834. package/src/daemon/shutdown-registry.ts +0 -40
  835. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  836. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  837. package/src/ipc/skill-ipc-types.ts +0 -54
  838. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  839. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  840. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  841. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  842. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  843. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  844. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  845. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  846. package/src/ipc/skill-routes/config.ts +0 -47
  847. package/src/ipc/skill-routes/events.ts +0 -147
  848. package/src/ipc/skill-routes/identity.ts +0 -21
  849. package/src/ipc/skill-routes/index.ts +0 -37
  850. package/src/ipc/skill-routes/log.ts +0 -40
  851. package/src/ipc/skill-routes/memory.ts +0 -92
  852. package/src/ipc/skill-routes/platform.ts +0 -39
  853. package/src/ipc/skill-routes/providers.ts +0 -162
  854. package/src/ipc/skill-routes/registries.ts +0 -338
  855. package/src/ipc/skill-server.ts +0 -831
  856. package/src/ipc/skill-socket-path.ts +0 -20
  857. package/src/jobs/register-job-handlers.ts +0 -74
  858. package/src/persistence/invite-store.ts +0 -481
  859. package/src/plugins/job-handler-registry.ts +0 -94
  860. package/src/providers/cache-control.ts +0 -26
  861. package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
  862. package/src/runtime/invite-redemption-service.ts +0 -828
  863. package/src/runtime/invite-redemption-templates.ts +0 -46
  864. package/src/runtime/runtime-mode.ts +0 -40
  865. package/src/runtime/slack-dm-text-delivery.ts +0 -177
  866. package/src/util/voice-code.ts +0 -31
@@ -72,8 +72,10 @@ function resolveIngressBaseUrlFromConfig(ingressConfig: unknown): string {
72
72
  );
73
73
  }
74
74
 
75
- // Default routeSetup mock — returns normal_call. Tests that need different
76
- // outcomes override `mockRouteSetupResult` before calling the handler.
75
+ // routeSetup mock — TwiML generation must never invoke it (setup routing
76
+ // is owned by the media-stream server at stream start). Tests set
77
+ // `mockRouteSetupResult` to interactive outcomes and assert the webhook
78
+ // still returns Stream TwiML without consulting the router.
77
79
  let mockRouteSetupResult: {
78
80
  outcome: { action: string; [key: string]: unknown };
79
81
  resolved: {
@@ -102,16 +104,18 @@ mock.module("../calls/relay-setup-router.js", () => ({
102
104
  },
103
105
  }));
104
106
 
105
- // Mock the phone channel admission reader used by the media-stream preflight.
106
- // Tests override mockAdmissionPolicy to exercise floor classification.
107
+ // Mock the phone channel admission reader and inbound trust reader.
108
+ // TwiML generation must not consult either — both are stream-start
109
+ // concerns owned by the media-stream server.
107
110
  let mockAdmissionPolicy: unknown = null;
111
+ let admissionPolicyReads = 0;
108
112
  mock.module("../calls/channel-admission-reader.js", () => ({
109
- getChannelAdmissionPolicy: async () => mockAdmissionPolicy,
113
+ getChannelAdmissionPolicy: async () => {
114
+ admissionPolicyReads++;
115
+ return mockAdmissionPolicy;
116
+ },
110
117
  }));
111
118
 
112
- // Mock the inbound trust reader used by the media-stream preflight. Captures
113
- // its args so tests can assert the inbound caller's verdict is fetched, and
114
- // returns mockInboundVerdict which is threaded into the preflight routeSetup.
115
119
  let mockInboundVerdict: unknown = null;
116
120
  let lastInboundVerdictArgs: Record<string, unknown> | null = null;
117
121
  mock.module("../calls/inbound-trust-reader.js", () => ({
@@ -128,6 +132,13 @@ mock.module("../calls/inbound-trust-reader.js", () => ({
128
132
  },
129
133
  }));
130
134
 
135
+ // Mock the STT+TTS credential-readiness preflight consulted at TwiML time.
136
+ // Tests override `mockCredentialReadiness` to exercise the not-ready gate.
137
+ let mockCredentialReadiness: Record<string, unknown> = { status: "ready" };
138
+ mock.module("../calls/telephony-credential-preflight.js", () => ({
139
+ resolveTelephonyCredentialReadiness: async () => mockCredentialReadiness,
140
+ }));
141
+
131
142
  mock.module("../config/env.js", () => ({
132
143
  isHttpAuthDisabled: () => true,
133
144
  getGatewayInternalBaseUrl: () => "http://gateway.internal:7830",
@@ -219,7 +230,7 @@ mock.module("../security/secure-keys.js", () => ({
219
230
  }));
220
231
 
221
232
  mock.module("../calls/twilio-provider.js", () => ({
222
- TwilioConversationRelayProvider: class {
233
+ TwilioVoiceProvider: class {
223
234
  readonly name = "twilio";
224
235
  static getAuthToken(): string | null {
225
236
  return null;
@@ -319,11 +330,6 @@ mock.module("../inbound/platform-callback-registration.js", () => ({
319
330
  }));
320
331
 
321
332
  mock.module("../inbound/public-ingress-urls.js", () => ({
322
- getTwilioRelayUrl: (ingressConfig: unknown) => {
323
- const base = resolveIngressBaseUrlFromConfig(ingressConfig);
324
- const wsBase = base.replace(/^http(s?)/, "ws$1");
325
- return `${wsBase}/webhooks/twilio/relay`;
326
- },
327
333
  getTwilioMediaStreamUrl: (ingressConfig: unknown) => {
328
334
  const base = resolveIngressBaseUrlFromConfig(ingressConfig);
329
335
  const wsBase = base.replace(/^http(s?)/, "ws$1");
@@ -364,7 +370,7 @@ import {
364
370
  updateCallSession,
365
371
  } from "../calls/call-store.js";
366
372
  import {
367
- buildWelcomeGreeting,
373
+ handleInternalVoiceWebhook,
368
374
  handleStatusCallback,
369
375
  handleVoiceWebhook,
370
376
  } from "../calls/twilio-routes.js";
@@ -488,9 +494,12 @@ describe("twilio webhook routes", () => {
488
494
  mockConfigObj.services.stt.provider = "deepgram" as any;
489
495
  // Reset admission policy + captured routeSetup context between tests
490
496
  mockAdmissionPolicy = null;
497
+ admissionPolicyReads = 0;
491
498
  lastRouteSetupCtx = null;
492
499
  mockInboundVerdict = null;
493
500
  lastInboundVerdictArgs = null;
501
+ // Reset credential readiness to ready
502
+ mockCredentialReadiness = { status: "ready" };
494
503
  // Reset routeSetup mock to default normal_call
495
504
  mockRouteSetupResult = {
496
505
  outcome: { action: "normal_call", isInbound: true },
@@ -830,26 +839,11 @@ describe("twilio webhook routes", () => {
830
839
  });
831
840
  });
832
841
 
833
- describe("buildWelcomeGreeting", () => {
834
- test("returns empty by default so orchestrator drives first opener", () => {
835
- const greeting = buildWelcomeGreeting("check store hours for tomorrow");
836
- expect(greeting).toBe("");
837
- });
838
-
839
- test("uses configured greeting override when provided", () => {
840
- const greeting = buildWelcomeGreeting(
841
- "check store hours",
842
- "Custom hello",
843
- );
844
- expect(greeting).toBe("Custom hello");
845
- });
846
- });
847
-
848
- // ── TwiML relay URL generation ──────────────────────────────────────
842
+ // ── TwiML stream URL generation ─────────────────────────────────────
849
843
  // Call handleVoiceWebhook directly since direct routes are blocked.
850
844
 
851
- describe("voice webhook TwiML relay URL", () => {
852
- test("TwiML relay URL uses placeholder for gateway resolution", async () => {
845
+ describe("voice webhook TwiML stream URL", () => {
846
+ test("TwiML stream URL uses placeholder for gateway resolution", async () => {
853
847
  const session = createTestSession("conv-twiml-1", "CA_twiml_1");
854
848
  const req = makeVoiceRequest(session.id, { CallSid: "CA_twiml_1" });
855
849
 
@@ -858,7 +852,7 @@ describe("twilio webhook routes", () => {
858
852
  expect(res.status).toBe(200);
859
853
  const twiml = await res.text();
860
854
  expect(twiml).toContain(
861
- "wss://__VELLUM_PUBLIC_BASE_URL__/webhooks/twilio/relay",
855
+ `wss://__VELLUM_PUBLIC_BASE_URL__/webhooks/twilio/media-stream/${session.id}`,
862
856
  );
863
857
  });
864
858
 
@@ -1031,8 +1025,8 @@ describe("twilio webhook routes", () => {
1031
1025
 
1032
1026
  expect(res.status).toBe(200);
1033
1027
  const twiml = await res.text();
1034
- expect(twiml).toContain("<ConversationRelay");
1035
- expect(twiml).toContain("callSessionId=");
1028
+ expect(twiml).toContain("<Stream");
1029
+ expect(twiml).toContain('<Parameter name="callSessionId"');
1036
1030
 
1037
1031
  // Verify session was created with the CallSid
1038
1032
  const session = getCallSessionByCallSid("CA_inbound_new_1");
@@ -1104,106 +1098,71 @@ describe("twilio webhook routes", () => {
1104
1098
 
1105
1099
  expect(res.status).toBe(200);
1106
1100
  const twiml = await res.text();
1107
- expect(twiml).toContain("<ConversationRelay");
1108
- expect(twiml).toContain(`callSessionId=${session.id}`);
1101
+ expect(twiml).toContain("<Stream");
1102
+ expect(twiml).toContain(`/webhooks/twilio/media-stream/${session.id}`);
1109
1103
  });
1110
1104
  });
1111
1105
 
1112
- // ── services.stt-driven TwiML routing ───────────────────────────────
1113
- // These tests assert that the voice webhook TwiML path is determined
1114
- // by services.stt.provider via resolveTelephonySttRouting the
1115
- // canonical telephony STT routing resolver.
1116
-
1117
- describe("services.stt-driven TwiML routing", () => {
1118
- test("outbound: deepgram -> ConversationRelay with transcriptionProvider=Deepgram", async () => {
1119
- mockConfigObj.services.stt.provider = "deepgram" as any;
1120
- const session = createTestSession("conv-stt-dg-1", "CA_stt_dg_1");
1121
- const req = makeVoiceRequest(session.id, { CallSid: "CA_stt_dg_1" });
1122
-
1123
- const res = await handleVoiceWebhook(req);
1124
- expect(res.status).toBe(200);
1125
-
1126
- const twiml = await res.text();
1127
- expect(twiml).toContain("<ConversationRelay");
1128
- expect(twiml).not.toContain("<Stream");
1129
- expect(twiml).toContain('transcriptionProvider="Deepgram"');
1130
- });
1106
+ // ── Provider-independent Stream TwiML ───────────────────────────────
1107
+ // Every call runs over the media-stream transport regardless of the
1108
+ // configured STT provider the webhook always emits <Connect><Stream>.
1109
+
1110
+ describe("Stream TwiML for every STT provider", () => {
1111
+ const providers = [
1112
+ "deepgram",
1113
+ "google-gemini",
1114
+ "openai-whisper",
1115
+ "xai",
1116
+ ] as const;
1117
+
1118
+ for (const provider of providers) {
1119
+ test(`outbound: ${provider} -> Stream TwiML`, async () => {
1120
+ mockConfigObj.services.stt.provider = provider as any;
1121
+ const session = createTestSession(
1122
+ `conv-stt-${provider}-1`,
1123
+ `CA_stt_${provider}_1`,
1124
+ );
1125
+ const req = makeVoiceRequest(session.id, {
1126
+ CallSid: `CA_stt_${provider}_1`,
1127
+ });
1131
1128
 
1132
- test("inbound: deepgram -> ConversationRelay with transcriptionProvider=Deepgram", async () => {
1133
- mockConfigObj.services.stt.provider = "deepgram" as any;
1134
- const req = makeInboundVoiceRequest({
1135
- CallSid: "CA_stt_dg_inbound_1",
1136
- From: "+14155551234",
1137
- To: "+15550001111",
1129
+ const res = await handleVoiceWebhook(req);
1130
+ expect(res.status).toBe(200);
1131
+
1132
+ const twiml = await res.text();
1133
+ expect(twiml).toContain("<Stream");
1134
+ expect(twiml).not.toContain("transcriptionProvider=");
1135
+ // callSessionId is in the URL path, not as a query param
1136
+ expect(twiml).toContain(
1137
+ `wss://__VELLUM_PUBLIC_BASE_URL__/webhooks/twilio/media-stream/${session.id}`,
1138
+ );
1139
+ expect(twiml).not.toContain("?callSessionId=");
1138
1140
  });
1139
1141
 
1140
- const res = await handleVoiceWebhook(req);
1141
- expect(res.status).toBe(200);
1142
-
1143
- const twiml = await res.text();
1144
- expect(twiml).toContain("<ConversationRelay");
1145
- expect(twiml).toContain('transcriptionProvider="Deepgram"');
1146
- expect(twiml).not.toContain("<Stream");
1147
- });
1148
-
1149
- test("outbound: google-gemini -> ConversationRelay with transcriptionProvider=Google", async () => {
1150
- mockConfigObj.services.stt.provider = "google-gemini" as any;
1151
- const session = createTestSession("conv-stt-gg-1", "CA_stt_gg_1");
1152
- const req = makeVoiceRequest(session.id, { CallSid: "CA_stt_gg_1" });
1153
-
1154
- const res = await handleVoiceWebhook(req);
1155
- expect(res.status).toBe(200);
1156
-
1157
- const twiml = await res.text();
1158
- expect(twiml).toContain("<ConversationRelay");
1159
- expect(twiml).not.toContain("<Stream");
1160
- expect(twiml).toContain('transcriptionProvider="Google"');
1161
- });
1162
-
1163
- test("outbound: openai-whisper -> Stream TwiML with path-based metadata (no ConversationRelay)", async () => {
1164
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1165
- const session = createTestSession("conv-stt-ow-1", "CA_stt_ow_1");
1166
- const req = makeVoiceRequest(session.id, { CallSid: "CA_stt_ow_1" });
1167
-
1168
- const res = await handleVoiceWebhook(req);
1169
- expect(res.status).toBe(200);
1142
+ test(`inbound: ${provider} -> Stream TwiML`, async () => {
1143
+ mockConfigObj.services.stt.provider = provider as any;
1144
+ const req = makeInboundVoiceRequest({
1145
+ CallSid: `CA_stt_${provider}_inbound_1`,
1146
+ From: "+14155551234",
1147
+ To: "+15550001111",
1148
+ });
1170
1149
 
1171
- const twiml = await res.text();
1172
- expect(twiml).toContain("<Stream");
1173
- expect(twiml).not.toContain("<ConversationRelay");
1174
- expect(twiml).not.toContain("transcriptionProvider=");
1175
- // callSessionId is in the URL path, not as a query param
1176
- expect(twiml).toContain(
1177
- `wss://__VELLUM_PUBLIC_BASE_URL__/webhooks/twilio/media-stream/${session.id}`,
1178
- );
1179
- expect(twiml).not.toContain("?callSessionId=");
1180
- });
1150
+ const res = await handleVoiceWebhook(req);
1151
+ expect(res.status).toBe(200);
1181
1152
 
1182
- test("inbound: openai-whisper -> Stream TwiML (no ConversationRelay)", async () => {
1183
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1184
- const req = makeInboundVoiceRequest({
1185
- CallSid: "CA_stt_ow_inbound_1",
1186
- From: "+14155551234",
1187
- To: "+15550001111",
1153
+ const twiml = await res.text();
1154
+ expect(twiml).toContain("<Stream");
1155
+ expect(twiml).not.toContain("transcriptionProvider=");
1188
1156
  });
1189
-
1190
- const res = await handleVoiceWebhook(req);
1191
- expect(res.status).toBe(200);
1192
-
1193
- const twiml = await res.text();
1194
- expect(twiml).toContain("<Stream");
1195
- expect(twiml).not.toContain("<ConversationRelay");
1196
- expect(twiml).not.toContain("transcriptionProvider=");
1197
- });
1157
+ }
1198
1158
 
1199
1159
  test("Stream TwiML includes auth token as Parameter", async () => {
1200
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1201
1160
  const session = createTestSession(
1202
- "conv-stt-ow-token-1",
1203
- "CA_stt_ow_token_1",
1161
+ "conv-stt-token-1",
1162
+ "CA_stt_token_1",
1204
1163
  );
1205
1164
  const req = makeVoiceRequest(session.id, {
1206
- CallSid: "CA_stt_ow_token_1",
1165
+ CallSid: "CA_stt_token_1",
1207
1166
  });
1208
1167
 
1209
1168
  const res = await handleVoiceWebhook(req);
@@ -1213,97 +1172,73 @@ describe("twilio webhook routes", () => {
1213
1172
  expect(twiml).toContain('<Parameter name="token"');
1214
1173
  expect(twiml).toContain('<Parameter name="callSessionId"');
1215
1174
  });
1216
-
1217
- test("routing is driven exclusively by services.stt.provider", async () => {
1218
- // Telephony STT routing reads services.stt.provider only.
1219
- // calls.voice.transcriptionProvider is a legacy config field that
1220
- // no longer participates in the call setup path.
1221
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1222
- const session = createTestSession(
1223
- "conv-stt-routing-1",
1224
- "CA_stt_routing_1",
1225
- );
1226
- const req = makeVoiceRequest(session.id, { CallSid: "CA_stt_routing_1" });
1227
-
1228
- const res = await handleVoiceWebhook(req);
1229
- expect(res.status).toBe(200);
1230
-
1231
- const twiml = await res.text();
1232
- // Must be Stream (from services.stt), NOT ConversationRelay
1233
- expect(twiml).toContain("<Stream");
1234
- expect(twiml).not.toContain("<ConversationRelay");
1235
- });
1236
1175
  });
1237
1176
 
1238
- // ── Media-stream preflight setup guardrails ──────────────────────────
1239
- // These tests assert that the TwiML preflight guard in
1240
- // buildVoiceWebhookTwiml rejects unsupported interactive setup actions
1241
- // for media-stream-custom calls before stream bootstrap, falling back
1242
- // to ConversationRelay for interactive flows.
1243
-
1244
- describe("media-stream preflight setup guardrails", () => {
1245
- test("media-stream: normal_call setup proceeds with Stream TwiML", async () => {
1246
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1247
- mockRouteSetupResult = {
1248
- outcome: { action: "normal_call", isInbound: true },
1249
- resolved: {
1250
- assistantId: "self",
1251
- isInbound: true,
1252
- otherPartyNumber: "+15559998888",
1253
- actorTrust: { trustClass: "guardian", memberRecord: null },
1254
- },
1255
- };
1256
-
1257
- const session = createTestSession("conv-ms-normal-1", "CA_ms_normal_1");
1258
- const req = makeVoiceRequest(session.id, { CallSid: "CA_ms_normal_1" });
1177
+ // ── Setup outcomes never affect TwiML ────────────────────────────────
1178
+ // Setup routing is owned by the media-stream server: it re-runs
1179
+ // routeSetup when Twilio's `start` frame arrives and drives every
1180
+ // outcome (verification, invite redemption, name capture, deny, normal)
1181
+ // over the stream. TwiML generation neither consults the router nor
1182
+ // branches on its outcome.
1183
+
1184
+ describe("setup outcomes never affect TwiML", () => {
1185
+ const outcomes: Array<Record<string, unknown>> = [
1186
+ { action: "normal_call", isInbound: true },
1187
+ {
1188
+ action: "deny",
1189
+ message: "This number is not authorized.",
1190
+ logReason: "Inbound voice ACL: blocked caller",
1191
+ },
1192
+ { action: "verification", assistantId: "self", fromNumber: "+14155551234" },
1193
+ { action: "name_capture", assistantId: "self", fromNumber: "+14155551234" },
1194
+ {
1195
+ action: "invite_redemption",
1196
+ assistantId: "self",
1197
+ fromNumber: "+14155551234",
1198
+ inviteeName: "Alice",
1199
+ },
1200
+ ];
1201
+
1202
+ for (const outcome of outcomes) {
1203
+ test(`${outcome.action as string} setup outcome still yields Stream TwiML`, async () => {
1204
+ mockRouteSetupResult = {
1205
+ outcome: outcome as { action: string },
1206
+ resolved: {
1207
+ assistantId: "self",
1208
+ isInbound: true,
1209
+ otherPartyNumber: "+14155551234",
1210
+ actorTrust: { trustClass: "unknown", memberRecord: null },
1211
+ },
1212
+ };
1213
+
1214
+ const session = createTestSession(
1215
+ `conv-outcome-${outcome.action as string}`,
1216
+ `CA_outcome_${outcome.action as string}`,
1217
+ );
1218
+ const req = makeVoiceRequest(session.id, {
1219
+ CallSid: `CA_outcome_${outcome.action as string}`,
1220
+ });
1259
1221
 
1260
- const res = await handleVoiceWebhook(req);
1261
- expect(res.status).toBe(200);
1222
+ const res = await handleVoiceWebhook(req);
1223
+ expect(res.status).toBe(200);
1262
1224
 
1263
- const twiml = await res.text();
1264
- expect(twiml).toContain("<Stream");
1265
- expect(twiml).not.toContain("<ConversationRelay");
1266
- });
1225
+ const twiml = await res.text();
1226
+ expect(twiml).toContain("<Stream");
1227
+ });
1228
+ }
1267
1229
 
1268
- test("media-stream: resolves the phone admission floor and threads it into the preflight routeSetup", async () => {
1269
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1230
+ test("TwiML generation does not invoke routeSetup or the admission/trust readers", async () => {
1270
1231
  mockAdmissionPolicy = "guardian_only";
1271
- mockRouteSetupResult = {
1272
- outcome: { action: "normal_call", isInbound: true },
1273
- resolved: {
1274
- assistantId: "self",
1275
- isInbound: true,
1276
- otherPartyNumber: "+15559998888",
1277
- actorTrust: { trustClass: "guardian", memberRecord: null },
1278
- },
1279
- };
1280
-
1281
- const session = createTestSession("conv-ms-floor-1", "CA_ms_floor_1");
1282
- const req = makeVoiceRequest(session.id, { CallSid: "CA_ms_floor_1" });
1283
-
1284
- const res = await handleVoiceWebhook(req);
1285
- expect(res.status).toBe(200);
1286
-
1287
- // The resolved floor was passed into the preflight routeSetup so its
1288
- // classification matches the real stream-start enforcement.
1289
- expect(lastRouteSetupCtx).not.toBeNull();
1290
- expect(lastRouteSetupCtx?.admissionPolicy).toBe("guardian_only");
1291
- });
1292
-
1293
- test("media-stream inbound: fetches the caller's verdict (From) and threads it into the preflight routeSetup", async () => {
1294
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1295
1232
  mockInboundVerdict = {
1296
1233
  channelType: "phone",
1297
1234
  actorExternalId: "+14155551234",
1298
- contactId: "contact-1",
1299
- channelId: "channel-1",
1300
1235
  status: "verified",
1301
1236
  policy: "allow",
1302
1237
  resolutionFailed: false,
1303
1238
  };
1304
1239
 
1305
1240
  const req = makeInboundVoiceRequest({
1306
- CallSid: "CA_ms_verdict_inbound_1",
1241
+ CallSid: "CA_no_routing_at_twiml_1",
1307
1242
  From: "+14155551234",
1308
1243
  To: "+15550001111",
1309
1244
  });
@@ -1311,46 +1246,38 @@ describe("twilio webhook routes", () => {
1311
1246
  const res = await handleVoiceWebhook(req);
1312
1247
  expect(res.status).toBe(200);
1313
1248
 
1314
- // Inbound: verdict fetched for the caller (From) on the phone channel.
1315
- expect(lastInboundVerdictArgs).toEqual({
1316
- channelType: "phone",
1317
- actorExternalId: "+14155551234",
1318
- });
1319
- // Verdict threaded into the preflight routeSetup.
1320
- expect(lastRouteSetupCtx?.verdict).toEqual(mockInboundVerdict);
1249
+ // Routing and its inputs are resolved at stream start, not TwiML time.
1250
+ expect(lastRouteSetupCtx).toBeNull();
1251
+ expect(admissionPolicyReads).toBe(0);
1252
+ expect(lastInboundVerdictArgs).toBeNull();
1321
1253
  });
1254
+ });
1322
1255
 
1323
- test("media-stream inbound: a blocked/denied member verdict is classified deny in the preflight", async () => {
1324
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1325
- mockInboundVerdict = {
1326
- channelType: "phone",
1327
- actorExternalId: "+14155551234",
1328
- contactId: "contact-1",
1329
- channelId: "channel-1",
1330
- status: "blocked",
1331
- policy: "deny",
1332
- resolutionFailed: false,
1333
- };
1334
- // The real router returns `deny` for a blocked member verdict; the mock
1335
- // reflects that outcome. deny is supported on media-stream, so the
1336
- // preflight still emits Stream TwiML (denial spoken at stream start).
1337
- mockRouteSetupResult = {
1338
- outcome: {
1339
- action: "deny",
1340
- message:
1341
- "This number is not authorized to reach the assistant right now.",
1342
- logReason: "Inbound voice ACL: member blocked",
1256
+ // ── Credential-readiness gate ─────────────────────────────────────────
1257
+ // The daemon performs STT and TTS for every call; when credentials are
1258
+ // missing the webhook returns audible <Say> setup-required TwiML plus
1259
+ // <Hangup/> instead of connecting a doomed stream, and records the gap
1260
+ // on the session.
1261
+
1262
+ describe("credential-readiness TwiML gate", () => {
1263
+ const notReady = {
1264
+ status: "not-ready",
1265
+ missing: [
1266
+ {
1267
+ kind: "stt",
1268
+ providerId: "deepgram",
1269
+ reason: 'No API key configured for credential provider "deepgram"',
1343
1270
  },
1344
- resolved: {
1345
- assistantId: "self",
1346
- isInbound: true,
1347
- otherPartyNumber: "+14155551234",
1348
- actorTrust: { trustClass: "unknown", memberRecord: null },
1349
- },
1350
- };
1271
+ ],
1272
+ userMessage:
1273
+ 'Phone calls are unavailable because they require an API key for the speech-to-text provider "deepgram".',
1274
+ };
1275
+
1276
+ test("inbound not-ready returns <Say> + <Hangup/> and records the preflight event", async () => {
1277
+ mockCredentialReadiness = notReady;
1351
1278
 
1352
1279
  const req = makeInboundVoiceRequest({
1353
- CallSid: "CA_ms_verdict_deny_1",
1280
+ CallSid: "CA_preflight_inbound_1",
1354
1281
  From: "+14155551234",
1355
1282
  To: "+15550001111",
1356
1283
  });
@@ -1358,198 +1285,141 @@ describe("twilio webhook routes", () => {
1358
1285
  const res = await handleVoiceWebhook(req);
1359
1286
  expect(res.status).toBe(200);
1360
1287
 
1361
- // Verdict was threaded into routeSetup, which denied the caller.
1362
- expect(lastRouteSetupCtx?.verdict).toEqual(mockInboundVerdict);
1363
1288
  const twiml = await res.text();
1364
- expect(twiml).toContain("<Stream");
1365
- expect(twiml).not.toContain("<ConversationRelay");
1366
- });
1289
+ expect(twiml).toContain("<Say>");
1290
+ expect(twiml).toContain("<Hangup/>");
1291
+ expect(twiml).toContain("speech-to-text provider");
1292
+ expect(twiml).not.toContain("<Stream");
1367
1293
 
1368
- test("media-stream: floor-denied caller classified deny still produces Stream TwiML (deny handled at stream level)", async () => {
1369
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1370
- mockAdmissionPolicy = "guardian_only";
1371
- // With the floor wired, the real router returns `deny` for a
1372
- // below-floor trusted_contact caller; the mock reflects that.
1373
- mockRouteSetupResult = {
1374
- outcome: {
1375
- action: "deny",
1376
- message:
1377
- "This number is not authorized to reach the assistant right now.",
1378
- logReason: "Inbound voice admission floor: guardian_only",
1379
- },
1380
- resolved: {
1381
- assistantId: "self",
1382
- isInbound: true,
1383
- otherPartyNumber: "+14155550000",
1384
- actorTrust: { trustClass: "trusted_contact", memberRecord: null },
1385
- },
1294
+ const session = getCallSessionByCallSid("CA_preflight_inbound_1");
1295
+ expect(session).not.toBeNull();
1296
+ const events = getCallEvents(session!.id);
1297
+ const preflightEvents = events.filter(
1298
+ (e) => e.eventType === "telephony_credential_preflight_failed",
1299
+ );
1300
+ expect(preflightEvents.length).toBe(1);
1301
+ const payload = JSON.parse(preflightEvents[0]!.payloadJson) as {
1302
+ missing: Array<{ kind: string; providerId: string }>;
1386
1303
  };
1304
+ expect(payload.missing).toHaveLength(1);
1305
+ expect(payload.missing[0]!.kind).toBe("stt");
1306
+ expect(payload.missing[0]!.providerId).toBe("deepgram");
1307
+ });
1308
+
1309
+ test("outbound webhook not-ready returns <Say> + <Hangup/> and records the preflight event", async () => {
1310
+ mockCredentialReadiness = notReady;
1387
1311
 
1388
1312
  const session = createTestSession(
1389
- "conv-ms-floor-deny-1",
1390
- "CA_ms_floor_deny_1",
1313
+ "conv-preflight-out-1",
1314
+ "CA_preflight_out_1",
1391
1315
  );
1392
1316
  const req = makeVoiceRequest(session.id, {
1393
- CallSid: "CA_ms_floor_deny_1",
1317
+ CallSid: "CA_preflight_out_1",
1394
1318
  });
1395
1319
 
1396
1320
  const res = await handleVoiceWebhook(req);
1397
1321
  expect(res.status).toBe(200);
1398
1322
 
1399
- // Policy was threaded; deny is supported on media-stream, so Stream
1400
- // TwiML is still produced (denial spoken + torn down at stream start).
1401
- expect(lastRouteSetupCtx?.admissionPolicy).toBe("guardian_only");
1402
1323
  const twiml = await res.text();
1403
- expect(twiml).toContain("<Stream");
1404
- expect(twiml).not.toContain("<ConversationRelay");
1405
- });
1324
+ expect(twiml).toContain("<Say>");
1325
+ expect(twiml).toContain("<Hangup/>");
1326
+ expect(twiml).not.toContain("<Stream");
1406
1327
 
1407
- test("media-stream: deny setup proceeds with Stream TwiML (deny is handled at stream level)", async () => {
1408
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1409
- mockRouteSetupResult = {
1410
- outcome: {
1411
- action: "deny",
1412
- message: "This number is not authorized.",
1413
- logReason: "Inbound voice ACL: blocked caller",
1414
- },
1415
- resolved: {
1416
- assistantId: "self",
1417
- isInbound: true,
1418
- otherPartyNumber: "+15559998888",
1419
- actorTrust: { trustClass: "unknown", memberRecord: null },
1420
- },
1421
- };
1328
+ const events = getCallEvents(session.id);
1329
+ expect(
1330
+ events.filter(
1331
+ (e) => e.eventType === "telephony_credential_preflight_failed",
1332
+ ).length,
1333
+ ).toBe(1);
1334
+ });
1422
1335
 
1423
- const session = createTestSession("conv-ms-deny-1", "CA_ms_deny_1");
1424
- const req = makeVoiceRequest(session.id, { CallSid: "CA_ms_deny_1" });
1336
+ test("ready readiness produces Stream TwiML with no preflight event", async () => {
1337
+ const session = createTestSession(
1338
+ "conv-preflight-ready-1",
1339
+ "CA_preflight_ready_1",
1340
+ );
1341
+ const req = makeVoiceRequest(session.id, {
1342
+ CallSid: "CA_preflight_ready_1",
1343
+ });
1425
1344
 
1426
1345
  const res = await handleVoiceWebhook(req);
1427
1346
  expect(res.status).toBe(200);
1428
1347
 
1429
1348
  const twiml = await res.text();
1430
- // Deny is supported on media-stream — should still produce Stream TwiML
1431
1349
  expect(twiml).toContain("<Stream");
1432
- expect(twiml).not.toContain("<ConversationRelay");
1433
- });
1350
+ expect(twiml).not.toContain("<Say>");
1434
1351
 
1435
- test("media-stream: verification setup falls back to ConversationRelay", async () => {
1436
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1437
- mockRouteSetupResult = {
1438
- outcome: {
1439
- action: "verification",
1440
- assistantId: "self",
1441
- fromNumber: "+14155551234",
1442
- },
1443
- resolved: {
1444
- assistantId: "self",
1445
- isInbound: true,
1446
- otherPartyNumber: "+14155551234",
1447
- actorTrust: { trustClass: "unknown", memberRecord: null },
1448
- },
1449
- };
1450
-
1451
- const session = createTestSession("conv-ms-verify-1", "CA_ms_verify_1");
1452
- const req = makeVoiceRequest(session.id, { CallSid: "CA_ms_verify_1" });
1453
-
1454
- const res = await handleVoiceWebhook(req);
1455
- expect(res.status).toBe(200);
1456
-
1457
- const twiml = await res.text();
1458
- // Interactive verification cannot work on media-stream — must fall back
1459
- expect(twiml).toContain("<ConversationRelay");
1460
- expect(twiml).not.toContain("<Stream");
1461
- expect(twiml).toContain('transcriptionProvider="Deepgram"');
1352
+ const events = getCallEvents(session.id);
1353
+ expect(
1354
+ events.filter(
1355
+ (e) => e.eventType === "telephony_credential_preflight_failed",
1356
+ ).length,
1357
+ ).toBe(0);
1462
1358
  });
1359
+ });
1463
1360
 
1464
- test("media-stream: name_capture setup falls back to ConversationRelay", async () => {
1465
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1466
- mockRouteSetupResult = {
1467
- outcome: {
1468
- action: "name_capture",
1469
- assistantId: "self",
1470
- fromNumber: "+14155551234",
1471
- },
1472
- resolved: {
1473
- assistantId: "self",
1474
- isInbound: true,
1475
- otherPartyNumber: "+14155551234",
1476
- actorTrust: { trustClass: "unknown", memberRecord: null },
1477
- },
1478
- };
1361
+ // ── Internal voice webhook (gateway forward) ─────────────────────────
1362
+ // The gateway's <Gather> voice-verify success path forwards through
1363
+ // /v1/internal/twilio/voice-webhook, which shares processVoiceWebhook.
1479
1364
 
1480
- const session = createTestSession("conv-ms-namecap-1", "CA_ms_namecap_1");
1481
- const req = makeVoiceRequest(session.id, {
1482
- CallSid: "CA_ms_namecap_1",
1365
+ describe("internal voice webhook (gateway forward)", () => {
1366
+ test("inbound forward yields Stream TwiML", async () => {
1367
+ const result = await handleInternalVoiceWebhook({
1368
+ body: {
1369
+ params: {
1370
+ CallSid: "CA_internal_fwd_1",
1371
+ From: "+14155551234",
1372
+ To: "+15550001111",
1373
+ },
1374
+ originalUrl: "https://ingress.example.com/webhooks/twilio/voice",
1375
+ },
1483
1376
  });
1484
1377
 
1485
- const res = await handleVoiceWebhook(req);
1486
- expect(res.status).toBe(200);
1487
-
1488
- const twiml = await res.text();
1489
- expect(twiml).toContain("<ConversationRelay");
1490
- expect(twiml).not.toContain("<Stream");
1378
+ const twiml = result.body as string;
1379
+ expect(twiml).toContain("<Stream");
1491
1380
  });
1492
1381
 
1493
- test("media-stream: invite_redemption setup falls back to ConversationRelay", async () => {
1494
- mockConfigObj.services.stt.provider = "openai-whisper" as any;
1495
- mockRouteSetupResult = {
1496
- outcome: {
1497
- action: "invite_redemption",
1498
- assistantId: "self",
1499
- fromNumber: "+14155551234",
1500
- inviteeName: "Alice",
1501
- },
1502
- resolved: {
1503
- assistantId: "self",
1504
- isInbound: true,
1505
- otherPartyNumber: "+14155551234",
1506
- actorTrust: { trustClass: "unknown", memberRecord: null },
1382
+ test("outbound forward (callSessionId in originalUrl) yields Stream TwiML", async () => {
1383
+ const session = createTestSession(
1384
+ "conv-internal-fwd-2",
1385
+ "CA_internal_fwd_2",
1386
+ );
1387
+ const result = await handleInternalVoiceWebhook({
1388
+ body: {
1389
+ params: { CallSid: "CA_internal_fwd_2" },
1390
+ originalUrl: `https://ingress.example.com/webhooks/twilio/voice?callSessionId=${session.id}`,
1507
1391
  },
1508
- };
1509
-
1510
- const session = createTestSession("conv-ms-invite-1", "CA_ms_invite_1");
1511
- const req = makeVoiceRequest(session.id, {
1512
- CallSid: "CA_ms_invite_1",
1513
1392
  });
1514
1393
 
1515
- const res = await handleVoiceWebhook(req);
1516
- expect(res.status).toBe(200);
1517
-
1518
- const twiml = await res.text();
1519
- expect(twiml).toContain("<ConversationRelay");
1520
- expect(twiml).not.toContain("<Stream");
1394
+ const twiml = result.body as string;
1395
+ expect(twiml).toContain("<Stream");
1396
+ expect(twiml).toContain(`/webhooks/twilio/media-stream/${session.id}`);
1521
1397
  });
1522
1398
 
1523
- test("conversation-relay-native: unsupported setup does not trigger preflight (not media-stream)", async () => {
1524
- // When the STT provider is deepgram (conversation-relay-native),
1525
- // the preflight guard is not invoked — setup flows are handled
1526
- // natively by the relay server.
1527
- mockConfigObj.services.stt.provider = "deepgram" as any;
1528
- mockRouteSetupResult = {
1529
- outcome: {
1530
- action: "verification",
1531
- assistantId: "self",
1532
- fromNumber: "+14155551234",
1533
- },
1534
- resolved: {
1535
- assistantId: "self",
1536
- isInbound: true,
1537
- otherPartyNumber: "+14155551234",
1538
- actorTrust: { trustClass: "unknown", memberRecord: null },
1539
- },
1399
+ test("inbound forward not-ready yields <Say> + <Hangup/>", async () => {
1400
+ mockCredentialReadiness = {
1401
+ status: "not-ready",
1402
+ missing: [
1403
+ { kind: "tts", providerId: "elevenlabs", reason: "missing key" },
1404
+ ],
1405
+ userMessage:
1406
+ 'Phone calls are unavailable because they require an API key for the text-to-speech provider "elevenlabs".',
1540
1407
  };
1541
1408
 
1542
- const session = createTestSession("conv-cr-verify-1", "CA_cr_verify_1");
1543
- const req = makeVoiceRequest(session.id, {
1544
- CallSid: "CA_cr_verify_1",
1409
+ const result = await handleInternalVoiceWebhook({
1410
+ body: {
1411
+ params: {
1412
+ CallSid: "CA_internal_fwd_3",
1413
+ From: "+14155551234",
1414
+ To: "+15550001111",
1415
+ },
1416
+ originalUrl: "https://ingress.example.com/webhooks/twilio/voice",
1417
+ },
1545
1418
  });
1546
1419
 
1547
- const res = await handleVoiceWebhook(req);
1548
- expect(res.status).toBe(200);
1549
-
1550
- const twiml = await res.text();
1551
- // ConversationRelay should be emitted regardless of setup outcome
1552
- expect(twiml).toContain("<ConversationRelay");
1420
+ const twiml = result.body as string;
1421
+ expect(twiml).toContain("<Say>");
1422
+ expect(twiml).toContain("<Hangup/>");
1553
1423
  expect(twiml).not.toContain("<Stream");
1554
1424
  });
1555
1425
  });