@vellumai/assistant 0.11.1 → 0.11.2-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/Dockerfile +1 -3
  2. package/README.md +1 -1
  3. package/eslint.config.mjs +28 -8
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  12. package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  13. package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
  14. package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
  15. package/openapi.yaml +292 -5
  16. package/package.json +1 -1
  17. package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
  18. package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
  19. package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
  20. package/src/__tests__/compaction-events.test.ts +139 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +35 -3
  22. package/src/__tests__/config-schema.test.ts +1 -0
  23. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
  24. package/src/__tests__/conversation-agent-loop.test.ts +180 -7
  25. package/src/__tests__/conversation-error.test.ts +211 -4
  26. package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
  27. package/src/__tests__/conversation-queue.test.ts +126 -0
  28. package/src/__tests__/conversation-retry-route.test.ts +46 -7
  29. package/src/__tests__/conversation-slash.test.ts +4 -2
  30. package/src/__tests__/conversation-summarize-route.test.ts +7 -1
  31. package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
  32. package/src/__tests__/document-create-dedupe.test.ts +2 -1
  33. package/src/__tests__/document-find-replace.test.ts +2 -1
  34. package/src/__tests__/document-tool-security.test.ts +2 -1
  35. package/src/__tests__/document-update-default-surface.test.ts +2 -1
  36. package/src/__tests__/document-workspace-file.test.ts +467 -0
  37. package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
  38. package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
  39. package/src/__tests__/http-user-message-parity.test.ts +66 -0
  40. package/src/__tests__/list-messages-attachments.test.ts +87 -0
  41. package/src/__tests__/list-messages-provider-error.test.ts +143 -0
  42. package/src/__tests__/list-messages-system-card.test.ts +100 -0
  43. package/src/__tests__/llm-resolver.test.ts +19 -9
  44. package/src/__tests__/managed-profile-guard.test.ts +23 -0
  45. package/src/__tests__/notification-platform-adapter.test.ts +130 -2
  46. package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
  47. package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
  48. package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
  49. package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
  50. package/src/__tests__/provider-error-scenarios.test.ts +140 -0
  51. package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
  52. package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
  53. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
  54. package/src/__tests__/skills.test.ts +27 -0
  55. package/src/__tests__/slack-channels-routes.test.ts +0 -2
  56. package/src/__tests__/slack-share-routes.test.ts +0 -3
  57. package/src/__tests__/slack-users-routes.test.ts +0 -2
  58. package/src/__tests__/subagent-tools.test.ts +11 -0
  59. package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
  60. package/src/__tests__/tool-result-spool.test.ts +5 -4
  61. package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
  62. package/src/__tests__/turn-events-store.test.ts +26 -0
  63. package/src/__tests__/ui-visual-surface.test.ts +695 -0
  64. package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
  65. package/src/__tests__/visible-app-context.test.ts +189 -0
  66. package/src/__tests__/workspace-git-service.test.ts +173 -33
  67. package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
  68. package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
  69. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
  70. package/src/agent/loop.ts +36 -7
  71. package/src/api/events/context-window-usage.ts +31 -0
  72. package/src/api/events/notification-intent.ts +8 -0
  73. package/src/api/events/ui-surface-pending.ts +35 -0
  74. package/src/api/index.ts +14 -0
  75. package/src/api/responses/conversation-message.ts +25 -4
  76. package/src/api/surfaces.ts +90 -1
  77. package/src/approvals/guardian-card-withdrawal.ts +66 -31
  78. package/src/approvals/guardian-decision-primitive.ts +4 -0
  79. package/src/calls/__tests__/call-setup-router.test.ts +156 -26
  80. package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
  81. package/src/calls/call-setup-router.ts +80 -36
  82. package/src/calls/voice-session-bridge.ts +173 -26
  83. package/src/cli/commands/inference.help.ts +3 -3
  84. package/src/cli/commands/notifications.help.ts +3 -2
  85. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
  86. package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
  87. package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
  88. package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
  89. package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
  90. package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
  91. package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
  92. package/src/cli/commands/platform/index.help.ts +92 -0
  93. package/src/cli/commands/platform/index.ts +7 -0
  94. package/src/cli/commands/platform/invoices.ts +132 -0
  95. package/src/cli/commands/usage.help.ts +1 -1
  96. package/src/cli/lib/list-installed-plugins.ts +2 -1
  97. package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
  98. package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
  99. package/src/config/assistant-feature-flags.ts +7 -2
  100. package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
  101. package/src/config/bundled-skills/subagent/SKILL.md +3 -1
  102. package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
  103. package/src/config/bundled-skills/visualize/SKILL.md +163 -0
  104. package/src/config/call-site-defaults.ts +3 -2
  105. package/src/config/default-profile-catalog.ts +75 -70
  106. package/src/config/default-profile-names.ts +13 -28
  107. package/src/config/env-registry.ts +1 -0
  108. package/src/config/feature-flag-registry.json +16 -0
  109. package/src/config/llm-resolver.ts +21 -5
  110. package/src/config/loader.ts +18 -11
  111. package/src/config/schemas/llm.ts +1 -9
  112. package/src/config/schemas/memory-retrospective.ts +9 -0
  113. package/src/config/schemas/monitoring.ts +28 -2
  114. package/src/config/schemas/workspace-git.ts +15 -0
  115. package/src/config/seed-inference-profiles.ts +19 -5
  116. package/src/context/post-turn-tool-result-truncation.ts +2 -2
  117. package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
  118. package/src/conversations/message-consolidation.ts +17 -15
  119. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
  120. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
  121. package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
  122. package/src/daemon/conversation-agent-loop.ts +107 -22
  123. package/src/daemon/conversation-error.ts +169 -48
  124. package/src/daemon/conversation-messaging.ts +87 -4
  125. package/src/daemon/conversation-process.ts +57 -45
  126. package/src/daemon/conversation-runtime-assembly.ts +57 -0
  127. package/src/daemon/conversation-store.ts +36 -1
  128. package/src/daemon/conversation-surfaces.ts +68 -6
  129. package/src/daemon/conversation-turn-finalize.ts +76 -18
  130. package/src/daemon/conversation.ts +122 -27
  131. package/src/daemon/lifecycle.ts +9 -0
  132. package/src/daemon/message-types/conversations.ts +8 -0
  133. package/src/daemon/message-types/surfaces.ts +3 -0
  134. package/src/documents/document-store.ts +247 -10
  135. package/src/home/__tests__/feed-types.test.ts +43 -8
  136. package/src/home/__tests__/feed-writer.test.ts +59 -0
  137. package/src/home/feed-types.ts +34 -7
  138. package/src/home/feed-writer.ts +15 -6
  139. package/src/live-voice/__tests__/activity-label.test.ts +95 -0
  140. package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
  141. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
  142. package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
  143. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
  144. package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
  145. package/src/live-voice/activity-label.ts +169 -0
  146. package/src/live-voice/live-activity-reporter.ts +35 -6
  147. package/src/live-voice/live-voice-session.ts +282 -41
  148. package/src/live-voice/protocol.ts +35 -0
  149. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
  150. package/src/messaging/providers/slack/adapter.ts +86 -19
  151. package/src/messaging/providers/slack/api.test.ts +85 -1
  152. package/src/messaging/providers/slack/api.ts +121 -288
  153. package/src/messaging/providers/slack/client.ts +20 -251
  154. package/src/messaging/providers/slack/send.test.ts +4 -9
  155. package/src/messaging/providers/slack/send.ts +1 -1
  156. package/src/messaging/providers/slack/types.ts +5 -0
  157. package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
  158. package/src/messaging/providers/slack/web-api-transport.ts +367 -0
  159. package/src/messaging/providers/slack/withdraw.ts +6 -14
  160. package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
  161. package/src/messaging/providers/telegram-bot/send.ts +23 -2
  162. package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
  163. package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
  164. package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
  165. package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
  166. package/src/monitoring/file-descriptors.ts +262 -0
  167. package/src/monitoring/process-memory.ts +4 -11
  168. package/src/monitoring/resource-sampler.ts +5 -0
  169. package/src/monitoring/worker.ts +12 -0
  170. package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
  171. package/src/notifications/__tests__/broadcaster.test.ts +347 -7
  172. package/src/notifications/__tests__/copy-composer.test.ts +53 -3
  173. package/src/notifications/__tests__/decision-engine.test.ts +349 -29
  174. package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
  175. package/src/notifications/__tests__/edit-notification.test.ts +330 -0
  176. package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
  177. package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
  178. package/src/notifications/adapters/macos.ts +3 -0
  179. package/src/notifications/adapters/platform.ts +90 -14
  180. package/src/notifications/adapters/telegram.ts +6 -4
  181. package/src/notifications/assistant-reply-producer.ts +219 -0
  182. package/src/notifications/broadcaster.ts +523 -317
  183. package/src/notifications/copy-composer.ts +27 -6
  184. package/src/notifications/decision-engine.ts +154 -77
  185. package/src/notifications/deterministic-checks.ts +6 -7
  186. package/src/notifications/edit-notification.ts +8 -4
  187. package/src/notifications/emit-signal.ts +49 -16
  188. package/src/notifications/guardian-delivery-recorder.ts +8 -2
  189. package/src/notifications/home-feed-side-effect.ts +43 -6
  190. package/src/notifications/notification-utils.ts +39 -4
  191. package/src/notifications/signal.ts +10 -0
  192. package/src/notifications/types.ts +17 -0
  193. package/src/persistence/bookmark-crud.ts +18 -9
  194. package/src/persistence/conversation-attention-store.ts +26 -0
  195. package/src/persistence/conversation-crud.ts +79 -32
  196. package/src/persistence/conversation-queries.ts +30 -15
  197. package/src/persistence/conversation-title-service.ts +5 -170
  198. package/src/persistence/conversation-types.ts +192 -1
  199. package/src/persistence/db-init.ts +14 -1
  200. package/src/persistence/db-maintenance.ts +5 -4
  201. package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
  202. package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
  203. package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
  204. package/src/persistence/embeddings/embedding-local.ts +2 -1
  205. package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
  206. package/src/persistence/embeddings/plugin-index.ts +84 -12
  207. package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
  208. package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
  209. package/src/persistence/planner-statistics.ts +14 -0
  210. package/src/persistence/schema/documents.ts +26 -11
  211. package/src/persistence/steps.ts +2 -0
  212. package/src/platform/client.test.ts +172 -2
  213. package/src/platform/client.ts +178 -55
  214. package/src/plugin-api/conversation-turn.ts +13 -0
  215. package/src/plugin-api/model-profiles.test.ts +6 -2
  216. package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
  217. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
  218. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
  219. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
  220. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
  221. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
  224. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
  225. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
  226. package/src/plugins/defaults/memory/host-utils.ts +5 -0
  227. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
  228. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
  229. package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
  230. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
  231. package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
  232. package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
  233. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
  234. package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
  235. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
  236. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
  237. package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
  238. package/src/plugins/defaults/memory/v2/injection.ts +48 -3
  239. package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
  240. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
  241. package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
  242. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  243. package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
  244. package/src/plugins/types.ts +17 -0
  245. package/src/prompts/templates/system-sections.ts +2 -2
  246. package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
  247. package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
  248. package/src/providers/call-site-routing.ts +77 -14
  249. package/src/providers/connection-resolution.ts +42 -2
  250. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
  251. package/src/providers/inference/adapter-factory.ts +70 -12
  252. package/src/providers/model-catalog.ts +0 -11
  253. package/src/providers/model-intents.ts +11 -0
  254. package/src/providers/registry.ts +8 -0
  255. package/src/providers/retry.ts +85 -17
  256. package/src/providers/types.ts +8 -1
  257. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  258. package/src/runtime/agent-wake.ts +132 -9
  259. package/src/runtime/channel-approval-types.ts +25 -0
  260. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
  261. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
  262. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
  263. package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
  264. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
  265. package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
  266. package/src/runtime/routes/consolidation-routes.ts +1 -1
  267. package/src/runtime/routes/conversation-management-routes.ts +18 -6
  268. package/src/runtime/routes/conversation-query-routes.ts +14 -23
  269. package/src/runtime/routes/conversation-routes.ts +113 -8
  270. package/src/runtime/routes/credential-routes.ts +1 -5
  271. package/src/runtime/routes/documents-routes.ts +207 -12
  272. package/src/runtime/routes/identity-routes.ts +3 -93
  273. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
  274. package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
  275. package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
  276. package/src/runtime/routes/notification-routes.ts +4 -1
  277. package/src/runtime/routes/platform-routes.ts +238 -3
  278. package/src/runtime/routes/playground/guard.ts +1 -2
  279. package/src/runtime/routes/slack-channel-routes.ts +2 -4
  280. package/src/runtime/routes/workspace-commit-routes.ts +49 -2
  281. package/src/runtime/routes/workspace-routes.ts +11 -12
  282. package/src/runtime/routes/workspace-utils.ts +49 -2
  283. package/src/runtime/services/conversation-serializer.ts +2 -4
  284. package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
  285. package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
  286. package/src/subagent/__tests__/consult-context.test.ts +84 -0
  287. package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
  288. package/src/subagent/consult-context.ts +410 -0
  289. package/src/subagent/consult-prompt.ts +38 -5
  290. package/src/subagent/manager.ts +9 -4
  291. package/src/subagent/types.ts +8 -0
  292. package/src/telemetry/telemetry-event-sources.ts +7 -0
  293. package/src/telemetry/telemetry-wire-source.json +1 -1
  294. package/src/telemetry/telemetry-wire.generated.ts +1 -0
  295. package/src/telemetry/turn-events-store.ts +30 -1
  296. package/src/telemetry/types.ts +24 -0
  297. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  298. package/src/tools/browser/pinned-tabs.ts +3 -1
  299. package/src/tools/subagent/spawn.ts +23 -1
  300. package/src/tools/terminal/safe-env.ts +1 -0
  301. package/src/tools/ui-surface/definitions.ts +39 -1
  302. package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
  303. package/src/tools/ui-surface/visual-validation.ts +787 -0
  304. package/src/tools/workflows/run-workflow.test.ts +1 -0
  305. package/src/util/__tests__/short-title.test.ts +229 -0
  306. package/src/util/__tests__/worker-compute.test.ts +65 -0
  307. package/src/util/cgroup-cpu.ts +93 -0
  308. package/src/util/errors.ts +27 -0
  309. package/src/util/process-tree.ts +19 -0
  310. package/src/util/short-title.ts +189 -0
  311. package/src/util/worker-compute.ts +83 -0
  312. package/src/workspace/byok-default-profile-ensure.ts +25 -15
  313. package/src/workspace/git-service.ts +79 -30
  314. package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
  315. package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
  316. package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
  317. package/src/workspace/migrations/registry.ts +6 -0
  318. package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
