@vellumai/assistant 0.9.0 → 0.10.0-staging.2

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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -46,23 +46,20 @@ import { eq } from "drizzle-orm";
46
46
 
47
47
  import { upsertContactChannel } from "../contacts/contacts-write.js";
48
48
  import type { Conversation } from "../daemon/conversation.js";
49
+ import {
50
+ createCanonicalGuardianDelivery,
51
+ createCanonicalGuardianRequest,
52
+ getCanonicalGuardianRequest,
53
+ } from "../memory/canonical-guardian-store.js";
49
54
  import { getDb } from "../memory/db-connection.js";
50
55
  import { initializeDb } from "../memory/db-init.js";
51
- import {
52
- createApprovalRequest,
53
- getPendingApprovalForRequest,
54
- } from "../memory/guardian-approvals.js";
55
56
  import { messages } from "../memory/schema/conversations.js";
56
57
  import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
57
58
  import * as pendingInteractions from "../runtime/pending-interactions.js";
58
- import {
59
- _clearApprovalPromptTsTrackerForTesting,
60
- trackApprovalPromptTs,
61
- } from "../runtime/routes/approval-prompt-ts-tracker.js";
62
59
  import {
63
60
  isSlackReactionEvent,
64
61
  parseSlackReactionCallbackData,
65
- } from "../runtime/routes/inbound-message-handler.js";
62
+ } from "../runtime/routes/inbound-stages/reaction-intercept.js";
66
63
  import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
67
64
  import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
68
65
 
@@ -84,7 +81,6 @@ function resetState(): void {
84
81
  db.run("DELETE FROM conversations");
85
82
  db.run("DELETE FROM contact_channels");
86
83
  db.run("DELETE FROM contacts");
87
- _clearApprovalPromptTsTrackerForTesting();
88
84
  }
89
85
 
