@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -0,0 +1,164 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { ChannelId } from "../../channels/types.js";
4
+
5
+ // Gateway guardian-delivery list: null = couldn't determine (gateway
6
+ // unreachable), [] = authoritatively no guardian, one active entry = bound.
7
+ let mockGuardianList: Array<Record<string, unknown>> | null = [];
8
+
9
+ mock.module("../../contacts/guardian-delivery-reader.js", () => ({
10
+ getGuardianDelivery: (_input?: { channelTypes?: string[] }) =>
11
+ Promise.resolve(mockGuardianList),
12
+ }));
13
+
14
+ // Local-resolver guardian binding, used to construct the expected guardian
15
+ // TrustContext via the existing resolver and prove equivalence.
16
+ let mockGuardianRecord: {
17
+ contact: Record<string, unknown>;
18
+ channel: Record<string, unknown>;
19
+ } | null = null;
20
+
21
+ mock.module("../../contacts/contact-store.js", () => ({
22
+ findGuardianForChannel: (_channelType: string) => mockGuardianRecord,
23
+ findContactByAddress: (_channelType: string, _address: string) => null,
24
+ }));
25
+
26
+ const { resolveLocalPrincipalTrustContext } = await import(
27
+ "../local-principal-trust.js"
28
+ );
29
+ const { resolveActorTrust, toTrustContext } = await import(
30
+ "../actor-trust-resolver.js"
31
+ );
32
+
33
+ const SOURCE_CHANNEL: ChannelId = "vellum";
34
+ const CONVERSATION_ID = "conv-1";
35
+ const GUARDIAN_PRINCIPAL_ID = "principal-guardian";
36
+ const GUARDIAN_ADDRESS = "guardian-address";
37
+ const GUARDIAN_CHAT_ID = "guardian-chat";
38
+
39
+ describe("resolveLocalPrincipalTrustContext", () => {
40
+ beforeEach(() => {
41
+ mockGuardianList = [];
42
+ mockGuardianRecord = null;
43
+ });
44
+
45
+ test("principal matching the gateway guardian → guardian ctx", async () => {
46
+ mockGuardianList = [
47
+ {
48
+ channelType: "vellum",
49
+ contactId: "contact-1",
50
+ principalId: GUARDIAN_PRINCIPAL_ID,
51
+ address: GUARDIAN_ADDRESS,
52
+ externalChatId: GUARDIAN_CHAT_ID,
53
+ status: "active",
54
+ },
55
+ ];
56
+
57
+ const ctx = await resolveLocalPrincipalTrustContext({
58
+ actorPrincipalId: GUARDIAN_PRINCIPAL_ID,
59
+ sourceChannel: SOURCE_CHANNEL,
60
+ conversationExternalId: CONVERSATION_ID,
61
+ });
62
+
63
+ expect(ctx.trustClass).toBe("guardian");
64
+ expect(ctx.guardianPrincipalId).toBe(GUARDIAN_PRINCIPAL_ID);
65
+ expect(ctx.guardianExternalUserId).toBe(GUARDIAN_ADDRESS);
66
+ expect(ctx.guardianChatId).toBe(GUARDIAN_CHAT_ID);
67
+ expect(ctx.requesterExternalUserId).toBe(GUARDIAN_PRINCIPAL_ID);
68
+ expect(ctx.requesterChatId).toBe(CONVERSATION_ID);
69
+ expect(ctx.sourceChannel).toBe(SOURCE_CHANNEL);
70
+ });
71
+
72
+ test("guardian ctx is equivalent to the prior toTrustContext output", async () => {
73
+ // The actor IS the guardian: its principal id is the guardian binding's
74
+ // address (vellum canonicalization is pass-through) so the local resolver
75
+ // classifies it guardian and we can compare the two outputs directly.
76
+ mockGuardianList = [
77
+ {
78
+ channelType: "vellum",
79
+ contactId: "contact-1",
80
+ principalId: GUARDIAN_ADDRESS,
81
+ address: GUARDIAN_ADDRESS,
82
+ externalChatId: GUARDIAN_CHAT_ID,
83
+ status: "active",
84
+ },
85
+ ];
86
+ mockGuardianRecord = {
87
+ contact: { id: "contact-1", principalId: GUARDIAN_ADDRESS },
88
+ channel: {
89
+ type: "vellum",
90
+ address: GUARDIAN_ADDRESS,
91
+ externalChatId: GUARDIAN_CHAT_ID,
92
+ status: "active",
93
+ },
94
+ };
95
+
96
+ const expected = toTrustContext(
97
+ resolveActorTrust({
98
+ assistantId: "assistant-1",
99
+ sourceChannel: SOURCE_CHANNEL,
100
+ conversationExternalId: CONVERSATION_ID,
101
+ actorExternalId: GUARDIAN_ADDRESS,
102
+ }),
103
+ CONVERSATION_ID,
104
+ );
105
+
106
+ const actual = await resolveLocalPrincipalTrustContext({
107
+ actorPrincipalId: GUARDIAN_ADDRESS,
108
+ sourceChannel: SOURCE_CHANNEL,
109
+ conversationExternalId: CONVERSATION_ID,
110
+ });
111
+
112
+ expect(actual).toEqual(expected);
113
+ });
114
+
115
+ test("non-matching principal → unknown ctx", async () => {
116
+ mockGuardianList = [
117
+ {
118
+ channelType: "vellum",
119
+ contactId: "contact-1",
120
+ principalId: GUARDIAN_PRINCIPAL_ID,
121
+ address: GUARDIAN_ADDRESS,
122
+ externalChatId: GUARDIAN_CHAT_ID,
123
+ status: "active",
124
+ },
125
+ ];
126
+
127
+ const ctx = await resolveLocalPrincipalTrustContext({
128
+ actorPrincipalId: "principal-other",
129
+ sourceChannel: SOURCE_CHANNEL,
130
+ conversationExternalId: CONVERSATION_ID,
131
+ });
132
+
133
+ expect(ctx.trustClass).toBe("unknown");
134
+ expect(ctx.requesterExternalUserId).toBe("principal-other");
135
+ expect(ctx.requesterChatId).toBe(CONVERSATION_ID);
136
+ expect(ctx.sourceChannel).toBe(SOURCE_CHANNEL);
137
+ expect(ctx.guardianPrincipalId).toBeUndefined();
138
+ });
139
+
140
+ test("empty guardian list → unknown ctx", async () => {
141
+ mockGuardianList = [];
142
+
143
+ const ctx = await resolveLocalPrincipalTrustContext({
144
+ actorPrincipalId: GUARDIAN_PRINCIPAL_ID,
145
+ sourceChannel: SOURCE_CHANNEL,
146
+ conversationExternalId: CONVERSATION_ID,
147
+ });
148
+
149
+ expect(ctx.trustClass).toBe("unknown");
150
+ });
151
+
152
+ test("null guardian list (gateway unreachable) → unknown (fail closed)", async () => {
153
+ mockGuardianList = null;
154
+
155
+ const ctx = await resolveLocalPrincipalTrustContext({
156
+ actorPrincipalId: GUARDIAN_PRINCIPAL_ID,
157
+ sourceChannel: SOURCE_CHANNEL,
158
+ conversationExternalId: CONVERSATION_ID,
159
+ });
160
+
161
+ expect(ctx.trustClass).toBe("unknown");
162
+ expect(ctx.requesterExternalUserId).toBe(GUARDIAN_PRINCIPAL_ID);
163
+ });
164
+ });
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import type { TrustVerdict } from "@vellumai/gateway-client";
4
4
 
