@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -38,6 +38,7 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
38
38
 
39
39
  const {
40
40
  DISK_PRESSURE_CLEAR_THRESHOLD_PERCENT,
41
+ DISK_PRESSURE_MIN_FREE_FLOOR_MB,
41
42
  DISK_PRESSURE_OVERRIDE_CONFIRMATION,
42
43
  DISK_PRESSURE_THRESHOLD_PERCENT,
43
44
  DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT,
@@ -342,4 +343,44 @@ describe("disk pressure guard", () => {
342
343
  setDiskUsage(DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT - 1);
343
344
  expect(evaluateDiskPressureNow().state).toBe("ok");
344
345
  });
346
+
347
+ test("stays ok at a critical usage percentage while ample free space remains", () => {
348
+ // 99% used of a large volume still leaves gigabytes free — above the floor.
349
+ const totalMb = 1_000_000;
350
+ const usedMb = Math.round(totalMb * 0.99); // freeMb ~= 10_000 MiB
351
+ setDiskUsage(usedMb, totalMb);
352
+ expect(diskSample!.freeMb).toBeGreaterThanOrEqual(
353
+ DISK_PRESSURE_MIN_FREE_FLOOR_MB,
354
+ );
355
+
356
+ const status = evaluateDiskPressureNow();
357
+
358
+ expect(status.state).toBe("ok");
359
+ expect(status.locked).toBe(false);
360
+ expect(status.effectivelyLocked).toBe(false);
361
+ });
362
+
363
+ test("stays ok at a warning usage percentage while ample free space remains", () => {
364
+ const totalMb = 1_000_000;
365
+ const usedMb = Math.round(totalMb * 0.85); // 85% used, freeMb ~= 150_000 MiB
366
+ setDiskUsage(usedMb, totalMb);
367
+
368
+ const status = evaluateDiskPressureNow();
369
+
370
+ expect(status.state).toBe("ok");
371
+ });
372
+
373
+ test("locks at a critical usage percentage once free space drops below the floor", () => {
374
+ // High percentage AND little absolute headroom: floor does not apply.
375
+ const totalMb = 100_000;
376
+ const freeMb = DISK_PRESSURE_MIN_FREE_FLOOR_MB - 1;
377
+ setDiskUsage(totalMb - freeMb, totalMb);
378
+ expect(diskSample!.freeMb).toBeLessThan(DISK_PRESSURE_MIN_FREE_FLOOR_MB);
379
+
380
+ const status = evaluateDiskPressureNow();
381
+
382
+ expect(status.state).toBe("critical");
383
+ expect(status.locked).toBe(true);
384
+ expect(status.effectivelyLocked).toBe(true);
385
+ });
345
386
  });
@@ -30,6 +30,8 @@ const addMessageCalls: Array<{
30
30
  }> = [];
31
31
 
