@vellumai/assistant 0.3.5 → 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 (486) 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 +26 -2
  164. package/src/config/env-registry.ts +162 -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 +156 -1137
  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 +217 -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 +54 -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 +315 -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 +60 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +226 -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 +96 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +19 -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 +10 -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 +160 -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 +119 -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/channel-approval-parser.ts +36 -2
  377. package/src/runtime/channel-approvals.ts +0 -21
  378. package/src/runtime/channel-guardian-service.ts +48 -7
  379. package/src/runtime/channel-readiness-service.ts +160 -34
  380. package/src/runtime/channel-readiness-types.ts +10 -4
  381. package/src/runtime/channel-retry-sweep.ts +184 -0
  382. package/src/runtime/guardian-context-resolver.ts +108 -0
  383. package/src/runtime/http-server.ts +275 -743
  384. package/src/runtime/http-types.ts +56 -3
  385. package/src/runtime/middleware/auth.ts +116 -0
  386. package/src/runtime/middleware/error-handler.ts +33 -0
  387. package/src/runtime/middleware/twilio-validation.ts +127 -0
  388. package/src/runtime/routes/app-routes.ts +1 -1
  389. package/src/runtime/routes/call-routes.ts +49 -6
  390. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  391. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  392. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  393. package/src/runtime/routes/channel-route-shared.ts +144 -0
  394. package/src/runtime/routes/channel-routes.ts +32 -1634
  395. package/src/runtime/routes/conversation-routes.ts +50 -7
  396. package/src/runtime/routes/events-routes.ts +2 -2
  397. package/src/runtime/routes/identity-routes.ts +126 -0
  398. package/src/runtime/routes/pairing-routes.ts +143 -0
  399. package/src/runtime/routes/run-routes.ts +15 -1
  400. package/src/runtime/run-orchestrator.ts +52 -34
  401. package/src/schedule/schedule-store.ts +36 -32
  402. package/src/schedule/scheduler.ts +3 -3
  403. package/src/security/encrypted-store.ts +5 -7
  404. package/src/security/oauth2.ts +45 -15
  405. package/src/security/parental-control-store.ts +183 -0
  406. package/src/security/secret-allowlist.ts +4 -3
  407. package/src/security/secret-scanner.ts +5 -5
  408. package/src/security/secure-keys.ts +1 -1
  409. package/src/security/token-manager.ts +3 -2
  410. package/src/services/vercel-deploy.ts +6 -2
  411. package/src/skills/tool-manifest.ts +3 -3
  412. package/src/skills/vellum-catalog-remote.ts +75 -16
  413. package/src/slack/slack-webhook.ts +2 -1
  414. package/src/swarm/orchestrator.ts +92 -1
  415. package/src/swarm/router-planner.ts +6 -9
  416. package/src/swarm/worker-prompts.ts +9 -12
  417. package/src/tasks/task-compiler.ts +19 -28
  418. package/src/tasks/task-runner.ts +1 -1
  419. package/src/tools/assets/search.ts +15 -14
  420. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  421. package/src/tools/browser/auto-navigate.ts +1 -0
  422. package/src/tools/browser/browser-execution.ts +10 -1
  423. package/src/tools/browser/browser-manager.ts +119 -4
  424. package/src/tools/browser/network-recorder.ts +5 -0
  425. package/src/tools/credentials/broker.ts +11 -2
  426. package/src/tools/credentials/metadata-store.ts +18 -14
  427. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  428. package/src/tools/credentials/vault.ts +49 -23
  429. package/src/tools/executor.ts +68 -9
  430. package/src/tools/host-terminal/cli-discover.ts +1 -1
  431. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  432. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  433. package/src/tools/network/script-proxy/server.ts +1 -1
  434. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  435. package/src/tools/network/web-fetch.ts +18 -2
  436. package/src/tools/network/web-search.ts +7 -3
  437. package/src/tools/reminder/reminder-store.ts +14 -15
  438. package/src/tools/schedule/create.ts +1 -0
  439. package/src/tools/schedule/list.ts +2 -1
  440. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  441. package/src/tools/skills/skill-script-runner.ts +24 -9
  442. package/src/tools/skills/skill-tool-factory.ts +1 -0
  443. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  444. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  445. package/src/tools/terminal/parser.ts +50 -0
  446. package/src/tools/watcher/delete.ts +6 -0
  447. package/src/tools/weather/service.ts +1 -1
  448. package/src/twitter/client.ts +190 -24
  449. package/src/twitter/session.ts +4 -3
  450. package/src/util/clipboard.ts +1 -1
  451. package/src/util/errors.ts +65 -8
  452. package/src/util/fs.ts +40 -0
  453. package/src/util/json.ts +10 -0
  454. package/src/util/log-redact.ts +189 -0
  455. package/src/util/logger.ts +19 -17
  456. package/src/util/object.ts +3 -0
  457. package/src/util/platform.ts +72 -365
  458. package/src/util/pricing.ts +1 -1
  459. package/src/util/promise-guard.ts +1 -1
  460. package/src/util/retry.ts +19 -0
  461. package/src/util/row-mapper.ts +79 -0
  462. package/src/util/silently.ts +21 -0
  463. package/src/watcher/engine.ts +5 -1
  464. package/src/watcher/provider-types.ts +20 -0
  465. package/src/watcher/providers/github.ts +156 -0
  466. package/src/watcher/providers/gmail.ts +1 -0
  467. package/src/watcher/providers/google-calendar.ts +1 -0
  468. package/src/watcher/providers/linear.ts +460 -0
  469. package/src/watcher/providers/slack.ts +1 -0
  470. package/src/work-items/work-item-runner.ts +1 -1
  471. package/src/workspace/git-service.ts +1 -1
  472. package/src/workspace/provider-commit-message-generator.ts +51 -22
  473. package/src/__tests__/call-bridge.test.ts +0 -517
  474. package/src/__tests__/session-process-bridge.test.ts +0 -244
  475. package/src/calls/call-bridge.ts +0 -168
  476. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  477. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  478. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  479. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  480. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  481. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  482. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  483. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  484. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  485. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  486. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -4,6 +4,11 @@
