@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
@@ -8,8 +8,6 @@
8
8
  */
9
9
 
10
10
  import { parseChannelId } from "../../channels/types.js";
11
- import { getConfig } from "../../config/loader.js";
12
- import { findConversation } from "../../daemon/conversation-registry.js";
13
11
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
14
12
  import {
15
13
  type AttentionState,
@@ -21,13 +19,11 @@ import {
21
19
  type ConversationRow,
22
20
  getConversation,
23
21
  getDisplayMetaForConversations,
22
+ isConversationProcessing,
24
23
  } from "../../memory/conversation-crud.js";
25
24
  import type { ExternalConversationBinding } from "../../memory/external-conversation-store.js";
26
25
  import { getBindingsForConversations } from "../../memory/external-conversation-store.js";
27
- import {
28
- buildSlackMessageDeepLinks,
29
- buildSlackWebChannelUrl,
30
- } from "../../messaging/providers/slack/deep-link.js";
26
+ import { buildChannelBindingMetadata } from "../../messaging/channel-binding-metadata.js";
31
27
 
32
28
  // ---------------------------------------------------------------------------
33
29
  // Helpers
@@ -126,48 +122,7 @@ function resolveSerializedGroupId(
126
122
  }
127
123
 
128
124
  function buildChannelBinding(binding: ExternalConversationBinding) {
129
- const externalChatName =
130
- binding.externalChatName?.trim() ||
131
- (binding.sourceChannel === "slack" ? binding.externalChatId : undefined);
132
- const slackConfig =
133
- binding.sourceChannel === "slack" ? getConfig().slack : undefined;
134
- const slackThreadLink =
135
- slackConfig && binding.externalThreadId
136
- ? buildSlackMessageDeepLinks({
137
- teamId: slackConfig.teamId,
138
- teamUrl: slackConfig.teamUrl,
139
- channelId: binding.externalChatId,
140
- messageTs: binding.externalThreadId,
141
- })
142
- : undefined;
143
- const slackThread =
144
- binding.sourceChannel === "slack" && binding.externalThreadId
145
- ? {
146
- channelId: binding.externalChatId,
147
- threadTs: binding.externalThreadId,
148
- ...(slackThreadLink ? { link: slackThreadLink } : {}),
149
- }
150
- : undefined;
151
- const slackChannelWebUrl = slackConfig
152
- ? buildSlackWebChannelUrl({
153
- teamUrl: slackConfig.teamUrl,
154
- channelId: binding.externalChatId,
155
- })
156
- : undefined;
157
- const slackChannel =
158
- binding.sourceChannel === "slack"
159
- ? {
160
- channelId: binding.externalChatId,
161
- name: externalChatName,
162
- ...(slackChannelWebUrl
163
- ? {
164
- link: {
165
- webUrl: slackChannelWebUrl,
166
- },
167
- }
168
- : {}),
169
- }
170
- : undefined;
125
+ const externalChatName = binding.externalChatName?.trim() || undefined;
171
126
 
172
127
  return {
173
128
  sourceChannel: binding.sourceChannel,
@@ -179,8 +134,10 @@ function buildChannelBinding(binding: ExternalConversationBinding) {
179
134
  externalUserId: binding.externalUserId,
180
135
  displayName: binding.displayName,
181
136
  username: binding.username,
182
- ...(slackThread ? { slackThread } : {}),
183
- ...(slackChannel ? { slackChannel } : {}),
137
+ // Channel-specific enrichment (e.g. Slack deep links) is contributed by
138
+ // the source channel's binding-metadata builder, keeping this serializer
139
+ // channel-agnostic.
140
+ ...buildChannelBindingMetadata(binding),
184
141
  };
185
142
  }
186
143
 
@@ -196,9 +153,9 @@ export function serializeConversationSummary(params: {
196
153
  parentCache: Map<string, ConversationRow | null>;
197
154
  /**
198
155
  * Whether the agent loop is currently mid-turn for this conversation.
199
- * Sourced from the in-memory daemon `Conversation.isProcessing()` flag
200
- * callers resolve via `findConversation(id)?.isProcessing() ?? false`
201
- * so cold (evicted / never-loaded) rows report `false`. Plumbed in
156
+ * Resolved by `isConversationProcessing(id)`, which checks the in-memory
157
+ * daemon flag first and falls back to the persisted
158
+ * `processing_started_at` column for cold conversations. Plumbed in
202
159
  * rather than read here so the serializer stays a pure shape mapper
203
160
  * with no daemon-store coupling.
204
161
  */
@@ -286,11 +243,9 @@ export function buildConversationDetailResponse(
286
243
  attentionState: attentionStates.get(conversation.id),
287
244
  displayMeta: displayMeta.get(conversation.id),
288
245
  parentCache,
289
- // Cold (evicted / never-loaded) rows aren't in the in-memory
290
- // store, so `findConversation` returns `undefined` and they
291
- // report `isProcessing: false` — by definition they aren't
292
- // mid-turn since the agent loop only runs on resident convs.
293
- isProcessing: findConversation(conversation.id)?.isProcessing() ?? false,
246
+ // Checks in-memory flag first (hot path), falls back to the
247
+ // persisted `processing_started_at` column for cold conversations.
248
+ isProcessing: isConversationProcessing(conversation.id),
294
249
  }),
295
250
  };
296
251
  }
@@ -59,9 +59,9 @@ export type ToolGrantRequestResult =
59
59
  * Returns a result indicating whether a new request was created, an existing
60
60
  * one was deduped, or the escalation failed (no binding, missing identity).
61
61
  */
62
- export function createOrReuseToolGrantRequest(
62
+ export async function createOrReuseToolGrantRequest(
63
63
  params: ToolGrantRequestParams,
64
- ): ToolGrantRequestResult {
64
+ ): Promise<ToolGrantRequestResult> {
65
65
  const {
66
66
  assistantId,
67
67
  sourceChannel,
@@ -78,7 +78,7 @@ export function createOrReuseToolGrantRequest(
78
78
  return { failed: true, reason: "missing_identity" };
79
79
  }
80
80
 
81
- const binding = getGuardianBinding(assistantId, sourceChannel);
81
+ const binding = await getGuardianBinding(assistantId, sourceChannel);
82
82
  if (!binding) {
83
83
  log.debug(
84
84
  { sourceChannel, assistantId },
@@ -4,10 +4,9 @@
4
4
  *
5
5
  * The gateway resolves a per-actor verdict from its ACL DB and stamps it onto
6
6
  * inbound `sourceMetadata`. These pure mappers turn that verdict into the same
7
- * {@link TrustContext} / {@link ResolvedMember} the local resolver would have
8
- * produced — ACL + identity only. INFO fields (notes, userFile, contactType,
9
- * interactionCount) are never carried on the wire; the consumer re-joins them
10
- * locally by contactId.
7
+ * {@link TrustContext} the local resolver would have produced — ACL + identity
8
+ * only. INFO fields (notes, userFile, contactType, interactionCount) are never
9
+ * carried on the wire; the consumer re-joins them locally by contactId.
11
10
  */
12
11
 
13
12
  import type { TrustVerdict } from "@vellumai/gateway-client";
@@ -18,12 +17,12 @@ import type {
18
17
  ChannelPolicy,
19
18
  ChannelStatus,
20
19
  ContactChannel,
20
+ ContactRole,
21
21
  ContactWithChannels,
22
22
  } from "../contacts/types.js";
23
23
  import type { TrustContext } from "../daemon/trust-context.js";
24
24
  import type { ActorTrustContext } from "./actor-trust-resolver.js";
25
25
  import { toTrustContext } from "./actor-trust-resolver.js";
26
- import type { ResolvedMember } from "./routes/inbound-stages/acl-enforcement.js";
27
26
 
28
27
  export interface TrustVerdictTransport {
29
28
  sourceChannel: ChannelId;
@@ -33,16 +32,17 @@ export interface TrustVerdictTransport {
33
32
  }
34
33
 
35
34
  /**
36
- * Build a {@link TrustContext} from a gateway verdict + transport identity.
35
+ * Reassemble an {@link ActorTrustContext} from a gateway verdict + transport
36
+ * identity (mirroring `resolveActorTrust`), without any local DB/IPC reads.
37
37
  *
38
- * Reassembles an {@link ActorTrustContext} (mirroring `resolveActorTrust`) and
39
- * routes it through {@link toTrustContext}, so the output is byte-identical to
40
- * the local resolution path.
38
+ * Pure: the voice path consumes this directly for routing on
39
+ * `actorTrust.trustClass`; {@link trustContextFromVerdict} routes it through
40
+ * {@link toTrustContext}.
41
41
  */
42
- export function trustContextFromVerdict(
42
+ export function actorTrustContextFromVerdict(
43
43
  verdict: TrustVerdict,
44
44
  input: TrustVerdictTransport,
45
- ): TrustContext {
45
+ ): ActorTrustContext {
46
46
  const canonicalSenderId = verdict.canonicalSenderId;
47
47
  const memberDisplayName = verdict.memberDisplayName;
48
48
  const senderDisplayName = input.actorDisplayName;
@@ -51,7 +51,7 @@ export function trustContextFromVerdict(
51
51
  ? `@${username}`
52
52
  : (canonicalSenderId ?? undefined);
53
53
 
54
- const actorTrustContext: ActorTrustContext = {
54
+ return {
55
55
  canonicalSenderId,
56
56
  guardianBindingMatch: verdict.guardianExternalUserId
57
57
  ? {
@@ -60,7 +60,12 @@ export function trustContextFromVerdict(
60
60
  }
61
61
  : null,
62
62
  guardianPrincipalId: verdict.guardianPrincipalId,
63
- memberRecord: null,
63
+ // Populate from the verdict so the voice path's ACL gates (which read
64
+ // actorTrust.memberRecord.channel status/policy) enforce blocked/revoked/
65
+ // deny/escalate. Null for memberless verdicts. Text path is unaffected:
66
+ // toTrustContext derives the same member fields trustContextFromVerdict
67
+ // already stamps.
68
+ memberRecord: memberRecordFromVerdict(verdict),
64
69
  trustClass: verdict.trustClass,
65
70
  actorMetadata: {
66
71
  identifier,
@@ -72,25 +77,57 @@ export function trustContextFromVerdict(
72
77
  trustStatus: verdict.trustClass,
73
78
  },
74
79
  };
80
+ }
75
81
 
82
+ /**
83
+ * Build a {@link TrustContext} from a gateway verdict + transport identity.
84
+ *
85
+ * Reassembles an {@link ActorTrustContext} (mirroring `resolveActorTrust`) and
86
+ * routes it through {@link toTrustContext}, so the output is byte-identical to
87
+ * the local resolution path.
88
+ */
89
+ export function trustContextFromVerdict(
90
+ verdict: TrustVerdict,
91
+ input: TrustVerdictTransport,
92
+ ): TrustContext {
76
93
  const context = toTrustContext(
77
- actorTrustContext,
94
+ actorTrustContextFromVerdict(verdict, input),
78
95
  input.conversationExternalId,
79
96
  );
80
97
 
81
98
  // Stamp the verdict's ACL member fields onto the context so downstream turn
82
99
  // assembly reads member status/policy from the verdict rather than a local
83
100
  // re-resolution. The contact ID anchors the local info-only join.
84
- const member = resolvedMemberFromVerdict(verdict);
101
+ const member = verdictMemberFromVerdict(verdict);
85
102
  if (member) {
86
- context.requesterContactId = member.contact.id;
87
- context.memberStatus = channelStatusToMemberStatus(member.channel.status);
88
- context.memberPolicy = member.channel.policy;
103
+ context.requesterContactId = member.contactId;
104
+ context.memberStatus = channelStatusToMemberStatus(member.status);
105
+ context.memberPolicy = member.policy;
89
106
  }
90
107
 
91
108
  return context;
92
109
  }
93
110
 
111
+ /**
112
+ * True when the verdict carries a member identity (contactId or channelId),
113
+ * regardless of whether that member resolves to a usable {@link VerdictMember}.
114
+ */
115
+ export function verdictHasMemberIdentity(verdict: TrustVerdict): boolean {
116
+ return !!(verdict.contactId || verdict.channelId);
117
+ }
118
+
119
+ /**
120
+ * True when the verdict claims a member identity but that member can't be
121
+ * resolved (partial/mixed-version verdict). Such a verdict is unusable —
122
+ * callers fall back to local resolution.
123
+ */
124
+ export function verdictMemberUnresolvable(verdict: TrustVerdict): boolean {
125
+ return (
126
+ verdictHasMemberIdentity(verdict) &&
127
+ verdictMemberFromVerdict(verdict) === null
128
+ );
129
+ }
130
+
94
131
  // Allowed ACL enum values, kept in sync with the ContactChannel union types.
95
132
  const CHANNEL_STATUS_VALUES: readonly ChannelStatus[] = [
96
133
  "active",
@@ -114,38 +151,64 @@ function isChannelPolicy(value: string): value is ChannelPolicy {
114
151
  }
115
152
 
116
153
  /**
117
- * Build a synthetic {@link ResolvedMember} from a gateway verdict.
154
+ * The ACL fields a gateway verdict carries for a resolved member, decoupled
155
+ * from the schema-derived {@link ContactChannel}.
156
+ */
157
+ export interface VerdictMember {
158
+ contactId: string;
159
+ channelId: string;
160
+ status: ChannelStatus;
161
+ policy: ChannelPolicy;
162
+ verifiedAt: number | null;
163
+ displayName: string | null;
164
+ }
165
+
166
+ /**
167
+ * Extract the narrow {@link VerdictMember} ACL view from a gateway verdict.
118
168
  *
119
- * ACL + identity only; info fields are placeholders, re-joined locally by
120
- * contactId. Returns null for memberless verdicts.
169
+ * Guards on contactId/channelId presence + known status/policy enums, failing
170
+ * closed to null otherwise.
121
171
  */
122
- export function resolvedMemberFromVerdict(
172
+ export function verdictMemberFromVerdict(
123
173
  verdict: TrustVerdict,
124
- ): ResolvedMember | null {
174
+ ): VerdictMember | null {
125
175
  if (!verdict.contactId || !verdict.channelId) return null;
126
- // Member verdict requires valid known status+policy enums, else null
127
- // (fail-closed): a partial/mixed-version verdict (absent OR
128
- // present-but-unknown ACL value) must not synthesize an active/allow channel
129
- // that would skip ingress ACL gates.
130
176
  if (!verdict.status || !verdict.policy) return null;
131
177
  if (!isChannelStatus(verdict.status) || !isChannelPolicy(verdict.policy)) {
132
178
  return null;
133
179
  }
134
180
 
135
- const channel: ContactChannel = {
136
- id: verdict.channelId,
181
+ return {
137
182
  contactId: verdict.contactId,
183
+ channelId: verdict.channelId,
184
+ status: verdict.status,
185
+ policy: verdict.policy,
186
+ verifiedAt: verdict.verifiedAt ?? null,
187
+ displayName: verdict.memberDisplayName ?? null,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Build the voice-path {@link ActorTrustContext.memberRecord} from a gateway
193
+ * verdict's narrow ACL view.
194
+ *
195
+ * ACL + identity only; info fields are placeholders, re-joined locally by
196
+ * contactId. Returns null for memberless/unresolvable verdicts.
197
+ */
198
+ function memberRecordFromVerdict(
199
+ verdict: TrustVerdict,
200
+ ): ActorTrustContext["memberRecord"] {
201
+ const member = verdictMemberFromVerdict(verdict);
202
+ if (!member) return null;
203
+
204
+ const channel: ContactChannel = {
205
+ id: member.channelId,
206
+ contactId: member.contactId,
138
207
  type: verdict.type ?? "",
139
208
  address: verdict.address ?? "",
140
209
  isPrimary: false,
141
210
  externalChatId: verdict.externalChatId ?? null,
142
- status: verdict.status,
143
- policy: verdict.policy,
144
- verifiedAt: verdict.verifiedAt ?? null,
145
- verifiedVia: verdict.verifiedVia ?? null,
146
211
  inviteId: null,
147
- revokedReason: null,
148
- blockedReason: null,
149
212
  lastSeenAt: null,
150
213
  interactionCount: 0,
151
214
  lastInteraction: null,
@@ -153,20 +216,28 @@ export function resolvedMemberFromVerdict(
153
216
  createdAt: 0,
154
217
  };
155
218
 
219
+ const role: ContactRole =
220
+ verdict.trustClass === "guardian" ? "guardian" : "contact";
221
+
156
222
  const contact: ContactWithChannels = {
157
- id: verdict.contactId,
158
- displayName: verdict.memberDisplayName ?? "",
223
+ id: member.contactId,
224
+ displayName: member.displayName ?? "",
159
225
  notes: null,
226
+ role,
160
227
  lastInteraction: null,
161
228
  interactionCount: 0,
162
229
  createdAt: 0,
163
230
  updatedAt: 0,
164
- role: verdict.trustClass === "guardian" ? "guardian" : "contact",
165
231
  contactType: "human",
166
- principalId: verdict.guardianPrincipalId ?? null,
167
232
  userFile: null,
168
233
  channels: [channel],
169
234
  };
170
235
 
171
- return { contact, channel };
236
+ return {
237
+ contact,
238
+ channel,
239
+ status: member.status,
240
+ policy: member.policy,
241
+ role,
242
+ };
172
243
  }
@@ -21,7 +21,7 @@ import {
21
21
  countRecentSendsToDestination,
22
22
  createOutboundSession,
23
23
  findActiveSession,
24
- getGuardianBinding,
24
+ isGuardianBoundForChannel,
25
25
  updateSessionDelivery,
26
26
  updateSessionStatus,
27
27
  } from "./channel-verification-service.js";
@@ -224,7 +224,7 @@ export async function startOutbound(
224
224
  originConversationId,
225
225
  );
226
226
  } else if (channel === "phone") {
227
- return startOutboundVoice(
227
+ return await startOutboundVoice(
228
228
  params.destination,
229
229
  assistantId,
230
230
  channel,
@@ -232,7 +232,7 @@ export async function startOutbound(
232
232
  originConversationId,
233
233
  );
234
234
  } else if (channel === "slack") {
235
- return startOutboundSlack(
235
+ return await startOutboundSlack(
236
236
  params.destination,
237
237
  assistantId,
238
238
  channel,
@@ -240,7 +240,7 @@ export async function startOutbound(
240
240
  originConversationId,
241
241
  );
242
242
  } else if (channel === "email") {
243
- return startOutboundEmail(
243
+ return await startOutboundEmail(
244
244
  params.destination,
245
245
  assistantId,
246
246
  channel,
@@ -274,8 +274,8 @@ async function startOutboundTelegram(
274
274
  };
275
275
  }
276
276
 
277
- const existingBinding = getGuardianBinding(assistantId, channel);
278
- if (existingBinding && !rebind) {
277
+ const alreadyBound = await isGuardianBoundForChannel(channel);
278
+ if (alreadyBound && !rebind) {
279
279
  return {
280
280
  success: false,
281
281
  error: "already_bound",
@@ -394,13 +394,13 @@ async function startOutboundTelegram(
394
394
  };
395
395
  }
396
396
 
397
- function startOutboundVoice(
397
+ async function startOutboundVoice(
398
398
  rawDestination: string | undefined,
399
399
  assistantId: string,
400
400
  channel: ChannelId,
401
401
  rebind?: boolean,
402
402
  originConversationId?: string,
403
- ): OutboundActionResult {
403
+ ): Promise<OutboundActionResult> {
404
404
  if (!rawDestination) {
405
405
  return {
406
406
  success: false,
@@ -422,8 +422,8 @@ function startOutboundVoice(
422
422
  };
423
423
  }
424
424
 
425
- const existingBinding = getGuardianBinding(assistantId, channel);
426
- if (existingBinding && !rebind) {
425
+ const alreadyBound = await isGuardianBoundForChannel(channel);
426
+ if (alreadyBound && !rebind) {
427
427
  return {
428
428
  success: false,
429
429
  error: "already_bound",
@@ -591,13 +591,13 @@ export function deliverVerificationEmail(
591
591
  })();
592
592
  }
593
593
 
594
- function startOutboundSlack(
594
+ async function startOutboundSlack(
595
595
  destination: string | undefined,
596
596
  assistantId: string,
597
597
  channel: ChannelId,
598
598
  rebind?: boolean,
599
599
  originConversationId?: string,
600
- ): OutboundActionResult {
600
+ ): Promise<OutboundActionResult> {
601
601
  if (!destination) {
602
602
  return {
603
603
  success: false,
@@ -607,8 +607,8 @@ function startOutboundSlack(
607
607
  };
608
608
  }
609
609
 
610
- const existingBinding = getGuardianBinding(assistantId, channel);
611
- if (existingBinding && !rebind) {
610
+ const alreadyBound = await isGuardianBoundForChannel(channel);
611
+ if (alreadyBound && !rebind) {
612
612
  return {
613
613
  success: false,
614
614
  error: "already_bound",
@@ -669,13 +669,13 @@ function startOutboundSlack(
669
669
  };
670
670
  }
671
671
 
672
- function startOutboundEmail(
672
+ async function startOutboundEmail(
673
673
  destination: string | undefined,
674
674
  assistantId: string,
675
675
  channel: ChannelId,
676
676
  rebind?: boolean,
677
677
  originConversationId?: string,
678
- ): OutboundActionResult {
678
+ ): Promise<OutboundActionResult> {
679
679
  if (!destination) {
680
680
  return {
681
681
  success: false,
@@ -688,8 +688,8 @@ function startOutboundEmail(
688
688
 
689
689
  const normalizedEmail = destination.trim().toLowerCase();
690
690
 
691
- const existingBinding = getGuardianBinding(assistantId, channel);
692
- if (existingBinding && !rebind) {
691
+ const alreadyBound = await isGuardianBoundForChannel(channel);
692
+ if (alreadyBound && !rebind) {
693
693
  return {
694
694
  success: false,
695
695
  error: "already_bound",
@@ -15,6 +15,7 @@ import { readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
 
17
17
  import { getOrCreateConversation } from "../daemon/conversation-store.js";
18
+ import { supersedePendingInteractionsOnEnqueue } from "../daemon/handlers/conversations.js";
18
19
  import type { UserMessageAttachment } from "../daemon/message-types/shared.js";
19
20
  import {
20
21
  processMessageInBackground,
@@ -131,6 +132,21 @@ async function dispatchUserMessage(params: {
131
132
  assistantMessageInterface: resolvedInterface,
132
133
  },
133
134
  });
135
+ if (!result.rejected) {
136
+ // Mirror the HTTP send path: a follow-up enqueued while the turn is busy
137
+ // auto-denies pending confirmations and supersedes a parked ask_question
138
+ // so it isn't stranded behind the prompt until the response backstop.
139
+ // Best-effort — the message is already queued, so a cleanup failure must
140
+ // not surface as an error that makes the CLI retry and enqueue a duplicate.
141
+ try {
142
+ supersedePendingInteractionsOnEnqueue(conversationId, requestId);
143
+ } catch (err) {
144
+ log.warn(
145
+ { err, conversationId },
146
+ "Post-enqueue supersession failed — queued message unaffected",
147
+ );
148
+ }
149
+ }
134
150
  return { accepted: !result.rejected };
135
151
  }
136
152
 
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { advisorRequestText, buildAdvisorSystem } from "../consult-prompt.js";
4
+
5
+ describe("buildAdvisorSystem", () => {
6
+ test("includes the senior-advisor framing", () => {
7
+ const prompt = buildAdvisorSystem(null);
8
+ expect(prompt).toContain("senior advisor");
9
+ });
10
+
11
+ test("embeds the parent prompt inside <agent_system_prompt> when provided", () => {
12
+ const prompt = buildAdvisorSystem("You are a coding agent.");
13
+ expect(prompt).toContain(
14
+ "<agent_system_prompt>\nYou are a coding agent.\n</agent_system_prompt>",
15
+ );
16
+ });
17
+
18
+ test("omits the <agent_system_prompt> block when no parent prompt is given", () => {
19
+ const prompt = buildAdvisorSystem(null);
20
+ expect(prompt).not.toContain("<agent_system_prompt>");
21
+ });
22
+ });
23
+
24
+ describe("advisorRequestText", () => {
25
+ test("is non-empty and asks for focused strategic guidance", () => {
26
+ const text = advisorRequestText();
27
+ expect(text.length).toBeGreaterThan(0);
28
+ expect(text).toContain("focused strategic guidance");
29
+ });
30
+
31
+ test("imposes no length cap", () => {
32
+ // The request must not constrain how much the advisor writes.
33
+ expect(advisorRequestText()).not.toContain("words");
34
+ });
35
+ });