@vellumai/assistant 0.11.4 → 0.11.5-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 (367) 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__/config-loader-backfill.test.ts +19 -10
  36. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  37. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  38. package/src/__tests__/conversation-attachments.test.ts +0 -1
  39. package/src/__tests__/conversation-error.test.ts +17 -0
  40. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  41. package/src/__tests__/conversation-pairing.test.ts +186 -0
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  43. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  44. package/src/__tests__/credential-routes.test.ts +22 -3
  45. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  46. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  47. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  48. package/src/__tests__/dm-backfill.test.ts +63 -0
  49. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  50. package/src/__tests__/edit-propagation.test.ts +107 -4
  51. package/src/__tests__/events-client-registration.test.ts +28 -0
  52. package/src/__tests__/file-write-tool.test.ts +4 -2
  53. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  54. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  55. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  56. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  57. package/src/__tests__/host-shell-tool.test.ts +23 -4
  58. package/src/__tests__/image-conversion.test.ts +143 -1
  59. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  60. package/src/__tests__/input-repairs.test.ts +207 -0
  61. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  62. package/src/__tests__/managed-profile-guard.test.ts +4 -2
  63. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  64. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  65. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  66. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  67. package/src/__tests__/mtime-cache.test.ts +2 -0
  68. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  69. package/src/__tests__/openai-provider.test.ts +22 -0
  70. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  71. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  72. package/src/__tests__/platform.test.ts +16 -1
  73. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  74. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  75. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  76. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  77. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  78. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  79. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  80. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  81. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  82. package/src/__tests__/reaction-persistence.test.ts +200 -7
  83. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  84. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  85. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  86. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  87. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  88. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  89. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  90. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  91. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  92. package/src/__tests__/tool-executor.test.ts +63 -38
  93. package/src/__tests__/tool-policy.test.ts +97 -1
  94. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  95. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  96. package/src/__tests__/validate-input.test.ts +243 -7
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
  99. package/src/__tests__/voice-session-bridge.test.ts +42 -0
  100. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  101. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  102. package/src/acp/acp-claude-oauth.ts +38 -21
  103. package/src/acp/prepare-agent-env.ts +146 -55
  104. package/src/agent/loop-tool-dedup.test.ts +161 -0
  105. package/src/agent/loop.ts +68 -22
  106. package/src/api/constants/app-tools.ts +34 -0
  107. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  108. package/src/api/index.ts +19 -0
  109. package/src/api/responses/resource-pressure-status.ts +25 -0
  110. package/src/approvals/guardian-channel-delivery.ts +70 -6
  111. package/src/approvals/guardian-request-resolvers.ts +32 -66
  112. package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
  113. package/src/calls/voice-session-bridge.ts +60 -6
  114. package/src/channels/__tests__/message-audience.test.ts +49 -0
  115. package/src/channels/__tests__/types.test.ts +17 -3
  116. package/src/channels/message-audience.ts +40 -0
  117. package/src/channels/types.ts +16 -12
  118. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  119. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  120. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  121. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  122. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  123. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  124. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  125. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  126. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  127. package/src/cli/commands/conversations.help.ts +34 -0
  128. package/src/cli/commands/conversations.ts +125 -0
  129. package/src/cli/commands/inference-providers.ts +9 -4
  130. package/src/cli/commands/inference.help.ts +9 -4
  131. package/src/cli/commands/keys.help.ts +13 -1
  132. package/src/cli/commands/keys.ts +26 -15
  133. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  134. package/src/cli/commands/memory/index.help.ts +41 -27
  135. package/src/cli/commands/memory/index.ts +2 -0
  136. package/src/cli/commands/memory/memory-v2.ts +58 -54
  137. package/src/cli/commands/memory/memory-validate.ts +18 -0
  138. package/src/cli/commands/monitoring.ts +1 -0
  139. package/src/cli/commands/plugins.help.ts +28 -31
  140. package/src/cli/commands/plugins.ts +10 -0
  141. package/src/cli/commands/skills.help.ts +13 -16
  142. package/src/cli/commands/trust.ts +3 -14
  143. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  144. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  145. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  146. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  147. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  148. package/src/cli/lib/bundled-marketplace.json +13 -0
  149. package/src/cli/lib/daemon-credential-client.ts +25 -3
  150. package/src/cli/lib/install-from-github.ts +35 -24
  151. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  152. package/src/cli/lib/plugin-surfaces.ts +27 -0
  153. package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
  154. package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
  155. package/src/config/default-profile-catalog.ts +5 -6
  156. package/src/config/feature-flag-registry.json +32 -32
  157. package/src/config/webhook-routing.ts +8 -0
  158. package/src/context/compactor.ts +31 -2
  159. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  160. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  161. package/src/daemon/conversation-error.ts +13 -0
  162. package/src/daemon/conversation-messaging.ts +22 -2
  163. package/src/daemon/conversation-process.ts +11 -0
  164. package/src/daemon/conversation-store.ts +10 -0
  165. package/src/daemon/conversation-surfaces.ts +21 -8
  166. package/src/daemon/conversation.ts +12 -10
  167. package/src/daemon/lifecycle.ts +6 -2
  168. package/src/daemon/message-types/conversations.ts +5 -7
  169. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  170. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  171. package/src/daemon/resource-pressure-guard.ts +387 -0
  172. package/src/daemon/shutdown-handlers.ts +2 -0
  173. package/src/daemon/startup-error.ts +16 -0
  174. package/src/daemon/trust-context.ts +14 -10
  175. package/src/daemon/unsendable-image-notice.ts +76 -0
  176. package/src/hooks/registry.ts +65 -51
  177. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  178. package/src/ipc/gateway-client.test.ts +1 -1
  179. package/src/ipc/gateway-client.ts +14 -21
  180. package/src/ipc/socket-cleanup.ts +2 -11
  181. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  182. package/src/messaging/provider-message-metadata.ts +128 -0
  183. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  184. package/src/messaging/providers/channel-transport.ts +90 -13
  185. package/src/messaging/providers/discord/send.ts +16 -0
  186. package/src/messaging/providers/discord/transport.ts +11 -1
  187. package/src/messaging/providers/index.ts +109 -16
  188. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  189. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  190. package/src/messaging/providers/slack/send.test.ts +6 -9
  191. package/src/messaging/providers/slack/send.ts +40 -28
  192. package/src/messaging/providers/slack/transport.ts +22 -26
  193. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  194. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  195. package/src/messaging/read-provider-metadata.test.ts +157 -0
  196. package/src/messaging/read-provider-metadata.ts +51 -0
  197. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  198. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  199. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  200. package/src/notifications/adapters/macos.ts +55 -0
  201. package/src/notifications/adapters/slack.ts +10 -5
  202. package/src/notifications/assistant-reply-producer.ts +37 -4
  203. package/src/notifications/conversation-pairing.ts +111 -21
  204. package/src/notifications/edit-notification.ts +38 -8
  205. package/src/notifications/emit-signal.ts +12 -14
  206. package/src/notifications/home-feed-side-effect.ts +218 -15
  207. package/src/notifications/types.ts +5 -0
  208. package/src/permissions/AGENTS.md +16 -0
  209. package/src/permissions/checker.test.ts +75 -122
  210. package/src/permissions/checker.ts +44 -560
  211. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  212. package/src/permissions/confirmation-guardian-request.ts +5 -1
  213. package/src/persistence/conversation-crud.ts +23 -20
  214. package/src/persistence/conversation-queries.ts +14 -1
  215. package/src/persistence/conversation-types.ts +22 -0
  216. package/src/persistence/delivery-crud.ts +122 -15
  217. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  218. package/src/plugin-api/conversation-turn.ts +27 -0
  219. package/src/plugin-api/credential-scope.test.ts +15 -0
  220. package/src/plugin-api/credential-scope.ts +14 -0
  221. package/src/plugin-api/index.ts +19 -1
  222. package/src/plugin-api/resolve-credential.ts +15 -10
  223. package/src/plugin-api/store-credential.ts +148 -0
  224. package/src/plugin-api/system-card.ts +36 -0
  225. package/src/plugin-api/vision-support.test.ts +82 -0
  226. package/src/plugin-api/vision-support.ts +14 -2
  227. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  228. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  229. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  230. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  231. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  232. package/src/plugins/defaults/main.ts +15 -0
  233. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  234. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  235. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  236. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  237. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  238. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  239. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  240. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  241. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  242. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  243. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  244. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  245. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  246. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  247. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  248. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  249. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  250. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  251. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  252. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  253. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  254. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  255. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  256. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  257. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  258. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  259. package/src/plugins/mtime-cache.ts +1 -1
  260. package/src/plugins/pipeline.ts +14 -7
  261. package/src/plugins/plugin-execution-context.ts +35 -11
  262. package/src/plugins/plugin-tree-walk.ts +63 -4
  263. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  264. package/src/providers/anthropic/client.ts +332 -241
  265. package/src/providers/connection-resolution.ts +2 -1
  266. package/src/providers/content-blocks.ts +22 -0
  267. package/src/providers/gemini/client.ts +5 -2
  268. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  269. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  270. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  271. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  272. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  273. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  274. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  275. package/src/providers/inference/adapter-factory.ts +33 -2
  276. package/src/providers/inference/auth.ts +13 -2
  277. package/src/providers/inference/credential-usage.ts +37 -0
  278. package/src/providers/inference/missing-credential-guard.ts +110 -0
  279. package/src/providers/inference/resolve-auth.ts +7 -7
  280. package/src/providers/media-resolve.ts +176 -15
  281. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  282. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  283. package/src/providers/openai/chat-completions-provider.ts +322 -42
  284. package/src/routes/route-host-protocol.ts +7 -0
  285. package/src/routes/worker.ts +31 -10
  286. package/src/runtime/AGENTS.md +35 -0
  287. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  288. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  289. package/src/runtime/assistant-event-hub.ts +38 -0
  290. package/src/runtime/channel-reply-delivery.ts +54 -31
  291. package/src/runtime/channel-retry-sweep.ts +6 -4
  292. package/src/runtime/effective-capabilities.test.ts +19 -0
  293. package/src/runtime/effective-capabilities.ts +25 -0
  294. package/src/runtime/guardian-reply-router.ts +6 -2
  295. package/src/runtime/http-errors.ts +1 -0
  296. package/src/runtime/http-server.ts +20 -5
  297. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  298. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  299. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  300. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  301. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  302. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  303. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  304. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  305. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  306. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  307. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  308. package/src/runtime/routes/approval-routes.ts +37 -4
  309. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  310. package/src/runtime/routes/canned-message-complete.ts +68 -29
  311. package/src/runtime/routes/client-routes.ts +113 -0
  312. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  313. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  314. package/src/runtime/routes/conversation-routes.ts +36 -22
  315. package/src/runtime/routes/credential-in-use.ts +85 -0
  316. package/src/runtime/routes/credential-routes.ts +55 -85
  317. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  318. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  319. package/src/runtime/routes/identity-routes.ts +5 -121
  320. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  321. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  322. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  323. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  324. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  325. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  326. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  327. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  328. package/src/runtime/routes/index.ts +2 -0
  329. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  330. package/src/runtime/routes/monitoring-routes.ts +9 -1
  331. package/src/runtime/routes/plugins-routes.ts +17 -5
  332. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  333. package/src/runtime/routes/secret-routes.ts +39 -8
  334. package/src/runtime/routes/settings-routes.ts +6 -6
  335. package/src/runtime/routes/surface-action-routes.ts +20 -19
  336. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  337. package/src/runtime/routes/user-route-resolution.ts +18 -1
  338. package/src/runtime/slack-reply-session.test.ts +23 -13
  339. package/src/runtime/slack-reply-session.ts +38 -55
  340. package/src/runtime/web-presence.ts +90 -0
  341. package/src/skills/validate-input.ts +177 -40
  342. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  343. package/src/subagent/consult-context.ts +7 -23
  344. package/src/tools/credentials/broker.ts +24 -79
  345. package/src/tools/credentials/ref-parse.ts +35 -0
  346. package/src/tools/credentials/resolve.ts +4 -10
  347. package/src/tools/credentials/store.ts +168 -0
  348. package/src/tools/credentials/tool-policy.ts +67 -0
  349. package/src/tools/executor.ts +53 -15
  350. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  351. package/src/tools/network/url-safety.ts +9 -16
  352. package/src/tools/network/web-search.ts +21 -3
  353. package/src/tools/permission-checker.ts +43 -52
  354. package/src/tools/schema-transforms.ts +40 -5
  355. package/src/tools/shared/input-repairs.ts +160 -0
  356. package/src/tools/skills/skill-tool-factory.ts +18 -4
  357. package/src/tools/subagent/spawn.ts +0 -1
  358. package/src/tools/tool-approval-handler.ts +16 -10
  359. package/src/tools/tool-types.ts +26 -13
  360. package/src/tools/types.ts +6 -6
  361. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  362. package/src/util/cgroup-memory.ts +3 -0
  363. package/src/util/container-cpu-sampler.ts +250 -0
  364. package/src/util/image-conversion.ts +178 -14
  365. package/src/util/platform.ts +93 -10
  366. package/src/permissions/ipc-risk-types.ts +0 -143
  367. package/src/permissions/risk-types.ts +0 -76
