@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
@@ -0,0 +1,583 @@
1
+ /**
2
+ * Tests for HostTransferProxy targetClientId behaviour (Phase 1).
3
+ *
4
+ * Covers:
5
+ * - requestToHost() explicit valid targetClientId → validates, broadcasts with targetClientId
6
+ * - requestToHost() auto-resolve when exactly one host_file-capable client → auto-resolves
7
+ * - requestToHost() unknown targetClientId → early error, no broadcast
8
+ * - requestToHost() incapable client → early error, no broadcast
9
+ * - requestToSandbox() explicit valid targetClientId → same 4 cases
10
+ * - Abort path sends host_transfer_cancel with targetClientId
11
+ * - cancel(requestId) reads targetClientId from pending interaction
12
+ * - dispose() reads targetClientId from pending interaction
13
+ * - getTargetClientIdForTransfer() returns correct value after requestToHost()
14
+ * - Timeout message includes client ID when resolvedTargetClientId is set
15
+ * - Regression: no-targetClientId requestToHost / requestToSandbox (smoke tests)
16
+ */
17
+ import { afterEach, describe, expect, jest, mock, test } from "bun:test";
18
+
19
+ const sentMessages: unknown[] = [];
20
+ const sentMessageOptions: unknown[] = [];
21
+ const resolvedInteractionIds: string[] = [];
22
+ let mockHasClient = false;
23
+ let mockCapableClients: Array<{ clientId: string; capabilities: string[] }> = [];
24
+ let mockClientRegistry: Map<string, { clientId: string; capabilities: string[] }> = new Map();
25
+
26
+ mock.module("../runtime/assistant-event-hub.js", () => ({
27
+ broadcastMessage: (msg: unknown, _conversationId?: string, options?: unknown) => {
28
+ sentMessages.push(msg);
29
+ sentMessageOptions.push(options);
30
+ },
31
+ assistantEventHub: {
32
+ getMostRecentClientByCapability: (cap: string) =>
33
+ cap === "host_file" && mockHasClient ? { id: "mock-client" } : null,
34
+ listClientsByCapability: (_cap: string) => mockCapableClients,
35
+ getClientById: (clientId: string) => mockClientRegistry.get(clientId),
36
+ },
37
+ }));
38
+
39
+ const pendingInteractionMap = new Map<string, Record<string, unknown>>();
40
+ mock.module("../runtime/pending-interactions.js", () => ({
41
+ register: (requestId: string, interaction: Record<string, unknown>) => {
42
+ pendingInteractionMap.set(requestId, interaction);
43
+ },
44
+ resolve: (requestId: string) => {
45
+ const interaction = pendingInteractionMap.get(requestId);
46
+ pendingInteractionMap.delete(requestId);
47
+ resolvedInteractionIds.push(requestId);
48
+ return interaction;
49
+ },
50
+ get: (requestId: string) => pendingInteractionMap.get(requestId),
51
+ getByKind: (_kind: string) =>
52
+ Array.from(pendingInteractionMap.entries())
53
+ .filter(([, v]) => v.kind === _kind)
54
+ .map(([requestId, v]) => ({ requestId, ...v })),
55
+ getByConversation: () => [],
56
+ removeByConversation: () => {},
57
+ clear: () => pendingInteractionMap.clear(),
58
+ }));
59
+
60
+ const { HostTransferProxy } = await import("../daemon/host-transfer-proxy.js");
61
+
62
+ /**
63
+ * Poll until sentMessages reaches the expected length.
64
+ * Needed because requestToHost() does async readFile before broadcasting.
65
+ */
66
+ async function waitForMessages(
67
+ msgs: unknown[],
68
+ expectedLength: number,
69
+ timeoutMs = 2000,
70
+ ): Promise<void> {
71
+ const start = Date.now();
72
+ while (msgs.length < expectedLength) {
73
+ if (Date.now() - start > timeoutMs) {
74
+ throw new Error(
75
+ `Timed out waiting for ${expectedLength} message(s), got ${msgs.length}`,
76
+ );
77
+ }
78
+ await new Promise((r) => setTimeout(r, 5));
79
+ }
80
+ }
81
+
82
+ describe("HostTransferProxy — targetClientId", () => {
83
+ let proxy: InstanceType<typeof HostTransferProxy>;
84
+
85
+ function setup() {
86
+ sentMessages.length = 0;
87
+ sentMessageOptions.length = 0;
88
+ resolvedInteractionIds.length = 0;
89
+ pendingInteractionMap.clear();
90
+ mockHasClient = false;
91
+ mockCapableClients = [];
92
+ mockClientRegistry = new Map();
93
+ HostTransferProxy.reset();
94
+ proxy = new (HostTransferProxy as any)();
95
+ }
96
+
97
+ function setupSingleClient(clientId = "client-1") {
98
+ const entry = { clientId, capabilities: ["host_file"] };
99
+ mockCapableClients = [entry];
100
+ mockClientRegistry.set(clientId, entry);
101
+ }
102
+
103
+ afterEach(() => {
104
+ proxy?.dispose();
105
+ HostTransferProxy.reset();
106
+ });
107
+
108
+ // ── requestToHost() — explicit valid targetClientId ───────────────────
109
+
110
+ describe("requestToHost() — explicit valid targetClientId", () => {
111
+ test("broadcasts with targetClientId in message body and options", async () => {
112
+ setup();
113
+ setupSingleClient("client-mac");
114
+
115
+ // Create a fake file for requestToHost to read
116
+ const fileContent = "hello";
117
+ const srcPath = `/tmp/htp-targeted-${Date.now()}.txt`;
118
+ await globalThis.Bun.write(srcPath, fileContent);
119
+
120
+ const resultPromise = proxy.requestToHost({
121
+ sourcePath: srcPath,
122
+ destPath: "/host/dest.txt",
123
+ overwrite: false,
124
+ conversationId: "conv-1",
125
+ targetClientId: "client-mac",
126
+ });
127
+
128
+ await waitForMessages(sentMessages, 1);
129
+
130
+ const sent = sentMessages[0] as Record<string, unknown>;
131
+ expect(sent.type).toBe("host_transfer_request");
132
+ expect(sent.targetClientId).toBe("client-mac");
133
+
134
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
135
+ expect(opts?.targetClientId).toBe("client-mac");
136
+
137
+ const requestId = sent.requestId as string;
138
+ proxy.resolveTransferResult(requestId, { isError: false, bytesWritten: 5 });
139
+
140
+ const result = await resultPromise;
141
+ expect(result.isError).toBe(false);
142
+ });
143
+ });
144
+
145
+ // ── requestToHost() — auto-resolve single capable client ─────────────
146
+
147
+ describe("requestToHost() — auto-resolve when exactly one capable client", () => {
148
+ test("auto-resolves targetClientId to the single capable client", async () => {
149
+ setup();
150
+ setupSingleClient("client-solo");
151
+
152
+ const srcPath = `/tmp/htp-targeted-solo-${Date.now()}.txt`;
153
+ await globalThis.Bun.write(srcPath, "content");
154
+
155
+ const resultPromise = proxy.requestToHost({
156
+ sourcePath: srcPath,
157
+ destPath: "/host/dest.txt",
158
+ overwrite: false,
159
+ conversationId: "conv-2",
160
+ });
161
+
162
+ await waitForMessages(sentMessages, 1);
163
+
164
+ const sent = sentMessages[0] as Record<string, unknown>;
165
+ expect(sent.targetClientId).toBe("client-solo");
166
+
167
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
168
+ expect(opts?.targetClientId).toBe("client-solo");
169
+
170
+ const requestId = sent.requestId as string;
171
+ proxy.resolveTransferResult(requestId, { isError: false });
172
+ const result = await resultPromise;
173
+ expect(result.isError).toBe(false);
174
+ });
175
+ });
176
+
177
+ // ── requestToHost() — unknown targetClientId ─────────────────────────
178
+
179
+ describe("requestToHost() — unknown targetClientId", () => {
180
+ test("returns error immediately without broadcasting", async () => {
181
+ setup();
182
+ // No clients registered
183
+
184
+ const result = await proxy.requestToHost({
185
+ sourcePath: "/tmp/file.txt",
186
+ destPath: "/host/dest.txt",
187
+ overwrite: false,
188
+ conversationId: "conv-3",
189
+ targetClientId: "client-ghost",
190
+ });
191
+
192
+ expect(result.isError).toBe(true);
193
+ expect(result.content).toContain("client-ghost");
194
+ expect(result.content).toContain("assistant clients list --capability host_file");
195
+ expect(sentMessages).toHaveLength(0);
196
+ });
197
+ });
198
+
199
+ // ── requestToHost() — incapable client ───────────────────────────────
200
+
201
+ describe("requestToHost() — client lacks host_file capability", () => {
202
+ test("returns error immediately without broadcasting", async () => {
203
+ setup();
204
+ mockClientRegistry.set("client-no-file", {
205
+ clientId: "client-no-file",
206
+ capabilities: ["host_bash"],
207
+ });
208
+
209
+ const result = await proxy.requestToHost({
210
+ sourcePath: "/tmp/file.txt",
211
+ destPath: "/host/dest.txt",
212
+ overwrite: false,
213
+ conversationId: "conv-4",
214
+ targetClientId: "client-no-file",
215
+ });
216
+
217
+ expect(result.isError).toBe(true);
218
+ expect(result.content).toContain("client-no-file");
219
+ expect(result.content).toContain("does not support host_file");
220
+ expect(sentMessages).toHaveLength(0);
221
+ });
222
+ });
223
+
224
+ // ── requestToSandbox() — explicit valid targetClientId ────────────────
225
+
226
+ describe("requestToSandbox() — explicit valid targetClientId", () => {
227
+ test("broadcasts with targetClientId in message body and options", async () => {
228
+ setup();
229
+ setupSingleClient("client-mac");
230
+
231
+ const resultPromise = proxy.requestToSandbox({
232
+ sourcePath: "/host/source.txt",
233
+ destPath: "/sandbox/dest.txt",
234
+ conversationId: "conv-5",
235
+ targetClientId: "client-mac",
236
+ });
237
+
238
+ expect(sentMessages).toHaveLength(1);
239
+ const sent = sentMessages[0] as Record<string, unknown>;
240
+ expect(sent.type).toBe("host_transfer_request");
241
+ expect(sent.targetClientId).toBe("client-mac");
242
+
243
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
244
+ expect(opts?.targetClientId).toBe("client-mac");
245
+
246
+ // Cancel to resolve the promise
247
+ proxy.cancel(sent.requestId as string);
248
+ await resultPromise;
249
+ });
250
+ });
251
+
252
+ // ── requestToSandbox() — auto-resolve single capable client ──────────
253
+
254
+ describe("requestToSandbox() — auto-resolve when exactly one capable client", () => {
255
+ test("auto-resolves targetClientId", async () => {
256
+ setup();
257
+ setupSingleClient("client-solo");
258
+
259
+ const resultPromise = proxy.requestToSandbox({
260
+ sourcePath: "/host/source.txt",
261
+ destPath: "/sandbox/dest.txt",
262
+ conversationId: "conv-6",
263
+ });
264
+
265
+ expect(sentMessages).toHaveLength(1);
266
+ const sent = sentMessages[0] as Record<string, unknown>;
267
+ expect(sent.targetClientId).toBe("client-solo");
268
+
269
+ proxy.cancel(sent.requestId as string);
270
+ await resultPromise;
271
+ });
272
+ });
273
+
274
+ // ── requestToSandbox() — unknown targetClientId ──────────────────────
275
+
276
+ describe("requestToSandbox() — unknown targetClientId", () => {
277
+ test("returns error immediately without broadcasting", async () => {
278
+ setup();
279
+
280
+ const result = await proxy.requestToSandbox({
281
+ sourcePath: "/host/source.txt",
282
+ destPath: "/sandbox/dest.txt",
283
+ conversationId: "conv-7",
284
+ targetClientId: "client-ghost",
285
+ });
286
+
287
+ expect(result.isError).toBe(true);
288
+ expect(result.content).toContain("client-ghost");
289
+ expect(sentMessages).toHaveLength(0);
290
+ });
291
+ });
292
+
293
+ // ── requestToSandbox() — incapable client ────────────────────────────
294
+
295
+ describe("requestToSandbox() — client lacks host_file capability", () => {
296
+ test("returns error immediately without broadcasting", async () => {
297
+ setup();
298
+ mockClientRegistry.set("client-no-file", {
299
+ clientId: "client-no-file",
300
+ capabilities: ["host_bash"],
301
+ });
302
+
303
+ const result = await proxy.requestToSandbox({
304
+ sourcePath: "/host/source.txt",
305
+ destPath: "/sandbox/dest.txt",
306
+ conversationId: "conv-8",
307
+ targetClientId: "client-no-file",
308
+ });
309
+
310
+ expect(result.isError).toBe(true);
311
+ expect(result.content).toContain("does not support host_file");
312
+ expect(sentMessages).toHaveLength(0);
313
+ });
314
+ });
315
+
316
+ // ── Abort path includes targetClientId in cancel (requestToHost) ──────
317
+
318
+ describe("abort path — requestToHost sends cancel with targetClientId", () => {
319
+ test("cancel broadcast includes targetClientId when request was targeted", async () => {
320
+ setup();
321
+ setupSingleClient("client-abc");
322
+
323
+ const srcPath = `/tmp/htp-targeted-abort-${Date.now()}.txt`;
324
+ await globalThis.Bun.write(srcPath, "content");
325
+
326
+ const controller = new AbortController();
327
+ const resultPromise = proxy.requestToHost(
328
+ {
329
+ sourcePath: srcPath,
330
+ destPath: "/host/dest.txt",
331
+ overwrite: false,
332
+ conversationId: "conv-9",
333
+ targetClientId: "client-abc",
334
+ },
335
+ controller.signal,
336
+ );
337
+
338
+ await waitForMessages(sentMessages, 1);
339
+
340
+ const sent = sentMessages[0] as Record<string, unknown>;
341
+ expect(sent.targetClientId).toBe("client-abc");
342
+
343
+ controller.abort();
344
+ const result = await resultPromise;
345
+ expect(result.isError).toBe(true);
346
+
347
+ // Second message is the cancel
348
+ expect(sentMessages).toHaveLength(2);
349
+ const cancelMsg = sentMessages[1] as Record<string, unknown>;
350
+ expect(cancelMsg.type).toBe("host_transfer_cancel");
351
+ expect(cancelMsg.targetClientId).toBe("client-abc");
352
+
353
+ const cancelOpts = sentMessageOptions[1] as Record<string, unknown> | undefined;
354
+ expect(cancelOpts?.targetClientId).toBe("client-abc");
355
+ });
356
+ });
357
+
358
+ // ── cancel(requestId) reads targetClientId from pending interaction ───
359
+
360
+ describe("cancel() reads targetClientId from pending interaction", () => {
361
+ test("cancel broadcast includes targetClientId", async () => {
362
+ setup();
363
+ setupSingleClient("client-xyz");
364
+
365
+ const resultPromise = proxy.requestToSandbox({
366
+ sourcePath: "/host/source.txt",
367
+ destPath: "/sandbox/dest.txt",
368
+ conversationId: "conv-10",
369
+ targetClientId: "client-xyz",
370
+ });
371
+
372
+ const sent = sentMessages[0] as Record<string, unknown>;
373
+ const requestId = sent.requestId as string;
374
+ expect(sent.targetClientId).toBe("client-xyz");
375
+
376
+ proxy.cancel(requestId);
377
+ const result = await resultPromise;
378
+ expect(result.isError).toBe(true);
379
+ expect(result.content).toBe("Transfer cancelled");
380
+
381
+ // Cancel message
382
+ expect(sentMessages).toHaveLength(2);
383
+ const cancelMsg = sentMessages[1] as Record<string, unknown>;
384
+ expect(cancelMsg.type).toBe("host_transfer_cancel");
385
+ expect(cancelMsg.targetClientId).toBe("client-xyz");
386
+
387
+ const cancelOpts = sentMessageOptions[1] as Record<string, unknown> | undefined;
388
+ expect(cancelOpts?.targetClientId).toBe("client-xyz");
389
+ });
390
+ });
391
+
392
+ // ── dispose() reads targetClientId from pending interaction ───────────
393
+
394
+ describe("dispose() reads targetClientId from pending interaction", () => {
395
+ test("dispose cancel broadcast includes targetClientId for targeted request", () => {
396
+ setup();
397
+ setupSingleClient("client-dispose");
398
+
399
+ const p = proxy.requestToSandbox({
400
+ sourcePath: "/host/source.txt",
401
+ destPath: "/sandbox/dest.txt",
402
+ conversationId: "conv-11",
403
+ targetClientId: "client-dispose",
404
+ });
405
+ p.catch(() => {}); // expected rejection on dispose
406
+
407
+ const sent = sentMessages[0] as Record<string, unknown>;
408
+ expect(sent.targetClientId).toBe("client-dispose");
409
+ const requestId = sent.requestId as string;
410
+
411
+ proxy.dispose();
412
+
413
+ const cancelMessages = sentMessages
414
+ .slice(1)
415
+ .filter(
416
+ (m) => (m as Record<string, unknown>).type === "host_transfer_cancel",
417
+ ) as Array<Record<string, unknown>>;
418
+ expect(cancelMessages).toHaveLength(1);
419
+ expect(cancelMessages[0].requestId).toBe(requestId);
420
+ expect(cancelMessages[0].targetClientId).toBe("client-dispose");
421
+ });
422
+ });
423
+
424
+ // ── getTargetClientIdForTransfer() ────────────────────────────────────
425
+
426
+ describe("getTargetClientIdForTransfer()", () => {
427
+ test("returns targetClientId after requestToHost()", async () => {
428
+ setup();
429
+ setupSingleClient("client-peek");
430
+
431
+ const srcPath = `/tmp/htp-targeted-peek-${Date.now()}.txt`;
432
+ await globalThis.Bun.write(srcPath, "content");
433
+
434
+ const resultPromise = proxy.requestToHost({
435
+ sourcePath: srcPath,
436
+ destPath: "/host/dest.txt",
437
+ overwrite: false,
438
+ conversationId: "conv-12",
439
+ targetClientId: "client-peek",
440
+ });
441
+
442
+ await waitForMessages(sentMessages, 1);
443
+
444
+ const sent = sentMessages[0] as Record<string, unknown>;
445
+ const transferId = sent.transferId as string;
446
+
447
+ expect(proxy.getTargetClientIdForTransfer(transferId)).toBe("client-peek");
448
+
449
+ // Clean up
450
+ const requestId = sent.requestId as string;
451
+ proxy.resolveTransferResult(requestId, { isError: false });
452
+ await resultPromise;
453
+ });
454
+
455
+ test("returns null for untargeted transfer", async () => {
456
+ setup();
457
+ // No clients — no auto-resolve
458
+
459
+ const srcPath = `/tmp/htp-targeted-null-${Date.now()}.txt`;
460
+ await globalThis.Bun.write(srcPath, "hello");
461
+
462
+ const resultPromise = proxy.requestToHost({
463
+ sourcePath: srcPath,
464
+ destPath: "/host/dest.txt",
465
+ overwrite: false,
466
+ conversationId: "conv-13",
467
+ });
468
+
469
+ await waitForMessages(sentMessages, 1);
470
+
471
+ const sent = sentMessages[0] as Record<string, unknown>;
472
+ const transferId = sent.transferId as string;
473
+
474
+ expect(proxy.getTargetClientIdForTransfer(transferId)).toBeNull();
475
+
476
+ const requestId = sent.requestId as string;
477
+ proxy.resolveTransferResult(requestId, { isError: false });
478
+ await resultPromise;
479
+ });
480
+
481
+ test("returns null for unknown transferId", () => {
482
+ setup();
483
+ expect(proxy.getTargetClientIdForTransfer("nonexistent-id")).toBeNull();
484
+ });
485
+ });
486
+
487
+ // ── Timeout message includes clientId ─────────────────────────────────
488
+
489
+ describe("timeout message includes clientId when targeted", () => {
490
+ test("timeout resolve message mentions resolvedTargetClientId for requestToSandbox", async () => {
491
+ setup();
492
+ setupSingleClient("client-timeout");
493
+
494
+ jest.useFakeTimers();
495
+ try {
496
+ const resultPromise = proxy.requestToSandbox({
497
+ sourcePath: "/host/source.txt",
498
+ destPath: "/sandbox/dest.txt",
499
+ conversationId: "conv-14",
500
+ targetClientId: "client-timeout",
501
+ });
502
+
503
+ const sent = sentMessages[0] as Record<string, unknown>;
504
+ expect(sent.targetClientId).toBe("client-timeout");
505
+
506
+ // Advance past the 120s default timeout
507
+ jest.advanceTimersByTime(121 * 1000);
508
+
509
+ const result = await resultPromise;
510
+ expect(result.isError).toBe(true);
511
+ expect(result.content).toContain("client-timeout");
512
+ } finally {
513
+ jest.useRealTimers();
514
+ }
515
+ });
516
+ });
517
+
518
+ // ── Regression: no-targetClientId path is unbroken ───────────────────
519
+
520
+ describe("regression — untargeted requestToHost completes normally", () => {
521
+ test("no-targetClientId requestToHost resolves successfully", async () => {
522
+ setup();
523
+ // Multiple clients so no auto-resolve
524
+ mockCapableClients = [
525
+ { clientId: "client-a", capabilities: ["host_file"] },
526
+ { clientId: "client-b", capabilities: ["host_file"] },
527
+ ];
528
+
529
+ const srcPath = `/tmp/htp-regression-tohost-${Date.now()}.txt`;
530
+ await globalThis.Bun.write(srcPath, "regression content");
531
+
532
+ const resultPromise = proxy.requestToHost({
533
+ sourcePath: srcPath,
534
+ destPath: "/host/dest.txt",
535
+ overwrite: false,
536
+ conversationId: "conv-reg-1",
537
+ });
538
+
539
+ await waitForMessages(sentMessages, 1);
540
+
541
+ const sent = sentMessages[0] as Record<string, unknown>;
542
+ expect(sent.type).toBe("host_transfer_request");
543
+ expect(sent.targetClientId).toBeUndefined();
544
+
545
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
546
+ expect(opts?.targetClientId).toBeUndefined();
547
+
548
+ const requestId = sent.requestId as string;
549
+ proxy.resolveTransferResult(requestId, { isError: false, bytesWritten: 18 });
550
+
551
+ const result = await resultPromise;
552
+ expect(result.isError).toBe(false);
553
+ expect(result.content).toContain("successfully");
554
+ });
555
+ });
556
+
557
+ describe("regression — untargeted requestToSandbox completes normally", () => {
558
+ test("no-targetClientId requestToSandbox resolves via cancel", async () => {
559
+ setup();
560
+ // Multiple clients so no auto-resolve
561
+ mockCapableClients = [
562
+ { clientId: "client-a", capabilities: ["host_file"] },
563
+ { clientId: "client-b", capabilities: ["host_file"] },
564
+ ];
565
+
566
+ const resultPromise = proxy.requestToSandbox({
567
+ sourcePath: "/host/source.txt",
568
+ destPath: "/sandbox/dest.txt",
569
+ conversationId: "conv-reg-2",
570
+ });
571
+
572
+ expect(sentMessages).toHaveLength(1);
573
+ const sent = sentMessages[0] as Record<string, unknown>;
574
+ expect(sent.type).toBe("host_transfer_request");
575
+ expect(sent.targetClientId).toBeUndefined();
576
+
577
+ proxy.cancel(sent.requestId as string);
578
+ const result = await resultPromise;
579
+ expect(result.isError).toBe(true);
580
+ expect(result.content).toBe("Transfer cancelled");
581
+ });
582
+ });
583
+ });