@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
@@ -9,6 +9,9 @@
9
9
  * Unlike HostBashProxy/HostFileProxy/HostTransferProxy, this is NOT a
10
10
  * singleton — each conversation gets its own instance because CU state
11
11
  * (step count, AX tree history, loop detection) is per-conversation.
12
+ *
13
+ * RPC lifecycle (resolve/reject/timer/detachAbort) is stored in
14
+ * pendingInteractions alongside routing metadata.
12
15
  */
13
16
 
14
17
  import { v4 as uuid } from "uuid";
@@ -24,7 +27,6 @@ import * as pendingInteractions from "../runtime/pending-interactions.js";
24
27
  import type { ToolExecutionResult } from "../tools/types.js";
25
28
  import { AssistantError, ErrorCode } from "../util/errors.js";
26
29
  import { getLogger } from "../util/logger.js";
27
- import type { ServerMessage } from "./message-protocol.js";
28
30
 
29
31
  const log = getLogger("host-cu-proxy");
30
32
 
@@ -62,28 +64,19 @@ export interface ActionRecord {
62
64
  reasoning?: string;
63
65
  }
64
66
 
65
- interface PendingRequest {
66
- resolve: (result: ToolExecutionResult) => void;
67
- reject: (err: Error) => void;
68
- timer: ReturnType<typeof setTimeout>;
69
- conversationId: string;
70
- /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
71
- detachAbort: () => void;
72
- }
73
-
74
67
  // ---------------------------------------------------------------------------
75
68
  // HostCuProxy
76
69
  // ---------------------------------------------------------------------------
77
70
 
