@vellumai/assistant 0.3.4 → 0.3.6

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 (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -2,6 +2,7 @@ import { createHmac, timingSafeEqual } from 'node:crypto';
2
2
  import { getLogger } from '../util/logger.js';
3
3
  import { getSecureKey } from '../security/secure-keys.js';
4
4
  import { getTwilioCredentials, twilioAuthHeader, twilioBaseUrl } from './twilio-rest.js';
5
+ import { ProviderError } from '../util/errors.js';
5
6
  import type { VoiceProvider, InitiateCallOptions } from './voice-provider.js';
6
7
 
7
8
  const log = getLogger('twilio-provider');
@@ -72,7 +73,7 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
72
73
  if (!res.ok) {
73
74
  const text = await res.text();
74
75
  log.error({ status: res.status, body: text }, 'Twilio initiateCall failed');
75
- throw new Error(`Twilio API error ${res.status}: ${text}`);
76
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
76
77
  }
77
78
 
78
79
  const data = (await res.json()) as { sid: string };
@@ -99,7 +100,7 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
99
100
  if (!res.ok) {
100
101
  const text = await res.text();
101
102
  log.error({ status: res.status, body: text, callSid }, 'Twilio endCall failed');
102
- throw new Error(`Twilio API error ${res.status}: ${text}`);
103
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
103
104
  }
104
105
 
105
106
  log.info({ callSid }, 'Twilio call ended');
@@ -118,7 +119,7 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
118
119
  if (!res.ok) {
119
120
  const text = await res.text();
120
121
  log.error({ status: res.status, body: text, callSid }, 'Twilio getCallStatus failed');
121
- throw new Error(`Twilio API error ${res.status}: ${text}`);
122
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
122
123
  }
123
124
 
124
125
  const data = (await res.json()) as { status: string };
@@ -203,8 +204,9 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
203
204
  ...(!incomingOk ? [`IncomingPhoneNumbers: ${incomingRes.status}`] : []),
204
205
  ...(!outgoingOk ? [`OutgoingCallerIds: ${outgoingRes.status}`] : []),
205
206
  ].join(', ');
206
- throw new Error(
207
+ throw new ProviderError(
207
208
  `Unable to verify caller ID eligibility for ${phoneNumber}: Twilio API error (${failedEndpoints}). The number may be eligible but could not be confirmed. Please check your Twilio credentials and try again.`,
209
+ 'twilio',
208
210
  );
209
211
  }
210
212
 
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { getSecureKey } from '../security/secure-keys.js';
10
+ import { ProviderError, ConfigError } from '../util/errors.js';
10
11
 
