@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -42,11 +42,12 @@
42
42
  "@sentry/node": "10.43.0",
43
43
  "@vellumai/ces-client": "file:../packages/ces-client",
44
44
  "@vellumai/credential-storage": "file:../packages/credential-storage",
45
- "@vellumai/service-contracts": "file:../packages/service-contracts",
46
45
  "@vellumai/egress-proxy": "file:../packages/egress-proxy",
47
46
  "@vellumai/gateway-client": "file:../packages/gateway-client",
47
+ "@vellumai/service-contracts": "file:../packages/service-contracts",
48
48
  "@vellumai/skill-host-contracts": "file:../packages/skill-host-contracts",
49
49
  "@vellumai/slack-text": "file:../packages/slack-text",
50
+ "@vellumai/twilio-client": "file:../packages/twilio-client",
50
51
  "archiver": "7.0.1",
51
52
  "commander": "13.1.0",
52
53
  "croner": "10.0.1",
@@ -61,6 +62,7 @@
61
62
  "playwright": "1.58.2",
62
63
  "postgres": "3.4.8",
63
64
  "rrule": "2.8.1",
65
+ "stemmer": "2.0.1",
64
66
  "tar-stream": "3.1.7",
65
67
  "tldts": "7.0.25",
66
68
  "uuid": "11.1.0",
@@ -77,7 +79,8 @@
77
79
  "@vellumai/egress-proxy",
78
80
  "@vellumai/gateway-client",
79
81
  "@vellumai/skill-host-contracts",
80
- "@vellumai/slack-text"
82
+ "@vellumai/slack-text",
83
+ "@vellumai/twilio-client"
81
84
  ],
