@vellumai/assistant 0.11.8 → 0.11.9-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/Dockerfile +8 -48
  3. package/docker-entrypoint.sh +5 -1
  4. package/docker-kata-apt-env.sh +3 -0
  5. package/docker-kata-apt-shims.sh +127 -0
  6. package/docker-kata-apt-wrapper.sh +45 -0
  7. package/docker-kata-chroot-exec.sh +35 -0
  8. package/docker-kata-pip.sh +8 -2
  9. package/docs/architecture/memory.md +8 -4
  10. package/docs/guardian-request-flow.md +18 -14
  11. package/docs/trusted-contact-access.md +1 -7
  12. package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
  13. package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  24. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
  25. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
  26. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
  27. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
  28. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
  29. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
  30. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
  31. package/node_modules/@vellumai/service-contracts/package.json +4 -1
  32. package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  33. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  34. package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  35. package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  36. package/openapi.yaml +193 -4
  37. package/package.json +2 -2
  38. package/src/__tests__/access-request-card-view.test.ts +6 -5
  39. package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
  40. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
  42. package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
  43. package/src/__tests__/attachments-store.test.ts +27 -0
  44. package/src/__tests__/attachments.test.ts +43 -0
  45. package/src/__tests__/canned-reply-release.test.ts +48 -5
  46. package/src/__tests__/channel-delivery-store.test.ts +129 -18
  47. package/src/__tests__/channel-reply-delivery.test.ts +553 -93
  48. package/src/__tests__/channel-retry-sweep.test.ts +199 -0
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
  50. package/src/__tests__/conversation-error.test.ts +15 -0
  51. package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
  52. package/src/__tests__/conversation-pairing.test.ts +141 -1
  53. package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
  54. package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
  56. package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
  57. package/src/__tests__/conversation-sync-tags.test.ts +2 -37
  58. package/src/__tests__/credential-health-service.test.ts +98 -10
  59. package/src/__tests__/delete-propagation.test.ts +469 -0
  60. package/src/__tests__/dm-persistence.test.ts +16 -0
  61. package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
  62. package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
  63. package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
  64. package/src/__tests__/guardian-gateway-sim.ts +0 -23
  65. package/src/__tests__/guardian-question-mode.test.ts +108 -0
  66. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
  67. package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
  68. package/src/__tests__/host-proxy-interface.test.ts +10 -0
  69. package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
  70. package/src/__tests__/injector-v3-suppression.test.ts +43 -9
  71. package/src/__tests__/list-messages-page-latest.test.ts +127 -0
  72. package/src/__tests__/list-messages-system-card.test.ts +103 -0
  73. package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
  74. package/src/__tests__/notification-decision-fallback.test.ts +199 -77
  75. package/src/__tests__/notification-decision-strategy.test.ts +198 -213
  76. package/src/__tests__/notification-discord-adapter.test.ts +25 -0
  77. package/src/__tests__/notification-slack-adapter.test.ts +133 -0
  78. package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
  79. package/src/__tests__/openai-provider.test.ts +5 -5
  80. package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
  81. package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
  82. package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
  83. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
  84. package/src/__tests__/process-message-display-content.test.ts +20 -9
  85. package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
  86. package/src/__tests__/provider-meta-persistence.test.ts +67 -0
  87. package/src/__tests__/reaction-persistence.test.ts +22 -198
  88. package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
  89. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
  90. package/src/__tests__/skill-load-tool.test.ts +27 -0
  91. package/src/__tests__/skills.test.ts +34 -1
  92. package/src/__tests__/strip-memory-injections.test.ts +3 -4
  93. package/src/__tests__/terminal-tools.test.ts +9 -0
  94. package/src/__tests__/thread-backfill.test.ts +5 -3
  95. package/src/__tests__/unified-turn-context-location.test.ts +76 -0
  96. package/src/__tests__/voice-session-bridge.test.ts +18 -0
  97. package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
  98. package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
  99. package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
  100. package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
  101. package/src/agent/attachments.ts +13 -2
  102. package/src/agent/loop.ts +3 -19
  103. package/src/api/README.md +9 -5
  104. package/src/api/index.ts +9 -8
  105. package/src/api/package.json +1 -0
  106. package/src/api/responses/conversation-message.ts +8 -0
  107. package/src/api/responses/home.ts +7 -18
  108. package/src/api/surfaces.ts +114 -7
  109. package/src/approvals/AGENTS.md +1 -1
  110. package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
  111. package/src/channels/__tests__/types.test.ts +22 -1
  112. package/src/channels/gateway-guardian-requests.ts +0 -22
  113. package/src/channels/types.ts +8 -6
  114. package/src/cli/__tests__/catalog-search-help.test.ts +12 -0
  115. package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
  116. package/src/cli/commands/channels/index.help.ts +15 -1
  117. package/src/cli/commands/channels/index.ts +6 -2
  118. package/src/cli/commands/db/__tests__/status.test.ts +22 -0
  119. package/src/cli/commands/db/index.help.ts +1 -1
  120. package/src/cli/commands/db/status.ts +172 -1
  121. package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
  122. package/src/cli/commands/platform/connect.ts +14 -5
  123. package/src/cli/commands/plugins.help.ts +6 -5
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
  125. package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
  126. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
  127. package/src/cli/lib/bundled-marketplace.json +14 -0
  128. package/src/cli/lib/install-from-github.ts +9 -5
  129. package/src/cli/lib/install-from-platform.ts +12 -1
  130. package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
  131. package/src/config/assistant-initiated-threads-gate.ts +50 -0
  132. package/src/config/bundled-skills/acp/SKILL.md +10 -3
  133. package/src/config/bundled-skills/schedule/SKILL.md +25 -11
  134. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
  135. package/src/config/call-site-defaults.ts +4 -1
  136. package/src/config/feature-flag-registry.json +21 -4
  137. package/src/config/schemas/memory-v3.ts +4 -3
  138. package/src/context/strip-injections.ts +17 -56
  139. package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
  140. package/src/conversations/message-consolidation.ts +4 -3
  141. package/src/credential-health/credential-health-service.ts +130 -0
  142. package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
  143. package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
  144. package/src/daemon/conversation-error.ts +2 -0
  145. package/src/daemon/conversation-messaging.ts +195 -47
  146. package/src/daemon/conversation-process.ts +20 -5
  147. package/src/daemon/conversation-runtime-assembly.ts +48 -39
  148. package/src/daemon/conversation-store.ts +159 -11
  149. package/src/daemon/conversation-tool-setup.ts +9 -2
  150. package/src/daemon/conversation.ts +290 -19
  151. package/src/daemon/dictation-text-processing.ts +2 -7
  152. package/src/daemon/handlers/config-channels.ts +33 -3
  153. package/src/daemon/handlers/shared.ts +9 -0
  154. package/src/daemon/port-oversized-content.test.ts +117 -0
  155. package/src/daemon/port-oversized-content.ts +110 -0
  156. package/src/daemon/process-message.ts +87 -16
  157. package/src/daemon/reaction-record.test.ts +23 -11
  158. package/src/daemon/reaction-record.ts +6 -9
  159. package/src/documents/document-store.ts +1 -5
  160. package/src/home/conversation-starter-validation.ts +1 -5
  161. package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
  162. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
  163. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
  164. package/src/live-voice/live-voice-photo.ts +544 -61
  165. package/src/live-voice/live-voice-session.ts +6 -2
  166. package/src/live-voice/protocol.ts +20 -1
  167. package/src/messaging/provider-message-metadata.ts +40 -1
  168. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
  169. package/src/messaging/providers/channel-transport.ts +28 -0
  170. package/src/messaging/providers/discord/send.ts +3 -2
  171. package/src/messaging/providers/slack/api.ts +11 -1
  172. package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
  173. package/src/messaging/providers/slack/message-metadata.ts +141 -1
  174. package/src/messaging/providers/slack/send.test.ts +58 -0
  175. package/src/messaging/providers/slack/send.ts +4 -4
  176. package/src/messaging/providers/slack/transport.ts +28 -2
  177. package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
  178. package/src/messaging/providers/telegram-bot/send.ts +93 -0
  179. package/src/messaging/providers/telegram-bot/transport.ts +71 -0
  180. package/src/messaging/reaction-envelopes.test.ts +73 -0
  181. package/src/messaging/reaction-envelopes.ts +33 -5
  182. package/src/messaging/read-provider-metadata.ts +4 -3
  183. package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
  184. package/src/monitoring/recovery/db.ts +35 -0
  185. package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
  186. package/src/monitoring/recovery/run-recovery.ts +6 -2
  187. package/src/monitoring/recovery/stale-processing.ts +3 -20
  188. package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
  189. package/src/notifications/AGENTS.md +2 -2
  190. package/src/notifications/README.md +2 -2
  191. package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
  192. package/src/notifications/__tests__/broadcaster.test.ts +35 -4
  193. package/src/notifications/__tests__/notification-utils.test.ts +31 -0
  194. package/src/notifications/access-request-copy.ts +126 -114
  195. package/src/notifications/adapters/discord.ts +9 -5
  196. package/src/notifications/adapters/shared.ts +17 -4
  197. package/src/notifications/adapters/slack.ts +14 -4
  198. package/src/notifications/adapters/telegram.ts +8 -4
  199. package/src/notifications/approval-card-data.ts +5 -2
  200. package/src/notifications/assistant-reply-producer.ts +7 -7
  201. package/src/notifications/broadcaster.ts +57 -25
  202. package/src/notifications/conversation-pairing.ts +68 -2
  203. package/src/notifications/copy-composer.ts +13 -32
  204. package/src/notifications/decision-engine.ts +56 -237
  205. package/src/notifications/guardian-delivery-recorder.ts +5 -9
  206. package/src/notifications/guardian-feed-projection.ts +0 -15
  207. package/src/notifications/guardian-question-mode.ts +147 -6
  208. package/src/notifications/notification-utils.ts +117 -1
  209. package/src/permissions/confirmation-guardian-request.ts +2 -2
  210. package/src/permissions/prompter.ts +1 -1
  211. package/src/persistence/conversation-crud.ts +93 -14
  212. package/src/persistence/conversation-queries.ts +144 -17
  213. package/src/persistence/conversation-types.ts +39 -5
  214. package/src/persistence/delivery-crud.ts +224 -28
  215. package/src/persistence/delivery-status.ts +21 -2
  216. package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
  217. package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
  218. package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
  219. package/src/persistence/schema/conversations.ts +71 -2
  220. package/src/persistence/schema-contract.test.ts +78 -0
  221. package/src/persistence/schema-contract.ts +96 -0
  222. package/src/persistence/steps.ts +4 -0
  223. package/src/platform/client.ts +55 -0
  224. package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
  225. package/src/plugins/defaults/injector-order.ts +2 -1
  226. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
  227. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
  228. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
  229. package/src/plugins/defaults/memory/injectors.ts +1 -1
  230. package/src/plugins/defaults/memory/memory-marker.ts +17 -7
  231. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
  232. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
  233. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
  234. package/src/plugins/defaults/memory/v3/injector.ts +14 -15
  235. package/src/plugins/defaults/memory/v3/types.ts +15 -4
  236. package/src/plugins/defaults/turn-context/injectors.ts +3 -0
  237. package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
  238. package/src/plugins/mcp-servers.ts +14 -2
  239. package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
  240. package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
  241. package/src/providers/anthropic/client.ts +21 -24
  242. package/src/providers/call-site-routing.ts +5 -4
  243. package/src/providers/connection-resolution.ts +29 -1
  244. package/src/providers/content-block-size.test.ts +82 -0
  245. package/src/providers/content-block-size.ts +99 -0
  246. package/src/providers/file-block-text.test.ts +64 -0
  247. package/src/providers/file-block-text.ts +28 -0
  248. package/src/providers/gemini/client.ts +12 -7
  249. package/src/providers/inference/auth.ts +6 -6
  250. package/src/providers/media-resolve.ts +14 -0
  251. package/src/providers/model-catalog.ts +31 -34
  252. package/src/providers/openai/chat-completions-provider.ts +9 -25
  253. package/src/providers/openai/responses-provider.ts +11 -18
  254. package/src/providers/registry.ts +10 -7
  255. package/src/providers/routing-identity.ts +2 -1
  256. package/src/providers/types.ts +1 -4
  257. package/src/providers/vellum-model-routing.ts +3 -2
  258. package/src/runtime/AGENTS.md +40 -15
  259. package/src/runtime/approval-message-composer.ts +1 -6
  260. package/src/runtime/assistant-event-hub.ts +2 -39
  261. package/src/runtime/channel-approval-types.ts +1 -5
  262. package/src/runtime/channel-reply-delivery.ts +182 -115
  263. package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
  264. package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
  265. package/src/runtime/channel-retry-sweep.ts +102 -1
  266. package/src/runtime/finalize-event-delivery.ts +4 -4
  267. package/src/runtime/guardian-action-message-composer.ts +1 -4
  268. package/src/runtime/guardian-reply-router.ts +4 -75
  269. package/src/runtime/http-router.ts +1 -5
  270. package/src/runtime/question-request-guardian-bridge.ts +2 -3
  271. package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
  272. package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
  273. package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
  274. package/src/runtime/routes/acp-routes.ts +1 -1
  275. package/src/runtime/routes/canned-reply-release.ts +19 -9
  276. package/src/runtime/routes/channel-route-shared.ts +0 -39
  277. package/src/runtime/routes/channel-verification-routes.ts +4 -1
  278. package/src/runtime/routes/conversation-list-routes.ts +44 -3
  279. package/src/runtime/routes/conversation-management-routes.ts +2 -1
  280. package/src/runtime/routes/conversation-routes.ts +138 -59
  281. package/src/runtime/routes/diagnostics-routes.ts +111 -60
  282. package/src/runtime/routes/guardian-action-routes.ts +10 -14
  283. package/src/runtime/routes/guardian-approval-interception.ts +0 -5
  284. package/src/runtime/routes/inbound-message-handler.ts +170 -48
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
  286. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
  287. package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
  288. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
  289. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
  290. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
  291. package/src/runtime/routes/index.ts +2 -0
  292. package/src/runtime/routes/platform-routes.ts +99 -10
  293. package/src/runtime/routes/sight-frame-routes.ts +136 -0
  294. package/src/runtime/sync/resource-sync-events.ts +14 -37
  295. package/src/runtime/sync/sync-publisher.test.ts +5 -4
  296. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  297. package/src/tools/client-os.ts +11 -1
  298. package/src/tools/host-filesystem/edit.ts +2 -2
  299. package/src/tools/host-filesystem/read.ts +2 -2
  300. package/src/tools/host-filesystem/transfer.ts +2 -2
  301. package/src/tools/host-filesystem/write.ts +2 -2
  302. package/src/tools/host-terminal/host-shell.ts +2 -2
  303. package/src/tools/skills/load.ts +1 -1
  304. package/src/tools/terminal/safe-env.ts +4 -0
  305. package/src/tools/tool-input-schemas.ts +2 -0
  306. package/src/tools/tool-manifest.ts +2 -0
  307. package/src/tools/ui-surface/definitions.ts +4 -3
  308. package/src/tools/watch/watch-retro-report.ts +205 -0
  309. package/src/watch/__tests__/watch-retro.test.ts +268 -40
  310. package/src/watch/watch-retro.ts +190 -77
  311. package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
  312. package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
  313. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
  314. package/src/workspace/migrations/registry.ts +4 -0
  315. package/docker-kata-pip-chroot.sh +0 -22
  316. package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
  317. package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
  318. package/src/api/events/conversation-list-invalidated.ts +0 -38