4
4
  * Parses inbound user text to determine whether it matches an approval,
5
5
  * rejection, or "approve always" intent. This module is transport-agnostic
6
6
  * and can be used by any channel adapter (Telegram, SMS, etc.).
7
+ *
8
+ * Both the standard and guardian approval flows now use the conversational
9
+ * approval engine as the primary classifier. This deterministic parser is
10
+ * retained only as a legacy fallback for when the conversational engine is
11
+ * not injected (i.e. approvalConversationGenerator is undefined).
7
12
  */
8
13
 
9
14
  import type { ApprovalAction, ApprovalDecisionResult } from './channel-approval-types.js';
@@ -44,6 +49,30 @@ const PHRASE_MAP = buildPhraseMap();
44
49
  // Public API
45
50
  // ---------------------------------------------------------------------------
46
51
 
52
+ // ---------------------------------------------------------------------------
53
+ // Run-reference tag extraction
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Pattern matching a `[ref:<runId>]` disambiguation tag appended to
58
+ * plain-text approval prompts. Guardians can include this tag in their
59
+ * reply so that `handleApprovalInterception` can resolve the correct
60
+ * pending approval when multiple approvals target the same chat.
61
+ */
62
+ const REF_TAG_RE = /\[ref:([^\]]+)\]/i;
63
+
64
+ /**
65
+ * Extract a run-reference tag from the text and return the cleaned
66
+ * decision text plus the extracted runId (if any).
67
+ */
68
+ function extractRefTag(text: string): { cleaned: string; runId?: string } {
69
+ const match = REF_TAG_RE.exec(text);
70
+ if (!match) return { cleaned: text };
71
+ const runId = match[1].trim();
72
+ const cleaned = text.replace(REF_TAG_RE, '').trim();
73
+ return { cleaned, runId: runId || undefined };
74
+ }
75
+
47
76
  /**
48
77
  * Parse a plain-text message into an approval decision.
49
78
  *
@@ -51,10 +80,15 @@ const PHRASE_MAP = buildPhraseMap();
51
80
  * of the known intent phrases, or `null` if it does not match.
52
81
  *
53
82
  * Matching is case-insensitive with leading/trailing whitespace trimmed.
83
+ *
84
+ * When the text contains a `[ref:<runId>]` tag (appended by the
85
+ * plain-text fallback path), the extracted runId is included in the
86
+ * result so the caller can disambiguate among multiple pending approvals.
54
87
  */
