@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f

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 (866) hide show
  1. package/ARCHITECTURE.md +94 -91
  2. package/Dockerfile +5 -16
  3. package/README.md +7 -10
  4. package/bun.lock +0 -4
  5. package/docs/architecture/security.md +20 -20
  6. package/docs/stt-provider-onboarding.md +4 -5
  7. package/docs/trusted-contact-access.md +7 -7
  8. package/eslint-rules/cli-no-daemon-internals.js +4 -0
  9. package/eslint.config.mjs +7 -1
  10. package/knip.json +0 -1
  11. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
  13. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
  14. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
  15. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
  17. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
  19. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
  21. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
  22. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
  23. package/openapi.yaml +1073 -225
  24. package/package.json +1 -4
  25. package/scripts/test.sh +35 -13
  26. package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
  27. package/src/__tests__/acp-session.test.ts +4 -1
  28. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
  29. package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
  30. package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
  31. package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
  32. package/src/__tests__/app-source-watcher.test.ts +11 -0
  33. package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
  34. package/src/__tests__/assistant-attachments.test.ts +62 -4
  35. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
  36. package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
  37. package/src/__tests__/attachments-store.test.ts +3 -0
  38. package/src/__tests__/background-shell-bash.test.ts +34 -0
  39. package/src/__tests__/background-shell-host-bash.test.ts +12 -0
  40. package/src/__tests__/background-tool-registry.test.ts +97 -0
  41. package/src/__tests__/background-tool-routes.test.ts +72 -1
  42. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  43. package/src/__tests__/btw-routes.test.ts +57 -3
  44. package/src/__tests__/call-controller.test.ts +474 -84
  45. package/src/__tests__/call-domain.test.ts +48 -1
  46. package/src/__tests__/call-pointer-messages.test.ts +57 -19
  47. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
  48. package/src/__tests__/call-routes-http.test.ts +15 -14
  49. package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
  50. package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
  51. package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
  52. package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
  53. package/src/__tests__/call-setup-flow.test.ts +311 -0
  54. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
  55. package/src/__tests__/channel-guardian.test.ts +4 -7
  56. package/src/__tests__/channel-readiness-service.test.ts +0 -6
  57. package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
  58. package/src/__tests__/channel-retry-sweep.test.ts +76 -64
  59. package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
  60. package/src/__tests__/clear-all-lexical.test.ts +90 -0
  61. package/src/__tests__/compactor-media-strip.test.ts +177 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +5 -5
  63. package/src/__tests__/config-schema.test.ts +17 -0
  64. package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
  65. package/src/__tests__/contacts-relay-reads.test.ts +188 -23
  66. package/src/__tests__/contacts-tools.test.ts +42 -1
  67. package/src/__tests__/contacts-write.test.ts +181 -0
  68. package/src/__tests__/context-search-conversations-source.test.ts +220 -81
  69. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  71. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  72. package/src/__tests__/conversation-agent-loop.test.ts +39 -0
  73. package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
  74. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
  75. package/src/__tests__/conversation-clear-safety.test.ts +0 -1
  76. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
  77. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
  78. package/src/__tests__/conversation-disk-view.test.ts +1 -0
  79. package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
  80. package/src/__tests__/conversation-error.test.ts +0 -18
  81. package/src/__tests__/conversation-fork-crud.test.ts +67 -0
  82. package/src/__tests__/conversation-history-web-search.test.ts +7 -137
  83. package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
  84. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
  85. package/src/__tests__/conversation-process-callsite.test.ts +37 -0
  86. package/src/__tests__/conversation-queue.test.ts +149 -0
  87. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
  88. package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
  89. package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
  90. package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
  91. package/src/__tests__/conversation-skill-tools.test.ts +73 -0
  92. package/src/__tests__/conversation-wipe.test.ts +20 -5
  93. package/src/__tests__/credential-prompt-route.test.ts +69 -0
  94. package/src/__tests__/credential-security-invariants.test.ts +1 -3
  95. package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
  96. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
  97. package/src/__tests__/edit-propagation.test.ts +56 -2
  98. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
  99. package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
  100. package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
  101. package/src/__tests__/guardian-question-mode.test.ts +57 -0
  102. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  103. package/src/__tests__/guardian-wait-controller.test.ts +619 -0
  104. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
  105. package/src/__tests__/heic-fixture.ts +55 -0
  106. package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
  107. package/src/__tests__/http-conversation-lineage.test.ts +6 -1
  108. package/src/__tests__/image-conversion.test.ts +170 -0
  109. package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
  110. package/src/__tests__/introduction-card-resolver.test.ts +354 -0
  111. package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
  112. package/src/__tests__/invite-routes-http.test.ts +246 -420
  113. package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
  114. package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
  115. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
  116. package/src/__tests__/list-messages-attachments.test.ts +159 -0
  117. package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
  118. package/src/__tests__/list-messages-queued.test.ts +25 -0
  119. package/src/__tests__/managed-store.test.ts +129 -1
  120. package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
  121. package/src/__tests__/media-stream-output.test.ts +350 -19
  122. package/src/__tests__/media-stream-server-integration.test.ts +943 -169
  123. package/src/__tests__/media-stream-stt-session.test.ts +498 -1
  124. package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
  125. package/src/__tests__/model-intents.test.ts +1 -1
  126. package/src/__tests__/mtime-cache.test.ts +19 -0
  127. package/src/__tests__/non-member-access-request.test.ts +349 -3
  128. package/src/__tests__/notification-decision-fallback.test.ts +104 -8
  129. package/src/__tests__/notification-decision-strategy.test.ts +57 -8
  130. package/src/__tests__/onboarding-template-contract.test.ts +27 -43
  131. package/src/__tests__/openai-provider.test.ts +33 -0
  132. package/src/__tests__/openai-responses-provider.test.ts +40 -0
  133. package/src/__tests__/persistence-layering-guard.test.ts +29 -20
  134. package/src/__tests__/persona-resolver.test.ts +56 -2
  135. package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
  136. package/src/__tests__/phone.test.ts +1 -15
  137. package/src/__tests__/plugin-bootstrap.test.ts +25 -196
  138. package/src/__tests__/plugin-disabled-state.test.ts +33 -0
  139. package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
  140. package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
  141. package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
  142. package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
  143. package/src/__tests__/plugin-pipeline.test.ts +63 -1
  144. package/src/__tests__/plugin-route-contribution.test.ts +62 -115
  145. package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
  146. package/src/__tests__/plugin-types.test.ts +0 -1
  147. package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
  148. package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
  149. package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
  150. package/src/__tests__/public-ingress-urls.test.ts +0 -48
  151. package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
  152. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
  153. package/src/__tests__/resolve-trust-class.test.ts +28 -10
  154. package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
  155. package/src/__tests__/schedule-retry.test.ts +38 -14
  156. package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
  157. package/src/__tests__/schedule-routes.test.ts +14 -6
  158. package/src/__tests__/schedule-store.test.ts +3 -1
  159. package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
  160. package/src/__tests__/scheduler-recurrence.test.ts +57 -107
  161. package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
  162. package/src/__tests__/scheduler-wake.test.ts +13 -23
  163. package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
  164. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  165. package/src/__tests__/shell-observability.test.ts +1 -0
  166. package/src/__tests__/skill-boundary-guard.test.ts +3 -4
  167. package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
  168. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
  169. package/src/__tests__/skill-script-runner-host.test.ts +53 -0
  170. package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
  171. package/src/__tests__/skill-tool-factory.test.ts +73 -1
  172. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
  173. package/src/__tests__/slack-inbound-verification.test.ts +36 -0
  174. package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
  175. package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
  176. package/src/__tests__/stt-hints.test.ts +2 -256
  177. package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
  178. package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
  179. package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
  180. package/src/__tests__/subagent-persistence.test.ts +122 -0
  181. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  182. package/src/__tests__/subagent-terminal-message.test.ts +99 -0
  183. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
  184. package/src/__tests__/system-prompt.test.ts +17 -118
  185. package/src/__tests__/task-scheduler.test.ts +68 -89
  186. package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
  187. package/src/__tests__/telephony-tts-guard.test.ts +538 -0
  188. package/src/__tests__/title-generate-hook.test.ts +16 -0
  189. package/src/__tests__/tool-approval-handler.test.ts +87 -1
  190. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
  191. package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
  192. package/src/__tests__/trust-context-guards.test.ts +21 -10
  193. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
  194. package/src/__tests__/twilio-provider.test.ts +17 -17
  195. package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
  196. package/src/__tests__/twilio-routes.test.ts +257 -387
  197. package/src/__tests__/twilio-validation.test.ts +1 -1
  198. package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
  199. package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
  200. package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
  201. package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
  202. package/src/__tests__/voice-quality.test.ts +2 -2
  203. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
  204. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
  205. package/src/__tests__/voice-session-bridge.test.ts +1 -10
  206. package/src/__tests__/workspace-git-service.test.ts +1 -1
  207. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
  208. package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
  209. package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
  210. package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
  211. package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
  212. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
  213. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
  214. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
  215. package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
  216. package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
  217. package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
  218. package/src/acp/__tests__/session-manager.test.ts +6 -0
  219. package/src/acp/agent-process.test.ts +116 -0
  220. package/src/acp/agent-process.ts +71 -0
  221. package/src/acp/auto-install.ts +12 -8
  222. package/src/acp/failure-error.test.ts +109 -0
  223. package/src/acp/failure-error.ts +111 -0
  224. package/src/acp/prepare-agent-env.ts +0 -12
  225. package/src/acp/resolve-agent.test.ts +6 -45
  226. package/src/acp/resolve-agent.ts +2 -4
  227. package/src/acp/session-manager.test.ts +312 -0
  228. package/src/acp/session-manager.ts +102 -46
  229. package/src/agent/ax-tree-compaction.test.ts +4 -1
  230. package/src/agent/image-optimize.ts +17 -127
  231. package/src/agent/loop.ts +45 -192
  232. package/src/api/events/background-tool-completed.test.ts +51 -0
  233. package/src/api/events/background-tool-completed.ts +29 -0
  234. package/src/api/events/background-tool-started.test.ts +36 -0
  235. package/src/api/events/background-tool-started.ts +28 -0
  236. package/src/api/events/conversation-error.ts +0 -1
  237. package/src/api/events/open-panel.ts +33 -0
  238. package/src/api/events/subagent-status-changed.ts +4 -1
  239. package/src/api/index.ts +19 -0
  240. package/src/api/responses/conversation-message.ts +34 -0
  241. package/src/approvals/guardian-decision-primitive.ts +74 -24
  242. package/src/approvals/guardian-request-resolvers.ts +450 -122
  243. package/src/apps/app-store.ts +1 -0
  244. package/src/apps/shared-app-links-store.ts +1 -0
  245. package/src/avatar/identity-avatar.ts +3 -7
  246. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  247. package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
  248. package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
  249. package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
  250. package/src/calls/call-controller.ts +134 -48
  251. package/src/calls/call-domain.ts +18 -42
  252. package/src/calls/call-pointer-messages.ts +51 -67
  253. package/src/calls/call-setup-flow-types.ts +76 -0
  254. package/src/calls/call-setup-flow.ts +1743 -0
  255. package/src/calls/call-speech-output.ts +87 -23
  256. package/src/calls/call-store.ts +4 -0
  257. package/src/calls/call-transport.ts +22 -36
  258. package/src/calls/fish-audio-client.ts +3 -0
  259. package/src/calls/gateway-invite-reader.ts +98 -0
  260. package/src/calls/guardian-wait-controller.ts +451 -0
  261. package/src/calls/media-stream-audio-transcode.ts +82 -2
  262. package/src/calls/media-stream-output.ts +142 -36
  263. package/src/calls/media-stream-server.ts +297 -178
  264. package/src/calls/media-stream-stt-session.ts +393 -50
  265. package/src/calls/media-turn-detector.ts +4 -6
  266. package/src/calls/relay-access-wait.ts +3 -4
  267. package/src/calls/relay-setup-router.ts +22 -43
  268. package/src/calls/relay-verification.ts +17 -24
  269. package/src/calls/resolve-call-tts-provider.ts +96 -29
  270. package/src/calls/stt-hints.ts +24 -87
  271. package/src/calls/telephony-credential-preflight.ts +159 -0
  272. package/src/calls/telephony-tts-capability.ts +183 -0
  273. package/src/calls/tts-call-strategy.ts +18 -20
  274. package/src/calls/twilio-provider.ts +3 -2
  275. package/src/calls/twilio-routes.ts +47 -351
  276. package/src/calls/types.ts +5 -3
  277. package/src/calls/voice-ingress-preflight.ts +9 -0
  278. package/src/calls/voice-quality.ts +13 -18
  279. package/src/calls/voice-session-bridge.ts +25 -35
  280. package/src/channels/__tests__/types.test.ts +28 -0
  281. package/src/channels/channel-verification-sessions.ts +7 -2
  282. package/src/channels/config.ts +0 -42
  283. package/src/channels/slack-thread-store.ts +12 -0
  284. package/src/channels/types.ts +23 -0
  285. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
  286. package/src/cli/commands/__tests__/schedules.test.ts +83 -34
  287. package/src/cli/commands/__tests__/status.test.ts +42 -2
  288. package/src/cli/commands/contacts.ts +2 -2
  289. package/src/cli/commands/credentials.ts +17 -0
  290. package/src/cli/commands/monitoring.ts +191 -0
  291. package/src/cli/commands/schedules-worker.ts +175 -0
  292. package/src/cli/commands/schedules.ts +169 -43
  293. package/src/cli/commands/status.ts +12 -1
  294. package/src/cli/commands/tools.ts +5 -1
  295. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
  296. package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
  297. package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
  298. package/src/cli/lib/search-plugins.ts +6 -0
  299. package/src/cli/lib/toggle-plugin.ts +21 -2
  300. package/src/cli/program.ts +3 -1
  301. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
  302. package/src/config/acp-defaults.test.ts +0 -10
  303. package/src/config/acp-defaults.ts +0 -6
  304. package/src/config/bundled-skills/acp/SKILL.md +13 -45
  305. package/src/config/bundled-skills/acp/TOOLS.json +3 -3
  306. package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
  307. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
  308. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
  309. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
  310. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  311. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
  312. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
  313. package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
  314. package/src/config/call-site-defaults.ts +0 -2
  315. package/src/config/feature-flag-registry.json +16 -8
  316. package/src/config/loader.ts +11 -0
  317. package/src/config/schema.ts +9 -1
  318. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
  319. package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
  320. package/src/config/schemas/call-site-catalog.ts +0 -12
  321. package/src/config/schemas/calls.ts +8 -0
  322. package/src/config/schemas/elevenlabs.ts +1 -1
  323. package/src/config/schemas/llm.ts +0 -2
  324. package/src/config/schemas/memory-v2.ts +1 -1
  325. package/src/config/schemas/memory-v3.ts +82 -0
  326. package/src/config/schemas/monitoring.ts +61 -0
  327. package/src/config/schemas/schedules.ts +35 -0
  328. package/src/config/seed-inference-profiles.ts +2 -2
  329. package/src/config/skills-plugin-filter.test.ts +96 -0
  330. package/src/config/skills.ts +26 -0
  331. package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
  332. package/src/contacts/canonical-guardian-store.ts +5 -0
  333. package/src/contacts/contact-store.ts +81 -35
  334. package/src/contacts/contacts-write.ts +50 -25
  335. package/src/contacts/member-write-relay.ts +115 -31
  336. package/src/contacts/types.ts +0 -8
  337. package/src/context/compactor.ts +29 -15
  338. package/src/context/outbound-sanitize.ts +202 -0
  339. package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
  340. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
  341. package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
  342. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
  343. package/src/daemon/abort-watchdog.ts +7 -0
  344. package/src/daemon/app-source-watcher.ts +9 -0
  345. package/src/daemon/assistant-attachments.ts +42 -0
  346. package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
  347. package/src/daemon/conversation-agent-loop.ts +61 -68
  348. package/src/daemon/conversation-error.ts +1 -15
  349. package/src/daemon/conversation-history.ts +32 -236
  350. package/src/daemon/conversation-initial-prompt.ts +77 -0
  351. package/src/daemon/conversation-lifecycle.ts +25 -1
  352. package/src/daemon/conversation-messaging.ts +2 -2
  353. package/src/daemon/conversation-plugin-scope.ts +38 -0
  354. package/src/daemon/conversation-process.ts +50 -14
  355. package/src/daemon/conversation-runtime-assembly.ts +36 -6
  356. package/src/daemon/conversation-skill-tools.ts +19 -3
  357. package/src/daemon/conversation-store.ts +3 -3
  358. package/src/daemon/conversation-surfaces.ts +171 -0
  359. package/src/daemon/conversation-tool-setup.test.ts +95 -0
  360. package/src/daemon/conversation-tool-setup.ts +116 -1
  361. package/src/daemon/conversation-turn-finalize.ts +211 -0
  362. package/src/daemon/conversation.ts +92 -1
  363. package/src/daemon/disk-pressure-policy.ts +2 -1
  364. package/src/daemon/event-loop-watchdog.ts +22 -6
  365. package/src/daemon/external-plugins-bootstrap.ts +47 -134
  366. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  367. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
  368. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
  369. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
  370. package/src/daemon/handlers/config-a2a.ts +7 -14
  371. package/src/daemon/handlers/config-channels.ts +2 -10
  372. package/src/daemon/handlers/conversation-history.ts +3 -1
  373. package/src/daemon/handlers/conversations.ts +0 -52
  374. package/src/daemon/handlers/shared.ts +4 -24
  375. package/src/daemon/handlers/skills.ts +4 -1
  376. package/src/daemon/host-cu-proxy.ts +1 -1
  377. package/src/daemon/lifecycle.ts +59 -472
  378. package/src/daemon/message-protocol.ts +3 -3
  379. package/src/daemon/message-provenance.ts +12 -14
  380. package/src/daemon/message-types/background-tools.ts +28 -0
  381. package/src/daemon/message-types/contacts.ts +2 -10
  382. package/src/daemon/message-types/conversations.ts +10 -0
  383. package/src/daemon/message-types/integrations.ts +2 -0
  384. package/src/daemon/message-types/surfaces.ts +1 -0
  385. package/src/daemon/message-types/sync.ts +1 -0
  386. package/src/daemon/pointer-conversation-trust.ts +23 -9
  387. package/src/daemon/pointer-turn-runner.ts +174 -0
  388. package/src/daemon/providers-setup.ts +16 -0
  389. package/src/daemon/shutdown-handlers.ts +139 -47
  390. package/src/daemon/skill-memory-refresh.ts +2 -2
  391. package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
  392. package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
  393. package/src/daemon/tool-setup-types.ts +8 -0
  394. package/src/daemon/trust-context.ts +21 -6
  395. package/src/daemon/turn-latency-tracker.test.ts +93 -0
  396. package/src/daemon/turn-latency-tracker.ts +38 -0
  397. package/src/daemon/wake-conversation-ops.ts +10 -8
  398. package/src/documents/document-comments-store.test.ts +7 -1
  399. package/src/documents/document-comments-store.ts +8 -0
  400. package/src/documents/document-store.ts +15 -0
  401. package/src/export/__tests__/transcript-formatter.test.ts +11 -0
  402. package/src/export/transcript-formatter.ts +49 -67
  403. package/src/home/__tests__/feed-writer.test.ts +247 -1
  404. package/src/home/conversation-starter-checkpoints.ts +1 -0
  405. package/src/home/feed-writer.ts +61 -0
  406. package/src/home/job-handlers/conversation-starters.ts +1 -0
  407. package/src/hooks/hook-loader.ts +12 -0
  408. package/src/hooks/registry.ts +32 -4
  409. package/src/inbound/public-ingress-urls.ts +0 -17
  410. package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
  411. package/src/ipc/assistant-server.ts +28 -7
  412. package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
  413. package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
  414. package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
  415. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
  416. package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
  417. package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
  418. package/src/ipc/routes/db-proxy.ts +5 -2
  419. package/src/ipc/routes/invite-ipc-routes.ts +53 -39
  420. package/src/jobs/worker.ts +9 -2
  421. package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
  422. package/src/live-voice/live-voice-archive.ts +5 -0
  423. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
  424. package/src/messaging/providers/channel-transport.ts +6 -0
  425. package/src/messaging/providers/index.ts +3 -0
  426. package/src/messaging/providers/slack/api.test.ts +49 -1
  427. package/src/messaging/providers/slack/api.ts +110 -0
  428. package/src/messaging/providers/slack/send.test.ts +85 -0
  429. package/src/messaging/providers/slack/send.ts +100 -28
  430. package/src/messaging/providers/slack/transport.ts +7 -13
  431. package/src/messaging/providers/whatsapp/send.ts +29 -9
  432. package/src/monitoring/__tests__/control.test.ts +235 -0
  433. package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
  434. package/src/monitoring/control.ts +105 -0
  435. package/src/monitoring/resource-sampler.ts +258 -0
  436. package/src/monitoring/sample-ring-buffer.ts +108 -0
  437. package/src/monitoring/worker.ts +94 -0
  438. package/src/notifications/README.md +1 -1
  439. package/src/notifications/access-request-copy.ts +94 -23
  440. package/src/notifications/adapters/slack.ts +89 -21
  441. package/src/notifications/approval-card-builder.ts +33 -18
  442. package/src/notifications/approval-card-data.ts +48 -15
  443. package/src/notifications/broadcaster.ts +25 -16
  444. package/src/notifications/decision-engine.ts +131 -32
  445. package/src/notifications/guardian-question-mode.ts +58 -10
  446. package/src/permissions/approval-policy.ts +8 -1
  447. package/src/permissions/checker.ts +2 -0
  448. package/src/permissions/gateway-threshold-reader.ts +7 -6
  449. package/src/permissions/ipc-risk-types.ts +1 -0
  450. package/src/permissions/secret-prompter.ts +11 -3
  451. package/src/permissions/types.ts +2 -2
  452. package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
  453. package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
  454. package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
  455. package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
  456. package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
  457. package/src/persistence/a2a-invite-store.ts +205 -0
  458. package/src/persistence/attachments-store.ts +94 -10
  459. package/src/persistence/checkpoints.ts +45 -0
  460. package/src/persistence/conversation-crud.ts +453 -260
  461. package/src/persistence/conversation-display-order-migration.ts +8 -2
  462. package/src/persistence/conversation-group-migration.test.ts +8 -0
  463. package/src/persistence/conversation-group-migration.ts +9 -0
  464. package/src/persistence/conversation-queries.ts +244 -125
  465. package/src/persistence/conversation-row-batch-delete.ts +9 -5
  466. package/src/persistence/conversation-search-lexical.ts +49 -0
  467. package/src/persistence/db-async-query.ts +42 -0
  468. package/src/persistence/db-connection.ts +13 -0
  469. package/src/persistence/delivery-channels.ts +0 -82
  470. package/src/persistence/delivery-crud.ts +27 -6
  471. package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
  472. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
  473. package/src/persistence/embeddings/embedding-identity.ts +17 -5
  474. package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
  475. package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
  476. package/src/persistence/embeddings/qdrant-client.ts +81 -5
  477. package/src/persistence/group-crud.ts +13 -1
  478. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
  479. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
  480. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
  481. package/src/persistence/job-handlers/cleanup.ts +57 -9
  482. package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
  483. package/src/persistence/job-handlers/message-lexical.ts +251 -0
  484. package/src/persistence/jobs-store.ts +102 -8
  485. package/src/persistence/jobs-worker.ts +68 -28
  486. package/src/persistence/llm-request-log-store.ts +35 -19
  487. package/src/persistence/llm-usage-store.ts +11 -0
  488. package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
  489. package/src/persistence/memory-lifecycle-hooks.ts +55 -1
  490. package/src/persistence/migrations/028-call-session-mode.ts +1 -1
  491. package/src/persistence/migrations/116-messages-fts.ts +8 -1
  492. package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
  493. package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
  494. package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
  495. package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
  496. package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
  497. package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
  498. package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
  499. package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
  500. package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
  501. package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
  502. package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
  503. package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
  504. package/src/persistence/raw-query.ts +50 -11
  505. package/src/persistence/schema/a2a.ts +14 -0
  506. package/src/persistence/schema/contacts.ts +0 -62
  507. package/src/persistence/schema/conversation-groups.ts +12 -0
  508. package/src/persistence/schema/conversations.ts +2 -0
  509. package/src/persistence/schema/documents.ts +65 -0
  510. package/src/persistence/schema/guardian.ts +4 -0
  511. package/src/persistence/schema/index.ts +4 -0
  512. package/src/persistence/schema/memory-injection.ts +62 -0
  513. package/src/persistence/schema/workflows.ts +55 -0
  514. package/src/persistence/slow-query-log.ts +437 -0
  515. package/src/persistence/slow-sync-log.ts +204 -0
  516. package/src/persistence/steps.ts +16 -0
  517. package/src/persistence/subagent-store.ts +125 -0
  518. package/src/persistence/worker-control.ts +45 -238
  519. package/src/plugin-api/event-hub-facade.ts +172 -0
  520. package/src/plugin-api/index.ts +9 -1
  521. package/src/plugin-api/types.ts +40 -9
  522. package/src/plugins/defaults/default-plugin-names.ts +60 -0
  523. package/src/plugins/defaults/index.ts +19 -29
  524. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
  525. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
  526. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
  527. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
  528. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
  529. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
  530. package/src/plugins/defaults/memory/config.ts +11 -0
  531. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
  532. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
  533. package/src/plugins/defaults/memory/embeddings.ts +77 -0
  534. package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
  535. package/src/plugins/defaults/memory/frontmatter.ts +50 -0
  536. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
  537. package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
  538. package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
  539. package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
  540. package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
  541. package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
  542. package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
  543. package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
  544. package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
  545. package/src/plugins/defaults/memory/graph/tools.ts +1 -1
  546. package/src/plugins/defaults/memory/hooks/init.ts +31 -5
  547. package/src/plugins/defaults/memory/indexer.ts +2 -3
  548. package/src/plugins/defaults/memory/injectors.ts +2 -2
  549. package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
  550. package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
  551. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
  552. package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
  553. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
  554. package/src/plugins/defaults/memory/job-handlers.ts +13 -12
  555. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
  556. package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
  557. package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
  558. package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
  559. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
  560. package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
  561. package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
  562. package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
  563. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
  564. package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
  565. package/src/plugins/defaults/memory/search/semantic.ts +2 -2
  566. package/src/plugins/defaults/memory/startup.ts +244 -0
  567. package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
  568. package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
  569. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
  570. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
  571. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
  572. package/src/plugins/defaults/memory/v2/activation.ts +2 -4
  573. package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
  574. package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
  575. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
  576. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
  577. package/src/plugins/defaults/memory/v2/migration.ts +2 -5
  578. package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
  579. package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
  580. package/src/plugins/defaults/memory/v2/router.ts +5 -5
  581. package/src/plugins/defaults/memory/v2/sim.ts +2 -4
  582. package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
  583. package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
  584. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
  585. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
  586. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
  587. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
  588. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
  589. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
  590. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
  591. package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
  592. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
  593. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
  594. package/src/plugins/defaults/memory/v3/card.ts +1 -4
  595. package/src/plugins/defaults/memory/v3/dense.ts +60 -25
  596. package/src/plugins/defaults/memory/v3/edge.ts +1 -1
  597. package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
  598. package/src/plugins/defaults/memory/v3/gate.ts +158 -0
  599. package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
  600. package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
  601. package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
  602. package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
  603. package/src/plugins/defaults/memory/v3/prune.ts +2 -2
  604. package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
  605. package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
  606. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
  607. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
  608. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
  609. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  610. package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
  611. package/src/plugins/injector-registry.ts +13 -2
  612. package/src/plugins/mtime-cache.ts +36 -47
  613. package/src/plugins/pipeline.ts +15 -1
  614. package/src/plugins/types.ts +14 -23
  615. package/src/prompts/__tests__/system-prompt.test.ts +170 -32
  616. package/src/prompts/persona-resolver.ts +13 -9
  617. package/src/prompts/system-prompt.ts +30 -9
  618. package/src/prompts/templates/BOOTSTRAP.md +16 -119
  619. package/src/prompts/templates/SOUL.md +43 -13
  620. package/src/prompts/templates/system-sections.ts +32 -27
  621. package/src/prompts/templates/users/default.md +19 -0
  622. package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
  623. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
  624. package/src/providers/model-intents.ts +2 -2
  625. package/src/providers/openai/chat-completions-provider.ts +14 -1
  626. package/src/providers/openai/responses-provider.ts +18 -0
  627. package/src/providers/placeholder-sentinels.ts +18 -8
  628. package/src/providers/retry.ts +1 -0
  629. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
  630. package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
  631. package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
  632. package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
  633. package/src/providers/speech-to-text/provider-catalog.ts +0 -89
  634. package/src/providers/speech-to-text/resolve.ts +55 -0
  635. package/src/providers/tool-progress-events.test.ts +50 -0
  636. package/src/providers/tool-progress-events.ts +99 -0
  637. package/src/providers/types.ts +1 -1
  638. package/src/providers/vellum-model-routing.test.ts +51 -0
  639. package/src/providers/vellum-model-routing.ts +84 -0
  640. package/src/runtime/AGENTS.md +5 -4
  641. package/src/runtime/__tests__/agent-wake.test.ts +158 -1
  642. package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
  643. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
  644. package/src/runtime/access-request-helper.ts +154 -3
  645. package/src/runtime/actor-trust-resolver.ts +7 -21
  646. package/src/runtime/agent-wake.ts +41 -1
  647. package/src/runtime/assistant-event.ts +86 -12
  648. package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
  649. package/src/runtime/channel-approval-types.ts +52 -2
  650. package/src/runtime/channel-approvals.ts +23 -8
  651. package/src/runtime/channel-invite-transport.ts +2 -2
  652. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  653. package/src/runtime/channel-readiness-service.ts +3 -5
  654. package/src/runtime/channel-retry-sweep.ts +18 -24
  655. package/src/runtime/channel-verification-service.ts +9 -11
  656. package/src/runtime/finalize-event-delivery.ts +30 -16
  657. package/src/runtime/guardian-action-service.ts +10 -11
  658. package/src/runtime/guardian-reply-router.ts +122 -21
  659. package/src/runtime/http-server.ts +44 -101
  660. package/src/runtime/introduction-policy.test.ts +214 -0
  661. package/src/runtime/introduction-policy.ts +284 -0
  662. package/src/runtime/invite-service.ts +104 -361
  663. package/src/runtime/local-actor-identity.test.ts +38 -0
  664. package/src/runtime/local-actor-identity.ts +14 -0
  665. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
  666. package/src/runtime/middleware/rate-limiter.ts +30 -0
  667. package/src/runtime/middleware/twilio-validation.ts +5 -7
  668. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
  669. package/src/runtime/migrations/origin-mode.ts +6 -6
  670. package/src/runtime/pre-first-message-gate.ts +1 -0
  671. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
  672. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
  673. package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
  674. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
  675. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
  676. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
  677. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
  678. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
  679. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
  680. package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
  681. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
  682. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
  683. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
  684. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  686. package/src/runtime/routes/acp-routes.ts +15 -3
  687. package/src/runtime/routes/app-management-routes.ts +4 -7
  688. package/src/runtime/routes/approval-routes.ts +4 -0
  689. package/src/runtime/routes/attachment-routes.ts +54 -3
  690. package/src/runtime/routes/avatar-routes.ts +0 -4
  691. package/src/runtime/routes/background-tool-routes.ts +34 -5
  692. package/src/runtime/routes/btw-routes.ts +32 -4
  693. package/src/runtime/routes/channel-route-shared.ts +18 -14
  694. package/src/runtime/routes/channel-verification-routes.ts +0 -3
  695. package/src/runtime/routes/contact-routes.ts +298 -117
  696. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  697. package/src/runtime/routes/conversation-management-routes.ts +74 -37
  698. package/src/runtime/routes/conversation-query-routes.ts +5 -2
  699. package/src/runtime/routes/conversation-routes.ts +222 -82
  700. package/src/runtime/routes/conversations-import-routes.ts +8 -1
  701. package/src/runtime/routes/credential-prompt-routes.ts +20 -5
  702. package/src/runtime/routes/debug-routes.ts +1 -0
  703. package/src/runtime/routes/documents-routes.ts +5 -2
  704. package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
  705. package/src/runtime/routes/global-search-routes.ts +2 -1
  706. package/src/runtime/routes/guardian-approval-interception.ts +51 -0
  707. package/src/runtime/routes/home-feed-routes.ts +60 -1
  708. package/src/runtime/routes/identity-routes.ts +4 -87
  709. package/src/runtime/routes/inbound-message-handler.ts +82 -41
  710. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
  711. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
  712. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
  713. package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
  714. package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
  715. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
  716. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
  717. package/src/runtime/routes/index.ts +6 -0
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
  719. package/src/runtime/routes/internal-twilio-routes.ts +0 -15
  720. package/src/runtime/routes/messages-lexical-routes.ts +75 -0
  721. package/src/runtime/routes/migration-routes.ts +69 -6
  722. package/src/runtime/routes/monitoring-routes.ts +237 -0
  723. package/src/runtime/routes/plugins-routes.ts +329 -8
  724. package/src/runtime/routes/schedule-routes.ts +49 -8
  725. package/src/runtime/routes/schedule-worker-routes.ts +210 -0
  726. package/src/runtime/routes/secret-routes.ts +1 -1
  727. package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
  728. package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
  729. package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
  730. package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
  731. package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
  732. package/src/runtime/services/analyze-conversation.ts +14 -4
  733. package/src/runtime/services/conversation-serializer.ts +5 -0
  734. package/src/runtime/slack-no-response.ts +47 -0
  735. package/src/runtime/slack-reply-session.test.ts +633 -0
  736. package/src/runtime/slack-reply-session.ts +382 -0
  737. package/src/runtime/slack-task-progress.ts +95 -0
  738. package/src/runtime/sync/resource-sync-events.ts +25 -0
  739. package/src/runtime/tool-grant-request-helper.ts +17 -1
  740. package/src/runtime/trust-class.test.ts +141 -0
  741. package/src/runtime/trust-class.ts +121 -0
  742. package/src/runtime/trust-verdict-consumer.ts +7 -6
  743. package/src/schedule/__tests__/run-script.test.ts +21 -0
  744. package/src/schedule/__tests__/worker-control.test.ts +241 -0
  745. package/src/schedule/run-script.ts +13 -6
  746. package/src/schedule/schedule-usage-store.ts +1 -0
  747. package/src/schedule/scheduler.ts +178 -127
  748. package/src/schedule/worker-control.ts +118 -0
  749. package/src/schedule/worker.ts +131 -0
  750. package/src/skills/__tests__/categories-cache.test.ts +38 -0
  751. package/src/skills/categories-cache.ts +40 -5
  752. package/src/skills/managed-store.ts +41 -6
  753. package/src/skills/validate-input.ts +47 -0
  754. package/src/stt/types.ts +5 -9
  755. package/src/subagent/manager.ts +267 -39
  756. package/src/subagent/types.ts +6 -2
  757. package/src/subagent/validate-allowlists.ts +65 -0
  758. package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
  759. package/src/telemetry/watchdog-direct-emit.ts +89 -0
  760. package/src/tools/acp/list-agents.test.ts +0 -6
  761. package/src/tools/background-tool-registry.ts +61 -0
  762. package/src/tools/document/document-comment-tool.test.ts +4 -0
  763. package/src/tools/host-terminal/host-shell.test.ts +397 -0
  764. package/src/tools/host-terminal/host-shell.ts +203 -14
  765. package/src/tools/skills/find-similar-skills.test.ts +152 -1
  766. package/src/tools/skills/find-similar-skills.ts +30 -4
  767. package/src/tools/skills/load.ts +36 -0
  768. package/src/tools/skills/scaffold-managed.ts +66 -34
  769. package/src/tools/skills/skill-script-runner.ts +17 -0
  770. package/src/tools/skills/skill-tool-factory.ts +5 -1
  771. package/src/tools/terminal/safe-env.ts +3 -0
  772. package/src/tools/terminal/shell.test.ts +177 -0
  773. package/src/tools/terminal/shell.ts +98 -4
  774. package/src/tools/tool-approval-handler.ts +106 -29
  775. package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
  776. package/src/tools/types.ts +29 -17
  777. package/src/tools/ui-surface/definitions.ts +3 -1
  778. package/src/tts/__tests__/provider-adapters.test.ts +37 -0
  779. package/src/tts/__tests__/provider-catalog.test.ts +21 -0
  780. package/src/tts/provider-catalog.ts +40 -9
  781. package/src/tts/providers/fish-audio-provider.ts +19 -10
  782. package/src/tts/types.ts +3 -4
  783. package/src/util/__tests__/process-tree.test.ts +10 -2
  784. package/src/util/__tests__/text-spacing.test.ts +53 -0
  785. package/src/util/cgroup-memory.ts +142 -0
  786. package/src/util/image-conversion.ts +281 -0
  787. package/src/util/phone.ts +1 -8
  788. package/src/util/platform.ts +33 -0
  789. package/src/util/process-tree.ts +10 -2
  790. package/src/util/text-spacing.ts +53 -0
  791. package/src/util/worker-process.ts +271 -0
  792. package/src/workflows/journal-store.ts +16 -1
  793. package/src/workflows/library.test.ts +35 -20
  794. package/src/workflows/library.ts +105 -66
  795. package/src/workspace/git-service.ts +1 -0
  796. package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
  797. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
  798. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
  799. package/src/workspace/migrations/118-seed-now-md.ts +64 -0
  800. package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
  801. package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
  802. package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
  803. package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
  804. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
  805. package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
  806. package/src/workspace/migrations/registry.ts +12 -0
  807. package/drizzle/0000_dizzy_maggott.sql +0 -301
  808. package/drizzle/meta/0000_snapshot.json +0 -1933
  809. package/drizzle/meta/_journal.json +0 -13
  810. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
  811. package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
  812. package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
  813. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
  814. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
  815. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
  816. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
  817. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
  818. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
  819. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
  820. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
  821. package/src/__tests__/invite-redemption-service.test.ts +0 -1180
  822. package/src/__tests__/invite-service-ipc.test.ts +0 -350
  823. package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
  824. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
  825. package/src/__tests__/relay-server.test.ts +0 -6153
  826. package/src/__tests__/telephony-stt-routing.test.ts +0 -329
  827. package/src/__tests__/voice-invite-redemption.test.ts +0 -702
  828. package/src/calls/relay-server.ts +0 -2463
  829. package/src/calls/telephony-stt-routing.ts +0 -145
  830. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
  831. package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
  832. package/src/daemon/daemon-skill-host.ts +0 -262
  833. package/src/daemon/message-types/meet.ts +0 -143
  834. package/src/daemon/shutdown-registry.ts +0 -40
  835. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  836. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  837. package/src/ipc/skill-ipc-types.ts +0 -54
  838. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  839. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  840. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  841. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  842. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  843. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  844. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  845. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  846. package/src/ipc/skill-routes/config.ts +0 -47
  847. package/src/ipc/skill-routes/events.ts +0 -147
  848. package/src/ipc/skill-routes/identity.ts +0 -21
  849. package/src/ipc/skill-routes/index.ts +0 -37
  850. package/src/ipc/skill-routes/log.ts +0 -40
  851. package/src/ipc/skill-routes/memory.ts +0 -92
  852. package/src/ipc/skill-routes/platform.ts +0 -39
  853. package/src/ipc/skill-routes/providers.ts +0 -162
  854. package/src/ipc/skill-routes/registries.ts +0 -338
  855. package/src/ipc/skill-server.ts +0 -831
  856. package/src/ipc/skill-socket-path.ts +0 -20
  857. package/src/jobs/register-job-handlers.ts +0 -74
  858. package/src/persistence/invite-store.ts +0 -481
  859. package/src/plugins/job-handler-registry.ts +0 -94
  860. package/src/providers/cache-control.ts +0 -26
  861. package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
  862. package/src/runtime/invite-redemption-service.ts +0 -828
  863. package/src/runtime/invite-redemption-templates.ts +0 -46
  864. package/src/runtime/runtime-mode.ts +0 -40
  865. package/src/runtime/slack-dm-text-delivery.ts +0 -177
  866. package/src/util/voice-code.ts +0 -31
