@vellumai/assistant 0.4.56 → 0.4.57

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 (450) 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 +185 -173
  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 +237 -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__/gateway-only-enforcement.test.ts +2 -2
  67. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  68. package/src/__tests__/gemini-image-service.test.ts +4 -4
  69. package/src/__tests__/gemini-provider.test.ts +6 -9
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  71. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  72. package/src/__tests__/host-shell-tool.test.ts +6 -6
  73. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  74. package/src/__tests__/intent-routing.test.ts +51 -99
  75. package/src/__tests__/invite-routes-http.test.ts +5 -0
  76. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  77. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  78. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  79. package/src/__tests__/media-generate-image.test.ts +32 -15
  80. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  81. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  82. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  83. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  84. package/src/__tests__/memory-regressions.test.ts +86 -90
  85. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  86. package/src/__tests__/migration-export-http.test.ts +26 -27
  87. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  88. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  89. package/src/__tests__/migration-validate-http.test.ts +16 -16
  90. package/src/__tests__/model-intents.test.ts +1 -1
  91. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  92. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  93. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  94. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  95. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  96. package/src/__tests__/notification-deep-link.test.ts +1 -1
  97. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  98. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  99. package/src/__tests__/oauth-store.test.ts +1 -3
  100. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  101. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  102. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  103. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  104. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  105. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  106. package/src/__tests__/qdrant-manager.test.ts +7 -7
  107. package/src/__tests__/ratelimit.test.ts +0 -74
  108. package/src/__tests__/recording-handler.test.ts +0 -1
  109. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  110. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  111. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  112. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  113. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  114. package/src/__tests__/schema-transforms.test.ts +114 -54
  115. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  116. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  117. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  118. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  119. package/src/__tests__/send-notification-tool.test.ts +2 -2
  120. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  121. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  122. package/src/__tests__/skill-memory.test.ts +547 -0
  123. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  124. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  125. package/src/__tests__/slack-channel-config.test.ts +109 -94
  126. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  127. package/src/__tests__/swarm-recursion.test.ts +2 -2
  128. package/src/__tests__/swarm-tool.test.ts +2 -2
  129. package/src/__tests__/system-prompt.test.ts +19 -66
  130. package/src/__tests__/telegram-config.test.ts +121 -0
  131. package/src/__tests__/terminal-tools.test.ts +1 -1
  132. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  135. package/src/__tests__/tool-executor.test.ts +1 -1
  136. package/src/__tests__/trace-emitter.test.ts +8 -1
  137. package/src/__tests__/trust-store.test.ts +7 -8
  138. package/src/__tests__/twilio-routes.test.ts +1 -18
  139. package/src/__tests__/user-reference.test.ts +82 -2
  140. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  141. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  142. package/src/approvals/guardian-request-resolvers.ts +3 -3
  143. package/src/avatar/ascii-renderer.ts +2 -2
  144. package/src/avatar/png-renderer.ts +2 -2
  145. package/src/avatar/resvg-lazy.ts +21 -0
  146. package/src/calls/guardian-dispatch.ts +1 -1
  147. package/src/calls/relay-access-wait.ts +2 -2
  148. package/src/calls/twilio-rest.ts +0 -248
  149. package/src/cli/AGENTS.md +5 -8
  150. package/src/cli/__tests__/notifications.test.ts +5 -5
  151. package/src/cli/commands/avatar.ts +64 -2
  152. package/src/cli/commands/conversations.ts +131 -1
  153. package/src/cli/commands/credentials.ts +2 -0
  154. package/src/cli/commands/notifications.ts +3 -3
  155. package/src/cli.ts +10 -0
  156. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  157. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  158. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  159. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  160. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  161. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  162. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  163. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  164. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  165. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  166. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  167. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  168. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  169. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  170. package/src/config/bundled-skills/document/SKILL.md +4 -4
  171. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  172. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  173. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  174. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  175. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  176. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  177. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  178. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  179. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  180. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  181. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  182. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  183. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  184. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  185. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  186. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  187. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  188. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  189. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  190. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  192. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  193. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  195. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  196. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  197. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  198. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  199. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  200. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  201. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  202. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  203. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  204. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  205. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  206. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  207. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  208. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  209. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  210. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  211. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  212. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  213. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  214. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  215. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  216. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  217. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  218. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  219. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  220. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  221. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  222. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  223. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  224. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  225. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  226. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  227. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  228. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  229. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  230. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  231. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  232. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  233. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  234. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  235. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  236. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  237. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  238. package/src/config/feature-flag-registry.json +33 -17
  239. package/src/config/schemas/sandbox.ts +1 -1
  240. package/src/config/schemas/services.ts +13 -3
  241. package/src/config/schemas/timeouts.ts +0 -10
  242. package/src/contacts/contact-store.ts +63 -0
  243. package/src/contacts/contacts-write.ts +1 -1
  244. package/src/daemon/assistant-attachments.ts +2 -2
  245. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  246. package/src/daemon/conversation-agent-loop.ts +7 -30
  247. package/src/daemon/conversation-error.ts +24 -0
  248. package/src/daemon/conversation-memory.ts +8 -7
  249. package/src/daemon/conversation-runtime-assembly.ts +139 -274
  250. package/src/daemon/conversation-slash.ts +7 -26
  251. package/src/daemon/conversation-surfaces.ts +14 -0
  252. package/src/daemon/conversation-tool-setup.ts +9 -8
  253. package/src/daemon/conversation.ts +2 -0
  254. package/src/daemon/daemon-control.ts +1 -1
  255. package/src/daemon/date-context.ts +10 -83
  256. package/src/daemon/handlers/config-channels.ts +12 -2
  257. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  258. package/src/daemon/handlers/config-telegram.ts +6 -1
  259. package/src/daemon/handlers/conversations.ts +2 -2
  260. package/src/daemon/handlers/skills.ts +4 -0
  261. package/src/daemon/lifecycle.ts +28 -4
  262. package/src/daemon/providers-setup.ts +1 -1
  263. package/src/daemon/server.ts +1 -5
  264. package/src/daemon/shutdown-handlers.ts +9 -3
  265. package/src/daemon/tool-side-effects.ts +40 -0
  266. package/src/daemon/trace-emitter.ts +25 -2
  267. package/src/events/domain-events.ts +1 -1
  268. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  269. package/src/inbound/platform-callback-registration.ts +0 -18
  270. package/src/media/app-icon-generator.ts +15 -8
  271. package/src/media/avatar-router.ts +15 -8
  272. package/src/media/gemini-image-service.ts +125 -21
  273. package/src/memory/attachments-store.ts +3 -3
  274. package/src/memory/channel-verification-sessions.ts +6 -6
  275. package/src/memory/conversation-crud.ts +196 -1
  276. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  277. package/src/memory/conversation-title-service.ts +2 -3
  278. package/src/memory/db-init.ts +25 -1
  279. package/src/memory/invite-store.ts +4 -4
  280. package/src/memory/items-extractor.ts +4 -4
  281. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  282. package/src/memory/jobs-store.ts +3 -2
  283. package/src/memory/jobs-worker.ts +7 -5
  284. package/src/memory/lifecycle-events-store.ts +63 -0
  285. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  286. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  287. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  288. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  289. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  290. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  291. package/src/memory/migrations/index.ts +6 -0
  292. package/src/memory/migrations/registry.ts +13 -0
  293. package/src/memory/retriever.test.ts +223 -96
  294. package/src/memory/retriever.ts +115 -138
  295. package/src/memory/schema/calls.ts +1 -1
  296. package/src/memory/schema/contacts.ts +1 -1
  297. package/src/memory/schema/infrastructure.ts +29 -0
  298. package/src/memory/schema/memory-core.ts +7 -17
  299. package/src/memory/schema/notifications.ts +1 -1
  300. package/src/memory/search/formatting.ts +23 -6
  301. package/src/memory/search/lexical.ts +2 -0
  302. package/src/memory/search/semantic.ts +2 -0
  303. package/src/memory/search/staleness.ts +1 -0
  304. package/src/memory/search/types.ts +4 -0
  305. package/src/memory/task-memory-cleanup.ts +96 -6
  306. package/src/memory/trace-event-store.ts +148 -0
  307. package/src/notifications/README.md +1 -1
  308. package/src/notifications/decision-engine.ts +2 -2
  309. package/src/notifications/emit-signal.ts +4 -4
  310. package/src/notifications/events-store.ts +4 -4
  311. package/src/notifications/signal.ts +1 -1
  312. package/src/oauth/manual-token-connection.ts +49 -25
  313. package/src/permissions/checker.ts +6 -5
  314. package/src/permissions/defaults.ts +4 -4
  315. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  316. package/src/prompts/cache-boundary.ts +8 -0
  317. package/src/prompts/system-prompt.ts +105 -634
  318. package/src/prompts/templates/BOOTSTRAP.md +166 -33
  319. package/src/prompts/templates/IDENTITY.md +8 -23
  320. package/src/prompts/templates/SOUL.md +20 -41
  321. package/src/prompts/templates/USER.md +3 -19
  322. package/src/prompts/user-reference.ts +14 -16
  323. package/src/providers/anthropic/client.ts +46 -2
  324. package/src/providers/gemini/client.ts +6 -9
  325. package/src/providers/managed-proxy/constants.ts +1 -7
  326. package/src/providers/managed-proxy/context.ts +0 -1
  327. package/src/providers/model-intents.ts +5 -5
  328. package/src/providers/openai/client.ts +10 -1
  329. package/src/providers/openrouter/client.ts +1 -0
  330. package/src/providers/ratelimit.ts +0 -35
  331. package/src/providers/registry.ts +3 -5
  332. package/src/providers/retry.ts +18 -1
  333. package/src/runtime/access-request-helper.ts +1 -1
  334. package/src/runtime/auth/route-policy.ts +7 -0
  335. package/src/runtime/channel-verification-service.ts +1 -1
  336. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  337. package/src/runtime/guardian-vellum-migration.ts +63 -1
  338. package/src/runtime/http-server.ts +8 -4
  339. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  340. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  341. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  342. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  343. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  344. package/src/runtime/routes/attachment-routes.ts +2 -2
  345. package/src/runtime/routes/btw-routes.ts +9 -0
  346. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  347. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  348. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  349. package/src/runtime/routes/conversation-routes.ts +49 -5
  350. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  351. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  352. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  353. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  354. package/src/runtime/routes/migration-routes.ts +25 -13
  355. package/src/runtime/routes/secret-routes.ts +18 -0
  356. package/src/runtime/routes/settings-routes.ts +8 -8
  357. package/src/runtime/routes/telemetry-routes.ts +53 -0
  358. package/src/runtime/routes/trace-event-routes.ts +62 -0
  359. package/src/runtime/tool-grant-request-helper.ts +1 -1
  360. package/src/runtime/verification-outbound-actions.ts +47 -31
  361. package/src/security/encrypted-store.ts +263 -78
  362. package/src/skills/catalog-install.ts +10 -0
  363. package/src/skills/managed-store.ts +2 -0
  364. package/src/skills/skill-memory.ts +220 -0
  365. package/src/subagent/manager.ts +1 -4
  366. package/src/telemetry/types.ts +10 -1
  367. package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
  368. package/src/telemetry/usage-telemetry-reporter.ts +51 -4
  369. package/src/tools/AGENTS.md +11 -11
  370. package/src/tools/acp/spawn.ts +1 -1
  371. package/src/tools/apps/executors.ts +8 -8
  372. package/src/tools/apps/registry.ts +1 -1
  373. package/src/tools/assets/materialize.ts +6 -6
  374. package/src/tools/assets/search.ts +10 -10
  375. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  376. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  377. package/src/tools/browser/auth-detector.ts +6 -6
  378. package/src/tools/browser/browser-execution.ts +13 -13
  379. package/src/tools/browser/browser-manager.ts +3 -3
  380. package/src/tools/browser/chrome-cdp.ts +5 -5
  381. package/src/tools/browser/jit-auth.ts +2 -2
  382. package/src/tools/browser/network-recorder.test.ts +2 -2
  383. package/src/tools/browser/network-recorder.ts +3 -3
  384. package/src/tools/browser/runtime-check.ts +3 -3
  385. package/src/tools/claude-code/claude-code.ts +2 -2
  386. package/src/tools/computer-use/definitions.ts +18 -18
  387. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  388. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  389. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  390. package/src/tools/credentials/broker-types.ts +5 -5
  391. package/src/tools/credentials/broker.ts +15 -15
  392. package/src/tools/credentials/metadata-store.ts +2 -2
  393. package/src/tools/credentials/resolve.ts +1 -1
  394. package/src/tools/credentials/selection.ts +1 -1
  395. package/src/tools/credentials/tool-policy.ts +1 -1
  396. package/src/tools/credentials/vault.ts +115 -25
  397. package/src/tools/execution-target.ts +2 -2
  398. package/src/tools/executor.ts +7 -7
  399. package/src/tools/filesystem/edit.ts +2 -2
  400. package/src/tools/filesystem/read.ts +1 -1
  401. package/src/tools/filesystem/write.ts +1 -1
  402. package/src/tools/host-filesystem/edit.ts +2 -1
  403. package/src/tools/host-filesystem/read.ts +2 -1
  404. package/src/tools/host-filesystem/write.ts +1 -1
  405. package/src/tools/host-terminal/host-shell.ts +9 -8
  406. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  407. package/src/tools/memory/definitions.ts +6 -5
  408. package/src/tools/memory/handlers.test.ts +1 -1
  409. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  410. package/src/tools/network/domain-normalize.ts +2 -2
  411. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  412. package/src/tools/network/web-fetch.ts +1 -1
  413. package/src/tools/network/web-search.ts +3 -3
  414. package/src/tools/permission-checker.ts +8 -8
  415. package/src/tools/registry.ts +7 -7
  416. package/src/tools/schedule/list.ts +2 -2
  417. package/src/tools/schema-transforms.ts +31 -21
  418. package/src/tools/secret-detection-handler.ts +1 -1
  419. package/src/tools/sensitive-output-placeholders.ts +1 -1
  420. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  421. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  422. package/src/tools/shared/filesystem/image-read.ts +25 -5
  423. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  424. package/src/tools/shared/shell-output.ts +1 -1
  425. package/src/tools/side-effects.ts +1 -1
  426. package/src/tools/skills/execute.ts +1 -1
  427. package/src/tools/skills/load.ts +3 -3
  428. package/src/tools/skills/sandbox-runner.ts +3 -3
  429. package/src/tools/subagent/read.ts +1 -1
  430. package/src/tools/subagent/spawn.ts +2 -2
  431. package/src/tools/swarm/delegate.ts +3 -3
  432. package/src/tools/system/request-permission.ts +5 -4
  433. package/src/tools/terminal/backends/native.ts +4 -4
  434. package/src/tools/terminal/parser.ts +6 -6
  435. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  436. package/src/tools/terminal/shell.ts +16 -16
  437. package/src/tools/tool-approval-handler.ts +21 -12
  438. package/src/tools/tool-manifest.ts +4 -4
  439. package/src/tools/types.ts +3 -3
  440. package/src/tools/ui-surface/definitions.ts +9 -37
  441. package/src/tools/watcher/list.ts +1 -1
  442. package/src/util/logger.ts +7 -2
  443. package/src/util/retry.ts +29 -1
  444. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  445. package/src/workspace/migrations/registry.ts +2 -0
  446. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  447. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  448. package/src/cli/reference.ts +0 -38
  449. package/src/memory/job-handlers/capability-cards.ts +0 -420
  450. package/src/runtime/routes/thread-starter-routes.ts +0 -294
