@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -10,10 +10,14 @@
10
10
  * fail-closed); this reader only reports the verdict or `null`.
11
11
  */
12
12
 
13
- import { type TrustVerdict, TrustVerdictSchema } from "@vellumai/gateway-client";
13
+ import {
14
+ type TrustVerdict,
15
+ TrustVerdictSchema,
16
+ } from "@vellumai/gateway-client";
14
17
 
15
18
  import type { ChannelId } from "../channels/types.js";
16
19
  import { ipcCall } from "../ipc/gateway-client.js";
20
+ import { setMemberVerdict } from "../runtime/member-verdict-cache.js";
17
21
 
18
22
  // Short IPC timeout so the read resolves promptly rather than stalling call
19
23
  // setup on a gateway that accepts the socket but hangs.
@@ -33,8 +37,26 @@ export async function getInboundTrustVerdict(input: {
33
37
  if (!result) return null;
34
38
 
35
39
  const parsed = TrustVerdictSchema.safeParse(result.verdict);
36
- return parsed.success ? parsed.data : null;
40
+ if (!parsed.success) return null;
41
+
42
+ // Single choke point: warm the member-verdict cache so the sync trust
43
+ // fallback resolves the member without a local ACL read.
44
+ setMemberVerdict(input.channelType, input.actorExternalId, parsed.data);
45
+ return parsed.data;
37
46
  } catch {
38
47
  return null;
39
48
  }
40
49
  }
50
+
51
+ /**
52
+ * Resolve the verdict for a phone caller by their external number. Callers
53
+ * compute `otherPartyNumber` from their own transport-specific direction.
54
+ */
55
+ export function getPhoneCallerVerdict(
56
+ otherPartyNumber: string | undefined,
57
+ ): Promise<TrustVerdict | null> {
58
+ return getInboundTrustVerdict({
59
+ channelType: "phone",
60
+ actorExternalId: otherPartyNumber || undefined,
61
+ });
62
+ }
@@ -52,6 +52,7 @@ import {
52
52
  } from "./call-store.js";
53
53
  import { getChannelAdmissionPolicy } from "./channel-admission-reader.js";
54
54
  import { finalizeCall } from "./finalize-call.js";
55
+ import { getPhoneCallerVerdict } from "./inbound-trust-reader.js";
55
56
  import { MediaStreamOutput } from "./media-stream-output.js";
56
57
  import { parseMediaStreamFrame } from "./media-stream-parser.js";
57
58
  import type { MediaStreamStartEvent } from "./media-stream-protocol.js";
@@ -391,6 +392,25 @@ export class MediaStreamCallSession {
391
392
  return;
392
393
  }
393
394
 
