@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
@@ -3,7 +3,7 @@
3
3
  * record, enforces allow/deny/escalate policies, handles invite token
4
4
  * intercepts, and notifies the guardian of denied access requests.
5
5
  */
6
- import type { SourceMetadata } from "@vellumai/gateway-client";
6
+ import type { AdmissionPolicy, SourceMetadata } from "@vellumai/gateway-client";
7
7
 
8
8
  import { isInviteCodeRedemptionEnabled } from "../../../channels/config.js";
9
9
  import type { ChannelId } from "../../../channels/types.js";
@@ -90,6 +90,18 @@ export interface AclEnforcementParams {
90
90
  replyCallbackUrl: string | undefined;
91
91
  assistantId: string;
92
92
  externalMessageId: string;
93
+ /**
94
+ * Effective admission policy for this request (gateway floor resolved with
95
+ * any per-conversation override). When set, ACL skips its hard-deny paths
96
+ * when the policy is permissive enough:
97
+ * - `strangers`: non-members and inactive (non-blocked) members are passed
98
+ * through so the admission floor can emit the final verdict.
99
+ * - `any_contact`: inactive `pending` members are passed through.
100
+ *
101
+ * Passing this in avoids having ACL fire guardian notifications and canned
102
+ * replies for senders who will be admitted by the floor stage anyway.
103
+ */
104
+ effectiveAdmissionPolicy?: AdmissionPolicy;
93
105
  }
94
106
 
95
107
  /** Resolved contact + channel pair from ACL enforcement. */
@@ -102,6 +114,12 @@ export interface AclResult {
102
114
  resolvedMember: ResolvedMember | null;
103
115
  /** When set, the caller must return this response immediately. */
104
116
  earlyResponse?: Record<string, unknown>;
117
+ /**
118
+ * True when a valid `pending_bootstrap` session was resolved during ACL
119
+ * enforcement. The caller must skip the admission policy floor so the
120
+ * bootstrap intercept stage can handle identity binding and emit its reply.
121
+ */
122
+ isValidatedBootstrap?: boolean;
105
123
  }
106
124
 
107
125
  /** Map ChannelStatus to the API-facing member status (excludes "unverified"). */
@@ -134,8 +152,11 @@ export async function enforceIngressAcl(
134
152
  replyCallbackUrl,
135
153
  assistantId,
136
154
  externalMessageId,
155
+ effectiveAdmissionPolicy,
137
156
  } = params;
138
157
 
158
+ let isValidatedBootstrap = false;
159
+
139
160
  // Trust signals from Slack users.info, forwarded via sourceMetadata.
140
161
  const isStranger = sourceMetadata?.isStranger ?? undefined;
141
162
  const isRestricted = sourceMetadata?.isRestricted ?? undefined;
