@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
@@ -3,16 +3,36 @@ import { afterEach, describe, expect, jest, mock, test } from "bun:test";
3
3
  const sentMessages: unknown[] = [];
4
4
  let mockHasClient = false;
5
5
 
6
+ interface MockClient {
7
+ clientId: string;
8
+ capabilities: string[];
9
+ actorPrincipalId?: string;
10
+ }
11
+
12
+ let mockClients: MockClient[] = [];
13
+
6
14
  mock.module("../runtime/assistant-event-hub.js", () => ({
7
15
  broadcastMessage: (msg: unknown) => sentMessages.push(msg),
8
16
  assistantEventHub: {
9
- getMostRecentClientByCapability: (cap: string) =>
10
- cap === "host_file" && mockHasClient ? { id: "mock-client" } : null,
11
- listClientsByCapability: (cap: string) =>
12
- cap === "host_file" && mockHasClient
17
+ getMostRecentClientByCapability: (cap: string) => {
18
+ if (mockClients.length > 0) {
19
+ return mockClients.find((c) => c.capabilities.includes(cap));
20
+ }
21
+ return cap === "host_file" && mockHasClient
22
+ ? { id: "mock-client" }
23
+ : null;
24
+ },
25
+ listClientsByCapability: (cap: string) => {
26
+ if (mockClients.length > 0) {
27
+ return mockClients.filter((c) => c.capabilities.includes(cap));
28
+ }
29
+ return cap === "host_file" && mockHasClient
13
30
  ? [{ clientId: "mock-client", capabilities: ["host_file"] }]
14
- : [],
15
- getClientById: (_id: string) => undefined,
31
+ : [];
32
+ },
33
+ getClientById: (id: string) => mockClients.find((c) => c.clientId === id),
34
+ getActorPrincipalIdForClient: (id: string) =>
35
+ mockClients.find((c) => c.clientId === id)?.actorPrincipalId,
16
36
  },
17
37
  }));
18
38
 
@@ -32,6 +52,7 @@ describe("HostFileProxy", () => {
32
52
  function setup() {
33
53
  sentMessages.length = 0;
34
54
  mockHasClient = false;
55
+ mockClients = [];
35
56
  pendingInteractions.clear();
36
57
  proxy = new (HostFileProxy as any)();
37
58
  }
@@ -584,4 +605,245 @@ describe("HostFileProxy", () => {
584
605
  expect(pendingInteractions.get(requestId)).toBeUndefined();
585
606
  });
586
607
  });
