@vellumai/assistant 0.7.1 → 0.7.2

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 (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
package/docs/plugins.md CHANGED
@@ -66,7 +66,7 @@ skill can omit middleware entirely.
66
66
 
67
67
  ## Where plugins live
68
68
 
69
- The assistant scans `~/.vellum/plugins/*` at startup. Any subdirectory
69
+ The assistant scans `<workspaceDir>/plugins/*` at startup. Any subdirectory
70
70
  containing `register.js` or `register.ts` is dynamic-imported once. The
71
71
  loader lives in
72
72
  [`assistant/src/plugins/user-loader.ts`](../src/plugins/user-loader.ts) and
@@ -78,9 +78,8 @@ has three key properties:
78
78
  - **Per-plugin isolation.** If one plugin throws at import time, the error
79
79
  is logged with the plugin directory and the loader moves on. Other
80
80
  plugins still load. One broken plugin cannot brick the assistant.
81
- - **Per-instance.** The scan runs under `vellumRoot()`, which honors the
82
- multi-instance `BASE_DATA_DIR` override. Each assistant instance loads
83
- its own plugin set.
81
+ - **Per-instance.** The scan runs under `vellumRoot()`. Each assistant
82
+ instance loads its own plugin set.
84
83
 
85
84
  The loader runs after first-party plugin registrations and before
86
85
  `bootstrapPlugins()` invokes every plugin's `init()`.
@@ -105,7 +104,7 @@ export interface PluginManifest {
105
104
 
106
105
  | Field | Required | Purpose |
107
106
  | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
- | `name` | yes | Unique plugin identifier. Duplicate names fail registration. Used as the directory under `~/.vellum/plugins-data/<name>/` and the attribution tag in logs. |
107
+ | `name` | yes | Unique plugin identifier. Duplicate names fail registration. Used as the directory under `<workspaceDir>/plugins-data/<name>/` and the attribution tag in logs. |
109
108
  | `version` | yes | Plugin's own semver. Informational — the registry does not compare it. |
110
109
  | `provides` | no | Reserved for future cross-plugin composition and not currently consumed by the assistant. Plugin authors may set this field, but no runtime code reads it yet — it is declared here so future cross-plugin work can land without a manifest version bump. Do not rely on it for any runtime behavior today. |
111
110
  | `requires` | yes | Must include `pluginRuntime: "v1"` at minimum. The registry checks every entry against `ASSISTANT_API_VERSIONS` and refuses to register plugins that ask for a capability or version the assistant does not expose. |
@@ -487,7 +486,7 @@ export interface PluginInitContext {
487
486
  config: unknown; // parsed config (or raw if no validator)
488
487
  credentials: Record<string, string>; // resolved credentials from requiresCredential
489
488
  logger: unknown; // pino child logger, tagged { plugin: <name> }
490
- pluginStorageDir: string; // ~/.vellum/plugins-data/<name>/ (created by bootstrap)
489
+ pluginStorageDir: string; // <workspaceDir>/plugins-data/<name>/ (created by bootstrap)
491
490
  assistantVersion: string; // assistant semver
492
491
  apiVersions: Record<string, string[]>; // ASSISTANT_API_VERSIONS, for runtime checks
493
492
  }
@@ -657,7 +656,7 @@ The registry's internal state is not mutable at runtime. `init()` and
657
656
  `onShutdown()` hooks are fired exactly once per assistant boot.
658
657
 
659
658
  If you need hot reload for development, symlink your plugin directory
660
- into `~/.vellum/plugins/` so edits propagate, and automate the restart
659
+ into `<workspaceDir>/plugins/` so edits propagate, and automate the restart
661
660
  loop externally.
662
661
 
663
662
  ## Troubleshooting
@@ -748,8 +747,7 @@ tail -f ~/.vellum/daemon.log \
748
747
 
749
748
  ### Plugin not loading at all
750
749
 
751
- - Confirm the directory is under `~/.vellum/plugins/` (or the per-instance
752
- equivalent under `$BASE_DATA_DIR/.vellum/plugins/`).
750
+ - Confirm the directory is under `<workspaceDir>/plugins/`.
753
751
  - Confirm it has a `register.ts` or `register.js` at the top level.
754
752
  - Check the assistant's stderr for a line like
755
753
  `loaded user plugin (side-effect import completed)` or
package/knip.json CHANGED
@@ -13,6 +13,7 @@
13
13
  "@vellumai/gateway-client",
14
14
  "@vellumai/service-contracts",
15
15
  "@vellumai/slack-text",
16
+ "@vellumai/twilio-client",
16
17
  "@resvg/resvg-js-darwin-arm64",
17
18
  "@resvg/resvg-js-darwin-x64",
18
19
  "@vellumai/skill-host-contracts",
@@ -17,6 +17,7 @@ export {
17
17
 
18
18
  export {
19
19
  ipcCall,
20
+ IpcCallError,
20
21
  PersistentIpcClient,
21
22
  } from "./ipc-client.js";
22
23
 
@@ -13,6 +13,38 @@ import { connect, type Socket } from "node:net";
13
13
  import type { IpcRequest, IpcResponse, Logger } from "./types.js";
14
14
  import { noopLogger } from "./types.js";
15
15
 
16
+ // ---------------------------------------------------------------------------
17
+ // Error surface
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * Error class thrown by `PersistentIpcClient.call` when the daemon returns
22
+ * a structured error envelope (i.e. `RouteError`-derived). Mirrors the HTTP
23
+ * adapter's `error.details` shape so IPC callers can branch on `errorCode`
24
+ * or recover machine-readable `errorDetails` (e.g. `version_incompatible`).
25
+ */
26
+ export class IpcCallError extends Error {
27
+ readonly statusCode?: number;
28
+ readonly errorCode?: string;
29
+ readonly errorDetails?: unknown;
30
+
31
+ constructor(
32
+ message: string,
33
+ fields: {
34
+ statusCode?: number;
35
+ errorCode?: string;
36
+ errorDetails?: unknown;
37
+ } = {},
38
+ ) {
39
+ super(message);
40
+ this.name = "IpcCallError";
41
+ if (fields.statusCode !== undefined) this.statusCode = fields.statusCode;
42
+ if (fields.errorCode !== undefined) this.errorCode = fields.errorCode;
43
+ if (fields.errorDetails !== undefined)
44
+ this.errorDetails = fields.errorDetails;
45
+ }
46
+ }
47
+
16
48
  // ---------------------------------------------------------------------------
17
49
  // Constants
18
50
  // ---------------------------------------------------------------------------
@@ -294,7 +326,13 @@ export class PersistentIpcClient {
294
326
  this.pending.delete(msg.id);
295
327
  clearTimeout(entry.timer);
296
328
  if (msg.error) {
297
- entry.reject(new Error(msg.error));
329
+ entry.reject(
330
+ new IpcCallError(msg.error, {
331
+ statusCode: msg.statusCode,
332
+ errorCode: msg.errorCode,
333
+ errorDetails: msg.errorDetails,
334
+ }),
335
+ );
298
336
  } else {
299
337
  entry.resolve(msg.result);
300
338
  }
@@ -105,6 +105,17 @@ export interface IpcResponse {
105
105
  id: string;
106
106
  result?: unknown;
107
107
  error?: string;
108
+ /** HTTP-style status code mirrored from `RouteError.statusCode`. */
109
+ statusCode?: number;
110
+ /** Machine-readable error code (e.g. "UNPROCESSABLE_ENTITY"). */
111
+ errorCode?: string;
112
+ /**
113
+ * Structured error payload mirroring `RouteError.details` — present only
114
+ * when the originating error carried a `details` field. Mirrors the HTTP
115
+ * adapter's `error.details` envelope so IPC clients can recover the same
116
+ * machine-readable context as HTTP clients.
117
+ */
118
+ errorDetails?: unknown;
108
119
  }
109
120
 
110
121
  // ---------------------------------------------------------------------------
@@ -7,6 +7,8 @@
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
9
  "./credential-rpc": "./src/credential-rpc.ts",
10
+ "./ingress": "./src/ingress.ts",
11
+ "./twilio-ingress": "./src/twilio-ingress.ts",
10
12
  "./trust-rules": "./src/trust-rules.ts",
11
13
  "./handles": "./src/handles.ts",
12
14
  "./grants": "./src/grants.ts",
@@ -33,6 +33,8 @@ describe("package independence", () => {
33
33
  "../transport.ts",
34
34
  "../credential-rpc.ts",
35
35
  "../trust-rules.ts",
36
+ "../ingress.ts",
37
+ "../twilio-ingress.ts",
36
38
  "../error.ts",
37
39
  ];
38
40
 
@@ -219,6 +221,7 @@ describe("ToolResponseBaseSchema", () => {
219
221
  result: { html: "<html></html>" },
220
222
  });
221
223
  expect(result.success).toBe(true);
224
+ if (!result.success) throw new Error("Expected successful response");
222
225
  expect(result.result).toEqual({ html: "<html></html>" });
223
226
  });
224
227
 
@@ -238,6 +241,7 @@ describe("ToolResponseBaseSchema", () => {
238
241
  },
239
242
  });
240
243
  expect(result.success).toBe(false);
244
+ if (result.success) throw new Error("Expected failed response");
241
245
  expect(result.error.code).toBe("TOOL_FAILED");
242
246
  });
