@vellumai/assistant 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Tests for HostFileProxy Phase 2 targetClientId behaviour.
3
+ *
4
+ * Covers:
5
+ * - Explicit targetClientId validation (valid, unknown, incapable)
6
+ * - Auto-resolve when exactly one host_file-capable client is connected
7
+ * - Untargeted broadcast when multiple capable clients are connected
8
+ * - targetClientId propagated into cancel messages (abort + dispose)
9
+ * - Timeout message includes clientId when resolvedTargetClientId is set
10
+ */
11
+ import { afterEach, describe, expect, jest, mock, test } from "bun:test";
12
+
13
+ const sentMessages: unknown[] = [];
14
+ const sentMessageOptions: unknown[] = [];
15
+ const resolvedInteractionIds: string[] = [];
16
+ let mockHasClient = false;
17
+ let mockCapableClients: Array<{ clientId: string; capabilities: string[] }> = [];
18
+ let mockClientRegistry: Map<string, { clientId: string; capabilities: string[] }> = new Map();
19
+
20
+ mock.module("../runtime/assistant-event-hub.js", () => ({
21
+ broadcastMessage: (msg: unknown, _conversationId?: string, options?: unknown) => {
22
+ sentMessages.push(msg);
23
+ sentMessageOptions.push(options);
24
+ },
25
+ assistantEventHub: {
26
+ getMostRecentClientByCapability: (cap: string) =>
27
+ cap === "host_file" && mockHasClient ? { id: "mock-client" } : null,
28
+ listClientsByCapability: (_cap: string) => mockCapableClients,
29
+ getClientById: (clientId: string) => mockClientRegistry.get(clientId),
30
+ },
31
+ }));
32
+
33
+ const pendingInteractionMap = new Map<string, Record<string, unknown>>();
34
+ mock.module("../runtime/pending-interactions.js", () => ({
35
+ register: (requestId: string, interaction: Record<string, unknown>) => {
36
+ pendingInteractionMap.set(requestId, interaction);
37
+ },
38
+ resolve: (requestId: string) => {
39
+ const interaction = pendingInteractionMap.get(requestId);
40
+ pendingInteractionMap.delete(requestId);
41
+ resolvedInteractionIds.push(requestId);
42
+ return interaction;
43
+ },
44
+ get: (requestId: string) => pendingInteractionMap.get(requestId),
45
+ getByKind: (_kind: string) => Array.from(pendingInteractionMap.entries())
46
+ .filter(([, v]) => v.kind === _kind)
47
+ .map(([requestId, v]) => ({ requestId, ...v })),
48
+ getByConversation: () => [],
49
+ removeByConversation: () => {},
50
+ }));
51
+
52
+ const { HostFileProxy } = await import("../daemon/host-file-proxy.js");
53
+
54
+ describe("HostFileProxy — targetClientId (Phase 2)", () => {
55
+ let proxy: InstanceType<typeof HostFileProxy>;
56
+
57
+ function setup() {
58
+ sentMessages.length = 0;
59
+ sentMessageOptions.length = 0;
60
+ resolvedInteractionIds.length = 0;
61
+ pendingInteractionMap.clear();
62
+ mockHasClient = false;
63
+ mockCapableClients = [];
64
+ mockClientRegistry = new Map();
65
+ proxy = new (HostFileProxy as any)();
66
+ }
67
+
68
+ function setupSingleClient(clientId = "client-1") {
69
+ const entry = { clientId, capabilities: ["host_file"] };
70
+ mockCapableClients = [entry];
71
+ mockClientRegistry.set(clientId, entry);
72
+ }
73
+
74
+ function setupMultipleClients(clientIds: string[]) {
75
+ mockCapableClients = clientIds.map((id) => ({
76
+ clientId: id,
77
+ capabilities: ["host_file"],
78
+ }));
79
+ for (const entry of mockCapableClients) {
80
+ mockClientRegistry.set(entry.clientId, entry);
81
+ }
82
+ }
83
+
84
+ afterEach(() => {
85
+ proxy?.dispose();
86
+ HostFileProxy.reset();
87
+ });
88
+
89
+ // ── Explicit targetClientId — valid ──────────────────────────────────
90
+
91
+ describe("explicit targetClientId — valid client with host_file", () => {
92
+ test("resolves to that client and broadcasts with targetClientId option", async () => {
93
+ setup();
94
+ setupSingleClient("client-mac");
95
+ // Also add a second client so explicit targeting is meaningful
96
+ const entry2 = { clientId: "client-linux", capabilities: ["host_file"] };
97
+ mockCapableClients.push(entry2);
98
+ mockClientRegistry.set("client-linux", entry2);
99
+
100
+ const resultPromise = proxy.request(
101
+ {
102
+ operation: "read",
103
+ path: "/home/user/notes.txt",
104
+ targetClientId: "client-mac",
105
+ },
106
+ "session-1",
107
+ );
108
+
109
+ expect(sentMessages).toHaveLength(1);
110
+ const sent = sentMessages[0] as Record<string, unknown>;
111
+ expect(sent.type).toBe("host_file_request");
112
+ expect(sent.targetClientId).toBe("client-mac");
113
+
114
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
115
+ expect(opts?.targetClientId).toBe("client-mac");
116
+
117
+ const requestId = sent.requestId as string;
118
+ proxy.resolve(requestId, { content: "file contents", isError: false });
119
+
120
+ const result = await resultPromise;
121
+ expect(result.isError).toBe(false);
122
+ });
123
+ });
124
+
125
+ // ── Explicit targetClientId — unknown client ─────────────────────────
126
+
127
+ describe("explicit targetClientId — unknown client", () => {
128
+ test("returns error result immediately without broadcasting", async () => {
129
+ setup();
130
+ setupSingleClient("client-mac");
131
+
132
+ const result = await proxy.request(
133
+ {
134
+ operation: "read",
135
+ path: "/tmp/file.txt",
136
+ targetClientId: "client-unknown",
137
+ },
138
+ "session-1",
139
+ );
140
+
141
+ expect(result.isError).toBe(true);
142
+ expect(result.content).toContain("client-unknown");
143
+ expect(result.content).toContain("assistant clients list --capability host_file");
144
+ // No pending entry should have been created
145
+ expect(sentMessages).toHaveLength(0);
146
+ });
147
+
148
+ test("does not create a pending entry for unknown client", async () => {
149
+ setup();
150
+
151
+ const result = await proxy.request(
152
+ {
153
+ operation: "write",
154
+ path: "/tmp/out.txt",
155
+ content: "data",
156
+ targetClientId: "client-ghost",
157
+ },
158
+ "session-1",
159
+ );
160
+
161
+ expect(result.isError).toBe(true);
162
+ expect(sentMessages).toHaveLength(0);
163
+ });
164
+ });
165
+
166
+ // ── Explicit targetClientId — incapable client ───────────────────────
167
+
168
+ describe("explicit targetClientId — connected but lacks host_file", () => {
169
+ test("returns error result immediately without broadcasting", async () => {
170
+ setup();
171
+ // Register a client that exists but does not have host_file
172
+ mockClientRegistry.set("client-no-file", {
173
+ clientId: "client-no-file",
174
+ capabilities: ["host_bash"],
175
+ });
176
+
177
+ const result = await proxy.request(
178
+ {
179
+ operation: "read",
180
+ path: "/tmp/test.txt",
181
+ targetClientId: "client-no-file",
182
+ },
183
+ "session-1",
184
+ );
185
+
186
+ expect(result.isError).toBe(true);
187
+ expect(result.content).toContain("client-no-file");
188
+ expect(result.content).toContain("does not support host_file");
189
+ expect(sentMessages).toHaveLength(0);
190
+ });
191
+ });
192
+
193
+ // ── Auto-resolve single capable client ───────────────────────────────
194
+
195
+ describe("auto-resolve single capable client", () => {
196
+ test("resolves target when exactly one host_file-capable client is connected", async () => {
197
+ setup();
198
+ setupSingleClient("client-solo");
199
+
200
+ const resultPromise = proxy.request(
201
+ { operation: "read", path: "/tmp/file.txt" },
202
+ "session-1",
203
+ );
204
+
205
+ expect(sentMessages).toHaveLength(1);
206
+ const sent = sentMessages[0] as Record<string, unknown>;
207
+ expect(sent.targetClientId).toBe("client-solo");
208
+
209
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
210
+ expect(opts?.targetClientId).toBe("client-solo");
211
+
212
+ const requestId = sent.requestId as string;
213
+ proxy.resolve(requestId, { content: "ok", isError: false });
214
+
215
+ const result = await resultPromise;
216
+ expect(result.isError).toBe(false);
217
+ });
218
+ });
219
+
220
+ // ── No target — multiple capable clients ─────────────────────────────
221
+
222
+ describe("no explicit target — multiple capable clients", () => {
223
+ test("broadcasts without targetClientId (untargeted)", async () => {
224
+ setup();
225
+ setupMultipleClients(["client-1", "client-2"]);
226
+
227
+ const resultPromise = proxy.request(
228
+ { operation: "read", path: "/tmp/file.txt" },
229
+ "session-1",
230
+ );
231
+
232
+ expect(sentMessages).toHaveLength(1);
233
+ const sent = sentMessages[0] as Record<string, unknown>;
234
+ expect(sent.type).toBe("host_file_request");
235
+ expect(sent.targetClientId).toBeUndefined();
236
+
237
+ const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
238
+ expect(opts?.targetClientId).toBeUndefined();
239
+
240
+ const requestId = sent.requestId as string;
241
+ proxy.resolve(requestId, { content: "ok", isError: false });
242
+
243
+ const result = await resultPromise;
244
+ expect(result.isError).toBe(false);
245
+ });
246
+ });
247
+
248
+ // ── targetClientId in cancel (abort signal) ──────────────────────────
249
+
250
+ describe("targetClientId in cancel — abort signal", () => {
251
+ test("cancel broadcast includes targetClientId when request was targeted", async () => {
252
+ setup();
253
+ setupSingleClient("client-abc");
254
+
255
+ const controller = new AbortController();
256
+ const resultPromise = proxy.request(
257
+ { operation: "read", path: "/tmp/file.txt" },
258
+ "session-1",
259
+ controller.signal,
260
+ );
261
+
262
+ const sent = sentMessages[0] as Record<string, unknown>;
263
+ const requestId = sent.requestId as string;
264
+ expect(sent.targetClientId).toBe("client-abc");
265
+
266
+ controller.abort();
267
+ await resultPromise;
268
+
269
+ // Second message is the cancel
270
+ expect(sentMessages).toHaveLength(2);
271
+ const cancelMsg = sentMessages[1] as Record<string, unknown>;
272
+ expect(cancelMsg.type).toBe("host_file_cancel");
273
+ expect(cancelMsg.requestId).toBe(requestId);
274
+ expect(cancelMsg.targetClientId).toBe("client-abc");
275
+
276
+ const cancelOpts = sentMessageOptions[1] as Record<string, unknown> | undefined;
277
+ expect(cancelOpts?.targetClientId).toBe("client-abc");
278
+ });
279
+ });
280
+
281
+ // ── targetClientId in cancel (dispose) ───────────────────────────────
282
+
283
+ describe("targetClientId in cancel — dispose", () => {
284
+ test("dispose cancel broadcast includes targetClientId for targeted request", () => {
285
+ setup();
286
+ setupSingleClient("client-xyz");
287
+
288
+ const p = proxy.request(
289
+ { operation: "read", path: "/tmp/file.txt" },
290
+ "session-1",
291
+ );
292
+ p.catch(() => {}); // expected rejection on dispose
293
+
294
+ const sent = sentMessages[0] as Record<string, unknown>;
295
+ expect(sent.targetClientId).toBe("client-xyz");
296
+ const requestId = sent.requestId as string;
297
+
298
+ proxy.dispose();
299
+
300
+ const cancelMessages = sentMessages
301
+ .slice(1)
302
+ .filter(
303
+ (m) => (m as Record<string, unknown>).type === "host_file_cancel",
304
+ ) as Array<Record<string, unknown>>;
305
+ expect(cancelMessages).toHaveLength(1);
306
+ expect(cancelMessages[0].requestId).toBe(requestId);
307
+ expect(cancelMessages[0].targetClientId).toBe("client-xyz");
308
+ });
309
+ });
310
+
311
+ // ── Timeout message includes clientId ────────────────────────────────
312
+
313
+ describe("timeout message includes clientId", () => {
314
+ test("timeout resolve message mentions resolvedTargetClientId", async () => {
315
+ setup();
316
+ setupSingleClient("client-mac");
317
+
318
+ jest.useFakeTimers();
319
+ try {
320
+ const resultPromise = proxy.request(
321
+ { operation: "read", path: "/tmp/slow.txt" },
322
+ "session-1",
323
+ );
324
+
325
+ const sent = sentMessages[0] as Record<string, unknown>;
326
+ expect(sent.targetClientId).toBe("client-mac");
327
+
328
+ // Advance past the 30s internal timeout
329
+ jest.advanceTimersByTime(31 * 1000);
330
+
331
+ const result = await resultPromise;
332
+ expect(result.isError).toBe(true);
333
+ expect(result.content).toContain("client-mac");
334
+ } finally {
335
+ jest.useRealTimers();
336
+ }
337
+ });
338
+ });
339
+ });
@@ -1,7 +1,6 @@
1
1
  import { afterEach, describe, expect, jest, mock, test } from "bun:test";