@@ -0,0 +1,330 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ import type { FeedItem } from "../../home/feed-types.js";
7
+ import type { NotificationDeliveryRow } from "../deliveries-store.js";
8
+
9
+ // ── Module mocks ───────────────────────────────────────────────────────
10
+ //
11
+ // The home-feed writer is deliberately NOT mocked: these tests exercise
12
+ // the real on-disk patch semantics through a temp workspace so the
13
+ // "an empty title never clears the field" guarantee is covered end to
14
+ // end. Only the sqlite-backed stores, the broadcaster, and the SSE hub
15
+ // are intercepted.
16
+
17
+ mock.module("../../runtime/assistant-event-hub.js", () => ({
18
+ assistantEventHub: {
19
+ publish: async () => {},
20
+ subscribe: () => () => {},
21
+ },
22
+ broadcastMessage: () => {},
23
+ }));
24
+
25
+ let decisionRow: { id: string } | null = null;
26
+ let deliveryRows: NotificationDeliveryRow[] = [];
27
+ const renderedCopyPatches: Array<{
28
+ id: string;
29
+ patch: { renderedTitle?: string; renderedBody?: string };
30
+ }> = [];
31
+
32
+ mock.module("../decisions-store.js", () => ({
33
+ findLatestDecisionByEventId: () => decisionRow,
34
+ }));
35
+
36
+ mock.module("../deliveries-store.js", () => ({
37
+ findDeliveriesByDecisionId: () => deliveryRows,
38
+ updateDeliveryRenderedCopy: (
39
+ id: string,
40
+ patch: { renderedTitle?: string; renderedBody?: string },
41
+ ) => {
42
+ renderedCopyPatches.push({ id, patch });
43
+ return true;
44
+ },
45
+ }));
46
+
47
+ const adapterUpdates: Array<{ title?: string; body?: string }> = [];
48
+ let adapterSupportsUpdate = true;
49
+
50
+ mock.module("../emit-signal.js", () => ({
51
+ getBroadcaster: () => ({
52
+ getAdapter: () =>
53
+ adapterSupportsUpdate
54
+ ? {
55
+ update: async (
56
+ _target: unknown,
57
+ patch: { title?: string; body?: string },
58
+ ) => {
59
+ adapterUpdates.push(patch);
60
+ return { success: true };
61
+ },
62
+ }
63
+ : {},
64
+ }),
65
+ }));
66
+
67
+ const { appendFeedItem, readHomeFeed } =
68
+ await import("../../home/feed-writer.js");
69
+ const { editNotification, normalizeFeedItemId, feedItemIdToSignalId } =
70
+ await import("../edit-notification.js");
71
+
72
+ // ── Fixtures ───────────────────────────────────────────────────────────
73
+
74
+ const SIGNAL_ID = "sig-1";
75
+ const FEED_ITEM_ID = `notif:${SIGNAL_ID}`;
76
+
77
+ function makeItem(overrides: Partial<FeedItem> = {}): FeedItem {
78
+ return {
79
+ id: FEED_ITEM_ID,
80
+ type: "notification",
81
+ priority: 50,
82
+ title: "Backup complete",
83
+ summary: "Nightly backup finished",
84
+ timestamp: "2026-04-14T12:00:00.000Z",
85
+ status: "new",
86
+ createdAt: "2026-04-14T12:00:00.000Z",
87
+ ...overrides,
88
+ } as FeedItem;
89
+ }
90
+
91
+ function makeDelivery(
92
+ overrides: Partial<NotificationDeliveryRow> = {},
93
+ ): NotificationDeliveryRow {
94
+ return {
95
+ id: "del-1",
96
+ notificationDecisionId: "dec-1",
97
+ channel: "slack",
98
+ destination: "C123",
99
+ status: "sent",
100
+ attempt: 1,
101
+ renderedTitle: "Backup complete",
102
+ renderedBody: "Nightly backup finished",
103
+ errorCode: null,
104
+ errorMessage: null,
105
+ sentAt: 1700000000000,
106
+ conversationId: null,
107
+ messageId: "1700000000.0001",
108
+ conversationStrategy: null,
109
+ conversationAction: null,
110
+ conversationTargetId: null,
111
+ conversationFallbackUsed: null,
112
+ clientDeliveryStatus: null,
113
+ clientDeliveryError: null,
114
+ clientDeliveryAt: null,
115
+ createdAt: 1700000000000,
116
+ updatedAt: 1700000000000,
117
+ ...overrides,
118
+ };
119
+ }
120
+
121
+ function readItem(id = FEED_ITEM_ID): FeedItem | undefined {
122
+ return readHomeFeed().items.find((item) => item.id === id);
123
+ }
124
+
125
+ let workspaceDir: string;
126
+ let origWorkspaceDir: string | undefined;
127
+
128
+ beforeEach(() => {
129
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-edit-notif-"));
130
+ origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
131
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
132
+ decisionRow = null;
133
+ deliveryRows = [];
134
+ renderedCopyPatches.length = 0;
135
+ adapterUpdates.length = 0;
136
+ adapterSupportsUpdate = true;
137
+ });
138
+
139
+ afterEach(() => {
140
+ if (origWorkspaceDir === undefined) {
141
+ delete process.env.VELLUM_WORKSPACE_DIR;
142
+ } else {
143
+ process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
144
+ }
145
+ try {
146
+ rmSync(workspaceDir, { recursive: true, force: true });
147
+ } catch {
148
+ // best-effort
149
+ }
150
+ });
151
+
152
+ // ── Tests ──────────────────────────────────────────────────────────────
153
+
154
+ describe("normalizeFeedItemId", () => {
155
+ test("adds the notif: prefix to a bare uuid and leaves prefixed ids alone", () => {
156
+ expect(normalizeFeedItemId(SIGNAL_ID)).toBe(FEED_ITEM_ID);
157
+ expect(normalizeFeedItemId(` ${FEED_ITEM_ID} `)).toBe(FEED_ITEM_ID);
158
+ expect(feedItemIdToSignalId(FEED_ITEM_ID)).toBe(SIGNAL_ID);
159
+ });
160
+ });
161
+
162
+ describe("editNotification", () => {
163
+ test("applies a title edit to the feed item", async () => {
164
+ await appendFeedItem(makeItem());
165
+
166
+ const result = await editNotification({
167
+ id: FEED_ITEM_ID,
168
+ title: "Backup finished early",
169
+ });
170
+
171
+ expect(result).not.toBeNull();
172
+ expect(result!.feedItem.title).toBe("Backup finished early");
173
+ expect(readItem()!.title).toBe("Backup finished early");
174
+ expect(readItem()!.summary).toBe("Nightly backup finished");
175
+ });
176
+
177
+ test("accepts a bare signal uuid as the id", async () => {
178
+ await appendFeedItem(makeItem());
179
+
180
+ const result = await editNotification({
181
+ id: SIGNAL_ID,
182
+ title: "Renamed via bare id",
183
+ });
184
+
185
+ expect(result!.feedItem.id).toBe(FEED_ITEM_ID);
186
+ expect(readItem()!.title).toBe("Renamed via bare id");
187
+ });
188
+
189
+ test("an empty title edit leaves the previous title intact", async () => {
190
+ await appendFeedItem(makeItem());
191
+
192
+ const result = await editNotification({ id: FEED_ITEM_ID, title: "" });
193
+
194
+ expect(result).not.toBeNull();
195
+ expect(result!.feedItem.title).toBe("Backup complete");
196
+ expect(readItem()!.title).toBe("Backup complete");
197
+ });
198
+
199
+ test("a whitespace-only title edit leaves the previous title intact", async () => {
200
+ await appendFeedItem(makeItem());
201
+
202
+ const result = await editNotification({ id: FEED_ITEM_ID, title: " \t" });
203
+
204
+ expect(result!.feedItem.title).toBe("Backup complete");
205
+ expect(readItem()!.title).toBe("Backup complete");
206
+ });
207
+
208
+ test("an empty title edit does not push a channel update", async () => {
209
+ await appendFeedItem(makeItem());
210
+ decisionRow = { id: "dec-1" };
211
+ deliveryRows = [makeDelivery()];
212
+
213
+ const result = await editNotification({ id: FEED_ITEM_ID, title: "" });
214
+
215
+ expect(result!.channels).toEqual([]);
216
+ expect(adapterUpdates).toHaveLength(0);
217
+ expect(renderedCopyPatches).toHaveLength(0);
218
+ });
219
+
220
+ test("a body-only edit does not disturb the title", async () => {
221
+ await appendFeedItem(makeItem());
222
+
223
+ const result = await editNotification({
224
+ id: FEED_ITEM_ID,
225
+ body: "Nightly backup finished in 4 minutes",
226
+ });
227
+
228
+ expect(result!.feedItem.title).toBe("Backup complete");
229
+ expect(result!.feedItem.summary).toBe(
230
+ "Nightly backup finished in 4 minutes",
231
+ );
232
+ expect(readItem()!.title).toBe("Backup complete");
233
+ });
234
+
235
+ test("a feed-only edit skips channel updates entirely", async () => {
236
+ await appendFeedItem(makeItem());
237
+ decisionRow = { id: "dec-1" };
238
+ deliveryRows = [makeDelivery()];
239
+
240
+ const result = await editNotification({
241
+ id: FEED_ITEM_ID,
242
+ status: "dismissed",
243
+ });
244
+
245
+ expect(result!.feedItem.status).toBe("dismissed");
246
+ expect(result!.channels).toEqual([]);
247
+ expect(adapterUpdates).toHaveLength(0);
248
+ });
249
+
250
+ test("returns null for an unknown id", async () => {
251
+ await appendFeedItem(makeItem());
252
+
253
+ const result = await editNotification({
254
+ id: "notif:does-not-exist",
255
+ title: "Nope",
256
+ });
257
+
258
+ expect(result).toBeNull();
259
+ });
260
+
261
+ test("pushes the new copy to update-capable channel deliveries", async () => {
262
+ await appendFeedItem(makeItem());
263
+ decisionRow = { id: "dec-1" };
264
+ deliveryRows = [makeDelivery()];
265
+
266
+ const result = await editNotification({
267
+ id: FEED_ITEM_ID,
268
+ title: "Backup finished early",
269
+ body: "Done in 4 minutes",
270
+ });
271
+
272
+ expect(adapterUpdates).toEqual([
273
+ { title: "Backup finished early", body: "Done in 4 minutes" },
274
+ ]);
275
+ expect(renderedCopyPatches).toEqual([
276
+ {
277
+ id: "del-1",
278
+ patch: {
279
+ renderedTitle: "Backup finished early",
280
+ renderedBody: "Done in 4 minutes",
281
+ },
282
+ },
283
+ ]);
284
+ expect(result!.channels).toEqual([
285
+ { channel: "slack", deliveryId: "del-1", outcome: "updated" },
286
+ ]);
287
+ });
288
+
289
+ test("reports channels whose adapter cannot edit in place as unsupported", async () => {
290
+ await appendFeedItem(makeItem());
291
+ decisionRow = { id: "dec-1" };
292
+ deliveryRows = [makeDelivery()];
293
+ adapterSupportsUpdate = false;
294
+
295
+ const result = await editNotification({
296
+ id: FEED_ITEM_ID,
297
+ title: "Backup finished early",
298
+ });
299
+
300
+ expect(result!.channels[0]!.outcome).toBe("unsupported");
301
+ expect(renderedCopyPatches).toHaveLength(0);
302
+ });
303
+
304
+ test("skips deliveries that never reached sent", async () => {
305
+ await appendFeedItem(makeItem());
306
+ decisionRow = { id: "dec-1" };
307
+ deliveryRows = [makeDelivery({ status: "failed" })];
308
+
309
+ const result = await editNotification({
310
+ id: FEED_ITEM_ID,
311
+ title: "Backup finished early",
312
+ });
313
+
314
+ expect(result!.channels[0]!.outcome).toBe("skipped");
315
+ expect(adapterUpdates).toHaveLength(0);
316
+ });
317
+
318
+ test("skips channel updates when the feed item has no persisted decision", async () => {
319
+ await appendFeedItem(makeItem());
320
+ decisionRow = null;
321
+
322
+ const result = await editNotification({
323
+ id: FEED_ITEM_ID,
324
+ title: "Backup finished early",
325
+ });
326
+
327
+ expect(result!.channels).toEqual([]);
328
+ expect(adapterUpdates).toHaveLength(0);
329
+ });
330
+ });
@@ -0,0 +1,71 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ const createGuardianRequestDeliveryMock = mock();
4
+ const updateGuardianRequestDeliveryMock = mock();
5
+
6
+ mock.module("../../channels/gateway-guardian-requests.js", () => ({
7
+ createGuardianRequestDelivery: (...args: unknown[]) =>
8
+ createGuardianRequestDeliveryMock(...args),
9
+ updateGuardianRequestDelivery: (...args: unknown[]) =>
10
+ updateGuardianRequestDeliveryMock(...args),
11
+ }));
12
+
13
+ import { recordGuardianRequestDeliveries } from "../guardian-delivery-recorder.js";
14
+ import type { NotificationDeliveryResult } from "../types.js";
15
+
16
+ beforeEach(() => {
17
+ createGuardianRequestDeliveryMock.mockReset();
18
+ updateGuardianRequestDeliveryMock.mockReset();
19
+ createGuardianRequestDeliveryMock.mockImplementation(async () => ({
20
+ id: `row-${createGuardianRequestDeliveryMock.mock.calls.length}`,
21
+ }));
22
+ updateGuardianRequestDeliveryMock.mockResolvedValue(undefined);
23
+ });
24
+
25
+ describe("recordGuardianRequestDeliveries", () => {
26
+ test("records rows only for addressable channels, skipping platform", async () => {
27
+ const deliveryResults: NotificationDeliveryResult[] = [
28
+ {
29
+ channel: "vellum",
30
+ destination: "vellum",
31
+ status: "sent",
32
+ conversationId: "conv-1",
33
+ },
34
+ {
35
+ // A forced platform push has no endpoint, so the broadcaster falls
36
+ // back to the channel name as the destination -- it must never be
37
+ // persisted as a destinationChatId.
38
+ channel: "platform",
39
+ destination: "platform",
40
+ status: "pending",
41
+ },
42
+ {
43
+ channel: "telegram",
44
+ destination: "12345",
45
+ status: "sent",
46
+ conversationId: "conv-1",
47
+ messageId: "tg-77",
48
+ },
49
+ ];
50
+
51
+ await recordGuardianRequestDeliveries({
52
+ requestId: "req-1",
53
+ deliveryResults,
54
+ });
55
+
56
+ const channels = createGuardianRequestDeliveryMock.mock.calls.map(
57
+ (call) => (call[0] as { destinationChannel: string }).destinationChannel,
58
+ );
59
+ expect(channels).toEqual(["vellum", "telegram"]);
60
+
61
+ const telegramRow = createGuardianRequestDeliveryMock.mock.calls[1]![0] as {
62
+ destinationChatId?: string;
63
+ destinationMessageId?: string;
64
+ };
65
+ expect(telegramRow.destinationChatId).toBe("12345");
66
+ expect(telegramRow.destinationMessageId).toBe("tg-77");
67
+
68
+ // Status patches apply only to the rows that were created.
69
+ expect(updateGuardianRequestDeliveryMock).toHaveBeenCalledTimes(2);
70
+ });
71
+ });
@@ -320,11 +320,9 @@ describe("writeHomeFeedItemForSignal", () => {
320
320
  expect(appendCalls).toHaveLength(0);
321
321
  });