243
247
 
@@ -0,0 +1,107 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ normalizeHttpPublicBaseUrl,
5
+ normalizePublicBaseUrl,
6
+ } from "../ingress.js";
7
+ import {
8
+ buildTwilioConnectActionUrl,
9
+ buildTwilioMediaStreamUrl,
10
+ buildTwilioPhoneNumberWebhookUrls,
11
+ buildTwilioRelayUrl,
12
+ buildTwilioVoiceWebhookUrl,
13
+ resolveTwilioPublicBaseUrl,
14
+ } from "../twilio-ingress.js";
15
+
16
+ describe("normalizePublicBaseUrl", () => {
17
+ test("trims whitespace and trailing slashes", () => {
18
+ expect(normalizePublicBaseUrl(" https://example.test/path/// ")).toBe(
19
+ "https://example.test/path",
20
+ );
21
+ });
22
+
23
+ test("rejects non-string and empty values", () => {
24
+ expect(normalizePublicBaseUrl(undefined)).toBeUndefined();
25
+ expect(normalizePublicBaseUrl(" ")).toBeUndefined();
26
+ });
27
+ });
28
+
29
+ describe("normalizeHttpPublicBaseUrl", () => {
30
+ test("normalizes valid HTTP and HTTPS URLs", () => {
31
+ expect(normalizeHttpPublicBaseUrl(" HTTPS://EXAMPLE.TEST/twilio ")).toBe(
32
+ "https://example.test/twilio",
33
+ );
34
+ expect(normalizeHttpPublicBaseUrl("https://example.test/twilio///")).toBe(
35
+ "https://example.test/twilio",
36
+ );
37
+ expect(normalizeHttpPublicBaseUrl("https://example.test")).toBe(
38
+ "https://example.test/",
39
+ );
40
+ });
41
+
42
+ test("rejects non-HTTP URLs and malformed values", () => {
43
+ expect(normalizeHttpPublicBaseUrl("ftp://example.test")).toBeUndefined();
44
+ expect(normalizeHttpPublicBaseUrl("notaurl")).toBeUndefined();
45
+ expect(normalizeHttpPublicBaseUrl("")).toBeUndefined();
46
+ });
47
+
48
+ test("rejects query strings and fragments instead of mutating them", () => {
49
+ expect(
50
+ normalizeHttpPublicBaseUrl("https://example.test/twilio?token=abc/"),
51
+ ).toBeUndefined();
52
+ expect(
53
+ normalizeHttpPublicBaseUrl("https://example.test/twilio#section/"),
54
+ ).toBeUndefined();
55
+ expect(
56
+ normalizeHttpPublicBaseUrl("https://example.test/twilio?"),
57
+ ).toBeUndefined();
58
+ expect(
59
+ normalizeHttpPublicBaseUrl("https://example.test/twilio#"),
60
+ ).toBeUndefined();
61
+ });
62
+ });
63
+
64
+ describe("Twilio ingress helpers", () => {
65
+ test("resolves public base URL with fallback", () => {
66
+ expect(
67
+ resolveTwilioPublicBaseUrl({
68
+ publicBaseUrl: " https://twilio.example.test/twilio/ ",
69
+ }),
70
+ ).toBe("https://twilio.example.test/twilio");
71
+ expect(
72
+ resolveTwilioPublicBaseUrl({
73
+ publicBaseUrl: " ",
74
+ }),
75
+ ).toBeUndefined();
76
+ expect(
77
+ resolveTwilioPublicBaseUrl({
78
+ publicBaseUrl: " ",
79
+ }, "https://fallback.example.test/"),
80
+ ).toBe("https://fallback.example.test");
81
+ expect(
82
+ resolveTwilioPublicBaseUrl({}, "https://fallback.example.test/"),
83
+ ).toBe("https://fallback.example.test");
84
+ });
85
+
86
+ test("builds Twilio webhook and WebSocket URLs from one base URL", () => {
87
+ expect(buildTwilioVoiceWebhookUrl("https://example.test")).toBe(
88
+ "https://example.test/webhooks/twilio/voice",
89
+ );
90
+ expect(buildTwilioVoiceWebhookUrl("https://example.test", "call-123")).toBe(
91
+ "https://example.test/webhooks/twilio/voice?callSessionId=call-123",
92
+ );
93
+ expect(buildTwilioConnectActionUrl("https://example.test")).toBe(
94
+ "https://example.test/webhooks/twilio/connect-action",
95
+ );
96
+ expect(buildTwilioRelayUrl("https://example.test")).toBe(
97
+ "wss://example.test/webhooks/twilio/relay",
98
+ );
99
+ expect(buildTwilioMediaStreamUrl("http://example.test")).toBe(
100
+ "ws://example.test/webhooks/twilio/media-stream",
101
+ );
102
+ expect(buildTwilioPhoneNumberWebhookUrls("https://example.test")).toEqual({
103
+ statusCallbackUrl: "https://example.test/webhooks/twilio/status",
104
+ voiceUrl: "https://example.test/webhooks/twilio/voice",
105
+ });
106
+ });
107
+ });
@@ -6,9 +6,11 @@
6
6
  *