@@ -166,7 +187,7 @@ export async function enforceIngressAcl(
166
187
  if (canonicalSenderId) {
167
188
  const contactResult = findContactChannel({
168
189
  channelType: sourceChannel,
169
- externalUserId: canonicalSenderId,
190
+ address: canonicalSenderId,
170
191
  externalChatId: conversationExternalId,
171
192
  });
172
193
  resolvedMember = contactResult
@@ -196,6 +217,7 @@ export async function enforceIngressAcl(
196
217
  bootstrapSessionForAcl.status === "pending_bootstrap"
197
218
  ) {
198
219
  denyNonMember = false;
220
+ isValidatedBootstrap = true;
199
221
  } else {
200
222
  log.info(
201
223
  { sourceChannel, hasValidBootstrapSession: false },
@@ -206,8 +228,10 @@ export async function enforceIngressAcl(
206
228
 
207
229
  // ── Invite token intercept (non-member) ──
208
230
  // /start invite deep links grant access without guardian approval.
209
- // Intercept here — before the deny gate so valid invites short-circuit
210
- // the ACL rejection and never reach the agent pipeline.
231
+ // Runs BEFORE the policy-aware bypass so a valid /start iv_<token>
232
+ // always redeems and creates a member record — even when the
233
+ // admission policy is `strangers` (which would otherwise admit the
234
+ // sender as a non-member before the token is consumed).
211
235
  if (inviteToken && denyNonMember) {
212
236
  const inviteResult = await handleInviteTokenIntercept({
213
237
  rawToken: inviteToken,
@@ -232,6 +256,8 @@ export async function enforceIngressAcl(
232
256
  // On channels with codeRedemptionEnabled, a bare 6-digit message may be
233
257
  // an invite code. Attempt redemption; on failure (no matching code) fall
234
258
  // through to normal processing — the number may be a regular message.
259
+ // Runs before the policy-aware bypass for the same reason as the token
260
+ // intercept above.
235
261
  if (denyNonMember && /^\d{6}$/.test(trimmedContent)) {
236
262
  const codeInterceptResult = await handleInviteCodeIntercept({
237
263
  code: trimmedContent,
@@ -252,6 +278,28 @@ export async function enforceIngressAcl(
252
278
  };
253
279
  }
254
280
 
281
+ // ── Policy-aware non-member bypass ──
282
+ // Skip the ACL deny gate so the admission floor stage emits the final
283
+ // verdict instead of the ACL prematurely firing guardian notifications,
284
+ // a canned reply, and (on Slack) a verification challenge.
285
+ // - `strangers` (floor 1): any sender (rank 1) clears the floor, so the
286
+ // stage admits.
287
+ // - `guardian_only` (floor 4): no non-guardian can clear the floor even
288
+ // after verifying (a verified contact is only rank 3), so the upgrade
289
+ // challenge is misleading — the stage denies with `shouldChallenge:
290
+ // false`. `trusted_contacts` is intentionally NOT bypassed here: a
291
+ // stranger there still can't reach the floor, but suppressing its
292
+ // self-verify challenge is a default-onboarding behavior change left
293
+ // for a separate §8.2 decision.
294
+ // Runs AFTER invite intercepts so valid tokens redeem first.
295
+ if (
296
+ denyNonMember &&
297
+ (effectiveAdmissionPolicy === "strangers" ||
298
+ effectiveAdmissionPolicy === "guardian_only")
299
+ ) {
300
+ denyNonMember = false;
301
+ }
302
+
255
303
  if (denyNonMember) {
256
304
  log.info(
257
305
  { sourceChannel, externalUserId: canonicalSenderId },
@@ -465,6 +513,7 @@ export async function enforceIngressAcl(
465
513
  bootstrapSessionForAcl.status === "pending_bootstrap"
466
514
  ) {
467
515
  denyInactiveMember = false;
516
+ isValidatedBootstrap = true;
468
517
  } else {
469
518
  log.info(
470
519
  {
@@ -481,7 +530,9 @@ export async function enforceIngressAcl(
481
530
  // Invite tokens can reactivate revoked/pending members without
482
531
  // requiring guardian approval, but blocked members are excluded so
483
532
  // they are short-circuited at the ACL layer rather than entering the
484
- // redemption path.
533
+ // redemption path. Runs BEFORE the policy-aware bypass so a valid
534
+ // invite always redeems and reactivates the member record, rather
535
+ // than the bypass admitting the sender in their inactive state.
485
536
  if (!isBlockedMember && inviteToken && denyInactiveMember) {
486
537
  const inviteResult = await handleInviteTokenIntercept({
487
538
  rawToken: inviteToken,
@@ -506,7 +557,8 @@ export async function enforceIngressAcl(
506
557
  // Codes can reactivate revoked/pending members; non-matching codes
507
558
  // fall through. Blocked members are excluded here for consistency —
508
559
  // the redemption service would reject them anyway, but early exit
509
- // avoids unnecessary work.
560
+ // avoids unnecessary work. Runs before the policy-aware bypass for
561
+ // the same reason as the token intercept above.
510
562
  if (
511
563
  !isBlockedMember &&
512
564
  denyInactiveMember &&
@@ -531,6 +583,36 @@ export async function enforceIngressAcl(
531
583
  };
532
584
  }
533
585
 
586
+ // ── Policy-aware inactive-member bypass ──
587
+ // `strangers` (floor 1): admit non-blocked, non-revoked senders
588
+ // (pending/unverified bypass the inactive-member deny gate).
589
+ // Revoked is an EXPLICIT governance action and stays denied even
590
+ // under the most permissive policy — it is not the same as an
591
+ // unknown stranger who has never interacted with the assistant.
592
+ // `any_contact` (floor 2): admit `pending` and `unverified` members
593
+ // (both classify as `unverified_contact` — rank 2 ≥ floor 2); deny
594
+ // `revoked` members (unknown rank 1 < floor 2).
595
+ // `guardian_only` (floor 4): route `pending`/`unverified` members to
596
+ // the floor stage for a plain denial. Verifying only lifts them to
597
+ // `trusted_contact` (rank 3 < floor 4), so the ACL's re-verify
598
+ // challenge would be misleading. `trusted_contacts` is NOT included:
599
+ // there, verifying reaches `trusted_contact` (rank 3 ≥ floor 3), so
600
+ // the challenge legitimately upgrades the sender into access.
601
+ // In every case skip the deny gate so the admission stage decides.
602
+ // Runs AFTER invite intercepts so valid tokens redeem first.
603
+ if (!isBlockedMember && denyInactiveMember) {
604
+ if (
605
+ (effectiveAdmissionPolicy === "strangers" &&
606
+ resolvedMember.channel.status !== "revoked") ||
607
+ ((effectiveAdmissionPolicy === "any_contact" ||
608
+ effectiveAdmissionPolicy === "guardian_only") &&
609
+ (resolvedMember.channel.status === "pending" ||
610
+ resolvedMember.channel.status === "unverified"))
611
+ ) {
612
+ denyInactiveMember = false;
613
+ }
614
+ }
615
+
534
616
  if (denyInactiveMember) {
535
617
  log.info(
536
618
  {
@@ -734,7 +816,7 @@ export async function enforceIngressAcl(
734
816
  }
735
817
  }
736
818
 
737
- return { resolvedMember };
819
+ return { resolvedMember, ...(isValidatedBootstrap && { isValidatedBootstrap }) };
738
820
  }
739
821
 
740
822
  // ---------------------------------------------------------------------------
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Unit tests for the `enforceAdmissionPolicy` pure function.
3
+ *
4
+ * Drives the floor-vs-rank logic in isolation — no I/O, no mocks needed.
5
+ * Integration-level coverage lives in the gateway's
6
+ * `handle-inbound-admission.test.ts` and in the full inbound-message
7
+ * handler integration tests.
8
+ */
9
+ import { describe, expect,test } from "bun:test";
10
+
11
+ import type { AdmissionPolicyInput } from "./admission-policy.js";
12
+ import { enforceAdmissionPolicy } from "./admission-policy.js";
13
+
14
+ function makeInput(overrides: Partial<AdmissionPolicyInput>): AdmissionPolicyInput {
15
+ return {
16
+ sourceChannel: "telegram",
17
+ trustClass: "unknown",
18
+ memberStatus: undefined,
19
+ policy: "trusted_contacts",
20
+ ...overrides,
21
+ };
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // §8.1 exempt channels — always admit
26
+ // ---------------------------------------------------------------------------
27
+
28
+ describe("enforceAdmissionPolicy — exempt channels", () => {
29
+ test("a2a short-circuits to admitted regardless of policy", () => {
30
+ const result = enforceAdmissionPolicy(
31
+ makeInput({ sourceChannel: "a2a", trustClass: "unknown", policy: "no_one" }),
32
+ );
33
+ expect(result.admitted).toBe(true);
34
+ });
35
+
36
+ test("phone is enforced (not exempt): the floor applies", () => {
37
+ // Voice ingress is wired, so `phone` is no longer exempt — an `unknown`
38
+ // caller is denied under `no_one`.
39
+ const result = enforceAdmissionPolicy(
40
+ makeInput({ sourceChannel: "phone", trustClass: "unknown", policy: "no_one" }),
41
+ );
42
+ expect(result.admitted).toBe(false);
43
+ });
44
+ });
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // §8 revoked members — denied regardless of policy
48
+ // ---------------------------------------------------------------------------
49
+
50
+ describe("enforceAdmissionPolicy — revoked member denial", () => {
51
+ test("revoked member is denied even under `strangers` (most permissive policy)", () => {
52
+ // Before the §8 fix, a revoked member with trustClass `unknown` (rank 1)
53
+ // would pass the `strangers` floor (floor 1, rank ≥ floor). The defense-
54
+ // in-depth short-circuit in this function now denies them regardless.
55
+ const result = enforceAdmissionPolicy(
56
+ makeInput({
57
+ trustClass: "unknown",
58
+ memberStatus: "revoked",
59
+ policy: "strangers",
60
+ }),
61
+ );
62
+ expect(result.admitted).toBe(false);
63
+ if (!result.admitted) {
64
+ expect(result.reason).toBe("member_revoked");
65
+ expect(result.shouldChallenge).toBe(false);
66
+ }
67
+ });
68
+
69
+ test("revoked member is denied under `any_contact`", () => {
70
+ const result = enforceAdmissionPolicy(
71
+ makeInput({
72
+ trustClass: "unknown",
73
+ memberStatus: "revoked",
74
+ policy: "any_contact",
75
+ }),
76
+ );
77
+ expect(result.admitted).toBe(false);
78
+ if (!result.admitted) expect(result.reason).toBe("member_revoked");
79
+ });
80
+
81
+ test("revoked member is denied under `trusted_contacts`", () => {
82
+ const result = enforceAdmissionPolicy(
83
+ makeInput({
84
+ trustClass: "unknown",
85
+ memberStatus: "revoked",
86
+ policy: "trusted_contacts",
87
+ }),
88
+ );
89
+ expect(result.admitted).toBe(false);
90
+ if (!result.admitted) expect(result.reason).toBe("member_revoked");
91
+ });
92
+ });
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // §8 blocked members — denied regardless of policy (existing behavior)
96
+ // ---------------------------------------------------------------------------
97
+
98
+ describe("enforceAdmissionPolicy — blocked member denial", () => {
99
+ test("blocked member is denied under `strangers`", () => {
100
+ const result = enforceAdmissionPolicy(
101
+ makeInput({ memberStatus: "blocked", policy: "strangers" }),
102
+ );
103
+ expect(result.admitted).toBe(false);
104
+ if (!result.admitted) expect(result.reason).toBe("member_blocked");
105
+ });
106
+ });
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Rank-vs-floor: non-revoked, non-blocked members
110
+ // ---------------------------------------------------------------------------
111
+
112
+ describe("enforceAdmissionPolicy — rank vs floor", () => {
113
+ test("guardian admitted under any policy including guardian_only", () => {
114
+ const result = enforceAdmissionPolicy(
115
+ makeInput({ trustClass: "guardian", policy: "guardian_only" }),
116
+ );
117
+ expect(result.admitted).toBe(true);
118
+ });
119
+
120
+ test("trusted_contact admitted under trusted_contacts", () => {
121
+ const result = enforceAdmissionPolicy(
122
+ makeInput({ trustClass: "trusted_contact", policy: "trusted_contacts" }),
123
+ );
124
+ expect(result.admitted).toBe(true);
125
+ });
126
+
127
+ test("unverified_contact admitted under any_contact floor", () => {
128
+ const result = enforceAdmissionPolicy(
129
+ makeInput({ trustClass: "unverified_contact", policy: "any_contact" }),
130
+ );
131
+ expect(result.admitted).toBe(true);
132
+ });
133
+
134
+ test("unknown (non-member) admitted under strangers floor", () => {
135
+ const result = enforceAdmissionPolicy(
136
+ makeInput({ trustClass: "unknown", memberStatus: undefined, policy: "strangers" }),
137
+ );
138
+ expect(result.admitted).toBe(true);
139
+ });
140
+
141
+ test("unknown denied under trusted_contacts floor", () => {
142
+ const result = enforceAdmissionPolicy(
143
+ makeInput({ trustClass: "unknown", policy: "trusted_contacts" }),
144
+ );
145
+ expect(result.admitted).toBe(false);
146
+ });
147
+
148
+ test("pending member (unverified_contact) admitted under strangers floor", () => {
149
+ const result = enforceAdmissionPolicy(
150
+ makeInput({ trustClass: "unverified_contact", memberStatus: "pending", policy: "strangers" }),
151
+ );
152
+ expect(result.admitted).toBe(true);
153
+ });
154
+ });
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Admission policy enforcement stage.
3
+ *
4
+ * Sits between `resolveTrustContext()` and the agent-loop dispatch in
5
+ * `inbound-message-handler.ts`. The gateway attaches a per-channel-type
6
+ * floor (`sourceMetadata.admissionPolicy`); this stage compares the floor
7
+ * to the resolved trust class's rank and either admits or denies.
8
+ *
9
+ * Deny semantics — see `wave-b-plan.md` §8.2:
10
+ *
11
+ * - `shouldChallenge: true` when the policy is one that re-verification
12
+ * could lift past (`any_contact`, `strangers`). The caller fires the
13
+ * existing Slack DM / email upgrade UX so the sender knows verification
14
+ * would admit them.
15
+ * - `shouldChallenge: false` for the stricter floors (`guardian_only`,
16
+ * `trusted_contacts`). Denials are silent — sender gets the standard
17
+ * canned reply; guardian still gets the access-request notification.
18
+ *
19
+ * Blocked / revoked members short-circuit to deny regardless of policy.
20
+ * The gateway kill switch (`no_one`) is enforced before forwarding, so
21
+ * this stage never sees a `no_one` policy on the wire; we still handle
22
+ * the value defensively for defense in depth and unit-test reachability.
23
+ */
24
+
25
+ import {
26
+ ADMISSION_FLOOR,
27
+ type AdmissionPolicy,
28
+ isAdmissionPolicyExemptChannel,
29
+ } from "@vellumai/gateway-client";
30
+
31
+ import type { ChannelId } from "../../../channels/types.js";
32
+ import type { ChannelStatus } from "../../../contacts/types.js";
33
+ import {
34
+ TRUST_CLASS_RANK,
35
+ type TrustClass,
36
+ } from "../../actor-trust-resolver.js";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Types
40
+ // ---------------------------------------------------------------------------
41
+
42
+ export interface AdmissionPolicyInput {
43
+ sourceChannel: ChannelId;
44
+ trustClass: TrustClass;
45
+ /**
46
+ * Channel record status for the resolved member, when one was found.
47
+ * Blocked/revoked short-circuit to deny regardless of floor.
48
+ */
49
+ memberStatus: ChannelStatus | undefined;
50
+ /** Per-channel-type floor attached by the gateway. */
51
+ policy: AdmissionPolicy;
52
+ }
53
+
54
+ export type AdmissionDenyReason =
55
+ | "member_blocked"
56
+ | "member_revoked"
57
+ | `admission_policy_${AdmissionPolicy}`;
58
+
59
+ export type AdmissionPolicyResult =
60
+ | { admitted: true }
61
+ | {
62
+ admitted: false;
63
+ reason: AdmissionDenyReason;
64
+ /**
65
+ * Whether the runtime should fire the re-verification upgrade UX
66
+ * (Slack DM / email guardian forwarder). Only meaningful when the
67
+ * resolved trust class could clear the floor after verification.
68
+ */
69
+ shouldChallenge: boolean;
70
+ /** Effective policy that produced the deny (after override resolution). */
71
+ effectivePolicy: AdmissionPolicy;
72
+ };
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Public API
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /**
79
+ * Policies under which completing verification could lift the sender past
80
+ * the floor. Used to decide whether to fire the upgrade UX on deny.
81
+ * `unverified_contact` (rank 2) reaches `any_contact` (floor 2) and
82
+ * `strangers` (floor 1); below those, verification still leaves the
83
+ * sender short of the floor (§8.2).
84
+ */
85
+ const POLICIES_THAT_COULD_UPGRADE: ReadonlySet<AdmissionPolicy> = new Set([
86
+ "any_contact",
87
+ "strangers",
88
+ ]);
89
+
90
+ /**
91
+ * Enforce the admission policy floor against the resolved trust class.
92
+ *
93
+ * Pure function — all I/O happens in the caller. Returns the canned
94
+ * admit/deny verdict; the caller wires denials into the existing
95
+ * canned-reply / guardian-notify pipeline used by `acl-enforcement.ts` for
96
+ * `not_a_member`.
97
+ */
98
+ export function enforceAdmissionPolicy(
99
+ input: AdmissionPolicyInput,
100
+ ): AdmissionPolicyResult {
101
+ // §8.1: short-circuit on internal exempt channels. The gateway should
102
+ // not have attached a policy for these in the first place; this is
103
+ // defense in depth and keeps the runtime fail-open if the gateway is
104
+ // ever called from a path that bypasses the kill-switch insertion.
105
+ if (isAdmissionPolicyExemptChannel(input.sourceChannel)) {
106
+ return { admitted: true };
107
+ }
108
+
109
+ // Blocked and revoked members never clear admission regardless of floor.
110
+ // `enforceIngressAcl` already short-circuits on both: blocked always, and
111
+ // revoked under any policy including `strangers` (§8 fix: revoked is an
112
+ // explicit governance action, not the same as an unknown stranger). The
113
+ // checks here are defense-in-depth so unit tests can drive the floor stage
114
+ // in isolation and a future refactor that reorders stages doesn't silently
115
+ // admit a blocked/revoked actor.
116
+ if (input.memberStatus === "blocked" || input.memberStatus === "revoked") {
117
+ return {
118
+ admitted: false,
119
+ reason: input.memberStatus === "blocked" ? "member_blocked" : "member_revoked",
120
+ shouldChallenge: false,
121
+ effectivePolicy: input.policy,
122
+ };
123
+ }
124
+
125
+ const effectivePolicy = input.policy;
126
+
127
+ const rank = TRUST_CLASS_RANK[input.trustClass];
128
+ const floor = ADMISSION_FLOOR[effectivePolicy];
129
+
130
+ if (rank >= floor) {
131
+ return { admitted: true };
132
+ }
133
+
134
+ return {
135
+ admitted: false,
136
+ reason: `admission_policy_${effectivePolicy}` as const,
137
+ shouldChallenge: POLICIES_THAT_COULD_UPGRADE.has(effectivePolicy),
138
+ effectivePolicy,
139
+ };
140
+ }
@@ -845,7 +845,7 @@ describe("Slack thinking status timing", () => {
845
845
  channel: channelId,
846
846
  threadTs,
847
847
  status: expect.any(String),
848
- loadingMessages: ["Working on it..."],
848
+ loadingMessages: ["Thinking\u2026"],
849
849
  },
850
850
  );
851
851
  const threadStatus = deliveredChannelReplies[0]!.payload
@@ -948,13 +948,13 @@ describe("Slack thinking status timing", () => {
948
948
  channel: channelId,
949
949
  threadTs,
950
950
  status: expect.any(String),
951
- loadingMessages: ["Working on it..."],
951
+ loadingMessages: ["Thinking\u2026"],
952
952
  },
953
953
  {
954
954
  channel: channelId,
955
955
  threadTs,
956
956
  status: expect.any(String),
957
- loadingMessages: ["Working on it..."],
957
+ loadingMessages: ["Thinking\u2026"],
958
958
  },
959
959
  {
960
960
  channel: channelId,
@@ -577,7 +577,7 @@ function createSlackThinkingStatusController(params: {
577
577
  }
578
578
 
579
579
  const SLACK_THINKING_MAX_DURATION_MS = 120_000;
580
- const SLACK_GENERIC_LOADING_MESSAGES = ["Working on it..."] as const;
580
+ const SLACK_GENERIC_LOADING_MESSAGES = ["Thinking…"] as const;
581
581
  const SLACK_THINKING_STATUSES = ["is on it", "is working hard"] as const;
582
582
 
583
583
  function getRandomSlackThinkingStatus(): string {
@@ -904,10 +904,12 @@ const globalNotifiedApprovalRequestIds = new Map<string, string>();
904
904
 
905
905
  /**
906
906
  * Start a poller that sends a one-shot "waiting for guardian approval" message
907
- * to the trusted contact when a confirmation_request enters guardian approval
908
- * wait. Deduplicates by requestId so each request only produces one message.
907
+ * to the trusted/unverified contact when a confirmation_request enters guardian
908
+ * approval wait. Deduplicates by requestId so each request only produces one
909
+ * message.
909
910
  *
910
- * Only activates for trusted-contact actors with a resolvable guardian route.
911
+ * Only activates for trusted_contact and unverified_contact actors with a
912
+ * resolvable guardian route.
911
913
  */
912
914
  function startTrustedContactApprovalNotifier(params: {
913
915
  conversationId: string;
@@ -928,8 +930,11 @@ function startTrustedContactApprovalNotifier(params: {
928
930
  assistantId,
929
931
  } = params;
930
932
 
931
- // Only notify trusted contacts who have a resolvable guardian route.
932
- if (trustClass !== "trusted_contact" || !guardianExternalUserId) {
933
+ // Only notify identity-known non-guardian contacts (trusted_contact and
934
+ // unverified_contact) who have a resolvable guardian route.
935
+ const isIdentityKnownNonGuardian =
936
+ trustClass === "trusted_contact" || trustClass === "unverified_contact";
937
+ if (!isIdentityKnownNonGuardian || !guardianExternalUserId) {
933
938
  return () => {};
934
939
  }
935
940
 
@@ -11,7 +11,6 @@ import type { ChannelId, InterfaceId } from "../../../channels/types.js";
11
11
  import { createCanonicalGuardianRequest } from "../../../memory/canonical-guardian-store.js";
12
12
  import { storePayload } from "../../../memory/delivery-crud.js";
13
13
  import { emitNotificationSignal } from "../../../notifications/emit-signal.js";
14
- import type { NotificationSourceChannel } from "../../../notifications/signal.js";
15
14
  import { getLogger } from "../../../util/logger.js";
16
15
  import { getGuardianBinding } from "../../channel-verification-service.js";
17
16
  import { GUARDIAN_APPROVAL_TTL_MS } from "../channel-route-shared.js";
@@ -133,7 +132,7 @@ export function handleEscalationIntercept(
133
132
  // channels, supplementing the direct guardian notification below.
134
133
  void emitNotificationSignal({
135
134
  sourceEventName: "ingress.escalation",
136
- sourceChannel: sourceChannel as NotificationSourceChannel,
135
+ sourceChannel,
137
136
  sourceContextId: conversationId,
138
137
  attentionHints: {
139
138
  requiresAction: true,
@@ -11,7 +11,6 @@
11
11
  import type { ChannelId } from "../../../channels/types.js";
12
12
  import { findGuardianForChannel } from "../../../contacts/contact-store.js";
13
13
  import { emitNotificationSignal } from "../../../notifications/emit-signal.js";
14
- import type { NotificationSourceChannel } from "../../../notifications/signal.js";
15
14
  import { getLogger } from "../../../util/logger.js";
16
15
  import {
17
16
  createOutboundSession,
@@ -164,7 +163,7 @@ export async function handleGuardianActivationIntercept(
164
163
  // ── Emit notification signal to deliver code to macOS app ──
165
164
  void emitNotificationSignal({
166
165
  sourceEventName: "guardian.channel_activation",
167
- sourceChannel: sourceChannel as NotificationSourceChannel,
166
+ sourceChannel,
168
167
  sourceContextId: `guardian-activation-${sourceChannel}-${rawSenderId}`,
169
168
  attentionHints: {
170
169
  requiresAction: true,
@@ -85,7 +85,7 @@ describe("handleGuardianReplyIntercept", () => {
85
85
  deliverChannelReplyCalls = [];
86
86
  });
87
87
 
88
- test("passes empty pendingRequestIds when Slack guardian sends message in non-delivery chat", async () => {
88
+ test("passes a blocked scope when Slack guardian sends message in non-delivery chat", async () => {
89
89
  // No delivery-scoped requests for this chat
90
90
  mockDeliveryScopedRequests = [];
91
91
  // Identity-based lookup would find a pending request in a different chat
@@ -95,8 +95,8 @@ describe("handleGuardianReplyIntercept", () => {
95
95
 
96
96
  expect(routeGuardianReplyCalls).toHaveLength(1);
97
97
  const ctx = routeGuardianReplyCalls[0] as Record<string, unknown>;
98
- // Must be [] (empty array), NOT undefined — blocks identity fallback
99
- expect(ctx.pendingRequestIds).toEqual([]);
98
+ // Must be a blocked scope, NOT undefined — blocks identity fallback
99
+ expect(ctx.pendingScope).toEqual({ mode: "blocked" });
100
100
  });
101
101
 
102
102
  test("preserves identity fallback for non-Slack channels when no deliveries exist", async () => {
@@ -110,11 +110,11 @@ describe("handleGuardianReplyIntercept", () => {
110
110
 
111
111
  expect(routeGuardianReplyCalls).toHaveLength(1);
112
112
  const ctx = routeGuardianReplyCalls[0] as Record<string, unknown>;
113
- // Must be undefined — identity-based fallback stays active
114
- expect(ctx.pendingRequestIds).toBeUndefined();
113
+ // Must be unset — identity-based fallback stays active
114
+ expect(ctx.pendingScope).toBeUndefined();
115
115
  });
116
116
 
117
- test("includes identity-unioned pendingRequestIds when Slack guardian is in delivery chat", async () => {
117
+ test("passes an identity-unioned scoped set when Slack guardian is in delivery chat", async () => {
118
118
  mockDeliveryScopedRequests = [{ id: "delivered-req" }];
119
119
  mockIdentityRequests = [
120
120
  { id: "delivered-req" },
@@ -125,7 +125,7 @@ describe("handleGuardianReplyIntercept", () => {
125
125
 
126
126
  expect(routeGuardianReplyCalls).toHaveLength(1);
127
127
  const ctx = routeGuardianReplyCalls[0] as Record<string, unknown>;
128
- const ids = ctx.pendingRequestIds as string[];
128
+ const ids = (ctx.pendingScope as { requestIds: string[] }).requestIds;
129
129
  expect(ids).toContain("delivered-req");
130
130
  expect(ids).toContain("identity-only-req");
131
131
  });