@@ -5,17 +5,14 @@ import type {
5
5
  ToolResultEvent,
6
6
  ToolUseStartEvent,
7
7
  } from "../api/index.js";
8
- import {
9
- extractThreadTsFromCallbackUrl,
10
- isSlackDeliveryCallbackUrl,
11
- } from "../channels/slack-callback-url.js";
12
- import type { ChannelId } from "../channels/types.js";
13
8
  import {
14
9
  incompleteVellumLinkSuffixLength,
15
10
  stripVellumLinks,
16
11
  } from "../daemon/assistant-attachments.js";
17
- import { sendChannelStreamOp } from "../messaging/providers/index.js";
18
- import { SLACK_STREAM_MARKDOWN_LIMIT } from "../messaging/providers/slack/api.js";
12
+ import {
13
+ getTransportForCallback,
14
+ sendChannelStreamOp,
15
+ } from "../messaging/providers/index.js";
19
16
  import { getLogger } from "../util/logger.js";
20
17
  import { needsBoundarySpace } from "../util/text-spacing.js";
21
18
  import {
@@ -27,7 +24,7 @@ import {
27
24
  mergeTaskProgressData,
28
25
  } from "./task-progress.js";
29
26
 
30
- const log = getLogger("slack-reply-session");
27
+ const log = getLogger("channel-reply-session");
31
28
 
32
29
  /**
33
30
  * Minimum gap between coalesced `appendStream` calls. `chat.appendStream` is a
@@ -44,101 +41,101 @@ const STREAM_COALESCE_MS = 400;
44
41
  * - `streamed`: the reply was delivered live into a single streamed message;
45
42
  * finalize skips re-posting text, reconciles `slackMeta.channelTs` to the
46
43
  * stream `ts`, and posts only attachments.
47
- * - `fallback`: no stream was opened (ineligible turn, no deliverable text, or
48
- * a failed `startStream`); finalize posts the full reply normally.
44
+ * - `fallback`: no stream was opened (ineligible turn, neither deliverable
45
+ * text nor a plan, or a failed `startStream`); finalize posts the full
46
+ * reply normally.
49
47
  */
50
- export type SlackStreamReconciliation =
48
+ export type StreamReconciliation =
51
49
  | { mode: "streamed"; messageTs: string; deliveredSegmentCount: number }
52
50
  | { mode: "fallback" };
53
51
 
54
- export type SlackReplySession = {
52
+ export type ChannelReplySession = {
55
53
  observeEvent: (msg: AssistantEvent) => void;
56
54
  /**
57
55
  * Settle in-flight stream operations, finalize the stream, and report how
58
56
  * durable delivery should reconcile. Call once after processing completes.
59
57
  */
60
- finish: () => Promise<SlackStreamReconciliation>;
58
+ finish: () => Promise<StreamReconciliation>;
61
59
  };
62
60
 
63
61
  /**
64
- * Whether a turn is eligible for native Slack reply streaming. Every eligible
65
- * turn resolves to a threaded reply, since `chat.startStream` requires a
66
- * `thread_ts`. Assistant-container DMs carry that thread implicitly; channel
67
- * turns (including app-mention threads) thread under the inbound message.
62
+ * Whether a channel can carry a reply that grows while the turn runs.
68
63
  *
69
- * DMs infer the reader, so they stream without recipient identity. Channels
70
- * must name the reader: `chat.startStream` requires both `recipient_user_id`
71
- * and `recipient_team_id`, so a channel turn missing either falls back to
72
- * durable delivery.
73
- *
74
- * @see https://docs.slack.dev/reference/methods/chat.startStream/
64
+ * Only the channel-level half is answered here, and only by asking whether
65
+ * the transport implements the operation: implementing it is the whole of
66
+ * declaring it. Whether THIS conversation can carry one is the platform's
67
+ * own rule (Slack streams into a thread, Telegram only into a private chat),
68
+ * so it is answered by the transport's own reply to `start` and reaches this
69
+ * module as an ordinary not-ok, which falls back exactly like a failure.
75
70
  */
76
- export function shouldStreamSlackReply(params: {
77
- sourceChannel: ChannelId;
78
- chatType?: string;
79
- replyCallbackUrl?: string;
80
- recipientUserId?: string;
81
- recipientTeamId?: string;
82
- }): boolean {
83
- if (params.sourceChannel !== "slack") {
71
+ export function channelCanStreamReply(replyCallbackUrl?: string): boolean {
72
+ if (!replyCallbackUrl) {
84
73
  return false;
85
74
  }
86
- if (!isSlackDeliveryCallbackUrl(params.replyCallbackUrl)) {
87
- return false;
88
- }
89
- if (extractThreadTsFromCallbackUrl(params.replyCallbackUrl) === null) {
90
- return false;
91
- }
92
- if (params.chatType === "im") {
93
- return true;
94
- }
95
- return Boolean(params.recipientUserId && params.recipientTeamId);
75
+ return getTransportForCallback(replyCallbackUrl)?.streamReply !== undefined;
96
76
  }
97
77
 
98
78
  type StreamState = "idle" | "streaming" | "fallback";
99
79
 
100
80
  /**
101
- * Owns the live-content lifecycle of one Slack DM reply: it consumes the
81
+ * Owns the live-content lifecycle of one growing reply: it consumes the
102
82
  * assistant token stream once, opens a streamed message (in plan display
103
- * mode) on first deliverable text, coalesces deltas into `appendStream`
104
- * calls, advances a native plan block from `task_progress` surfaces, and
105
- * finalizes with `stopStream`.
83
+ * mode) on the first deliverable text or progress plan, whichever comes
84
+ * first, coalesces deltas into `appendStream` calls, advances a native plan
85
+ * block from `task_progress` surfaces, and finalizes with `stopStream`.
106
86
  *
107
87
  * Any stream-call failure degrades gracefully: the session abandons streaming
108
88
  * (state `fallback`) and reports back so durable finalize posts the full reply
109
89
  * via the normal path. Returns `undefined` for turns that are not eligible to
110
90
  * stream.
111
91
  */
112
- export function createSlackReplySession(params: {
113
- sourceChannel: ChannelId;
114
- chatType?: string;
92
+ export function createChannelReplySession(params: {
115
93
  replyCallbackUrl?: string;
116
94
  chatId: string;
117
- /** Slack user ID of the reader; required to stream into a channel. */
95
+ /**
96
+ * The one reader a restricted stream is shown to, when the turn names one.
97
+ * Travels as the operation's `audience`; a channel that cannot restrict a
98
+ * growing reply ignores it, and one that requires a named reader in a room
99
+ * refuses the start without it.
100
+ */
118
101
  recipientUserId?: string;
119
- /** Slack team ID of the reader; required to stream into a channel. */
102
+ /** The reader's org, alongside {@link recipientUserId}. */
120
103
  recipientTeamId?: string;
121
104
  /** Gap between coalesced `appendStream` calls. Defaults to {@link STREAM_COALESCE_MS}. */
122
105
  coalesceMs?: number;
123
106
  /**
124
- * Invoked once with the streamed message `ts` the moment the Slack stream
125
- * opens. Lets the caller durably record the `ts` before delivery finalizes,
126
- * so a crash mid-turn leaves a breadcrumb: a retry or deduplicated
127
- * redelivery reconciles against the already-visible message instead of
128
- * posting a duplicate reply.
107
+ * Invoked once with the streamed message's id the moment a stream that
108
+ * BECOMES the reply opens, so the caller can durably record it before
109
+ * delivery finalizes: a crash mid-turn then leaves a breadcrumb, and a
110
+ * retry reconciles against the already-visible message instead of posting
111
+ * a duplicate reply.
112
+ *
113
+ * Never invoked for a preview stream. A preview leaves no message behind,
114
+ * so there is nothing for a retry to reconcile against, and recording its
115
+ * id would point recovery at a message that never existed: the retry would
116
+ * try to edit it and the reply would be lost rather than posted.
129
117
  */
130
118
  onStreamOpen?: (streamTs: string) => void;
131
- }): SlackReplySession | undefined {
132
- if (!shouldStreamSlackReply(params) || !params.replyCallbackUrl) {
119
+ }): ChannelReplySession | undefined {
120
+ if (
121
+ !params.replyCallbackUrl ||
122
+ !channelCanStreamReply(params.replyCallbackUrl)
123
+ ) {
133
124
  return undefined;
134
125
  }
135
126
  const { chatId, recipientUserId, recipientTeamId } = params;
136
127
  const coalesceMs = params.coalesceMs ?? STREAM_COALESCE_MS;
137
128
  const replyCallbackUrl = params.replyCallbackUrl;
138
- const threadTs = extractThreadTsFromCallbackUrl(replyCallbackUrl);
139
- if (threadTs === null) {
140
- return undefined;
141
- }
129
+ // What the stream leaves behind is the channel's business; the session only
130
+ // needs to know whether the reply is still owed once the stream ends.
131
+ const streamIsTheReply =
132
+ getTransportForCallback(replyCallbackUrl)?.streamPersists === true;
133
+ // How much text one operation may carry. The channel declares it; splitting
134
+ // stays here because this is what tracks how much the channel has actually
135
+ // taken, and that mark may only advance once per confirmed operation.
136
+ const maxTextChars =
137
+ getTransportForCallback(replyCallbackUrl)?.maxStreamTextChars ??
138
+ Number.POSITIVE_INFINITY;
142
139
 
143
140
  let state: StreamState = "idle";
144
141
  let started = false;
@@ -157,11 +154,11 @@ export function createSlackReplySession(params: {
157
154
  let pendingSegmentBoundary = false;
158
155
 
159
156
  let activeProgress: StreamPlan | undefined;
160
- // Fingerprint of the plan state last delivered to Slack, so progress that
157
+ // Fingerprint of the plan state last delivered, so progress that
161
158
  // advances without new body text still flushes as a task-only append.
162
159
  let deliveredProgressKey: string | undefined;
163
- // Set once a chunks-only append is rejected (e.g. a Slack tier that
164
- // requires `markdown_text` on every append): the session stops attempting
160
+ // Set once a plan-only append is rejected (a channel that requires words
161
+ // on every operation): the session stops attempting
165
162
  // them and progress rides the next text append or `stopStream` instead,
166
163
  // so a rejecting workspace pays one failed call per turn, not one per
167
164
  // progress update.
@@ -173,7 +170,7 @@ export function createSlackReplySession(params: {
173
170
  const cleanedText = (): string =>
174
171
  stripVellumLinks(rawText).replace(NO_RESPONSE_INLINE_RE, "");
175
172
 
176
- // Text safe to append to Slack's append-only stream: while more deltas may
173
+ // Text safe to hand to an append-only stream: while more deltas may
177
174
  // arrive, a trailing `[label](vellum://…)` link that is still being assembled
178
175
  // is withheld so its internal path is never emitted before the link closes
179
176
  // (and `stripVellumLinks` can remove it). Once `finished`, no delta can
@@ -197,15 +194,18 @@ export function createSlackReplySession(params: {
197
194
  const enqueueStart = (): void => {
198
195
  enqueue(async () => {
199
196
  const clean = streamableText();
200
- if (clean.trim().length === 0) {
197
+ const plan = activeProgress;
198
+ // A plan alone opens the stream; text alone does too. Neither means
199
+ // there is nothing to show yet, so the start is left for a later flush.
200
+ if (clean.trim().length === 0 && !plan) {
201
201
  return;
202
202
  }
203
- const firstChunk = clean.slice(0, SLACK_STREAM_MARKDOWN_LIMIT);
204
- const plan = activeProgress;
203
+ // Open on what one operation may carry; the rest drains through the
204
+ // appends that follow, each advancing the delivered mark on its own.
205
+ const firstChunk = clean.slice(0, maxTextChars);
205
206
  try {
206
207
  const result = await sendChannelStreamOp(replyCallbackUrl, chatId, {
207
208
  action: "start",
208
- anchorMessageId: threadTs,
209
209
  text: clean,
210
210
  appended: firstChunk,
211
211
  ...(plan ? { plan } : {}),
@@ -224,23 +224,25 @@ export function createSlackReplySession(params: {
224
224
  confirmedLength = firstChunk.length;
225
225
  deliveredProgressKey = progressKey(plan);
226
226
  state = "streaming";
227
- // The stream is already open on Slack's side, so an `onStreamOpen`
227
+ // The stream is already open on the channel's side, so an `onStreamOpen`
228
228
  // failure must not downgrade to fallback and repost the visible
229
229
  // reply. Losing the breadcrumb only forfeits crash-window dedup —
230
230
  // strictly better than a guaranteed duplicate post.
231
231
  try {
232
- params.onStreamOpen?.(result.ts);
232
+ if (streamIsTheReply) {
233
+ params.onStreamOpen?.(result.ts);
234
+ }
233
235
  } catch (err) {
234
236
  log.warn(
235
237
  { err, chatId },
236
- "Slack onStreamOpen callback failed; keeping streamed state",
238
+ "onStreamOpen callback failed; keeping streamed state",
237
239
  );
238
240
  }
239
241
  } else {
240
242
  state = "fallback";
241
243
  }
242
244
  } catch (err) {
243
- log.warn({ err, chatId }, "Slack startStream failed; falling back");
245
+ log.warn({ err, chatId }, "Stream start failed; falling back");
244
246
  state = "fallback";
245
247
  }
246
248
  });
@@ -255,13 +257,15 @@ export function createSlackReplySession(params: {
255
257
  const clean = streamableText();
256
258
  const plan = activeProgress;
257
259
  const key = progressKey(plan);
258
- // `chat.appendStream` caps `markdown_text` per call, so a delta wider
259
- // than the limit drains across successive append calls. Each append
260
- // carries the current task state, advancing the plan alongside text.
260
+ // One operation per chunk the channel will accept, and the delivered
261
+ // mark advances only for a chunk the channel confirmed. Advancing it for
262
+ // a whole delta the channel took in pieces would let a failure part-way
263
+ // through re-send the part that already landed, and the reader would see
264
+ // it twice. The plan rides along, so it advances with the text.
261
265
  while (confirmedLength < clean.length) {
262
266
  const chunk = clean.slice(
263
267
  confirmedLength,
264
- confirmedLength + SLACK_STREAM_MARKDOWN_LIMIT,
268
+ confirmedLength + maxTextChars,
265
269
  );
266
270
  try {
267
271
  await sendChannelStreamOp(replyCallbackUrl, chatId, {
@@ -274,10 +278,7 @@ export function createSlackReplySession(params: {
274
278
  confirmedLength += chunk.length;
275
279
  deliveredProgressKey = key ?? deliveredProgressKey;
276
280
  } catch (err) {
277
- log.warn(
278
- { err, chatId },
279
- "Slack appendStream failed; deferring delta",
280
- );
281
+ log.warn({ err, chatId }, "Stream append failed; deferring delta");
281
282
  return;
282
283
  }
283
284
  }
@@ -300,13 +301,28 @@ export function createSlackReplySession(params: {
300
301
  taskOnlyAppendsDisabled = true;
301
302
  log.warn(
302
303
  { err, chatId },
303
- "Slack task-only appendStream failed; deferring progress to text appends",
304
+ "Plan-only append failed; deferring progress to text appends",
304
305
  );
305
306
  }
306
307
  }
307
308
  });
308
309
  };
309
310
 
311
+ /**
312
+ * Whether there is anything worth opening the stream for.
313
+ *
314
+ * A plan counts on its own. `chat.startStream` takes `markdown_text` as
315
+ * optional and fixes `task_display_mode` at start, so a plan-only start is
316
+ * both legal and the only way task cards can tick while the turn works: the
317
+ * model is told to show its progress card early on a multi-step turn, which
318
+ * is precisely the moment it has produced no prose. Requiring text first
319
+ * would hold every such plan back until the final answer, by which point
320
+ * the steps it was meant to narrate are already done.
321
+ */
322
+ const hasOpenableContent = (): boolean =>
323
+ hasDeliverableAssistantText(streamableText()) ||
324
+ activeProgress !== undefined;
325
+
310
326
  const flush = (): void => {
311
327
  if (coalesceTimer) {
312
328
  clearTimeout(coalesceTimer);
@@ -316,7 +332,7 @@ export function createSlackReplySession(params: {
316
332
  return;
317
333
  }
318
334
  if (!started) {
319
- if (!hasDeliverableAssistantText(streamableText())) {
335
+ if (!hasOpenableContent()) {
320
336
  return;
321
337
  }
322
338
  started = true;
@@ -453,7 +469,10 @@ export function createSlackReplySession(params: {
453
469
 
454
470
  // A reply that completed before the first coalesced flush still streams
455
471
  // as a single start→stop so the transcript holds one streamed message.
456
- if (!started && hasDeliverableAssistantText(rawText)) {
472
+ // A turn whose only output was a plan opens here too, so work the model
473
+ // narrated only as steps still leaves the user something to read.
474
+ // `finished` is already set, so the shared check reads the full text.
475
+ if (!started && hasOpenableContent()) {
457
476
  started = true;
458
477
  enqueueStart();
459
478
  }
@@ -481,7 +500,7 @@ export function createSlackReplySession(params: {
481
500
  } catch (err) {
482
501
  log.warn(
483
502
  { err, chatId },
484
- "Slack stopStream failed; falling back to durable delivery",
503
+ "Stream stop failed; falling back to durable delivery",
485
504
  );
486
505
  state = "fallback";
487
506
  }
@@ -489,7 +508,9 @@ export function createSlackReplySession(params: {
489
508
 
490
509
  await opChain;
491
510
 
492
- if (state === "streaming" && streamTs) {
511
+ // A preview stream reports `fallback`: the draft evaporates, so the
512
+ // reply is still owed and durable delivery sends it as it always would.
513
+ if (state === "streaming" && streamTs && streamIsTheReply) {
493
514
  return { mode: "streamed", messageTs: streamTs, deliveredSegmentCount };
494
515
  }
495
516
  return { mode: "fallback" };
@@ -15,6 +15,10 @@ import { findConversation } from "../daemon/conversation-registry.js";
15
15
  import { getDiskPressureStatus } from "../daemon/disk-pressure-guard.js";
16
16
  import { classifyDiskPressureTurnPolicy } from "../daemon/disk-pressure-policy.js";
17
17
  import type { TrustContext } from "../daemon/trust-context-types.js";
18
+ import {
19
+ type ProviderMessageMetadata,
20
+ providerMessageMetadataSchema,
21
+ } from "../messaging/provider-message-metadata.js";
18
22
  import { updateDeliveredSegmentCount } from "../persistence/delivery-channels.js";
19
23
  import {
20
24
  clearPayload,
@@ -107,6 +111,13 @@ function parseTrustRuntimeContext(value: unknown): TrustContext | undefined {
107
111
  : undefined,
108
112
  requesterChatId:
109
113
  typeof raw.requesterChatId === "string" ? raw.requesterChatId : undefined,
114
+ // The triggering message's own id and thread, stamped at ingress. A
115
+ // replayed turn keeps them so what reads them (the turn's chat and thread
116
+ // lines, approval-card source links) sees the same turn the live path ran.
117
+ sourceMessageId:
118
+ typeof raw.sourceMessageId === "string" ? raw.sourceMessageId : undefined,
119
+ sourceThreadId:
120
+ typeof raw.sourceThreadId === "string" ? raw.sourceThreadId : undefined,
110
121
  requesterContactId:
111
122
  typeof raw.requesterContactId === "string"
112
123
  ? raw.requesterContactId
@@ -200,6 +211,68 @@ function buildReplaySlackInbound(params: {
200
211
  };
201
212
  }
202
213
 
214
+ /**
215
+ * Read the neutral `channelInbound` envelope the live path captured onto the
216
+ * payload (via `storeInboundChannelMetadata`), the non-Slack counterpart of
217
+ * {@link parseStoredSlackInbound}: the EXACT envelope the live turn used, so
218
+ * the replayed row carries an identical `providerMeta`.
219
+ */
220
+ function parseStoredChannelInbound(
221
+ value: unknown,
222
+ ): ProviderMessageMetadata | undefined {
223
+ const parsed = providerMessageMetadataSchema.safeParse(value);
224
+ return parsed.success ? parsed.data : undefined;
225
+ }
226
+
227
+ /**
228
+ * Fallback when the payload carries no captured `channelInbound`, the
229
+ * non-Slack counterpart of {@link buildReplaySlackInbound}: reconstruct the
230
+ * envelope from what the payload has always carried, so a crash between
231
+ * `storePayload` and `storeInboundChannelMetadata` recovers the same row a
232
+ * live turn would have persisted. `messageId` mirrors the live
233
+ * `sourceMessageId ?? externalMessageId` derivation; `displayName` mirrors
234
+ * the live `actorDisplayName ?? actorUsername` (stored as the payload's
235
+ * `senderName` / `senderUsername`); `actorExternalId` comes from the same
236
+ * trust context the live envelope read.
237
+ */
238
+ function buildReplayChannelInbound(params: {
239
+ sourceChannel: ChannelId;
240
+ externalChatId: string | undefined;
241
+ sourceMetadata: import("@vellumai/gateway-client").SourceMetadata | undefined;
242
+ externalMessageId: string | undefined;
243
+ senderDisplayName: string | undefined;
244
+ actorExternalId: string | undefined;
245
+ }): ProviderMessageMetadata | undefined {
246
+ if (params.sourceChannel === "slack" || !params.externalChatId) {
247
+ return undefined;
248
+ }
249
+ const messageId =
250
+ (typeof params.sourceMetadata?.messageId === "string"
251
+ ? params.sourceMetadata.messageId
252
+ : undefined) ?? params.externalMessageId;
253
+ if (!messageId) {
254
+ return undefined;
255
+ }
256
+ const threadId =
257
+ typeof params.sourceMetadata?.threadId === "string" &&
258
+ params.sourceMetadata.threadId.trim().length > 0
259
+ ? params.sourceMetadata.threadId.trim()
260
+ : undefined;
261
+ return {
262
+ source: params.sourceChannel,
263
+ conversationExternalId: params.externalChatId,
264
+ messageId,
265
+ eventKind: "message",
266
+ ...(threadId ? { threadId } : {}),
267
+ ...(params.senderDisplayName
268
+ ? { displayName: params.senderDisplayName }
269
+ : {}),
270
+ ...(params.actorExternalId
271
+ ? { actorExternalId: params.actorExternalId }
272
+ : {}),
273
+ };
274
+ }
275
+
203
276
  /**
204
277
  * Periodically retry failed channel inbound events that have passed
205
278
  * their exponential backoff delay.
@@ -428,6 +501,26 @@ export async function sweepFailedEvents(
428
501
  sourceMetadata,
429
502
  externalMessageId,
430
503
  });
504
+ // Empty strings fall through to the username, mirroring the live
505
+ // `actorDisplayName ?? actorUsername` derivation (`"" ?? x` would not).
506
+ const storedSenderName =
507
+ typeof payload.senderName === "string" && payload.senderName
508
+ ? payload.senderName
509
+ : undefined;
510
+ const storedSenderUsername =
511
+ typeof payload.senderUsername === "string" && payload.senderUsername
512
+ ? payload.senderUsername
513
+ : undefined;
514
+ const replayChannelInbound =
515
+ parseStoredChannelInbound(payload.channelInbound) ??
516
+ buildReplayChannelInbound({
517
+ sourceChannel,
518
+ externalChatId,
519
+ sourceMetadata,
520
+ externalMessageId,
521
+ senderDisplayName: storedSenderName ?? storedSenderUsername,
522
+ actorExternalId: trustContext.requesterExternalUserId,
523
+ });
431
524
  // The captured `slackInbound` carries the sender's Slack `app_context`, so
432
525
  // the replayed turn is prepared with the same context block the live turn
433
526
  // rendered. Payloads predating the capture simply have none.
@@ -462,6 +555,10 @@ export async function sweepFailedEvents(
462
555
  ...(prepared.displayContent !== undefined
463
556
  ? { displayContent: prepared.displayContent }
464
557
  : {}),
558
+ // Not idempotency-bearing (the derived key reads only `slackInbound`),
559
+ // so it is shared with the incomplete-turn re-run below: the completing
560
+ // row should carry the same `providerMeta` a live turn would.
561
+ ...(replayChannelInbound ? { channelInbound: replayChannelInbound } : {}),
465
562
  };
466
563
 
467
564
  let userMessageId: string | undefined;
@@ -544,6 +641,10 @@ export async function sweepFailedEvents(
544
641
  { eventId: event.id, conversationId: event.conversationId },
545
642
  "Skipping retry delivery: a sibling event owns delivery for the deduplicated turn",
546
643
  );
644
+ // The sibling owns delivery, so settle this row rather than leaving it
645
+ // `pending`, which stranded-delivery recovery reads as delivery still
646
+ // owed and would act on after a restart.
647
+ markDeliveryDelivered(event.id);
547
648
  } else if (replyCallbackUrl && externalChatId) {
548
649
  try {
549
650
  await finalizeEventDelivery({
@@ -554,7 +655,7 @@ export async function sweepFailedEvents(
554
655
  assistantId,
555
656
  replyMessageId,
556
657
  userMessageId,
557
- slackReplySession: undefined,
658
+ replySession: undefined,
558
659
  priorStreamMessageTs,
559
660
  });
560
661
  } catch (err) {
@@ -4,7 +4,7 @@ import {
4
4
  recordDeliveryFailure,
5
5
  } from "../persistence/delivery-status.js";
6
6
  import { deliverReplyViaCallback } from "./channel-reply-delivery.js";
7
- import type { SlackReplySession } from "./slack-reply-session.js";
7
+ import type { ChannelReplySession } from "./channel-reply-session.js";
8
8
 
9
9
  /**
10
10
  * Owns the complete delivery-after-processing sequence for a channel
@@ -26,7 +26,7 @@ export async function finalizeEventDelivery(params: {
26
26
  assistantId: string | undefined;
27
27
  replyMessageId: string | undefined;
28
28
  userMessageId: string | undefined;
29
- slackReplySession: SlackReplySession | undefined;
29
+ replySession: ChannelReplySession | undefined;
30
30
  /**
31
31
  * `ts` of a Slack message streamed on a previous, failed attempt. A retry
32
32
  * has no live stream of its own, so it edits this message in place rather
@@ -42,11 +42,11 @@ export async function finalizeEventDelivery(params: {
42
42
  assistantId,
43
43
  replyMessageId,
44
44
  userMessageId,
45
- slackReplySession,
45
+ replySession,
46
46
  priorStreamMessageTs,
47
47
  } = params;
48
48
 
49
- const reconciliation = await slackReplySession?.finish();
49
+ const reconciliation = await replySession?.finish();
50
50
 
51
51
  // A streamed reply already delivered its text live into a single message;
52
52
  // durable delivery skips that text, reconciles `slackMeta.channelTs` to the
@@ -54,10 +54,7 @@ export function includesRequiredKeywords(
54
54
  return true;
55
55
  }
56
56
  return requiredKeywords.every((keyword) => {
57
- const re = new RegExp(
58
- `\\b${keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
59
- "i",
60
- );
57
+ const re = new RegExp(`\\b${RegExp.escape(keyword)}\\b`, "i");
61
58
  return re.test(text);
62
59
  });
63
60
  }