608
+
609
+ describe("same-user binding (sourceActorPrincipalId)", () => {
610
+ test("targeted request from same user reaches pendingInteractions", async () => {
611
+ setup();
612
+ mockClients = [
613
+ {
614
+ clientId: "client-A",
615
+ capabilities: ["host_file"],
616
+ actorPrincipalId: "user-A",
617
+ },
618
+ ];
619
+
620
+ const resultPromise = proxy.request(
621
+ { operation: "read", path: "/tmp/test.txt" },
622
+ "session-1",
623
+ undefined,
624
+ "client-A",
625
+ "user-A",
626
+ );
627
+
628
+ // Request was registered (made it past the same-user gate).
629
+ expect(sentMessages).toHaveLength(1);
630
+ const sent = sentMessages[0] as Record<string, unknown>;
631
+ const requestId = sent.requestId as string;
632
+ expect(pendingInteractions.get(requestId)).toBeDefined();
633
+
634
+ // Drain to avoid leaks.
635
+ proxy.resolve(requestId, { content: "ok", isError: false });
636
+ await resultPromise;
637
+ });
638
+
639
+ test("targeted request from a different user is rejected", async () => {
640
+ setup();
641
+ mockClients = [
642
+ {
643
+ clientId: "client-A",
644
+ capabilities: ["host_file"],
645
+ actorPrincipalId: "user-A",
646
+ },
647
+ ];
648
+
649
+ const result = await proxy.request(
650
+ { operation: "read", path: "/tmp/test.txt" },
651
+ "session-1",
652
+ undefined,
653
+ "client-A",
654
+ "user-B",
655
+ );
656
+
657
+ expect(result.isError).toBe(true);
658
+ expect(result.content).toContain(
659
+ "Submitting actor does not match the target client's actor",
660
+ );
661
+ // No host_file_request was broadcast.
662
+ expect(sentMessages).toHaveLength(0);
663
+ });
664
+
665
+ test("targeted request to a client with no actor principal is rejected", async () => {
666
+ setup();
667
+ mockClients = [
668
+ {
669
+ clientId: "client-A",
670
+ capabilities: ["host_file"],
671
+ // actorPrincipalId omitted (legacy/service-token client).
672
+ },
673
+ ];
674
+
675
+ const result = await proxy.request(
676
+ { operation: "read", path: "/tmp/test.txt" },
677
+ "session-1",
678
+ undefined,
679
+ "client-A",
680
+ "user-A",
681
+ );
682
+
683
+ expect(result.isError).toBe(true);
684
+ expect(result.content).toContain(
685
+ "Submitting actor does not match the target client's actor",
686
+ );
687
+ expect(sentMessages).toHaveLength(0);
688
+ });
689
+
690
+ test("targeted request without source principal is rejected", async () => {
691
+ setup();
692
+ mockClients = [
693
+ {
694
+ clientId: "client-A",
695
+ capabilities: ["host_file"],
696
+ actorPrincipalId: "user-A",
697
+ },
698
+ ];
699
+
700
+ const result = await proxy.request(
701
+ { operation: "read", path: "/tmp/test.txt" },
702
+ "session-1",
703
+ undefined,
704
+ "client-A",
705
+ undefined,
706
+ );
707
+
708
+ expect(result.isError).toBe(true);
709
+ expect(result.content).toContain(
710
+ "Submitting actor does not match the target client's actor",
711
+ );
712
+ expect(sentMessages).toHaveLength(0);
713
+ });
714
+
715
+ test("untargeted request with no auto-resolve match still broadcasts (legacy path unchanged)", async () => {
716
+ setup();
717
+ // No matching same-user clients available.
718
+ mockClients = [
719
+ {
720
+ clientId: "client-A",
721
+ capabilities: ["host_file"],
722
+ actorPrincipalId: "user-A",
723
+ },
724
+ ];
725
+
726
+ const resultPromise = proxy.request(
727
+ { operation: "read", path: "/tmp/test.txt" },
728
+ "session-1",
729
+ undefined,
730
+ undefined,
731
+ "user-B", // No same-user match → no auto-resolve, broadcast untargeted.
732
+ );
733
+
734
+ expect(sentMessages).toHaveLength(1);
735
+ const sent = sentMessages[0] as Record<string, unknown>;
736
+ expect(sent.targetClientId).toBeUndefined();
737
+ const requestId = sent.requestId as string;
738
+
739
+ proxy.resolve(requestId, { content: "ok", isError: false });
740
+ await resultPromise;
741
+ });
742
+
743
+ test("auto-resolve picks the same-user client when there's exactly one", async () => {
744
+ setup();
745
+ mockClients = [
746
+ {
747
+ clientId: "client-A",
748
+ capabilities: ["host_file"],
749
+ actorPrincipalId: "user-A",
750
+ },
751
+ {
752
+ clientId: "client-B",
753
+ capabilities: ["host_file"],
754
+ actorPrincipalId: "user-B",
755
+ },
756
+ ];
757
+
758
+ const resultPromise = proxy.request(
759
+ { operation: "read", path: "/tmp/test.txt" },
760
+ "session-1",
761
+ undefined,
762
+ undefined,
763
+ "user-A",
764
+ );
765
+
766
+ // Auto-resolved to client-A and broadcast targeted at it.
767
+ expect(sentMessages).toHaveLength(1);
768
+ const sent = sentMessages[0] as Record<string, unknown>;
769
+ expect(sent.targetClientId).toBe("client-A");
770
+ const requestId = sent.requestId as string;
771
+
772
+ proxy.resolve(requestId, { content: "ok", isError: false });
773
+ await resultPromise;
774
+ });
775
+
776
+ test("auto-resolve falls through when no client matches the source user", async () => {
777
+ setup();
778
+ mockClients = [
779
+ {
780
+ clientId: "client-A",
781
+ capabilities: ["host_file"],
782
+ actorPrincipalId: "user-A",
783
+ },
784
+ ];
785
+
786
+ const resultPromise = proxy.request(
787
+ { operation: "read", path: "/tmp/test.txt" },
788
+ "session-1",
789
+ undefined,
790
+ undefined,
791
+ "user-C",
792
+ );
793
+
794
+ // No same-user client → no auto-resolve, broadcast untargeted.
795
+ expect(sentMessages).toHaveLength(1);
796
+ const sent = sentMessages[0] as Record<string, unknown>;
797
+ expect(sent.targetClientId).toBeUndefined();
798
+ const requestId = sent.requestId as string;
799
+
800
+ proxy.resolve(requestId, { content: "ok", isError: false });
801
+ await resultPromise;
802
+ });
803
+
804
+ test("legacy embedded targetClientId in input still goes through the same-user gate", async () => {
805
+ setup();
806
+ mockClients = [
807
+ {
808
+ clientId: "client-A",
809
+ capabilities: ["host_file"],
810
+ actorPrincipalId: "user-A",
811
+ },
812
+ ];
813
+
814
+ // Same-user via embedded input.targetClientId — should succeed.
815
+ const okPromise = proxy.request(
816
+ {
817
+ operation: "read",
818
+ path: "/tmp/ok.txt",
819
+ targetClientId: "client-A",
820
+ },
821
+ "session-1",
822
+ undefined,
823
+ undefined,
824
+ "user-A",
825
+ );
826
+ expect(sentMessages).toHaveLength(1);
827
+ const okRequestId = (sentMessages[0] as Record<string, unknown>)
828
+ .requestId as string;
829
+ proxy.resolve(okRequestId, { content: "ok", isError: false });
830
+ await okPromise;
831
+
832
+ // Cross-user via embedded input.targetClientId — should be rejected.
833
+ sentMessages.length = 0;
834
+ const rejectResult = await proxy.request(
835
+ {
836
+ operation: "read",
837
+ path: "/tmp/bad.txt",
838
+ targetClientId: "client-A",
839
+ },
840
+ "session-1",
841
+ undefined,
842
+ undefined,
843
+ "user-B",
844
+ );
845
+ expect(rejectResult.isError).toBe(true);
846
+ expect(sentMessages).toHaveLength(0);
847
+ });
848
+ });
587
849
  });
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Tests for the host-file-result route 403 guard introduced in Phase 2.
2
+ * Tests for the host-file-result route 403 guard.
3
3
  *
