@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,413 @@
1
+ /**
2
+ * Buffer-vs-streaming `.vbundle` import parity suite.
3
+ *
4
+ * Pins the existing disk-outcome equivalence between `commitImport`
5
+ * (buffer-based) and `streamCommitImport` (streaming) as a regression net
6
+ * BEFORE any production code is migrated to a shared policy module.
7
+ *
8
+ * Each test builds one archive, mkdtemps two sibling workspaces, seeds them
9
+ * identically, runs each importer against its own workspace, and asserts the
10
+ * post-import disk trees are byte-for-byte identical:
11
+ *
12
+ * expect(walkDiskTree(streamWs)).toEqual(walkDiskTree(bufferWs))
13
+ *
14
+ * Per-case invariants (carry-forward markers survive, persona lands at the
15
+ * right disk path, traversal entries do not erase the workspace, etc.) are
16
+ * asserted on top of disk-tree equality.
17
+ */
18
+
19
+ import { createHash } from "node:crypto";
20
+ import {
21
+ existsSync,
22
+ mkdirSync,
23
+ mkdtempSync,
24
+ readdirSync,
25
+ readFileSync,
26
+ realpathSync,
27
+ rmSync,
28
+ writeFileSync,
29
+ } from "node:fs";
30
+ import { tmpdir } from "node:os";
31
+ import { dirname, join, relative } from "node:path";
32
+ import { Readable } from "node:stream";
33
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
34
+
35
+ import { buildVBundle } from "../vbundle-builder.js";
36
+ import { DefaultPathResolver } from "../vbundle-import-analyzer.js";
37
+ import { commitImport } from "../vbundle-importer.js";
38
+ import { streamCommitImport } from "../vbundle-streaming-importer.js";
39
+ import { defaultV1Options } from "./v1-test-helpers.js";
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Shared helpers
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Allocate a fresh workspace path under a temp parent dir we own. The parent
47
+ * is realpath-resolved so macOS `/var` → `/private/var` symlink mismatches
48
+ * don't trip the streaming importer's `rebaseOntoTempWorkspace` containment
49
+ * check (which compares `resolve(diskPath)` against `resolve(workspaceDir)`).
50
+ *
51
+ * Returns `<parent>/workspace`. The directory itself is NOT created — tests
52
+ * that need it pre-existing call `mkdirSync` themselves.
53
+ */
54
+ function freshWorkspace(): string {
55
+ const parent = realpathSync(
56
+ mkdtempSync(join(tmpdir(), "vbundle-import-parity-")),
57
+ );
58
+ return join(parent, "workspace");
59
+ }
60
+
61
+ /** Best-effort cleanup of a workspace's parent dir. */
62
+ function cleanupWorkspaceParent(workspaceDir: string): void {
63
+ try {
64
+ rmSync(join(workspaceDir, ".."), { recursive: true, force: true });
65
+ } catch {
66
+ // best-effort
67
+ }
68
+ }
69
+
70
+ function sha256Hex(data: Uint8Array): string {
71
+ return createHash("sha256").update(data).digest("hex");
72
+ }
73
+
74
+ /**
75
+ * Recursively walk `root` and return a `Map<relPath, sha256Hex>` for every
76
+ * regular file. Skips dot-prefixed scratch dirs the streaming importer may
77
+ * leave behind on failure paths (`.import-*`, `.pre-import-*`) plus the
78
+ * import marker file — the buffer importer never produces these, so they'd
79
+ * spuriously break parity if included.
80
+ *
81
+ * Two importers are parity-equivalent for a given input iff the maps they
82
+ * produce on identically-seeded sibling workspaces are equal.
83
+ */
84
+ function walkDiskTree(root: string): Map<string, string> {
85
+ const out = new Map<string, string>();
86
+ if (!existsSync(root)) return out;
87
+
88
+ const stack: string[] = [root];
89
+ while (stack.length > 0) {
90
+ const dir = stack.pop()!;
91
+ let entries;
92
+ try {
93
+ entries = readdirSync(dir, { withFileTypes: true });
94
+ } catch {
95
+ continue;
96
+ }
97
+ for (const entry of entries) {
98
+ // Skip streaming-importer scratch artifacts so they don't show up as
99
+ // false negatives in the parity comparison.
100
+ if (
101
+ entry.name.startsWith(".import-") ||
102
+ entry.name.startsWith(".pre-import-") ||
103
+ entry.name === ".import-marker.json"
104
+ ) {
105
+ continue;
106
+ }
107
+ const abs = join(dir, entry.name);
108
+ if (entry.isDirectory()) {
109
+ stack.push(abs);
110
+ } else if (entry.isFile()) {
111
+ const rel = relative(root, abs);
112
+ out.set(rel, sha256Hex(readFileSync(abs)));
113
+ }
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+
119
+ interface SeedFile {
120
+ relPath: string;
121
+ content: string | Uint8Array;
122
+ }
123
+
124
+ /** Mkdir parents and write each file to `workspaceDir`. */
125
+ function seedLiveWorkspace(workspaceDir: string, files: SeedFile[]): void {
126
+ mkdirSync(workspaceDir, { recursive: true });
127
+ for (const { relPath, content } of files) {
128
+ const abs = join(workspaceDir, relPath);
129
+ mkdirSync(dirname(abs), { recursive: true });
130
+ writeFileSync(abs, content);
131
+ }
132
+ }
133
+
134
+ function runBufferImport(workspaceDir: string, archive: Uint8Array): void {
135
+ const result = commitImport({
136
+ archiveData: archive,
137
+ pathResolver: new DefaultPathResolver(workspaceDir),
138
+ workspaceDir,
139
+ });
140
+ if (!result.ok) {
141
+ throw new Error(
142
+ `buffer commitImport unexpectedly failed: ${JSON.stringify(result)}`,
143
+ );
144
+ }
145
+ }
146
+
147
+ async function runStreamImport(
148
+ workspaceDir: string,
149
+ archive: Uint8Array,
150
+ importCredentials?: (
151
+ credentials: Array<{ account: string; value: string }>,
152
+ ) => Promise<void>,
153
+ ): Promise<void> {
154
+ const result = await streamCommitImport({
155
+ source: Readable.from([Buffer.from(archive)]),
156
+ pathResolver: new DefaultPathResolver(workspaceDir),
157
+ workspaceDir,
158
+ importCredentials,
159
+ });
160
+ if (!result.ok) {
161
+ throw new Error(
162
+ `streamCommitImport unexpectedly failed: ${JSON.stringify(result)}`,
163
+ );
164
+ }
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Parity tests
169
+ // ---------------------------------------------------------------------------
170
+
171
+ describe("vbundle import parity (buffer vs streaming)", () => {
172
+ let bufferWs: string;
173
+ let streamWs: string;
174
+
175
+ beforeEach(() => {
176
+ bufferWs = freshWorkspace();
177
+ streamWs = freshWorkspace();
178
+ });
179
+
180
+ afterEach(() => {
181
+ cleanupWorkspaceParent(bufferWs);
182
+ cleanupWorkspaceParent(streamWs);
183
+ });
184
+
185
+ test("A — full workspace + credentials: identical disk outcome", async () => {
186
+ const dbBytes = new Uint8Array(16);
187
+ for (let i = 0; i < dbBytes.length; i++) dbBytes[i] = (i * 17) & 0xff;
188
+
189
+ const configJson = JSON.stringify({ version: 1 });
190
+ const metadataJson = JSON.stringify({
191
+ version: 5,
192
+ credentials: [
193
+ {
194
+ credentialId: "id-openai-api_key",
195
+ service: "openai",
196
+ field: "api_key",
197
+ allowedTools: [],
198
+ allowedDomains: [],
199
+ createdAt: 1700000000000,
200
+ updatedAt: 1700000000000,
201
+ },
202
+ ],
203
+ });
204
+
205
+ const openaiKey = new Uint8Array(16);
206
+ for (let i = 0; i < openaiKey.length; i++) openaiKey[i] = (i + 5) & 0xff;
207
+ const anthropicKey = new TextEncoder().encode("sk-ant-test");
208
+
209
+ const { archive } = buildVBundle({
210
+ files: [
211
+ { path: "workspace/data/db/assistant.db", data: dbBytes },
212
+ {
213
+ path: "workspace/config.json",
214
+ data: new TextEncoder().encode(configJson),
215
+ },
216
+ {
217
+ path: "workspace/data/credentials/metadata.json",
218
+ data: new TextEncoder().encode(metadataJson),
219
+ },
220
+ { path: "credentials/openai-key", data: openaiKey },
221
+ { path: "credentials/anthropic-key", data: anthropicKey },
222
+ ],
223
+ ...defaultV1Options(),
224
+ });
225
+
226
+ // Pre-create both workspaces (streaming importer expects to operate
227
+ // against an existing dir and the atomic-swap path requires it).
228
+ mkdirSync(bufferWs, { recursive: true });
229
+ mkdirSync(streamWs, { recursive: true });
230
+
231
+ runBufferImport(bufferWs, archive);
232
+ await runStreamImport(streamWs, archive, async () => {
233
+ // parity test: credentials are intercepted but not persisted
234
+ });
235
+
236
+ const bufferMap = walkDiskTree(bufferWs);
237
+ const streamMap = walkDiskTree(streamWs);
238
+
239
+ expect(streamMap).toEqual(bufferMap);
240
+
241
+ // Sanity: the three workspace-bound files we expect both importers to
242
+ // land are present in the parity map.
243
+ expect(bufferMap.has("data/db/assistant.db")).toBe(true);
244
+ expect(bufferMap.has("config.json")).toBe(true);
245
+ expect(bufferMap.has("data/credentials/metadata.json")).toBe(true);
246
+ });
247
+
248
+ test("B — config-only partial bundle: live preserved paths survive on both sides", async () => {
249
+ const seeds: SeedFile[] = [
250
+ { relPath: "data/db/marker", content: "db-marker" },
251
+ { relPath: "data/qdrant/marker", content: "qdrant-marker" },
252
+ { relPath: "embedding-models/marker", content: "embedding-marker" },
253
+ { relPath: "deprecated/marker", content: "deprecated-marker" },
254
+ ];
255
+
256
+ seedLiveWorkspace(bufferWs, seeds);
257
+ seedLiveWorkspace(streamWs, seeds);
258
+
259
+ const configJson = JSON.stringify({ version: 1 });
260
+ const { archive } = buildVBundle({
261
+ files: [
262
+ // Validator requires the DB entry (legacy or workspace-prefixed).
263
+ // It's not the focus of this case — config.json is — but it must
264
+ // be present for the bundle to validate.
265
+ { path: "data/db/assistant.db", data: new Uint8Array() },
266
+ {
267
+ path: "workspace/config.json",
268
+ data: new TextEncoder().encode(configJson),
269
+ },
270
+ ],
271
+ ...defaultV1Options(),
272
+ });
273
+
274
+ runBufferImport(bufferWs, archive);
275
+ await runStreamImport(streamWs, archive);
276
+
277
+ const bufferMap = walkDiskTree(bufferWs);
278
+ const streamMap = walkDiskTree(streamWs);
279
+
280
+ expect(streamMap).toEqual(bufferMap);
281
+
282
+ // Each carry-forward marker must still be on disk.
283
+ for (const seed of seeds) {
284
+ expect(bufferMap.has(seed.relPath)).toBe(true);
285
+ expect(streamMap.has(seed.relPath)).toBe(true);
286
+ }
287
+ });
288
+
289
+ test("C — legacy prompts/USER.md: both importers land it at users/<slug>.md", async () => {
290
+ const personaBody = `_ Lines starting with _ are comments - they won't appear in the system prompt
291
+
292
+ # User Profile
293
+
294
+ - Preferred name/reference: Captain Parity
295
+ - Pronouns: they/them
296
+ - Locale: en-US
297
+ - Work role: Quartermaster
298
+ - Goals: Verify importer parity
299
+ - Hobbies/fun: Diff'ing trees
300
+ - Daily tools: Terminal
301
+ `;
302
+
303
+ // Pre-create users/ in both workspaces so the resolver's containment
304
+ // check has a real on-disk parent to anchor against.
305
+ mkdirSync(join(bufferWs, "users"), { recursive: true });
306
+ mkdirSync(join(streamWs, "users"), { recursive: true });
307
+
308
+ const { archive } = buildVBundle({
309
+ files: [
310
+ { path: "data/db/assistant.db", data: new Uint8Array() },
311
+ {
312
+ path: "prompts/USER.md",
313
+ data: new TextEncoder().encode(personaBody),
314
+ },
315
+ ],
316
+ ...defaultV1Options(),
317
+ });
318
+
319
+ const bufferGuardianPath = join(bufferWs, "users", "captain.md");
320
+ const streamGuardianPath = join(streamWs, "users", "captain.md");
321
+
322
+ const bufferResolver = new DefaultPathResolver(
323
+ bufferWs,
324
+ undefined,
325
+ () => bufferGuardianPath,
326
+ );
327
+ const streamResolver = new DefaultPathResolver(
328
+ streamWs,
329
+ undefined,
330
+ () => streamGuardianPath,
331
+ );
332
+
333
+ const bufferResult = commitImport({
334
+ archiveData: archive,
335
+ pathResolver: bufferResolver,
336
+ workspaceDir: bufferWs,
337
+ });
338
+ expect(bufferResult.ok).toBe(true);
339
+
340
+ const streamResult = await streamCommitImport({
341
+ source: Readable.from([Buffer.from(archive)]),
342
+ pathResolver: streamResolver,
343
+ workspaceDir: streamWs,
344
+ });
345
+ expect(streamResult.ok).toBe(true);
346
+
347
+ const bufferMap = walkDiskTree(bufferWs);
348
+ const streamMap = walkDiskTree(streamWs);
349
+
350
+ expect(streamMap).toEqual(bufferMap);
351
+
352
+ expect(existsSync(bufferGuardianPath)).toBe(true);
353
+ expect(existsSync(streamGuardianPath)).toBe(true);
354
+ expect(readFileSync(bufferGuardianPath, "utf-8")).toBe(personaBody);
355
+ expect(readFileSync(streamGuardianPath, "utf-8")).toBe(personaBody);
356
+ });
357
+
358
+ test("D — no workspace entries at all: legacy bundle leaves seeded files in place", async () => {
359
+ const seeds: SeedFile[] = [
360
+ { relPath: "unrelated.txt", content: "stay" },
361
+ { relPath: "custom-dir/note.md", content: "# note" },
362
+ ];
363
+
364
+ seedLiveWorkspace(bufferWs, seeds);
365
+ seedLiveWorkspace(streamWs, seeds);
366
+
367
+ const dbBytes = new TextEncoder().encode("legacy-db-payload");
368
+ const { archive } = buildVBundle({
369
+ files: [{ path: "data/db/assistant.db", data: dbBytes }],
370
+ ...defaultV1Options(),
371
+ });
372
+
373
+ runBufferImport(bufferWs, archive);
374
+ await runStreamImport(streamWs, archive);
375
+
376
+ const bufferMap = walkDiskTree(bufferWs);
377
+ const streamMap = walkDiskTree(streamWs);
378
+
379
+ expect(streamMap).toEqual(bufferMap);
380
+
381
+ for (const seed of seeds) {
382
+ expect(bufferMap.has(seed.relPath)).toBe(true);
383
+ expect(streamMap.has(seed.relPath)).toBe(true);
384
+ }
385
+ });
386
+
387
+ test("E — path-traversal workspace entry: both importers refuse to clear", async () => {
388
+ seedLiveWorkspace(bufferWs, [{ relPath: "marker.txt", content: "keep" }]);
389
+ seedLiveWorkspace(streamWs, [{ relPath: "marker.txt", content: "keep" }]);
390
+
391
+ const { archive } = buildVBundle({
392
+ files: [
393
+ { path: "data/db/assistant.db", data: new Uint8Array() },
394
+ {
395
+ path: "workspace/../../etc/passwd",
396
+ data: new TextEncoder().encode("nope"),
397
+ },
398
+ ],
399
+ ...defaultV1Options(),
400
+ });
401
+
402
+ runBufferImport(bufferWs, archive);
403
+ await runStreamImport(streamWs, archive);
404
+
405
+ const bufferMap = walkDiskTree(bufferWs);
406
+ const streamMap = walkDiskTree(streamWs);
407
+
408
+ expect(streamMap).toEqual(bufferMap);
409
+
410
+ expect(bufferMap.has("marker.txt")).toBe(true);
411
+ expect(streamMap.has("marker.txt")).toBe(true);
412
+ });
413
+ });
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Unit tests for the pure policy module shared by both vbundle importers.
3
+ *
4
+ * No `node:fs`, no temp dirs — every test exercises a constant or a
5
+ * predicate over strings.
6
+ */
7
+
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import {
11
+ compareSemver,
12
+ CONFIG_ARCHIVE_PATHS,
13
+ CREDENTIAL_METADATA_ARCHIVE_PATH,
14
+ evaluateRuntimeCompatibility,
15
+ isConfigArchivePath,
16
+ isCredentialMetadataArchivePath,
17
+ isLegacyPersonaArchivePath,
18
+ isWorkspaceNamespacedArchivePath,
19
+ LEGACY_USER_MD_ARCHIVE_PATH,
20
+ partitionWorkspacePreserveSkipDirs,
21
+ WORKSPACE_PRESERVE_PATHS,
22
+ } from "../vbundle-import-policy.js";
23
+
24
+ describe("LEGACY_USER_MD_ARCHIVE_PATH", () => {
25
+ test("equals the legacy guardian persona archive path", () => {
26
+ expect(LEGACY_USER_MD_ARCHIVE_PATH).toBe("prompts/USER.md");
27
+ });
28
+ });
29
+
30
+ describe("CONFIG_ARCHIVE_PATHS", () => {
31
+ test("contains exactly the two known config archive paths", () => {
32
+ expect(CONFIG_ARCHIVE_PATHS.size).toBe(2);
33
+ expect(CONFIG_ARCHIVE_PATHS.has("workspace/config.json")).toBe(true);
34
+ expect(CONFIG_ARCHIVE_PATHS.has("config/settings.json")).toBe(true);
35
+ });
36
+ });
37
+
38
+ describe("CREDENTIAL_METADATA_ARCHIVE_PATH", () => {
39
+ test("equals the workspace-namespaced credential metadata path", () => {
40
+ expect(CREDENTIAL_METADATA_ARCHIVE_PATH).toBe(
41
+ "workspace/data/credentials/metadata.json",
42
+ );
43
+ });
44
+ });
45
+
46
+ describe("WORKSPACE_PRESERVE_PATHS", () => {
47
+ test("matches the literal 4-element ordered list", () => {
48
+ expect(WORKSPACE_PRESERVE_PATHS).toEqual([
49
+ "embedding-models",
50
+ "deprecated",
51
+ "data/db",
52
+ "data/qdrant",
53
+ ]);
54
+ });
55
+ });
56
+
57
+ describe("isWorkspaceNamespacedArchivePath", () => {
58
+ test("true for paths under workspace/", () => {
59
+ expect(isWorkspaceNamespacedArchivePath("workspace/foo")).toBe(true);
60
+ expect(isWorkspaceNamespacedArchivePath("workspace/data/db/x")).toBe(true);
61
+ });
62
+
63
+ test("false for non-workspace paths", () => {
64
+ expect(isWorkspaceNamespacedArchivePath("prompts/USER.md")).toBe(false);
65
+ expect(isWorkspaceNamespacedArchivePath("data/db/assistant.db")).toBe(
66
+ false,
67
+ );
68
+ expect(isWorkspaceNamespacedArchivePath("")).toBe(false);
69
+ expect(isWorkspaceNamespacedArchivePath("workspace")).toBe(false);
70
+ });
71
+ });
72
+
73
+ describe("isLegacyPersonaArchivePath", () => {
74
+ test("true only for the exact legacy path", () => {
75
+ expect(isLegacyPersonaArchivePath("prompts/USER.md")).toBe(true);
76
+ });
77
+
78
+ test("false for near-misses and unrelated paths", () => {
79
+ expect(isLegacyPersonaArchivePath("prompts/USER")).toBe(false);
80
+ expect(isLegacyPersonaArchivePath("workspace/prompts/USER.md")).toBe(false);
81
+ expect(isLegacyPersonaArchivePath("")).toBe(false);
82
+ });
83
+ });
84
+
85
+ describe("isConfigArchivePath", () => {
86
+ test("true for both members of CONFIG_ARCHIVE_PATHS", () => {
87
+ expect(isConfigArchivePath("workspace/config.json")).toBe(true);
88
+ expect(isConfigArchivePath("config/settings.json")).toBe(true);
89
+ });
90
+
91
+ test("false for non-members", () => {
92
+ expect(isConfigArchivePath("workspace/foo.json")).toBe(false);
93
+ expect(isConfigArchivePath("config/settings")).toBe(false);
94
+ expect(isConfigArchivePath("")).toBe(false);
95
+ });
96
+ });
97
+
98
+ describe("isCredentialMetadataArchivePath", () => {
99
+ test("true for the exact constant", () => {
100
+ expect(
101
+ isCredentialMetadataArchivePath(
102
+ "workspace/data/credentials/metadata.json",
103
+ ),
104
+ ).toBe(true);
105
+ });
106
+
107
+ test("false for the legacy non-prefixed form and empty string", () => {
108
+ expect(
109
+ isCredentialMetadataArchivePath("data/credentials/metadata.json"),
110
+ ).toBe(false);
111
+ expect(isCredentialMetadataArchivePath("")).toBe(false);
112
+ });
113
+ });
114
+
115
+ describe("partitionWorkspacePreserveSkipDirs", () => {
116
+ test("splits preserve-paths into top-level vs data-subdir skip sets", () => {
117
+ const { topLevelSkipDirs, dataSubdirSkipDirs } =
118
+ partitionWorkspacePreserveSkipDirs();
119
+
120
+ expect(topLevelSkipDirs.size).toBe(2);
121
+ expect(topLevelSkipDirs.has("embedding-models")).toBe(true);
122
+ expect(topLevelSkipDirs.has("deprecated")).toBe(true);
123
+
124
+ expect(dataSubdirSkipDirs.size).toBe(2);
125
+ expect(dataSubdirSkipDirs.has("db")).toBe(true);
126
+ expect(dataSubdirSkipDirs.has("qdrant")).toBe(true);
127
+ });
128
+ });
129
+
130
+ describe("compareSemver", () => {
131
+ test("0.10.0 > 0.9.0 (numeric, not lexical)", () => {
132
+ expect(compareSemver("0.10.0", "0.9.0")).toBe(1);
133
+ });
134
+
135
+ test("equal triples return 0", () => {
136
+ expect(compareSemver("0.7.1", "0.7.1")).toBe(0);
137
+ });
138
+
139
+ test("smaller patch returns -1", () => {
140
+ expect(compareSemver("0.7.0", "0.7.1")).toBe(-1);
141
+ });
142
+
143
+ test("prerelease tag is stripped (treated as base release)", () => {
144
+ expect(compareSemver("0.7.1-staging.1", "0.7.1")).toBe(0);
145
+ });
146
+
147
+ test("non-version string returns null", () => {
148
+ expect(compareSemver("not-a-version", "0.7.1")).toBe(null);
149
+ });
150
+
151
+ test("two-part version returns null", () => {
152
+ expect(compareSemver("1.2", "0.7.1")).toBe(null);
153
+ });
154
+
155
+ // Regression: Number.parseInt accepts numeric prefixes and ignores
156
+ // trailing junk ("0foo" → 0), which previously coerced malformed
157
+ // triples through the gate. parseSemverTriple now requires each
158
+ // component to match /^\d+$/ exactly.
159
+ test("trailing junk on a component returns null (left arg)", () => {
160
+ expect(compareSemver("0.8.0foo", "0.8.0")).toBe(null);
161
+ });
162
+
163
+ test("trailing junk on a component returns null (right arg)", () => {
164
+ expect(compareSemver("0.8.0", "0.7.1xyz")).toBe(null);
165
+ });
166
+
167
+ // Leading zeros are accepted: "01.02.03" parses to the same numeric
168
+ // triple as "1.2.3" since Number("01") === 1. We pin this behavior
169
+ // so a future tightening doesn't accidentally regress callers that
170
+ // pass zero-padded versions from upstream tooling.
171
+ test("leading zeros parse equal to un-padded triple", () => {
172
+ expect(compareSemver("01.02.03", "1.2.3")).toBe(0);
173
+ });
174
+
175
+ test("leading whitespace returns null", () => {
176
+ expect(compareSemver(" 0.8.0", "0.8.0")).toBe(null);
177
+ });
178
+
179
+ test("negative component returns null", () => {
180
+ expect(compareSemver("0.8.-1", "0.8.0")).toBe(null);
181
+ });
182
+ });
183
+
184
+ describe("evaluateRuntimeCompatibility", () => {
185
+ test("legacy sentinel passes regardless of runtime version", () => {
186
+ expect(
187
+ evaluateRuntimeCompatibility(
188
+ { min_runtime_version: "0.0.0-legacy", max_runtime_version: null },
189
+ "0.7.1",
190
+ ),
191
+ ).toEqual({ ok: true });
192
+ });
193
+
194
+ test("runtime above min with no max passes", () => {
195
+ expect(
196
+ evaluateRuntimeCompatibility(
197
+ { min_runtime_version: "0.7.0", max_runtime_version: null },
198
+ "0.7.1",
199
+ ),
200
+ ).toEqual({ ok: true });
201
+ });
202
+
203
+ test("runtime below min fails with full echo", () => {
204
+ const compat = {
205
+ min_runtime_version: "0.8.0",
206
+ max_runtime_version: null,
207
+ };
208
+ expect(evaluateRuntimeCompatibility(compat, "0.7.1")).toEqual({
209
+ ok: false,
210
+ reason: "version_incompatible",
211
+ bundle_compat: compat,
212
+ runtime_version: "0.7.1",
213
+ });
214
+ });
215
+
216
+ test("runtime above max fails", () => {
217
+ const compat = {
218
+ min_runtime_version: "0.7.0",
219
+ max_runtime_version: "0.7.5",
220
+ };
221
+ expect(evaluateRuntimeCompatibility(compat, "0.8.0")).toEqual({
222
+ ok: false,
223
+ reason: "version_incompatible",
224
+ bundle_compat: compat,
225
+ runtime_version: "0.8.0",
226
+ });
227
+ });
228
+
229
+ test("max is inclusive", () => {
230
+ expect(
231
+ evaluateRuntimeCompatibility(
232
+ { min_runtime_version: "0.7.0", max_runtime_version: "0.7.5" },
233
+ "0.7.5",
234
+ ),
235
+ ).toEqual({ ok: true });
236
+ });
237
+
238
+ test("unparsable min skips the gate (fail-open)", () => {
239
+ expect(
240
+ evaluateRuntimeCompatibility(
241
+ { min_runtime_version: "garbage", max_runtime_version: null },
242
+ "0.7.1",
243
+ ),
244
+ ).toEqual({ ok: true });
245
+ });
246
+
247
+ // Regression for Codex feedback: a malformed min like "0.8.0foo"
248
+ // previously coerced to [0, 8, 0] via Number.parseInt and incorrectly
249
+ // produced a version_incompatible decision against runtime "0.7.1".
250
+ // With strict per-component digit matching, the parse fails and the
251
+ // gate fails open.
252
+ test("malformed min with trailing junk fails open, does not block", () => {
253
+ expect(
254
+ evaluateRuntimeCompatibility(
255
+ { min_runtime_version: "0.8.0foo", max_runtime_version: null },
256
+ "0.7.1",
257
+ ),
258
+ ).toEqual({ ok: true });
259
+ });
260
+ });