@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
@@ -1,7 +1,6 @@
1
1
  import { afterEach, describe, expect, jest, mock, test } from "bun:test";
2
2
 
3
3
  const sentMessages: unknown[] = [];
4
- const resolvedInteractionIds: string[] = [];
5
4
  let mockHasClient = false;
6
5
 
7
6
  mock.module("../runtime/assistant-event-hub.js", () => ({
@@ -12,17 +11,8 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
12
11
  },
13
12
  }));
14
13
 
15
- mock.module("../runtime/pending-interactions.js", () => ({
16
- resolve: (requestId: string) => {
17
- resolvedInteractionIds.push(requestId);
18
- return undefined;
19
- },
20
- get: () => undefined,
21
- getByKind: () => [],
22
- getByConversation: () => [],
23
- removeByConversation: () => {},
24
- }));
25
-
14
+ // Use the REAL pending-interactions module the proxy self-registers here.
15
+ const pendingInteractions = await import("../runtime/pending-interactions.js");
26
16
  const { HostCuProxy } = await import("../daemon/host-cu-proxy.js");
27
17
 
28
18
  describe("HostCuProxy", () => {
@@ -30,13 +20,14 @@ describe("HostCuProxy", () => {
30
20
 
31
21
  function setup(maxSteps?: number) {
32
22
  sentMessages.length = 0;
33
- resolvedInteractionIds.length = 0;
34
23
  mockHasClient = false;
24
+ pendingInteractions.clear();
35
25
  proxy = new HostCuProxy(maxSteps);
36
26
  }
37
27
 
38
28
  afterEach(() => {
39
29
  proxy?.dispose();
30
+ pendingInteractions.clear();
40
31
  });
41
32
 
42
33
  // -------------------------------------------------------------------------
@@ -66,9 +57,9 @@ describe("HostCuProxy", () => {
66
57
  expect(typeof sent.requestId).toBe("string");
67
58
 
68
59
  const requestId = sent.requestId as string;
69
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
60
+ expect(pendingInteractions.get(requestId)).toBeDefined();
70
61
 
71
- proxy.resolve(requestId, {
62
+ proxy.processObservation(requestId, {
72
63
  axTree: "Button [1]\nLabel [2]",
73
64
  executionResult: "Clicked element 42",
74
65
  });
@@ -78,7 +69,7 @@ describe("HostCuProxy", () => {
78
69
  expect(result.content).toContain("<ax-tree>");
79
70
  expect(result.content).toContain("CURRENT SCREEN STATE:");
80
71
  expect(result.isError).toBe(false);
81
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
72
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
82
73
  });
83
74
 
84
75
  test("formats error observation correctly", async () => {
@@ -94,7 +85,7 @@ describe("HostCuProxy", () => {
94
85
  const sent = sentMessages[0] as Record<string, unknown>;
95
86
  const requestId = sent.requestId as string;
96
87
 
97
- proxy.resolve(requestId, {
88
+ proxy.processObservation(requestId, {
98
89
  executionError: "Element not found",
99
90
  axTree: "Window [1]",
100
91
  });
@@ -118,7 +109,7 @@ describe("HostCuProxy", () => {
118
109
  const sent = sentMessages[0] as Record<string, unknown>;
119
110
  const requestId = sent.requestId as string;
120
111
 
121
- proxy.resolve(requestId, {
112
+ proxy.processObservation(requestId, {
122
113
  axTree: "Button [1]",
123
114
  screenshot: "base64data",
124
115
  screenshotWidthPx: 1920,
@@ -142,7 +133,7 @@ describe("HostCuProxy", () => {
142
133
  test("resolves with unknown requestId is silently ignored", () => {
143
134
  setup();
144
135
  // Should not throw
145
- proxy.resolve("unknown-id", { axTree: "something" });
136
+ proxy.processObservation("unknown-id", { axTree: "something" });
146
137
  });
147
138
  });
148
139
 
@@ -165,10 +156,10 @@ describe("HostCuProxy", () => {
165
156
 
166
157
  const sent = sentMessages[0] as Record<string, unknown>;
167
158
  const requestId = sent.requestId as string;
168
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
159
+ expect(pendingInteractions.get(requestId)).toBeDefined();
169
160
 
170
161
  // Resolve to avoid test hanging
171
- proxy.resolve(requestId, { axTree: "resolved" });
162
+ proxy.processObservation(requestId, { axTree: "resolved" });
172
163
  await resultPromise;
173
164
  });
174
165
  });
@@ -193,14 +184,14 @@ describe("HostCuProxy", () => {
193
184
 
194
185
  const sent = sentMessages[0] as Record<string, unknown>;
195
186
  const requestId = sent.requestId as string;
196
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
187
+ expect(pendingInteractions.get(requestId)).toBeDefined();
197
188
 
198
189
  controller.abort();
199
190
 
200
191
  const result = await resultPromise;
201
192
  expect(result.content).toContain("Aborted");
202
193
  expect(result.isError).toBe(true);
203
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
194
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
204
195
  });
205
196
 
206
197
  test("sends host_cu_cancel to client on abort", async () => {
@@ -296,7 +287,7 @@ describe("HostCuProxy", () => {
296
287
  expect(sentMessages).toHaveLength(1); // Message was sent
297
288
 
298
289
  const sent = sentMessages[0] as Record<string, unknown>;
299
- proxy.resolve(sent.requestId as string, { axTree: "screen" });
290
+ proxy.processObservation(sent.requestId as string, { axTree: "screen" });
300
291
 
301
292
  const result = await resultPromise;
302
293
  expect(result.isError).toBe(false);
@@ -370,7 +361,7 @@ describe("HostCuProxy", () => {
370
361
  );
371
362
  proxy.recordAction("computer_use_click", { element_id: 1 });
372
363
  const sent1 = sentMessages[0] as Record<string, unknown>;
373
- proxy.resolve(sent1.requestId as string, {
364
+ proxy.processObservation(sent1.requestId as string, {
374
365
  axTree: "Button [1]",
375
366
  });
376
367
  await p1;
@@ -384,7 +375,7 @@ describe("HostCuProxy", () => {
384
375
  );
385
376
  proxy.recordAction("computer_use_click", { element_id: 1 });
386
377
  const sent2 = sentMessages[1] as Record<string, unknown>;
387
- proxy.resolve(sent2.requestId as string, {
378
+ proxy.processObservation(sent2.requestId as string, {
388
379
  axTree: "Button [1]",
389
380
  // No axDiff — screen unchanged
390
381
  });
@@ -402,7 +393,7 @@ describe("HostCuProxy", () => {
402
393
  );
403
394
  proxy.recordAction("computer_use_click", { element_id: 1 });
404
395
  const sent3 = sentMessages[2] as Record<string, unknown>;
405
- proxy.resolve(sent3.requestId as string, {
396
+ proxy.processObservation(sent3.requestId as string, {
406
397
  axTree: "Button [1]",
407
398
  });
408
399
  const result3 = await p3;
@@ -424,7 +415,7 @@ describe("HostCuProxy", () => {
424
415
  1,
425
416
  );
426
417
  const sent1 = sentMessages[0] as Record<string, unknown>;
427
- proxy.resolve(sent1.requestId as string, {
418
+ proxy.processObservation(sent1.requestId as string, {
428
419
  axTree: "Button [1]",
429
420
  // No axDiff on first observation — this is normal, not unchanged
430
421
  });
@@ -444,7 +435,7 @@ describe("HostCuProxy", () => {
444
435
  );
445
436
  proxy.recordAction("computer_use_click", { element_id: 1 });
446
437
  const sent1 = sentMessages[0] as Record<string, unknown>;
447
- proxy.resolve(sent1.requestId as string, {
438
+ proxy.processObservation(sent1.requestId as string, {
448
439
  axTree: "Button [1]",
449
440
  });
450
441
  await p1;
@@ -458,7 +449,7 @@ describe("HostCuProxy", () => {
458
449
  );
459
450
  proxy.recordAction("computer_use_wait", { duration_ms: 2000 });
460
451
  const sent2 = sentMessages[1] as Record<string, unknown>;
461
- proxy.resolve(sent2.requestId as string, {
452
+ proxy.processObservation(sent2.requestId as string, {
462
453
  axTree: "Button [1]",
463
454
  // No axDiff — screen unchanged, but that's expected after wait
464
455
  });
@@ -478,7 +469,7 @@ describe("HostCuProxy", () => {
478
469
  );
479
470
  proxy.recordAction("computer_use_click", { element_id: 1 });
480
471
  const sent1 = sentMessages[0] as Record<string, unknown>;
481
- proxy.resolve(sent1.requestId as string, {
472
+ proxy.processObservation(sent1.requestId as string, {
482
473
  axTree: "Button [1]",
483
474
  });
484
475
  await p1;
@@ -492,7 +483,7 @@ describe("HostCuProxy", () => {
492
483
  );
493
484
  proxy.recordAction("computer_use_click", { element_id: 1 });
494
485
  const sent2 = sentMessages[1] as Record<string, unknown>;
495
- proxy.resolve(sent2.requestId as string, {
486
+ proxy.processObservation(sent2.requestId as string, {
496
487
  axTree: "Button [1]",
497
488
  });
498
489
  await p2;
@@ -507,7 +498,7 @@ describe("HostCuProxy", () => {
507
498
  );
508
499
  proxy.recordAction("computer_use_click", { element_id: 2 });
509
500
  const sent3 = sentMessages[2] as Record<string, unknown>;
510
- proxy.resolve(sent3.requestId as string, {
501
+ proxy.processObservation(sent3.requestId as string, {
511
502
  axTree: "TextField [1]",
512
503
  axDiff: "+ TextField [1]\n- Button [1]",
513
504
  });
@@ -719,11 +710,11 @@ describe("HostCuProxy", () => {
719
710
 
720
711
  const sent = sentMessages[0] as Record<string, unknown>;
721
712
  const requestId = sent.requestId as string;
722
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
713
+ expect(pendingInteractions.get(requestId)).toBeDefined();
723
714
 
724
715
  proxy.dispose();
725
716
 
726
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
717
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
727
718
  await expect(resultPromise).rejects.toThrow("Host CU proxy disposed");
728
719
  });
729
720
 
@@ -786,9 +777,9 @@ describe("HostCuProxy", () => {
786
777
  expect(result.content).toContain("Aborted");
787
778
 
788
779
  // Late resolve should be silently ignored (no throw, no double-resolve)
789
- proxy.resolve(requestId, { axTree: "late response" });
780
+ proxy.processObservation(requestId, { axTree: "late response" });
790
781
 
791
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
782
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
792
783
  });
793
784
  });
794
785
 
@@ -811,17 +802,11 @@ describe("HostCuProxy", () => {
811
802
  const s = source as any;
812
803
  const origAdd = source.addEventListener.bind(source);
813
804
  const origRemove = source.removeEventListener.bind(source);
814
- s.addEventListener = (
815
- type: string,
816
- ...rest: any[]
817
- ) => {
805
+ s.addEventListener = (type: string, ...rest: any[]) => {
818
806
  addCalls.push(type);
819
807
  return (origAdd as any)(type, ...rest);
820
808
  };
821
- s.removeEventListener = (
822
- type: string,
823
- ...rest: any[]
824
- ) => {
809
+ s.removeEventListener = (type: string, ...rest: any[]) => {
825
810
  removeCalls.push(type);
826
811
  return (origRemove as any)(type, ...rest);
827
812
  };
@@ -847,7 +832,7 @@ describe("HostCuProxy", () => {
847
832
 
848
833
  const requestId = (sentMessages[0] as Record<string, unknown>)
849
834
  .requestId as string;
850
- proxy.resolve(requestId, { axTree: "Button [1]" });
835
+ proxy.processObservation(requestId, { axTree: "Button [1]" });
851
836
  await resultPromise;
852
837
 
853
838
  // Listener is detached after normal completion.
@@ -881,7 +866,7 @@ describe("HostCuProxy", () => {
881
866
 
882
867
  const requestId = (sentMessages[0] as Record<string, unknown>)
883
868
  .requestId as string;
884
- expect(proxy.hasPendingRequest(requestId)).toBe(true);
869
+ expect(pendingInteractions.get(requestId)).toBeDefined();
885
870
 
886
871
  // Advance past the 60s internal timeout.
887
872
  jest.advanceTimersByTime(61 * 1000);
@@ -889,7 +874,7 @@ describe("HostCuProxy", () => {
889
874
  const result = await resultPromise;
890
875
  expect(result.isError).toBe(true);
891
876
  expect(result.content).toContain("Host CU proxy timed out");
892
- expect(proxy.hasPendingRequest(requestId)).toBe(false);
877
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
893
878
 
894
879
  // Listener is detached after the timer fires.
895
880
  expect(spy.removeCalls).toEqual(["abort"]);
@@ -927,7 +912,7 @@ describe("HostCuProxy", () => {
927
912
  controller.abort();
928
913
 
929
914
  await resultPromise;
930
- expect(resolvedInteractionIds).toContain(requestId);
915
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
931
916
  });
932
917
 
933
918
  test("fires on dispose", async () => {
@@ -948,7 +933,7 @@ describe("HostCuProxy", () => {
948
933
  // dispose rejects pending requests — catch to avoid unhandled rejection
949
934
  await resultPromise.catch(() => {});
950
935
 
951
- expect(resolvedInteractionIds).toContain(requestId);
936
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
952
937
  });
953
938
 
954
939
  test("does not fire on normal client-initiated resolve", async () => {
@@ -964,10 +949,10 @@ describe("HostCuProxy", () => {
964
949
  const sent = sentMessages[0] as Record<string, unknown>;
965
950
  const requestId = sent.requestId as string;
966
951
 
967
- proxy.resolve(requestId, { axTree: "Button [1]" });
952
+ proxy.processObservation(requestId, { axTree: "Button [1]" });
968
953
 
969
954
  await resultPromise;
970
- expect(resolvedInteractionIds).toEqual([]);
955
+ expect(pendingInteractions.get(requestId)).toBeUndefined();
971
956
  });
972
957
  });
973
958
 
@@ -988,4 +973,8 @@ describe("HostCuProxy", () => {
988
973
  expect(proxy.isAvailable()).toBe(true);
989
974
  });
990
975
  });
976
+
977
+ // targetClientId validation lives at the surfaceProxyResolver layer (so an
978
+ // invalid ID does not burn a step or pollute action history before being
979
+ // rejected). See cu-unified-flow.test.ts for those tests.
991
980
  });
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Tests for the host-cu-result route 403 guard introduced in Phase 2.
3
+ *
4
+ * Covers:
5
+ * 1. Targeted + correct x-vellum-client-id header → 200 accepted
6
+ * 2. Targeted + missing header → 400 BadRequestError
7
+ * 3. Targeted + wrong header → 403 ForbiddenError, interaction NOT consumed
8
+ * 4. Untargeted (no targetClientId, no header) → 200 accepted (regression)
9
+ *
10
+ * Resolution goes through conversation.hostCuProxy?.resolve(...). The
11
+ * conversation store is mocked to return a controlled conversation object.
12
+ *
13
+ * Note: host-cu-routes.ts has a deep import chain (conversation-store →
14
+ * conversation.ts → ces-client → service-contracts) that requires mocking
15
+ * before the module loads. We use dynamic imports to ensure all mocks are
16
+ * registered before the route module is evaluated.
17
+ */
18
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
19
+
20
+ // ── Module mocks ─────────────────────────────────────────────────────────────
21
+ // Must be registered before the host-cu-routes module is loaded.
22
+
23
+ mock.module("../config/env.js", () => ({
24
+ isHttpAuthDisabled: () => true,
25
+ hasUngatedHttpAuthDisabled: () => false,
26
+ getPlatformBaseUrl: () => "https://platform.example.com",
27
+ getGatewayInternalBaseUrl: () => "http://localhost:8080",
28
+ getIngressPublicBaseUrl: () => undefined,
29
+ setIngressPublicBaseUrl: () => {},
30
+ getRuntimeHttpPort: () => 3000,
31
+ getRuntimeHttpHost: () => "0.0.0.0",
32
+ getSentryDsn: () => "",
33
+ getQdrantUrlEnv: () => undefined,
34
+ getQdrantHttpPortEnv: () => undefined,
35
+ getQdrantReadyzTimeoutMs: () => undefined,
36
+ getOllamaBaseUrlEnv: () => undefined,
37
+ setPlatformBaseUrl: () => {},
38
+ getAssistantDomain: () => "example.com",
39
+ setPlatformAssistantId: () => {},
40
+ getPlatformAssistantId: () => "test-assistant-id",
41
+ setPlatformOrganizationId: () => {},
42
+ getPlatformOrganizationId: () => "test-org-id",
43
+ setPlatformUserId: () => {},
44
+ getPlatformUserId: () => "test-user-id",
45
+ getPlatformInternalApiKey: () => "test-api-key",
46
+ validateEnv: () => {},
47
+ }));
48
+
49
+ import type { PendingInteraction } from "../runtime/pending-interactions.js";
50
+
51
+ const pendingStore = new Map<string, PendingInteraction>();
52
+ const resolvedIds: string[] = [];
53
+
54
+ mock.module("../runtime/pending-interactions.js", () => ({
55
+ get: (requestId: string) => pendingStore.get(requestId),
56
+ resolve: (requestId: string) => {
57
+ const entry = pendingStore.get(requestId);
58
+ if (entry) {
59
+ pendingStore.delete(requestId);
60
+ resolvedIds.push(requestId);
61
+ }
62
+ return entry;
63
+ },
64
+ }));
65
+
66
+ interface CuResolveCall {
67
+ requestId: string;
68
+ payload: Record<string, unknown>;
69
+ }
70
+
71
+ const cuResolveSpy: CuResolveCall[] = [];
72
+
73
+ // Controlled conversation map: conversationId → conversation object
74
+ const conversationStore = new Map<string, { hostCuProxy?: { processObservation: (...args: unknown[]) => void } }>();
75
+
76
+ mock.module("../daemon/conversation-store.js", () => ({
77
+ findConversation: (conversationId: string) => conversationStore.get(conversationId),
78
+ }));
79
+
80
+ // ── Real imports (after mocks) ──────────────────────────────────────────────
81
+ // Use dynamic import to ensure the mocks above are applied before loading.
82
+
83
+ import {
84
+ BadRequestError,
85
+ ForbiddenError,
86
+ } from "../runtime/routes/errors.js";
87
+
88
+ const { ROUTES } = await import("../runtime/routes/host-cu-routes.js");
89
+
90
+ afterAll(() => {
91
+ mock.restore();
92
+ });
93
+
94
+ const handleHostCuResult = ROUTES.find(
95
+ (r: { endpoint: string }) => r.endpoint === "host-cu-result",
96
+ )!.handler;
97
+
98
+ // ── Helpers ──────────────────────────────────────────────────────────────────
99
+
100
+ function registerPending(
101
+ requestId: string,
102
+ overrides: Partial<PendingInteraction> = {},
103
+ ): void {
104
+ const entry: PendingInteraction = {
105
+ conversationId: "conv-cu-1",
106
+ kind: "host_cu",
107
+ ...overrides,
108
+ };
109
+ pendingStore.set(requestId, entry);
110
+ }
111
+
112
+ function registerConversation(conversationId = "conv-cu-1"): void {
113
+ conversationStore.set(conversationId, {
114
+ hostCuProxy: {
115
+ processObservation(requestId: unknown, payload: unknown) {
116
+ // Simulate what the real processObservation does: consume the pending interaction
117
+ pendingStore.delete(requestId as string);
118
+ resolvedIds.push(requestId as string);
119
+ cuResolveSpy.push({
120
+ requestId: requestId as string,
121
+ payload: payload as Record<string, unknown>,
122
+ });
123
+ },
124
+ },
125
+ });
126
+ }
127
+
128
+ function cuBody(requestId: string): Record<string, unknown> {
129
+ return {
130
+ requestId,
131
+ axTree: "Button [1]",
132
+ executionResult: "Clicked",
133
+ };
134
+ }
135
+
136
+ // ── Tests ─────────────────────────────────────────────────────────────────────
137
+
138
+ describe("handleHostCuResult — Phase 2 targetClientId guard", () => {
139
+ beforeEach(() => {
140
+ pendingStore.clear();
141
+ conversationStore.clear();
142
+ resolvedIds.length = 0;
143
+ cuResolveSpy.length = 0;
144
+ // Default: register a conversation with a hostCuProxy
145
+ registerConversation("conv-cu-1");
146
+ });
147
+
148
+ // ── 1. Targeted + correct header → 200 ────────────────────────────────────
149
+
150
+ describe("targeted + correct x-vellum-client-id header", () => {
151
+ test("returns { accepted: true } and resolves the interaction", async () => {
152
+ const requestId = "req-cu-targeted-match";
153
+ registerPending(requestId, { targetClientId: "client-A" });
154
+
155
+ const result = await handleHostCuResult({
156
+ body: cuBody(requestId),
157
+ headers: { "x-vellum-client-id": "client-A" },
158
+ });
159
+
160
+ expect(result).toEqual({ accepted: true });
161
+ expect(cuResolveSpy).toHaveLength(1);
162
+ expect(cuResolveSpy[0].requestId).toBe(requestId);
163
+ expect(resolvedIds).toContain(requestId);
164
+ });
165
+
166
+ test("trims whitespace from header before comparing", async () => {
167
+ const requestId = "req-cu-targeted-trim";
168
+ registerPending(requestId, { targetClientId: "client-A" });
169
+
170
+ const result = await handleHostCuResult({
171
+ body: cuBody(requestId),
172
+ headers: { "x-vellum-client-id": " client-A " },
173
+ });
174
+
175
+ expect(result).toEqual({ accepted: true });
176
+ });
177
+ });
178
+
179
+ // ── 2. Targeted + missing header → 400 ────────────────────────────────────
180
+
181
+ describe("targeted + missing x-vellum-client-id header", () => {
182
+ test("throws BadRequestError (400) when header is absent", () => {
183
+ const requestId = "req-cu-targeted-no-header";
184
+ registerPending(requestId, { targetClientId: "client-A" });
185
+
186
+ expect(() =>
187
+ handleHostCuResult({ body: cuBody(requestId) }),
188
+ ).toThrow(BadRequestError);
189
+ });
190
+
191
+ test("throws BadRequestError (400) when header is empty string", () => {
192
+ const requestId = "req-cu-targeted-empty-header";
193
+ registerPending(requestId, { targetClientId: "client-A" });
194
+
195
+ expect(() =>
196
+ handleHostCuResult({
197
+ body: cuBody(requestId),
198
+ headers: { "x-vellum-client-id": " " },
199
+ }),
200
+ ).toThrow(BadRequestError);
201
+ });
202
+
203
+ test("interaction is NOT resolved on 400 (still pending)", () => {
204
+ const requestId = "req-cu-targeted-no-header-stays";
205
+ registerPending(requestId, { targetClientId: "client-A" });
206
+
207
+ try {
208
+ handleHostCuResult({ body: cuBody(requestId) });
209
+ } catch {
210
+ // expected
211
+ }
212
+
213
+ expect(resolvedIds).not.toContain(requestId);
214
+ expect(pendingStore.has(requestId)).toBe(true);
215
+ });
216
+ });
217
+
218
+ // ── 3. Targeted + wrong header → 403 ──────────────────────────────────────
219
+
220
+ describe("targeted + wrong x-vellum-client-id header", () => {
221
+ test("throws ForbiddenError (403) when client ID does not match", () => {
222
+ const requestId = "req-cu-targeted-mismatch";
223
+ registerPending(requestId, { targetClientId: "client-A" });
224
+
225
+ expect(() =>
226
+ handleHostCuResult({
227
+ body: cuBody(requestId),
228
+ headers: { "x-vellum-client-id": "client-B" },
229
+ }),
230
+ ).toThrow(ForbiddenError);
231
+ });
232
+
233
+ test("ForbiddenError message names both submitting and expected client", () => {
234
+ const requestId = "req-cu-targeted-mismatch-msg";
235
+ registerPending(requestId, { targetClientId: "client-A" });
236
+
237
+ let caught: unknown;
238
+ try {
239
+ handleHostCuResult({
240
+ body: cuBody(requestId),
241
+ headers: { "x-vellum-client-id": "client-B" },
242
+ });
243
+ } catch (e) {
244
+ caught = e;
245
+ }
246
+
247
+ expect(caught).toBeInstanceOf(ForbiddenError);
248
+ const msg = (caught as ForbiddenError).message;
249
+ expect(msg).toContain("client-B");
250
+ expect(msg).toContain("client-A");
251
+ });
252
+
253
+ test("interaction is NOT consumed on 403 (pendingInteractions.get still returns it)", () => {
254
+ const requestId = "req-cu-targeted-mismatch-stays";
255
+ registerPending(requestId, { targetClientId: "client-A" });
256
+
257
+ try {
258
+ handleHostCuResult({
259
+ body: cuBody(requestId),
260
+ headers: { "x-vellum-client-id": "client-B" },
261
+ });
262
+ } catch {
263
+ // expected
264
+ }
265
+
266
+ expect(resolvedIds).not.toContain(requestId);
267
+ expect(pendingStore.has(requestId)).toBe(true);
268
+ });
269
+ });
270
+
271
+ // ── 4. Untargeted — regression ────────────────────────────────────────────
272
+
273
+ describe("untargeted request (no targetClientId)", () => {
274
+ test("accepts when no header is provided", async () => {
275
+ const requestId = "req-cu-untargeted-no-header";
276
+ registerPending(requestId);
277
+
278
+ const result = await handleHostCuResult({
279
+ body: cuBody(requestId),
280
+ });
281
+
282
+ expect(result).toEqual({ accepted: true });
283
+ expect(cuResolveSpy).toHaveLength(1);
284
+ expect(resolvedIds).toContain(requestId);
285
+ });
286
+
287
+ test("accepts when header is present (header ignored for untargeted)", async () => {
288
+ const requestId = "req-cu-untargeted-with-header";
289
+ registerPending(requestId);
290
+
291
+ const result = await handleHostCuResult({
292
+ body: cuBody(requestId),
293
+ headers: { "x-vellum-client-id": "client-whatever" },
294
+ });
295
+
296
+ expect(result).toEqual({ accepted: true });
297
+ expect(cuResolveSpy).toHaveLength(1);
298
+ });
299
+ });
300
+ });
@@ -1,7 +1,29 @@
1
1
  import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { afterEach, describe, expect, test } from "bun:test";
4
+ import { afterEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ import type { HostFileInput } from "../daemon/host-file-proxy.js";
7
+ import type { ToolExecutionResult } from "../tools/types.js";
8
+
9
+ // Mock HostFileProxy singleton so proxy delegation tests can control it.
10
+ let mockFileProxyAvailable = false;
11
+ let mockFileProxyRequestFn: (
12
+ input: HostFileInput,
13
+ conversationId: string,
14
+ signal?: AbortSignal,
15
+ ) => Promise<ToolExecutionResult> = () => Promise.resolve({ content: "", isError: false });
16
+
17
+ mock.module("../daemon/host-file-proxy.js", () => ({
18
+ HostFileProxy: {
19
+ get instance() {
20
+ return {
21
+ isAvailable: () => mockFileProxyAvailable,
22
+ request: mockFileProxyRequestFn,
23
+ };
24
+ },
25
+ },
26
+ }));
5
27
 
6
28
  import { hostFileEditTool } from "../tools/host-filesystem/edit.js";
7
29
  import type { ToolContext } from "../tools/types.js";
@@ -20,6 +42,8 @@ afterEach(() => {
20
42
  for (const dir of testDirs.splice(0)) {
21
43
  rmSync(dir, { recursive: true, force: true });
22
44
  }
45
+ mockFileProxyAvailable = false;
46
+ mockFileProxyRequestFn = () => Promise.resolve({ content: "", isError: false });
23
47
  });
24
48
 
25
49
  describe("host_file_edit tool", () => {
@@ -268,4 +292,26 @@ describe("host_file_edit tool", () => {
268
292
  result.content.includes("Successfully edited"),
269
293
  ).toBe(true);
270
294
  });
295
+
296
+ test("passes target_client_id to HostFileProxy.instance.request", async () => {
297
+ const capturedInputs: HostFileInput[] = [];
298
+ mockFileProxyAvailable = true;
299
+ mockFileProxyRequestFn = async (input) => {
300
+ capturedInputs.push(input);
301
+ return { content: "proxied edit", isError: false };
302
+ };
303
+
304
+ await hostFileEditTool.execute(
305
+ {
306
+ path: "/host/file.txt",
307
+ old_string: "old",
308
+ new_string: "new",
309
+ target_client_id: "client-x",
310
+ },
311
+ makeContext(),
312
+ );
313
+
314
+ expect(capturedInputs).toHaveLength(1);
315
+ expect(capturedInputs[0].targetClientId).toBe("client-x");
316
+ });
271
317
  });