395
+ // Verdict-first caller trust so this transport enforces the same gateway
396
+ // ACL as ConversationRelay. routeSetup uses it when present and not
397
+ // resolutionFailed, else falls back to local resolution. The reader returns
398
+ // null on failure, keeping the local path on a gateway blip.
399
+ const isInbound = session?.initiatedFromConversationId == null;
400
+ const otherPartyNumber = isInbound ? from : to;
401
+ const verdict = await getPhoneCallerVerdict(otherPartyNumber);
402
+
403
+ // The verdict read above yields the event loop; abort if the session was
404
+ // disposed meanwhile, matching the admission-read guard above.
405
+ if (this.disposed) {
406
+ log.info(
407
+ { callSessionId: this.callSessionId },
408
+ "Media-stream session disposed during verdict read — aborting setup",
409
+ );
410
+ this.setupRouting = false;
411
+ return;
412
+ }
413
+
394
414
  const { outcome, resolved } = routeSetup({
395
415
  callSessionId: this.callSessionId,
396
416
  session: session ?? null,
@@ -398,6 +418,7 @@ export class MediaStreamCallSession {
398
418
  to,
399
419
  customParameters: event.start.customParameters,
400
420
  admissionPolicy,
421
+ verdict,
401
422
  });
402
423
 
403
424
  log.info(
@@ -9,6 +9,7 @@
9
9
  import { findContactChannel } from "../contacts/contact-store.js";
10
10
  import { getCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
11
11
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
12
+ import { getCachedMemberAcl } from "../runtime/member-verdict-cache.js";
12
13
  import { getLogger } from "../util/logger.js";
13
14
  import {
14
15
  getGuardianWaitUpdateInitialIntervalMs,
@@ -252,12 +253,11 @@ export function emitAccessRequestCallbackHandoff(
252
253
  address: fromNumber,
253
254
  externalChatId: fromNumber,
254
255
  });
255
- if (
256
- contactResult &&
257
- contactResult.channel.status === "active" &&
258
- contactResult.channel.policy === "allow"
259
- ) {
260
- requesterMemberId = contactResult.channel.id;
256
+ if (contactResult) {
257
+ const acl = getCachedMemberAcl("phone", fromNumber);
258
+ if (acl?.status === "active" && acl.policy === "allow") {
259
+ requesterMemberId = contactResult.channel.id;
260
+ }
261
261
  }
262
262
  } catch (err) {
263
263
  log.warn(
@@ -8,15 +8,17 @@
8
8
 
9
9
  import { randomInt } from "node:crypto";
10
10
 
11
- import type { AdmissionPolicy } from "@vellumai/gateway-client";
11
+ import type { AdmissionPolicy, TrustVerdict } from "@vellumai/gateway-client";
12
12
  import type { ServerWebSocket } from "bun";
13
13
 
14
14
  import {
15
- findGuardianForChannel,
16
- listGuardianChannels,
17
- } from "../contacts/contact-store.js";
15
+ getGuardianDelivery,
16
+ getGuardianDeliveryFresh,
17
+ voiceGuardianDisplayName,
18
+ } from "../contacts/guardian-delivery-reader.js";
18
19
  import { getAssistantName } from "../daemon/identity-helpers.js";
19
20
  import type { ServerMessage } from "../daemon/message-protocol.js";
21
+ import type { TrustContext } from "../daemon/trust-context.js";
20
22
  import { getCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
21
23
  import { addMessage } from "../memory/conversation-crud.js";
22
24
  import { revokeScopedApprovalGrantsForContext } from "../memory/scoped-approval-grants.js";
@@ -26,6 +28,11 @@ import {
26
28
  resolveActorTrust,
27
29
  toTrustContext,
28
30
  } from "../runtime/actor-trust-resolver.js";
31
+ import {
32
+ trustContextFromVerdict,
33
+ verdictHasMemberIdentity,
34
+ verdictMemberUnresolvable,
35
+ } from "../runtime/trust-verdict-consumer.js";
29
36
  import {
30
37
  composeVerificationVoice,
31
38
  GUARDIAN_VERIFY_TEMPLATE_KEYS,
@@ -50,6 +57,10 @@ import {
50
57
  import { ConversationRelayTransport } from "./call-transport.js";
51
58
  import { getChannelAdmissionPolicy } from "./channel-admission-reader.js";
52
59
  import { finalizeCall } from "./finalize-call.js";
60
+ import {
61
+ getInboundTrustVerdict,
62
+ getPhoneCallerVerdict,
63
+ } from "./inbound-trust-reader.js";
53
64
  import {
54
65
  classifyWaitUtterance,
55
66
  emitAccessRequestCallbackHandoff,
@@ -252,6 +263,10 @@ export class RelayConnection {
252
263
  private accessRequestWaitStartedAt: number = 0;
253
264
  private heartbeatSequence = 0;
254
265
 
266
+ // Guardian displayName primed from the gateway binding at setup, read
267
+ // synchronously by the heartbeat-driven wait-label path.
268
+ private primedGuardianDisplayName: string | undefined;
269
+
255
270
  // In-wait prompt handling state
256
271
  private lastInWaitReplyAt = 0;
257
272
  private static readonly IN_WAIT_REPLY_COOLDOWN_MS = 3000;
@@ -261,6 +276,12 @@ export class RelayConnection {
261
276
  private callbackOptIn = false;
262
277
  private callbackHandoffNotified = false;
263
278
 
279
+ // True while mid-call trust is being re-resolved (async). handlePrompt defers
280
+ // prompts to trustReResolvePending so a verified caller can't start a turn
281
+ // under the stale pre-verification trust context during the await window.
282
+ private trustReResolving = false;
283
+ private trustReResolvePending: RelayPromptMessage[] = [];
284
+
264
285
  constructor(ws: ServerWebSocket<RelayWebSocketData>, callSessionId: string) {
265
286
  this.ws = ws;
266
287
  this.callSessionId = callSessionId;
@@ -581,12 +602,33 @@ export class RelayConnection {
581
602
  const session = getCallSession(this.callSessionId);
582
603
  this.recordSetupBookkeeping(session, msg);
583
604
 
605
+ // Prime the guardian displayName and warm the phone-channel guardian-
606
+ // delivery cache before routeSetup's SYNC resolveActorTrust fallback. That
607
+ // fallback reads the IO-free cache snapshot keyed per channel-filter; daemon
608
+ // startup warms only `vellum`, so a cold-process phone call would otherwise
609
+ // misclassify a guardian during a gateway verdict blip. Read fresh: gateway-
610
+ // side binding writes don't invalidate the daemon cache, so a stale empty
611
+ // snapshot from an earlier setup would survive the TTL otherwise. Both are
612
+ // independent IPC reads on different cache keys, so run them concurrently
613
+ // off the sync resolver's hot path.
614
+ await Promise.all([
615
+ this.primeGuardianDisplayName(),
616
+ getGuardianDeliveryFresh({ channelTypes: ["phone"] }),
617
+ ]);
618
+
584
619
  // Resolve the phone channel's inbound admission floor. The reader fails
585
620
  // open to `null` by contract, so a transport hiccup admits the caller.
586
621
  const admissionPolicy = await getChannelAdmissionPolicy("phone");
587
622
 
623
+ // Verdict-first caller trust. routeSetup uses it when present and not
624
+ // resolutionFailed, else falls back to local resolution. The reader
625
+ // returns null on failure, so a gateway blip keeps the local path.
626
+ const isInbound = session?.initiatedFromConversationId == null;
627
+ const otherPartyNumber = isInbound ? msg.from : msg.to;
628
+ const verdict = await getPhoneCallerVerdict(otherPartyNumber);
629
+
588
630
  try {
589
- await this.routeSetupOutcome(msg, session, admissionPolicy);
631
+ await this.routeSetupOutcome(msg, session, admissionPolicy, verdict);
590
632
  } catch (err) {
591
633
  // Never leave the connection stranded in "setting_up": a setup that
592
634
  // throws before reaching a terminal outcome would otherwise drop every
@@ -615,6 +657,7 @@ export class RelayConnection {
615
657
  msg: RelaySetupMessage,
616
658
  session: ReturnType<typeof getCallSession>,
617
659
  admissionPolicy: AdmissionPolicy | null,
660
+ verdict: TrustVerdict | null,
618
661
  ): Promise<void> {
619
662
  const { outcome, resolved } = routeSetup({
620
663
  callSessionId: this.callSessionId,
@@ -623,6 +666,7 @@ export class RelayConnection {
623
666
  to: msg.to,
624
667
  customParameters: msg.customParameters,
625
668
  admissionPolicy,
669
+ verdict,
626
670
  });
627
671
 
628
672
  const initialTrustContext = toTrustContext(
@@ -780,7 +824,7 @@ export class RelayConnection {
780
824
  from,
781
825
  trustClass: resolved.actorTrust.trustClass,
782
826
  channelId: resolved.actorTrust.memberRecord?.channel.id,
783
- memberPolicy: resolved.actorTrust.memberRecord?.channel.policy,
827
+ memberPolicy: resolved.actorTrust.memberRecord?.policy,
784
828
  });
785
829
  this.connectionState = "disconnecting";
786
830
  updateCallSession(this.callSessionId, {
@@ -871,6 +915,102 @@ export class RelayConnection {
871
915
  );
872
916
  }
873
917
 
918
+ /**
919
+ * Re-resolve caller trust after a mid-call verification/activation. Prefers
920
+ * the gateway verdict (authoritative right after the gateway updated the
921
+ * binding); falls back to local resolution on a missing/failed/unusable
922
+ * verdict so a blip never drops the call. Mirrors the setup path's
923
+ * verdict-first-with-fallback condition.
924
+ */
925
+ private async resolveMidCallTrustContext(
926
+ assistantId: string,
927
+ fromNumber: string,
928
+ ): Promise<TrustContext> {
929
+ const verdict = await getInboundTrustVerdict({
930
+ channelType: "phone",
931
+ actorExternalId: fromNumber,
932
+ });
933
+
934
+ // Only a MEMBERLESS unknown verdict is treated as a stale gateway view and
935
+ // falls back to local: the caller was just activated, and invite redemption
936
+ // writes the channel assistant-side, so the gateway may not see the member
937
+ // yet — local resolution has it. A MEMBERFUL unknown verdict (blocked/revoked
938
+ // member, carrying contactId/channelId) is honored so its deny ACL is
939
+ // enforced; falling back could lose the gateway's member status if local
940
+ // state is stale.
941
+ const memberlessUnknown =
942
+ verdict?.trustClass === "unknown" && !verdictHasMemberIdentity(verdict);
943
+ const usable =
944
+ verdict &&
945
+ !verdict.resolutionFailed &&
946
+ !verdictMemberUnresolvable(verdict) &&
947
+ !memberlessUnknown;
948
+
949
+ if (usable) {
950
+ return trustContextFromVerdict(verdict, {
951
+ sourceChannel: "phone",
952
+ conversationExternalId: fromNumber,
953
+ });
954
+ }
955
+
956
+ // Warm the phone-channel guardian-delivery cache before the SYNC
957
+ // resolveActorTrust fallback, which reads the IO-free per-channel snapshot
958
+ // that daemon startup leaves cold for `phone`. Read fresh: gateway-side
959
+ // binding writes don't invalidate the daemon cache, so a stale empty
960
+ // snapshot would otherwise survive the TTL and misclassify the guardian.
961
+ await getGuardianDeliveryFresh({ channelTypes: ["phone"] });
962
+
963
+ return toTrustContext(
964
+ resolveActorTrust({
965
+ assistantId,
966
+ sourceChannel: "phone",
967
+ conversationExternalId: fromNumber,
968
+ actorExternalId: fromNumber,
969
+ }),
970
+ fromNumber,
971
+ );
972
+ }
973
+
974
+ /**
975
+ * Re-resolve mid-call trust and install it on the controller, deferring any
976
+ * prompt that arrives during the async window. Guards the gap between
977
+ * connectionState flipping to "connected" and the upgraded context landing so
978
+ * a verified caller never starts a turn under the stale pre-verification
979
+ * trust. Clears the guard in a finally and flushes buffered prompts under the
980
+ * new context, so an IPC error can't wedge the call.
981
+ */
982
+ private async reResolveAndApplyTrustContext(
983
+ assistantId: string,
984
+ fromNumber: string,
985
+ ): Promise<void> {
986
+ if (!this.controller) return;
987
+ this.trustReResolving = true;
988
+ try {
989
+ const context = await this.resolveMidCallTrustContext(
990
+ assistantId,
991
+ fromNumber,
992
+ );
993
+ this.controller.setTrustContext(context);
994
+ } finally {
995
+ this.trustReResolving = false;
996
+ this.flushDeferredPrompts();
997
+ }
998
+ }
999
+
1000
+ /**
1001
+ * Replay prompts buffered while trust was re-resolving. Runs after the
1002
+ * upgraded context is installed so deferred utterances are answered under the
1003
+ * correct trust.
1004
+ */
1005
+ private flushDeferredPrompts(): void {
1006
+ if (this.trustReResolvePending.length === 0) return;
1007
+ const pending = this.trustReResolvePending;
1008
+ this.trustReResolvePending = [];
1009
+ for (const msg of pending) {
1010
+ void this.handlePrompt(msg);
1011
+ }
1012
+ }
1013
+
874
1014
  /**
875
1015
  * Shared post-activation handoff for all trusted-contact success paths
876
1016
  * (access-request approval, invite redemption, verification code).
@@ -878,7 +1018,7 @@ export class RelayConnection {
878
1018
  * transition copy, and marks the next utterance as opening-ack so the
879
1019
  * LLM continues naturally.
880
1020
  */
881
- private continueCallAfterTrustedContactActivation(params: {
1021
+ private async continueCallAfterTrustedContactActivation(params: {
882
1022
  assistantId: string;
883
1023
  fromNumber: string;
884
1024
  activationReason?:
@@ -893,26 +1033,25 @@ export class RelayConnection {
893
1033
  * address.
894
1034
  */
895
1035
  inviteeName?: string | null;
896
- }): void {
1036
+ }): Promise<void> {
897
1037
  const { assistantId, fromNumber } = params;
898
1038
 
899
1039
  // Contact activation is handled by the gateway — the assistant no
900
1040
  // longer writes contact/channel records on inbound voice calls.
901
1041
 
902
- const updatedTrust = resolveActorTrust({
903
- assistantId,
904
- sourceChannel: "phone",
905
- conversationExternalId: fromNumber,
906
- actorExternalId: fromNumber,
907
- });
1042
+ // Reach connected and clear wait/verification flags before re-resolving
1043
+ // trust, so a prompt buffered during re-resolution flushes onto the
1044
+ // real-turn path, not the verification/wait branches.
1045
+ this.connectionState = "connected";
1046
+ this.verificationSessionActive = false;
1047
+ this.inviteRedemptionActive = false;
1048
+ this.accessRequestWaitActive = false;
1049
+ updateCallSession(this.callSessionId, { status: "in_progress" });
908
1050
 
909
1051
  if (this.controller) {
910
- this.controller.setTrustContext(toTrustContext(updatedTrust, fromNumber));
1052
+ await this.reResolveAndApplyTrustContext(assistantId, fromNumber);
911
1053
  }
912
1054
 
913
- this.connectionState = "connected";
914
- updateCallSession(this.callSessionId, { status: "in_progress" });
915
-
916
1055
  const guardianLabel = this.resolveGuardianLabel();
917
1056
  let handoffText: string;
918
1057
 
@@ -1045,7 +1184,7 @@ export class RelayConnection {
1045
1184
  const assistantId = this.verificationAssistantId;
1046
1185
  const fromNumber = this.verificationFromNumber;
1047
1186
 
1048
- const result = attemptVerificationCode({
1187
+ const result = await attemptVerificationCode({
1049
1188
  verificationAssistantId: assistantId,
1050
1189
  verificationFromNumber: fromNumber,
1051
1190
  enteredCode,
@@ -1091,15 +1230,7 @@ export class RelayConnection {
1091
1230
 
1092
1231
  // Update trust context on the controller so the LLM knows this is the guardian
1093
1232
  if (this.controller) {
1094
- const verifiedActorTrust = resolveActorTrust({
1095
- assistantId,
1096
- sourceChannel: "phone",
1097
- conversationExternalId: fromNumber,
1098
- actorExternalId: fromNumber,
1099
- });
1100
- this.controller.setTrustContext(
1101
- toTrustContext(verifiedActorTrust, fromNumber),
1102
- );
1233
+ await this.reResolveAndApplyTrustContext(assistantId, fromNumber);
1103
1234
  }
1104
1235
 
1105
1236
  // Mark session as in-progress and transition to guardian conversation
@@ -1116,7 +1247,7 @@ export class RelayConnection {
1116
1247
  );
1117
1248
  }
1118
1249
  } else if (result.verificationType === "trusted_contact") {
1119
- this.continueCallAfterTrustedContactActivation({
1250
+ await this.continueCallAfterTrustedContactActivation({
1120
1251
  assistantId,
1121
1252
  fromNumber,
1122
1253
  activationReason: "trusted_contact_verified",
@@ -1125,15 +1256,7 @@ export class RelayConnection {
1125
1256
  // Inbound guardian verification: binding already handled above,
1126
1257
  // proceed to normal call flow.
1127
1258
  if (this.controller) {
1128
- const verifiedActorTrust = resolveActorTrust({
1129
- assistantId,
1130
- sourceChannel: "phone",
1131
- conversationExternalId: fromNumber,
1132
- actorExternalId: fromNumber,
1133
- });
1134
- this.controller.setTrustContext(
1135
- toTrustContext(verifiedActorTrust, fromNumber),
1136
- );
1259
+ await this.reResolveAndApplyTrustContext(assistantId, fromNumber);
1137
1260
  this.startNormalCallFlow(this.controller, true);
1138
1261
  }
1139
1262
  }
@@ -1296,7 +1419,7 @@ export class RelayConnection {
1296
1419
  * Creates a canonical access request, notifies the guardian, and
1297
1420
  * enters the bounded wait loop for the guardian decision.
1298
1421
  */
1299
- private handleNameCaptureResponse(callerName: string): void {
1422
+ private async handleNameCaptureResponse(callerName: string): Promise<void> {
1300
1423
  if (!this.accessRequestAssistantId || !this.accessRequestFromNumber) {
1301
1424
  return;
1302
1425
  }
@@ -1317,7 +1440,7 @@ export class RelayConnection {
1317
1440
  // Create canonical access request and notify the guardian, including
1318
1441
  // the caller's spoken name and voice channel metadata.
1319
1442
  try {
1320
- const accessResult = notifyGuardianOfAccessRequest({
1443
+ const accessResult = await notifyGuardianOfAccessRequest({
1321
1444
  canonicalAssistantId: this.accessRequestAssistantId,
1322
1445
  sourceChannel: "phone",
1323
1446
  conversationExternalId: this.accessRequestFromNumber,
@@ -1410,7 +1533,7 @@ export class RelayConnection {
1410
1533
  }
1411
1534
 
1412
1535
  if (request.status === "approved") {
1413
- this.handleAccessRequestApproved();
1536
+ void this.handleAccessRequestApproved();
1414
1537
  } else if (request.status === "denied") {
1415
1538
  void this.handleAccessRequestDenied();
1416
1539
  }
@@ -1462,7 +1585,7 @@ export class RelayConnection {
1462
1585
  * Handle an approved access request: activate the caller as a trusted
1463
1586
  * contact, update runtime context, and continue with normal call flow.
1464
1587
  */
1465
- private handleAccessRequestApproved(): void {
1588
+ private async handleAccessRequestApproved(): Promise<void> {
1466
1589
  this.clearAccessRequestWait();
1467
1590
 
1468
1591
  const assistantId = this.accessRequestAssistantId!;
@@ -1480,7 +1603,7 @@ export class RelayConnection {
1480
1603
  "Access request approved — caller activated and continuing call",
1481
1604
  );
1482
1605
 
1483
- this.continueCallAfterTrustedContactActivation({
1606
+ await this.continueCallAfterTrustedContactActivation({
1484
1607
  assistantId,
1485
1608
  fromNumber,
1486
1609
  activationReason: "access_approved",
@@ -1688,7 +1811,7 @@ export class RelayConnection {
1688
1811
  "Voice invite redemption succeeded",
1689
1812
  );
1690
1813
 
1691
- this.continueCallAfterTrustedContactActivation({
1814
+ await this.continueCallAfterTrustedContactActivation({
1692
1815
  assistantId: this.inviteRedemptionAssistantId,
1693
1816
  fromNumber: this.inviteRedemptionFromNumber,
1694
1817
  activationReason: "invite_redeemed",
@@ -1731,15 +1854,20 @@ export class RelayConnection {
1731
1854
  * Resolve a human-readable guardian label for voice wait copy.
1732
1855
  * Delegates to the shared resolveGuardianName() which checks the
1733
1856
  * guardian's per-user persona file (users/<slug>.md) first, then falls
1734
- * back to Contact.displayName, then DEFAULT_USER_REFERENCE.
1857
+ * back to the primed gateway-binding displayName, then
1858
+ * DEFAULT_USER_REFERENCE.
1735
1859
  */
1736
1860
  private resolveGuardianLabel(): string {
1737
- // Look up the guardian contact for a displayName fallback
1738
- const voiceGuardian = findGuardianForChannel("phone");
1739
- const guardianChannels = voiceGuardian ? null : listGuardianChannels();
1740
- const guardianContact = voiceGuardian?.contact ?? guardianChannels?.contact;
1861
+ return resolveGuardianName(this.primedGuardianDisplayName);
1862
+ }
1741
1863
 
1742
- return resolveGuardianName(guardianContact?.displayName);
1864
+ /**
1865
+ * Prime the guardian displayName from the gateway binding so the
1866
+ * synchronous wait-label path can read it without an IPC round-trip.
1867
+ */
1868
+ private async primeGuardianDisplayName(): Promise<void> {
1869
+ const list = await getGuardianDelivery();
1870
+ this.primedGuardianDisplayName = voiceGuardianDisplayName(list);
1743
1871
  }
1744
1872
 
1745
1873
  /**
@@ -1917,6 +2045,15 @@ export class RelayConnection {
1917
2045
  return;
1918
2046
  }
1919
2047
 
2048
+ // Defer (don't drop) prompts while trust is being re-resolved mid-call so a
2049
+ // verified caller's first utterance runs under the upgraded context, not the
2050
+ // stale pre-verification one. flushDeferredPrompts replays them in order
2051
+ // once the new context lands.
2052
+ if (this.trustReResolving) {
2053
+ this.trustReResolvePending.push(msg);
2054
+ return;
2055
+ }
2056
+
1920
2057
  // Prompts arriving before setup routing/ACL completes are dropped — never
1921
2058
  // persisted or emitted — so a not-yet-authorized caller's speech can't be
1922
2059
  // stored ahead of the admission floor / trust ACL decision.
@@ -1942,7 +2079,7 @@ export class RelayConnection {
1942
2079
  { callSessionId: this.callSessionId, callerName },
1943
2080
  "Name captured from unknown inbound caller",
1944
2081
  );
1945
- this.handleNameCaptureResponse(callerName);
2082
+ await this.handleNameCaptureResponse(callerName);
1946
2083
  return;
1947
2084
  }
1948
2085
 
@@ -6,7 +6,7 @@
6
6
  * next — without performing any side effects itself.
7
7
  */
8
8
 
9
- import type { AdmissionPolicy } from "@vellumai/gateway-client";
9
+ import type { AdmissionPolicy, TrustVerdict } from "@vellumai/gateway-client";
10
10
 
11
11
  import { getConfig } from "../config/loader.js";
12
12
  import { getContact } from "../contacts/contact-store.js";
@@ -21,6 +21,10 @@ import {
21
21
  type AdmissionPolicyResult,
22
22
  enforceAdmissionPolicy,
23
23
  } from "../runtime/routes/inbound-stages/admission-policy.js";
24
+ import {
25
+ actorTrustContextFromVerdict,
26
+ verdictMemberUnresolvable,
27
+ } from "../runtime/trust-verdict-consumer.js";
24
28
  import { getLogger } from "../util/logger.js";
25
29
  import type { CallSession } from "./types.js";
26
30
 
@@ -40,6 +44,12 @@ interface SetupContext {
40
44
  * preserving all pre-admission behavior.
41
45
  */
42
46
  admissionPolicy?: AdmissionPolicy | null;
47
+ /**
48
+ * Gateway-stamped caller trust verdict. When present and not
49
+ * `resolutionFailed`, the caller's trust is built from it; otherwise the
50
+ * router falls back to local resolution.
51
+ */
52
+ verdict?: TrustVerdict | null;
43
53
  }
44
54
 
45
55
  // ── Setup outcomes ───────────────────────────────────────────────────
@@ -112,12 +122,32 @@ export function routeSetup(ctx: SetupContext): {
112
122
  const isInbound = ctx.session?.initiatedFromConversationId == null;
113
123
  const otherPartyNumber = isInbound ? ctx.from : ctx.to;
114
124
 
115
- const actorTrust = resolveActorTrust({
116
- assistantId,
117
- sourceChannel: "phone",
118
- conversationExternalId: otherPartyNumber,
119
- actorExternalId: otherPartyNumber || undefined,
120
- });
125
+ // Verdict-first: build caller trust from the gateway verdict when present.
126
+ // Voice falls back to local resolution on a missing/failed verdict so a
127
+ // gateway blip does not drop a known guardian's call — the deliberate
128
+ // difference from the fail-closed text path.
129
+ //
130
+ // A verdict that claims a member (contactId/channelId) but whose ACL can't be
131
+ // reassembled (malformed/mixed-version status·policy) also falls back to local
132
+ // resolution, so voice never trusts a member it cannot ACL-check. A real
133
+ // stranger verdict (no member identity) still takes the verdict path —
134
+ // memberRecord null is correct there.
135
+ const usable =
136
+ ctx.verdict &&
137
+ !ctx.verdict.resolutionFailed &&
138
+ !verdictMemberUnresolvable(ctx.verdict);
139
+ const actorTrust = usable
140
+ ? actorTrustContextFromVerdict(ctx.verdict!, {
141
+ sourceChannel: "phone",
142
+ conversationExternalId: otherPartyNumber,
143
+ actorDisplayName: undefined,
144
+ })
145
+ : resolveActorTrust({
146
+ assistantId,
147
+ sourceChannel: "phone",
148
+ conversationExternalId: otherPartyNumber,
149
+ actorExternalId: otherPartyNumber || undefined,
150
+ });
121
151
 
122
152
  const resolved: SetupResolved = {
123
153
  assistantId,
@@ -216,7 +246,7 @@ export function routeSetup(ctx: SetupContext): {
216
246
  ? enforceAdmissionPolicy({
217
247
  sourceChannel: "phone",
218
248
  trustClass: actorTrust.trustClass,
219
- memberStatus: actorTrust.memberRecord?.channel.status,
249
+ memberStatus: actorTrust.memberRecord?.status,
220
250
  policy: ctx.admissionPolicy!,
221
251
  })
222
252
  : ({ admitted: true } as const);
@@ -253,7 +283,7 @@ export function routeSetup(ctx: SetupContext): {
253
283
  !pendingChallenge
254
284
  ) {
255
285
  // Check for blocked caller
256
- if (actorTrust.memberRecord?.channel.status === "blocked") {
286
+ if (actorTrust.memberRecord?.status === "blocked") {
257
287
  log.info(
258
288
  {
259
289
  callSessionId: ctx.callSessionId,
@@ -336,14 +366,14 @@ export function routeSetup(ctx: SetupContext): {
336
366
  // gateway and assistant DBs) still get useful guidance instead of
337
367
  // the "I don't recognize this number" name-capture script.
338
368
  const unverifiedStatuses = new Set(["unverified", "pending"]);
339
- const memberChannel = actorTrust.memberRecord?.channel;
340
- if (memberChannel && unverifiedStatuses.has(memberChannel.status)) {
369
+ const member = actorTrust.memberRecord;
370
+ if (member && unverifiedStatuses.has(member.status)) {
341
371
  log.info(
342
372
  {
343
373
  callSessionId: ctx.callSessionId,
344
374
  from: ctx.from,
345
- channelId: memberChannel.id,
346
- channelStatus: memberChannel.status,
375
+ channelId: member.channel.id,
376
+ channelStatus: member.status,
347
377
  },
348
378
  "Inbound voice ACL: known but unverified caller — returning verification guidance",
349
379
  );
@@ -352,8 +382,8 @@ export function routeSetup(ctx: SetupContext): {
352
382
  action: "unverified_caller",
353
383
  assistantId,
354
384
  fromNumber: ctx.from,
355
- displayName: actorTrust.memberRecord!.contact.displayName,
356
- isGuardian: actorTrust.memberRecord!.contact.role === "guardian",
385
+ displayName: member.contact.displayName,
386
+ isGuardian: member.role === "guardian",
357
387
  },
358
388
  resolved,
359
389
  };
@@ -379,7 +409,7 @@ export function routeSetup(ctx: SetupContext): {
379
409
  }
380
410
 
381
411
  // Members with policy: 'deny'
382
- if (actorTrust.memberRecord?.channel.policy === "deny") {
412
+ if (actorTrust.memberRecord?.policy === "deny") {
383
413
  log.info(
384
414
  {
385
415
  callSessionId: ctx.callSessionId,
@@ -400,7 +430,7 @@ export function routeSetup(ctx: SetupContext): {
400
430
  }
401
431
 
402
432
  // Members with policy: 'escalate' — live calls can't wait for approval
403
- if (actorTrust.memberRecord?.channel.policy === "escalate") {
433
+ if (actorTrust.memberRecord?.policy === "escalate") {
404
434
  log.info(
405
435
  {
406
436
  callSessionId: ctx.callSessionId,