@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
@@ -7,12 +7,8 @@ import type { AdmissionPolicy, SourceMetadata } from "@vellumai/gateway-client";
7
7
 
8
8
  import { isInviteCodeRedemptionEnabled } from "../../../channels/config.js";
9
9
  import type { ChannelId } from "../../../channels/types.js";
10
- import { findGuardianForChannel } from "../../../contacts/contact-store.js";
10
+ import { getGuardianDelivery } from "../../../contacts/guardian-delivery-reader.js";
11
11
  import { channelStatusToMemberStatus } from "../../../contacts/member-status.js";
12
- import type {
13
- ContactChannel,
14
- ContactWithChannels,
15
- } from "../../../contacts/types.js";
16
12
  import { deleteInbound, recordInbound } from "../../../memory/delivery-crud.js";
17
13
  import { markProcessed } from "../../../memory/delivery-status.js";
18
14
  import {
@@ -25,6 +21,7 @@ import { getLogger } from "../../../util/logger.js";
25
21
  import { truncate } from "../../../util/truncate.js";
26
22
  import { hashVoiceCode } from "../../../util/voice-code.js";
27
23
  import { notifyGuardianOfAccessRequest } from "../../access-request-helper.js";
24
+ import { resolveAnchoredGuardian } from "../../anchored-guardian.js";
28
25
  import { getInviteAdapterRegistry } from "../../channel-invite-transport.js";
29
26
  import {
30
27
  createOutboundSession,
@@ -38,7 +35,8 @@ import {
38
35
  redeemInviteByCode,
39
36
  } from "../../invite-redemption-service.js";
40
37
  import { getInviteRedemptionReply } from "../../invite-redemption-templates.js";
41
- import { resolvedMemberFromVerdict } from "../../trust-verdict-consumer.js";
38
+ import type { VerdictMember } from "../../trust-verdict-consumer.js";
39
+ import { verdictMemberFromVerdict } from "../../trust-verdict-consumer.js";
42
40
 
43
41
  const log = getLogger("runtime-http");
44
42
 
@@ -46,28 +44,20 @@ const log = getLogger("runtime-http");
46
44
  * Resolve the guardian's display name for use in requester-facing messages.
47
45
  *
48
46
  * Uses the assistant's anchored vellum principal to validate the guardian
49
- * contact, matching the same strategy used by `notifyGuardianOfAccessRequest`.
50
- * This prevents stale or cross-assistant contacts from leaking a wrong name.
47
+ * binding, matching the same strategy used by `notifyGuardianOfAccessRequest`.
48
+ * This prevents stale or cross-assistant bindings from leaking a wrong name.
49
+ * Cosmetic copy, not an admission decision, so a null gateway list degrades
50
+ * gracefully to the default reference.
51
51
  */
52
- function resolveGuardianLabel(sourceChannel: ChannelId): string {
53
- const vellumGuardian = findGuardianForChannel("vellum");
54
- const anchoredPrincipalId = vellumGuardian?.contact.principalId;
55
-
56
- if (!anchoredPrincipalId) {
57
- return resolveGuardianName(undefined);
58
- }
59
-
60
- // Try source-channel guardian, but only accept it when the principal
61
- // matches the assistant's anchor.
62
- const sourceGuardian = findGuardianForChannel(sourceChannel);
63
- if (
64
- sourceGuardian &&
65
- sourceGuardian.contact.principalId === anchoredPrincipalId
66
- ) {
67
- return resolveGuardianName(sourceGuardian.contact.displayName);
68
- }
69
-
70
- return resolveGuardianName(vellumGuardian.contact.displayName);
52
+ async function resolveGuardianLabel(sourceChannel: ChannelId): Promise<string> {
53
+ // Cosmetic copy, not an admission decision: no local-store fallback, and a
54
+ // missing anchor principal degrades to the default reference.
55
+ const anchored = resolveAnchoredGuardian({
56
+ guardians: await getGuardianDelivery(),
57
+ sourceChannel,
58
+ requireAnchorPrincipal: true,
59
+ });
60
+ return resolveGuardianName(anchored?.displayName);
71
61
  }
72
62
 
73
63
  // ---------------------------------------------------------------------------
@@ -102,14 +92,8 @@ export interface AclEnforcementParams {
102
92
  effectiveAdmissionPolicy?: AdmissionPolicy;
103
93
  }
104
94
 
105
- /** Resolved contact + channel pair from ACL enforcement. */
106
- export type ResolvedMember = {
107
- contact: ContactWithChannels;
108
- channel: ContactChannel;
109
- };
110
-
111
95
  export interface AclResult {
112
- resolvedMember: ResolvedMember | null;
96
+ resolvedMember: VerdictMember | null;
113
97
  /** When set, the caller must return this response immediately. */
114
98
  earlyResponse?: Record<string, unknown>;
115
99
  /**
@@ -173,9 +157,27 @@ export async function enforceIngressAcl(
173
157
  };
174
158
  }
175
159
 
160
+ // Gateway attempted resolution but failed (DB error) → fail-closed deny,
161
+ // distinct from an absent verdict and from a real stranger. TEXT does not
162
+ // fall back to local ACL reads; the sender can retry.
163
+ if (verdict.resolutionFailed === true) {
164
+ log.warn(
165
+ { sourceChannel, externalUserId: canonicalSenderId },
166
+ "Ingress ACL: gateway trust resolution failed, denying fail-closed",
167
+ );
168
+ return {
169
+ resolvedMember: null,
170
+ earlyResponse: {
171
+ accepted: true,
172
+ denied: true,
173
+ reason: "not_a_member",
174
+ },
175
+ };
176
+ }
177
+
176
178
  // Member resolved from the gateway verdict (ACL + identity only); null for a
177
179
  // stranger verdict, which falls through to the non-member intercepts.
178
- const resolvedMember: ResolvedMember | null = resolvedMemberFromVerdict(verdict);
180
+ const resolvedMember: VerdictMember | null = verdictMemberFromVerdict(verdict);
179
181
 
180
182
  // A verdict carrying member identity but no resolvable member
181
183
  // (malformed/unknown ACL) fails closed, not treated as a stranger.
@@ -329,7 +331,7 @@ export async function enforceIngressAcl(
329
331
  if (slackVerifyResult.initiated) {
330
332
  // Still notify the guardian about the access attempt
331
333
  try {
332
- notifyGuardianOfAccessRequest({
334
+ await notifyGuardianOfAccessRequest({
333
335
  canonicalAssistantId,
334
336
  sourceChannel,
335
337
  conversationExternalId,
@@ -369,7 +371,7 @@ export async function enforceIngressAcl(
369
371
  try {
370
372
  await deliverChannelReply(dmCallbackUrl, {
371
373
  chatId: senderUserId,
372
- text: `I don't recognize you yet! I've let ${resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
374
+ text: `I don't recognize you yet! I've let ${await resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
373
375
  assistantId,
374
376
  });
375
377
  } catch (err) {
@@ -404,7 +406,7 @@ export async function enforceIngressAcl(
404
406
 
405
407
  if (emailVerifyResult.initiated) {
406
408
  try {
407
- notifyGuardianOfAccessRequest({
409
+ await notifyGuardianOfAccessRequest({
408
410
  canonicalAssistantId,
409
411
  sourceChannel,
410
412
  conversationExternalId,
@@ -443,7 +445,7 @@ export async function enforceIngressAcl(
443
445
  // deduplication, canonical request creation, and notification emission.
444
446
  let guardianNotified = false;
445
447
  try {
446
- const accessResult = notifyGuardianOfAccessRequest({
448
+ const accessResult = await notifyGuardianOfAccessRequest({
447
449
  canonicalAssistantId,
448
450
  sourceChannel,
449
451
  conversationExternalId,
@@ -467,7 +469,7 @@ export async function enforceIngressAcl(
467
469
  }
468
470
 
469
471
  const replyText = guardianNotified
470
- ? `Hmm looks like you don't have access to talk to me. I'll let ${resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
472
+ ? `Hmm looks like you don't have access to talk to me. I'll let ${await resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
471
473
  : "Sorry, you haven't been approved to message this assistant.";
472
474
  let replyDelivered = false;
473
475
  if (replyCallbackUrl) {
@@ -507,8 +509,8 @@ export async function enforceIngressAcl(
507
509
  }
508
510
 
509
511
  if (resolvedMember) {
510
- if (resolvedMember.channel.status !== "active") {
511
- const isBlockedMember = resolvedMember.channel.status === "blocked";
512
+ if (resolvedMember.status !== "active") {
513
+ const isBlockedMember = resolvedMember.status === "blocked";
512
514
  // Bootstrap commands must pass through for re-verifiable states
513
515
  // (pending/revoked), but never for blocked members.
514
516
  let denyInactiveMember = true;
@@ -529,7 +531,7 @@ export async function enforceIngressAcl(
529
531
  log.info(
530
532
  {
531
533
  sourceChannel,
532
- channelId: resolvedMember.channel.id,
534
+ channelId: resolvedMember.channelId,
533
535
  hasValidBootstrapSession: false,
534
536
  },
535
537
  "Ingress ACL: inactive member bootstrap bypass denied",
@@ -614,11 +616,11 @@ export async function enforceIngressAcl(
614
616
  if (!isBlockedMember && denyInactiveMember) {
615
617
  if (
616
618
  (effectiveAdmissionPolicy === "strangers" &&
617
- resolvedMember.channel.status !== "revoked") ||
619
+ resolvedMember.status !== "revoked") ||
618
620
  ((effectiveAdmissionPolicy === "any_contact" ||
619
621
  effectiveAdmissionPolicy === "guardian_only") &&
620
- (resolvedMember.channel.status === "pending" ||
621
- resolvedMember.channel.status === "unverified"))
622
+ (resolvedMember.status === "pending" ||
623
+ resolvedMember.status === "unverified"))
622
624
  ) {
623
625
  denyInactiveMember = false;
624
626
  }
@@ -628,8 +630,8 @@ export async function enforceIngressAcl(
628
630
  log.info(
629
631
  {
630
632
  sourceChannel,
631
- channelId: resolvedMember.channel.id,
632
- status: resolvedMember.channel.status,
633
+ channelId: resolvedMember.channelId,
634
+ status: resolvedMember.status,
633
635
  },
634
636
  "Ingress ACL: member not active, denying",
635
637
  );
@@ -639,7 +641,7 @@ export async function enforceIngressAcl(
639
641
  // the guardian made an explicit decision to block them.
640
642
  if (
641
643
  sourceChannel === "slack" &&
642
- resolvedMember.channel.status !== "blocked" &&
644
+ resolvedMember.status !== "blocked" &&
643
645
  (canonicalSenderId ?? rawSenderId)
644
646
  ) {
645
647
  const slackVerifyResult = initiateSlackVerificationChallenge({
@@ -649,7 +651,7 @@ export async function enforceIngressAcl(
649
651
 
650
652
  if (slackVerifyResult.initiated) {
651
653
  try {
652
- notifyGuardianOfAccessRequest({
654
+ await notifyGuardianOfAccessRequest({
653
655
  canonicalAssistantId,
654
656
  sourceChannel,
655
657
  conversationExternalId,
@@ -657,7 +659,7 @@ export async function enforceIngressAcl(
657
659
  actorDisplayName,
658
660
  actorUsername,
659
661
  previousMemberStatus: channelStatusToMemberStatus(
660
- resolvedMember.channel.status,
662
+ resolvedMember.status,
661
663
  ),
662
664
  messagePreview: truncate(
663
665
  trimmedContent,
@@ -688,7 +690,7 @@ export async function enforceIngressAcl(
688
690
  try {
689
691
  await deliverChannelReply(dmCallbackUrl, {
690
692
  chatId: senderUserId,
691
- text: `I don't recognize you yet! I've let ${resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
693
+ text: `I don't recognize you yet! I've let ${await resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
692
694
  assistantId,
693
695
  });
694
696
  } catch (err) {
@@ -715,9 +717,9 @@ export async function enforceIngressAcl(
715
717
  // re-approve. Blocked members are intentionally excluded — the
716
718
  // guardian already made an explicit decision to block them.
717
719
  let guardianNotified = false;
718
- if (resolvedMember.channel.status !== "blocked") {
720
+ if (resolvedMember.status !== "blocked") {
719
721
  try {
720
- const accessResult = notifyGuardianOfAccessRequest({
722
+ const accessResult = await notifyGuardianOfAccessRequest({
721
723
  canonicalAssistantId,
722
724
  sourceChannel,
723
725
  conversationExternalId,
@@ -725,7 +727,7 @@ export async function enforceIngressAcl(
725
727
  actorDisplayName,
726
728
  actorUsername,
727
729
  previousMemberStatus: channelStatusToMemberStatus(
728
- resolvedMember.channel.status,
730
+ resolvedMember.status,
729
731
  ),
730
732
  messagePreview: truncate(
731
733
  trimmedContent,
@@ -745,7 +747,7 @@ export async function enforceIngressAcl(
745
747
  }
746
748
 
747
749
  const inactiveReplyText = guardianNotified
748
- ? `Hmm looks like you don't have access to talk to me. I'll let ${resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
750
+ ? `Hmm looks like you don't have access to talk to me. I'll let ${await resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
749
751
  : "Sorry, you haven't been approved to message this assistant.";
750
752
  let inactiveReplyDelivered = false;
751
753
  if (replyCallbackUrl) {
@@ -779,16 +781,16 @@ export async function enforceIngressAcl(
779
781
  earlyResponse: {
780
782
  accepted: true,
781
783
  denied: true,
782
- reason: `member_${channelStatusToMemberStatus(resolvedMember.channel.status)}`,
784
+ reason: `member_${channelStatusToMemberStatus(resolvedMember.status)}`,
783
785
  ...(!inactiveReplyDelivered && { replyText: inactiveReplyText }),
784
786
  },
785
787
  };
786
788
  }
787
789
  }
788
790
 
789
- if (resolvedMember.channel.policy === "deny") {
791
+ if (resolvedMember.policy === "deny") {
790
792
  log.info(
791
- { sourceChannel, channelId: resolvedMember.channel.id },
793
+ { sourceChannel, channelId: resolvedMember.channelId },
792
794
  "Ingress ACL: member policy deny",
793
795
  );
794
796
  const denyReplyText =
@@ -8,7 +8,10 @@
8
8
  * focused on orchestration.
9
9
  */
10
10
  import type { ChannelId, InterfaceId } from "../../../channels/types.js";
11
- import { findGuardianForChannel } from "../../../contacts/contact-store.js";
11
+ import {
12
+ getGuardianDelivery,
13
+ guardianForChannel,
14
+ } from "../../../contacts/guardian-delivery-reader.js";
12
15
  import type { ServerMessage } from "../../../daemon/message-protocol.js";
13
16
  import type { TrustContext } from "../../../daemon/trust-context.js";
14
17
  import {
@@ -960,10 +963,15 @@ function startTrustedContactApprovalNotifier(params: {
960
963
 
961
964
  if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
962
965
  globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
963
- const guardian = findGuardianForChannel(sourceChannel);
964
- const guardianName = resolveGuardianName(
965
- guardian?.contact.displayName,
966
- );
966
+ // Gateway-resolved guardian display name (display-only).
967
+ const guardians = await getGuardianDelivery({
968
+ channelTypes: [sourceChannel],
969
+ });
970
+ const displayName = guardians
971
+ ? (guardianForChannel(guardians, sourceChannel)?.displayName ??
972
+ undefined)
973
+ : undefined;
974
+ const guardianName = resolveGuardianName(displayName);
967
975
  const waitingText = `Waiting for ${guardianName}'s approval...`;
968
976
  try {
969
977
  await deliverChannelReply(replyCallbackUrl, {
@@ -13,8 +13,8 @@ import { storePayload } from "../../../memory/delivery-crud.js";
13
13
  import { emitNotificationSignal } from "../../../notifications/emit-signal.js";
14
14
  import { getLogger } from "../../../util/logger.js";
15
15
  import { getGuardianBinding } from "../../channel-verification-service.js";
16
+ import type { VerdictMember } from "../../trust-verdict-consumer.js";
16
17
  import { GUARDIAN_APPROVAL_TTL_MS } from "../channel-route-shared.js";
17
- import type { ResolvedMember } from "./acl-enforcement.js";
18
18
 
19
19
  const log = getLogger("runtime-http");
20
20
 
@@ -23,7 +23,7 @@ const log = getLogger("runtime-http");
23
23
  // ---------------------------------------------------------------------------
24
24
 
25
25
  export interface EscalationInterceptParams {
26
- resolvedMember: ResolvedMember | null;
26
+ resolvedMember: VerdictMember | null;
27
27
  canonicalAssistantId: string;
28
28
  sourceChannel: ChannelId;
29
29
  sourceInterface: InterfaceId;
@@ -49,9 +49,9 @@ export interface EscalationInterceptParams {
49
49
  * Returns a Response if the escalation was handled (the pipeline should
50
50
  * short-circuit), or null to continue the pipeline.
51
51
  */
52
- export function handleEscalationIntercept(
52
+ export async function handleEscalationIntercept(
53
53
  params: EscalationInterceptParams,
54
- ): Record<string, unknown> | null {
54
+ ): Promise<Record<string, unknown> | null> {
55
55
  const {
56
56
  resolvedMember,
57
57
  canonicalAssistantId,
@@ -72,15 +72,15 @@ export function handleEscalationIntercept(
72
72
  rawSenderId,
73
73
  } = params;
74
74
 
75
- if (resolvedMember?.channel.policy !== "escalate") {
75
+ if (resolvedMember?.policy !== "escalate") {
76
76
  return null;
77
77
  }
78
78
 
79
- const binding = getGuardianBinding(canonicalAssistantId, sourceChannel);
79
+ const binding = await getGuardianBinding(canonicalAssistantId, sourceChannel);
80
80
  if (!binding) {
81
81
  // Fail-closed: can't escalate without a guardian to route to
82
82
  log.info(
83
- { sourceChannel, channelId: resolvedMember.channel.id },
83
+ { sourceChannel, channelId: resolvedMember.channelId },
84
84
  "Ingress ACL: escalate policy but no guardian binding, denying",
85
85
  );
86
86
  return {
@@ -4,7 +4,9 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
4
  // Mocks — must be set up before importing the module under test
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
- let mockGuardian: { contact: unknown; channel: unknown } | null = null;
7
+ // Gateway guardian-delivery list: empty = unbound, one entry = bound,
8
+ // null = gateway unreachable.
9
+ let mockGuardianList: Array<Record<string, unknown>> | null = [];
8
10
  let mockActiveSession: Record<string, unknown> | null = null;
9
11
  let mockSessionResult = {
10
12
  sessionId: "sess-1",
@@ -20,8 +22,14 @@ let deliverChannelReplyCalls: unknown[][] = [];
20
22
  let emitNotificationSignalCalls: unknown[] = [];
21
23
  let messageIdCounter = 0;
22
24
 
23
- mock.module("../../../contacts/contact-store.js", () => ({
24
- findGuardianForChannel: () => mockGuardian,
25
+ mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
26
+ // Existence guard reads fresh (uncached) only this variant is stubbed.
27
+ getGuardianDeliveryFresh: () => Promise.resolve(mockGuardianList),
28
+ guardianForChannel: (
29
+ list: Array<{ channelType: string; status: string }>,
30
+ channelType: string,
31
+ ) =>
32
+ list.find((g) => g.channelType === channelType && g.status === "active"),
25
33
  }));
26
34
 
27
35
  mock.module("../../channel-verification-service.js", () => ({
@@ -96,7 +104,7 @@ function makeParams(
96
104
 
97
105
  describe("handleGuardianActivationIntercept", () => {
98
106
  beforeEach(() => {
99
- mockGuardian = null;
107
+ mockGuardianList = [];
100
108
  mockActiveSession = null;
101
109
  mockSessionResult = {
102
110
  sessionId: "sess-1",
@@ -155,10 +163,15 @@ describe("handleGuardianActivationIntercept", () => {
155
163
  });
156
164
 
157
165
  test("bare /start with existing guardian returns null", async () => {
158
- mockGuardian = {
159
- contact: { id: "contact-1", role: "guardian" },
160
- channel: { id: "ch-1", type: "telegram" },
161
- };
166
+ mockGuardianList = [{ channelType: "telegram", status: "active" }];
167
+
168
+ const result = await handleGuardianActivationIntercept(makeParams());
169
+ expect(result).toBeNull();
170
+ expect(createOutboundSessionCalls).toHaveLength(0);
171
+ });
172
+
173
+ test("null guardian list (gateway unreachable) does NOT auto-start", async () => {
174
+ mockGuardianList = null;
162
175
 
163
176
  const result = await handleGuardianActivationIntercept(makeParams());
164
177
  expect(result).toBeNull();
@@ -9,7 +9,10 @@
9
9
  * the guardian binding, and sends a success reply.
10
10
  */
11
11
  import type { ChannelId } from "../../../channels/types.js";
12
- import { findGuardianForChannel } from "../../../contacts/contact-store.js";
12
+ import {
13
+ getGuardianDeliveryFresh,
14
+ guardianForChannel,
15
+ } from "../../../contacts/guardian-delivery-reader.js";
13
16
  import { emitNotificationSignal } from "../../../notifications/emit-signal.js";
14
17
  import { getLogger } from "../../../util/logger.js";
15
18
  import {
@@ -88,8 +91,16 @@ export async function handleGuardianActivationIntercept(
88
91
  // Only proceed for Telegram (can be extended later)
89
92
  if (sourceChannel !== "telegram") return null;
90
93
 
91
- // If a guardian already exists for this channel, continue to normal flow
92
- if (findGuardianForChannel(sourceChannel)) return null;
94
+ // If a guardian already exists for this channel, continue to normal flow.
95
+ // Null-list (gateway unreachable) is treated as guardian-present so a
96
+ // transient miss does NOT spuriously auto-start verification.
97
+ // Read fresh: gateway-side binding writes don't invalidate the daemon cache.
98
+ const guardianList = await getGuardianDeliveryFresh({
99
+ channelTypes: [sourceChannel],
100
+ });
101
+ if (guardianList === null || guardianForChannel(guardianList, sourceChannel)) {
102
+ return null;
103
+ }
93
104
 
94
105
  // Can't bind a session without sender identity
95
106
  if (!rawSenderId) return null;
@@ -18,6 +18,7 @@
18
18
  import type { SourceMetadata } from "@vellumai/gateway-client";
19
19
 
20
20
  import type { ChannelId, InterfaceId } from "../../../channels/types.js";
21
+ import { getGuardianDeliveryFresh } from "../../../contacts/guardian-delivery-reader.js";
21
22
  import { getDiskPressureStatus } from "../../../daemon/disk-pressure-guard.js";
22
23
  import { classifyDiskPressureTurnPolicy } from "../../../daemon/disk-pressure-policy.js";
23
24
  import { addMessage } from "../../../memory/conversation-crud.js";
@@ -35,6 +36,7 @@ import {
35
36
  import { getLogger } from "../../../util/logger.js";
36
37
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
37
38
  import type { ApprovalConversationGenerator } from "../../http-types.js";
39
+ import { setMemberVerdict } from "../../member-verdict-cache.js";
38
40
  import { resolveTrustContext } from "../../trust-context-resolver.js";
39
41
  import { handleGuardianReplyIntercept } from "./guardian-reply-intercept.js";
40
42
 
@@ -126,6 +128,23 @@ export async function handleSlackReactionIntercept(
126
128
  approvalConversationGenerator,
127
129
  } = params;
128
130
 
131
+ // Warm the channel-specific guardian-delivery cache before the SYNC trust
132
+ // resolve below. The sync resolver reads the IO-free cache snapshot; on a
133
+ // cold process only `vellum` is warmed at startup, so a Slack guardian
134
+ // reaction would otherwise misclassify as `unknown` and drop. Read fresh:
135
+ // gateway-side binding writes don't invalidate the daemon cache, so a stale
136
+ // empty snapshot would otherwise survive the TTL. This await runs in the
137
+ // already-async intercept, off the sync resolver's hot path.
138
+ await getGuardianDeliveryFresh({ channelTypes: [sourceChannel] });
139
+
140
+ // Reactions carry the gateway-stamped verdict but skip getInboundTrustVerdict,
141
+ // which warms the member-verdict cache the sync resolver reads. Seed it from
142
+ // the stamped verdict so an active non-guardian contact's reaction resolves
143
+ // instead of failing closed to `unknown` on a cold cache.
144
+ if (sourceMetadata?.trustVerdict) {
145
+ setMemberVerdict(sourceChannel, rawSenderId, sourceMetadata.trustVerdict);
146
+ }
147
+
129
148
  // Classify the reactor. No timezone enrichment — reactions never drive an
130
149
  // agent turn, so only the trust class / guardian principal matter.
131
150
  const trustCtx = resolveTrustContext({
@@ -102,6 +102,7 @@ import { ROUTES as OAUTH_COMMANDS_ROUTES } from "./oauth-commands-routes.js";
102
102
  import { ROUTES as OAUTH_CONNECT_ROUTES } from "./oauth-connect-routes.js";
103
103
  import { ROUTES as OAUTH_LIFECYCLE_ROUTES } from "./oauth-lifecycle-routes.js";
104
104
  import { ROUTES as OAUTH_PROVIDERS_ROUTES } from "./oauth-providers.js";
105
+ import { ROUTES as ONBOARDING_CHECKIN_ROUTES } from "./onboarding-checkin-routes.js";
105
106
  import { ROUTES as PLATFORM_ROUTES } from "./platform-routes.js";
106
107
  import { ROUTES as PLAYGROUND_ROUTES } from "./playground/index.js";
107
108
  import { ROUTES as PLUGINS_ROUTES } from "./plugins-routes.js";
@@ -234,6 +235,7 @@ export const ROUTES: RouteDefinition[] = [
234
235
  ...OAUTH_LIFECYCLE_ROUTES,
235
236
  ...OAUTH_COMMANDS_ROUTES,
236
237
  ...OAUTH_PROVIDERS_ROUTES,
238
+ ...ONBOARDING_CHECKIN_ROUTES,
237
239
  ...PLATFORM_ROUTES,
238
240
  ...PLAYGROUND_ROUTES,
239
241
  ...PLUGINS_ROUTES,
@@ -40,10 +40,31 @@ export interface LlmContextSection {
40
40
  language?: string;
41
41
  }
42
42
 
43
+ /**
44
+ * Structured error extracted from a rejected call's response payload.
45
+ * Mirrors the on-disk `responsePayload.error` shape produced by
46
+ * `buildProviderErrorResponsePayload`. Present only when the call failed
47
+ * before returning a response — consumers branch on its presence to
48
+ * render the call as failed.
49
+ */
50
+ export interface LlmContextError {
51
+ name?: string;
52
+ message?: string;
53
+ code?: string;
54
+ provider?: string;
55
+ statusCode?: number;
56
+ retryAfterMs?: number;
57
+ apiErrorCode?: string;
58
+ apiErrorType?: string;
59
+ apiErrorParam?: string;
60
+ requestId?: string;
61
+ }
62
+
43
63
  export interface LlmContextNormalizationResult {
44
64
  summary?: LlmContextSummary;
45
65
  requestSections?: LlmContextSection[];
46
66
  responseSections?: LlmContextSection[];
67
+ error?: LlmContextError;
47
68
  }
48
69
 
49
70
  interface NormalizedPayloadCandidate {
@@ -55,6 +76,68 @@ interface NormalizedPayloadCandidate {
55
76
 
56
77
  export function normalizeLlmContextPayloads(
57
78
  input: LlmContextNormalizationInput,
79
+ ): LlmContextNormalizationResult {
80
+ const base = normalizeSuccessPayloads(input);
81
+ const error = normalizeProviderErrorPayload(input.responsePayload);
82
+ if (!error) {
83
+ return base;
84
+ }
85
+ // A rejected call has no response sections to render — only the request
86
+ // side normalized. Attach the structured error so the inspector can show
87
+ // a failure banner and treat the cost as $0.00 instead of silently
88
+ // falling back to "section rendering unavailable".
89
+ return { ...base, error };
90
+ }
91
+
92
+ /**
93
+ * Detect a provider/transport error stored in the response payload.
94
+ *
95
+ * Error rows are written as `{ error: { name, message, code?, provider?,
96
+ * statusCode?, retryAfterMs? } }` by `buildProviderErrorResponsePayload`.
97
+ * Successful provider responses never carry a top-level `error` object, so
98
+ * the presence of one (with at least one identifying field) is a reliable
99
+ * signal that the call failed.
100
+ */
101
+ function normalizeProviderErrorPayload(
102
+ responsePayload: unknown,
103
+ ): LlmContextError | null {
104
+ const error = asRecord(asRecord(responsePayload)?.error);
105
+ if (!error) {
106
+ return null;
107
+ }
108
+
109
+ const name = asString(error.name);
110
+ const message = asString(error.message);
111
+ const code = asString(error.code);
112
+ // Require at least one identifying field so an unrelated `error` key on a
113
+ // success payload isn't misread as a provider failure.
114
+ if (name === undefined && message === undefined && code === undefined) {
115
+ return null;
116
+ }
117
+
118
+ const normalized: LlmContextError = {};
119
+ if (name !== undefined) normalized.name = name;
120
+ if (message !== undefined) normalized.message = message;
121
+ if (code !== undefined) normalized.code = code;
122
+ const provider = asString(error.provider);
123
+ if (provider !== undefined) normalized.provider = provider;
124
+ const statusCode = asNumber(error.statusCode);
125
+ if (statusCode !== undefined) normalized.statusCode = statusCode;
126
+ const retryAfterMs = asNumber(error.retryAfterMs);
127
+ if (retryAfterMs !== undefined) normalized.retryAfterMs = retryAfterMs;
128
+ const apiErrorCode = asString(error.apiErrorCode);
129
+ if (apiErrorCode !== undefined) normalized.apiErrorCode = apiErrorCode;
130
+ const apiErrorType = asString(error.apiErrorType);
131
+ if (apiErrorType !== undefined) normalized.apiErrorType = apiErrorType;
132
+ const apiErrorParam = asString(error.apiErrorParam);
133
+ if (apiErrorParam !== undefined) normalized.apiErrorParam = apiErrorParam;
134
+ const requestId = asString(error.requestId);
135
+ if (requestId !== undefined) normalized.requestId = requestId;
136
+ return normalized;
137
+ }
138
+
139
+ function normalizeSuccessPayloads(
140
+ input: LlmContextNormalizationInput,
58
141
  ): LlmContextNormalizationResult {
59
142
  const requestCandidates = [
60
143
  normalizeOpenAiRequestPayload(input.requestPayload),