@vellumai/assistant 0.9.1-staging.1 → 0.10.0-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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -2,8 +2,13 @@
2
2
  * NotificationSignal -- the flexible input from producers.
3
3
  * Uses free-form event names and structured attention hints that let the
4
4
  * decision engine route contextually.
5
+ *
6
+ * All data shapes are defined as Zod schemas — types are derived via
7
+ * `z.infer` so runtime validation and compile-time types stay in sync.
5
8
  */
6
9
 
10
+ import { z } from "zod";
11
+
7
12
  import type { ConversationCreateType } from "../memory/conversation-crud.js";
8
13
  import type { GuardianQuestionPayload } from "./guardian-question-mode.js";
9
14
 
@@ -14,7 +19,11 @@ export const NOTIFICATION_SOURCE_CHANNELS = [
14
19
  { id: "vellum", description: "Vellum native client (macOS/iOS)" },
15
20
  { id: "phone", description: "Phone call pipeline" },
16
21
  { id: "telegram", description: "Telegram channel" },
22
+ { id: "whatsapp", description: "WhatsApp channel" },
17
23
  { id: "slack", description: "Slack channel" },
24
+ { id: "email", description: "Email channel" },
25
+ { id: "platform", description: "Platform-managed channel" },
26
+ { id: "a2a", description: "Agent-to-agent protocol channel" },
18
27
  { id: "scheduler", description: "Scheduled task runner (reminders, cron)" },
19
28
  { id: "watcher", description: "File/event watcher subsystem" },
20
29
  ] as const;
