@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
@@ -12,32 +12,29 @@ mock.module("../config/loader.js", () => ({
12
12
  getConfig: () => mockConfig,
13
13
  loadConfig: () => mockConfig,
14
14
  invalidateConfigCache: () => {},
15
- saveConfig: () => {},
16
15
  }));
17
16
 
18
17
  const sentMessages: unknown[] = [];
19
- const resolvedInteractionIds: string[] = [];
18
+ const sentMessageOptions: unknown[] = [];
20
19
  let mockHasClient = false;
20
+ let mockCapableClients: Array<{ clientId: string; capabilities: string[] }> = [];
21
+ let mockClientRegistry: Map<string, { clientId: string; capabilities: string[] }> = new Map();
21
22
 
22
23
  mock.module("../runtime/assistant-event-hub.js", () => ({
23
- broadcastMessage: (msg: unknown) => sentMessages.push(msg),
24
+ broadcastMessage: (msg: unknown, _conversationId?: string, options?: unknown) => {
25
+ sentMessages.push(msg);
26
+ sentMessageOptions.push(options);
27
+ },
24
28
  assistantEventHub: {
25
29
  getMostRecentClientByCapability: (cap: string) =>
26
30
  cap === "host_bash" && mockHasClient ? { id: "mock-client" } : null,
31
+ listClientsByCapability: (_cap: string) => mockCapableClients,
32
+ getClientById: (clientId: string) => mockClientRegistry.get(clientId),
27
33
  },
28
34
  }));
29
35
 
30
- mock.module("../runtime/pending-interactions.js", () => ({
31
- resolve: (requestId: string) => {
32
- resolvedInteractionIds.push(requestId);
33
- return undefined;
34
- },
35
- get: () => undefined,
36
- getByKind: () => [],
37
- getByConversation: () => [],
38
- removeByConversation: () => {},
39
- }));
40
-
36
+ // Use the REAL pending-interactions module the proxy self-registers here.
37
+ const pendingInteractions = await import("../runtime/pending-interactions.js");
41
38
  const { HostBashProxy } = await import("../daemon/host-bash-proxy.js");
42
39
 
