@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,172 @@
1
+ /**
2
+ * Shared invariants and predicate functions consumed by both the buffer-
3
+ * based `commitImport` and the streaming `streamCommitImport`. These
4
+ * decisions must stay in lockstep across both importers — moving them
5
+ * here removes the parallel-implementation skew risk that would otherwise
6
+ * grow as either importer evolves.
7
+ *
8
+ * Pure: no `node:fs`, no I/O, no async. Functions over strings + manifest
9
+ * data shapes only.
10
+ */
11
+
12
+ export const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
13
+
14
+ export const CONFIG_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
15
+ "workspace/config.json",
16
+ "config/settings.json",
17
+ ]);
18
+
19
+ export const CREDENTIAL_METADATA_ARCHIVE_PATH =
20
+ "workspace/data/credentials/metadata.json";
21
+
22
+ export const WORKSPACE_PRESERVE_PATHS: readonly string[] = [
23
+ "embedding-models",
24
+ "deprecated",
25
+ "data/db",
26
+ "data/qdrant",
27
+ ];
28
+
29
+ export function isWorkspaceNamespacedArchivePath(archivePath: string): boolean {
30
+ return archivePath.startsWith("workspace/");
31
+ }
32
+
33
+ export function isLegacyPersonaArchivePath(archivePath: string): boolean {
34
+ return archivePath === LEGACY_USER_MD_ARCHIVE_PATH;
35
+ }
36
+
37
+ export function isConfigArchivePath(archivePath: string): boolean {
38
+ return CONFIG_ARCHIVE_PATHS.has(archivePath);
39
+ }
40
+
41
+ export function isCredentialMetadataArchivePath(archivePath: string): boolean {
42
+ return archivePath === CREDENTIAL_METADATA_ARCHIVE_PATH;
43
+ }
44
+
45
+ /**
46
+ * Partition `WORKSPACE_PRESERVE_PATHS` into the two skip sets the buffer
47
+ * importer's selective-clear loop consumes:
48
+ *
49
+ * - `topLevelSkipDirs`: single-segment preserve-paths (e.g. "embedding-models").
50
+ * - `dataSubdirSkipDirs`: second segment of `data/<x>` preserve-paths
51
+ * (e.g. "db" for "data/db").
52
+ *
53
+ * Stays in sync with WORKSPACE_PRESERVE_PATHS automatically — adding a
54
+ * new entry of either shape doesn't require touching the buffer importer.
55
+ * Multi-segment paths outside the `data/` subtree are intentionally
56
+ * unsupported here; the buffer importer's walk doesn't recurse into
57
+ * arbitrary subdirs. If a future preserve-path needs deeper coverage,
58
+ * widen this helper and the buffer importer's walk together.
59
+ */
60
+ export function partitionWorkspacePreserveSkipDirs(): {
61
+ topLevelSkipDirs: ReadonlySet<string>;
62
+ dataSubdirSkipDirs: ReadonlySet<string>;
63
+ } {
64
+ const topLevelSkipDirs = new Set<string>();
65
+ const dataSubdirSkipDirs = new Set<string>();
66
+ for (const rel of WORKSPACE_PRESERVE_PATHS) {
67
+ const parts = rel.split("/");
68
+ if (parts.length === 1) {
69
+ topLevelSkipDirs.add(parts[0]!);
70
+ } else if (parts.length === 2 && parts[0] === "data") {
71
+ dataSubdirSkipDirs.add(parts[1]!);
72
+ }
73
+ }
74
+ return { topLevelSkipDirs, dataSubdirSkipDirs };
75
+ }
76
+
77
+ export const LEGACY_RUNTIME_VERSION_SENTINEL = "0.0.0-legacy";
78
+
79
+ type SemverTriple = readonly [number, number, number];
80
+
81
+ function parseSemverTriple(version: string): SemverTriple | null {
82
+ // Strip optional prerelease/build suffix ("-foo", "+sha"). We treat
83
+ // "0.7.1-staging.1" as equal-to-release "0.7.1" for gating purposes.
84
+ // The platform-side check uses packaging.version.Version, which sorts
85
+ // prereleases BEFORE the corresponding release; matching that exactly
86
+ // would require a fuller parser — for runtime-side defense-in-depth
87
+ // this conservative read is sufficient (matches release of the same
88
+ // base triple).
89
+ const base = version.split(/[-+]/)[0] ?? version;
90
+ const parts = base.split(".");
91
+ if (parts.length !== 3) return null;
92
+ // Reject components with anything other than ASCII digits to avoid
93
+ // Number.parseInt's lenient prefix-parse — e.g. "0.8.0foo" would
94
+ // otherwise coerce to [0, 8, 0] and silently pass the gate, defeating
95
+ // the parse-failure-fail-open contract in evaluateRuntimeCompatibility.
96
+ // Leading zeros (e.g. "01.02.03") are intentionally accepted since
97
+ // they round-trip to the same numeric triple as the un-padded form.
98
+ if (!parts.every((p) => /^\d+$/.test(p))) return null;
99
+ const [maj, min, pat] = parts.map((p) => Number(p));
100
+ if (![maj, min, pat].every((n) => Number.isFinite(n) && n >= 0)) {
101
+ return null;
102
+ }
103
+ return [maj!, min!, pat!] as const;
104
+ }
105
+
106
+ // -1 if a < b, 0 if equal, +1 if a > b. Returns null on parse failure.
107
+ export function compareSemver(a: string, b: string): -1 | 0 | 1 | null {
108
+ const ta = parseSemverTriple(a);
109
+ const tb = parseSemverTriple(b);
110
+ if (!ta || !tb) return null;
111
+ for (let i = 0; i < 3; i++) {
112
+ if (ta[i]! < tb[i]!) return -1;
113
+ if (ta[i]! > tb[i]!) return +1;
114
+ }
115
+ return 0;
116
+ }
117
+
118
+ export interface RuntimeCompatibility {
119
+ min_runtime_version: string;
120
+ max_runtime_version: string | null;
121
+ }
122
+
123
+ export type RuntimeCompatibilityResult =
124
+ | { ok: true }
125
+ | {
126
+ ok: false;
127
+ reason: "version_incompatible";
128
+ bundle_compat: RuntimeCompatibility;
129
+ runtime_version: string;
130
+ };
131
+
132
+ export function formatRuntimeCompatibilityMessage(
133
+ compat: RuntimeCompatibility,
134
+ runtimeVersion: string,
135
+ ): string {
136
+ const range = compat.max_runtime_version
137
+ ? `${compat.min_runtime_version}–${compat.max_runtime_version}`
138
+ : `${compat.min_runtime_version}+`;
139
+ return `Cannot import: bundle requires runtime ${range}, but this runtime is ${runtimeVersion}. Update your runtime before importing.`;
140
+ }
141
+
142
+ export function evaluateRuntimeCompatibility(
143
+ compat: RuntimeCompatibility,
144
+ runtimeVersion: string,
145
+ ): RuntimeCompatibilityResult {
146
+ if (compat.min_runtime_version === LEGACY_RUNTIME_VERSION_SENTINEL) {
147
+ return { ok: true };
148
+ }
149
+ const minCmp = compareSemver(runtimeVersion, compat.min_runtime_version);
150
+ if (minCmp === null) return { ok: true };
151
+ if (minCmp < 0) {
152
+ return {
153
+ ok: false,
154
+ reason: "version_incompatible",
155
+ bundle_compat: compat,
156
+ runtime_version: runtimeVersion,
157
+ };
158
+ }
159
+ if (compat.max_runtime_version !== null) {
160
+ const maxCmp = compareSemver(runtimeVersion, compat.max_runtime_version);
161
+ if (maxCmp === null) return { ok: true };
162
+ if (maxCmp > 0) {
163
+ return {
164
+ ok: false,
165
+ reason: "version_incompatible",
166
+ bundle_compat: compat,
167
+ runtime_version: runtimeVersion,
168
+ };
169
+ }
170
+ }
171
+ return { ok: true };
172
+ }
@@ -16,72 +16,30 @@ import { createHash } from "node:crypto";
16
16
  import {
17
17
  copyFileSync,
18
18
  existsSync,
19
+ lstatSync,
19
20
  mkdirSync,
20
21
  readdirSync,
21
22
  readFileSync,
23
+ readlinkSync,
22
24
  rmSync,
25
+ symlinkSync,
23
26
  writeFileSync,
24
27
  } from "node:fs";
