@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -0,0 +1,156 @@
1
+ /**
2
+ * A2A direct delivery adapter.
3
+ *
4
+ * Completes an A2A task with response artifacts and optionally POSTs the
5
+ * completed task to the requester's push notification URL.
6
+ */
7
+
8
+ import type {
9
+ ChannelDeliveryResult,
10
+ ChannelReplyPayload,
11
+ } from "@vellumai/gateway-client";
12
+
13
+ import {
14
+ A2A_CONTENT_TYPE,
15
+ A2A_VERSION,
16
+ A2A_VERSION_HEADER,
17
+ } from "../../../a2a/protocol-constants.js";
18
+ import type { Part } from "../../../a2a/protocol-types.js";
19
+ import * as taskStore from "../../../a2a/task-store.js";
20
+ import { getLogger } from "../../../util/logger.js";
21
+ import {
22
+ computeRetryDelay,
23
+ isRetryableStatus,
24
+ sleep,
25
+ } from "../../../util/retry.js";
26
+
27
+ const log = getLogger("a2a-deliver");
28
+
29
+ const MAX_RETRIES = 3;
30
+ const PUSH_TIMEOUT_MS = 15_000;
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** Extract the `taskId` query parameter from a callback URL. */
37
+ function parseTaskId(callbackUrl: string): string | null {
38
+ try {
39
+ return new URL(callbackUrl).searchParams.get("taskId");
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /** Build A2A parts from a channel reply payload. */
46
+ function buildParts(payload: ChannelReplyPayload): Part[] {
47
+ const parts: Part[] = [];
48
+
49
+ if (payload.text) {
50
+ parts.push({ kind: "text", text: payload.text });
51
+ }
52
+
53
+ if (payload.attachments) {
54
+ for (const att of payload.attachments) {
55
+ parts.push({
56
+ kind: "file",
57
+ filename: att.filename,
58
+ media_type: att.mimeType,
59
+ url: att.data,
60
+ });
61
+ }
62
+ }
63
+
64
+ return parts;
65
+ }
66
+
67
+ /** POST the completed task to the requester's push URL with retry. */
68
+ async function pushNotification(
69
+ pushUrl: string,
70
+ taskJson: unknown,
71
+ ): Promise<void> {
72
+ let lastError: Error | null = null;
73
+
74
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
75
+ if (attempt > 0) {
76
+ await sleep(computeRetryDelay(attempt - 1));
77
+ }
78
+
79
+ try {
80
+ const response = await fetch(pushUrl, {
81
+ method: "POST",
82
+ headers: {
83
+ "Content-Type": A2A_CONTENT_TYPE,
84
+ [A2A_VERSION_HEADER]: A2A_VERSION,
85
+ },
86
+ body: JSON.stringify(taskJson),
87
+ signal: AbortSignal.timeout(PUSH_TIMEOUT_MS),
88
+ });
89
+
90
+ if (response.ok) return;
91
+
92
+ const body = await response.text().catch(() => "");
93
+ lastError = new Error(
94
+ `Push notification failed with status ${response.status}: ${body}`,
95
+ );
96
+
97
+ if (!isRetryableStatus(response.status)) {
98
+ break;
99
+ }
100
+ } catch (err) {
101
+ lastError = err instanceof Error ? err : new Error(String(err));
102
+ }
103
+ }
104
+
105
+ // Push failure is logged but doesn't propagate
106
+ log.warn(
107
+ { pushUrl, error: lastError?.message },
108
+ "A2A push notification failed after retries",
109
+ );
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Public API
114
+ // ---------------------------------------------------------------------------
115
+
116
+ /** Deliver an assistant reply as an A2A task completion. */
117
+ export async function deliverA2AReply(
118
+ callbackUrl: string,
119
+ payload: ChannelReplyPayload,
120
+ ): Promise<ChannelDeliveryResult> {
121
+ const taskId = parseTaskId(callbackUrl);
122
+ if (!taskId) {
123
+ return { ok: false };
124
+ }
125
+
126
+ const parts = buildParts(payload);
127
+ if (parts.length === 0) {
128
+ log.debug({ taskId }, "No content to deliver; skipping A2A completion");
129
+ return { ok: true };
130
+ }
131
+
132
+ let completedTask;
133
+ try {
134
+ completedTask = taskStore.completeWithArtifacts(taskId, [
135
+ { artifact_id: crypto.randomUUID(), parts },
136
+ ]);
137
+ } catch (err) {
138
+ const message = err instanceof Error ? err.message : String(err);
139
+ log.error({ taskId, error: message }, "Failed to complete A2A task");
140
+ return { ok: false };
141
+ }
142
+
143
+ // Push notification — fire-and-forget
144
+ const pushUrl = taskStore.getPushUrl(taskId);
145
+ if (pushUrl) {
146
+ pushNotification(pushUrl, completedTask).catch((err) => {
147
+ log.error(
148
+ { taskId, pushUrl, error: String(err) },
149
+ "Unexpected push notification error",
150
+ );
151
+ });
152
+ }
153
+
154
+ log.info({ taskId }, "A2A reply delivered");
155
+ return { ok: true };
156
+ }
@@ -422,13 +422,16 @@ async function executeBatchCall(
422
422
  /** Max concurrent individual getMessage requests (matches batch concurrency) */
423
423
  const INDIVIDUAL_CONCURRENCY = BATCH_CONCURRENCY;
424
424
 
425
+ /** Delay between waves of individual fetches to avoid rate-limit storms (ms). */
426
+ const INTER_WAVE_DELAY_MS = 500;
427
+
425
428
  /**
426
429
  * Fetch all messages individually using getMessage (no batch endpoint).
427
430
  * Used as a fallback when the batch API is unavailable (e.g. platform connections
428
431
  * that cannot expose raw tokens for the multipart batch endpoint).
429
432
  *
430
- * Processes messages in waves of INDIVIDUAL_CONCURRENCY to avoid unbounded
431
- * parallelism that would trigger 429s on high-volume paths like senderDigest.
433
+ * Processes messages in waves of INDIVIDUAL_CONCURRENCY with a brief inter-wave
434
+ * delay to avoid triggering upstream rate limits on high-volume paths.
432
435
  */
433
436
  async function fetchMessagesIndividually(
434
437
  connection: OAuthConnection,
@@ -441,6 +444,10 @@ async function fetchMessagesIndividually(
441
444
  const results: GmailMessage[] = [];
442
445
  for (let i = 0; i < messageIds.length; i += INDIVIDUAL_CONCURRENCY) {
443
446
  signal?.throwIfAborted();
447
+ // Delay between waves (skip before the first wave)
448
+ if (i > 0) {
449
+ await signalAwareSleep(INTER_WAVE_DELAY_MS, signal);
450
+ }
444
451
  const wave = messageIds.slice(i, i + INDIVIDUAL_CONCURRENCY);
445
452
  const waveResults = await Promise.all(
446
453
  wave.map((id) =>
@@ -5,7 +5,7 @@
5
5
  * matcher and send logic here. The gateway-client consults
6
6
  * `isDirectDelivery()` before falling back to the HTTP proxy path.
7
7
  *
8
- * Currently supported: WhatsApp, Telegram, Slack.
8
+ * Currently supported: WhatsApp, Telegram, Slack, A2A.
9
9
  */
10
10
 
11
11
  import type {
@@ -15,6 +15,7 @@ import type {
15
15
  import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
16
16
 
17
17
  import { getLogger } from "../../util/logger.js";
18
+ import { deliverA2AReply } from "./a2a/deliver.js";
18
19
  import {
19
20
  sendSlackAssistantThreadStatus,
20
21
  sendSlackAttachments,
@@ -59,6 +60,10 @@ function isSlackCallback(callbackUrl: string): boolean {
59
60
  }
60
61
  }
61
62
 
63
+ function isA2ACallback(callbackUrl: string): boolean {
64
+ return matchesPathname(callbackUrl, "/deliver/a2a");
65
+ }
66
+
62
67
  function parseSlackCallbackParams(callbackUrl: string): {
63
68
  channel?: string;
64
69
  threadTs?: string;
@@ -233,7 +238,8 @@ export function isDirectDelivery(callbackUrl: string): boolean {
233
238
  return (
234
239
  isWhatsAppCallback(callbackUrl) ||
235
240
  isTelegramCallback(callbackUrl) ||
236
- isSlackCallback(callbackUrl)
241
+ isSlackCallback(callbackUrl) ||
242
+ isA2ACallback(callbackUrl)
237
243
  );
238
244
  }
239
245
 
@@ -254,6 +260,9 @@ export async function deliverDirect(
254
260
  if (isSlackCallback(callbackUrl)) {
255
261
  return deliverSlack(callbackUrl, payload);
256
262
  }
263
+ if (isA2ACallback(callbackUrl)) {
264
+ return deliverA2AReply(callbackUrl, payload);
265
+ }
257
266
 
258
267
  // Defensive — isDirectDelivery should have returned false.
259
268
  throw new Error(
@@ -24,13 +24,18 @@ mock.module("../../../../security/secure-keys.js", () => ({
24
24
  }));
25
25
 
26
26
  // OAuth helpers are exercised only when no bot_token is cached. The adapter
27
- // imports them at module load route them through a stub that signals any
28
- // OAuth fallback with a distinctive error so tests can assert on it.
27
+ // imports them at module load, so route them through a configurable stub.
29
28
  const OAUTH_FALLBACK_SENTINEL = "OAUTH_FALLBACK_NOT_STUBBED";
30
- mock.module("../../../../oauth/connection-resolver.js", () => ({
31
- resolveOAuthConnection: async (): Promise<OAuthConnection> => {
29
+ const resolveOAuthConnectionMock = mock(
30
+ async (
31
+ _provider: string,
32
+ _opts?: { account?: string },
33
+ ): Promise<OAuthConnection> => {
32
34
  throw new Error(OAUTH_FALLBACK_SENTINEL);
33
35
  },
36
+ );
37
+ mock.module("../../../../oauth/connection-resolver.js", () => ({
38
+ resolveOAuthConnection: resolveOAuthConnectionMock,
34
39
  }));
35
40
  mock.module("../../../../oauth/oauth-store.js", () => ({
36
41
  isProviderConnected: async () => false,
@@ -44,7 +49,7 @@ mock.module("../../../../contacts/contacts-write.js", () => ({
44
49
  upsertContactChannel: () => {},
45
50
  }));
46
51
 
47
- import { slackProvider } from "../adapter.js";
52
+ import { slackProvider, withSlackBotToken } from "../adapter.js";
48
53
 
49
54
  // ── fetch capture ───────────────────────────────────────────────────────────
50
55
 
@@ -108,9 +113,23 @@ function fakeSlackResponse(url: string): Record<string, unknown> {
108
113
  const BOT_TOKEN = "xoxb-BOT";
109
114
  const USER_TOKEN = "xoxp-USER";
110
115
 
116
+ function makeOAuthConnection(account: string, token: string): OAuthConnection {
117
+ return {
118
+ id: `conn-${account}`,
119
+ provider: "slack",
120
+ accountInfo: account,
121
+ request: async () => ({ status: 200, headers: {}, body: { ok: true } }),
122
+ withToken: async <T>(fn: (rawToken: string) => Promise<T>) => fn(token),
123
+ };
124
+ }
125
+
111
126
  describe("Slack adapter token routing", () => {
112
127
  beforeEach(() => {
113
128
  captured.length = 0;
129
+ resolveOAuthConnectionMock.mockReset();
130
+ resolveOAuthConnectionMock.mockImplementation(async () => {
131
+ throw new Error(OAUTH_FALLBACK_SENTINEL);
132
+ });
114
133
  getSecureKeyAsyncMock.mockReset();
115
134
  getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
116
135
  if (key === credentialKey("slack_channel", "bot_token")) return BOT_TOKEN;
@@ -279,4 +298,25 @@ describe("Slack adapter token routing", () => {
279
298
  OAUTH_FALLBACK_SENTINEL,
280
299
  );
281
300
  });
301
+
302
+ test("raw bot token helper resolves the requested OAuth account even when cache is warm", async () => {
303
+ getSecureKeyAsyncMock.mockImplementation(async () => null);
304
+ resolveOAuthConnectionMock.mockImplementation(
305
+ async (_provider: string, opts?: { account?: string }) => {
306
+ const account = opts?.account ?? "default";
307
+ return makeOAuthConnection(account, `token-${account}`);
308
+ },
309
+ );
310
+
311
+ await slackProvider.resolveConnection!("workspace-a");
312
+
313
+ const result = await withSlackBotToken("workspace-b", async (token) => {
314
+ return token;
315
+ });
316
+
317
+ expect(result).toBe("token-workspace-b");
318
+ expect(resolveOAuthConnectionMock).toHaveBeenCalledWith("slack", {
319
+ account: "workspace-b",
320
+ });
321
+ });
282
322
  });
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Unit tests for the assistant-side Slack file downloader used by the
3
+ * thread-backfill image-hydration path.
4
+ *
5
+ * The downloader has three contract-level behaviors worth pinning:
6
+ * 1. URL selection — `url_private_download` is preferred over `url_private`.
7
+ * 2. Bearer auth — the bot token MUST be sent on the initial request.
8
+ * 3. Manual cross-origin redirect handling — the CDN URL is signed and the
9
+ * Authorization header MUST NOT be re-sent on the second hop (Slack
10
+ * rejects the signed URL when an unexpected Authorization is present).
11
+ * 4. Returns null when no usable URL is present.
12
+ */
13
+
14
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
15
+
16
+ mock.module("../../../../util/logger.js", () => ({
17
+ getLogger: () =>
18
+ new Proxy({} as Record<string, unknown>, {
19
+ get: () => () => {},
20
+ }),
21
+ }));
22
+
23
+ import { downloadSlackFile } from "../download.js";
24
+
25
+ interface CapturedFetchCall {
26
+ url: string;
27
+ init: RequestInit | undefined;
28
+ }
29
+
30
+ let calls: CapturedFetchCall[];
31
+ let responses: Response[];
32
+ let originalFetch: typeof fetch;
33
+
34
+ beforeEach(() => {
35
+ calls = [];
36
+ responses = [];
37
+ originalFetch = globalThis.fetch;
38
+ globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
39
+ calls.push({
40
+ url: typeof input === "string" ? input : input.toString(),
41
+ init,
42
+ });
43
+ const next = responses.shift();
44
+ if (!next) {
45
+ throw new Error("downloadSlackFile test: no canned response available");
46
+ }
47
+ return next;
48
+ }) as typeof fetch;
49
+ });
50
+
51
+ afterEach(() => {
52
+ globalThis.fetch = originalFetch;
53
+ });
54
+
55
+ describe("downloadSlackFile", () => {
56
+ test("returns null when neither url_private_download nor url_private is present", async () => {
57
+ const result = await downloadSlackFile(
58
+ { name: "screenshot.png", mimetype: "image/png" },
59
+ "xoxb-test",
60
+ );
61
+ expect(result).toBeNull();
62
+ expect(calls.length).toBe(0);
63
+ });
64
+
65
+ test("prefers url_private_download over url_private", async () => {
66
+ responses.push(
67
+ new Response(new Uint8Array([1, 2, 3]).buffer, {
68
+ status: 200,
69
+ headers: { "Content-Type": "image/png" },
70
+ }),
71
+ );
72
+ await downloadSlackFile(
73
+ {
74
+ id: "F1",
75
+ name: "shot.png",
76
+ mimetype: "image/png",
77
+ urlPrivateDownload: "https://files.slack.com/files-pri/T/F1/download",
78
+ urlPrivate: "https://files.slack.com/files-pri/T/F1/inline",
79
+ },
80
+ "xoxb-test",
81
+ );
82
+ expect(calls.length).toBe(1);
83
+ expect(calls[0].url).toBe(
84
+ "https://files.slack.com/files-pri/T/F1/download",
85
+ );
86
+ });
87
+
88
+ test("sends bot token as Bearer on the initial request and base64-encodes the body", async () => {
89
+ responses.push(
90
+ new Response(new Uint8Array([0xde, 0xad, 0xbe, 0xef]).buffer, {
91
+ status: 200,
92
+ headers: { "Content-Type": "image/png" },
93
+ }),
94
+ );
95
+ const result = await downloadSlackFile(
96
+ {
97
+ id: "F1",
98
+ name: "shot.png",
99
+ mimetype: "image/png",
100
+ urlPrivate: "https://files.slack.com/files-pri/T/F1/inline",
101
+ },
102
+ "xoxb-test-token",
103
+ );
104
+ expect(calls.length).toBe(1);
105
+ const auth = (calls[0].init?.headers as Record<string, string>)
106
+ ?.Authorization;
107
+ expect(auth).toBe("Bearer xoxb-test-token");
108
+ expect(calls[0].init?.redirect).toBe("manual");
109
+ expect(result).not.toBeNull();
110
+ expect(result?.filename).toBe("shot.png");
111
+ expect(result?.mimeType).toBe("image/png");
112
+ // 0xdeadbeef → "3q2+7w==" in base64.
113
+ expect(result?.data).toBe("3q2+7w==");
114
+ });
115
+
116
+ test("follows a 302 to the signed CDN URL without re-sending the bearer token", async () => {
117
+ responses.push(
118
+ new Response(null, {
119
+ status: 302,
120
+ headers: {
121
+ Location:
122
+ "https://files-edge.slack.com/files-tmb/T-F1-abc/cdn-signed?t=1700000000",
123
+ },
124
+ }),
125
+ );
126
+ responses.push(
127
+ new Response(new Uint8Array([1, 2]).buffer, {
128
+ status: 200,
129
+ headers: { "Content-Type": "image/jpeg" },
130
+ }),
131
+ );
132
+ const result = await downloadSlackFile(
133
+ {
134
+ id: "F1",
135
+ name: "photo.jpg",
136
+ mimetype: "image/jpeg",
137
+ urlPrivateDownload: "https://files.slack.com/files-pri/T/F1/download",
138
+ },
139
+ "xoxb-test",
140
+ );
141
+ expect(calls.length).toBe(2);
142
+ expect(calls[0].init?.redirect).toBe("manual");
143
+ const secondAuth = (calls[1].init?.headers as Record<string, string>)
144
+ ?.Authorization;
145
+ expect(secondAuth).toBeUndefined();
146
+ expect(calls[1].url).toBe(
147
+ "https://files-edge.slack.com/files-tmb/T-F1-abc/cdn-signed?t=1700000000",
148
+ );
149
+ expect(result?.data).toBe(Buffer.from([1, 2]).toString("base64"));
150
+ });
151
+
152
+ test("resolves a relative Location header against the original URL", async () => {
153
+ responses.push(
154
+ new Response(null, {
155
+ status: 302,
156
+ headers: { Location: "/files-tmb/cdn-signed?t=1700" },
157
+ }),
158
+ );
159
+ responses.push(
160
+ new Response(new Uint8Array([9]).buffer, {
161
+ status: 200,
162
+ headers: { "Content-Type": "image/png" },
163
+ }),
164
+ );
165
+ await downloadSlackFile(
166
+ {
167
+ id: "F1",
168
+ name: "x.png",
169
+ urlPrivateDownload: "https://files.slack.com/a/b/download",
170
+ },
171
+ "xoxb-test",
172
+ );
173
+ expect(calls[1].url).toBe(
174
+ "https://files.slack.com/files-tmb/cdn-signed?t=1700",
175
+ );
176
+ });
177
+
178
+ test("throws when the second hop responds non-2xx", async () => {
179
+ responses.push(
180
+ new Response(null, {
181
+ status: 302,
182
+ headers: { Location: "https://files-edge.slack.com/cdn?t=1" },
183
+ }),
184
+ );
185
+ responses.push(
186
+ new Response(null, { status: 403, statusText: "Forbidden" }),
187
+ );
188
+ await expect(
189
+ downloadSlackFile(
190
+ {
191
+ id: "F1",
192
+ name: "x.png",
193
+ urlPrivateDownload: "https://files.slack.com/a/b/download",
194
+ },
195
+ "xoxb-test",
196
+ ),
197
+ ).rejects.toThrow(/403/);
198
+ });
199
+
200
+ test("throws when a redirect has no Location header", async () => {
201
+ responses.push(new Response(null, { status: 302 }));
202
+ await expect(
203
+ downloadSlackFile(
204
+ {
205
+ id: "F1",
206
+ name: "x.png",
207
+ urlPrivateDownload: "https://files.slack.com/a/b/download",
208
+ },
209
+ "xoxb-test",
210
+ ),
211
+ ).rejects.toThrow(/no Location header/);
212
+ });
213
+
214
+ test("falls back to response Content-Type when file.mimetype is absent", async () => {
215
+ responses.push(
216
+ new Response(new Uint8Array([1]).buffer, {
217
+ status: 200,
218
+ headers: { "Content-Type": "image/webp; charset=binary" },
219
+ }),
220
+ );
221
+ const result = await downloadSlackFile(
222
+ {
223
+ id: "F1",
224
+ name: "photo.webp",
225
+ urlPrivate: "https://files.slack.com/files-pri/T/F1/inline",
226
+ },
227
+ "xoxb-test",
228
+ );
229
+ expect(result?.mimeType).toBe("image/webp");
230
+ });
231
+ });
@@ -99,6 +99,30 @@ function getWriteAuth(connection?: OAuthConnection): OAuthConnection | string {
99
99
  return getSlackAuth(connection);
100
100
  }
101
101
 
102
+ /**
103
+ * Resolve the bot token (raw string) and pass it to `fn`. Returns the
104
+ * callback's result, or `null` when no Slack auth is available.
105
+ *
106
+ * Bridges the Socket Mode case (cached string token) and the OAuth case
107
+ * (`OAuthConnection.withToken`) for callers that need a raw token to hand
108
+ * to a non-Slack-client API call — currently `downloadSlackFile` for inline
109
+ * file/image fetches. Slack-client method calls should keep going through
110
+ * `getReadAuth` / `getWriteAuth` and pass the union through.
111
+ */
112
+ export async function withSlackBotToken<T>(
113
+ account: string | undefined,
114
+ fn: (token: string) => Promise<T>,
115
+ ): Promise<T | null> {
116
+ // Resolve for this call's account even when the process cache is warm.
117
+ // Multi-workspace backfills can interleave, so use the returned connection
118
+ // directly instead of accepting any previously cached workspace token.
119
+ const resolvedAuth = await slackProvider.resolveConnection?.(account);
120
+ const auth = resolvedAuth ?? _cachedSlackWriteAuth;
121
+ if (!auth) return null;
122
+ if (typeof auth === "string") return fn(auth);
123
+ return auth.withToken(fn);
124
+ }
125
+
102
126
  /**
103
127
  * Run a read-path Slack call, falling back to the bot token if the cached
104
128
  * user token is rejected with an auth error. On fallback, the read cache is
@@ -192,15 +216,31 @@ function mapConversation(conv: SlackConversation): Conversation {
192
216
  };
193
217
  }
194
218
 
195
- function mapSlackFiles(
196
- files: SlackMessage["files"],
197
- ): Array<{ id?: string; name: string; mimetype?: string }> | undefined {
219
+ function mapSlackFiles(files: SlackMessage["files"]):
220
+ | Array<{
221
+ id?: string;
222
+ name: string;
223
+ mimetype?: string;
224
+ /**
225
+ * Transient — only present on the in-flight `ProviderMessage.metadata`.
226
+ * The persisted `slackFiles` shape carries `{ id, name, mimetype }` only
227
+ * (see `slackFileMetadataSchema`). Callers that hydrate image attachments
228
+ * during backfill rely on this URL; persistence strips it before write.
229
+ */
230
+ urlPrivateDownload?: string;
231
+ urlPrivate?: string;
232
+ }>
233
+ | undefined {
198
234
  if (!files || files.length === 0) return undefined;
199
235
  const mapped = files
200
236
  .map((file) => ({
201
237
  ...(file.id ? { id: file.id } : {}),
202
238
  name: file.name,
203
239
  ...(file.mimetype ? { mimetype: file.mimetype } : {}),
240
+ ...(file.url_private_download
241
+ ? { urlPrivateDownload: file.url_private_download }
242
+ : {}),
243
+ ...(file.url_private ? { urlPrivate: file.url_private } : {}),
204
244
  }))
205
245
  .filter((file) => file.name.length > 0);
206
246
  return mapped.length > 0 ? mapped : undefined;
@@ -419,8 +459,6 @@ export const slackProvider: MessagingProvider = {
419
459
  if (conv.type === "dm" && conv.metadata?.dmUserId) {
420
460
  const dmUserId = conv.metadata.dmUserId as string;
421
461
  conv.name = await resolveUserName(auth, dmUserId);
422
-
423
-
424
462
  }
425
463
  }
426
464
 
@@ -20,8 +20,10 @@ import type {
20
20
  SlackConversationsListResponse,
21
21
  SlackConversationsOpenResponse,
22
22
  SlackPostMessageResponse,
23
+ SlackReactionsAddResponse,
23
24
  SlackSearchMessagesResponse,
24
25
  SlackUserInfoResponse,
26
+ SlackUsersListResponse,
25
27
  } from "./types.js";
26
28
 
27
29
  const SLACK_API_BASE = "https://slack.com/api";
@@ -432,3 +434,28 @@ export async function searchMessages(
432
434
  },
433
435
  );
434
436
  }
437
+
438
+ export async function addReaction(
439
+ connectionOrToken: OAuthConnection | string,
440
+ channel: string,
441
+ timestamp: string,
442
+ name: string,
443
+ ): Promise<SlackReactionsAddResponse> {
444
+ return request<SlackReactionsAddResponse>(
445
+ connectionOrToken,
446
+ "reactions.add",
447
+ undefined,
448
+ { channel, timestamp, name },
449
+ );
450
+ }
451
+
452
+ export async function listUsers(
453
+ connectionOrToken: OAuthConnection | string,
454
+ limit = 200,
455
+ cursor?: string,
456
+ ): Promise<SlackUsersListResponse> {
457
+ return request<SlackUsersListResponse>(connectionOrToken, "users.list", {
458
+ limit: String(limit),
459
+ cursor,
460
+ });
461
+ }