@vellumai/assistant 0.9.1-staging.1 → 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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -7,7 +7,6 @@
7
7
 
8
8
  import { z } from "zod";
9
9
 
10
- import { buildApprovalCardBlocks } from "./approval-card-builder.js";
11
10
  import {
12
11
  nonEmpty,
13
12
  sanitizeIdentityField,
@@ -312,74 +311,88 @@ export function buildAccessRequestContractText(
312
311
  return lines.join("\n");
313
312
  }
314
313
 
315
- // ── Seed content blocks (Surface-based rendering) ───────────────────────────
314
+ // ── Card view model ─────────────────────────────────────────────────────────
316
315
 
317
316
  /**
318
- * Build structured content blocks for an access request notification seed
319
- * message. Produces a `ui_surface` card block that the web/macOS/iOS apps
320
- * render as an interactive card via `SurfaceRouter CardSurface`, plus a
321
- * plain-text fallback block for search, CLI display, and backward-compatible
322
- * clients that don't support surfaces.
317
+ * Display-ready projection of an access request, shared by every renderer
318
+ * (the Vellum Surface card and the Slack Card block). It carries the
319
+ * sanitized, pre-computed facts each renderer needs identity sanitizing,
320
+ * warnings, permalink, DM detection, preview sanitizing so that projection
321
+ * lives in exactly one place. Renderers lay these facts out in their
322
+ * channel-native shape without re-deriving them.
323
323
  */
324
- export function buildAccessRequestSeedContentBlocks(
325
- payload: Record<string, unknown>,
326
- ): unknown[] {
327
- const p = parseAccessRequestPayload(payload);
324
+ export interface AccessRequestCardView {
325
+ /** Sanitized display name (actorDisplayName ?? senderIdentifier, else "Someone"). */
326
+ displayName: string;
327
+ /** Sanitized username, without the leading `@`. */
328
+ username: string | undefined;
329
+ /** Sanitized external ID. */
330
+ externalId: string | undefined;
331
+ sourceChannel: string | undefined;
332
+ conversationExternalId: string | undefined;
333
+ /** Whether the source Slack conversation is a DM. */
334
+ isSlackDm: boolean;
335
+ /** Slack permalink — present only for a slack source with conversation + ts. */
336
+ messagePermalink: string | undefined;
337
+ /** Sanitized message preview, or undefined when blank after sanitizing. */
338
+ messagePreview: string | undefined;
339
+ /** Human-readable trust/security warnings. */
340
+ warnings: string[];
341
+ guardianResolutionSource: string | undefined;
342
+ requestId: string | undefined;
343
+ }
328
344
 
345
+ /**
346
+ * Project a parsed access-request payload into display-ready card facts.
347
+ *
348
+ * The payload is parsed once upstream — the broadcaster resolves
349
+ * `accessRequestContext`, and the Surface seed path parses the raw payload —
350
+ * so this takes the parsed payload rather than re-parsing it.
351
+ */
352
+ export function buildAccessRequestCardView(
353
+ p: ParsedAccessRequestPayload,
354
+ ): AccessRequestCardView {
329
355
  const rawName = nonEmpty(p.actorDisplayName) ?? nonEmpty(p.senderIdentifier);
330
356
  const displayName = rawName ? sanitizeIdentityField(rawName) : "Someone";
331
357
 
332
- const metadata: Array<{ label: string; value: string }> = [];
358
+ const rawUsername = nonEmpty(p.actorUsername);
359
+ const username = rawUsername ? sanitizeIdentityField(rawUsername) : undefined;
333
360
 
334
- if (p.actorUsername) {
335
- metadata.push({
336
- label: "Username",
337
- value: `@${sanitizeIdentityField(p.actorUsername)}`,
338
- });
339
- }
361
+ const rawExternalId = nonEmpty(p.actorExternalId);
362
+ const externalId = rawExternalId
363
+ ? sanitizeIdentityField(rawExternalId)
364
+ : undefined;
340
365
 
341
- if (p.sourceChannel === "slack" && p.conversationExternalId) {
342
- const isDm = isSlackDmConversation(p.conversationExternalId);
343
- metadata.push({
344
- label: "Source",
345
- value: isDm
346
- ? "Slack — Direct message"
347
- : `Slack — #${p.conversationExternalId}`,
348
- });
349
- } else if (p.sourceChannel) {
350
- metadata.push({ label: "Source", value: p.sourceChannel });
351
- }
366
+ const sourceChannel = nonEmpty(p.sourceChannel);
367
+ const conversationExternalId = nonEmpty(p.conversationExternalId);
368
+ const messageTs = nonEmpty(p.messageTs);
352
369
 
353
- const warnings = buildAccessRequestWarnings(p);
354
- const bodyParts: string[] = [];
370
+ const isSlackDm =
371
+ sourceChannel === "slack" && conversationExternalId != null
372
+ ? isSlackDmConversation(conversationExternalId)
373
+ : false;
355
374
 
356
- if (p.messagePreview) {
357
- bodyParts.push(`> "${sanitizeMessagePreview(p.messagePreview)}"`);
358
- }
359
- for (const w of warnings) {
360
- bodyParts.push(`⚠️ ${w}`);
361
- }
362
- if (p.sourceChannel === "slack" && p.conversationExternalId && p.messageTs) {
363
- const permalink = buildSlackMessagePermalink(
364
- p.conversationExternalId,
365
- p.messageTs,
366
- );
367
- bodyParts.push(`[View message](${permalink})`);
368
- }
375
+ const messagePermalink =
376
+ sourceChannel === "slack" && conversationExternalId && messageTs
377
+ ? buildSlackMessagePermalink(conversationExternalId, messageTs)
378
+ : undefined;
379
+
380
+ const rawPreview = nonEmpty(p.messagePreview);
381
+ const messagePreview = rawPreview
382
+ ? sanitizeMessagePreview(rawPreview) || undefined
383
+ : undefined;
369
384
 
370
- const body =
371
- bodyParts.length > 0
372
- ? bodyParts.join("\n\n")
373
- : "No additional context available.";
374
-
375
- return buildApprovalCardBlocks({
376
- surfaceIdPrefix: "access-request",
377
- cardTitle: "Access Request",
378
- requesterName: displayName,
379
- subtitle: "Requesting access to the assistant",
380
- body,
381
- metadata,
382
- requestId: p.requestId,
383
- fallbackText: buildAccessRequestContractText(payload),
384
- });
385
+ return {
386
+ displayName,
387
+ username,
388
+ externalId,
389
+ sourceChannel,
390
+ conversationExternalId,
391
+ isSlackDm,
392
+ messagePermalink,
393
+ messagePreview,
394
+ warnings: buildAccessRequestWarnings(p),
395
+ guardianResolutionSource: nonEmpty(p.guardianResolutionSource),
396
+ requestId: nonEmpty(p.requestId),
397
+ };
385
398
  }
@@ -16,18 +16,11 @@ import { sendSlackReply } from "../../messaging/providers/slack/send.js";
16
16
  import type { ApprovalUIMetadata } from "../../runtime/channel-approval-types.js";
17
17
  import { getLogger } from "../../util/logger.js";
18
18
  import {
19
+ type AccessRequestCardView,
20
+ buildAccessRequestCardView,
19
21
  buildAccessRequestInviteDirective,
20
- buildAccessRequestWarnings,
21
- buildSlackMessagePermalink,
22
- isSlackDmConversation,
23
- type ParsedAccessRequestPayload,
24
22
  } from "../access-request-copy.js";
25
- import {
26
- nonEmpty,
27
- sanitizeIdentityField,
28
- sanitizeMessagePreview,
29
- truncate,
30
- } from "../notification-utils.js";
23
+ import { truncate } from "../notification-utils.js";
31
24
  import type {
32
25
  ChannelAdapter,
33
26
  ChannelDeliveryPayload,
@@ -61,57 +54,48 @@ function buildCardActions(approval: ApprovalUIMetadata): unknown[] {
61
54
  // ---------------------------------------------------------------------------
62
55
 
63
56
  /** Concise requester identity for the card subtitle (≤150 chars). */
64
- function buildAccessRequestSubtitle(p: ParsedAccessRequestPayload): string {
65
- const rawName = nonEmpty(p.actorDisplayName) ?? nonEmpty(p.senderIdentifier);
66
- const displayName = rawName ? sanitizeIdentityField(rawName) : "Someone";
67
- const parts = [displayName];
68
-
69
- const username = nonEmpty(p.actorUsername);
70
- if (username) {
71
- const safe = sanitizeIdentityField(username);
72
- if (safe !== displayName) parts.push(`(@${safe})`);
57
+ function buildAccessRequestSubtitle(view: AccessRequestCardView): string {
58
+ const parts = [view.displayName];
59
+
60
+ if (view.username && view.username !== view.displayName) {
61
+ parts.push(`(@${view.username})`);
73
62
  }
74
63
 
75
- if (p.sourceChannel) parts.push(`via ${p.sourceChannel}`);
64
+ if (view.sourceChannel) parts.push(`via ${view.sourceChannel}`);
76
65
 
77
66
  return truncate(parts.join(" "), 150);
78
67
  }
79
68
 
80
69
  /** Card body: message preview when available, otherwise a default label. */
81
- function buildAccessRequestBody(p: ParsedAccessRequestPayload): string {
82
- if (p.messagePreview) {
83
- const sanitized = sanitizeMessagePreview(p.messagePreview);
84
- if (sanitized) {
85
- // Truncate content before wrapping so formatting chars stay balanced.
86
- // Wrapper `> _"..."_` is 6 chars; reserve space for them.
87
- const trimmed = truncate(sanitized, 200 - 6);
88
- return `> _"${trimmed}"_`;
89
- }
70
+ function buildAccessRequestBody(view: AccessRequestCardView): string {
71
+ if (view.messagePreview) {
72
+ // Truncate content before wrapping so formatting chars stay balanced.
73
+ // Wrapper `> _"..."_` is 6 chars; reserve space for them.
74
+ const trimmed = truncate(view.messagePreview, 200 - 6);
75
+ return `> _"${trimmed}"_`;
90
76
  }
91
77
  return "Requesting access to the assistant";
92
78
  }
93
79
 
94
80
  /** Source-channel context block with Slack permalink when available. */
95
81
  function buildSourceContextBlock(
96
- p: ParsedAccessRequestPayload,
82
+ view: AccessRequestCardView,
97
83
  ): unknown | undefined {
98
- if (p.sourceChannel !== "slack" || !p.conversationExternalId) {
84
+ if (view.sourceChannel !== "slack" || !view.conversationExternalId) {
99
85
  return undefined;
100
86
  }
101
87
 
102
- const permalink = p.messageTs
103
- ? buildSlackMessagePermalink(p.conversationExternalId, p.messageTs)
104
- : undefined;
88
+ const permalink = view.messagePermalink;
105
89
 
106
90
  let sourceText: string;
107
- if (isSlackDmConversation(p.conversationExternalId)) {
91
+ if (view.isSlackDm) {
108
92
  sourceText = permalink
109
93
  ? `Source: Slack — Direct message · <${permalink}|View message>`
110
94
  : "Source: Slack — Direct message";
111
95
  } else {
112
96
  sourceText = permalink
113
- ? `Source: Slack — <#${p.conversationExternalId}> · <${permalink}|View message>`
114
- : `Source: Slack — <#${p.conversationExternalId}>`;
97
+ ? `Source: Slack — <#${view.conversationExternalId}> · <${permalink}|View message>`
98
+ : `Source: Slack — <#${view.conversationExternalId}>`;
115
99
  }
116
100
 
117
101
  return {
@@ -122,27 +106,12 @@ function buildSourceContextBlock(
122
106
 
123
107
  /** Stable requester identifier context block (external ID when it adds info). */
124
108
  function buildRequesterIdBlock(
125
- p: ParsedAccessRequestPayload,
109
+ view: AccessRequestCardView,
126
110
  ): unknown | undefined {
127
- const safeExternalId = nonEmpty(
128
- p.actorExternalId ? sanitizeIdentityField(p.actorExternalId) : undefined,
129
- );
111
+ const safeExternalId = view.externalId;
130
112
  if (!safeExternalId) return undefined;
131
113
 
132
- const displayedName = nonEmpty(
133
- p.actorDisplayName
134
- ? sanitizeIdentityField(p.actorDisplayName)
135
- : p.senderIdentifier
136
- ? sanitizeIdentityField(p.senderIdentifier)
137
- : undefined,
138
- );
139
- const displayedUsername = nonEmpty(
140
- p.actorUsername ? sanitizeIdentityField(p.actorUsername) : undefined,
141
- );
142
- if (
143
- safeExternalId === displayedName ||
144
- safeExternalId === displayedUsername
145
- ) {
114
+ if (safeExternalId === view.displayName || safeExternalId === view.username) {
146
115
  return undefined;
147
116
  }
148
117
 
@@ -156,7 +125,8 @@ function buildRequesterIdBlock(
156
125
  * Build Slack blocks for an access request using a native Card block.
157
126
  *
158
127
  * Layout:
159
- * Card — title + subtitle (identity) + body (preview) + actions + subtext (warnings)
128
+ * Card — title + subtitle (identity) + body (preview) + actions
129
+ * Context — security warnings (revoked/restricted/stranger), when present
160
130
  * Context — source permalink (when the request is from Slack)
161
131
  * Context — stable requester ID (when it adds info beyond subtitle)
162
132
  * Context — invite directive
@@ -166,32 +136,42 @@ function buildAccessRequestCardBlocks(
166
136
  payload: ChannelDeliveryPayload,
167
137
  ): unknown[] {
168
138
  const approval = payload.approvalContext!;
169
- const p = payload.accessRequestContext!;
139
+ const view = buildAccessRequestCardView(payload.accessRequestContext!);
170
140
  const blocks: unknown[] = [];
171
141
 
172
- const subtitle = buildAccessRequestSubtitle(p);
173
- const body = buildAccessRequestBody(p);
142
+ const subtitle = buildAccessRequestSubtitle(view);
143
+ const body = buildAccessRequestBody(view);
174
144
 
175
- const warnings = buildAccessRequestWarnings(p);
176
- const subtext =
177
- warnings.length > 0
178
- ? truncate(warnings.map((w) => `:warning: ${w}`).join(" · "), 200)
145
+ const warningsText =
146
+ view.warnings.length > 0
147
+ ? truncate(view.warnings.map((w) => `:warning: ${w}`).join(" · "), 200)
179
148
  : undefined;
180
149
 
181
150
  const card: Record<string, unknown> = {
182
151
  type: "card",
183
- title: { type: "plain_text", text: "Access Request" },
152
+ title: { type: "mrkdwn", text: "Access Request" },
184
153
  subtitle: { type: "mrkdwn", text: subtitle },
185
154
  body: { type: "mrkdwn", text: body },
186
155
  actions: buildCardActions(approval),
187
156
  };
188
- if (subtext) card.subtext = { type: "mrkdwn", text: subtext };
189
157
  blocks.push(card);
190
158
 
191
- const sourceContext = buildSourceContextBlock(p);
159
+ // Security warnings (revoked / restricted / stranger) render in a context
160
+ // block under the card. Slack's card block schema has no field for them
161
+ // (https://docs.slack.dev/reference/block-kit/blocks/card-block) and Slack
162
+ // silently drops unknown card fields, so a dedicated block is needed to
163
+ // surface them to the guardian.
164
+ if (warningsText) {
165
+ blocks.push({
166
+ type: "context",
167
+ elements: [{ type: "mrkdwn", text: warningsText }],
168
+ });
169
+ }
170
+
171
+ const sourceContext = buildSourceContextBlock(view);
192
172
  if (sourceContext) blocks.push(sourceContext);
193
173
 
194
- const idBlock = buildRequesterIdBlock(p);
174
+ const idBlock = buildRequesterIdBlock(view);
195
175
  if (idBlock) blocks.push(idBlock);
196
176
 
197
177
  blocks.push({
@@ -200,16 +180,16 @@ function buildAccessRequestCardBlocks(
200
180
  });
201
181
 
202
182
  if (
203
- (p.guardianResolutionSource === "vellum-anchor" ||
204
- p.guardianResolutionSource === "none") &&
205
- p.sourceChannel
183
+ (view.guardianResolutionSource === "vellum-anchor" ||
184
+ view.guardianResolutionSource === "none") &&
185
+ view.sourceChannel
206
186
  ) {
207
187
  blocks.push({
208
188
  type: "context",
209
189
  elements: [
210
190
  {
211
191
  type: "mrkdwn",
212
- text: `_You haven't verified your identity on ${p.sourceChannel} yet. If this was you trying to message your assistant, say "help me verify as guardian on ${p.sourceChannel}" to set up direct access._`,
192
+ text: `_You haven't verified your identity on ${view.sourceChannel} yet. If this was you trying to message your assistant, say "help me verify as guardian on ${view.sourceChannel}" to set up direct access._`,
213
193
  },
214
194
  ],
215
195
  });
@@ -249,7 +229,7 @@ function buildToolApprovalCardBlocks(
249
229
  const card: Record<string, unknown> = {
250
230
  type: "card",
251
231
  title: {
252
- type: "plain_text",
232
+ type: "mrkdwn",
253
233
  text: details ? "Tool Approval" : "Approval Request",
254
234
  },
255
235
  body: {
@@ -280,8 +260,10 @@ function buildToolApprovalCardBlocks(
280
260
  /**
281
261
  * Build Slack blocks for any notification carrying approval context.
282
262
  * Dispatches to the appropriate card builder based on source event type.
263
+ *
264
+ * Exported for characterization tests of the approval-card block output.
283
265
  */
284
- function buildApprovalNotificationBlocks(
266
+ export function buildApprovalNotificationBlocks(
285
267
  payload: ChannelDeliveryPayload,
286
268
  messageText: string,
287
269
  ): unknown[] {