@vellumai/assistant 0.11.4 → 0.11.5-staging.1

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 (358) hide show
  1. package/AGENTS.md +2 -2
  2. package/ARCHITECTURE.md +29 -4
  3. package/README.md +1 -1
  4. package/docs/architecture/memory.md +17 -5
  5. package/docs/architecture/security.md +96 -152
  6. package/docs/guardian-request-flow.md +13 -2
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  16. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
  17. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
  19. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  21. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/openapi.yaml +193 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/anthropic-provider.test.ts +10 -3
  26. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  27. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  28. package/src/__tests__/channel-approval-routes.test.ts +4 -0
  29. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
  30. package/src/__tests__/channel-reply-delivery.test.ts +26 -13
  31. package/src/__tests__/checker.test.ts +20 -314
  32. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  33. package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
  34. package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
  35. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/conversation-attachments.test.ts +0 -1
  38. package/src/__tests__/conversation-error.test.ts +17 -0
  39. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  40. package/src/__tests__/conversation-pairing.test.ts +186 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  42. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  43. package/src/__tests__/credential-routes.test.ts +22 -3
  44. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  45. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  46. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  47. package/src/__tests__/dm-backfill.test.ts +63 -0
  48. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  49. package/src/__tests__/edit-propagation.test.ts +107 -4
  50. package/src/__tests__/events-client-registration.test.ts +28 -0
  51. package/src/__tests__/file-write-tool.test.ts +4 -2
  52. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  53. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  54. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  55. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  56. package/src/__tests__/host-shell-tool.test.ts +23 -4
  57. package/src/__tests__/image-conversion.test.ts +143 -1
  58. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  59. package/src/__tests__/input-repairs.test.ts +207 -0
  60. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  61. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  62. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  63. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  64. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  65. package/src/__tests__/mtime-cache.test.ts +2 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  67. package/src/__tests__/openai-provider.test.ts +22 -0
  68. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  69. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  70. package/src/__tests__/platform.test.ts +16 -1
  71. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  72. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  73. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  74. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  75. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  76. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  77. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  78. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  79. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  80. package/src/__tests__/reaction-persistence.test.ts +200 -7
  81. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  82. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  83. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  84. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  85. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  86. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  87. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  88. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  89. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  90. package/src/__tests__/tool-executor.test.ts +63 -38
  91. package/src/__tests__/tool-policy.test.ts +97 -1
  92. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  93. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  94. package/src/__tests__/validate-input.test.ts +243 -7
  95. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  97. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  98. package/src/acp/acp-claude-oauth.ts +38 -21
  99. package/src/acp/prepare-agent-env.ts +146 -55
  100. package/src/agent/loop-tool-dedup.test.ts +161 -0
  101. package/src/agent/loop.ts +68 -22
  102. package/src/api/constants/app-tools.ts +34 -0
  103. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  104. package/src/api/index.ts +19 -0
  105. package/src/api/responses/resource-pressure-status.ts +25 -0
  106. package/src/approvals/guardian-channel-delivery.ts +70 -6
  107. package/src/approvals/guardian-request-resolvers.ts +32 -66
  108. package/src/channels/__tests__/message-audience.test.ts +49 -0
  109. package/src/channels/__tests__/types.test.ts +17 -3
  110. package/src/channels/message-audience.ts +40 -0
  111. package/src/channels/types.ts +16 -12
  112. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  113. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  114. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  115. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  116. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  117. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  118. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  119. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  120. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  121. package/src/cli/commands/conversations.help.ts +34 -0
  122. package/src/cli/commands/conversations.ts +125 -0
  123. package/src/cli/commands/inference-providers.ts +9 -4
  124. package/src/cli/commands/inference.help.ts +9 -4
  125. package/src/cli/commands/keys.help.ts +13 -1
  126. package/src/cli/commands/keys.ts +26 -15
  127. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  128. package/src/cli/commands/memory/index.help.ts +41 -27
  129. package/src/cli/commands/memory/index.ts +2 -0
  130. package/src/cli/commands/memory/memory-v2.ts +58 -54
  131. package/src/cli/commands/memory/memory-validate.ts +18 -0
  132. package/src/cli/commands/monitoring.ts +1 -0
  133. package/src/cli/commands/plugins.help.ts +28 -31
  134. package/src/cli/commands/plugins.ts +10 -0
  135. package/src/cli/commands/skills.help.ts +13 -16
  136. package/src/cli/commands/trust.ts +3 -14
  137. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  138. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  139. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  140. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  141. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  142. package/src/cli/lib/bundled-marketplace.json +13 -0
  143. package/src/cli/lib/daemon-credential-client.ts +25 -3
  144. package/src/cli/lib/install-from-github.ts +35 -24
  145. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  146. package/src/cli/lib/plugin-surfaces.ts +27 -0
  147. package/src/config/feature-flag-registry.json +35 -19
  148. package/src/config/webhook-routing.ts +8 -0
  149. package/src/context/compactor.ts +31 -2
  150. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  151. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  152. package/src/daemon/conversation-error.ts +13 -0
  153. package/src/daemon/conversation-messaging.ts +22 -2
  154. package/src/daemon/conversation-process.ts +11 -0
  155. package/src/daemon/conversation-store.ts +10 -0
  156. package/src/daemon/conversation-surfaces.ts +21 -8
  157. package/src/daemon/conversation.ts +12 -10
  158. package/src/daemon/lifecycle.ts +6 -2
  159. package/src/daemon/message-types/conversations.ts +5 -7
  160. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  161. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  162. package/src/daemon/resource-pressure-guard.ts +387 -0
  163. package/src/daemon/shutdown-handlers.ts +2 -0
  164. package/src/daemon/startup-error.ts +16 -0
  165. package/src/daemon/trust-context.ts +14 -10
  166. package/src/daemon/unsendable-image-notice.ts +76 -0
  167. package/src/hooks/registry.ts +65 -51
  168. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  169. package/src/ipc/gateway-client.test.ts +1 -1
  170. package/src/ipc/gateway-client.ts +14 -21
  171. package/src/ipc/socket-cleanup.ts +2 -11
  172. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  173. package/src/messaging/provider-message-metadata.ts +128 -0
  174. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  175. package/src/messaging/providers/channel-transport.ts +90 -13
  176. package/src/messaging/providers/discord/send.ts +16 -0
  177. package/src/messaging/providers/discord/transport.ts +11 -1
  178. package/src/messaging/providers/index.ts +109 -16
  179. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  180. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  181. package/src/messaging/providers/slack/send.test.ts +6 -9
  182. package/src/messaging/providers/slack/send.ts +40 -28
  183. package/src/messaging/providers/slack/transport.ts +22 -26
  184. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  185. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  186. package/src/messaging/read-provider-metadata.test.ts +157 -0
  187. package/src/messaging/read-provider-metadata.ts +51 -0
  188. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  189. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  190. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  191. package/src/notifications/adapters/macos.ts +55 -0
  192. package/src/notifications/adapters/slack.ts +10 -5
  193. package/src/notifications/assistant-reply-producer.ts +37 -4
  194. package/src/notifications/conversation-pairing.ts +111 -21
  195. package/src/notifications/edit-notification.ts +38 -8
  196. package/src/notifications/emit-signal.ts +12 -14
  197. package/src/notifications/home-feed-side-effect.ts +218 -15
  198. package/src/notifications/types.ts +5 -0
  199. package/src/permissions/AGENTS.md +16 -0
  200. package/src/permissions/checker.test.ts +75 -122
  201. package/src/permissions/checker.ts +44 -560
  202. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  203. package/src/permissions/confirmation-guardian-request.ts +5 -1
  204. package/src/persistence/conversation-crud.ts +23 -20
  205. package/src/persistence/conversation-queries.ts +14 -1
  206. package/src/persistence/conversation-types.ts +22 -0
  207. package/src/persistence/delivery-crud.ts +122 -15
  208. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  209. package/src/plugin-api/conversation-turn.ts +27 -0
  210. package/src/plugin-api/credential-scope.test.ts +15 -0
  211. package/src/plugin-api/credential-scope.ts +14 -0
  212. package/src/plugin-api/index.ts +19 -1
  213. package/src/plugin-api/resolve-credential.ts +15 -10
  214. package/src/plugin-api/store-credential.ts +148 -0
  215. package/src/plugin-api/system-card.ts +36 -0
  216. package/src/plugin-api/vision-support.test.ts +82 -0
  217. package/src/plugin-api/vision-support.ts +14 -2
  218. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  219. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  220. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  221. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  222. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  223. package/src/plugins/defaults/main.ts +15 -0
  224. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  225. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  226. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  227. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  228. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  229. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  230. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  231. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  232. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  233. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  234. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  235. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  236. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  237. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  238. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  239. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  240. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  241. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  242. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  243. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  244. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  245. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  246. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  247. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  248. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  249. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  250. package/src/plugins/mtime-cache.ts +1 -1
  251. package/src/plugins/pipeline.ts +14 -7
  252. package/src/plugins/plugin-execution-context.ts +35 -11
  253. package/src/plugins/plugin-tree-walk.ts +63 -4
  254. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  255. package/src/providers/anthropic/client.ts +332 -241
  256. package/src/providers/connection-resolution.ts +2 -1
  257. package/src/providers/content-blocks.ts +22 -0
  258. package/src/providers/gemini/client.ts +5 -2
  259. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  260. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  261. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  262. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  263. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  264. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  265. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  266. package/src/providers/inference/adapter-factory.ts +33 -2
  267. package/src/providers/inference/auth.ts +13 -2
  268. package/src/providers/inference/credential-usage.ts +37 -0
  269. package/src/providers/inference/missing-credential-guard.ts +110 -0
  270. package/src/providers/inference/resolve-auth.ts +7 -7
  271. package/src/providers/media-resolve.ts +176 -15
  272. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  273. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  274. package/src/providers/openai/chat-completions-provider.ts +322 -42
  275. package/src/routes/route-host-protocol.ts +7 -0
  276. package/src/routes/worker.ts +31 -10
  277. package/src/runtime/AGENTS.md +35 -0
  278. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  279. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  280. package/src/runtime/assistant-event-hub.ts +38 -0
  281. package/src/runtime/channel-reply-delivery.ts +54 -31
  282. package/src/runtime/channel-retry-sweep.ts +6 -4
  283. package/src/runtime/effective-capabilities.test.ts +19 -0
  284. package/src/runtime/effective-capabilities.ts +25 -0
  285. package/src/runtime/guardian-reply-router.ts +6 -2
  286. package/src/runtime/http-errors.ts +1 -0
  287. package/src/runtime/http-server.ts +20 -5
  288. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  289. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  290. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  291. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  292. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  293. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  294. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  295. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  296. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  297. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  298. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  299. package/src/runtime/routes/approval-routes.ts +37 -4
  300. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  301. package/src/runtime/routes/canned-message-complete.ts +68 -29
  302. package/src/runtime/routes/client-routes.ts +113 -0
  303. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  304. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  305. package/src/runtime/routes/conversation-routes.ts +36 -22
  306. package/src/runtime/routes/credential-in-use.ts +85 -0
  307. package/src/runtime/routes/credential-routes.ts +55 -85
  308. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  309. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  310. package/src/runtime/routes/identity-routes.ts +5 -121
  311. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  312. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  313. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  314. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  315. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  316. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  317. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  318. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  319. package/src/runtime/routes/index.ts +2 -0
  320. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  321. package/src/runtime/routes/monitoring-routes.ts +9 -1
  322. package/src/runtime/routes/plugins-routes.ts +17 -5
  323. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  324. package/src/runtime/routes/secret-routes.ts +39 -8
  325. package/src/runtime/routes/settings-routes.ts +6 -6
  326. package/src/runtime/routes/surface-action-routes.ts +20 -19
  327. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  328. package/src/runtime/routes/user-route-resolution.ts +18 -1
  329. package/src/runtime/slack-reply-session.test.ts +23 -13
  330. package/src/runtime/slack-reply-session.ts +38 -55
  331. package/src/runtime/web-presence.ts +90 -0
  332. package/src/skills/validate-input.ts +177 -40
  333. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  334. package/src/subagent/consult-context.ts +7 -23
  335. package/src/tools/credentials/broker.ts +24 -79
  336. package/src/tools/credentials/ref-parse.ts +35 -0
  337. package/src/tools/credentials/resolve.ts +4 -10
  338. package/src/tools/credentials/store.ts +168 -0
  339. package/src/tools/credentials/tool-policy.ts +67 -0
  340. package/src/tools/executor.ts +53 -15
  341. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  342. package/src/tools/network/url-safety.ts +9 -16
  343. package/src/tools/network/web-search.ts +21 -3
  344. package/src/tools/permission-checker.ts +43 -52
  345. package/src/tools/schema-transforms.ts +40 -5
  346. package/src/tools/shared/input-repairs.ts +160 -0
  347. package/src/tools/skills/skill-tool-factory.ts +18 -4
  348. package/src/tools/subagent/spawn.ts +0 -1
  349. package/src/tools/tool-approval-handler.ts +16 -10
  350. package/src/tools/tool-types.ts +26 -13
  351. package/src/tools/types.ts +6 -6
  352. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  353. package/src/util/cgroup-memory.ts +3 -0
  354. package/src/util/container-cpu-sampler.ts +250 -0
  355. package/src/util/image-conversion.ts +178 -14
  356. package/src/util/platform.ts +93 -10
  357. package/src/permissions/ipc-risk-types.ts +0 -143
  358. package/src/permissions/risk-types.ts +0 -76