25
- import { dirname, join } from "node:path";
28
+ import { dirname, join, resolve, sep } from "node:path";
26
29
 
27
30
  import { sanitizeConfigForTransfer } from "../../config/sanitize-for-transfer.js";
28
31
  import { isGuardianPersonaCustomized } from "../../prompts/persona-resolver.js";
29
32
  import { getLogger } from "../../util/logger.js";
33
+ import { APP_VERSION } from "../../version.js";
30
34
  import type { PathResolver } from "./vbundle-import-analyzer.js";
35
+ import type { RuntimeCompatibility } from "./vbundle-import-policy.js";
36
+ import * as policy from "./vbundle-import-policy.js";
31
37
  import { mergeMetadataPreservingVellum } from "./vbundle-metadata-merge.js";
32
38
  import type { ManifestType, VBundleTarEntry } from "./vbundle-validator.js";
33
39
  import { validateVBundle } from "./vbundle-validator.js";
34
40
 
35
41
  const log = getLogger("vbundle-importer");
36
42
 
37
- /** Archive path for the legacy guardian user persona file. */
38
- export const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
39
-
40
- /**
41
- * Archive paths recognized as JSON config files that must be run through
42
- * `sanitizeConfigForTransfer` before writing to disk. Exported so the
43
- * streaming importer can apply the same defense-in-depth treatment.
44
- */
45
- export const CONFIG_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
46
- "workspace/config.json",
47
- "config/settings.json",
48
- ]);
49
-
50
- /**
51
- * Archive path for the credential metadata file. On import, bundle contents
52
- * must be merged with the target's live `vellum:*` entries so the gateway's
53
- * `readServiceCredentials` can still locate the platform API key after a
54
- * local→platform teleport. Both importers consult this constant.
55
- */
56
- const CREDENTIAL_METADATA_ARCHIVE_PATH =
57
- "workspace/data/credentials/metadata.json";
58
-
59
- /**
60
- * Paths inside the workspace directory that must be preserved across an
61
- * import when the bundle does not carry entries for them.
62
- *
63
- * Each entry is a path RELATIVE to the workspace root. Two kinds of live
64
- * data warrant carry-over:
65
- *
66
- * - `embedding-models` / `deprecated`: large local caches / quarantine
67
- * directories that are never shipped inside bundles but are expensive
68
- * or impossible to reconstruct from an import.
69
- * - `data/db` / `data/qdrant`: user-critical state (SQLite assistant DB;
70
- * Qdrant vector store). If the bundle omits them — e.g. a partial
71
- * bundle covering only prompts/config — the live copies must survive.
72
- *
73
- * Both the buffer-based `commitImport` (which selectively clears the
74
- * workspace in place) and the streaming importer (which swaps the
75
- * workspace with a freshly-populated temp tree) consult this list so
76
- * their behavior stays in sync.
77
- */
78
- export const WORKSPACE_PRESERVE_PATHS: readonly string[] = [
79
- "embedding-models",
80
- "deprecated",
81
- "data/db",
82
- "data/qdrant",
83
- ];
84
-
85
43
  // ---------------------------------------------------------------------------
