@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
@@ -13,7 +13,11 @@
13
13
 
14
14
  import type { ChannelId } from "../channels/types.js";
15
15
  import { isHttpAuthDisabled } from "../config/env.js";
16
- import { findGuardianForChannel } from "../contacts/contact-store.js";
16
+ import {
17
+ getGuardianDelivery,
18
+ guardianForChannel,
19
+ peekCachedGuardianDelivery,
20
+ } from "../contacts/guardian-delivery-reader.js";
17
21
  import type { TrustContext } from "../daemon/trust-context.js";
18
22
  import { getLogger } from "../util/logger.js";
19
23
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
@@ -45,14 +49,57 @@ export function buildLocalAuthContext(conversationId: string): AuthContext {
45
49
  }
46
50
 
47
51
  /**
48
- * Look up the local vellum guardian's principalId from the contacts table.
52
+ * Resolve the local vellum guardian's principalId from the gateway.
53
+ *
54
+ * The gateway owns guardian binding; this reads it through the cached
55
+ * `getGuardianDelivery` reader (PR-3 TTL + single-flight) so hot paths don't
56
+ * storm the IPC.
49
57
  *
50
58
  * Returns `undefined` when no vellum guardian binding exists (e.g. fresh
51
- * install before bootstrap). Callers should treat that case as
52
- * "not yet available" and either fall back or proceed without a principalId.
59
+ * install before bootstrap, or the gateway is unreachable). Callers should
60
+ * treat that case as "not yet available" and proceed without a principalId.
61
+ */
62
+ export async function findLocalGuardianPrincipalId(): Promise<
63
+ string | undefined
64
+ > {
65
+ const list = await getGuardianDelivery({ channelTypes: ["vellum"] });
66
+ if (!list) return undefined;
67
+ return guardianForChannel(list, "vellum")?.principalId ?? undefined;
68
+ }
69
+
70
+ /**
71
+ * Eagerly warm the gateway guardian-delivery cache for the vellum channel.
72
+ *
73
+ * The SSE eager-subscribe path resolves the actor principal synchronously via
74
+ * {@link findLocalGuardianPrincipalIdFromStore}, which reads only the IO-free
75
+ * cache snapshot. On a cold cache (auth-disabled / local startup, before any
76
+ * async `getGuardianDelivery` has run) it returns undefined, so the FIRST SSE
77
+ * registration would carry no `actorPrincipalId` and host-proxy same-user
78
+ * targeting would regress until a later reconnect warms the cache.
79
+ *
80
+ * Called during daemon startup (after the gateway IPC is reachable) so the
81
+ * cache is populated before clients register. Best-effort: a cold gateway
82
+ * leaves the cache empty (failures aren't cached), and the async hot paths
83
+ * warm it on their next read.
53
84
  */