@@ -175,12 +175,23 @@ export type ConnectionModel = z.infer<typeof ConnectionModelSchema>;
175
175
  /**
176
176
  * Providers whose connections require an explicit `baseUrl` and non-empty
177
177
  * `models` list (openai-compatible endpoints have no fixed upstream, so the
178
- * user must supply both). Every other provider derives these from its catalog
179
- * entry and rejects a client-supplied `baseUrl`.
178
+ * user must supply both).
180
179
  */
181
180
  export const PROVIDERS_REQUIRING_BASE_URL_AND_MODELS: ReadonlySet<string> =
182
181
  new Set(["openai-compatible"]);
183
182
 
183
+ /**
184
+ * Providers that persist a client-supplied `baseUrl`. openai-compatible
185
+ * requires one (see above); ollama has a well-known local default and treats
186
+ * a stored URL as an optional override. Every other provider derives its
187
+ * upstream from the catalog and rejects a client-supplied `baseUrl` so a
188
+ * keyed connection cannot be pointed at an attacker-controlled host.
189
+ */
190
+ export const PROVIDERS_ALLOWING_CUSTOM_BASE_URL: ReadonlySet<string> = new Set([
191
+ ...PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
192
+ "ollama",
193
+ ]);
194
+
184
195
  // ---------------------------------------------------------------------------
