@vellumai/assistant 0.7.1 → 0.7.2

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 (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -71,38 +71,7 @@ function mockStore(
71
71
  // ---------------------------------------------------------------------------
72
72
 
73
73
  describe("executeAppCreate", () => {
74
- test("flag off: creates legacy app with root index.html", async () => {
75
- const files: Record<string, string> = {};
76
- let createdParams: Record<string, unknown> | undefined;
77
- const app = makeLegacyApp();
78
- const store: AppStore = {
79
- ...mockStore(app, files),
80
- createApp: (params) => {
81
- createdParams = params as unknown as Record<string, unknown>;
82
- return app;
83
- },
84
- };
85
-
86
- const result = await executeAppCreate(
87
- {
88
- name: "Test App",
89
- html: "<html><body>Hello</body></html>",
90
- },
91
- store,
92
- );
93
-
94
- expect(result.isError).toBe(false);
95
- // Legacy path: no formatVersion set, htmlDefinition is the provided html
96
- expect(createdParams?.formatVersion).toBeUndefined();
97
- expect(createdParams?.htmlDefinition).toBe(
98
- "<html><body>Hello</body></html>",
99
- );
100
- // No src/ files should be written
101
- expect(files["src/index.html"]).toBeUndefined();
102
- expect(files["src/main.tsx"]).toBeUndefined();
103
- });
104
-
105
- test("flag on: creates multifile app with src/ scaffold", async () => {
74
+ test("creates multifile app with src/ scaffold", async () => {
106
75
  const files: Record<string, string> = {};
107
76
  let createdParams: Record<string, unknown> | undefined;
108
77
  const app = makeMultifileApp({ name: "New App" });
@@ -117,7 +86,6 @@ describe("executeAppCreate", () => {
117
86
  const result = await executeAppCreate(
118
87
  {
119
88
  name: "New App",
120
- featureFlags: { multifileEnabled: true },
121
89
  },
122
90
  store,
123
91
  );
@@ -136,7 +104,7 @@ describe("executeAppCreate", () => {
136
104
  expect(files["src/main.tsx"]).toContain('{"Hello, New App!"}');
137
105
  });
138
106
 
139
- test("flag on with explicit html: uses provided html as src/index.html", async () => {
107
+ test("rejects retired html shortcut", async () => {
140
108
  const files: Record<string, string> = {};
141
109
  const app = makeMultifileApp({ name: "Custom App" });
142
110
  const store: AppStore = {
@@ -144,22 +112,41 @@ describe("executeAppCreate", () => {
144
112
  createApp: () => app,
145
113
  };
146
114
 
147
- const customHtml =
148
- '<!DOCTYPE html><html><head></head><body><div id="root"></div></body></html>';
149
115
  const result = await executeAppCreate(
150
116
  {
151
117
  name: "Custom App",
152
- html: customHtml,
153
- featureFlags: { multifileEnabled: true },
118
+ html: "<!DOCTYPE html><html><body></body></html>",
154
119
  },
155
120
  store,
156
121
  );
157
122
 
158
- expect(result.isError).toBe(false);
159
- // Explicit HTML should be used instead of scaffold
160
- expect(files["src/index.html"]).toBe(customHtml);
161
- // main.tsx scaffold should still be written
162
- expect(files["src/main.tsx"]).toBeDefined();
123
+ expect(result.isError).toBe(true);
124
+ const parsed = JSON.parse(result.content);
125
+ expect(parsed.error).toContain("app_create no longer accepts html");
126
+ expect(files["src/index.html"]).toBeUndefined();
127
+ expect(files["src/main.tsx"]).toBeUndefined();
128
+ });
129
+
130
+ test("rejects retired pages shortcut", async () => {
131
+ const files: Record<string, string> = {};
132
+ const app = makeMultifileApp({ name: "Custom App" });
133
+ const store = mockStore(app, files);
134
+
135
+ const result = await executeAppCreate(
136
+ {
137
+ name: "Custom App",
138
+ pages: {
139
+ "settings.html": "<!DOCTYPE html><html><body></body></html>",
140
+ },
141
+ },
142
+ store,
143
+ );
144
+
145
+ expect(result.isError).toBe(true);
146
+ const parsed = JSON.parse(result.content);
147
+ expect(parsed.error).toContain("app_create no longer accepts pages");
148
+ expect(files["src/index.html"]).toBeUndefined();
149
+ expect(files["src/main.tsx"]).toBeUndefined();
163
150
  });
164
151
  });
165
152
 
@@ -178,6 +178,7 @@ function makeIdleSession(opts?: {
178
178
  updateClient: () => {},
179
179
  setHostBrowserProxy: () => {},
180
180
  setHostCuProxy: () => {},
181
+ setHostAppControlProxy: () => {},
181
182
  addPreactivatedSkillId: () => {},
182
183
  enqueueMessage: () => ({ queued: false, requestId: "noop" }),
183
184
  hasAnyPendingConfirmation: () => false,
@@ -204,8 +205,9 @@ function makeIdleSession(opts?: {
204
205
  }
205
206
 
206
207
  /**
207
- * Conversation whose agent loop emits a confirmation_request, so the hub
208
- * publisher registers a pending interaction automatically.
208
+ * Conversation whose agent loop emits a confirmation_request. The mock
209
+ * self-registers in pendingInteractions (as PermissionPrompter.prompt() does)
210
+ * so the /v1/confirm endpoint can route the response.
209
211
  */
210
212
  function makeConfirmationEmittingSession(opts?: {
211
213
  onConfirmation?: (requestId: string, decision: string) => void;
@@ -241,6 +243,7 @@ function makeConfirmationEmittingSession(opts?: {
241
243
  updateClient: () => {},
242
244
  setHostBrowserProxy: () => {},
243
245
  setHostCuProxy: () => {},
246
+ setHostAppControlProxy: () => {},
244
247
  addPreactivatedSkillId: () => {},
245
248
  enqueueMessage: () => ({ queued: false, requestId: "noop" }),
246
249
  hasAnyPendingConfirmation: () => false,
@@ -249,8 +252,22 @@ function makeConfirmationEmittingSession(opts?: {
249
252
  _messageId: string,
250
253
  onEvent: (msg: ServerMessage) => void,
251
254
  ) => {
252
- // Emit confirmation_request this triggers the hub publisher to register
253
- // the pending interaction
255
+ // Simulate PermissionPrompter.prompt(): self-register in pendingInteractions
256
+ // before emitting the SSE event (registration no longer happens via broadcastMessage).
257
+ pendingInteractions.register(reqId, {
258
+ conversationId: "conv-auto",
259
+ kind: "confirmation",
260
+ confirmationDetails: {
261
+ toolName: tool,
262
+ input: { command: "ls" },
263
+ riskLevel: "medium",
264
+ allowlistOptions: [
265
+ { label: "Allow ls", description: "Allow ls command", pattern: "ls" },
266
+ ],
267
+ scopeOptions: [{ label: "This conversation", scope: "session" }],
268
+ persistentDecisionsAllowed: true,
269
+ },
270
+ });
254
271
  onEvent({
255
272
  type: "confirmation_request",
256
273
  requestId: reqId,
@@ -620,8 +637,8 @@ describe("standalone approval endpoints — HTTP layer", () => {
620
637
 
621
638
  // ── Hub publisher integration ────────────────────────────────────────
622
639
 
623
- describe("hub publisher registers pending interactions", () => {
624
- test("confirmation_request events register pending interactions", async () => {
640
+ describe("full round-trip: emit register → confirm", () => {
641
+ test("confirmation_request: self-registered interaction resolves via /v1/confirm", async () => {
625
642
  const confirmReceived: Array<{
626
643
  requestId: string;
627
644
  decision: string;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Tests for machineName field in AssistantEventHub client registration.
3
+ *
4
+ * Validates:
5
+ * - subscribing with machineName set results in listClients() returning the name
6
+ * - subscribing without machineName results in listClients() returning undefined
7
+ */
8
+ import { describe, expect, mock, test } from "bun:test";
9
+
10
+ mock.module("../util/logger.js", () => ({
11
+ getLogger: () =>
12
+ new Proxy({} as Record<string, unknown>, {
13
+ get: () => () => {},
14
+ }),
15
+ }));
16
+
17
+ mock.module("../config/loader.js", () => ({
18
+ getConfig: () => ({
19
+ ui: {},
20
+ model: "test",
21
+ provider: "test",
22
+ memory: { enabled: false },
23
+ rateLimit: { maxRequestsPerMinute: 0 },
24
+ secretDetection: { enabled: false },
25
+ }),
26
+ }));
27
+
28
+ import { initializeDb } from "../memory/db-init.js";
29
+ import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
30
+ import { handleSubscribeAssistantEvents } from "../runtime/routes/events-routes.js";
31
+
32
+ initializeDb();
33
+
34
+ describe("AssistantEventHub — machineName", () => {
35
+ test("subscribing with machineName returns it from listClients()", () => {
36
+ const ac = new AbortController();
37
+ const hub = new AssistantEventHub();
38
+
39
+ handleSubscribeAssistantEvents(
40
+ {
41
+ headers: {
42
+ "x-vellum-client-id": "client-with-name-001",
43
+ "x-vellum-interface-id": "macos",
44
+ "x-vellum-machine-name": "alice-mbp.local",
45
+ },
46
+ abortSignal: ac.signal,
47
+ },
48
+ { hub },
49
+ );
50
+
51
+ const clients = hub.listClients();
52
+ const entry = clients.find((c) => c.clientId === "client-with-name-001");
53
+ expect(entry).toBeDefined();
54
+ expect(entry?.machineName).toBe("alice-mbp.local");
55
+
56
+ ac.abort();
57
+ });
58
+
59
+ test("subscribing without machineName returns undefined from listClients()", () => {
60
+ const ac = new AbortController();
61
+ const hub = new AssistantEventHub();
62
+
63
+ handleSubscribeAssistantEvents(
64
+ {
65
+ headers: {
66
+ "x-vellum-client-id": "client-without-name-001",
67
+ "x-vellum-interface-id": "macos",
68
+ },
69
+ abortSignal: ac.signal,
70
+ },
71
+ { hub },
72
+ );
73
+
74
+ const clients = hub.listClients();
75
+ const entry = clients.find(
76
+ (c) => c.clientId === "client-without-name-001",
77
+ );
78
+ expect(entry).toBeDefined();
79
+ expect(entry?.machineName).toBeUndefined();
80
+
81
+ ac.abort();
82
+ });
83
+
84
+ test("machineName is trimmed when set", () => {
85
+ const ac = new AbortController();
86
+ const hub = new AssistantEventHub();
87
+
88
+ handleSubscribeAssistantEvents(
89
+ {
90
+ headers: {
91
+ "x-vellum-client-id": "client-with-trimmed-name-001",
92
+ "x-vellum-interface-id": "macos",
93
+ "x-vellum-machine-name": " bob-mbp.local ",
94
+ },
95
+ abortSignal: ac.signal,
96
+ },
97
+ { hub },
98
+ );
99
+
100
+ const clients = hub.listClients();
101
+ const entry = clients.find(
102
+ (c) => c.clientId === "client-with-trimmed-name-001",
103
+ );
104
+ expect(entry).toBeDefined();
105
+ expect(entry?.machineName).toBe("bob-mbp.local");
106
+
107
+ ac.abort();
108
+ });
109
+
110
+ test("direct hub subscribe with machineName returns it from listClients()", () => {
111
+ const hub = new AssistantEventHub();
112
+
113
+ hub.subscribe({
114
+ type: "client",
115
+ clientId: "direct-client-001",
116
+ interfaceId: "macos",
117
+ capabilities: ["host_bash"],
118
+ machineName: "charlie-mbp.local",
119
+ callback: () => {},
120
+ });
121
+
122
+ const clients = hub.listClients();
123
+ const entry = clients.find((c) => c.clientId === "direct-client-001");
124
+ expect(entry).toBeDefined();
125
+ expect(entry?.machineName).toBe("charlie-mbp.local");
126
+ });
127
+
128
+ test("direct hub subscribe without machineName returns undefined from listClients()", () => {
129
+ const hub = new AssistantEventHub();
130
+
131
+ hub.subscribe({
132
+ type: "client",
133
+ clientId: "direct-client-no-name-001",
134
+ interfaceId: "macos",
135
+ capabilities: ["host_bash"],
136
+ callback: () => {},
137
+ });
138
+
139
+ const clients = hub.listClients();
140
+ const entry = clients.find(
141
+ (c) => c.clientId === "direct-client-no-name-001",
142
+ );
143
+ expect(entry).toBeDefined();
144
+ expect(entry?.machineName).toBeUndefined();
145
+ });
146
+ });
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Tests for targeted delivery in AssistantEventHub.
3
+ *
4
+ * Validates:
5
+ * - hub.publish(event, { targetClientId }) delivers only to the named client,
6
+ * even when that subscriber's filter.conversationId doesn't match.
7
+ * - hub.publish(event, { targetClientId }) does NOT deliver to other clients.
8
+ * - hub.publish(event, { targetClientId, targetCapability }) skips subscribers
9
+ * that don't have the required capability.
10
+ * - hub.publish(event, { targetCapability }) (untargeted) still applies
11
+ * conversation scoping normally.
12
+ * - getClientById() returns the correct entry or undefined.
13
+ */
14
+ import { describe, expect, test } from "bun:test";
15
+
16
+ import type { AssistantEvent } from "../runtime/assistant-event.js";
17
+ import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
18
+
19
+ function makeEvent(overrides: Partial<AssistantEvent> = {}): AssistantEvent {
20
+ return {
21
+ id: "evt_test",
22
+ conversationId: "sess_web",
23
+ emittedAt: "2026-05-03T00:00:00.000Z",
24
+ message: {
25
+ type: "assistant_text_delta",
26
+ conversationId: "sess_web",
27
+ text: "hi",
28
+ },
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ // ── Targeted delivery ─────────────────────────────────────────────────────────
34
+
35
+ describe("AssistantEventHub — targeted delivery (targetClientId)", () => {
36
+ test("delivers only to the named client, bypassing conversation filter", async () => {
37
+ const hub = new AssistantEventHub();
38
+ const receivedA: AssistantEvent[] = [];
39
+ const receivedB: AssistantEvent[] = [];
40
+
41
+ // client-a is subscribed to "sess_macos" — different from the event's "sess_web"
42
+ hub.subscribe({
43
+ type: "client",
44
+ clientId: "client-a",
45
+ interfaceId: "macos",
46
+ capabilities: ["host_bash"],
47
+ filter: { conversationId: "sess_macos" },
48
+ callback: (e) => {
49
+ receivedA.push(e);
50
+ },
51
+ });
52
+
53
+ // client-b is subscribed to "sess_web" — same as the event's conversationId
54
+ hub.subscribe({
55
+ type: "client",
56
+ clientId: "client-b",
57
+ interfaceId: "macos",
58
+ capabilities: ["host_bash"],
59
+ filter: { conversationId: "sess_web" },
60
+ callback: (e) => {
61
+ receivedB.push(e);
62
+ },
63
+ });
64
+
65
+ // Target client-a specifically — should bypass its conversation filter
66
+ await hub.publish(makeEvent({ conversationId: "sess_web" }), {
67
+ targetClientId: "client-a",
68
+ });
69
+
70
+ // client-a receives it despite mismatched conversationId
71
+ expect(receivedA).toHaveLength(1);
72
+ // client-b does NOT receive it even though its conversationId matches
73
+ expect(receivedB).toHaveLength(0);
74
+ });
75
+
76
+ test("does not deliver to a client with a different clientId", async () => {
77
+ const hub = new AssistantEventHub();
78
+ const receivedA: AssistantEvent[] = [];
79
+ const receivedB: AssistantEvent[] = [];
80
+
81
+ hub.subscribe({
82
+ type: "client",
83
+ clientId: "client-a",
84
+ interfaceId: "macos",
85
+ capabilities: ["host_bash"],
86
+ callback: (e) => {
87
+ receivedA.push(e);
88
+ },
89
+ });
90
+
91
+ hub.subscribe({
92
+ type: "client",
93
+ clientId: "client-b",
94
+ interfaceId: "macos",
95
+ capabilities: ["host_bash"],
96
+ callback: (e) => {
97
+ receivedB.push(e);
98
+ },
99
+ });
100
+
101
+ await hub.publish(makeEvent(), { targetClientId: "client-a" });
102
+
103
+ expect(receivedA).toHaveLength(1);
104
+ expect(receivedB).toHaveLength(0);
105
+ });
106
+
107
+ test("targeted delivery with wrong capability does not deliver", async () => {
108
+ const hub = new AssistantEventHub();
109
+ const receivedA: AssistantEvent[] = [];
110
+
111
+ // client-a only has host_file capability, NOT host_bash
112
+ hub.subscribe({
113
+ type: "client",
114
+ clientId: "client-a",
115
+ interfaceId: "macos",
116
+ capabilities: ["host_file"],
117
+ callback: (e) => {
118
+ receivedA.push(e);
119
+ },
120
+ });
121
+
122
+ await hub.publish(makeEvent(), {
123
+ targetClientId: "client-a",
124
+ targetCapability: "host_bash",
125
+ });
126
+
127
+ // client-a is the target but lacks the required capability — not delivered
128
+ expect(receivedA).toHaveLength(0);
129
+ });
130
+
131
+ test("targeted delivery with matching capability delivers", async () => {
132
+ const hub = new AssistantEventHub();
133
+ const receivedA: AssistantEvent[] = [];
134
+
135
+ hub.subscribe({
136
+ type: "client",
137
+ clientId: "client-a",
138
+ interfaceId: "macos",
139
+ capabilities: ["host_bash"],
140
+ callback: (e) => {
141
+ receivedA.push(e);
142
+ },
143
+ });
144
+
145
+ await hub.publish(makeEvent(), {
146
+ targetClientId: "client-a",
147
+ targetCapability: "host_bash",
148
+ });
149
+
150
+ expect(receivedA).toHaveLength(1);
151
+ });
152
+
153
+ test("process-type subscriber is never matched by targetClientId", async () => {
154
+ const hub = new AssistantEventHub();
155
+ const received: AssistantEvent[] = [];
156
+
157
+ hub.subscribe({
158
+ type: "process",
159
+ callback: (e) => {
160
+ received.push(e);
161
+ },
162
+ });
163
+
164
+ await hub.publish(makeEvent(), { targetClientId: "some-client" });
165
+
166
+ // Process subscribers have no clientId — they should never receive targeted events
167
+ expect(received).toHaveLength(0);
168
+ });
169
+ });
170
+
171
+ // ── Untargeted delivery unchanged ─────────────────────────────────────────────
172
+
173
+ describe("AssistantEventHub — untargeted capability targeting is unchanged", () => {
174
+ test("targetCapability without targetClientId still applies conversation scoping", async () => {
175
+ const hub = new AssistantEventHub();
176
+ const receivedA: AssistantEvent[] = [];
177
+ const receivedB: AssistantEvent[] = [];
178
+
179
+ hub.subscribe({
180
+ type: "client",
181
+ clientId: "client-a",
182
+ interfaceId: "macos",
183
+ capabilities: ["host_bash"],
184
+ filter: { conversationId: "sess_A" },
185
+ callback: (e) => {
186
+ receivedA.push(e);
187
+ },
188
+ });
189
+
190
+ hub.subscribe({
191
+ type: "client",
192
+ clientId: "client-b",
193
+ interfaceId: "macos",
194
+ capabilities: ["host_bash"],
195
+ filter: { conversationId: "sess_B" },
196
+ callback: (e) => {
197
+ receivedB.push(e);
198
+ },
199
+ });
200
+
201
+ await hub.publish(makeEvent({ conversationId: "sess_A" }), {
202
+ targetCapability: "host_bash",
203
+ });
204
+
205
+ expect(receivedA).toHaveLength(1);
206
+ expect(receivedB).toHaveLength(0);
207
+ });
208
+ });
209
+
210
+ // ── getClientById ─────────────────────────────────────────────────────────────
211
+
212
+ describe("AssistantEventHub — getClientById()", () => {
213
+ test("returns the client entry for the given clientId", () => {
214
+ const hub = new AssistantEventHub();
215
+
216
+ hub.subscribe({
217
+ type: "client",
218
+ clientId: "client-x",
219
+ interfaceId: "macos",
220
+ capabilities: ["host_bash"],
221
+ callback: () => {},
222
+ });
223
+
224
+ const entry = hub.getClientById("client-x");
225
+ expect(entry).toBeDefined();
226
+ expect(entry?.clientId).toBe("client-x");
227
+ });
228
+
229
+ test("returns undefined when no client has the given clientId", () => {
230
+ const hub = new AssistantEventHub();
231
+
232
+ hub.subscribe({
233
+ type: "client",
234
+ clientId: "client-x",
235
+ interfaceId: "macos",
236
+ capabilities: ["host_bash"],
237
+ callback: () => {},
238
+ });
239
+
240
+ expect(hub.getClientById("client-y")).toBeUndefined();
241
+ });
242
+
243
+ test("returns undefined after the subscriber is disposed", () => {
244
+ const hub = new AssistantEventHub();
245
+
246
+ const sub = hub.subscribe({
247
+ type: "client",
248
+ clientId: "client-x",
249
+ interfaceId: "macos",
250
+ capabilities: ["host_bash"],
251
+ callback: () => {},
252
+ });
253
+
254
+ sub.dispose();
255
+ expect(hub.getClientById("client-x")).toBeUndefined();
256
+ });
257
+ });
@@ -1,7 +1,12 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
2
 
3
3
  import type { AssistantEvent } from "../runtime/assistant-event.js";
4
- import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
4
+ import {
5
+ AssistantEventHub,
6
+ broadcastMessage,
7
+ capabilityForMessageType,
8
+ } from "../runtime/assistant-event-hub.js";
9
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
5
10
 
6
11
  function makeEvent(overrides: Partial<AssistantEvent> = {}): AssistantEvent {
7
12
  return {
@@ -350,3 +355,105 @@ describe("AssistantEventHub — re-entrancy / snapshot isolation", () => {
350
355
  expect(received).toHaveLength(1);
351
356
  });
352
357
  });
358
+
359
+ // ── capabilityForMessageType — host-prefix routing ───────────────────────────
360
+
361
+ describe("capabilityForMessageType — host-prefix routing", () => {
362
+ test("two-segment domains map to their capability", () => {
363
+ expect(capabilityForMessageType("host_bash_request")).toBe("host_bash");
364
+ expect(capabilityForMessageType("host_bash_cancel")).toBe("host_bash");
365
+ expect(capabilityForMessageType("host_file_request")).toBe("host_file");
366
+ expect(capabilityForMessageType("host_cu_request")).toBe("host_cu");
367
+ expect(capabilityForMessageType("host_cu_cancel")).toBe("host_cu");
368
+ expect(capabilityForMessageType("host_browser_request")).toBe(
369
+ "host_browser",
370
+ );
371
+ });
372
+
373
+ test("host_transfer_* piggybacks on host_file capability", () => {
374
+ expect(capabilityForMessageType("host_transfer_request")).toBe("host_file");
375
+ expect(capabilityForMessageType("host_transfer_cancel")).toBe("host_file");
376
+ });
377
+
378
+ test("three-segment host_app_control routes to its own capability (longest-prefix wins)", () => {
379
+ expect(capabilityForMessageType("host_app_control_request")).toBe(
380
+ "host_app_control",
381
+ );
382
+ expect(capabilityForMessageType("host_app_control_cancel")).toBe(
383
+ "host_app_control",
384
+ );
385
+ });
386
+
387
+ test("non-host messages return undefined (broadcast)", () => {
388
+ expect(capabilityForMessageType("assistant_text_delta")).toBeUndefined();
389
+ expect(capabilityForMessageType("confirmation_request")).toBeUndefined();
390
+ expect(
391
+ capabilityForMessageType("conversation_list_invalidated"),
392
+ ).toBeUndefined();
393
+ });
394
+
395
+ test("unknown host_<domain>_* prefixes return undefined", () => {
396
+ expect(capabilityForMessageType("host_unknown_request")).toBeUndefined();
397
+ });
398
+ });
399
+
400
+ // ── broadcastMessage — pending interaction registration ─────────────────────
401
+ //
402
+ // Host proxy interactions (host_bash, host_cu, host_file, host_browser,
403
+ // host_app_control, host_transfer) are registered in pendingInteractions by
404
+ // the proxy itself (in its request() method), not by the event hub. This
405
+ // avoids overwriting the RPC lifecycle state (rpcResolve/rpcReject/timer)
406
+ // that the proxy stores alongside conversationId/kind.
407
+ //
408
+ // confirmation_request and secret_request are also NOT registered here — the
409
+ // prompters (PermissionPrompter, SecretPrompter) self-register in their
410
+ // prompt() methods, just like the host proxies. broadcastMessage is purely a
411
+ // message-delivery mechanism; it has no registration side effects.
412
+
413
+ describe("broadcastMessage — pending interaction registration", () => {
414
+ beforeEach(() => {
415
+ pendingInteractions.clear();
416
+ });
417
+
418
+ test("does NOT register confirmation_request — PermissionPrompter self-registers", () => {
419
+ broadcastMessage({
420
+ type: "confirmation_request",
421
+ requestId: "req-confirm-1",
422
+ conversationId: "conv-1",
423
+ toolName: "bash",
424
+ input: { command: "rm -rf /" },
425
+ riskLevel: "high",
426
+ executionTarget: "sandbox",
427
+ allowlistOptions: [],
428
+ scopeOptions: [],
429
+ } as never);
430
+
431
+ expect(pendingInteractions.get("req-confirm-1")).toBeUndefined();
432
+ });
433
+
434
+ test("does NOT register secret_request — SecretPrompter self-registers", () => {
435
+ broadcastMessage({
436
+ type: "secret_request",
437
+ requestId: "req-secret-1",
438
+ conversationId: "conv-1",
439
+ service: "github",
440
+ field: "token",
441
+ } as never);
442
+
443
+ expect(pendingInteractions.get("req-secret-1")).toBeUndefined();
444
+ });
445
+
446
+ test("does NOT register host proxy requests — proxies self-register", () => {
447
+ // host_bash, host_cu, host_file, host_browser, host_app_control, and
448
+ // host_transfer are registered by the proxy in request(), not here.
449
+ broadcastMessage({
450
+ type: "host_bash_request",
451
+ requestId: "req-bash-1",
452
+ conversationId: "conv-1",
453
+ command: "echo hi",
454
+ timeout_ms: 1000,
455
+ } as never);
456
+
457
+ expect(pendingInteractions.get("req-bash-1")).toBeUndefined();
458
+ });
459
+ });