54
- export function findLocalGuardianPrincipalId(): string | undefined {
55
- return findGuardianForChannel("vellum")?.contact.principalId ?? undefined;
85
+ export async function warmLocalGuardianPrincipalCache(): Promise<void> {
86
+ await findLocalGuardianPrincipalId();
87
+ }
88
+
89
+ /**
90
+ * Synchronous read of the vellum guardian's principalId for paths that cannot
91
+ * await {@link findLocalGuardianPrincipalId} — namely the SSE eager-subscribe
92
+ * path (`events-routes`), which registers before the stream is created.
93
+ *
94
+ * Reads the same gateway-owned binding as the async path via a sync, IO-free
95
+ * snapshot of the guardian-delivery cache (kept fresh by the async hot paths
96
+ * and event-driven invalidation), so SSE registers the SAME principal the
97
+ * send/result routes resolve.
98
+ */
99
+ export function findLocalGuardianPrincipalIdFromStore(): string | undefined {
100
+ const cached = peekCachedGuardianDelivery({ channelTypes: ["vellum"] });
101
+ if (!cached) return undefined;
102
+ return guardianForChannel(cached, "vellum")?.principalId ?? undefined;
56
103
  }
57
104
 
58
105
  /**
@@ -76,12 +123,35 @@ export function findLocalGuardianPrincipalId(): string | undefined {
76
123
  * yet (e.g. fresh install before bootstrap); callers must treat this the
77
124
  * same as a missing principal.
78
125
  */
79
- export function resolveActorPrincipalIdForLocalGuardian(
126
+ export async function resolveActorPrincipalIdForLocalGuardian(
127
+ rawHeader: string | undefined,
128
+ ): Promise<string | undefined> {
129
+ if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
130
+
131
+ const guardianPrincipalId = await findLocalGuardianPrincipalId();
132
+ if (guardianPrincipalId) return guardianPrincipalId;
133
+
134
+ log.warn(
135
+ "dev-bypass actor principal received but no vellum guardian binding found; returning undefined",
136
+ );
137
+ return undefined;
138
+ }
139
+
140
+ /**
141
+ * Synchronous variant of {@link resolveActorPrincipalIdForLocalGuardian} for
142
+ * the SSE eager-subscribe path, which registers before the response stream is
143
+ * created and cannot await. Resolves the guardian from the IO-free gateway
144
+ * cache snapshot first (same source the async path reads), falling back to the
145
+ * local store when the cache is cold — so SSE registers the SAME principal the
146
+ * send/result routes resolve and host-proxy targeting matches the same-user
147
+ * client even when the local contact row is stale.
148
+ */
149
+ export function resolveActorPrincipalIdForLocalGuardianSync(
80
150
  rawHeader: string | undefined,
81
151
  ): string | undefined {
82
152
  if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
83
153
 
84
- const guardianPrincipalId = findLocalGuardianPrincipalId();
154
+ const guardianPrincipalId = findLocalGuardianPrincipalIdFromStore();
85
155
  if (guardianPrincipalId) return guardianPrincipalId;
86
156
 
87
157
  log.warn(
@@ -102,12 +172,12 @@ export function resolveActorPrincipalIdForLocalGuardian(
102
172
  * bootstrap), falls back to a minimal guardian context so the local
103
173
  * user is not incorrectly denied.
104
174
  */
105
- export function resolveLocalTrustContext(
175
+ export async function resolveLocalTrustContext(
106
176
  sourceChannel: ChannelId = "vellum",
107
- ): TrustContext {
177
+ ): Promise<TrustContext> {
108
178
  const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
109
179
 
110
- const guardianPrincipalId = findLocalGuardianPrincipalId();
180
+ const guardianPrincipalId = await findLocalGuardianPrincipalId();
111
181
  if (guardianPrincipalId) {
112
182
  const trustCtx = resolveTrustContext({
113
183
  assistantId,
@@ -139,10 +209,12 @@ export function resolveLocalTrustContext(
139
209
  * downstream code to resolve guardian context using the same
140
210
  * `authContext.actorPrincipalId` path as HTTP sessions.
141
211
  */
142
- export function resolveLocalAuthContext(conversationId: string): AuthContext {
212
+ export async function resolveLocalAuthContext(
213
+ conversationId: string,
214
+ ): Promise<AuthContext> {
143
215
  const authContext = buildLocalAuthContext(conversationId);
144
216
 
145
- const guardianPrincipalId = findLocalGuardianPrincipalId();
217
+ const guardianPrincipalId = await findLocalGuardianPrincipalId();
146
218
  if (guardianPrincipalId) {
147
219
  return { ...authContext, actorPrincipalId: guardianPrincipalId };
148
220
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Derives a local principal's {@link TrustContext} from the gateway guardian
3
+ * binding. Fails closed to unknown on a missing or null read.
4
+ */
5
+
6
+ import type { ChannelId } from "../channels/types.js";
7
+ import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
8
+ import type { TrustContext } from "../daemon/trust-context.js";
9
+ import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
10
+
11
+ export interface ResolveLocalPrincipalTrustInput {
12
+ actorPrincipalId: string;
13
+ sourceChannel: ChannelId;
14
+ conversationExternalId: string;
15
+ }
16
+
17
+ /** Guardian match → guardian ctx; miss or null read → unknown (fail closed). */
18
+ export async function resolveLocalPrincipalTrustContext(
19
+ input: ResolveLocalPrincipalTrustInput,
20
+ ): Promise<TrustContext> {
21
+ const unknownContext: TrustContext = {
22
+ sourceChannel: input.sourceChannel,
23
+ trustClass: "unknown",
24
+ requesterExternalUserId: input.actorPrincipalId,
25
+ requesterChatId: input.conversationExternalId,
26
+ };
27
+
28
+ // Fail closed: a null read means the gateway is unreachable — never grant
29
+ // guardian on a miss.
30
+ const guardians = await getGuardianDelivery({ channelTypes: ["vellum"] });
31
+ if (!guardians) return unknownContext;
32
+
33
+ const guardian = guardians.find(
34
+ (g) => g.principalId === input.actorPrincipalId,
35
+ );
36
+ if (!guardian) return unknownContext;
37
+
38
+ return {
39
+ sourceChannel: input.sourceChannel,
40
+ trustClass: "guardian",
41
+ guardianChatId: guardian.externalChatId ?? input.conversationExternalId,
42
+ guardianExternalUserId:
43
+ canonicalizeInboundIdentity(input.sourceChannel, guardian.address) ??
44
+ undefined,
45
+ guardianPrincipalId: guardian.principalId ?? undefined,
46
+ // Mirror toTrustContext: with no username the requester identifier is the
47
+ // canonical sender id, which for a vellum principal is actorPrincipalId.
48
+ requesterIdentifier: input.actorPrincipalId,
49
+ requesterExternalUserId: input.actorPrincipalId,
50
+ requesterChatId: input.conversationExternalId,
51
+ };
52
+ }
@@ -229,6 +229,15 @@ export function getByConversation(
229
229
  * /v1/host-browser-result, /v1/host-app-control-result, or
230
230
  * /v1/host-transfer-result after completing the operation, get a 404, and the
231
231
  * proxy timer would fire with a spurious timeout error.
232
+ *
233
+ * `question` interactions are also skipped: a new message supersedes an open
234
+ * ask_question by steering to it (see the enqueue path in
235
+ * conversation-routes.ts), which aborts the parked turn and settles the
236
+ * question via its abort signal. Clearing the entry here instead would drop it
237
+ * without settling the prompt's Promise (questions carry no `rpcResolve`
238
+ * fallback like secrets do) and would strip the steer of the entry it needs to
239
+ * fire — which can co-occur with a confirmation, since one model response can
240
+ * open both tools concurrently.
232
241
  */
233
242
  export function removeByConversation(
234
243
  conversationId: string,
@@ -244,7 +253,8 @@ export function removeByConversation(
244
253
  interaction.kind !== "host_browser" &&
245
254
  interaction.kind !== "host_app_control" &&
246
255
  interaction.kind !== "host_transfer" &&
247
- interaction.kind !== "acp_confirmation"
256
+ interaction.kind !== "acp_confirmation" &&
257
+ interaction.kind !== "question"
248
258
  ) {
249
259
  // resolve() clears the stored timer and detaches abort listeners.
250
260
  resolve(requestId, state);
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { beforeEach, describe, expect, mock, test } from "bun:test";
12
12
 
13
+ import type { GuardianDelivery } from "@vellumai/gateway-client";
13
14
  import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
14
15
 
15
16
  type IpcCall = { method: string; params?: Record<string, unknown> };
@@ -73,8 +74,16 @@ mock.module("../../channel-verification-service.js", () => ({
73
74
  getGuardianBinding: mock(() => guardianBinding),
74
75
  }));
75
76
 
76
- // Contact-store lookup that resolves the guardian's channel to downgrade.
77
- let contactChannel: { id: string; status: string } | null = null;
77
+ // Contact-store lookup that resolves the guardian's channel to downgrade. The
78
+ // channel carries the type/address/externalChatId the gateway delivery is
79
+ // matched against (see deliveryForChannel).
80
+ let contactChannel: {
81
+ id: string;
82
+ status: string;
83
+ type: string;
84
+ address: string;
85
+ externalChatId: string;
86
+ } | null = null;
78
87
  const actualContactStore = await import("../../../contacts/contact-store.js");
79
88
  mock.module("../../../contacts/contact-store.js", () => ({
80
89
  ...actualContactStore,
@@ -83,6 +92,20 @@ mock.module("../../../contacts/contact-store.js", () => ({
83
92
  ),
84
93
  }));
85
94
 
95
+ // Gateway delivery (ACL source of truth). The revoke gate relays only when the
96
+ // matching delivery is live (active/pending/unverified); already-revoked or a
97
+ // missing delivery short-circuits the relay.
98
+ let guardianDeliveries: GuardianDelivery[] | null = null;
99
+ mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
100
+ getGuardianDelivery: mock(async (input?: { channelTypes?: string[] }) => {
101
+ if (guardianDeliveries == null) return null;
102
+ if (!input?.channelTypes) return guardianDeliveries;
103
+ return guardianDeliveries.filter((g) =>
104
+ input.channelTypes!.includes(g.channelType),
105
+ );
106
+ }),
107
+ }));
108
+
86
109
  // Guard: the contact-channel ACL write moves to the gateway relay and must
87
110
  // never run locally. The assistant-owned guardian-binding teardown
88
111
  // (revokeGuardianBinding) still runs locally — assert it is invoked.
@@ -122,7 +145,24 @@ describe("verification revoke relay", () => {
122
145
  guardianExternalUserId: "guardian-user",
123
146
  guardianDeliveryChatId: "chat-1",
124
147
  };
125
- contactChannel = { id: "ch1", status: "active" };
148
+ contactChannel = {
149
+ id: "ch1",
150
+ status: "active",
151
+ type: "telegram",
152
+ address: "guardian-user",
153
+ externalChatId: "chat-1",
154
+ };
155
+ // Gateway delivery is live by default, so the revoke relay fires.
156
+ guardianDeliveries = [
157
+ {
158
+ channelType: "telegram",
159
+ contactId: "c1",
160
+ address: "guardian-user",
161
+ externalChatId: "chat-1",
162
+ status: "active",
163
+ verifiedAt: 1700000000,
164
+ },
165
+ ];
126
166
  ipcCallPersistentMock.mockClear();
127
167
  cancelOutboundMock.mockClear();
128
168
  revokePendingSessionsMock.mockClear();
@@ -263,8 +303,19 @@ describe("verification revoke relay", () => {
263
303
  expect(result.bound).toBe(false);
264
304
  });
265
305
 
266
- test("skips the relay when the resolved channel is already revoked", async () => {
267
- contactChannel = { id: "ch1", status: "revoked" };
306
+ test("skips the relay when the gateway delivery is already revoked", async () => {
307
+ // The gateway (source of truth) already shows the channel revoked, so the
308
+ // redundant relay is skipped even though session/binding teardown runs.
309
+ guardianDeliveries = [
310
+ {
311
+ channelType: "telegram",
312
+ contactId: "c1",
313
+ address: "guardian-user",
314
+ externalChatId: "chat-1",
315
+ status: "revoked",
316
+ verifiedAt: 1700000000,
317
+ },
318
+ ];
268
319
 
269
320
  await revokeHandler({ body: { channel: "telegram" } });
270
321
 
@@ -24,7 +24,7 @@ mock.module("../../../daemon/handlers/config-channels.js", () => ({
24
24
  verifyTrustedContactCalls.push([contactChannelId, assistantId]);
25
25
  return verifyTrustedContactImpl(contactChannelId, assistantId);
26
26
  },
27
- createInboundChallenge: () => ({ success: true }),
27
+ createInboundChallenge: async () => ({ success: true }),
28
28
  getVerificationStatus: () => ({ success: true }),
29
29
  revokeVerificationForChannel: () => ({ success: true }),
30
30
  }));
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Unit tests for the contacts read API.
3
+ *
4
+ * `handleListContacts`, `handleGetContact`, and the `search_contacts`
5
+ * no-filter case relay to the gateway rich read (`contacts_list_rich` /
6
+ * `contacts_get_rich`), which is the source of truth for the ACL fields
7
+ * (`role`/`status`/`policy`/`verifiedAt`/`interactionCount`/`lastInteraction`).
8
+ * These tests assert the serialized response shape is gateway-sourced and
9
+ * unchanged for the web client, that no read path falls back to the assistant
10
+ * DB, and that a relay failure fails closed (surfaces an error) instead of
11
+ * reading local ACL.
12
+ */
13
+
14
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
15
+
16
+ import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
17
+ import { z } from "zod";
18
+
19
+ let ipcCalls: { method: string; params?: Record<string, unknown> }[] = [];
20
+ let ipcResult: unknown = {};
21
+ let ipcError: Error | undefined;
22
+
23
+ const ipcCallPersistentMock = mock(
24
+ async (method: string, params?: Record<string, unknown>) => {
25
+ ipcCalls.push({ method, params });
26
+ if (ipcError) throw ipcError;
27
+ return ipcResult;
28
+ },
29
+ );
30
+
31
+ const actualGatewayClient = await import("../../../ipc/gateway-client.js");
32
+
33
+ mock.module("../../../ipc/gateway-client.js", () => ({
34
+ ...actualGatewayClient,
35
+ ipcCallPersistent: ipcCallPersistentMock,
36
+ }));
37
+
38
+ // Guard: fail loudly if any read path falls back to the assistant DB for ACL
39
+ // fields. The relay read paths must source role/status/stats from the gateway.
40
+ const actualContactStore = await import("../../../contacts/contact-store.js");
41
+
42
+ const contactStoreReadGuard = mock(() => {
43
+ throw new Error(
44
+ "assistant contact-store read must not happen on the gateway relay path",
45
+ );
46
+ });
47
+
48
+ // Filtered/native reads (search) legitimately go to the assistant DB. Drive
49
+ // them deterministically so the daemon-native response shape can be asserted.
50
+ let searchContactsResult: unknown[] = [];
51
+ const searchContactsMock = mock(() => searchContactsResult);
52
+
53
+ mock.module("../../../contacts/contact-store.js", () => ({
54
+ ...actualContactStore,
55
+ getContact: contactStoreReadGuard,
56
+ listContacts: contactStoreReadGuard,
57
+ getAssistantContactMetadata: contactStoreReadGuard,
58
+ searchContacts: searchContactsMock,
59
+ }));
60
+
61
+ const { handleListContacts, handleGetContact, ROUTES } =
62
+ await import("../contact-routes.js");
63
+
64
+ // Daemon-native contact: INFO is hydrated locally; channel-level ACL fields
65
+ // (status/policy/verification) are gateway-owned and absent on native reads.
66
+ // Contact-level `role` is stored locally (NOT NULL) and always returned.
67
+ const nativeContact = {
68
+ id: "ct_2",
69
+ displayName: "Bob",
70
+ notes: null,
71
+ role: "contact",
72
+ contactType: "human",
73
+ lastInteraction: 4200,
74
+ interactionCount: 4,
75
+ createdAt: 1000,
76
+ updatedAt: 1500,
77
+ userFile: "bob.md",
78
+ channels: [
79
+ {
80
+ id: "ch_2",
81
+ contactId: "ct_2",
82
+ type: "phone",
83
+ address: "+15550200",
84
+ isPrimary: true,
85
+ externalChatId: null,
86
+ inviteId: null,
87
+ lastSeenAt: 4100,
88
+ interactionCount: 4,
89
+ lastInteraction: 4200,
90
+ updatedAt: 1500,
91
+ createdAt: 1000,
92
+ },
93
+ ],
94
+ };
95
+
96
+ const gatewayChannel = {
97
+ id: "ch_1",
98
+ contactId: "ct_1",
99
+ type: "sms",
100
+ address: "+15550100",
101
+ isPrimary: true,
102
+ externalUserId: null,
103
+ status: "active",
104
+ policy: "allow",
105
+ verifiedAt: 1700,
106
+ verifiedVia: "invite",
107
+ lastSeenAt: 1800,
108
+ interactionCount: 7,
109
+ lastInteraction: 1900,
110
+ revokedReason: null,
111
+ blockedReason: null,
112
+ };
113
+
114
+ const gatewayContact = {
115
+ id: "ct_1",
116
+ displayName: "Alice",
117
+ role: "guardian",
118
+ notes: "a note",
119
+ contactType: "human",
120
+ lastInteraction: 1900,
121
+ interactionCount: 7,
122
+ createdAt: 1000,
123
+ updatedAt: 1500,
124
+ channels: [gatewayChannel],
125
+ };
126
+
127
+ describe("contacts read API relays from the gateway", () => {
128
+ beforeEach(() => {
129
+ ipcCalls = [];
130
+ ipcResult = {};
131
+ ipcError = undefined;
132
+ ipcCallPersistentMock.mockClear();
133
+ contactStoreReadGuard.mockClear();
134
+ searchContactsResult = [];
135
+ searchContactsMock.mockClear();
136
+ });
137
+
138
+ test("list relays to contacts_list_rich and serializes the gateway ACL fields", async () => {
139
+ ipcResult = { ok: true, contacts: [gatewayContact] };
140
+
141
+ const result = await handleListContacts({ limit: "50" });
142
+
143
+ expect(ipcCalls).toEqual([
144
+ { method: "contacts_list_rich", params: { limit: 50 } },
145
+ ]);
146
+ expect(result.ok).toBe(true);
147
+ expect(result.contacts).toHaveLength(1);
148
+
149
+ const [contact] = result.contacts;
150
+ // ACL fields are gateway-sourced and reach the web client unchanged.
151
+ expect((contact as { role?: string }).role).toBe("guardian");
152
+ expect(contact.interactionCount).toBe(7);
153
+ expect(contact.lastInteraction).toBe(1900);
154
+ const channel = contact.channels[0] as Record<string, unknown>;
155
+ expect(channel.status).toBe("active");
156
+ expect(channel.policy).toBe("allow");
157
+ expect(channel.verifiedAt).toBe(1700);
158
+ expect(channel.interactionCount).toBe(7);
159
+ expect(channel.lastInteraction).toBe(1900);
160
+ // Channel compat echoes address into externalUserId for older clients.
161
+ expect(channel.externalUserId).toBe("+15550100");
162
+
163
+ expect(contactStoreReadGuard).not.toHaveBeenCalled();
164
+ });
165
+
166
+ test("list forwards the role filter to the gateway", async () => {
167
+ ipcResult = { ok: true, contacts: [] };
168
+
169
+ await handleListContacts({ limit: "10", role: "guardian" });
170
+
171
+ expect(ipcCalls).toEqual([
172
+ {
173
+ method: "contacts_list_rich",
174
+ params: { limit: 10, role: "guardian" },
175
+ },
176
+ ]);
177
+ });
178
+
179
+ test("list fails closed when the gateway relay is unavailable", async () => {
180
+ ipcError = new IpcCallError("gateway down", {
181
+ statusCode: 503,
182
+ errorCode: "UNAVAILABLE",
183
+ });
184
+
185
+ await expect(handleListContacts({ limit: "50" })).rejects.toMatchObject({
186
+ message: "gateway down",
187
+ statusCode: 503,
188
+ });
189
+ // No assistant-DB fallback read.
190
+ expect(contactStoreReadGuard).not.toHaveBeenCalled();
191
+ });
192
+
193
+ test("get relays to contacts_get_rich and serializes the gateway ACL fields", async () => {
194
+ ipcResult = { ok: true, contact: gatewayContact };
195
+
196
+ const result = await handleGetContact("ct_1");
197
+
198
+ expect(ipcCalls).toEqual([
199
+ { method: "contacts_get_rich", params: { contactId: "ct_1" } },
200
+ ]);
201
+ expect(result.ok).toBe(true);
202
+ expect(result.contact.role).toBe("guardian");
203
+ expect(result.contact.interactionCount).toBe(7);
204
+ const channel = result.contact.channels[0] as Record<string, unknown>;
205
+ expect(channel.status).toBe("active");
206
+ expect(channel.externalUserId).toBe("+15550100");
207
+ expect(contactStoreReadGuard).not.toHaveBeenCalled();
208
+ });
209
+
210
+ test("get surfaces a clean gateway not-found as a 404", async () => {
211
+ ipcResult = { ok: true };
212
+
213
+ await expect(handleGetContact("missing")).rejects.toMatchObject({
214
+ statusCode: 404,
215
+ });
216
+ expect(contactStoreReadGuard).not.toHaveBeenCalled();
217
+ });
218
+
219
+ test("get fails closed on a relay failure (no assistant-DB fallback)", async () => {
220
+ ipcError = new IpcCallError("gateway down", {
221
+ statusCode: 503,
222
+ errorCode: "UNAVAILABLE",
223
+ });
224
+
225
+ await expect(handleGetContact("ct_1")).rejects.toMatchObject({
226
+ message: "gateway down",
227
+ statusCode: 503,
228
+ });
229
+ expect(contactStoreReadGuard).not.toHaveBeenCalled();
230
+ });
231
+
232
+ test("search no-filter relays to the gateway list read", async () => {
233
+ ipcResult = { ok: true, contacts: [gatewayContact] };
234
+
235
+ const route = ROUTES.find((r) => r.operationId === "search_contacts");
236
+ expect(route).toBeDefined();
237
+
238
+ const contacts = (await route!.handler({ body: {} })) as Array<{
239
+ role: string;
240
+ interactionCount: number;
241
+ channels: { status: string }[];
242
+ }>;
243
+
244
+ expect(ipcCalls).toEqual([
245
+ { method: "contacts_list_rich", params: { limit: 50 } },
246
+ ]);
247
+ expect(contacts[0].role).toBe("guardian");
248
+ expect(contacts[0].interactionCount).toBe(7);
249
+ expect(contacts[0].channels[0].status).toBe("active");
250
+ expect(contactStoreReadGuard).not.toHaveBeenCalled();
251
+ });
252
+ });
253
+
254
+ describe("filtered/native contact reads stay daemon-native", () => {
255
+ const listRoute = ROUTES.find((r) => r.operationId === "listContacts")!;
256
+ const listResponseSchema = listRoute.responseBody as z.ZodTypeAny;
257
+ const searchRoute = ROUTES.find((r) => r.operationId === "search_contacts")!;
258
+ const searchResponseSchema = searchRoute.responseBody as z.ZodTypeAny;
259
+
260
+ beforeEach(() => {
261
+ ipcCalls = [];
262
+ ipcResult = {};
263
+ ipcError = undefined;
264
+ ipcCallPersistentMock.mockClear();
265
+ contactStoreReadGuard.mockClear();
266
+ searchContactsResult = [];
267
+ searchContactsMock.mockClear();
268
+ });
269
+
270
+ test("query-filtered list serves daemon-native INFO and validates against the response schema", async () => {
271
+ searchContactsResult = [nativeContact];
272
+
273
+ const result = await handleListContacts({ query: "Bob", limit: "10" });
274
+
275
+ // No gateway relay for a true search.
276
+ expect(ipcCalls).toEqual([]);
277
+ expect(searchContactsMock).toHaveBeenCalled();
278
+
279
+ const [contact] = result.contacts;
280
+ // INFO telemetry is present (re-hydrated locally, not dropped).
281
+ expect(contact.interactionCount).toBe(4);
282
+ expect(contact.lastInteraction).toBe(4200);
283
+ const channel = contact.channels[0] as Record<string, unknown>;
284
+ expect(channel.interactionCount).toBe(4);
285
+ expect(channel.lastSeenAt).toBe(4100);
286
+ expect(channel.externalUserId).toBe("+15550200");
287
+ // Contact-level `role` is locally stored (NOT NULL) and always present.
288
+ expect((contact as { role: string }).role).toBe("contact");
289
+ // Channel-level ACL fields (status/policy) are gateway-owned and absent.
290
+ expect("status" in channel).toBe(false);
291
+ expect(() => listResponseSchema.parse(result)).not.toThrow();
292
+ });
293
+
294
+ test("POST search with a filter validates against the response schema", async () => {
295
+ searchContactsResult = [nativeContact];
296
+
297
+ const contacts = (await searchRoute.handler({
298
+ body: { query: "Bob" },
299
+ })) as unknown[];
300
+
301
+ expect(ipcCalls).toEqual([]);
302
+ expect(searchContactsMock).toHaveBeenCalled();
303
+ expect(() => searchResponseSchema.parse(contacts)).not.toThrow();
304
+ });
305
+ });