@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
@@ -23,11 +23,13 @@
23
23
  import type { ConversationStrategy } from "../channels/config.js";
24
24
  import { getConversationStrategy } from "../channels/config.js";
25
25
  import type { ChannelId } from "../channels/types.js";
26
+ import { isAssistantInitiatedThreadsEnabled } from "../config/assistant-initiated-threads-gate.js";
26
27
  import {
27
28
  addMessage,
28
29
  createConversation,
29
30
  getConversation,
30
31
  } from "../persistence/conversation-crud.js";
32
+ import { ASSISTANT_INITIATED_SOURCE } from "../persistence/conversation-types.js";
31
33
  import {
32
34
  getBindingByChannelChat,
33
35
  upsertOutboundBinding,
@@ -106,13 +108,77 @@ export interface PairingOptions {
106
108
  * Errors are caught and logged — this function never throws so the
107
109
  * notification pipeline is not disrupted by pairing failures.
108
110
  */
109
- export async function pairDeliveryWithConversation(
111
+ /**
112
+ * The event the assistant emits when it has a thought worth the user's time
113
+ * ("assistant.share" - the notifications skill's default), as opposed to a
114
+ * transactional request or a system alert.
115
+ */
116
+ const ASSISTANT_SHARE_EVENT = "assistant.share";
117
+
118
+ /**
119
+ * Promote a background share into an assistant-initiated thread, under the
120
+ * `assistant-initiated-threads` flag.
121
+ *
122
+ * The heartbeat's "have a thought, share it" path emits an assistant.share
123
+ * signal from its own background conversation, and the passive-vellum rule
124
+ * below would append the body there - a row the sidebar never shows. When the
125
+ * section exists, that share is exactly what it is for, so the signal is
126
+ * rewritten to materialize a fresh standard conversation stamped
127
+ * {@link ASSISTANT_INITIATED_SOURCE}, which is the section's membership mark.
128
+ *
129
+ * Deliberately narrow, in every direction it can be:
130
+ * - vellum channel only: other channels deliver the share as a native
131
+ * message and need no in-app thread;
132
+ * - assistant.share only: transactional events keep their own pairing
133
+ * rules, and their threads stay out of the section by source;
134
+ * - only when the producing conversation is a background/scheduled run (or
135
+ * nothing resolvable): a share emitted from inside a user-facing thread
136
+ * keeps the append, since the user is already looking at that thread;
137
+ * - never over an explicit `conversationMetadata.source` or an existing
138
+ * `requiresConversation`: a producer that declared its own filing wins.
139
+ *
140
+ * Flag off, the signal passes through untouched and shares keep the passive
141
+ * append: nothing changes for anyone outside the rollout.
142
+ */
143
+ function withAssistantInitiatedThread(
110
144
  signal: NotificationSignal,
111
145
  channel: NotificationChannel,
146
+ ): NotificationSignal {
147
+ if (
148
+ channel !== "vellum" ||
149
+ signal.sourceEventName !== ASSISTANT_SHARE_EVENT ||
150
+ signal.requiresConversation === true ||
151
+ signal.conversationMetadata?.source !== undefined ||
152
+ !isAssistantInitiatedThreadsEnabled()
153
+ ) {
154
+ return signal;
155
+ }
156
+ const producing = getConversation(signal.sourceContextId);
157
+ if (
158
+ producing &&
159
+ producing.conversationType !== "background" &&
160
+ producing.conversationType !== "scheduled"
161
+ ) {
162
+ return signal;
163
+ }
164
+ return {
165
+ ...signal,
166
+ requiresConversation: true,
167
+ conversationMetadata: {
168
+ ...signal.conversationMetadata,
169
+ source: ASSISTANT_INITIATED_SOURCE,
170
+ },
171
+ };
172
+ }
173
+
174
+ export async function pairDeliveryWithConversation(
175
+ rawSignal: NotificationSignal,
176
+ channel: NotificationChannel,
112
177
  copy: RenderedChannelCopy,
113
178
  options?: PairingOptions,
114
179
  ): Promise<PairingResult> {
115
180
  try {
181
+ const signal = withAssistantInitiatedThread(rawSignal, channel);
116
182
  const strategy = getConversationStrategy(channel as ChannelId);
117
183
 
118
184
  if (strategy === "not_deliverable" || strategy === "push_only") {
@@ -490,7 +556,7 @@ export async function pairDeliveryWithConversation(
490
556
  };
491
557
  } catch (err) {
492
558
  log.error(
493
- { err, signalId: signal.signalId, channel },
559
+ { err, signalId: rawSignal.signalId, channel },
494
560
  "Failed to pair notification delivery with conversation — continuing without pairing",
495
561
  );
496
562
  const fallbackStrategy = (() => {
@@ -13,7 +13,7 @@ import { normalizeTitle } from "../util/short-title.js";
13
13
  import { truncate } from "../util/truncate.js";
14
14
  import {
15
15
  accessRequestCardTitle,
16
- buildAccessRequestContractText,
16
+ buildAccessRequestContextText,
17
17
  isAdmittedIntroduction,
18
18
  } from "./access-request-copy.js";
19
19
  import {
@@ -21,10 +21,8 @@ import {
21
21
  buildToolApprovalSeedContentBlocks,
22
22
  } from "./approval-card-data.js";
23
23
  import {
24
- buildGuardianRequestCodeInstruction,
24
+ applyGuardianReplyMechanics,
25
25
  parseGuardianQuestionPayload,
26
- resolveGuardianInstructionModeFromPayload,
27
- resolveGuardianQuestionInstructionMode,
28
26
  } from "./guardian-question-mode.js";
29
27
  import {
30
28
  nonEmpty,
@@ -231,34 +229,12 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
231
229
  ? buildToolApprovalSeedContentBlocks(parsed)
232
230
  : buildToolApprovalSeedContentBlocks(payload)) ?? undefined;
233
231
 
234
- const requestCode = parsed
235
- ? nonEmpty(parsed.requestCode)
236
- : nonEmpty(
237
- typeof payload.requestCode === "string"
238
- ? payload.requestCode
239
- : undefined,
240
- );
241
-
242
- if (!requestCode) {
243
- return {
244
- title: "Guardian Question",
245
- body: question,
246
- conversationSeedMessage,
247
- seedContentBlocks,
248
- };
249
- }
250
-
251
- const normalizedCode = requestCode.toUpperCase();
252
- const modeResolution = parsed
253
- ? resolveGuardianInstructionModeFromPayload(parsed)
254
- : resolveGuardianQuestionInstructionMode(payload);
255
- const instruction = buildGuardianRequestCodeInstruction(
256
- normalizedCode,
257
- modeResolution.mode,
258
- );
232
+ // The request-code reply instruction is not part of the template: whether
233
+ // a channel's copy carries it is decided per channel by
234
+ // `applyGuardianReplyMechanics` when the copy is composed.
259
235
  return {
260
236
  title: "Guardian Question",
261
- body: `${question}\n\n${instruction}`,
237
+ body: question,
262
238
  conversationSeedMessage,
263
239
  seedContentBlocks,
264
240
  };
@@ -275,7 +251,7 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
275
251
 
276
252
  "ingress.access_request": (payload) => ({
277
253
  title: accessRequestCardTitle(isAdmittedIntroduction(payload)),
278
- body: buildAccessRequestContractText(payload),
254
+ body: buildAccessRequestContextText(payload),
279
255
  seedContentBlocks: buildAccessRequestSeedContentBlocks(payload),
280
256
  }),
281
257
 
@@ -438,7 +414,12 @@ export function composeFallbackCopy(
438
414
 
439
415
  const result: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {};
440
416
  for (const ch of channels) {
441
- result[ch] = applyChannelDefaults(ch, baseCopy);
417
+ // Mechanics first, so a chat channel's derived deliveryText inherits the
418
+ // instruction the rule adds to the body.
419
+ result[ch] = applyChannelDefaults(
420
+ ch,
421
+ applyGuardianReplyMechanics(baseCopy, ch, signal),
422
+ );
442
423
  }
443
424
  return result;
444
425
  }
@@ -28,12 +28,8 @@ import type { Provider } from "../providers/types.js";
28
28
  import { getLogger } from "../util/logger.js";
29
29
  import { truncate } from "../util/truncate.js";
30
30
  import {
31
- buildAccessRequestContractText,
32
- buildAccessRequestInviteDirective,
33
- hasAccessRequestInstructions,
34
- hasInviteFlowDirective,
35
- isHandshakeOfferedForPayload,
36
- parseAccessRequestPayload,
31
+ ensureAccessRequestInviteDirectiveInCopy,
32
+ stripAccessRequestReplyMechanicsFromCopy,
37
33
  } from "./access-request-copy.js";
38
34
  import {
39
35
  buildAccessRequestSeedContentBlocks,
@@ -47,15 +43,10 @@ import {
47
43
  import { composeFallbackCopy, resolveTitle } from "./copy-composer.js";
48
44
  import { createDecision } from "./decisions-store.js";
49
45
  import {
50
- buildGuardianRequestCodeInstruction,
46
+ applyGuardianReplyMechanics,
51
47
  buildQuestionDeliveryText,
52
- hasGuardianRequestCodeInstruction,
53
48
  parseGuardianQuestionPayload,
54
- parseInteractiveApprovalPayload,
55
49
  resolveGuardianInstructionModeFromPayload,
56
- resolveGuardianQuestionInstructionMode,
57
- stripConflictingGuardianRequestInstructions,
58
- stripGuardianRequestCodeInstructions,
59
50
  } from "./guardian-question-mode.js";
60
51
  import {
61
52
  nonEmpty,
@@ -165,6 +156,7 @@ function buildSystemPrompt(
165
156
  ` - Avoid intermediary-instruction phrasing like "consider telling the guardian", "ask the recipient to", or "the assistant should remind them". Rewrite it as final copy the recipient can act on directly.`,
166
157
  ` - For telegram: 1-2 concise sentences.`,
167
158
  ` - For slack approval requests: the message renders inside an interactive card with Approve/Reject buttons. Describe only what needs approval — never include approval/reference codes, code-reply instructions, or directions to respond in another app.`,
159
+ ` - For vellum and platform guardian requests: the copy is read in the notification bell, the banner, and the push, which act through the request card. Never include reference codes, code-reply instructions, or directions to respond elsewhere.`,
168
160
  `- \`conversationSeedMessage\` is the opening message in the internal notification conversation and also the expanded detail shown in the home feed. It should be richer and more contextual than the popup body.`,
169
161
  ` - For vellum (desktop): use structured markdown for readability. Break content into bullet points, numbered lists, or short sections with **bold** labels. Avoid long unbroken paragraphs — scan-friendly formatting is preferred.`,
170
162
  ` - Never dump raw JSON. Include only human-readable context.`,
@@ -600,77 +592,14 @@ export function validateConversationActions(
600
592
  return result;
601
593
  }
602
594
 
603
- function ensureGuardianRequestCodeInCopy(
604
- copy: RenderedChannelCopy,
605
- requestCode: string,
606
- mode: "approval" | "answer",
607
- ): RenderedChannelCopy {
608
- const instruction = buildGuardianRequestCodeInstruction(requestCode, mode);
609
-
610
- const ensureText = (text: string | undefined): string => {
611
- const base = typeof text === "string" ? text.trim() : "";
612
- const sanitized = stripConflictingGuardianRequestInstructions(
613
- base,
614
- requestCode,
615
- mode,
616
- );
617
- if (hasGuardianRequestCodeInstruction(sanitized, requestCode, mode)) {
618
- return sanitized;
619
- }
620
- return sanitized.length > 0
621
- ? `${sanitized}\n\n${instruction}`
622
- : instruction;
623
- };
624
-
625
- return {
626
- ...copy,
627
- body: ensureText(copy.body),
628
- deliveryText: copy.deliveryText
629
- ? ensureText(copy.deliveryText)
630
- : copy.deliveryText,
631
- conversationSeedMessage: copy.conversationSeedMessage
632
- ? ensureText(copy.conversationSeedMessage)
633
- : copy.conversationSeedMessage,
634
- };
635
- }
636
-
637
- /**
638
- * Remove request-code reply instructions from copy for a channel whose
639
- * approval UI makes them redundant. Instruction-only fields keep their
640
- * original text rather than becoming empty — downstream treats an empty
641
- * body as missing copy.
642
- */
643
- function stripGuardianRequestCodeInCopy(
644
- copy: RenderedChannelCopy,
645
- requestCode: string,
646
- ): RenderedChannelCopy {
647
- const strip = (text: string): string => {
648
- const stripped = stripGuardianRequestCodeInstructions(text, requestCode);
649
- return stripped.length > 0 ? stripped : text;
650
- };
651
-
652
- return {
653
- ...copy,
654
- body: strip(copy.body),
655
- deliveryText: copy.deliveryText
656
- ? strip(copy.deliveryText)
657
- : copy.deliveryText,
658
- conversationSeedMessage: copy.conversationSeedMessage
659
- ? strip(copy.conversationSeedMessage)
660
- : copy.conversationSeedMessage,
661
- };
662
- }
663
-
664
595
  /**
665
596
  * Guardian questions that share a conversation require explicit request-code
666
- * targeting. Enforce request-code instructions in rendered copy so guardians
667
- * can always disambiguate replies even when model copy omits them.
668
- *
669
- * Slack is the exception for approval-mode questions: the Slack adapter
670
- * renders those as an interactive card with Approve/Reject buttons (see
671
- * `resolveApprovalContext` in broadcaster.ts, gated on the same
672
- * `parseInteractiveApprovalPayload`), so code-reply instructions are
673
- * redundant noise there and are stripped instead of enforced in.
597
+ * targeting, so the reply-mechanics rule runs on every decision path: a
598
+ * channel the guardian answers by typing gets the instruction enforced into
599
+ * its copy even when the model left it out, and a channel that acts through
600
+ * a card (the vellum bell and banner, the platform push, Slack approval
601
+ * cards) gets it stripped, model-authored code phrasing included. The
602
+ * per-channel rule is `guardianCopyCarriesReplyMechanics`.
674
603
  */
675
604
  function enforceGuardianRequestCode(
676
605
  decision: NotificationDecision,
@@ -679,17 +608,6 @@ function enforceGuardianRequestCode(
679
608
  if (signal.sourceEventName !== "guardian.question") {
680
609
  return decision;
681
610
  }
682
- const rawCode = signal.contextPayload.requestCode;
683
- if (typeof rawCode !== "string" || rawCode.trim().length === 0) {
684
- return decision;
685
- }
686
-
687
- const requestCode = rawCode.trim().toUpperCase();
688
- const modeResolution = resolveGuardianQuestionInstructionMode(
689
- signal.contextPayload,
690
- );
691
- const slackRendersApprovalButtons =
692
- parseInteractiveApprovalPayload(signal.contextPayload) != null;
693
611
  const nextCopy: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {
694
612
  ...decision.renderedCopy,
695
613
  };
@@ -699,14 +617,7 @@ function enforceGuardianRequestCode(
699
617
  if (!copy) {
700
618
  continue;
701
619
  }
702
- nextCopy[channel] =
703
- channel === "slack" && slackRendersApprovalButtons
704
- ? stripGuardianRequestCodeInCopy(copy, requestCode)
705
- : ensureGuardianRequestCodeInCopy(
706
- copy,
707
- requestCode,
708
- modeResolution.mode,
709
- );
620
+ nextCopy[channel] = applyGuardianReplyMechanics(copy, channel, signal);
710
621
  }
711
622
 
712
623
  return {
@@ -759,6 +670,10 @@ function ensureSeedContentBlocks(
759
670
  *
760
671
  * Scoped to answer mode. A `pending_question` carrying a tool name is a voice
761
672
  * tool approval, where composed copy is the right thing.
673
+ *
674
+ * The pinned text carries the reference-code instruction; the
675
+ * reply-mechanics pass runs after this one and keeps or strips it per
676
+ * channel, so this pin never has to know which surfaces take a typed reply.
762
677
  */
763
678
  function pinQuestionDeliveryCopy(
764
679
  decision: NotificationDecision,
@@ -814,131 +729,38 @@ function enforceToolApprovalSeedBlocks(
814
729
  }
815
730
 
816
731
  /**
817
- * Access-request notifications require deterministic instruction elements:
818
- * - Request-code approve/reject directive (when requestCode is present)
819
- * - Exact "open invite flow" phrase (always required)
820
- *
821
- * When requestCode IS present: use the full hasAccessRequestInstructions
822
- * check (approve+reject+invite) and append the complete contract text if
823
- * any element is missing.
824
- *
825
- * When requestCode is NOT present: still check for the invite-flow
826
- * directive and append it if missing. Per the documented contract, the
827
- * invite directive should always be present in access-request copy.
732
+ * Access-request copy never carries the request-code directive: that is the
733
+ * card's `plainTextFallback`, appended by a transport only when it sends
734
+ * text without buttons. A model's echo of it comes out on every channel.
735
+ * The invite-flow directive is the opposite case, context rather than
736
+ * mechanics (no surface has an invite button, so the sentence is the only
737
+ * way to start the flow), and model-composed copy that leaves it out gets
738
+ * it appended. The Surface card is ensured on every decision path, the
739
+ * same way tool approvals are.
828
740
  */
829
- function enforceAccessRequestInstructions(
741
+ function enforceAccessRequestCopy(
830
742
  decision: NotificationDecision,
831
743
  signal: NotificationSignal,
832
744
  ): NotificationDecision {
833
745
  if (signal.sourceEventName !== "ingress.access_request") {
834
746
  return decision;
835
747
  }
836
-
837
- const rawCode = signal.contextPayload.requestCode;
838
- const hasRequestCode =
839
- typeof rawCode === "string" && rawCode.trim().length > 0;
840
-
841
748
  const nextCopy: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {
842
749
  ...decision.renderedCopy,
843
750
  };
844
-
845
- if (hasRequestCode) {
846
- const requestCode = rawCode.trim().toUpperCase();
847
- const contractText = buildAccessRequestContractText(signal.contextPayload);
848
- const handshakeOffered = isHandshakeOfferedForPayload(
849
- parseAccessRequestPayload(signal.contextPayload),
850
- );
851
-
852
- for (const channel of Object.keys(nextCopy) as NotificationChannel[]) {
853
- const copy = nextCopy[channel];
854
- if (!copy) {
855
- continue;
856
- }
857
- nextCopy[channel] = ensureAccessRequestInstructionsInCopy(
858
- copy,
859
- requestCode,
860
- contractText,
861
- handshakeOffered,
862
- );
863
- }
864
- } else {
865
- // No requestCode — still enforce the invite-flow directive.
866
- const inviteDirective = buildAccessRequestInviteDirective();
867
-
868
- for (const channel of Object.keys(nextCopy) as NotificationChannel[]) {
869
- const copy = nextCopy[channel];
870
- if (!copy) {
871
- continue;
872
- }
873
- nextCopy[channel] = ensureInviteFlowDirectiveInCopy(
874
- copy,
875
- inviteDirective,
876
- );
751
+ for (const channel of Object.keys(nextCopy) as NotificationChannel[]) {
752
+ const copy = nextCopy[channel];
753
+ if (!copy) {
754
+ continue;
877
755
  }
756
+ nextCopy[channel] = ensureAccessRequestInviteDirectiveInCopy(
757
+ stripAccessRequestReplyMechanicsFromCopy(copy, signal.contextPayload),
758
+ );
878
759
  }
879
-
880
- let result: NotificationDecision = {
881
- ...decision,
882
- renderedCopy: nextCopy,
883
- };
884
-
885
- // Ensure seedContentBlocks on all paths (LLM, assistant_tool, fallback).
886
- result = ensureSeedContentBlocks(
887
- result,
760
+ return ensureSeedContentBlocks(
761
+ { ...decision, renderedCopy: nextCopy },
888
762
  buildAccessRequestSeedContentBlocks(signal.contextPayload),
889
763
  );
890
-
891
- return result;
892
- }
893
-
894
- function ensureAccessRequestInstructionsInCopy(
895
- copy: RenderedChannelCopy,
896
- requestCode: string,
897
- contractText: string,
898
- handshakeOffered: boolean,
899
- ): RenderedChannelCopy {
900
- const ensureText = (text: string | undefined): string => {
901
- const base = typeof text === "string" ? text.trim() : "";
902
- if (hasAccessRequestInstructions(base, requestCode, { handshakeOffered })) {
903
- return base;
904
- }
905
- return base.length > 0 ? `${base}\n\n${contractText}` : contractText;
906
- };
907
-
908
- return {
909
- ...copy,
910
- body: ensureText(copy.body),
911
- deliveryText: copy.deliveryText
912
- ? ensureText(copy.deliveryText)
913
- : copy.deliveryText,
914
- conversationSeedMessage: copy.conversationSeedMessage
915
- ? ensureText(copy.conversationSeedMessage)
916
- : copy.conversationSeedMessage,
917
- };
918
- }
919
-
920
- function ensureInviteFlowDirectiveInCopy(
921
- copy: RenderedChannelCopy,
922
- inviteDirective: string,
923
- ): RenderedChannelCopy {
924
- const ensureText = (text: string | undefined): string => {
925
- const base = typeof text === "string" ? text.trim() : "";
926
- if (hasInviteFlowDirective(base)) {
927
- return base;
928
- }
929
- return base.length > 0 ? `${base}\n\n${inviteDirective}` : inviteDirective;
930
- };
931
-
932
- return {
933
- ...copy,
934
- body: ensureText(copy.body),
935
- deliveryText: copy.deliveryText
936
- ? ensureText(copy.deliveryText)
937
- : copy.deliveryText,
938
- conversationSeedMessage: copy.conversationSeedMessage
939
- ? ensureText(copy.conversationSeedMessage)
940
- : copy.conversationSeedMessage,
941
- };
942
764
  }
943
765
 
944
766
  // ── Producer pass-through decisions ────────────────────────────────────
@@ -993,17 +815,28 @@ function buildPassThroughDecision(params: {
993
815
  verbatimCopy: true,
994
816
  ...(deepLinkTarget ? { deepLinkTarget } : {}),
995
817
  };
818
+ decision = applyDecisionGuards(decision, signal);
819
+ decision.persistedDecisionId = persistDecision(signal, decision);
820
+ return decision;
821
+ }
822
+
823
+ /**
824
+ * The deterministic guards every decision passes through once the model,
825
+ * the assistant-tool pass-through, or the fallback has rendered copy. In
826
+ * dependency order: the question pin writes the copy the reply-mechanics
827
+ * pass then settles per channel, and the seed-block guard is independent
828
+ * of both.
829
+ */
830
+ function applyDecisionGuards(
831
+ decision: NotificationDecision,
832
+ signal: NotificationSignal,
833
+ ): NotificationDecision {
834
+ decision = pinQuestionDeliveryCopy(decision, signal);
996
835
  decision = enforceGuardianRequestCode(decision, signal);
997
836
  decision = enforceToolApprovalSeedBlocks(decision, signal);
998
- decision = pinQuestionDeliveryCopy(decision, signal);
999
- decision = enforceAccessRequestInstructions(decision, signal);
837
+ decision = enforceAccessRequestCopy(decision, signal);
1000
838
  decision = enforceGuardianRequestConversationAffinity(decision, signal);
1001
- decision = enforceConversationAffinity(
1002
- decision,
1003
- signal.conversationAffinityHint,
1004
- );
1005
- decision.persistedDecisionId = persistDecision(signal, decision);
1006
- return decision;
839
+ return enforceConversationAffinity(decision, signal.conversationAffinityHint);
1007
840
  }
1008
841
 
1009
842
  // ── Core evaluation function ───────────────────────────────────────────
@@ -1118,15 +951,9 @@ export async function evaluateSignal(
1118
951
  log.warn(
1119
952
  "Configured provider unavailable for notification decision, using fallback",
1120
953
  );
1121
- let decision = buildFallbackDecision(signal, availableChannels);
1122
- decision = enforceGuardianRequestCode(decision, signal);
1123
- decision = enforceToolApprovalSeedBlocks(decision, signal);
1124
- decision = pinQuestionDeliveryCopy(decision, signal);
1125
- decision = enforceAccessRequestInstructions(decision, signal);
1126
- decision = enforceGuardianRequestConversationAffinity(decision, signal);
1127
- decision = enforceConversationAffinity(
1128
- decision,
1129
- signal.conversationAffinityHint,
954
+ const decision = applyDecisionGuards(
955
+ buildFallbackDecision(signal, availableChannels),
956
+ signal,
1130
957
  );
1131
958
  decision.persistedDecisionId = persistDecision(signal, decision);
1132
959
  return decision;
@@ -1150,15 +977,7 @@ export async function evaluateSignal(
1150
977
  decision = buildFallbackDecision(signal, availableChannels);
1151
978
  }
1152
979
 
1153
- decision = enforceGuardianRequestCode(decision, signal);
1154
- decision = enforceToolApprovalSeedBlocks(decision, signal);
1155
- decision = pinQuestionDeliveryCopy(decision, signal);
1156
- decision = enforceAccessRequestInstructions(decision, signal);
1157
- decision = enforceGuardianRequestConversationAffinity(decision, signal);
1158
- decision = enforceConversationAffinity(
1159
- decision,
1160
- signal.conversationAffinityHint,
1161
- );
980
+ decision = applyDecisionGuards(decision, signal);
1162
981
  decision.persistedDecisionId = persistDecision(signal, decision);
1163
982
 
1164
983
  return decision;
@@ -8,12 +8,8 @@
8
8
  * reached. Capturing the surface address here — the conversation id for the
9
9
  * in-app vellum card, or the channel-native message id (e.g. Slack `ts`) for a
10
10
  * channel card — is what lets a delivered card be addressed back to its request
11
- * later:
12
- *
13
- * - to withdraw it in place when the request resolves
14
- * (`approvals/guardian-card-withdrawal.ts`), and
15
- * - to resolve an emoji reaction on the card to the right request when several
16
- * are pending in the same chat (the by-destination-message lookup).
11
+ * later, to withdraw it in place when the request resolves
12
+ * (`approvals/guardian-card-withdrawal.ts`).
17
13
  *
18
14
  * Every producer records through here so the addressing convention lives in one
19
15
  * place and cannot drift between the path that writes the row and the paths that
@@ -48,7 +44,7 @@ export interface ApprovalCardDeliveryAddress {
48
44
  conversationId?: string;
49
45
  /** Channel addressing: the chat the card was delivered to. */
50
46
  chatId?: string;
51
- /** Channel-native message id (e.g. Slack `ts`) — the reaction/withdrawal key. */
47
+ /** Channel-native message id (e.g. Slack `ts`): the withdrawal key. */
52
48
  messageId?: string;
53
49
  /** Initial delivery status (defaults to `DELIVERY_STATUS.pending`). */
54
50
  status?: string;
@@ -78,7 +74,7 @@ export async function recordApprovalCardDelivery(
78
74
  } catch (err) {
79
75
  log.error(
80
76
  { err, requestId: address.requestId, channel: address.channel },
81
- "Failed to record approval card delivery; reaction/withdrawal on this card will not resolve",
77
+ "Failed to record approval card delivery; withdrawal of this card will not resolve",
82
78
  );
83
79
  return null;
84
80
  }
@@ -100,7 +96,7 @@ export async function recordApprovalCardDelivery(
100
96
  * `conversationId`. Platform push results are skipped entirely: a push
101
97
  * has no channel-native card surface to address back to.
102
98
  * Channel results additionally carry the chat (`destination`) and channel-native
103
- * id (`messageId`) used to match inbound replies/reactions; a blank `destination`
99
+ * id (`messageId`) used to match inbound replies; a blank `destination`
104
100
  * is recorded as unknown rather than persisting the literal channel name as a
105
101
  * chat id. Status has two readers: the voice guardian-action sweep acts only
106
102
  * on `sent`/`pending` rows, and card withdrawal skips rows already marked
@@ -13,13 +13,11 @@
13
13
  * daemon updates it wherever `withdrawGuardianRequestCards` settles the
14
14
  * other surfaces.
15
15
  */
16
- import { z } from "zod";
17
16
 
18
17
  import {
19
18
  type FeedItem,
20
19
  type FeedItemGuardianIntent,
21
20
  type FeedItemGuardianRequest,
22
- FeedItemGuardianStatusSchema,
23
21
  isPendingGuardianFeedItem,
24
22
  } from "../api/responses/home.js";
25
23
  import {
@@ -47,19 +45,6 @@ import { readPayloadString } from "./notification-utils.js";
47
45
 
48
46
  const log = getLogger("guardian-feed-projection");
49
47
 
50
- // The wire enum in `api/responses/home.ts` cannot import the gateway
51
- // contract (the api directory is copied verbatim into client packages),
52
- // so it mirrors `GuardianRequestStatusSchema` by value. This assignment
53
- // fails to compile if the enums ever diverge in either direction.
54
- type FeedStatus = z.infer<typeof FeedItemGuardianStatusSchema>;
55
- type StatusEnumsAligned = [GuardianRequestStatus] extends [FeedStatus]
56
- ? [FeedStatus] extends [GuardianRequestStatus]
57
- ? true
58
- : never
59
- : never;
60
- const _statusEnumsAligned: StatusEnumsAligned = true;
61
- void _statusEnumsAligned;
62
-
63
48
  /**
64
49
  * The signal events that carry a guardian request. `guardian.question`
65
50
  * covers tool approvals, tool grants, and questions; access requests