90
86
  function seedActiveMember(): void {
@@ -421,21 +417,35 @@ function seedGuardianForChannel(): void {
421
417
  function seedPendingGuardianApprovalForReaction(
422
418
  requestId: string,
423
419
  conversationId: string,
420
+ reactedTs: string,
424
421
  ): void {
425
- createApprovalRequest({
426
- runId: `run-${requestId}`,
427
- requestId,
422
+ // Canonical tool_approval request keyed by the confirmation requestId — the
423
+ // same record assistant-event-hub creates for every confirmation.
424
+ createCanonicalGuardianRequest({
425
+ id: requestId,
426
+ kind: "tool_approval",
427
+ sourceType: "channel",
428
+ sourceChannel: "slack",
428
429
  conversationId,
429
- channel: "slack",
430
430
  requesterExternalUserId: "requester-user-1",
431
431
  requesterChatId: SLACK_CHANNEL_ID,
432
432
  guardianExternalUserId: GUARDIAN_USER_ID,
433
- guardianChatId: SLACK_CHANNEL_ID,
433
+ guardianPrincipalId: GUARDIAN_USER_ID,
434
434
  toolName: GUARDIAN_REACTION_TOOL,
435
+ status: "pending",
435
436
  expiresAt: Date.now() + 300_000,
436
437
  });
437
438
 
438
- // Register a pending interaction so applyGuardianDecision finds the
439
+ // The delivered approval card request mapping the reaction resolves
440
+ // against (destination_message_id = the reacted Slack ts).
441
+ createCanonicalGuardianDelivery({
442
+ requestId,
443
+ destinationChannel: "slack",
444
+ destinationChatId: SLACK_CHANNEL_ID,
445
+ destinationMessageId: reactedTs,
446
+ });
447
+
448
+ // Register a pending interaction so the tool_approval resolver finds the
439
449
  // requester-side hook to drive `allow`.
440
450
  const handleConfirmationResponse = mock(() => {});
441
451
  const _mockSession = {
@@ -466,18 +476,17 @@ describe("guardian approval-by-reaction integration via handleChannelInbound", (
466
476
  db.run("DELETE FROM conversations");
467
477
  db.run("DELETE FROM contact_channels");
468
478
  db.run("DELETE FROM contacts");
469
- db.run("DELETE FROM channel_guardian_approval_requests");
479
+ db.run("DELETE FROM canonical_guardian_requests");
480
+ db.run("DELETE FROM canonical_guardian_deliveries");
470
481
  pendingInteractions.clear();
471
- _clearApprovalPromptTsTrackerForTesting();
472
482
  msgCounter = 0;
473
483
  });
474
484
 
475
485
  test("guardian reaction on pending approval applies decision and persists no transcript row", async () => {
476
486
  seedGuardianForChannel();
477
487
  const requestId = "req-guardian-react-1";
478
- // Conversation must exist so the approval row's conversation_id FK
479
- // resolves; reuse an inbound seed by sending a no-op message that the
480
- // ACL allows. Easier: insert directly via the DB layer.
488
+ // Back the canonical request and its pending interaction with a real
489
+ // conversation row, inserted directly via the DB layer.
481
490
  const db = getDb();
482
491
  const conversationId = "conv-react-test-1";
483
492
  const now = Date.now();
@@ -491,12 +500,15 @@ describe("guardian approval-by-reaction integration via handleChannelInbound", (
491
500
  )
492
501
  .run(conversationId, now, now);
493
502
 
494
- seedPendingGuardianApprovalForReaction(requestId, conversationId);
495
-
496
503
  const reactedTs = "1700000099.000001";
497
- // Simulate the approval prompt having been delivered on this ts so the
498
- // guardian reaction is scoped to a tracked prompt message.
499
- trackApprovalPromptTs("slack", SLACK_CHANNEL_ID, reactedTs);
504
+ // The approval card was delivered on this ts; its canonical delivery row
505
+ // is how the guardian reaction is scoped to a known approval message.
506
+ seedPendingGuardianApprovalForReaction(
507
+ requestId,
508
+ conversationId,
509
+ reactedTs,
510
+ );
511
+
500
512
  const body = {
501
513
  sourceChannel: "slack",
502
514
  interface: "slack",
@@ -541,11 +553,11 @@ describe("guardian approval-by-reaction integration via handleChannelInbound", (
541
553
 
542
554
  expect(resp.status).toBe(200);
543
555
  expect(json.accepted).toBe(true);
544
- expect(json.approval).toBe("guardian_decision_applied");
556
+ expect(json.canonicalRouter).toBe("canonical_decision_applied");
545
557
  expect(approvalConversationGenerator).not.toHaveBeenCalled();
546
558
 
547
- // The pending approval row is resolved (no longer pending).
548
- expect(getPendingApprovalForRequest(requestId)).toBeNull();
559
+ // The canonical request is resolved (no longer pending).
560
+ expect(getCanonicalGuardianRequest(requestId)?.status).toBe("approved");
549
561
 
550
562
  // No transcript row was written for the reaction itself — resolved
551
563
  // guardian approval reactions have no transcript representation.
@@ -563,3 +575,112 @@ describe("guardian approval-by-reaction integration via handleChannelInbound", (
563
575
  expect(reactionRows.length).toBe(0);
564
576
  });
565
577
  });
578
+
579
+ // ---------------------------------------------------------------------------
580
+ // Reaction access-control regression (LUM-2489)
581
+ // ---------------------------------------------------------------------------
582
+ //
583
+ // A reaction is a passive signal, not an access attempt. Because reactions are
584
+ // dispatched before the ingress pipeline, an unknown user's 👍 must never run
585
+ // ACL (no trusted-contact verification handshake), create a conversation, or
586
+ // write a binding — it is dropped as channel noise. A known contact's reaction
587
+ // is recorded; neither triggers a verification challenge.
588
+
589
+ describe("reaction access control (no verification handshake)", () => {
590
+ const STRANGER_USER_ID = "U_REACTION_STRANGER";
591
+ const CONTACT_USER_ID = "U_REACTION_CONTACT";
592
+ // Guardian's approval channel is a DM, distinct from the public channel the
593
+ // reaction lands in (mirroring production). Reusing the public channel id
594
+ // would let a reactor match the guardian's channel via findContactChannel's
595
+ // externalChatId fallback and mask the bug.
596
+ const GUARDIAN_DM_CHAT = "D_GUARDIAN_DM";
597
+
598
+ function tableCount(table: string): number {
599
+ return (
600
+ getDb().$client.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get() as {
601
+ n: number;
602
+ }
603
+ ).n;
604
+ }
605
+
606
+ beforeEach(() => {
607
+ resetState();
608
+ getDb().run("DELETE FROM channel_verification_sessions");
609
+ // The assistant has a guardian (as in production); the reactors below are
610
+ // different users.
611
+ createGuardianBinding({
612
+ channel: "slack",
613
+ guardianExternalUserId: GUARDIAN_USER_ID,
614
+ guardianDeliveryChatId: GUARDIAN_DM_CHAT,
615
+ guardianPrincipalId: GUARDIAN_USER_ID,
616
+ });
617
+ msgCounter = 0;
618
+ });
619
+
620
+ test("stranger's reaction is dropped — no challenge, session, conversation, or row", async () => {
621
+ let agentDispatched = false;
622
+ const processMessage = async (): Promise<{ messageId: string }> => {
623
+ agentDispatched = true;
624
+ return { messageId: "should-not-be-called" };
625
+ };
626
+
627
+ const req = buildReactionRequest("reaction:thumbsup", {
628
+ actorExternalId: STRANGER_USER_ID,
629
+ actorDisplayName: "Outside Reactor",
630
+ actorUsername: "outsider",
631
+ });
632
+ const resp = await handleChannelInbound(
633
+ req,
634
+ processMessage,
635
+ TEST_BEARER_TOKEN,
636
+ );
637
+ const json = (await resp.json()) as Record<string, unknown>;
638
+
639
+ // Accepted as a passive signal — never denied or turned into a challenge.
640
+ expect(json.accepted).toBe(true);
641
+ expect(json.denied).not.toBe(true);
642
+ expect(json.reason).not.toBe("verification_challenge_sent");
643
+ expect(json.verificationSessionId).toBeUndefined();
644
+ expect(agentDispatched).toBe(false);
645
+
646
+ // No verification handshake, and no side effects: a dropped reaction leaves
647
+ // no transcript row, no conversation, and no binding.
648
+ expect(tableCount("channel_verification_sessions")).toBe(0);
649
+ expect(readPersistedMessages().length).toBe(0);
650
+ expect(tableCount("conversations")).toBe(0);
651
+ expect(tableCount("external_conversation_bindings")).toBe(0);
652
+ });
653
+
654
+ test("known contact's reaction is recorded — no challenge", async () => {
655
+ // A pending contact classifies as `unverified_contact` — a known tier, so
656
+ // its reactions are recorded. On a real message it would be re-challenged,
657
+ // but a reaction must not trigger that.
658
+ upsertContactChannel({
659
+ sourceChannel: "slack",
660
+ externalUserId: CONTACT_USER_ID,
661
+ externalChatId: SLACK_CHANNEL_ID,
662
+ status: "pending",
663
+ policy: "allow",
664
+ displayName: "Pending Contact",
665
+ });
666
+
667
+ const req = buildReactionRequest("reaction:tada", {
668
+ actorExternalId: CONTACT_USER_ID,
669
+ actorDisplayName: "Pending Contact",
670
+ actorUsername: "pending_contact",
671
+ });
672
+ const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
673
+ const json = (await resp.json()) as Record<string, unknown>;
674
+
675
+ expect(json.denied).not.toBe(true);
676
+ expect(json.reason).not.toBe("verification_challenge_sent");
677
+ expect(tableCount("channel_verification_sessions")).toBe(0);
678
+
679
+ const rows = readPersistedMessages();
680
+ expect(rows.length).toBe(1);
681
+ const envelope = JSON.parse(rows[0].metadata!) as Record<string, unknown>;
682
+ const slackMeta = readSlackMetadata(envelope.slackMeta as string);
683
+ expect(slackMeta?.eventKind).toBe("reaction");
684
+ expect(slackMeta?.reaction?.emoji).toBe("tada");
685
+ });
686
+ });
@@ -21,11 +21,7 @@ import {
21
21
  unregisterWorkspaceTool,
22
22
  } from "../tools/registry.js";
23
23
  import { eagerModuleToolNames, explicitTools } from "../tools/tool-manifest.js";
24
- import type {
25
- Tool,
26
- ToolContext,
27
- ToolExecutionResult,
28
- } from "../tools/types.js";
24
+ import type { Tool, ToolContext, ToolExecutionResult } from "../tools/types.js";
29
25
 
30
26
  // Clean up global registry after this file completes to prevent
31
27
  // contamination of subsequent test files in combined runs.
@@ -115,12 +111,11 @@ describe("tool manifest", () => {
115
111
  expect(eagerModuleToolNames.length).toBe(11);
116
112
  });
117
113
 
118
- test("explicit tools list includes memory and credential tools", () => {
114
+ test("explicit tools list includes memory tools", () => {
119
115
  const names = explicitTools.map((t) => t.name);
120
116
  expect(names).toContain("recall");
121
117
  expect(names.filter((name) => name === "recall")).toHaveLength(1);
122
118
  expect(names).toContain("remember");
123
- expect(names).toContain("credential_store");
124
119
  });
125
120
 
126
121
  test("registered tool count is at least eager + host", async () => {
@@ -16,6 +16,7 @@
16
16
  import { createHash, randomUUID } from "node:crypto";
17
17
  import {
18
18
  afterAll,
19
+ beforeAll,
19
20
  beforeEach,
20
21
  describe,
21
22
  expect,
@@ -25,6 +26,10 @@ import {
25
26
  test,
26
27
  } from "bun:test";
27
28
 
29
+ import { ADMISSION_FLOOR } from "@vellumai/gateway-client";
30
+
31
+ import { TRUST_CLASS_RANK } from "../runtime/actor-trust-resolver.js";
32
+
28
33
  // ── Platform + logger mocks (must come before any source imports) ────
29
34
 
30
35
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -100,6 +105,80 @@ mock.module("../config/loader.js", () => ({
100
105
  loadConfig: () => mockConfig,
101
106
  }));
102
107
 
108
+ // ── Channel admission policy reader mock ────────────────────────────
109
+ //
110
+ // handleSetup resolves the phone admission floor via this reader and forwards
111
+ // it into routeSetup. Tests drive the floor by setting `mockAdmissionPolicy`
112
+ // (or making the reader throw to exercise the fail-open guard).
113
+
114
+ let mockAdmissionPolicy:
115
+ | import("@vellumai/gateway-client").AdmissionPolicy
116
+ | null = null;
117
+ // Optional gate to hold the reader open mid-setup so tests can drive the
118
+ // race window where a prompt arrives while handleSetup is still awaiting.
119
+ let mockAdmissionGate: Promise<void> | null = null;
120
+ // `mock.module` is process-global in Bun and leaks into sibling files that run
121
+ // later in the same `bun test` invocation. channel-admission-reader.test.ts
122
+ // imports the reader for real, so an unconditional stub here would feed it this
123
+ // stub and break its assertions. Delegate to the real implementation unless
124
+ // this file's tests are active (`admissionMockActive`, toggled in
125
+ // beforeAll/afterAll). Snapshot the real exports into a plain object NOW,
126
+ // before the stub registers — a module namespace is a live view, so reading
127
+ // the real export after the stub installs would resolve back to the stub
128
+ // (infinite recursion).
129
+ const realChannelAdmissionReaderModule = {
130
+ ...(await import("../calls/channel-admission-reader.js")),
131
+ };
132
+ let admissionMockActive = false;
133
+ mock.module("../calls/channel-admission-reader.js", () => ({
134
+ ...realChannelAdmissionReaderModule,
135
+ getChannelAdmissionPolicy: async (
136
+ channelType: import("../channels/types.js").ChannelId,
137
+ ) => {
138
+ if (!admissionMockActive) {
139
+ return realChannelAdmissionReaderModule.getChannelAdmissionPolicy(
140
+ channelType,
141
+ );
142
+ }
143
+ if (mockAdmissionGate) await mockAdmissionGate;
144
+ return mockAdmissionPolicy;
145
+ },
146
+ }));
147
+
148
+ // Real floor semantics, mirroring relay-setup-router.test.ts. The
149
+ // enforceAdmissionPolicy mock omits the exempt-channel short-circuit so the
150
+ // deny path can be exercised end-to-end regardless of channel.
151
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
152
+ const realAdmissionPolicyModule = require("../runtime/routes/inbound-stages/admission-policy.js");
153
+ mock.module("../runtime/routes/inbound-stages/admission-policy.js", () => ({
154
+ ...realAdmissionPolicyModule,
155
+ enforceAdmissionPolicy: (input: {
156
+ trustClass: string;
157
+ memberStatus: string | undefined;
158
+ policy: import("@vellumai/gateway-client").AdmissionPolicy;
159
+ }) => {
160
+ if (input.memberStatus === "blocked" || input.memberStatus === "revoked") {
161
+ return {
162
+ admitted: false,
163
+ reason:
164
+ input.memberStatus === "blocked" ? "member_blocked" : "member_revoked",
165
+ shouldChallenge: false,
166
+ effectivePolicy: input.policy,
167
+ };
168
+ }
169
+ const rank =
170
+ (TRUST_CLASS_RANK as Record<string, number>)[input.trustClass] ?? 0;
171
+ const floor = ADMISSION_FLOOR[input.policy];
172
+ if (rank >= floor) return { admitted: true };
173
+ return {
174
+ admitted: false,
175
+ reason: `admission_policy_${input.policy}`,
176
+ shouldChallenge: false,
177
+ effectivePolicy: input.policy,
178
+ };
179
+ },
180
+ }));
181
+
103
182
  // ── TTS provider mocks (for call-speech-output) ─────────────────────
104
183
 
105
184
  let mockTtsProviderId: string = "elevenlabs";
@@ -240,7 +319,15 @@ import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
240
319
 
241
320
  initializeDb();
242
321
 
322
+ // Activate the channel-admission-reader stub only while this file's tests run,
323
+ // so the registered (process-global) mock delegates to the real module for
324
+ // sibling files that load later in the same worker.
325
+ beforeAll(() => {
326
+ admissionMockActive = true;
327
+ });
328
+
243
329
  afterAll(() => {
330
+ admissionMockActive = false;
244
331
  resetDbForTesting();
245
332
  });
246
333
 
@@ -404,6 +491,8 @@ describe("relay-server", () => {
404
491
  activeRelayConnections.clear();
405
492
  mockUserReference = "my human";
406
493
  mockAssistantName = "Vellum";
494
+ mockAdmissionPolicy = null;
495
+ mockAdmissionGate = null;
407
496
  mockSendMessage.mockImplementation(createMockProviderResponse(["Hello"]));
408
497
  mockConfig.calls.verification.enabled = false;
409
498
  mockConfig.calls.verification.maxAttempts = 3;
@@ -840,6 +929,121 @@ describe("relay-server", () => {
840
929
  relay.destroy();
841
930
  });
842
931
 
932
+ test("handleMessage: prompt arriving during setup (setting_up) is dropped", async () => {
933
+ ensureConversation("conv-relay-setting-up");
934
+ const session = createCallSession({
935
+ conversationId: "conv-relay-setting-up",
936
+ provider: "twilio",
937
+ fromNumber: "+15551111111",
938
+ toNumber: "+15552222222",
939
+ });
940
+
941
+ addTrustedVoiceContact("+15551111111");
942
+
943
+ const { ws, relay } = createMockWs(session.id);
944
+
945
+ // Hold the admission reader open so handleSetup stays in "setting_up".
946
+ let releaseGate: () => void = () => {};
947
+ mockAdmissionGate = new Promise<void>((resolve) => {
948
+ releaseGate = resolve;
949
+ });
950
+
951
+ // Fire setup without awaiting — it suspends inside getChannelAdmissionPolicy.
952
+ const setupPromise = relay.handleMessage(
953
+ JSON.stringify({
954
+ type: "setup",
955
+ callSid: "CA_setting_up_123",
956
+ from: "+15551111111",
957
+ to: "+15552222222",
958
+ }),
959
+ );
960
+
961
+ // Let the setup task reach the await; state must be "setting_up".
962
+ await new Promise((resolve) => setTimeout(resolve, 0));
963
+ expect(relay.getConnectionState()).toBe("setting_up");
964
+
965
+ // A prompt arriving now must be dropped: no transcript, no fallback.
966
+ await relay.handleMessage(
967
+ JSON.stringify({
968
+ type: "prompt",
969
+ voicePrompt: "Wire me money",
970
+ lang: "en-US",
971
+ last: true,
972
+ }),
973
+ );
974
+
975
+ const textMessages = ws.sentMessages
976
+ .map((m) => JSON.parse(m))
977
+ .filter((m: { type: string }) => m.type === "text");
978
+ expect(
979
+ textMessages.some((m) => (m.token ?? "").includes("still setting up")),
980
+ ).toBe(false);
981
+ expect(relay.getConversationHistory().length).toBe(0);
982
+ expect(
983
+ getMessages("conv-relay-setting-up").filter((m) => m.role === "user")
984
+ .length,
985
+ ).toBe(0);
986
+
987
+ // Release the reader and let setup finish.
988
+ releaseGate();
989
+ await setupPromise;
990
+ await new Promise((resolve) => setTimeout(resolve, 10));
991
+ expect(relay.getConnectionState()).toBe("connected");
992
+
993
+ relay.destroy();
994
+ });
995
+
996
+ test("handleMessage: prompt after normal-call setup completes is handled", async () => {
997
+ ensureConversation("conv-relay-postsetup");
998
+ const session = createCallSession({
999
+ conversationId: "conv-relay-postsetup",
1000
+ provider: "twilio",
1001
+ fromNumber: "+15551111111",
1002
+ toNumber: "+15552222222",
1003
+ });
1004
+
1005
+ addTrustedVoiceContact("+15551111111");
1006
+ mockSendMessage.mockImplementation(
1007
+ createMockProviderResponse(["Sure, happy to help."]),
1008
+ );
1009
+
1010
+ const { relay } = createMockWs(session.id);
1011
+
1012
+ await relay.handleMessage(
1013
+ JSON.stringify({
1014
+ type: "setup",
1015
+ callSid: "CA_postsetup_123",
1016
+ from: "+15551111111",
1017
+ to: "+15552222222",
1018
+ }),
1019
+ );
1020
+
1021
+ // Setup completed normally → "connected".
1022
+ expect(relay.getConnectionState()).toBe("connected");
1023
+
1024
+ await new Promise((resolve) => setTimeout(resolve, 10));
1025
+
1026
+ // A subsequent prompt routes to the controller (no "still setting up").
1027
+ await relay.handleMessage(
1028
+ JSON.stringify({
1029
+ type: "prompt",
1030
+ voicePrompt: "Hello there",
1031
+ lang: "en-US",
1032
+ last: true,
1033
+ }),
1034
+ );
1035
+
1036
+ await new Promise((resolve) => setTimeout(resolve, 10));
1037
+
1038
+ const userMessages = getMessages("conv-relay-postsetup").filter(
1039
+ (m) => m.role === "user",
1040
+ );
1041
+ expect(userMessages.length).toBeGreaterThan(0);
1042
+ expect(relay.getConnectionState()).toBe("connected");
1043
+
1044
+ relay.destroy();
1045
+ });
1046
+
843
1047
  // ── Interrupt handling ──────────────────────────────────────────
844
1048
 
845
1049
  test("handleMessage: interrupt message is handled without error", async () => {
@@ -2722,6 +2926,87 @@ describe("relay-server", () => {
2722
2926
  relay.destroy();
2723
2927
  });
2724
2928
 
2929
+ // ── Inbound admission floor (reader → routeSetup wiring) ───────────
2930
+
2931
+ test("admission floor: reader floor that excludes caller denies the call end-to-end", async () => {
2932
+ ensureConversation("conv-admission-floor-deny");
2933
+ const session = createCallSession({
2934
+ conversationId: "conv-admission-floor-deny",
2935
+ provider: "twilio",
2936
+ fromNumber: "+15557776666",
2937
+ toNumber: "+15551111111",
2938
+ });
2939
+
2940
+ // A trusted contact (rank 3) is below the `guardian_only` floor (rank 4).
2941
+ addTrustedVoiceContact("+15557776666");
2942
+ mockAdmissionPolicy = "guardian_only";
2943
+
2944
+ const { ws, relay } = createMockWs(session.id);
2945
+
2946
+ await relay.handleMessage(
2947
+ JSON.stringify({
2948
+ type: "setup",
2949
+ callSid: "CA_admission_floor_deny",
2950
+ from: "+15557776666",
2951
+ to: "+15551111111",
2952
+ }),
2953
+ );
2954
+
2955
+ // Floor-excluded caller takes the deny path.
2956
+ expect(relay.getConnectionState()).toBe("disconnecting");
2957
+
2958
+ const updated = getCallSession(session.id);
2959
+ expect(updated).not.toBeNull();
2960
+ expect(updated!.status).toBe("failed");
2961
+
2962
+ const textMessages = ws.sentMessages
2963
+ .map((raw) => JSON.parse(raw) as { type: string; token?: string })
2964
+ .filter((m) => m.type === "text");
2965
+ expect(
2966
+ textMessages.some((m) => (m.token ?? "").includes("not authorized")),
2967
+ ).toBe(true);
2968
+
2969
+ await new Promise((resolve) => setTimeout(resolve, 10));
2970
+
2971
+ relay.destroy();
2972
+ });
2973
+
2974
+ test("admission floor: null policy from reader leaves the call flow unchanged", async () => {
2975
+ ensureConversation("conv-admission-floor-null");
2976
+ const session = createCallSession({
2977
+ conversationId: "conv-admission-floor-null",
2978
+ provider: "twilio",
2979
+ fromNumber: "+15557776666",
2980
+ toNumber: "+15551111111",
2981
+ });
2982
+
2983
+ addTrustedVoiceContact("+15557776666");
2984
+ mockAdmissionPolicy = null; // no floor supplied
2985
+
2986
+ mockSendMessage.mockImplementation(
2987
+ createMockProviderResponse(["Hello, how can I help you?"]),
2988
+ );
2989
+
2990
+ const { relay } = createMockWs(session.id);
2991
+
2992
+ await relay.handleMessage(
2993
+ JSON.stringify({
2994
+ type: "setup",
2995
+ callSid: "CA_admission_floor_null",
2996
+ from: "+15557776666",
2997
+ to: "+15551111111",
2998
+ }),
2999
+ );
3000
+
3001
+ // Trusted contact proceeds normally — no deny.
3002
+ expect(relay.getConnectionState()).toBe("connected");
3003
+
3004
+ const updated = getCallSession(session.id);
3005
+ expect(updated!.status).toBe("in_progress");
3006
+
3007
+ relay.destroy();
3008
+ });
3009
+
2725
3010
  test("name capture flow: access request creates canonical request for guardian", async () => {
2726
3011
  ensureConversation("conv-access-req-canonical");
2727
3012
  const session = createCallSession({
@@ -256,7 +256,6 @@ describe("WhatsApp channel ingress attachment resolution", () => {
256
256
  {
257
257
  type: "whatsapp",
258
258
  address: WHATSAPP_USER_ID,
259
- externalUserId: WHATSAPP_USER_ID,
260
259
  status: "active",
261
260
  policy: "allow",
262
261
  },
@@ -1,10 +1,6 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- // These tests exercise the PATCH-side `workflowName` validation, which only
4
- // runs once the `workflows` feature flag is ON (the flag gate short-circuits
5
- // first when it is off). Mock the flag resolver to ON so the validation path is
6
- // reachable; the sibling schedule-routes.test.ts keeps the flag OFF and asserts
7
- // the flag-gate behavior, so the two files do not conflict.
3
+ // These tests exercise the PATCH-side `workflowName` validation.
8
4
  mock.module("../util/logger.js", () => ({
9
5
  getLogger: () =>
10
6
  new Proxy({} as Record<string, unknown>, {
@@ -12,11 +8,6 @@ mock.module("../util/logger.js", () => ({
12
8
  }),
13
9
  }));
14
10
 
15
- mock.module("../config/assistant-feature-flags.js", () => ({
16
- isAssistantFeatureFlagEnabled: (key: string) => key === "workflows",
17
- getAssistantFeatureFlagValue: (key: string) => key === "workflows",
18
- }));
19
-
20
11
  mock.module("../config/loader.js", () => ({
21
12
  getConfig: () => ({}),
22
13
  invalidateConfigCache: () => {},
@@ -1243,36 +1243,6 @@ describe("POST /schedules — create", () => {
1243
1243
  ).toThrow("Only 'execute' and 'workflow' modes are supported");
1244
1244
  });
1245
1245
 
1246
- test("rejects workflow-mode creation when the workflows flag is off", () => {
1247
- // The mocked getConfig returns no feature flags, so `workflows` is off.
1248
- expect(() =>
1249
- postCreate({
1250
- name: "Triage",
1251
- description: "Triage the inbox every morning",
1252
- expression: "0 9 * * *",
1253
- message: "triage inbox",
1254
- mode: "workflow",
1255
- workflowName: "triage-inbox",
1256
- }),
1257
- ).toThrow("Workflows are not enabled");
1258
- });
1259
-
1260
- test("rejects PATCH switching to workflow mode when the flag is off", () => {
1261
- const schedule = createSchedule({
1262
- name: "Plain execute",
1263
- cronExpression: "0 9 * * *",
1264
- message: "hi",
1265
- syntax: "cron",
1266
- });
1267
- const patch = findRoute("schedules/:id", "PATCH");
1268
- expect(() =>
1269
- patch.handler({
1270
- pathParams: { id: schedule.id },
1271
- body: { mode: "workflow", workflowName: "triage-inbox" },
1272
- }),
1273
- ).toThrow("Workflows are not enabled");
1274
- });
1275
-
1276
1246
  test("rejects an unparseable expression", () => {
1277
1247
  expect(() =>
1278
1248
  postCreate({