@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
@@ -15,7 +15,10 @@ function isDuplicateColumnError(err: unknown): boolean {
15
15
 
16
16
  function ensureDisplayOrderColumns(): void {
17
17
  try {
18
- rawRun("ALTER TABLE conversations ADD COLUMN display_order INTEGER");
18
+ rawRun(
19
+ "conversation:migrateDisplayOrder",
20
+ "ALTER TABLE conversations ADD COLUMN display_order INTEGER",
21
+ );
19
22
  } catch (err) {
20
23
  if (!isDuplicateColumnError(err)) {
21
24
  log.error({ err }, "Failed to add display_order column");
@@ -23,7 +26,10 @@ function ensureDisplayOrderColumns(): void {
23
26
  }
24
27
  }
25
28
  try {
26
- rawRun("ALTER TABLE conversations ADD COLUMN is_pinned INTEGER DEFAULT 0");
29
+ rawRun(
30
+ "conversation:migrateIsPinned",
31
+ "ALTER TABLE conversations ADD COLUMN is_pinned INTEGER DEFAULT 0",
32
+ );
27
33
  } catch (err) {
28
34
  if (!isDuplicateColumnError(err)) {
29
35
  log.error({ err }, "Failed to add is_pinned column");
@@ -36,16 +36,19 @@ describe("ensureGroupMigration — reflections cleanup", () => {
36
36
  `);
37
37
  try {
38
38
  rawRun(
39
+ "test:addGroupIdColumn",
39
40
  "ALTER TABLE conversations ADD COLUMN group_id TEXT REFERENCES conversation_groups(id) ON DELETE SET NULL",
40
41
  );
41
42
  } catch {
42
43
  // column already present — ok
43
44
  }
44
45
  rawRun(
46
+ "test:seedReflectionsGroup",
45
47
  "INSERT OR IGNORE INTO conversation_groups (id, name, sort_position, is_system_group) VALUES ('system:reflections', 'Reflections', 100, TRUE)",
46
48
  );
47
49
  const now = Math.floor(Date.now() / 1000);
48
50
  rawRun(
51
+ "test:seedLegacyConv",
49
52
  "INSERT INTO conversations (id, created_at, updated_at, group_id) VALUES (?, ?, ?, ?)",
50
53
  "legacy-refl-1",
51
54
  now,
@@ -58,6 +61,7 @@ describe("ensureGroupMigration — reflections cleanup", () => {
58
61
 
59
62
  test("moves legacy system:reflections conversations to system:background", () => {
60
63
  const legacy = rawGet<{ group_id: string | null }>(
64
+ "test:getLegacyGroupId",
61
65
  "SELECT group_id FROM conversations WHERE id = 'legacy-refl-1'",
62
66
  );
63
67
  expect(legacy?.group_id).toBe("system:background");
@@ -65,6 +69,7 @@ describe("ensureGroupMigration — reflections cleanup", () => {
65
69
 
66
70
  test("deletes the orphaned system:reflections group row", () => {
67
71
  const refl = rawGet<{ id: string }>(
72
+ "test:getReflectionsGroup",
68
73
  "SELECT id FROM conversation_groups WHERE id = 'system:reflections'",
69
74
  );
70
75
  expect(refl).toBeNull();
@@ -72,6 +77,7 @@ describe("ensureGroupMigration — reflections cleanup", () => {
72
77
 
73
78
  test("seeds the current system groups without system:reflections", () => {
74
79
  const systemIds = rawAll<{ id: string }>(
80
+ "test:listSystemGroups",
75
81
  "SELECT id FROM conversation_groups WHERE id LIKE 'system:%' ORDER BY id",
76
82
  ).map((r) => r.id);
77
83
  expect(systemIds).toEqual([
@@ -84,6 +90,7 @@ describe("ensureGroupMigration — reflections cleanup", () => {
84
90
 
85
91
  test("records the cleanup sentinel so the step is idempotent", () => {
86
92
  const sentinel = rawGet<{ id: string }>(
93
+ "test:getSentinel",
87
94
  "SELECT id FROM conversation_groups WHERE id = '_reflections_group_deleted_complete'",
88
95
  );
89
96
  expect(sentinel?.id).toBe("_reflections_group_deleted_complete");
@@ -92,6 +99,7 @@ describe("ensureGroupMigration — reflections cleanup", () => {
92
99
  test("re-running ensureGroupMigration does not recreate the stale group", () => {
93
100
  expect(() => ensureGroupMigration()).not.toThrow();
94
101
  const refl = rawGet<{ id: string }>(
102
+ "test:getReflectionsGroup",
95
103
  "SELECT id FROM conversation_groups WHERE id = 'system:reflections'",
96
104
  );
97
105
  expect(refl).toBeNull();
@@ -59,6 +59,7 @@ export function ensureGroupMigration(): void {
59
59
  // only swallow duplicate-column errors, log+throw everything else.
60
60
  try {
61
61
  rawRun(
62
+ "group:migrateAddGroupId",
62
63
  "ALTER TABLE conversations ADD COLUMN group_id TEXT REFERENCES conversation_groups(id) ON DELETE SET NULL",
63
64
  );
64
65
  } catch (err) {
@@ -84,18 +85,22 @@ export function ensureGroupMigration(): void {
84
85
  // transaction so a crash between the shift and the sentinel can't cause
85
86
  // repeated drift on restart.
86
87
  const sortShiftDone = rawGet<{ id: string }>(
88
+ "group:migrateSortShiftGuard",
87
89
  "SELECT id FROM conversation_groups WHERE id = '_sort_shift_complete'",
88
90
  );
89
91
  if (!sortShiftDone) {
90
92
  try {
91
93
  rawExec("BEGIN");
92
94
  rawRun(
95
+ "group:migrateSortShift:shift",
93
96
  "UPDATE conversation_groups SET sort_position = sort_position + 1 WHERE is_system_group = 0 AND sort_position >= 3",
94
97
  );
95
98
  rawRun(
99
+ "group:migrateSortShift:setAll",
96
100
  "UPDATE conversation_groups SET sort_position = 3 WHERE id = 'system:all' AND sort_position != 3",
97
101
  );
98
102
  rawRun(
103
+ "group:migrateSortShift:marker",
99
104
  `INSERT OR IGNORE INTO conversation_groups (id, name, sort_position, is_system_group, created_at, updated_at)
100
105
  VALUES ('_sort_shift_complete', '_sort_shift_complete', -1, TRUE, ${now}, ${now})`,
101
106
  );
@@ -115,6 +120,7 @@ export function ensureGroupMigration(): void {
115
120
  // would backfill it back into system:background). A persistent marker row in
116
121
  // conversation_groups tracks whether backfill has already completed.
117
122
  const backfillDone = rawGet<{ id: string }>(
123
+ "group:migrateBackfillGuard",
118
124
  "SELECT id FROM conversation_groups WHERE id = '_backfill_complete'",
119
125
  );
120
126
 
@@ -200,6 +206,7 @@ export function ensureGroupMigration(): void {
200
206
  // Uses its own sentinel so it runs exactly once, even on existing installations
201
207
  // where the original backfill already completed.
202
208
  const allBackfillDone = rawGet<{ id: string }>(
209
+ "group:migrateBackfillAllGuard",
203
210
  "SELECT id FROM conversation_groups WHERE id = '_backfill_all_complete'",
204
211
  );
205
212
 
@@ -226,6 +233,7 @@ export function ensureGroupMigration(): void {
226
233
 
227
234
  // 6. One-time migration: move auto-analysis conversations from system:reflections to system:background
228
235
  const reflectionsMigrateDone = rawGet<{ id: string }>(
236
+ "group:migrateReflectionsGuard",
229
237
  "SELECT id FROM conversation_groups WHERE id = '_reflections_to_background_complete'",
230
238
  );
231
239
 
@@ -262,6 +270,7 @@ export function ensureGroupMigration(): void {
262
270
  // place causes the macOS sidebar to render an empty duplicate "Reflections"
263
271
  // entry with a fallback folder icon alongside the Background sub-group.
264
272
  const reflectionsGroupDeleted = rawGet<{ id: string }>(
273
+ "group:migrateReflDeleteGuard",
265
274
  "SELECT id FROM conversation_groups WHERE id = '_reflections_group_deleted_complete'",
266
275
  );
267
276
 
@@ -7,10 +7,12 @@ import {
7
7
  wrapUntrustedContent,
8
8
  } from "../security/untrusted-content.js";
9
9
  import { getLogger } from "../util/logger.js";
10
+ import { isLexicalBackfillComplete } from "./checkpoints.js";
10
11
  import type { ConversationRow } from "./conversation-crud.js";
11
12
  import { parseConversation } from "./conversation-crud.js";
12
13
  import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
13
14
  import { ensureGroupMigration } from "./conversation-group-migration.js";
15
+ import { searchMessageIdsLexical } from "./conversation-search-lexical.js";
14
16
  import type { ConversationType } from "./conversation-types.js";
15
17
  import { getDb } from "./db-connection.js";
16
18
  import { rawAll } from "./raw-query.js";
@@ -19,34 +21,54 @@ import { conversations, messages } from "./schema/index.js";
19
21
  const log = getLogger("conversation-store");
20
22
 
21
23
  /**
22
- * Build an FTS5 MATCH query string from natural text by extracting tokens.
23
- * Used for messages_fts full-text search over conversation content.
24
+ * Max distinct visible conversations {@link searchConversations} collects from
25
+ * content matches before merging with title matches and the final
26
+ * `updated_at`-ordered `LIMIT`. Both backends cap on distinct conversations (not
27
+ * matching messages) so one chatty conversation can't crowd others out; the FTS
28
+ * path enforces this in SQL (`SELECT DISTINCT conversation_id … LIMIT`).
24
29
  */
25
- export function buildFtsMatchQuery(
26
- text: string,
27
- opts?: { allowFts5Syntax?: boolean },
28
- ): string | null {
29
- // If the query already contains FTS5 operators, pass it through directly
30
- // so callers (e.g. the archive recall tool) can use exact-phrase, AND, OR,
31
- // NOT, NEAR syntax. Only enabled when the caller explicitly opts in —
32
- // user-facing search should always go through normal tokenization to avoid
33
- // FTS5 boolean semantics leaking into sidebar/global search.
34
- if (
35
- opts?.allowFts5Syntax &&
36
- /\bAND\b|\bOR\b|\bNOT\b|\bNEAR\s*\(|"[^"]+"/.test(text)
37
- ) {
38
- return text.trim();
39
- }
30
+ const CONVERSATION_SEARCH_DISTINCT_LIMIT = 1000;
31
+
32
+ /**
33
+ * Message-candidate over-fetch for the Qdrant lexical backend.
34
+ *
35
+ * Qdrant ranks at message grain and has no visibility/archived filtering, so a
36
+ * naive cap at {@link CONVERSATION_SEARCH_DISTINCT_LIMIT} *messages* could be
37
+ * consumed entirely by one chatty visible conversation or by private/archived
38
+ * candidates, starving other matching visible conversations — unlike the FTS
39
+ * path, which caps on distinct conversations *after* the visibility JOIN.
40
+ *
41
+ * To approximate that behavior we over-fetch this many message candidates, then
42
+ * filter/dedupe to distinct visible conversations in code, so the effective cap
43
+ * lands on {@link CONVERSATION_SEARCH_DISTINCT_LIMIT} distinct visible
44
+ * conversations. A few thousand candidates reliably yields far more than the
45
+ * caller's `limit` (default 20) distinct visible conversations except in
46
+ * pathological mega-conversation cases.
47
+ *
48
+ * This is a proportionate over-fetch, NOT a hard parity guarantee: a query
49
+ * whose top ~{@link QDRANT_SEARCH_CANDIDATE_LIMIT} candidates all fall in a
50
+ * handful of conversations can still under-surface. The stronger fix — Qdrant
51
+ * `search_groups` (group_by `conversation_id`) or true score-ordered
52
+ * pagination — is deferred to pre-flip hardening if Gate A shows regressions.
53
+ *
54
+ * Kept well under bun:sqlite's ~32k bound-parameter ceiling so the
55
+ * `WHERE m.id IN (…)` visibility query stays a single statement.
56
+ */
57
+ const QDRANT_SEARCH_CANDIDATE_LIMIT = 5000;
40
58
 
41
- const tokens = text
59
+ /**
60
+ * Whether `text` yields at least one usable lexical token (≥2 chars of
61
+ * `[a-z0-9_]`). Queries with none — non-ASCII, single-char, or
62
+ * punctuation-only input like "你", "é", "C++" — cannot be served by content
63
+ * matching: the sparse encoder would still emit noisy 1-char tokens for them,
64
+ * so the read sites skip the lexical index entirely and fall back to their
65
+ * no-token behavior (title matches only / no evidence).
66
+ */
67
+ export function hasLexicalTokens(text: string): boolean {
68
+ return text
42
69
  .toLowerCase()
43
70
  .split(/[^a-z0-9_]+/g)
44
- .map((token) => token.trim())
45
- .filter((token) => token.length >= 2);
46
- if (tokens.length === 0) return null;
47
- const unique = [...new Set(tokens)].slice(0, 24);
48
- // Space-separated quoted tokens are implicit AND in FTS5.
49
- return unique.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
71
+ .some((token) => token.trim().length >= 2);
50
72
  }
51
73
 
52
74
  /**
@@ -259,7 +281,9 @@ export function getMessageRoleStatsByConversation(
259
281
  conversationIds: string[],
260
282
  role: string = "assistant",
261
283
  ): Map<string, { count: number; lastAt: number }> {
262
- if (conversationIds.length === 0) return new Map();
284
+ if (conversationIds.length === 0) {
285
+ return new Map();
286
+ }
263
287
  const db = getDb();
264
288
  const rows = db
265
289
  .select({
@@ -353,6 +377,7 @@ export function listConversationsByTitlePrefix(
353
377
  created_at: number;
354
378
  }
355
379
  const rows = rawAll<Row>(
380
+ "conversation:listByTitlePrefix",
356
381
  `SELECT c.id, c.title,
357
382
  (SELECT COUNT(*) FROM messages WHERE conversation_id = c.id) AS message_count,
358
383
  c.created_at
@@ -410,7 +435,9 @@ export function isLastUserMessageToolResult(conversationId: string): boolean {
410
435
  .limit(1)
411
436
  .get();
412
437
 
413
- if (!lastUserMsg) return false;
438
+ if (!lastUserMsg) {
439
+ return false;
440
+ }
414
441
 
415
442
  try {
416
443
  const parsed = JSON.parse(lastUserMsg.content);
@@ -448,82 +475,187 @@ export interface ConversationSearchResult {
448
475
  }>;
449
476
  }
450
477
 
478
+ interface ConversationSearchMsgRow {
479
+ id: string;
480
+ role: string;
481
+ content: string;
482
+ created_at: number;
483
+ }
484
+
451
485
  /**
452
- * Full-text search across message content using FTS5.
453
- * Uses the messages_fts virtual table for fast tokenized matching on message
454
- * content, with a LIKE fallback on conversation titles. Returns matching
455
- * conversations with their relevant messages, ordered by most recently updated.
486
+ * SQL `LIKE` pattern escaping the three wildcard metacharacters (`\`, `%`, `_`)
487
+ * so a literal user query matches as literal text under `ESCAPE '\\'`. Wrapped
488
+ * in `%...%` for a substring match.
456
489
  */
457
- export function searchConversations(
490
+ function likeContainsPattern(query: string): string {
491
+ return `%${query
492
+ .replace(/\\/g, "\\\\")
493
+ .replace(/%/g, "\\%")
494
+ .replace(/_/g, "\\_")}%`;
495
+ }
496
+
497
+ /**
498
+ * Whether the sparse Qdrant `messages_lexical` index — the only source of
499
+ * message-content matches — is a safe read source. Content matching is
500
+ * unavailable (title matches only) until the one-time upgrade backfill has
501
+ * fully drained: a partially populated collection would silently miss older
502
+ * content (an empty result — not a throw). Indexing itself is unconditional
503
+ * host infrastructure, so completion is the only gate; the recall read site
504
+ * applies the same one via the shared {@link isLexicalBackfillComplete}.
505
+ */
506
+ function isMessageContentSearchAvailable(): boolean {
507
+ return isLexicalBackfillComplete();
508
+ }
509
+
510
+ /**
511
+ * Full-text search across message content.
512
+ *
513
+ * Message-content candidates come from the sparse `messages_lexical` Qdrant
514
+ * index (BM25-style), filtered by the visibility/archived SQL predicates and
515
+ * merged with a `LIKE` match on conversation titles; matching conversations
516
+ * return with their relevant messages, ordered by most recently updated.
517
+ *
518
+ * Content matching is index-only — there is no `messages.content` scan
519
+ * fallback and no other content source. Only the title arm can match while
520
+ * the index is not a safe read source ({@link isMessageContentSearchAvailable}),
521
+ * for a query that tokenizes to nothing under the shared tokenizer (non-ASCII
522
+ * or single-char input like "你", "é", "C++"), or when the Qdrant lexical
523
+ * lookup fails (logged). An unindexed or unreachable index yields fewer
524
+ * results, by design.
525
+ *
526
+ * The content cap lands on distinct conversations, not matching messages:
527
+ * Qdrant ranks at message grain, so the read over-fetches a candidate pool
528
+ * ({@link QDRANT_SEARCH_CANDIDATE_LIMIT}) and dedupes to
529
+ * {@link CONVERSATION_SEARCH_DISTINCT_LIMIT} distinct visible conversations
530
+ * in score order (see the constants' docstrings).
531
+ */
532
+ export async function searchConversations(
458
533
  query: string,
459
534
  opts?: { limit?: number; maxMessagesPerConversation?: number },
460
- ): ConversationSearchResult[] {
461
- if (!query.trim()) return [];
535
+ ): Promise<ConversationSearchResult[]> {
536
+ if (!query.trim()) {
537
+ return [];
538
+ }
462
539
 
463
540
  ensureGroupMigration();
464
541
  const db = getDb();
542
+ const trimmed = query.trim();
465
543
  const limit = opts?.limit ?? 20;
466
544
  const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
467
545
 
468
- const ftsMatch = buildFtsMatchQuery(query.trim());
469
-
470
- // LIKE pattern for title matching (FTS only covers message content).
471
- const titlePattern = `%${query
472
- .replace(/\\/g, "\\\\")
473
- .replace(/%/g, "\\%")
474
- .replace(/_/g, "\\_")}%`;
475
-
476
- interface ConvIdRow {
477
- conversation_id: string;
478
- }
479
-
480
- // Collect conversation IDs from FTS message matches and title LIKE matches,
481
- // then merge them to produce the final set of matching conversations.
482
- // Both paths LIMIT on distinct conversation_id to prevent a single
483
- // conversation with many matching messages from crowding out others.
484
- const ftsConvIds = new Set<string>();
485
- if (ftsMatch) {
546
+ const hasTokens = hasLexicalTokens(trimmed);
547
+ const contentSearchAvailable = isMessageContentSearchAvailable();
548
+
549
+ // LIKE pattern for title matching (message-content indexes don't cover titles).
550
+ const titlePattern = likeContainsPattern(query);
551
+
552
+ // Collect conversation IDs from message-content matches and title LIKE
553
+ // matches, then merge them to produce the final set of matching
554
+ // conversations. Content paths LIMIT on distinct conversation_id to prevent a
555
+ // single conversation with many matching messages from crowding out others.
556
+ const contentConvIds = new Set<string>();
557
+
558
+ // When the Qdrant backend answers, `qdrantCandidatesByConv` maps each
559
+ // conversation to its candidate message ids so the per-conversation message
560
+ // fetch reuses the single lexical round-trip instead of issuing a second one.
561
+ // On a lexical-lookup failure it stays null, so the content arm contributes
562
+ // nothing and only title matches surface.
563
+ let qdrantCandidatesByConv: Map<string, string[]> | null = null;
564
+
565
+ if (hasTokens && contentSearchAvailable) {
566
+ // The lexical index is written asynchronously by the memory job queue, so
567
+ // for a brief window after an edit it can lag SQLite: a search for a term
568
+ // just removed from a message may still return that message id (or miss one
569
+ // just added). This is an accepted async-indexing tradeoff — transient, and
570
+ // results are re-sorted by `updated_at` — not re-verified against live
571
+ // content here.
572
+ let candidates: Awaited<ReturnType<typeof searchMessageIdsLexical>> = [];
486
573
  try {
487
- const ftsRows = rawAll<ConvIdRow>(
488
- `
489
- SELECT DISTINCT m.conversation_id
490
- FROM messages_fts f
491
- JOIN messages m ON m.id = f.message_id
492
- JOIN conversations c ON c.id = m.conversation_id
493
- WHERE messages_fts MATCH ? AND ${standardListingVisibilitySql("c")} AND c.archived_at IS NULL
494
- LIMIT 1000
495
- `,
496
- ftsMatch,
574
+ candidates = await searchMessageIdsLexical(
575
+ trimmed,
576
+ QDRANT_SEARCH_CANDIDATE_LIMIT,
497
577
  );
498
- for (const row of ftsRows) ftsConvIds.add(row.conversation_id);
499
578
  } catch (err) {
500
579
  log.warn(
501
580
  { err, query: query.slice(0, 80) },
502
- "searchConversations: FTS query failed — falling through to title matches",
581
+ "searchConversations: Qdrant lexical query failed — returning title matches only",
503
582
  );
504
583
  }
505
- } else if (query.trim()) {
506
- // FTS tokens were all dropped (non-ASCII, single-char, etc.) — fall back to
507
- // LIKE-based message content search so queries like "你", "é", or "C++" still
508
- // match message text.
509
- const likePattern = `%${query
510
- .replace(/\\/g, "\\\\")
511
- .replace(/%/g, "\\%")
512
- .replace(/_/g, "\\_")}%`;
513
- const likeRows = rawAll<ConvIdRow>(
514
- `
515
- SELECT DISTINCT m.conversation_id
516
- FROM messages m
517
- JOIN conversations c ON c.id = m.conversation_id
518
- WHERE m.content LIKE ? ESCAPE '\\' AND ${standardListingVisibilitySql("c")} AND c.archived_at IS NULL
519
- LIMIT 1000
520
- `,
521
- likePattern,
584
+
585
+ if (candidates.length > 0) {
586
+ const candidateIds = candidates.map((c) => c.messageId);
587
+ // Filter the lexical candidates down to visible, non-archived
588
+ // conversations in SQL (Qdrant has no visibility filtering). No SQL
589
+ // LIMIT here: the row count is already bounded by the candidate pool
590
+ // (≤ QDRANT_SEARCH_CANDIDATE_LIMIT), and capping in SQL would cap on
591
+ // messages, not distinct conversations. The distinct-conversation cap is
592
+ // applied below in Qdrant score order.
593
+ interface CandidateRow {
594
+ id: string;
595
+ conversation_id: string;
596
+ }
597
+ const placeholders = candidateIds.map(() => "?").join(",");
598
+ const visibleRows = rawAll<CandidateRow>(
599
+ "conversation:searchConversations:visibleCandidates",
600
+ `
601
+ SELECT m.id, m.conversation_id
602
+ FROM messages m
603
+ JOIN conversations c ON c.id = m.conversation_id
604
+ WHERE m.id IN (${placeholders}) AND ${standardListingVisibilitySql("c")} AND c.archived_at IS NULL
605
+ `,
606
+ ...candidateIds,
607
+ );
608
+ const visibleConvByMessageId = new Map<string, string>();
609
+ for (const row of visibleRows) {
610
+ visibleConvByMessageId.set(row.id, row.conversation_id);
611
+ }
612
+
613
+ // Walk candidates in Qdrant score order (highest first). Bucket each
614
+ // visible candidate's message id under its conversation and stop
615
+ // collecting NEW conversations once CONVERSATION_SEARCH_DISTINCT_LIMIT
616
+ // distinct visible conversations are seen — so one chatty conversation
617
+ // can't starve others, matching the FTS distinct-conversation cap.
618
+ qdrantCandidatesByConv = new Map();
619
+ for (const candidate of candidates) {
620
+ const convId = visibleConvByMessageId.get(candidate.messageId);
621
+ if (!convId) {
622
+ continue;
623
+ }
624
+ const bucket = qdrantCandidatesByConv.get(convId);
625
+ if (bucket) {
626
+ bucket.push(candidate.messageId);
627
+ } else {
628
+ if (
629
+ qdrantCandidatesByConv.size >= CONVERSATION_SEARCH_DISTINCT_LIMIT
630
+ ) {
631
+ continue;
632
+ }
633
+ qdrantCandidatesByConv.set(convId, [candidate.messageId]);
634
+ contentConvIds.add(convId);
635
+ }
636
+ }
637
+ }
638
+ } else if (hasTokens) {
639
+ // The lexical index is not a safe read source (memory disabled, or the
640
+ // one-time upgrade backfill still draining) — the query can only match by
641
+ // conversation title below. The breadcrumb explains an otherwise
642
+ // surprising lack of content matches.
643
+ log.info(
644
+ { query: query.slice(0, 80) },
645
+ "searchConversations: lexical index unavailable — title matches only",
646
+ );
647
+ } else {
648
+ // The query tokenized to nothing (non-ASCII, single-char, etc. — "你",
649
+ // "é", "C++"). Content matching is index-only, so such queries can only
650
+ // match by conversation title below. The breadcrumb explains an otherwise
651
+ // surprising empty result.
652
+ log.info(
653
+ { query: query.slice(0, 80) },
654
+ "searchConversations: query produced no lexical tokens — title matches only",
522
655
  );
523
- for (const row of likeRows) ftsConvIds.add(row.conversation_id);
524
656
  }
525
657
 
526
- // Title-only matches (FTS doesn't index conversation titles).
658
+ // Title-only matches (message-content indexes don't cover conversation titles).
527
659
  const titleMatchConvs = db
528
660
  .select({ id: conversations.id })
529
661
  .from(conversations)
@@ -535,12 +667,16 @@ export function searchConversations(
535
667
  ),
536
668
  )
537
669
  .all();
538
- for (const row of titleMatchConvs) ftsConvIds.add(row.id);
670
+ for (const row of titleMatchConvs) {
671
+ contentConvIds.add(row.id);
672
+ }
539
673
 
540
- if (ftsConvIds.size === 0) return [];
674
+ if (contentConvIds.size === 0) {
675
+ return [];
676
+ }
541
677
 
542
678
  // Fetch the matching conversation rows, ordered by updatedAt, capped at limit.
543
- const convIds = [...ftsConvIds];
679
+ const convIds = [...contentConvIds];
544
680
  const placeholders = convIds.map(() => "?").join(",");
545
681
  interface ConvRow {
546
682
  id: string;
@@ -548,6 +684,7 @@ export function searchConversations(
548
684
  updated_at: number;
549
685
  }
550
686
  const matchingConversations = rawAll<ConvRow>(
687
+ "conversation:searchConversations:matchingConversations",
551
688
  `SELECT id, title, updated_at FROM conversations
552
689
  WHERE id IN (${placeholders})
553
690
  ORDER BY updated_at DESC
@@ -556,57 +693,39 @@ export function searchConversations(
556
693
  limit,
557
694
  );
558
695
 
559
- if (matchingConversations.length === 0) return [];
696
+ if (matchingConversations.length === 0) {
697
+ return [];
698
+ }
560
699
 
561
700
  const results: ConversationSearchResult[] = [];
562
701
 
563
702
  for (const conv of matchingConversations) {
564
- interface MsgRow {
565
- id: string;
566
- role: string;
567
- content: string;
568
- created_at: number;
569
- }
570
- let matchingMsgs: MsgRow[] = [];
571
- if (ftsMatch) {
572
- try {
573
- matchingMsgs = rawAll<MsgRow>(
703
+ // Conversations without content candidates — title-only matches, every
704
+ // match on a no-token query, and all matches while content search is
705
+ // unavailable or the Qdrant lookup failed — carry no content excerpts:
706
+ // `matchingMsgs` stays empty.
707
+ let matchingMsgs: ConversationSearchMsgRow[] = [];
708
+ if (hasTokens && contentSearchAvailable) {
709
+ // Reuse the lexical candidates already fetched above — no second Qdrant
710
+ // round-trip. Select this conversation's candidate message rows by id,
711
+ // ordered oldest-first.
712
+ const candidateIds = qdrantCandidatesByConv?.get(conv.id) ?? [];
713
+ if (candidateIds.length > 0) {
714
+ const msgPlaceholders = candidateIds.map(() => "?").join(",");
715
+ matchingMsgs = rawAll<ConversationSearchMsgRow>(
716
+ "conversation:searchConversations:matchingMessages",
574
717
  `
575
- SELECT m.id, m.role, m.content, m.created_at
576
- FROM messages_fts f
577
- JOIN messages m ON m.id = f.message_id
578
- WHERE messages_fts MATCH ? AND m.conversation_id = ?
579
- ORDER BY m.created_at ASC
718
+ SELECT id, role, content, created_at
719
+ FROM messages
720
+ WHERE conversation_id = ? AND id IN (${msgPlaceholders})
721
+ ORDER BY created_at ASC
580
722
  LIMIT ?
581
723
  `,
582
- ftsMatch,
583
724
  conv.id,
725
+ ...candidateIds,
584
726
  maxMsgsPerConv,
585
727
  );
586
- } catch (err) {
587
- log.warn(
588
- { err, conversationId: conv.id },
589
- "searchConversations: FTS per-conversation query failed",
590
- );
591
728
  }
592
- } else if (query.trim()) {
593
- // LIKE fallback for non-ASCII / short-token queries.
594
- const msgLikePattern = `%${query
595
- .replace(/\\/g, "\\\\")
596
- .replace(/%/g, "\\%")
597
- .replace(/_/g, "\\_")}%`;
598
- matchingMsgs = rawAll<MsgRow>(
599
- `
600
- SELECT id, role, content, created_at
601
- FROM messages
602
- WHERE conversation_id = ? AND content LIKE ? ESCAPE '\\'
603
- ORDER BY created_at ASC
604
- LIMIT ?
605
- `,
606
- conv.id,
607
- msgLikePattern,
608
- maxMsgsPerConv,
609
- );
610
729
  }
611
730
 
612
731
  results.push({
@@ -45,12 +45,16 @@ import {
45
45
  /**
46
46
  * Default batch size for the chunked delete. Each batch is one `DELETE` that
47
47
  * auto-commits, so this bounds how long the subprocess holds the write lock
48
- * before releasing it to in-process writers. Kept small so the worst-case wait
49
- * for a contending foreground write is one short batch, even on a bloated
50
- * database; per-batch statement overhead stays negligible and delete latency
51
- * doesn't matter (nobody waits on a background GC).
48
+ * before releasing it to in-process writers; the worst-case wait for a
49
+ * contending foreground write is one batch, and delete latency doesn't matter
50
+ * (nobody waits on a background GC).
51
+ *
52
+ * A deleted row costs a plain b-tree delete plus any FK cascade deletes —
53
+ * cheap and roughly proportional to the row count, with no per-row trigger
54
+ * work — so a batch this size still holds the lock only briefly while
55
+ * amortizing the per-batch subprocess spawn cost.
52
56
  */
53
- export const DEFAULT_DELETE_BATCH_SIZE = 50;
57
+ export const DEFAULT_DELETE_BATCH_SIZE = 200;
54
58
 
55
59
  /**
56
60
  * Pause inserted between batch subprocess calls. Without it the delete releases