@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
@@ -19,6 +19,7 @@ import {
19
19
  } from "../../../contacts/guardian-delivery-reader.js";
20
20
  import { isConversationBusyError } from "../../../daemon/conversation-messaging.js";
21
21
  import type { TrustContext } from "../../../daemon/trust-context-types.js";
22
+ import type { ProviderMessageMetadata } from "../../../messaging/provider-message-metadata.js";
22
23
  import {
23
24
  channelActivityRefreshMs,
24
25
  setChannelActivity,
@@ -27,6 +28,7 @@ import {
27
28
  import {
28
29
  getSiblingStreamedReplyTs,
29
30
  linkMessage,
31
+ storeInboundChannelMetadata,
30
32
  storeInboundSlackMetadata,
31
33
  storeReplyMessageId,
32
34
  storeStreamedReplyTs,
@@ -34,6 +36,7 @@ import {
34
36
  import {
35
37
  deferRetryUntilIdle,
36
38
  isDeduplicatedDeliveryOwnedBySibling,
39
+ markDeliveryDelivered,
37
40
  markProcessed,
38
41
  recordProcessingFailure,
39
42
  } from "../../../persistence/delivery-status.js";
@@ -45,6 +48,7 @@ import {
45
48
  getApprovalInfoByConversation,
46
49
  getChannelApprovalPrompt,
47
50
  } from "../../channel-approvals.js";
51
+ import { createChannelReplySession } from "../../channel-reply-session.js";
48
52
  import { deliverChannelReply } from "../../gateway-client.js";
49
53
  import type {
50
54
  ApprovalCopyGenerator,
@@ -52,7 +56,6 @@ import type {
52
56
  SlackInboundMessageMetadata,
53
57
  } from "../../http-types.js";
54
58
  import { hasDeliverableAssistantText } from "../../no-response.js";
55
- import { createSlackReplySession } from "../../slack-reply-session.js";
56
59
  import { isContactTrustClass } from "../../trust-class.js";
57
60
  import { resolveRoutingState } from "../../trust-context-resolver.js";
58
61
  import { finalizeEventDelivery } from "../channel-delivery-routes.js";
@@ -110,6 +113,30 @@ export interface BackgroundProcessingParams {
110
113
  * `slackMeta` envelope for the chronological renderer.
111
114
  */
112
115
  slackInbound?: SlackInboundMessageMetadata;
116
+ /**
117
+ * Neutral per-row channel envelope for non-Slack channels, the counterpart
118
+ * of `slackInbound`: threaded through to `persistUserMessage` so the row
119
+ * is tagged with `providerMeta` and can say which external message it is.
120
+ * A reaction-driven wake turn rides the same lane with its reaction
121
+ * envelope, so the turn's own persisted row IS the reaction row.
122
+ */
123
+ channelInbound?: ProviderMessageMetadata;
124
+ /** See `ProcessMessageOptions.slackReactionRowMeta`. TRANSITIONAL. */
125
+ slackReactionRowMeta?: string;
126
+ /** Explicit idempotency key for this turn's persisted row. */
127
+ clientMessageId?: string;
128
+ /** Persist the triggering row without indexing it. */
129
+ skipUserMessageIndexing?: boolean;
130
+ /**
131
+ * Called when the turn could not run because a non-channel turn re-took
132
+ * the processing lock after admission. When present it REPLACES the
133
+ * retry-sweep deferral: that lane rebuilds a turn from its stored payload
134
+ * as a plain message, so a caller whose event stores nothing a turn can be
135
+ * rebuilt from (a reaction wake, whose payload is delivery-only) degrades
136
+ * through this hook instead, e.g. by persisting the passive row the turn
137
+ * would have created.
138
+ */
139
+ onTurnLostToBusy?: () => Promise<void>;
113
140
  }
114
141
 
115
142
  /**
@@ -141,16 +168,26 @@ export function processChannelMessageInBackground(
141
168
  clientTimezone,
142
169
  slackBotMentioned,
143
170
  slackInbound,
171
+ channelInbound,
172
+ slackReactionRowMeta,
173
+ clientMessageId,
174
+ skipUserMessageIndexing,
175
+ onTurnLostToBusy,
144
176
  } = params;
145
177
 
146
- // Capture the Slack ingress metadata onto the stored payload up front — before
147
- // the admission wait or any processing — so if the daemon dies mid-wait or
148
- // mid-turn, the retry sweep replays with the SAME `slackInbound` this turn
149
- // used. That keeps the derived idempotency key identical (the replay dedups
150
- // against a turn this attempt already persisted) and carries full slackMeta.
178
+ // Capture the channel ingress metadata onto the stored payload up front,
179
+ // before the admission wait or any processing, so if the daemon dies
180
+ // mid-wait or mid-turn, the retry sweep replays with the SAME envelope this
181
+ // turn used. For Slack that keeps the derived idempotency key identical
182
+ // (the replay dedups against a turn this attempt already persisted) and
183
+ // carries full slackMeta; for every other channel it carries the same
184
+ // `providerMeta` onto the replayed row.
151
185
  if (slackInbound) {
152
186
  storeInboundSlackMetadata(eventId, slackInbound);
153
187
  }
188
+ if (channelInbound) {
189
+ storeInboundChannelMetadata(eventId, channelInbound);
190
+ }
154
191
 
155
192
  // Defer the whole turn + delivery until the conversation's processing lock is
156
193
  // free, serialized per conversation so same-conversation replies stay ordered.
@@ -210,9 +247,7 @@ export function processChannelMessageInBackground(
210
247
  }
211
248
  : undefined;
212
249
  let replyMessageId: string | undefined;
213
- const slackReplySession = createSlackReplySession({
214
- sourceChannel,
215
- chatType,
250
+ const replySession = createChannelReplySession({
216
251
  replyCallbackUrl,
217
252
  chatId: externalChatId,
218
253
  recipientUserId: slackInbound?.actorExternalUserId,
@@ -230,7 +265,7 @@ export function processChannelMessageInBackground(
230
265
  ) {
231
266
  replyMessageId = msg.messageId;
232
267
  }
233
- slackReplySession?.observeEvent(msg);
268
+ replySession?.observeEvent(msg);
234
269
  channelActivity?.observeEvent(msg);
235
270
  };
236
271
 
@@ -252,6 +287,10 @@ export function processChannelMessageInBackground(
252
287
  ...(displayContent !== undefined ? { displayContent } : {}),
253
288
  ...(cmdIntent ? { commandIntent: cmdIntent } : {}),
254
289
  ...(slackInbound ? { slackInbound } : {}),
290
+ ...(channelInbound ? { channelInbound } : {}),
291
+ ...(slackReactionRowMeta ? { slackReactionRowMeta } : {}),
292
+ ...(clientMessageId ? { clientMessageId } : {}),
293
+ ...(skipUserMessageIndexing ? { skipUserMessageIndexing } : {}),
255
294
  onEvent: observeAgentEvent,
256
295
  sourceChannel,
257
296
  sourceInterface,
@@ -259,22 +298,40 @@ export function processChannelMessageInBackground(
259
298
  userMessageId = result.messageId;
260
299
  deduplicatedIngress = result.deduplicated === true;
261
300
  linkMessage(eventId, userMessageId);
262
- markProcessed(eventId);
301
+ // Record the reply row BEFORE marking the event processed. Stranded
302
+ // delivery recovery treats `processed` as the point an event becomes
303
+ // eligible and reads the reply id from the stored payload, so an
304
+ // event that becomes eligible first would be skipped by that step
305
+ // and never deliver. Crashing between these two leaves the row
306
+ // `pending` instead, which the orphan step already recovers.
263
307
  replyMessageId ??= result.assistantMessageId;
264
308
  if (replyMessageId) {
265
309
  storeReplyMessageId(eventId, replyMessageId);
266
310
  }
311
+ markProcessed(eventId);
267
312
  } catch (err) {
268
313
  // Stop any live Slack stream cleanly. Its `ts` is already durably
269
314
  // recorded via `onStreamOpen`, so the retry sweep can reconcile
270
315
  // against that message rather than posting a duplicate.
271
- await slackReplySession?.finish();
316
+ await replySession?.finish();
272
317
  if (isConversationBusyError(err)) {
318
+ if (onTurnLostToBusy) {
319
+ // The sweep's processing lane cannot rebuild this turn (see the
320
+ // hook's doc), so the caller degrades in its own way instead.
321
+ // Nothing was persisted: the busy error is thrown before the
322
+ // row insert.
323
+ log.info(
324
+ { conversationId, eventId },
325
+ "Channel turn lost the processing lock after admission; degrading via caller fallback",
326
+ );
327
+ await onTurnLostToBusy();
328
+ return;
329
+ }
273
330
  // Admission observed the conversation idle, but a non-channel turn
274
331
  // (web / wake / voice) re-took the processing lock before this turn
275
332
  // could. Re-schedule for the retry sweep without burning a retry
276
333
  // attempt (`deferRetryUntilIdle`) so it reprocesses and delivers from
277
- // the stored payload once the lock frees — a plain processing-failure
334
+ // the stored payload once the lock frees: a plain processing-failure
278
335
  // record would classify the busy message as fatal and dead-letter it
279
336
  // (a silent drop), and even a retryable one could exhaust the budget
280
337
  // under sustained contention.
@@ -289,6 +346,13 @@ export function processChannelMessageInBackground(
289
346
  { err, conversationId },
290
347
  "Background channel message processing failed",
291
348
  );
349
+ if (onTurnLostToBusy) {
350
+ // A caller that opted out of sweep replay has no retry lane at
351
+ // all: its event is already marked processed, so a failure record
352
+ // would only feed the sweep an event it cannot rebuild. The
353
+ // best-effort turn is simply lost.
354
+ return;
355
+ }
292
356
  recordProcessingFailure(eventId, err);
293
357
  return;
294
358
  }
@@ -330,6 +394,10 @@ export function processChannelMessageInBackground(
330
394
  { conversationId, eventId },
331
395
  "Skipping channel reply delivery for deduplicated ingress event; a prior attempt owns delivery",
332
396
  );
397
+ // The sibling owns delivery, so settle this row rather than leaving it
398
+ // `pending`, which stranded-delivery recovery reads as delivery still
399
+ // owed and would act on after a restart.
400
+ markDeliveryDelivered(eventId);
333
401
  } else if (replyCallbackUrl) {
334
402
  try {
335
403
  await finalizeEventDelivery({
@@ -340,7 +408,7 @@ export function processChannelMessageInBackground(
340
408
  assistantId,
341
409
  replyMessageId,
342
410
  userMessageId,
343
- slackReplySession,
411
+ replySession,
344
412
  ...(recoveredStreamMessageTs
345
413
  ? { priorStreamMessageTs: recoveredStreamMessageTs }
346
414
  : {}),
@@ -9,7 +9,6 @@
9
9
  * Extracted from inbound-message-handler.ts to keep the top-level handler
10
10
  * focused on orchestration.
11
11
  */
12
- import type { InboundReactionPayload } from "@vellumai/gateway-client";
13
12
 
14
13
  import {
15
14
  listGuardianRequestsOrEmpty,
@@ -37,22 +36,11 @@ export interface GuardianReplyInterceptParams {
37
36
  trimmedContent: string;
38
37
  hasCallbackData: boolean;
39
38
  callbackData: string | undefined;
40
- reaction?: InboundReactionPayload;
41
- /**
42
- * For emoji-reaction decisions: the channel-native id (Slack `ts`) of the
43
- * message the reaction was attached to. Threaded to the router so it can
44
- * recover the target request from the reacted card's delivery record.
45
- */
46
- reactedMessageTs?: string;
47
39
  rawSenderId: string | undefined;
48
40
  canonicalSenderId: string | null;
49
41
  sourceChannel: ChannelId;
50
42
  conversationExternalId: string;
51
- /**
52
- * Conversation the message arrived in. Reactions resolve their target by the
53
- * reacted card's own address and are routed before any conversation is
54
- * known, so they pass none.
55
- */
43
+ /** Conversation the message arrived in. */
56
44
  conversationId?: string;
57
45
  /** Inbound event id, echoed on the consumed response when the caller has one. */
58
46
  eventId?: string;
@@ -84,8 +72,6 @@ export async function handleGuardianReplyIntercept(
84
72
  trimmedContent,
85
73
  hasCallbackData,
86
74
  callbackData,
87
- reaction,
88
- reactedMessageTs,
89
75
  rawSenderId,
90
76
  canonicalSenderId,
91
77
  sourceChannel,
@@ -137,43 +123,34 @@ export async function handleGuardianReplyIntercept(
137
123
  // guardian can reply before the row is persisted. Cross-chat
138
124
  // contamination is unlikely there because each chat is a distinct
139
125
  // conversation with no thread concept.
140
- // Reactions address one specific request by the reacted card's message id,
141
- // so they bypass the pending-request list scoping that the text/NL paths
142
- // need — the router's reaction branch resolves the target directly.
143
- const isReaction = reaction !== undefined;
144
126
  let pendingScope: GuardianPendingScope | undefined;
145
- if (!isReaction) {
146
- // Hint reads degrade to empty on gateway failure: Slack then blocks the
147
- // identity fallback (safe), other channels keep it (unchanged posture).
148
- const deliveryScopedPendingRequests =
149
- await listPendingRequestsByDestinationOrEmpty({
150
- channel: sourceChannel,
151
- chatId: conversationExternalId,
152
- });
153
- if (deliveryScopedPendingRequests.length > 0) {
154
- const deliveryIds = new Set(
155
- deliveryScopedPendingRequests.map((r) => r.id),
156
- );
157
- // Also include identity-based pending requests so we don't hide them
158
- const identityId = canonicalSenderId ?? rawSenderId!;
159
- const identityPending = await listGuardianRequestsOrEmpty({
160
- status: "pending",
161
- guardianExternalUserId: identityId,
162
- });
163
- for (const r of identityPending) {
164
- deliveryIds.add(r.id);
165
- }
166
- pendingScope = { mode: "scoped", requestIds: [...deliveryIds] };
167
- } else if (sourceChannel === "slack") {
168
- // Block identity-based fallback on Slack to prevent cross-chat
169
- // NL/free-text interception. See comment above for rationale.
170
- pendingScope = { mode: "blocked" };
127
+ // Hint reads degrade to empty on gateway failure: Slack then blocks the
128
+ // identity fallback (safe), other channels keep it (unchanged posture).
129
+ const deliveryScopedPendingRequests =
130
+ await listPendingRequestsByDestinationOrEmpty({
131
+ channel: sourceChannel,
132
+ chatId: conversationExternalId,
133
+ });
134
+ if (deliveryScopedPendingRequests.length > 0) {
135
+ const deliveryIds = new Set(deliveryScopedPendingRequests.map((r) => r.id));
136
+ // Also include identity-based pending requests so we don't hide them
137
+ const identityId = canonicalSenderId ?? rawSenderId!;
138
+ const identityPending = await listGuardianRequestsOrEmpty({
139
+ status: "pending",
140
+ guardianExternalUserId: identityId,
141
+ });
142
+ for (const r of identityPending) {
143
+ deliveryIds.add(r.id);
171
144
  }
145
+ pendingScope = { mode: "scoped", requestIds: [...deliveryIds] };
146
+ } else if (sourceChannel === "slack") {
147
+ // Block identity-based fallback on Slack to prevent cross-chat
148
+ // NL/free-text interception. See comment above for rationale.
149
+ pendingScope = { mode: "blocked" };
172
150
  }
173
151
 
174
152
  const routerResult = await routeGuardianReply({
175
153
  messageText: trimmedContent,
176
- channel: sourceChannel,
177
154
  actor: {
178
155
  actorPrincipalId: guardianPrincipalId ?? undefined,
179
156
  actorExternalUserId: canonicalSenderId ?? rawSenderId!,
@@ -182,8 +159,6 @@ export async function handleGuardianReplyIntercept(
182
159
  },
183
160
  conversationId,
184
161
  callbackData,
185
- ...(reaction ? { reaction } : {}),
186
- reactedMessageTs,
187
162
  pendingScope,
188
163
  approvalConversationGenerator,
189
164
  channelDeliveryContext: {