7
7
  * - `@vellumai/service-contracts/credential-rpc` — transport, RPC, handles, grants, rendering, error
8
8
  * - `@vellumai/service-contracts/trust-rules` — trust-rule types and parsing helpers
9
+ * - `@vellumai/service-contracts/twilio-ingress` — shared Twilio ingress config constants
10
+ * - `@vellumai/service-contracts/ingress` — shared public ingress URL helpers
9
11
  *
10
12
  * Fine-grained subpaths are also available for low-friction migration:
11
- * `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`
13
+ * `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`, `./ingress`, `./twilio-ingress`
12
14
  *
13
15
  * Neutral wire-protocol contracts for communication between the assistant
14
16
  * daemon and the Credential Execution Service (CES). This package is
@@ -23,3 +25,5 @@ export * from "./grants.js";
23
25
  export * from "./rpc.js";
24
26
  export * from "./rendering.js";
25
27
  export * from "./trust-rules.js";
28
+ export * from "./ingress.js";
29
+ export * from "./twilio-ingress.js";
@@ -0,0 +1,24 @@
1
+ export function normalizePublicBaseUrl(value: unknown): string | undefined {
2
+ if (typeof value !== "string") return undefined;
3
+ const normalized = value.trim().replace(/\/+$/, "");
4
+ return normalized.length > 0 ? normalized : undefined;
5
+ }
6
+
7
+ export function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {
8
+ if (typeof value !== "string") return undefined;
9
+ const trimmed = value.trim();
10
+ if (trimmed.length === 0) return undefined;
11
+ if (/[?#]/.test(trimmed)) return undefined;
12
+
13
+ try {
14
+ const url = new URL(trimmed);
15
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
16
+ return undefined;
17
+ }
18
+ if (!url.hostname) return undefined;
19
+ url.pathname = url.pathname.replace(/\/+$/, "") || "/";
20
+ return url.toString();
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
@@ -0,0 +1,84 @@
1
+ import { normalizePublicBaseUrl } from "./ingress.js";
2
+
3
+ export const TWILIO_VOICE_WEBHOOK_PATH = "/webhooks/twilio/voice";
4
+ export const TWILIO_STATUS_WEBHOOK_PATH = "/webhooks/twilio/status";
5
+ export const TWILIO_CONNECT_ACTION_WEBHOOK_PATH =
6
+ "/webhooks/twilio/connect-action";
7
+ export const TWILIO_RELAY_WEBHOOK_PATH = "/webhooks/twilio/relay";
8
+ export const TWILIO_MEDIA_STREAM_WEBHOOK_PATH = "/webhooks/twilio/media-stream";
9
+
10
+ /**
11
+ * Sentinel placeholder embedded in TwiML by the assistant where the real
12
+ * public base URL should go. The gateway replaces `wss://__VELLUM_PUBLIC_BASE_URL__/…`
13
+ * with the actual public URL (from Velay registration, config, or the
14
+ * `X-Vellum-Ingress-URL` header) before returning TwiML to Twilio.
15
+ *
16
+ * The placeholder uses `https://` so that `buildTwilioRelayUrl` /
17
+ * `buildTwilioMediaStreamUrl` can apply the standard `http→ws` scheme
18
+ * conversion, producing `wss://__VELLUM_PUBLIC_BASE_URL__/…` in the output.
19
+ */
20
+ export const TWILIO_PUBLIC_BASE_URL_PLACEHOLDER =
21
+ "https://__VELLUM_PUBLIC_BASE_URL__";
22
+
23
+ /**
24
+ * The WebSocket-scheme form of the placeholder that appears in TwiML after
25
+ * the `http→ws` scheme conversion applied by the URL builders.
26
+ */
27
+ export const TWILIO_PUBLIC_BASE_WSS_PLACEHOLDER =
28
+ "wss://__VELLUM_PUBLIC_BASE_URL__";
29
+
30
+ export { normalizePublicBaseUrl } from "./ingress.js";
31
+
32
+ export type TwilioPhoneNumberWebhookUrls = {
33
+ statusCallbackUrl: string;
34
+ voiceUrl: string;
35
+ };
36
+
37
+ export function resolveTwilioPublicBaseUrl(
38
+ ingress: { publicBaseUrl?: unknown } | undefined,
39
+ fallbackPublicBaseUrl?: unknown,
40
+ ): string | undefined {
41
+ const publicBaseUrl = normalizePublicBaseUrl(ingress?.publicBaseUrl);
42
+ if (publicBaseUrl) return publicBaseUrl;
43
+
44
+ return normalizePublicBaseUrl(fallbackPublicBaseUrl);
45
+ }
46
+
47
+ export function buildTwilioVoiceWebhookUrl(
48
+ baseUrl: string,
49
+ callSessionId?: string,
50
+ ): string {
51
+ if (callSessionId) {
52
+ return `${baseUrl}${TWILIO_VOICE_WEBHOOK_PATH}?callSessionId=${callSessionId}`;
53
+ }
54
+ return `${baseUrl}${TWILIO_VOICE_WEBHOOK_PATH}`;
55
+ }
56
+
57
+ export function buildTwilioStatusWebhookUrl(baseUrl: string): string {
58
+ return `${baseUrl}${TWILIO_STATUS_WEBHOOK_PATH}`;
59
+ }
60
+
61
+ export function buildTwilioConnectActionUrl(baseUrl: string): string {
62
+ return `${baseUrl}${TWILIO_CONNECT_ACTION_WEBHOOK_PATH}`;
63
+ }
64
+
65
+ export function buildTwilioRelayUrl(baseUrl: string): string {
66
+ return `${toTwilioWebSocketBaseUrl(baseUrl)}${TWILIO_RELAY_WEBHOOK_PATH}`;
67
+ }
68
+
69
+ export function buildTwilioMediaStreamUrl(baseUrl: string): string {
70
+ return `${toTwilioWebSocketBaseUrl(baseUrl)}${TWILIO_MEDIA_STREAM_WEBHOOK_PATH}`;
71
+ }
72
+
73
+ export function buildTwilioPhoneNumberWebhookUrls(
74
+ baseUrl: string,
75
+ ): TwilioPhoneNumberWebhookUrls {
76
+ return {
77
+ statusCallbackUrl: buildTwilioStatusWebhookUrl(baseUrl),
78
+ voiceUrl: buildTwilioVoiceWebhookUrl(baseUrl),
79
+ };
80
+ }
81
+
82
+ function toTwilioWebSocketBaseUrl(baseUrl: string): string {
83
+ return baseUrl.replace(/^http(s?)/, "ws$1");
84
+ }
@@ -84,3 +84,12 @@ export function formatSseFrame(event: AssistantEvent): string {
84
84
  export function formatSseHeartbeat(): string {
85
85
  return ": heartbeat\n\n";
86
86
  }
87
+
88
+ /**
89
+ * Format a keep-alive as both an SSE comment (for proxy keepalive) and a
90
+ * data-bearing event (so fetch-based SSE clients that cannot observe comment
91
+ * lines can still detect heartbeats for disconnect watchdogs).
92
+ */
93
+ export function formatSseHeartbeatWithData(): string {
94
+ return `${formatSseHeartbeat()}event: assistant_event\ndata: ${JSON.stringify({ type: "heartbeat" })}\n\n`;
95
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "@vellumai/twilio-client",
7
+ "devDependencies": {
8
+ "@types/bun": "1.3.10",
9
+ "typescript": "5.9.3",
10
+ },
11
+ },
12
+ },
13
+ "packages": {
14
+ "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
15
+
16
+ "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
17
+
18
+ "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
19
+
20
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
21
+
22
+ "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
23
+ }
24
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@vellumai/twilio-client",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ },
10
+ "scripts": {
11
+ "typecheck": "bunx tsc --noEmit",
12
+ "test": "bun test src/"
13
+ },
14
+ "devDependencies": {
15
+ "@types/bun": "1.3.10",
16
+ "typescript": "5.9.3"
17
+ }
18
+ }
@@ -0,0 +1,128 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ import {
4
+ lookupIncomingPhoneNumberSid,
5
+ twilioAuthHeader,
6
+ twilioBaseUrl,
7
+ TwilioRestError,
8
+ updatePhoneNumberWebhooks,
9
+ type TwilioFetch,
10
+ } from "../index.js";
11
+
12
+ const ACCOUNT_SID = "AC123";
13
+ const AUTH_TOKEN = "auth-token";
14
+ const PHONE_NUMBER = "+15550100";
15
+ const PHONE_NUMBER_SID = "PN123";
16
+
17
+ function jsonResponse(body: unknown, status = 200): Response {
18
+ return new Response(JSON.stringify(body), {
19
+ headers: { "Content-Type": "application/json" },
20
+ status,
21
+ });
22
+ }
23
+
24
+ describe("twilioAuthHeader", () => {
25
+ test("returns a Basic auth header", () => {
26
+ expect(twilioAuthHeader("AC_test_sid", "test_token")).toBe(
27
+ "Basic " + Buffer.from("AC_test_sid:test_token").toString("base64"),
28
+ );
29
+ });
30
+ });
31
+
32
+ describe("twilioBaseUrl", () => {
33
+ test("constructs the account-scoped REST API URL", () => {
34
+ expect(twilioBaseUrl("AC_abc123")).toBe(
35
+ "https://api.twilio.com/2010-04-01/Accounts/AC_abc123",
36
+ );
37
+ });
38
+ });
39
+
40
+ describe("lookupIncomingPhoneNumberSid", () => {
41
+ test("returns the matching incoming phone number SID", async () => {
42
+ const fetchImpl = mock<TwilioFetch>(async () =>
43
+ jsonResponse({
44
+ incoming_phone_numbers: [
45
+ { phone_number: "+15550199", sid: "PN_OTHER" },
46
+ { phone_number: PHONE_NUMBER, sid: PHONE_NUMBER_SID },
47
+ ],
48
+ }),
49
+ );
50
+
51
+ await expect(
52
+ lookupIncomingPhoneNumberSid({
53
+ accountSid: ACCOUNT_SID,
54
+ authToken: AUTH_TOKEN,
55
+ fetchImpl,
56
+ phoneNumber: PHONE_NUMBER,
57
+ }),
58
+ ).resolves.toBe(PHONE_NUMBER_SID);
59
+ });
60
+
61
+ test("throws TwilioRestError for lookup failures", async () => {
62
+ const fetchImpl = mock<TwilioFetch>(
63
+ async () => new Response("unavailable", { status: 503 }),
64
+ );
65
+
66
+ await expect(
67
+ lookupIncomingPhoneNumberSid({
68
+ accountSid: ACCOUNT_SID,
69
+ authToken: AUTH_TOKEN,
70
+ fetchImpl,
71
+ phoneNumber: PHONE_NUMBER,
72
+ }),
73
+ ).rejects.toBeInstanceOf(TwilioRestError);
74
+ });
75
+ });
76
+
77
+ describe("updatePhoneNumberWebhooks", () => {
78
+ test("looks up the SID and posts voice webhook settings", async () => {
79
+ const calls: Array<{ input: string | URL | Request; init?: RequestInit }> =
80
+ [];
81
+ const fetchImpl = mock<TwilioFetch>(
82
+ async (input: string | URL | Request, init?: RequestInit) => {
83
+ calls.push({ input, init });
84
+ if (calls.length === 1) {
85
+ return jsonResponse({
86
+ incoming_phone_numbers: [
87
+ { phone_number: PHONE_NUMBER, sid: PHONE_NUMBER_SID },
88
+ ],
89
+ });
90
+ }
91
+ return jsonResponse({});
92
+ },
93
+ );
94
+
95
+ await updatePhoneNumberWebhooks({
96
+ accountSid: ACCOUNT_SID,
97
+ authToken: AUTH_TOKEN,
98
+ fetchImpl,
99
+ phoneNumber: PHONE_NUMBER,
100
+ webhooks: {
101
+ statusCallbackUrl: "https://example.test/webhooks/twilio/status",
102
+ voiceUrl: "https://example.test/webhooks/twilio/voice",
103
+ },
104
+ });
105
+
106
+ expect(calls).toHaveLength(2);
107
+ expect(String(calls[0].input)).toContain(
108
+ `/Accounts/${ACCOUNT_SID}/IncomingPhoneNumbers.json?PhoneNumber=%2B15550100`,
109
+ );
110
+ expect(String(calls[1].input)).toContain(
111
+ `/Accounts/${ACCOUNT_SID}/IncomingPhoneNumbers/${PHONE_NUMBER_SID}.json`,
112
+ );
113
+ expect(calls[1].init?.method).toBe("POST");
114
+ expect(calls[1].init?.headers).toEqual({
115
+ Authorization:
116
+ "Basic " +
117
+ Buffer.from(`${ACCOUNT_SID}:${AUTH_TOKEN}`).toString("base64"),
118
+ "Content-Type": "application/x-www-form-urlencoded",
119
+ });
120
+ const body = new URLSearchParams(String(calls[1].init?.body));
121
+ expect(body.get("VoiceUrl")).toBe(
122
+ "https://example.test/webhooks/twilio/voice",
123
+ );
124
+ expect(body.get("StatusCallback")).toBe(
125
+ "https://example.test/webhooks/twilio/status",
126
+ );
127
+ });
128
+ });