@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
@@ -10,19 +10,9 @@ import { formatShellOutput } from "../tools/shared/shell-output.js";
10
10
  import type { ToolExecutionResult } from "../tools/types.js";
11
11
  import { AssistantError, ErrorCode } from "../util/errors.js";
12
12
  import { getLogger } from "../util/logger.js";
13
- import type { ServerMessage } from "./message-protocol.js";
14
13
 
15
14
  const log = getLogger("host-bash-proxy");
16
15
 
17
- interface PendingRequest {
18
- resolve: (result: ToolExecutionResult) => void;
19
- reject: (err: Error) => void;
20
- timer: ReturnType<typeof setTimeout>;
21
- timeoutSec: number;
22
- conversationId: string;
23
- /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
24
- detachAbort: () => void;
25
- }
26
16
 
27
17
  export class HostBashProxy {
28
18
  private static _instance: HostBashProxy | null = null;
@@ -53,8 +43,6 @@ export class HostBashProxy {
53
43
  HostBashProxy._instance = null;
54
44
  }
55
45
 
56
- private pending = new Map<string, PendingRequest>();
57
-
58
46
  /**
59
47
  * Whether a client with `host_bash` capability is connected.
60
48
  */
@@ -64,16 +52,13 @@ export class HostBashProxy {
64
52
  );
65
53
  }
66
54
 