4
4
  * Covers:
5
- * 1. Targeted + correct x-vellum-client-id header → 200 accepted
6
- * 2. Targeted + missing header → 400 BadRequestError
7
- * 3. Targeted + wrong header → 403 ForbiddenError, interaction NOT consumed
5
+ * 1. Targeted + correct x-vellum-client-id header (and matching actor) → 200
6
+ * 2. Targeted + missing client-id header → 400 BadRequestError
7
+ * 3. Targeted + wrong client-id header → 403 ForbiddenError, interaction NOT consumed
8
8
  * 4. Untargeted (no targetClientId, no header) → 200 accepted (regression)
9
+ * 5. Targeted + matching client-id but mismatched actor principal → 403, NOT consumed
10
+ * 6. Targeted + matching client-id but missing actor principal header → 403, NOT consumed
11
+ * 7. Targeted + matching client-id but target client has no stored actor → 403, NOT consumed
9
12
  */
10
13
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
11
14
 
@@ -61,12 +64,20 @@ mock.module("../daemon/host-file-proxy.js", () => ({
61
64
  },
62
65
  }));
63
66
 
67
+ // Stub event hub so tests control what actorPrincipalId is associated with
68
+ // each connected client.
69
+ const clientActors = new Map<string, string>();
70
+
71
+ mock.module("../runtime/assistant-event-hub.js", () => ({
72
+ assistantEventHub: {
73
+ getActorPrincipalIdForClient: (clientId: string) =>
74
+ clientActors.get(clientId),
75
+ },
76
+ }));
77
+
64
78
  // ── Real imports (after mocks) ──────────────────────────────────────────────
65
79
 
66
- import {
67
- BadRequestError,
68
- ForbiddenError,
69
- } from "../runtime/routes/errors.js";
80
+ import { BadRequestError, ForbiddenError } from "../runtime/routes/errors.js";
70
81
  import { ROUTES } from "../runtime/routes/host-file-routes.js";
71
82
 