2
2
 
3
3
  const sentMessages: unknown[] = [];
4
- const resolvedInteractionIds: string[] = [];
5
4
  let mockHasClient = false;
6
5
 
7
6
  mock.module("../runtime/assistant-event-hub.js", () => ({
@@ -9,20 +8,16 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
9
8
  assistantEventHub: {
10
9
  getMostRecentClientByCapability: (cap: string) =>
11
10
  cap === "host_file" && mockHasClient ? { id: "mock-client" } : null,
11
+ listClientsByCapability: (cap: string) =>
12
+ cap === "host_file" && mockHasClient
13
+ ? [{ clientId: "mock-client", capabilities: ["host_file"] }]
14
+ : [],
15
+ getClientById: (_id: string) => undefined,
12
16
  },
13
17
  }));
14
18
 
15
- mock.module("../runtime/pending-interactions.js", () => ({
16
- resolve: (requestId: string) => {
17
- resolvedInteractionIds.push(requestId);
18
- return undefined;
19
- },
20
- get: () => undefined,
21
- getByKind: () => [],
22
- getByConversation: () => [],
23
- removeByConversation: () => {},
24
- }));
25
-
19
+ // Use the REAL pending-interactions module the proxy self-registers here.
20
+ const pendingInteractions = await import("../runtime/pending-interactions.js");
26
21
  const { HostFileProxy } = await import("../daemon/host-file-proxy.js");