67
- private send(msg: ServerMessage): void {
68
- broadcastMessage(msg, undefined, { targetCapability: "host_bash" });
69
- }
70
-
71
55
  request(
72
56
  input: {
73
57
  command: string;
74
58
  working_dir?: string;
75
59
  timeout_seconds?: number;
76
60
  env?: Record<string, string>;
61
+ targetClientId?: string;
77
62
  },
78
63
  conversationId: string,
79
64
  signal?: AbortSignal,
@@ -83,30 +68,50 @@ export class HostBashProxy {
83
68
  return Promise.resolve(result);
84
69
  }
85
70
 
71
+ const capableClients = assistantEventHub.listClientsByCapability("host_bash");
72
+
73
+ let resolvedTargetClientId: string | undefined;
74
+
75
+ if (input.targetClientId) {
76
+ const target = assistantEventHub.getClientById(input.targetClientId);
77
+ if (!target || !target.capabilities.includes("host_bash")) {
78
+ return Promise.resolve({
79
+ content: `Error: client "${input.targetClientId}" is not connected or does not support host_bash. Run \`assistant clients list --capability host_bash\` to see available clients.`,
80
+ isError: true,
81
+ });
82
+ }
83
+ resolvedTargetClientId = input.targetClientId;
84
+ } else if (capableClients.length === 1) {
85
+ // Auto-resolve when exactly one capable client is connected.
86
+ resolvedTargetClientId = capableClients[0].clientId;
87
+ }
88
+ // capableClients.length === 0 or > 1 without explicit target: resolvedTargetClientId
89
+ // stays undefined and falls through to untargeted broadcast — the existing timeout/error
90
+ // path handles the zero-client case, and multi-client ambiguity is enforced at the tool
91
+ // executor layer (not here) once target_client_id is exposed in the tool schema.
92
+
86
93
  const requestId = uuid();
87
94
 
88
95
  return new Promise<ToolExecutionResult>((resolve, reject) => {
89
96
  const shellMaxTimeoutSec = getConfig().timeouts.shellMaxTimeoutSec;
90
97
  const timeoutSec = input.timeout_seconds ?? shellMaxTimeoutSec;
91
- // Proxy timeout: slightly after client-side timeout, but before executor's outer timeout
92
98
  const proxyTimeoutSec = timeoutSec + 3;
93
99
 
94
- // Declared up-front so onAbort (defined before detachAbort is assigned)
95
- // can close over a stable reference once it's wired below.
96
100
  let detachAbort: () => void = () => {};
97
101
 
98
102
  const timer = setTimeout(() => {
99
- this.pending.delete(requestId);
100
- detachAbort();
101
103
  pendingInteractions.resolve(requestId);
102
104
  log.warn(
103
105
  { requestId, command: input.command },
104
106
  "Host bash proxy request timed out",
105
107
  );
108
+ const timeoutMessage = resolvedTargetClientId
109
+ ? `Host bash proxy timed out waiting for response from client ${resolvedTargetClientId}`
110
+ : "Host bash proxy timed out waiting for client response";
106
111
  resolve(
107
112
  formatShellOutput(
108
113
  "",
109
- "Host bash proxy timed out waiting for client response",
114
+ timeoutMessage,
110
115
  null,
111
116
  true,
112
117
  timeoutSec,
@@ -116,19 +121,21 @@ export class HostBashProxy {
116
121
 
117
122
  if (signal) {
118
123
  const onAbort = () => {
119
- if (this.pending.has(requestId)) {
120
- clearTimeout(timer);
121
- this.pending.delete(requestId);
122
- detachAbort();
124
+ if (pendingInteractions.get(requestId)) {
123
125
  pendingInteractions.resolve(requestId);
124
126
  try {
125
- this.send({
126
- type: "host_bash_cancel",
127
- requestId,
127
+ broadcastMessage(
128
+ {
129
+ type: "host_bash_cancel",
130
+ requestId,
131
+ conversationId,
132
+ targetClientId: resolvedTargetClientId,
133
+ },
128
134
  conversationId,
129
- });
135
+ { targetClientId: resolvedTargetClientId },
136
+ );
130
137
  } catch {
131
- // Best-effort cancel notification — connection may already be closed.
138
+ // Best-effort cancel notification
132
139
  }
133
140
  resolve(formatShellOutput("", "Aborted", null, false, 0));
134
141
  }
@@ -137,31 +144,35 @@ export class HostBashProxy {
137
144
  detachAbort = () => signal.removeEventListener("abort", onAbort);
138
145
  }
139
146
 
140
- this.pending.set(requestId, {
141
- resolve,
142
- reject,
143
- timer,
144
- timeoutSec,
147
+ pendingInteractions.register(requestId, {
145
148
  conversationId,
149
+ kind: "host_bash",
150
+ rpcResolve: resolve,
151
+ rpcReject: reject,
152
+ timer,
146
153
  detachAbort,
154
+ targetClientId: resolvedTargetClientId,
155
+ metadata: { timeoutSec },
147
156
  });
148
157
 
149
158
  try {
150
- this.send({
151
- type: "host_bash_request",
152
- requestId,
159
+ broadcastMessage(
160
+ {
161
+ type: "host_bash_request",
162
+ requestId,
163
+ conversationId,
164
+ command: input.command,
165
+ working_dir: input.working_dir,
166
+ timeout_seconds: input.timeout_seconds,
167
+ targetClientId: resolvedTargetClientId,
168
+ ...(input.env && Object.keys(input.env).length > 0
169
+ ? { env: input.env }
170
+ : {}),
171
+ },
153
172
  conversationId,
154
- command: input.command,
155
- working_dir: input.working_dir,
156
- timeout_seconds: input.timeout_seconds,
157
- ...(input.env && Object.keys(input.env).length > 0
158
- ? { env: input.env }
159
- : {}),
160
- });
173
+ { targetClientId: resolvedTargetClientId },
174
+ );
161
175
  } catch (err) {
162
- clearTimeout(timer);
163
- this.pending.delete(requestId);
164
- detachAbort();
165
176
  pendingInteractions.resolve(requestId);
166
177
  log.warn(
167
178
  { requestId, command: input.command, err },
@@ -172,7 +183,10 @@ export class HostBashProxy {
172
183
  });
173
184
  }
174
185
 
175
- resolve(
186
+ /**
187
+ * Process a client result and resolve the RPC. Called by route handlers.
188
+ */
189
+ resolveResult(
176
190
  requestId: string,
177
191
  response: {
178
192
  stdout: string;
@@ -181,49 +195,45 @@ export class HostBashProxy {
181
195
  timedOut: boolean;
182
196
  },
183
197
  ): void {
184
- const entry = this.pending.get(requestId);
185
- if (!entry) {
198
+ const interaction = pendingInteractions.resolve(requestId);
199
+ if (!interaction?.rpcResolve) {
186
200
  log.warn({ requestId }, "No pending host bash request for response");
187
201
  return;
188
202
  }
189
- clearTimeout(entry.timer);
190
- entry.detachAbort();
191
- this.pending.delete(requestId);
203
+ const timeoutSec = (interaction.metadata?.timeoutSec as number) ?? 0;
192
204
  const result = formatShellOutput(
193
205
  response.stdout,
194
206
  response.stderr,
195
207
  response.exitCode,
196
208
  response.timedOut,
197
- entry.timeoutSec,
209
+ timeoutSec,
198
210
  );
199
- entry.resolve(result);
200
- }
201
-
202
- hasPendingRequest(requestId: string): boolean {
203
- return this.pending.has(requestId);
211
+ interaction.rpcResolve(result);
204
212
  }
205
213
 
206
214
  dispose(): void {
207
- for (const [requestId, entry] of this.pending) {
208
- clearTimeout(entry.timer);
209
- entry.detachAbort();
210
- pendingInteractions.resolve(requestId);
211
- try {
212
- this.send({
215
+ for (const entry of pendingInteractions.getByKind("host_bash")) {
216
+ pendingInteractions.resolve(entry.requestId);
217
+ try {
218
+ broadcastMessage(
219
+ {
213
220
  type: "host_bash_cancel",
214
- requestId,
221
+ requestId: entry.requestId,
215
222
  conversationId: entry.conversationId,
216
- });
217
- } catch {
218
- // Best-effort cancel notification — connection may already be closed.
219
- }
220
- entry.reject(
223
+ targetClientId: entry.targetClientId,
224
+ },
225
+ entry.conversationId,
226
+ { targetClientId: entry.targetClientId },
227
+ );
228
+ } catch {
229
+ // Best-effort cancel notification — connection may already be closed.
230
+ }
231
+ entry.rpcReject?.(
221
232
  new AssistantError(
222
233
  "Host bash proxy disposed",
223
234
  ErrorCode.INTERNAL_ERROR,
224
235
  ),
225
236
  );
226
237
  }
227
- this.pending.clear();
228
238
  }
229
239
  }
@@ -1,11 +1,14 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
- import { buildAssistantEvent } from "../runtime/assistant-event.js";
4
- import { assistantEventHub } from "../runtime/assistant-event-hub.js";
3
+ import type { InterfaceId } from "../channels/types.js";
4
+ import {
5
+ assistantEventHub,
6
+ broadcastMessage,
7
+ } from "../runtime/assistant-event-hub.js";
8
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
5
9
  import type { ToolExecutionResult } from "../tools/types.js";
6
10
  import { AssistantError, ErrorCode } from "../util/errors.js";
7
11
  import { getLogger } from "../util/logger.js";
8
- import type { ServerMessage } from "./message-protocol.js";
9
12
  import type { HostBrowserRequest } from "./message-types/host-browser.js";
10
13
 
11
14
  /** Distributive omit that preserves union variant fields. */
@@ -21,13 +24,11 @@ export type HostBrowserInput = DistributiveOmit<
21
24
 
22
25
  const log = getLogger("host-browser-proxy");
23
26
 
24
- interface PendingRequest {
25
- resolve: (result: ToolExecutionResult) => void;
26
- reject: (err: Error) => void;
27
- timer: ReturnType<typeof setTimeout>;
28
- /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
29
- detachAbort: () => void;
30
- }
27
+ /** Interface priority order for host_browser: Chrome Extension first, macOS SSE bridge second. */
28
+ const HOST_BROWSER_INTERFACE_PREFERENCE: InterfaceId[] = [
29
+ "chrome-extension",
30
+ "macos",
31
+ ];
31
32
 
32
33
  export class HostBrowserProxy {
33
34
  private static _instance: HostBrowserProxy | null = null;
@@ -58,29 +59,29 @@ export class HostBrowserProxy {
58
59
  HostBrowserProxy._instance = null;
59
60
  }
60
61
 
61
- private pending = new Map<string, PendingRequest>();
62
-
63
62
  /**
64
63
  * Whether a client with `host_browser` capability is connected.
64
+ * Returns `true` when either the Chrome Extension or the macOS SSE
65
+ * bridge is available — i.e. any transport can forward host-browser
66
+ * requests.
65
67
  */
66
68
  isAvailable(): boolean {
67
69
  return (
68
- assistantEventHub.getMostRecentClientByCapability("host_browser") != null
70
+ assistantEventHub.getPreferredClientByCapability(
71
+ "host_browser",
72
+ HOST_BROWSER_INTERFACE_PREFERENCE,
73
+ ) != null
69
74
  );
70
75
  }
71
76
 
72
77
  /**
73
- * Publish a ServerMessage through the assistant event hub, targeted at
74
- * subscribers with the `host_browser` capability.
78
+ * Whether a Chrome Extension client specifically is connected.
79
+ * Returns `false` when only the macOS SSE bridge is available.
80
+ * Unlike {@link isAvailable}, this does not consider the macOS bridge
81
+ * a valid extension transport.
75
82
  */
76
- private send(msg: ServerMessage): void {
77
- void assistantEventHub
78
- .publish(buildAssistantEvent(msg), {
79
- targetCapability: "host_browser",
80
- })
81
- .catch((err) => {
82
- log.warn({ err }, "failed to publish host_browser event to hub");
83
- });
83
+ hasExtensionClient(): boolean {
84
+ return assistantEventHub.listClientsByInterface("chrome-extension").length > 0;
84
85
  }
85
86
 
86
87
  request(
@@ -95,42 +96,34 @@ export class HostBrowserProxy {
95
96
  const requestId = uuid();
96
97
 
97
98
  return new Promise<ToolExecutionResult>((resolve, reject) => {
98
- // CDP operations should be fast — 30 second default timeout matches host_file.
99
99
  const timeoutSec = input.timeout_seconds ?? 30;
100
100
 
101
- // Declared up-front so onAbort (defined before detachAbort is assigned)
102
- // can close over a stable reference once it's wired below.
103
101
  let detachAbort: () => void = () => {};
104
102
 
105
103
  const timer = setTimeout(() => {
106
- this.pending.delete(requestId);
107
- detachAbort();
104
+ pendingInteractions.resolve(requestId);
108
105
  log.warn(
109
106
  { requestId, cdpMethod: input.cdpMethod },
110
107
  "Host browser proxy request timed out",
111
108
  );
112
109
  resolve({
113
110
  content:
114
- "Host browser proxy timed out waiting for extension response (check browser-relay connectivity and /v1/host-browser-result callback failures such as 404/401).",
111
+ "Host browser proxy timed out waiting for extension response (check SSE connectivity and /v1/host-browser-result callback failures such as 404/401).",
115
112
  isError: true,
116
113
  });
117
114
  }, timeoutSec * 1000);
118
115
 
119
116
  if (signal) {
120
117
  const onAbort = () => {
121
- if (this.pending.has(requestId)) {
122
- clearTimeout(timer);
123
- this.pending.delete(requestId);
124
- // Abort fired — nothing to detach, but call the no-op for symmetry
125
- // so callers can rely on detachAbort being idempotent.
126
- detachAbort();
118
+ if (pendingInteractions.get(requestId)) {
119
+ pendingInteractions.resolve(requestId);
127
120
  try {
128
- this.send({
121
+ broadcastMessage({
129
122
  type: "host_browser_cancel",
130
123
  requestId,
131
- } as ServerMessage);
124
+ });
132
125
  } catch {
133
- // Best-effort cancel notification — connection may already be closed.
126
+ // Best-effort cancel notification
134
127
  }
135
128
  resolve({ content: "Aborted", isError: true });
136
129
  }
@@ -139,13 +132,22 @@ export class HostBrowserProxy {
139
132
  detachAbort = () => signal.removeEventListener("abort", onAbort);
140
133
  }
141
134
 
142
- this.pending.set(requestId, { resolve, reject, timer, detachAbort });
135
+ pendingInteractions.register(requestId, {
136
+ conversationId,
137
+ kind: "host_browser",
138
+ rpcResolve: resolve,
139
+ rpcReject: reject,
140
+ timer,
141
+ detachAbort,
142
+ });
143
143
 
144
144
  try {
145
- if (!this.isAvailable()) {
146
- clearTimeout(timer);
147
- this.pending.delete(requestId);
148
- detachAbort();
145
+ const preferredClient = assistantEventHub.getPreferredClientByCapability(
146
+ "host_browser",
147
+ HOST_BROWSER_INTERFACE_PREFERENCE,
148
+ );
149
+ if (!preferredClient) {
150
+ pendingInteractions.resolve(requestId);
149
151
  reject(
150
152
  new Error(
151
153
  "host_browser send failed: no active extension connection",
@@ -154,19 +156,13 @@ export class HostBrowserProxy {
154
156
  return;
155
157
  }
156
158
 
157
- this.send({
158
- ...input,
159
- type: "host_browser_request",
160
- requestId,
159
+ broadcastMessage(
160
+ { ...input, type: "host_browser_request", requestId, conversationId },
161
161
  conversationId,
162
- } as ServerMessage);
162
+ { targetClientId: preferredClient.clientId },
163
+ );
163
164
  } catch (err) {
164
- // Sender threw synchronously (e.g. client transport error during
165
- // event emission). Clean up pending state and timer so we don't
166
- // leak an in-flight entry that nothing will ever resolve.
167
- clearTimeout(timer);
168
- this.pending.delete(requestId);
169
- detachAbort();
165
+ pendingInteractions.resolve(requestId);
170
166
  log.warn(
171
167
  { requestId, cdpMethod: input.cdpMethod, err },
172
168
  "Host browser proxy send failed",
@@ -176,55 +172,44 @@ export class HostBrowserProxy {
176
172
  });
177
173
  }
178
174
 
179
- resolve(
175
+ /**
176
+ * Process a client result and resolve the RPC. Called by route handlers.
177
+ */
178
+ resolveResult(
180
179
  requestId: string,
181
180
  response: { content: string; isError: boolean },
182
181
  ): void {
183
- const entry = this.pending.get(requestId);
184
- if (!entry) {
185
- // Benign race, not an error. A late result frame with no matching
186
- // pending entry means one of:
187
- // - the proxy-side setTimeout has already resolved the caller;
188
- // - the caller's AbortSignal fired and the entry was torn down;
189
- // - a duplicate result frame was delivered (e.g. retry after a
190
- // transient WS drop).
191
- // Log at debug so operators don't chase false-positive "timeout"
192
- // alerts on what is actually a cleanly-handled race.
182
+ const interaction = pendingInteractions.resolve(requestId);
183
+ if (!interaction?.rpcResolve) {
193
184
  log.debug(
194
185
  { requestId },
195
186
  "Ignoring host_browser_result for unknown or already-resolved request",
196
187
  );
197
188
  return;
198
189
  }
199
- clearTimeout(entry.timer);
200
- entry.detachAbort();
201
- this.pending.delete(requestId);
202
- entry.resolve({ content: response.content, isError: response.isError });
203
- }
204
-
205
- hasPendingRequest(requestId: string): boolean {
206
- return this.pending.has(requestId);
190
+ interaction.rpcResolve({
191
+ content: response.content,
192
+ isError: response.isError,
193
+ });
207
194
  }
208
195
 
209
196
  dispose(): void {
210
- for (const [requestId, entry] of this.pending) {
211
- clearTimeout(entry.timer);
212
- entry.detachAbort();
197
+ for (const entry of pendingInteractions.getByKind("host_browser")) {
198
+ pendingInteractions.resolve(entry.requestId);
213
199
  try {
214
- this.send({
200
+ broadcastMessage({
215
201
  type: "host_browser_cancel",
216
- requestId,
217
- } as ServerMessage);
202
+ requestId: entry.requestId,
203
+ });
218
204
  } catch {
219
- // Best-effort cancel notification — connection may already be closed.
205
+ // Best-effort cancel notification
220
206
  }
221
- entry.reject(
207
+ entry.rpcReject?.(
222
208
  new AssistantError(
223
209
  "Host browser proxy disposed",
224
210
  ErrorCode.INTERNAL_ERROR,
225
211
  ),
226
212
  );
227
213
  }
228
- this.pending.clear();
229
214
  }
230
215
  }