5
+ import { channelStatusToMemberStatus } from "../../contacts/member-status.js";
5
6
  import type {
6
7
  ContactChannel,
7
8
  ContactWithChannels,
@@ -9,8 +10,12 @@ import type {
9
10
  import type { ActorTrustContext } from "../actor-trust-resolver.js";
10
11
  import { toTrustContext } from "../actor-trust-resolver.js";
11
12
  import {
13
+ actorTrustContextFromVerdict,
12
14
  resolvedMemberFromVerdict,
13
15
  trustContextFromVerdict,
16
+ verdictHasMemberIdentity,
17
+ verdictMemberFromVerdict,
18
+ verdictMemberUnresolvable,
14
19
  } from "../trust-verdict-consumer.js";
15
20
 
16
21
  const CONV = "conv-123";
@@ -163,6 +168,195 @@ describe("trustContextFromVerdict", () => {
163
168
  });
164
169
  });
165
170
 
171
+ describe("actorTrustContextFromVerdict", () => {
172
+ test("maps guardian verdict fields", () => {
173
+ const verdict = {
174
+ trustClass: "guardian",
175
+ canonicalSenderId: "+15550100",
176
+ guardianExternalUserId: "+15550100",
177
+ guardianDeliveryChatId: "chat-9",
178
+ guardianPrincipalId: "vellum-principal-abc",
179
+ memberDisplayName: "Alice",
180
+ } satisfies TrustVerdict;
181
+
182
+ const ctx = actorTrustContextFromVerdict(verdict, {
183
+ sourceChannel: "phone",
184
+ conversationExternalId: CONV,
185
+ actorUsername: "alice",
186
+ actorDisplayName: "Alice Sender",
187
+ });
188
+
189
+ expect(ctx).toEqual({
190
+ canonicalSenderId: "+15550100",
191
+ guardianBindingMatch: {
192
+ guardianExternalUserId: "+15550100",
193
+ guardianDeliveryChatId: "chat-9",
194
+ },
195
+ guardianPrincipalId: "vellum-principal-abc",
196
+ memberRecord: null,
197
+ trustClass: "guardian",
198
+ actorMetadata: {
199
+ identifier: "@alice",
200
+ displayName: "Alice",
201
+ senderDisplayName: "Alice Sender",
202
+ memberDisplayName: "Alice",
203
+ username: "alice",
204
+ channel: "phone",
205
+ trustStatus: "guardian",
206
+ },
207
+ });
208
+ });
209
+
210
+ test("guardianBindingMatch is null without guardianExternalUserId", () => {
211
+ const verdict = {
212
+ trustClass: "trusted_contact",
213
+ canonicalSenderId: "+15550101",
214
+ memberDisplayName: "Bob",
215
+ } satisfies TrustVerdict;
216
+
217
+ const ctx = actorTrustContextFromVerdict(verdict, {
218
+ sourceChannel: "phone",
219
+ conversationExternalId: CONV,
220
+ });
221
+
222
+ expect(ctx.guardianBindingMatch).toBeNull();
223
+ expect(ctx.trustClass).toBe("trusted_contact");
224
+ // identifier falls back to canonicalSenderId when no username.
225
+ expect(ctx.actorMetadata.identifier).toBe("+15550101");
226
+ // displayName uses memberDisplayName when present.
227
+ expect(ctx.actorMetadata.displayName).toBe("Bob");
228
+ expect(ctx.actorMetadata.channel).toBe("phone");
229
+ expect(ctx.memberRecord).toBeNull();
230
+ });
231
+
232
+ test("displayName falls back to actorDisplayName; identifier uses @username", () => {
233
+ const verdict = {
234
+ trustClass: "unverified_contact",
235
+ canonicalSenderId: "u-1",
236
+ } satisfies TrustVerdict;
237
+
238
+ const ctx = actorTrustContextFromVerdict(verdict, {
239
+ sourceChannel: "slack",
240
+ conversationExternalId: CONV,
241
+ actorUsername: "carol",
242
+ actorDisplayName: "Carol Display",
243
+ });
244
+
245
+ expect(ctx.trustClass).toBe("unverified_contact");
246
+ expect(ctx.actorMetadata.identifier).toBe("@carol");
247
+ expect(ctx.actorMetadata.displayName).toBe("Carol Display");
248
+ expect(ctx.actorMetadata.memberDisplayName).toBeUndefined();
249
+ expect(ctx.actorMetadata.trustStatus).toBe("unverified_contact");
250
+ });
251
+
252
+ test("maps unknown verdict with no identity overrides", () => {
253
+ const verdict = {
254
+ trustClass: "unknown",
255
+ canonicalSenderId: "u-2",
256
+ } satisfies TrustVerdict;
257
+
258
+ const ctx = actorTrustContextFromVerdict(verdict, {
259
+ sourceChannel: "slack",
260
+ conversationExternalId: CONV,
261
+ });
262
+
263
+ expect(ctx.trustClass).toBe("unknown");
264
+ expect(ctx.guardianBindingMatch).toBeNull();
265
+ expect(ctx.guardianPrincipalId).toBeUndefined();
266
+ expect(ctx.memberRecord).toBeNull();
267
+ expect(ctx.actorMetadata.identifier).toBe("u-2");
268
+ expect(ctx.actorMetadata.displayName).toBeUndefined();
269
+ });
270
+
271
+ test("member verdict populates memberRecord from the verdict (voice ACL)", () => {
272
+ const verdict = {
273
+ trustClass: "trusted_contact",
274
+ canonicalSenderId: "u-1",
275
+ contactId: "contact-1",
276
+ channelId: "channel-1",
277
+ type: "slack",
278
+ address: "u-1",
279
+ status: "blocked",
280
+ policy: "deny",
281
+ } satisfies TrustVerdict;
282
+
283
+ const ctx = actorTrustContextFromVerdict(verdict, {
284
+ sourceChannel: "slack",
285
+ conversationExternalId: CONV,
286
+ });
287
+
288
+ expect(ctx.memberRecord).not.toBeNull();
289
+ expect(ctx.memberRecord!.contact.id).toBe("contact-1");
290
+ expect(ctx.memberRecord!.channel.id).toBe("channel-1");
291
+ expect(ctx.memberRecord!.channel.status).toBe("blocked");
292
+ expect(ctx.memberRecord!.channel.policy).toBe("deny");
293
+ });
294
+
295
+ test("stranger verdict (no contactId/channelId) leaves memberRecord null", () => {
296
+ const verdict = {
297
+ trustClass: "unknown",
298
+ canonicalSenderId: "u-9",
299
+ } satisfies TrustVerdict;
300
+
301
+ const ctx = actorTrustContextFromVerdict(verdict, {
302
+ sourceChannel: "slack",
303
+ conversationExternalId: CONV,
304
+ });
305
+
306
+ expect(ctx.memberRecord).toBeNull();
307
+ });
308
+
309
+ test("malformed member verdict (unknown status) leaves memberRecord null (fail-closed)", () => {
310
+ const verdict = {
311
+ trustClass: "trusted_contact",
312
+ canonicalSenderId: "u-10",
313
+ contactId: "contact-10",
314
+ channelId: "channel-10",
315
+ status: "quarantined",
316
+ policy: "allow",
317
+ } satisfies TrustVerdict;
318
+
319
+ const ctx = actorTrustContextFromVerdict(verdict, {
320
+ sourceChannel: "slack",
321
+ conversationExternalId: CONV,
322
+ });
323
+
324
+ expect(ctx.memberRecord).toBeNull();
325
+ });
326
+
327
+ test("trustContextFromVerdict equals member stamp applied to toTrustContext(actorTrustContextFromVerdict)", () => {
328
+ const verdict = {
329
+ trustClass: "trusted_contact",
330
+ canonicalSenderId: "u-1",
331
+ contactId: "contact-1",
332
+ channelId: "channel-1",
333
+ type: "slack",
334
+ address: "u-1",
335
+ status: "unverified",
336
+ policy: "escalate",
337
+ memberDisplayName: "Dora",
338
+ } satisfies TrustVerdict;
339
+ const input = {
340
+ sourceChannel: "slack",
341
+ conversationExternalId: CONV,
342
+ actorUsername: "dora",
343
+ actorDisplayName: "Dora Display",
344
+ } as const;
345
+
346
+ const expected = toTrustContext(
347
+ actorTrustContextFromVerdict(verdict, input),
348
+ input.conversationExternalId,
349
+ );
350
+ const member = resolvedMemberFromVerdict(verdict);
351
+ expect(member).not.toBeNull();
352
+ expected.requesterContactId = member!.contact.id;
353
+ expected.memberStatus = channelStatusToMemberStatus(member!.channel.status);
354
+ expected.memberPolicy = member!.channel.policy;
355
+
356
+ expect(trustContextFromVerdict(verdict, input)).toEqual(expected);
357
+ });
358
+ });
359
+
166
360
  describe("toTrustContext member grounding", () => {
167
361
  function memberChannel(
168
362
  overrides: Partial<ContactChannel> = {},
@@ -207,9 +401,7 @@ describe("toTrustContext member grounding", () => {
207
401
  };
208
402
  }
209
403
 
210
- function ctxWithMember(
211
- channel: ContactChannel,
212
- ): ActorTrustContext {
404
+ function ctxWithMember(channel: ContactChannel): ActorTrustContext {
213
405
  return {
214
406
  canonicalSenderId: "+15550100",
215
407
  guardianBindingMatch: null,
@@ -415,3 +607,143 @@ describe("resolvedMemberFromVerdict", () => {
415
607
  }
416
608
  });
417
609
  });
610
+
611
+ describe("verdictMemberFromVerdict", () => {
612
+ test("active member verdict yields the narrow ACL view", () => {
613
+ const verdict = {
614
+ trustClass: "trusted_contact",
615
+ canonicalSenderId: "u-1",
616
+ contactId: "contact-1",
617
+ channelId: "channel-1",
618
+ type: "slack",
619
+ address: "u-1",
620
+ status: "active",
621
+ policy: "allow",
622
+ verifiedAt: 1700000000,
623
+ memberDisplayName: "Dora",
624
+ } satisfies TrustVerdict;
625
+
626
+ expect(verdictMemberFromVerdict(verdict)).toEqual({
627
+ contactId: "contact-1",
628
+ channelId: "channel-1",
629
+ status: "active",
630
+ policy: "allow",
631
+ verifiedAt: 1700000000,
632
+ displayName: "Dora",
633
+ });
634
+ });
635
+
636
+ test("blocked member verdict surfaces status/policy verbatim, null defaults", () => {
637
+ const verdict = {
638
+ trustClass: "unknown",
639
+ canonicalSenderId: "u-3",
640
+ contactId: "contact-3",
641
+ channelId: "channel-3",
642
+ status: "blocked",
643
+ policy: "deny",
644
+ } satisfies TrustVerdict;
645
+
646
+ expect(verdictMemberFromVerdict(verdict)).toEqual({
647
+ contactId: "contact-3",
648
+ channelId: "channel-3",
649
+ status: "blocked",
650
+ policy: "deny",
651
+ verifiedAt: null,
652
+ displayName: null,
653
+ });
654
+ });
655
+
656
+ test("memberless verdict (no contactId/channelId) returns null", () => {
657
+ expect(
658
+ verdictMemberFromVerdict({
659
+ trustClass: "unknown",
660
+ canonicalSenderId: "u-2",
661
+ } satisfies TrustVerdict),
662
+ ).toBeNull();
663
+ });
664
+
665
+ test("invalid enum (unknown status/policy) returns null (fail-closed)", () => {
666
+ expect(
667
+ verdictMemberFromVerdict({
668
+ trustClass: "trusted_contact",
669
+ canonicalSenderId: "u-7",
670
+ contactId: "contact-7",
671
+ channelId: "channel-7",
672
+ status: "quarantined",
673
+ policy: "allow",
674
+ } satisfies TrustVerdict),
675
+ ).toBeNull();
676
+ expect(
677
+ verdictMemberFromVerdict({
678
+ trustClass: "trusted_contact",
679
+ canonicalSenderId: "u-6",
680
+ contactId: "contact-6",
681
+ channelId: "channel-6",
682
+ status: "active",
683
+ policy: "bogus",
684
+ } satisfies TrustVerdict),
685
+ ).toBeNull();
686
+ });
687
+ });
688
+
689
+ describe("verdict predicates", () => {
690
+ test("verdictHasMemberIdentity is true with contactId or channelId", () => {
691
+ expect(
692
+ verdictHasMemberIdentity({
693
+ trustClass: "unknown",
694
+ canonicalSenderId: "u-1",
695
+ contactId: "contact-1",
696
+ } satisfies TrustVerdict),
697
+ ).toBe(true);
698
+ expect(
699
+ verdictHasMemberIdentity({
700
+ trustClass: "unknown",
701
+ canonicalSenderId: "u-1",
702
+ channelId: "channel-1",
703
+ } satisfies TrustVerdict),
704
+ ).toBe(true);
705
+ });
706
+
707
+ test("verdictHasMemberIdentity is false for a memberless verdict", () => {
708
+ expect(
709
+ verdictHasMemberIdentity({
710
+ trustClass: "unknown",
711
+ canonicalSenderId: "u-1",
712
+ } satisfies TrustVerdict),
713
+ ).toBe(false);
714
+ });
715
+
716
+ test("verdictMemberUnresolvable is true when member identity present but ACL unsynthesizable", () => {
717
+ expect(
718
+ verdictMemberUnresolvable({
719
+ trustClass: "trusted_contact",
720
+ canonicalSenderId: "u-1",
721
+ contactId: "contact-1",
722
+ channelId: "channel-1",
723
+ policy: "allow",
724
+ } satisfies TrustVerdict),
725
+ ).toBe(true);
726
+ });
727
+
728
+ test("verdictMemberUnresolvable is false for a usable member verdict", () => {
729
+ expect(
730
+ verdictMemberUnresolvable({
731
+ trustClass: "trusted_contact",
732
+ canonicalSenderId: "u-1",
733
+ contactId: "contact-1",
734
+ channelId: "channel-1",
735
+ status: "active",
736
+ policy: "allow",
737
+ } satisfies TrustVerdict),
738
+ ).toBe(false);
739
+ });
740
+
741
+ test("verdictMemberUnresolvable is false for a memberless verdict", () => {
742
+ expect(
743
+ verdictMemberUnresolvable({
744
+ trustClass: "unknown",
745
+ canonicalSenderId: "u-1",
746
+ } satisfies TrustVerdict),
747
+ ).toBe(false);
748
+ });
749
+ });
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import type { ChannelId } from "../channels/types.js";
15
- import { findGuardianForChannel } from "../contacts/contact-store.js";
15
+ import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
16
16
  import type { ChannelStatus } from "../contacts/types.js";
17
17
  import {
18
18
  createCanonicalGuardianRequest,
@@ -25,6 +25,7 @@ import {
25
25
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
26
26
  import type { GuardianResolutionSource } from "../notifications/signal.js";
27
27
  import { getLogger } from "../util/logger.js";
28
+ import { resolveAnchoredGuardian } from "./anchored-guardian.js";
28
29
  import { GUARDIAN_APPROVAL_TTL_MS } from "./routes/channel-route-shared.js";
29
30
 
30
31
  const log = getLogger("access-request-helper");
@@ -70,12 +71,12 @@ export type AccessRequestResult =
70
71
  * trust anchor and only accepts source-channel contacts that match it. This
71
72
  * prevents stale or cross-assistant contacts from being bound to the request.
72
73
  *
73
- * This is intentionally synchronous with respect to the canonical store writes
74
- * and fire-and-forget for the notification signal emission.
74
+ * The canonical store writes complete before this resolves; the notification
75
+ * signal emission is fire-and-forget.
75
76
  */
76
- export function notifyGuardianOfAccessRequest(
77
+ export async function notifyGuardianOfAccessRequest(
77
78
  params: AccessRequestParams,
78
- ): AccessRequestResult {
79
+ ): Promise<AccessRequestResult> {
79
80
  const {
80
81
  canonicalAssistantId,
81
82
  sourceChannel,
@@ -94,40 +95,19 @@ export function notifyGuardianOfAccessRequest(
94
95
  return { notified: false, reason: "no_sender_id" };
95
96
  }
96
97
 
97
- // Resolve guardian identity with assistant-anchored strategy:
98
- // 1. Ensure the assistant has a vellum guardian principal (trust anchor)
99
- // 2. Use source-channel guardian only when principal matches anchor
100
- // 3. Fallback to vellum guardian identity for this assistant principal
101
- let guardianExternalUserId: string | null = null;
102
- let guardianPrincipalId: string | null = null;
103
- let guardianBindingChannel: string | null = null;
104
- let guardianResolutionSource: GuardianResolutionSource = "none";
105
-
106
- const vellumGuardian = findGuardianForChannel("vellum");
107
- const assistantGuardianPrincipalId = vellumGuardian?.contact.principalId;
108
-
109
- // Try source-channel guardian, but only if it maps to the assistant's
110
- // anchored principal. This blocks cross-assistant/stale contact selection.
111
- const sourceGuardian = findGuardianForChannel(sourceChannel);
112
- if (
113
- assistantGuardianPrincipalId &&
114
- sourceGuardian &&
115
- sourceGuardian.contact.principalId === assistantGuardianPrincipalId
116
- ) {
117
- guardianExternalUserId = sourceGuardian.channel.address;
118
- guardianPrincipalId = sourceGuardian.contact.principalId;
119
- guardianBindingChannel = sourceGuardian.channel.type;
120
- guardianResolutionSource = "source-channel-contact";
121
- }
122
-
123
- // Access requests always require a principal. If source-channel resolution
124
- // did not match the assistant anchor, use the anchored vellum identity.
125
- if (!guardianPrincipalId && vellumGuardian) {
126
- guardianExternalUserId = vellumGuardian.channel.address;
127
- guardianPrincipalId = assistantGuardianPrincipalId ?? null;
128
- guardianBindingChannel = guardianBindingChannel ?? "vellum";
129
- guardianResolutionSource = "vellum-anchor";
130
- }
98
+ // Resolve guardian identity with the assistant-anchored strategy (gateway
99
+ // source-channel match validated against the vellum anchor, else the vellum
100
+ // anchor), with a LOCAL-store fallback when the gateway read is empty.
101
+ const anchored = resolveAnchoredGuardian({
102
+ guardians: await getGuardianDelivery(),
103
+ sourceChannel,
104
+ useLocalFallback: true,
105
+ });
106
+ const guardianExternalUserId = anchored?.address ?? null;
107
+ const guardianPrincipalId = anchored?.principalId ?? null;
108
+ const guardianBindingChannel = anchored?.channelType ?? null;
109
+ const guardianResolutionSource: GuardianResolutionSource =
110
+ anchored?.source ?? "none";
131
111
 
132
112
  log.debug(
133
113
  {
@@ -323,8 +323,8 @@ export function toTrustContext(
323
323
  requesterMemberDisplayName: ctx.actorMetadata.memberDisplayName,
324
324
  requesterExternalUserId: ctx.canonicalSenderId ?? undefined,
325
325
  requesterChatId: conversationExternalId,
326
- // Member grounding from a real memberRecord (voice path); the verdict path
327
- // (memberRecord=null) stamps these from the verdict instead.
326
+ // Member grounding from the resolved memberRecord (voice + verdict paths
327
+ // both populate it).
328
328
  requesterContactId: ctx.memberRecord?.contact.id,
329
329
  memberStatus: ctx.memberRecord
330
330
  ? channelStatusToMemberStatus(ctx.memberRecord.channel.status)