@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
@@ -16,7 +16,10 @@ import {
16
16
  } from "../../../memory/canonical-guardian-store.js";
17
17
  import { getLogger } from "../../../util/logger.js";
18
18
  import { deliverChannelReply } from "../../gateway-client.js";
19
- import { routeGuardianReply } from "../../guardian-reply-router.js";
19
+ import {
20
+ type GuardianPendingScope,
21
+ routeGuardianReply,
22
+ } from "../../guardian-reply-router.js";
20
23
  import type { ApprovalConversationGenerator } from "../../http-types.js";
21
24
 
22
25
  const log = getLogger("runtime-http");
@@ -30,6 +33,12 @@ export interface GuardianReplyInterceptParams {
30
33
  trimmedContent: string;
31
34
  hasCallbackData: boolean;
32
35
  callbackData: string | undefined;
36
+ /**
37
+ * For emoji-reaction decisions: the channel-native id (Slack `ts`) of the
38
+ * message the reaction was attached to. Threaded to the router so it can
39
+ * recover the target request from the reacted card's delivery record.
40
+ */
41
+ reactedMessageTs?: string;
33
42
  rawSenderId: string | undefined;
34
43
  canonicalSenderId: string | null;
35
44
  canonicalAssistantId: string;
@@ -65,6 +74,7 @@ export async function handleGuardianReplyIntercept(
65
74
  trimmedContent,
66
75
  hasCallbackData,
67
76
  callbackData,
77
+ reactedMessageTs,
68
78
  rawSenderId,
69
79
  canonicalSenderId,
70
80
  canonicalAssistantId,
@@ -102,43 +112,51 @@ export async function handleGuardianReplyIntercept(
102
112
  // based pending requests so that requests without delivery rows (e.g.
103
113
  // tool_approval requests created inline) are not silently excluded.
104
114
  //
105
- // On Slack, when no delivery-scoped results exist for the current
106
- // chat, pass [] (empty array) instead of undefined. This prevents the
107
- // router's identity-based fallback from intercepting unrelated
115
+ // On Slack, when no delivery-scoped results exist for the current chat,
116
+ // use `{ mode: "blocked" }` rather than identity-fallback. This prevents
117
+ // the router's identity-based fallback from intercepting unrelated
108
118
  // messages in other channels/threads — a cross-chat hijacking vector
109
119
  // unique to Slack where a single guardian is active in many threaded
110
120
  // contexts. Explicit callbacks (apr:<id>:<action>) and request codes
111
121
  // still work cross-chat because they carry specific request
112
- // identifiers and bypass the pendingRequests list.
122
+ // identifiers and bypass the pending-request scope.
113
123
  //
114
- // Non-Slack channels (Telegram, WhatsApp) keep undefined so the
124
+ // Non-Slack channels (Telegram, WhatsApp) leave the scope unset so the
115
125
  // identity-based fallback stays active. On those channels, delivery
116
126
  // rows are created asynchronously (fire-and-forget .then()) so the
117
127
  // guardian can reply before the row is persisted. Cross-chat
118
128
  // contamination is unlikely there because each chat is a distinct
119
129
  // conversation with no thread concept.
120
- const deliveryScopedPendingRequests =
121
- listPendingCanonicalGuardianRequestsByDestinationChat(
122
- sourceChannel,
123
- conversationExternalId,
124
- );
125
- let pendingRequestIds: string[] | undefined;
126
- if (deliveryScopedPendingRequests.length > 0) {
127
- const deliveryIds = new Set(deliveryScopedPendingRequests.map((r) => r.id));
128
- // Also include identity-based pending requests so we don't hide them
129
- const identityId = canonicalSenderId ?? rawSenderId!;
130
- const identityPending = listCanonicalGuardianRequests({
131
- status: "pending",
132
- guardianExternalUserId: identityId,
133
- });
134
- for (const r of identityPending) {
135
- deliveryIds.add(r.id);
130
+ // Reactions address one specific request by the reacted card's message id,
131
+ // so they bypass the pending-request list scoping that the text/NL paths
132
+ // need — the router's reaction branch resolves the target directly.
133
+ const isReaction = callbackData?.startsWith("reaction:") === true;
134
+ let pendingScope: GuardianPendingScope | undefined;
135
+ if (!isReaction) {
136
+ const deliveryScopedPendingRequests =
137
+ listPendingCanonicalGuardianRequestsByDestinationChat(
138
+ sourceChannel,
139
+ conversationExternalId,
140
+ );
141
+ if (deliveryScopedPendingRequests.length > 0) {
142
+ const deliveryIds = new Set(
143
+ deliveryScopedPendingRequests.map((r) => r.id),
144
+ );
145
+ // Also include identity-based pending requests so we don't hide them
146
+ const identityId = canonicalSenderId ?? rawSenderId!;
147
+ const identityPending = listCanonicalGuardianRequests({
148
+ status: "pending",
149
+ guardianExternalUserId: identityId,
150
+ });
151
+ for (const r of identityPending) {
152
+ deliveryIds.add(r.id);
153
+ }
154
+ pendingScope = { mode: "scoped", requestIds: [...deliveryIds] };
155
+ } else if (sourceChannel === "slack") {
156
+ // Block identity-based fallback on Slack to prevent cross-chat
157
+ // NL/free-text interception. See comment above for rationale.
158
+ pendingScope = { mode: "blocked" };
136
159
  }
137
- pendingRequestIds = [...deliveryIds];
138
- } else if (sourceChannel === "slack") {
139
- // Block identity-based fallback on Slack to prevent cross-chat
140
- // NL/free-text interception. See comment above for rationale.
141
- pendingRequestIds = [];
142
160
  }
143
161
 
144
162
  const routerResult = await routeGuardianReply({
@@ -152,7 +170,8 @@ export async function handleGuardianReplyIntercept(
152
170
  },
153
171
  conversationId,
154
172
  callbackData,
155
- pendingRequestIds,
173
+ reactedMessageTs,
174
+ pendingScope,
156
175
  approvalConversationGenerator,
157
176
  channelDeliveryContext: {
158
177
  replyCallbackUrl,
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Slack reaction intercept stage.
3
+ *
4
+ * Reactions are passive channel signals — not messages, and not access
5
+ * attempts. They are dispatched here *before* the message pipeline (ACL,
6
+ * admission floor, disk-pressure block, conversation binding) so that:
7
+ *
8
+ * - a 👍 never triggers an ingress access challenge / verification handshake
9
+ * or an access-request notification (LUM-2489),
10
+ * - a stranger's reaction creates no conversation, binding, or transcript
11
+ * row — it is dropped as channel noise,
12
+ * - a known contact's reaction is recorded as an inline transcript signal,
13
+ * - a guardian's reaction on an approval card is routed through the canonical
14
+ * guardian decision pipeline (the same path as buttons and text replies).
15
+ *
16
+ * Reactions never drive an agent turn.
17
+ */
18
+ import type { SourceMetadata } from "@vellumai/gateway-client";
19
+
20
+ import type { ChannelId, InterfaceId } from "../../../channels/types.js";
21
+ import { getDiskPressureStatus } from "../../../daemon/disk-pressure-guard.js";
22
+ import { classifyDiskPressureTurnPolicy } from "../../../daemon/disk-pressure-policy.js";
23
+ import { addMessage } from "../../../memory/conversation-crud.js";
24
+ import {
25
+ clearPayload,
26
+ linkMessage,
27
+ recordInbound,
28
+ } from "../../../memory/delivery-crud.js";
29
+ import { markProcessed } from "../../../memory/delivery-status.js";
30
+ import { upsertBinding } from "../../../memory/external-conversation-store.js";
31
+ import {
32
+ type SlackMessageMetadata,
33
+ writeSlackMetadata,
34
+ } from "../../../messaging/providers/slack/message-metadata.js";
35
+ import { getLogger } from "../../../util/logger.js";
36
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
37
+ import type { ApprovalConversationGenerator } from "../../http-types.js";
38
+ import { resolveTrustContext } from "../../trust-context-resolver.js";
39
+ import { handleGuardianReplyIntercept } from "./guardian-reply-intercept.js";
40
+
41
+ const log = getLogger("runtime-http");
42
+
43
+ /**
44
+ * Detect a Slack reaction event by inspecting the inbound payload's
45
+ * `callbackData` prefix. The gateway encodes reactions as a unified
46
+ * `SlackInboundEvent` with `callbackData` of the form `reaction:<emoji>`
47
+ * (added) or `reaction_removed:<emoji>` (removed) — see
48
+ * `gateway/src/slack/normalize.ts`. This helper centralizes that convention
49
+ * so the daemon can route reactions to this dedicated stage instead of the
50
+ * agent-response pipeline.
51
+ */
52
+ export function isSlackReactionEvent(body: {
53
+ sourceChannel?: string;
54
+ callbackData?: string;
55
+ }): boolean {
56
+ if (body.sourceChannel !== "slack") return false;
57
+ const cb = body.callbackData;
58
+ if (typeof cb !== "string") return false;
59
+ return cb.startsWith("reaction:") || cb.startsWith("reaction_removed:");
60
+ }
61
+
62
+ /**
63
+ * Parse a reaction `callbackData` string into its op (added/removed) and
64
+ * emoji name. Returns `null` when the input is not a reaction prefix or
65
+ * when the emoji portion is empty.
66
+ */
67
+ export function parseSlackReactionCallbackData(
68
+ callbackData: string,
69
+ ): { op: "added" | "removed"; emoji: string } | null {
70
+ let op: "added" | "removed";
71
+ let emoji: string;
72
+ if (callbackData.startsWith("reaction_removed:")) {
73
+ op = "removed";
74
+ emoji = callbackData.slice("reaction_removed:".length);
75
+ } else if (callbackData.startsWith("reaction:")) {
76
+ op = "added";
77
+ emoji = callbackData.slice("reaction:".length);
78
+ } else {
79
+ return null;
80
+ }
81
+ if (emoji.length === 0) return null;
82
+ return { op, emoji };
83
+ }
84
+
85
+ export interface ReactionInterceptParams {
86
+ /** The reaction callbackData (`reaction:<emoji>` / `reaction_removed:<emoji>`). */
87
+ callbackData: string;
88
+ sourceChannel: ChannelId;
89
+ sourceInterface: InterfaceId | undefined;
90
+ conversationExternalId: string;
91
+ externalMessageId: string;
92
+ canonicalAssistantId: string;
93
+ rawSenderId: string | undefined;
94
+ canonicalSenderId: string | null;
95
+ actorDisplayName: string | undefined;
96
+ actorUsername: string | undefined;
97
+ replyCallbackUrl: string | undefined;
98
+ sourceMetadata: SourceMetadata | undefined;
99
+ /** Slack channel display name, for the conversation binding. */
100
+ slackChannelName: string | null;
101
+ approvalConversationGenerator: ApprovalConversationGenerator | undefined;
102
+ }
103
+
104
+ /**
105
+ * Handle a Slack reaction event end to end. Always consumes the event (the
106
+ * caller dispatches here only for `isSlackReactionEvent`), returning the
107
+ * response the top-level handler should short-circuit with.
108
+ */
109
+ export async function handleSlackReactionIntercept(
110
+ params: ReactionInterceptParams,
111
+ ): Promise<Record<string, unknown>> {
112
+ const {
113
+ callbackData,
114
+ sourceChannel,
115
+ sourceInterface,
116
+ conversationExternalId,
117
+ externalMessageId,
118
+ canonicalAssistantId,
119
+ rawSenderId,
120
+ canonicalSenderId,
121
+ actorDisplayName,
122
+ actorUsername,
123
+ replyCallbackUrl,
124
+ sourceMetadata,
125
+ slackChannelName,
126
+ approvalConversationGenerator,
127
+ } = params;
128
+
129
+ // Classify the reactor. No timezone enrichment — reactions never drive an
130
+ // agent turn, so only the trust class / guardian principal matter.
131
+ const trustCtx = resolveTrustContext({
132
+ assistantId: canonicalAssistantId,
133
+ sourceChannel,
134
+ conversationExternalId,
135
+ actorExternalId: rawSenderId,
136
+ actorUsername,
137
+ actorDisplayName,
138
+ });
139
+
140
+ // Drop strangers before any write. `unknown` covers no contact record and
141
+ // blocked/revoked contacts — a reaction from them is channel noise. Dropping
142
+ // here (before recordInbound/upsertBinding) means no empty conversation or
143
+ // binding is created on their behalf.
144
+ if (trustCtx.trustClass === "unknown") {
145
+ log.debug(
146
+ { sourceChannel, conversationExternalId },
147
+ "Dropping reaction from unknown actor",
148
+ );
149
+ return { accepted: true, reaction: "dropped_unknown_actor" };
150
+ }
151
+
152
+ const reactedMessageTs =
153
+ typeof sourceMetadata?.messageId === "string"
154
+ ? sourceMetadata.messageId
155
+ : undefined;
156
+ const threadTs =
157
+ typeof sourceMetadata?.threadId === "string" &&
158
+ sourceMetadata.threadId.trim().length > 0
159
+ ? sourceMetadata.threadId.trim()
160
+ : undefined;
161
+
162
+ // Record for dedup + conversation resolution (known contacts only — strangers
163
+ // were dropped above).
164
+ const result = recordInbound(
165
+ sourceChannel,
166
+ conversationExternalId,
167
+ externalMessageId,
168
+ {
169
+ sourceMessageId: reactedMessageTs,
170
+ assistantId: canonicalAssistantId,
171
+ sourceThreadId: threadTs,
172
+ },
173
+ );
174
+
175
+ // Respect disk-pressure cleanup so reactions don't bypass storage
176
+ // protection. Guardians resolve to `allow-cleanup-mode` (not `block`), so a
177
+ // guardian's approval-by-reaction still flows.
178
+ const diskPressure = classifyDiskPressureTurnPolicy(getDiskPressureStatus(), {
179
+ sourceChannel,
180
+ sourceInterface,
181
+ trustContext: {
182
+ sourceChannel: trustCtx.sourceChannel,
183
+ trustClass: trustCtx.trustClass,
184
+ },
185
+ });
186
+ if (diskPressure.action === "block") {
187
+ // Block silently: a reaction is a passive signal, so the message
188
+ // pipeline's "storage is low, try again" notice is meaningless for an
189
+ // emoji — there is nothing to retry. Mark the event processed and stop
190
+ // before binding/persistence.
191
+ if (!result.duplicate) {
192
+ clearPayload(result.eventId);
193
+ markProcessed(result.eventId);
194
+ }
195
+ return {
196
+ accepted: true,
197
+ duplicate: result.duplicate,
198
+ eventId: result.eventId,
199
+ diskPressure: "blocked",
200
+ reason: diskPressure.reason,
201
+ };
202
+ }
203
+
204
+ // Maintain the conversation binding, matching the message pipeline. Scoped to
205
+ // the daemon's own assistant so assistant-scoped legacy routes don't clobber
206
+ // each other's binding metadata.
207
+ if (canonicalAssistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
208
+ upsertBinding({
209
+ conversationId: result.conversationId,
210
+ sourceChannel,
211
+ externalChatId: conversationExternalId,
212
+ externalChatName: slackChannelName,
213
+ externalThreadId: threadTs ?? null,
214
+ externalUserId: canonicalSenderId ?? rawSenderId ?? null,
215
+ displayName: actorDisplayName ?? null,
216
+ username: actorUsername ?? null,
217
+ });
218
+ }
219
+
220
+ // Guardian approval-by-reaction → canonical decision pipeline, exactly like
221
+ // buttons and text replies. Only `reaction:` (added) expresses intent;
222
+ // `reaction_removed:` never does. `handleGuardianReplyIntercept` self-gates
223
+ // on `trustClass === "guardian"`, so a contact's reaction returns no response
224
+ // and falls through to persistence.
225
+ const isReactionAdded = callbackData.startsWith("reaction:");
226
+ if (isReactionAdded && replyCallbackUrl && !result.duplicate) {
227
+ const reactionIntercept = await handleGuardianReplyIntercept({
228
+ isDuplicate: result.duplicate,
229
+ trimmedContent: "",
230
+ hasCallbackData: true,
231
+ callbackData,
232
+ reactedMessageTs,
233
+ rawSenderId,
234
+ canonicalSenderId,
235
+ canonicalAssistantId,
236
+ sourceChannel,
237
+ conversationExternalId,
238
+ conversationId: result.conversationId,
239
+ eventId: result.eventId,
240
+ replyCallbackUrl,
241
+ trustClass: trustCtx.trustClass,
242
+ guardianPrincipalId: trustCtx.guardianPrincipalId,
243
+ approvalConversationGenerator,
244
+ });
245
+ // Consumed as a guardian decision (applied, or a surfaced failure delivered
246
+ // as an ephemeral reply). Short-circuit so we do not also persist a
247
+ // transcript row.
248
+ if (reactionIntercept.response) {
249
+ return reactionIntercept.response;
250
+ }
251
+ }
252
+
253
+ // Record the reaction as an inline transcript signal. Requires the reacted
254
+ // message ts to anchor the rendering.
255
+ if (!reactedMessageTs) {
256
+ log.debug(
257
+ { conversationId: result.conversationId, eventId: result.eventId },
258
+ "Skipping reaction persistence: missing sourceMetadata.messageId",
259
+ );
260
+ return {
261
+ accepted: result.accepted,
262
+ duplicate: result.duplicate,
263
+ eventId: result.eventId,
264
+ };
265
+ }
266
+
267
+ try {
268
+ await persistSlackReactionAsMessage({
269
+ conversationId: result.conversationId,
270
+ conversationExternalId,
271
+ eventId: result.eventId,
272
+ callbackData,
273
+ actorDisplayName,
274
+ threadTs,
275
+ reactedMessageTs,
276
+ duplicate: result.duplicate,
277
+ });
278
+ } catch (err) {
279
+ log.error(
280
+ { err, conversationId: result.conversationId, eventId: result.eventId },
281
+ "Failed to persist Slack reaction event",
282
+ );
283
+ }
284
+
285
+ return {
286
+ accepted: result.accepted,
287
+ duplicate: result.duplicate,
288
+ eventId: result.eventId,
289
+ };
290
+ }
291
+
292
+ /**
293
+ * Persist a Slack reaction event as a `messages` row with a `slackMeta`
294
+ * envelope so the renderer can surface it inline in the chronological
295
+ * transcript. Reactions do not trigger an agent response — the row is written
296
+ * and the inbound event is linked, but the agent loop is not dispatched.
297
+ *
298
+ * The caller is expected to have run `recordInbound` already so that
299
+ * deduplication and conversation resolution have happened. Duplicate inbound
300
+ * events are skipped here to keep persistence idempotent.
301
+ */
302
+ async function persistSlackReactionAsMessage(params: {
303
+ conversationId: string;
304
+ conversationExternalId: string;
305
+ eventId: string;
306
+ callbackData: string;
307
+ actorDisplayName?: string;
308
+ threadTs?: string;
309
+ reactedMessageTs: string;
310
+ duplicate: boolean;
311
+ }): Promise<void> {
312
+ if (params.duplicate) return;
313
+
314
+ const parsed = parseSlackReactionCallbackData(params.callbackData);
315
+ if (!parsed) {
316
+ log.debug(
317
+ {
318
+ conversationId: params.conversationId,
319
+ callbackData: params.callbackData,
320
+ },
321
+ "Skipping reaction persistence: unparseable callbackData",
322
+ );
323
+ return;
324
+ }
325
+
326
+ const slackMeta: SlackMessageMetadata = {
327
+ source: "slack",
328
+ channelId: params.conversationExternalId,
329
+ channelTs: params.reactedMessageTs,
330
+ eventKind: "reaction",
331
+ ...(params.threadTs ? { threadTs: params.threadTs } : {}),
332
+ ...(params.actorDisplayName
333
+ ? { displayName: params.actorDisplayName }
334
+ : {}),
335
+ reaction: {
336
+ emoji: parsed.emoji,
337
+ targetChannelTs: params.reactedMessageTs,
338
+ op: parsed.op,
339
+ ...(params.actorDisplayName
340
+ ? { actorDisplayName: params.actorDisplayName }
341
+ : {}),
342
+ },
343
+ };
344
+
345
+ // Sentinel content — Slack transcript renderers read `slackMeta` to format
346
+ // the reaction line; the literal text is never displayed to the model.
347
+ const persisted = await addMessage(
348
+ params.conversationId,
349
+ "user",
350
+ "[reaction]",
351
+ {
352
+ metadata: { slackMeta: writeSlackMetadata(slackMeta) },
353
+ skipIndexing: true,
354
+ },
355
+ );
356
+ linkMessage(params.eventId, persisted.id);
357
+ markProcessed(params.eventId);
358
+ }
@@ -90,6 +90,7 @@ import { ROUTES as INTERNAL_TWILIO_ROUTES } from "./internal-twilio-routes.js";
90
90
  import { ROUTES as LLM_CALL_SITES_ROUTES } from "./llm-call-sites-routes.js";
91
91
  import { ROUTES as LOG_EXPORT_ROUTES } from "./log-export-routes.js";
92
92
  import { ROUTES as MCP_AUTH_ROUTES } from "./mcp-auth-routes.js";
93
+ import { ROUTES as MEMORY_EVAL_ROUTES } from "./memory-eval-routes.js";
93
94
  import { ROUTES as MEMORY_ITEM_ROUTES } from "./memory-item-routes.js";
94
95
  import { ROUTES as MEMORY_V2_ROUTES } from "./memory-v2-routes.js";
95
96
  import { ROUTES as MEMORY_V3_ROUTES } from "./memory-v3-routes.js";
@@ -222,6 +223,7 @@ export const ROUTES: RouteDefinition[] = [
222
223
  ...INTERNAL_TWILIO_ROUTES,
223
224
  ...LOG_EXPORT_ROUTES,
224
225
  ...LLM_CALL_SITES_ROUTES,
226
+ ...MEMORY_EVAL_ROUTES,
225
227
  ...MEMORY_ITEM_ROUTES,
226
228
  ...MEMORY_V2_ROUTES,
227
229
  ...MEMORY_V3_ROUTES,
@@ -28,6 +28,7 @@ let mockSetConfigResult: SlackChannelConfigResult = {
28
28
  hasAppToken: false,
29
29
  hasUserToken: false,
30
30
  connected: false,
31
+ threadMode: "mention_only",
31
32
  };
32
33
 
33
34
  mock.module("../../../../../daemon/handlers/config-slack-channel.js", () => ({
@@ -37,6 +38,7 @@ mock.module("../../../../../daemon/handlers/config-slack-channel.js", () => ({
37
38
  hasAppToken: z.boolean(),
38
39
  hasUserToken: z.boolean(),
39
40
  connected: z.boolean(),
41
+ threadMode: z.enum(["mention_only", "mention_then_thread"]),
40
42
  teamId: z.string().optional(),
41
43
  teamName: z.string().optional(),
42
44
  teamUrl: z.string().optional(),
@@ -45,6 +47,7 @@ mock.module("../../../../../daemon/handlers/config-slack-channel.js", () => ({
45
47
  error: z.string().optional(),
46
48
  warning: z.string().optional(),
47
49
  }),
50
+ SlackThreadMode: z.enum(["mention_only", "mention_then_thread"]),
48
51
  setSlackChannelConfig: async (
49
52
  botToken?: string,
50
53
  appToken?: string,
@@ -59,6 +62,7 @@ mock.module("../../../../../daemon/handlers/config-slack-channel.js", () => ({
59
62
  hasAppToken: false,
60
63
  hasUserToken: false,
61
64
  connected: false,
65
+ threadMode: "mention_only",
62
66
  }),
63
67
  clearSlackChannelConfig: async (): Promise<SlackChannelConfigResult> => ({
64
68
  success: true,
@@ -66,7 +70,9 @@ mock.module("../../../../../daemon/handlers/config-slack-channel.js", () => ({
66
70
  hasAppToken: false,
67
71
  hasUserToken: false,
68
72
  connected: false,
73
+ threadMode: "mention_only",
69
74
  }),
75
+ patchSlackChannelConfig: () => {},
70
76
  }));
71
77
 
72
78
  import { BadRequestError } from "../../../errors.js";
@@ -81,6 +87,7 @@ describe("POST /v1/integrations/slack/channel/config", () => {
81
87
  hasAppToken: false,
82
88
  hasUserToken: false,
83
89
  connected: false,
90
+ threadMode: "mention_only",
84
91
  };
85
92
  });
86
93
 
@@ -129,6 +136,7 @@ describe("POST /v1/integrations/slack/channel/config", () => {
129
136
  hasAppToken: false,
130
137
  hasUserToken: false,
131
138
  connected: false,
139
+ threadMode: "mention_only",
132
140
  error: 'Invalid user token: must start with "xoxp-"',
133
141
  };
134
142
 
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * GET /v1/integrations/slack/channel/config — get current config status
5
5
  * POST /v1/integrations/slack/channel/config — validate and store credentials
6
+ * PATCH /v1/integrations/slack/channel/config — update channel settings
6
7
  * DELETE /v1/integrations/slack/channel/config — clear credentials
7
8
  */
8
9
 
@@ -11,8 +12,10 @@ import { z } from "zod";
11
12
  import {
12
13
  clearSlackChannelConfig,
13
14
  getSlackChannelConfig,
15
+ patchSlackChannelConfig,
14
16
  setSlackChannelConfig,
15
17
  SlackChannelConfigResultSchema,
18
+ SlackThreadMode,
16
19
  } from "../../../../daemon/handlers/config-slack-channel.js";
17
20
  import { ACTOR_PRINCIPALS } from "../../../auth/route-policy.js";
18
21
  import { BadRequestError } from "../../errors.js";
@@ -43,6 +46,20 @@ export async function handleSetSlackChannelConfig({
43
46
  return result;
44
47
  }
45
48
 
49
+ async function handlePatchSlackChannelConfig({ body = {} }: RouteHandlerArgs) {
50
+ const { threadMode } = body as { threadMode?: string };
51
+ if (threadMode !== undefined) {
52
+ const parsed = SlackThreadMode.safeParse(threadMode);
53
+ if (!parsed.success) {
54
+ throw new BadRequestError(
55
+ "threadMode must be 'mention_only' or 'mention_then_thread'",
56
+ );
57
+ }
58
+ patchSlackChannelConfig(parsed.data);
59
+ }
60
+ return getSlackChannelConfig();
61
+ }
62
+
46
63
  async function handleClearSlackChannelConfig() {
47
64
  return clearSlackChannelConfig();
48
65
  }
@@ -84,6 +101,25 @@ export const ROUTES: RouteDefinition[] = [
84
101
  }),
85
102
  responseBody: SlackChannelConfigResultSchema,
86
103
  },
104
+ {
105
+ operationId: "integrations_slack_channel_config_patch",
106
+ endpoint: "integrations/slack/channel/config",
107
+ method: "PATCH",
108
+ policy: {
109
+ requiredScopes: ["settings.write"],
110
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
111
+ },
112
+ summary: "Update Slack channel settings",
113
+ description: "Update Slack channel behavior settings (e.g. thread mode).",
114
+ tags: ["integrations"],
115
+ handler: handlePatchSlackChannelConfig,
116
+ requestBody: z.object({
117
+ threadMode: SlackThreadMode.describe(
118
+ "Controls whether the bot follows threads after an initial @mention",
119
+ ).optional(),
120
+ }),
121
+ responseBody: SlackChannelConfigResultSchema,
122
+ },
87
123
  {
88
124
  operationId: "integrations_slack_channel_config_delete",
89
125
  endpoint: "integrations/slack/channel/config",
@@ -53,7 +53,7 @@ function handleRecordAuthFallback({ body }: RouteHandlerArgs) {
53
53
 
54
54
  const recorded = recordAuthFallbackCounts(window_start, window_end, mapped);
55
55
  if (recorded === 0) {
56
- // collectUsageData disabled — counts dropped to honor the opt-out.
56
+ // share_analytics consent off — counts dropped to honor the opt-out.
57
57
  return { skipped: true };
58
58
  }
59
59
  log.debug({ recorded }, "Recorded auth-fallback counts");