43
40
  describe("HostBashProxy", () => {
@@ -45,14 +42,34 @@ describe("HostBashProxy", () => {
45
42
 
46
43
  function setup() {
47
44
  sentMessages.length = 0;
48
- resolvedInteractionIds.length = 0;
45
+ sentMessageOptions.length = 0;
49
46
  mockHasClient = false;
47
+ mockCapableClients = [];
48
+ mockClientRegistry = new Map();
49
+ pendingInteractions.clear();
50
50
  proxy = new (HostBashProxy as any)();
51
51
  }
52
52
 
53
+ function setupSingleClient(clientId = "client-1") {
54
+ const entry = { clientId, capabilities: ["host_bash"] };
55
+ mockCapableClients = [entry];
56
+ mockClientRegistry.set(clientId, entry);
57
+ }
58
+
59
+ function setupMultipleClients(clientIds: string[]) {
60
+ mockCapableClients = clientIds.map((id) => ({
61
+ clientId: id,
62
+ capabilities: ["host_bash"],
63
+ }));
64
+ for (const entry of mockCapableClients) {
65
+ mockClientRegistry.set(entry.clientId, entry);
66
+ }
67
+ }
68
+
53
69
  afterEach(() => {
54
70
  proxy?.dispose();
55
71
  HostBashProxy.reset();
72
+ pendingInteractions.clear();
56
73
  });
57
74
 
58
75
  describe("request/resolve lifecycle (happy path)", () => {
@@ -74,10 +91,10 @@ describe("HostBashProxy", () => {
74
91
  expect(typeof sent.requestId).toBe("string");
75
92
 
76
93
  const requestId = sent.requestId as string;
77
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
94
+ expect(pendingInteractions.get(requestId)).toBeDefined();
78
95
 
79
96
  // Simulate client response
80
- proxy.resolve(requestId, {
97
+ proxy.resolveResult(requestId, {
81
98
  stdout: "hello\n",
82
99
  stderr: "",
83
100
  exitCode: 0,
@@ -87,7 +104,7 @@ describe("HostBashProxy", () => {
87
104
  const result = await resultPromise;
88
105
  expect(result.content).toContain("hello");
89
106
  expect(result.isError).toBe(false);
90
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
107
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
91
108
  });
92
109
 
93
110
  test("forwards env field in host_bash_request message", async () => {
@@ -107,7 +124,7 @@ describe("HostBashProxy", () => {
107
124
  expect(sent.env).toEqual({ VELLUM_UNTRUSTED_SHELL: "1" });
108
125
 
109
126
  const requestId = sent.requestId as string;
110
- proxy.resolve(requestId, {
127
+ proxy.resolveResult(requestId, {
111
128
  stdout: "locked\n",
112
129
  stderr: "",
113
130
  exitCode: 0,
@@ -129,7 +146,7 @@ describe("HostBashProxy", () => {
129
146
  expect(sent.env).toBeUndefined();
130
147
 
131
148
  const requestId = sent.requestId as string;
132
- proxy.resolve(requestId, {
149
+ proxy.resolveResult(requestId, {
133
150
  stdout: "normal\n",
134
151
  stderr: "",
135
152
  exitCode: 0,
@@ -147,7 +164,7 @@ describe("HostBashProxy", () => {
147
164
  const sent = sentMessages[0] as Record<string, unknown>;
148
165
  const requestId = sent.requestId as string;
149
166
 
150
- proxy.resolve(requestId, {
167
+ proxy.resolveResult(requestId, {
151
168
  stdout: "",
152
169
  stderr: "command not found",
153
170
  exitCode: 127,
@@ -170,7 +187,7 @@ describe("HostBashProxy", () => {
170
187
  const sent = sentMessages[0] as Record<string, unknown>;
171
188
  const requestId = sent.requestId as string;
172
189
 
173
- proxy.resolve(requestId, {
190
+ proxy.resolveResult(requestId, {
174
191
  stdout: "partial",
175
192
  stderr: "",
176
193
  exitCode: null,
@@ -196,10 +213,10 @@ describe("HostBashProxy", () => {
196
213
 
197
214
  const sent = sentMessages[0] as Record<string, unknown>;
198
215
  const requestId = sent.requestId as string;
199
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
216
+ expect(pendingInteractions.get(requestId)).toBeDefined();
200
217
 
201
218
  // Resolve to avoid test hanging
202
- proxy.resolve(requestId, {
219
+ proxy.resolveResult(requestId, {
203
220
  stdout: "",
204
221
  stderr: "",
205
222
  exitCode: 0,
@@ -226,13 +243,13 @@ describe("HostBashProxy", () => {
226
243
 
227
244
  const sent = sentMessages[0] as Record<string, unknown>;
228
245
  const requestId = sent.requestId as string;
229
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
246
+ expect(pendingInteractions.get(requestId)).toBeDefined();
230
247
 
231
248
  controller.abort();
232
249
 
233
250
  const result = await resultPromise;
234
251
  expect(result.content).toContain("Aborted");
235
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
252
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
236
253
  });
237
254
 
238
255
  test("sends host_bash_cancel to client on abort", async () => {
@@ -300,11 +317,11 @@ describe("HostBashProxy", () => {
300
317
 
301
318
  const sent = sentMessages[0] as Record<string, unknown>;
302
319
  const requestId = sent.requestId as string;
303
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
320
+ expect(pendingInteractions.get(requestId)).toBeDefined();
304
321
 
305
322
  proxy.dispose();
306
323
 
307
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
324
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
308
325
  expect(resultPromise).rejects.toThrow("Host bash proxy disposed");
309
326
  });
310
327
 
@@ -336,7 +353,7 @@ describe("HostBashProxy", () => {
336
353
  });
337
354
 
338
355
  describe("late resolve after abort", () => {
339
- test("resolve is a no-op after abort (entry already deleted)", async () => {
356
+ test("resolveResult is a no-op after abort (entry already deleted)", async () => {
340
357
  setup();
341
358
 
342
359
  const controller = new AbortController();
@@ -353,23 +370,23 @@ describe("HostBashProxy", () => {
353
370
  const result = await resultPromise;
354
371
  expect(result.content).toContain("Aborted");
355
372
 
356
- // Late resolve should be silently ignored (no throw, no double-resolve)
357
- proxy.resolve(requestId, {
373
+ // Late resolveResult should be silently ignored (no throw, no double-resolve)
374
+ proxy.resolveResult(requestId, {
358
375
  stdout: "late",
359
376
  stderr: "",
360
377
  exitCode: 0,
361
378
  timedOut: false,
362
379
  });
363
380
 
364
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
381
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
365
382
  });
366
383
  });
367
384
 
368
- describe("resolve with unknown requestId", () => {
385
+ describe("resolveResult with unknown requestId", () => {
369
386
  test("silently ignores unknown requestId", () => {
370
387
  setup();
371
388
  // Should not throw
372
- proxy.resolve("unknown-id", {
389
+ proxy.resolveResult("unknown-id", {
373
390
  stdout: "",
374
391
  stderr: "",
375
392
  exitCode: 0,
@@ -401,7 +418,7 @@ describe("HostBashProxy", () => {
401
418
  return { signal: source, addCalls, removeCalls };
402
419
  }
403
420
 
404
- test("removes abort listener from signal after resolve completes", async () => {
421
+ test("removes abort listener from signal after resolveResult completes", async () => {
405
422
  setup();
406
423
  const controller = new AbortController();
407
424
  const spy = spySignal(controller.signal);
@@ -417,7 +434,7 @@ describe("HostBashProxy", () => {
417
434
 
418
435
  const requestId = (sentMessages[0] as Record<string, unknown>)
419
436
  .requestId as string;
420
- proxy.resolve(requestId, {
437
+ proxy.resolveResult(requestId, {
421
438
  stdout: "hello\n",
422
439
  stderr: "",
423
440
  exitCode: 0,
@@ -465,8 +482,8 @@ describe("HostBashProxy", () => {
465
482
  });
466
483
  });
467
484
 
468
- describe("pendingInteractions.resolve callback", () => {
469
- test("fires on abort", async () => {
485
+ describe("pendingInteractions cleanup", () => {
486
+ test("cleans up on abort", async () => {
470
487
  setup();
471
488
 
472
489
  const controller = new AbortController();
@@ -478,14 +495,15 @@ describe("HostBashProxy", () => {
478
495
 
479
496
  const sent = sentMessages[0] as Record<string, unknown>;
480
497
  const requestId = sent.requestId as string;
498
+ expect(pendingInteractions.get(requestId)).toBeDefined();
481
499
 
482
500
  controller.abort();
483
501
  await resultPromise;
484
502
 
485
- expect(resolvedInteractionIds).toEqual([requestId]);
503
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
486
504
  });
487
505
 
488
- test("fires for each pending request on dispose", () => {
506
+ test("cleans up for each pending request on dispose", () => {
489
507
  setup();
490
508
 
491
509
  const p1 = proxy.request({ command: "echo a" }, "session-1");
@@ -497,15 +515,16 @@ describe("HostBashProxy", () => {
497
515
  (m) => m.requestId as string,
498
516
  );
499
517
  expect(ids).toHaveLength(2);
518
+ expect(pendingInteractions.get(ids[0])).toBeDefined();
519
+ expect(pendingInteractions.get(ids[1])).toBeDefined();
500
520
 
501
521
  proxy.dispose();
502
522
 
503
- expect(resolvedInteractionIds).toHaveLength(2);
504
- expect(resolvedInteractionIds).toContain(ids[0]);
505
- expect(resolvedInteractionIds).toContain(ids[1]);
523
+ expect(pendingInteractions.get(ids[0])).toBeUndefined();
524
+ expect(pendingInteractions.get(ids[1])).toBeUndefined();
506
525
  });
507
526
 
508
- test("does not fire on normal client-initiated resolve", async () => {
527
+ test("cleans up on normal client-initiated resolveResult", async () => {
509
528
  setup();
510
529
 
511
530
  const resultPromise = proxy.request(
@@ -515,9 +534,9 @@ describe("HostBashProxy", () => {
515
534
 
516
535
  const sent = sentMessages[0] as Record<string, unknown>;
517
536
  const requestId = sent.requestId as string;
537
+ expect(pendingInteractions.get(requestId)).toBeDefined();
518
538
 
519
- // Normal resolve from client — should NOT trigger pendingInteractions.resolve
520
- proxy.resolve(requestId, {
539
+ proxy.resolveResult(requestId, {
521
540
  stdout: "hello",
522
541
  stderr: "",
523
542
  exitCode: 0,
@@ -525,7 +544,187 @@ describe("HostBashProxy", () => {
525
544
  });
526
545
 
527
546
  await resultPromise;
528
- expect(resolvedInteractionIds).toEqual([]);
547
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
548
+ });
549
+ });
550
+
551
+ describe("target client routing", () => {
552
+ test("auto-resolves when exactly one capable client is connected", async () => {
553
+ setup();
554
+ setupSingleClient("client-abc");
555
+
556
+ const resultPromise = proxy.request(
557
+ { command: "echo hello" },
558
+ "session-1",
559
+ );
560
+
561
+ expect(sentMessages).toHaveLength(1);
562
+ const sent = sentMessages[0] as Record<string, unknown>;
563
+ expect(sent.targetClientId).toBe("client-abc");
564
+
565
+ // Options passed to broadcastMessage should also have targetClientId
566
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
567
+ expect(opts?.targetClientId).toBe("client-abc");
568
+
569
+ const requestId = sent.requestId as string;
570
+ proxy.resolveResult(requestId, {
571
+ stdout: "hello\n",
572
+ stderr: "",
573
+ exitCode: 0,
574
+ timedOut: false,
575
+ });
576
+
577
+ const result = await resultPromise;
578
+ expect(result.isError).toBe(false);
579
+ });
580
+
581
+ test("uses explicit targetClientId when it is valid", async () => {
582
+ setup();
583
+ setupSingleClient("client-abc");
584
+ // Also register a second client so we're sure explicit targeting works
585
+ const entry2 = { clientId: "client-xyz", capabilities: ["host_bash"] };
586
+ mockCapableClients.push(entry2);
587
+ mockClientRegistry.set("client-xyz", entry2);
588
+
589
+ const resultPromise = proxy.request(
590
+ { command: "echo hello", targetClientId: "client-abc" },
591
+ "session-1",
592
+ );
593
+
594
+ expect(sentMessages).toHaveLength(1);
595
+ const sent = sentMessages[0] as Record<string, unknown>;
596
+ expect(sent.targetClientId).toBe("client-abc");
597
+
598
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
599
+ expect(opts?.targetClientId).toBe("client-abc");
600
+
601
+ const requestId = sent.requestId as string;
602
+ proxy.resolveResult(requestId, {
603
+ stdout: "ok\n",
604
+ stderr: "",
605
+ exitCode: 0,
606
+ timedOut: false,
607
+ });
608
+
609
+ const result = await resultPromise;
610
+ expect(result.isError).toBe(false);
611
+ });
612
+
613
+ test("returns error for explicit targetClientId that is not connected", async () => {
614
+ setup();
615
+ setupSingleClient("client-abc");
616
+
617
+ const result = await proxy.request(
618
+ { command: "echo hello", targetClientId: "client-unknown" },
619
+ "session-1",
620
+ );
621
+
622
+ // Should return error without broadcasting
623
+ expect(result.isError).toBe(true);
624
+ expect(result.content).toContain("client-unknown");
625
+ expect(result.content).toContain("assistant clients list --capability host_bash");
626
+ expect(sentMessages).toHaveLength(0);
627
+ });
628
+
629
+ test("returns error for explicit targetClientId that is connected but lacks host_bash", async () => {
630
+ setup();
631
+ // Register a client without host_bash capability
632
+ mockClientRegistry.set("client-no-bash", {
633
+ clientId: "client-no-bash",
634
+ capabilities: [],
635
+ });
636
+
637
+ const result = await proxy.request(
638
+ { command: "echo hello", targetClientId: "client-no-bash" },
639
+ "session-1",
640
+ );
641
+
642
+ expect(result.isError).toBe(true);
643
+ expect(result.content).toContain("client-no-bash");
644
+ expect(result.content).toContain("does not support host_bash");
645
+ expect(sentMessages).toHaveLength(0);
646
+ });
647
+
648
+ test("falls through to untargeted broadcast when multiple capable clients are connected and no targetClientId", async () => {
649
+ setup();
650
+ setupMultipleClients(["client-1", "client-2", "client-3"]);
651
+
652
+ const resultPromise = proxy.request(
653
+ { command: "echo hello" },
654
+ "session-1",
655
+ );
656
+
657
+ // Should broadcast without an early error return
658
+ expect(sentMessages).toHaveLength(1);
659
+ const sent = sentMessages[0] as Record<string, unknown>;
660
+ expect(sent.type).toBe("host_bash_request");
661
+ // No target client resolved — untargeted broadcast
662
+ expect(sent.targetClientId).toBeUndefined();
663
+
664
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
665
+ expect(opts?.targetClientId).toBeUndefined();
666
+
667
+ // Manually resolve to clean up
668
+ const requestId = sent.requestId as string;
669
+ proxy.resolveResult(requestId, {
670
+ stdout: "hello\n",
671
+ stderr: "",
672
+ exitCode: 0,
673
+ timedOut: false,
674
+ });
675
+
676
+ const result = await resultPromise;
677
+ expect(result.isError).toBe(false);
678
+ });
679
+
680
+ test("falls through to broadcast when zero capable clients (existing timeout path)", async () => {
681
+ setup();
682
+ // mockCapableClients is empty (default), so capableClients.length === 0
683
+
684
+ const resultPromise = proxy.request(
685
+ { command: "echo hello" },
686
+ "session-1",
687
+ );
688
+
689
+ // Should still broadcast (no early return)
690
+ expect(sentMessages).toHaveLength(1);
691
+ const sent = sentMessages[0] as Record<string, unknown>;
692
+ expect(sent.type).toBe("host_bash_request");
693
+ // targetClientId is undefined when no clients present
694
+ expect(sent.targetClientId).toBeUndefined();
695
+
696
+ // Manually resolve to clean up
697
+ const requestId = sent.requestId as string;
698
+ proxy.resolveResult(requestId, {
699
+ stdout: "",
700
+ stderr: "",
701
+ exitCode: 0,
702
+ timedOut: false,
703
+ });
704
+
705
+ await resultPromise;
706
+ });
707
+
708
+ test("includes targetClientId in timeout error message when client was resolved", async () => {
709
+ setup();
710
+ setupSingleClient("client-mac");
711
+
712
+ jest.useFakeTimers();
713
+ try {
714
+ const resultPromise = proxy.request(
715
+ { command: "echo slow", timeout_seconds: 30 },
716
+ "session-1",
717
+ );
718
+
719
+ // Proxy timeout = 33s; advance past it
720
+ jest.advanceTimersByTime(34 * 1000);
721
+
722
+ const result = await resultPromise;
723
+ expect(result.isError).toBe(true);
724
+ expect(result.content).toContain("client-mac");
725
+ } finally {
726
+ jest.useRealTimers();
727
+ }
529
728
  });
530
729
  });
531
730
  });