78
71
  export class HostCuProxy {
79
- private pending = new Map<string, PendingRequest>();
80
-
81
72
  // CU state tracking (per-conversation)
82
73
  private _stepCount = 0;
83
74
  private _maxSteps: number;
84
75
  private _previousAXTree: string | undefined;
85
76
  private _consecutiveUnchangedSteps = 0;
86
77
  private _actionHistory: ActionRecord[] = [];
78
+ /** Request IDs owned by this instance — used to scope dispose(). */
79
+ private _ownedRequests = new Set<string>();
87
80
 
88
81
  constructor(maxSteps = loadConfig().maxStepsPerSession) {
89
82
  this._maxSteps = maxSteps;
@@ -121,17 +114,7 @@ export class HostCuProxy {
121
114
  * Whether a client with `host_cu` capability is connected.
122
115
  */
123
116
  isAvailable(): boolean {
124
- return (
125
- assistantEventHub.getMostRecentClientByCapability("host_cu") != null
126
- );
127
- }
128
-
129
- // ---------------------------------------------------------------------------
130
- // Send helper
131
- // ---------------------------------------------------------------------------
132
-
133
- private send(msg: ServerMessage): void {
134
- broadcastMessage(msg, undefined, { targetCapability: "host_cu" });
117
+ return assistantEventHub.getMostRecentClientByCapability("host_cu") != null;
135
118
  }
136
119
 
137
120
  // ---------------------------------------------------------------------------
@@ -145,6 +128,7 @@ export class HostCuProxy {
145
128
  stepNumber: number,
146
129
  reasoning?: string,
147
130
  signal?: AbortSignal,
131
+ targetClientId?: string,
148
132
  ): Promise<ToolExecutionResult> {
149
133
  if (signal?.aborted) {
150
134
  return Promise.resolve({
@@ -153,7 +137,6 @@ export class HostCuProxy {
153
137
  });
154
138
  }
155
139
 
156
- // Enforce step limit before sending to client
157
140
  if (this._stepCount > this._maxSteps) {
158
141
  return Promise.resolve({
159
142
  content: `Step limit (${this._maxSteps}) exceeded. Call computer_use_done to finish.`,
@@ -167,8 +150,7 @@ export class HostCuProxy {
167
150
  let detachAbort: () => void = () => {};
168
151
 
169
152
  const timer = setTimeout(() => {
170
- this.pending.delete(requestId);
171
- detachAbort();
153
+ this._ownedRequests.delete(requestId);
172
154
  pendingInteractions.resolve(requestId);
173
155
  log.warn({ requestId, toolName }, "Host CU proxy request timed out");
174
156
  resolve({
@@ -179,19 +161,24 @@ export class HostCuProxy {
179
161
 
180
162
  if (signal) {
181
163
  const onAbort = () => {
182
- if (this.pending.has(requestId)) {
183
- clearTimeout(timer);
184
- this.pending.delete(requestId);
185
- detachAbort();
164
+ if (pendingInteractions.get(requestId)) {
165
+ this._ownedRequests.delete(requestId);
186
166
  pendingInteractions.resolve(requestId);
187
167
  try {
188
- this.send({
189
- type: "host_cu_cancel",
190
- requestId,
168
+ broadcastMessage(
169
+ {
170
+ type: "host_cu_cancel",
171
+ requestId,
172
+ conversationId,
173
+ ...(targetClientId != null
174
+ ? { targetClientId }
175
+ : {}),
176
+ },
191
177
  conversationId,
192
- });
178
+ { targetClientId },
179
+ );
193
180
  } catch {
194
- // Best-effort cancel notification — connection may already be closed.
181
+ // Best-effort cancel notification
195
182
  }
196
183
  resolve({ content: "Aborted", isError: true });
197
184
  }
@@ -200,22 +187,36 @@ export class HostCuProxy {
200
187
  detachAbort = () => signal.removeEventListener("abort", onAbort);
201
188
  }
202
189
 
203
- this.pending.set(requestId, { resolve, reject, timer, conversationId, detachAbort });
190
+ this._ownedRequests.add(requestId);
191
+
192
+ pendingInteractions.register(requestId, {
193
+ conversationId,
194
+ kind: "host_cu",
195
+ targetClientId,
196
+ rpcResolve: resolve,
197
+ rpcReject: reject,
198
+ timer,
199
+ detachAbort,
200
+ });
204
201
 
205
202
  try {
206
- this.send({
207
- type: "host_cu_request",
208
- requestId,
203
+ broadcastMessage(
204
+ {
205
+ type: "host_cu_request",
206
+ requestId,
207
+ conversationId,
208
+ toolName,
209
+ input,
210
+ stepNumber,
211
+ reasoning,
212
+ // Include in body so receiving client can verify targeted endpoint.
213
+ ...(targetClientId != null ? { targetClientId } : {}),
214
+ },
209
215
  conversationId,
210
- toolName,
211
- input,
212
- stepNumber,
213
- reasoning,
214
- });
216
+ { targetClientId },
217
+ );
215
218
  } catch (err) {
216
- clearTimeout(timer);
217
- this.pending.delete(requestId);
218
- detachAbort();
219
+ this._ownedRequests.delete(requestId);
219
220
  pendingInteractions.resolve(requestId);
220
221
  log.warn({ requestId, toolName, err }, "Host CU proxy send failed");
221
222
  reject(err instanceof Error ? err : new Error(String(err)));
@@ -223,28 +224,27 @@ export class HostCuProxy {
223
224
  });
224
225
  }
225
226
 
226
- resolve(requestId: string, observation: CuObservationResult): void {
227
- const entry = this.pending.get(requestId);
228
- if (!entry) {
227
+ /**
228
+ * Process a CU observation from the client and resolve the RPC.
229
+ * Updates CU state (step tracking, AX tree history) and formats
230
+ * the observation into a ToolExecutionResult.
231
+ */
232
+ processObservation(
233
+ requestId: string,
234
+ observation: CuObservationResult,
235
+ ): ToolExecutionResult | undefined {
236
+ this._ownedRequests.delete(requestId);
237
+ const interaction = pendingInteractions.resolve(requestId);
238
+ if (!interaction?.rpcResolve) {
229
239
  log.warn({ requestId }, "No pending host CU request for response");
230
- return;
240
+ return undefined;
231
241
  }
232
- clearTimeout(entry.timer);
233
- entry.detachAbort();
234
- this.pending.delete(requestId);
235
242
 
236
- // Capture pre-update state so formatObservation sees the correct previous AX tree
237
243
  const prevAXTree = this._previousAXTree;
238
-
239
- // Update CU state from observation
240
244
  this.updateStateFromObservation(observation);
241
-
242
245
  const result = this.formatObservation(observation, prevAXTree);
243
- entry.resolve(result);
244
- }
245
-
246
- hasPendingRequest(requestId: string): boolean {
247
- return this.pending.has(requestId);
246
+ interaction.rpcResolve(result);
247
+ return result;
248
248
  }
249
249
 
250
250
  // ---------------------------------------------------------------------------
@@ -267,7 +267,6 @@ export class HostCuProxy {
267
267
  input,
268
268
  reasoning,
269
269
  });
270
- // Keep history bounded
271
270
  if (this._actionHistory.length > MAX_HISTORY_ENTRIES) {
272
271
  this._actionHistory = this._actionHistory.slice(-MAX_HISTORY_ENTRIES);
273
272
  }
@@ -297,7 +296,6 @@ export class HostCuProxy {
297
296
  const prevTree = previousAXTree;
298
297
  const parts: string[] = [];
299
298
 
300
- // Surface user guidance prominently so the model sees it first
301
299
  if (obs.userGuidance) {
302
300
  parts.push(`USER GUIDANCE: ${obs.userGuidance}`);
303
301
  parts.push("");
@@ -308,12 +306,10 @@ export class HostCuProxy {
308
306
  parts.push("");
309
307
  }
310
308
 
311
- // AX tree diff / unchanged warning
312
309
  if (obs.axDiff) {
313
310
  parts.push(obs.axDiff);
314
311
  parts.push("");
315
312
  } else if (prevTree != null && obs.axTree != null) {
316
- // Skip unchanged warning after wait actions — they intentionally yield no immediate change
317
313
  const lastAction =
318
314
  this._actionHistory.length > 0
319
315
  ? this._actionHistory[this._actionHistory.length - 1]
@@ -321,7 +317,6 @@ export class HostCuProxy {
321
317
  const isWaitAction = lastAction?.toolName === "computer_use_wait";
322
318
 
323
319
  if (!isWaitAction) {
324
- // No diff means the screen didn't change
325
320
  if (
326
321
  this._consecutiveUnchangedSteps >=
327
322
  CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD
@@ -338,7 +333,6 @@ export class HostCuProxy {
338
333
  }
339
334
  }
340
335
 
341
- // Loop detection: identical actions repeated
342
336
  if (this._actionHistory.length >= LOOP_DETECTION_WINDOW) {
343
337
  const recent = this._actionHistory.slice(-LOOP_DETECTION_WINDOW);
344
338
  const allIdentical = recent.every(
@@ -354,7 +348,6 @@ export class HostCuProxy {
354
348
  }
355
349
  }
356
350
 
357
- // Current screen state wrapped in markers for history compaction
358
351
  if (obs.axTree) {
359
352
  parts.push("<ax-tree>");
360
353
  parts.push("CURRENT SCREEN STATE:");
@@ -362,7 +355,6 @@ export class HostCuProxy {
362
355
  parts.push("</ax-tree>");
363
356
  }
364
357
 
365
- // Secondary windows for cross-app awareness
366
358
  if (obs.secondaryWindows) {
367
359
  parts.push("");
368
360
  parts.push(obs.secondaryWindows);
@@ -372,7 +364,6 @@ export class HostCuProxy {
372
364
  );
373
365
  }
374
366
 
375
- // Screenshot metadata
376
367
  const screenshotMeta = this.formatScreenshotMetadata(obs);
377
368
  if (screenshotMeta.length > 0) {
378
369
  parts.push("");
@@ -381,7 +372,6 @@ export class HostCuProxy {
381
372
 
382
373
  const content = parts.join("\n").trim() || "Action executed";
383
374
 
384
- // Build content blocks for screenshot
385
375
  const contentBlocks: ContentBlock[] = [];
386
376
  if (obs.screenshot) {
387
377
  contentBlocks.push({
@@ -410,31 +400,36 @@ export class HostCuProxy {
410
400
  // ---------------------------------------------------------------------------
411
401
 
412
402
  dispose(): void {
413
- for (const [requestId, entry] of this.pending) {
414
- clearTimeout(entry.timer);
415
- entry.detachAbort();
416
- pendingInteractions.resolve(requestId);
403
+ for (const requestId of this._ownedRequests) {
404
+ const entry = pendingInteractions.resolve(requestId);
405
+ if (!entry) continue;
417
406
  try {
418
- this.send({
419
- type: "host_cu_cancel",
420
- requestId,
421
- conversationId: entry.conversationId,
422
- });
407
+ broadcastMessage(
408
+ {
409
+ type: "host_cu_cancel",
410
+ requestId,
411
+ conversationId: entry.conversationId,
412
+ ...(entry.targetClientId != null
413
+ ? { targetClientId: entry.targetClientId }
414
+ : {}),
415
+ },
416
+ entry.conversationId,
417
+ { targetClientId: entry.targetClientId as string | undefined },
418
+ );
423
419
  } catch {
424
- // Best-effort cancel notification — connection may already be closed.
420
+ // Best-effort cancel notification
425
421
  }
426
- entry.reject(
422
+ entry.rpcReject?.(
427
423
  new AssistantError("Host CU proxy disposed", ErrorCode.INTERNAL_ERROR),
428
424
  );
429
425
  }
430
- this.pending.clear();
426
+ this._ownedRequests.clear();
431
427
  }
432
428
 
433
429
  // ---------------------------------------------------------------------------
434
430
  // Private helpers
435
431
  // ---------------------------------------------------------------------------
436
432
 
437
- /** Update consecutive-unchanged tracking from an incoming observation. */
438
433
  private updateStateFromObservation(obs: CuObservationResult): void {
439
434
  if (this._stepCount > 0) {
440
435
  if (
@@ -9,7 +9,6 @@ import { readImageBase64 } from "../tools/shared/filesystem/image-read.js";
9
9
  import type { ToolExecutionResult } from "../tools/types.js";
10
10
  import { AssistantError, ErrorCode } from "../util/errors.js";
11
11
  import { getLogger } from "../util/logger.js";
12
- import type { ServerMessage } from "./message-protocol.js";
13
12
  import type { HostFileRequest } from "./message-types/host-file.js";
14
13
 
15
14
  /** Distributive omit that preserves union variant fields. */
@@ -25,17 +24,6 @@ export type HostFileInput = DistributiveOmit<
25
24
 
26
25
  const log = getLogger("host-file-proxy");
27
26
 
28
- interface PendingRequest {
29
- resolve: (result: ToolExecutionResult) => void;
30
- reject: (err: Error) => void;
31
- timer: ReturnType<typeof setTimeout>;
32
- operation: HostFileInput["operation"];
33
- path: string;
34
- conversationId: string;
35
- /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
36
- detachAbort: () => void;
37
- }
38
-
39
27
  export class HostFileProxy {
40
28
  private static _instance: HostFileProxy | null = null;
41
29
 
@@ -65,8 +53,6 @@ export class HostFileProxy {
65
53
  HostFileProxy._instance = null;
66
54
  }
67
55
 
68
- private pending = new Map<string, PendingRequest>();
69
-
70
56
  /**
71
57
  * Whether a client with `host_file` capability is connected.
72
58
  * Note: host_file covers both file operations and transfers.
@@ -77,56 +63,81 @@ export class HostFileProxy {
77
63
  );
78
64
  }
79
65
 
80
- private send(msg: ServerMessage): void {
81
- broadcastMessage(msg, undefined, { targetCapability: "host_file" });
82
- }
83
-
84
66
  request(
85
67
  input: HostFileInput,
86
68
  conversationId: string,
87
69
  signal?: AbortSignal,
70
+ targetClientId?: string,
88
71
  ): Promise<ToolExecutionResult> {
89
72
  if (signal?.aborted) {
90
73
  return Promise.resolve({ content: "Aborted", isError: true });
91
74
  }
92
75
 
76
+ // Resolve targetClientId: explicit → validate; single capable client → auto-resolve.
77
+ // Callers may embed targetClientId in the input object (tool handlers) or pass it as
78
+ // the 4th parameter (legacy). Prefer the explicit param; fall back to input field.
79
+ let resolvedTargetClientId: string | undefined = targetClientId ?? input.targetClientId;
80
+ if (resolvedTargetClientId != null) {
81
+ const client = assistantEventHub.getClientById(resolvedTargetClientId);
82
+ if (!client) {
83
+ return Promise.resolve({
84
+ content: `No connected client with id '${resolvedTargetClientId}' supports host_file. Run \`assistant clients list --capability host_file\` to see available clients.`,
85
+ isError: true,
86
+ });
87
+ }
88
+ if (!client.capabilities.includes("host_file")) {
89
+ return Promise.resolve({
90
+ content: `Client '${resolvedTargetClientId}' does not support host_file. Run \`assistant clients list --capability host_file\` to see available clients.`,
91
+ isError: true,
92
+ });
93
+ }
94
+ } else {
95
+ const capable = assistantEventHub.listClientsByCapability("host_file");
96
+ if (capable.length === 1) {
97
+ resolvedTargetClientId = capable[0].clientId;
98
+ }
99
+ }
100
+
93
101
  const requestId = uuid();
94
102
 
95
103
  return new Promise<ToolExecutionResult>((resolve, reject) => {
96
- // File operations should be fast — 30 second timeout.
97
104
  const timeoutSec = 30;
98
105
 
99
106
  let detachAbort: () => void = () => {};
100
107
 
101
108
  const timer = setTimeout(() => {
102
- this.pending.delete(requestId);
103
- detachAbort();
104
109
  pendingInteractions.resolve(requestId);
105
110
  log.warn(
106
111
  { requestId, operation: input.operation },
107
112
  "Host file proxy request timed out",
108
113
  );
109
114
  resolve({
110
- content: "Host file proxy timed out waiting for client response",
115
+ content: resolvedTargetClientId
116
+ ? `Host file proxy timed out waiting for response from client '${resolvedTargetClientId}'`
117
+ : "Host file proxy timed out waiting for client response",
111
118
  isError: true,
112
119
  });
113
120
  }, timeoutSec * 1000);
114
121
 
115
122
  if (signal) {
116
123
  const onAbort = () => {
117
- if (this.pending.has(requestId)) {
118
- clearTimeout(timer);
119
- this.pending.delete(requestId);
120
- detachAbort();
124
+ if (pendingInteractions.get(requestId)) {
121
125
  pendingInteractions.resolve(requestId);
122
126
  try {
123
- this.send({
124
- type: "host_file_cancel",
125
- requestId,
127
+ broadcastMessage(
128
+ {
129
+ type: "host_file_cancel",
130
+ requestId,
131
+ conversationId,
132
+ ...(resolvedTargetClientId != null
133
+ ? { targetClientId: resolvedTargetClientId }
134
+ : {}),
135
+ },
126
136
  conversationId,
127
- });
137
+ { targetClientId: resolvedTargetClientId },
138
+ );
128
139
  } catch {
129
- // Best-effort cancel notification — connection may already be closed.
140
+ // Best-effort cancel notification
130
141
  }
131
142
  resolve({ content: "Aborted", isError: true });
132
143
  }
@@ -135,27 +146,34 @@ export class HostFileProxy {
135
146
  detachAbort = () => signal.removeEventListener("abort", onAbort);
136
147
  }
137
148
 
138
- this.pending.set(requestId, {
139
- resolve,
140
- reject,
141
- timer,
142
- operation: input.operation,
143
- path: input.path,
149
+ pendingInteractions.register(requestId, {
144
150
  conversationId,
151
+ kind: "host_file",
152
+ targetClientId: resolvedTargetClientId,
153
+ rpcResolve: resolve,
154
+ rpcReject: reject,
155
+ timer,
145
156
  detachAbort,
157
+ metadata: { operation: input.operation, path: input.path },
146
158
  });
147
159
 
148
160
  try {
149
- this.send({
150
- ...input,
151
- type: "host_file_request",
152
- requestId,
161
+ broadcastMessage(
162
+ {
163
+ ...input,
164
+ type: "host_file_request",
165
+ requestId,
166
+ conversationId,
167
+ // Always include in message body so the receiving client can verify
168
+ // which endpoint was targeted (even when auto-resolved).
169
+ ...(resolvedTargetClientId != null
170
+ ? { targetClientId: resolvedTargetClientId }
171
+ : {}),
172
+ },
153
173
  conversationId,
154
- });
174
+ { targetClientId: resolvedTargetClientId },
175
+ );
155
176
  } catch (err) {
156
- clearTimeout(timer);
157
- this.pending.delete(requestId);
158
- detachAbort();
159
177
  pendingInteractions.resolve(requestId);
160
178
  log.warn(
161
179
  { requestId, operation: input.operation, err },
@@ -166,55 +184,61 @@ export class HostFileProxy {
166
184
  });
167
185
  }
168
186
 
187
+ /**
188
+ * Process a client result and resolve the RPC. Called by route handlers.
189
+ */
169
190
  resolve(
170
191
  requestId: string,
171
192
  response: { content: string; isError: boolean; imageData?: string },
172
193
  ): void {
173
- const entry = this.pending.get(requestId);
174
- if (!entry) {
194
+ const interaction = pendingInteractions.resolve(requestId);
195
+ if (!interaction?.rpcResolve) {
175
196
  log.warn({ requestId }, "No pending host file request for response");
176
197
  return;
177
198
  }
178
- clearTimeout(entry.timer);
179
- entry.detachAbort();
180
- this.pending.delete(requestId);
199
+ const meta = interaction.metadata ?? {};
181
200
  if (
182
- entry.operation === "read" &&
201
+ meta.operation === "read" &&
183
202
  !response.isError &&
184
203
  typeof response.imageData === "string" &&
185
204
  response.imageData.length > 0
186
205
  ) {
187
- entry.resolve(readImageBase64(response.imageData, entry.path));
206
+ interaction.rpcResolve(
207
+ readImageBase64(response.imageData, meta.path as string),
208
+ );
188
209
  return;
189
210
  }
190
- entry.resolve({ content: response.content, isError: response.isError });
191
- }
192
-
193
- hasPendingRequest(requestId: string): boolean {
194
- return this.pending.has(requestId);
211
+ interaction.rpcResolve({
212
+ content: response.content,
213
+ isError: response.isError,
214
+ });
195
215
  }
196
216
 
197
217
  dispose(): void {
198
- for (const [requestId, entry] of this.pending) {
199
- clearTimeout(entry.timer);
200
- entry.detachAbort();
201
- pendingInteractions.resolve(requestId);
218
+ for (const entry of pendingInteractions.getByKind("host_file")) {
219
+ pendingInteractions.resolve(entry.requestId);
202
220
  try {
203
- this.send({
204
- type: "host_file_cancel",
205
- requestId,
206
- conversationId: entry.conversationId,
207
- });
221
+ broadcastMessage(
222
+ {
223
+ type: "host_file_cancel",
224
+ requestId: entry.requestId,
225
+ conversationId: entry.conversationId,
226
+ ...(entry.targetClientId != null
227
+ ? { targetClientId: entry.targetClientId }
228
+ : {}),
229
+ },
230
+ entry.conversationId,
231
+ { targetClientId: entry.targetClientId as string | undefined },
232
+ );
208
233
  } catch {
209
- // Best-effort cancel notification — connection may already be closed.
234
+ // Best-effort cancel notification
210
235
  }
211
- entry.reject(
236
+ entry.rpcReject?.(
212
237
  new AssistantError(
213
238
  "Host file proxy disposed",
214
239
  ErrorCode.INTERNAL_ERROR,
215
240
  ),
216
241
  );
217
242
  }
218
- this.pending.clear();
219
243
  }
220
244
  }