11
12
  export interface TwilioCredentials {
12
13
  accountSid: string;
@@ -18,7 +19,7 @@ export function getTwilioCredentials(): TwilioCredentials {
18
19
  const accountSid = getSecureKey('credential:twilio:account_sid');
19
20
  const authToken = getSecureKey('credential:twilio:auth_token');
20
21
  if (!accountSid || !authToken) {
21
- throw new Error(
22
+ throw new ConfigError(
22
23
  'Twilio credentials not configured. Set credential:twilio:account_sid and credential:twilio:auth_token via the credential_store tool.',
23
24
  );
24
25
  }
@@ -58,7 +59,7 @@ export async function listIncomingPhoneNumbers(
58
59
 
59
60
  if (!res.ok) {
60
61
  const text = await res.text();
61
- throw new Error(`Twilio API error ${res.status}: ${text}`);
62
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
62
63
  }
63
64
 
64
65
  const data = (await res.json()) as {
@@ -102,7 +103,7 @@ export async function searchAvailableNumbers(
102
103
 
103
104
  if (!res.ok) {
104
105
  const text = await res.text();
105
- throw new Error(`Twilio API error ${res.status}: ${text}`);
106
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
106
107
  }
107
108
 
108
109
  const data = (await res.json()) as {
@@ -139,7 +140,7 @@ export async function provisionPhoneNumber(
139
140
 
140
141
  if (!res.ok) {
141
142
  const text = await res.text();
142
- throw new Error(`Twilio API error ${res.status}: ${text}`);
143
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
143
144
  }
144
145
 
145
146
  const data = (await res.json()) as {
@@ -155,6 +156,38 @@ export async function provisionPhoneNumber(
155
156
  };
156
157
  }
157
158
 
159
+ /** Fetch the current status of a Twilio message by SID. */
160
+ export async function fetchMessageStatus(
161
+ accountSid: string,
162
+ authToken: string,
163
+ messageSid: string,
164
+ ): Promise<{ status: string; errorCode?: string; errorMessage?: string }> {
165
+ const res = await fetch(
166
+ `${twilioBaseUrl(accountSid)}/Messages/${encodeURIComponent(messageSid)}.json`,
167
+ {
168
+ method: 'GET',
169
+ headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
170
+ },
171
+ );
172
+
173
+ if (!res.ok) {
174
+ const text = await res.text();
175
+ throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
176
+ }
177
+
178
+ const data = (await res.json()) as {
179
+ status?: string;
180
+ error_code?: number | null;
181
+ error_message?: string | null;
182
+ };
183
+
184
+ return {
185
+ status: data.status ?? 'unknown',
186
+ errorCode: data.error_code != null ? String(data.error_code) : undefined,
187
+ errorMessage: data.error_message ?? undefined,
188
+ };
189
+ }
190
+
158
191
  export interface WebhookUrls {
159
192
  voiceUrl: string;
160
193
  statusCallbackUrl: string;
@@ -185,7 +218,7 @@ export async function updatePhoneNumberWebhooks(
185
218
 
186
219
  if (!listRes.ok) {
187
220
  const text = await listRes.text();
188
- throw new Error(`Twilio API error ${listRes.status} looking up phone number: ${text}`);
221
+ throw new ProviderError(`Twilio API error ${listRes.status} looking up phone number: ${text}`, 'twilio', listRes.status);
189
222
  }
190
223
 
191
224
  const listData = (await listRes.json()) as {
@@ -194,7 +227,7 @@ export async function updatePhoneNumberWebhooks(
194
227
 
195
228
  const match = listData.incoming_phone_numbers.find((n) => n.phone_number === phoneNumber);
196
229
  if (!match) {
197
- throw new Error(`Phone number ${phoneNumber} not found on Twilio account ${accountSid}`);
230
+ throw new ProviderError(`Phone number ${phoneNumber} not found on Twilio account ${accountSid}`, 'twilio');
198
231
  }
199
232
 
200
233
  // Update the phone number's webhook configuration
@@ -221,6 +254,274 @@ export async function updatePhoneNumberWebhooks(
221
254
 
222
255
  if (!updateRes.ok) {
223
256
  const text = await updateRes.text();
224
- throw new Error(`Twilio API error ${updateRes.status} updating webhooks: ${text}`);
257
+ throw new ProviderError(`Twilio API error ${updateRes.status} updating webhooks: ${text}`, 'twilio', updateRes.status);
258
+ }
259
+ }
260
+
261
+ // ── Toll-Free Verification ──────────────────────────────────────────────
262
+
263
+ /** Twilio Messaging API base URL for toll-free verification endpoints. */
264
+ const TOLLFREE_VERIFICATION_BASE = 'https://messaging.twilio.com/v1/Tollfree/Verifications';
265
+
266
+ export interface TollFreeVerification {
267
+ sid: string;
268
+ status: string;
269
+ rejectionReason?: string;
270
+ rejectionReasons?: string[];
271
+ errorCode?: string;
272
+ editAllowed?: boolean;
273
+ editExpiration?: string;
274
+ regulationType?: string;
275
+ }
276
+
277
+ function parseTollFreeVerification(raw: Record<string, unknown>): TollFreeVerification {
278
+ return {
279
+ sid: raw.sid as string,
280
+ status: raw.status as string,
281
+ rejectionReason: (raw.rejection_reason as string) ?? undefined,
282
+ rejectionReasons: (raw.rejection_reasons as string[]) ?? undefined,
283
+ errorCode: (raw.error_code != null ? String(raw.error_code) : undefined),
284
+ editAllowed: (raw.edit_allowed as boolean) ?? undefined,
285
+ editExpiration: (raw.edit_expiration as string) ?? undefined,
286
+ regulationType: (raw.regulation_type as string) ?? undefined,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Get toll-free verification status for a phone number.
292
+ * If `phoneNumberSid` is provided, filters by that SID; otherwise returns the
293
+ * first verification found.
294
+ */
295
+ export async function getTollFreeVerificationStatus(
296
+ accountSid: string,
297
+ authToken: string,
298
+ phoneNumberSid?: string,
299
+ ): Promise<TollFreeVerification | null> {
300
+ const params = new URLSearchParams();
301
+ if (phoneNumberSid) params.set('TollfreePhoneNumberSid', phoneNumberSid);
302
+
303
+ const url = params.toString()
304
+ ? `${TOLLFREE_VERIFICATION_BASE}?${params.toString()}`
305
+ : TOLLFREE_VERIFICATION_BASE;
306
+
307
+ const res = await fetch(url, {
308
+ method: 'GET',
309
+ headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
310
+ });
311
+
312
+ if (!res.ok) {
313
+ const text = await res.text();
314
+ throw new ProviderError(`Twilio Toll-Free Verification API error ${res.status}: ${text}`, 'twilio', res.status);
315
+ }
316
+
317
+ const data = (await res.json()) as { verifications?: Array<Record<string, unknown>> };
318
+ const verifications = data.verifications ?? [];
319
+ if (verifications.length === 0) return null;
320
+
321
+ return parseTollFreeVerification(verifications[0]);
322
+ }
323
+
324
+ /** Fetch a specific toll-free verification by SID. */
325
+ export async function getTollFreeVerificationBySid(
326
+ accountSid: string,
327
+ authToken: string,
328
+ verificationSid: string,
329
+ ): Promise<TollFreeVerification | null> {
330
+ const res = await fetch(`${TOLLFREE_VERIFICATION_BASE}/${encodeURIComponent(verificationSid)}`, {
331
+ method: 'GET',
332
+ headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
333
+ });
334
+
335
+ if (res.status === 404) {
336
+ return null;
337
+ }
338
+
339
+ if (!res.ok) {
340
+ const text = await res.text();
341
+ throw new ProviderError(`Twilio Toll-Free Verification fetch error ${res.status}: ${text}`, 'twilio', res.status);
342
+ }
343
+
344
+ const data = (await res.json()) as Record<string, unknown>;
345
+ return parseTollFreeVerification(data);
346
+ }
347
+
348
+ export interface TollFreeVerificationSubmitParams {
349
+ tollfreePhoneNumberSid: string;
350
+ businessName: string;
351
+ businessWebsite: string;
352
+ notificationEmail: string;
353
+ useCaseCategories: string[];
354
+ useCaseSummary: string;
355
+ productionMessageSample: string;
356
+ optInImageUrls: string[];
357
+ optInType: string;
358
+ messageVolume: string;
359
+ businessType?: string;
360
+ customerProfileSid?: string;
361
+ }
362
+
363
+ /** Submit a new toll-free verification request. */
364
+ export async function submitTollFreeVerification(
365
+ accountSid: string,
366
+ authToken: string,
367
+ params: TollFreeVerificationSubmitParams,
368
+ ): Promise<TollFreeVerification> {
369
+ const body = new URLSearchParams();
370
+ body.set('TollfreePhoneNumberSid', params.tollfreePhoneNumberSid);
371
+ body.set('BusinessName', params.businessName);
372
+ body.set('BusinessWebsite', params.businessWebsite);
373
+ body.set('NotificationEmail', params.notificationEmail);
374
+ body.set('UseCaseSummary', params.useCaseSummary);
375
+ body.set('ProductionMessageSample', params.productionMessageSample);
376
+ body.set('OptInType', params.optInType);
377
+ body.set('MessageVolume', params.messageVolume);
378
+ body.set('BusinessType', params.businessType ?? 'SOLE_PROPRIETOR');
379
+
380
+ for (const cat of params.useCaseCategories) {
381
+ body.append('UseCaseCategories', cat);
382
+ }
383
+ for (const url of params.optInImageUrls) {
384
+ body.append('OptInImageUrls', url);
385
+ }
386
+ if (params.customerProfileSid) {
387
+ body.set('CustomerProfileSid', params.customerProfileSid);
388
+ }
389
+
390
+ const res = await fetch(TOLLFREE_VERIFICATION_BASE, {
391
+ method: 'POST',
392
+ headers: {
393
+ Authorization: twilioAuthHeader(accountSid, authToken),
394
+ 'Content-Type': 'application/x-www-form-urlencoded',
395
+ },
396
+ body: body.toString(),
397
+ });
398
+
399
+ if (!res.ok) {
400
+ const text = await res.text();
401
+ throw new ProviderError(`Twilio Toll-Free Verification submit error ${res.status}: ${text}`, 'twilio', res.status);
402
+ }
403
+
404
+ const data = (await res.json()) as Record<string, unknown>;
405
+ return parseTollFreeVerification(data);
406
+ }
407
+
408
+ /** Update an existing toll-free verification. */
409
+ export async function updateTollFreeVerification(
410
+ accountSid: string,
411
+ authToken: string,
412
+ verificationSid: string,
413
+ params: Partial<TollFreeVerificationSubmitParams>,
414
+ ): Promise<TollFreeVerification> {
415
+ const body = new URLSearchParams();
416
+ if (params.businessName) body.set('BusinessName', params.businessName);
417
+ if (params.businessWebsite) body.set('BusinessWebsite', params.businessWebsite);
418
+ if (params.notificationEmail) body.set('NotificationEmail', params.notificationEmail);
419
+ if (params.useCaseSummary) body.set('UseCaseSummary', params.useCaseSummary);
420
+ if (params.productionMessageSample) body.set('ProductionMessageSample', params.productionMessageSample);
421
+ if (params.optInType) body.set('OptInType', params.optInType);
422
+ if (params.messageVolume) body.set('MessageVolume', params.messageVolume);
423
+ if (params.businessType) body.set('BusinessType', params.businessType);
424
+ if (params.useCaseCategories) {
425
+ for (const cat of params.useCaseCategories) {
426
+ body.append('UseCaseCategories', cat);
427
+ }
428
+ }
429
+ if (params.optInImageUrls) {
430
+ for (const url of params.optInImageUrls) {
431
+ body.append('OptInImageUrls', url);
432
+ }
433
+ }
434
+ if (params.customerProfileSid) body.set('CustomerProfileSid', params.customerProfileSid);
435
+
436
+ const res = await fetch(`${TOLLFREE_VERIFICATION_BASE}/${encodeURIComponent(verificationSid)}`, {
437
+ method: 'POST',
438
+ headers: {
439
+ Authorization: twilioAuthHeader(accountSid, authToken),
440
+ 'Content-Type': 'application/x-www-form-urlencoded',
441
+ },
442
+ body: body.toString(),
443
+ });
444
+
445
+ if (!res.ok) {
446
+ const text = await res.text();
447
+ throw new ProviderError(`Twilio Toll-Free Verification update error ${res.status}: ${text}`, 'twilio', res.status);
448
+ }
449
+
450
+ const data = (await res.json()) as Record<string, unknown>;
451
+ return parseTollFreeVerification(data);
452
+ }
453
+
454
+ /** Delete a toll-free verification. */
455
+ export async function deleteTollFreeVerification(
456
+ accountSid: string,
457
+ authToken: string,
458
+ verificationSid: string,
459
+ ): Promise<void> {
460
+ const res = await fetch(`${TOLLFREE_VERIFICATION_BASE}/${encodeURIComponent(verificationSid)}`, {
461
+ method: 'DELETE',
462
+ headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
463
+ });
464
+
465
+ if (!res.ok) {
466
+ const text = await res.text();
467
+ throw new ProviderError(`Twilio Toll-Free Verification delete error ${res.status}: ${text}`, 'twilio', res.status);
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Get the SID for an incoming phone number.
473
+ * Looks up the number via `IncomingPhoneNumbers.json?PhoneNumber=...`.
474
+ */
475
+ export async function getPhoneNumberSid(
476
+ accountSid: string,
477
+ authToken: string,
478
+ phoneNumber: string,
479
+ ): Promise<string | null> {
480
+ const res = await fetch(
481
+ `${twilioBaseUrl(accountSid)}/IncomingPhoneNumbers.json?PhoneNumber=${encodeURIComponent(phoneNumber)}`,
482
+ {
483
+ method: 'GET',
484
+ headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
485
+ },
486
+ );
487
+
488
+ if (!res.ok) {
489
+ const text = await res.text();
490
+ throw new ProviderError(`Twilio API error ${res.status} looking up phone number SID: ${text}`, 'twilio', res.status);
491
+ }
492
+
493
+ const data = (await res.json()) as {
494
+ incoming_phone_numbers: Array<{ sid: string; phone_number: string }>;
495
+ };
496
+
497
+ const match = data.incoming_phone_numbers.find((n) => n.phone_number === phoneNumber);
498
+ return match?.sid ?? null;
499
+ }
500
+
501
+ /**
502
+ * Release (delete) an incoming phone number from the Twilio account.
503
+ * Looks up the SID by phone number then sends a DELETE request.
504
+ */
505
+ export async function releasePhoneNumber(
506
+ accountSid: string,
507
+ authToken: string,
508
+ phoneNumber: string,
509
+ ): Promise<void> {
510
+ const sid = await getPhoneNumberSid(accountSid, authToken, phoneNumber);
511
+ if (!sid) {
512
+ throw new ProviderError(`Phone number ${phoneNumber} not found on Twilio account ${accountSid}`, 'twilio');
513
+ }
514
+
515
+ const res = await fetch(
516
+ `${twilioBaseUrl(accountSid)}/IncomingPhoneNumbers/${sid}.json`,
517
+ {
518
+ method: 'DELETE',
519
+ headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
520
+ },
521
+ );
522
+
523
+ if (!res.ok) {
524
+ const text = await res.text();
525
+ throw new ProviderError(`Twilio API error ${res.status} releasing phone number: ${text}`, 'twilio', res.status);
225
526
  }
226
527
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { getLogger } from '../util/logger.js';
10
+ import { getCallWelcomeGreeting } from '../config/env.js';
10
11
  import {
11
12
  getCallSession,
12
13
  getCallSessionByCallSid,
@@ -25,7 +26,9 @@ import { getTwilioConfig } from './twilio-config.js';
25
26
  import { loadConfig } from '../config/loader.js';
26
27
  import { getTwilioRelayUrl } from '../inbound/public-ingress-urls.js';
27
28
  import { fireCallCompletionNotifier } from './call-state.js';
29
+ import { persistCallCompletionMessage } from './call-conversation-messages.js';
28
30
  import { resolveVoiceQualityProfile, isVoiceProfileValid } from './voice-quality.js';
31
+ import { createInboundVoiceSession } from './call-domain.js';
29
32
 
30
33
  const log = getLogger('twilio-routes');
31
34
 
@@ -43,15 +46,18 @@ function escapeXml(str: string): string {
43
46
  export function generateTwiML(
44
47
  callSessionId: string,
45
48
  relayUrl: string,
46
- welcomeGreeting: string,
49
+ welcomeGreeting: string | null,
47
50
  profile: { language: string; transcriptionProvider: string; ttsProvider: string; voice: string },
48
51
  ): string {
52
+ const greetingAttr = welcomeGreeting && welcomeGreeting.trim().length > 0
53
+ ? `\n welcomeGreeting="${escapeXml(welcomeGreeting.trim())}"`
54
+ : '';
49
55
  return `<?xml version="1.0" encoding="UTF-8"?>
50
56
  <Response>
51
57
  <Connect>
52
58
  <ConversationRelay
53
59
  url="${escapeXml(relayUrl)}?callSessionId=${escapeXml(callSessionId)}"
54
- welcomeGreeting="${escapeXml(welcomeGreeting)}"
60
+ ${greetingAttr}
55
61
  voice="${escapeXml(profile.voice)}"
56
62
  language="${escapeXml(profile.language)}"
57
63
  transcriptionProvider="${escapeXml(profile.transcriptionProvider)}"
@@ -63,6 +69,16 @@ export function generateTwiML(
63
69
  </Response>`;
64
70
  }
65
71
 
72
+ export function buildWelcomeGreeting(task: string | null, configuredGreeting?: string): string {
73
+ void task;
74
+ const override = configuredGreeting?.trim();
75
+ if (override) return override;
76
+ // The contextual first opener now comes from the call orchestrator's
77
+ // initial LLM turn. Keep Twilio's relay-level greeting empty by default
78
+ // so we don't speak a deterministic static line first.
79
+ return '';
80
+ }
81
+
66
82
  /**
67
83
  * Resolve the WebSocket relay URL from Twilio config.
68
84
  *
@@ -106,16 +122,43 @@ function mapTwilioStatus(twilioStatus: string): CallStatus | null {
106
122
  /**
107
123
  * Receives the initial voice webhook when Twilio connects the call.
108
124
  * Returns TwiML XML that tells Twilio to open a ConversationRelay WebSocket.
125
+ *
126
+ * Supports two modes:
127
+ * - **Outbound** (callSessionId present in query): uses the existing session
128
+ * - **Inbound** (callSessionId absent): creates or reuses a session keyed
129
+ * by the Twilio CallSid. The optional `forwardedAssistantId` is resolved
130
+ * by the gateway from the "To" phone number.
109
131
  */
110
- export async function handleVoiceWebhook(req: Request): Promise<Response> {
132
+ export async function handleVoiceWebhook(req: Request, forwardedAssistantId?: string): Promise<Response> {
111
133
  const url = new URL(req.url);
112
134
  const callSessionId = url.searchParams.get('callSessionId');
113
135
 
136
+ // Parse the Twilio POST body to capture CallSid and caller metadata.
137
+ const formBody = new URLSearchParams(await req.text());
138
+ const callSid = formBody.get('CallSid');
139
+ const callerFrom = formBody.get('From') ?? '';
140
+ const callerTo = formBody.get('To') ?? '';
141
+
142
+ // ── Inbound mode: no callSessionId in query ─────────────────────
114
143
  if (!callSessionId) {
115
- log.warn('Voice webhook called without callSessionId');
116
- return new Response('Missing callSessionId', { status: 400 });
144
+ if (!callSid) {
145
+ log.warn('Inbound voice webhook called without CallSid');
146
+ return new Response('Missing CallSid', { status: 400 });
147
+ }
148
+
149
+ log.info({ callSid, from: callerFrom, to: callerTo, assistantId: forwardedAssistantId }, 'Inbound voice webhook — creating/reusing session');
150
+
151
+ const { session } = createInboundVoiceSession({
152
+ callSid,
153
+ fromNumber: callerFrom,
154
+ toNumber: callerTo,
155
+ assistantId: forwardedAssistantId,
156
+ });
157
+
158
+ return buildVoiceWebhookTwiml(session.id, session.assistantId ?? undefined, session.task);
117
159
  }
118
160
 
161
+ // ── Outbound mode: callSessionId is present ─────────────────────
119
162
  const session = getCallSession(callSessionId);
120
163
  if (!session) {
121
164
  log.warn({ callSessionId }, 'Voice webhook: call session not found');
@@ -127,16 +170,25 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
127
170
  return new Response('Call session is no longer active', { status: 410 });
128
171
  }
129
172
 
130
- // Parse the Twilio POST body to capture CallSid immediately, so status
131
- // callbacks (keyed by CallSid) can locate this session even if the
132
- // WebSocket relay hasn't been set up yet.
133
- const formBody = new URLSearchParams(await req.text());
134
- const callSid = formBody.get('CallSid');
173
+ // Capture CallSid immediately so status callbacks can locate this session
135
174
  if (callSid && callSid !== session.providerCallSid) {
136
175
  updateCallSession(callSessionId, { providerCallSid: callSid });
137
176
  log.info({ callSessionId, callSid }, 'Stored CallSid from voice webhook');
138
177
  }
139
178
 
179
+ return buildVoiceWebhookTwiml(callSessionId, session.assistantId ?? undefined, session.task);
180
+ }
181
+
182
+ /**
183
+ * Shared TwiML generation for both inbound and outbound voice webhooks.
184
+ * Resolves voice quality profile, relay URL, and welcome greeting,
185
+ * then returns a Response with the generated TwiML.
186
+ */
187
+ function buildVoiceWebhookTwiml(
188
+ callSessionId: string,
189
+ assistantId: string | undefined,
190
+ task: string | null,
191
+ ): Response {
140
192
  let profile = resolveVoiceQualityProfile(loadConfig());
141
193
 
142
194
  log.info({ callSessionId, mode: profile.mode, ttsProvider: profile.ttsProvider, voice: profile.voice }, 'Voice quality profile resolved');
@@ -175,7 +227,7 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
175
227
  });
176
228
  }
177
229
 
178
- const twilioConfig = getTwilioConfig();
230
+ const twilioConfig = getTwilioConfig(assistantId);
179
231
  let relayUrl: string;
180
232
  try {
181
233
  relayUrl = getTwilioRelayUrl(loadConfig());
@@ -183,7 +235,7 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
183
235
  // Fallback to legacy resolution when ingress is not configured
184
236
  relayUrl = resolveRelayUrl(twilioConfig.wssBaseUrl, twilioConfig.webhookBaseUrl);
185
237
  }
186
- const welcomeGreeting = process.env.CALL_WELCOME_GREETING ?? 'Hello, how can I help you today?';
238
+ const welcomeGreeting = buildWelcomeGreeting(task, getCallWelcomeGreeting());
187
239
 
188
240
  const twiml = generateTwiML(callSessionId, relayUrl, welcomeGreeting, profile);
189
241
 
@@ -270,6 +322,7 @@ export async function handleStatusCallback(req: Request): Promise<Response> {
270
322
  expirePendingQuestions(session.id);
271
323
 
272
324
  if (!wasTerminal) {
325
+ persistCallCompletionMessage(session.conversationId, session.id);
273
326
  fireCallCompletionNotifier(session.conversationId, session.id);
274
327
  }
275
328
  }
@@ -1,5 +1,5 @@
1
1
  export type CallStatus = 'initiated' | 'ringing' | 'in_progress' | 'waiting_on_user' | 'completed' | 'failed' | 'cancelled';
2
- export type CallEventType = 'call_started' | 'call_connected' | 'caller_spoke' | 'assistant_spoke' | 'user_question_asked' | 'user_answered' | 'user_instruction_relayed' | 'call_ended' | 'call_failed';
2
+ export type CallEventType = 'call_started' | 'call_connected' | 'caller_spoke' | 'assistant_spoke' | 'user_question_asked' | 'user_answered' | 'user_instruction_relayed' | 'call_ended' | 'call_failed' | 'callee_verification_started' | 'callee_verification_succeeded' | 'callee_verification_failed' | 'guardian_voice_verification_started' | 'guardian_voice_verification_succeeded' | 'guardian_voice_verification_failed';
3
3
  export type PendingQuestionStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
4
4
 
5
5
  export interface CallSession {
@@ -13,6 +13,8 @@ export interface CallSession {
13
13
  status: CallStatus;
14
14
  callerIdentityMode: string | null;
15
15
  callerIdentitySource: string | null;
16
+ assistantId: string | null;
17
+ initiatedFromConversationId?: string | null;
16
18
  startedAt: number | null;
17
19
  endedAt: number | null;
18
20
  lastError: string | null;
@@ -0,0 +1,25 @@
1
+ export const CHANNEL_IDS = [
2
+ 'telegram', 'sms', 'voice', 'macos', 'ios', 'whatsapp', 'slack', 'email',
3
+ ] as const;
4
+
5
+ export type ChannelId = (typeof CHANNEL_IDS)[number];
6
+
7
+ export function isChannelId(value: unknown): value is ChannelId {
8
+ return typeof value === 'string' && (CHANNEL_IDS as readonly string[]).includes(value);
9
+ }
10
+
11
+ export function parseChannelId(value: unknown): ChannelId | null {
12
+ return isChannelId(value) ? value : null;
13
+ }
14
+
15
+ export function assertChannelId(value: unknown, field: string): ChannelId {
16
+ if (!isChannelId(value)) {
17
+ throw new Error(`Invalid channel ID for ${field}: ${String(value)}. Valid values: ${CHANNEL_IDS.join(', ')}`);
18
+ }
19
+ return value;
20
+ }
21
+
22
+ export interface TurnChannelContext {
23
+ userMessageChannel: ChannelId;
24
+ assistantMessageChannel: ChannelId;
25
+ }