@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,156 @@
1
+ /**
2
+ * Unit tests for `resolveAnchoredGuardian`.
3
+ *
4
+ * Covers the gateway arms (source-channel match validated against the vellum
5
+ * anchor, vellum-anchor fallback), the LOCAL-store fallback when the gateway
6
+ * list is empty, and the cosmetic `requireAnchorPrincipal` guard.
7
+ */
8
+ import { afterEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
11
+
12
+ // Local store fallback is mocked so we can drive both arms deterministically.
13
+ let localGuardians: Record<
14
+ string,
15
+ { contact: { principalId: string | null; displayName: string }; channel: { address: string; type: string } } | null
16
+ > = {};
17
+ mock.module("../contacts/contact-store.js", () => ({
18
+ findGuardianForChannel: (channelType: string) =>
19
+ localGuardians[channelType] ?? null,
20
+ }));
21
+
22
+ const { resolveAnchoredGuardian } = await import("./anchored-guardian.js");
23
+
24
+ function gw(g: Partial<GuardianDelivery> & { channelType: string; address: string }): GuardianDelivery {
25
+ return {
26
+ contactId: `c-${g.channelType}`,
27
+ status: "active",
28
+ ...g,
29
+ };
30
+ }
31
+
32
+ afterEach(() => {
33
+ localGuardians = {};
34
+ });
35
+
36
+ describe("resolveAnchoredGuardian — gateway arm", () => {
37
+ test("source-channel guardian matching the anchor wins", () => {
38
+ const result = resolveAnchoredGuardian({
39
+ guardians: [
40
+ gw({ channelType: "vellum", address: "v-addr", principalId: "p-anchor", displayName: "Vellum" }),
41
+ gw({ channelType: "telegram", address: "tg-addr", principalId: "p-anchor", displayName: "Alice" }),
42
+ ],
43
+ sourceChannel: "telegram",
44
+ });
45
+ expect(result).toEqual({
46
+ principalId: "p-anchor",
47
+ address: "tg-addr",
48
+ displayName: "Alice",
49
+ channelType: "telegram",
50
+ source: "source-channel-contact",
51
+ });
52
+ });
53
+
54
+ test("source-channel guardian NOT matching the anchor falls back to vellum-anchor", () => {
55
+ const result = resolveAnchoredGuardian({
56
+ guardians: [
57
+ gw({ channelType: "vellum", address: "v-addr", principalId: "p-anchor", displayName: "Vellum" }),
58
+ gw({ channelType: "telegram", address: "tg-addr", principalId: "p-other", displayName: "Stale" }),
59
+ ],
60
+ sourceChannel: "telegram",
61
+ });
62
+ expect(result).toEqual({
63
+ principalId: "p-anchor",
64
+ address: "v-addr",
65
+ displayName: "Vellum",
66
+ channelType: "vellum",
67
+ source: "vellum-anchor",
68
+ });
69
+ });
70
+
71
+ test("no source-channel guardian falls back to vellum-anchor", () => {
72
+ const result = resolveAnchoredGuardian({
73
+ guardians: [
74
+ gw({ channelType: "vellum", address: "v-addr", principalId: "p-anchor", displayName: "Vellum" }),
75
+ ],
76
+ sourceChannel: "telegram",
77
+ });
78
+ expect(result?.source).toBe("vellum-anchor");
79
+ expect(result?.principalId).toBe("p-anchor");
80
+ });
81
+ });
82
+
83
+ describe("resolveAnchoredGuardian — local fallback", () => {
84
+ test("gateway empty + local source-channel match returns the local record", () => {
85
+ localGuardians = {
86
+ vellum: { contact: { principalId: "p-local", displayName: "LocalVellum" }, channel: { address: "lv-addr", type: "vellum" } },
87
+ telegram: { contact: { principalId: "p-local", displayName: "LocalAlice" }, channel: { address: "ltg-addr", type: "telegram" } },
88
+ };
89
+ const result = resolveAnchoredGuardian({
90
+ guardians: null,
91
+ sourceChannel: "telegram",
92
+ useLocalFallback: true,
93
+ });
94
+ expect(result).toEqual({
95
+ principalId: "p-local",
96
+ address: "ltg-addr",
97
+ displayName: "LocalAlice",
98
+ channelType: "telegram",
99
+ source: "source-channel-contact",
100
+ });
101
+ });
102
+
103
+ test("gateway empty + only local vellum returns the local vellum-anchor", () => {
104
+ localGuardians = {
105
+ vellum: { contact: { principalId: "p-local", displayName: "LocalVellum" }, channel: { address: "lv-addr", type: "vellum" } },
106
+ };
107
+ const result = resolveAnchoredGuardian({
108
+ guardians: null,
109
+ sourceChannel: "telegram",
110
+ useLocalFallback: true,
111
+ });
112
+ expect(result?.source).toBe("vellum-anchor");
113
+ expect(result?.address).toBe("lv-addr");
114
+ });
115
+
116
+ test("gateway empty + no local + fallback disabled returns null", () => {
117
+ const result = resolveAnchoredGuardian({
118
+ guardians: null,
119
+ sourceChannel: "telegram",
120
+ });
121
+ expect(result).toBeNull();
122
+ });
123
+
124
+ test("nothing anywhere returns null", () => {
125
+ const result = resolveAnchoredGuardian({
126
+ guardians: [],
127
+ sourceChannel: "telegram",
128
+ useLocalFallback: true,
129
+ });
130
+ expect(result).toBeNull();
131
+ });
132
+ });
133
+
134
+ describe("resolveAnchoredGuardian — requireAnchorPrincipal (cosmetic label)", () => {
135
+ test("vellum guardian with a null principal degrades to null", () => {
136
+ const result = resolveAnchoredGuardian({
137
+ guardians: [
138
+ gw({ channelType: "vellum", address: "v-addr", principalId: null, displayName: "Vellum" }),
139
+ ],
140
+ sourceChannel: "telegram",
141
+ requireAnchorPrincipal: true,
142
+ });
143
+ expect(result).toBeNull();
144
+ });
145
+
146
+ test("without requireAnchorPrincipal a null-principal vellum still resolves", () => {
147
+ const result = resolveAnchoredGuardian({
148
+ guardians: [
149
+ gw({ channelType: "vellum", address: "v-addr", principalId: null, displayName: "Vellum" }),
150
+ ],
151
+ sourceChannel: "telegram",
152
+ });
153
+ expect(result?.source).toBe("vellum-anchor");
154
+ expect(result?.principalId).toBeNull();
155
+ });
156
+ });
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Shared anchored-guardian resolution.
3
+ *
4
+ * Resolves the guardian identity for an inbound access request using the
5
+ * assistant's vellum principal as the trust anchor: a source-channel guardian
6
+ * is only accepted when its principal matches the anchor, otherwise the vellum
7
+ * anchor identity is used. This blocks stale or cross-assistant contacts from
8
+ * being bound to a request.
9
+ *
10
+ * Gateway-first: resolves from the gateway delivery list, then falls back to
11
+ * the LOCAL dual-written binding when the gateway read is empty/unavailable
12
+ * (restart, timeout, malformed IPC). The local fallback is transitional and is
13
+ * removed in a later step.
14
+ */
15
+
16
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
17
+
18
+ import type { ChannelId } from "../channels/types.js";
19
+ import { findGuardianForChannel } from "../contacts/contact-store.js";
20
+ import { guardianForChannel } from "../contacts/guardian-delivery-reader.js";
21
+ import type { GuardianResolutionSource } from "../notifications/signal.js";
22
+
23
+ /** Resolved guardian identity, anchored on the assistant's vellum principal. */
24
+ export interface AnchoredGuardian {
25
+ principalId: string | null;
26
+ address: string;
27
+ displayName: string | null;
28
+ channelType: string;
29
+ source: GuardianResolutionSource;
30
+ }
31
+
32
+ export interface ResolveAnchoredGuardianInput {
33
+ /** Gateway delivery list; `null` when the gateway read failed/was empty. */
34
+ guardians: GuardianDelivery[] | null;
35
+ sourceChannel: ChannelId;
36
+ /**
37
+ * Fall back to the LOCAL dual-written binding when the gateway arm resolves
38
+ * nothing. Access-request enables this to avoid an undecidable request; the
39
+ * cosmetic guardian-label path leaves it off so a missing gateway read
40
+ * degrades gracefully to the default reference.
41
+ */
42
+ useLocalFallback?: boolean;
43
+ /**
44
+ * Require a non-null anchor principal for the vellum-anchor arm. When the
45
+ * vellum guardian has no principal, return `null` instead of a vellum-anchor
46
+ * record. Matches the cosmetic label path, which degrades to the default
47
+ * reference when the anchor principal is absent.
48
+ */
49
+ requireAnchorPrincipal?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Resolve the anchored guardian for `sourceChannel`, or `null` when none can be
54
+ * resolved. Gateway source-channel match → that record; gateway anchor-only →
55
+ * vellum-anchor; gateway empty + local has it → local fallback record.
56
+ */
57
+ export function resolveAnchoredGuardian(
58
+ input: ResolveAnchoredGuardianInput,
59
+ ): AnchoredGuardian | null {
60
+ const { sourceChannel, useLocalFallback, requireAnchorPrincipal } = input;
61
+ const guardians = input.guardians ?? [];
62
+
63
+ const vellumGuardian = guardianForChannel(guardians, "vellum");
64
+ const anchorPrincipalId = vellumGuardian?.principalId;
65
+
66
+ let resolved: AnchoredGuardian | null = null;
67
+
68
+ // Source-channel guardian, but only when it maps to the assistant's anchored
69
+ // principal. This blocks cross-assistant/stale binding selection.
70
+ const sourceGuardian = guardianForChannel(guardians, sourceChannel);
71
+ if (
72
+ anchorPrincipalId &&
73
+ sourceGuardian &&
74
+ sourceGuardian.principalId === anchorPrincipalId
75
+ ) {
76
+ resolved = {
77
+ principalId: sourceGuardian.principalId,
78
+ address: sourceGuardian.address,
79
+ displayName: sourceGuardian.displayName ?? null,
80
+ channelType: sourceGuardian.channelType,
81
+ source: "source-channel-contact",
82
+ };
83
+ } else if (vellumGuardian && !(requireAnchorPrincipal && !anchorPrincipalId)) {
84
+ // Source-channel resolution did not match the anchor → use the anchored
85
+ // vellum identity.
86
+ resolved = {
87
+ principalId: anchorPrincipalId ?? null,
88
+ address: vellumGuardian.address,
89
+ displayName: vellumGuardian.displayName ?? null,
90
+ channelType: "vellum",
91
+ source: "vellum-anchor",
92
+ };
93
+ }
94
+
95
+ // Gateway resolved a principal — done.
96
+ if (resolved?.principalId) {
97
+ return resolved;
98
+ }
99
+
100
+ if (!useLocalFallback) {
101
+ return resolved;
102
+ }
103
+
104
+ // Fallback: gateway read was empty/unavailable (or carried no principal), so
105
+ // resolve from the LOCAL dual-written binding to avoid an undecidable
106
+ // request. A local match overwrites the principal-less gateway record; with
107
+ // no local match the gateway record (if any) is retained.
108
+ const localVellum = findGuardianForChannel("vellum");
109
+ const localAnchorPrincipalId = localVellum?.contact.principalId;
110
+ const localSource = findGuardianForChannel(sourceChannel);
111
+ if (
112
+ localAnchorPrincipalId &&
113
+ localSource &&
114
+ localSource.contact.principalId === localAnchorPrincipalId
115
+ ) {
116
+ return {
117
+ principalId: localSource.contact.principalId,
118
+ address: localSource.channel.address,
119
+ displayName: localSource.contact.displayName,
120
+ channelType: localSource.channel.type,
121
+ source: "source-channel-contact",
122
+ };
123
+ }
124
+ if (localVellum) {
125
+ return {
126
+ principalId: localAnchorPrincipalId ?? null,
127
+ address: localVellum.channel.address,
128
+ displayName: localVellum.contact.displayName,
129
+ channelType: "vellum",
130
+ source: "vellum-anchor",
131
+ };
132
+ }
133
+
134
+ return resolved;
135
+ }
@@ -749,7 +749,7 @@ async function createCanonicalRequestForConfirmation(
749
749
  });
750
750
 
751
751
  if (trustContext && conversation) {
752
- bridgeConfirmationRequestToGuardian({
752
+ await bridgeConfirmationRequestToGuardian({
753
753
  canonicalRequest,
754
754
  trustContext,
755
755
  conversationId,
@@ -50,14 +50,21 @@
50
50
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
51
51
  import { dirname, join } from "node:path";
52
52
 
53
+ import {
54
+ SSE_REPLAY_RING_AGE_LIMIT_MS,
55
+ SSE_REPLAY_RING_COUNT_LIMIT,
56
+ } from "../api/constants/sse-replay.js";
53
57
  import { getWorkspaceDir } from "../util/platform.js";
54
58
  import type { AssistantEvent } from "./assistant-event.js";
55
59
 
56
60
  // ── Tunables ─────────────────────────────────────────────────────────
57
61
 
58
- const RING_COUNT_LIMIT = 200;
62
+ // Count and age bounds on the replay ring. Shared with the web client
63
+ // (via `@vellumai/assistant-api`) so its seq-gap tolerance is sized
64
+ // against the same numbers the daemon buffers against.
65
+ const RING_COUNT_LIMIT = SSE_REPLAY_RING_COUNT_LIMIT;
59
66
  const RING_SIZE_LIMIT_BYTES = 256 * 1024;
60
- const RING_AGE_LIMIT_MS = 30_000;
67
+ const RING_AGE_LIMIT_MS = SSE_REPLAY_RING_AGE_LIMIT_MS;
61
68
 
62
69
  /**
63
70
  * Cap on how many conversations retain a persisted-seq entry. Unlike the
@@ -0,0 +1,99 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
4
+
5
+ let mockGuardians: GuardianDelivery[] | null = null;
6
+ let authDisabled = false;
7
+
8
+ mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
9
+ getGuardianDelivery: async () => mockGuardians,
10
+ // Real active-status selector so the auth gate enforces status==="active".
11
+ guardianForChannel: (list: GuardianDelivery[], channelType: string) =>
12
+ list.find((g) => g.channelType === channelType && g.status === "active"),
13
+ }));
14
+
15
+ mock.module("../../../config/env.js", () => ({
16
+ isHttpAuthDisabled: () => authDisabled,
17
+ }));
18
+
19
+ import { requireBoundGuardian } from "../require-bound-guardian.js";
20
+ import type { AuthContext } from "../types.js";
21
+
22
+ function ctx(actorPrincipalId?: string): AuthContext {
23
+ return {
24
+ subject: "sub",
25
+ principalType: "actor",
26
+ assistantId: "self",
27
+ actorPrincipalId,
28
+ scopeProfile: "actor_client_v1",
29
+ scopes: new Set(),
30
+ policyEpoch: 0,
31
+ };
32
+ }
33
+
34
+ function guardian(principalId: string): GuardianDelivery {
35
+ return {
36
+ channelType: "vellum",
37
+ contactId: "guardian-contact",
38
+ principalId,
39
+ address: principalId,
40
+ status: "active",
41
+ };
42
+ }
43
+
44
+ describe("requireBoundGuardian", () => {
45
+ beforeEach(() => {
46
+ mockGuardians = null;
47
+ authDisabled = false;
48
+ });
49
+
50
+ test("admits the bound guardian", async () => {
51
+ mockGuardians = [guardian("vellum-principal-abc")];
52
+ const result = await requireBoundGuardian(ctx("vellum-principal-abc"));
53
+ expect(result).toBeNull();
54
+ });
55
+
56
+ test("denies a non-guardian actor", async () => {
57
+ mockGuardians = [guardian("vellum-principal-abc")];
58
+ const result = await requireBoundGuardian(ctx("vellum-principal-other"));
59
+ expect(result).not.toBeNull();
60
+ expect(result!.status).toBe(403);
61
+ });
62
+
63
+ test("denies when actor principal is missing", async () => {
64
+ mockGuardians = [guardian("vellum-principal-abc")];
65
+ const result = await requireBoundGuardian(ctx(undefined));
66
+ expect(result).not.toBeNull();
67
+ expect(result!.status).toBe(403);
68
+ });
69
+
70
+ test("fails closed on a null list (gateway unreachable)", async () => {
71
+ mockGuardians = null;
72
+ const result = await requireBoundGuardian(ctx("vellum-principal-abc"));
73
+ expect(result).not.toBeNull();
74
+ expect(result!.status).toBe(403);
75
+ });
76
+
77
+ test("denies when no vellum guardian is bound", async () => {
78
+ mockGuardians = [];
79
+ const result = await requireBoundGuardian(ctx("vellum-principal-abc"));
80
+ expect(result).not.toBeNull();
81
+ expect(result!.status).toBe(403);
82
+ });
83
+
84
+ test("denies a non-active (revoked) vellum row matching the actor", async () => {
85
+ mockGuardians = [
86
+ { ...guardian("vellum-principal-abc"), status: "revoked" },
87
+ ];
88
+ const result = await requireBoundGuardian(ctx("vellum-principal-abc"));
89
+ expect(result).not.toBeNull();
90
+ expect(result!.status).toBe(403);
91
+ });
92
+
93
+ test("dev bypass admits when auth is disabled", async () => {
94
+ authDisabled = true;
95
+ mockGuardians = null;
96
+ const result = await requireBoundGuardian(ctx(undefined));
97
+ expect(result).toBeNull();
98
+ });
99
+ });
@@ -1,15 +1,20 @@
1
1
  import { isHttpAuthDisabled } from "../../config/env.js";
2
- import { findGuardianForChannel } from "../../contacts/contact-store.js";
2
+ import {
3
+ getGuardianDelivery,
4
+ guardianForChannel,
5
+ } from "../../contacts/guardian-delivery-reader.js";
3
6
  import { httpError } from "../http-errors.js";
4
7
  import type { AuthContext } from "./types.js";
5
8
 
6
9
  /**
7
10
  * Verify the actor from AuthContext is the bound guardian for the vellum channel.
8
- * Returns an error Response if not, or null if allowed.
11
+ * Sources the guardian from the gateway binding and fails closed when the
12
+ * gateway is unreachable (null list). Returns an error Response if not
13
+ * authorized, or null if allowed.
9
14
  */
10
- export function requireBoundGuardian(
15
+ export async function requireBoundGuardian(
11
16
  authContext: AuthContext,
12
- ): Response | null {
17
+ ): Promise<Response | null> {
13
18
  // Dev bypass: when auth is disabled, skip guardian binding check
14
19
  // (mirrors enforcePolicy dev bypass in route-policy.ts)
15
20
  if (isHttpAuthDisabled()) {
@@ -22,17 +27,22 @@ export function requireBoundGuardian(
22
27
  403,
23
28
  );
24
29
  }
25
- const guardianResult = findGuardianForChannel("vellum");
26
- if (!guardianResult) {
27
- // No guardian yet in pre-bootstrap state, allow through
28
- return null;
29
- }
30
- if (guardianResult.channel.address !== authContext.actorPrincipalId) {
30
+ const guardians = await getGuardianDelivery({ channelTypes: ["vellum"] });
31
+ if (!guardians) {
32
+ // Gateway unreachablefail closed.
31
33
  return httpError(
32
34
  "FORBIDDEN",
33
35
  "Actor is not the bound guardian for this channel",
34
36
  403,
35
37
  );
36
38
  }
37
- return null;
39
+ const guardian = guardianForChannel(guardians, "vellum");
40
+ if (guardian && guardian.principalId === authContext.actorPrincipalId) {
41
+ return null;
42
+ }
43
+ return httpError(
44
+ "FORBIDDEN",
45
+ "Actor is not the bound guardian for this channel",
46
+ 403,
47
+ );
38
48
  }
@@ -9,8 +9,12 @@
9
9
  import { createHash, randomBytes } from "crypto";
10
10
  import { v4 as uuid } from "uuid";
11
11
 
12
- import { findGuardianForChannel } from "../contacts/contact-store.js";
13
12
  import { revokeGuardianBinding } from "../contacts/contacts-write.js";
13
+ import {
14
+ getGuardianDelivery,
15
+ getGuardianDeliveryFresh,
16
+ guardianForChannel,
17
+ } from "../contacts/guardian-delivery-reader.js";
14
18
  import type {
15
19
  GuardianBinding,
16
20
  IdentityBindingStatus,
@@ -319,50 +323,71 @@ export function validateAndConsumeVerification(
319
323
 
320
324
  /**
321
325
  * Look up the active guardian binding for a given assistant and channel.
322
- * Reads from the contacts table via findGuardianForChannel and
323
- * synthesizes a GuardianBinding-shaped object.
324
- * Returns null when no contacts match.
326
+ * Reads the gateway-owned GuardianDelivery and synthesizes a
327
+ * GuardianBinding-shaped object. Returns null when no guardian is bound or
328
+ * the gateway is unreachable.
325
329
  */
326
- export function getGuardianBinding(
330
+ export async function getGuardianBinding(
327
331
  assistantId: string,
328
332
  channel: string,
329
- ): GuardianBinding | null {
330
- const result = findGuardianForChannel(channel);
331
- if (result) {
332
- return {
333
- id: result.channel.id,
334
- assistantId,
335
- channel,
336
- guardianExternalUserId: result.channel.address,
337
- guardianDeliveryChatId: result.channel.externalChatId ?? "",
338
- guardianPrincipalId: result.contact.principalId ?? "",
339
- status: "active" as const,
340
- verifiedAt: result.channel.verifiedAt ?? 0,
341
- verifiedVia: result.channel.verifiedVia ?? "",
342
- metadataJson: null,
343
- createdAt: result.channel.createdAt,
344
- updatedAt: result.channel.updatedAt ?? result.channel.createdAt,
345
- };
346
- }
333
+ ): Promise<GuardianBinding | null> {
334
+ const list = await getGuardianDelivery({ channelTypes: [channel] });
335
+ const delivery = list ? guardianForChannel(list, channel) : undefined;
336
+ if (!delivery) return null;
347
337
 
348
- return null;
338
+ const now = Date.now();
339
+ return {
340
+ id: delivery.contactId,
341
+ assistantId,
342
+ channel,
343
+ guardianExternalUserId: delivery.address,
344
+ guardianDeliveryChatId: delivery.externalChatId ?? "",
345
+ guardianPrincipalId: delivery.principalId ?? "",
346
+ status: "active" as const,
347
+ verifiedAt: delivery.verifiedAt ?? 0,
348
+ // verifiedVia is not carried on the delivery contract; a bound guardian
349
+ // is verified by definition.
350
+ verifiedVia: "verified",
351
+ metadataJson: null,
352
+ createdAt: now,
353
+ updatedAt: now,
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Gateway-backed guardian-existence check: is a guardian already bound for
359
+ * this channel? Presence-only idempotency guard, NOT an ACL-field read.
360
+ *
361
+ * Null-list fail direction: a `null` from the gateway (unreachable / malformed)
362
+ * is "unknown" — returns `true` so an unreachable gateway is treated as
363
+ * already-bound. Callers gate session creation on a falsy result, so this
364
+ * blocks a new binding on a transient miss rather than spuriously creating a
365
+ * second one.
366
+ */
367
+ export async function isGuardianBoundForChannel(
368
+ channel: string,
369
+ ): Promise<boolean> {
370
+ // Existence guards read fresh because gateway-side binding writes don't
371
+ // invalidate the daemon cache.
372
+ const list = await getGuardianDeliveryFresh({ channelTypes: [channel] });
373
+ if (list === null) return true;
374
+ return !!guardianForChannel(list, channel);
349
375
  }
350
376
 
351
377
  /**
352
378
  * Check whether the given external user is the active guardian for
353
379
  * the specified assistant and channel.
354
380
  */
355
- export function isGuardian(
381
+ export async function isGuardian(
356
382
  assistantId: string,
357
383
  channel: string,
358
384
  address: string,
359
- ): boolean {
360
- const result = findGuardianForChannel(channel);
361
- if (result) {
362
- return result.channel.address.toLowerCase() === address.toLowerCase();
363
- }
385
+ ): Promise<boolean> {
386
+ const list = await getGuardianDelivery({ channelTypes: [channel] });
387
+ const delivery = list ? guardianForChannel(list, channel) : undefined;
388
+ if (!delivery) return false;
364
389
 
365
- return false;
390
+ return delivery.address.toLowerCase() === address.toLowerCase();
366
391
  }
367
392
 
368
393
  /**
@@ -70,9 +70,9 @@ export type BridgeConfirmationRequestResult =
70
70
  *
71
71
  * Fire-and-forget safe: notification emission errors are logged but not propagated.
72
72
  */
73
- export function bridgeConfirmationRequestToGuardian(
73
+ export async function bridgeConfirmationRequestToGuardian(
74
74
  params: BridgeConfirmationRequestParams,
75
- ): BridgeConfirmationRequestResult {
75
+ ): Promise<BridgeConfirmationRequestResult> {
76
76
  const {
77
77
  canonicalRequest,
78
78
  trustContext,
@@ -100,7 +100,7 @@ export function bridgeConfirmationRequestToGuardian(
100
100
  }
101
101
 
102
102
  const sourceChannel = trustContext.sourceChannel;
103
- const binding = getGuardianBinding(assistantId, sourceChannel);
103
+ const binding = await getGuardianBinding(assistantId, sourceChannel);
104
104
  if (!binding) {
105
105
  log.debug(
106
106
  { sourceChannel, assistantId },