@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
@@ -13,25 +13,24 @@ import * as pendingInteractions from "../runtime/pending-interactions.js";
13
13
  import type { ToolExecutionResult } from "../tools/types.js";
14
14
  import { AssistantError, ErrorCode } from "../util/errors.js";
15
15
  import { getLogger } from "../util/logger.js";
16
- import type { ServerMessage } from "./message-protocol.js";
17
16
 
18
17
  const log = getLogger("host-transfer-proxy");
19
18
 
20
- interface PendingTransfer {
21
- resolve: (result: ToolExecutionResult) => void;
22
- reject: (err: Error) => void;
23
- timer: ReturnType<typeof setTimeout>;
19
+ /**
20
+ * Lightweight entry for the transfers map (keyed by transferId).
21
+ * Points back to the requestId so route handlers can correlate
22
+ * content endpoints with the pending interaction.
23
+ */
24
+ interface TransferEntry {
24
25
  requestId: string;
25
26
  transferId: string;
26
- conversationId: string;
27
27
  direction: "to_host" | "to_sandbox";
28
28
  filePath: string;
29
29
  overwrite?: boolean;
30
30
  sizeBytes?: number;
31
31
  sha256?: string;
32
32
  fileBuffer?: Buffer;
33
- /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
34
- detachAbort: () => void;
33
+ targetClientId?: string;
35
34
  }
36
35
 
37
36
  /**
@@ -82,10 +81,22 @@ export class HostTransferProxy {
82
81
  HostTransferProxy._instance = null;
83
82
  }
84
83
 
85
- /** Pending transfers keyed by requestId (for resolution from client results). */
86
- private pending = new Map<string, PendingTransfer>();
87
84
  /** Pending transfers keyed by transferId (for content endpoint lookups). */
88
- private transfers = new Map<string, PendingTransfer>();
85
+ private transfers = new Map<string, TransferEntry>();
86
+ /**
87
+ * Briefly retains size/sha256 of a just-consumed transfer so the GET-content
88
+ * route's `resolveResponseHeaders` callback (which the HTTP adapter invokes
89
+ * AFTER the request handler) can still set `Content-Length` and
90
+ * `X-Transfer-SHA256` headers. Without this, the handler's `getTransferContent`
91
+ * call deletes the entry before the header resolver runs, and the resolver
92
+ * silently falls back to default headers — meaning the documented response
93
+ * headers were never actually sent. Entries here self-clear on read; a 30s
94
+ * fallback timer prevents long-term retention if the resolver never runs.
95
+ */
96
+ private justConsumedMetadata = new Map<
97
+ string,
98
+ { sizeBytes: number; sha256: string }
99
+ >();
89
100
 
90
101
  /**
91
102
  * Whether a client with `host_file` capability is connected.
@@ -97,10 +108,6 @@ export class HostTransferProxy {
97
108
  );
98
109
  }
99
110
 
100
- private send(msg: ServerMessage): void {
101
- broadcastMessage(msg, undefined, { targetCapability: "host_file" });
102
- }
103
-
104
111
  /**
105
112
  * Request a file transfer from the sandbox to the host machine.
106
113
  *
@@ -114,6 +121,7 @@ export class HostTransferProxy {
114
121
  destPath: string;
115
122
  overwrite: boolean;
116
123
  conversationId: string;
124
+ targetClientId?: string;
117
125
  },
118
126
  signal?: AbortSignal,
119
127
  ): Promise<ToolExecutionResult> {
@@ -121,13 +129,32 @@ export class HostTransferProxy {
121
129
  return Promise.resolve({ content: "Aborted", isError: true });
122
130
  }
123
131
 
132
+ let resolvedTargetClientId: string | undefined = input.targetClientId;
133
+ if (resolvedTargetClientId != null) {
134
+ const client = assistantEventHub.getClientById(resolvedTargetClientId);
135
+ if (!client) {
136
+ return Promise.resolve({
137
+ content: `No connected client with id '${resolvedTargetClientId}' supports host_file. Run \`assistant clients list --capability host_file\` to see available clients.`,
138
+ isError: true,
139
+ });
140
+ }
141
+ if (!client.capabilities.includes("host_file")) {
142
+ return Promise.resolve({
143
+ content: `Client '${resolvedTargetClientId}' does not support host_file. Run \`assistant clients list --capability host_file\` to see available clients.`,
144
+ isError: true,
145
+ });
146
+ }
147
+ } else {
148
+ const capable = assistantEventHub.listClientsByCapability("host_file");
149
+ if (capable.length === 1) resolvedTargetClientId = capable[0].clientId;
150
+ }
151
+
124
152
  const requestId = uuid();
125
153
  const transferId = uuid();
126
154
 
127
155
  return new Promise<ToolExecutionResult>((resolve, reject) => {
128
156
  readFile(input.sourcePath)
129
157
  .then((fileBuffer) => {
130
- // Check again after async read in case signal fired during I/O.
131
158
  if (signal?.aborted) {
132
159
  resolve({ content: "Aborted", isError: true });
133
160
  return;
@@ -142,37 +169,40 @@ export class HostTransferProxy {
142
169
  let detachAbort: () => void = () => {};
143
170
 
144
171
  const timer = setTimeout(() => {
145
- this.pending.delete(requestId);
146
172
  this.transfers.delete(transferId);
147
- detachAbort();
148
173
  pendingInteractions.resolve(requestId);
149
174
  log.warn(
150
175
  { requestId, transferId, direction: "to_host" },
151
176
  "Host transfer proxy request timed out",
152
177
  );
153
178
  resolve({
154
- content:
155
- "Host transfer proxy timed out waiting for client response",
179
+ content: resolvedTargetClientId
180
+ ? `Host transfer proxy timed out waiting for response from client '${resolvedTargetClientId}'`
181
+ : "Host transfer proxy timed out waiting for client response",
156
182
  isError: true,
157
183
  });
158
184
  }, timeoutMs);
159
185
 
160
186
  if (signal) {
161
187
  const onAbort = () => {
162
- if (this.pending.has(requestId)) {
163
- clearTimeout(timer);
164
- this.pending.delete(requestId);
188
+ if (pendingInteractions.get(requestId)) {
165
189
  this.transfers.delete(transferId);
166
- detachAbort();
167
190
  pendingInteractions.resolve(requestId);
168
191
  try {
169
- this.send({
170
- type: "host_transfer_cancel",
171
- requestId,
172
- conversationId: input.conversationId,
173
- });
192
+ broadcastMessage(
193
+ {
194
+ type: "host_transfer_cancel",
195
+ requestId,
196
+ conversationId: input.conversationId,
197
+ ...(resolvedTargetClientId != null
198
+ ? { targetClientId: resolvedTargetClientId }
199
+ : {}),
200
+ },
201
+ input.conversationId,
202
+ { targetClientId: resolvedTargetClientId },
203
+ );
174
204
  } catch {
175
- // Best-effort cancel notification — connection may already be closed.
205
+ // Best-effort cancel notification
176
206
  }
177
207
  resolve({ content: "Aborted", isError: true });
178
208
  }
@@ -181,40 +211,49 @@ export class HostTransferProxy {
181
211
  detachAbort = () => signal.removeEventListener("abort", onAbort);
182
212
  }
183
213
 
184
- const entry: PendingTransfer = {
185
- resolve,
186
- reject,
187
- timer,
214
+ this.transfers.set(transferId, {
188
215
  requestId,
189
216
  transferId,
190
- conversationId: input.conversationId,
191
217
  direction: "to_host",
192
218
  filePath: input.destPath,
193
219
  sizeBytes,
194
220
  sha256,
195
221
  fileBuffer,
222
+ targetClientId: resolvedTargetClientId,
223
+ });
224
+
225
+ pendingInteractions.register(requestId, {
226
+ conversationId: input.conversationId,
227
+ kind: "host_transfer",
228
+ targetClientId: resolvedTargetClientId,
229
+ rpcResolve: resolve,
230
+ rpcReject: reject,
231
+ timer,
196
232
  detachAbort,
197
- };
198
- this.pending.set(requestId, entry);
199
- this.transfers.set(transferId, entry);
233
+ metadata: { transferId },
234
+ });
200
235
 
201
236
  try {
202
- this.send({
203
- type: "host_transfer_request",
204
- requestId,
205
- conversationId: input.conversationId,
206
- direction: "to_host",
207
- transferId,
208
- destPath: input.destPath,
209
- sizeBytes,
210
- sha256,
211
- overwrite: input.overwrite,
212
- });
237
+ broadcastMessage(
238
+ {
239
+ type: "host_transfer_request",
240
+ requestId,
241
+ conversationId: input.conversationId,
242
+ direction: "to_host",
243
+ transferId,
244
+ destPath: input.destPath,
245
+ sizeBytes,
246
+ sha256,
247
+ overwrite: input.overwrite,
248
+ ...(resolvedTargetClientId != null
249
+ ? { targetClientId: resolvedTargetClientId }
250
+ : {}),
251
+ },
252
+ input.conversationId,
253
+ { targetClientId: resolvedTargetClientId },
254
+ );
213
255
  } catch (err) {
214
- clearTimeout(timer);
215
- this.pending.delete(requestId);
216
256
  this.transfers.delete(transferId);
217
- detachAbort();
218
257
  pendingInteractions.resolve(requestId);
219
258
  log.warn(
220
259
  { requestId, transferId, err },
@@ -249,6 +288,7 @@ export class HostTransferProxy {
249
288
  destPath: string;
250
289
  overwrite?: boolean;
251
290
  conversationId: string;
291
+ targetClientId?: string;
252
292
  },
253
293
  signal?: AbortSignal,
254
294
  ): Promise<ToolExecutionResult> {
@@ -256,6 +296,26 @@ export class HostTransferProxy {
256
296
  return Promise.resolve({ content: "Aborted", isError: true });
257
297
  }
258
298
 
299
+ let resolvedTargetClientId: string | undefined = input.targetClientId;
300
+ if (resolvedTargetClientId != null) {
301
+ const client = assistantEventHub.getClientById(resolvedTargetClientId);
302
+ if (!client) {
303
+ return Promise.resolve({
304
+ content: `No connected client with id '${resolvedTargetClientId}' supports host_file. Run \`assistant clients list --capability host_file\` to see available clients.`,
305
+ isError: true,
306
+ });
307
+ }
308
+ if (!client.capabilities.includes("host_file")) {
309
+ return Promise.resolve({
310
+ content: `Client '${resolvedTargetClientId}' does not support host_file. Run \`assistant clients list --capability host_file\` to see available clients.`,
311
+ isError: true,
312
+ });
313
+ }
314
+ } else {
315
+ const capable = assistantEventHub.listClientsByCapability("host_file");
316
+ if (capable.length === 1) resolvedTargetClientId = capable[0].clientId;
317
+ }
318
+
259
319
  const requestId = uuid();
260
320
  const transferId = uuid();
261
321
 
@@ -265,36 +325,40 @@ export class HostTransferProxy {
265
325
  let detachAbort: () => void = () => {};
266
326
 
267
327
  const timer = setTimeout(() => {
268
- this.pending.delete(requestId);
269
328
  this.transfers.delete(transferId);
270
- detachAbort();
271
329
  pendingInteractions.resolve(requestId);
272
330
  log.warn(
273
331
  { requestId, transferId, direction: "to_sandbox" },
274
332
  "Host transfer proxy request timed out",
275
333
  );
276
334
  resolve({
277
- content: "Host transfer proxy timed out waiting for client response",
335
+ content: resolvedTargetClientId
336
+ ? `Host transfer proxy timed out waiting for response from client '${resolvedTargetClientId}'`
337
+ : "Host transfer proxy timed out waiting for client response",
278
338
  isError: true,
279
339
  });
280
340
  }, timeoutMs);
281
341
 
282
342
  if (signal) {
283
343
  const onAbort = () => {
284
- if (this.pending.has(requestId)) {
285
- clearTimeout(timer);
286
- this.pending.delete(requestId);
344
+ if (pendingInteractions.get(requestId)) {
287
345
  this.transfers.delete(transferId);
288
- detachAbort();
289
346
  pendingInteractions.resolve(requestId);
290
347
  try {
291
- this.send({
292
- type: "host_transfer_cancel",
293
- requestId,
294
- conversationId: input.conversationId,
295
- });
348
+ broadcastMessage(
349
+ {
350
+ type: "host_transfer_cancel",
351
+ requestId,
352
+ conversationId: input.conversationId,
353
+ ...(resolvedTargetClientId != null
354
+ ? { targetClientId: resolvedTargetClientId }
355
+ : {}),
356
+ },
357
+ input.conversationId,
358
+ { targetClientId: resolvedTargetClientId },
359
+ );
296
360
  } catch {
297
- // Best-effort cancel notification — connection may already be closed.
361
+ // Best-effort cancel notification
298
362
  }
299
363
  resolve({ content: "Aborted", isError: true });
300
364
  }
@@ -303,35 +367,44 @@ export class HostTransferProxy {
303
367
  detachAbort = () => signal.removeEventListener("abort", onAbort);
304
368
  }
305
369
 
306
- const entry: PendingTransfer = {
307
- resolve,
308
- reject,
309
- timer,
370
+ this.transfers.set(transferId, {
310
371
  requestId,
311
372
  transferId,
312
- conversationId: input.conversationId,
313
373
  direction: "to_sandbox",
314
374
  filePath: input.destPath,
315
375
  overwrite: input.overwrite,
376
+ targetClientId: resolvedTargetClientId,
377
+ });
378
+
379
+ pendingInteractions.register(requestId, {
380
+ conversationId: input.conversationId,
381
+ kind: "host_transfer",
382
+ targetClientId: resolvedTargetClientId,
383
+ rpcResolve: resolve,
384
+ rpcReject: reject,
385
+ timer,
316
386
  detachAbort,
317
- };
318
- this.pending.set(requestId, entry);
319
- this.transfers.set(transferId, entry);
387
+ metadata: { transferId },
388
+ });
320
389
 
321
390
  try {
322
- this.send({
323
- type: "host_transfer_request",
324
- requestId,
325
- conversationId: input.conversationId,
326
- direction: "to_sandbox",
327
- transferId,
328
- sourcePath: input.sourcePath,
329
- });
391
+ broadcastMessage(
392
+ {
393
+ type: "host_transfer_request",
394
+ requestId,
395
+ conversationId: input.conversationId,
396
+ direction: "to_sandbox",
397
+ transferId,
398
+ sourcePath: input.sourcePath,
399
+ ...(resolvedTargetClientId != null
400
+ ? { targetClientId: resolvedTargetClientId }
401
+ : {}),
402
+ },
403
+ input.conversationId,
404
+ { targetClientId: resolvedTargetClientId },
405
+ );
330
406
  } catch (err) {
331
- clearTimeout(timer);
332
- this.pending.delete(requestId);
333
407
  this.transfers.delete(transferId);
334
- detachAbort();
335
408
  pendingInteractions.resolve(requestId);
336
409
  log.warn(
337
410
  { requestId, transferId, err },
@@ -353,23 +426,21 @@ export class HostTransferProxy {
353
426
  errorMessage?: string;
354
427
  },
355
428
  ): void {
356
- const entry = this.pending.get(requestId);
357
- if (!entry) {
429
+ const interaction = pendingInteractions.resolve(requestId);
430
+ if (!interaction?.rpcResolve) {
358
431
  log.warn({ requestId }, "No pending host transfer request for response");
359
432
  return;
360
433
  }
361
- clearTimeout(entry.timer);
362
- entry.detachAbort();
363
- this.pending.delete(requestId);
364
- this.transfers.delete(entry.transferId);
434
+ const transferId = interaction.metadata?.transferId as string | undefined;
435
+ if (transferId) this.transfers.delete(transferId);
365
436
 
366
437
  if (result.isError) {
367
- entry.resolve({
438
+ interaction.rpcResolve({
368
439
  content: result.errorMessage ?? "Host transfer failed",
369
440
  isError: true,
370
441
  });
371
442
  } else {
372
- entry.resolve({
443
+ interaction.rpcResolve({
373
444
  content: `File transferred successfully${result.bytesWritten != null ? ` (${result.bytesWritten} bytes)` : ""}`,
374
445
  isError: false,
375
446
  });
@@ -394,8 +465,21 @@ export class HostTransferProxy {
394
465
  ) {
395
466
  return null;
396
467
  }
397
- // Single-use: consume the transfer from the transfers map.
398
- // The pending map entry stays alive for the result resolution.
468
+ // Stash size/sha256 so the GET-content route's `resolveResponseHeaders`
469
+ // callback can still set `Content-Length` and `X-Transfer-SHA256` on the
470
+ // response. The HTTP adapter invokes the handler (this method) BEFORE the
471
+ // response-header resolver, so without this stash the resolver sees a
472
+ // deleted entry and silently falls back to default headers.
473
+ this.justConsumedMetadata.set(transferId, {
474
+ sizeBytes: entry.sizeBytes,
475
+ sha256: entry.sha256,
476
+ });
477
+ // Fallback cleanup: if the resolver never reads (e.g., handler error after
478
+ // consume, request abort), drop the metadata after a short grace window.
479
+ // `unref()` so the timer never holds the process open.
480
+ setTimeout(() => {
481
+ this.justConsumedMetadata.delete(transferId);
482
+ }, 30_000).unref?.();
399
483
  this.transfers.delete(transferId);
400
484
  return {
401
485
  buffer: entry.fileBuffer,
@@ -404,6 +488,23 @@ export class HostTransferProxy {
404
488
  };
405
489
  }
406
490
 
491
+ /**
492
+ * Returns and clears the size/sha256 metadata for a transfer that was just
493
+ * consumed by `getTransferContent`. Intended for use by the GET-content
494
+ * route's `resolveResponseHeaders` callback to populate `Content-Length` and
495
+ * `X-Transfer-SHA256` response headers. Returns null if no metadata is
496
+ * cached (e.g., transfer was never consumed, or already read by a previous
497
+ * resolver call).
498
+ */
499
+ takeJustConsumedTransferMetadata(
500
+ transferId: string,
501
+ ): { sizeBytes: number; sha256: string } | null {
502
+ const meta = this.justConsumedMetadata.get(transferId);
503
+ if (!meta) return null;
504
+ this.justConsumedMetadata.delete(transferId);
505
+ return meta;
506
+ }
507
+
407
508
  /**
408
509
  * Receive file content from the client for a to_sandbox transfer (the PUT content endpoint).
409
510
  *
@@ -437,29 +538,25 @@ export class HostTransferProxy {
437
538
 
438
539
  const { requestId } = entry;
439
540
 
440
- // Enforce overwrite policy before writing.
441
541
  if (entry.overwrite !== true && existsSync(entry.filePath)) {
442
542
  const errorMsg = `Destination file already exists: ${entry.filePath}. Set overwrite to true to replace it.`;
443
- clearTimeout(entry.timer);
444
- entry.detachAbort();
445
- this.pending.delete(requestId);
543
+ const interaction = pendingInteractions.resolve(requestId);
446
544
  this.transfers.delete(transferId);
447
- entry.resolve({ content: errorMsg, isError: true });
545
+ interaction?.rpcResolve?.({ content: errorMsg, isError: true });
448
546
  return { accepted: false, error: errorMsg };
449
547
  }
450
548
 
451
549
  const cleanup = () => {
452
- clearTimeout(entry.timer);
453
- entry.detachAbort();
454
- this.pending.delete(requestId);
550
+ pendingInteractions.resolve(requestId);
455
551
  this.transfers.delete(transferId);
456
552
  };
457
553
 
458
554
  try {
459
555
  await mkdir(dirname(entry.filePath), { recursive: true });
460
556
  await writeFile(entry.filePath, data);
557
+ const interaction = pendingInteractions.get(requestId);
461
558
  cleanup();
462
- entry.resolve({
559
+ interaction?.rpcResolve?.({
463
560
  content: `File received and written to ${entry.filePath} (${data.length} bytes)`,
464
561
  isError: false,
465
562
  });
@@ -470,31 +567,37 @@ export class HostTransferProxy {
470
567
  { transferId, filePath: entry.filePath, err },
471
568
  "Failed to write received transfer content",
472
569
  );
570
+ const interaction = pendingInteractions.get(requestId);
473
571
  cleanup();
474
- entry.resolve({ content: errorMsg, isError: true });
572
+ interaction?.rpcResolve?.({ content: errorMsg, isError: true });
475
573
  return { accepted: false, error: errorMsg };
476
574
  }
477
575
  }
478
576
 
479
577
  /** Cancel a pending transfer by requestId. */
480
578
  cancel(requestId: string): void {
481
- const entry = this.pending.get(requestId);
482
- if (!entry) return;
483
- clearTimeout(entry.timer);
484
- entry.detachAbort();
485
- this.pending.delete(requestId);
486
- this.transfers.delete(entry.transferId);
579
+ const interaction = pendingInteractions.get(requestId);
580
+ if (!interaction) return;
581
+ const transferId = interaction.metadata?.transferId as string | undefined;
582
+ if (transferId) this.transfers.delete(transferId);
487
583
  pendingInteractions.resolve(requestId);
488
584
  try {
489
- this.send({
490
- type: "host_transfer_cancel",
491
- requestId,
492
- conversationId: entry.conversationId,
493
- });
585
+ broadcastMessage(
586
+ {
587
+ type: "host_transfer_cancel",
588
+ requestId,
589
+ conversationId: interaction.conversationId,
590
+ ...(interaction.targetClientId != null
591
+ ? { targetClientId: interaction.targetClientId }
592
+ : {}),
593
+ },
594
+ interaction.conversationId,
595
+ { targetClientId: interaction.targetClientId },
596
+ );
494
597
  } catch {
495
- // Best-effort cancel notification — connection may already be closed.
598
+ // Best-effort cancel notification
496
599
  }
497
- entry.resolve({ content: "Transfer cancelled", isError: true });
600
+ interaction.rpcResolve?.({ content: "Transfer cancelled", isError: true });
498
601
  }
499
602
 
500
603
  hasPendingTransfer(transferId: string): boolean {
@@ -511,28 +614,43 @@ export class HostTransferProxy {
511
614
  return entry?.requestId ?? null;
512
615
  }
513
616
 
617
+ /**
618
+ * Look up the targetClientId for a given transferId without consuming the entry.
619
+ * Routes call this to verify ownership without affecting the transfer state.
620
+ * Returns null when untargeted (no validation needed).
621
+ */
622
+ getTargetClientIdForTransfer(transferId: string): string | null {
623
+ return this.transfers.get(transferId)?.targetClientId ?? null;
624
+ }
625
+
514
626
  dispose(): void {
515
- for (const [requestId, entry] of this.pending) {
516
- clearTimeout(entry.timer);
517
- entry.detachAbort();
518
- pendingInteractions.resolve(requestId);
627
+ for (const entry of pendingInteractions.getByKind("host_transfer")) {
628
+ const transferId = entry.metadata?.transferId as string | undefined;
629
+ if (transferId) this.transfers.delete(transferId);
630
+ pendingInteractions.resolve(entry.requestId);
519
631
  try {
520
- this.send({
632
+ broadcastMessage(
633
+ {
521
634
  type: "host_transfer_cancel",
522
- requestId,
635
+ requestId: entry.requestId,
523
636
  conversationId: entry.conversationId,
524
- });
525
- } catch {
526
- // Best-effort cancel notification — connection may already be closed.
527
- }
528
- entry.reject(
637
+ ...(entry.targetClientId != null
638
+ ? { targetClientId: entry.targetClientId }
639
+ : {}),
640
+ },
641
+ entry.conversationId,
642
+ { targetClientId: entry.targetClientId },
643
+ );
644
+ } catch {
645
+ // Best-effort cancel notification
646
+ }
647
+ entry.rpcReject?.(
529
648
  new AssistantError(
530
649
  "Host transfer proxy disposed",
531
650
  ErrorCode.INTERNAL_ERROR,
532
651
  ),
533
652
  );
534
653
  }
535
- this.pending.clear();
536
654
  this.transfers.clear();
537
655
  }
538
656
  }