@@ -22,6 +31,18 @@ export const NOTIFICATION_SOURCE_CHANNELS = [
22
31
  export type NotificationSourceChannel =
23
32
  (typeof NOTIFICATION_SOURCE_CHANNELS)[number]["id"];
24
33
 
34
+ /** Typed tuple of all source channel IDs — usable with `z.enum()`. */
35
+ export const NOTIFICATION_SOURCE_CHANNEL_IDS = NOTIFICATION_SOURCE_CHANNELS.map(
36
+ (c) => c.id,
37
+ ) as unknown as readonly [
38
+ NotificationSourceChannel,
39
+ ...NotificationSourceChannel[],
40
+ ];
41
+
42
+ export const NotificationSourceChannelSchema = z.enum(
43
+ NOTIFICATION_SOURCE_CHANNEL_IDS,
44
+ );
45
+
25
46
  export function isNotificationSourceChannel(
26
47
  value: unknown,
27
48
  ): value is NotificationSourceChannel {
@@ -114,15 +135,24 @@ export type NotificationSourceEventName =
114
135
 
115
136
  // ── Attention hints & routing ──────────────────────────────────────────
116
137
 
117
- export interface AttentionHints {
118
- requiresAction: boolean;
119
- urgency: "low" | "medium" | "high" | "critical";
120
- deadlineAt?: number; // epoch ms
121
- isAsyncBackground: boolean;
122
- visibleInSourceNow: boolean;
123
- }
138
+ export const UrgencySchema = z.enum(["low", "medium", "high", "critical"]);
139
+ export type Urgency = z.infer<typeof UrgencySchema>;
124
140
 
125
- export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
141
+ export const AttentionHintsSchema = z.object({
142
+ requiresAction: z.boolean(),
143
+ urgency: UrgencySchema,
144
+ deadlineAt: z.number().optional(),
145
+ isAsyncBackground: z.boolean(),
146
+ visibleInSourceNow: z.boolean(),
147
+ });
148
+ export type AttentionHints = z.infer<typeof AttentionHintsSchema>;
149
+
150
+ export const RoutingIntentSchema = z.enum([
151
+ "single_channel",
152
+ "multi_channel",
153
+ "all_channels",
154
+ ]);
155
+ export type RoutingIntent = z.infer<typeof RoutingIntentSchema>;
126
156
 
127
157
  // ── Typed context payloads ──────────────────────────────────────────────
128
158
 
@@ -140,42 +170,48 @@ export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
140
170
  * This is channel-agnostic by design — any channel's access request that
141
171
  * resolves to a non-source-channel guardian gets the same treatment.
142
172
  */
143
- export type GuardianResolutionSource =
144
- | "source-channel-contact"
145
- | "vellum-anchor"
146
- | "none";
147
-
148
- export interface AccessRequestContextPayload {
149
- requestId: string;
150
- requestCode: string;
151
- sourceChannel: string;
152
- conversationExternalId: string;
153
- actorExternalId: string;
154
- actorDisplayName: string | null;
155
- actorUsername: string | null;
156
- senderIdentifier: string;
157
- guardianBindingChannel: string | null;
158
- guardianResolutionSource: GuardianResolutionSource;
159
- previousMemberStatus: string | null;
160
- /** Preview of the requester's original message (first ~200 chars). */
161
- messagePreview: string | null;
162
- /** Slack-specific: user is from an external workspace (Slack Connect). */
163
- isStranger?: boolean;
164
- /** Slack-specific: user is a guest / restricted account. */
165
- isRestricted?: boolean;
166
- /** Slack message timestamp (e.g. "1234567890.123456") for permalink construction. */
167
- messageTs?: string;
168
- }
173
+ export const GuardianResolutionSourceSchema = z.enum([
174
+ "source-channel-contact",
175
+ "vellum-anchor",
176
+ "none",
177
+ ]);
178
+ export type GuardianResolutionSource = z.infer<
179
+ typeof GuardianResolutionSourceSchema
180
+ >;
169
181
 
170
- export interface GuardianChannelActivationPayload {
171
- verificationCode: string;
172
- sourceChannel: string;
173
- actorExternalId: string;
174
- actorDisplayName: string | null;
175
- actorUsername: string | null;
176
- sessionId: string;
177
- expiresAt: number;
178
- }
182
+ export const AccessRequestContextPayloadSchema = z.object({
183
+ requestId: z.string(),
184
+ requestCode: z.string(),
185
+ sourceChannel: z.string(),
186
+ conversationExternalId: z.string(),
187
+ actorExternalId: z.string(),
188
+ actorDisplayName: z.string().nullable(),
189
+ actorUsername: z.string().nullable(),
190
+ senderIdentifier: z.string(),
191
+ guardianBindingChannel: z.string().nullable(),
192
+ guardianResolutionSource: GuardianResolutionSourceSchema,
193
+ previousMemberStatus: z.string().nullable(),
194
+ messagePreview: z.string().nullable(),
195
+ isStranger: z.boolean().optional(),
196
+ isRestricted: z.boolean().optional(),
197
+ messageTs: z.string().optional(),
198
+ });
199
+ export type AccessRequestContextPayload = z.infer<
200
+ typeof AccessRequestContextPayloadSchema
201
+ >;
202
+
203
+ export const GuardianChannelActivationPayloadSchema = z.object({
204
+ verificationCode: z.string(),
205
+ sourceChannel: z.string(),
206
+ actorExternalId: z.string(),
207
+ actorDisplayName: z.string().nullable(),
208
+ actorUsername: z.string().nullable(),
209
+ sessionId: z.string(),
210
+ expiresAt: z.number(),
211
+ });
212
+ export type GuardianChannelActivationPayload = z.infer<
213
+ typeof GuardianChannelActivationPayloadSchema
214
+ >;
179
215
 
180
216
  export interface NotificationEventContextPayloadMap {
181
217
  "guardian.question": GuardianQuestionPayload;
@@ -1,15 +1,19 @@
1
1
  /**
2
2
  * Core domain types for the unified notification system.
3
3
  *
4
- * Defines the channel-adapter interfaces that the broadcaster and adapters
5
- * depend on, plus the decision engine output contract.
4
+ * Data shapes are defined as Zod schemas types derived via `z.infer`
5
+ * so runtime validation and compile-time types stay in sync.
6
+ *
7
+ * Behavioral interfaces (ChannelAdapter) remain as TypeScript interfaces.
6
8
  */
7
9
 
10
+ import { ApprovalUIMetadataSchema } from "@vellumai/gateway-client";
11
+ import { z } from "zod";
12
+
8
13
  import type { ChannelPolicies } from "../channels/config.js";
9
14
  import type { ChannelId } from "../channels/types.js";
10
- import type { ApprovalUIMetadata } from "../runtime/channel-approval-types.js";
11
- import type { ParsedAccessRequestPayload } from "./access-request-copy.js";
12
- import type { AttentionHints } from "./signal.js";
15
+ import { AccessRequestPayloadSchema } from "./access-request-copy.js";
16
+ import { UrgencySchema } from "./signal.js";
13
17
 
14
18
  /**
15
19
  * Derived from the channel policy registry: only channels whose
@@ -22,38 +26,39 @@ export type NotificationChannel = {
22
26
  }[keyof ChannelPolicies] &
23
27
  ChannelId;
24
28
 
25
- export type NotificationDeliveryStatus =
26
- | "pending"
27
- | "sent"
28
- | "failed"
29
- | "skipped";
30
-
31
- /** Result of attempting to deliver a notification to a single channel. */
32
- export interface NotificationDeliveryResult {
33
- channel: NotificationChannel;
34
- destination: string;
35
- status: NotificationDeliveryStatus;
36
- errorCode?: string;
37
- errorMessage?: string;
38
- sentAt?: number;
39
- conversationId?: string;
40
- messageId?: string;
41
- conversationStrategy?: string;
42
- }
43
-
44
- // -- Channel adapter interfaces -----------------------------------------------
45
-
46
- /** Result returned by a channel adapter after attempting to send. */
47
- export interface DeliveryResult {
48
- success: boolean;
49
- error?: string;
50
- /**
51
- * Channel-native message identifier captured at send time (e.g. Slack `ts`).
52
- * Persisted onto `notification_deliveries.messageId` so later edits can
53
- * target the same message via `ChannelAdapter.update()`.
54
- */
55
- messageId?: string;
56
- }
29
+ export const NotificationDeliveryStatusSchema = z.enum([
30
+ "pending",
31
+ "sent",
32
+ "failed",
33
+ "skipped",
34
+ ]);
35
+ export type NotificationDeliveryStatus = z.infer<
36
+ typeof NotificationDeliveryStatusSchema
37
+ >;
38
+
39
+ export const NotificationDeliveryResultSchema = z.object({
40
+ channel: z.string(),
41
+ destination: z.string(),
42
+ status: NotificationDeliveryStatusSchema,
43
+ errorCode: z.string().optional(),
44
+ errorMessage: z.string().optional(),
45
+ sentAt: z.number().optional(),
46
+ conversationId: z.string().optional(),
47
+ messageId: z.string().optional(),
48
+ conversationStrategy: z.string().optional(),
49
+ });
50
+ export type NotificationDeliveryResult = z.infer<
51
+ typeof NotificationDeliveryResultSchema
52
+ >;
53
+
54
+ // -- Channel adapter data shapes ----------------------------------------------
55
+
56
+ export const DeliveryResultSchema = z.object({
57
+ success: z.boolean(),
58
+ error: z.string().optional(),
59
+ messageId: z.string().optional(),
60
+ });
61
+ export type DeliveryResult = z.infer<typeof DeliveryResultSchema>;
57
62
 
58
63
  /** Resolved destination for a specific channel. */
59
64
  export interface ChannelDestination {
@@ -70,58 +75,44 @@ export interface ChannelDestination {
70
75
  * conversations keyed by (sourceChannel, externalChatId).
71
76
  */
72
77
  export interface DestinationBindingContext {
73
- /** The channel this binding belongs to (e.g. "telegram", "slack"). */
74
78
  sourceChannel: NotificationChannel;
75
- /** The channel-specific chat/conversation identifier (e.g. Telegram chat ID, phone number). */
76
79
  externalChatId: string;
77
- /** Optional external user identifier within the chat. */
78
80
  externalUserId?: string;
79
81
  }
80
82
 
81
- /**
82
- * Delivery payload assembled from the decision engine's rendered copy
83
- * plus contextual fields the adapters need for formatting and routing.
84
- */
85
- export interface ChannelDeliveryPayload {
86
- /** Delivery audit record ID — passed through to the client for ack correlation. */
87
- deliveryId?: string;
88
- sourceEventName: string;
89
- copy: RenderedChannelCopy;
90
- deepLinkTarget?: Record<string, unknown>;
91
- /** Original signal context payload — available for channel-specific structured rendering. */
92
- contextPayload?: Record<string, unknown>;
93
- /**
94
- * Forwarded from the originating signal so adapters can make
95
- * urgency-aware decisions (e.g. the vellum adapter suppresses the OS
96
- * banner for non-urgent intents while still emitting the conversation
97
- * pairing side effects).
98
- */
99
- urgency: AttentionHints["urgency"];
100
- /**
101
- * Structured approval context for notifications that require guardian
102
- * action buttons (approve/reject). Built centrally by the broadcaster
103
- * so adapters can render channel-native approval UI without re-parsing
104
- * `contextPayload`.
105
- */
106
- approvalContext?: ApprovalUIMetadata;
107
- /**
108
- * Pre-parsed access request context for `ingress.access_request` signals.
109
- * Built centrally by the broadcaster so adapters can render structured
110
- * access-request cards without re-parsing `contextPayload`.
111
- */
112
- accessRequestContext?: ParsedAccessRequestPayload;
113
- }
83
+ // -- Rendered copy & delivery payload -----------------------------------------
84
+
85
+ export const RenderedChannelCopySchema = z.object({
86
+ title: z.string(),
87
+ body: z.string(),
88
+ deliveryText: z.string().optional(),
89
+ conversationTitle: z.string().optional(),
90
+ conversationSeedMessage: z.string().optional(),
91
+ seedContentBlocks: z.array(z.unknown()).optional(),
92
+ });
93
+ export type RenderedChannelCopy = z.infer<typeof RenderedChannelCopySchema>;
94
+
95
+ export const ChannelDeliveryPayloadSchema = z.object({
96
+ deliveryId: z.string().optional(),
97
+ sourceEventName: z.string(),
98
+ copy: RenderedChannelCopySchema,
99
+ deepLinkTarget: z.record(z.string(), z.unknown()).optional(),
100
+ contextPayload: z.record(z.string(), z.unknown()).optional(),
101
+ urgency: UrgencySchema,
102
+ approvalContext: ApprovalUIMetadataSchema.optional(),
103
+ accessRequestContext: AccessRequestPayloadSchema.optional(),
104
+ });
105
+ export type ChannelDeliveryPayload = z.infer<
106
+ typeof ChannelDeliveryPayloadSchema
107
+ >;
114
108
 
115
- /**
116
- * Patch supplied when an already-delivered notification is edited.
117
- * Adapters only need to act on `title` and `body` — feed-only fields
118
- * like `urgency` and `status` are handled by the home-feed patch.
119
- */
120
109
  export interface ChannelUpdatePayload {
121
110
  title?: string;
122
111
  body?: string;
123
112
  }
124
113
 
114
+ // -- Channel adapter interface ------------------------------------------------
115
+
125
116
  /** Interface that each channel adapter must implement. */
126
117
  export interface ChannelAdapter {
127
118
  channel: NotificationChannel;
@@ -129,77 +120,57 @@ export interface ChannelAdapter {
129
120
  payload: ChannelDeliveryPayload,
130
121
  destination: ChannelDestination,
131
122
  ): Promise<DeliveryResult>;
132
- /**
133
- * Optional: edit a previously-sent message in place. Channels that
134
- * cannot edit (push, email, SMS) omit this and the router treats
135
- * them as `"unsupported"`. Adapters that implement it use
136
- * `delivery.messageId` (captured at send time) as the channel-native
137
- * handle for the in-place update.
138
- */
139
123
  update?(
140
124
  delivery: ChannelUpdateContext,
141
125
  patch: ChannelUpdatePayload,
142
126
  ): Promise<DeliveryResult>;
143
127
  }
144
128
 
145
- /** Per-delivery state an adapter needs to update a previously-sent message. */
146
129
  export interface ChannelUpdateContext {
147
- /** notification_deliveries.id */
148
130
  deliveryId: string;
149
- /** Channel-native destination (e.g. Slack chat id). */
150
131
  destination: string;
151
- /** Channel-native message identifier captured at send time. */
152
132
  messageId: string | null;
153
133
  }
154
134
 
155
- // -- Decision engine output ---------------------------------------------------
156
-
157
- /** Rendered notification copy for a single channel. */
158
- export interface RenderedChannelCopy {
159
- title: string;
160
- body: string;
161
- /** Channel-native delivery text (e.g. Telegram chat message body). */
162
- deliveryText?: string;
163
- conversationTitle?: string;
164
- conversationSeedMessage?: string;
165
- /**
166
- * Structured content blocks for the seed message. When present,
167
- * conversation pairing stores `JSON.stringify(seedContentBlocks)` as the
168
- * message content instead of the plain-text seed, enabling Surface
169
- * rendering in the web/macOS/iOS apps.
170
- *
171
- * Typically includes a `ui_surface` block (rendered as an interactive
172
- * card) followed by a `text` block (plain-text fallback for search,
173
- * backward-compatible clients, and CLI display).
174
- */
175
- seedContentBlocks?: unknown[];
176
- }
177
-
178
135
  // -- Conversation action types ------------------------------------------------
179
136
 
180
- /** Start a new conversation for the notification delivery. */
181
- export interface ConversationActionStartNew {
182
- action: "start_new";
183
- }
137
+ export const ConversationActionSchema = z.discriminatedUnion("action", [
138
+ z.object({ action: z.literal("start_new") }),
139
+ z.object({
140
+ action: z.literal("reuse_existing"),
141
+ conversationId: z.string(),
142
+ }),
143
+ ]);
144
+ export type ConversationAction = z.infer<typeof ConversationActionSchema>;
184
145
 
185
- /** Reuse an existing conversation identified by conversationId. */
186
- export interface ConversationActionReuseExisting {
187
- action: "reuse_existing";
188
- conversationId: string;
189
- }
146
+ // -- Decision engine output ---------------------------------------------------
190
147
 
191
- /** Per-channel conversation action — either start a new conversation or reuse an existing one. */
192
- export type ConversationAction =
193
- | ConversationActionStartNew
194
- | ConversationActionReuseExisting;
148
+ export const NotificationDecisionSchema = z.object({
149
+ shouldNotify: z.boolean(),
150
+ selectedChannels: z.array(z.string()),
151
+ reasoningSummary: z.string(),
152
+ renderedCopy: z.record(z.string(), RenderedChannelCopySchema),
153
+ conversationActions: z
154
+ .record(z.string(), ConversationActionSchema)
155
+ .optional(),
156
+ deepLinkTarget: z.record(z.string(), z.unknown()).optional(),
157
+ dedupeKey: z.string(),
158
+ confidence: z.number(),
159
+ fallbackUsed: z.boolean(),
160
+ persistedDecisionId: z.string().optional(),
161
+ });
195
162
 
196
- /** Output produced by the notification decision engine for a given signal. */
163
+ /**
164
+ * Decision engine output. `selectedChannels` and `renderedCopy` are keyed
165
+ * by `NotificationChannel` — narrower than the Zod schema's `string` since
166
+ * `NotificationChannel` is a computed type derived from the channel config
167
+ * registry and cannot be expressed as a Zod enum.
168
+ */
197
169
  export interface NotificationDecision {
198
170
  shouldNotify: boolean;
199
171
  selectedChannels: NotificationChannel[];
200
172
  reasoningSummary: string;
201
173
  renderedCopy: Partial<Record<NotificationChannel, RenderedChannelCopy>>;
202
- /** Per-channel conversation actions decided by the model. Absent channels default to start_new. */
203
174
  conversationActions?: Partial<
204
175
  Record<NotificationChannel, ConversationAction>
205
176
  >;
@@ -207,6 +178,5 @@ export interface NotificationDecision {
207
178
  dedupeKey: string;
208
179
  confidence: number;
209
180
  fallbackUsed: boolean;
210
- /** UUID of the persisted decision row (set after persistence in the decision engine). */
211
181
  persistedDecisionId?: string;
212
182
  }
@@ -125,6 +125,16 @@ mock.module("../ipc/gateway-client.js", () => ({
125
125
 
126
126
  // ── Import the module under test AFTER mocks are set up ──────────────────────
127
127
 
128
+ import {
129
+ mkdtempSync,
130
+ rmSync,
131
+ symlinkSync,
132
+ unlinkSync,
133
+ writeFileSync,
134
+ } from "node:fs";
135
+ import { tmpdir } from "node:os";
136
+ import { join } from "node:path";
137
+
128
138
  import {
129
139
  check,
130
140
  classifyRisk,
@@ -231,6 +241,47 @@ describe("Permission Checker (gateway IPC)", () => {
231
241
  expect(result2.reason).toBe("Cached test");
232
242
  });
233
243
 
244
+ test("file-tool cache misses when a symlink target is retargeted", async () => {
245
+ // File risk depends on filesystem state: the cache key folds in the
246
+ // symlink-resolved target, so the same raw input must NOT return a stale
247
+ // cached result after the symlink is pointed somewhere new.
248
+ const dir = mkdtempSync(join(tmpdir(), "risk-cache-symlink-"));
249
+ try {
250
+ const benign = join(dir, "benign.txt");
251
+ const other = join(dir, "other.txt");
252
+ writeFileSync(benign, "ok");
253
+ writeFileSync(other, "ok");
254
+ const link = join(dir, "link.txt");
255
+ symlinkSync(benign, link);
256
+
257
+ mockIpcClassifyRiskResult = {
258
+ risk: "low",
259
+ reason: "benign",
260
+ matchType: "registry",
261
+ scopeOptions: [],
262
+ };
263
+ const first = await classifyRisk("file_read", { path: link });
264
+ expect(first.level).toBe(RiskLevel.Low);
265
+
266
+ // Retarget the symlink to a different real file; raw input unchanged.
267
+ unlinkSync(link);
268
+ symlinkSync(other, link);
269
+
270
+ mockIpcClassifyRiskResult = {
271
+ risk: "high",
272
+ reason: "now sensitive",
273
+ matchType: "registry",
274
+ scopeOptions: [],
275
+ };
276
+ const second = await classifyRisk("file_read", { path: link });
277
+ // Cache must have missed and re-classified against the new target.
278
+ expect(second.level).toBe(RiskLevel.High);
279
+ expect(second.reason).toBe("now sensitive");
280
+ } finally {
281
+ rmSync(dir, { recursive: true, force: true });
282
+ }
283
+ });
284
+
234
285
  test("preserves commandCandidates from gateway response", async () => {
235
286
  mockIpcClassifyRiskResult = {
236
287
  risk: "low",