72
83
  afterAll(() => {
@@ -83,10 +94,16 @@ function registerPending(
83
94
  requestId: string,
84
95
  overrides: Partial<PendingInteraction> = {},
85
96
  ): void {
97
+ const targetActorPrincipalId =
98
+ overrides.targetActorPrincipalId ??
99
+ (overrides.targetClientId
100
+ ? clientActors.get(overrides.targetClientId)
101
+ : undefined);
86
102
  pendingStore.set(requestId, {
87
103
  conversationId: "conv-1",
88
104
  kind: "host_file",
89
105
  ...overrides,
106
+ targetActorPrincipalId,
90
107
  });
91
108
  }
92
109
 
@@ -100,23 +117,28 @@ function fileBody(requestId: string): Record<string, unknown> {
100
117
 
101
118
  // ── Tests ────────────────────────────────────────────────────────────────────
102
119
 
103
- describe("handleHostFileResult — Phase 2 targetClientId guard", () => {
120
+ describe("handleHostFileResult — targetClientId guard", () => {
104
121
  beforeEach(() => {
105
122
  pendingStore.clear();
106
123
  resolvedIds.length = 0;
107
124
  resolveSpy.length = 0;
125
+ clientActors.clear();
108
126
  });
109
127
 
110
- // ── 1. Targeted + correct header → 200 ────────────────────────────────────
128
+ // ── 1. Targeted + correct headers (client + actor) → 200 ──────────────────
111
129
 
112
130
  describe("targeted + correct x-vellum-client-id header", () => {
113
131
  test("returns { accepted: true } and resolves the interaction", async () => {
114
132
  const requestId = "req-file-targeted-match";
133
+ clientActors.set("client-A", "actor-1");
115
134
  registerPending(requestId, { targetClientId: "client-A" });
116
135
 
117
136
  const result = await handleHostFileResult({
118
137
  body: fileBody(requestId),
119
- headers: { "x-vellum-client-id": "client-A" },
138
+ headers: {
139
+ "x-vellum-client-id": "client-A",
140
+ "x-vellum-actor-principal-id": "actor-1",
141
+ },
120
142
  });
121
143
 
122
144
  expect(result).toEqual({ accepted: true });
@@ -127,27 +149,31 @@ describe("handleHostFileResult — Phase 2 targetClientId guard", () => {
127
149
 
128
150
  test("trims whitespace from header before comparing", async () => {
129
151
  const requestId = "req-file-targeted-trim";
152
+ clientActors.set("client-A", "actor-1");
130
153
  registerPending(requestId, { targetClientId: "client-A" });
131
154
 
132
155
  const result = await handleHostFileResult({
133
156
  body: fileBody(requestId),
134
- headers: { "x-vellum-client-id": " client-A " },
157
+ headers: {
158
+ "x-vellum-client-id": " client-A ",
159
+ "x-vellum-actor-principal-id": " actor-1 ",
160
+ },
135
161
  });
136
162
 
137
163
  expect(result).toEqual({ accepted: true });
138
164
  });
139
165
  });
140
166
 
141
- // ── 2. Targeted + missing header → 400 ────────────────────────────────────
167
+ // ── 2. Targeted + missing client-id header → 400 ──────────────────────────
142
168
 
143
169
  describe("targeted + missing x-vellum-client-id header", () => {
144
170
  test("throws BadRequestError (400) when header is absent", () => {
145
171
  const requestId = "req-file-targeted-no-header";
146
172
  registerPending(requestId, { targetClientId: "client-A" });
147
173
 
148
- expect(() =>
149
- handleHostFileResult({ body: fileBody(requestId) }),
150
- ).toThrow(BadRequestError);
174
+ expect(() => handleHostFileResult({ body: fileBody(requestId) })).toThrow(
175
+ BadRequestError,
176
+ );
151
177
  });
152
178
 
153
179
  test("throws BadRequestError (400) when header is empty string", () => {
@@ -177,7 +203,7 @@ describe("handleHostFileResult — Phase 2 targetClientId guard", () => {
177
203
  });
178
204
  });
179
205
 
180
- // ── 3. Targeted + wrong header → 403 ──────────────────────────────────────
206
+ // ── 3. Targeted + wrong client-id header → 403 ────────────────────────────
181
207
 
182
208
  describe("targeted + wrong x-vellum-client-id header", () => {
183
209
  test("throws ForbiddenError (403) when client ID does not match", () => {
@@ -259,4 +285,136 @@ describe("handleHostFileResult — Phase 2 targetClientId guard", () => {
259
285
  expect(resolveSpy).toHaveLength(1);
260
286
  });
261
287
  });
288
+
289
+ // ── 5. Targeted + matching client but mismatched actor → 403 ──────────────
290
+
291
+ describe("targeted + actor principal mismatch", () => {
292
+ test("throws ForbiddenError (403) when submitting actor does not match target client's actor", () => {
293
+ const requestId = "req-file-actor-mismatch";
294
+ clientActors.set("client-A", "actor-1");
295
+ registerPending(requestId, { targetClientId: "client-A" });
296
+
297
+ expect(() =>
298
+ handleHostFileResult({
299
+ body: fileBody(requestId),
300
+ headers: {
301
+ "x-vellum-client-id": "client-A",
302
+ "x-vellum-actor-principal-id": "actor-2",
303
+ },
304
+ }),
305
+ ).toThrow(ForbiddenError);
306
+ });
307
+
308
+ test("interaction is NOT consumed on actor-mismatch 403", () => {
309
+ const requestId = "req-file-actor-mismatch-stays";
310
+ clientActors.set("client-A", "actor-1");
311
+ registerPending(requestId, { targetClientId: "client-A" });
312
+
313
+ try {
314
+ handleHostFileResult({
315
+ body: fileBody(requestId),
316
+ headers: {
317
+ "x-vellum-client-id": "client-A",
318
+ "x-vellum-actor-principal-id": "actor-2",
319
+ },
320
+ });
321
+ } catch {
322
+ // expected
323
+ }
324
+
325
+ expect(resolvedIds).not.toContain(requestId);
326
+ expect(pendingStore.has(requestId)).toBe(true);
327
+ });
328
+ });
329
+
330
+ // ── 6. Targeted + matching client but missing actor header → 403 ──────────
331
+
332
+ describe("targeted + missing x-vellum-actor-principal-id header", () => {
333
+ test("throws ForbiddenError (403) when submitting actor header is absent", () => {
334
+ const requestId = "req-file-actor-missing";
335
+ clientActors.set("client-A", "actor-1");
336
+ registerPending(requestId, { targetClientId: "client-A" });
337
+
338
+ expect(() =>
339
+ handleHostFileResult({
340
+ body: fileBody(requestId),
341
+ headers: { "x-vellum-client-id": "client-A" },
342
+ }),
343
+ ).toThrow(ForbiddenError);
344
+ });
345
+
346
+ test("throws ForbiddenError (403) when submitting actor header is empty string", () => {
347
+ const requestId = "req-file-actor-empty";
348
+ clientActors.set("client-A", "actor-1");
349
+ registerPending(requestId, { targetClientId: "client-A" });
350
+
351
+ expect(() =>
352
+ handleHostFileResult({
353
+ body: fileBody(requestId),
354
+ headers: {
355
+ "x-vellum-client-id": "client-A",
356
+ "x-vellum-actor-principal-id": " ",
357
+ },
358
+ }),
359
+ ).toThrow(ForbiddenError);
360
+ });
361
+
362
+ test("interaction is NOT consumed when submitting actor is missing", () => {
363
+ const requestId = "req-file-actor-missing-stays";
364
+ clientActors.set("client-A", "actor-1");
365
+ registerPending(requestId, { targetClientId: "client-A" });
366
+
367
+ try {
368
+ handleHostFileResult({
369
+ body: fileBody(requestId),
370
+ headers: { "x-vellum-client-id": "client-A" },
371
+ });
372
+ } catch {
373
+ // expected
374
+ }
375
+
376
+ expect(resolvedIds).not.toContain(requestId);
377
+ expect(pendingStore.has(requestId)).toBe(true);
378
+ });
379
+ });
380
+
381
+ // ── 7. Targeted + target client has no stored actor → 403 ─────────────────
382
+
383
+ describe("targeted + target client has no stored actor", () => {
384
+ test("throws ForbiddenError (403) when target client has no actorPrincipalId on record", () => {
385
+ const requestId = "req-file-target-no-actor";
386
+ registerPending(requestId, { targetClientId: "client-A" });
387
+ // intentionally do not set clientActors entry for client-A
388
+
389
+ expect(() =>
390
+ handleHostFileResult({
391
+ body: fileBody(requestId),
392
+ headers: {
393
+ "x-vellum-client-id": "client-A",
394
+ "x-vellum-actor-principal-id": "actor-1",
395
+ },
396
+ }),
397
+ ).toThrow(ForbiddenError);
398
+ });
399
+
400
+ test("interaction is NOT consumed when target client has no stored actor", () => {
401
+ const requestId = "req-file-target-no-actor-stays";
402
+ registerPending(requestId, { targetClientId: "client-A" });
403
+
404
+ try {
405
+ handleHostFileResult({
406
+ body: fileBody(requestId),
407
+ headers: {
408
+ "x-vellum-client-id": "client-A",
409
+ "x-vellum-actor-principal-id": "actor-1",
410
+ },
411
+ });
412
+ } catch {
413
+ // expected
414
+ }
415
+
416
+ expect(resolvedIds).not.toContain(requestId);
417
+ expect(pendingStore.has(requestId)).toBe(true);
418
+ });
419
+ });
262
420
  });