86
44
  // Public types
87
45
  // ---------------------------------------------------------------------------
@@ -130,6 +88,12 @@ export type ImportCommitResult =
130
88
  errors: Array<{ code: string; message: string; path?: string }>;
131
89
  }
132
90
  | { ok: false; reason: "extraction_failed"; message: string }
91
+ | {
92
+ ok: false;
93
+ reason: "version_incompatible";
94
+ bundle_compat: RuntimeCompatibility;
95
+ runtime_version: string;
96
+ }
133
97
  | {
134
98
  ok: false;
135
99
  reason: "write_failed";
@@ -154,6 +118,28 @@ function generateBackupPath(diskPath: string): string {
154
118
  return `${diskPath}.backup-${timestamp}`;
155
119
  }
156
120
 
121
+ /**
122
+ * Defense-in-depth: returns true if `linkTarget`, when resolved relative to
123
+ * the symlink's own directory (`dirname(diskPath)`), lands outside the
124
+ * supplied `workspaceDir`. The validator (`validateVBundle`) already enforces
125
+ * archive-relative containment, but we re-check here so the buffer importer
126
+ * is safe even if a caller passes a hand-built `preValidatedManifest`.
127
+ *
128
+ * Returns false when `workspaceDir` is undefined — the importer is permitted
129
+ * to write outside any workspace in that mode (e.g. legacy hooks-only
130
+ * imports).
131
+ */
132
+ function isOutsideWorkspace(
133
+ diskPath: string,
134
+ linkTarget: string,
135
+ workspaceDir: string | undefined,
136
+ ): boolean {
137
+ if (!workspaceDir) return false;
138
+ const resolved = resolve(dirname(diskPath), linkTarget);
139
+ const ws = resolve(workspaceDir);
140
+ return resolved !== ws && !resolved.startsWith(ws + sep);
141
+ }
142
+
157
143
  // ---------------------------------------------------------------------------
158
144
  // Core importer
159
145
  // ---------------------------------------------------------------------------
@@ -217,19 +203,29 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
217
203
  entryMap = validation.entries;
218
204
  }