@@ -17,6 +17,14 @@
17
17
  * associations the authored link graph does not record.
18
18
  * Each lane only ever ADDS candidates, so the pool is recall-safe by
19
19
  * construction.
20
+ * 1b. OPT-IN injection gate (`deps.gateConfig.enabled`, default off): once the
21
+ * CURRENT-message finder lanes have run, `checkV3Gate` scores the needle +
22
+ * dense hits against the tuned thresholds. A closed gate skips the
23
+ * selectPool LLM call for the turn — returning empty selections (or, when
24
+ * `bypassForCore` is set, selecting over the stable prefix only via the
25
+ * same selector dispatch as the normal path, honoring `selectorEnabled`) —
26
+ * so a low-signal turn pays no selector cost. The gate is pass-open: any
27
+ * throw inside it logs a warning and falls through to normal selection.
20
28
  * 2. Build the candidate pool in CACHE ORDER: the stable prefix —
21
29
  * `[...core (file order), ...hot (score order), ...fresh (recency order),
22
30
  * ...always-candidate (skills pinned every turn)]`, all computed at lane
@@ -47,12 +55,19 @@
47
55
  */
48
56
 
49
57
  import type { AssistantConfig } from "../../../../config/schema.js";
50
- import { denseLane } from "./dense.js";
58
+ import { recordWatchdogEvent } from "../../../../telemetry/watchdog-events-store.js";
59
+ import { getLogger } from "../../../../util/logger.js";
60
+ import { denseLaneScored } from "./dense.js";
51
61
  import type { EdgeGraph } from "./edge.js";
