@vellumai/assistant 0.9.0 → 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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Tests for the inbound admission-floor enforcement in `routeSetup`.
3
+ *
4
+ * `routeSetup` is pure routing logic but reads several module-level singletons
5
+ * (config, trust resolver, pending verification session, invite store). These
6
+ * are mocked so the table below can drive trust class, member status, pending
7
+ * challenge, and active invites independently of any DB.
8
+ *
9
+ * The floor verdict is exercised through the REAL `enforceAdmissionPolicy`
10
+ * floor tables (`TRUST_CLASS_RANK` × `ADMISSION_FLOOR`), with the exempt-channel
11
+ * short-circuit bypassed in the mock so the tests assert the true floor
12
+ * semantics independent of any channel's exempt status (`phone` is now
13
+ * enforced).
14
+ */
15
+
16
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
17
+
18
+ import {
19
+ ADMISSION_FLOOR,
20
+ type AdmissionPolicy,
21
+ } from "@vellumai/gateway-client";
22
+
23
+ import type {
24
+ ChannelPolicy,
25
+ ChannelStatus,
26
+ } from "../../contacts/types.js";
27
+ import type {
28
+ ActorTrustContext,
29
+ TrustClass,
30
+ } from "../../runtime/actor-trust-resolver.js";
31
+ import { TRUST_CLASS_RANK } from "../../runtime/actor-trust-resolver.js";
32
+
33
+ mock.module("../../util/logger.js", () => ({
34
+ getLogger: () =>
35
+ new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
36
+ }));
37
+
38
+ mock.module("../../config/loader.js", () => ({
39
+ getConfig: () => ({ calls: { verification: { enabled: false } } }),
40
+ }));
41
+
42
+ // Controllable resolved trust context.
43
+ let nextTrust: ActorTrustContext;
44
+ mock.module("../../runtime/actor-trust-resolver.js", () => ({
45
+ resolveActorTrust: () => nextTrust,
46
+ // Re-export the real rank table; the floor mock below consumes it.
47
+ TRUST_CLASS_RANK,
48
+ }));
49
+
50
+ // Controllable pending verification challenge.
51
+ let pendingChallenge: unknown = null;
52
+ mock.module("../../runtime/channel-verification-service.js", () => ({
53
+ getPendingSession: () => pendingChallenge,
54
+ }));
55
+
56
+ // Controllable active voice invites.
57
+ let activeInvites: Array<{
58
+ friendName: string | null;
59
+ guardianName: string | null;
60
+ expiresAt?: number;
61
+ }> = [];
62
+ mock.module("../../memory/invite-store.js", () => ({
63
+ findActiveVoiceInvites: () => activeInvites,
64
+ }));
65
+
66
+ // Real floor semantics, exemption bypassed (see file header).
67
+ mock.module(
68
+ "../../runtime/routes/inbound-stages/admission-policy.js",
69
+ () => ({
70
+ enforceAdmissionPolicy: (input: {
71
+ trustClass: TrustClass;
72
+ memberStatus: ChannelStatus | undefined;
73
+ policy: AdmissionPolicy;
74
+ }) => {
75
+ if (input.memberStatus === "blocked" || input.memberStatus === "revoked") {
76
+ return {
77
+ admitted: false,
78
+ reason:
79
+ input.memberStatus === "blocked"
80
+ ? "member_blocked"
81
+ : "member_revoked",
82
+ shouldChallenge: false,
83
+ effectivePolicy: input.policy,
84
+ };
85
+ }
86
+ const rank = TRUST_CLASS_RANK[input.trustClass];
87
+ const floor = ADMISSION_FLOOR[input.policy];
88
+ if (rank >= floor) return { admitted: true };
89
+ return {
90
+ admitted: false,
91
+ reason: `admission_policy_${input.policy}`,
92
+ shouldChallenge: false,
93
+ effectivePolicy: input.policy,
94
+ };
95
+ },
96
+ }),
97
+ );
98
+
99
+ const { routeSetup } = await import("../relay-setup-router.js");
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Fixtures
103
+ // ---------------------------------------------------------------------------
104
+
105
+ function makeTrust(
106
+ trustClass: TrustClass,
107
+ channel?: { status: ChannelStatus; policy?: ChannelPolicy; role?: string },
108
+ ): ActorTrustContext {
109
+ const memberRecord = channel
110
+ ? {
111
+ contact: {
112
+ displayName: "Test Caller",
113
+ role: channel.role ?? "trusted_contact",
114
+ } as never,
115
+ channel: {
116
+ id: "ch_1",
117
+ status: channel.status,
118
+ policy: channel.policy ?? "allow",
119
+ } as never,
120
+ }
121
+ : null;
122
+ return {
123
+ canonicalSenderId: "+15551234567",
124
+ guardianBindingMatch: null,
125
+ memberRecord,
126
+ trustClass,
127
+ actorMetadata: {
128
+ identifier: "+15551234567",
129
+ displayName: undefined,
130
+ senderDisplayName: undefined,
131
+ memberDisplayName: undefined,
132
+ username: undefined,
133
+ channel: "phone",
134
+ trustStatus: trustClass,
135
+ },
136
+ } as ActorTrustContext;
137
+ }
138
+
139
+ function route(admissionPolicy?: AdmissionPolicy | null) {
140
+ return routeSetup({
141
+ callSessionId: "cs_1",
142
+ session: null, // inbound
143
+ from: "+15551234567",
144
+ to: "+15559999999",
145
+ admissionPolicy,
146
+ });
147
+ }
148
+
149
+ beforeEach(() => {
150
+ pendingChallenge = null;
151
+ activeInvites = [];
152
+ });
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Floor table: trustClass × policy → admit/deny
156
+ // ---------------------------------------------------------------------------
157
+
158
+ describe("routeSetup — admission floor table", () => {
159
+ // For "known" classes (trusted_contact, guardian) the resolver yields a
160
+ // member channel; unknown/unverified flow through the unknown ACL branch.
161
+ function setTrust(trustClass: TrustClass) {
162
+ if (trustClass === "guardian") {
163
+ nextTrust = makeTrust("guardian", {
164
+ status: "active",
165
+ role: "guardian",
166
+ });
167
+ } else if (trustClass === "trusted_contact") {
168
+ nextTrust = makeTrust("trusted_contact", { status: "active" });
169
+ } else if (trustClass === "unverified_contact") {
170
+ nextTrust = makeTrust("unverified_contact", { status: "unverified" });
171
+ } else {
172
+ nextTrust = makeTrust("unknown");
173
+ }
174
+ }
175
+
176
+ const cases: Array<{
177
+ policy: AdmissionPolicy;
178
+ admits: TrustClass[];
179
+ denies: TrustClass[];
180
+ }> = [
181
+ {
182
+ policy: "strangers",
183
+ admits: ["unknown", "unverified_contact", "trusted_contact", "guardian"],
184
+ denies: [],
185
+ },
186
+ {
187
+ policy: "any_contact",
188
+ admits: ["unverified_contact", "trusted_contact", "guardian"],
189
+ denies: ["unknown"],
190
+ },
191
+ {
192
+ policy: "trusted_contacts",
193
+ admits: ["trusted_contact", "guardian"],
194
+ denies: ["unknown", "unverified_contact"],
195
+ },
196
+ {
197
+ policy: "guardian_only",
198
+ admits: ["guardian"],
199
+ denies: ["unknown", "unverified_contact", "trusted_contact"],
200
+ },
201
+ {
202
+ policy: "no_one",
203
+ admits: [],
204
+ denies: ["unknown", "unverified_contact", "trusted_contact", "guardian"],
205
+ },
206
+ ];
207
+
208
+ for (const { policy, admits, denies } of cases) {
209
+ for (const trustClass of denies) {
210
+ test(`${policy} denies ${trustClass}`, () => {
211
+ setTrust(trustClass);
212
+ const { outcome } = route(policy);
213
+ expect(outcome.action).toBe("deny");
214
+ if (outcome.action === "deny") {
215
+ expect(outcome.logReason).toBe(
216
+ `Inbound voice admission floor: ${policy}`,
217
+ );
218
+ }
219
+ });
220
+ }
221
+ for (const trustClass of admits) {
222
+ test(`${policy} admits ${trustClass}`, () => {
223
+ setTrust(trustClass);
224
+ const { outcome } = route(policy);
225
+ expect(outcome.action).not.toBe("deny");
226
+ });
227
+ }
228
+ }
229
+ });
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // Blocked / revoked always deny
233
+ // ---------------------------------------------------------------------------
234
+
235
+ describe("routeSetup — blocked / revoked", () => {
236
+ test("blocked caller is denied (pre-floor ACL check)", () => {
237
+ nextTrust = makeTrust("unknown", { status: "blocked" });
238
+ const { outcome } = route("strangers");
239
+ expect(outcome.action).toBe("deny");
240
+ });
241
+
242
+ test("revoked member is denied under permissive floor", () => {
243
+ // Revoked → resolver classifies as unknown; the floor mock denies on
244
+ // memberStatus regardless of the permissive policy.
245
+ nextTrust = makeTrust("unknown", { status: "revoked" });
246
+ const { outcome } = route("strangers");
247
+ expect(outcome.action).toBe("deny");
248
+ });
249
+ });
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // admissionPolicy null/undefined → current behavior unchanged
253
+ // ---------------------------------------------------------------------------
254
+
255
+ describe("routeSetup — no policy preserves current behavior", () => {
256
+ test("unknown caller → name_capture", () => {
257
+ nextTrust = makeTrust("unknown");
258
+ expect(route(null).outcome.action).toBe("name_capture");
259
+ expect(route(undefined).outcome.action).toBe("name_capture");
260
+ });
261
+
262
+ test("unverified known caller → unverified_caller", () => {
263
+ nextTrust = makeTrust("unverified_contact", { status: "unverified" });
264
+ expect(route(null).outcome.action).toBe("unverified_caller");
265
+ });
266
+
267
+ test("trusted contact → normal_call", () => {
268
+ nextTrust = makeTrust("trusted_contact", { status: "active" });
269
+ expect(route(null).outcome.action).toBe("normal_call");
270
+ });
271
+
272
+ test("guardian → normal_call", () => {
273
+ nextTrust = makeTrust("guardian", { status: "active", role: "guardian" });
274
+ expect(route(null).outcome.action).toBe("normal_call");
275
+ });
276
+
277
+ test("member policy deny still denies", () => {
278
+ nextTrust = makeTrust("trusted_contact", {
279
+ status: "active",
280
+ policy: "deny",
281
+ });
282
+ expect(route(null).outcome.action).toBe("deny");
283
+ });
284
+ });
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // Active permissive floor admits straight to a normal call
288
+ // ---------------------------------------------------------------------------
289
+ // When a policy is ACTIVELY set and admits the caller, the floor is the access
290
+ // decision: unknown/unverified callers bypass name_capture / unverified_caller
291
+ // and connect directly. With a null policy the legacy identity flows persist.
292
+
293
+ describe("routeSetup — permissive floor admits to normal_call", () => {
294
+ test("any_contact admits an unverified_contact to normal_call (not unverified_caller)", () => {
295
+ nextTrust = makeTrust("unverified_contact", { status: "unverified" });
296
+ const { outcome } = route("any_contact");
297
+ expect(outcome.action).toBe("normal_call");
298
+ });
299
+
300
+ test("strangers admits an unknown caller to normal_call (not name_capture)", () => {
301
+ nextTrust = makeTrust("unknown");
302
+ const { outcome } = route("strangers");
303
+ expect(outcome.action).toBe("normal_call");
304
+ });
305
+
306
+ test("strangers admits an unverified_contact to normal_call", () => {
307
+ nextTrust = makeTrust("unverified_contact", { status: "unverified" });
308
+ const { outcome } = route("strangers");
309
+ expect(outcome.action).toBe("normal_call");
310
+ });
311
+
312
+ test("null policy preserves legacy name_capture for unknown caller", () => {
313
+ nextTrust = makeTrust("unknown");
314
+ expect(route(null).outcome.action).toBe("name_capture");
315
+ });
316
+
317
+ test("null policy preserves legacy unverified_caller for unverified caller", () => {
318
+ nextTrust = makeTrust("unverified_contact", { status: "unverified" });
319
+ expect(route(null).outcome.action).toBe("unverified_caller");
320
+ });
321
+
322
+ test("trusted_contacts (default) still denies unknown and unverified", () => {
323
+ nextTrust = makeTrust("unknown");
324
+ expect(route("trusted_contacts").outcome.action).toBe("deny");
325
+ nextTrust = makeTrust("unverified_contact", { status: "unverified" });
326
+ expect(route("trusted_contacts").outcome.action).toBe("deny");
327
+ });
328
+ });
329
+
330
+ // ---------------------------------------------------------------------------
331
+ // Invites & pending challenges bypass the floor
332
+ // ---------------------------------------------------------------------------
333
+
334
+ describe("routeSetup — floor bypasses", () => {
335
+ test("active voice invite bypasses the floor (no_one policy)", () => {
336
+ nextTrust = makeTrust("unknown");
337
+ activeInvites = [{ friendName: "Friend", guardianName: "Guardian" }];
338
+ const { outcome } = route("no_one");
339
+ expect(outcome.action).toBe("invite_redemption");
340
+ });
341
+
342
+ test("pending verification challenge bypasses the floor (no_one policy)", () => {
343
+ // A pending challenge for a known member routes to verification regardless
344
+ // of the floor.
345
+ nextTrust = makeTrust("trusted_contact", { status: "active" });
346
+ pendingChallenge = { id: "vs_1" };
347
+ const { outcome } = route("no_one");
348
+ expect(outcome.action).toBe("verification");
349
+ });
350
+ });
@@ -78,7 +78,8 @@ export function resetPointerMessageProcessor(): void {
78
78
  * Resolve whether the audience for a pointer message is trusted.
79
79
  *
80
80
  * Trusted when:
81
- * - recent message provenance trust class is 'guardian' or 'trusted_contact'
81
+ * - recent message provenance trust class is 'guardian', 'trusted_contact',
82
+ * or 'unverified_contact'
82
83
  * - conversation origin channel is 'vellum' (desktop app)
83
84
  *
84
85
  * Untrusted by default when insufficient evidence.
@@ -86,11 +87,16 @@ export function resetPointerMessageProcessor(): void {
86
87
  function resolvePointerAudienceTrust(conversationId: string): boolean {
87
88
  try {
88
89
  // Check provenance trust class on recent messages first — this catches
89
- // trusted contacts who initiate calls from gateway channels (e.g. WhatsApp)
90
- // where the conversation itself isn't desktop-origin.
90
+ // identity-known non-guardian contacts (trusted and unverified) who
91
+ // initiate calls from gateway channels (e.g. WhatsApp) where the
92
+ // conversation itself isn't desktop-origin.
91
93
  const provenance =
92
94
  getConversationRecentProvenanceTrustClass(conversationId);
93
- if (provenance === "guardian" || provenance === "trusted_contact")
95
+ if (
96
+ provenance === "guardian" ||
97
+ provenance === "trusted_contact" ||
98
+ provenance === "unverified_contact"
99
+ )
94
100
  return true;
95
101
 
96
102
  const originChannel = getConversationOriginChannel(conversationId);
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Gateway-backed channel admission policy reader.
3
+ *
4
+ * Reads a channel's resolved admission floor from the gateway via the
5
+ * `get_channel_admission_policy` IPC route. Admission must never break call
6
+ * setup, so this reader FAILS OPEN: any transport failure, malformed shape,
7
+ * or thrown error resolves to `null` (= admit, no enforcement). A policy is
8
+ * only returned when the gateway responds with `{ policy: <valid policy> }`.
9
+ *
10
+ * Caches per channelType with a short TTL, mirroring the conversation
11
+ * threshold cache in `../permissions/gateway-threshold-reader.ts`. Only the
12
+ * result of a SUCCESSFUL gateway round-trip is cached (a valid policy, or an
13
+ * explicit `{ policy: null }` "no enforcement" answer); a transport failure /
14
+ * throw / malformed shape fails open to `null` WITHOUT caching, so a recovered
15
+ * gateway is re-consulted on the next call setup rather than skipping the
16
+ * admission floor for the rest of the TTL.
17
+ */
18
+
19
+ import {
20
+ type AdmissionPolicy,
21
+ isAdmissionPolicy,
22
+ } from "@vellumai/gateway-client";
23
+
24
+ import type { ChannelId } from "../channels/types.js";
25
+ import { ipcCall } from "../ipc/gateway-client.js";
26
+
27
+ const CACHE_TTL_MS = 5_000;
28
+
29
+ // Short IPC timeout so admission fails open PROMPTLY: a gateway that accepts
30
+ // the socket but stalls must never delay a live call handshake. 1s is generous
31
+ // under normal conditions yet far below ipcCall's 5s default.
32
+ const ADMISSION_IPC_TIMEOUT_MS = 1_000;
33
+
34
+ const cache = new Map<
35
+ ChannelId,
36
+ { policy: AdmissionPolicy | null; timestamp: number }
37
+ >();
38
+
39
+ /** Test-only: clear the policy cache. */
40
+ export function _clearCacheForTesting(): void {
41
+ cache.clear();
42
+ }
43
+
44
+ /**
45
+ * Outcome of a single gateway fetch. Only a SUCCESSFUL round-trip (a valid
46
+ * policy, or an explicit `{ policy: null }` "no enforcement" answer) is
47
+ * cacheable. A transport failure, thrown error, or malformed shape is NOT
48
+ * cached so a recovered gateway is re-consulted on the next call setup.
49
+ */
50
+ type FetchResult =
51
+ | { ok: true; policy: AdmissionPolicy | null }
52
+ | { ok: false };
53
+
54
+ async function fetchAdmissionPolicy(
55
+ channelType: ChannelId,
56
+ ): Promise<FetchResult> {
57
+ try {
58
+ // ipcCall() returns undefined on transport failure (socket not found,
59
+ // timeout, parse error). That, a throw, or a malformed shape is a FAILURE:
60
+ // fail open without caching so the next setup re-attempts the IPC.
61
+ const result = (await ipcCall(
62
+ "get_channel_admission_policy",
63
+ { channelType },
64
+ ADMISSION_IPC_TIMEOUT_MS,
65
+ )) as { policy?: unknown } | null | undefined;
66
+
67
+ if (result === undefined) return { ok: false };
68
+ if (result && isAdmissionPolicy(result.policy)) {
69
+ return { ok: true, policy: result.policy };
70
+ }
71
+ // Explicit "no enforcement" — the gateway successfully answered. Cacheable.
72
+ if (result && result.policy === null) return { ok: true, policy: null };
73
+ // Anything else (missing/invalid policy field) is a malformed shape.
74
+ return { ok: false };
75
+ } catch {
76
+ return { ok: false };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Resolve a channel's admission policy from the gateway, or `null` on any
82
+ * failure / absence. `null` means admit with no enforcement.
83
+ *
84
+ * Always fails open to `null` on failure, but only caches the result of a
85
+ * SUCCESSFUL gateway round-trip — a transient hiccup must not skip the
86
+ * admission floor for the rest of the TTL.
87
+ */
88
+ export async function getChannelAdmissionPolicy(
89
+ channelType: ChannelId,
90
+ ): Promise<AdmissionPolicy | null> {
91
+ const cached = cache.get(channelType);
92
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
93
+ return cached.policy;
94
+ }
95
+
96
+ const result = await fetchAdmissionPolicy(channelType);
97
+ if (!result.ok) {
98
+ // Fail open WITHOUT caching so a recovered gateway is re-consulted next time.
99
+ return null;
100
+ }
101
+
102
+ cache.set(channelType, { policy: result.policy, timestamp: Date.now() });
103
+ return result.policy;
104
+ }
@@ -9,14 +9,15 @@
9
9
 
10
10
  import { findGuardianForChannel } from "../contacts/contact-store.js";
11
11
  import {
12
- createCanonicalGuardianDelivery,
13
12
  createCanonicalGuardianRequest,
14
13
  listCanonicalGuardianDeliveries,
15
14
  listCanonicalGuardianRequests,
16
- updateCanonicalGuardianDelivery,
17
15
  } from "../memory/canonical-guardian-store.js";
16
+ import {
17
+ recordApprovalCardDelivery,
18
+ recordGuardianRequestDeliveries,
19
+ } from "../notifications/canonical-delivery-recorder.js";
18
20
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
19
- import type { NotificationDeliveryResult } from "../notifications/types.js";
20
21
  import { getLogger } from "../util/logger.js";
21
22
  import { getUserConsultationTimeoutMs } from "./call-constants.js";
22
23
  import type { CallPendingQuestion } from "./types.js";
@@ -39,17 +40,6 @@ export interface GuardianDispatchParams {
39
40
  inputDigest?: string;
40
41
  }
41
42
 
42
- function applyDeliveryStatus(
43
- deliveryId: string,
44
- result: NotificationDeliveryResult,
45
- ): void {
46
- if (result.status === "sent") {
47
- updateCanonicalGuardianDelivery(deliveryId, { status: "sent" });
48
- return;
49
- }
50
- updateCanonicalGuardianDelivery(deliveryId, { status: "failed" });
51
- }
52
-
53
43
  /**
54
44
  * Dispatch a guardian action request to all configured channels.
55
45
  * Fire-and-forget: errors are logged but do not propagate.
@@ -179,7 +169,7 @@ async function dispatchGuardianQuestionInner(
179
169
 
180
170
  // Route through the canonical notification pipeline. The paired vellum
181
171
  // conversation from this pipeline is the canonical guardian conversation.
182
- let vellumDeliveryId: string | null = null;
172
+ let vellumDeliveryId: string | undefined;
183
173
  const requestCode =
184
174
  request.requestCode ?? request.id.slice(0, 6).toUpperCase();
185
175
  const signalResult = await emitNotificationSignal({
@@ -208,44 +198,26 @@ async function dispatchGuardianQuestionInner(
208
198
  onConversationCreated: (info) => {
209
199
  if (info.sourceEventName !== "guardian.question" || vellumDeliveryId)
210
200
  return;
211
- const delivery = createCanonicalGuardianDelivery({
201
+ vellumDeliveryId = recordApprovalCardDelivery({
212
202
  requestId: request.id,
213
- destinationChannel: "vellum",
214
- destinationConversationId: info.conversationId,
215
- });
216
- vellumDeliveryId = delivery.id;
203
+ channel: "vellum",
204
+ conversationId: info.conversationId,
205
+ })?.id;
217
206
  },
218
207
  });
219
208
 
220
- for (const result of signalResult.deliveryResults) {
221
- if (result.channel === "vellum") {
222
- if (!vellumDeliveryId) {
223
- const delivery = createCanonicalGuardianDelivery({
224
- requestId: request.id,
225
- destinationChannel: "vellum",
226
- destinationConversationId: result.conversationId,
227
- });
228
- vellumDeliveryId = delivery.id;
229
- }
230
- applyDeliveryStatus(vellumDeliveryId, result);
231
- continue;
232
- }
233
-
234
- const delivery = createCanonicalGuardianDelivery({
235
- requestId: request.id,
236
- destinationChannel: result.channel,
237
- destinationChatId:
238
- result.destination.length > 0 ? result.destination : undefined,
239
- });
240
- applyDeliveryStatus(delivery.id, result);
241
- }
209
+ vellumDeliveryId = recordGuardianRequestDeliveries({
210
+ requestId: request.id,
211
+ deliveryResults: signalResult.deliveryResults,
212
+ vellumDeliveryId,
213
+ });
242
214
 
243
215
  if (!vellumDeliveryId) {
244
- const fallback = createCanonicalGuardianDelivery({
216
+ recordApprovalCardDelivery({
245
217
  requestId: request.id,
246
- destinationChannel: "vellum",
218
+ channel: "vellum",
219
+ status: "failed",
247
220
  });
248
- updateCanonicalGuardianDelivery(fallback.id, { status: "failed" });
249
221
  log.warn(
250
222
  { requestId: request.id, reason: signalResult.reason },
251
223
  "Notification pipeline did not produce a vellum delivery result",