@vellumai/assistant 0.4.56 → 0.5.0

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 (457) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +3 -0
  3. package/README.md +11 -11
  4. package/docs/architecture/integrations.md +2 -2
  5. package/docs/architecture/memory.md +3 -4
  6. package/docs/credential-execution-service.md +13 -20
  7. package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +7 -7
  10. package/src/__tests__/anthropic-provider.test.ts +172 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
  12. package/src/__tests__/approval-cascade.test.ts +2 -2
  13. package/src/__tests__/approval-routes-http.test.ts +3 -4
  14. package/src/__tests__/asset-materialize-tool.test.ts +5 -5
  15. package/src/__tests__/asset-search-tool.test.ts +1 -1
  16. package/src/__tests__/assistant-attachments.test.ts +5 -5
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
  19. package/src/__tests__/attachments-store.test.ts +2 -2
  20. package/src/__tests__/avatar-e2e.test.ts +5 -3
  21. package/src/__tests__/browser-skill-endstate.test.ts +0 -1
  22. package/src/__tests__/call-routes-http.test.ts +2 -2
  23. package/src/__tests__/callback-handoff-copy.test.ts +1 -1
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
  25. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  26. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  27. package/src/__tests__/checker.test.ts +31 -32
  28. package/src/__tests__/chrome-cdp.test.ts +47 -18
  29. package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
  30. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  31. package/src/__tests__/config-schema.test.ts +9 -18
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
  34. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
  35. package/src/__tests__/conversation-agent-loop.test.ts +11 -4
  36. package/src/__tests__/conversation-attachments.test.ts +1 -1
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
  38. package/src/__tests__/conversation-error.test.ts +33 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
  40. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  41. package/src/__tests__/conversation-pairing.test.ts +1 -1
  42. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
  44. package/src/__tests__/conversation-queue.test.ts +23 -14
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +204 -185
  47. package/src/__tests__/conversation-seed-composer.test.ts +1 -1
  48. package/src/__tests__/conversation-slash-queue.test.ts +4 -4
  49. package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
  50. package/src/__tests__/conversation-starter-routes.test.ts +291 -0
  51. package/src/__tests__/conversation-wipe.test.ts +438 -0
  52. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
  53. package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
  54. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
  55. package/src/__tests__/credential-security-e2e.test.ts +20 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault-unit.test.ts +227 -0
  58. package/src/__tests__/credentials-cli.test.ts +3 -0
  59. package/src/__tests__/date-context.test.ts +59 -377
  60. package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
  61. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
  62. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  63. package/src/__tests__/encrypted-store.test.ts +249 -15
  64. package/src/__tests__/ephemeral-permissions.test.ts +4 -5
  65. package/src/__tests__/event-bus.test.ts +3 -3
  66. package/src/__tests__/file-read-tool.test.ts +40 -0
  67. package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
  68. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  69. package/src/__tests__/gemini-image-service.test.ts +4 -4
  70. package/src/__tests__/gemini-provider.test.ts +6 -9
  71. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  72. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  73. package/src/__tests__/host-file-read-tool.test.ts +87 -0
  74. package/src/__tests__/host-shell-tool.test.ts +6 -6
  75. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  76. package/src/__tests__/identity-intro-cache.test.ts +209 -0
  77. package/src/__tests__/intent-routing.test.ts +51 -99
  78. package/src/__tests__/invite-routes-http.test.ts +5 -0
  79. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  80. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  81. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  82. package/src/__tests__/media-generate-image.test.ts +32 -15
  83. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  84. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  86. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  87. package/src/__tests__/memory-regressions.test.ts +86 -90
  88. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  89. package/src/__tests__/migration-export-http.test.ts +26 -27
  90. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  91. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  92. package/src/__tests__/migration-validate-http.test.ts +16 -16
  93. package/src/__tests__/model-intents.test.ts +2 -2
  94. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  95. package/src/__tests__/non-member-access-request.test.ts +3 -3
  96. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  97. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  98. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  99. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  100. package/src/__tests__/notification-deep-link.test.ts +1 -1
  101. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  102. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  103. package/src/__tests__/oauth-store.test.ts +1 -3
  104. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  105. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  106. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  107. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  108. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  109. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  110. package/src/__tests__/qdrant-manager.test.ts +7 -7
  111. package/src/__tests__/ratelimit.test.ts +0 -74
  112. package/src/__tests__/recording-handler.test.ts +0 -1
  113. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  115. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  116. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  117. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  118. package/src/__tests__/schema-transforms.test.ts +114 -54
  119. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  120. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  121. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  122. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  123. package/src/__tests__/send-notification-tool.test.ts +2 -2
  124. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  125. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  126. package/src/__tests__/skill-memory.test.ts +549 -0
  127. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  128. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  129. package/src/__tests__/slack-channel-config.test.ts +109 -94
  130. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  131. package/src/__tests__/swarm-recursion.test.ts +2 -2
  132. package/src/__tests__/swarm-tool.test.ts +2 -2
  133. package/src/__tests__/system-prompt.test.ts +19 -66
  134. package/src/__tests__/telegram-config.test.ts +121 -0
  135. package/src/__tests__/terminal-tools.test.ts +1 -1
  136. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  138. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  139. package/src/__tests__/tool-executor.test.ts +1 -1
  140. package/src/__tests__/trace-emitter.test.ts +8 -1
  141. package/src/__tests__/trust-store.test.ts +7 -8
  142. package/src/__tests__/twilio-routes.test.ts +1 -18
  143. package/src/__tests__/user-reference.test.ts +82 -2
  144. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  145. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  146. package/src/approvals/guardian-request-resolvers.ts +3 -3
  147. package/src/avatar/ascii-renderer.ts +2 -2
  148. package/src/avatar/png-renderer.ts +2 -2
  149. package/src/avatar/resvg-lazy.ts +21 -0
  150. package/src/calls/guardian-dispatch.ts +1 -1
  151. package/src/calls/relay-access-wait.ts +2 -2
  152. package/src/calls/twilio-rest.ts +0 -248
  153. package/src/cli/AGENTS.md +5 -8
  154. package/src/cli/__tests__/notifications.test.ts +5 -5
  155. package/src/cli/commands/avatar.ts +64 -2
  156. package/src/cli/commands/conversations.ts +131 -1
  157. package/src/cli/commands/credentials.ts +2 -0
  158. package/src/cli/commands/notifications.ts +3 -3
  159. package/src/cli.ts +10 -0
  160. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  161. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  162. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  163. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  164. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  165. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  166. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  167. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  168. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  169. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  170. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  171. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  172. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  173. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  174. package/src/config/bundled-skills/document/SKILL.md +4 -4
  175. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  176. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  177. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  178. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  179. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  180. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  181. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  182. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  183. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  184. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  185. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  186. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  187. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  188. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  189. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  190. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  192. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  193. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  195. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  196. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  197. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  198. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  199. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  200. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  201. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  202. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  203. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  204. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  205. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  206. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  207. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  208. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  209. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  210. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  211. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  212. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  213. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  214. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  215. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  216. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  217. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  218. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  219. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  220. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  221. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  222. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  223. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  224. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  225. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  226. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  227. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  228. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  229. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  230. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  231. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  232. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  233. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  234. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  235. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  236. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  237. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  238. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  240. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  241. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  242. package/src/config/feature-flag-registry.json +33 -17
  243. package/src/config/schemas/sandbox.ts +1 -1
  244. package/src/config/schemas/services.ts +13 -3
  245. package/src/config/schemas/timeouts.ts +0 -10
  246. package/src/contacts/contact-store.ts +63 -0
  247. package/src/contacts/contacts-write.ts +1 -1
  248. package/src/daemon/assistant-attachments.ts +2 -2
  249. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  250. package/src/daemon/conversation-agent-loop.ts +7 -30
  251. package/src/daemon/conversation-error.ts +24 -0
  252. package/src/daemon/conversation-memory.ts +8 -7
  253. package/src/daemon/conversation-runtime-assembly.ts +141 -275
  254. package/src/daemon/conversation-slash.ts +7 -26
  255. package/src/daemon/conversation-surfaces.ts +14 -0
  256. package/src/daemon/conversation-tool-setup.ts +9 -8
  257. package/src/daemon/conversation.ts +2 -0
  258. package/src/daemon/daemon-control.ts +1 -1
  259. package/src/daemon/date-context.ts +10 -83
  260. package/src/daemon/handlers/config-channels.ts +12 -2
  261. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  262. package/src/daemon/handlers/config-telegram.ts +6 -1
  263. package/src/daemon/handlers/conversations.ts +2 -2
  264. package/src/daemon/handlers/skills.ts +4 -0
  265. package/src/daemon/lifecycle.ts +28 -4
  266. package/src/daemon/providers-setup.ts +1 -1
  267. package/src/daemon/server.ts +1 -5
  268. package/src/daemon/shutdown-handlers.ts +9 -3
  269. package/src/daemon/tool-side-effects.ts +40 -0
  270. package/src/daemon/trace-emitter.ts +26 -2
  271. package/src/events/domain-events.ts +1 -1
  272. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  273. package/src/inbound/platform-callback-registration.ts +0 -18
  274. package/src/media/app-icon-generator.ts +15 -8
  275. package/src/media/avatar-router.ts +15 -8
  276. package/src/media/gemini-image-service.ts +125 -21
  277. package/src/memory/attachments-store.ts +3 -3
  278. package/src/memory/channel-verification-sessions.ts +6 -6
  279. package/src/memory/conversation-crud.ts +196 -1
  280. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  281. package/src/memory/conversation-title-service.ts +2 -3
  282. package/src/memory/db-init.ts +25 -1
  283. package/src/memory/invite-store.ts +4 -4
  284. package/src/memory/items-extractor.ts +4 -4
  285. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  286. package/src/memory/jobs-store.ts +3 -2
  287. package/src/memory/jobs-worker.ts +7 -5
  288. package/src/memory/lifecycle-events-store.ts +63 -0
  289. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  290. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  291. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  292. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  293. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  294. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  295. package/src/memory/migrations/index.ts +6 -0
  296. package/src/memory/migrations/registry.ts +13 -0
  297. package/src/memory/retriever.test.ts +223 -96
  298. package/src/memory/retriever.ts +115 -138
  299. package/src/memory/schema/calls.ts +1 -1
  300. package/src/memory/schema/contacts.ts +1 -1
  301. package/src/memory/schema/infrastructure.ts +29 -0
  302. package/src/memory/schema/memory-core.ts +7 -17
  303. package/src/memory/schema/notifications.ts +1 -1
  304. package/src/memory/search/formatting.ts +23 -6
  305. package/src/memory/search/lexical.ts +2 -0
  306. package/src/memory/search/semantic.ts +2 -0
  307. package/src/memory/search/staleness.ts +5 -1
  308. package/src/memory/search/types.ts +4 -0
  309. package/src/memory/task-memory-cleanup.ts +96 -6
  310. package/src/memory/trace-event-store.ts +148 -0
  311. package/src/notifications/README.md +1 -1
  312. package/src/notifications/decision-engine.ts +45 -4
  313. package/src/notifications/emit-signal.ts +5 -4
  314. package/src/notifications/events-store.ts +4 -4
  315. package/src/notifications/signal.ts +1 -1
  316. package/src/oauth/manual-token-connection.ts +49 -25
  317. package/src/permissions/checker.ts +6 -5
  318. package/src/permissions/defaults.ts +4 -4
  319. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  320. package/src/prompts/cache-boundary.ts +8 -0
  321. package/src/prompts/system-prompt.ts +105 -634
  322. package/src/prompts/templates/BOOTSTRAP.md +172 -33
  323. package/src/prompts/templates/IDENTITY.md +8 -24
  324. package/src/prompts/templates/SOUL.md +20 -41
  325. package/src/prompts/templates/USER.md +3 -19
  326. package/src/prompts/user-reference.ts +14 -16
  327. package/src/providers/anthropic/client.ts +51 -19
  328. package/src/providers/gemini/client.ts +6 -9
  329. package/src/providers/managed-proxy/constants.ts +1 -7
  330. package/src/providers/managed-proxy/context.ts +0 -1
  331. package/src/providers/model-intents.ts +5 -5
  332. package/src/providers/openai/client.ts +10 -1
  333. package/src/providers/openrouter/client.ts +1 -0
  334. package/src/providers/ratelimit.ts +0 -35
  335. package/src/providers/registry.ts +3 -5
  336. package/src/providers/retry.ts +18 -1
  337. package/src/runtime/access-request-helper.ts +16 -2
  338. package/src/runtime/auth/route-policy.ts +7 -0
  339. package/src/runtime/channel-verification-service.ts +1 -1
  340. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  341. package/src/runtime/guardian-vellum-migration.ts +61 -1
  342. package/src/runtime/http-server.ts +8 -4
  343. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  344. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  345. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  346. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  347. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  348. package/src/runtime/routes/attachment-routes.ts +2 -2
  349. package/src/runtime/routes/btw-routes.ts +93 -0
  350. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  351. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  352. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-routes.ts +49 -5
  354. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  355. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  356. package/src/runtime/routes/identity-intro-cache.ts +105 -0
  357. package/src/runtime/routes/identity-routes.ts +51 -0
  358. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  359. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  360. package/src/runtime/routes/migration-routes.ts +25 -13
  361. package/src/runtime/routes/secret-routes.ts +18 -0
  362. package/src/runtime/routes/settings-routes.ts +9 -9
  363. package/src/runtime/routes/telemetry-routes.ts +53 -0
  364. package/src/runtime/routes/trace-event-routes.ts +62 -0
  365. package/src/runtime/tool-grant-request-helper.ts +1 -1
  366. package/src/runtime/verification-outbound-actions.ts +47 -31
  367. package/src/security/encrypted-store.ts +262 -78
  368. package/src/skills/catalog-install.ts +10 -0
  369. package/src/skills/managed-store.ts +2 -0
  370. package/src/skills/skill-memory.ts +222 -0
  371. package/src/subagent/manager.ts +1 -4
  372. package/src/telemetry/types.ts +10 -1
  373. package/src/telemetry/usage-telemetry-reporter.test.ts +7 -2
  374. package/src/telemetry/usage-telemetry-reporter.ts +53 -4
  375. package/src/tools/AGENTS.md +11 -11
  376. package/src/tools/acp/spawn.ts +1 -1
  377. package/src/tools/apps/executors.ts +8 -8
  378. package/src/tools/apps/registry.ts +1 -1
  379. package/src/tools/assets/materialize.ts +6 -6
  380. package/src/tools/assets/search.ts +10 -10
  381. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  382. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  383. package/src/tools/browser/auth-detector.ts +6 -6
  384. package/src/tools/browser/browser-execution.ts +13 -13
  385. package/src/tools/browser/browser-manager.ts +3 -3
  386. package/src/tools/browser/chrome-cdp.ts +5 -5
  387. package/src/tools/browser/jit-auth.ts +2 -2
  388. package/src/tools/browser/network-recorder.test.ts +2 -2
  389. package/src/tools/browser/network-recorder.ts +3 -3
  390. package/src/tools/browser/runtime-check.ts +3 -3
  391. package/src/tools/claude-code/claude-code.ts +2 -2
  392. package/src/tools/computer-use/definitions.ts +18 -18
  393. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  394. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  395. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  396. package/src/tools/credentials/broker-types.ts +5 -5
  397. package/src/tools/credentials/broker.ts +15 -15
  398. package/src/tools/credentials/metadata-store.ts +2 -2
  399. package/src/tools/credentials/resolve.ts +1 -1
  400. package/src/tools/credentials/selection.ts +1 -1
  401. package/src/tools/credentials/tool-policy.ts +1 -1
  402. package/src/tools/credentials/vault.ts +115 -25
  403. package/src/tools/execution-target.ts +2 -2
  404. package/src/tools/executor.ts +7 -7
  405. package/src/tools/filesystem/edit.ts +2 -2
  406. package/src/tools/filesystem/read.ts +15 -4
  407. package/src/tools/filesystem/write.ts +1 -1
  408. package/src/tools/host-filesystem/edit.ts +2 -1
  409. package/src/tools/host-filesystem/read.ts +18 -1
  410. package/src/tools/host-filesystem/write.ts +1 -1
  411. package/src/tools/host-terminal/host-shell.ts +9 -8
  412. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  413. package/src/tools/memory/definitions.ts +6 -5
  414. package/src/tools/memory/handlers.test.ts +1 -1
  415. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  416. package/src/tools/network/domain-normalize.ts +2 -2
  417. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  418. package/src/tools/network/web-fetch.ts +1 -1
  419. package/src/tools/network/web-search.ts +3 -3
  420. package/src/tools/permission-checker.ts +8 -8
  421. package/src/tools/registry.ts +7 -7
  422. package/src/tools/schedule/list.ts +2 -2
  423. package/src/tools/schema-transforms.ts +31 -21
  424. package/src/tools/secret-detection-handler.ts +1 -1
  425. package/src/tools/sensitive-output-placeholders.ts +1 -1
  426. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  427. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  428. package/src/tools/shared/filesystem/image-read.ts +25 -5
  429. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  430. package/src/tools/shared/shell-output.ts +1 -1
  431. package/src/tools/side-effects.ts +1 -1
  432. package/src/tools/skills/execute.ts +1 -1
  433. package/src/tools/skills/load.ts +3 -3
  434. package/src/tools/skills/sandbox-runner.ts +3 -3
  435. package/src/tools/subagent/read.ts +1 -1
  436. package/src/tools/subagent/spawn.ts +2 -2
  437. package/src/tools/swarm/delegate.ts +3 -3
  438. package/src/tools/system/request-permission.ts +5 -4
  439. package/src/tools/terminal/backends/native.ts +4 -4
  440. package/src/tools/terminal/parser.ts +6 -6
  441. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  442. package/src/tools/terminal/shell.ts +16 -16
  443. package/src/tools/tool-approval-handler.ts +21 -12
  444. package/src/tools/tool-manifest.ts +4 -4
  445. package/src/tools/types.ts +3 -3
  446. package/src/tools/ui-surface/definitions.ts +9 -37
  447. package/src/tools/watcher/list.ts +1 -1
  448. package/src/util/logger.ts +7 -2
  449. package/src/util/pricing.ts +4 -0
  450. package/src/util/retry.ts +29 -1
  451. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  452. package/src/workspace/migrations/registry.ts +2 -0
  453. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  454. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  455. package/src/cli/reference.ts +0 -38
  456. package/src/memory/job-handlers/capability-cards.ts +0 -420
  457. package/src/runtime/routes/thread-starter-routes.ts +0 -294