55
88
  export function parseApprovalDecision(text: string): ApprovalDecisionResult | null {
56
- const normalised = text.trim().toLowerCase();
89
+ const { cleaned, runId } = extractRefTag(text);
90
+ const normalised = cleaned.trim().toLowerCase();
57
91
  const action = PHRASE_MAP.get(normalised);
58
92
  if (!action) return null;
59
- return { action, source: 'plain_text' };
93
+ return { action, source: 'plain_text', ...(runId ? { runId } : {}) };
60
94
  }
@@ -7,7 +7,6 @@
7
7
  * 1. Detect pending confirmations for a conversation
8
8
  * 2. Build human-readable approval prompts with action buttons
9
9
  * 3. Consume user decisions and apply them to the underlying run
10
- * 4. Build reminder prompts when non-decision messages arrive
11
10
  */
12
11
 
13
12
  import { getPendingConfirmationsByConversation, getRun } from '../memory/runs-store.js';
@@ -206,23 +205,3 @@ const RICH_APPROVAL_CHANNELS: ReadonlySet<string> = new Set(['telegram']);
206
205
  export function channelSupportsRichApprovalUI(channel: string): boolean {
207
206
  return RICH_APPROVAL_CHANNELS.has(channel);
208
207
  }
209
-
210
- // ---------------------------------------------------------------------------
211
- // 6. Reminder prompt for non-decision messages
212
- // ---------------------------------------------------------------------------
213
-
214
- /**
215
- * Build a reminder prompt when the user sends a non-decision message while
216
- * an approval is pending. Reuses the original actions and fallback text
217
- * but prefixes the prompt text with a reminder.
218
- */
219
- export function buildReminderPrompt(
220
- pendingPrompt: ChannelApprovalPrompt,
221
- ): ChannelApprovalPrompt {
222
- const reminderPrefix = composeApprovalMessage({ scenario: 'reminder_prompt' });
223
- return {
224
- promptText: `${reminderPrefix}\n\n${pendingPrompt.promptText}`,
225
- actions: pendingPrompt.actions,
226
- plainTextFallback: `${reminderPrefix}\n\n${pendingPrompt.plainTextFallback}`,
227
- };
228
- }
@@ -12,14 +12,16 @@ import {
12
12
  createBinding,
13
13
  getActiveBinding,
14
14
  revokeBinding as storeRevokeBinding,
15
+ revokePendingChallenges as storeRevokePendingChallenges,
15
16
  createChallenge,
16
17
  findPendingChallengeByHash,
18
+ findPendingChallengeForChannel,
17
19
  consumeChallenge,
18
20
  getRateLimit,
19
21
  recordInvalidAttempt,
20
22
  resetRateLimit,
21
23
  } from '../memory/channel-guardian-store.js';
22
- import type { GuardianBinding } from '../memory/channel-guardian-store.js';
24
+ import type { GuardianBinding, VerificationChallenge } from '../memory/channel-guardian-store.js';
23
25
  import { composeApprovalMessage } from './approval-message-composer.js';
24
26
 
25
27
  // ---------------------------------------------------------------------------
