@vellumai/assistant 0.3.5 → 0.3.7

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 (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -1,178 +1,20 @@
1
1
  /**
2
- * Natural language query tool for media events.
2
+ * Query media tool sends natural language queries against video
3
+ * analysis data (map output) via Claude for intelligent answers.
3
4
  *
4
- * Accepts a free-text query and optional asset_id, parses the query into
5
- * structured filters via simple keyword matching, then delegates to the
6
- * generic retrieval service. Domain-specific keyword mappings live here;
7
- * the retrieval layer remains fully generic.
5
+ * Replaces the old keyword-matching approach with an LLM-powered
6
+ * reduce/query step that can answer arbitrary questions about the
7
+ * video content.
8
8
  */
9
9
 
10
10
  import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
11
- import { retrieveEvents, type RetrievalFilters } from '../services/retrieval-service.js';
12
- import { getTrackingProfile, type CapabilityTier } from '../../../../memory/media-store.js';
13
- import { getCapabilitiesByTier, getCapabilityByName } from '../services/capability-registry.js';
11
+ import { reduceForAsset, type ReduceOptions, type ReduceResult } from '../services/reduce.js';
14
12
 
15
13
  // ---------------------------------------------------------------------------
16
- // NL query parsing
14
+ // Exported function for job handler use (one-shot merge mode)
17
15
  // ---------------------------------------------------------------------------
18
16
 
19
- /** Maps domain-specific keywords to canonical event type labels. */
20
- const EVENT_TYPE_KEYWORDS: Record<string, string> = {
21
- // Basketball
22
- turnover: 'turnover',
23
- turnovers: 'turnover',
24
- steal: 'turnover',
25
- steals: 'turnover',
26
- // Soccer / football
27
- goal: 'goal',
28
- goals: 'goal',
29
- score: 'goal',
30
- scores: 'goal',
31
- // Generic
32
- highlight: 'highlight',
33
- highlights: 'highlight',
34
- scene_change: 'scene_change',
35
- 'scene change': 'scene_change',
36
- 'scene changes': 'scene_change',
37
- foul: 'foul',
38
- fouls: 'foul',
39
- shot: 'shot',
40
- shots: 'shot',
41
- rebound: 'rebound',
42
- rebounds: 'rebound',
43
- assist: 'assist',
44
- assists: 'assist',
45
- block: 'block',
46
- blocks: 'block',
47
- transition: 'transition',
48
- transitions: 'transition',
49
- fast_break: 'fast_break',
50
- 'fast break': 'fast_break',
51
- 'fast breaks': 'fast_break',
52
- };
53
-
54
- /**
55
- * Extract a numeric limit from phrases like "top 5", "first 3", "last 10".
56
- * Returns undefined if no limit phrase is found.
57
- */
58
- function parseLimit(query: string): number | undefined {
59
- const match = query.match(/(?:top|first|last|show|find|get)\s+(\d+)(?!\s*min)/i);
60
- if (match) return parseInt(match[1], 10);
61
- // Also match trailing "N events/moments"
62
- const trailingMatch = query.match(/(\d+)\s+(?:events?|moments?|plays?|clips?)/i);
63
- if (trailingMatch) return parseInt(trailingMatch[1], 10);
64
- return undefined;
65
- }
66
-
67
- /**
68
- * Extract a confidence threshold from the query.
69
- */
70
- function parseConfidence(query: string): number | undefined {
71
- const lower = query.toLowerCase();
72
- if (lower.includes('high confidence') || lower.includes('very confident') || lower.includes('most confident')) {
73
- return 0.8;
74
- }
75
- if (lower.includes('confident') || lower.includes('medium confidence')) {
76
- return 0.5;
77
- }
78
- // Explicit threshold: "confidence > 0.7" or "confidence above 0.7"
79
- const explicitMatch = lower.match(/confidence\s*(?:>|above|over|>=)\s*([\d.]+)/);
80
- if (explicitMatch) return parseFloat(explicitMatch[1]);
81
- return undefined;
82
- }
83
-
84
- /**
85
- * Extract a time range from the query (returns seconds).
86
- * Supports phrases like "first half", "second half", "first N minutes",
87
- * "last N minutes", "between M and N minutes".
88
- */
89
- function parseTimeRange(query: string): { startTimeMin?: number; startTimeMax?: number } {
90
- const lower = query.toLowerCase();
91
-
92
- // "first half" / "second half" — these are relative and require knowing
93
- // total duration, which we don't have. Use reasonable game defaults:
94
- // assume ~48 min game → 2880s, half = 1440s
95
- if (lower.includes('first half')) {
96
- return { startTimeMin: 0, startTimeMax: 1440 };
97
- }
98
- if (lower.includes('second half')) {
99
- return { startTimeMin: 1440 };
100
- }
101
-
102
- // "first N minutes"
103
- const firstNMin = lower.match(/first\s+(\d+)\s*min/);
104
- if (firstNMin) {
105
- return { startTimeMin: 0, startTimeMax: parseInt(firstNMin[1], 10) * 60 };
106
- }
107
-
108
- // "last N minutes"
109
- // Without knowing total duration we can't compute this precisely,
110
- // so we skip it and let the retrieval return all results.
111
-
112
- // "between M and N minutes"
113
- const betweenMatch = lower.match(/between\s+(\d+)\s*(?:and|to|-)\s*(\d+)\s*min/);
114
- if (betweenMatch) {
115
- return {
116
- startTimeMin: parseInt(betweenMatch[1], 10) * 60,
117
- startTimeMax: parseInt(betweenMatch[2], 10) * 60,
118
- };
119
- }
120
-
121
- return {};
122
- }
123
-
124
- /**
125
- * Extract the event type from the query by matching known keywords.
126
- */
127
- function parseEventType(query: string): string | undefined {
128
- const lower = query.toLowerCase();
129
-
130
- // Check multi-word keywords first (longer matches win)
131
- const multiWordKeys = Object.keys(EVENT_TYPE_KEYWORDS)
132
- .filter((k) => k.includes(' ') || k.includes('_'))
133
- .sort((a, b) => b.length - a.length);
134
-
135
- for (const key of multiWordKeys) {
136
- if (lower.includes(key)) return EVENT_TYPE_KEYWORDS[key];
137
- }
138
-
139
- // Check single-word keywords
140
- const words = lower.replace(/[^\w\s]/g, '').split(/\s+/);
141
- for (const word of words) {
142
- if (EVENT_TYPE_KEYWORDS[word]) return EVENT_TYPE_KEYWORDS[word];
143
- }
144
-
145
- return undefined;
146
- }
147
-
148
- /**
149
- * Parse a natural language query into structured retrieval filters.
150
- */
151
- function parseQuery(query: string, assetId?: string): RetrievalFilters {
152
- const eventType = parseEventType(query);
153
- const limit = parseLimit(query);
154
- const minConfidence = parseConfidence(query);
155
- const timeRange = parseTimeRange(query);
156
-
157
- return {
158
- assetId,
159
- eventType,
160
- minConfidence,
161
- limit: limit ?? 10,
162
- sortBy: 'confidence',
163
- ...timeRange,
164
- };
165
- }
166
-
167
- // ---------------------------------------------------------------------------
168
- // Formatting
169
- // ---------------------------------------------------------------------------
170
-
171
- function formatTimestamp(seconds: number): string {
172
- const mins = Math.floor(seconds / 60);
173
- const secs = Math.floor(seconds % 60);
174
- return `${mins}:${secs.toString().padStart(2, '0')}`;
175
- }
17
+ export { reduceForAsset } from '../services/reduce.js';
176
18
 
177
19
  // ---------------------------------------------------------------------------
178
20
  // Tool entry point
@@ -180,121 +22,44 @@ function formatTimestamp(seconds: number): string {
180
22
 
181
23
  export async function run(
182
24
  input: Record<string, unknown>,
183
- _context: ToolContext,
25
+ context: ToolContext,
184
26
  ): Promise<ToolExecutionResult> {
185
- const query = input.query as string | undefined;
186
- if (!query) {
187
- return { content: 'query is required.', isError: true };
188
- }
189
-
190
27
  const assetId = input.asset_id as string | undefined;
191
28
  if (!assetId) {
192
- return { content: 'asset_id is required to scope the query to a specific media asset.', isError: true };
29
+ return { content: 'asset_id is required.', isError: true };
193
30
  }
194
31
 
195
- const filters = parseQuery(query, assetId);
196
-
197
- // Retrieve without limit so we can apply capability filtering first, then limit
198
- const userLimit = filters.limit;
199
- const result = retrieveEvents({ ...filters, limit: 0 });
200
-
201
- // Determine which capabilities are allowed based on the tracking profile
202
- const profile = getTrackingProfile(assetId);
203
-
204
- let allowedEventTypes: Set<string> | null = null;
205
- const tierForEventType = new Map<string, CapabilityTier>();
206
-
207
- if (profile) {
208
- // Use the explicit profile: only enabled capabilities pass
209
- allowedEventTypes = new Set<string>();
210
- for (const [capName, entry] of Object.entries(profile.capabilities)) {
211
- if (entry.enabled) {
212
- allowedEventTypes.add(capName);
213
- tierForEventType.set(capName, entry.tier);
214
- }
215
- }
216
- } else {
217
- // No profile: default to 'ready' tier capabilities only
218
- const readyCaps = getCapabilitiesByTier('ready');
219
- if (readyCaps.length > 0) {
220
- allowedEventTypes = new Set(readyCaps.map((c) => c.name));
221
- for (const cap of readyCaps) {
222
- tierForEventType.set(cap.name, 'ready');
223
- }
224
- }
225
- // If no capabilities are registered at all, allow everything (pass null)
226
- }
227
-
228
- // Filter events by allowed capabilities, then apply the user-requested limit
229
- let filteredEvents = result.events;
230
- if (allowedEventTypes !== null) {
231
- filteredEvents = filteredEvents.filter((e) => allowedEventTypes!.has(e.eventType));
232
- }
233
- if (userLimit && filteredEvents.length > userLimit) {
234
- filteredEvents = filteredEvents.slice(0, userLimit);
235
- }
236
-
237
- if (filteredEvents.length === 0) {
238
- const parts = ['No matching events found'];
239
- if (filters.eventType) parts.push(`for event type "${filters.eventType}"`);
240
- if (filters.minConfidence) parts.push(`with confidence >= ${filters.minConfidence}`);
241
- if (!profile) parts.push('(defaulting to ready-tier capabilities only)');
242
- parts.push(`in asset ${assetId}.`);
243
- return { content: parts.join(' '), isError: false };
32
+ const query = input.query as string | undefined;
33
+ if (!query) {
34
+ return { content: 'query is required.', isError: true };
244
35
  }
245
36
 
246
- const tierLabels: Record<string, string> = {
247
- ready: '[Ready]',
248
- beta: '[Beta]',
249
- experimental: '[Experimental]',
250
- };
37
+ const systemPrompt = input.system_prompt as string | undefined;
38
+ const model = input.model as string | undefined;
251
39
 
252
- const tierDisclaimers: Record<string, string> = {
253
- beta: 'Beta results may have accuracy gaps.',
254
- experimental: 'Experimental results are early-stage; expect noise.',
40
+ const options: ReduceOptions = {
41
+ query,
42
+ systemPrompt,
43
+ model,
255
44
  };
256
45
 
257
- const eventSummaries = filteredEvents.map((e, i) => {
258
- const tier = tierForEventType.get(e.eventType) ?? getCapabilityByName(e.eventType)?.tier;
259
- const tierLabel = tier ? tierLabels[tier] ?? `[${tier}]` : '[Ready]';
260
- const disclaimer = tier ? tierDisclaimers[tier] : undefined;
46
+ try {
47
+ const result: ReduceResult = await reduceForAsset(assetId, options, context.onOutput);
261
48
 
262
49
  return {
263
- rank: i + 1,
264
- id: e.id,
265
- eventType: e.eventType,
266
- tierLabel,
267
- ...(disclaimer ? { confidenceDisclaimer: disclaimer } : {}),
268
- timeRange: `${formatTimestamp(e.startTime)} – ${formatTimestamp(e.endTime)}`,
269
- startTime: e.startTime,
270
- endTime: e.endTime,
271
- confidence: Math.round(e.confidence * 100) / 100,
272
- reasons: e.reasons,
273
- metadata: e.metadata,
50
+ content: JSON.stringify({
51
+ query,
52
+ answer: result.answer,
53
+ model: result.model,
54
+ usage: {
55
+ inputTokens: result.inputTokens,
56
+ outputTokens: result.outputTokens,
57
+ },
58
+ }, null, 2),
59
+ isError: false,
274
60
  };
275
- });
276
-
277
- // Collect disclaimers for any non-ready tiers present in results
278
- const activeTiers = new Set(eventSummaries.map((e) => e.tierLabel));
279
- const disclaimers: string[] = [];
280
- if (activeTiers.has('[Beta]')) disclaimers.push(tierDisclaimers.beta);
281
- if (activeTiers.has('[Experimental]')) disclaimers.push(tierDisclaimers.experimental);
282
-
283
- return {
284
- content: JSON.stringify({
285
- query,
286
- parsedFilters: {
287
- eventType: filters.eventType ?? null,
288
- minConfidence: filters.minConfidence ?? null,
289
- limit: filters.limit,
290
- startTimeMin: filters.startTimeMin ?? null,
291
- startTimeMax: filters.startTimeMax ?? null,
292
- },
293
- trackingProfile: profile ? 'custom' : 'default (ready tier only)',
294
- ...(disclaimers.length > 0 ? { disclaimers } : {}),
295
- totalResults: eventSummaries.length,
296
- events: eventSummaries,
297
- }, null, 2),
298
- isError: false,
299
- };
61
+ } catch (err) {
62
+ const msg = (err as Error).message;
63
+ return { content: msg, isError: true };
64
+ }
300
65
  }
@@ -20,7 +20,12 @@ Gmail, Slack, and Telegram setup all require a publicly reachable URL for OAuth
20
20
  2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Install and load the **google-oauth-setup** skill (which depends on **public-ingress** for the redirect URI):
21
21
  - Call `vellum_skills_catalog` with `action: "install"` and `skill_id: "google-oauth-setup"`.
22
22
  - Then call `skill_load` with `skill: "google-oauth-setup"`.
23
- - Tell the user: *"Gmail isn't connected yet. I've loaded a setup guide that will walk you through creating Google credentials and connecting your account."*
23
+ - Tell the user Gmail isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
24
+ - **message:** "Ready to set up Gmail?"
25
+ - **detail:** "I'll open a browser where you sign in to Google, then automate everything else — creating a project, enabling APIs, and connecting your account. Takes 2-3 minutes and you can watch in the browser preview panel."
26
+ - **confirmLabel:** "Get Started"
27
+ - **cancelLabel:** "Not Now"
28
+ - If the user confirms, briefly acknowledge (e.g., "Setting up Gmail now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
24
29
  3. **If the user provides a client_id directly in chat:** Call `credential_store` with `action: "oauth2_connect"`, `service: "gmail"`, and `client_id: "<their value>"`. Include `client_secret` too if they provide one. Everything else is auto-filled.
25
30
 
26
31
  ### Slack
@@ -28,7 +33,12 @@ Gmail, Slack, and Telegram setup all require a publicly reachable URL for OAuth
28
33
  2. **If it fails because no client_id is found:** The user needs to create a Slack App first. Install and load the **slack-oauth-setup** skill (which depends on **public-ingress** for the redirect URI):
29
34
  - Call `vellum_skills_catalog` with `action: "install"` and `skill_id: "slack-oauth-setup"`.
30
35
  - Then call `skill_load` with `skill: "slack-oauth-setup"`.
31
- - Tell the user: *"Slack isn't connected yet. I've loaded a setup guide that will walk you through creating a Slack App and connecting your workspace."*
36
+ - Tell the user Slack isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
37
+ - **message:** "Ready to set up Slack?"
38
+ - **detail:** "I'll walk you through creating a Slack App and connecting your workspace. The process takes a few minutes, and I'll ask for your approval before each step."
39
+ - **confirmLabel:** "Get Started"
40
+ - **cancelLabel:** "Not Now"
41
+ - Wait for the user to confirm before proceeding with the setup guide. If they decline, acknowledge and let them know they can set it up later.
32
42
  3. **If the user provides client_id and client_secret directly in chat:** Call `credential_store` with `action: "oauth2_connect"`, `service: "slack"`, `client_id`, and `client_secret`. Everything else is auto-filled. Note: Slack always requires a client_secret.
33
43
 
34
44
  ### Telegram
@@ -7,12 +7,9 @@ import { memoryItems } from '../../../../memory/schema.js';
7
7
  import { enqueueMemoryJob } from '../../../../memory/jobs-store.js';
8
8
  import { extractStylePatterns } from '../../../../messaging/style-analyzer.js';
9
9
  import { truncate } from '../../../../util/truncate.js';
10
+ import { clampUnitInterval } from '../../../../memory/validation.js';
10
11
  import { resolveProvider, withProviderToken, ok, err } from './shared.js';
11
12
 
12
- function clamp(value: number, min: number, max: number): number {
13
- return Math.min(max, Math.max(min, value));
14
- }
15
-
16
13
  function upsertMemoryItem(opts: {
17
14
  kind: string;
18
15
  subject: string;
@@ -35,7 +32,7 @@ function upsertMemoryItem(opts: {
35
32
  .set({
36
33
  statement: opts.statement,
37
34
  status: 'active',
38
- importance: Math.max(existing.importance ?? 0, opts.importance),
35
+ importance: clampUnitInterval(Math.max(existing.importance ?? 0, opts.importance)),
39
36
  lastSeenAt: now,
40
37
  verificationState: 'assistant_inferred',
41
38
  })
@@ -51,7 +48,7 @@ function upsertMemoryItem(opts: {
51
48
  statement: opts.statement,
52
49
  status: 'active',
53
50
  confidence: 0.8,
54
- importance: opts.importance,
51
+ importance: clampUnitInterval(opts.importance),
55
52
  fingerprint,
56
53
  verificationState: 'assistant_inferred',
57
54
  scopeId: opts.scopeId,
@@ -90,7 +87,7 @@ export async function run(input: Record<string, unknown>, context: ToolContext):
90
87
 
91
88
  for (const pattern of result.stylePatterns) {
92
89
  const subject = `${provider.id} writing style: ${pattern.aspect}`;
93
- const importance = clamp(pattern.importance ?? 0.65, 0.55, 0.85);
90
+ const importance = clampUnitInterval(Math.min(0.85, Math.max(0.55, pattern.importance ?? 0.65)));
94
91
  upsertMemoryItem({ kind: 'style', subject, statement: pattern.summary, importance, scopeId });
95
92
  savedCount++;
96
93
  }
@@ -1,7 +1,7 @@
1
1
  import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
2
2
  import { resolveProvider, withProviderToken, ok, err } from './shared.js';
3
3
 
4
- export async function run(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
4
+ export async function run(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
5
5
  const platform = input.platform as string | undefined;
6
6
  const conversationId = input.conversation_id as string;
7
7
  const threadId = input.thread_id as string;
@@ -22,6 +22,7 @@ export async function run(input: Record<string, unknown>, _context: ToolContext)
22
22
  return withProviderToken(provider, async (token) => {
23
23
  const result = await provider.sendMessage(token, conversationId, text, {
24
24
  threadId,
25
+ assistantId: context.assistantId,
25
26
  });
26
27
 
27
28
  return ok(`Reply sent (ID: ${result.id}).`);
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: "Phone Calls"
3
- description: "Set up Twilio for outgoing phone calls and place AI-powered voice calls on behalf of the user"
3
+ description: "Set up Twilio for AI-powered voice calls both outgoing calls on behalf of the user and incoming calls where the assistant answers as a receptionist"
4
4
  user-invocable: true
5
5
  metadata: {"vellum": {"emoji": "📞", "requires": {"config": ["calls.enabled"]}}}
6
6
  includes: ["public-ingress"]
7
7
  ---
8
8
 
9
- You are helping the user set up and make outgoing phone calls via Twilio. This skill covers the full lifecycle: Twilio account setup, credential storage, public ingress configuration, enabling the calls feature, placing calls, and monitoring live transcripts.
9
+ You are helping the user set up and manage phone calls via Twilio. This skill covers the full lifecycle: Twilio account setup, credential storage, public ingress configuration, enabling the calls feature, placing outbound calls, receiving inbound calls, and monitoring live transcripts.
10
10
 
11
11
  ## Prerequisites — Shared Twilio Setup
12
12
 
@@ -21,7 +21,9 @@ If Twilio is already configured (check `twilio_config` with `action: "get"`), sk
21
21
 
22
22
  ## Overview
23
23
 
24
- The calling system uses Twilio's ConversationRelay to place outbound phone calls. Twilio works out of the box as the default voice provider. Optionally, you can enable ElevenLabs integration for higher-quality, more natural-sounding voices — but this is entirely optional.
24
+ The calling system uses Twilio's ConversationRelay for both **outbound** and **inbound** voice calls. Twilio works out of the box as the default voice provider. Optionally, you can enable ElevenLabs integration for higher-quality, more natural-sounding voices — but this is entirely optional.
25
+
26
+ ### Outbound calls
25
27
 
26
28
  When a call is placed:
27
29
 
@@ -31,6 +33,17 @@ When a call is placed:
31
33
  4. An LLM-driven orchestrator manages the conversation — receiving caller speech (transcribed by Deepgram), generating responses via Claude, and streaming text back for TTS playback
32
34
  5. The transcript is relayed live to the user's conversation thread
33
35
 
36
+ ### Inbound calls
37
+
38
+ When someone dials the assistant's Twilio phone number:
39
+
40
+ 1. Twilio sends a voice webhook to the gateway at `/webhooks/twilio/voice` (no `callSessionId` in the URL)
41
+ 2. The gateway resolves which assistant owns the dialed number via `resolveAssistantByPhoneNumber`, falling back to the standard routing chain (chat_id, user_id, default/reject). Unmapped numbers are rejected with TwiML `<Reject>`.
42
+ 3. The runtime creates a new session keyed by the Twilio CallSid (`createInboundVoiceSession`)
43
+ 4. Twilio opens a ConversationRelay WebSocket. The relay detects the call is inbound when `initiatedFromConversationId == null` and optionally gates the call behind **guardian voice verification** if a pending challenge exists.
44
+ 5. Once verified (or if no challenge is pending), the LLM orchestrator greets the caller in a receptionist style: "Hello, this is [user]'s assistant. How can I help you today?"
45
+ 6. The assistant converses naturally, using ASK_GUARDIAN to consult the user when needed, just like outbound calls.
46
+
34
47
  Three voice quality modes are available:
35
48
  - **`twilio_standard`** (default) — Fully supported. Standard Twilio TTS with Google voices. No extra setup required.
36
49
  - **`twilio_elevenlabs_tts`** — Fully supported. Uses ElevenLabs voices through Twilio ConversationRelay for more natural speech.
@@ -181,6 +194,25 @@ credential_store action=store service=twilio field=user_phone_number value=+1415
181
194
  | `calls.callerIdentity.allowPerCallOverride` | Whether per-call mode selection is allowed | `true` |
182
195
  | `calls.callerIdentity.userNumber` | Optional E.164 phone number for user-number mode (alternative to storing via `credential_store`) | *(empty)* |
183
196
 
197
+ ## DTMF Callee Verification
198
+
199
+ An optional verification step where the callee must enter a numeric code via their phone's keypad (DTMF tones) before the call proceeds. This ensures the intended person has answered the phone.
200
+
201
+ ### How it works
202
+
203
+ 1. When the call connects and DTMF verification is enabled, a random numeric code is generated (length configured by `calls.verification.codeLength`).
204
+ 2. The verification code is shared with the guardian in the initiating conversation so they know what code was issued.
205
+ 3. The AI voice agent speaks the code digit-by-digit to the callee and asks them to enter it on their keypad.
206
+ 4. The callee enters the code via DTMF (phone keypad tones).
207
+ 5. If the code matches, the call proceeds normally. If the code is incorrect, the agent may re-prompt or end the call depending on configuration.
208
+
209
+ ### Configuration
210
+
211
+ | Setting | Description | Default |
212
+ |---|---|---|
213
+ | `calls.verification.enabled` | Enable DTMF callee verification | `false` |
214
+ | `calls.verification.codeLength` | Number of digits in the verification code | `6` |
215
+
184
216
  ## Optional: Higher Quality Voice with ElevenLabs
185
217
 
186
218
  ElevenLabs integration is entirely optional. The standard Twilio-only setup works unchanged — this section is only relevant if you want to improve voice quality.
@@ -264,7 +296,7 @@ To go back to the default voice at any time:
264
296
  vellum config set calls.voice.mode twilio_standard
265
297
  ```
266
298
 
267
- ## Making Calls
299
+ ## Making Outbound Calls
268
300
 
269
301
  Use the `call_start` tool to place outbound calls. Every call requires:
270
302
  - **phone_number**: The number to call in E.164 format (e.g. `+14155551234`)
@@ -319,6 +351,30 @@ On Twilio trial accounts, outbound calls can ONLY be made to **verified numbers*
319
351
  1. Tell the user they need to verify the number at https://console.twilio.com/us1/develop/phone-numbers/manage/verified
320
352
  2. Or upgrade to a paid Twilio account to call any number
321
353
 
354
+ ## Receiving Inbound Calls
355
+
356
+ Once Twilio is configured and the assistant has a phone number, inbound calls work automatically. When someone dials the assistant's number:
357
+
358
+ 1. The gateway resolves the assistant by phone number and forwards to the runtime
359
+ 2. A new voice session is created, keyed by the Twilio CallSid
360
+ 3. The LLM-driven orchestrator answers in receptionist mode — greeting the caller warmly and asking how it can help
361
+ 4. The conversation proceeds naturally, with ASK_GUARDIAN dispatches to consult the user when needed
362
+
363
+ No additional configuration is needed beyond the standard Twilio setup (Steps 1-5 above). As long as `calls.enabled` is `true` and the phone number has been provisioned/assigned, inbound calls are handled automatically.
364
+
365
+ ### Guardian voice verification for inbound calls
366
+
367
+ Optionally, the user can require callers to verify themselves by entering a six-digit code before the call proceeds. This is managed through the **channel guardian verification** system:
368
+
369
+ 1. The user initiates a verification challenge from the desktop UI for the `voice` channel
370
+ 2. A six-digit code is generated and shown to the user
371
+ 3. When the next inbound call arrives, the relay server detects the pending challenge and prompts the caller: "Please enter your six-digit verification code using your keypad, or speak the digits now."
372
+ 4. The caller enters the code via DTMF (keypad) or by speaking the digits
373
+ 5. If the code matches, a guardian binding is created and the call proceeds normally
374
+ 6. If verification fails after 3 attempts, the call ends with "Verification failed. Goodbye."
375
+
376
+ This feature is separate from the outbound DTMF callee verification. It uses the `ChannelGuardianService` challenge system rather than the per-call verification config.
377
+
322
378
  ## Live Call Monitoring
323
379
 
324
380
  ### Showing the live transcript
@@ -343,37 +399,31 @@ By default, always show the live transcript of the call as it happens. When a ca
343
399
 
344
400
  ### Interacting with a live call
345
401
 
346
- During an active call, the user can type messages in the chat thread to interact with the AI voice agent in real time. Messages are automatically routed to the call via the call bridge, which decides how to handle them based on the call's current state:
347
-
348
- #### Mode 1: Answering questions
402
+ During an active call, the user can interact with the AI voice agent via the HTTP API endpoints:
349
403
 
350
- When the AI voice agent encounters something it needs user input for, a **pending question** appears in the chat. The call status changes to `waiting_on_user`.
404
+ #### Answering questions
351
405
 
352
- 1. A **pending question** appears in `call_status` output
353
- 2. Present the question prominently to the user:
406
+ When the AI voice agent encounters something it needs user input for, it dispatches an **ASK_GUARDIAN** request to all configured guardian channels (mac desktop, Telegram, SMS). The call status changes to `waiting_on_user`.
354
407
 
355
- ```
356
- The person on the call asked something the assistant needs your help with:
357
- "They're asking if you'd prefer the smoking or non-smoking section?"
358
- ```
359
-
360
- 3. The user replies directly in the chat — since there is a pending question, the reply is automatically routed as an **answer** to the AI voice agent
361
- 4. The AI voice agent receives the answer and continues the conversation naturally
408
+ 1. The question is delivered simultaneously to every configured channel. The first channel to respond wins (first-response-wins semantics) -- once one channel provides an answer, the other channels receive a "already answered" notice.
409
+ 2. On the mac desktop, a guardian request thread is created with the question. On Telegram/SMS, the question text and a request code are delivered via the gateway.
410
+ 3. If DTMF callee verification is enabled, the callee must enter a verification code before the call proceeds (see the **DTMF Callee Verification** section above).
411
+ 4. The guardian provides an answer through whichever channel they prefer. The answer is routed to the AI voice agent, which continues the conversation naturally.
362
412
 
363
413
  **Important:** Respond to pending questions quickly. There is a consultation timeout (default: 2 minutes). If no answer is provided in time, the AI voice agent will move on.
364
414
 
365
- #### Mode 2: Steering with instructions
415
+ #### Steering with instructions
366
416
 
367
- When there is **no pending question** but the call is still active, any message the user types in the chat is treated as a **steering instruction**. This lets the user proactively guide the call in real time — for example:
417
+ When there is **no pending question** but the call is still active, the user can send steering instructions via the HTTP API (`POST /v1/calls/:id/instruction`) to proactively guide the call in real time — for example:
368
418
 
369
419
  - "Ask them about their cancellation policy too"
370
420
  - "Wrap up the call, we have what we need"
371
421
  - "Switch to asking about weekend availability instead"
372
422
  - "Be more assertive about getting a discount"
373
423
 
374
- The instruction is injected into the AI voice agent's conversation context as high-priority input, and the agent adjusts its behavior accordingly. A confirmation message ("Instruction relayed to active call.") appears in the chat thread.
424
+ The instruction is injected into the AI voice agent's conversation context as high-priority input, and the agent adjusts its behavior accordingly.
375
425
 
376
- **The user does not need to do anything special** just type a message. The system automatically determines whether it should be an answer or an instruction based on whether a question is pending.
426
+ **Note:** Mid-call steering via the desktop chat thread is no longer supported. The desktop thread only receives pointer/status messages about the call. To steer a call, use the HTTP API endpoints directly.
377
427
 
378
428
  ### Call status values
379
429
 
@@ -414,6 +464,7 @@ The `context` field is powerful — use it to give the agent background that hel
414
464
 
415
465
  ### Things the AI voice agent handles well
416
466
 
467
+ **Outbound calls:**
417
468
  - Making reservations and appointments
418
469
  - Checking business hours, availability, or pricing
419
470
  - Confirming or rescheduling existing appointments
@@ -421,6 +472,12 @@ The `context` field is powerful — use it to give the agent background that hel
421
472
  - Simple customer service interactions
422
473
  - Leaving voicemails (it will speak the message if voicemail picks up)
423
474
 
475
+ **Inbound calls:**
476
+ - Answering as a receptionist and routing caller requests to the user via ASK_GUARDIAN
477
+ - Taking messages when the user is unavailable
478
+ - Answering questions the assistant already knows from memory/context
479
+ - Screening calls with guardian voice verification
480
+
424
481
  ### Things to be aware of
425
482
 
426
483
  - Calls have a maximum duration (configurable via `calls.maxDurationSeconds`, default: 1 hour)
@@ -470,6 +527,14 @@ vellum config set calls.disclosure.text "Just so you know, this is an assistant
470
527
  vellum config set calls.userConsultTimeoutSeconds 300
471
528
  ```
472
529
 
530
+ ## Accepted Regressions
531
+
532
+ The following behavioral changes were introduced with the cross-channel guardian architecture (voice-cross-guardian):
533
+
534
+ - **No more mid-call steering via desktop chat.** The call bridge that routed desktop chat messages to the active call has been removed. The desktop chat thread only receives pointer/status messages about the call. To steer a call, use the HTTP API endpoints directly (`POST /v1/calls/:id/instruction`).
535
+ - **No live transcript mirror in the initiating chat.** The initiating desktop conversation no longer receives a real-time mirror of the call transcript. The initiating chat only gets pointer/status messages (call started, call ended, question asked, etc.).
536
+ - **Guardian questions are dispatched cross-channel.** Rather than appearing only in the initiating desktop thread, ASK_GUARDIAN questions are now dispatched to all configured guardian channels (mac desktop, Telegram, SMS) simultaneously. The first channel to respond wins.
537
+
473
538
  ## Troubleshooting
474
539
 
475
540
  ### "Twilio credentials not configured"
@@ -0,0 +1,14 @@
1
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="16" height="16" fill="#ffffff"/>
3
+ <rect x="2" y="2" width="12" height="12" fill="#000000"/>
4
+ <rect x="4" y="5" width="2" height="1" fill="#ffffff"/>
5
+ <rect x="5" y="4" width="1" height="3" fill="#ffffff"/>
6
+ <rect x="6" y="5" width="2" height="1" fill="#ffffff"/>
7
+ <rect x="10" y="5" width="2" height="1" fill="#ffffff"/>
8
+ <rect x="11" y="4" width="1" height="3" fill="#ffffff"/>
9
+ <rect x="12" y="5" width="2" height="1" fill="#ffffff"/>
10
+ <rect x="4" y="9" width="1" height="2" fill="#ffffff"/>
11
+ <rect x="5" y="8" width="6" height="1" fill="#ffffff"/>
12
+ <rect x="11" y="9" width="1" height="2" fill="#ffffff"/>
13
+ <rect x="5" y="11" width="6" height="1" fill="#ffffff"/>
14
+ </svg>