52
62
  import { edgeExpand } from "./edge.js";
53
63
  import type { EntityIndex } from "./entity-lane.js";
54
64
  import { entityLane } from "./entity-lane.js";
55
- import type { PoolCandidate, StableCandidate } from "./pool-select.js";
65
+ import { checkV3Gate, type V3GateConfig, type V3GateResult } from "./gate.js";
66
+ import type {
67
+ PoolCandidate,
68
+ SelectorPool,
69
+ StableCandidate,
70
+ } from "./pool-select.js";
56
71
  import { selectAllPoolCandidates, selectPool } from "./pool-select.js";
57
72
  import type { SectionNeedle } from "./section-needle.js";
58
73
  import type {
@@ -64,6 +79,32 @@ import type {
64
79
  Slug,
65
80
  } from "./types.js";
66
81
 
82
+ // Named to disambiguate from the unrelated `src/config/memory-v3-gate.ts`: this
83
+ // logger names the per-turn INJECTION gate wired in below.
84
+ const log = getLogger("memory-v3-injection-gate");
85
+
86
+ /** `check_name` of the `watchdog` telemetry event recorded once per
87
+ * injection-gate run. The platform's memory admin analytics count and filter
88
+ * on this exact string — keep it stable. */
89
+ export const MEMORY_V3_INJECTION_GATE_CHECK_NAME = "memory_v3_injection_gate";
90
+
91
+ /** Record one injection-gate run to usage telemetry. `detail` keys are the
92
+ * platform-side contract (snake_case, scores and reason codes only — never
93
+ * conversation content). Never throws: the gate is pass-open by design, and a
94
+ * telemetry failure must not cost the turn its memory either. */
95
+ function recordGateRun(detail: Record<string, unknown>): void {
96
+ try {
97
+ recordWatchdogEvent({
98
+ checkName: MEMORY_V3_INJECTION_GATE_CHECK_NAME,
99
+ value: 1,
100
+ detail,
101
+ });
102
+ } catch {
103
+ // recordWatchdogEvent already no-ops on opt-out and a missing telemetry
104
+ // DB; anything past that is not worth failing the turn over.
105
+ }
106
+ }
107
+
67
108
  /** Default number of BM25 needle articles to fold into the pool. */
68
109
  export const DEFAULT_NEEDLE_K = 12;
69
110
  /** Default number of dense-lane articles to fold into the pool. */
@@ -137,6 +178,10 @@ export interface OrchestrateDeps {
137
178
  /** Whether to run the selector LLM. False passes all pooled candidates
138
179
  * straight through as selections, preserving pool-order slug dedup. */
139
180
  selectorEnabled?: boolean;
181
+ /** Per-turn injection gate config (`memory.v3.gate` tuning + the flag-derived
182
+ * `enabled`, assembled in observeTurn). Omitted/disabled → the gate never
183
+ * runs and every turn proceeds to selectPool as before. */
184
+ gateConfig?: V3GateConfig;
140
185
  }
141
186
 
142
187
  /** A finder-lane candidate: the slug, the descriptor that justified it, and
@@ -209,6 +254,35 @@ export async function orchestrate(
209
254
  );
210
255
  const stablePrefix = new Set<Slug>([...coreHotFresh, ...always]);
211
256
 
257
+ // The stable prefix as FULL CARDS, in cache order. Pre-rendered at lane init
258
+ // (`prefixCards`) so the rendered prefix is byte-identical across turns. A
259
+ // missing card is a lane-init bug and throws (silently degrading would violate
260
+ // the byte-stable-prefix contract). Built lazily so the gate's bypass path can
261
+ // reuse the exact same construction as the normal step-2 pool assembly.
262
+ const buildStable = (): StableCandidate[] =>
263
+ [...core, ...hot, ...fresh, ...always].map((slug) => {
264
+ const card = deps.prefixCards.get(slug);
265
+ if (card === undefined) {
266
+ // Lane init renders a card for every core/hot slug; a hole here means
267
+ // the lanes and the card map are out of sync. Throw rather than render
268
+ // a degraded card — the caller (observeTurn) logs and skips the turn,
269
+ // which is better than silently breaking the byte-stable prefix.
270
+ throw new Error(
271
+ `memory-v3: no pre-rendered card for stable-prefix slug "${slug}"`,
272
+ );
273
+ }
274
+ return { slug, card };
275
+ });
276
+
277
+ // Run the selector over a pool, or pass its candidates straight through when
278
+ // the selector LLM is disabled (`selectorEnabled: false`, the new-user
279
+ // profile). Shared by the normal step-3 selection and the gate's
280
+ // bypass-to-stable path so the two cannot diverge.
281
+ const runSelection = async (pool: SelectorPool): Promise<SelectedPage[]> =>
282
+ deps.selectorEnabled === false
283
+ ? selectAllPoolCandidates(pool)
284
+ : selectPool(pool, turn, deps.selectorPrompt);
285
+
212
286
  // Step 1: needle (sync BM25) and the enabled dense lane (async embed +
213
287
  // Qdrant) run in parallel. Both return distinct articles each tagged with
214
288
  // their best-scoring section index/ordinal. The reply-query pass re-runs the
@@ -223,18 +297,27 @@ export async function orchestrate(
223
297
  replyK > 0 ? (turn.previousAssistantMessage ?? "").trim() : "";
224
298
  const denseEnabled = denseK > 0;
225
299
  const [needled, densed, replyNeedled, replyDensed] = await Promise.all([
226
- Promise.resolve(deps.needle.query(turn.currentMessage, needleK)),
300
+ Promise.resolve(deps.needle.queryScored(turn.currentMessage, needleK)),
227
301
  denseEnabled
228
- ? denseLane(deps.denseConfig, turn.currentMessage, denseK)
302
+ ? denseLaneScored(deps.denseConfig, turn.currentMessage, denseK)
229
303
  : Promise.resolve([]),
230
304
  Promise.resolve(
231
- replyQuery.length > 0 ? deps.needle.query(replyQuery, replyK) : [],
305
+ replyQuery.length > 0 ? deps.needle.queryScored(replyQuery, replyK) : [],
232
306
  ),
233
307
  replyQuery.length > 0 && denseEnabled
234
- ? denseLane(deps.denseConfig, replyQuery, replyK)
308
+ ? denseLaneScored(deps.denseConfig, replyQuery, replyK)
235
309
  : Promise.resolve([]),
236
310
  ]);
237
311
 
312
+ // Dense hits restricted to pages still in the live section index. A deleted
313
+ // page's points can linger in Qdrant; the candidate pool already drops those
314
+ // (see the dense finder loop), so the gate must score the same live set —
315
+ // stale low-scored hits for deleted pages must neither fake dense availability
316
+ // nor drag the gate closed while the live lanes have candidates.
317
+ const liveDensed = densed.filter((hit) =>
318
+ deps.sectionIndex.byArticle.has(hit.article),
319
+ );
320
+
238
321
  // `matchedSections` records the matched `Section` (when one is known) for
239
322
  // every finder hit — INCLUDING hits on stable-prefix slugs — for downstream
240
323
  // injection/spotlight. `finder` accumulates one entry per distinct
@@ -337,6 +420,101 @@ export async function orchestrate(
337
420
  }
338
421
  }
339
422
 
423
+ // Step 1b''': opt-in injection gate. With the CURRENT-message finder lanes in
424
+ // hand (needle + dense — NOT reply/entity/edge, which only add recall), decide
425
+ // whether retrieval is confident enough to spend the selectPool LLM call this
426
+ // turn. Default-off via `?.enabled`; pass-open on any throw (a gate bug must
427
+ // never drop a turn's memory). A closed gate either hard-skips selection
428
+ // (empty selections) or, when `bypassForCore` is set, runs selectPool over the
429
+ // stable prefix only — never the finder tail.
430
+ //
431
+ // The gate is dense-gated: it only runs when the live dense lane produced hits
432
+ // (`liveDensed.length > 0`). In healthy operation dense returns top-k hits for
433
+ // any query, so zero live dense hits means dense is unavailable — denseK=0 (the
434
+ // new-user/small-corpus profile), a degraded embedding backend, a Qdrant error
435
+ // (`denseLaneScored` swallows these to `[]`), or only stale deleted-page points
436
+ // returned — NOT low relevance. checkV3Gate reads only finder scores and cannot
437
+ // tell those apart, so without live dense signal we pass open rather than
438
+ // suppress all memory (not even the core/hot/fresh prefix) on every
439
+ // lexically-weak turn.
440
+ if (deps.gateConfig?.enabled) {
441
+ if (liveDensed.length === 0) {
442
+ // No live dense hits (denseK=0 / degraded backend / Qdrant error swallowed
443
+ // to [] / only stale deleted-page points returned). That means dense is
444
+ // unavailable, not low relevance, so pass open — but record it so rollout
445
+ // telemetry sees these turns instead of silently skipping the gate.
446
+ log.info(
447
+ {
448
+ conversationId: turn.conversationId,
449
+ turnNumber: turn.turnNumber,
450
+ reason: "dense_unavailable",
451
+ pass: true,
452
+ },
453
+ "memory-v3 injection gate: dense lane unavailable, passing open",
454
+ );
455
+ recordGateRun({ pass: true, reason: "dense_unavailable" });
456
+ } else {
457
+ let gate: V3GateResult | null = null;
458
+ try {
459
+ gate = checkV3Gate({
460
+ needleHits: needled,
461
+ denseHits: liveDensed,
462
+ config: deps.gateConfig,
463
+ });
464
+ } catch (err) {
465
+ log.warn(
466
+ {
467
+ err: err instanceof Error ? err.message : String(err),
468
+ conversationId: turn.conversationId,
469
+ },
470
+ "memory-v3 injection gate threw; passing open (proceeding with selection)",
471
+ );
472
+ recordGateRun({ pass: true, reason: "gate_error" });
473
+ }
474
+ if (gate) {
475
+ log.info(
476
+ {
477
+ conversationId: turn.conversationId,
478
+ turnNumber: turn.turnNumber,
479
+ gate,
480
+ },
481
+ "memory-v3 injection gate decision",
482
+ );
483
+ recordGateRun({
484
+ pass: gate.pass,
485
+ reason: gate.reason,
486
+ top_dense_score: gate.topDenseScore,
487
+ top_norm_sparse_score: gate.topNormSparseScore,
488
+ checked_articles: gate.checkedArticles,
489
+ });
490
+ if (!gate.pass) {
491
+ // A closed gate produces no finder lane and no matched sections; only
492
+ // the `selections` differ between bypass (stable prefix) and hard-skip.
493
+ const closed = (selections: SelectedPage[]): OrchestrateResult => ({
494
+ selections,
495
+ matchedSections: new Map(),
496
+ lanes: { core, hot, fresh, finder: [] },
497
+ });
498
+ if (deps.gateConfig.bypassForCore) {
499
+ // Select over the stable prefix only. `runSelection` mirrors the
500
+ // normal path: with the selector off it passes the stable candidates
501
+ // straight through rather than forcing the selectPool LLM call —
502
+ // which a selector-disabled assistant has no provider for, so it
503
+ // throws MemoryV3RetrievalUnavailableError and escapes the gate on a
504
+ // benign low-signal turn. Reaching here means the assistant is
505
+ // explicitly configured with `selectorEnabled: false` AND
506
+ // `denseK > 0` (the dense-gated gate only runs with dense hits; the
507
+ // new-user profile sets `denseK: 0`, so the gate never runs for it).
508
+ return closed(
509
+ await runSelection({ stable: buildStable(), finder: [] }),
510
+ );
511
+ }
512
+ return closed([]);
513
+ }
514
+ }
515
+ }
516
+ }
517
+
340
518
  // Step 1c: edge expansion over the top needle+dense article seeds. `alive`
341
519
  // skips slugs already in the pool — finder-surfaced AND stable-prefix slugs
342
520
  // (an edge hit carries no matched section, so re-surfacing a core/hot page
@@ -408,21 +586,7 @@ export async function orchestrate(
408
586
  // its own snippet line so its CURRENT relevance stays visible; `selectPool`
409
587
  // dedupes selections by slug. Finder candidates with no match text fall
410
588
  // back to the page's lead-section snippet.
411
- const stable: StableCandidate[] = [...core, ...hot, ...fresh, ...always].map(
412
- (slug) => {
413
- const card = deps.prefixCards.get(slug);
414
- if (card === undefined) {
415
- // Lane init renders a card for every core/hot slug; a hole here means
416
- // the lanes and the card map are out of sync. Throw rather than render
417
- // a degraded card — the caller (observeTurn) logs and skips the turn,
418
- // which is better than silently breaking the byte-stable prefix.
419
- throw new Error(
420
- `memory-v3: no pre-rendered card for stable-prefix slug "${slug}"`,
421
- );
422
- }
423
- return { slug, card };
424
- },
425
- );
589
+ const stable = buildStable();
426
590
  const finderTail: PoolCandidate[] = finder.map((c) => ({
427
591
  slug: c.slug,
428
592
  lane: c.lane,
@@ -437,10 +601,7 @@ export async function orchestrate(
437
601
  // contract. `selectorPrompt` is the (optionally overridden) instruction
438
602
  // scaffold; `undefined` falls through to the bundled default.
439
603
  const pool = { stable, finder: finderTail };
440
- const selections =
441
- deps.selectorEnabled === false
442
- ? selectAllPoolCandidates(pool)
443
- : await selectPool(pool, turn, deps.selectorPrompt);
604
+ const selections = await runSelection(pool);
444
605
 
445
606
  return {
446
607
  selections,
@@ -10,8 +10,8 @@
10
10
  * empty pool → [].
11
11
  *
12
12
  * `mock.module` is process-global and leaks into sibling files in a directory
13
- * run, so the provider-send-message stub DELEGATES to the real implementation
14
- * (keeping the real `extractToolUse`) unless this test is actively running
13
+ * run, so the `@vellumai/plugin-api` stub DELEGATES to the real
14
+ * `getConfiguredProvider` unless this test is actively running
15
15
  * (`selectMockActive`) — mirrors `prune.test.ts` / `ever-injected-store.test.ts`.
16
16
  */
17
17
 
@@ -25,9 +25,7 @@ import type {
25
25
 
26
26
  import type { MemoryRoutingTurn, Slug } from "./types.js";
27
27
 
28
- const realProviderSend = {
29
- ...(await import("../../../../providers/provider-send-message.js")),
30
- };
28
+ const realPluginApi = await import("@vellumai/plugin-api");
31
29
 
32
30
  let selectMockActive = false;
33
31
  let sendMessageImpl: (() => Promise<ProviderResponse>) | null = null;
@@ -40,14 +38,14 @@ const mockProvider = {
40
38
  },
41
39
  } as unknown as Provider;
42
40
 
43
- mock.module("../../../../providers/provider-send-message.js", () => ({
44
- ...realProviderSend,
41
+ mock.module("@vellumai/plugin-api", () => ({
42
+ ...realPluginApi,
45
43
  getConfiguredProvider: (
46
- ...args: Parameters<typeof realProviderSend.getConfiguredProvider>
44
+ ...args: Parameters<typeof realPluginApi.getConfiguredProvider>
47
45
  ) =>
48
46
  selectMockActive
49
47
  ? Promise.resolve(mockProvider)
50
- : realProviderSend.getConfiguredProvider(...args),
48
+ : realPluginApi.getConfiguredProvider(...args),
51
49
  }));
52
50
 
53
51
  const { MemoryV3RetrievalUnavailableError, selectPool } =
@@ -51,19 +51,19 @@ import type {
51
51
  Message,
52
52
  ToolUseContent,
53
53
  } from "@vellumai/plugin-api";
54
+ import { getConfiguredProvider } from "@vellumai/plugin-api";
54
55
  import { z } from "zod";
55
56
 
56
57
  import { classifyConversationError } from "../../../../daemon/conversation-error.js";
57
58
  import type { PendingConversationNotice } from "../../../../daemon/conversation-notices.js";
58
- import { cachedTextBlock } from "../../../../providers/cache-control.js";
59
- import {
60
- extractToolUse,
61
- getConfiguredProvider,
62
- } from "../../../../providers/provider-send-message.js";
63
- import type { ToolDefinition } from "../../../../providers/types.js";
64
59
  import { redactLogString } from "../../../../util/log-redact.js";
65
60
  import { getLogger } from "../../../../util/logger.js";
66
61
  import { truncate } from "../../../../util/truncate.js";
62
+ import {
63
+ cachedTextBlock,
64
+ extractToolUse,
65
+ type ToolDefinition,
66
+ } from "../llm-helpers.js";
67
67
  import { loadPromptOverride } from "../prompt-override.js";
68
68
  import { retryForResult } from "./llm-retry.js";
69
69
  import type { MemoryRoutingTurn, SelectedPage, Slug } from "./types.js";
@@ -60,9 +60,9 @@
60
60
 
61
61
  import type { ContentBlock, Message } from "@vellumai/plugin-api";
62
62
 
63
- import { getConfig } from "../../../../config/loader.js";
64
63
  import { getDb, getSqliteFrom } from "../../../../persistence/db-connection.js";
65
64
  import { getLogger } from "../../../../util/logger.js";
65
+ import { getMemoryConfig } from "../config.js";
66
66
  import { unwrapMemoryBlock, wrapMemoryBlock } from "../memory-marker.js";
67
67
  import {
68
68
  INJECTED_CONCEPT_HEADER_REGEX,
@@ -393,7 +393,7 @@ export async function runPruneValve(
393
393
  options: PruneValveOptions,
394
394
  ): Promise<PrunePlan | null> {
395
395
  // Defensive read: test configs may omit the prune block entirely.
396
- const pruneConfig = getConfig().memory?.v3?.prune;
396
+ const pruneConfig = getMemoryConfig()?.v3?.prune;
397
397
  if (!pruneConfig) return null;
398
398
 
399
399
  // Planning needs only the store (cheap); the persisted-metadata scan for
@@ -28,7 +28,6 @@ import type { AssistantConfig } from "../../../../config/types.js";
28
28
  import { deleteMemoryCheckpoint } from "../../../../persistence/checkpoints.js";
29
29
  import { getDb } from "../../../../persistence/db-connection.js";
30
30
  import {
31
- embedWithBackend,
32
31
  geminiCacheExtras,
33
32
  getMemoryBackendStatus,
34
33
  } from "../../../../persistence/embeddings/embedding-backend.js";
@@ -37,8 +36,8 @@ import {
37
36
  writeEmbeddingCache,
38
37
  } from "../../../../persistence/embeddings/embedding-cache.js";
39
38
  import { embeddingInputContentHash } from "../../../../persistence/embeddings/embedding-types.js";
40
- import { resolveQdrantUrl } from "../../../../persistence/embeddings/qdrant-client.js";
41
39
  import { getLogger } from "../../../../util/logger.js";
40
+ import { embedWithBackend, resolveQdrantUrl } from "../embeddings.js";
42
41
  import type { Section } from "./types.js";
43
42
 
44
43
  const log = getLogger("memory-v3-section-dense-store");
@@ -77,12 +76,10 @@ let _collectionReady = false;
77
76
  * Shared by both section-dense lanes (this store and the dense read lane in
78
77
  * `dense.ts`) so they reuse one client instance and one reset hook.
79
78
  */
80
- export function getSectionDenseClient(
81
- config: AssistantConfig,
82
- ): QdrantRestClient {
79
+ export function getSectionDenseClient(): QdrantRestClient {
83
80
  if (_client) return _client;
84
81
  _client = new QdrantRestClient({
85
- url: resolveQdrantUrl(config),
82
+ url: resolveQdrantUrl(),
86
83
  checkCompatibility: false,
87
84
  });
88
85
  return _client;
@@ -108,7 +105,7 @@ export async function ensureSectionCollection(
108
105
  ): Promise<void> {
109
106
  if (_collectionReady) return;
110
107
 
111
- const client = getSectionDenseClient(config);
108
+ const client = getSectionDenseClient();
112
109
  const vectorSize = config.memory.qdrant.vectorSize;
113
110
  const onDisk = config.memory.qdrant.onDisk;
114
111
 
@@ -218,7 +215,7 @@ export async function ensureSectionCollection(
218
215
  export async function recreateSectionCollection(
219
216
  config: AssistantConfig,
220
217
  ): Promise<void> {
221
- const client = getSectionDenseClient(config);
218
+ const client = getSectionDenseClient();
222
219
  const exists = await client.collectionExists(SECTION_COLLECTION);
223
220
  if (exists.exists) {
224
221
  await client.deleteCollection(SECTION_COLLECTION);
@@ -296,7 +293,7 @@ export async function upsertSections(
296
293
 
297
294
  if (points.length === 0) return;
298
295
 
299
- await getSectionDenseClient(config).upsert(SECTION_COLLECTION, {
296
+ await getSectionDenseClient().upsert(SECTION_COLLECTION, {
300
297
  wait: true,
301
298
  points,
302
299
  });
@@ -418,7 +415,7 @@ export async function deleteSectionsForArticle(
418
415
  ): Promise<void> {
419
416
  await ensureSectionCollection(config);
420
417
 
421
- await getSectionDenseClient(config).delete(SECTION_COLLECTION, {
418
+ await getSectionDenseClient().delete(SECTION_COLLECTION, {
422
419
  wait: true,
423
420
  filter: { must: [{ key: "article", match: { value: article } }] },
424
421
  });
@@ -440,7 +437,7 @@ export async function listSectionArticles(
440
437
  ): Promise<string[]> {
441
438
  await ensureSectionCollection(config);
442
439
 
443
- const client = getSectionDenseClient(config);
440
+ const client = getSectionDenseClient();
444
441
  const articles = new Set<string>();
445
442
  let offset: string | number | undefined = undefined;
446
443
  const maxIterations = 10_000;
@@ -25,6 +25,14 @@ const HEAD_WEIGHT = 2.5;
25
25
  /** `body`-field weight in the BM25F term-frequency blend. */
26
26
  const BODY_WEIGHT = 1;
27
27
 
28
+ /** A scored section-needle hit: the article, its best-scoring section index
29
+ * (into `SectionIndex.sections`), and that section's raw BM25F score. */
30
+ export interface SectionNeedleScoredHit {
31
+ article: Slug;
32
+ section: number;
33
+ score: number;
34
+ }
35
+
28
36
  export interface SectionNeedle {
29
37
  /**
30
38
  * Returns up to `k` distinct articles ranked by BM25F score (descending),
@@ -32,6 +40,11 @@ export interface SectionNeedle {
32
40
  * `SectionIndex.sections`). Ties break by `(article, ordinal)`.
33
41
  */
34
42
  query(text: string, k: number): { article: Slug; section: number }[];
43
+ /**
44
+ * Like {@link query} but includes the raw BM25F score per hit, in the same
45
+ * rank order (score desc, then (article, ordinal) asc).
46
+ */
47
+ queryScored(text: string, k: number): SectionNeedleScoredHit[];
35
48
  /**
36
49
  * Highest-scoring section index (into `SectionIndex.sections`) for `article`
37
50
  * against `queryText`. Returns the article's first section index when no term
@@ -164,10 +177,7 @@ export function buildSectionNeedle(index: SectionIndex): SectionNeedle {
164
177
  return sa.article.localeCompare(sc.article) || sa.ordinal - sc.ordinal;
165
178
  }
166
179
 
167
- function query(
168
- text: string,
169
- k: number,
170
- ): { article: Slug; section: number }[] {
180
+ function queryScored(text: string, k: number): SectionNeedleScoredHit[] {
171
181
  if (k <= 0 || docCount === 0) return [];
172
182
 
173
183
  const queryTerms = new Set(tokenize(text));
@@ -179,17 +189,27 @@ export function buildSectionNeedle(index: SectionIndex): SectionNeedle {
179
189
  // Dedupe to distinct articles, keeping each article's best (first-ranked)
180
190
  // section, until we have `k` articles.
181
191
  const seen = new Set<Slug>();
182
- const result: { article: Slug; section: number }[] = [];
192
+ const result: SectionNeedleScoredHit[] = [];
183
193
  for (const doc of ranked) {
184
194
  const article = sections[doc]!.article;
185
195
  if (seen.has(article)) continue;
186
196
  seen.add(article);
187
- result.push({ article, section: doc });
197
+ result.push({ article, section: doc, score: scores.get(doc) ?? 0 });
188
198
  if (result.length >= k) break;
189
199
  }
190
200
  return result;
191
201
  }
192
202
 
203
+ function query(
204
+ text: string,
205
+ k: number,
206
+ ): { article: Slug; section: number }[] {
207
+ return queryScored(text, k).map(({ article, section }) => ({
208
+ article,
209
+ section,
210
+ }));
211
+ }
212
+
193
213
  function bestSection(article: Slug, queryText: string): number {
194
214
  const docs = index.byArticle.get(article);
195
215
  if (!docs || docs.length === 0) return -1;
@@ -211,5 +231,5 @@ export function buildSectionNeedle(index: SectionIndex): SectionNeedle {
211
231
  return best;
212
232
  }
213
233
 
214
- return { query, bestSection, idf };
234
+ return { query, queryScored, bestSection, idf };
215
235
  }
@@ -44,6 +44,7 @@ import { buildEdgeGraph } from "./edge.js";
44
44
  import type { EntityIndex } from "./entity-lane.js";
45
45
  import { buildEntityIndex } from "./entity-lane.js";
46
46
  import { computeFreshSet } from "./fresh-set.js";
47
+ import { isMemoryV3InjectionGateEnabled } from "./gate-flag.js";
47
48
  import { computeHotSet } from "./hot-set.js";
48
49
  import { computeLearnedEdgeGraph } from "./learned-edges.js";
49
50
  import type { OrchestrateResult } from "./orchestrate.js";
@@ -586,6 +587,10 @@ export async function observeTurn(
586
587
  if (cfg.memory.enabled === false) return null;
587
588
  const lanes = await getLanes(cfg);
588
589
  const v3 = cfg.memory.v3;
590
+ // Resolve the effective gate enable once for the turn: the feature flag
591
+ // AND the `memory.v3.gate.enabled` config kill-switch. Tuning lives in
592
+ // `memory.v3.gate`.
593
+ const gateEnabled = isMemoryV3InjectionGateEnabled(cfg);
589
594
  // Re-resolve the corpus-adaptive tuning each turn from the CURRENT config
590
595
  // (with the lane-build corpus-size signal) so a live config.json edit to a
591
596
  // per-turn knob (selectorEnabled, denseK, replyQueryK, edge.*) takes effect
@@ -619,6 +624,11 @@ export async function observeTurn(
619
624
  v3.selectorPromptPath,
620
625
  getWorkspaceDir(),
621
626
  ),
627
+ // Per-turn injection gate: the `memory.v3.gate` tuning with the raw
628
+ // config `enabled` overwritten by the effective enable (flag AND config).
629
+ // The spread is the compile-time drift guard — if the gate schema and
630
+ // `V3GateConfig` diverge, this stops typechecking.
631
+ gateConfig: { ...v3.gate, enabled: gateEnabled },
622
632
  });
623
633
 
624
634
  // A zero-selection turn over a non-trivial pool is unusual enough to be
@@ -35,10 +35,7 @@ import {
35
35
  conversations,
36
36
  messages,
37
37
  } from "../../../../persistence/schema/index.js";
38
- import {
39
- FRONTMATTER_REGEX,
40
- parseFrontmatterFields,
41
- } from "../../../../skills/frontmatter.js";
38
+ import { FRONTMATTER_REGEX, parseFrontmatterFields } from "../frontmatter.js";
42
39
  import { injectedConceptHeader } from "../v2/injected-block-slugs.js";
43
40
  import { slugFromConceptPath } from "../v2/page-store.js";
44
41
  import { renderCard } from "../v3/card.js";
@@ -19,6 +19,11 @@ import { getConversation } from "../../../../persistence/conversation-crud.js";
19
19
  import { queueGenerateConversationTitle } from "../../../../persistence/conversation-title-service.js";
20
20
 
21
21
  const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
22
+ // Hidden machine signals (e.g. the channel-setup wizard-close marker) are
23
+ // not user speech — a title minted from one would surface invisible
24
+ // scaffolding text in the sidebar, and the LLM call is wasted.
25
+ if (ctx.isHiddenPrompt) return;
26
+
22
27
  // System conversations (background/scheduled) carry a deterministic title
23
28
  // from bootstrap. Their own job prompts arrive as non-interactive turns and
24
29
  // must not spend an LLM call on a title nobody reads — only a genuine user
@@ -32,6 +32,7 @@ const unifiedTurnContextInjector: Injector = {
32
32
  const text = buildUnifiedTurnContextBlock({
33
33
  timestamp,
34
34
  interfaceName: ctx.interfaceName,
35
+ clientOs: ctx.clientOs,
35
36
  channelName: ctx.channelName,
36
37
  actorContext: ctx.actorContext,
37
38
  configuredUserTimezone: ctx.configuredUserTimezone,
@@ -18,6 +18,7 @@ import { resolveCapabilities } from "../../../runtime/capabilities.js";
18
18
  export interface UnifiedTurnContextOptions {
19
19
  timestamp: string;
20
20
  interfaceName?: string;
21
+ clientOs?: string;
21
22
  channelName?: string;
22
23
  actorContext?: InboundActorContext | null;
23
24
  configuredUserTimezone?: string | null;
@@ -100,6 +101,9 @@ export function buildUnifiedTurnContextBlock(
100
101
  if (options.interfaceName) {
101
102
  lines.push(`interface: ${options.interfaceName}`);
102
103
  }
104
+ if (options.clientOs) {
105
+ lines.push(`client_os: ${options.clientOs}`);
106
+ }
103
107
 
104
108
  // Actor identity and trust fields — only for non-guardian turns.
105
109
  if (options.actorContext) {
@@ -182,6 +186,14 @@ export function buildUnifiedTurnContextBlock(
182
186
 
183
187
  // Behavioral guidance - only for non-guardian actors where social
184
188
  // engineering defense matters. Guardian case needs no instruction.
189
+ //
190
+ // Scope: this is identity/verification hygiene (don't infer guardian status
191
+ // from tone, don't self-approve, don't explain the verification system).
192
+ // Its complement — the data-disclosure boundary (never reveal the guardian's
193
+ // private data: schedule, contacts, files, memories) — lives in the
194
+ // `users/default.md` persona guardrail, gated by
195
+ // `derivePersonaTrustFlags()` in `runtime/trust-class.ts`. Keep the two
196
+ // distinct.
185
197
  switch (resolveCapabilities(ctx.trustClass).promptTrustGuidance) {
186
198
  case "social-engineering-defense": {
187
199
  lines.push("");
@@ -96,8 +96,16 @@ export function unregisterPluginInjectors(pluginName: string): void {
96
96
  * and tool registries honor. The order-sorted union is memoized across the
97
97
  * filter (rebuilt only on a registration change); only the small per-plugin
98
98
  * disabled check re-runs each call.
99
+ *
100
+ * `effectiveEnabledPlugins` layers the per-chat plugin scope on top of the
101
+ * global disabled check: when non-null, an injector's contributing plugin must
102
+ * also be a member of the set or the injector is excluded for this turn. `null`
103
+ * (or omitted) means no per-chat restriction — every globally-enabled plugin's
104
+ * injectors run, unchanged.
99
105
  */
100
- export function getRegisteredInjectors(): Injector[] {
106
+ export function getRegisteredInjectors(
107
+ effectiveEnabledPlugins?: Set<string> | null,
108
+ ): Injector[] {
101
109
  if (cachedChain === null) {
102
110
  const pairs: Array<{ plugin: string; injector: Injector }> = [];
103
111
  for (const [plugin, injectors] of injectorsByPlugin) {
@@ -110,7 +118,10 @@ export function getRegisteredInjectors(): Injector[] {
110
118
  const isEnabled = (plugin: string): boolean => {
111
119
  let enabled = enabledByPlugin.get(plugin);
112
120
  if (enabled === undefined) {
113
- enabled = !isPluginDisabled(plugin);
121
+ enabled =
122
+ !isPluginDisabled(plugin) &&
123
+ (effectiveEnabledPlugins == null ||
124
+ effectiveEnabledPlugins.has(plugin));
114
125
  enabledByPlugin.set(plugin, enabled);
115
126
  }
116
127
  return enabled;