@@ -66,19 +68,33 @@ function hashSecret(secret: string): string {
66
68
  // Service
67
69
  // ---------------------------------------------------------------------------
68
70
 
71
+ /**
72
+ * Generate a six-digit numeric secret for voice channel challenges.
73
+ * Uses cryptographic randomness to pick a number in [100000, 999999].
74
+ */
75
+ function generateVoiceSecret(): string {
76
+ const buf = randomBytes(4);
77
+ const num = buf.readUInt32BE(0);
78
+ // Map to the range [100000, 999999] (900000 possible values)
79
+ return String(100000 + (num % 900000));
80
+ }
81
+
69
82
  /**
70
83
  * Create a new verification challenge for a guardian candidate.
71
84
  *
72
- * Generates a random secret, hashes it (SHA-256), and stores the
73
- * challenge record with a 10-minute TTL. The raw secret is returned
74
- * so it can be displayed to the user; only the hash is persisted.
85
+ * For voice channels, generates a six-digit numeric secret that can be
86
+ * spoken aloud. For all other channels, generates a 32-byte hex secret.
87
+ *
88
+ * Hashes the secret (SHA-256) and stores the challenge record with a
89
+ * 10-minute TTL. The raw secret is returned so it can be displayed to
90
+ * the user; only the hash is persisted.
75
91
  */
76
92
  export function createVerificationChallenge(
77
93
  assistantId: string,
78
94
  channel: string,
79
95
  sessionId?: string,
80
96
  ): CreateChallengeResult {
81
- const secret = randomBytes(32).toString('hex');
97
+ const secret = channel === 'voice' ? generateVoiceSecret() : randomBytes(32).toString('hex');
82
98
  const challengeHash = hashSecret(secret);
83
99
  const challengeId = uuid();
84
100
  const expiresAt = Date.now() + CHALLENGE_TTL_MS;
@@ -102,6 +118,7 @@ export function createVerificationChallenge(
102
118
  ttlSeconds,
103
119
  instruction: composeApprovalMessage({
104
120
  scenario: 'guardian_verify_challenge_setup',
121
+ channel,
105
122
  verifyCommand,
106
123
  ttlSeconds,
107
124
  }),
@@ -131,7 +148,7 @@ export function validateAndConsumeChallenge(
131
148
  ): ValidateChallengeResult {
132
149
  // ── Rate-limit check ──
133
150
  const existing = getRateLimit(assistantId, channel, actorExternalUserId, actorChatId);
134
- if (existing && existing.lockedUntil !== null && Date.now() < existing.lockedUntil) {
151
+ if (existing && existing.lockedUntil != null && Date.now() < existing.lockedUntil) {
135
152
  // Use the same generic failure message to avoid leaking whether the
136
153
  // actor is rate-limited vs. the code is genuinely wrong.
137
154
  return {
@@ -224,7 +241,7 @@ export function isGuardian(
224
241
  externalUserId: string,
225
242
  ): boolean {
226
243
  const binding = getActiveBinding(assistantId, channel);
227
- return binding !== null && binding.guardianExternalUserId === externalUserId;
244
+ return binding != null && binding.guardianExternalUserId === externalUserId;
228
245
  }
229
246
 
230
247
  /**
@@ -236,3 +253,27 @@ export function revokeBinding(
236
253
  ): boolean {
237
254
  return storeRevokeBinding(assistantId, channel);
238
255
  }
256
+
257
+ /**
258
+ * Revoke all pending challenges for a given assistant and channel.
259
+ * Called when the user cancels verification so that stale challenges
260
+ * don't gate inbound calls.
261
+ */
262
+ export function revokePendingChallenges(
263
+ assistantId: string,
264
+ channel: string,
265
+ ): void {
266
+ storeRevokePendingChallenges(assistantId, channel);
267
+ }
268
+
269
+ /**
270
+ * Look up a pending (non-expired) verification challenge for a given
271
+ * assistant and channel. Used by relay setup to detect whether an active
272
+ * voice verification session exists.
273
+ */
274
+ export function getPendingChallenge(
275
+ assistantId: string,
276
+ channel: string,
277
+ ): VerificationChallenge | null {
278
+ return findPendingChallengeForChannel(assistantId, channel);
279
+ }
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ChannelId,
3
3
  ChannelProbe,
4
+ ChannelProbeContext,
4
5
  ChannelReadinessSnapshot,
5
6
  ReadinessCheckResult,
6
7
  } from './channel-readiness-types.js';
@@ -11,6 +12,7 @@ import {
11
12
  } from '../calls/twilio-rest.js';
12
13
  import { getSecureKey } from '../security/secure-keys.js';
13
14
  import { loadRawConfig } from '../config/loader.js';
15
+ import { getTwilioPhoneNumberEnv } from '../config/env.js';
14
16
 
15
17
  /** Remote check results are cached for 5 minutes before being considered stale. */
16
18
  export const REMOTE_TTL_MS = 5 * 60 * 1000;
@@ -29,9 +31,54 @@ function hasIngressConfigured(): boolean {
29
31
  }
30
32
  }
31
33
 
34
+ function getAssistantMappedPhoneNumber(
35
+ smsConfig: Record<string, unknown>,
36
+ assistantId?: string,
37
+ ): string | undefined {
38
+ if (!assistantId) return undefined;
39
+ const mapping = (smsConfig.assistantPhoneNumbers as Record<string, string> | undefined) ?? {};
40
+ return mapping[assistantId];
41
+ }
42
+
43
+ function hasAnyAssistantMappedPhoneNumber(smsConfig: Record<string, unknown>): boolean {
44
+ const mapping = (smsConfig.assistantPhoneNumbers as Record<string, string> | undefined) ?? {};
45
+ return Object.keys(mapping).length > 0;
46
+ }
47
+
48
+ function hasAnyAssistantMappedPhoneNumberSafe(): boolean {
49
+ try {
50
+ const raw = loadRawConfig();
51
+ const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
52
+ return hasAnyAssistantMappedPhoneNumber(smsConfig);
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Resolve SMS from-number with canonical precedence:
60
+ * assistant mapping -> env override -> config sms.phoneNumber -> secure key fallback.
61
+ */
62
+ function resolveSmsPhoneNumber(assistantId?: string): string {
63
+ try {
64
+ const raw = loadRawConfig();
65
+ const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
66
+ const mapped = getAssistantMappedPhoneNumber(smsConfig, assistantId);
67
+ return mapped
68
+ || getTwilioPhoneNumberEnv()
69
+ || (smsConfig.phoneNumber as string)
70
+ || getSecureKey('credential:twilio:phone_number')
71
+ || '';
72
+ } catch {
73
+ return getTwilioPhoneNumberEnv()
74
+ || getSecureKey('credential:twilio:phone_number')
75
+ || '';
76
+ }
77
+ }
78
+
32
79
  const smsProbe: ChannelProbe = {
33
80
  channel: 'sms',
34
- runLocalChecks(): ReadinessCheckResult[] {
81
+ runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[] {
35
82
  const results: ReadinessCheckResult[] = [];
36
83
 
37
84
  const hasCreds = hasTwilioCredentials();
@@ -43,23 +90,18 @@ const smsProbe: ChannelProbe = {
43
90
  : 'Twilio Account SID and Auth Token are not configured',
44
91
  });
45
92
 
46
- let hasPhone = !!process.env.TWILIO_PHONE_NUMBER;
47
- if (!hasPhone) {
48
- try {
49
- const raw = loadRawConfig();
50
- const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
51
- hasPhone = !!(smsConfig.phoneNumber as string);
52
- } catch { /* ignore */ }
53
- }
54
- if (!hasPhone) {
55
- hasPhone = !!getSecureKey('credential:twilio:phone_number');
56
- }
93
+ const resolvedNumber = resolveSmsPhoneNumber(context?.assistantId);
94
+ const hasPhone = !!resolvedNumber || (!context?.assistantId && hasAnyAssistantMappedPhoneNumberSafe());
57
95
  results.push({
58
96
  name: 'phone_number',
59
97
  passed: hasPhone,
60
98
  message: hasPhone
61
- ? 'Phone number is assigned'
62
- : 'No phone number assigned',
99
+ ? (context?.assistantId && !resolvedNumber
100
+ ? `Assistant ${context.assistantId} has no direct mapping, but SMS phone numbers are assigned`
101
+ : 'Phone number is assigned')
102
+ : (context?.assistantId
103
+ ? `No phone number assigned for assistant ${context.assistantId}`
104
+ : 'No phone number assigned'),
63
105
  });
64
106
 
65
107
  const hasIngress = hasIngressConfigured();
@@ -73,20 +115,14 @@ const smsProbe: ChannelProbe = {
73
115
 
74
116
  return results;
75
117
  },
76
- async runRemoteChecks(): Promise<ReadinessCheckResult[]> {
118
+ async runRemoteChecks(context?: ChannelProbeContext): Promise<ReadinessCheckResult[]> {
77
119
  if (!hasTwilioCredentials()) return [];
78
120
 
79
121
  const accountSid = getSecureKey('credential:twilio:account_sid');
80
122
  const authToken = getSecureKey('credential:twilio:auth_token');
81
123
  if (!accountSid || !authToken) return [];
82
124
 
83
- // Resolve the assigned phone number using fallback chain
84
- const raw = loadRawConfig();
85
- const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
86
- const phoneNumber = (smsConfig.phoneNumber as string)
87
- || getSecureKey('credential:twilio:phone_number')
88
- || process.env.TWILIO_PHONE_NUMBER
89
- || '';
125
+ const phoneNumber = resolveSmsPhoneNumber(context?.assistantId);
90
126
  if (!phoneNumber) return [];
91
127
 
92
128
  // Only toll-free numbers need verification checks
@@ -130,6 +166,73 @@ const smsProbe: ChannelProbe = {
130
166
  },
131
167
  };
132
168
 
169
+ // ── Voice Probe ─────────────────────────────────────────────────────────────
170
+
171
+ /**
172
+ * Resolve voice from-number with the same precedence as SMS:
173
+ * assistant mapping -> env override -> config sms.phoneNumber -> secure key fallback.
174
+ *
175
+ * Voice and SMS share the same Twilio phone number infrastructure, so the
176
+ * resolution logic is identical to resolveSmsPhoneNumber.
177
+ */
178
+ function resolveVoicePhoneNumber(assistantId?: string): string {
179
+ try {
180
+ const raw = loadRawConfig();
181
+ const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
182
+ const mapped = getAssistantMappedPhoneNumber(smsConfig, assistantId);
183
+ return mapped
184
+ || getTwilioPhoneNumberEnv()
185
+ || (smsConfig.phoneNumber as string)
186
+ || getSecureKey('credential:twilio:phone_number')
187
+ || '';
188
+ } catch {
189
+ return getTwilioPhoneNumberEnv()
190
+ || getSecureKey('credential:twilio:phone_number')
191
+ || '';
192
+ }
193
+ }
194
+
195
+ const voiceProbe: ChannelProbe = {
196
+ channel: 'voice',
197
+ runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[] {
198
+ const results: ReadinessCheckResult[] = [];
199
+
200
+ const hasCreds = hasTwilioCredentials();
201
+ results.push({
202
+ name: 'twilio_credentials',
203
+ passed: hasCreds,
204
+ message: hasCreds
205
+ ? 'Twilio credentials are configured'
206
+ : 'Twilio Account SID and Auth Token are not configured',
207
+ });
208
+
209
+ const resolvedNumber = resolveVoicePhoneNumber(context?.assistantId);
210
+ const hasPhone = !!resolvedNumber || (!context?.assistantId && hasAnyAssistantMappedPhoneNumberSafe());
211
+ results.push({
212
+ name: 'phone_number',
213
+ passed: hasPhone,
214
+ message: hasPhone
215
+ ? (context?.assistantId && !resolvedNumber
216
+ ? `Assistant ${context.assistantId} has no direct mapping, but phone numbers are assigned`
217
+ : 'Phone number is assigned for voice calls')
218
+ : (context?.assistantId
219
+ ? `No phone number assigned for assistant ${context.assistantId}`
220
+ : 'No phone number assigned for voice calls'),
221
+ });
222
+
223
+ const hasIngress = hasIngressConfigured();
224
+ results.push({
225
+ name: 'ingress',
226
+ passed: hasIngress,
227
+ message: hasIngress
228
+ ? 'Public ingress URL is configured'
229
+ : 'Public ingress URL is not configured or disabled',
230
+ });
231
+
232
+ return results;
233
+ },
234
+ };
235
+
133
236
  // ── Telegram Probe ──────────────────────────────────────────────────────────
134
237
 
135
238
  const telegramProbe: ChannelProbe = {
@@ -173,7 +276,7 @@ const telegramProbe: ChannelProbe = {
173
276
 
174
277
  export class ChannelReadinessService {
175
278
  private probes = new Map<ChannelId, ChannelProbe>();
176
- private snapshots = new Map<ChannelId, ChannelReadinessSnapshot>();
279
+ private snapshots = new Map<string, ChannelReadinessSnapshot>();
177
280
 
178
281
  registerProbe(probe: ChannelProbe): void {
179
282
  this.probes.set(probe.channel, probe);
@@ -187,6 +290,7 @@ export class ChannelReadinessService {
187
290
  async getReadiness(
188
291
  channel?: ChannelId,
189
292
  includeRemote?: boolean,
293
+ assistantId?: string,
190
294
  ): Promise<ChannelReadinessSnapshot[]> {
191
295
  const channels = channel
192
296
  ? [channel]
@@ -200,32 +304,40 @@ export class ChannelReadinessService {
200
304
  continue;
201
305
  }
202
306
 
203
- const localChecks = probe.runLocalChecks();
307
+ const probeContext: ChannelProbeContext = { assistantId };
308
+ const localChecks = probe.runLocalChecks(probeContext);
204
309
  let remoteChecks: ReadinessCheckResult[] | undefined;
205
310
  let remoteChecksFreshlyFetched = false;
311
+ let remoteChecksAffectReadiness = false;
206
312
  let stale = false;
207
313
 
208
- const cached = this.snapshots.get(ch);
314
+ const cacheKey = this.snapshotCacheKey(ch, assistantId);
315
+ const cached = this.snapshots.get(cacheKey);
209
316
  const now = Date.now();
210
317
 
211
318
  if (includeRemote && probe.runRemoteChecks) {
212
319
  const cacheExpired = !cached || !cached.remoteChecks || (now - cached.checkedAt) >= REMOTE_TTL_MS;
213
320
  if (cacheExpired) {
214
- remoteChecks = await probe.runRemoteChecks();
321
+ remoteChecks = await probe.runRemoteChecks(probeContext);
215
322
  remoteChecksFreshlyFetched = true;
323
+ remoteChecksAffectReadiness = true;
216
324
  } else {
217
325
  // Reuse cached remote checks
218
326
  remoteChecks = cached.remoteChecks;
327
+ remoteChecksAffectReadiness = true;
219
328
  }
220
329
  } else if (cached?.remoteChecks) {
221
- // Surface cached remote checks even when not explicitly requested,
222
- // but mark stale if TTL has elapsed
330
+ // Surface cached remote checks for visibility but never let them affect
331
+ // readiness when the caller explicitly opted out of remote checks.
223
332
  remoteChecks = cached.remoteChecks;
224
333
  stale = (now - cached.checkedAt) >= REMOTE_TTL_MS;
334
+ remoteChecksAffectReadiness = false;
225
335
  }
226
336
 
227
337
  const allLocalPassed = localChecks.every((c) => c.passed);
228
- const allRemotePassed = remoteChecks ? remoteChecks.every((c) => c.passed) : true;
338
+ const allRemotePassed = (remoteChecks && remoteChecksAffectReadiness)
339
+ ? remoteChecks.every((c) => c.passed)
340
+ : true;
229
341
  const ready = allLocalPassed && allRemotePassed;
230
342
 
231
343
  const reasons: Array<{ code: string; text: string }> = [];
@@ -234,7 +346,7 @@ export class ChannelReadinessService {
234
346
  reasons.push({ code: check.name, text: check.message });
235
347
  }
236
348
  }
237
- if (remoteChecks) {
349
+ if (remoteChecks && remoteChecksAffectReadiness) {
238
350
  for (const check of remoteChecks) {
239
351
  if (!check.passed) {
240
352
  reasons.push({ code: check.name, text: check.message });
@@ -252,7 +364,7 @@ export class ChannelReadinessService {
252
364
  remoteChecks,
253
365
  };
254
366
 
255
- this.snapshots.set(ch, snapshot);
367
+ this.snapshots.set(cacheKey, snapshot);
256
368
  results.push(snapshot);
257
369
  }
258
370
 
@@ -260,8 +372,17 @@ export class ChannelReadinessService {
260
372
  }
261
373
 
262
374
  /** Clear cached snapshot for a specific channel, forcing re-evaluation on next call. */
263
- invalidateChannel(channel: ChannelId): void {
264
- this.snapshots.delete(channel);
375
+ invalidateChannel(channel: ChannelId, assistantId?: string): void {
376
+ if (assistantId) {
377
+ this.snapshots.delete(this.snapshotCacheKey(channel, assistantId));
378
+ return;
379
+ }
380
+ const prefix = `${channel}::`;
381
+ for (const key of this.snapshots.keys()) {
382
+ if (key.startsWith(prefix)) {
383
+ this.snapshots.delete(key);
384
+ }
385
+ }
265
386
  }
266
387
 
267
388
  /** Clear all cached snapshots. */
@@ -279,14 +400,19 @@ export class ChannelReadinessService {
279
400
  localChecks: [],
280
401
  };
281
402
  }
403
+
404
+ private snapshotCacheKey(channel: ChannelId, assistantId?: string): string {
405
+ return `${channel}::${assistantId ?? '__default__'}`;
406
+ }
282
407
  }
283
408
 
284
409
  // ── Factory ─────────────────────────────────────────────────────────────────
285
410
 
286
- /** Create a service instance with built-in SMS and Telegram probes registered. */
411
+ /** Create a service instance with built-in SMS, Voice, and Telegram probes registered. */
287
412
  export function createReadinessService(): ChannelReadinessService {
288
413
  const service = new ChannelReadinessService();
289
414
  service.registerProbe(smsProbe);
415
+ service.registerProbe(voiceProbe);
290
416
  service.registerProbe(telegramProbe);
291
417
  return service;
292
418
  }
@@ -1,7 +1,8 @@
1
1
  // Channel readiness types — reusable primitive for all channels.
2
2
 
3
- /** Logical channel identifier. Well-known channels have literal types; custom channels use string. */
4
- export type ChannelId = 'sms' | 'telegram' | string;
3
+ import type { ChannelId } from '../channels/types.js';
4
+
5
+ export type { ChannelId };
5
6
 
6
7
  /** Result of a single readiness check (local or remote). */
7
8
  export interface ReadinessCheckResult {
@@ -21,9 +22,14 @@ export interface ChannelReadinessSnapshot {
21
22
  remoteChecks?: ReadinessCheckResult[];
22
23
  }
23
24
 
25
+ /** Optional probe context for assistant-scoped readiness checks. */
26
+ export interface ChannelProbeContext {
27
+ assistantId?: string;
28
+ }
29
+
24
30
  /** Probe interface that channels implement to provide readiness checks. */
25
31
  export interface ChannelProbe {
26
32
  channel: ChannelId;
27
- runLocalChecks(): ReadinessCheckResult[];
28
- runRemoteChecks?(): Promise<ReadinessCheckResult[]>;
33
+ runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[];
34
+ runRemoteChecks?(context?: ChannelProbeContext): Promise<ReadinessCheckResult[]>;
29
35
  }