@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
@@ -1,16 +1,23 @@
1
1
  /**
2
2
  * Channel conversation deletion handler.
3
3
  */
4
- import { deleteConversationKey } from "../../memory/conversation-key-store.js";
5
- import { deleteBindingByChannelChat } from "../../memory/external-conversation-store.js";
6
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
4
+ import {
5
+ deleteConversationKey,
6
+ getOrCreateConversation,
7
+ } from "../../memory/conversation-key-store.js";
8
+ import { buildScopedConversationKey } from "../../memory/delivery-crud.js";
9
+ import {
10
+ deleteBindingByChannelChat,
11
+ deleteBindingByChannelChatThread,
12
+ } from "../../memory/external-conversation-store.js";
7
13
  import { BadRequestError } from "./errors.js";
8
14
  import type { RouteHandlerArgs } from "./types.js";
9
15
 
10
16
  export function handleDeleteConversation({ body = {} }: RouteHandlerArgs) {
11
- const { sourceChannel, conversationExternalId } = body as {
17
+ const { sourceChannel, conversationExternalId, sourceThreadId } = body as {
12
18
  sourceChannel?: string;
13
19
  conversationExternalId?: string;
20
+ sourceThreadId?: string;
14
21
  };
15
22
 
16
23
  if (!sourceChannel || typeof sourceChannel !== "string") {
@@ -20,14 +27,27 @@ export function handleDeleteConversation({ body = {} }: RouteHandlerArgs) {
20
27
  throw new BadRequestError("conversationExternalId is required");
21
28
  }
22
29
 
23
- const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
30
+ const normalizedThreadId = sourceThreadId?.trim() || undefined;
24
31
 
25
- const scopedKey = `asst:${assistantId}:${sourceChannel}:${conversationExternalId}`;
32
+ const scopedKey = buildScopedConversationKey(
33
+ sourceChannel,
34
+ conversationExternalId,
35
+ normalizedThreadId,
36
+ );
26
37
  deleteConversationKey(scopedKey);
27
- if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
28
- const legacyKey = `${sourceChannel}:${conversationExternalId}`;
38
+ const legacyKey = `${sourceChannel}:${conversationExternalId}`;
39
+ if (!normalizedThreadId) {
29
40
  deleteConversationKey(legacyKey);
30
41
  deleteBindingByChannelChat(sourceChannel, conversationExternalId);
42
+ } else {
43
+ if (sourceChannel === "slack") {
44
+ getOrCreateConversation(scopedKey);
45
+ }
46
+ deleteBindingByChannelChatThread(
47
+ sourceChannel,
48
+ conversationExternalId,
49
+ normalizedThreadId,
50
+ );
31
51
  }
32
52
 
33
53
  return { ok: true };
@@ -4,6 +4,10 @@
4
4
  * verification, guardian action answers, approval interception, and
5
5
  * invite token redemption.
6
6
  */
7
+ import {
8
+ attachmentsToContentBlocks,
9
+ type MessageAttachmentInput,
10
+ } from "../../agent/attachments.js";
7
11
  import { getChannelPermissionProfile } from "../../channels/permission-profiles.js";
8
12
  import {
9
13
  CHANNEL_IDS,
@@ -20,7 +24,12 @@ import { classifyDiskPressureTurnPolicy } from "../../daemon/disk-pressure-polic
20
24
  import { processMessage } from "../../daemon/process-message.js";
21
25
  import type { TrustContext } from "../../daemon/trust-context.js";
22
26
  import { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
23
- import { getAttachmentsByIds } from "../../memory/attachments-store.js";
27
+ import {
28
+ attachInlineAttachmentToMessage,
29
+ AttachmentUploadError,
30
+ getAttachmentsByIds,
31
+ validateAttachmentUpload,
32
+ } from "../../memory/attachments-store.js";
24
33
  import {
25
34
  recordConversationSeenSignal,
26
35
  type SignalType,
@@ -30,6 +39,7 @@ import {
30
39
  getMessageById,
31
40
  getMessages,
32
41
  selectSlackMetaCandidateMetadata,
42
+ updateMessageContent,
33
43
  updateMessageMetadata,
34
44
  } from "../../memory/conversation-crud.js";
35
45
  import {
@@ -45,18 +55,21 @@ import {
45
55
  import { markProcessed } from "../../memory/delivery-status.js";
46
56
  import { upsertBinding } from "../../memory/external-conversation-store.js";
47
57
  import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
58
+ import { withSlackBotToken } from "../../messaging/providers/slack/adapter.js";
48
59
  import {
49
60
  backfillDm,
50
61
  backfillThreadWindowPage,
51
62
  type SlackBackfillWindowPage,
52
63
  } from "../../messaging/providers/slack/backfill.js";
64
+ import { downloadSlackFile } from "../../messaging/providers/slack/download.js";
53
65
  import {
54
66
  mergeSlackMetadata,
55
- readSlackMetadata,
67
+ readSlackMetadataFromMessageMetadata,
56
68
  type SlackFileMetadata,
57
69
  type SlackMessageMetadata,
58
70
  writeSlackMetadata,
59
71
  } from "../../messaging/providers/slack/message-metadata.js";
72
+ import type { ContentBlock } from "../../providers/types.js";
60
73
  import { wrapUntrustedContent } from "../../security/untrusted-content.js";
61
74
  import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
62
75
  import { getLogger } from "../../util/logger.js";
@@ -454,6 +467,12 @@ export async function handleChannelInbound({
454
467
  typeof sourceMetadata?.messageId === "string"
455
468
  ? sourceMetadata.messageId
456
469
  : undefined;
470
+ const slackThreadTs =
471
+ sourceChannel === "slack" &&
472
+ typeof sourceMetadata?.threadId === "string" &&
473
+ sourceMetadata.threadId.trim().length > 0
474
+ ? sourceMetadata.threadId.trim()
475
+ : undefined;
457
476
 
458
477
  if (isEdit && !sourceMessageId) {
459
478
  throw new BadRequestError("sourceMetadata.messageId is required for edits");
@@ -466,6 +485,7 @@ export async function handleChannelInbound({
466
485
  conversationExternalId,
467
486
  externalMessageId,
468
487
  sourceMessageId,
488
+ sourceThreadId: slackThreadTs,
469
489
  canonicalAssistantId,
470
490
  assistantId,
471
491
  content,
@@ -478,7 +498,11 @@ export async function handleChannelInbound({
478
498
  sourceChannel,
479
499
  conversationExternalId,
480
500
  externalMessageId,
481
- { sourceMessageId, assistantId: canonicalAssistantId },
501
+ {
502
+ sourceMessageId,
503
+ assistantId: canonicalAssistantId,
504
+ sourceThreadId: slackThreadTs,
505
+ },
482
506
  );
483
507
 
484
508
  const replyCallbackUrl = body.replyCallbackUrl;
@@ -523,6 +547,7 @@ export async function handleChannelInbound({
523
547
  conversationId: result.conversationId,
524
548
  sourceChannel,
525
549
  externalChatId: conversationExternalId,
550
+ externalThreadId: slackThreadTs ?? null,
526
551
  externalUserId: canonicalSenderId ?? rawSenderId ?? null,
527
552
  displayName: body.actorDisplayName ?? null,
528
553
  username: body.actorUsername ?? null,
@@ -1018,11 +1043,6 @@ export async function handleChannelInbound({
1018
1043
  // this into a `slackMeta` sub-object in the row's metadata column so
1019
1044
  // the chronological renderer can reconstruct thread structure without
1020
1045
  // re-fetching from Slack.
1021
- const slackThreadTs =
1022
- sourceChannel === "slack" &&
1023
- typeof sourceMetadata?.threadId === "string"
1024
- ? sourceMetadata.threadId
1025
- : undefined;
1026
1046
  const slackInbound =
1027
1047
  sourceChannel === "slack"
1028
1048
  ? {
@@ -1034,6 +1054,9 @@ export async function handleChannelInbound({
1034
1054
  displayName: body.actorDisplayName ?? body.actorUsername!,
1035
1055
  }
1036
1056
  : {}),
1057
+ ...(trustCtx.requesterExternalUserId
1058
+ ? { actorExternalUserId: trustCtx.requesterExternalUserId }
1059
+ : {}),
1037
1060
  }
1038
1061
  : undefined;
1039
1062
 
@@ -1048,6 +1071,8 @@ export async function handleChannelInbound({
1048
1071
  sourceMetadata.account.length > 0
1049
1072
  ? sourceMetadata.account
1050
1073
  : undefined;
1074
+ const slackBotMentioned =
1075
+ sourceChannel === "slack" && sourceMetadata?.slackBotMentioned === true;
1051
1076
 
1052
1077
  // ── DM cold-start backfill ──
1053
1078
  // First time a Slack DM lands in a conversation that has fewer than
@@ -1071,6 +1096,7 @@ export async function handleChannelInbound({
1071
1096
  conversationId: result.conversationId,
1072
1097
  channelId: conversationExternalId,
1073
1098
  account: slackAccount,
1099
+ guardianExternalUserId: trustCtx.guardianExternalUserId,
1074
1100
  latestTs: boundingTs,
1075
1101
  });
1076
1102
  }
@@ -1093,6 +1119,7 @@ export async function handleChannelInbound({
1093
1119
  threadTs: slackThreadTs,
1094
1120
  excludeChannelTs: slackInbound?.channelTs,
1095
1121
  account: slackAccount,
1122
+ guardianExternalUserId: trustCtx.guardianExternalUserId,
1096
1123
  });
1097
1124
  const lateJoinNotice = buildSlackLateJoinNotice(backfillResult);
1098
1125
  if (lateJoinNotice) slackRuntimeContextNotice = lateJoinNotice;
@@ -1103,10 +1130,14 @@ export async function handleChannelInbound({
1103
1130
  const contentForProcessing =
1104
1131
  trustCtx.trustClass !== "guardian"
1105
1132
  ? wrapUntrustedContent(trimmedContent, {
1106
- source: "webhook",
1133
+ source: sourceChannel === "slack" ? "slack" : "webhook",
1107
1134
  sourceDetail: trustCtx.requesterIdentifier,
1108
1135
  })
1109
1136
  : trimmedContent;
1137
+ const displayContentForProcessing =
1138
+ sourceChannel === "slack" && trustCtx.trustClass !== "guardian"
1139
+ ? trimmedContent
1140
+ : undefined;
1110
1141
 
1111
1142
  // Fire-and-forget: process the message and deliver the reply in the background.
1112
1143
  // The HTTP response returns immediately so the gateway webhook is not blocked.
@@ -1117,6 +1148,7 @@ export async function handleChannelInbound({
1117
1148
  conversationId: result.conversationId,
1118
1149
  eventId: result.eventId,
1119
1150
  content: contentForProcessing,
1151
+ displayContent: displayContentForProcessing,
1120
1152
  attachmentIds: hasAttachments ? attachmentIds : undefined,
1121
1153
  sourceChannel,
1122
1154
  sourceInterface,
@@ -1131,6 +1163,7 @@ export async function handleChannelInbound({
1131
1163
  assistantId: canonicalAssistantId,
1132
1164
  approvalCopyGenerator,
1133
1165
  chatType: sourceChatType,
1166
+ slackBotMentioned,
1134
1167
  slackInbound,
1135
1168
  });
1136
1169
  }
@@ -1330,25 +1363,6 @@ function countSlackMetaMessages(conversationId: string): number {
1330
1363
  return count;
1331
1364
  }
1332
1365
 
1333
- function readSlackMetadataFromMessageMetadata(
1334
- metadata: string | null | undefined,
1335
- ): SlackMessageMetadata | null {
1336
- if (!metadata) return null;
1337
- let parent: Record<string, unknown> | null = null;
1338
- try {
1339
- const parsed = JSON.parse(metadata) as unknown;
1340
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1341
- parent = parsed as Record<string, unknown>;
1342
- }
1343
- } catch {
1344
- return null;
1345
- }
1346
- if (!parent) return null;
1347
- const raw = parent.slackMeta;
1348
- if (typeof raw !== "string") return null;
1349
- return readSlackMetadata(raw);
1350
- }
1351
-
1352
1366
  /**
1353
1367
  * Build the set of `slackMeta.channelTs` values already stored on a
1354
1368
  * conversation. Used by both DM cold-start backfill and thread gap/delta
@@ -1433,11 +1447,10 @@ function readStoredSlackThreadState(
1433
1447
  * `slackMeta` envelope.
1434
1448
  *
1435
1449
  * Shared insertion point for any path that hydrates Slack history lazily
1436
- * (DM cold-start backfill, thread gap/delta backfill, etc.). Role is derived
1437
- * from `message.metadata.isBot` bot-authored rows map to `"assistant"` so
1438
- * our own prior replies (and any other bot traffic) are not rehydrated as
1439
- * user turns, which would otherwise corrupt speaker attribution and make
1440
- * the assistant treat its own outputs as new user input on later turns.
1450
+ * (DM cold-start backfill, thread gap/delta backfill, etc.). Backfilled Slack
1451
+ * rows are always persisted as `user` history: `assistant` rows are reserved
1452
+ * for messages produced by the local assistant loop, not third-party channel
1453
+ * replay.
1441
1454
  * Caller is responsible for dedup checks before invoking; this helper
1442
1455
  * performs no idempotency check itself.
1443
1456
  */
@@ -1445,9 +1458,21 @@ async function persistBackfilledSlackMessage(params: {
1445
1458
  conversationId: string;
1446
1459
  channelId: string;
1447
1460
  message: ProviderMessage;
1461
+ account?: string;
1462
+ guardianExternalUserId?: string;
1448
1463
  }): Promise<void> {
1449
1464
  const { message } = params;
1450
- const slackFiles = readSlackFilesFromProviderMetadata(message.metadata);
1465
+ const slackFilesWithUrls = readSlackFilesWithUrlsFromProviderMetadata(
1466
+ message.metadata,
1467
+ );
1468
+ // Persisted shape strips the transient URL fields; only `{ id, name,
1469
+ // mimetype }` is allowed by `slackFileMetadataSchema`.
1470
+ const slackFiles: SlackFileMetadata[] = slackFilesWithUrls.map((f) => ({
1471
+ ...(f.id ? { id: f.id } : {}),
1472
+ name: f.name,
1473
+ ...(f.mimetype ? { mimetype: f.mimetype } : {}),
1474
+ }));
1475
+ const actorExternalUserId = message.sender?.id?.trim();
1451
1476
  const slackMeta: SlackMessageMetadata = {
1452
1477
  source: "slack",
1453
1478
  channelId: params.channelId,
@@ -1455,20 +1480,163 @@ async function persistBackfilledSlackMessage(params: {
1455
1480
  eventKind: "message",
1456
1481
  ...(message.threadId ? { threadTs: message.threadId } : {}),
1457
1482
  ...(message.sender?.name ? { displayName: message.sender.name } : {}),
1483
+ ...(actorExternalUserId ? { actorExternalUserId } : {}),
1458
1484
  ...(slackFiles.length > 0 ? { slackFiles } : {}),
1459
1485
  };
1460
- const role = message.metadata?.isBot === true ? "assistant" : "user";
1461
- await addMessage(params.conversationId, role, message.text ?? "", {
1486
+
1487
+ const isGuardian = isBackfilledSlackGuardianMessage(
1488
+ message,
1489
+ params.guardianExternalUserId,
1490
+ );
1491
+ const role = "user";
1492
+
1493
+ const rawText = message.text ?? "";
1494
+
1495
+ const persisted = await addMessage(params.conversationId, role, rawText, {
1462
1496
  slackMeta: writeSlackMetadata(slackMeta),
1497
+ provenanceTrustClass: isGuardian ? "guardian" : "unknown",
1498
+ provenanceSourceChannel: "slack",
1499
+ ...(params.guardianExternalUserId
1500
+ ? { provenanceGuardianExternalUserId: params.guardianExternalUserId }
1501
+ : {}),
1502
+ ...(actorExternalUserId
1503
+ ? { provenanceRequesterIdentifier: actorExternalUserId }
1504
+ : {}),
1463
1505
  });
1506
+
1507
+ // Hydrate image attachments inline, then rewrite the saved row to include
1508
+ // `type: "image"` content blocks. Slack context assembly reloads from
1509
+ // `messages.content`; the attachment link alone is not part of the model
1510
+ // transcript. Non-image files keep the marker produced by the Slack renderer.
1511
+ const imageFiles = slackFilesWithUrls.filter(
1512
+ (f) =>
1513
+ (f.urlPrivateDownload || f.urlPrivate) &&
1514
+ typeof f.mimetype === "string" &&
1515
+ f.mimetype.startsWith("image/"),
1516
+ );
1517
+ if (imageFiles.length === 0) return;
1518
+
1519
+ const hydratedAttachments = await withSlackBotToken(
1520
+ params.account,
1521
+ async (token) => {
1522
+ const attachments: MessageAttachmentInput[] = [];
1523
+
1524
+ for (let i = 0; i < imageFiles.length; i++) {
1525
+ const file = imageFiles[i];
1526
+ try {
1527
+ const downloaded = await downloadSlackFile(file, token);
1528
+ if (!downloaded) continue;
1529
+ const validation = validateAttachmentUpload(
1530
+ downloaded.filename,
1531
+ downloaded.mimeType,
1532
+ );
1533
+ if (!validation.ok) {
1534
+ log.warn(
1535
+ {
1536
+ filename: downloaded.filename,
1537
+ mimeType: downloaded.mimeType,
1538
+ error: validation.error,
1539
+ channelTs: message.id,
1540
+ },
1541
+ "Skipping backfilled Slack image: validation failed",
1542
+ );
1543
+ continue;
1544
+ }
1545
+ attachInlineAttachmentToMessage(
1546
+ persisted.id,
1547
+ i,
1548
+ downloaded.filename,
1549
+ downloaded.mimeType,
1550
+ downloaded.data,
1551
+ );
1552
+ attachments.push({
1553
+ filename: downloaded.filename,
1554
+ mimeType: downloaded.mimeType,
1555
+ data: downloaded.data,
1556
+ });
1557
+ } catch (err) {
1558
+ if (err instanceof AttachmentUploadError) {
1559
+ log.warn(
1560
+ {
1561
+ filename: file.name,
1562
+ error: err.message,
1563
+ channelTs: message.id,
1564
+ },
1565
+ "Skipping backfilled Slack image: upload error",
1566
+ );
1567
+ continue;
1568
+ }
1569
+ log.warn(
1570
+ { err, fileId: file.id, name: file.name, channelTs: message.id },
1571
+ "Failed to hydrate backfilled Slack image; proceeding without it",
1572
+ );
1573
+ }
1574
+ }
1575
+
1576
+ return attachments;
1577
+ },
1578
+ );
1579
+ if (hydratedAttachments === null) {
1580
+ log.debug(
1581
+ { conversationId: params.conversationId, channelTs: message.id },
1582
+ "No Slack token available for backfill image hydration; skipping",
1583
+ );
1584
+ return;
1585
+ }
1586
+
1587
+ if (hydratedAttachments.length > 0) {
1588
+ updateMessageContent(
1589
+ persisted.id,
1590
+ JSON.stringify(
1591
+ buildBackfilledSlackContentBlocks(rawText, hydratedAttachments),
1592
+ ),
1593
+ );
1594
+ }
1595
+ }
1596
+
1597
+ function buildBackfilledSlackContentBlocks(
1598
+ text: string,
1599
+ attachments: MessageAttachmentInput[],
1600
+ ): ContentBlock[] {
1601
+ const blocks: ContentBlock[] = [];
1602
+ if (text.trim().length > 0) {
1603
+ blocks.push({ type: "text", text });
1604
+ }
1605
+ blocks.push(...attachmentsToContentBlocks(attachments));
1606
+ return blocks;
1607
+ }
1608
+
1609
+ function isBackfilledSlackGuardianMessage(
1610
+ message: ProviderMessage,
1611
+ guardianExternalUserId: string | undefined,
1612
+ ): boolean {
1613
+ const rawSenderId = message.sender?.id?.trim();
1614
+ if (!rawSenderId || !guardianExternalUserId) return false;
1615
+ const canonicalSender =
1616
+ canonicalizeInboundIdentity("slack", rawSenderId) ?? rawSenderId;
1617
+ const canonicalGuardian =
1618
+ canonicalizeInboundIdentity("slack", guardianExternalUserId) ??
1619
+ guardianExternalUserId.trim();
1620
+ return canonicalSender === canonicalGuardian;
1621
+ }
1622
+
1623
+ /**
1624
+ * Transient view of `slackFiles` that preserves the download URLs added by
1625
+ * `mapSlackFiles` on the in-flight `ProviderMessage`. These URLs never reach
1626
+ * persisted storage — see `slackFileMetadataSchema`. The backfill image
1627
+ * hydration path is the only consumer; URLs are absent from persisted rows.
1628
+ */
1629
+ interface SlackFileWithUrls extends SlackFileMetadata {
1630
+ urlPrivateDownload?: string;
1631
+ urlPrivate?: string;
1464
1632
  }
1465
1633
 
1466
- function readSlackFilesFromProviderMetadata(
1634
+ function readSlackFilesWithUrlsFromProviderMetadata(
1467
1635
  metadata: Record<string, unknown> | undefined,
1468
- ): SlackFileMetadata[] {
1636
+ ): SlackFileWithUrls[] {
1469
1637
  const raw = metadata?.slackFiles;
1470
1638
  if (!Array.isArray(raw)) return [];
1471
- const files: SlackFileMetadata[] = [];
1639
+ const files: SlackFileWithUrls[] = [];
1472
1640
  for (const item of raw) {
1473
1641
  if (item === null || typeof item !== "object" || Array.isArray(item)) {
1474
1642
  continue;
@@ -1484,6 +1652,13 @@ function readSlackFilesFromProviderMetadata(
1484
1652
  ...(typeof record.mimetype === "string" && record.mimetype.length > 0
1485
1653
  ? { mimetype: record.mimetype }
1486
1654
  : {}),
1655
+ ...(typeof record.urlPrivateDownload === "string" &&
1656
+ record.urlPrivateDownload.length > 0
1657
+ ? { urlPrivateDownload: record.urlPrivateDownload }
1658
+ : {}),
1659
+ ...(typeof record.urlPrivate === "string" && record.urlPrivate.length > 0
1660
+ ? { urlPrivate: record.urlPrivate }
1661
+ : {}),
1487
1662
  });
1488
1663
  }
1489
1664
  return files;
@@ -1515,6 +1690,7 @@ async function tryBackfillSlackDmIfCold(params: {
1515
1690
  conversationId: string;
1516
1691
  channelId: string;
1517
1692
  account?: string;
1693
+ guardianExternalUserId?: string;
1518
1694
  latestTs?: string;
1519
1695
  }): Promise<void> {
1520
1696
  const existing = _dmBackfillInFlight.get(params.conversationId);
@@ -1533,6 +1709,7 @@ async function runBackfillSlackDmIfCold(params: {
1533
1709
  conversationId: string;
1534
1710
  channelId: string;
1535
1711
  account?: string;
1712
+ guardianExternalUserId?: string;
1536
1713
  latestTs?: string;
1537
1714
  }): Promise<void> {
1538
1715
  try {
@@ -1573,6 +1750,10 @@ async function runBackfillSlackDmIfCold(params: {
1573
1750
  conversationId: params.conversationId,
1574
1751
  channelId: params.channelId,
1575
1752
  message,
1753
+ ...(params.account ? { account: params.account } : {}),
1754
+ ...(params.guardianExternalUserId
1755
+ ? { guardianExternalUserId: params.guardianExternalUserId }
1756
+ : {}),
1576
1757
  });
1577
1758
  seen.add(message.id);
1578
1759
  written++;
@@ -2045,9 +2226,21 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
2045
2226
  * connection.
2046
2227
  */
2047
2228
  account?: string;
2229
+ /**
2230
+ * Canonical Slack user ID for the guardian, when a verified Slack guardian
2231
+ * binding exists. Backfilled messages from this sender are trusted history
2232
+ * and should not be wrapped as external content.
2233
+ */
2234
+ guardianExternalUserId?: string;
2048
2235
  }): Promise<SlackThreadBackfillResult> {
2049
- const { conversationId, channelId, threadTs, excludeChannelTs, account } =
2050
- params;
2236
+ const {
2237
+ conversationId,
2238
+ channelId,
2239
+ threadTs,
2240
+ excludeChannelTs,
2241
+ account,
2242
+ guardianExternalUserId,
2243
+ } = params;
2051
2244
 
2052
2245
  try {
2053
2246
  const upperBoundTs = parseSlackTimestamp(excludeChannelTs)
@@ -2132,6 +2325,8 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
2132
2325
  conversationId,
2133
2326
  channelId,
2134
2327
  message,
2328
+ ...(account ? { account } : {}),
2329
+ ...(guardianExternalUserId ? { guardianExternalUserId } : {}),
2135
2330
  });
2136
2331
  threadState.storedChannelTs.add(message.id);
2137
2332
  persisted++;
@@ -50,6 +50,7 @@ import {
50
50
  isBoundGuardianActor,
51
51
  processChannelMessageInBackground,
52
52
  shouldStartSlackThinkingStatusForText,
53
+ shouldStartSlackThinkingStatusImmediately,
53
54
  } from "./background-dispatch.js";
54
55
 
55
56
  describe("isBoundGuardianActor", () => {
@@ -201,6 +202,116 @@ describe("Slack thinking status timing", () => {
201
202
  ).toBe(true);
202
203
  });
203
204
 
205
+ test("starts Slack thinking status immediately for DMs and direct mentions", () => {
206
+ expect(
207
+ shouldStartSlackThinkingStatusImmediately({
208
+ sourceChannel: "slack",
209
+ chatType: "im",
210
+ }),
211
+ ).toBe(true);
212
+ expect(
213
+ shouldStartSlackThinkingStatusImmediately({
214
+ sourceChannel: "slack",
215
+ slackBotMentioned: true,
216
+ }),
217
+ ).toBe(true);
218
+ expect(
219
+ shouldStartSlackThinkingStatusImmediately({
220
+ sourceChannel: "slack",
221
+ chatType: "channel",
222
+ }),
223
+ ).toBe(false);
224
+ expect(
225
+ shouldStartSlackThinkingStatusImmediately({
226
+ sourceChannel: "telegram",
227
+ chatType: "im",
228
+ slackBotMentioned: true,
229
+ }),
230
+ ).toBe(false);
231
+ });
232
+
233
+ test("sets Slack thinking indicator immediately for a DM", async () => {
234
+ const conversationId = "conv-dm-immediate-status";
235
+ const channelId = "D-DM-IMMEDIATE";
236
+ const messageTs = "1700000000.000010";
237
+
238
+ const processMessage: MessageProcessor = async () => {
239
+ expect(deliveredChannelReplies).toHaveLength(1);
240
+ expect(deliveredChannelReplies[0]!.payload.reaction).toEqual({
241
+ action: "add",
242
+ name: "eyes",
243
+ messageTs,
244
+ });
245
+ return { messageId: "user-msg-dm-immediate" };
246
+ };
247
+
248
+ processChannelMessageInBackground({
249
+ processMessage,
250
+ conversationId,
251
+ eventId: "evt-dm-immediate-status",
252
+ content: "dm message",
253
+ sourceChannel: "slack",
254
+ sourceInterface: "slack",
255
+ externalChatId: channelId,
256
+ trustCtx,
257
+ metadataHints: [],
258
+ chatType: "im",
259
+ replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&messageTs=${messageTs}`,
260
+ });
261
+
262
+ await flush();
263
+
264
+ const reactions = deliveredChannelReplies.map(
265
+ (entry) => entry.payload.reaction,
266
+ );
267
+ expect(reactions).toEqual([
268
+ { action: "add", name: "eyes", messageTs },
269
+ { action: "remove", name: "eyes", messageTs },
270
+ ]);
271
+ });
272
+
273
+ test("sets Slack thinking status immediately for an app mention", async () => {
274
+ const conversationId = "conv-mention-immediate-status";
275
+ const channelId = "C-MENTION-IMMEDIATE";
276
+ const threadTs = "1700000000.000011";
277
+
278
+ const processMessage: MessageProcessor = async () => {
279
+ expect(deliveredChannelReplies).toHaveLength(1);
280
+ expect(deliveredChannelReplies[0]!.payload.assistantThreadStatus).toEqual(
281
+ {
282
+ channel: channelId,
283
+ threadTs,
284
+ status: "is thinking...",
285
+ },
286
+ );
287
+ return { messageId: "user-msg-mention-immediate" };
288
+ };
289
+
290
+ processChannelMessageInBackground({
291
+ processMessage,
292
+ conversationId,
293
+ eventId: "evt-mention-immediate-status",
294
+ content: "@assistant please respond",
295
+ sourceChannel: "slack",
296
+ sourceInterface: "slack",
297
+ externalChatId: channelId,
298
+ trustCtx,
299
+ metadataHints: [],
300
+ slackBotMentioned: true,
301
+ replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&threadTs=${threadTs}`,
302
+ });
303
+
304
+ await flush();
305
+
306
+ const statuses = deliveredChannelReplies.map((entry) => {
307
+ const status = entry.payload.assistantThreadStatus as
308
+ | { status?: string }
309
+ | undefined;
310
+ return status?.status;
311
+ });
312
+ expect(statuses).toEqual(["is thinking...", ""]);
313
+ });
314
+
204
315
  test("does not set Slack thinking status for no_response text deltas", async () => {
205
316
  const conversationId = "conv-no-response-status";
206
317
  const channelId = "C-NO-RESPONSE";