185
196
  // Full connection shape used by CRUD layer
186
197
  // ---------------------------------------------------------------------------
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Which provider connections dispatch through a given credential.
3
+ *
4
+ * Deleting a credential silently breaks every connection that resolves its
5
+ * auth through it, so the delete surfaces ask this before removing anything.
6
+ */
7
+
8
+ import { getDb } from "../../persistence/db-connection.js";
9
+ import { normalizeCredentialRef } from "../../security/credential-key.js";
10
+ import { listConnections } from "./connections.js";
11
+
12
+ /**
13
+ * Names of the provider connections whose auth resolves to
14
+ * `credentialAccount`, sorted for a stable message.
15
+ *
16
+ * Both sides are run through `normalizeCredentialRef` so a row storing the
17
+ * short `openrouter:api_key` form matches the canonical
18
+ * `credential/openrouter/api_key` account. Auth variants without a credential
19
+ * (`platform`, `none`) reference no account and never match.
20
+ *
21
+ * Read failures propagate: a caller that cannot enumerate the dependants must
22
+ * refuse the delete rather than treat the credential as unused, which is the
23
+ * exact silent breakage this check exists to prevent.
24
+ */
25
+ export function findConnectionsUsingCredential(
26
+ credentialAccount: string,
27
+ ): string[] {
28
+ const account = normalizeCredentialRef(credentialAccount);
29
+ return listConnections(getDb())
30
+ .filter(
31
+ (connection) =>
32
+ "credential" in connection.auth &&
33
+ normalizeCredentialRef(connection.auth.credential) === account,
34
+ )
35
+ .map((connection) => connection.name)
36
+ .sort();
37
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Receive-side guard for credential-backed connections.
3
+ *
4
+ * A provider instance bakes in the credential it resolved at construction, so
5
+ * a credential removed afterwards is still dispatched with. Several upstreams
6
+ * answer an unusable key with `200` and an empty `content` array rather than a
7
+ * `401`, which reaches the user as a blank assistant turn with nothing to act
8
+ * on. This wrapper converts that pair (no usable content, and the
9
+ * connection's credential definitively absent from the vault) into the
10
+ * same `ConnectionResolutionError` the pre-dispatch preflight raises, so the
11
+ * conversation layer renders its actionable "missing credential" error.
12
+ *
13
+ * A response with content passes through untouched, and an unreachable
14
+ * credential store (`indeterminate`) is never reported as a missing
15
+ * credential.
16
+ */
17
+
18
+ import type { ProviderRouteAttribution } from "../../util/errors.js";
19
+ import { getLogger } from "../../util/logger.js";
20
+ import { hasToolUse, hasVisibleText } from "../content-blocks.js";
21
+ import { checkCredentialPresence } from "../provider-availability.js";
22
+ import { ConnectionResolutionError } from "../routing-identity.js";
23
+ import type {
24
+ Message,
25
+ Provider,
26
+ ProviderResponse,
27
+ SendMessageOptions,
28
+ } from "../types.js";
29
+
30
+ const log = getLogger("providers/missing-credential-guard");
31
+
32
+ export class MissingCredentialGuardProvider implements Provider {
33
+ readonly name: string;
34
+ readonly tokenEstimationProvider?: string;
35
+ readonly defaultModel?: string;
36
+ readonly supportsNativeWebSearch?: boolean;
37
+ readonly countInputTokens?: NonNullable<Provider["countInputTokens"]>;
38
+
39
+ constructor(
40
+ private readonly inner: Provider,
41
+ private readonly connection: {
42
+ name: string;
43
+ credentialAccount: string;
44
+ },
45
+ ) {
46
+ this.name = inner.name;
47
+ this.tokenEstimationProvider = inner.tokenEstimationProvider;
48
+ this.defaultModel = inner.defaultModel;
49
+ this.supportsNativeWebSearch = inner.supportsNativeWebSearch;
50
+ if (inner.countInputTokens) {
51
+ this.countInputTokens = inner.countInputTokens.bind(inner);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Route attribution is assigned onto the outermost provider after
57
+ * construction (`attachProviderRoute`), and read back off it by usage
58
+ * accounting, so it reads and writes through to the wrapped instance.
59
+ */
60
+ get routeAttribution(): ProviderRouteAttribution | undefined {
61
+ return this.inner.routeAttribution;
62
+ }
63
+
64
+ set routeAttribution(attribution: ProviderRouteAttribution | undefined) {
65
+ this.inner.routeAttribution = attribution;
66
+ }
67
+
68
+ supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
69
+ return this.inner.supportsNativeWebSearchFor
70
+ ? this.inner.supportsNativeWebSearchFor(options)
71
+ : this.inner.supportsNativeWebSearch === true;
72
+ }
73
+
74
+ async sendMessage(
75
+ messages: Message[],
76
+ options?: SendMessageOptions,
77
+ ): Promise<ProviderResponse> {
78
+ const response = await this.inner.sendMessage(messages, options);
79
+ if (hasVisibleText(response.content) || hasToolUse(response.content)) {
80
+ return response;
81
+ }
82
+
83
+ // An empty turn has causes other than a deleted key (a genuinely empty
84
+ // completion, a refusal, a stop-sequence hit), so the credential read is
85
+ // what decides, and only its definitive `absent` answer does.
86
+ const presence = await checkCredentialPresence(
87
+ this.connection.credentialAccount,
88
+ );
89
+ if (presence !== "absent") {
90
+ return response;
91
+ }
92
+
93
+ log.warn(
94
+ {
95
+ connectionName: this.connection.name,
96
+ credential: this.connection.credentialAccount,
97
+ model: response.model,
98
+ stopReason: response.stopReason,
99
+ },
100
+ "Upstream returned no usable content for a connection whose credential is absent, so a configuration error is surfaced rather than an empty turn",
101
+ );
102
+
103
+ throw new ConnectionResolutionError(
104
+ this.connection.name,
105
+ "missing_credential",
106
+ `provider_connection "${this.connection.name}" answered with an empty response and has no credential stored: its API key was removed`,
107
+ { model: response.model },
108
+ );
109
+ }
110
+ }
@@ -17,7 +17,7 @@ import {
17
17
  } from "../platform-proxy/context.js";
18
18
  import {
19
19
  type Auth,
20
- PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
20
+ PROVIDERS_ALLOWING_CUSTOM_BASE_URL,
21
21
  type ResolvedAuth,
22
22
  } from "./auth.js";
23
23
 
@@ -36,14 +36,14 @@ export async function resolveAuth(
36
36
  { ok: true; resolved: ResolvedAuth } | { ok: false; error: ResolveAuthError }
37
37
  > {
38
38
  // Defense-in-depth: strip baseUrl for providers that should not accept one.
39
- // The route layer rejects base_url for non-openai-compatible providers, but
40
- // this guard catches any code path that bypasses route validation (e.g.
41
- // corrupted DB rows, direct calls from internal code).
39
+ // The route layer rejects base_url for those providers, but this guard
40
+ // catches any code path that bypasses route validation (e.g. corrupted DB
41
+ // rows, direct calls from internal code).
42
42
  let safeBaseUrl = opts.baseUrl;
43
- if (safeBaseUrl && !PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
43
+ if (safeBaseUrl && !PROVIDERS_ALLOWING_CUSTOM_BASE_URL.has(provider)) {
44
44
  log.warn(
45
45
  { provider, baseUrl: safeBaseUrl },
46
- `Stripping baseUrl for provider "${provider}" — base_url is only valid for openai-compatible providers.`,
46
+ `Stripping baseUrl for provider "${provider}": base_url is only valid for openai-compatible and ollama providers.`,
47
47
  );
48
48
  safeBaseUrl = null;
49
49
  }
@@ -85,7 +85,7 @@ export async function resolveAuth(
85
85
 
86
86
  case "none":
87
87
  // Keyless endpoints still need their custom baseUrl threaded through
88
- // (LM Studio / vLLM openai-compatible servers).
88
+ // (Ollama, LM Studio / vLLM openai-compatible servers).
89
89
  return {
90
90
  ok: true,
91
91
  resolved: {
@@ -12,13 +12,21 @@
12
12
  * can keep reading `block.source.data` exactly as before.
13
13
  *
14
14
  * Blocks that already carry base64 (a live, in-flight turn) pass through
15
- * untouched — no disk read — so only reloaded history pays the resolution cost,
15
+ * untouched (no disk read) so only reloaded history pays the resolution cost,
16
16
  * and only on its first send after reload. The walk is pure: it returns fresh
17
17
  * block/message objects and never mutates the caller's in-memory history.
18
+ *
19
+ * The walk is also the last place an image block can be checked before a
20
+ * provider serializes it, so it is where an image the provider cannot decode
21
+ * is replaced by a text note: see {@link UNSENDABLE_IMAGE_FORMAT_NOTE}.
18
22
  */
19
23
 
20
24
  import { optimizeImageForTransport } from "../agent/image-optimize.js";
21
25
  import { getAttachmentContent } from "../persistence/attachments-store.js";
26
+ import {
27
+ heifImageMimeType,
28
+ sniffImageMimeType,
29
+ } from "../util/image-conversion.js";
22
30
  import { getLogger } from "../util/logger.js";
23
31
  import type {
24
32
  Base64MediaSource,
@@ -43,6 +51,65 @@ export function mediaSourceBytes(source: MediaSource): Buffer | null {
43
51
  return getAttachmentContent(source.attachmentId);
44
52
  }
45
53
 
54
+ /**
55
+ * Whether an image source's bytes are not one of the formats providers accept,
56
+ * so {@link resolveMediaReferences} replaces the block with
57
+ * {@link UNSENDABLE_IMAGE_FORMAT_NOTE} instead of sending it. Applies the same
58
+ * rule as {@link resolveImageBlock}, including its treatment of a reference
59
+ * whose payload is gone: that is an unavailable attachment, not an unreadable
60
+ * format. Lets a caller holding a persisted block ask whether the model will
61
+ * see the image.
62
+ *
63
+ * Answers for the formats every provider reads, so HEIF counts as unsendable
64
+ * even though a Gemini turn ({@link MediaResolveOptions}) would carry it.
65
+ */
66
+ export function isUnsendableImageSource(source: MediaSource): boolean {
67
+ const bytes =
68
+ source.type === "base64"
69
+ ? imageHeadBytes(source.data)
70
+ : getAttachmentContent(source.attachmentId);
71
+ return bytes != null && sendableImageMimeType(bytes, {}) === null;
72
+ }
73
+
74
+ /**
75
+ * Media types a provider accepts for an image block beyond the four every
76
+ * provider reads (PNG, JPEG, GIF, WebP).
77
+ *
78
+ * HEIF is the one format where providers genuinely disagree: Gemini decodes
79
+ * `image/heic` and `image/heif` directly
80
+ * (https://ai.google.dev/gemini-api/docs/image-understanding), while
81
+ * OpenAI-compatible and Anthropic endpoints answer HTTP 400 for the whole
82
+ * request. HEIF bytes reach a provider whenever transcoding was unavailable
83
+ * (`convertImageToJpeg` is backed by macOS `sips`), so a Linux-hosted assistant
84
+ * on Gemini depends on them passing through.
85
+ */
86
+ export interface MediaResolveOptions {
87
+ acceptsHeif?: boolean;
88
+ }
89
+
90
+ // 16 base64 chars decode to the 12 bytes the longest signature needs.
91
+ function imageHeadBytes(dataBase64: string): Buffer {
92
+ return Buffer.from(dataBase64.slice(0, 16), "base64");
93
+ }
94
+
95
+ /**
96
+ * Media type the provider should be told an image is, or null when it cannot
97
+ * read the bytes at all.
98
+ */
99
+ function sendableImageMimeType(
100
+ bytes: Uint8Array,
101
+ options: MediaResolveOptions,
102
+ ): string | null {
103
+ const sniffed = sniffImageMimeType(bytes);
104
+ if (sniffed) {
105
+ return sniffed;
106
+ }
107
+ if (options.acceptsHeif) {
108
+ return heifImageMimeType(bytes);
109
+ }
110
+ return null;
111
+ }
112
+
46
113
  /**
47
114
  * Narrow a media source to its base64 arm. After {@link resolveMediaReferences}
48
115
  * runs, provider transforms only ever see base64 sources; this asserts that
@@ -96,9 +163,57 @@ export function resolveMediaSourceData(
96
163
  return { data: bytes.toString("base64"), media_type: source.media_type };
97
164
  }
98
165
 
99
- async function resolveImageBlock(block: ImageContent): Promise<ContentBlock> {
166
+ /**
167
+ * Note that replaces an image block whose bytes are not one of the formats
168
+ * providers accept (PNG, JPEG, GIF, WebP). Sending the block instead costs the
169
+ * user the whole turn: an OpenAI-compatible endpoint answers HTTP 400 ("The
170
+ * image data you provided does not represent a valid image") for the entire
171
+ * request, so one unreadable image in a batch of eight blocks the reply to all
172
+ * of them. The note keeps the turn alive and tells the model what it is
173
+ * missing, and the daemon posts a system card naming the file so the user
174
+ * knows too.
175
+ */
176
+ export const UNSENDABLE_IMAGE_FORMAT_NOTE =
177
+ "[Image omitted: its format is not one the model can read (PNG, JPEG, GIF, and WebP are)]";
178
+
179
+ /**
180
+ * Resolve an image block into one the provider can accept, keyed on the bytes
181
+ * rather than on the declared media type.
182
+ *
183
+ * The declaration is only a claim: web clients derive it from the filename
184
+ * extension, so a HEIC photo saved as `.png` arrives declared `image/png`. One
185
+ * sniff covers both failure modes that claim produces. Bytes that sniff as
186
+ * nothing the target provider reads are unsendable (a corrupt head, or a format
187
+ * outside both the universal four and the provider's own extras: AVIF, BMP,
188
+ * TIFF, SVG) and become {@link UNSENDABLE_IMAGE_FORMAT_NOTE}. Bytes that sniff
189
+ * as something other than the declaration are relabeled, since a mismatch is
190
+ * fatal on its own (Anthropic answers "image does not match the provided media
191
+ * type").
192
+ *
193
+ * The sniff runs ahead of the transport optimization so sendability cannot
194
+ * depend on an unrelated size threshold: optimization rewrites only the images
195
+ * it decides to rescale.
196
+ */
197
+ async function resolveImageBlock(
198
+ block: ImageContent,
199
+ options: MediaResolveOptions,
200
+ ): Promise<ContentBlock> {
100
201
  if (block.source.type === "base64") {
101
- return block;
202
+ const sniffed = sendableImageMimeType(
203
+ imageHeadBytes(block.source.data),
204
+ options,
205
+ );
206
+ if (!sniffed) {
207
+ log.warn(
208
+ { declaredMediaType: block.source.media_type },
209
+ "Inline image is not in a provider-readable format; substituting a text note",
210
+ );
211
+ return { type: "text", text: UNSENDABLE_IMAGE_FORMAT_NOTE };
212
+ }
213
+ return {
214
+ type: "image",
215
+ source: { type: "base64", media_type: sniffed, data: block.source.data },
216
+ };
102
217
  }
103
218
  const bytes = getAttachmentContent(block.source.attachmentId);
104
219
  if (!bytes) {
@@ -111,11 +226,22 @@ async function resolveImageBlock(block: ImageContent): Promise<ContentBlock> {
111
226
  text: "[Attachment unavailable: image could not be loaded]",
112
227
  };
113
228
  }
229
+ const sniffed = sendableImageMimeType(bytes, options);
230
+ if (!sniffed) {
231
+ log.warn(
232
+ {
233
+ attachmentId: block.source.attachmentId,
234
+ declaredMediaType: block.source.media_type,
235
+ },
236
+ "Referenced image is not in a provider-readable format; substituting a text note",
237
+ );
238
+ return { type: "text", text: UNSENDABLE_IMAGE_FORMAT_NOTE };
239
+ }
114
240
  // Apply the same transport optimization the inline-base64 path used, so a
115
241
  // reloaded (reference) turn sends the model the same bytes a live turn would.
116
242
  const { data, mediaType } = await optimizeImageForTransport(
117
243
  bytes.toString("base64"),
118
- block.source.media_type,
244
+ sniffed,
119
245
  );
120
246
  return {
121
247
  type: "image",
@@ -161,10 +287,13 @@ function resolveFileBlock(block: FileContent): ContentBlock {
161
287
  };
162
288
  }
163
289
 
164
- async function resolveBlock(block: ContentBlock): Promise<ContentBlock> {
290
+ async function resolveBlock(
291
+ block: ContentBlock,
292
+ options: MediaResolveOptions,
293
+ ): Promise<ContentBlock> {
165
294
  switch (block.type) {
166
295
  case "image":
167
- return resolveImageBlock(block);
296
+ return resolveImageBlock(block, options);
168
297
  case "file":
169
298
  return resolveFileBlock(block);
170
299
  case "tool_result": {
@@ -174,7 +303,9 @@ async function resolveBlock(block: ContentBlock): Promise<ContentBlock> {
174
303
  }
175
304
  return {
176
305
  ...block,
177
- contentBlocks: await Promise.all(block.contentBlocks.map(resolveBlock)),
306
+ contentBlocks: await Promise.all(
307
+ block.contentBlocks.map((nested) => resolveBlock(nested, options)),
308
+ ),
178
309
  };
179
310
  }
180
311
  default:
@@ -182,13 +313,40 @@ async function resolveBlock(block: ContentBlock): Promise<ContentBlock> {
182
313
  }
183
314
  }
184
315
 
185
- function contentHasReference(content: ContentBlock[]): boolean {
316
+ /**
317
+ * Whether an inline image needs rebuilding before it can be sent: its payload
318
+ * is unreadable, or its declared media type is not exactly what the bytes are.
319
+ * The comparison is exact so a non-canonical spelling of a real format is
320
+ * rewritten too (`image/jpg` is not in any provider's accepted set, and clients
321
+ * derive it from a `.jpg` extension). Sniffing here, rather than rebuilding
322
+ * every inline block unconditionally, keeps the clean live turn on the identity
323
+ * fast path below at the cost of decoding a 12-byte head per image.
324
+ */
325
+ function base64ImageNeedsRewrite(
326
+ source: Base64MediaSource,
327
+ options: MediaResolveOptions,
328
+ ): boolean {
329
+ return (
330
+ sendableImageMimeType(imageHeadBytes(source.data), options) !==
331
+ source.media_type
332
+ );
333
+ }
334
+
335
+ function contentNeedsResolution(
336
+ content: ContentBlock[],
337
+ options: MediaResolveOptions,
338
+ ): boolean {
186
339
  return content.some((block) => {
187
- if (block.type === "image" || block.type === "file") {
340
+ if (block.type === "image") {
341
+ return block.source.type === "workspace_ref"
342
+ ? true
343
+ : base64ImageNeedsRewrite(block.source, options);
344
+ }
345
+ if (block.type === "file") {
188
346
  return block.source.type === "workspace_ref";
189
347
  }
190
348
  if (block.type === "tool_result" && block.contentBlocks?.length) {
191
- return contentHasReference(block.contentBlocks);
349
+ return contentNeedsResolution(block.contentBlocks, options);
192
350
  }
193
351
  return false;
194
352
  });
@@ -196,21 +354,24 @@ function contentHasReference(content: ContentBlock[]): boolean {
196
354
 
197
355
  /**
198
356
  * Return a copy of `messages` with every {@link WorkspaceRefMediaSource}
199
- * resolved to inline base64. Messages with no references are returned unchanged
200
- * (same object reference) so the common all-base64 live turn does no allocation
201
- * or disk I/O.
357
+ * resolved to inline base64 and every image block reconciled with its own
358
+ * bytes. Messages that need neither are returned unchanged (same object
359
+ * reference) so the common live turn does no allocation or disk I/O.
202
360
  */
203
361
  export function resolveMediaReferences(
204
362
  messages: Message[],
363
+ options: MediaResolveOptions = {},
205
364
  ): Promise<Message[]> {
206
365
  return Promise.all(
207
366
  messages.map(async (message) => {
208
- if (!contentHasReference(message.content)) {
367
+ if (!contentNeedsResolution(message.content, options)) {
209
368
  return message;
210
369
  }
211
370
  return {
212
371
  ...message,
213
- content: await Promise.all(message.content.map(resolveBlock)),
372
+ content: await Promise.all(
373
+ message.content.map((block) => resolveBlock(block, options)),
374
+ ),
214
375
  };
215
376
  }),
216
377
  );