219
205
 
206
+ // Defense-in-depth: refuse to import a bundle whose declared compat range
207
+ // excludes this runtime BEFORE any state mutation. The platform-side gate
208
+ // is the primary check; this catches legacy bundles whose ExportJob row
209
+ // predates PR #5470 (compat columns NULL → platform gate skipped) and
210
+ // any caller that bypasses the platform-issued signed URL flow.
211
+ const compatResult = policy.evaluateRuntimeCompatibility(
212
+ manifest.compatibility,
213
+ APP_VERSION,
214
+ );
215
+ if (!compatResult.ok) {
216
+ return {
217
+ ok: false,
218
+ reason: "version_incompatible",
219
+ bundle_compat: compatResult.bundle_compat,
220
+ runtime_version: compatResult.runtime_version,
221
+ };
222
+ }
223
+
220
224
  // Directories to preserve when clearing the workspace. Derived from the
221
225
  // shared WORKSPACE_PRESERVE_PATHS list so the streaming importer's
222
226
  // carry-over logic and this in-place clear stay in sync.
223
- const WORKSPACE_SKIP_DIRS = new Set<string>();
224
- const DATA_SKIP_DIRS = new Set<string>();
225
- for (const rel of WORKSPACE_PRESERVE_PATHS) {
226
- const parts = rel.split("/");
227
- if (parts.length === 1) {
228
- WORKSPACE_SKIP_DIRS.add(parts[0]);
229
- } else if (parts.length === 2 && parts[0] === "data") {
230
- DATA_SKIP_DIRS.add(parts[1]);
231
- }
232
- }
227
+ const { topLevelSkipDirs, dataSubdirSkipDirs } =
228
+ policy.partitionWorkspacePreserveSkipDirs();
233
229
 
234
230
  // Step 1b: Clear the workspace directory before restore if the bundle
