@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
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import type { ChannelId } from "../channels/types.js";
15
- import { findGuardianForChannel } from "../contacts/contact-store.js";
15
+ import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
16
16
  import type { ChannelStatus } from "../contacts/types.js";
17
17
  import {
18
18
  createCanonicalGuardianRequest,
@@ -25,6 +25,7 @@ import {
25
25
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
26
26
  import type { GuardianResolutionSource } from "../notifications/signal.js";
27
27
  import { getLogger } from "../util/logger.js";
28
+ import { resolveAnchoredGuardian } from "./anchored-guardian.js";
28
29
  import { GUARDIAN_APPROVAL_TTL_MS } from "./routes/channel-route-shared.js";
29
30
 
30
31
  const log = getLogger("access-request-helper");
@@ -70,12 +71,12 @@ export type AccessRequestResult =
70
71
  * trust anchor and only accepts source-channel contacts that match it. This
71
72
  * prevents stale or cross-assistant contacts from being bound to the request.
72
73
  *
73
- * This is intentionally synchronous with respect to the canonical store writes
74
- * and fire-and-forget for the notification signal emission.
74
+ * The canonical store writes complete before this resolves; the notification
75
+ * signal emission is fire-and-forget.
75
76
  */
76
- export function notifyGuardianOfAccessRequest(
77
+ export async function notifyGuardianOfAccessRequest(
77
78
  params: AccessRequestParams,
78
- ): AccessRequestResult {
79
+ ): Promise<AccessRequestResult> {
79
80
  const {
80
81
  canonicalAssistantId,
81
82
  sourceChannel,
@@ -94,40 +95,18 @@ export function notifyGuardianOfAccessRequest(
94
95
  return { notified: false, reason: "no_sender_id" };
95
96
  }
96
97
 
97
- // Resolve guardian identity with assistant-anchored strategy:
98
- // 1. Ensure the assistant has a vellum guardian principal (trust anchor)
99
- // 2. Use source-channel guardian only when principal matches anchor
100
- // 3. Fallback to vellum guardian identity for this assistant principal
101
- let guardianExternalUserId: string | null = null;
102
- let guardianPrincipalId: string | null = null;
103
- let guardianBindingChannel: string | null = null;
104
- let guardianResolutionSource: GuardianResolutionSource = "none";
105
-
106
- const vellumGuardian = findGuardianForChannel("vellum");
107
- const assistantGuardianPrincipalId = vellumGuardian?.contact.principalId;
108
-
109
- // Try source-channel guardian, but only if it maps to the assistant's
110
- // anchored principal. This blocks cross-assistant/stale contact selection.
111
- const sourceGuardian = findGuardianForChannel(sourceChannel);
112
- if (
113
- assistantGuardianPrincipalId &&
114
- sourceGuardian &&
115
- sourceGuardian.contact.principalId === assistantGuardianPrincipalId
116
- ) {
117
- guardianExternalUserId = sourceGuardian.channel.address;
118
- guardianPrincipalId = sourceGuardian.contact.principalId;
119
- guardianBindingChannel = sourceGuardian.channel.type;
120
- guardianResolutionSource = "source-channel-contact";
121
- }
122
-
123
- // Access requests always require a principal. If source-channel resolution
124
- // did not match the assistant anchor, use the anchored vellum identity.
125
- if (!guardianPrincipalId && vellumGuardian) {
126
- guardianExternalUserId = vellumGuardian.channel.address;
127
- guardianPrincipalId = assistantGuardianPrincipalId ?? null;
128
- guardianBindingChannel = guardianBindingChannel ?? "vellum";
129
- guardianResolutionSource = "vellum-anchor";
130
- }
98
+ // Resolve guardian identity with the assistant-anchored strategy (gateway
99
+ // source-channel match validated against the vellum anchor, else the vellum
100
+ // anchor).
101
+ const anchored = resolveAnchoredGuardian({
102
+ guardians: await getGuardianDelivery(),
103
+ sourceChannel,
104
+ });
105
+ const guardianExternalUserId = anchored?.address ?? null;
106
+ const guardianPrincipalId = anchored?.principalId ?? null;
107
+ const guardianBindingChannel = anchored?.channelType ?? null;
108
+ const guardianResolutionSource: GuardianResolutionSource =
109
+ anchored?.source ?? "none";
131
110
 
132
111
  log.debug(
133
112
  {
@@ -17,15 +17,23 @@
17
17
  */
18
18
 
19
19
  import type { ChannelId } from "../channels/types.js";
20
+ import { findContactByAddress } from "../contacts/contact-store.js";
20
21
  import {
21
- findContactByAddress,
22
- findGuardianForChannel,
23
- } from "../contacts/contact-store.js";
22
+ guardianForChannel,
23
+ peekCachedGuardianDelivery,
24
+ } from "../contacts/guardian-delivery-reader.js";
24
25
  import { channelStatusToMemberStatus } from "../contacts/member-status.js";
25
- import type { ContactChannel, ContactWithChannels } from "../contacts/types.js";
26
+ import type {
27
+ ChannelPolicy,
28
+ ChannelStatus,
29
+ ContactChannel,
30
+ ContactRole,
31
+ ContactWithChannels,
32
+ } from "../contacts/types.js";
26
33
  import type { TrustContext } from "../daemon/trust-context.js";
27
34
  import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
28
35
  import { getLogger } from "../util/logger.js";
36
+ import { getCachedMemberAcl } from "./member-verdict-cache.js";
29
37
 
30
38
  const log = getLogger("actor-trust-resolver");
31
39
 
@@ -78,10 +86,18 @@ export interface ActorTrustContext {
78
86
  } | null;
79
87
  /** Canonical principal ID from the guardian binding. */
80
88
  guardianPrincipalId?: string;
81
- /** Resolved contact + channel for this sender, if any. */
89
+ /**
90
+ * Resolved contact + channel for this sender, if any. The ACL view
91
+ * (status/policy/role) is carried here rather than on the contact/channel
92
+ * objects, sourced from the gateway verdict — the verdict path reads it
93
+ * inline, the sync fallback from the in-memory member-verdict cache.
94
+ */
82
95
  memberRecord: {
83
96
  contact: ContactWithChannels;
84
97
  channel: ContactChannel;
98
+ status: ChannelStatus;
99
+ policy: ChannelPolicy;
100
+ role: ContactRole;
85
101
  } | null;
86
102
  /** Trust classification. */
87
103
  trustClass: TrustClass;
@@ -175,21 +191,27 @@ export function resolveActorTrust(
175
191
  }
176
192
 
177
193
  // --- Guardian lookup ---
178
- const guardianResult = findGuardianForChannel(input.sourceChannel);
194
+ // Sync read of the gateway guardian delivery from the IO-free cache snapshot
195
+ // (kept warm by the async hot paths + daemon-startup warm). A cold cache
196
+ // yields no guardian match, the same outcome as no binding.
197
+ const cachedGuardians = peekCachedGuardianDelivery({
198
+ channelTypes: [input.sourceChannel],
199
+ });
200
+ const guardianDelivery = cachedGuardians
201
+ ? guardianForChannel(cachedGuardians, input.sourceChannel)
202
+ : undefined;
179
203
  let guardianBindingMatch: ActorTrustContext["guardianBindingMatch"] = null;
180
204
  let guardianPrincipalId: string | undefined;
181
205
  let isGuardian = false;
182
206
 
183
- if (guardianResult) {
184
- const { contact: guardianContact, channel: guardianChannel } =
185
- guardianResult;
207
+ if (guardianDelivery) {
186
208
  guardianBindingMatch = {
187
- guardianExternalUserId: guardianChannel.address,
188
- guardianDeliveryChatId: guardianChannel.externalChatId,
209
+ guardianExternalUserId: guardianDelivery.address,
210
+ guardianDeliveryChatId: guardianDelivery.externalChatId ?? null,
189
211
  };
190
- guardianPrincipalId = guardianContact.principalId ?? undefined;
212
+ guardianPrincipalId = guardianDelivery.principalId ?? undefined;
191
213
  isGuardian =
192
- guardianChannel.address.toLowerCase() === canonicalSenderId.toLowerCase();
214
+ guardianDelivery.address.toLowerCase() === canonicalSenderId.toLowerCase();
193
215
  }
194
216
 
195
217
  log.debug(
@@ -213,7 +235,12 @@ export function resolveActorTrust(
213
235
  ch.address.toLowerCase() === canonicalSenderId.toLowerCase(),
214
236
  );
215
237
  if (byAddress && byAddressChannel) {
216
- memberRecord = { contact: byAddress, channel: byAddressChannel };
238
+ const acl = getCachedMemberAcl(input.sourceChannel, canonicalSenderId);
239
+ if (acl) {
240
+ memberRecord = { contact: byAddress, channel: byAddressChannel, ...acl };
241
+ }
242
+ // Fail-closed: already in the sync fallback (no live verdict) and no cached
243
+ // verdict → leave memberRecord null so trustClass resolves to unknown.
217
244
  }
218
245
 
219
246
  log.debug(
@@ -252,7 +279,7 @@ export function resolveActorTrust(
252
279
  if (isGuardian) {
253
280
  trustClass = "guardian";
254
281
  } else if (memberMatchesSender && memberRecord) {
255
- const status = memberRecord.channel.status;
282
+ const status = memberRecord.status;
256
283
  if (status === "active") {
257
284
  trustClass = "trusted_contact";
258
285
  } else if (status === "unverified" || status === "pending") {
@@ -323,12 +350,12 @@ export function toTrustContext(
323
350
  requesterMemberDisplayName: ctx.actorMetadata.memberDisplayName,
324
351
  requesterExternalUserId: ctx.canonicalSenderId ?? undefined,
325
352
  requesterChatId: conversationExternalId,
326
- // Member grounding from a real memberRecord (voice path); the verdict path
327
- // (memberRecord=null) stamps these from the verdict instead.
353
+ // Member grounding from the resolved memberRecord (voice + verdict paths
354
+ // both populate it).
328
355
  requesterContactId: ctx.memberRecord?.contact.id,
329
356
  memberStatus: ctx.memberRecord
330
- ? channelStatusToMemberStatus(ctx.memberRecord.channel.status)
357
+ ? channelStatusToMemberStatus(ctx.memberRecord.status)
331
358
  : undefined,
332
- memberPolicy: ctx.memberRecord?.channel.policy,
359
+ memberPolicy: ctx.memberRecord?.policy,
333
360
  };
334
361
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Unit tests for `resolveAnchoredGuardian`.
3
+ *
4
+ * Covers the gateway arms (source-channel match validated against the vellum
5
+ * anchor, vellum-anchor fallback) and the cosmetic `requireAnchorPrincipal`
6
+ * guard.
7
+ */
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
11
+
12
+ import { resolveAnchoredGuardian } from "./anchored-guardian.js";
13
+
14
+ function gw(g: Partial<GuardianDelivery> & { channelType: string; address: string }): GuardianDelivery {
15
+ return {
16
+ contactId: `c-${g.channelType}`,
17
+ status: "active",
18
+ ...g,
19
+ };
20
+ }
21
+
22
+ describe("resolveAnchoredGuardian — gateway arm", () => {
23
+ test("source-channel guardian matching the anchor wins", () => {
24
+ const result = resolveAnchoredGuardian({
25
+ guardians: [
26
+ gw({ channelType: "vellum", address: "v-addr", principalId: "p-anchor", displayName: "Vellum" }),
27
+ gw({ channelType: "telegram", address: "tg-addr", principalId: "p-anchor", displayName: "Alice" }),
28
+ ],
29
+ sourceChannel: "telegram",
30
+ });
31
+ expect(result).toEqual({
32
+ principalId: "p-anchor",
33
+ address: "tg-addr",
34
+ displayName: "Alice",
35
+ channelType: "telegram",
36
+ source: "source-channel-contact",
37
+ });
38
+ });
39
+
40
+ test("source-channel guardian NOT matching the anchor falls back to vellum-anchor", () => {
41
+ const result = resolveAnchoredGuardian({
42
+ guardians: [
43
+ gw({ channelType: "vellum", address: "v-addr", principalId: "p-anchor", displayName: "Vellum" }),
44
+ gw({ channelType: "telegram", address: "tg-addr", principalId: "p-other", displayName: "Stale" }),
45
+ ],
46
+ sourceChannel: "telegram",
47
+ });
48
+ expect(result).toEqual({
49
+ principalId: "p-anchor",
50
+ address: "v-addr",
51
+ displayName: "Vellum",
52
+ channelType: "vellum",
53
+ source: "vellum-anchor",
54
+ });
55
+ });
56
+
57
+ test("no source-channel guardian falls back to vellum-anchor", () => {
58
+ const result = resolveAnchoredGuardian({
59
+ guardians: [
60
+ gw({ channelType: "vellum", address: "v-addr", principalId: "p-anchor", displayName: "Vellum" }),
61
+ ],
62
+ sourceChannel: "telegram",
63
+ });
64
+ expect(result?.source).toBe("vellum-anchor");
65
+ expect(result?.principalId).toBe("p-anchor");
66
+ });
67
+ });
68
+
69
+ describe("resolveAnchoredGuardian — gateway empty", () => {
70
+ test("null gateway list returns null", () => {
71
+ const result = resolveAnchoredGuardian({
72
+ guardians: null,
73
+ sourceChannel: "telegram",
74
+ });
75
+ expect(result).toBeNull();
76
+ });
77
+
78
+ test("empty gateway list returns null", () => {
79
+ const result = resolveAnchoredGuardian({
80
+ guardians: [],
81
+ sourceChannel: "telegram",
82
+ });
83
+ expect(result).toBeNull();
84
+ });
85
+ });
86
+
87
+ describe("resolveAnchoredGuardian — requireAnchorPrincipal (cosmetic label)", () => {
88
+ test("vellum guardian with a null principal degrades to null", () => {
89
+ const result = resolveAnchoredGuardian({
90
+ guardians: [
91
+ gw({ channelType: "vellum", address: "v-addr", principalId: null, displayName: "Vellum" }),
92
+ ],
93
+ sourceChannel: "telegram",
94
+ requireAnchorPrincipal: true,
95
+ });
96
+ expect(result).toBeNull();
97
+ });
98
+
99
+ test("without requireAnchorPrincipal a null-principal vellum still resolves", () => {
100
+ const result = resolveAnchoredGuardian({
101
+ guardians: [
102
+ gw({ channelType: "vellum", address: "v-addr", principalId: null, displayName: "Vellum" }),
103
+ ],
104
+ sourceChannel: "telegram",
105
+ });
106
+ expect(result?.source).toBe("vellum-anchor");
107
+ expect(result?.principalId).toBeNull();
108
+ });
109
+ });
@@ -0,0 +1,86 @@
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
+ * Resolves from the gateway delivery list, the source of truth for guardian
11
+ * principals.
12
+ */
13
+
14
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
15
+
16
+ import type { ChannelId } from "../channels/types.js";
17
+ import { guardianForChannel } from "../contacts/guardian-delivery-reader.js";
18
+ import type { GuardianResolutionSource } from "../notifications/signal.js";
19
+
20
+ /** Resolved guardian identity, anchored on the assistant's vellum principal. */
21
+ export interface AnchoredGuardian {
22
+ principalId: string | null;
23
+ address: string;
24
+ displayName: string | null;
25
+ channelType: string;
26
+ source: GuardianResolutionSource;
27
+ }
28
+
29
+ export interface ResolveAnchoredGuardianInput {
30
+ /** Gateway delivery list; `null` when the gateway read failed/was empty. */
31
+ guardians: GuardianDelivery[] | null;
32
+ sourceChannel: ChannelId;
33
+ /**
34
+ * Require a non-null anchor principal for the vellum-anchor arm. When the
35
+ * vellum guardian has no principal, return `null` instead of a vellum-anchor
36
+ * record. Matches the cosmetic label path, which degrades to the default
37
+ * reference when the anchor principal is absent.
38
+ */
39
+ requireAnchorPrincipal?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Resolve the anchored guardian for `sourceChannel`, or `null` when none can be
44
+ * resolved. Gateway source-channel match → that record; gateway anchor-only →
45
+ * vellum-anchor.
46
+ */
47
+ export function resolveAnchoredGuardian(
48
+ input: ResolveAnchoredGuardianInput,
49
+ ): AnchoredGuardian | null {
50
+ const { sourceChannel, requireAnchorPrincipal } = input;
51
+ const guardians = input.guardians ?? [];
52
+
53
+ const vellumGuardian = guardianForChannel(guardians, "vellum");
54
+ const anchorPrincipalId = vellumGuardian?.principalId;
55
+
56
+ let resolved: AnchoredGuardian | null = null;
57
+
58
+ // Source-channel guardian, but only when it maps to the assistant's anchored
59
+ // principal. This blocks cross-assistant/stale binding selection.
60
+ const sourceGuardian = guardianForChannel(guardians, sourceChannel);
61
+ if (
62
+ anchorPrincipalId &&
63
+ sourceGuardian &&
64
+ sourceGuardian.principalId === anchorPrincipalId
65
+ ) {
66
+ resolved = {
67
+ principalId: sourceGuardian.principalId,
68
+ address: sourceGuardian.address,
69
+ displayName: sourceGuardian.displayName ?? null,
70
+ channelType: sourceGuardian.channelType,
71
+ source: "source-channel-contact",
72
+ };
73
+ } else if (vellumGuardian && !(requireAnchorPrincipal && !anchorPrincipalId)) {
74
+ // Source-channel resolution did not match the anchor → use the anchored
75
+ // vellum identity.
76
+ resolved = {
77
+ principalId: anchorPrincipalId ?? null,
78
+ address: vellumGuardian.address,
79
+ displayName: vellumGuardian.displayName ?? null,
80
+ channelType: "vellum",
81
+ source: "vellum-anchor",
82
+ };
83
+ }
84
+
85
+ return resolved;
86
+ }
@@ -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
  }