@@ -8,6 +8,7 @@
8
8
  * focused on orchestration.
9
9
  */
10
10
  import type { AssistantEvent } from "../../../api/index.js";
11
+ import { resolveGuardianPromptDelivery } from "../../../approvals/guardian-channel-delivery.js";
11
12
  import {
12
13
  extractMessageTsFromCallbackUrl,
13
14
  extractThreadTsFromCallbackUrl,
@@ -20,6 +21,12 @@ import {
20
21
  } from "../../../contacts/guardian-delivery-reader.js";
21
22
  import { isConversationBusyError } from "../../../daemon/conversation-messaging.js";
22
23
  import type { TrustContext } from "../../../daemon/trust-context-types.js";
24
+ import {
25
+ sendChannelReaction,
26
+ sendChannelTyping,
27
+ setChannelThreadStatus,
28
+ supportsChannelTyping,
29
+ } from "../../../messaging/providers/index.js";
23
30
  import {
24
31
  getSiblingStreamedReplyTs,
25
32
  linkMessage,
@@ -161,25 +168,15 @@ export function processChannelMessageInBackground(
161
168
  // `channel-turn-admission.ts` for why channel turns defer rather than route
162
169
  // through the SSE-oriented conversation queue.
163
170
  void withChannelTurnAdmission(conversationId, async () => {
164
- const typingCallbackUrl = shouldEmitTelegramTyping(
165
- sourceChannel,
166
- replyCallbackUrl,
167
- )
168
- ? replyCallbackUrl
169
- : undefined;
170
- const stopTypingHeartbeat = typingCallbackUrl
171
- ? startTelegramTypingHeartbeat(
172
- typingCallbackUrl,
173
- externalChatId,
174
- assistantId,
175
- )
176
- : undefined;
171
+ const stopTypingHeartbeat =
172
+ replyCallbackUrl && supportsChannelTyping(replyCallbackUrl)
173
+ ? startTypingHeartbeat(replyCallbackUrl, externalChatId)
174
+ : undefined;
177
175
 
178
176
  const slackThinkingStatus = createSlackThinkingStatusController({
179
177
  sourceChannel,
180
178
  replyCallbackUrl,
181
179
  chatId: externalChatId,
182
- assistantId,
183
180
  startImmediately: shouldStartSlackThinkingStatusImmediately({
184
181
  sourceChannel,
185
182
  chatType,
@@ -193,6 +190,7 @@ export function processChannelMessageInBackground(
193
190
  externalChatId,
194
191
  trustClass: trustCtx.trustClass,
195
192
  guardianExternalUserId: trustCtx.guardianExternalUserId,
193
+ guardianChatId: trustCtx.guardianChatId,
196
194
  requesterExternalUserId: trustCtx.requesterExternalUserId,
197
195
  replyCallbackUrl,
198
196
  assistantId,
@@ -230,7 +228,6 @@ export function processChannelMessageInBackground(
230
228
  chatType,
231
229
  replyCallbackUrl,
232
230
  chatId: externalChatId,
233
- assistantId,
234
231
  recipientUserId: slackInbound?.actorExternalUserId,
235
232
  recipientTeamId: slackInbound?.actorTeamId,
236
233
  // Durably record the streamed message `ts` the instant the stream
@@ -387,40 +384,23 @@ export function processChannelMessageInBackground(
387
384
  // ---------------------------------------------------------------------------
388
385
 
389
386
  /**
390
- * How often the typing status is re-sent while a turn runs.
387
+ * How often the working indicator is re-sent while a turn runs.
391
388
  *
392
- * `sendChatAction` sets a status that expires on its own after a few seconds,
393
- * and the Bot API offers no stop/start pair to hold one open, so showing
394
- * typing across a multi-second turn means re-sending on a timer. The interval
395
- * has to stay under that expiry or the indicator blinks off between beats;
396
- * anyone changing it should check the current window first.
389
+ * Every channel that has one expires it after a few seconds and offers no
390
+ * start/stop pair to hold it open, so showing it across a multi-second turn
391
+ * means re-sending on a timer. The interval has to stay under the shortest
392
+ * expiry of any channel that implements `typing`: Telegram is about five
393
+ * seconds, Discord about ten. Anyone changing it should check the current
394
+ * windows first.
397
395
  *
398
- * No explicit stop is needed on the delivery path: Telegram clears the status
399
- * as soon as the bot sends a message.
396
+ * No explicit stop is needed: both clear the indicator when the bot posts.
400
397
  *
401
398
  * https://core.telegram.org/bots/api#sendchataction
399
+ * https://discord.com/developers/docs/resources/channel#trigger-typing-indicator
402
400
  */
403
- const TELEGRAM_TYPING_INTERVAL_MS = 4_000;
404
-
405
- function shouldEmitTelegramTyping(
406
- sourceChannel: ChannelId,
407
- replyCallbackUrl?: string,
408
- ): boolean {
409
- if (sourceChannel !== "telegram" || !replyCallbackUrl) {
410
- return false;
411
- }
412
- try {
413
- return new URL(replyCallbackUrl).pathname.endsWith("/deliver/telegram");
414
- } catch {
415
- return replyCallbackUrl.endsWith("/deliver/telegram");
416
- }
417
- }
401
+ const TYPING_INTERVAL_MS = 4_000;
418
402
 
419
- function startTelegramTypingHeartbeat(
420
- callbackUrl: string,
421
- chatId: string,
422
- assistantId?: string,
423
- ): () => void {
403
+ function startTypingHeartbeat(callbackUrl: string, chatId: string): () => void {
424
404
  let active = true;
425
405
  let inFlight = false;
426
406
 
@@ -429,11 +409,7 @@ function startTelegramTypingHeartbeat(
429
409
  return;
430
410
  }
431
411
  inFlight = true;
432
- void deliverChannelReply(callbackUrl, {
433
- chatId,
434
- chatAction: "typing",
435
- assistantId,
436
- })
412
+ void sendChannelTyping(callbackUrl, chatId)
437
413
  .catch((err) => {
438
414
  log.debug(
439
415
  { err, chatId },
@@ -447,7 +423,7 @@ function startTelegramTypingHeartbeat(
447
423
 
448
424
  emitTyping();
449
425
 
450
- const interval = setInterval(emitTyping, TELEGRAM_TYPING_INTERVAL_MS);
426
+ const interval = setInterval(emitTyping, TYPING_INTERVAL_MS);
451
427
  (interval as { unref?: () => void }).unref?.();
452
428
 
453
429
  return () => {
@@ -498,16 +474,9 @@ function createSlackThinkingStatusController(params: {
498
474
  sourceChannel: ChannelId;
499
475
  replyCallbackUrl?: string;
500
476
  chatId: string;
501
- assistantId?: string;
502
477
  startImmediately?: boolean;
503
478
  }): SlackThinkingStatusController | undefined {
504
- const {
505
- sourceChannel,
506
- replyCallbackUrl,
507
- chatId,
508
- assistantId,
509
- startImmediately,
510
- } = params;
479
+ const { sourceChannel, replyCallbackUrl, chatId, startImmediately } = params;
511
480
  if (
512
481
  !replyCallbackUrl ||
513
482
  !shouldEmitSlackThinkingStatus(sourceChannel, replyCallbackUrl)
@@ -532,7 +501,6 @@ function createSlackThinkingStatusController(params: {
532
501
  slackThinkingStatus = setSlackThinkingStatus(
533
502
  callbackUrl,
534
503
  chatId,
535
- assistantId,
536
504
  currentLoadingMessages,
537
505
  );
538
506
  lastSentLoadingMessageKey = getLoadingMessagesKey(currentLoadingMessages);
@@ -649,7 +617,6 @@ function getTaskProgressLoadingMessage(
649
617
  function setSlackThinkingStatus(
650
618
  callbackUrl: string,
651
619
  chatId: string,
652
- assistantId?: string,
653
620
  loadingMessages?: string[],
654
621
  ): SlackThinkingStatusHandle {
655
622
  let cleared = false;
@@ -668,15 +635,13 @@ function setSlackThinkingStatus(
668
635
  };
669
636
  }
670
637
 
671
- const addPromise = deliverChannelReply(callbackUrl, {
638
+ const addPromise = sendChannelReaction(callbackUrl, {
672
639
  chatId,
673
- assistantId,
674
- reaction: { action: "add", name: "eyes", messageTs },
640
+ messageId: messageTs,
641
+ emoji: "eyes",
642
+ action: "add",
675
643
  }).catch((err) => {
676
- log.debug(
677
- { err, chatId, messageTs },
678
- "Failed to add Slack eyes reaction",
679
- );
644
+ log.debug({ err, chatId, messageTs }, "Failed to add eyes reaction");
680
645
  });
681
646
 
682
647
  const clearReaction = (): void => {
@@ -686,14 +651,15 @@ function setSlackThinkingStatus(
686
651
  cleared = true;
687
652
  clearTimeout(safetyTimer);
688
653
  void addPromise.then(() =>
689
- deliverChannelReply(callbackUrl, {
654
+ sendChannelReaction(callbackUrl, {
690
655
  chatId,
691
- assistantId,
692
- reaction: { action: "remove", name: "eyes", messageTs },
656
+ messageId: messageTs,
657
+ emoji: "eyes",
658
+ action: "remove",
693
659
  }).catch((err) => {
694
660
  log.debug(
695
661
  { err, chatId, messageTs },
696
- "Failed to remove Slack eyes reaction",
662
+ "Failed to remove eyes reaction",
697
663
  );
698
664
  }),
699
665
  );
@@ -713,15 +679,11 @@ function setSlackThinkingStatus(
713
679
 
714
680
  // Track the set promise so clear waits for it to settle first,
715
681
  // preventing a race where clear arrives at Slack before set.
716
- let statusPromise = deliverChannelReply(callbackUrl, {
682
+ let statusPromise = setChannelThreadStatus(callbackUrl, {
717
683
  chatId,
718
- assistantId,
719
- assistantThreadStatus: {
720
- channel: chatId,
721
- threadTs,
722
- status: getRandomSlackThinkingStatus(),
723
- ...(loadingMessages ? { loadingMessages } : {}),
724
- },
684
+ threadTs,
685
+ status: getRandomSlackThinkingStatus(),
686
+ ...(loadingMessages ? { loadingMessages } : {}),
725
687
  }).catch((err) => {
726
688
  log.debug({ err, chatId, threadTs }, "Failed to set Slack thinking status");
727
689
  });
@@ -731,17 +693,13 @@ function setSlackThinkingStatus(
731
693
  return;
732
694
  }
733
695
  statusPromise = statusPromise.then(() =>
734
- deliverChannelReply(callbackUrl, {
696
+ setChannelThreadStatus(callbackUrl, {
735
697
  chatId,
736
- assistantId,
737
- assistantThreadStatus: {
738
- channel: chatId,
739
- threadTs,
740
- status: getRandomSlackThinkingStatus(),
741
- ...(nextLoadingMessages
742
- ? { loadingMessages: nextLoadingMessages }
743
- : {}),
744
- },
698
+ threadTs,
699
+ status: getRandomSlackThinkingStatus(),
700
+ ...(nextLoadingMessages
701
+ ? { loadingMessages: nextLoadingMessages }
702
+ : {}),
745
703
  }).catch((err) => {
746
704
  log.debug(
747
705
  { err, chatId, threadTs },
@@ -758,14 +716,10 @@ function setSlackThinkingStatus(
758
716
  cleared = true;
759
717
  clearTimeout(safetyTimer);
760
718
  void statusPromise.then(() =>
761
- deliverChannelReply(callbackUrl, {
719
+ setChannelThreadStatus(callbackUrl, {
762
720
  chatId,
763
- assistantId,
764
- assistantThreadStatus: {
765
- channel: chatId,
766
- threadTs,
767
- status: "",
768
- },
721
+ threadTs,
722
+ status: "",
769
723
  }).catch((err) => {
770
724
  log.debug(
771
725
  { err, chatId, threadTs },
@@ -796,6 +750,7 @@ function startPendingApprovalPromptWatcher(params: {
796
750
  externalChatId: string;
797
751
  trustClass: TrustContext["trustClass"];
798
752
  guardianExternalUserId?: string;
753
+ guardianChatId?: string;
799
754
  requesterExternalUserId?: string;
800
755
  replyCallbackUrl: string;
801
756
  assistantId?: string;
@@ -807,6 +762,7 @@ function startPendingApprovalPromptWatcher(params: {
807
762
  externalChatId,
808
763
  trustClass,
809
764
  guardianExternalUserId,
765
+ guardianChatId,
810
766
  requesterExternalUserId,
811
767
  replyCallbackUrl,
812
768
  assistantId,
@@ -838,9 +794,17 @@ function startPendingApprovalPromptWatcher(params: {
838
794
  const info = pending[0];
839
795
  if (prompt && info && !deliveredRequestIds.has(info.requestId)) {
840
796
  deliveredRequestIds.add(info.requestId);
797
+ // Addressed to the guardian's own chat, not the chat the turn is
798
+ // running in, which can be a room that reads the tool and its
799
+ // command preview.
800
+ const promptDelivery = resolveGuardianPromptDelivery({
801
+ turnChatId: externalChatId,
802
+ turnCallbackUrl: replyCallbackUrl,
803
+ guardianChatId,
804
+ });
841
805
  const delivered = await deliverGeneratedApprovalPrompt({
842
- replyCallbackUrl,
843
- chatId: externalChatId,
806
+ replyCallbackUrl: promptDelivery.callbackUrl,
807
+ chatId: promptDelivery.chatId,
844
808
  sourceChannel,
845
809
  assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
846
810
  prompt,
@@ -11,20 +11,23 @@
11
11
  * the message's metadata via a single transactional content+metadata update,
12
12
  * so downstream renderers can surface the edited marker.
13
13
  *
14
- * Extracted from inbound-message-handler.ts to keep the top-level handler
15
- * focused on orchestration.
14
+ * An edit belongs to the conversation of the message it changes, so the stage
15
+ * resolves that message before it records anything, and an edit with no
16
+ * resolvable target is dropped.
16
17
  */
17
18
  import type { ChannelId } from "../../../channels/types.js";
18
19
  import {
19
20
  mergeSlackMetadata,
20
21
  readSlackMetadata,
21
22
  } from "../../../messaging/providers/slack/message-metadata.js";
23
+ import type { MessageRow } from "../../../persistence/conversation-crud.js";
22
24
  import {
23
25
  getMessageById,
24
26
  updateMessageContent,
25
27
  updateMessageContentAndMetadata,
26
28
  } from "../../../persistence/conversation-crud.js";
27
29
  import {
30
+ findInboundEvent,
28
31
  findMessageBySourceId,
29
32
  recordInbound,
30
33
  } from "../../../persistence/delivery-crud.js";
@@ -71,19 +74,19 @@ export async function handleEditIntercept(
71
74
  content,
72
75
  } = params;
73
76
 
74
- // Dedup the edit event itself (retried edited_message webhooks)
75
- const editResult = recordInbound(
77
+ // Dedup the edit event itself (retried edited_message webhooks) before the
78
+ // lookup below, so a redelivery answers immediately instead of waiting out
79
+ // the retry window a second time.
80
+ const alreadyRecorded = findInboundEvent(
76
81
  sourceChannel,
77
82
  conversationExternalId,
78
83
  externalMessageId,
79
- { sourceMessageId, sourceThreadId },
80
84
  );
81
-
82
- if (editResult.duplicate) {
85
+ if (alreadyRecorded) {
83
86
  return {
84
87
  accepted: true,
85
88
  duplicate: true,
86
- eventId: editResult.eventId,
89
+ eventId: alreadyRecorded.eventId,
87
90
  };
88
91
  }
89
92
 
@@ -117,81 +120,100 @@ export async function handleEditIntercept(
117
120
  }
118
121
  }
119
122
 
120
- if (original) {
121
- const newContent = content ?? "";
122
- // Short-circuit no-op edits: Slack fires `message_changed` for link
123
- // unfurls and other decorations where the text is identical to the
124
- // previous revision. Skipping the DB write here covers that case and
125
- // also drops trivially-redundant edit webhooks. We only have the
126
- // authoritative previous text once the original row is located, so
127
- // this check lives after the lookup.
128
- const existingRow = getMessageById(original.messageId);
129
- if (
130
- existingRow &&
131
- stringifyMessageContent(existingRow.content) === newContent
132
- ) {
133
- log.debug(
134
- {
135
- assistantId,
136
- sourceChannel,
137
- sourceMessageId,
138
- messageId: original.messageId,
139
- },
140
- "Edit text unchanged; skipping update",
141
- );
142
- return {
143
- accepted: true,
144
- duplicate: false,
145
- noop: true,
146
- eventId: editResult.eventId,
147
- };
148
- }
149
- if (sourceChannel === "slack") {
150
- // Slack edits stamp `slackMeta.editedAt` so the chronological
151
- // transcript renderer can surface the edited marker. The merge
152
- // tolerates rows that lack slackMeta enrichment by synthesizing
153
- // the minimum-required fields from the lookup data.
154
- applySlackEditMetadata({
155
- messageId: original.messageId,
156
- conversationExternalId,
157
- sourceMessageId,
158
- sourceThreadId,
159
- newContent,
160
- });
161
- } else {
162
- updateMessageContent(original.messageId, newContent);
163
- }
164
- // The edit changed searchable text (the no-op guard above already returned
165
- // for identical content) and this path bypasses the `addMessage` persist
166
- // path, so reindex the message into the lexical index — the idempotent
167
- // upsert replaces the stale Qdrant point with the edited content.
168
- enqueueLexicalIndexForMessage(original.messageId);
169
- log.info(
170
- { assistantId, sourceMessageId, messageId: original.messageId },
171
- "Updated message content from edited_message",
172
- );
173
- } else {
174
- // For Slack, treat missing-target edits as `debug` (the row may have
175
- // been compacted, never stored, or pre-date this upgrade); for other
176
- // channels, retain the louder `warn` since their edit pipelines
177
- // historically expect the row to exist.
123
+ if (!original) {
124
+ // Nothing to edit, and nothing to record it against: recording it anyway
125
+ // would resolve a conversation from the edit's own address and create one
126
+ // there.
127
+ //
128
+ // Slack keeps this at `debug`, since the row may have been compacted or
129
+ // never stored. Other channels keep the louder `warn`, since their edit
130
+ // pipelines expect the row to exist.
131
+ const missingTarget = {
132
+ assistantId,
133
+ sourceChannel,
134
+ conversationExternalId,
135
+ sourceMessageId,
136
+ };
178
137
  if (sourceChannel === "slack") {
179
- log.debug(
180
- {
181
- assistantId,
182
- sourceChannel,
183
- channelId: conversationExternalId,
184
- externalMessageId: sourceMessageId,
185
- },
186
- "Slack edit target not found, ignoring",
187
- );
138
+ log.debug(missingTarget, "Slack edit target not found, ignoring");
188
139
  } else {
189
140
  log.warn(
190
- { assistantId, sourceChannel, conversationExternalId, sourceMessageId },
141
+ missingTarget,
191
142
  "Could not find original message for edit after retries, ignoring",
192
143
  );
193
144
  }
145
+ return { accepted: true, duplicate: false, edited: false };
146
+ }
147
+
148
+ // The edit belongs to the conversation of the message it changes. Resolving
149
+ // one from the edit's own address would key it on the edited message's
150
+ // timestamp and create a conversation there.
151
+ const editResult = recordInbound(
152
+ sourceChannel,
153
+ conversationExternalId,
154
+ externalMessageId,
155
+ { sourceMessageId, conversationId: original.conversationId },
156
+ );
157
+ if (editResult.duplicate) {
158
+ // Two deliveries of one edit raced through the probe above while neither
159
+ // had recorded yet. The first owns the write; applying it twice would
160
+ // re-enqueue lexical indexing for no change.
161
+ return { accepted: true, duplicate: true, eventId: editResult.eventId };
162
+ }
163
+
164
+ const newContent = content ?? "";
165
+ // Short-circuit no-op edits: Slack fires `message_changed` for link
166
+ // unfurls and other decorations where the text is identical to the
167
+ // previous revision. Skipping the DB write here covers that case and
168
+ // also drops trivially-redundant edit webhooks. We only have the
169
+ // authoritative previous text once the original row is located, so
170
+ // this check lives after the lookup.
171
+ const existingRow = getMessageById(original.messageId);
172
+ if (
173
+ existingRow &&
174
+ stringifyMessageContent(existingRow.content) === newContent
175
+ ) {
176
+ log.debug(
177
+ {
178
+ assistantId,
179
+ sourceChannel,
180
+ sourceMessageId,
181
+ messageId: original.messageId,
182
+ },
183
+ "Edit text unchanged; skipping update",
184
+ );
185
+ return {
186
+ accepted: true,
187
+ duplicate: false,
188
+ noop: true,
189
+ eventId: editResult.eventId,
190
+ };
191
+ }
192
+ if (sourceChannel === "slack") {
193
+ // Slack edits stamp `slackMeta.editedAt` so the chronological
194
+ // transcript renderer can surface the edited marker. The merge
195
+ // tolerates rows that lack slackMeta enrichment by synthesizing
196
+ // the minimum-required fields from the lookup data.
197
+ applySlackEditMetadata({
198
+ messageId: original.messageId,
199
+ existingRow,
200
+ conversationExternalId,
201
+ sourceMessageId,
202
+ sourceThreadId,
203
+ newContent,
204
+ });
205
+ } else {
206
+ updateMessageContent(original.messageId, newContent);
194
207
  }
208
+ // The edit changed searchable text (the no-op guard above already returned
209
+ // for identical content) and this path bypasses the `addMessage` persist
210
+ // path, so reindex the message into the lexical index — the idempotent
211
+ // upsert replaces the stale Qdrant point with the edited content.
212
+ enqueueLexicalIndexForMessage(original.messageId);
213
+ log.info(
214
+ { assistantId, sourceMessageId, messageId: original.messageId },
215
+ "Updated message content from edited_message",
216
+ );
195
217
 
196
218
  return {
197
219
  accepted: true,
@@ -217,6 +239,8 @@ export async function handleEditIntercept(
217
239
  */
218
240
  function applySlackEditMetadata(params: {
219
241
  messageId: string;
242
+ /** The stored row, already read by the caller's no-op guard. */
243
+ existingRow: MessageRow | null;
220
244
  conversationExternalId: string;
221
245
  sourceMessageId: string;
222
246
  sourceThreadId?: string;
@@ -224,13 +248,13 @@ function applySlackEditMetadata(params: {
224
248
  }): void {
225
249
  const {
226
250
  messageId,
251
+ existingRow: row,
227
252
  conversationExternalId,
228
253
  sourceMessageId,
229
254
  sourceThreadId,
230
255
  newContent,
231
256
  } = params;
232
257
 
233
- const row = getMessageById(messageId);
234
258
  const outerMetadata: Record<string, unknown> =
235
259
  row?.metadata != null ? safeParseRecord(row.metadata) : {};
236
260
  const existingSlackMeta =
@@ -13,6 +13,7 @@ import {
13
13
  listGuardianRequestsOrEmpty,
14
14
  listPendingRequestsByDestinationOrEmpty,
15
15
  } from "../../../channels/gateway-guardian-requests.js";
16
+ import { audienceForReader } from "../../../channels/message-audience.js";
16
17
  import type { ChannelId } from "../../../channels/types.js";
17
18
  import { getLogger } from "../../../util/logger.js";
18
19
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
@@ -44,8 +45,14 @@ export interface GuardianReplyInterceptParams {
44
45
  canonicalSenderId: string | null;
45
46
  sourceChannel: ChannelId;
46
47
  conversationExternalId: string;
47
- conversationId: string;
48
- eventId: string;
48
+ /**
49
+ * Conversation the message arrived in. Reactions resolve their target by the
50
+ * reacted card's own address and are routed before any conversation is
51
+ * known, so they pass none.
52
+ */
53
+ conversationId?: string;
54
+ /** Inbound event id, echoed on the consumed response when the caller has one. */
55
+ eventId?: string;
49
56
  replyCallbackUrl: string | undefined;
50
57
  trustClass: string;
51
58
  guardianPrincipalId: string | null | undefined;
@@ -190,11 +197,13 @@ export async function handleGuardianReplyIntercept(
190
197
  assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
191
198
  };
192
199
  // On Slack, send guardian management replies (disambiguation, pending
193
- // request lists, etc.) as ephemeral so only the guardian sees them.
194
- if (sourceChannel === "slack" && (canonicalSenderId ?? rawSenderId)) {
195
- routerReplyPayload.ephemeral = true;
196
- routerReplyPayload.user = (canonicalSenderId ?? rawSenderId)!;
197
- }
200
+ // request lists, etc.) so only the guardian sees them where a room is
201
+ // shared.
202
+ routerReplyPayload.audience = audienceForReader(
203
+ sourceChannel,
204
+ conversationExternalId,
205
+ canonicalSenderId ?? rawSenderId,
206
+ );
198
207
  try {
199
208
  await deliverChannelReply(replyCallbackUrl, routerReplyPayload);
200
209
  } catch (err) {
@@ -209,7 +218,7 @@ export async function handleGuardianReplyIntercept(
209
218
  response: {
210
219
  accepted: true,
211
220
  duplicate: false,
212
- eventId,
221
+ ...(eventId ? { eventId } : {}),
213
222
  canonicalRouter: routerResult.type,
214
223
  requestId: routerResult.requestId,
215
224
  },