@vellumai/assistant 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Tests for HostAppControlProxy instantiation in `prepareConversationForMessage`
3
+ * (and the parallel block in `conversation-routes.ts`).
4
+ *
5
+ * Verifies that:
6
+ * - A macOS client connection unconditionally attaches a HostAppControlProxy
7
+ * and preactivates the `app-control` skill — regardless of whether the
8
+ * `app-control` feature flag is on or off. The flag is read only by the
9
+ * skill-projection layer, never gates the proxy.
10
+ * - A non-macOS client connection (where
11
+ * `supportsHostProxy(_, "host_app_control")` returns false) leaves the
12
+ * proxy unattached.
13
+ * - The skill-projection layer filters the `app-control` skill out of the
14
+ * projected tool list when the feature flag is off, even when it is in
15
+ * the preactivated set — proving that the gating point is the projection
16
+ * layer rather than the proxy attachment site.
17
+ *
18
+ * The first set of tests mirrors the production gating logic from
19
+ * `prepareConversationForMessage` (in `assistant/src/daemon/process-message.ts`)
20
+ * and the parallel block in `assistant/src/runtime/routes/conversation-routes.ts`
21
+ * — both unconditionally instantiate `HostAppControlProxy` and preactivate
22
+ * `"app-control"` when `supportsHostProxy(interfaceId, "host_app_control")`
23
+ * returns true. Calling the real prepare/route helpers directly would require
24
+ * mocking the full processMessage/handleSendMessage stack (slash router, agent
25
+ * loop, persistence, secret scanner, …), so we test the logic against the
26
+ * real `supportsHostProxy` predicate plus a fake Conversation.
27
+ *
28
+ * The second set of tests calls the real `projectSkillTools` to confirm
29
+ * that the feature flag — not the proxy attachment — controls whether
30
+ * `app-control` tools end up in the LLM tool list.
31
+ */
32
+
33
+ import * as realFs from "node:fs";
34
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
35
+
36
+ import type { InterfaceId } from "../channels/types.js";
37
+ import { supportsHostProxy } from "../channels/types.js";
38
+ import type { SkillSummary, SkillToolManifest } from "../config/skills.js";
39
+ import { RiskLevel } from "../permissions/types.js";
40
+ import type { Tool } from "../tools/types.js";
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Module mocks for the skill-projection layer
44
+ // ---------------------------------------------------------------------------
45
+
46
+ mock.module("../util/logger.js", () => ({
47
+ getLogger: () =>
48
+ new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
49
+ }));
50
+
51
+ let appControlFlagEnabled = false;
52
+ mock.module("../config/assistant-feature-flags.js", () => ({
53
+ isAssistantFeatureFlagEnabled: (key: string) => {
54
+ if (key === "app-control") return appControlFlagEnabled;
55
+ return true;
56
+ },
57
+ loadDefaultsRegistry: () => ({}),
58
+ }));
59
+
60
+ mock.module("../config/skill-state.js", () => ({
61
+ skillFlagKey: (skill: { featureFlag?: string }) =>
62
+ skill.featureFlag ?? undefined,
63
+ }));
64
+
65
+ mock.module("../config/loader.js", () => ({
66
+ getConfig: () => ({
67
+ skills: { entries: {}, allowBundled: null },
68
+ }),
69
+ loadConfig: () => ({
70
+ skills: { entries: {}, allowBundled: null },
71
+ }),
72
+ invalidateConfigCache: () => {},
73
+ }));
74
+
75
+ let mockCatalog: SkillSummary[] = [];
76
+ let mockManifests: Record<string, SkillToolManifest | null> = {};
77
+ const mockRegisteredTools = new Map<string, Tool[]>();
78
+ const mockSkillRefCount = new Map<string, number>();
79
+
80
+ mock.module("../config/skills.js", () => ({
81
+ loadSkillCatalog: () => mockCatalog,
82
+ }));
83
+
84
+ mock.module("../skills/active-skill-tools.js", () => ({
85
+ deriveActiveSkills: () => [],
86
+ }));
87
+
88
+ mock.module("../skills/tool-manifest.js", () => ({
89
+ parseToolManifestFile: (filePath: string) => {
90
+ const parts = filePath.split("/");
91
+ const skillId = parts[parts.length - 2];
92
+ const manifest = mockManifests[skillId];
93
+ if (!manifest) {
94
+ throw new Error(`Mock: no manifest for skill "${skillId}"`);
95
+ }
96
+ return manifest;
97
+ },
98
+ }));
99
+
100
+ mock.module("../tools/skills/skill-tool-factory.js", () => ({
101
+ createSkillToolsFromManifest: (
102
+ entries: SkillToolManifest["tools"],
103
+ skillId: string,
104
+ _skillDir: string,
105
+ versionHash: string,
106
+ bundled?: boolean,
107
+ ): Tool[] =>
108
+ entries.map((entry) => ({
109
+ name: entry.name,
110
+ description: entry.description,
111
+ category: entry.category,
112
+ defaultRiskLevel: RiskLevel.Medium,
113
+ origin: "skill" as const,
114
+ ownerSkillId: skillId,
115
+ ownerSkillVersionHash: versionHash,
116
+ ownerSkillBundled: bundled ?? undefined,
117
+ getDefinition: () => ({
118
+ name: entry.name,
119
+ description: entry.description,
120
+ input_schema: entry.input_schema as object,
121
+ }),
122
+ execute: async () => ({ content: "", isError: false }),
123
+ })),
124
+ }));
125
+
126
+ mock.module("../tools/registry.js", () => ({
127
+ registerSkillTools: (tools: Tool[]) => {
128
+ const skillIds = new Set<string>();
129
+ for (const tool of tools) {
130
+ const skillId = tool.ownerSkillId!;
131
+ skillIds.add(skillId);
132
+ const existing = mockRegisteredTools.get(skillId) ?? [];
133
+ existing.push(tool);
134
+ mockRegisteredTools.set(skillId, existing);
135
+ }
136
+ for (const id of skillIds) {
137
+ mockSkillRefCount.set(id, (mockSkillRefCount.get(id) ?? 0) + 1);
138
+ }
139
+ return tools;
140
+ },
141
+ unregisterSkillTools: (skillId: string) => {
142
+ const current = mockSkillRefCount.get(skillId) ?? 0;
143
+ if (current > 1) {
144
+ mockSkillRefCount.set(skillId, current - 1);
145
+ return;
146
+ }
147
+ mockSkillRefCount.delete(skillId);
148
+ mockRegisteredTools.delete(skillId);
149
+ },
150
+ getTool: (name: string) => {
151
+ let found: Tool | undefined;
152
+ for (const tools of mockRegisteredTools.values()) {
153
+ for (const tool of tools) {
154
+ if (tool.name === name) found = tool;
155
+ }
156
+ }
157
+ return found;
158
+ },
159
+ getSkillToolNames: () => {
160
+ const names: string[] = [];
161
+ for (const tools of mockRegisteredTools.values()) {
162
+ for (const tool of tools) {
163
+ names.push(tool.name);
164
+ }
165
+ }
166
+ return names;
167
+ },
168
+ }));
169
+
170
+ mock.module("node:fs", () => ({
171
+ ...realFs,
172
+ existsSync: (p: string) => {
173
+ if (typeof p === "string" && p.endsWith("TOOLS.json")) {
174
+ const parts = p.split("/");
175
+ const skillId = parts[parts.length - 2];
176
+ return skillId in mockManifests;
177
+ }
178
+ return realFs.existsSync(p);
179
+ },
180
+ }));
181
+
182
+ mock.module("../skills/version-hash.js", () => ({
183
+ computeSkillVersionHash: (skillDir: string) => {
184
+ const parts = skillDir.split("/");
185
+ return `v1:default-hash-${parts[parts.length - 1]}`;
186
+ },
187
+ }));
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Imports under test (after mocks)
191
+ // ---------------------------------------------------------------------------
192
+
193
+ const { HostAppControlProxy } =
194
+ await import("../daemon/host-app-control-proxy.js");
195
+ const { preactivateHostProxySkills } =
196
+ await import("../daemon/host-proxy-preactivation.js");
197
+ const { projectSkillTools, resetSkillToolProjection } =
198
+ await import("../daemon/conversation-skill-tools.js");
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Conversation surface — captures proxy attachment + preactivations
202
+ // ---------------------------------------------------------------------------
203
+
204
+ interface FakeConversation {
205
+ conversationId: string;
206
+ hostAppControlProxy?: unknown;
207
+ preactivatedSkillIds: string[];
208
+ isProcessing(): boolean;
209
+ setHostAppControlProxy(proxy: unknown): void;
210
+ addPreactivatedSkillId(id: string): void;
211
+ }
212
+
213
+ function makeFakeConversation(): FakeConversation {
214
+ const conv: FakeConversation = {
215
+ conversationId: "conv-app-control-instantiation",
216
+ hostAppControlProxy: undefined,
217
+ preactivatedSkillIds: [],
218
+ isProcessing: () => false,
219
+ setHostAppControlProxy(proxy: unknown) {
220
+ this.hostAppControlProxy = proxy;
221
+ },
222
+ addPreactivatedSkillId(id: string) {
223
+ if (!this.preactivatedSkillIds.includes(id)) {
224
+ this.preactivatedSkillIds.push(id);
225
+ }
226
+ },
227
+ };
228
+ return conv;
229
+ }
230
+
231
+ /**
232
+ * Replica of the gating block from `prepareConversationForMessage`
233
+ * (process-message.ts) and `conversation-routes.ts`. The proxy-attachment
234
+ * step still lives inline at each call site (the proxy constructors take
235
+ * different argument shapes), but the preactivation step routes through the
236
+ * shared `preactivateHostProxySkills` helper exactly as the production code
237
+ * does.
238
+ */
239
+ function applyAppControlInstantiation(
240
+ conv: FakeConversation,
241
+ interfaceId: InterfaceId,
242
+ ): void {
243
+ if (supportsHostProxy(interfaceId, "host_app_control")) {
244
+ if (!conv.isProcessing() || !conv.hostAppControlProxy) {
245
+ conv.setHostAppControlProxy(new HostAppControlProxy(conv.conversationId));
246
+ }
247
+ } else if (!conv.isProcessing()) {
248
+ conv.setHostAppControlProxy(undefined);
249
+ }
250
+ if (!conv.isProcessing()) {
251
+ preactivateHostProxySkills(conv, interfaceId);
252
+ }
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Skill fixtures
257
+ // ---------------------------------------------------------------------------
258
+
259
+ function makeAppControlSkill(): SkillSummary {
260
+ return {
261
+ id: "app-control",
262
+ name: "app-control",
263
+ displayName: "App Control",
264
+ description: "Drive a specific named app via raw input",
265
+ directoryPath: "/skills/app-control",
266
+ skillFilePath: "/skills/app-control/SKILL.md",
267
+ bundled: true,
268
+ source: "bundled",
269
+ featureFlag: "app-control",
270
+ };
271
+ }
272
+
273
+ function makeAppControlManifest(): SkillToolManifest {
274
+ return {
275
+ version: 1,
276
+ tools: [
277
+ {
278
+ name: "app_control_start",
279
+ description: "Start a session against a named app",
280
+ category: "app-control",
281
+ risk: "medium" as const,
282
+ input_schema: { type: "object", properties: {} },
283
+ executor: "run.ts",
284
+ execution_target: "host" as const,
285
+ },
286
+ {
287
+ name: "app_control_observe",
288
+ description: "Observe the focused app's window",
289
+ category: "app-control",
290
+ risk: "low" as const,
291
+ input_schema: { type: "object", properties: {} },
292
+ executor: "run.ts",
293
+ execution_target: "host" as const,
294
+ },
295
+ ],
296
+ };
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Tests — proxy instantiation
301
+ // ---------------------------------------------------------------------------
302
+
303
+ describe("HostAppControlProxy instantiation gate", () => {
304
+ beforeEach(() => {
305
+ appControlFlagEnabled = false;
306
+ });
307
+
308
+ test("macOS client attaches HostAppControlProxy and preactivates app-control (flag off)", () => {
309
+ appControlFlagEnabled = false;
310
+ const conv = makeFakeConversation();
311
+
312
+ applyAppControlInstantiation(conv, "macos");
313
+
314
+ // Proxy is attached unconditionally — no flag check at instantiation.
315
+ expect(conv.hostAppControlProxy).toBeInstanceOf(HostAppControlProxy);
316
+ expect(conv.preactivatedSkillIds).toContain("app-control");
317
+ });
318
+
319
+ test("macOS client attaches HostAppControlProxy and preactivates app-control (flag on)", () => {
320
+ appControlFlagEnabled = true;
321
+ const conv = makeFakeConversation();
322
+
323
+ applyAppControlInstantiation(conv, "macos");
324
+
325
+ expect(conv.hostAppControlProxy).toBeInstanceOf(HostAppControlProxy);
326
+ expect(conv.preactivatedSkillIds).toContain("app-control");
327
+ });
328
+
329
+ test("non-macOS client (slack) does not attach HostAppControlProxy nor preactivate app-control", () => {
330
+ appControlFlagEnabled = true;
331
+ const conv = makeFakeConversation();
332
+
333
+ applyAppControlInstantiation(conv, "slack");
334
+
335
+ expect(conv.hostAppControlProxy).toBeUndefined();
336
+ expect(conv.preactivatedSkillIds).not.toContain("app-control");
337
+ });
338
+
339
+ test("chrome-extension client does not attach HostAppControlProxy (host_app_control unsupported)", () => {
340
+ appControlFlagEnabled = true;
341
+ const conv = makeFakeConversation();
342
+ // Sanity check: chrome-extension supports host_browser, NOT host_app_control.
343
+ expect(supportsHostProxy("chrome-extension", "host_app_control")).toBe(
344
+ false,
345
+ );
346
+
347
+ applyAppControlInstantiation(conv, "chrome-extension");
348
+
349
+ expect(conv.hostAppControlProxy).toBeUndefined();
350
+ expect(conv.preactivatedSkillIds).not.toContain("app-control");
351
+ });
352
+ });
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // Tests — skill-projection feature-flag gating
356
+ // ---------------------------------------------------------------------------
357
+
358
+ describe("Skill projection — app-control feature-flag gating", () => {
359
+ beforeEach(() => {
360
+ mockCatalog = [makeAppControlSkill()];
361
+ mockManifests = { "app-control": makeAppControlManifest() };
362
+ mockRegisteredTools.clear();
363
+ mockSkillRefCount.clear();
364
+ resetSkillToolProjection();
365
+ });
366
+
367
+ test("flag off: app-control is filtered out of projected tools even when preactivated", () => {
368
+ appControlFlagEnabled = false;
369
+
370
+ const sessionState = new Map<string, string>();
371
+ const result = projectSkillTools([], {
372
+ preactivatedSkillIds: ["app-control"],
373
+ previouslyActiveSkillIds: sessionState,
374
+ });
375
+
376
+ expect(result.allowedToolNames.has("app_control_start")).toBe(false);
377
+ expect(result.allowedToolNames.has("app_control_observe")).toBe(false);
378
+ });
379
+
380
+ test("flag on: app-control tools are projected when preactivated", () => {
381
+ appControlFlagEnabled = true;
382
+
383
+ const sessionState = new Map<string, string>();
384
+ const result = projectSkillTools([], {
385
+ preactivatedSkillIds: ["app-control"],
386
+ previouslyActiveSkillIds: sessionState,
387
+ });
388
+
389
+ expect(result.allowedToolNames.has("app_control_start")).toBe(true);
390
+ expect(result.allowedToolNames.has("app_control_observe")).toBe(true);
391
+ });
392
+ });
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Lifecycle tests for HostAppControlProxy attachment to a Conversation.
3
+ *
4
+ * Verifies that:
5
+ * - `setHostAppControlProxy` stores the proxy and disposes any prior proxy
6
+ * when replaced with a different instance.
7
+ * - `Conversation.dispose()` calls `dispose()` on the attached proxy and
8
+ * nulls the field so a subsequent `setHostAppControlProxy(newProxy)`
9
+ * cleanly attaches without double-disposing.
10
+ *
11
+ * Mirrors the dependency mocking pattern used by
12
+ * `conversation-lifecycle.test.ts` so we can construct a real Conversation
13
+ * without bringing up the full daemon stack.
14
+ */
15
+
16
+ import { describe, expect, mock, test } from "bun:test";
17
+
18
+ mock.module("../util/logger.js", () => ({
19
+ getLogger: () =>
20
+ new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
21
+ }));
22
+
23
+ mock.module("../providers/registry.js", () => ({
24
+ getProvider: () => ({ name: "mock-provider" }),
25
+ initializeProviders: () => {},
26
+ }));
27
+
28
+ mock.module("../config/loader.js", () => ({
29
+ getConfig: () => ({
30
+ ui: {},
31
+ llm: {
32
+ default: {
33
+ provider: "mock-provider",
34
+ model: "mock-model",
35
+ maxTokens: 4096,
36
+ effort: "max" as const,
37
+ speed: "standard" as const,
38
+ temperature: null,
39
+ thinking: { enabled: false, streamThinking: true },
40
+ contextWindow: {
41
+ enabled: true,
42
+ maxInputTokens: 100000,
43
+ targetBudgetRatio: 0.3,
44
+ compactThreshold: 0.8,
45
+ summaryBudgetRatio: 0.05,
46
+ overflowRecovery: {
47
+ enabled: true,
48
+ safetyMarginRatio: 0.05,
49
+ maxAttempts: 3,
50
+ interactiveLatestTurnCompression: "summarize",
51
+ nonInteractiveLatestTurnCompression: "truncate",
52
+ },
53
+ },
54
+ },
55
+ profiles: {},
56
+ callSites: {},
57
+ pricingOverrides: [],
58
+ },
59
+ rateLimit: { maxRequestsPerMinute: 0 },
60
+ }),
61
+ loadRawConfig: () => ({}),
62
+ saveRawConfig: () => {},
63
+ invalidateConfigCache: () => {},
64
+ }));
65
+
66
+ mock.module("../prompts/system-prompt.js", () => ({
67
+ buildSystemPrompt: () => "system prompt",
68
+ }));
69
+
70
+ mock.module("../permissions/trust-store.js", () => ({
71
+ clearCache: () => {},
72
+ }));
73
+
74
+ mock.module("../security/secret-allowlist.js", () => ({
75
+ resetAllowlist: () => {},
76
+ }));
77
+
78
+ mock.module("../memory/conversation-crud.js", () => ({
79
+ updateConversationContextWindow: () => {},
80
+ deleteMessageById: () => {},
81
+ updateConversationTitle: () => {},
82
+ updateConversationUsage: () => {},
83
+ provenanceFromTrustContext: () => ({
84
+ source: "user",
85
+ trustContext: undefined,
86
+ }),
87
+ getConversationOriginInterface: () => null,
88
+ getConversationOriginChannel: () => null,
89
+ getMessages: () => [],
90
+ getConversation: () => null,
91
+ createConversation: () => ({ id: "conv-app-control" }),
92
+ addMessage: async () => ({ id: "persisted-1" }),
93
+ setConversationOriginChannelIfUnset: () => {},
94
+ setConversationOriginInterfaceIfUnset: () => {},
95
+ }));
96
+
97
+ mock.module("../memory/conversation-queries.js", () => ({
98
+ listConversations: () => [],
99
+ }));
100
+
101
+ // Stub graph_extract / auto-analysis enqueue paths so dispose's best-effort
102
+ // background work doesn't reach into real subsystems during the test.
103
+ mock.module("../memory/jobs-store.js", () => ({
104
+ enqueueMemoryJob: () => {},
105
+ }));
106
+
107
+ mock.module("../memory/auto-analysis-enqueue.js", () => ({
108
+ enqueueAutoAnalysisIfEnabled: () => {},
109
+ }));
110
+
111
+ mock.module("../memory/auto-analysis-guard.js", () => ({
112
+ isAutoAnalysisConversation: () => false,
113
+ }));
114
+
115
+ import { Conversation } from "../daemon/conversation.js";
116
+ import type { HostAppControlProxy } from "../daemon/host-app-control-proxy.js";
117
+
118
+ /**
119
+ * Minimal stand-in for HostAppControlProxy that records dispose() calls.
120
+ * The Conversation only invokes `dispose()` on the proxy in the lifecycle
121
+ * paths under test, so we don't need the rest of the API.
122
+ */
123
+ function makeFakeProxy(): {
124
+ proxy: HostAppControlProxy;
125
+ disposeCount: () => number;
126
+ } {
127
+ let disposed = 0;
128
+ const fake = {
129
+ dispose() {
130
+ disposed++;
131
+ },
132
+ } as unknown as HostAppControlProxy;
133
+ return { proxy: fake, disposeCount: () => disposed };
134
+ }
135
+
136
+ function makeConversation(): Conversation {
137
+ const provider = {
138
+ name: "mock",
139
+ sendMessage: async () => ({
140
+ content: [],
141
+ model: "mock",
142
+ usage: { inputTokens: 0, outputTokens: 0 },
143
+ stopReason: "end_turn",
144
+ }),
145
+ };
146
+ const conv = new Conversation(
147
+ "conv-app-control",
148
+ provider,
149
+ "system prompt",
150
+ 4096,
151
+ () => {},
152
+ "/tmp",
153
+ );
154
+ conv.setTrustContext({ trustClass: "guardian", sourceChannel: "vellum" });
155
+ return conv;
156
+ }
157
+
158
+ describe("Conversation — HostAppControlProxy lifecycle", () => {
159
+ test("setHostAppControlProxy stores the proxy", () => {
160
+ const conversation = makeConversation();
161
+ const { proxy, disposeCount } = makeFakeProxy();
162
+
163
+ conversation.setHostAppControlProxy(proxy);
164
+
165
+ expect(conversation.hostAppControlProxy).toBe(proxy);
166
+ expect(disposeCount()).toBe(0);
167
+ });
168
+
169
+ test("setHostAppControlProxy disposes prior proxy when replaced", () => {
170
+ const conversation = makeConversation();
171
+ const first = makeFakeProxy();
172
+ const second = makeFakeProxy();
173
+
174
+ conversation.setHostAppControlProxy(first.proxy);
175
+ conversation.setHostAppControlProxy(second.proxy);
176
+
177
+ expect(first.disposeCount()).toBe(1);
178
+ expect(second.disposeCount()).toBe(0);
179
+ expect(conversation.hostAppControlProxy).toBe(second.proxy);
180
+ });
181
+
182
+ test("setHostAppControlProxy with the same instance does not redispose", () => {
183
+ const conversation = makeConversation();
184
+ const { proxy, disposeCount } = makeFakeProxy();
185
+
186
+ conversation.setHostAppControlProxy(proxy);
187
+ conversation.setHostAppControlProxy(proxy);
188
+
189
+ expect(disposeCount()).toBe(0);
190
+ expect(conversation.hostAppControlProxy).toBe(proxy);
191
+ });
192
+
193
+ test("setHostAppControlProxy(undefined) disposes the existing proxy", () => {
194
+ const conversation = makeConversation();
195
+ const { proxy, disposeCount } = makeFakeProxy();
196
+
197
+ conversation.setHostAppControlProxy(proxy);
198
+ conversation.setHostAppControlProxy(undefined);
199
+
200
+ expect(disposeCount()).toBe(1);
201
+ expect(conversation.hostAppControlProxy).toBeUndefined();
202
+ });
203
+
204
+ test("Conversation.dispose() disposes the attached proxy and nulls the field", () => {
205
+ const conversation = makeConversation();
206
+ const { proxy, disposeCount } = makeFakeProxy();
207
+
208
+ conversation.setHostAppControlProxy(proxy);
209
+ conversation.dispose();
210
+
211
+ expect(disposeCount()).toBe(1);
212
+ expect(conversation.hostAppControlProxy).toBeUndefined();
213
+ });
214
+
215
+ test("Conversation.dispose() is a no-op when no proxy is attached", () => {
216
+ const conversation = makeConversation();
217
+
218
+ expect(() => conversation.dispose()).not.toThrow();
219
+ expect(conversation.hostAppControlProxy).toBeUndefined();
220
+ });
221
+
222
+ test("setHostAppControlProxy after dispose cleanly attaches without double-disposing the prior proxy", () => {
223
+ const conversation = makeConversation();
224
+ const first = makeFakeProxy();
225
+ const second = makeFakeProxy();
226
+
227
+ conversation.setHostAppControlProxy(first.proxy);
228
+ conversation.dispose();
229
+ // After dispose the field is nulled, so attaching a new proxy must NOT
230
+ // call dispose() on the (already-disposed) prior proxy a second time.
231
+ conversation.setHostAppControlProxy(second.proxy);
232
+
233
+ expect(first.disposeCount()).toBe(1);
234
+ expect(second.disposeCount()).toBe(0);
235
+ expect(conversation.hostAppControlProxy).toBe(second.proxy);
236
+ });
237
+ });
@@ -140,14 +140,12 @@ mock.module("../config/loader.js", () => ({
140
140
  getConfig: () => mockConfig,
141
141
  getConfigReadOnly: () => mockConfig,
142
142
  loadConfig: () => mockConfig,
143
- saveConfig: () => {},
144
143
  invalidateConfigCache: () => {},
145
144
  loadRawConfig: () => ({}),
146
145
  saveRawConfig: () => {},
147
146
  getNestedValue: () => undefined,
148
147
  setNestedValue: () => {},
149
148
  applyNestedDefaults: (c: unknown) => c,
150
- deepMergeMissing: () => {},
151
149
  deepMergeOverwrite: () => {},
152
150
  mergeDefaultWorkspaceConfig: () => {},
153
151
  }));
@@ -308,6 +308,42 @@ describe("loadFromDb metadata injection rehydration", () => {
308
308
  expect(messages[2].content).toEqual([{ type: "text", text: "Second" }]);
309
309
  });
310
310
 
311
+ test("historical wrapped memoryInjectedBlock rehydrates singly-wrapped", async () => {
312
+ // Historical v2 rows persisted `injectedBlockText` already wrapped in
313
+ // `<memory>...</memory>`. After unifying v2's storage with v1's
314
+ // unwrapped contract, the rehydrate path must defensively strip any
315
+ // pre-existing wrapper so old rows don't render double-wrapped.
316
+ mockConversation = defaultConv();
317
+ mockDbMessages = [
318
+ {
319
+ id: "m1",
320
+ role: "user",
321
+ content: JSON.stringify([{ type: "text", text: "Hi" }]),
322
+ metadata: JSON.stringify({
323
+ memoryInjectedBlock: "<memory>\nremember: alice\n</memory>",
324
+ }),
325
+ },
326
+ {
327
+ id: "m2",
328
+ role: "assistant",
329
+ content: JSON.stringify([{ type: "text", text: "Hello" }]),
330
+ },
331
+ ];
332
+
333
+ const conversation = makeConversation();
334
+ await conversation.loadFromDb();
335
+ const messages = conversation.getMessages();
336
+
337
+ expect(messages).toHaveLength(2);
338
+ const firstBlock = messages[0].content[0];
339
+ expect(firstBlock).toEqual({
340
+ type: "text",
341
+ text: "<memory>\nremember: alice\n</memory>",
342
+ });
343
+ if (firstBlock.type !== "text") throw new Error("unexpected block type");
344
+ expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
345
+ });
346
+
311
347
  test("malformed metadata is tolerated: load does not throw, content unchanged", async () => {
312
348
  mockConversation = defaultConv();
313
349
  mockDbMessages = [