@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
@@ -151,6 +151,57 @@ describe("ui_show dynamic_page app substitute guard", () => {
151
151
  expect(proxied).toBe(false);
152
152
  });
153
153
 
154
+ test("redirects a weak open model to a declarative surface, not 'resend HTML'", async () => {
155
+ let proxied = false;
156
+
157
+ const result = await uiShowTool.execute(
158
+ {
159
+ surface_type: "dynamic_page",
160
+ title: "Fable 5 vs MiniMax M3",
161
+ data: {},
162
+ },
163
+ {
164
+ conversationId: "conversation-123",
165
+ workingDir: "/tmp",
166
+ trustClass: "guardian",
167
+ attribution: {
168
+ resolvedModel: "accounts/fireworks/models/minimax-m3",
169
+ } as never,
170
+ proxyToolResolver: async () => {
171
+ proxied = true;
172
+ return { content: "proxied", isError: false };
173
+ },
174
+ },
175
+ );
176
+
177
+ expect(result.isError).toBe(true);
178
+ expect(result.content).toContain('surface_type: "table"');
179
+ expect(result.content).toContain("work_result");
180
+ expect(result.content).not.toContain("Resend ui_show with the full HTML");
181
+ expect(proxied).toBe(false);
182
+ });
183
+
184
+ test("keeps the resend-HTML hint for a capable model", async () => {
185
+ const result = await uiShowTool.execute(
186
+ {
187
+ surface_type: "dynamic_page",
188
+ title: "Fable 5 vs MiniMax M3",
189
+ data: {},
190
+ },
191
+ {
192
+ conversationId: "conversation-123",
193
+ workingDir: "/tmp",
194
+ trustClass: "guardian",
195
+ attribution: { resolvedModel: "claude-opus-4-8" } as never,
196
+ proxyToolResolver: async () => ({ content: "proxied", isError: false }),
197
+ },
198
+ );
199
+
200
+ expect(result.isError).toBe(true);
201
+ expect(result.content).toContain("Resend ui_show with the full HTML");
202
+ expect(result.content).not.toContain('surface_type: "table"');
203
+ });
204
+
154
205
  test("rejects dynamic_page with whitespace-only html and does not proxy", async () => {
155
206
  let proxied = false;
156
207
 
@@ -25,12 +25,14 @@ mock.module("../ipc/socket-path.js", () => ({
25
25
  // Track calls to refreshOverridesFromGateway
26
26
  // ---------------------------------------------------------------------------
27
27
  let refreshCallCount = 0;
28
+ let refreshReturnsLoaded = true;
28
29
 
29
30
  mock.module("../config/assistant-feature-flags.js", () => ({
30
31
  refreshOverridesFromGateway: async () => {
31
32
  refreshCallCount++;
33
+ return refreshReturnsLoaded;
32
34
  },
33
- initFeatureFlagOverrides: async () => {},
35
+ initFeatureFlagOverrides: async () => true,
34
36
  clearFeatureFlagOverridesCache: () => {},
35
37
  isAssistantFeatureFlagEnabled: () => true,
36
38
  }));
@@ -60,6 +62,32 @@ mock.module("../tools/registry.js", () => ({
60
62
  },
61
63
  }));
62
64
 
65
+ // ---------------------------------------------------------------------------
66
+ // Track reconcileFlagGatedProfiles calls and let each test control whether it
67
+ // reports a config change, so we can assert the listener broadcasts only when
68
+ // the managed profile set actually changes.
69
+ // ---------------------------------------------------------------------------
70
+ let reconcileCallCount = 0;
71
+ let reconcileReturns = false;
72
+
73
+ mock.module("../config/sync-gated-profiles.js", () => ({
74
+ reconcileFlagGatedProfiles: () => {
75
+ reconcileCallCount++;
76
+ return reconcileReturns;
77
+ },
78
+ }));
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Track publishConfigChanged so we can assert the picker-refresh broadcast.
82
+ // ---------------------------------------------------------------------------
83
+ let configChangedCount = 0;
84
+
85
+ mock.module("../runtime/sync/resource-sync-events.js", () => ({
86
+ publishConfigChanged: () => {
87
+ configChangedCount++;
88
+ },
89
+ }));
90
+
63
91
  // ---------------------------------------------------------------------------
64
92
  // Dynamic imports (after mock.module)
65
93
  // ---------------------------------------------------------------------------
@@ -115,8 +143,12 @@ describe("gateway-flag-listener", () => {
115
143
  beforeEach(() => {
116
144
  mkdirSync(testRoot, { recursive: true });
117
145
  refreshCallCount = 0;
146
+ refreshReturnsLoaded = true;
118
147
  syncToolsCallCount = 0;
119
148
  publishedTagSets = [];
149
+ reconcileCallCount = 0;
150
+ reconcileReturns = false;
151
+ configChangedCount = 0;
120
152
  testServer = createTestServer();
121
153
  });
122
154
 
@@ -180,6 +212,83 @@ describe("gateway-flag-listener", () => {
180
212
  ]);
181
213
  });
182
214
 
215
+ test("reconciles flag-gated profiles and broadcasts config_changed when the profile set changes", async () => {
216
+ reconcileReturns = true;
217
+ await new Promise<void>((resolve) => {
218
+ testServer.server.listen(socketPath, resolve);
219
+ });
220
+
221
+ startGatewayFlagListener();
222
+ await testServer.waitForClient();
223
+ await new Promise((r) => setTimeout(r, 100));
224
+
225
+ // Connect path reconciles once and, since it reports a change, broadcasts.
226
+ expect(reconcileCallCount).toBe(1);
227
+ expect(configChangedCount).toBe(1);
228
+
229
+ testServer.emit("feature_flags_changed");
230
+ await new Promise((r) => setTimeout(r, 100));
231
+
232
+ expect(reconcileCallCount).toBe(2);
233
+ expect(configChangedCount).toBe(2);
234
+ });
235
+
236
+ test("reconciles but does not broadcast config_changed when the profile set is unchanged", async () => {
237
+ reconcileReturns = false;
238
+ await new Promise<void>((resolve) => {
239
+ testServer.server.listen(socketPath, resolve);
240
+ });
241
+
242
+ startGatewayFlagListener();
243
+ await testServer.waitForClient();
244
+ await new Promise((r) => setTimeout(r, 100));
245
+
246
+ testServer.emit("feature_flags_changed");
247
+ await new Promise((r) => setTimeout(r, 100));
248
+
249
+ expect(reconcileCallCount).toBeGreaterThanOrEqual(2);
250
+ expect(configChangedCount).toBe(0);
251
+ });
252
+
253
+ test("does not reconcile profiles when the refresh reports flags did not load", async () => {
254
+ reconcileReturns = true;
255
+ refreshReturnsLoaded = false;
256
+ await new Promise<void>((resolve) => {
257
+ testServer.server.listen(socketPath, resolve);
258
+ });
259
+
260
+ startGatewayFlagListener();
261
+ await testServer.waitForClient();
262
+ await new Promise((r) => setTimeout(r, 100));
263
+
264
+ // Tool sync stays unconditional; the profile reconcile/broadcast are gated.
265
+ expect(syncToolsCallCount).toBe(1);
266
+ expect(reconcileCallCount).toBe(0);
267
+ expect(configChangedCount).toBe(0);
268
+
269
+ testServer.emit("feature_flags_changed");
270
+ await new Promise((r) => setTimeout(r, 100));
271
+
272
+ expect(syncToolsCallCount).toBe(2);
273
+ expect(reconcileCallCount).toBe(0);
274
+ expect(configChangedCount).toBe(0);
275
+ });
276
+
277
+ test("reconciles profiles when the refresh reports flags loaded", async () => {
278
+ reconcileReturns = true;
279
+ refreshReturnsLoaded = true;
280
+ await new Promise<void>((resolve) => {
281
+ testServer.server.listen(socketPath, resolve);
282
+ });
283
+
284
+ startGatewayFlagListener();
285
+ await testServer.waitForClient();
286
+ await new Promise((r) => setTimeout(r, 100));
287
+
288
+ expect(reconcileCallCount).toBe(1);
289
+ expect(configChangedCount).toBe(1);
290
+ });
291
+
183
292
  test("ignores non-flag events", async () => {
184
293
  await new Promise<void>((resolve) => {
185
294
  testServer.server.listen(socketPath, resolve);
@@ -44,7 +44,7 @@ describe("healGuardianBindingDrift", () => {
44
44
  const guardian = findGuardianForChannel("vellum");
45
45
  expect(guardian).not.toBeNull();
46
46
  expect(guardian!.contact.principalId).toBe("vellum-principal-old-uuid");
47
- expect(guardian!.channel.externalUserId).toBe("vellum-principal-old-uuid");
47
+ expect(guardian!.channel.address).toBe("vellum-principal-old-uuid");
48
48
  });
49
49
 
50
50
  test("no-op when principals already match", () => {
@@ -0,0 +1,403 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ mock.module("../util/logger.js", () => ({
4
+ getLogger: () =>
5
+ new Proxy({} as Record<string, unknown>, {
6
+ get: () => () => {},
7
+ }),
8
+ truncateForLog: (value: string) => value,
9
+ }));
10
+
11
+ const completeSurfaceAndNotify = mock(() => {});
12
+ mock.module("../daemon/conversation-surfaces.js", () => ({
13
+ completeSurfaceAndNotify,
14
+ }));
15
+
16
+ const withdrawSlackApprovalCard = mock(
17
+ async (_params: Record<string, unknown>) => {},
18
+ );
19
+ mock.module("../messaging/providers/slack/withdraw.js", () => ({
20
+ withdrawSlackApprovalCard,
21
+ }));
22
+
23
+ import { withdrawGuardianRequestCards } from "../approvals/guardian-card-withdrawal.js";
24
+ import {
25
+ type CanonicalGuardianRequest,
26
+ createCanonicalGuardianDelivery,
27
+ createCanonicalGuardianRequest,
28
+ getPendingCanonicalRequestByDestinationMessage,
29
+ listCanonicalGuardianDeliveries,
30
+ } from "../memory/canonical-guardian-store.js";
31
+ import { getDb } from "../memory/db-connection.js";
32
+ import { initializeDb } from "../memory/db-init.js";
33
+ import {
34
+ recordApprovalCardDelivery,
35
+ recordGuardianRequestDeliveries,
36
+ } from "../notifications/canonical-delivery-recorder.js";
37
+
38
+ initializeDb();
39
+
40
+ const PRINCIPAL_ID = "withdrawal-test-principal";
41
+
42
+ function resetTables(): void {
43
+ const db = getDb();
44
+ db.run("DELETE FROM canonical_guardian_deliveries");
45
+ db.run("DELETE FROM canonical_guardian_requests");
46
+ }
47
+
48
+ function makeRequest(
49
+ overrides: Partial<Parameters<typeof createCanonicalGuardianRequest>[0]> = {},
50
+ ): CanonicalGuardianRequest {
51
+ return createCanonicalGuardianRequest({
52
+ kind: "access_request",
53
+ sourceType: "channel",
54
+ sourceChannel: "slack",
55
+ guardianPrincipalId: PRINCIPAL_ID,
56
+ ...overrides,
57
+ });
58
+ }
59
+
60
+ describe("withdrawGuardianRequestCards", () => {
61
+ beforeEach(() => {
62
+ resetTables();
63
+ completeSurfaceAndNotify.mockClear();
64
+ withdrawSlackApprovalCard.mockClear();
65
+ });
66
+
67
+ test("withdraws + broadcasts the in-app card when the decision came from another surface", async () => {
68
+ const req = makeRequest();
69
+ createCanonicalGuardianDelivery({
70
+ requestId: req.id,
71
+ destinationChannel: "vellum",
72
+ destinationConversationId: "conv-1",
73
+ });
74
+
75
+ await withdrawGuardianRequestCards({
76
+ request: req,
77
+ status: "approved",
78
+ originChannel: "slack",
79
+ });
80
+
81
+ expect(completeSurfaceAndNotify).toHaveBeenCalledTimes(1);
82
+ expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
83
+ "conv-1",
84
+ `access-request-${req.id}`,
85
+ "Approved",
86
+ );
87
+ });
88
+
89
+ test("skips the in-app card when the decision originated in-app", async () => {
90
+ const req = makeRequest();
91
+ createCanonicalGuardianDelivery({
92
+ requestId: req.id,
93
+ destinationChannel: "vellum",
94
+ destinationConversationId: "conv-1",
95
+ });
96
+
97
+ await withdrawGuardianRequestCards({
98
+ request: req,
99
+ status: "approved",
100
+ originChannel: "vellum",
101
+ });
102
+
103
+ // The acting in-app client already completed its own card optimistically.
104
+ expect(completeSurfaceAndNotify).not.toHaveBeenCalled();
105
+ });
106
+
107
+ test("withdraws the Slack card with decider and decision time", async () => {
108
+ const req = makeRequest({ decidedByExternalUserId: "U-guardian" });
109
+ createCanonicalGuardianDelivery({
110
+ requestId: req.id,
111
+ destinationChannel: "slack",
112
+ destinationChatId: "C123",
113
+ destinationMessageId: "1700000000.0001",
114
+ });
115
+
116
+ await withdrawGuardianRequestCards({
117
+ request: req,
118
+ status: "denied",
119
+ originChannel: "vellum",
120
+ });
121
+
122
+ expect(withdrawSlackApprovalCard).toHaveBeenCalledTimes(1);
123
+ const [params] = withdrawSlackApprovalCard.mock.calls[0];
124
+ expect(params).toMatchObject({
125
+ channel: "C123",
126
+ messageTs: "1700000000.0001",
127
+ status: "denied",
128
+ decidedByExternalUserId: "U-guardian",
129
+ });
130
+ expect(typeof (params as { decidedAtMs?: number }).decidedAtMs).toBe(
131
+ "number",
132
+ );
133
+ });
134
+
135
+ test("skips the Slack edit when no channel message id was captured", async () => {
136
+ const req = makeRequest();
137
+ createCanonicalGuardianDelivery({
138
+ requestId: req.id,
139
+ destinationChannel: "slack",
140
+ destinationChatId: "C123",
141
+ });
142
+
143
+ await withdrawGuardianRequestCards({ request: req, status: "approved" });
144
+
145
+ expect(withdrawSlackApprovalCard).not.toHaveBeenCalled();
146
+ });
147
+
148
+ test("withdraws every surface (including in-app broadcast) when no origin channel", async () => {
149
+ const req = makeRequest();
150
+ createCanonicalGuardianDelivery({
151
+ requestId: req.id,
152
+ destinationChannel: "vellum",
153
+ destinationConversationId: "conv-1",
154
+ });
155
+ createCanonicalGuardianDelivery({
156
+ requestId: req.id,
157
+ destinationChannel: "slack",
158
+ destinationChatId: "C1",
159
+ destinationMessageId: "1.0",
160
+ });
161
+
162
+ await withdrawGuardianRequestCards({ request: req, status: "expired" });
163
+
164
+ expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
165
+ "conv-1",
166
+ `access-request-${req.id}`,
167
+ "Expired",
168
+ );
169
+ expect(withdrawSlackApprovalCard).toHaveBeenCalledTimes(1);
170
+ });
171
+
172
+ test("ignores channels without in-place edit support (telegram)", async () => {
173
+ const req = makeRequest({ sourceChannel: "telegram" });
174
+ createCanonicalGuardianDelivery({
175
+ requestId: req.id,
176
+ destinationChannel: "telegram",
177
+ destinationChatId: "T1",
178
+ destinationMessageId: "9",
179
+ });
180
+
181
+ await withdrawGuardianRequestCards({ request: req, status: "approved" });
182
+
183
+ expect(withdrawSlackApprovalCard).not.toHaveBeenCalled();
184
+ expect(completeSurfaceAndNotify).not.toHaveBeenCalled();
185
+ });
186
+
187
+ test("is best-effort: a failing surface never blocks the others or throws", async () => {
188
+ withdrawSlackApprovalCard.mockImplementationOnce(async () => {
189
+ throw new Error("slack unavailable");
190
+ });
191
+ const req = makeRequest();
192
+ createCanonicalGuardianDelivery({
193
+ requestId: req.id,
194
+ destinationChannel: "slack",
195
+ destinationChatId: "C1",
196
+ destinationMessageId: "1.0",
197
+ });
198
+ createCanonicalGuardianDelivery({
199
+ requestId: req.id,
200
+ destinationChannel: "vellum",
201
+ destinationConversationId: "conv-1",
202
+ });
203
+
204
+ await expect(
205
+ withdrawGuardianRequestCards({
206
+ request: req,
207
+ status: "approved",
208
+ originChannel: "telegram",
209
+ }),
210
+ ).resolves.toBeUndefined();
211
+
212
+ // The in-app card was still withdrawn despite the Slack failure.
213
+ expect(completeSurfaceAndNotify).toHaveBeenCalledTimes(1);
214
+ });
215
+
216
+ test("tool-approval cards resolve to the tool-approval surface id", async () => {
217
+ const req = makeRequest({ kind: "tool_approval", toolName: "shell" });
218
+ createCanonicalGuardianDelivery({
219
+ requestId: req.id,
220
+ destinationChannel: "vellum",
221
+ destinationConversationId: "conv-1",
222
+ });
223
+
224
+ await withdrawGuardianRequestCards({
225
+ request: req,
226
+ status: "approved",
227
+ originChannel: "telegram",
228
+ });
229
+
230
+ expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
231
+ "conv-1",
232
+ `tool-approval-${req.id}`,
233
+ "Approved",
234
+ );
235
+ });
236
+ });
237
+
238
+ describe("recordApprovalCardDelivery", () => {
239
+ beforeEach(() => {
240
+ resetTables();
241
+ });
242
+
243
+ test("records a channel card with its addressing and status", () => {
244
+ const req = makeRequest();
245
+ const delivery = recordApprovalCardDelivery({
246
+ requestId: req.id,
247
+ channel: "slack",
248
+ chatId: "C1",
249
+ messageId: "1700000000.0001",
250
+ status: "sent",
251
+ });
252
+ expect(delivery?.destinationChannel).toBe("slack");
253
+ expect(delivery?.destinationChatId).toBe("C1");
254
+ expect(delivery?.destinationMessageId).toBe("1700000000.0001");
255
+ expect(delivery?.status).toBe("sent");
256
+ });
257
+
258
+ test("records a vellum card addressed by conversation id, defaulting to pending", () => {
259
+ const req = makeRequest();
260
+ const delivery = recordApprovalCardDelivery({
261
+ requestId: req.id,
262
+ channel: "vellum",
263
+ conversationId: "conv-x",
264
+ });
265
+ expect(delivery?.destinationConversationId).toBe("conv-x");
266
+ expect(delivery?.destinationChatId).toBeNull();
267
+ expect(delivery?.status).toBe("pending");
268
+ });
269
+
270
+ test("lets a Slack reaction resolve back to its request (LUM-2502)", () => {
271
+ // A delivered Slack approval card must be addressable by (channel, chat, ts)
272
+ // so an emoji reaction on it resolves to the right request rather than
273
+ // silently falling through to transcript persistence.
274
+ const req = makeRequest();
275
+ recordApprovalCardDelivery({
276
+ requestId: req.id,
277
+ channel: "slack",
278
+ chatId: "C-guardian",
279
+ messageId: "1700000000.5678",
280
+ status: "sent",
281
+ });
282
+
283
+ const resolved = getPendingCanonicalRequestByDestinationMessage(
284
+ "slack",
285
+ "C-guardian",
286
+ "1700000000.5678",
287
+ );
288
+ expect(resolved?.id).toBe(req.id);
289
+ });
290
+ });
291
+
292
+ describe("recordGuardianRequestDeliveries", () => {
293
+ beforeEach(() => {
294
+ resetTables();
295
+ withdrawSlackApprovalCard.mockClear();
296
+ });
297
+
298
+ test("records each delivery with addressing + status and returns the vellum id", () => {
299
+ const req = makeRequest();
300
+ const vellumId = recordGuardianRequestDeliveries({
301
+ requestId: req.id,
302
+ deliveryResults: [
303
+ {
304
+ channel: "vellum",
305
+ destination: "",
306
+ status: "sent",
307
+ conversationId: "conv-1",
308
+ },
309
+ {
310
+ channel: "slack",
311
+ destination: "C999",
312
+ status: "sent",
313
+ messageId: "1700000000.1234",
314
+ },
315
+ ],
316
+ });
317
+
318
+ const deliveries = listCanonicalGuardianDeliveries(req.id);
319
+ expect(deliveries).toHaveLength(2);
320
+ const vellum = deliveries.find((d) => d.destinationChannel === "vellum");
321
+ const slack = deliveries.find((d) => d.destinationChannel === "slack");
322
+ expect(vellumId).toBe(vellum?.id);
323
+ expect(vellum?.destinationConversationId).toBe("conv-1");
324
+ expect(vellum?.status).toBe("sent");
325
+ expect(slack?.destinationChatId).toBe("C999");
326
+ expect(slack?.destinationMessageId).toBe("1700000000.1234");
327
+ expect(slack?.status).toBe("sent");
328
+ });
329
+
330
+ test("reuses a pre-created vellum row instead of creating a second", () => {
331
+ const req = makeRequest();
332
+ const pre = recordApprovalCardDelivery({
333
+ requestId: req.id,
334
+ channel: "vellum",
335
+ conversationId: "conv-1",
336
+ });
337
+
338
+ const vellumId = recordGuardianRequestDeliveries({
339
+ requestId: req.id,
340
+ deliveryResults: [
341
+ {
342
+ channel: "vellum",
343
+ destination: "",
344
+ status: "sent",
345
+ conversationId: "conv-1",
346
+ },
347
+ ],
348
+ vellumDeliveryId: pre?.id,
349
+ });
350
+
351
+ expect(vellumId).toBe(pre?.id);
352
+ const deliveries = listCanonicalGuardianDeliveries(req.id);
353
+ expect(deliveries).toHaveLength(1);
354
+ expect(deliveries[0].status).toBe("sent");
355
+ });
356
+
357
+ test("marks a non-sent delivery result as failed (status now tracked for all producers)", () => {
358
+ const req = makeRequest();
359
+ recordGuardianRequestDeliveries({
360
+ requestId: req.id,
361
+ deliveryResults: [
362
+ { channel: "slack", destination: "C1", status: "failed" },
363
+ ],
364
+ });
365
+ const [delivery] = listCanonicalGuardianDeliveries(req.id);
366
+ expect(delivery.status).toBe("failed");
367
+ });
368
+
369
+ test("omits chat id when the channel destination is empty", () => {
370
+ const req = makeRequest();
371
+ recordGuardianRequestDeliveries({
372
+ requestId: req.id,
373
+ deliveryResults: [{ channel: "slack", destination: "", status: "sent" }],
374
+ });
375
+ const [delivery] = listCanonicalGuardianDeliveries(req.id);
376
+ expect(delivery.destinationChatId).toBeNull();
377
+ });
378
+
379
+ test("records a Slack delivery the withdrawal path can then edit in place", async () => {
380
+ const req = makeRequest();
381
+ recordGuardianRequestDeliveries({
382
+ requestId: req.id,
383
+ deliveryResults: [
384
+ {
385
+ channel: "slack",
386
+ destination: "C999",
387
+ status: "sent",
388
+ messageId: "1700000000.1234",
389
+ },
390
+ ],
391
+ });
392
+
393
+ await withdrawGuardianRequestCards({
394
+ request: req,
395
+ status: "approved",
396
+ originChannel: "vellum",
397
+ });
398
+ const [params] = withdrawSlackApprovalCard.mock.calls[0];
399
+ expect((params as { messageTs?: string }).messageTs).toBe(
400
+ "1700000000.1234",
401
+ );
402
+ });
403
+ });
@@ -235,8 +235,10 @@ describe("applyCanonicalGuardianDecision", () => {
235
235
 
236
236
  test("rejects decision when request has no guardianPrincipalId", async () => {
237
237
  // unknown_kind is not in DECISIONABLE_KINDS so it can be created without
238
- // guardianPrincipalId, but the decision primitive still rejects because
239
- // the request is missing its principal binding.
238
+ // guardianPrincipalId. A request with no bound principal can never be
239
+ // authorized by anyone — this is a data-integrity fault
240
+ // (request_misconfigured), not an authorization denial against the actor,
241
+ // so it must not be reported as identity_mismatch / "no permission".
240
242
  const req = createCanonicalGuardianRequest({
241
243
  kind: "unknown_kind",
242
244
  sourceType: "channel",
@@ -253,7 +255,7 @@ describe("applyCanonicalGuardianDecision", () => {
253
255
 
254
256
  expect(result.applied).toBe(false);
255
257
  if (result.applied) return;
256
- expect(result.reason).toBe("identity_mismatch");
258
+ expect(result.reason).toBe("request_misconfigured");
257
259
  });
258
260
 
259
261
  test("rejects decision when actor has no guardianPrincipalId", async () => {
@@ -36,10 +36,6 @@ import {
36
36
  import * as approvalMessageComposer from "../runtime/approval-message-composer.js";
37
37
  import * as gatewayClient from "../runtime/gateway-client.js";
38
38
  import * as pendingInteractions from "../runtime/pending-interactions.js";
39
- import {
40
- _clearApprovalPromptTsTrackerForTesting,
41
- trackApprovalPromptTs,
42
- } from "../runtime/routes/approval-prompt-ts-tracker.js";
43
39
  import { handleApprovalInterception } from "../runtime/routes/guardian-approval-interception.js";
44
40
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
45
41
 
@@ -69,7 +65,6 @@ function resetTables(): void {
69
65
  /* tables may not exist yet */
70
66
  }
71
67
  pendingInteractions.clear();
72
- _clearApprovalPromptTsTrackerForTesting();
73
68
  }
74
69
 
75
70
  function createTestGuardianApproval(
@@ -224,36 +219,9 @@ describe("guardian grant minting on tool-approval decisions", () => {
224
219
  composeSpy.mockRestore();
225
220
  });
226
221
 
227
- test("guardian reaction white_check_mark maps to approve_once (legacy compat)", async () => {
228
- const requestId = "req-grant-reaction-1";
229
- createTestGuardianApproval(requestId, {
230
- conversationId: CONVERSATION_ID,
231
- channel: "slack",
232
- guardianChatId: GUARDIAN_CHAT,
233
- });
234
- registerPendingInteraction(requestId, CONVERSATION_ID, TOOL_NAME);
235
- const approvalMessageTs = "1700000000.000100";
236
- trackApprovalPromptTs("slack", GUARDIAN_CHAT, approvalMessageTs);
237
-
238
- const result = await handleApprovalInterception({
239
- conversationId: "guardian-conv-1",
240
- callbackData: "reaction:white_check_mark",
241
- content: "",
242
- conversationExternalId: GUARDIAN_CHAT,
243
- sourceChannel: "slack",
244
- actorExternalId: GUARDIAN_USER,
245
- replyCallbackUrl: "https://gateway.test/deliver",
246
- trustCtx: makeTrustContext(),
247
- assistantId: ASSISTANT_ID,
248
- approvalMessageTs,
249
- });
250
-
251
- // white_check_mark is mapped to approve_once (backward compat) — the
252
- // pending approval is resolved and a grant is minted.
253
- expect(result.handled).toBe(true);
254
- expect(result.type).toBe("guardian_decision_applied");
255
- expect(countGrants()).toBe(1);
256
- });
222
+ // Reaction-based approvals are exercised against the canonical pipeline in
223
+ // slack-reaction-canonical-approval.test.ts — reactions route through
224
+ // routeGuardianReply, not handleApprovalInterception.
257
225
 
258
226
  // ── 2. approve_once for non-tool-approval does NOT mint a grant ──
259
227