@@ -67,8 +67,8 @@ import { and, eq } from "drizzle-orm";
67
67
  import { DEFAULT_CONFIG } from "../config/defaults.js";
68
68
  import { vectorToBlob } from "../memory/job-utils.js";
69
69
 
70
- // Disable LLM extraction in tests to avoid real API calls and ensure
71
- // deterministic pattern-based extraction.
70
+ // Disable LLM extraction and summarization in tests to avoid real API calls
71
+ // and ensure deterministic pattern-based extraction / fallback summaries.
72
72
  const TEST_CONFIG = {
73
73
  ...DEFAULT_CONFIG,
74
74
  memory: {
@@ -77,6 +77,10 @@ const TEST_CONFIG = {
77
77
  ...DEFAULT_CONFIG.memory.extraction,
78
78
  useLLM: false,
79
79
  },
80
+ summarization: {
81
+ ...DEFAULT_CONFIG.memory.summarization,
82
+ useLLM: false,
83
+ },
80
84
  },
81
85
  };
82
86
 
@@ -86,6 +90,7 @@ mock.module("../config/loader.js", () => ({
86
90
  invalidateConfigCache: () => {},
87
91
  }));
88
92
  import { estimateTextTokens } from "../context/token-estimator.js";
93
+ import { stripUserTextBlocksByPrefix } from "../daemon/conversation-runtime-assembly.js";
89
94
  import {
90
95
  getMemorySystemStatus,
91
96
  requestMemoryBackfill,
@@ -120,8 +125,7 @@ import {
120
125
  escapeXmlTags,
121
126
  formatAbsoluteTime,
122
127
  formatRelativeTime,
123
- injectMemoryRecallAsSeparateMessage,
124
- stripMemoryRecallMessages,
128
+ injectMemoryRecallAsUserBlock,
125
129
  } from "../memory/retriever.js";
126
130
  import {
127
131
  conversations,
@@ -133,6 +137,7 @@ import {
133
137
  messages,
134
138
  } from "../memory/schema.js";
135
139
  import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
140
+ import type { Message } from "../providers/types.js";
136
141
 
137
142
  describe("Memory regressions", () => {
138
143
  beforeAll(() => {
@@ -308,133 +313,124 @@ describe("Memory regressions", () => {
308
313
  expect(recall.enabled).toBe(true);
309
314
  });
310
315
 
311
- test("memory recall injection via separate message and stripped from runtime history", () => {
316
+ test("memory recall injection as user block and stripped from runtime history", () => {
312
317
  const memoryRecallText =
313
- "<memory_context>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
314
- const originalMessages = [
318
+ "<memory_context __injected>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
319
+ const originalMessages: Message[] = [
315
320
  {
316
- role: "user" as const,
317
- content: [{ type: "text", text: "Actual user request" }],
321
+ role: "user",
322
+ content: [{ type: "text" as const, text: "Actual user request" }],
318
323
  },
319
324
  ];
320
- const injected = injectMemoryRecallAsSeparateMessage(
325
+ const injected = injectMemoryRecallAsUserBlock(
321
326
  originalMessages,
322
327
  memoryRecallText,
323
328
  );
324
329
 
325
- expect(injected).toHaveLength(3);
330
+ // Memory context prepended to last user message as content block
331
+ expect(injected).toHaveLength(1);
326
332
  expect(injected[0].role).toBe("user");
327
- expect(injected[0].content[0].text).toBe(memoryRecallText);
328
- expect(injected[1].role as string).toBe("assistant");
329
- expect(injected[2].role).toBe("user");
330
- expect(injected[2].content[0].text).toBe("Actual user request");
331
-
332
- const cleaned = stripMemoryRecallMessages(injected, memoryRecallText);
333
+ expect(injected[0].content).toHaveLength(2);
334
+ const b0 = injected[0].content[0];
335
+ const b1 = injected[0].content[1];
336
+ expect(b0.type === "text" && b0.text).toBe(memoryRecallText);
337
+ expect(b1.type === "text" && b1.text).toBe("Actual user request");
338
+
339
+ // Stripped by prefix-based stripping
340
+ const cleaned = stripUserTextBlocksByPrefix(injected, [
341
+ "<memory_context __injected>",
342
+ ]);
333
343
  expect(cleaned).toHaveLength(1);
334
- expect(cleaned[0].content[0].text).toBe("Actual user request");
344
+ expect(cleaned[0].content).toHaveLength(1);
345
+ const cb0 = cleaned[0].content[0];
346
+ expect(cb0.type === "text" && cb0.text).toBe("Actual user request");
335
347
  });
336
348
 
337
- test("recall stripping removes last matching block in merged content after deep-repair", () => {
349
+ test("prefix-based stripping removes all <memory_context> blocks from merged content", () => {
338
350
  const memoryRecallText =
339
- "[Memory Recall v1]\n- [item:abc] user prefers concise answers";
340
- // Simulate deep-repair merging two consecutive user messages where both
341
- // contain the recall text. The injected (active) recall block is the last one.
342
- const mergedUserMessage = {
343
- role: "user" as const,
351
+ "<memory_context __injected>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
352
+ // Simulate deep-repair merging where multiple memory context blocks exist.
353
+ // Prefix-based stripping removes all blocks starting with <memory_context __injected>.
354
+ const mergedUserMessage: Message = {
355
+ role: "user",
344
356
  content: [
345
- { type: "text", text: memoryRecallText },
346
- { type: "text", text: "Earlier user request" },
347
- { type: "text", text: memoryRecallText },
348
- { type: "text", text: "Latest user request" },
357
+ { type: "text" as const, text: memoryRecallText },
358
+ { type: "text" as const, text: "Earlier user request" },
359
+ { type: "text" as const, text: memoryRecallText },
360
+ { type: "text" as const, text: "Latest user request" },
349
361
  ],
350
362
  };
351
363
 
352
- const cleaned = stripMemoryRecallMessages(
364
+ const cleaned = stripUserTextBlocksByPrefix(
353
365
  [mergedUserMessage],
354
- memoryRecallText,
366
+ ["<memory_context __injected>"],
355
367
  );
356
368
  expect(cleaned).toHaveLength(1);
357
- // The last (active) recall block should be stripped, the first (leaked) one preserved
358
369
  expect(cleaned[0].content).toEqual([
359
- { type: "text", text: memoryRecallText },
360
370
  { type: "text", text: "Earlier user request" },
361
371
  { type: "text", text: "Latest user request" },
362
372
  ]);
363
373
  });
364
374
 
365
- test("separate_context_message injects memory as user+assistant pair before last user message", () => {
366
- const history = [
367
- { role: "user" as const, content: [{ type: "text", text: "Hello" }] },
368
- { role: "assistant" as const, content: [{ type: "text", text: "Hi!" }] },
375
+ test("injectMemoryRecallAsUserBlock prepends memory to last user message", () => {
376
+ const history: Message[] = [
377
+ { role: "user", content: [{ type: "text" as const, text: "Hello" }] },
378
+ { role: "assistant", content: [{ type: "text" as const, text: "Hi!" }] },
369
379
  {
370
- role: "user" as const,
371
- content: [{ type: "text", text: "Tell me about X" }],
380
+ role: "user",
381
+ content: [{ type: "text" as const, text: "Tell me about X" }],
372
382
  },
373
383
  ];
374
- const recallText = "<memory>Some recalled fact</memory>";
375
- const result = injectMemoryRecallAsSeparateMessage(history, recallText);
376
- // Should have 5 messages: original 2 + injected user + injected assistant ack + original last user
377
- expect(result).toHaveLength(5);
384
+ const recallText =
385
+ "<memory_context __injected>\n\n<relevant_context>\nSome recalled fact\n</relevant_context>\n\n</memory_context>";
386
+ const result = injectMemoryRecallAsUserBlock(history, recallText);
387
+ // Same number of messages — no synthetic pair
388
+ expect(result).toHaveLength(3);
378
389
  expect(result[0]).toBe(history[0]);
379
390
  expect(result[1]).toBe(history[1]);
380
- // Injected context message
381
- expect(result[2].role).toBe("user");
382
- expect(result[2].content).toEqual([{ type: "text", text: recallText }]);
383
- // Assistant acknowledgment
384
- expect(result[3].role).toBe("assistant");
385
- expect(result[3].content).toEqual([
386
- { type: "text", text: "[Memory context loaded.]" },
387
- ]);
388
- // Original user message preserved unchanged
389
- expect(result[4]).toBe(history[2]);
391
+ // Last user message has memory prepended
392
+ const r0 = result[2].content[0];
393
+ const r1 = result[2].content[1];
394
+ expect(r0.type === "text" && r0.text).toBe(recallText);
395
+ expect(r1.type === "text" && r1.text).toBe("Tell me about X");
390
396
  });
391
397
 
392
- test("separate_context_message with empty text is a no-op", () => {
393
- const history = [
394
- { role: "user" as const, content: [{ type: "text", text: "Hello" }] },
398
+ test("injectMemoryRecallAsUserBlock with empty text is a no-op", () => {
399
+ const history: Message[] = [
400
+ { role: "user", content: [{ type: "text" as const, text: "Hello" }] },
395
401
  ];
396
- const result = injectMemoryRecallAsSeparateMessage(history, " ");
402
+ const result = injectMemoryRecallAsUserBlock(history, " ");
397
403
  expect(result).toBe(history);
398
404
  });
399
405
 
400
- test("stripMemoryRecallMessages removes separate_context_message pair", () => {
401
- const recallText = "<memory>Some recalled fact</memory>";
402
- const messages = [
403
- { role: "user" as const, content: [{ type: "text", text: "Hello" }] },
404
- { role: "assistant" as const, content: [{ type: "text", text: "Hi!" }] },
405
- // Injected context message pair
406
- { role: "user" as const, content: [{ type: "text", text: recallText }] },
406
+ test("stripUserTextBlocksByPrefix removes memory_context block from user message", () => {
407
+ const recallText =
408
+ "<memory_context __injected>\n\n<relevant_context>\nSome recalled fact\n</relevant_context>\n\n</memory_context>";
409
+ const msgs: Message[] = [
410
+ { role: "user", content: [{ type: "text" as const, text: "Hello" }] },
407
411
  {
408
- role: "assistant" as const,
409
- content: [{ type: "text", text: "[Memory context loaded.]" }],
410
- },
411
- // Real user message
412
- {
413
- role: "user" as const,
414
- content: [{ type: "text", text: "Tell me about X" }],
412
+ role: "assistant",
413
+ content: [{ type: "text" as const, text: "Hi!" }],
415
414
  },
416
- ];
417
- const cleaned = stripMemoryRecallMessages(messages, recallText);
418
- expect(cleaned).toHaveLength(3);
419
- expect(cleaned[0].content[0].text).toBe("Hello");
420
- expect(cleaned[1].content[0].text).toBe("Hi!");
421
- expect(cleaned[2].content[0].text).toBe("Tell me about X");
422
- });
423
-
424
- test("stripMemoryRecallMessages falls back to prepend_user_block when no separate pair found", () => {
425
- const recallText = "<memory>Fact</memory>";
426
- const messages = [
427
415
  {
428
- role: "user" as const,
416
+ role: "user",
429
417
  content: [
430
- { type: "text", text: recallText },
431
- { type: "text", text: "User query" },
418
+ { type: "text" as const, text: recallText },
419
+ { type: "text" as const, text: "Tell me about X" },
432
420
  ],
433
421
  },
434
422
  ];
435
- const cleaned = stripMemoryRecallMessages(messages, recallText);
436
- expect(cleaned).toHaveLength(1);
437
- expect(cleaned[0].content).toEqual([{ type: "text", text: "User query" }]);
423
+ const cleaned = stripUserTextBlocksByPrefix(msgs, [
424
+ "<memory_context __injected>",
425
+ ]);
426
+ expect(cleaned).toHaveLength(3);
427
+ const c0 = cleaned[0].content[0];
428
+ const c1 = cleaned[1].content[0];
429
+ const c2 = cleaned[2].content[0];
430
+ expect(c0.type === "text" && c0.text).toBe("Hello");
431
+ expect(c1.type === "text" && c1.text).toBe("Hi!");
432
+ expect(cleaned[2].content).toHaveLength(1);
433
+ expect(c2.type === "text" && c2.text).toBe("Tell me about X");
438
434
  });
439
435
 
440
436
  test("aborting memory recall embedding returns a non-degraded aborted recall result", async () => {
@@ -1819,7 +1815,7 @@ describe("Memory regressions", () => {
1819
1815
  totalOutputTokens: 0,
1820
1816
  totalEstimatedCost: 0,
1821
1817
  contextSummary: null,
1822
- contextCompactedMessageCount: 0,
1818
+ contextCompactedMessageCount: 1,
1823
1819
  contextCompactedAt: null,
1824
1820
  })
1825
1821
  .run();
@@ -2064,7 +2060,7 @@ describe("Memory regressions", () => {
2064
2060
  totalOutputTokens: 0,
2065
2061
  totalEstimatedCost: 0,
2066
2062
  contextSummary: null,
2067
- contextCompactedMessageCount: 0,
2063
+ contextCompactedMessageCount: 1,
2068
2064
  contextCompactedAt: null,
2069
2065
  })
2070
2066
  .run();
@@ -2138,7 +2134,7 @@ describe("Memory regressions", () => {
2138
2134
  totalOutputTokens: 0,
2139
2135
  totalEstimatedCost: 0,
2140
2136
  contextSummary: null,
2141
- contextCompactedMessageCount: 0,
2137
+ contextCompactedMessageCount: 1,
2142
2138
  contextCompactedAt: null,
2143
2139
  })
2144
2140
  .run();
@@ -53,13 +53,14 @@ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
53
53
  const testDir = realpathSync(
54
54
  mkdtempSync(join(tmpdir(), "migration-cross-version-test-")),
55
55
  );
56
- const testDbDir = join(testDir, "db");
56
+ const testDbDir = join(testDir, "data", "db");
57
57
  const testDbPath = join(testDbDir, "assistant.db");
58
58
  const testConfigPath = join(testDir, "config.json");
59
59
 
60
60
  mock.module("../util/platform.js", () => ({
61
61
  getRootDir: () => testDir,
62
- getDataDir: () => testDir,
62
+ getDataDir: () => join(testDir, "data"),
63
+ getWorkspaceDir: () => testDir,
63
64
  getWorkspaceConfigPath: () => testConfigPath,
64
65
  isMacOS: () => process.platform === "darwin",
65
66
  isLinux: () => process.platform === "linux",
@@ -83,9 +84,8 @@ mock.module("../config/loader.js", () => ({
83
84
  model: "test",
84
85
  provider: "test",
85
86
  memory: { enabled: false },
86
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
87
+ rateLimit: { maxRequestsPerMinute: 0 },
87
88
  secretDetection: { enabled: false },
88
- sandbox: { enabled: false },
89
89
  }),
90
90
  }));
91
91
 
@@ -445,7 +445,7 @@ describe("schema version compatibility", () => {
445
445
  { schema_version: "3.0" },
446
446
  );
447
447
 
448
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
448
+ const resolver = new DefaultPathResolver(undefined, testDir);
449
449
  const result = commitImport({
450
450
  archiveData: vbundle,
451
451
  pathResolver: resolver,
@@ -463,7 +463,7 @@ describe("schema version compatibility", () => {
463
463
  schema_version: "5.0-beta",
464
464
  });
465
465
 
466
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
466
+ const resolver = new DefaultPathResolver(undefined, testDir);
467
467
  const validationResult = validateVBundle(vbundle);
468
468
  expect(validationResult.manifest).toBeDefined();
469
469
 
@@ -774,9 +774,10 @@ describe("round-trip: export -> validate -> preflight -> import", () => {
774
774
  expect(preflightBody.files).toBeDefined();
775
775
  expect(preflightBody.manifest).toBeDefined();
776
776
 
777
- // The files should show "unchanged" since we exported from the same disk
777
+ // The files should show "unchanged" since we exported from the same disk.
778
+ // New format uses workspace/ prefix for archive paths.
778
779
  const dbFile = preflightBody.files!.find(
779
- (f) => f.path === "data/db/assistant.db",
780
+ (f) => f.path === "workspace/data/db/assistant.db",
780
781
  );
781
782
  expect(dbFile).toBeDefined();
782
783
  expect(dbFile!.action).toBe("unchanged");
@@ -829,7 +830,7 @@ describe("round-trip: export -> validate -> preflight -> import", () => {
829
830
  );
830
831
 
831
832
  // Step 3: Analyze (preflight)
832
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
833
+ const resolver = new DefaultPathResolver(undefined, testDir);
833
834
  const report = analyzeImport({
834
835
  manifest: validationResult.manifest!,
835
836
  pathResolver: resolver,
@@ -888,13 +889,14 @@ describe("partial failure scenarios", () => {
888
889
  { path: "data/db/assistant.db", data: newDbData },
889
890
  ]);
890
891
 
891
- // Point to a path that cannot be written (a non-existent deeply nested
892
- // directory whose parent is a regular file, not a directory)
893
- const blockerPath = join(testDir, "blocker-file");
894
- writeFileSync(blockerPath, "I am a file, not a directory");
895
- const impossibleDbPath = join(blockerPath, "nested", "deep", "db.db");
892
+ // Point to a workspace path where data/db cannot be created
893
+ // (a regular file blocks directory creation)
894
+ const blockerWorkspace = join(testDir, "blocker-workspace");
895
+ mkdirSync(blockerWorkspace, { recursive: true });
896
+ // Create "data" as a regular file — mkdirSync("data/db") will fail
897
+ writeFileSync(join(blockerWorkspace, "data"), "I am a file, not a directory");
896
898
 
897
- const resolver = new DefaultPathResolver(impossibleDbPath, testConfigPath);
899
+ const resolver = new DefaultPathResolver(undefined, blockerWorkspace);
898
900
  const result = commitImport({
899
901
  archiveData: vbundle,
900
902
  pathResolver: resolver,
@@ -908,11 +910,11 @@ describe("partial failure scenarios", () => {
908
910
  }
909
911
 
910
912
  // Clean up
911
- rmSync(blockerPath, { force: true });
913
+ rmSync(blockerWorkspace, { recursive: true, force: true });
912
914
  });
913
915
 
914
916
  test("commitImport with invalid archive returns validation_failed", () => {
915
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
917
+ const resolver = new DefaultPathResolver(undefined, testDir);
916
918
  const result = commitImport({
917
919
  archiveData: new Uint8Array([0xba, 0xad, 0xf0, 0x0d]),
918
920
  pathResolver: resolver,
@@ -1000,7 +1002,7 @@ describe("partial failure scenarios", () => {
1000
1002
  ]);
1001
1003
  const corruptVBundle = gzipSync(tar);
1002
1004
 
1003
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1005
+ const resolver = new DefaultPathResolver(undefined, testDir);
1004
1006
  const result = commitImport({
1005
1007
  archiveData: corruptVBundle,
1006
1008
  pathResolver: resolver,
@@ -1032,7 +1034,7 @@ describe("edge cases", () => {
1032
1034
  expect(result.manifest?.files[0].size).toBe(0);
1033
1035
 
1034
1036
  // Import should also succeed
1035
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1037
+ const resolver = new DefaultPathResolver(undefined, testDir);
1036
1038
  const importResult = commitImport({
1037
1039
  archiveData: vbundle,
1038
1040
  pathResolver: resolver,
@@ -1186,7 +1188,10 @@ describe("edge cases", () => {
1186
1188
  expect(missingError!.message).toContain("data/extra/ghost.bin");
1187
1189
  });
1188
1190
 
1189
- test("bundle with only manifest (no required db) fails validation", () => {
1191
+ test("bundle with only manifest (no data files) is valid", () => {
1192
+ // After the workspace walk refactor, only manifest.json is required.
1193
+ // A bundle with no data files is structurally valid — it just won't
1194
+ // restore anything meaningful.
1190
1195
  const manifestWithoutChecksum = {
1191
1196
  schema_version: "1.0",
1192
1197
  created_at: new Date().toISOString(),
@@ -1205,12 +1210,7 @@ describe("edge cases", () => {
1205
1210
  const vbundle = gzipSync(tar);
1206
1211
  const result = validateVBundle(vbundle);
1207
1212
 
1208
- expect(result.is_valid).toBe(false);
1209
- expect(
1210
- result.errors.some(
1211
- (e) => e.code === "MISSING_ENTRY" && e.path === "data/db/assistant.db",
1212
- ),
1213
- ).toBe(true);
1213
+ expect(result.is_valid).toBe(true);
1214
1214
  });
1215
1215
 
1216
1216
  test("completely empty gzip content (no tar entries) fails gracefully", () => {
@@ -1424,7 +1424,7 @@ describe("diagnostic quality", () => {
1424
1424
  });
1425
1425
 
1426
1426
  test("import commit validation_failed response includes error codes and messages", () => {
1427
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1427
+ const resolver = new DefaultPathResolver(undefined, testDir);
1428
1428
  const result = commitImport({
1429
1429
  archiveData: new Uint8Array([0x00]),
1430
1430
  pathResolver: resolver,
@@ -1633,7 +1633,7 @@ describe("builder -> validator consistency", () => {
1633
1633
 
1634
1634
  describe("import analyzer edge cases", () => {
1635
1635
  test("all-unchanged files produce correct summary", () => {
1636
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1636
+ const resolver = new DefaultPathResolver(undefined, testDir);
1637
1637
  const existingConfig = new Uint8Array(readFileSync(testConfigPath));
1638
1638
 
1639
1639
  const report = analyzeImport({
@@ -1666,8 +1666,8 @@ describe("import analyzer edge cases", () => {
1666
1666
 
1667
1667
  test("all-create scenario (fresh install) produces correct summary", () => {
1668
1668
  const resolver = new DefaultPathResolver(
1669
- join(testDir, "nonexistent-dir", "db.db"),
1670
- join(testDir, "nonexistent-dir", "config.json"),
1669
+ undefined,
1670
+ join(testDir, "nonexistent-workspace"),
1671
1671
  );
1672
1672
 
1673
1673
  const report = analyzeImport({
@@ -1698,7 +1698,7 @@ describe("import analyzer edge cases", () => {
1698
1698
  });
1699
1699
 
1700
1700
  test("mixed create/overwrite/unchanged produces accurate counts", () => {
1701
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1701
+ const resolver = new DefaultPathResolver(undefined, testDir);
1702
1702
 
1703
1703
  // db: different from disk -> overwrite
1704
1704
  // config: same as disk -> unchanged
@@ -1731,7 +1731,7 @@ describe("import analyzer edge cases", () => {
1731
1731
  });
1732
1732
 
1733
1733
  test("unknown archive path produces UNKNOWN_ARCHIVE_PATH conflict", () => {
1734
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1734
+ const resolver = new DefaultPathResolver(undefined, testDir);
1735
1735
 
1736
1736
  const report = analyzeImport({
1737
1737
  manifest: {
@@ -27,13 +27,14 @@ import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
27
27
  const testDir = realpathSync(
28
28
  mkdtempSync(join(tmpdir(), "migration-export-http-test-")),
29
29
  );
30
- const testDbDir = join(testDir, "db");
30
+ const testDbDir = join(testDir, "data", "db");
31
31
  const testDbPath = join(testDbDir, "assistant.db");
32
32
  const testConfigPath = join(testDir, "config.json");
33
33
 
34
34
  mock.module("../util/platform.js", () => ({
35
35
  getRootDir: () => testDir,
36
- getDataDir: () => testDir,
36
+ getDataDir: () => join(testDir, "data"),
37
+ getWorkspaceDir: () => testDir,
37
38
  getWorkspaceConfigPath: () => testConfigPath,
38
39
  isMacOS: () => process.platform === "darwin",
39
40
  isLinux: () => process.platform === "linux",
@@ -57,9 +58,8 @@ mock.module("../config/loader.js", () => ({
57
58
  model: "test",
58
59
  provider: "test",
59
60
  memory: { enabled: false },
60
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
61
+ rateLimit: { maxRequestsPerMinute: 0 },
61
62
  secretDetection: { enabled: false },
62
- sandbox: { enabled: false },
63
63
  }),
64
64
  }));
65
65
 
@@ -206,10 +206,10 @@ describe("handleMigrationExport", () => {
206
206
  expect(manifest.created_at).toBeDefined();
207
207
  expect(manifest.manifest_sha256).toBeDefined();
208
208
 
209
- // Verify file entries
209
+ // Verify file entries — workspace walk uses workspace/ prefix
210
210
  const filePaths = manifest.files.map((f) => f.path);
211
- expect(filePaths).toContain("data/db/assistant.db");
212
- expect(filePaths).toContain("config/settings.json");
211
+ expect(filePaths).toContain("workspace/data/db/assistant.db");
212
+ expect(filePaths).toContain("workspace/config.json");
213
213
 
214
214
  // Verify each file entry has proper sha256 and size
215
215
  for (const file of manifest.files) {
@@ -299,7 +299,7 @@ describe("export data population", () => {
299
299
  const archiveData = new Uint8Array(await res.arrayBuffer());
300
300
  const entries = parseTarEntries(archiveData);
301
301
 
302
- const dbEntry = entries.find((e) => e.name === "data/db/assistant.db");
302
+ const dbEntry = entries.find((e) => e.name === "workspace/data/db/assistant.db");
303
303
  expect(dbEntry).toBeDefined();
304
304
  expect(dbEntry!.data.length).toBe(SQLITE_HEADER.length);
305
305
  // Verify the exported data matches the test fixture exactly
@@ -315,7 +315,7 @@ describe("export data population", () => {
315
315
  const archiveData = new Uint8Array(await res.arrayBuffer());
316
316
  const entries = parseTarEntries(archiveData);
317
317
 
318
- const configEntry = entries.find((e) => e.name === "config/settings.json");
318
+ const configEntry = entries.find((e) => e.name === "workspace/config.json");
319
319
  expect(configEntry).toBeDefined();
320
320
 
321
321
  const configContent = new TextDecoder().decode(configEntry!.data);
@@ -336,13 +336,13 @@ describe("export data population", () => {
336
336
  const manifest = validationResult.manifest!;
337
337
 
338
338
  const dbFile = manifest.files.find(
339
- (f) => f.path === "data/db/assistant.db",
339
+ (f) => f.path === "workspace/data/db/assistant.db",
340
340
  );
341
341
  expect(dbFile).toBeDefined();
342
342
  expect(dbFile!.size).toBe(SQLITE_HEADER.length);
343
343
 
344
344
  const configFile = manifest.files.find(
345
- (f) => f.path === "config/settings.json",
345
+ (f) => f.path === "workspace/config.json",
346
346
  );
347
347
  expect(configFile).toBeDefined();
348
348
  const expectedConfigSize = Buffer.byteLength(
@@ -382,7 +382,7 @@ describe("export data population", () => {
382
382
  const manifest = validationResult.manifest!;
383
383
 
384
384
  const dbFile = manifest.files.find(
385
- (f) => f.path === "data/db/assistant.db",
385
+ (f) => f.path === "workspace/data/db/assistant.db",
386
386
  );
387
387
  expect(dbFile).toBeDefined();
388
388
  // The skeleton used size 0 — real export should have actual content
@@ -398,7 +398,7 @@ describe("export data population", () => {
398
398
  const archiveData = new Uint8Array(await res.arrayBuffer());
399
399
  const entries = parseTarEntries(archiveData);
400
400
 
401
- const configEntry = entries.find((e) => e.name === "config/settings.json");
401
+ const configEntry = entries.find((e) => e.name === "workspace/config.json");
402
402
  expect(configEntry).toBeDefined();
403
403
 
404
404
  const configContent = new TextDecoder().decode(configEntry!.data);
@@ -413,42 +413,41 @@ describe("export data population", () => {
413
413
  // ---------------------------------------------------------------------------
414
414
 
415
415
  describe("export graceful fallback", () => {
416
- test("missing database produces valid archive with empty db entry", async () => {
416
+ test("nonexistent workspace produces valid archive with no files", async () => {
417
417
  const { buildExportVBundle } =
418
418
  await import("../runtime/migrations/vbundle-builder.js");
419
419
 
420
420
  const result = buildExportVBundle({
421
- dbPath: join(testDir, "nonexistent.db"),
422
- configPath: testConfigPath,
421
+ workspaceDir: join(testDir, "nonexistent-workspace"),
423
422
  });
424
423
 
425
424
  const validationResult = validateVBundle(result.archive);
426
425
  expect(validationResult.is_valid).toBe(true);
427
-
428
- const dbFile = result.manifest.files.find(
429
- (f) => f.path === "data/db/assistant.db",
430
- );
431
- expect(dbFile).toBeDefined();
432
- expect(dbFile!.size).toBe(0);
426
+ expect(result.manifest.files).toHaveLength(0);
433
427
  });
434
428
 
435
- test("missing config produces valid archive with empty JSON config", async () => {
429
+ test("workspace walk includes db and config under workspace/ prefix", async () => {
436
430
  const { buildExportVBundle } =
437
431
  await import("../runtime/migrations/vbundle-builder.js");
438
432
 
439
433
  const result = buildExportVBundle({
440
- dbPath: testDbPath,
441
- configPath: join(testDir, "nonexistent-config.json"),
434
+ workspaceDir: testDir,
442
435
  });
443
436
 
444
437
  const validationResult = validateVBundle(result.archive);
445
438
  expect(validationResult.is_valid).toBe(true);
446
439
 
440
+ const dbFile = result.manifest.files.find(
441
+ (f) => f.path === "workspace/data/db/assistant.db",
442
+ );
443
+ expect(dbFile).toBeDefined();
444
+ expect(dbFile!.size).toBeGreaterThan(0);
445
+
447
446
  const configFile = result.manifest.files.find(
448
- (f) => f.path === "config/settings.json",
447
+ (f) => f.path === "workspace/config.json",
449
448
  );
450
449
  expect(configFile).toBeDefined();
451
- expect(configFile!.size).toBe(2); // "{}" is 2 bytes
450
+ expect(configFile!.size).toBeGreaterThan(0);
452
451
  });
453
452
  });
454
453