32
32
  mock.module("../memory/conversation-crud.js", () => ({
33
+ setConversationProcessingStartedAt: () => {},
34
+ isConversationProcessing: () => false,
33
35
  addMessage: async (
34
36
  conversationId: string,
35
37
  role: string,
@@ -19,13 +19,18 @@ mock.module("../channels/config.js", () => ({
19
19
  getDeliverableChannels: () => ["vellum", "telegram"],
20
20
  }));
21
21
 
22
- mock.module("../contacts/contact-store.js", () => ({
23
- findGuardianForChannel: (_channelType: string, _assistantId: string) => null,
22
+ // Guardian connectivity is resolved from the gateway pull. No active guardian
23
+ // binding binding-based channels (telegram) are not reported connected.
24
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
25
+ getGuardianDelivery: async () => [],
26
+ guardianForChannel: () => undefined,
24
27
  }));
25
28
 
26
- // Note: stale mock for channel-guardian-store.js removed the barrel was
27
- // deleted and none of the functions it mocked (getActiveBinding) existed in
28
- // the barrel.
29
+ // connectivity falls back to the local contacts read on a per-channel gateway
30
+ // no-match; no local binding telegram stays disconnected.
31
+ mock.module("../contacts/contact-store.js", () => ({
32
+ findGuardianForChannel: () => null,
33
+ }));
29
34
 
30
35
  mock.module("../notifications/adapters/macos.js", () => ({
31
36
  VellumAdapter: class {
@@ -29,7 +29,13 @@ mock.module("../config/env.js", () => ({
29
29
  }));
30
30
 
31
31
  mock.module("../runtime/local-actor-identity.js", () => ({
32
- findLocalGuardianPrincipalId: () => fakeGuardianPrincipalId,
32
+ findLocalGuardianPrincipalIdFromStore: () => fakeGuardianPrincipalId,
33
+ resolveActorPrincipalIdForLocalGuardianSync: (
34
+ rawHeader: string | undefined,
35
+ ) => {
36
+ if (rawHeader !== "dev-bypass" || !fakeHttpAuthDisabled) return rawHeader;
37
+ return fakeGuardianPrincipalId;
38
+ },
33
39
  }));
34
40
 
35
41
  mock.module("../util/logger.js", () => ({
@@ -51,6 +51,8 @@ const createdConversations: Array<{
51
51
  let conversationIdCounter = 0;
52
52
 
53
53
  mock.module("../memory/conversation-crud.js", () => ({
54
+ setConversationProcessingStartedAt: () => {},
55
+ isConversationProcessing: () => false,
54
56
  setConversationOriginChannelIfUnset: () => {},
55
57
  updateConversationContextWindow: () => {},
56
58
  deleteMessageById: () => {},
@@ -7,6 +7,16 @@ mock.module("../util/logger.js", () => ({
7
7
  }),
8
8
  }));
9
9
 
10
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
11
+
12
+ let mockGuardians: GuardianDelivery[] | null = [];
13
+
14
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
15
+ getGuardianDelivery: async () => mockGuardians,
16
+ guardianForChannel: (list: GuardianDelivery[], channelType: string) =>
17
+ list.find((g) => g.channelType === channelType && g.status === "active"),
18
+ }));
19
+
10
20
  import { findGuardianForChannel } from "../contacts/contact-store.js";
11
21
  import { getDb } from "../memory/db-connection.js";
12
22
  import { initializeDb } from "../memory/db-init.js";
@@ -21,12 +31,24 @@ function resetTables(): void {
21
31
  db.run("DELETE FROM contacts");
22
32
  }
23
33
 
34
+ /** Gateway delivery mirroring the local guardian binding's principal. */
35
+ function gatewayGuardian(principalId: string): GuardianDelivery {
36
+ return {
37
+ channelType: "vellum",
38
+ contactId: "guardian-contact",
39
+ principalId,
40
+ address: principalId,
41
+ status: "active",
42
+ };
43
+ }
44
+
24
45
  describe("healGuardianBindingDrift", () => {
25
46
  beforeEach(() => {
26
47
  resetTables();
48
+ mockGuardians = [];
27
49
  });
28
50
 
29
- test("heals drift when both principals have vellum-principal- prefix", () => {
51
+ test("heals drift when both principals have vellum-principal- prefix", async () => {
30
52
  // Simulate DB reset: new guardian binding with a different UUID
31
53
  createGuardianBinding({
32
54
  channel: "vellum",
@@ -35,9 +57,10 @@ describe("healGuardianBindingDrift", () => {
35
57
  guardianPrincipalId: "vellum-principal-new-uuid",
36
58
  verifiedVia: "startup-migration",
37
59
  });
60
+ mockGuardians = [gatewayGuardian("vellum-principal-new-uuid")];
38
61
 
39
62
  // Client arrives with the old JWT principal
40
- const healed = healGuardianBindingDrift("vellum-principal-old-uuid");
63
+ const healed = await healGuardianBindingDrift("vellum-principal-old-uuid");
41
64
  expect(healed).toBe(true);
42
65
 
43
66
  // Guardian binding now matches the old JWT
@@ -47,7 +70,31 @@ describe("healGuardianBindingDrift", () => {
47
70
  expect(guardian!.channel.address).toBe("vellum-principal-old-uuid");
48
71
  });
49
72
 
50
- test("no-op when principals already match", () => {
73
+ test("repairs the stale local mirror even when the gateway already matches the JWT", async () => {
74
+ // Gateway binding already matches the incoming JWT principal, but the
75
+ // local mirror is stale — the gateway-source-of-truth drift mode. The
76
+ // /v1/messages trust path still reads the local mirror in this plan, so
77
+ // a stale row must be repaired or the actor stays classified `unknown`.
78
+ createGuardianBinding({
79
+ channel: "vellum",
80
+ guardianExternalUserId: "vellum-principal-stale-local",
81
+ guardianDeliveryChatId: "local",
82
+ guardianPrincipalId: "vellum-principal-stale-local",
83
+ verifiedVia: "startup-migration",
84
+ });
85
+ mockGuardians = [gatewayGuardian("vellum-principal-jwt")];
86
+
87
+ const healed = await healGuardianBindingDrift("vellum-principal-jwt");
88
+ expect(healed).toBe(true);
89
+
90
+ // Local mirror now matches the JWT, so a subsequent local trust
91
+ // resolution classifies the actor as guardian rather than unknown.
92
+ const guardian = findGuardianForChannel("vellum");
93
+ expect(guardian!.contact.principalId).toBe("vellum-principal-jwt");
94
+ expect(guardian!.channel.address).toBe("vellum-principal-jwt");
95
+ });
96
+
97
+ test("no-op when principals already match", async () => {
51
98
  createGuardianBinding({
52
99
  channel: "vellum",
53
100
  guardianExternalUserId: "vellum-principal-same",
@@ -55,12 +102,13 @@ describe("healGuardianBindingDrift", () => {
55
102
  guardianPrincipalId: "vellum-principal-same",
56
103
  verifiedVia: "startup-migration",
57
104
  });
105
+ mockGuardians = [gatewayGuardian("vellum-principal-same")];
58
106
 
59
- const healed = healGuardianBindingDrift("vellum-principal-same");
107
+ const healed = await healGuardianBindingDrift("vellum-principal-same");
60
108
  expect(healed).toBe(false);
61
109
  });
62
110
 
63
- test("refuses to heal when incoming principal lacks vellum-principal- prefix", () => {
111
+ test("refuses to heal when incoming principal lacks vellum-principal- prefix", async () => {
64
112
  createGuardianBinding({
65
113
  channel: "vellum",
66
114
  guardianExternalUserId: "vellum-principal-aaa",
@@ -68,9 +116,10 @@ describe("healGuardianBindingDrift", () => {
68
116
  guardianPrincipalId: "vellum-principal-aaa",
69
117
  verifiedVia: "startup-migration",
70
118
  });
119
+ mockGuardians = [gatewayGuardian("vellum-principal-aaa")];
71
120
 
72
121
  // External/platform principal — should NOT be adopted
73
- const healed = healGuardianBindingDrift("platform-user-12345");
122
+ const healed = await healGuardianBindingDrift("platform-user-12345");
74
123
  expect(healed).toBe(false);
75
124
 
76
125
  // Guardian unchanged
@@ -78,7 +127,7 @@ describe("healGuardianBindingDrift", () => {
78
127
  expect(guardian!.contact.principalId).toBe("vellum-principal-aaa");
79
128
  });
80
129
 
81
- test("refuses to heal when stored principal lacks vellum-principal- prefix", () => {
130
+ test("refuses to heal when stored principal lacks vellum-principal- prefix", async () => {
82
131
  createGuardianBinding({
83
132
  channel: "vellum",
84
133
  guardianExternalUserId: "verified-phone-guardian",
@@ -86,17 +135,33 @@ describe("healGuardianBindingDrift", () => {
86
135
  guardianPrincipalId: "verified-phone-guardian",
87
136
  verifiedVia: "challenge",
88
137
  });
138
+ mockGuardians = [gatewayGuardian("verified-phone-guardian")];
89
139
 
90
140
  // Even with a vellum-principal- incoming, don't overwrite a real binding
91
- const healed = healGuardianBindingDrift("vellum-principal-attacker");
141
+ const healed = await healGuardianBindingDrift("vellum-principal-attacker");
92
142
  expect(healed).toBe(false);
93
143
 
94
144
  const guardian = findGuardianForChannel("vellum");
95
145
  expect(guardian!.contact.principalId).toBe("verified-phone-guardian");
96
146
  });
97
147
 
98
- test("returns false when no guardian binding exists", () => {
99
- const healed = healGuardianBindingDrift("vellum-principal-orphan");
148
+ test("returns false when gateway reports no guardian binding", async () => {
149
+ mockGuardians = [];
150
+ const healed = await healGuardianBindingDrift("vellum-principal-orphan");
151
+ expect(healed).toBe(false);
152
+ });
153
+
154
+ test("returns false when the gateway is unreachable (null list)", async () => {
155
+ createGuardianBinding({
156
+ channel: "vellum",
157
+ guardianExternalUserId: "vellum-principal-aaa",
158
+ guardianDeliveryChatId: "local",
159
+ guardianPrincipalId: "vellum-principal-aaa",
160
+ verifiedVia: "startup-migration",
161
+ });
162
+ mockGuardians = null;
163
+
164
+ const healed = await healGuardianBindingDrift("vellum-principal-old-uuid");
100
165
  expect(healed).toBe(false);
101
166
  });
102
167
  });
@@ -24,6 +24,11 @@ mock.module("../config/loader.js", () => ({
24
24
  }),
25
25
  }));
26
26
 
27
+ // The pending_question request principal is resolved via the SAME local source
28
+ // the Vellum actor uses — findGuardianForChannel("vellum")?.contact.principalId
29
+ // — so the stamped principal always equals the submitting actor principal. The
30
+ // real contacts DB is seeded in resetTables(); tests model drift / missing
31
+ // guardian by reseeding or clearing that local binding directly.
27
32
  const emitCalls: unknown[] = [];
28
33
  let conversationCreatedFromMock: ConversationCreatedInfo | null = null;
29
34
  let mockEmitResult: {
@@ -154,11 +159,18 @@ describe("guardian-dispatch", () => {
154
159
  "SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?",
155
160
  )
156
161
  .get(session.id) as
157
- | { id: string; status: string; question_text: string }
162
+ | {
163
+ id: string;
164
+ status: string;
165
+ question_text: string;
166
+ guardian_principal_id: string | null;
167
+ }
158
168
  | undefined;
159
169
  expect(request).toBeDefined();
160
170
  expect(request!.status).toBe("pending");
161
171
  expect(request!.question_text).toBe("What is the gate code?");
172
+ // principalId comes from the local guardian binding (same source the actor submits)
173
+ expect(request!.guardian_principal_id).toBe("test-principal-id");
162
174
 
163
175
  const vellumDelivery = raw
164
176
  .query(
@@ -175,6 +187,88 @@ describe("guardian-dispatch", () => {
175
187
  expect(typeof signalParams.onConversationCreated).toBe("function");
176
188
  });
177
189
 
190
+ test("stamps the request principal from the local source the actor submits, not the (possibly drifted) gateway binding", async () => {
191
+ // Simulate gateway/local binding drift: the local guardian binding (the
192
+ // source the actor submit path reads) carries a different principal than
193
+ // the gateway would. The request must be stamped with that local value so
194
+ // a later actor submission matches (no identity_mismatch under drift).
195
+ const db = getDb();
196
+ db.run("DELETE FROM contact_channels");
197
+ db.run("DELETE FROM contacts");
198
+ createGuardianBinding({
199
+ channel: "vellum",
200
+ guardianExternalUserId: "local-actor-principal",
201
+ guardianDeliveryChatId: "local",
202
+ guardianPrincipalId: "local-actor-principal",
203
+ verifiedVia: "bootstrap",
204
+ });
205
+
206
+ const convId = "conv-dispatch-drift";
207
+ ensureConversation(convId);
208
+
209
+ const session = createCallSession({
210
+ conversationId: convId,
211
+ provider: "twilio",
212
+ fromNumber: "+15550001111",
213
+ toNumber: "+15550002222",
214
+ });
215
+ const pq = createPendingQuestion(session.id, "Drifted bindings?");
216
+
217
+ await dispatchGuardianQuestion({
218
+ callSessionId: session.id,
219
+ conversationId: convId,
220
+ assistantId: "self",
221
+ pendingQuestion: pq,
222
+ });
223
+
224
+ const raw = (db as unknown as { $client: import("bun:sqlite").Database })
225
+ .$client;
226
+ const request = raw
227
+ .query(
228
+ "SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?",
229
+ )
230
+ .get(session.id) as
231
+ | { guardian_principal_id: string | null }
232
+ | undefined;
233
+ expect(request).toBeDefined();
234
+ expect(request!.guardian_principal_id).toBe("local-actor-principal");
235
+ });
236
+
237
+ test("skips dispatch when no local guardian binding exists (no principal to stamp)", async () => {
238
+ const db = getDb();
239
+ db.run("DELETE FROM contact_channels");
240
+ db.run("DELETE FROM contacts");
241
+
242
+ const convId = "conv-dispatch-no-principal";
243
+ ensureConversation(convId);
244
+
245
+ const session = createCallSession({
246
+ conversationId: convId,
247
+ provider: "twilio",
248
+ fromNumber: "+15550001111",
249
+ toNumber: "+15550002222",
250
+ });
251
+ const pq = createPendingQuestion(session.id, "No principal available");
252
+
253
+ await dispatchGuardianQuestion({
254
+ callSessionId: session.id,
255
+ conversationId: convId,
256
+ assistantId: "self",
257
+ pendingQuestion: pq,
258
+ });
259
+
260
+ // No request is created and the pipeline is never invoked.
261
+ const raw = (db as unknown as { $client: import("bun:sqlite").Database })
262
+ .$client;
263
+ const request = raw
264
+ .query(
265
+ "SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?",
266
+ )
267
+ .get(session.id) as { id: string } | null;
268
+ expect(request).toBeNull();
269
+ expect(emitCalls).toHaveLength(0);
270
+ });
271
+
178
272
  test("creates a telegram guardian delivery with binding metadata when pipeline sends telegram", async () => {
179
273
  const convId = "conv-dispatch-2";
180
274
  ensureConversation(convId);
@@ -84,6 +84,19 @@ globalThis.fetch = (async (
84
84
  return originalFetch(input, init as never);
85
85
  }) as unknown as typeof fetch;
86
86
 
87
+ // Guardian-delivery reader mock — the inbound challenge guard reads guardian
88
+ // existence from the gateway. These tests seed no binding, so report an empty
89
+ // list (not bound) rather than a null that would fail closed as already-bound.
90
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
91
+ getGuardianDelivery: async () => [],
92
+ getGuardianDeliveryFresh: async () => [],
93
+ guardianForChannel: (
94
+ list: Array<{ channelType: string; status: string }>,
95
+ channelType: string,
96
+ ) =>
97
+ list.find((g) => g.channelType === channelType && g.status === "active"),
98
+ }));
99
+
87
100
  // ---------------------------------------------------------------------------
88
101
  // Now import modules under test (after mocks are in place)
89
102
  // ---------------------------------------------------------------------------
@@ -70,6 +70,8 @@ mock.module("../schedule/recurrence-engine.js", () => ({
70
70
 
71
71
  const createdConversations: Array<{ conversationType: string }> = [];
72
72
  mock.module("../memory/conversation-crud.js", () => ({
73
+ setConversationProcessingStartedAt: () => {},
74
+ isConversationProcessing: () => false,
73
75
  getConversation: () => null,
74
76
  getMessages: () => [],
75
77
  createConversation: (opts: { conversationType: string }) => {
@@ -137,6 +137,8 @@ const mockStoredMessages: Array<{
137
137
  }> = [];
138
138
 
139
139
  mock.module("../memory/conversation-crud.js", () => ({
140
+ setConversationProcessingStartedAt: () => {},
141
+ isConversationProcessing: () => false,
140
142
  setConversationOriginChannelIfUnset: () => {},
141
143
  updateConversationContextWindow: () => {},
142
144
  deleteMessageById: () => {},
@@ -138,16 +138,11 @@ function stampTrustVerdict(body: Record<string, unknown>): void {
138
138
  const channelType = String(body.sourceChannel ?? "");
139
139
  const actorExternalId =
140
140
  typeof body.actorExternalId === "string" ? body.actorExternalId : undefined;
141
- const externalChatId =
142
- typeof body.conversationExternalId === "string"
143
- ? body.conversationExternalId
144
- : undefined;
145
141
  if (!channelType) return;
146
142
 
147
143
  const verdict = resolveLocalTrustVerdict({
148
144
  channelType,
149
145
  actorExternalId,
150
- externalChatId,
151
146
  });
152
147
  body.sourceMetadata = { ...(meta ?? {}), trustVerdict: verdict };
153
148
  }
@@ -156,15 +151,14 @@ function stampTrustVerdict(body: Record<string, unknown>): void {
156
151
  export function resolveLocalTrustVerdict(input: {
157
152
  channelType: string;
158
153
  actorExternalId?: string;
159
- externalChatId?: string;
160
154
  }): TrustVerdict {
161
155
  const canonicalSenderId = input.actorExternalId ?? null;
162
156
 
157
+ // Match the gateway's address-only member resolution (no externalChatId).
163
158
  const member = input.actorExternalId
164
159
  ? findContactChannel({
165
160
  channelType: input.channelType,
166
161
  address: input.actorExternalId,
167
- externalChatId: input.externalChatId,
168
162
  })
169
163
  : null;
170
164
  const guardian = findGuardianForChannel(input.channelType);
@@ -62,7 +62,7 @@ afterAll(() => {
62
62
 
63
63
  const handleHostAppControlResult = ROUTES.find(
64
64
  (r) => r.endpoint === "host-app-control-result",
65
- )!.handler;
65
+ )!.handler as (args: Record<string, unknown>) => Promise<unknown>;
66
66
 
67
67
  // ── Tests ────────────────────────────────────────────────────────────
68
68
 
@@ -395,7 +395,7 @@ describe("handleHostAppControlResult — same-actor guard", () => {
395
395
  ).toThrow(BadRequestError);
396
396
  });
397
397
 
398
- test("targeted + missing header: interaction is NOT consumed (still pending)", () => {
398
+ test("targeted + missing header: interaction is NOT consumed (still pending)", async () => {
399
399
  const requestId = "ac-req-targeted-no-header-stays";
400
400
  pending.set(requestId, {
401
401
  conversationId: "conv-1",
@@ -404,13 +404,11 @@ describe("handleHostAppControlResult — same-actor guard", () => {
404
404
  targetActorPrincipalId: "user-1",
405
405
  });
406
406
 
407
- try {
408
- handleHostAppControlResult({
409
- body: { requestId, state: "running" },
410
- });
411
- } catch {
407
+ await handleHostAppControlResult({
408
+ body: { requestId, state: "running" },
409
+ }).catch(() => {
412
410
  // expected
413
- }
411
+ });
414
412
 
415
413
  expect(pending.has(requestId)).toBe(true);
416
414
  });
@@ -437,7 +435,7 @@ describe("handleHostAppControlResult — same-actor guard", () => {
437
435
  ).toThrow(ForbiddenError);
438
436
  });
439
437
 
440
- test("targeted + wrong client id: interaction is NOT consumed", () => {
438
+ test("targeted + wrong client id: interaction is NOT consumed", async () => {
441
439
  const requestId = "ac-req-targeted-wrong-client-stays";
442
440
  pending.set(requestId, {
443
441
  conversationId: "conv-1",
@@ -446,17 +444,15 @@ describe("handleHostAppControlResult — same-actor guard", () => {
446
444
  targetActorPrincipalId: "user-1",
447
445
  });
448
446
 
449
- try {
450
- handleHostAppControlResult({
451
- body: { requestId, state: "running" },
452
- headers: {
453
- "x-vellum-client-id": "client-B",
454
- "x-vellum-actor-principal-id": "user-1",
455
- },
456
- });
457
- } catch {
447
+ await handleHostAppControlResult({
448
+ body: { requestId, state: "running" },
449
+ headers: {
450
+ "x-vellum-client-id": "client-B",
451
+ "x-vellum-actor-principal-id": "user-1",
452
+ },
453
+ }).catch(() => {
458
454
  // expected
459
- }
455
+ });
460
456
 
461
457
  expect(pending.has(requestId)).toBe(true);
462
458
  });
@@ -483,7 +479,7 @@ describe("handleHostAppControlResult — same-actor guard", () => {
483
479
  ).toThrow(ForbiddenError);
484
480
  });
485
481
 
486
- test("targeted + wrong actor principal: interaction is NOT consumed", () => {
482
+ test("targeted + wrong actor principal: interaction is NOT consumed", async () => {
487
483
  const requestId = "ac-req-targeted-wrong-actor-stays";
488
484
  pending.set(requestId, {
489
485
  conversationId: "conv-1",
@@ -492,17 +488,15 @@ describe("handleHostAppControlResult — same-actor guard", () => {
492
488
  targetActorPrincipalId: "user-1",
493
489
  });
494
490
 
495
- try {
496
- handleHostAppControlResult({
497
- body: { requestId, state: "running" },
498
- headers: {
499
- "x-vellum-client-id": "client-A",
500
- "x-vellum-actor-principal-id": "user-2",
501
- },
502
- });
503
- } catch {
491
+ await handleHostAppControlResult({
492
+ body: { requestId, state: "running" },
493
+ headers: {
494
+ "x-vellum-client-id": "client-A",
495
+ "x-vellum-actor-principal-id": "user-2",
496
+ },
497
+ }).catch(() => {
504
498
  // expected
505
- }
499
+ });
506
500
 
507
501
  expect(pending.has(requestId)).toBe(true);
508
502
  });