@vellumai/assistant 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,214 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { runApprovalConversationTurn } from '../runtime/approval-conversation-turn.js';
3
+ import type {
4
+ ApprovalConversationContext,
5
+ ApprovalConversationGenerator,
6
+ ApprovalConversationResult,
7
+ } from '../runtime/http-types.js';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Helpers
11
+ // ---------------------------------------------------------------------------
12
+
13
+ function makeContext(overrides: Partial<ApprovalConversationContext> = {}): ApprovalConversationContext {
14
+ return {
15
+ toolName: 'execute_shell',
16
+ allowedActions: ['approve_once', 'approve_always', 'reject'],
17
+ role: 'guardian',
18
+ pendingApprovals: [{ runId: 'run-1', toolName: 'execute_shell' }],
19
+ userMessage: 'yes, go ahead',
20
+ ...overrides,
21
+ };
22
+ }
23
+
24
+ function makeGenerator(result: ApprovalConversationResult): ApprovalConversationGenerator {
25
+ return async () => result;
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Tests
30
+ // ---------------------------------------------------------------------------
31
+
32
+ describe('runApprovalConversationTurn', () => {
33
+ test('successful keep_pending response (non-decision message)', async () => {
34
+ const result = await runApprovalConversationTurn(
35
+ makeContext({ userMessage: 'what does this tool do?' }),
36
+ makeGenerator({
37
+ disposition: 'keep_pending',
38
+ replyText: 'This tool runs shell commands. Would you like to approve it?',
39
+ }),
40
+ );
41
+ expect(result.disposition).toBe('keep_pending');
42
+ expect(result.replyText).toBe('This tool runs shell commands. Would you like to approve it?');
43
+ expect(result.targetRunId).toBeUndefined();
44
+ });
45
+
46
+ test('successful approve_once response', async () => {
47
+ const result = await runApprovalConversationTurn(
48
+ makeContext(),
49
+ makeGenerator({
50
+ disposition: 'approve_once',
51
+ replyText: 'Approved! Running the command now.',
52
+ targetRunId: 'run-1',
53
+ }),
54
+ );
55
+ expect(result.disposition).toBe('approve_once');
56
+ expect(result.replyText).toBe('Approved! Running the command now.');
57
+ expect(result.targetRunId).toBe('run-1');
58
+ });
59
+
60
+ test('successful reject response', async () => {
61
+ const result = await runApprovalConversationTurn(
62
+ makeContext(),
63
+ makeGenerator({
64
+ disposition: 'reject',
65
+ replyText: 'Request denied.',
66
+ targetRunId: 'run-1',
67
+ }),
68
+ );
69
+ expect(result.disposition).toBe('reject');
70
+ expect(result.replyText).toBe('Request denied.');
71
+ });
72
+
73
+ test('fail-closed on generator throwing an error', async () => {
74
+ const throwingGenerator: ApprovalConversationGenerator = async () => {
75
+ throw new Error('provider timeout');
76
+ };
77
+ const result = await runApprovalConversationTurn(makeContext(), throwingGenerator);
78
+ expect(result.disposition).toBe('keep_pending');
79
+ expect(result.replyText).toContain("couldn't process");
80
+ });
81
+
82
+ test('fail-closed on generator returning malformed output', async () => {
83
+ const malformedGenerator: ApprovalConversationGenerator = async () => {
84
+ // Return an object missing the required replyText
85
+ return { disposition: 'approve_once', replyText: '' } as ApprovalConversationResult;
86
+ };
87
+ const result = await runApprovalConversationTurn(makeContext(), malformedGenerator);
88
+ expect(result.disposition).toBe('keep_pending');
89
+ expect(result.replyText).toContain("couldn't process");
90
+ });
91
+
92
+ test('fail-closed on invalid disposition', async () => {
93
+ const badDisposition: ApprovalConversationGenerator = async () => {
94
+ return { disposition: 'yolo' as 'approve_once', replyText: 'Sure!' };
95
+ };
96
+ const result = await runApprovalConversationTurn(makeContext(), badDisposition);
97
+ expect(result.disposition).toBe('keep_pending');
98
+ expect(result.replyText).toContain("couldn't process");
99
+ });
100
+
101
+ test('fail-closed when disposition is not in allowedActions', async () => {
102
+ // Context only allows approve_once and reject (no approve_always)
103
+ const restrictedContext = makeContext({
104
+ allowedActions: ['approve_once', 'reject'],
105
+ });
106
+
107
+ const result = await runApprovalConversationTurn(
108
+ restrictedContext,
109
+ makeGenerator({
110
+ disposition: 'approve_always',
111
+ replyText: 'Approved permanently!',
112
+ targetRunId: 'run-1',
113
+ }),
114
+ );
115
+ expect(result.disposition).toBe('keep_pending');
116
+ expect(result.replyText).toContain("couldn't process");
117
+ });
118
+
119
+ test('keep_pending is always allowed regardless of allowedActions', async () => {
120
+ const restrictedContext = makeContext({
121
+ allowedActions: ['approve_once', 'reject'],
122
+ });
123
+
124
+ const result = await runApprovalConversationTurn(
125
+ restrictedContext,
126
+ makeGenerator({
127
+ disposition: 'keep_pending',
128
+ replyText: 'Can you tell me more about this request?',
129
+ }),
130
+ );
131
+ expect(result.disposition).toBe('keep_pending');
132
+ expect(result.replyText).toBe('Can you tell me more about this request?');
133
+ });
134
+
135
+ test('fail-closed when single pending approval and hallucinated targetRunId', async () => {
136
+ // Only one pending approval, but model returns a non-matching targetRunId
137
+ const result = await runApprovalConversationTurn(
138
+ makeContext({
139
+ pendingApprovals: [{ runId: 'run-1', toolName: 'execute_shell' }],
140
+ }),
141
+ makeGenerator({
142
+ disposition: 'approve_once',
143
+ replyText: 'Approved!',
144
+ targetRunId: 'run-nonexistent',
145
+ }),
146
+ );
147
+ expect(result.disposition).toBe('keep_pending');
148
+ expect(result.replyText).toContain("couldn't process");
149
+ });
150
+
151
+ test('fail-closed when targetRunId does not match any pending approval', async () => {
152
+ const contextWithMultiple = makeContext({
153
+ pendingApprovals: [
154
+ { runId: 'run-1', toolName: 'execute_shell' },
155
+ { runId: 'run-2', toolName: 'file_write' },
156
+ ],
157
+ });
158
+
159
+ // Hallucinated run ID that doesn't match any pending approval
160
+ const result = await runApprovalConversationTurn(
161
+ contextWithMultiple,
162
+ makeGenerator({
163
+ disposition: 'approve_once',
164
+ replyText: 'Approved!',
165
+ targetRunId: 'run-nonexistent',
166
+ }),
167
+ );
168
+ expect(result.disposition).toBe('keep_pending');
169
+ expect(result.replyText).toContain("couldn't process");
170
+ });
171
+
172
+ test('targetRunId validation when multiple pending approvals', async () => {
173
+ const contextWithMultiple = makeContext({
174
+ pendingApprovals: [
175
+ { runId: 'run-1', toolName: 'execute_shell' },
176
+ { runId: 'run-2', toolName: 'file_write' },
177
+ ],
178
+ });
179
+
180
+ // Decision-bearing disposition without targetRunId should fail-close
181
+ const resultWithoutTarget = await runApprovalConversationTurn(
182
+ contextWithMultiple,
183
+ makeGenerator({
184
+ disposition: 'approve_once',
185
+ replyText: 'Approved!',
186
+ // no targetRunId
187
+ }),
188
+ );
189
+ expect(resultWithoutTarget.disposition).toBe('keep_pending');
190
+ expect(resultWithoutTarget.replyText).toContain("couldn't process");
191
+
192
+ // Decision-bearing disposition with targetRunId should succeed
193
+ const resultWithTarget = await runApprovalConversationTurn(
194
+ contextWithMultiple,
195
+ makeGenerator({
196
+ disposition: 'approve_once',
197
+ replyText: 'Approved!',
198
+ targetRunId: 'run-1',
199
+ }),
200
+ );
201
+ expect(resultWithTarget.disposition).toBe('approve_once');
202
+ expect(resultWithTarget.targetRunId).toBe('run-1');
203
+
204
+ // Non-decision disposition without targetRunId should pass through fine
205
+ const resultKeepPending = await runApprovalConversationTurn(
206
+ contextWithMultiple,
207
+ makeGenerator({
208
+ disposition: 'keep_pending',
209
+ replyText: 'Which request would you like to approve?',
210
+ }),
211
+ );
212
+ expect(resultKeepPending.disposition).toBe('keep_pending');
213
+ });
214
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Guard test: ensures no production hard-coded approval lifecycle copy creeps
3
+ * back into route/service files outside of the composer module.
4
+ *
5
+ * The composer file (`approval-message-composer.ts`) is intentionally excluded
6
+ * since that is where deterministic fallback copy legitimately lives.
7
+ */
8
+
9
+ import { readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { describe, test, expect } from 'bun:test';
12
+
13
+ const SCANNED_FILES = [
14
+ 'runtime/channel-approvals.ts',
15
+ 'runtime/routes/channel-routes.ts',
16
+ 'runtime/channel-guardian-service.ts',
17
+ ];
18
+
19
+ const BANNED_PATTERNS: { pattern: RegExp; description: string }[] = [
20
+ { pattern: /The assistant wants to use/i, description: 'old standard prompt' },
21
+ { pattern: /Do you want to allow this/i, description: 'old approval question' },
22
+ { pattern: /['"`]I'm still waiting/i, description: 'old reminder prefix (string literal)' },
23
+ { pattern: /['"`].*is requesting to run/i, description: 'old guardian prompt (string literal)' },
24
+ { pattern: /['"`]Sent to guardian/i, description: 'old forwarding notice (string literal)' },
25
+ { pattern: /['"`]Guardian verified successfully/i, description: 'old verify success (string literal)' },
26
+ { pattern: /['"`]Verification failed/i, description: 'old verify failure (string literal)' },
27
+ { pattern: /['"`]Your request has been sent/i, description: 'old request forwarded notice (string literal)' },
28
+ { pattern: /['"`]No guardian is configured/i, description: 'old no-binding notice (string literal)' },
29
+ ];
30
+
31
+ describe('approval hardcoded copy guard', () => {
32
+ for (const file of SCANNED_FILES) {
33
+ test(`${file} does not contain banned approval copy literals`, () => {
34
+ const content = readFileSync(join(__dirname, '..', file), 'utf-8');
35
+ for (const { pattern, description: _description } of BANNED_PATTERNS) {
36
+ const match = content.match(pattern);
37
+ expect(match).toBeNull();
38
+ }
39
+ });
40
+ }
41
+ });
@@ -0,0 +1,253 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ composeApprovalMessage,
4
+ getFallbackMessage,
5
+ } from '../runtime/approval-message-composer.js';
6
+ import type {
7
+ ApprovalMessageScenario,
8
+ ApprovalMessageContext,
9
+ } from '../runtime/approval-message-composer.js';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Every scenario must produce a non-empty string
13
+ // ---------------------------------------------------------------------------
14
+
15
+ const ALL_SCENARIOS: ApprovalMessageScenario[] = [
16
+ 'standard_prompt',
17
+ 'guardian_prompt',
18
+ 'reminder_prompt',
19
+ 'guardian_delivery_failed',
20
+ 'guardian_request_forwarded',
21
+ 'guardian_disambiguation',
22
+ 'guardian_identity_mismatch',
23
+ 'request_pending_guardian',
24
+ 'guardian_decision_outcome',
25
+ 'guardian_expired_requester',
26
+ 'guardian_expired_guardian',
27
+ 'guardian_verify_success',
28
+ 'guardian_verify_failed',
29
+ 'guardian_verify_challenge_setup',
30
+ 'guardian_verify_status_bound',
31
+ 'guardian_verify_status_unbound',
32
+ 'guardian_deny_no_identity',
33
+ 'guardian_deny_no_binding',
34
+ ];
35
+
36
+ describe('approval-message-composer', () => {
37
+ // -----------------------------------------------------------------------
38
+ // Fallback messages — every scenario produces non-empty output
39
+ // -----------------------------------------------------------------------
40
+
41
+ describe('getFallbackMessage', () => {
42
+ for (const scenario of ALL_SCENARIOS) {
43
+ test(`scenario "${scenario}" produces a non-empty string`, () => {
44
+ const msg = getFallbackMessage({ scenario });
45
+ expect(typeof msg).toBe('string');
46
+ expect(msg.trim().length).toBeGreaterThan(0);
47
+ });
48
+ }
49
+
50
+ test('standard_prompt includes toolName when provided', () => {
51
+ const msg = getFallbackMessage({
52
+ scenario: 'standard_prompt',
53
+ toolName: 'execute_shell',
54
+ });
55
+ expect(msg).toContain('execute_shell');
56
+ });
57
+
58
+ test('guardian_prompt includes requester identifier and toolName', () => {
59
+ const msg = getFallbackMessage({
60
+ scenario: 'guardian_prompt',
61
+ toolName: 'write_file',
62
+ requesterIdentifier: 'alice',
63
+ });
64
+ expect(msg).toContain('alice');
65
+ expect(msg).toContain('write_file');
66
+ });
67
+
68
+ test('guardian_delivery_failed includes toolName when provided', () => {
69
+ const msg = getFallbackMessage({
70
+ scenario: 'guardian_delivery_failed',
71
+ toolName: 'execute_shell',
72
+ });
73
+ expect(msg).toContain('execute_shell');
74
+ });
75
+
76
+ test('guardian_request_forwarded includes toolName', () => {
77
+ const msg = getFallbackMessage({
78
+ scenario: 'guardian_request_forwarded',
79
+ toolName: 'execute_shell',
80
+ });
81
+ expect(msg).toContain('execute_shell');
82
+ });
83
+
84
+ test('guardian_disambiguation includes pendingCount', () => {
85
+ const msg = getFallbackMessage({
86
+ scenario: 'guardian_disambiguation',
87
+ pendingCount: 3,
88
+ });
89
+ expect(msg).toContain('3');
90
+ });
91
+
92
+ test('guardian_decision_outcome includes decision and toolName', () => {
93
+ const msg = getFallbackMessage({
94
+ scenario: 'guardian_decision_outcome',
95
+ decision: 'approved',
96
+ toolName: 'read_file',
97
+ });
98
+ expect(msg).toContain('approved');
99
+ expect(msg).toContain('read_file');
100
+ });
101
+
102
+ test('guardian_expired_requester includes toolName', () => {
103
+ const msg = getFallbackMessage({
104
+ scenario: 'guardian_expired_requester',
105
+ toolName: 'deploy',
106
+ });
107
+ expect(msg).toContain('deploy');
108
+ expect(msg).toContain('expired');
109
+ });
110
+
111
+ test('guardian_expired_guardian includes requester and toolName', () => {
112
+ const msg = getFallbackMessage({
113
+ scenario: 'guardian_expired_guardian',
114
+ requesterIdentifier: 'bob',
115
+ toolName: 'delete_file',
116
+ });
117
+ expect(msg).toContain('bob');
118
+ expect(msg).toContain('delete_file');
119
+ });
120
+
121
+ test('guardian_verify_failed includes failureReason', () => {
122
+ const msg = getFallbackMessage({
123
+ scenario: 'guardian_verify_failed',
124
+ failureReason: 'Code did not match.',
125
+ });
126
+ expect(msg).toContain('Code did not match.');
127
+ });
128
+
129
+ test('guardian_verify_challenge_setup includes verifyCommand and ttlSeconds', () => {
130
+ const msg = getFallbackMessage({
131
+ scenario: 'guardian_verify_challenge_setup',
132
+ verifyCommand: '/verify abc123',
133
+ ttlSeconds: 30,
134
+ });
135
+ expect(msg).toContain('/verify abc123');
136
+ expect(msg).toContain('30');
137
+ });
138
+ });
139
+
140
+ // -----------------------------------------------------------------------
141
+ // composeApprovalMessage — layered source selection
142
+ // -----------------------------------------------------------------------
143
+
144
+ describe('composeApprovalMessage', () => {
145
+ test('returns assistantPreface when provided (primary source)', () => {
146
+ const preface = 'The assistant already said something helpful.';
147
+ const msg = composeApprovalMessage({
148
+ scenario: 'standard_prompt',
149
+ toolName: 'execute_shell',
150
+ assistantPreface: preface,
151
+ });
152
+ expect(msg).toBe(preface);
153
+ });
154
+
155
+ test('ignores empty assistantPreface and falls back to template', () => {
156
+ const msg = composeApprovalMessage({
157
+ scenario: 'standard_prompt',
158
+ toolName: 'execute_shell',
159
+ assistantPreface: '',
160
+ });
161
+ expect(msg).toContain('execute_shell');
162
+ expect(msg).not.toBe('');
163
+ });
164
+
165
+ test('ignores whitespace-only assistantPreface', () => {
166
+ const msg = composeApprovalMessage({
167
+ scenario: 'standard_prompt',
168
+ toolName: 'execute_shell',
169
+ assistantPreface: ' ',
170
+ });
171
+ expect(msg).toContain('execute_shell');
172
+ });
173
+
174
+ test('falls back to deterministic template when no assistantPreface', () => {
175
+ const msg = composeApprovalMessage({
176
+ scenario: 'guardian_prompt',
177
+ toolName: 'write_file',
178
+ requesterIdentifier: 'charlie',
179
+ });
180
+ expect(msg).toContain('charlie');
181
+ expect(msg).toContain('write_file');
182
+ });
183
+
184
+ test('fallback matches getFallbackMessage output', () => {
185
+ const ctx: ApprovalMessageContext = {
186
+ scenario: 'reminder_prompt',
187
+ };
188
+ expect(composeApprovalMessage(ctx)).toBe(getFallbackMessage(ctx));
189
+ });
190
+ });
191
+
192
+ // -----------------------------------------------------------------------
193
+ // Verification scenario resilience — composed messages contain key facts
194
+ // -----------------------------------------------------------------------
195
+
196
+ describe('verification scenario resilience', () => {
197
+ test('guardian_verify_challenge_setup includes verify command and TTL', () => {
198
+ const msg = composeApprovalMessage({
199
+ scenario: 'guardian_verify_challenge_setup',
200
+ verifyCommand: '/guardian_verify abc123def456',
201
+ ttlSeconds: 600,
202
+ });
203
+ expect(typeof msg).toBe('string');
204
+ expect(msg.length).toBeGreaterThan(0);
205
+ expect(msg).toContain('/guardian_verify abc123def456');
206
+ expect(msg).toContain('600');
207
+ });
208
+
209
+ test('guardian_verify_failed includes failure reason', () => {
210
+ const msg = composeApprovalMessage({
211
+ scenario: 'guardian_verify_failed',
212
+ failureReason: 'Too many attempts. Please try again later.',
213
+ });
214
+ expect(typeof msg).toBe('string');
215
+ expect(msg.length).toBeGreaterThan(0);
216
+ expect(msg).toContain('Too many attempts');
217
+ });
218
+
219
+ test('guardian_verify_failed with invalid-or-expired reason includes that reason', () => {
220
+ const msg = composeApprovalMessage({
221
+ scenario: 'guardian_verify_failed',
222
+ failureReason: 'The verification code is invalid or has expired.',
223
+ });
224
+ expect(typeof msg).toBe('string');
225
+ expect(msg.length).toBeGreaterThan(0);
226
+ expect(msg).toContain('invalid or has expired');
227
+ });
228
+
229
+ test('guardian_verify_success produces a non-empty success message', () => {
230
+ const msg = composeApprovalMessage({
231
+ scenario: 'guardian_verify_success',
232
+ });
233
+ expect(typeof msg).toBe('string');
234
+ expect(msg.length).toBeGreaterThan(0);
235
+ });
236
+
237
+ test('guardian_verify_status_bound produces a non-empty message', () => {
238
+ const msg = composeApprovalMessage({
239
+ scenario: 'guardian_verify_status_bound',
240
+ });
241
+ expect(typeof msg).toBe('string');
242
+ expect(msg.length).toBeGreaterThan(0);
243
+ });
244
+
245
+ test('guardian_verify_status_unbound produces a non-empty message', () => {
246
+ const msg = composeApprovalMessage({
247
+ scenario: 'guardian_verify_status_unbound',
248
+ });
249
+ expect(typeof msg).toBe('string');
250
+ expect(msg.length).toBeGreaterThan(0);
251
+ });
252
+ });
253
+ });
@@ -34,6 +34,7 @@ function createMockPage(closed = false) {
34
34
  keyboard: { press: async () => {} },
35
35
  mouse: { click: async () => {}, move: async () => {}, wheel: async () => {} },
36
36
  bringToFront: async () => {},
37
+ on: () => {},
37
38
  };
38
39
  }
39
40
 
@@ -0,0 +1,130 @@
1
+ import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ const testDir = mkdtempSync(join(tmpdir(), 'call-conversation-messages-test-'));
7
+
8
+ mock.module('../util/platform.js', () => ({
9
+ getDataDir: () => testDir,
10
+ isMacOS: () => process.platform === 'darwin',
11
+ isLinux: () => process.platform === 'linux',
12
+ isWindows: () => process.platform === 'win32',
13
+ getSocketPath: () => join(testDir, 'test.sock'),
14
+ getPidPath: () => join(testDir, 'test.pid'),
15
+ getDbPath: () => join(testDir, 'test.db'),
16
+ getLogPath: () => join(testDir, 'test.log'),
17
+ ensureDataDir: () => {},
18
+ }));
19
+
20
+ mock.module('../util/logger.js', () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, {
23
+ get: () => () => {},
24
+ }),
25
+ }));
26
+
27
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
28
+ import { conversations } from '../memory/schema.js';
29
+ import { createCallSession, updateCallSession, recordCallEvent } from '../calls/call-store.js';
30
+ import { getMessages } from '../memory/conversation-store.js';
31
+ import { buildCallCompletionMessage, persistCallCompletionMessage } from '../calls/call-conversation-messages.js';
32
+
33
+ initializeDb();
34
+
35
+ function ensureConversation(id: string): void {
36
+ const db = getDb();
37
+ const now = Date.now();
38
+ db.insert(conversations).values({
39
+ id,
40
+ title: `Conversation ${id}`,
41
+ createdAt: now,
42
+ updatedAt: now,
43
+ }).run();
44
+ }
45
+
46
+ function resetTables(): void {
47
+ const db = getDb();
48
+ db.run('DELETE FROM call_events');
49
+ db.run('DELETE FROM call_pending_questions');
50
+ db.run('DELETE FROM call_sessions');
51
+ db.run('DELETE FROM messages');
52
+ db.run('DELETE FROM conversations');
53
+ }
54
+
55
+ function getLatestAssistantText(conversationId: string): string {
56
+ const rows = getMessages(conversationId).filter((m) => m.role === 'assistant');
57
+ expect(rows.length).toBeGreaterThan(0);
58
+ const latest = rows[rows.length - 1];
59
+ const parsed = JSON.parse(latest.content) as Array<{ type: string; text?: string }>;
60
+ return parsed.filter((b) => b.type === 'text').map((b) => b.text ?? '').join('');
61
+ }
62
+
63
+ describe('call-conversation-messages', () => {
64
+ beforeEach(() => {
65
+ resetTables();
66
+ });
67
+
68
+ afterAll(() => {
69
+ resetDb();
70
+ try {
71
+ rmSync(testDir, { recursive: true });
72
+ } catch {
73
+ // best-effort cleanup
74
+ }
75
+ });
76
+
77
+ test('buildCallCompletionMessage labels failed calls correctly', () => {
78
+ const conversationId = 'conv-call-msg-failed';
79
+ ensureConversation(conversationId);
80
+ const session = createCallSession({
81
+ conversationId,
82
+ provider: 'twilio',
83
+ fromNumber: '+15550001111',
84
+ toNumber: '+15550002222',
85
+ });
86
+
87
+ updateCallSession(session.id, { status: 'in_progress', startedAt: 1_000 });
88
+ updateCallSession(session.id, { status: 'failed', endedAt: 6_000 });
89
+ recordCallEvent(session.id, 'call_connected');
90
+ recordCallEvent(session.id, 'call_failed');
91
+
92
+ expect(buildCallCompletionMessage(session.id)).toBe('**Call failed** (5s). 2 event(s) recorded.');
93
+ });
94
+
95
+ test('buildCallCompletionMessage labels cancelled calls correctly', () => {
96
+ const conversationId = 'conv-call-msg-cancelled';
97
+ ensureConversation(conversationId);
98
+ const session = createCallSession({
99
+ conversationId,
100
+ provider: 'twilio',
101
+ fromNumber: '+15550001111',
102
+ toNumber: '+15550002222',
103
+ });
104
+
105
+ updateCallSession(session.id, { status: 'in_progress', startedAt: 1_000 });
106
+ updateCallSession(session.id, { status: 'cancelled', endedAt: 4_000 });
107
+ recordCallEvent(session.id, 'call_connected');
108
+ recordCallEvent(session.id, 'call_ended');
109
+
110
+ expect(buildCallCompletionMessage(session.id)).toBe('**Call cancelled** (3s). 2 event(s) recorded.');
111
+ });
112
+
113
+ test('persistCallCompletionMessage keeps completed label when status is completed', () => {
114
+ const conversationId = 'conv-call-msg-completed';
115
+ ensureConversation(conversationId);
116
+ const session = createCallSession({
117
+ conversationId,
118
+ provider: 'twilio',
119
+ fromNumber: '+15550001111',
120
+ toNumber: '+15550002222',
121
+ });
122
+
123
+ updateCallSession(session.id, { status: 'completed' });
124
+ recordCallEvent(session.id, 'call_ended');
125
+
126
+ const summary = persistCallCompletionMessage(conversationId, session.id);
127
+ expect(summary).toBe('**Call completed**. 1 event(s) recorded.');
128
+ expect(getLatestAssistantText(conversationId)).toBe('**Call completed**. 1 event(s) recorded.');
129
+ });
130
+ });
@@ -34,10 +34,10 @@ mock.module('../util/logger.js', () => ({
34
34
  }));
35
35
 
36
36
  mock.module('../calls/twilio-config.js', () => ({
37
- getTwilioConfig: () => ({
37
+ getTwilioConfig: (assistantId?: string) => ({
38
38
  accountSid: 'AC_test',
39
39
  authToken: 'test_token',
40
- phoneNumber: '+15550001111',
40
+ phoneNumber: assistantId === 'ast-alpha' ? '+15550003333' : '+15550001111',
41
41
  webhookBaseUrl: 'https://test.example.com',
42
42
  wssBaseUrl: 'wss://test.example.com',
43
43
  }),
@@ -97,6 +97,16 @@ describe('resolveCallerIdentity — strict implicit-default policy', () => {
97
97
  }
98
98
  });
99
99
 
100
+ test('assistant_number resolves from assistant-scoped Twilio number when assistantId is provided', async () => {
101
+ const result = await resolveCallerIdentity(makeConfig(), undefined, 'ast-alpha');
102
+ expect(result.ok).toBe(true);
103
+ if (result.ok) {
104
+ expect(result.mode).toBe('assistant_number');
105
+ expect(result.fromNumber).toBe('+15550003333');
106
+ expect(result.source).toBe('implicit_default');
107
+ }
108
+ });
109
+
100
110
  test('explicit user_number succeeds when eligible', async () => {
101
111
  const result = await resolveCallerIdentity(
102
112
  makeConfig({ userNumber: '+15550002222' }),