82
85
  "overrides": {
83
86
  "lodash": "4.18.1",
@@ -93,7 +93,7 @@ describe("app-builder skill tool scripts", () => {
93
93
 
94
94
  test("delegates to executeAppCreate and returns result", async () => {
95
95
  const result = await appCreateScript.run(
96
- { name: "My App", html: "<p>Hello</p>" },
96
+ { name: "My App" },
97
97
  makeContext(),
98
98
  );
99
99
  expect(result.isError).toBe(false);
@@ -107,7 +107,7 @@ describe("app-builder skill tool scripts", () => {
107
107
  isError: false,
108
108
  });
109
109
  const result = await appCreateScript.run(
110
- { name: "Auto App", html: "<div/>" },
110
+ { name: "Auto App" },
111
111
  makeContext({ proxyToolResolver: proxy }),
112
112
  );
113
113
  expect(result.isError).toBe(false);
@@ -118,7 +118,7 @@ describe("app-builder skill tool scripts", () => {
118
118
 
119
119
  test("handles missing proxyToolResolver gracefully", async () => {
120
120
  const result = await appCreateScript.run(
121
- { name: "No Proxy", html: "<p/>" },
121
+ { name: "No Proxy" },
122
122
  makeContext(),
123
123
  );
124
124
  expect(result.isError).toBe(false);
@@ -1,4 +1,10 @@
1
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
2
8
  import { readFile } from "node:fs/promises";
3
9
  import { tmpdir } from "node:os";
4
10
  import { join } from "node:path";
@@ -22,6 +28,8 @@ const mockApps = new Map<string, Record<string, unknown>>();
22
28
  mock.module("../memory/app-store.js", () => ({
23
29
  getApp: (id: string) => mockApps.get(id) ?? null,
24
30
  getAppsDir: () => testAppsDir,
31
+ getAppDirPath: (id: string) => join(testAppsDir, id),
32
+ isMultifileApp: (app: Record<string, unknown>) => app.formatVersion === 2,
25
33
  }));
26
34
 
27
35
  // Mock content-id to avoid pulling in crypto internals
@@ -29,6 +37,52 @@ mock.module("../util/content-id.js", () => ({
29
37
  computeContentId: () => "abcd1234abcd1234",
30
38
  }));
31
39
 
40
+ let compileAppOverride:
41
+ | ((appDir: string) => Promise<{
42
+ ok: boolean;
43
+ errors: Array<{
44
+ text: string;
45
+ location?: { file: string; line: number; column: number };
46
+ }>;
47
+ warnings: Array<{ text: string }>;
48
+ durationMs: number;
49
+ }>)
50
+ | undefined;
51
+
52
+ async function compileAppFixture(appDir: string) {
53
+ if (compileAppOverride) {
54
+ return compileAppOverride(appDir);
55
+ }
56
+
57
+ const srcDir = join(appDir, "src");
58
+ const distDir = join(appDir, "dist");
59
+ rmSync(distDir, { recursive: true, force: true });
60
+ mkdirSync(distDir, { recursive: true });
61
+
62
+ let html = readFileSync(join(srcDir, "index.html"), "utf-8");
63
+ const cssPath = join(srcDir, "styles.css");
64
+ if (existsSync(cssPath)) {
65
+ writeFileSync(join(distDir, "main.css"), readFileSync(cssPath));
66
+ html = html.replace(
67
+ "</head>",
68
+ ' <link rel="stylesheet" href="main.css">\n </head>',
69
+ );
70
+ }
71
+ html = html.replace(
72
+ "</body>",
73
+ ' <script type="module" src="main.js"></script>\n </body>',
74
+ );
75
+
76
+ writeFileSync(join(distDir, "index.html"), html);
77
+ writeFileSync(join(distDir, "main.js"), "console.log('compiled');");
78
+
79
+ return { ok: true, errors: [], warnings: [], durationMs: 1 };
80
+ }
81
+
82
+ mock.module("../bundler/app-compiler.js", () => ({
83
+ compileApp: compileAppFixture,
84
+ }));
85
+
32
86
  // Mock bundle-signer (not exercised in these tests)
33
87
  mock.module("./bundle-signer.js", () => ({
34
88
  signBundle: async () => ({}),
@@ -43,6 +97,7 @@ import type { AppManifest } from "../bundler/manifest.js";
43
97
 
44
98
  describe("packageApp", () => {
45
99
  afterEach(() => {
100
+ compileAppOverride = undefined;
46
101
  mockApps.clear();
47
102
  try {
48
103
  rmSync(testAppsDir, { recursive: true, force: true });
@@ -92,6 +147,29 @@ describe("packageApp", () => {
92
147
  return appDef;
93
148
  }
94
149
 
150
+ function setupLegacyApp(appId: string) {
151
+ const appDir = join(testAppsDir, appId);
152
+ mkdirSync(appDir, { recursive: true });
153
+ writeFileSync(
154
+ join(appDir, "index.html"),
155
+ "<!DOCTYPE html><html><body><h1>Legacy</h1></body></html>",
156
+ );
157
+
158
+ const appDef = {
159
+ id: appId,
160
+ name: "Legacy App",
161
+ description: "A legacy app",
162
+ schemaJson: "{}",
163
+ htmlDefinition: "<unused>",
164
+ createdAt: Date.now(),
165
+ updatedAt: Date.now(),
166
+ };
167
+ writeFileSync(join(testAppsDir, `${appId}.json`), JSON.stringify(appDef));
168
+ mockApps.set(appId, appDef);
169
+
170
+ return appDef;
171
+ }
172
+
95
173
  test("packages an app with compiled dist/ files in the zip", async () => {
96
174
  const appId = "multi-test-1";
97
175
  setupApp(appId, { withCss: true });
@@ -140,6 +218,17 @@ describe("packageApp", () => {
140
218
  const appDir = join(testAppsDir, appId);
141
219
  const srcDir = join(appDir, "src");
142
220
  mkdirSync(srcDir, { recursive: true });
221
+ compileAppOverride = async () => ({
222
+ ok: false,
223
+ errors: [
224
+ {
225
+ text: "Expected identifier",
226
+ location: { file: "src/main.tsx", line: 1, column: 20 },
227
+ },
228
+ ],
229
+ warnings: [],
230
+ durationMs: 1,
231
+ });
143
232
 
144
233
  // Write intentionally broken source so esbuild fails
145
234
  writeFileSync(join(srcDir, "main.tsx"), "const x: number = {{{BROKEN");
@@ -165,6 +254,70 @@ describe("packageApp", () => {
165
254
  );
166
255
  });
167
256
 
257
+ test("rejects formatVersion 2 apps missing src files before bundling", async () => {
258
+ const appId = "multi-missing-src";
259
+ const appDir = join(testAppsDir, appId);
260
+ mkdirSync(appDir, { recursive: true });
261
+ writeFileSync(
262
+ join(appDir, "index.html"),
263
+ "<!DOCTYPE html><html><body><h1>Wrong root</h1></body></html>",
264
+ );
265
+
266
+ const appDef = {
267
+ id: appId,
268
+ name: "Missing Source App",
269
+ schemaJson: "{}",
270
+ htmlDefinition: "<unused>",
271
+ createdAt: Date.now(),
272
+ updatedAt: Date.now(),
273
+ formatVersion: 2,
274
+ };
275
+ writeFileSync(join(testAppsDir, `${appId}.json`), JSON.stringify(appDef));
276
+ mockApps.set(appId, appDef);
277
+
278
+ await expect(packageApp(appId)).rejects.toThrow(
279
+ /missing src\/index\.html and src\/main\.tsx/,
280
+ );
281
+ });
282
+
283
+ test("rejects app_create scaffold before sharing", async () => {
284
+ const appId = "multi-default-scaffold";
285
+ const appDir = join(testAppsDir, appId);
286
+ const srcDir = join(appDir, "src");
287
+ mkdirSync(srcDir, { recursive: true });
288
+ writeFileSync(
289
+ join(srcDir, "index.html"),
290
+ '<!DOCTYPE html><html><body><div id="app"></div></body></html>',
291
+ );
292
+ writeFileSync(
293
+ join(srcDir, "main.tsx"),
294
+ `import { render } from 'preact';
295
+
296
+ function App() {
297
+ return <div>{"Hello, Scaffold!"}</div>;
298
+ }
299
+
300
+ render(<App />, document.getElementById('app')!);
301
+ `,
302
+ );
303
+
304
+ const appDef = {
305
+ id: appId,
306
+ name: "Scaffold App",
307
+ schemaJson: "{}",
308
+ htmlDefinition: "<unused>",
309
+ createdAt: Date.now(),
310
+ updatedAt: Date.now(),
311
+ formatVersion: 2,
312
+ };
313
+ writeFileSync(join(testAppsDir, `${appId}.json`), JSON.stringify(appDef));
314
+ mockApps.set(appId, appDef);
315
+
316
+ await expect(packageApp(appId)).rejects.toThrow(
317
+ /still has the default src\/main\.tsx scaffold/,
318
+ );
319
+ });
320
+
168
321
  test("app without CSS omits main.css from zip", async () => {
169
322
  const appId = "multi-no-css";
170
323
  setupApp(appId, { withCss: false });
@@ -177,4 +330,20 @@ describe("packageApp", () => {
177
330
  expect(zip.file("main.js")).not.toBeNull();
178
331
  expect(zip.file("main.css")).toBeNull();
179
332
  });
333
+
334
+ test("keeps legacy single-file apps packageable", async () => {
335
+ const appId = "legacy-single-file";
336
+ setupLegacyApp(appId);
337
+
338
+ const result = await packageApp(appId);
339
+ const zipData = await readFile(result.bundlePath);
340
+ const zip = await JSZip.loadAsync(zipData);
341
+
342
+ const indexContent = await zip.file("index.html")!.async("string");
343
+ const manifestJson = await zip.file("manifest.json")!.async("string");
344
+ const manifest: AppManifest = JSON.parse(manifestJson);
345
+
346
+ expect(indexContent).toContain("<h1>Legacy</h1>");
347
+ expect(manifest.format_version).toBe(1);
348
+ });
180
349
  });
@@ -0,0 +1,374 @@
1
+ /**
2
+ * End-to-end app-control flow test.
3
+ *
4
+ * Drives a fake conversation through `app_control_start` →
5
+ * `app_control_observe` → `app_control_stop` using the real
6
+ * {@link HostAppControlProxy} (so the loop guard, singleton lock, and
7
+ * result-formatting paths are exercised) plus the real route handler from
8
+ * `host-app-control-routes.ts`. The mock layer captures broadcast
9
+ * envelopes and bridges them back through the route handler the way the
10
+ * desktop client does in production.
11
+ *
12
+ * Mirrors `cu-unified-flow.test.ts` for the CU pathway. App-control
13
+ * differs in two notable ways:
14
+ * 1. Result payloads carry `pngBase64` (not screenshots-as-strings) and
15
+ * surface as image content blocks with `media_type: "image/png"`.
16
+ * 2. A module-level singleton lock guards `app_control_start` — only one
17
+ * conversation may hold an active session at a time.
18
+ */
19
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Module mocks (must be installed before importing the units under test)
23
+ // ---------------------------------------------------------------------------
24
+ //
25
+ // Both the proxy (host-proxy-base) and the route handler reach into the
26
+ // `pendingInteractions` and `assistantEventHub` modules. We mock both so
27
+ // the test can:
28
+ // - capture every broadcast envelope (assertion + driving the route),
29
+ // - register pending interactions on broadcast (the way production
30
+ // code does for other host-* requests in `assistant-event-hub.ts`),
31
+ // - resolve those entries from the route handler.
32
+ //
33
+ // `conversation-store` is intentionally NOT mocked: replacing it would
34
+ // break sibling exports (deleteConversation, etc.) that the
35
+ // `conversation-surfaces.ts` import chain pulls in transitively. We use
36
+ // the real `setConversation` to register fake conversation entries.
37
+
38
+ const sentMessages: unknown[] = [];
39
+ let mockHasClient = true;
40
+
41
+ interface PendingEntry {
42
+ conversationId: string;
43
+ kind: string;
44
+ }
45
+ const pending = new Map<string, PendingEntry>();
46
+
47
+ mock.module("../runtime/assistant-event-hub.js", () => ({
48
+ broadcastMessage: (msg: unknown) => {
49
+ sentMessages.push(msg);
50
+ const m = msg as Record<string, unknown>;
51
+ if (
52
+ m.type === "host_app_control_request" &&
53
+ typeof m.requestId === "string" &&
54
+ typeof m.conversationId === "string"
55
+ ) {
56
+ pending.set(m.requestId, {
57
+ conversationId: m.conversationId,
58
+ kind: "host_app_control",
59
+ });
60
+ }
61
+ },
62
+ assistantEventHub: {
63
+ getMostRecentClientByCapability: (cap: string) =>
64
+ cap === "host_app_control" && mockHasClient
65
+ ? { id: "mock-client" }
66
+ : null,
67
+ },
68
+ }));
69
+
70
+ mock.module("../runtime/pending-interactions.js", () => ({
71
+ register: (requestId: string, entry: PendingEntry) =>
72
+ pending.set(requestId, entry),
73
+ get: (requestId: string) => pending.get(requestId),
74
+ resolve: (requestId: string) => {
75
+ const entry = pending.get(requestId);
76
+ if (entry) pending.delete(requestId);
77
+ return entry;
78
+ },
79
+ getByKind: () => [],
80
+ getByConversation: () => [],
81
+ removeByConversation: () => {},
82
+ }));
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Real imports (after mocks)
86
+ // ---------------------------------------------------------------------------
87
+
88
+ const {
89
+ HostAppControlProxy,
90
+ _getActiveAppControlConversationId,
91
+ _resetActiveAppControlConversationId,
92
+ } = await import("../daemon/host-app-control-proxy.js");
93
+ const { ROUTES } = await import("../runtime/routes/host-app-control-routes.js");
94
+ const { surfaceProxyResolver } =
95
+ await import("../daemon/conversation-surfaces.js");
96
+ const { setConversation, clearConversations } =
97
+ await import("../daemon/conversation-store.js");
98
+ type SurfaceConversationContext =
99
+ import("../daemon/conversation-surfaces.js").SurfaceConversationContext;
100
+
101
+ const handleHostAppControlResult = ROUTES.find(
102
+ (r) => r.endpoint === "host-app-control-result",
103
+ )!.handler;
104
+
105
+ // Tiny base64 PNG-ish placeholder. Content is irrelevant to the result
106
+ // path — the proxy hashes the string for its loop guard but never decodes
107
+ // it, and the resulting content block carries the bytes through verbatim.
108
+ const TINY_PNG_B64 = "iVBORw0KGgoAAAA";
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Test helpers
112
+ // ---------------------------------------------------------------------------
113
+
114
+ /**
115
+ * Build a SurfaceConversationContext with a real proxy attached. Only the
116
+ * fields the app-control branch reads are populated — see
117
+ * `cu-unified-flow.test.ts` for the analogous shape.
118
+ */
119
+ function buildContext(
120
+ proxy: InstanceType<typeof HostAppControlProxy>,
121
+ conversationId = "test-conv",
122
+ ): SurfaceConversationContext {
123
+ return {
124
+ conversationId,
125
+ traceEmitter: { emit: () => {} },
126
+ sendToClient: () => {},
127
+ pendingSurfaceActions: new Map(),
128
+ lastSurfaceAction: new Map(),
129
+ surfaceState: new Map(),
130
+ surfaceUndoStacks: new Map(),
131
+ accumulatedSurfaceState: new Map(),
132
+ surfaceActionRequestIds: new Set(),
133
+ currentTurnSurfaces: [],
134
+ hostAppControlProxy: proxy,
135
+ isProcessing: () => false,
136
+ enqueueMessage: () => ({ queued: false, requestId: "r1" }),
137
+ getQueueDepth: () => 0,
138
+ processMessage: async () => "",
139
+ withSurface: async (_id, fn) => fn(),
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Register the proxy in the real conversation store keyed by
145
+ * `conversationId` so the route handler's `findConversation()` lookup
146
+ * routes POSTed results back to it. The fake conversation only needs the
147
+ * `hostAppControlProxy` field — the route handler never reads anything
148
+ * else off it.
149
+ */
150
+ function registerConversation(
151
+ conversationId: string,
152
+ proxy: InstanceType<typeof HostAppControlProxy>,
153
+ ): void {
154
+ setConversation(conversationId, {
155
+ hostAppControlProxy: proxy,
156
+ } as never);
157
+ }
158
+
159
+ /**
160
+ * Drive the full server → client → server roundtrip: post a result payload
161
+ * through the real route handler for the most recently broadcast request.
162
+ */
163
+ async function postResult(body: Record<string, unknown>): Promise<void> {
164
+ const sent = sentMessages[sentMessages.length - 1] as Record<string, unknown>;
165
+ const rid = sent.requestId as string;
166
+ await handleHostAppControlResult({ body: { ...body, requestId: rid } });
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Tests
171
+ // ---------------------------------------------------------------------------
172
+
173
+ describe("app-control end-to-end flow", () => {
174
+ beforeEach(() => {
175
+ sentMessages.length = 0;
176
+ pending.clear();
177
+ clearConversations();
178
+ mockHasClient = true;
179
+ _resetActiveAppControlConversationId();
180
+ });
181
+
182
+ afterEach(() => {
183
+ _resetActiveAppControlConversationId();
184
+ clearConversations();
185
+ });
186
+
187
+ // -------------------------------------------------------------------------
188
+ // Test 1: app_control_start
189
+ // -------------------------------------------------------------------------
190
+
191
+ test("app_control_start: SSE broadcast → POST result → ToolExecutionResult, lock acquired", async () => {
192
+ const conversationId = "conv-start";
193
+ const proxy = new HostAppControlProxy(conversationId);
194
+ const ctx = buildContext(proxy, conversationId);
195
+ registerConversation(conversationId, proxy);
196
+
197
+ const resultPromise = surfaceProxyResolver(ctx, "app_control_start", {
198
+ tool: "start",
199
+ app: "com.example.app",
200
+ });
201
+
202
+ // SSE broadcast captured with the expected envelope shape.
203
+ expect(sentMessages).toHaveLength(1);
204
+ const sent = sentMessages[0] as Record<string, unknown>;
205
+ expect(sent.type).toBe("host_app_control_request");
206
+ expect(sent.toolName).toBe("app_control_start");
207
+ expect(sent.conversationId).toBe(conversationId);
208
+ expect(sent.input).toEqual({ tool: "start", app: "com.example.app" });
209
+ expect(typeof sent.requestId).toBe("string");
210
+
211
+ // The pending interaction is registered (so the route handler can find it).
212
+ const requestId = sent.requestId as string;
213
+ expect(pending.has(requestId)).toBe(true);
214
+
215
+ // Post a result through the real route handler. The proxy resolves on
216
+ // the next tick.
217
+ await postResult({
218
+ state: "running",
219
+ pngBase64: TINY_PNG_B64,
220
+ executionResult: "App launched",
221
+ windowBounds: { x: 0, y: 0, width: 1280, height: 800 },
222
+ });
223
+
224
+ const result = await resultPromise;
225
+
226
+ expect(result.isError).toBe(false);
227
+ expect(result.content).toContain("State: running");
228
+ expect(result.content).toContain("App launched");
229
+ expect(result.content).toContain("1280x800 at (0, 0)");
230
+ expect(result.contentBlocks).toBeDefined();
231
+ expect(result.contentBlocks).toHaveLength(1);
232
+ expect(result.contentBlocks![0]).toEqual({
233
+ type: "image",
234
+ source: {
235
+ type: "base64",
236
+ media_type: "image/png",
237
+ data: TINY_PNG_B64,
238
+ },
239
+ });
240
+
241
+ // Singleton lock is held by this conversation now.
242
+ expect(_getActiveAppControlConversationId()).toBe(conversationId);
243
+
244
+ proxy.dispose();
245
+ });
246
+
247
+ // -------------------------------------------------------------------------
248
+ // Test 2: app_control_observe
249
+ // -------------------------------------------------------------------------
250
+
251
+ test("app_control_observe: result payload includes image content block", async () => {
252
+ const conversationId = "conv-observe";
253
+ const proxy = new HostAppControlProxy(conversationId);
254
+ const ctx = buildContext(proxy, conversationId);
255
+ registerConversation(conversationId, proxy);
256
+
257
+ const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
258
+ tool: "observe",
259
+ app: "com.example.app",
260
+ });
261
+
262
+ expect(sentMessages).toHaveLength(1);
263
+ const sent = sentMessages[0] as Record<string, unknown>;
264
+ expect(sent.toolName).toBe("app_control_observe");
265
+
266
+ await postResult({
267
+ state: "running",
268
+ pngBase64: TINY_PNG_B64,
269
+ executionResult: "Window observed",
270
+ });
271
+
272
+ const result = await resultPromise;
273
+
274
+ expect(result.isError).toBe(false);
275
+ expect(result.content).toContain("State: running");
276
+ expect(result.content).toContain("Window observed");
277
+ expect(result.contentBlocks).toBeDefined();
278
+ expect(result.contentBlocks).toHaveLength(1);
279
+ expect(result.contentBlocks![0]).toEqual({
280
+ type: "image",
281
+ source: {
282
+ type: "base64",
283
+ media_type: "image/png",
284
+ data: TINY_PNG_B64,
285
+ },
286
+ });
287
+
288
+ proxy.dispose();
289
+ });
290
+
291
+ // -------------------------------------------------------------------------
292
+ // Test 3: app_control_stop short-circuits locally
293
+ // -------------------------------------------------------------------------
294
+
295
+ test("app_control_stop: no SSE broadcast, dispose called, lock released", async () => {
296
+ const conversationId = "conv-stop";
297
+ const proxy = new HostAppControlProxy(conversationId);
298
+ const ctx = buildContext(proxy, conversationId);
299
+ registerConversation(conversationId, proxy);
300
+
301
+ // Acquire the lock first via a real start round-trip — otherwise stop
302
+ // is a no-op against an unset lock.
303
+ const startPromise = surfaceProxyResolver(ctx, "app_control_start", {
304
+ tool: "start",
305
+ app: "com.example.app",
306
+ });
307
+ await postResult({ state: "running", pngBase64: TINY_PNG_B64 });
308
+ await startPromise;
309
+ expect(_getActiveAppControlConversationId()).toBe(conversationId);
310
+
311
+ // Wrap dispose to verify it was called by the resolver.
312
+ let disposeCalls = 0;
313
+ const realDispose = proxy.dispose.bind(proxy);
314
+ proxy.dispose = () => {
315
+ disposeCalls++;
316
+ realDispose();
317
+ };
318
+
319
+ sentMessages.length = 0;
320
+
321
+ const result = await surfaceProxyResolver(ctx, "app_control_stop", {
322
+ tool: "stop",
323
+ });
324
+
325
+ expect(result.isError).toBe(false);
326
+ expect(result.content.toLowerCase()).toContain("stopped");
327
+ // No broadcast on the local short-circuit.
328
+ expect(sentMessages).toHaveLength(0);
329
+ expect(disposeCalls).toBe(1);
330
+ // Lock released.
331
+ expect(_getActiveAppControlConversationId()).toBeUndefined();
332
+ });
333
+
334
+ // -------------------------------------------------------------------------
335
+ // Test 4: singleton lock blocks a second conversation
336
+ // -------------------------------------------------------------------------
337
+
338
+ test("singleton lock: second conversation's start is rejected naming the holder", async () => {
339
+ const proxyA = new HostAppControlProxy("conv-a");
340
+ const ctxA = buildContext(proxyA, "conv-a");
341
+ registerConversation("conv-a", proxyA);
342
+
343
+ const startA = surfaceProxyResolver(ctxA, "app_control_start", {
344
+ tool: "start",
345
+ app: "com.example.app",
346
+ });
347
+ await postResult({ state: "running", pngBase64: TINY_PNG_B64 });
348
+ const resultA = await startA;
349
+ expect(resultA.isError).toBe(false);
350
+ expect(_getActiveAppControlConversationId()).toBe("conv-a");
351
+
352
+ // Second conversation tries to start while conv-a holds the lock.
353
+ sentMessages.length = 0;
354
+ const proxyB = new HostAppControlProxy("conv-b");
355
+ const ctxB = buildContext(proxyB, "conv-b");
356
+ registerConversation("conv-b", proxyB);
357
+
358
+ const resultB = await surfaceProxyResolver(ctxB, "app_control_start", {
359
+ tool: "start",
360
+ app: "com.example.app",
361
+ });
362
+
363
+ expect(resultB.isError).toBe(true);
364
+ expect(resultB.content).toContain("conv-a");
365
+ expect(resultB.content.toLowerCase()).toContain(
366
+ "currently holds the app-control session",
367
+ );
368
+ // No envelope was dispatched for the rejected start.
369
+ expect(sentMessages).toHaveLength(0);
370
+
371
+ proxyA.dispose();
372
+ proxyB.dispose();
373
+ });
374
+ });