@@ -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
 
@@ -48,13 +48,14 @@ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
48
48
  const testDir = realpathSync(
49
49
  mkdtempSync(join(tmpdir(), "migration-import-commit-http-test-")),
50
50
  );
51
- const testDbDir = join(testDir, "db");
51
+ const testDbDir = join(testDir, "data", "db");
52
52
  const testDbPath = join(testDbDir, "assistant.db");
53
53
  const testConfigPath = join(testDir, "config.json");
54
54
 
55
55
  mock.module("../util/platform.js", () => ({
56
56
  getRootDir: () => testDir,
57
- getDataDir: () => testDir,
57
+ getDataDir: () => join(testDir, "data"),
58
+ getWorkspaceDir: () => testDir,
58
59
  getWorkspaceConfigPath: () => testConfigPath,
59
60
  isMacOS: () => process.platform === "darwin",
60
61
  isLinux: () => process.platform === "linux",
@@ -78,9 +79,8 @@ mock.module("../config/loader.js", () => ({
78
79
  model: "test",
79
80
  provider: "test",
80
81
  memory: { enabled: false },
81
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
82
+ rateLimit: { maxRequestsPerMinute: 0 },
82
83
  secretDetection: { enabled: false },
83
- sandbox: { enabled: false },
84
84
  }),
85
85
  }));
86
86
 
@@ -377,10 +377,13 @@ describe("handleMigrationImport", () => {
377
377
  expect(writtenData).toEqual(newDbData);
378
378
  });
379
379
 
380
- test("creates backup of existing files before overwriting", async () => {
380
+ test("workspace is cleared before restore — files are created fresh", async () => {
381
+ // Only new-format bundles (workspace/ prefix) trigger workspace clearing.
382
+ // With clearing, existing files are removed before writing, so all files
383
+ // in the bundle are "created" (not "overwritten") and no backups are made.
381
384
  const newDbData = new Uint8Array([0x01, 0x02, 0x03]);
382
385
  const vbundle = createValidVBundle([
383
- { path: "data/db/assistant.db", data: newDbData },
386
+ { path: "workspace/data/db/assistant.db", data: newDbData },
384
387
  ]);
385
388
  const req = new Request("http://localhost/v1/migrations/import", {
386
389
  method: "POST",
@@ -392,29 +395,22 @@ describe("handleMigrationImport", () => {
392
395
  const body = (await res.json()) as ImportCommitResponse;
393
396
 
394
397
  expect(body.success).toBe(true);
395
- expect(body.summary.backups_created).toBeGreaterThan(0);
398
+ expect(body.summary.backups_created).toBe(0);
396
399
 
397
- // Verify backup was created
398
- const dbFile = body.files.find((f) => f.path === "data/db/assistant.db");
400
+ const dbFile = body.files.find(
401
+ (f) => f.path === "workspace/data/db/assistant.db",
402
+ );
399
403
  expect(dbFile).toBeDefined();
400
- expect(dbFile!.action).toBe("overwritten");
401
- expect(dbFile!.backup_path).not.toBeNull();
402
- expect(existsSync(dbFile!.backup_path!)).toBe(true);
403
-
404
- // Verify backup contains the original data
405
- const backupData = new Uint8Array(readFileSync(dbFile!.backup_path!));
406
- expect(backupData).toEqual(EXISTING_DB_DATA);
404
+ expect(dbFile!.action).toBe("created");
407
405
  });
408
406
 
409
- test("reports correct actions for created and overwritten files", async () => {
410
- // Remove the config file so it will be "created" instead of "overwritten"
411
- rmSync(testConfigPath, { force: true });
412
-
407
+ test("reports all files as created after workspace clear", async () => {
408
+ // New-format workspace/ paths trigger clearing both files are fresh.
413
409
  const newDbData = new Uint8Array([0xaa, 0xbb]);
414
410
  const newConfigData = new TextEncoder().encode('{"provider":"openai"}');
415
411
  const vbundle = createValidVBundle([
416
- { path: "data/db/assistant.db", data: newDbData },
417
- { path: "config/settings.json", data: newConfigData },
412
+ { path: "workspace/data/db/assistant.db", data: newDbData },
413
+ { path: "workspace/config.json", data: newConfigData },
418
414
  ]);
419
415
  const req = new Request("http://localhost/v1/migrations/import", {
420
416
  method: "POST",
@@ -427,14 +423,16 @@ describe("handleMigrationImport", () => {
427
423
 
428
424
  expect(body.success).toBe(true);
429
425
 
430
- const dbFile = body.files.find((f) => f.path === "data/db/assistant.db");
426
+ const dbFile = body.files.find(
427
+ (f) => f.path === "workspace/data/db/assistant.db",
428
+ );
431
429
  const configFile = body.files.find(
432
- (f) => f.path === "config/settings.json",
430
+ (f) => f.path === "workspace/config.json",
433
431
  );
434
432
 
435
- expect(dbFile!.action).toBe("overwritten");
433
+ // Both are "created" because workspace was cleared before writing
434
+ expect(dbFile!.action).toBe("created");
436
435
  expect(configFile!.action).toBe("created");
437
- expect(configFile!.backup_path).toBeNull();
438
436
  });
439
437
 
440
438
  test("summary counts match file details", async () => {
@@ -641,7 +639,7 @@ describe("commitImport", () => {
641
639
  { path: "data/db/assistant.db", data: newDbData },
642
640
  ]);
643
641
 
644
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
642
+ const resolver = new DefaultPathResolver(undefined, testDir);
645
643
  const result = commitImport({
646
644
  archiveData: vbundle,
647
645
  pathResolver: resolver,
@@ -655,7 +653,7 @@ describe("commitImport", () => {
655
653
  });
656
654
 
657
655
  test("returns validation_failed for invalid bundles", () => {
658
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
656
+ const resolver = new DefaultPathResolver(undefined, testDir);
659
657
  const result = commitImport({
660
658
  archiveData: new Uint8Array([0xba, 0xad]),
661
659
  pathResolver: resolver,
@@ -671,17 +669,16 @@ describe("commitImport", () => {
671
669
  });
672
670
 
673
671
  test("creates parent directories if they do not exist", () => {
674
- // Use a path that does not exist yet
675
- const nonexistentDir = join(testDir, "new-subdir");
676
- const newDbPath = join(nonexistentDir, "assistant.db");
677
- const newConfigPath = join(testDir, "new-config.json");
672
+ // Use a workspace that does not exist yet
673
+ const nonexistentWorkspace = join(testDir, "new-workspace");
674
+ const expectedDbPath = join(nonexistentWorkspace, "data", "db", "assistant.db");
678
675
 
679
676
  const dbData = new Uint8Array([0x01, 0x02, 0x03]);
680
677
  const vbundle = createValidVBundle([
681
678
  { path: "data/db/assistant.db", data: dbData },
682
679
  ]);
683
680
 
684
- const resolver = new DefaultPathResolver(newDbPath, newConfigPath);
681
+ const resolver = new DefaultPathResolver(undefined, nonexistentWorkspace);
685
682
  const result = commitImport({
686
683
  archiveData: vbundle,
687
684
  pathResolver: resolver,
@@ -690,11 +687,11 @@ describe("commitImport", () => {
690
687
  expect(result.ok).toBe(true);
691
688
  if (result.ok) {
692
689
  expect(result.report.files[0].action).toBe("created");
693
- expect(existsSync(newDbPath)).toBe(true);
690
+ expect(existsSync(expectedDbPath)).toBe(true);
694
691
  }
695
692
 
696
693
  // Clean up
697
- rmSync(nonexistentDir, { recursive: true, force: true });
694
+ rmSync(nonexistentWorkspace, { recursive: true, force: true });
698
695
  });
699
696
 
700
697
  test("post-write integrity: written file SHA-256 matches expected", () => {
@@ -705,7 +702,7 @@ describe("commitImport", () => {
705
702
  { path: "data/db/assistant.db", data: newDbData },
706
703
  ]);
707
704
 
708
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
705
+ const resolver = new DefaultPathResolver(undefined, testDir);
709
706
  const result = commitImport({
710
707
  archiveData: vbundle,
711
708
  pathResolver: resolver,
@@ -735,7 +732,7 @@ describe("commitImport", () => {
735
732
  { path: "config/settings.json", data: newConfigData },
736
733
  ]);
737
734
 
738
- const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
735
+ const resolver = new DefaultPathResolver(undefined, testDir);
739
736
  const result = commitImport({
740
737
  archiveData: vbundle,
741
738
  pathResolver: resolver,
@@ -755,6 +752,137 @@ describe("commitImport", () => {
755
752
  });
756
753
  });
757
754
 
755
+ // ---------------------------------------------------------------------------
756
+ // Workspace clearing tests
757
+ // ---------------------------------------------------------------------------
758
+
759
+ describe("commitImport — workspace clearing", () => {
760
+ const skillsDir = join(testDir, "skills");
761
+ const hooksDir = join(testDir, "hooks");
762
+
763
+ afterEach(() => {
764
+ // Restore test fixture files for subsequent tests
765
+ mkdirSync(testDbDir, { recursive: true });
766
+ writeFileSync(testDbPath, EXISTING_DB_DATA);
767
+ writeFileSync(testConfigPath, JSON.stringify(EXISTING_CONFIG, null, 2));
768
+ // Clean up skills/hooks dirs
769
+ for (const dir of [skillsDir, hooksDir]) {
770
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
771
+ }
772
+ });
773
+
774
+ test("clears stale files via workspace clearing (new-format workspace/ entries)", () => {
775
+ mkdirSync(join(skillsDir, "stale-skill"), { recursive: true });
776
+ writeFileSync(join(skillsDir, "stale-skill", "SKILL.md"), "stale");
777
+
778
+ const skillData = new TextEncoder().encode("# New Skill");
779
+ const vbundle = createValidVBundle([
780
+ { path: "workspace/skills/new-skill/SKILL.md", data: skillData },
781
+ ]);
782
+
783
+ const resolver = new DefaultPathResolver(undefined, testDir);
784
+ const result = commitImport({
785
+ archiveData: vbundle,
786
+ pathResolver: resolver,
787
+ workspaceDir: testDir,
788
+ });
789
+
790
+ expect(result.ok).toBe(true);
791
+ if (!result.ok) return;
792
+
793
+ // New skill written
794
+ expect(existsSync(join(skillsDir, "new-skill", "SKILL.md"))).toBe(true);
795
+ expect(readFileSync(join(skillsDir, "new-skill", "SKILL.md"), "utf8")).toBe(
796
+ "# New Skill",
797
+ );
798
+
799
+ // Stale skill removed by workspace clearing
800
+ expect(existsSync(join(skillsDir, "stale-skill"))).toBe(false);
801
+ });
802
+
803
+ test("old-format skills/ entries do not trigger workspace clearing", () => {
804
+ mkdirSync(join(skillsDir, "stale-skill"), { recursive: true });
805
+ writeFileSync(join(skillsDir, "stale-skill", "SKILL.md"), "stale");
806
+
807
+ const skillData = new TextEncoder().encode("# New Skill");
808
+ const vbundle = createValidVBundle([
809
+ { path: "skills/new-skill/SKILL.md", data: skillData },
810
+ ]);
811
+
812
+ const resolver = new DefaultPathResolver(undefined, testDir);
813
+ const result = commitImport({
814
+ archiveData: vbundle,
815
+ pathResolver: resolver,
816
+ workspaceDir: testDir,
817
+ });
818
+
819
+ expect(result.ok).toBe(true);
820
+ if (!result.ok) return;
821
+
822
+ // New skill written
823
+ expect(existsSync(join(skillsDir, "new-skill", "SKILL.md"))).toBe(true);
824
+
825
+ // Stale skill survives — old-format bundles don't trigger workspace clearing
826
+ expect(existsSync(join(skillsDir, "stale-skill", "SKILL.md"))).toBe(true);
827
+ });
828
+
829
+ test("hooks/ entries import to hooksDir (not workspace/hooks/)", () => {
830
+ // Use a separate hooks dir outside the workspace, like production layout
831
+ const externalHooksDir = join(testDir, ".hooks-external");
832
+ mkdirSync(externalHooksDir, { recursive: true });
833
+
834
+ const hookData = new TextEncoder().encode("#!/bin/sh\necho new");
835
+ const vbundle = createValidVBundle([
836
+ { path: "hooks/new-hook/hook.sh", data: hookData },
837
+ ]);
838
+
839
+ const resolver = new DefaultPathResolver(undefined, testDir, externalHooksDir);
840
+ const result = commitImport({
841
+ archiveData: vbundle,
842
+ pathResolver: resolver,
843
+ workspaceDir: testDir,
844
+ });
845
+
846
+ expect(result.ok).toBe(true);
847
+ if (!result.ok) return;
848
+
849
+ // Hook written to the external hooks dir, not workspace/hooks/
850
+ expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(true);
851
+ expect(
852
+ readFileSync(join(externalHooksDir, "new-hook", "hook.sh"), "utf8"),
853
+ ).toBe("#!/bin/sh\necho new");
854
+
855
+ // Cleanup
856
+ rmSync(externalHooksDir, { recursive: true, force: true });
857
+ });
858
+
859
+ test("without workspaceDir, no clearing happens", () => {
860
+ mkdirSync(join(skillsDir, "existing-skill"), { recursive: true });
861
+ writeFileSync(
862
+ join(skillsDir, "existing-skill", "SKILL.md"),
863
+ "should survive",
864
+ );
865
+
866
+ const vbundle = createValidVBundle([
867
+ { path: "skills/new-skill/SKILL.md", data: new TextEncoder().encode("new") },
868
+ ]);
869
+
870
+ const resolver = new DefaultPathResolver(undefined, testDir);
871
+ // No workspaceDir — no clearing
872
+ const result = commitImport({
873
+ archiveData: vbundle,
874
+ pathResolver: resolver,
875
+ });
876
+
877
+ expect(result.ok).toBe(true);
878
+
879
+ // Existing skill survives (no workspace clearing without workspaceDir)
880
+ expect(existsSync(join(skillsDir, "existing-skill", "SKILL.md"))).toBe(
881
+ true,
882
+ );
883
+ });
884
+ });
885
+
758
886
  // ---------------------------------------------------------------------------
759
887
  // Auth policy registration tests
760
888
  // ---------------------------------------------------------------------------