@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
@@ -14,16 +14,20 @@ import {
14
14
  getMessages,
15
15
  updateMessageContent,
16
16
  } from "../memory/conversation-crud.js";
17
- import { broadcastMessage } from "../runtime/assistant-event-hub.js";
17
+ import {
18
+ assistantEventHub,
19
+ broadcastMessage,
20
+ } from "../runtime/assistant-event-hub.js";
18
21
  import type {
19
22
  InteractiveUiRequest,
20
23
  InteractiveUiResult,
21
- } from "../runtime/interactive-ui.js";
24
+ } from "../runtime/interactive-ui-types.js";
22
25
  import type { ToolExecutionResult } from "../tools/types.js";
23
26
  import { getLogger } from "../util/logger.js";
24
27
  import { isPlainObject } from "../util/object.js";
25
28
  import { buildConversationErrorMessage } from "./conversation-error.js";
26
29
  import { launchConversation } from "./conversation-launch.js";
30
+ import type { HostAppControlProxy } from "./host-app-control-proxy.js";
27
31
  import type { HostCuProxy } from "./host-cu-proxy.js";
28
32
  import type {
29
33
  CardSurfaceData,
@@ -41,6 +45,7 @@ import type {
41
45
  } from "./message-protocol.js";
42
46
  import { INTERACTIVE_SURFACE_TYPES } from "./message-protocol.js";
43
47
  import type { ConversationTransportMetadata } from "./message-types/conversations.js";
48
+ import type { HostAppControlInput } from "./message-types/host-app-control.js";
44
49
  import type { UserMessageAttachment } from "./message-types/shared.js";
45
50
  import type { TrustContext } from "./trust-context.js";
46
51
 
@@ -320,6 +325,15 @@ export interface SurfaceConversationContext {
320
325
  }>;
321
326
  /** Optional proxy for delegating computer-use actions to a connected desktop client. */
322
327
  hostCuProxy?: HostCuProxy;
328
+ /** Optional proxy for delegating per-app app-control actions to a connected desktop client. */
329
+ hostAppControlProxy?: HostAppControlProxy;
330
+ /**
331
+ * Setter that lets the resolver detach the conversation's app-control proxy
332
+ * after `app_control_stop`. Disposes the existing proxy when transitioning
333
+ * to undefined so subsequent tool calls cleanly fail with "unavailable"
334
+ * rather than dispatching to a torn-down proxy.
335
+ */
336
+ setHostAppControlProxy?(proxy: HostAppControlProxy | undefined): void;
323
337
  /** True when no interactive client is connected (headless / channel-only). */
324
338
  readonly hasNoClient?: boolean;
325
339
  isProcessing(): boolean;
@@ -1206,7 +1220,11 @@ export async function handleSurfaceAction(
1206
1220
 
1207
1221
  const requestId = uuid();
1208
1222
  ctx.surfaceActionRequestIds.add(requestId);
1209
- const onEvent = (msg: ServerMessage) => broadcastMessage(msg);
1223
+ // Pass conversationId so events without an inline conversationId (e.g.
1224
+ // text_delta) are published with the correct conversation scope and
1225
+ // reach the SSE subscriber filtered to this conversation.
1226
+ const onEvent = (msg: ServerMessage) =>
1227
+ broadcastMessage(msg, ctx.conversationId);
1210
1228
 
1211
1229
  ctx.traceEmitter.emit("request_received", "Surface action received", {
1212
1230
  requestId,
@@ -1440,7 +1458,11 @@ export async function handleSurfaceAction(
1440
1458
 
1441
1459
  const requestId = uuid();
1442
1460
  ctx.surfaceActionRequestIds.add(requestId);
1443
- const onEvent = (msg: ServerMessage) => broadcastMessage(msg);
1461
+ // Pass conversationId so events without an inline conversationId (e.g.
1462
+ // text_delta) are published with the correct conversation scope and
1463
+ // reach the SSE subscriber filtered to this conversation.
1464
+ const onEvent = (msg: ServerMessage) =>
1465
+ broadcastMessage(msg, ctx.conversationId);
1444
1466
 
1445
1467
  ctx.traceEmitter.emit("request_received", "Surface action received", {
1446
1468
  requestId,
@@ -1761,6 +1783,56 @@ export async function surfaceProxyResolver(
1761
1783
  // Record the action and proxy to the connected desktop client
1762
1784
  const reasoning =
1763
1785
  typeof input.reasoning === "string" ? input.reasoning : undefined;
1786
+ const targetClientId =
1787
+ typeof input.target_client_id === "string" && input.target_client_id !== ""
1788
+ ? input.target_client_id
1789
+ : undefined;
1790
+
1791
+ // Validate targetClientId before recordAction so an invalid ID does not
1792
+ // burn a step or pollute action history. (HostBashProxy / HostFileProxy
1793
+ // both validate at the tool-resolution layer before incrementing any
1794
+ // state; this mirrors that behaviour for CU.)
1795
+ if (targetClientId != null) {
1796
+ const client = assistantEventHub.getClientById(targetClientId);
1797
+ if (!client) {
1798
+ return {
1799
+ content: `No connected client with id '${targetClientId}'. Run \`assistant clients list --capability host_cu\` to see available clients.`,
1800
+ isError: true,
1801
+ };
1802
+ }
1803
+ if (!client.capabilities.includes("host_cu")) {
1804
+ return {
1805
+ content: `Client '${targetClientId}' does not support host_cu. Run \`assistant clients list --capability host_cu\` to see available clients.`,
1806
+ isError: true,
1807
+ };
1808
+ }
1809
+ }
1810
+
1811
+ // Guard: require explicit targeting when multiple CU-capable clients are
1812
+ // connected. The tool schemas document target_client_id as "required when
1813
+ // multiple clients support host_cu" but nothing enforced it at runtime
1814
+ // until now. Without this guard, the request would broadcast to all
1815
+ // capable clients simultaneously, causing the same CU action to execute
1816
+ // on multiple machines.
1817
+ //
1818
+ // Asymmetry with host_bash / host_file (host-shell.ts): the bash/file
1819
+ // guard additionally checks `transportInterface != null &&
1820
+ // !supportsHostProxy(transportInterface)` and so only fires for non-host-
1821
+ // proxy transports (web, Slack). For CU that check would be a no-op:
1822
+ // every host_cu-capable client is host-proxy-capable by definition
1823
+ // (host_cu only ships on macOS and the Chrome extension), so there is no
1824
+ // host_cu-capable transport for which auto-routing-to-self would be
1825
+ // appropriate. We therefore fire whenever there is genuine ambiguity.
1826
+ if (targetClientId == null) {
1827
+ const cuClients = assistantEventHub.listClientsByCapability("host_cu");
1828
+ if (cuClients.length > 1) {
1829
+ return {
1830
+ content: `Error: multiple clients support host_cu. Specify which client to target with \`target_client_id\`. Run \`assistant clients list --capability host_cu\` to see client IDs and labels.`,
1831
+ isError: true,
1832
+ };
1833
+ }
1834
+ }
1835
+
1764
1836
  ctx.hostCuProxy.recordAction(toolName, input, reasoning);
1765
1837
  return ctx.hostCuProxy.request(
1766
1838
  toolName,
@@ -1769,6 +1841,55 @@ export async function surfaceProxyResolver(
1769
1841
  ctx.hostCuProxy.stepCount,
1770
1842
  reasoning,
1771
1843
  signal,
1844
+ targetClientId,
1845
+ );
1846
+ }
1847
+
1848
+ // Route app-control proxy tools (all app_control_* tool variants)
1849
+ if (toolName.startsWith("app_control_")) {
1850
+ if (!ctx.hostAppControlProxy || !ctx.hostAppControlProxy.isAvailable()) {
1851
+ return {
1852
+ content:
1853
+ "App control is not available — enable the `app-control` feature flag and connect a macOS client.",
1854
+ isError: true,
1855
+ };
1856
+ }
1857
+
1858
+ // `app_control_stop` resolves immediately: tear down the proxy without
1859
+ // a client round-trip. Mirrors CU's terminal-tool short-circuit
1860
+ // (`computer_use_done` / `computer_use_respond`). Clear the
1861
+ // conversation's reference (setter disposes the existing proxy) so a
1862
+ // later `app_control_observe`/etc. cleanly fails with "unavailable"
1863
+ // instead of dispatching against a torn-down proxy, and so a sibling
1864
+ // conversation can acquire the released singleton lock without the
1865
+ // disposed proxy still being addressable.
1866
+ if (toolName === "app_control_stop") {
1867
+ if (ctx.setHostAppControlProxy) {
1868
+ ctx.setHostAppControlProxy(undefined);
1869
+ } else {
1870
+ ctx.hostAppControlProxy.dispose();
1871
+ }
1872
+ return { content: "App control stopped.", isError: false };
1873
+ }
1874
+
1875
+ // The TS `HostAppControlInput` (and the Swift mirror) is a discriminated
1876
+ // union on `tool` ("start" | "observe" | "press" | …). The agent's raw
1877
+ // tool input only carries the action-specific payload (app, x/y, text,
1878
+ // …) — the discriminator is implied by `toolName` (`app_control_<tool>`).
1879
+ // Inject it here so the proxy's singleton-lock guard (`input.tool ===
1880
+ // "start"`) and the Swift client's discriminated-union decoder both see
1881
+ // the field they require.
1882
+ const tool = toolName.slice("app_control_".length);
1883
+ const inputWithTool = {
1884
+ ...input,
1885
+ tool,
1886
+ } as unknown as HostAppControlInput;
1887
+
1888
+ return ctx.hostAppControlProxy.request(
1889
+ toolName,
1890
+ inputWithTool,
1891
+ ctx.conversationId,
1892
+ signal ?? new AbortController().signal,
1772
1893
  );
1773
1894
  }
1774
1895
 
@@ -13,12 +13,12 @@ import {
13
13
  } from "../channels/types.js";
14
14
  import { isHttpAuthDisabled } from "../config/env.js";
15
15
  import { getIsPlatform } from "../config/env-registry.js";
16
- import type { CesClient } from "../credential-execution/client.js";
17
16
  import { getBindingByConversation } from "../memory/external-conversation-store.js";
18
17
  import type { PermissionPrompter } from "../permissions/prompter.js";
19
18
  import type { SecretPrompter } from "../permissions/secret-prompter.js";
20
19
  import type { Message, ToolDefinition } from "../providers/types.js";
21
20
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
21
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
22
22
  import { coreAppProxyTools } from "../tools/apps/definitions.js";
23
23
  import { registerConversationSender } from "../tools/browser/browser-screencast.js";
24
24
  import type { ToolExecutor } from "../tools/executor.js";
@@ -43,7 +43,6 @@ import {
43
43
  projectSkillTools,
44
44
  type SkillProjectionCache,
45
45
  } from "./conversation-skill-tools.js";
46
- import type { SurfaceConversationContext } from "./conversation-surfaces.js";
47
46
  import { surfaceProxyResolver } from "./conversation-surfaces.js";
48
47
  import {
49
48
  isDoordashCommand,
@@ -72,49 +71,8 @@ export function resolveTrustClass(
72
71
  return trustContext?.trustClass ?? "unknown";
73
72
  }
74
73
 
75
- // ── Context Interface ────────────────────────────────────────────────
76
-
77
- /**
78
- * Subset of Conversation state that the tool executor callback reads at
79
- * call time (not construction time). These are captured by the
80
- * returned closure, so they must be live references.
81
- */
82
- export interface ToolSetupContext extends SurfaceConversationContext {
83
- readonly conversationId: string;
84
- assistantId?: string;
85
- currentRequestId?: string;
86
- workingDir: string;
87
- abortController: AbortController | null;
88
- /** When set, only tools in this set may execute during the current turn. */
89
- allowedToolNames?: Set<string>;
90
- /** Conversation memory policy used to propagate scopeId into ToolContext. */
91
- memoryPolicy: { scopeId: string };
92
- /** True when the conversation has no connected client (HTTP-only path). */
93
- hasNoClient?: boolean;
94
- /** When true, the conversation is executing a task run and must not become interactive. */
95
- headlessLock?: boolean;
96
- /** When set, this conversation is executing a task run. Used to retrieve ephemeral permission rules. */
97
- taskRunId?: string;
98
- /** Guardian runtime context for the conversation — trustClass is propagated into ToolContext for control-plane policy enforcement. */
99
- trustContext?: TrustContext;
100
- /** Voice/call session ID, if the conversation originates from a call. Propagated into ToolContext for scoped grant consumption. */
101
- callSessionId?: string;
102
- /** CES RPC client for credential execution operations. Injected when CES tools are enabled and the CES process is available. */
103
- cesClient?: CesClient;
104
- /** The interface ID of the connected client driving the current turn (e.g. "macos", "chrome-extension"). Propagated into ToolContext for browser backend selection. */
105
- readonly transportInterface?: InterfaceId;
106
-
107
- /** Turn-scoped flag: true when any tool call in the current turn received explicit user approval via interactive prompt. Cleared at turn end. */
108
- approvedViaPromptThisTurn?: boolean;
109
- /**
110
- * Per-turn snapshot of the resolved inference-profile override, set by
111
- * `runAgentLoopImpl`. Propagated into `ToolContext.overrideProfile` so
112
- * tools that spawn nested invocations (e.g. `subagent_spawn`) can forward
113
- * the override without round-tripping through a row read that would
114
- * return `undefined` for the in-flight (background) subagent.
115
- */
116
- currentTurnOverrideProfile?: string;
117
- }
74
+ import type { ToolSetupContext } from "./tool-setup-types.js";
75
+ export type { ToolSetupContext } from "./tool-setup-types.js";
118
76
 
119
77
  // ── buildToolDefinitions ─────────────────────────────────────────────
120
78
 
@@ -258,16 +216,6 @@ export function createToolExecutor(
258
216
  // Clone to avoid mutating shared input objects
259
217
  const toolInput = { ...rawToolInput };
260
218
 
261
- // Propagate outer activity when inner input lacks a valid one
262
- if (
263
- typeof input.activity === "string" &&
264
- input.activity &&
265
- (typeof toolInput.activity !== "string" ||
266
- toolInput.activity.length === 0)
267
- ) {
268
- toolInput.activity = input.activity;
269
- }
270
-
271
219
  if (!toolName) {
272
220
  return {
273
221
  content:
@@ -437,6 +385,19 @@ export function isToolActiveForContext(
437
385
  // Per-capability check is authoritative for structural support: if the
438
386
  // transport cannot service this capability, the tool is filtered out.
439
387
  if (transport && capability && !supportsHostProxy(transport, capability)) {
388
+ // Cross-client exception: allow host_bash for non-host-proxy interfaces when
389
+ // at least one capable client is connected via the event hub.
390
+ // Only applies to host_bash (not host_file, host_cu, host_browser — Phase 2).
391
+ // Excludes chrome-extension (security boundary: extension only gets host_browser)
392
+ // and hasNoClient turns (no interactive approval UI available).
393
+ if (
394
+ capability === "host_bash" &&
395
+ transport !== "chrome-extension" &&
396
+ !ctx.hasNoClient &&
397
+ assistantEventHub.listClientsByCapability("host_bash").length > 0
398
+ ) {
399
+ return true;
400
+ }
440
401
  return false;
441
402
  }
442
403
 
@@ -117,6 +117,7 @@ import {
117
117
  createToolExecutor,
118
118
  } from "./conversation-tool-setup.js";
119
119
  import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./conversation-workspace.js";
120
+ import type { HostAppControlProxy } from "./host-app-control-proxy.js";
120
121
  import { HostCuProxy } from "./host-cu-proxy.js";
121
122
  import type {
122
123
  ServerMessage,
@@ -204,6 +205,14 @@ export class Conversation {
204
205
  /** @internal */ taskRunId?: string;
205
206
  /** @internal */ callSessionId?: string;
206
207
  /** @internal */ hostCuProxy?: HostCuProxy;
208
+ /**
209
+ * Per-conversation host app-control proxy. Set via
210
+ * `setHostAppControlProxy` and disposed in `dispose()`. The
211
+ * `/v1/host-app-control-result` route forwards result payloads to the
212
+ * awaiting promise via this reference.
213
+ * @internal
214
+ */
215
+ hostAppControlProxy?: HostAppControlProxy;
207
216
  /** @internal */ cesClient?: CesClient;
208
217
  /** @internal */ readonly queue = new MessageQueue();
209
218
  /** @internal */ currentActiveSurfaceId?: string;
@@ -755,9 +764,12 @@ export class Conversation {
755
764
  clearTimeout(timer);
756
765
  }
757
766
  this.recentlyCompletedStandaloneSurfaces.clear();
758
- // Only dispose the per-conversation CU proxy. Bash/File/Transfer are
759
- // singletons — their lifecycle is managed by static disposeInstance().
767
+ // Only dispose the per-conversation CU and app-control proxies.
768
+ // Bash/File/Transfer are singletons — their lifecycle is managed by
769
+ // static disposeInstance().
760
770
  this.hostCuProxy?.dispose();
771
+ this.hostAppControlProxy?.dispose();
772
+ this.hostAppControlProxy = undefined;
761
773
  // CES client is owned by DaemonServer — just drop the reference.
762
774
  // Do NOT close it here; the server manages the CES lifecycle.
763
775
  this.cesClient = undefined;
@@ -936,6 +948,13 @@ export class Conversation {
936
948
  this.hostCuProxy = proxy;
937
949
  }
938
950
 
951
+ setHostAppControlProxy(proxy: HostAppControlProxy | undefined): void {
952
+ if (this.hostAppControlProxy && this.hostAppControlProxy !== proxy) {
953
+ this.hostAppControlProxy.dispose();
954
+ }
955
+ this.hostAppControlProxy = proxy;
956
+ }
957
+
939
958
  // ── Server-authoritative state signals ─────────────────────────────
940
959
 
941
960
  emitConfirmationStateChanged(
@@ -6,8 +6,8 @@
6
6
  */
7
7
 
8
8
  import { isPlainObject } from "../util/object.js";
9
- import type { ToolSetupContext } from "./conversation-tool-setup.js";
10
9
  import type { CardSurfaceData } from "./message-protocol.js";
10
+ import type { ToolSetupContext } from "./tool-setup-types.js";
11
11
 
12
12
  interface DoordashStep {
13
13
  label: string;
@@ -52,7 +52,10 @@ export interface HistoryToolCall {
52
52
  riskReason?: string;
53
53
  /** ID of the trust rule that matched this invocation (if any). */
54
54
  matchedTrustRuleId?: string;
55
- /** Whether the tool was auto-approved (true) or required explicit user input (false). */
55
+ /**
56
+ * @deprecated Use `approvalMode` and `approvalReason` instead.
57
+ * Kept for backward compatibility during the migration window.
58
+ */
56
59
  autoApproved?: boolean;
57
60
  /** How the approval decision was reached: prompted, auto, blocked, or unknown (legacy). */
58
61
  approvalMode?: string;
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Host app-control proxy.
3
+ *
4
+ * Proxies app-control actions (start, observe, press, combo, type, click,
5
+ * drag, stop) to the desktop client. Targets a specific application by
6
+ * bundle ID or process name — distinct from the system-wide computer-use
7
+ * proxy ({@link HostCuProxy}).
8
+ *
9
+ * Lifecycle (pending map, timeout, abort SSE, dispose, isAvailable) lives
10
+ * in {@link HostProxyBase}; this class layers app-control-specific state
11
+ * (PNG-hash loop guard) and the result-payload → ToolExecutionResult
12
+ * translation on top.
13
+ *
14
+ * **Singleton lock.** Only one conversation may hold an active app-control
15
+ * session at a time. The lock is module-level (`activeAppControlConversationId`)
16
+ * because a session targets the user's actual desktop application, which
17
+ * is a host-wide resource. The lock is acquired on a successful
18
+ * `app_control_start` and released when the owning proxy's `dispose()`
19
+ * fires. A second conversation that calls `start` while the lock is held
20
+ * receives an `isError: true` tool result naming the holding conversation.
21
+ *
22
+ * **No step cap.** Unlike {@link HostCuProxy} which enforces a per-session
23
+ * step ceiling via `loadConfig().maxStepsPerSession`, app-control sessions
24
+ * are not capped. App-control flows are typically narrower (single-app,
25
+ * shorter horizons) and the loop guard plus user oversight are the
26
+ * intended safeguards.
27
+ */
28
+
29
+ import { createHash } from "node:crypto";
30
+
31
+ import type { ContentBlock } from "../providers/types.js";
32
+ import type { ToolExecutionResult } from "../tools/types.js";
33
+ import { getLogger } from "../util/logger.js";
34
+ import { HostProxyBase, HostProxyRequestError } from "./host-proxy-base.js";
35
+ import type {
36
+ HostAppControlInput,
37
+ HostAppControlResultPayload,
38
+ } from "./message-types/host-app-control.js";
39
+
40
+ const log = getLogger("host-app-control-proxy");
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Constants
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const REQUEST_TIMEOUT_MS = 60 * 1000;
47
+ // Threshold of 4 means the warning fires on the 5th identical observation:
48
+ // the first observation establishes the baseline (count = 0), each
49
+ // subsequent identical observation increments the counter, so count = 4 is
50
+ // reached on the 5th total observation.
51
+ const STUCK_REPEAT_THRESHOLD = 4;
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Tool name constants
55
+ // ---------------------------------------------------------------------------
56
+ //
57
+ // Kept here (rather than imported from PR 5's tool registrations) so the
58
+ // proxy is independently testable. PR 5 must use these same string values.
59
+
60
+ const TOOL_START = "app_control_start";
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Module-level singleton lock
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Conversation id that currently owns the active app-control session, or
68
+ * `undefined` if no session is active. Set on a successful
69
+ * `app_control_start`; cleared by the owning proxy's `dispose()`.
70
+ *
71
+ * Exported for test inspection only. Production code paths must not read
72
+ * or mutate this directly — use the proxy methods.
73
+ */
74
+ let activeAppControlConversationId: string | undefined;
75
+
76
+ /** Test-only helper: read current lock owner. */
77
+ export function _getActiveAppControlConversationId(): string | undefined {
78
+ return activeAppControlConversationId;
79
+ }
80
+
81
+ /** Test-only helper: clear lock between test cases. */
82
+ export function _resetActiveAppControlConversationId(): void {
83
+ activeAppControlConversationId = undefined;
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // HostAppControlProxy
88
+ // ---------------------------------------------------------------------------
89
+
90
+ export class HostAppControlProxy extends HostProxyBase<
91
+ HostAppControlInput,
92
+ HostAppControlResultPayload
93
+ > {
94
+ /** Conversation that owns this proxy instance. Used by `dispose()` to release the singleton lock only when this proxy is the holder. */
95
+ private readonly conversationId: string;
96
+
97
+ /** sha256 hex of the most recent observation's `pngBase64`, or undefined. */
98
+ private lastObservationHash?: string;
99
+
100
+ /**
101
+ * Number of consecutive observations whose PNG hash matched the previous
102
+ * one. Reset to 0 when a different hash is observed. When this reaches
103
+ * {@link STUCK_REPEAT_THRESHOLD}, results carry a `"stuck"` warning.
104
+ */
105
+ private observationHashRepeatCount = 0;
106
+
107
+ constructor(conversationId: string) {
108
+ super({
109
+ capabilityName: "host_app_control",
110
+ requestEventName: "host_app_control_request",
111
+ cancelEventName: "host_app_control_cancel",
112
+ resultPendingKind: "host_app_control",
113
+ timeoutMs: REQUEST_TIMEOUT_MS,
114
+ disposedMessage: "Host app-control proxy disposed",
115
+ });
116
+ this.conversationId = conversationId;
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // State accessors (testing / external inspection)
121
+ // ---------------------------------------------------------------------------
122
+
123
+ get observationRepeatCount(): number {
124
+ return this.observationHashRepeatCount;
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Public request entry point
129
+ // ---------------------------------------------------------------------------
130
+
131
+ /**
132
+ * Dispatch an app-control tool call to the desktop client. Catches the
133
+ * base's typed lifecycle errors (timeout/aborted/disposed) and returns
134
+ * a `ToolExecutionResult` instead of letting them bubble.
135
+ */
136
+ async request(
137
+ toolName: string,
138
+ input: HostAppControlInput,
139
+ conversationId: string,
140
+ signal: AbortSignal,
141
+ ): Promise<ToolExecutionResult> {
142
+ if (signal.aborted) {
143
+ return { content: "Aborted", isError: true };
144
+ }
145
+
146
+ // Singleton-lock guard for `start`. Other tools assume a session
147
+ // already exists and are not gated here.
148
+ if (toolName === TOOL_START) {
149
+ if (
150
+ activeAppControlConversationId != null &&
151
+ activeAppControlConversationId !== conversationId
152
+ ) {
153
+ return {
154
+ content:
155
+ `Another conversation (${activeAppControlConversationId}) currently holds the ` +
156
+ `app-control session. Wait for it to finish, or call app_control_stop ` +
157
+ `from that conversation first.`,
158
+ isError: true,
159
+ };
160
+ }
161
+ }
162
+
163
+ try {
164
+ const payload = await this.dispatchRequest(
165
+ toolName,
166
+ input,
167
+ conversationId,
168
+ signal,
169
+ );
170
+ return this.handleSuccess(toolName, payload);
171
+ } catch (err) {
172
+ if (err instanceof HostProxyRequestError) {
173
+ if (err.reason === "timeout") {
174
+ log.warn({ toolName }, "Host app-control proxy request timed out");
175
+ return {
176
+ content:
177
+ "Host app-control proxy timed out waiting for client response",
178
+ isError: true,
179
+ };
180
+ }
181
+ if (err.reason === "aborted") {
182
+ return { content: "Aborted", isError: true };
183
+ }
184
+ }
185
+ // `disposed` and any other unexpected errors propagate.
186
+ throw err;
187
+ }
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Result handling
192
+ // ---------------------------------------------------------------------------
193
+
194
+ private handleSuccess(
195
+ toolName: string,
196
+ payload: HostAppControlResultPayload,
197
+ ): ToolExecutionResult {
198
+ // Update PNG-hash loop tracking only for the "running" state — other
199
+ // states (missing/minimized) intentionally won't carry a
200
+ // representative window screenshot, so they should not feed the guard.
201
+ let stuck = false;
202
+ if (payload.state === "running" && payload.pngBase64) {
203
+ const hash = createHash("sha256").update(payload.pngBase64).digest("hex");
204
+ if (hash === this.lastObservationHash) {
205
+ this.observationHashRepeatCount++;
206
+ } else {
207
+ this.observationHashRepeatCount = 0;
208
+ }
209
+ this.lastObservationHash = hash;
210
+ if (this.observationHashRepeatCount >= STUCK_REPEAT_THRESHOLD) {
211
+ stuck = true;
212
+ }
213
+ }
214
+
215
+ // Acquire the singleton lock on a successful `start`.
216
+ if (toolName === TOOL_START && payload.state === "running") {
217
+ activeAppControlConversationId = this.conversationId;
218
+ }
219
+
220
+ return this.formatResult(payload, stuck);
221
+ }
222
+
223
+ private formatResult(
224
+ payload: HostAppControlResultPayload,
225
+ stuck: boolean,
226
+ ): ToolExecutionResult {
227
+ const parts: string[] = [];
228
+
229
+ if (stuck) {
230
+ parts.push(
231
+ `WARNING: ${this.observationHashRepeatCount} consecutive observations ` +
232
+ `produced an identical screenshot — the app appears stuck. Try a ` +
233
+ `different action or call app_control_stop and restart.`,
234
+ );
235
+ parts.push("");
236
+ }
237
+
238
+ parts.push(`State: ${payload.state}`);
239
+
240
+ if (payload.windowBounds) {
241
+ const { x, y, width, height } = payload.windowBounds;
242
+ parts.push(`Window bounds: ${width}x${height} at (${x}, ${y})`);
243
+ }
244
+
245
+ if (payload.executionResult) {
246
+ parts.push("");
247
+ parts.push(payload.executionResult);
248
+ }
249
+
250
+ const isError = payload.executionError != null;
251
+ const errorPrefix = isError
252
+ ? `Action failed: ${payload.executionError}`
253
+ : null;
254
+
255
+ const baseContent = parts.join("\n").trim() || `State: ${payload.state}`;
256
+ const content = errorPrefix
257
+ ? `${errorPrefix}\n\n${baseContent}`
258
+ : baseContent;
259
+
260
+ const contentBlocks: ContentBlock[] = [];
261
+ if (payload.pngBase64) {
262
+ contentBlocks.push({
263
+ type: "image",
264
+ source: {
265
+ type: "base64",
266
+ media_type: "image/png",
267
+ data: payload.pngBase64,
268
+ },
269
+ });
270
+ }
271
+
272
+ return {
273
+ content,
274
+ isError,
275
+ ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
276
+ };
277
+ }
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // Lifecycle
281
+ // ---------------------------------------------------------------------------
282
+
283
+ /**
284
+ * Reject pending requests via the base, then release the singleton lock
285
+ * if this proxy is the holder. Idempotent: safe to call multiple times.
286
+ */
287
+ override dispose(): void {
288
+ super.dispose();
289
+ if (activeAppControlConversationId === this.conversationId) {
290
+ activeAppControlConversationId = undefined;
291
+ }
292
+ }
293
+ }