322
322
 
323
- test("writes a feed item with undefined title when only the body is available", async () => {
324
- // Regression: when `notifications send` is called without `--title`, the
325
- // notification pipeline must not manufacture a title (the LLM's rendered
326
- // copy echoes the body into `renderedCopy.title`). Leave `title`
327
- // undefined so renderers fall back to `summary` instead of stuttering.
323
+ test("derives a title from the summary when only the body is available", async () => {
324
+ // With no authored candidate at all, the title is derived from the
325
+ // summary rather than left off: every row carries a headline.
328
326
  conversationRow = { conversationType: "background" };
329
327
  const signal = makeSignal({
330
328
  sourceEventName: "example.event",
@@ -335,14 +333,14 @@ describe("writeHomeFeedItemForSignal", () => {
335
333
 
336
334
  expect(item).not.toBeNull();
337
335
  expect(appendCalls).toHaveLength(1);
338
- expect(appendCalls[0]!.title).toBeUndefined();
336
+ expect(appendCalls[0]!.title).toBe("Real body");
339
337
  expect(appendCalls[0]!.summary).toBe("Real body");
340
338
  });
341
339
 
342
- test("ignores LLM-rendered title when no payload title was supplied", async () => {
343
- // The LLM often echoes the body verbatim into `renderedCopy.title` when
344
- // the source didn't pass one. The home-feed writer must NOT promote that
345
- // echo into the feed item — only an explicit source title is honored.
340
+ test("uses the LLM-rendered title when no payload title was supplied", async () => {
341
+ // The model authors `renderedCopy.title` as a topic headline, and the
342
+ // decision engine validates it before it gets here, so it is the second
343
+ // candidate after the payload title.
346
344
  conversationRow = { conversationType: "background" };
347
345
  const signal = makeSignal({
348
346
  sourceEventName: "example.event",
@@ -351,7 +349,7 @@ describe("writeHomeFeedItemForSignal", () => {
351
349
  const decision = makeDecision({
352
350
  renderedCopy: {
353
351
  vellum: {
354
- title: "Real body",
352
+ title: "Nightly sync finished",
355
353
  body: "Real body",
356
354
  },
357
355
  },
@@ -361,10 +359,170 @@ describe("writeHomeFeedItemForSignal", () => {
361
359
 
362
360
  expect(item).not.toBeNull();
363
361
  expect(appendCalls).toHaveLength(1);
364
- expect(appendCalls[0]!.title).toBeUndefined();
362
+ expect(appendCalls[0]!.title).toBe("Nightly sync finished");
365
363
  expect(appendCalls[0]!.summary).toBe("Real body");
366
364
  });
367
365
 
366
+ test("payload title wins over the rendered title when it is clean", async () => {
367
+ conversationRow = { conversationType: "background" };
368
+ const signal = makeSignal({
369
+ sourceEventName: "example.event",
370
+ contextPayload: { title: "Payload headline" },
371
+ });
372
+ const decision = makeDecision({
373
+ renderedCopy: {
374
+ vellum: {
375
+ title: "Model headline",
376
+ body: "A description of what the run did.",
377
+ },
378
+ },
379
+ });
380
+
381
+ const item = await writeHomeFeedItemForSignal(signal, decision);
382
+
383
+ expect(item?.title).toBe("Payload headline");
384
+ expect(appendCalls[0]!.title).toBe("Payload headline");
385
+ });
386
+
387
+ test("keeps a payload title that opens the summary", async () => {
388
+ // A correct topic headline is usually the opening noun phrase of the body,
389
+ // so an authored title that overlaps the summary still wins.
390
+ conversationRow = { conversationType: "background" };
391
+ const signal = makeSignal({
392
+ sourceEventName: "example.event",
393
+ contextPayload: { title: "Nightly Backup Failure" },
394
+ });
395
+ const decision = makeDecision({
396
+ renderedCopy: {
397
+ vellum: {
398
+ title: "Reminder plumbing check",
399
+ body: "Nightly backup failure on db-primary at 02:14. Retry scheduled.",
400
+ },
401
+ },
402
+ });
403
+
404
+ const item = await writeHomeFeedItemForSignal(signal, decision);
405
+
406
+ expect(item?.title).toBe("Nightly Backup Failure");
407
+ expect(appendCalls[0]!.summary).toBe(
408
+ "Nightly backup failure on db-primary at 02:14. Retry scheduled.",
409
+ );
410
+ });
411
+
412
+ test("keeps a payload title that matches the summary's first sentence", async () => {
413
+ conversationRow = { conversationType: "background" };
414
+ const signal = makeSignal({
415
+ sourceEventName: "example.event",
416
+ contextPayload: { title: "Test notifications reminder." },
417
+ });
418
+ const decision = makeDecision({
419
+ renderedCopy: {
420
+ vellum: {
421
+ title: "Model headline",
422
+ body: "Test notifications reminder. It fired on schedule.",
423
+ },
424
+ },
425
+ });
426
+
427
+ const item = await writeHomeFeedItemForSignal(signal, decision);
428
+
429
+ expect(item?.title).toBe("Test notifications reminder.");
430
+ expect(appendCalls[0]!.summary).toBe(
431
+ "Test notifications reminder. It fired on schedule.",
432
+ );
433
+ });
434
+
435
+ test("always writes a non-empty title even when the copy is unusable", async () => {
436
+ // `normalizeTitle` rejects prose-shaped candidates, so both authored
437
+ // titles drop out and the derivation carries the row.
438
+ conversationRow = { conversationType: "background" };
439
+ const signal = makeSignal({
440
+ sourceEventName: "example.event",
441
+ contextPayload: { title: "I need to name this notification somehow" },
442
+ });
443
+ const decision = makeDecision({
444
+ renderedCopy: {
445
+ vellum: {
446
+ title: "Let me summarize what happened",
447
+ body: "Disk usage crossed 90 percent. Cleanup ran.",
448
+ },
449
+ },
450
+ });
451
+
452
+ const item = await writeHomeFeedItemForSignal(signal, decision);
453
+
454
+ expect(item?.title).toBe("Disk usage crossed 90 percent.");
455
+ expect(appendCalls[0]!.title).toBeTruthy();
456
+ });
457
+
458
+ test("derives a single-line plain title from a markdown-headed conversation seed", async () => {
459
+ // The summary prefers `conversationSeedMessage`, which carries structured
460
+ // markdown and hard line breaks. The derived title must not.
461
+ conversationRow = { conversationType: "background" };
462
+ const seed =
463
+ "## Nightly Backup Report\n\nThe backup job on db-primary failed at 02:14.";
464
+ const signal = makeSignal({ sourceEventName: "example.event" });
465
+ const decision = makeDecision({
466
+ renderedCopy: {
467
+ vellum: {
468
+ title: "",
469
+ body: "The backup job failed.",
470
+ conversationSeedMessage: seed,
471
+ },
472
+ },
473
+ });
474
+
475
+ const item = await writeHomeFeedItemForSignal(signal, decision);
476
+
477
+ expect(item?.title).toBe(
478
+ "Nightly Backup Report The backup job on db-primary failed at…",
479
+ );
480
+ expect(item?.summary).toBe(seed);
481
+ });
482
+
483
+ test("strips tilde fences and indented headings, matching migration 138", async () => {
484
+ // Workspace migration 138 backfills titles with its own self-contained copy
485
+ // of these rules. A backfilled title and a freshly written one must match
486
+ // for the same summary, so both accept CommonMark's 3-space indent and both
487
+ // fence styles.
488
+ conversationRow = { conversationType: "background" };
489
+ const seed = " ## Indented heading\n\n~~~\ntilde fence\n~~~\nBody text.";
490
+ const signal = makeSignal({ sourceEventName: "example.event" });
491
+ const decision = makeDecision({
492
+ renderedCopy: {
493
+ vellum: {
494
+ title: "",
495
+ body: "Body text.",
496
+ conversationSeedMessage: seed,
497
+ },
498
+ },
499
+ });
500
+
501
+ const item = await writeHomeFeedItemForSignal(signal, decision);
502
+
503
+ expect(item?.title).toBe("Indented heading tilde fence Body text.");
504
+ });
505
+
506
+ test("derives a single-line plain title from a markdown-list conversation seed", async () => {
507
+ conversationRow = { conversationType: "background" };
508
+ const seed = "- Ran 12 checks\n- 3 failed\n\nSee the log for details.";
509
+ const signal = makeSignal({ sourceEventName: "example.event" });
510
+ const decision = makeDecision({
511
+ renderedCopy: {
512
+ vellum: {
513
+ title: "",
514
+ body: "Check run finished.",
515
+ conversationSeedMessage: seed,
516
+ },
517
+ },
518
+ });
519
+
520
+ const item = await writeHomeFeedItemForSignal(signal, decision);
521
+
522
+ expect(item?.title).toBe("Ran 12 checks 3 failed See the log for details.");
523
+ expect(item?.summary).toBe(seed);
524
+ });
525
+
368
526
  test("treats whitespace-only rendered copy and payload values as missing and returns null", async () => {
369
527
  conversationRow = { conversationType: "background" };
370
528
  const signal = makeSignal({
@@ -101,12 +101,15 @@ export class VellumAdapter implements ChannelAdapter {
101
101
  this.broadcast({
102
102
  type: "notification_intent",
103
103
  deliveryId: payload.deliveryId,
104
+ correlationId: payload.correlationId,
104
105
  sourceEventName: payload.sourceEventName,
105
106
  title: payload.copy.title,
106
107
  body: payload.copy.body,
107
108
  deepLinkMetadata: payload.deepLinkTarget,
108
109
  targetGuardianPrincipalId,
109
110
  silent,
111
+ remotePushDispatched: payload.remotePushDispatched,
112
+ remotePushPlatforms: payload.remotePushPlatforms,
110
113
  } as AssistantEvent);
111
114
 
112
115
  log.info(