235
231
  // contains new-format workspace/ entries. This ensures an exact-match
@@ -247,7 +243,9 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
247
243
  // "workspace/../../etc/passwd") from triggering a workspace purge while
248
244
  // resolving to nothing.
249
245
  const hasWorkspaceEntries = manifest.contents.some(
250
- (f) => f.path.startsWith("workspace/") && !!pathResolver.resolve(f.path),
246
+ (f) =>
247
+ policy.isWorkspaceNamespacedArchivePath(f.path) &&
248
+ !!pathResolver.resolve(f.path),
251
249
  );
252
250
 
253
251
  // Capture the target's credential metadata BEFORE the workspace clear
@@ -257,7 +255,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
257
255
  // (`vellum:*`) entries across the overwrite.
258
256
  let liveCredentialMetadataJson: string | null = null;
259
257
  const credentialMetadataDiskPath = pathResolver.resolve(
260
- CREDENTIAL_METADATA_ARCHIVE_PATH,
258
+ policy.CREDENTIAL_METADATA_ARCHIVE_PATH,
261
259
  );
262
260
  if (credentialMetadataDiskPath && existsSync(credentialMetadataDiskPath)) {
263
261
  try {
@@ -279,7 +277,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
279
277
  // Clear workspace contents selectively, preserving skip dirs
280
278
  const topEntries = readdirSync(workspaceDir, { withFileTypes: true });
281
279
  for (const entry of topEntries) {
282
- if (WORKSPACE_SKIP_DIRS.has(entry.name)) continue;
280
+ if (topLevelSkipDirs.has(entry.name)) continue;
283
281
 
284
282
  const entryPath = join(workspaceDir, entry.name);
285
283
  if (entry.name === "data" && entry.isDirectory()) {
@@ -287,7 +285,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
287
285
  // (critical user data) but clear everything else
288
286
  const dataEntries = readdirSync(entryPath, { withFileTypes: true });
289
287
  for (const dataEntry of dataEntries) {
290
- if (DATA_SKIP_DIRS.has(dataEntry.name)) continue;
288
+ if (dataSubdirSkipDirs.has(dataEntry.name)) continue;
291
289
  rmSync(join(entryPath, dataEntry.name), {
292
290
  recursive: true,
293
291
  force: true,
@@ -339,6 +337,185 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
339
337
  continue;
340
338
  }
341
339
 
340
+ // Symlink branch: recreate the entry on disk as a real symlink so the
341
+ // post-import workspace mirrors the source's link topology rather than
342
+ // duplicating bytes. The validator already enforces archive-relative
343
+ // containment, sha256-over-target, and size==0 — we still reapply
344
+ // absolute-target and workspace-escape gates here so a hand-built
345
+ // `preValidatedManifest` cannot bypass them.
346
+ if (fileEntry.link_target !== undefined) {
347
+ const archiveEntry = entryMap.get(fileEntry.path);
348
+ if (!archiveEntry) {
349
+ importedFiles.push({
350
+ path: fileEntry.path,
351
+ disk_path: diskPath,
352
+ action: "skipped",
353
+ size: 0,
354
+ sha256: fileEntry.sha256,
355
+ backup_path: null,
356
+ });
357
+ warnings.push(
358
+ `Skipped "${fileEntry.path}": declared in manifest but not found in archive`,
359
+ );
360
+ continue;
361
+ }
362
+
363
+ // Legacy guardian persona (prompts/USER.md) is translated to the
364
+ // current guardian's users/<slug>.md by DefaultPathResolver. If the
365
+ // bundle ships USER.md as a symlink and the target already holds
366
+ // user-authored content, skip rather than clobber — mirrors the
367
+ // protection in the regular-file branch below.
368
+ if (
369
+ policy.isLegacyPersonaArchivePath(fileEntry.path) &&
370
+ isGuardianPersonaCustomized(diskPath)
371
+ ) {
372
+ log.warn(
373
+ { archivePath: fileEntry.path, diskPath },
374
+ "Skipping legacy prompts/USER.md symlink import: guardian persona is already customized",
375
+ );
376
+ warnings.push(
377
+ `Skipped "${fileEntry.path}": guardian persona at "${diskPath}" is already customized`,
378
+ );
379
+ importedFiles.push({
380
+ path: fileEntry.path,
381
+ disk_path: diskPath,
382
+ action: "skipped",
383
+ size: 0,
384
+ sha256: fileEntry.sha256,
385
+ backup_path: null,
386
+ });
387
+ continue;
388
+ }
389
+
390
+ // Defense-in-depth path-traversal gate.
391
+ if (
392
+ fileEntry.link_target.startsWith("/") ||
393
+ isOutsideWorkspace(diskPath, fileEntry.link_target, workspaceDir)
394
+ ) {
395
+ importedFiles.push({
396
+ path: fileEntry.path,
397
+ disk_path: diskPath,
398
+ action: "skipped",
399
+ size: 0,
400
+ sha256: fileEntry.sha256,
401
+ backup_path: null,
402
+ });
403
+ warnings.push(
404
+ `Skipped "${fileEntry.path}": symlink target "${fileEntry.link_target}" escapes workspace`,
405
+ );
406
+ continue;
407
+ }
408
+
409
+ // Back up an existing entry at diskPath, if any. Use `lstatSync` so we
410
+ // detect a pre-existing dangling symlink (which `existsSync` reports
411
+ // as missing) — `symlinkSync` would otherwise fail with EEXIST. For
412
+ // regular files and resolvable symlinks we copy the file contents into
413
+ // the backup, matching the existing contract; for dangling symlinks
414
+ // we preserve the linkname via `readlinkSync`+`symlinkSync` so the
415
+ // original entry can be inspected after the import. The pre-existing
416
+ // entry is removed before `symlinkSync` so the new symlink can land.
417
+ let backupPath: string | null = null;
418
+ let action: ImportFileAction;
419
+ let preExistingEntry = false;
420
+ let preExistingIsSymlink = false;
421
+ try {
422
+ const stats = lstatSync(diskPath);
423
+ preExistingEntry = true;
424
+ preExistingIsSymlink = stats.isSymbolicLink();
425
+ } catch {
426
+ // ENOENT — no pre-existing entry at this path.
427
+ }
428
+ if (preExistingEntry) {
429
+ backupPath = generateBackupPath(diskPath);
430
+ try {
431
+ if (preExistingIsSymlink) {
432
+ const oldTarget = readlinkSync(diskPath);
433
+ symlinkSync(oldTarget, backupPath);
434
+ } else {
435
+ copyFileSync(diskPath, backupPath);
436
+ }
437
+ backupsCreated++;
438
+ } catch (err) {
439
+ return {
440
+ ok: false,
441
+ reason: "write_failed",
442
+ message: `Failed to back up "${diskPath}": ${
443
+ err instanceof Error ? err.message : String(err)
444
+ }`,
445
+ partial_report: buildPartialReport(
446
+ importedFiles,
447
+ manifest,
448
+ warnings,
449
+ backupsCreated,
450
+ ),
451
+ };
452
+ }
453
+ action = "overwritten";
454
+ try {
455
+ rmSync(diskPath, { force: true });
456
+ } catch {
457
+ /* best effort — symlinkSync below will surface the real error */
458
+ }
459
+ } else {
460
+ action = "created";
461
+ }
462
+
463
+ // Ensure parent directory exists.
464
+ const parentDir = dirname(diskPath);
465
+ if (!existsSync(parentDir)) {
466
+ try {
467
+ mkdirSync(parentDir, { recursive: true });
468
+ } catch (err) {
469
+ return {
470
+ ok: false,
471
+ reason: "write_failed",
472
+ message: `Failed to create directory "${parentDir}": ${
473
+ err instanceof Error ? err.message : String(err)
474
+ }`,
475
+ partial_report: buildPartialReport(
476
+ importedFiles,
477
+ manifest,
478
+ warnings,
479
+ backupsCreated,
480
+ ),
481
+ };
482
+ }
483
+ }
484
+
485
+ // Create the symlink. The target is stored verbatim — OS symlink
486
+ // semantics resolve it relative to the symlink's own directory at
487
+ // use time.
488
+ try {
489
+ symlinkSync(fileEntry.link_target, diskPath);
490
+ } catch (err) {
491
+ return {
492
+ ok: false,
493
+ reason: "write_failed",
494
+ message: `Failed to create symlink "${diskPath}" -> "${fileEntry.link_target}": ${
495
+ err instanceof Error ? err.message : String(err)
496
+ }`,
497
+ partial_report: buildPartialReport(
498
+ importedFiles,
499
+ manifest,
500
+ warnings,
501
+ backupsCreated,
502
+ ),
503
+ };
504
+ }
505
+
506
+ importedFiles.push({
507
+ path: fileEntry.path,
508
+ disk_path: diskPath,
509
+ action,
510
+ size: 0,
511
+ sha256: fileEntry.sha256,
512
+ backup_path: backupPath,
513
+ });
514
+ // Skip the regular-file branches (and the post-write integrity check,
515
+ // which would dereference the symlink and read the target's bytes).
516
+ continue;
517
+ }
518
+
342
519
  const archiveEntry = entryMap.get(fileEntry.path);
343
520
  if (!archiveEntry) {
344
521
  // File declared in manifest but not found in archive — should not
@@ -363,7 +540,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
363
540
  // than clobber — the user has curated their persona since the
364
541
  // bundle was exported.
365
542
  if (
366
- fileEntry.path === LEGACY_USER_MD_ARCHIVE_PATH &&
543
+ policy.isLegacyPersonaArchivePath(fileEntry.path) &&
367
544
  isGuardianPersonaCustomized(diskPath)
368
545
  ) {
369
546
  log.warn(
@@ -438,7 +615,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
438
615
 
439
616
  // Sanitize config files to strip environment-specific fields (defense-in-depth)
440
617
  let dataToWrite: Uint8Array = archiveEntry.data;
441
- if (CONFIG_ARCHIVE_PATHS.has(fileEntry.path)) {
618
+ if (policy.isConfigArchivePath(fileEntry.path)) {
442
619
  const configJson = new TextDecoder().decode(archiveEntry.data);
443
620
  const sanitized = sanitizeConfigForTransfer(configJson);
444
621
  dataToWrite = new TextEncoder().encode(sanitized);
@@ -450,7 +627,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
450
627
  // would wipe them and break the gateway's vellum credential read.
451
628
  // We use the snapshot captured BEFORE the workspace clear because
452
629
  // Step 1b may have already removed the live file.
453
- if (fileEntry.path === CREDENTIAL_METADATA_ARCHIVE_PATH) {
630
+ if (policy.isCredentialMetadataArchivePath(fileEntry.path)) {
454
631
  const bundleJson = new TextDecoder().decode(archiveEntry.data);
455
632
  const merged = mergeMetadataPreservingVellum(
456
633
  bundleJson,
@@ -536,8 +713,8 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
536
713
  // run (e.g. workspaceDir unset) the live metadata.json is still on
537
714
  // disk untouched — we must not rewrite it here or we would drop the
538
715
  // non-vellum entries the caller chose to keep.
539
- const bundleHadMetadata = manifest.contents.some(
540
- (f) => f.path === CREDENTIAL_METADATA_ARCHIVE_PATH,
716
+ const bundleHadMetadata = manifest.contents.some((f) =>
717
+ policy.isCredentialMetadataArchivePath(f.path),
541
718
  );
542
719
  if (
543
720
  workspaceWasCleared &&