27
22
 
28
23
  // Minimal PNG header
@@ -36,14 +31,15 @@ describe("HostFileProxy", () => {
36
31
 
37
32
  function setup() {
38
33
  sentMessages.length = 0;
39
- resolvedInteractionIds.length = 0;
40
34
  mockHasClient = false;
35
+ pendingInteractions.clear();
41
36
  proxy = new (HostFileProxy as any)();
42
37
  }
43
38
 
44
39
  afterEach(() => {
45
40
  proxy?.dispose();
46
41
  HostFileProxy.reset();
42
+ pendingInteractions.clear();
47
43
  });
48
44
 
49
45
  describe("request/resolve lifecycle (happy path)", () => {
@@ -68,7 +64,7 @@ describe("HostFileProxy", () => {
68
64
  expect(typeof sent.requestId).toBe("string");
69
65
 
70
66
  const requestId = sent.requestId as string;
71
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
67
+ expect(pendingInteractions.get(requestId)).toBeDefined();
72
68
 
73
69
  // Simulate client response
74
70
  proxy.resolve(requestId, {
@@ -79,7 +75,7 @@ describe("HostFileProxy", () => {
79
75
  const result = await resultPromise;
80
76
  expect(result.content).toBe("file contents here");
81
77
  expect(result.isError).toBe(false);
82
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
78
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
83
79
  });
84
80
 
85
81
  test("resolves error responses correctly", async () => {
@@ -208,7 +204,7 @@ describe("HostFileProxy", () => {
208
204
 
209
205
  const sent = sentMessages[0] as Record<string, unknown>;
210
206
  const requestId = sent.requestId as string;
211
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
207
+ expect(pendingInteractions.get(requestId)).toBeDefined();
212
208
 
213
209
  // Resolve to avoid test hanging (actual 30s timeout too long for test)
214
210
  proxy.resolve(requestId, {
@@ -236,14 +232,14 @@ describe("HostFileProxy", () => {
236
232
 
237
233
  const sent = sentMessages[0] as Record<string, unknown>;
238
234
  const requestId = sent.requestId as string;
239
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
235
+ expect(pendingInteractions.get(requestId)).toBeDefined();
240
236
 
241
237
  controller.abort();
242
238
 
243
239
  const result = await resultPromise;
244
240
  expect(result.content).toBe("Aborted");
245
241
  expect(result.isError).toBe(true);
246
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
242
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
247
243
  });
248
244
 
249
245
  test("sends host_file_cancel to client on abort", async () => {
@@ -321,11 +317,11 @@ describe("HostFileProxy", () => {
321
317
 
322
318
  const sent = sentMessages[0] as Record<string, unknown>;
323
319
  const requestId = sent.requestId as string;
324
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
320
+ expect(pendingInteractions.get(requestId)).toBeDefined();
325
321
 
326
322
  proxy.dispose();
327
323
 
328
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
324
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
329
325
  expect(resultPromise).rejects.toThrow("Host file proxy disposed");
330
326
  });
331
327
 
@@ -389,7 +385,7 @@ describe("HostFileProxy", () => {
389
385
  isError: false,
390
386
  });
391
387
 
392
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
388
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
393
389
  });
394
390
  });
395
391
 
@@ -457,7 +453,10 @@ describe("HostFileProxy", () => {
457
453
 
458
454
  const requestId = (sentMessages[0] as Record<string, unknown>)
459
455
  .requestId as string;
460
- proxy.resolve(requestId, { content: "file contents", isError: false });
456
+ proxy.resolve(requestId, {
457
+ content: "file contents",
458
+ isError: false,
459
+ });
461
460
  await resultPromise;
462
461
 
463
462
  // Listener is detached after normal completion.
@@ -488,7 +487,7 @@ describe("HostFileProxy", () => {
488
487
 
489
488
  const requestId = (sentMessages[0] as Record<string, unknown>)
490
489
  .requestId as string;
491
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
490
+ expect(pendingInteractions.get(requestId)).toBeDefined();
492
491
 
493
492
  // Advance past the 30s internal timeout.
494
493
  jest.advanceTimersByTime(31 * 1000);
@@ -496,7 +495,7 @@ describe("HostFileProxy", () => {
496
495
  const result = await resultPromise;
497
496
  expect(result.isError).toBe(true);
498
497
  expect(result.content).toContain("Host file proxy timed out");
499
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
498
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
500
499
 
501
500
  // Listener is detached after the timer fires.
502
501
  expect(spy.removeCalls).toEqual(["abort"]);
@@ -510,8 +509,8 @@ describe("HostFileProxy", () => {
510
509
  });
511
510
  });
512
511
 
513
- describe("pendingInteractions.resolve callback", () => {
514
- test("fires on abort", async () => {
512
+ describe("pendingInteractions cleanup", () => {
513
+ test("cleans up on abort", async () => {
515
514
  setup();
516
515
 
517
516
  const controller = new AbortController();
@@ -526,29 +525,23 @@ describe("HostFileProxy", () => {
526
525
 
527
526
  const sent = sentMessages[0] as Record<string, unknown>;
528
527
  const requestId = sent.requestId as string;
528
+ expect(pendingInteractions.get(requestId)).toBeDefined();
529
529
 
530
530
  controller.abort();
531
531
  await resultPromise;
532
532
 
533
- expect(resolvedInteractionIds).toContain(requestId);
533
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
534
534
  });
535
535
 
536
- test("fires for each pending request on dispose", () => {
536
+ test("cleans up for each pending request on dispose", () => {
537
537
  setup();
538
538
 
539
- // Create two pending requests and catch rejections from dispose
540
539
  const p1 = proxy.request(
541
- {
542
- operation: "read",
543
- path: "/tmp/a.txt",
544
- },
540
+ { operation: "read", path: "/tmp/a.txt" },
545
541
  "session-1",
546
542
  );
547
543
  const p2 = proxy.request(
548
- {
549
- operation: "read",
550
- path: "/tmp/b.txt",
551
- },
544
+ { operation: "read", path: "/tmp/b.txt" },
552
545
  "session-1",
553
546
  );
554
547
  p1.catch(() => {}); // Expected rejection on dispose
@@ -558,15 +551,16 @@ describe("HostFileProxy", () => {
558
551
  (m) => m.requestId as string,
559
552
  );
560
553
  expect(ids).toHaveLength(2);
554
+ expect(pendingInteractions.get(ids[0])).toBeDefined();
555
+ expect(pendingInteractions.get(ids[1])).toBeDefined();
561
556
 
562
557
  proxy.dispose();
563
558
 
564
- expect(resolvedInteractionIds).toHaveLength(2);
565
- expect(resolvedInteractionIds).toContain(ids[0]);
566
- expect(resolvedInteractionIds).toContain(ids[1]);
559
+ expect(pendingInteractions.get(ids[0])).toBeUndefined();
560
+ expect(pendingInteractions.get(ids[1])).toBeUndefined();
567
561
  });
568
562
 
569
- test("does not fire on normal client-initiated resolve", async () => {
563
+ test("cleans up on normal client-initiated resolveResult", async () => {
570
564
  setup();
571
565
 
572
566
  const resultPromise = proxy.request(
@@ -579,15 +573,15 @@ describe("HostFileProxy", () => {
579
573
 
580
574
  const sent = sentMessages[0] as Record<string, unknown>;
581
575
  const requestId = sent.requestId as string;
576
+ expect(pendingInteractions.get(requestId)).toBeDefined();
582
577
 
583
- // Normal resolve from client — should NOT trigger pendingInteractions.resolve
584
578
  proxy.resolve(requestId, {
585
579
  content: "file contents",
586
580
  isError: false,
587
581
  });
588
582
 
589
583
  await resultPromise;
590
- expect(resolvedInteractionIds).toEqual([]);
584
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
591
585
  });
592
586
  });
593
587
  });
@@ -304,4 +304,21 @@ describe("host_file_read image support", () => {
304
304
  expect(result.content).toContain("2 second line");
305
305
  expect((result as any).contentBlocks).toBeUndefined();
306
306
  });
307
+
308
+ test("passes target_client_id to HostFileProxy.instance.request", async () => {
309
+ const capturedInputs: HostFileInput[] = [];
310
+ mockFileProxyAvailable = true;
311
+ mockFileProxyRequestFn = async (input) => {
312
+ capturedInputs.push(input);
313
+ return { content: "proxied", isError: false };
314
+ };
315
+
316
+ await hostFileReadTool.execute(
317
+ { path: "/host/notes.txt", target_client_id: "client-x" },
318
+ makeContext(),
319
+ );
320
+
321
+ expect(capturedInputs).toHaveLength(1);
322
+ expect(capturedInputs[0].targetClientId).toBe("client-x");
323
+ });
307
324
  });