@vellumai/assistant 0.7.2 → 0.7.3

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 (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -1,404 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Memory v2 — Qdrant collection for skill autoinjection
3
- // ---------------------------------------------------------------------------
4
- //
5
- // Owns a dedicated Qdrant collection, keyed by skill `id`, that holds dense +
6
- // sparse embeddings of every enabled skill's `buildSkillContent` snippet. The
7
- // collection is separate from `memory_v2_concept_pages` because skills are
8
- // stateless (no `everInjected` dedup, no on-disk concept pages, no edges) and
9
- // re-presented every turn — keeping them in their own collection means skill
10
- // pruning and reseeding never touches concept-page state.
11
- //
12
- // Mirrors the dense + sparse named-vectors layout used by
13
- // `ensureConceptPageCollection` in `qdrant.ts`. Connection settings — URL,
14
- // vector size, on-disk storage — flow through the same env → config
15
- // precedence as v1 via `resolveQdrantUrl` and `config.memory.qdrant.*`.
16
- //
17
- // Per-channel queries: `hybridQuerySkills` runs separate dense and sparse
18
- // queries (no Qdrant-side RRF). Callers do their own weighted-sum fusion using
19
- // `dense_weight` / `sparse_weight` from `config.memory.v2`, which RRF fusion
20
- // would discard.
21
-
22
- import { QdrantClient as QdrantRestClient } from "@qdrant/js-client-rest";
23
- import { v5 as uuidv5 } from "uuid";
24
-
25
- import { getConfig } from "../../config/loader.js";
26
- import { getLogger } from "../../util/logger.js";
27
- import type { SparseEmbedding } from "../embedding-types.js";
28
- import { resolveQdrantUrl } from "../qdrant-client.js";
29
-
30
- const log = getLogger("memory-v2-skill-qdrant");
31
-
32
- /** Name of the dedicated Qdrant collection holding skill embeddings. */
33
- export const MEMORY_V2_SKILLS_COLLECTION = "memory_v2_skills";
34
-
35
- /**
36
- * Stable UUIDv5 namespace used to derive a deterministic Qdrant point ID from
37
- * a skill id. The namespace is an arbitrary fixed UUID; what matters is that
38
- * the same id always maps to the same point ID so upserts replace in place
39
- * instead of accumulating duplicates. Distinct from `qdrant.ts`'s
40
- * `SLUG_NAMESPACE` so a skill id that happens to collide with a concept-page
41
- * slug still maps to a different point ID across the two collections.
42
- */
43
- export const SKILL_NAMESPACE = "f1903e7f-1b9d-4c15-ac46-3540b8b0a9f6";
44
-
45
- /**
46
- * Per-channel score for a single skill hit returned by hybrid query.
47
- * Module-private — `sim.ts` consumes the fields by duck-typing rather than
48
- * naming the type, so there is no benefit to exporting it.
49
- */
50
- interface SkillQueryResult {
51
- id: string;
52
- /**
53
- * Dense cosine similarity, when the id appeared in the dense top-`limit`.
54
- * `undefined` if the id only appeared in the sparse channel.
55
- */
56
- denseScore?: number;
57
- /**
58
- * Sparse score, when the id appeared in the sparse top-`limit`.
59
- * `undefined` if the id only appeared in the dense channel. Lives on a
60
- * different scale than `denseScore` — callers must normalize before fusing.
61
- */
62
- sparseScore?: number;
63
- }
64
-
65
- let _client: QdrantRestClient | null = null;
66
- let _collectionReady = false;
67
-
68
- /** Lazily create a Qdrant REST client bound to the resolved URL. */
69
- function getClient(): QdrantRestClient {
70
- if (_client) return _client;
71
- const config = getConfig();
72
- _client = new QdrantRestClient({
73
- url: resolveQdrantUrl(config),
74
- checkCompatibility: false,
75
- });
76
- return _client;
77
- }
78
-
79
- /**
80
- * Create the v2 skills collection if it does not already exist.
81
- * Idempotent: a no-op when the collection is already present.
82
- *
83
- * Vector layout mirrors `ensureConceptPageCollection` — named dense (cosine,
84
- * configurable size + on-disk) and sparse vectors. The vector size and
85
- * on-disk flag inherit from `config.memory.qdrant` so v2 stays aligned with
86
- * the user's existing embedding backend without separate knobs.
87
- */
88
- export async function ensureSkillCollection(): Promise<void> {
89
- if (_collectionReady) return;
90
-
91
- const client = getClient();
92
- const config = getConfig();
93
- const vectorSize = config.memory.qdrant.vectorSize;
94
- const onDisk = config.memory.qdrant.onDisk;
95
-
96
- try {
97
- const exists = await client.collectionExists(MEMORY_V2_SKILLS_COLLECTION);
98
- if (exists.exists) {
99
- _collectionReady = true;
100
- return;
101
- }
102
- } catch (err) {
103
- // Treat "not found"-shaped errors as "needs creation" and fall through.
104
- if (!isCollectionMissing(err)) throw err;
105
- }
106
-
107
- log.info(
108
- { collection: MEMORY_V2_SKILLS_COLLECTION, vectorSize },
109
- "Creating Qdrant collection for memory v2 skills",
110
- );
111
-
112
- try {
113
- await client.createCollection(MEMORY_V2_SKILLS_COLLECTION, {
114
- vectors: {
115
- dense: {
116
- size: vectorSize,
117
- distance: "Cosine",
118
- on_disk: onDisk,
119
- },
120
- },
121
- sparse_vectors: {
122
- sparse: {}, // Qdrant auto-infers sparse vector params
123
- },
124
- hnsw_config: {
125
- on_disk: onDisk,
126
- m: 16,
127
- ef_construct: 100,
128
- },
129
- on_disk_payload: onDisk,
130
- });
131
- } catch (err) {
132
- // 409 = a concurrent caller created the collection — that's fine.
133
- if (
134
- err instanceof Error &&
135
- "status" in err &&
136
- (err as { status: number }).status === 409
137
- ) {
138
- _collectionReady = true;
139
- return;
140
- }
141
- throw err;
142
- }
143
-
144
- // Eagerly index `id` so future per-id filters (e.g. inspecting a single
145
- // skill's stored payload) don't pay a one-time indexing cost. Mirrors the
146
- // up-front `slug` index in `ensureConceptPageCollection`.
147
- await client.createPayloadIndex(MEMORY_V2_SKILLS_COLLECTION, {
148
- field_name: "id",
149
- field_schema: "keyword",
150
- });
151
-
152
- _collectionReady = true;
153
- }
154
-
155
- /**
156
- * Upsert a skill's dense + sparse embedding. The point ID is derived
157
- * deterministically from the skill id so subsequent calls for the same id
158
- * replace the prior point in place rather than accumulating duplicates.
159
- */
160
- export async function upsertSkillEmbedding(params: {
161
- id: string;
162
- content: string;
163
- dense: number[];
164
- sparse: SparseEmbedding;
165
- updatedAt: number;
166
- }): Promise<void> {
167
- await ensureSkillCollection();
168
-
169
- const { id, content, dense, sparse, updatedAt } = params;
170
- const client = getClient();
171
- const pointId = pointIdForId(id);
172
-
173
- const upsertOnce = () =>
174
- client.upsert(MEMORY_V2_SKILLS_COLLECTION, {
175
- wait: true,
176
- points: [
177
- {
178
- id: pointId,
179
- vector: { dense, sparse },
180
- payload: {
181
- id,
182
- content,
183
- updated_at: updatedAt,
184
- },
185
- },
186
- ],
187
- });
188
-
189
- try {
190
- await upsertOnce();
191
- } catch (err) {
192
- if (isCollectionMissing(err)) {
193
- _collectionReady = false;
194
- await ensureSkillCollection();
195
- await upsertOnce();
196
- return;
197
- }
198
- throw err;
199
- }
200
- }
201
-
202
- /**
203
- * Remove every skill point whose `payload.id` is not in `activeIds`. Used by
204
- * `seedV2SkillEntries` to drop stale points after a skill is uninstalled or
205
- * disabled. Idempotent: when the live points already equal `activeIds`,
206
- * the function performs a single scroll and no deletes.
207
- *
208
- * Implementation: paginate the collection with the Qdrant `scroll` API
209
- * (payload-only, no vectors) and delete by point ID for any payload whose
210
- * `id` is missing from the active set.
211
- */
212
- export async function pruneSkillsExcept(
213
- activeIds: readonly string[],
214
- ): Promise<void> {
215
- await ensureSkillCollection();
216
-
217
- const client = getClient();
218
- const activeSet = new Set(activeIds);
219
-
220
- const doPrune = async (): Promise<void> => {
221
- const stalePointIds: Array<string | number> = [];
222
- let offset: string | number | undefined = undefined;
223
- // Guard against a pathological pagination loop.
224
- const maxIterations = 10_000;
225
- const batchSize = 256;
226
- for (let i = 0; i < maxIterations; i++) {
227
- const result = await client.scroll(MEMORY_V2_SKILLS_COLLECTION, {
228
- limit: batchSize,
229
- with_payload: true,
230
- with_vector: false,
231
- ...(offset !== undefined ? { offset } : {}),
232
- });
233
- for (const point of result.points) {
234
- const payloadId = (point.payload as { id?: unknown } | null)?.id;
235
- if (typeof payloadId !== "string") continue;
236
- if (!activeSet.has(payloadId)) {
237
- stalePointIds.push(point.id);
238
- }
239
- }
240
- const next = result.next_page_offset;
241
- if (next == null) break;
242
- offset = typeof next === "string" ? next : (next as number);
243
- }
244
-
245
- if (stalePointIds.length === 0) return;
246
-
247
- await client.delete(MEMORY_V2_SKILLS_COLLECTION, {
248
- wait: true,
249
- points: stalePointIds,
250
- });
251
- };
252
-
253
- try {
254
- await doPrune();
255
- } catch (err) {
256
- if (isCollectionMissing(err)) {
257
- _collectionReady = false;
258
- await ensureSkillCollection();
259
- await doPrune();
260
- return;
261
- }
262
- throw err;
263
- }
264
- }
265
-
266
- /**
267
- * Run separate dense and sparse queries against the skills collection and
268
- * return per-channel scores per skill id. Callers fuse these — typically via
269
- * a normalized weighted-sum — because RRF would discard the score magnitudes
270
- * the activation formula needs.
271
- *
272
- * Each channel returns up to `limit` hits. A skill is included in the result
273
- * if it appears in either channel; the missing channel's score is left
274
- * `undefined` so callers can detect single-channel matches.
275
- *
276
- * `restrictToIds`, when provided, filters the search server-side to only
277
- * those ids (Qdrant `id IN [...]` filter). Used by `simSkillBatch` when the
278
- * candidate set is already known so the activation scorer gets scores for
279
- * exactly those ids rather than Qdrant's global top-`limit`. An empty list
280
- * short-circuits to no results — the caller is asking for "nothing", not
281
- * "everything". Undefined queries the full collection.
282
- */
283
- export async function hybridQuerySkills(
284
- dense: number[],
285
- sparse: SparseEmbedding,
286
- limit: number,
287
- restrictToIds?: readonly string[],
288
- options?: { skipSparse?: boolean },
289
- ): Promise<SkillQueryResult[]> {
290
- if (restrictToIds && restrictToIds.length === 0) {
291
- // An empty restriction means "no candidates"; skip the round-trip.
292
- return [];
293
- }
294
-
295
- await ensureSkillCollection();
296
-
297
- const client = getClient();
298
- const filter = restrictToIds
299
- ? { must: [{ key: "id", match: { any: [...restrictToIds] } }] }
300
- : undefined;
301
-
302
- // Same opt-in short-circuit as `hybridQueryConceptPages`: skip the sparse
303
- // round-trip entirely so we sidestep the Qdrant 1.13.x sparse-index OOM
304
- // crash when operators flip sparse off via `sparse_weight: 0`.
305
- const skipSparse = options?.skipSparse ?? false;
306
-
307
- const denseQuery = () =>
308
- client.query(MEMORY_V2_SKILLS_COLLECTION, {
309
- query: dense,
310
- using: "dense",
311
- limit,
312
- with_payload: true,
313
- filter,
314
- });
315
- const sparseQuery = () =>
316
- client.query(MEMORY_V2_SKILLS_COLLECTION, {
317
- query: sparse,
318
- using: "sparse",
319
- limit,
320
- with_payload: true,
321
- filter,
322
- });
323
-
324
- // Run both queries concurrently — they hit independent named vectors.
325
- const emptyResult = {
326
- points: [] as Array<{ payload?: unknown; score?: number }>,
327
- };
328
- const runQueries = async () =>
329
- Promise.all([denseQuery(), skipSparse ? emptyResult : sparseQuery()]);
330
-
331
- let denseResults;
332
- let sparseResults;
333
- try {
334
- [denseResults, sparseResults] = await runQueries();
335
- } catch (err) {
336
- if (isCollectionMissing(err)) {
337
- _collectionReady = false;
338
- await ensureSkillCollection();
339
- [denseResults, sparseResults] = await runQueries();
340
- } else {
341
- throw err;
342
- }
343
- }
344
-
345
- // Merge by id. Missing-side scores stay undefined so the fuser can tell
346
- // "no match in this channel" apart from "match with score 0".
347
- const merged = new Map<string, SkillQueryResult>();
348
- for (const point of denseResults.points ?? []) {
349
- const id = (point.payload as { id?: unknown } | null)?.id;
350
- if (typeof id !== "string") continue;
351
- merged.set(id, { id, denseScore: point.score ?? 0 });
352
- }
353
- for (const point of sparseResults.points ?? []) {
354
- const id = (point.payload as { id?: unknown } | null)?.id;
355
- if (typeof id !== "string") continue;
356
- const existing = merged.get(id);
357
- if (existing) {
358
- existing.sparseScore = point.score ?? 0;
359
- } else {
360
- merged.set(id, { id, sparseScore: point.score ?? 0 });
361
- }
362
- }
363
-
364
- return Array.from(merged.values());
365
- }
366
-
367
- /**
368
- * Detect "collection not found" errors so callers can reset readiness and
369
- * retry after an external deletion (e.g. workspace reset). Re-implemented
370
- * locally rather than imported from `qdrant.ts` to keep this module
371
- * self-contained — the helper is small and the duplication is cleaner than
372
- * exporting an internal detail across files.
373
- */
374
- function isCollectionMissing(err: unknown): boolean {
375
- if (
376
- err &&
377
- typeof err === "object" &&
378
- "status" in err &&
379
- (err as { status: number }).status === 404
380
- ) {
381
- return true;
382
- }
383
- const msg = err instanceof Error ? err.message : String(err);
384
- return (
385
- msg.includes("Not found") ||
386
- msg.includes("doesn't exist") ||
387
- msg.includes("not found")
388
- );
389
- }
390
-
391
- /**
392
- * Derive the deterministic Qdrant point ID for a skill id. Qdrant requires
393
- * UUID/integer IDs; UUIDv5 keeps the mapping stable across processes so
394
- * upserts replace in place.
395
- */
396
- function pointIdForId(id: string): string {
397
- return uuidv5(id, SKILL_NAMESPACE);
398
- }
399
-
400
- /** @internal Test-only: reset module-level singletons. */
401
- export function _resetMemoryV2SkillQdrantForTests(): void {
402
- _client = null;
403
- _collectionReady = false;
404
- }
@@ -1,198 +0,0 @@
1
- /**
2
- * Handle bash debug signals delivered via signal files from the CLI.
3
- *
4
- * Each invocation writes JSON to a unique `signals/bash.<requestId>` file.
5
- * ConfigWatcher detects the new file and invokes {@link handleBashSignal},
6
- * which reads the payload, spawns the command, and writes the result to
7
- * `signals/bash.<requestId>.result` for the CLI to pick up.
8
- *
9
- * Per-request filenames avoid dropped commands when overlapping invocations
10
- * race on the same signal file.
11
- *
12
- * **Security**: This handler is gated behind the `VELLUM_DEBUG` environment
13
- * variable. When debug mode is off (the default), the daemon ignores bash
14
- * signal files entirely. This prevents untrusted file-write operations
15
- * (e.g. from prompt injection or a compromised skill) from bypassing the
16
- * normal tool-approval flow for shell execution.
17
- */
18
-
19
- import { spawn } from "node:child_process";
20
- import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
21
- import { join } from "node:path";
22
-
23
- import { getIsContainerized } from "../config/env-registry.js";
24
- import { getLogger } from "../util/logger.js";
25
- import { getSignalsDir, getWorkspaceDir } from "../util/platform.js";
26
-
27
- const log = getLogger("signal:bash");
28
-
29
- function isDebugMode(): boolean {
30
- return (
31
- process.env.VELLUM_DEBUG === "1" || process.env.VELLUM_DEBUG === "true"
32
- );
33
- }
34
-
35
- const DEFAULT_TIMEOUT_MS = 30_000;
36
-
37
- interface BashSignalPayload {
38
- requestId: string;
39
- command: string;
40
- timeoutMs?: number;
41
- }
42
-
43
- interface BashSignalResult {
44
- requestId: string;
45
- stdout: string;
46
- stderr: string;
47
- exitCode: number | null;
48
- timedOut: boolean;
49
- error?: string;
50
- }
51
-
52
- function writeResult(requestId: string, result: BashSignalResult): void {
53
- try {
54
- writeFileSync(
55
- join(getSignalsDir(), `bash.${requestId}.result`),
56
- JSON.stringify(result),
57
- );
58
- } catch (err) {
59
- log.error({ err, requestId }, "Failed to write bash signal result");
60
- }
61
- }
62
-
63
- /**
64
- * Read a `signals/bash.<requestId>` file, execute the command, and write
65
- * the result to `signals/bash.<requestId>.result`. Called by ConfigWatcher
66
- * when a matching signal file is created or modified.
67
- */
68
- export function handleBashSignal(filename: string): void {
69
- if (getIsContainerized()) return;
70
-
71
- if (!isDebugMode()) {
72
- log.warn(
73
- { filename },
74
- "Bash signal ignored — debug mode is not enabled (set VELLUM_DEBUG=1)",
75
- );
76
- // Write an error result so the CLI gets a clear rejection instead of timing out.
77
- const match = filename.match(/^bash\.(.+)$/);
78
- if (match) {
79
- writeResult(match[1], {
80
- requestId: match[1],
81
- stdout: "",
82
- stderr: "",
83
- exitCode: null,
84
- timedOut: false,
85
- error:
86
- "Bash signals are disabled. The running assistant process must have been started with VELLUM_DEBUG=1 (setting it on the CLI command alone is not enough). Restart the assistant with: vellum sleep && VELLUM_DEBUG=1 vellum wake",
87
- });
88
- }
89
- return;
90
- }
91
-
92
- const signalPath = join(getSignalsDir(), filename);
93
- let raw: string;
94
- try {
95
- raw = readFileSync(signalPath, "utf-8");
96
- } catch {
97
- // File may already be deleted (e.g. re-trigger from our own unlinkSync).
98
- return;
99
- }
100
-
101
- let payload: BashSignalPayload;
102
- try {
103
- payload = JSON.parse(raw) as BashSignalPayload;
104
- } catch (err) {
105
- log.error({ err, filename }, "Failed to parse bash signal file");
106
- return;
107
- }
108
-
109
- try {
110
- unlinkSync(signalPath);
111
- } catch {
112
- // Best-effort cleanup; the file may already be gone.
113
- }
114
-
115
- const { requestId, command, timeoutMs } = payload;
116
-
117
- if (!requestId || typeof requestId !== "string") {
118
- log.warn("Bash signal missing requestId");
119
- return;
120
- }
121
- if (!command || typeof command !== "string") {
122
- log.warn({ requestId }, "Bash signal missing command");
123
- return;
124
- }
125
-
126
- const effectiveTimeout =
127
- typeof timeoutMs === "number" && timeoutMs > 0
128
- ? timeoutMs
129
- : DEFAULT_TIMEOUT_MS;
130
-
131
- log.info({ requestId, command }, "Executing bash signal command");
132
-
133
- const stdoutChunks: Buffer[] = [];
134
- const stderrChunks: Buffer[] = [];
135
- let timedOut = false;
136
- let resultWritten = false;
137
-
138
- const child = spawn("bash", ["-c", command], {
139
- cwd: getWorkspaceDir(),
140
- stdio: ["ignore", "pipe", "pipe"],
141
- detached: true,
142
- });
143
-
144
- const timer = setTimeout(() => {
145
- timedOut = true;
146
- try {
147
- process.kill(-child.pid!, "SIGKILL");
148
- } catch {
149
- // Process group may have already exited.
150
- }
151
- }, effectiveTimeout);
152
-
153
- child.stdout.on("data", (data: Buffer) => {
154
- stdoutChunks.push(data);
155
- });
156
-
157
- child.stderr.on("data", (data: Buffer) => {
158
- stderrChunks.push(data);
159
- });
160
-
161
- child.on("close", (code) => {
162
- clearTimeout(timer);
163
- if (resultWritten) return;
164
- resultWritten = true;
165
-
166
- const stdout = Buffer.concat(stdoutChunks).toString();
167
- const stderr = Buffer.concat(stderrChunks).toString();
168
-
169
- log.info(
170
- { requestId, exitCode: code, timedOut },
171
- "Bash signal command completed",
172
- );
173
-
174
- writeResult(requestId, {
175
- requestId,
176
- stdout,
177
- stderr,
178
- exitCode: code,
179
- timedOut,
180
- });
181
- });
182
-
183
- child.on("error", (err) => {
184
- clearTimeout(timer);
185
- if (resultWritten) return;
186
- resultWritten = true;
187
-
188
- log.error({ err, requestId }, "Failed to spawn bash signal command");
189
- writeResult(requestId, {
190
- requestId,
191
- stdout: "",
192
- stderr: "",
193
- exitCode: null,
194
- timedOut: false,
195
- error: err.message,
196
- });
197
- });
198
- }