@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
@@ -25,7 +25,9 @@ import { extract as tarExtract } from "tar-stream";
25
25
  export interface StreamedTarHeader {
26
26
  name: string;
27
27
  size: number;
28
- type: "file" | "directory" | "pax-header" | "other";
28
+ type: "file" | "directory" | "pax-header" | "symlink" | "other";
29
+ /** Populated only when `type === "symlink"`; the symlink's target string from the tar header. */
30
+ linkname?: string;
29
31
  }
30
32
 
31
33
  export interface StreamedTarEntry {
@@ -57,6 +59,8 @@ function normalizeHeaderType(
57
59
  return "directory";
58
60
  case "pax-header":
59
61
  return "pax-header";
62
+ case "symlink":
63
+ return "symlink";
60
64
  default:
61
65
  return "other";
62
66
  }
@@ -125,12 +129,17 @@ export async function* parseVBundleStream(
125
129
  // Avoid unhandled "error" on body streams destroyed mid-flight; the
126
130
  // extractor itself propagates the real error to its "error" listener.
127
131
  body.on("error", () => {});
132
+ const normalizedType = normalizeHeaderType(header.type);
133
+ const surfaced: StreamedTarHeader = {
134
+ name: header.name,
135
+ size: header.size,
136
+ type: normalizedType,
137
+ };
138
+ if (normalizedType === "symlink" && header.linkname) {
139
+ surfaced.linkname = header.linkname;
140
+ }
128
141
  pushEntry({
129
- header: {
130
- name: header.name,
131
- size: header.size,
132
- type: normalizeHeaderType(header.type),
133
- },
142
+ header: surfaced,
134
143
  body,
135
144
  next,
136
145
  });
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { createHash, randomUUID } from "node:crypto";
17
+ import { posix } from "node:path";
17
18
  import { gunzipSync } from "node:zlib";
18
19
 
19
20
  import { z } from "zod";
@@ -26,6 +27,7 @@ const ManifestFileEntry = z.object({
26
27
  path: z.string().min(1),
27
28
  sha256: z.string().regex(/^[0-9a-f]{64}$/),
28
29
  size_bytes: z.number().int().nonnegative(),
30
+ link_target: z.string().min(1).optional(),
29
31
  });
30
32
 
31
33
  const AssistantInfo = z.object({
@@ -201,6 +203,9 @@ export interface VBundleTarEntry {
201
203
  name: string;
202
204
  data: Uint8Array;
203
205
  size: number;
206
+ /** Set when the tar entry is typeflag-2 (symlink); carries the link target
207
+ * decoded from the ustar linkname field. */
208
+ linkname?: string;
204
209
  }
205
210
 
206
211
  export interface VBundleValidationResult {
@@ -220,6 +225,9 @@ interface TarEntry {
220
225
  name: string;
221
226
  data: Uint8Array;
222
227
  size: number;
228
+ /** Set when the tar entry is typeflag-2 (symlink); carries the link target
229
+ * decoded from the ustar linkname field. */
230
+ linkname?: string;
223
231
  }
224
232
 
225
233
  /**
@@ -282,6 +290,25 @@ function parseTar(buffer: Uint8Array): TarEntry[] {
282
290
  continue;
283
291
  }
284
292
 
293
+ // Symlink (type '2') — empty body regardless of declared size; the link
294
+ // target lives in the ustar linkname field (157..256). We preserve the
295
+ // tar-declared `size` here (rather than forcing it to 0) so the
296
+ // downstream `archiveEntry.size !== 0` check can surface
297
+ // `FILE_SIZE_MISMATCH` on malformed symlink headers. The body itself is
298
+ // always an empty buffer — symlinks have no data body even if the
299
+ // header lies about it.
300
+ if (typeFlag === "2") {
301
+ const linkname = decodeNullTerminated(header, 157, 100);
302
+ entries.push({
303
+ name: normalizePath(name),
304
+ data: new Uint8Array(0),
305
+ size,
306
+ linkname,
307
+ });
308
+ offset = dataStart + dataBlocks * BLOCK_SIZE;
309
+ continue;
310
+ }
311
+
285
312
  // Regular file or hard link
286
313
  if (typeFlag === "0" || typeFlag === "\0" || typeFlag === "") {
287
314
  entries.push({ name: normalizePath(name), data, size });
@@ -509,6 +536,93 @@ export function validateVBundle(data: Uint8Array): VBundleValidationResult {
509
536
  continue;
510
537
  }
511
538
 
539
+ if (fileEntry.link_target !== undefined) {
540
+ // Symlink branch: typeflag agreement, linkname agreement, sha over the
541
+ // link target string, size==0 on both sides, and path-traversal rejection.
542
+ if (archiveEntry.linkname === undefined) {
543
+ errors.push({
544
+ code: "SYMLINK_TYPEFLAG_MISMATCH",
545
+ message: `Manifest declares symlink for ${fileEntry.path} but tar entry is not typeflag-2`,
546
+ path: fileEntry.path,
547
+ });
548
+ continue;
549
+ }
550
+
551
+ if (archiveEntry.linkname !== fileEntry.link_target) {
552
+ errors.push({
553
+ code: "LINK_TARGET_MISMATCH",
554
+ message: `Symlink linkname mismatch for ${fileEntry.path}: manifest declares "${fileEntry.link_target}", tar carries "${archiveEntry.linkname}"`,
555
+ path: fileEntry.path,
556
+ });
557
+ continue;
558
+ }
559
+
560
+ if (archiveEntry.size !== 0) {
561
+ errors.push({
562
+ code: "FILE_SIZE_MISMATCH",
563
+ message: `Size mismatch for ${fileEntry.path}: manifest declares ${fileEntry.size_bytes} bytes, archive has ${archiveEntry.size} bytes`,
564
+ path: fileEntry.path,
565
+ });
566
+ }
567
+
568
+ if (fileEntry.size_bytes !== 0) {
569
+ errors.push({
570
+ code: "FILE_SIZE_MISMATCH",
571
+ message: `Size mismatch for ${fileEntry.path}: manifest declares ${fileEntry.size_bytes} bytes, archive has ${archiveEntry.size} bytes`,
572
+ path: fileEntry.path,
573
+ });
574
+ }
575
+
576
+ const expected = sha256Hex(fileEntry.link_target);
577
+ if (expected !== fileEntry.sha256) {
578
+ errors.push({
579
+ code: "FILE_CHECKSUM_MISMATCH",
580
+ message: `Checksum mismatch for ${fileEntry.path}: expected ${fileEntry.sha256}, computed ${expected}`,
581
+ path: fileEntry.path,
582
+ });
583
+ }
584
+
585
+ // Absolute POSIX targets are unconstrained by the bundle root — reject
586
+ // them up front. The `posix.normalize` guard below only catches
587
+ // `..`-based escapes; an absolute path like `/etc/passwd` survives
588
+ // normalization unchanged and would otherwise pass.
589
+ if (fileEntry.link_target.startsWith("/")) {
590
+ errors.push({
591
+ code: "SYMLINK_TARGET_ESCAPES_ARCHIVE",
592
+ message: `Symlink target is absolute, which escapes the archive root for ${fileEntry.path}: target=${fileEntry.link_target}`,
593
+ path: fileEntry.path,
594
+ });
595
+ } else {
596
+ const normalized = posix.normalize(
597
+ posix.join(posix.dirname(fileEntry.path), fileEntry.link_target),
598
+ );
599
+ // Defense-in-depth: also reject if the joined+normalized path is
600
+ // absolute, in case `dirname` ever resolves to an absolute root.
601
+ if (
602
+ normalized.startsWith("../") ||
603
+ normalized === ".." ||
604
+ normalized.startsWith("/")
605
+ ) {
606
+ errors.push({
607
+ code: "SYMLINK_TARGET_ESCAPES_ARCHIVE",
608
+ message: `Symlink target escapes archive root for ${fileEntry.path}: target=${fileEntry.link_target}, normalized=${normalized}`,
609
+ path: fileEntry.path,
610
+ });
611
+ }
612
+ }
613
+ continue;
614
+ }
615
+
616
+ if (archiveEntry.linkname !== undefined) {
617
+ // Tar carries a typeflag-2 entry but manifest declares a regular file.
618
+ errors.push({
619
+ code: "SYMLINK_NOT_DECLARED",
620
+ message: `Tar entry ${fileEntry.path} is typeflag-2 but manifest does not declare link_target`,
621
+ path: fileEntry.path,
622
+ });
623
+ continue;
624
+ }
625
+
512
626
  // Verify size
513
627
  if (archiveEntry.size !== fileEntry.size_bytes) {
514
628
  errors.push({
@@ -3,10 +3,14 @@
3
3
  * confirmation, secret, host_bash, host_file, host_cu, host_browser, and
4
4
  * host_transfer interactions.
5
5
  *
6
- * When the agent loop emits a confirmation_request, secret_request,
7
- * host_bash_request, host_file_request, host_cu_request,
8
- * host_browser_request, or host_transfer_request, the onEvent callback
9
- * registers the interaction here.
6
+ * For confirmation_request and secret_request, the onEvent callback in
7
+ * assistant-event-hub registers the interaction here.
8
+ *
9
+ * For host proxy interactions (host_bash, host_file, host_cu, host_browser,
10
+ * host_transfer), the proxy itself registers with full RPC lifecycle state
11
+ * (resolve/reject callbacks, timer, abort detach). This eliminates the
12
+ * per-proxy `private pending` maps — all pending state lives here.
13
+ *
10
14
  * Standalone HTTP endpoints (/v1/confirm, /v1/secret, /v1/trust-rules,
11
15
  * /v1/host-bash-result, /v1/host-file-result, /v1/host-cu-result,
12
16
  * /v1/host-browser-result) look up the conversation from this tracker to
@@ -14,6 +18,7 @@
14
18
  */
15
19
 
16
20
  import type { UserDecision } from "../permissions/types.js";
21
+ import type { ToolExecutionResult } from "../tools/types.js";
17
22
 
18
23
  export interface ConfirmationDetails {
19
24
  toolName: string;
@@ -46,11 +51,27 @@ export interface PendingInteraction {
46
51
  | "host_file"
47
52
  | "host_cu"
48
53
  | "host_browser"
54
+ | "host_app_control"
49
55
  | "host_transfer"
50
56
  | "acp_confirmation";
51
57
  confirmationDetails?: ConfirmationDetails;
52
58
  /** For ACP permissions: resolves directly without a Conversation object. */
53
59
  directResolve?: (decision: UserDecision) => void;
60
+ /** When set, the host_bash request should be routed to this specific client. */
61
+ targetClientId?: string;
62
+
63
+ // -- RPC lifecycle (populated by host proxies) --
64
+
65
+ /** Resolve the caller's Promise with a tool execution result. */
66
+ rpcResolve?: (result: ToolExecutionResult) => void;
67
+ /** Reject the caller's Promise with an error. */
68
+ rpcReject?: (err: Error) => void;
69
+ /** Proxy-side timeout timer. Cleared on resolve/abort/dispose. */
70
+ timer?: ReturnType<typeof setTimeout>;
71
+ /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
72
+ detachAbort?: () => void;
73
+ /** Proxy-specific metadata (e.g. timeoutSec for bash, operation/path for file). */
74
+ metadata?: Record<string, unknown>;
54
75
  }
55
76
 
56
77
  const pending = new Map<string, PendingInteraction>();
@@ -64,12 +85,15 @@ export function register(
64
85
 
65
86
  /**
66
87
  * Remove and return the pending interaction for the given requestId.
88
+ * Auto-clears the proxy timer and detaches the abort listener if present.
67
89
  * Returns undefined if no interaction is registered.
68
90
  */
69
91
  export function resolve(requestId: string): PendingInteraction | undefined {
70
92
  const interaction = pending.get(requestId);
71
93
  if (interaction) {
72
94
  pending.delete(requestId);
95
+ if (interaction.timer != null) clearTimeout(interaction.timer);
96
+ interaction.detachAbort?.();
73
97
  }
74
98
  return interaction;
75
99
  }
@@ -102,11 +126,12 @@ export function getByConversation(
102
126
  * Remove pending confirmation and secret interactions for a given conversation.
103
127
  * Used when auto-denying all pending interactions (e.g. new user message).
104
128
  *
105
- * host_bash, host_file, host_cu, host_browser, and host_transfer interactions
106
- * are intentionally skipped — they represent in-flight tool executions proxied
107
- * to the client, not confirmations to auto-deny. Removing them would orphan
108
- * the request: the client would POST to /v1/host-bash-result,
109
- * /v1/host-file-result, /v1/host-cu-result, /v1/host-browser-result, or
129
+ * host_bash, host_file, host_cu, host_browser, host_app_control, and
130
+ * host_transfer interactions are intentionally skipped — they represent
131
+ * in-flight tool executions proxied to the client, not confirmations to
132
+ * auto-deny. Removing them would orphan the request: the client would POST to
133
+ * /v1/host-bash-result, /v1/host-file-result, /v1/host-cu-result,
134
+ * /v1/host-browser-result, /v1/host-app-control-result, or
110
135
  * /v1/host-transfer-result after completing the operation, get a 404, and the
111
136
  * proxy timer would fire with a spurious timeout error.
112
137
  */
@@ -118,6 +143,7 @@ export function removeByConversation(conversationId: string): void {
118
143
  interaction.kind !== "host_file" &&
119
144
  interaction.kind !== "host_cu" &&
120
145
  interaction.kind !== "host_browser" &&
146
+ interaction.kind !== "host_app_control" &&
121
147
  interaction.kind !== "host_transfer" &&
122
148
  interaction.kind !== "acp_confirmation"
123
149
  ) {
@@ -22,7 +22,6 @@ import { tmpdir } from "node:os";
22
22
  import { join } from "node:path";
23
23
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
24
24
 
25
- import type { BackupRunResult } from "../../../backup/backup-worker.js";
26
25
  import type { RestoreResult, VerifyResult } from "../../../backup/restore.js";
27
26
  import type { BackupConfig } from "../../../config/schema.js";
28
27
  import { BackupConfigSchema } from "../../../config/schema.js";
@@ -114,23 +113,6 @@ mock.module("../../../backup/backup-key.js", () => ({
114
113
  ensureBackupKey: async (_path: string) => mockBackupKey ?? Buffer.alloc(32),
115
114
  }));
116
115
 
117
- // -- Backup worker mock ----------------------------------------------------
118
-
119
- let mockCreateSnapshotResult: BackupRunResult | null = null;
120
- let mockCreateSnapshotError: Error | null = null;
121
- let mockCreateSnapshotCalls = 0;
122
-
123
- mock.module("../../../backup/backup-worker.js", () => ({
124
- createSnapshotNow: async (_config: BackupConfig, _now: Date) => {
125
- mockCreateSnapshotCalls += 1;
126
- if (mockCreateSnapshotError) throw mockCreateSnapshotError;
127
- if (mockCreateSnapshotResult == null) {
128
- throw new Error("Test forgot to set mockCreateSnapshotResult");
129
- }
130
- return mockCreateSnapshotResult;
131
- },
132
- }));
133
-
134
116
  // -- Restore module mock ---------------------------------------------------
135
117
 
136
118
  interface RestoreCall {
@@ -179,21 +161,20 @@ mock.module("../../../backup/restore.js", () => ({
179
161
  restoreFromSnapshot: async (
180
162
  path: string,
181
163
  opts: {
182
- key?: Buffer;
183
164
  workspaceDir?: string;
184
165
  },
185
166
  ) => {
186
167
  recoveryCallOrder.push("restoreFromSnapshot");
187
168
  lastRestoreArgs = {
188
169
  path,
189
- hasKey: opts.key != null,
170
+ hasKey: false,
190
171
  workspaceDir: opts.workspaceDir,
191
172
  };
192
173
  if (mockRestoreError) throw mockRestoreError;
193
174
  return mockRestoreResult;
194
175
  },
195
- verifySnapshot: async (path: string, opts: { key?: Buffer }) => {
196
- lastVerifyArgs = { path, hasKey: opts.key != null };
176
+ verifySnapshot: async (path: string) => {
177
+ lastVerifyArgs = { path, hasKey: false };
197
178
  return mockVerifyResult;
198
179
  },
199
180
  }));
@@ -248,9 +229,6 @@ beforeEach(() => {
248
229
  }
249
230
  mockBackupKey = Buffer.alloc(32, 0xaa);
250
231
  mockReadBackupKeyCalls = 0;
251
- mockCreateSnapshotResult = null;
252
- mockCreateSnapshotError = null;
253
- mockCreateSnapshotCalls = 0;
254
232
  lastRestoreArgs = null;
255
233
  lastVerifyArgs = null;
256
234
  mockRestoreError = null;
@@ -431,105 +409,19 @@ describe("handleBackupList", () => {
431
409
  });
432
410
  });
433
411
 
412
+ // ---------------------------------------------------------------------------
434
413
  // ---------------------------------------------------------------------------
435
414
  // handleBackupCreate
436
415
  // ---------------------------------------------------------------------------
437
416
 
438
417
  describe("handleBackupCreate", () => {
439
- const fakeRunResult: BackupRunResult = {
440
- local: {
441
- path: "/tmp/fake/backup-20260411-100000.vbundle",
442
- filename: "backup-20260411-100000.vbundle",
443
- createdAt: new Date("2026-04-11T10:00:00Z"),
444
- sizeBytes: 100,
445
- encrypted: false,
446
- },
447
- offsite: [],
448
- durationMs: 42,
449
- };
450
-
451
- test("manual create bypasses enabled flag and succeeds with disabled config", async () => {
452
- mockBackupConfig = makeConfig({
453
- enabled: false,
454
- localDirectory: LOCAL_DIR,
455
- offsite: { enabled: false, destinations: null },
456
- });
457
- mockCreateSnapshotResult = fakeRunResult;
458
-
459
- const result = await handleBackupCreate();
460
- expect(result.durationMs).toBe(42);
461
- expect(result.offsite).toEqual([]);
462
- expect(mockCreateSnapshotCalls).toBe(1);
463
- });
464
-
465
- test("plaintext-only destinations do not create backup.key file", async () => {
466
- const plaintextDir = join(ROOT, "offsite-plain");
467
- mkdirSync(plaintextDir, { recursive: true });
468
- mockBackupConfig = makeConfig({
469
- enabled: true,
470
- localDirectory: LOCAL_DIR,
471
- offsite: {
472
- enabled: true,
473
- destinations: [{ path: plaintextDir, encrypt: false }],
474
- },
475
- });
476
- mockCreateSnapshotResult = fakeRunResult;
477
- mockReadBackupKeyCalls = 0;
478
-
479
- await handleBackupCreate();
480
- expect(mockReadBackupKeyCalls).toBe(0);
481
- const keyFileExists = await import("node:fs").then((m) =>
482
- m.existsSync(join(ROOT, "workspace", ".backup.key")),
483
- );
484
- expect(keyFileExists).toBe(false);
485
- });
486
-
487
- test("concurrent call throws ConflictError when mock raises 'snapshot in progress'", async () => {
488
- mockBackupConfig = makeConfig({ localDirectory: LOCAL_DIR });
489
- mockCreateSnapshotError = new Error("snapshot in progress");
490
-
491
- try {
492
- await handleBackupCreate();
493
- expect.unreachable("should have thrown");
494
- } catch (err) {
495
- expect(err).toBeInstanceOf(RouteError);
496
- expect((err as RouteError).statusCode).toBe(409);
497
- expect((err as RouteError).code).toBe("CONFLICT");
498
- }
499
- });
500
-
501
- test("cross-process conflict ('locked by pid N') is still mapped to 409", async () => {
502
- mockBackupConfig = makeConfig({ localDirectory: LOCAL_DIR });
503
- mockCreateSnapshotError = new Error(
504
- "snapshot in progress (locked by pid 12345)",
418
+ test("always throws BadRequestError redirecting to gateway", async () => {
419
+ await expect(handleBackupCreate()).rejects.toThrow(
420
+ "Backup snapshot creation has moved to the gateway",
505
421
  );
506
-
507
- try {
508
- await handleBackupCreate();
509
- expect.unreachable("should have thrown");
510
- } catch (err) {
511
- expect(err).toBeInstanceOf(RouteError);
512
- expect((err as RouteError).statusCode).toBe(409);
513
- expect((err as RouteError).code).toBe("CONFLICT");
514
- }
515
- });
516
-
517
- test("other errors are surfaced as 500", async () => {
518
- mockCreateSnapshotError = new Error("disk full");
519
-
520
- try {
521
- await handleBackupCreate();
522
- expect.unreachable("should have thrown");
523
- } catch (err) {
524
- expect(err).toBeInstanceOf(RouteError);
525
- expect((err as RouteError).statusCode).toBe(500);
526
- expect((err as RouteError).code).toBe("INTERNAL_ERROR");
527
- expect((err as RouteError).message).toBe("disk full");
528
- }
529
422
  });
530
423
  });
531
424
 
532
- // ---------------------------------------------------------------------------
533
425
  // handleBackupRestore
534
426
  // ---------------------------------------------------------------------------
535
427
 
@@ -613,29 +505,7 @@ describe("handleBackupRestore", () => {
613
505
  expect(lastRestoreArgs!.path).toBe(expectedRealpath);
614
506
  });
615
507
 
616
- test("encrypted .vbundle.enc inside local dir loads key and restores", async () => {
617
- const snapshotPath = writeBackupFile(
618
- LOCAL_DIR,
619
- "backup-20260411-100000.vbundle.enc",
620
- );
621
- mockBackupConfig = makeConfig({
622
- localDirectory: LOCAL_DIR,
623
- offsite: { enabled: true, destinations: [] },
624
- });
625
- mockBackupKey = Buffer.alloc(32, 0xbb);
626
- mockReadBackupKeyCalls = 0;
627
-
628
- await handleBackupRestore({
629
- body: { path: snapshotPath },
630
- pathParams: {},
631
- queryParams: {},
632
- });
633
- expect(mockReadBackupKeyCalls).toBe(1);
634
- expect(lastRestoreArgs).not.toBeNull();
635
- expect(lastRestoreArgs!.hasKey).toBe(true);
636
- });
637
-
638
- test("encrypted bundle with missing backup.key throws BadRequestError", async () => {
508
+ test("encrypted .vbundle.enc is rejected with gateway redirect error", async () => {
639
509
  const snapshotPath = writeBackupFile(
640
510
  LOCAL_DIR,
641
511
  "backup-20260411-100000.vbundle.enc",
@@ -644,7 +514,6 @@ describe("handleBackupRestore", () => {
644
514
  localDirectory: LOCAL_DIR,
645
515
  offsite: { enabled: true, destinations: [] },
646
516
  });
647
- mockBackupKey = null;
648
517
 
649
518
  try {
650
519
  await handleBackupRestore({
@@ -656,7 +525,7 @@ describe("handleBackupRestore", () => {
656
525
  } catch (err) {
657
526
  expect(err).toBeInstanceOf(RouteError);
658
527
  expect((err as RouteError).statusCode).toBe(400);
659
- expect((err as RouteError).message).toMatch(/backup.key is missing/);
528
+ expect((err as RouteError).message).toMatch(/gateway/i);
660
529
  }
661
530
  expect(lastRestoreArgs).toBeNull();
662
531
  });
@@ -794,7 +663,7 @@ describe("handleBackupVerify", () => {
794
663
  expect(result.valid).toBe(true);
795
664
  });
796
665
 
797
- test("encrypted bundle with key loads key and forwards to verifySnapshot", async () => {
666
+ test("encrypted bundle is rejected with gateway redirect error", async () => {
798
667
  const snapshotPath = writeBackupFile(
799
668
  LOCAL_DIR,
800
669
  "backup-20260411-100000.vbundle.enc",
@@ -803,16 +672,19 @@ describe("handleBackupVerify", () => {
803
672
  localDirectory: LOCAL_DIR,
804
673
  offsite: { enabled: true, destinations: [] },
805
674
  });
806
- mockBackupKey = Buffer.alloc(32, 0xcc);
807
- mockReadBackupKeyCalls = 0;
808
675
 
809
- await handleBackupVerify({
810
- body: { path: snapshotPath },
811
- pathParams: {},
812
- queryParams: {},
813
- });
814
- expect(mockReadBackupKeyCalls).toBe(1);
815
- expect(lastVerifyArgs!.hasKey).toBe(true);
676
+ try {
677
+ await handleBackupVerify({
678
+ body: { path: snapshotPath },
679
+ pathParams: {},
680
+ queryParams: {},
681
+ });
682
+ expect.unreachable("should have thrown");
683
+ } catch (err) {
684
+ expect(err).toBeInstanceOf(RouteError);
685
+ expect((err as RouteError).statusCode).toBe(400);
686
+ expect((err as RouteError).message).toMatch(/gateway/i);
687
+ }
816
688
  });
817
689
 
818
690
  test("path outside allowed directories throws BadRequestError", async () => {
@@ -29,6 +29,7 @@ mock.module("../../../config/loader.js", () => ({
29
29
  },
30
30
  }));
31
31
 
32
+ import type { ConversationCreateType } from "../../../memory/conversation-crud.js";
32
33
  import { getDb } from "../../../memory/db-connection.js";
33
34
  import { initializeDb } from "../../../memory/db-init.js";
34
35
  import {
@@ -39,8 +40,10 @@ import {
39
40
  recordMemoryV2ActivationLog,
40
41
  } from "../../../memory/memory-v2-activation-log-store.js";
41
42
  import {
43
+ conversations,
42
44
  llmRequestLogs,
43
45
  memoryV2ActivationLogs,
46
+ messages,
44
47
  } from "../../../memory/schema.js";
45
48
  import { ROUTES } from "../conversation-query-routes.js";
46
49
 
@@ -68,6 +71,40 @@ function clearTables(): void {
68
71
  const db = getDb();
69
72
  db.delete(llmRequestLogs).run();
70
73
  db.delete(memoryV2ActivationLogs).run();
74
+ db.delete(messages).run();
75
+ db.delete(conversations).run();
76
+ }
77
+
78
+ function seedConversationAndMessage(args: {
79
+ conversationId: string;
80
+ messageId: string;
81
+ source: string;
82
+ conversationType: ConversationCreateType;
83
+ }): void {
84
+ const now = Date.now();
85
+ getDb()
86
+ .insert(conversations)
87
+ .values({
88
+ id: args.conversationId,
89
+ title: null,
90
+ createdAt: now,
91
+ updatedAt: now,
92
+ source: args.source,
93
+ conversationType: args.conversationType,
94
+ memoryScopeId: "default",
95
+ })
96
+ .run();
97
+ getDb()
98
+ .insert(messages)
99
+ .values({
100
+ id: args.messageId,
101
+ conversationId: args.conversationId,
102
+ role: "assistant",
103
+ content: "",
104
+ createdAt: now,
105
+ metadata: null,
106
+ })
107
+ .run();
71
108
  }
72
109
 
73
110
  function seedRequestLog(messageId: string, id: string): void {
@@ -143,6 +180,67 @@ describe("GET /v1/messages/:id/llm-context — memoryV2Activation", () => {
143
180
  });
144
181
  });
145
182
 
183
+ describe("GET /v1/messages/:id/llm-context — conversationKind", () => {
184
+ beforeEach(() => {
185
+ clearTables();
186
+ });
187
+
188
+ test("returns 'background_memory_consolidation' for memory_v2_consolidation source", async () => {
189
+ seedConversationAndMessage({
190
+ conversationId: "conv-mem-consol",
191
+ messageId: "msg-mem-consol",
192
+ source: "memory_v2_consolidation",
193
+ conversationType: "background",
194
+ });
195
+
196
+ const body = (await dispatchLlmContext("msg-mem-consol")) as {
197
+ conversationKind: string;
198
+ logs: unknown[];
199
+ };
200
+
201
+ expect(body.conversationKind).toBe("background_memory_consolidation");
202
+ expect(body.logs).toEqual([]);
203
+ });
204
+
205
+ test("returns 'background' for non-consolidation background conversations", async () => {
206
+ seedConversationAndMessage({
207
+ conversationId: "conv-bg",
208
+ messageId: "msg-bg",
209
+ source: "memory_consolidation",
210
+ conversationType: "background",
211
+ });
212
+
213
+ const body = (await dispatchLlmContext("msg-bg")) as {
214
+ conversationKind: string;
215
+ };
216
+
217
+ expect(body.conversationKind).toBe("background");
218
+ });
219
+
220
+ test("returns 'user' for standard conversations", async () => {
221
+ seedConversationAndMessage({
222
+ conversationId: "conv-user",
223
+ messageId: "msg-user",
224
+ source: "user",
225
+ conversationType: "standard",
226
+ });
227
+
228
+ const body = (await dispatchLlmContext("msg-user")) as {
229
+ conversationKind: string;
230
+ };
231
+
232
+ expect(body.conversationKind).toBe("user");
233
+ });
234
+
235
+ test("falls back to 'user' when the message can't be resolved", async () => {
236
+ const body = (await dispatchLlmContext("msg-missing")) as {
237
+ conversationKind: string;
238
+ };
239
+
240
+ expect(body.conversationKind).toBe("user");
241
+ });
242
+ });
243
+
146
244
  describe("PUT /v1/config/llm/profiles/:name", () => {
147
245
  beforeEach(() => {
148
246
  savedRawConfig = null;