@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
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * A .vbundle is a gzip-compressed tar archive containing:
5
5
  * - manifest.json: metadata with schema_version, checksums, and bundle info
6
- * - data/db/assistant.db: the SQLite database with conversations and memory
7
- * - config/settings.json: the assistant configuration
6
+ * - workspace/: the entire ~/.vellum/workspace/ directory tree (DB, config,
7
+ * skills, hooks, prompts, attachments, etc.) excluding large/regenerable
8
+ * dirs (embedding-models/, data/qdrant/)
9
+ * - trust/trust.json: trust rules (optional, lives in protected/ outside workspace)
8
10
  */
9
11
 
10
12
  import { createHash } from "node:crypto";
11
- import { existsSync, readFileSync } from "node:fs";
13
+ import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
14
+ import { join, relative } from "node:path";
12
15
  import { gzipSync } from "node:zlib";
13
16
 
14
17
  import type {
@@ -107,12 +110,72 @@ function computeHeaderChecksum(header: Uint8Array): number {
107
110
  return sum;
108
111
  }
109
112
 
110
- function createTarEntry(name: string, data: Uint8Array): Uint8Array {
111
- const header = new Uint8Array(BLOCK_SIZE);
113
+ /**
114
+ * Build a PAX extended header entry for paths that exceed the 100-byte ustar
115
+ * limit. The PAX entry is a special tar record (typeflag 'x') whose body
116
+ * contains "key=value" records. The following data entry uses a truncated name
117
+ * in its ustar header, but tar extractors use the PAX path attribute instead.
118
+ */
119
+ function createPaxPathEntry(name: string): Uint8Array {
112
120
  const encoder = new TextEncoder();
113
121
 
114
- // File name (0-99)
122
+ // Build PAX payload: "<length> path=<name>\n"
123
+ // The length field includes itself, the space, and the trailing newline.
124
+ const record = `path=${name}\n`;
125
+ // Start with a guess for the decimal length prefix
126
+ let prefix = `${record.length + 2} `; // +2 for prefix digit + space (min)
127
+ let full = `${prefix}${record}`;
128
+ // Iterate until the length prefix is self-consistent
129
+ while (new TextEncoder().encode(full).length !== Number.parseInt(prefix)) {
130
+ prefix = `${new TextEncoder().encode(full).length} `;
131
+ full = `${prefix}${record}`;
132
+ }
133
+ const paxData = encoder.encode(full);
134
+
135
+ // Build a ustar header for the PAX entry itself
136
+ const header = new Uint8Array(BLOCK_SIZE);
137
+
138
+ // Use a synthetic name for the PAX header entry
139
+ const paxName = encoder.encode("PaxHeader/entry");
140
+ header.set(paxName.subarray(0, 100), 0);
141
+
142
+ writeOctal(header, 100, 8, 0o644);
143
+ writeOctal(header, 108, 8, 0);
144
+ writeOctal(header, 116, 8, 0);
145
+ writeOctal(header, 124, 12, paxData.length);
146
+ writeOctal(header, 136, 12, Math.floor(Date.now() / 1000));
147
+
148
+ // Type flag 'x' = PAX extended header for the next entry
149
+ header[156] = "x".charCodeAt(0);
150
+
151
+ const magic = encoder.encode("ustar\0");
152
+ header.set(magic, 257);
153
+ header[263] = "0".charCodeAt(0);
154
+ header[264] = "0".charCodeAt(0);
155
+
156
+ const checksum = computeHeaderChecksum(header);
157
+ writeOctal(header, 148, 7, checksum);
158
+ header[155] = 0x20;
159
+
160
+ const paddedData = padToBlock(paxData);
161
+ const result = new Uint8Array(header.length + paddedData.length);
162
+ result.set(header, 0);
163
+ result.set(paddedData, header.length);
164
+ return result;
165
+ }
166
+
167
+ function createTarEntry(name: string, data: Uint8Array): Uint8Array {
168
+ const encoder = new TextEncoder();
115
169
  const nameBytes = encoder.encode(name);
170
+
171
+ // If the name exceeds 100 bytes, emit a PAX extended header first
172
+ // so that the full path is preserved in the archive.
173
+ const needsPax = nameBytes.length > 100;
174
+ const paxEntry = needsPax ? createPaxPathEntry(name) : null;
175
+
176
+ const header = new Uint8Array(BLOCK_SIZE);
177
+
178
+ // File name (0-99) — truncated if >100 bytes; PAX header carries the full name
116
179
  header.set(nameBytes.subarray(0, 100), 0);
117
180
 
118
181
  // File mode (100-107): 0644
@@ -148,10 +211,18 @@ function createTarEntry(name: string, data: Uint8Array): Uint8Array {
148
211
 
149
212
  // Combine header + padded data
150
213
  const paddedData = padToBlock(data);
151
- const result = new Uint8Array(header.length + paddedData.length);
152
- result.set(header, 0);
153
- result.set(paddedData, header.length);
154
- return result;
214
+ const fileEntry = new Uint8Array(header.length + paddedData.length);
215
+ fileEntry.set(header, 0);
216
+ fileEntry.set(paddedData, header.length);
217
+
218
+ if (paxEntry) {
219
+ const result = new Uint8Array(paxEntry.length + fileEntry.length);
220
+ result.set(paxEntry, 0);
221
+ result.set(fileEntry, paxEntry.length);
222
+ return result;
223
+ }
224
+
225
+ return fileEntry;
155
226
  }
156
227
 
157
228
  function createTarArchive(
@@ -230,24 +301,119 @@ export function buildVBundle(options: BuildVBundleOptions): BuildVBundleResult {
230
301
  return { archive, manifest };
231
302
  }
232
303
 
304
+ // ---------------------------------------------------------------------------
305
+ // Directory walker — recursively collects files for archive inclusion
306
+ // ---------------------------------------------------------------------------
307
+
308
+ interface WalkDirectoryOptions {
309
+ /** Include binary files (files containing null bytes). Default: false. */
310
+ includeBinary?: boolean;
311
+ /** Directory names to skip (matched against immediate child name). */
312
+ skipDirs?: string[];
313
+ }
314
+
315
+ /**
316
+ * Recursively walk a directory and return all non-symlink files as
317
+ * VBundleFileEntry objects with paths prefixed by `archivePrefix`.
318
+ *
319
+ * By default, binary files (detected via null-byte heuristic in the first
320
+ * 8 KB) are skipped. Pass `includeBinary: true` to include them.
321
+ */
322
+ function walkDirectory(
323
+ dir: string,
324
+ archivePrefix: string,
325
+ options: WalkDirectoryOptions = {},
326
+ ): VBundleFileEntry[] {
327
+ const { includeBinary = false, skipDirs = [] } = options;
328
+ const entries: VBundleFileEntry[] = [];
329
+
330
+ function walk(currentDir: string): void {
331
+ const dirEntries = readdirSync(currentDir, { withFileTypes: true });
332
+ for (const entry of dirEntries) {
333
+ const fullPath = join(currentDir, entry.name);
334
+
335
+ // Skip symlinks
336
+ const stat = lstatSync(fullPath);
337
+ if (stat.isSymbolicLink()) continue;
338
+
339
+ if (stat.isDirectory()) {
340
+ // Check skip list against the relative path from the walk root
341
+ const relDir = relative(dir, fullPath);
342
+ if (skipDirs.some((s) => relDir === s || relDir.startsWith(s + "/"))) {
343
+ continue;
344
+ }
345
+ walk(fullPath);
346
+ } else if (stat.isFile()) {
347
+ // Skip SQLite auxiliary files — these are ephemeral and race-prone
348
+ // with the live DB connection. The WAL is checkpointed before the
349
+ // walk, so the main .db file has all committed rows.
350
+ if (
351
+ entry.name.endsWith(".db-wal") ||
352
+ entry.name.endsWith(".db-shm") ||
353
+ entry.name.endsWith(".db-journal")
354
+ ) {
355
+ continue;
356
+ }
357
+
358
+ const data = new Uint8Array(readFileSync(fullPath));
359
+
360
+ // Skip binary files unless explicitly included
361
+ if (!includeBinary) {
362
+ const checkLength = Math.min(data.length, 8192);
363
+ let isBinary = false;
364
+ for (let i = 0; i < checkLength; i++) {
365
+ if (data[i] === 0) {
366
+ isBinary = true;
367
+ break;
368
+ }
369
+ }
370
+ if (isBinary) continue;
371
+ }
372
+
373
+ const relativePath = relative(dir, fullPath);
374
+ entries.push({
375
+ path: `${archivePrefix}/${relativePath}`,
376
+ data,
377
+ });
378
+ }
379
+ }
380
+ }
381
+
382
+ walk(dir);
383
+ return entries;
384
+ }
385
+
233
386
  // ---------------------------------------------------------------------------
234
387
  // Export builder — reads real data from disk
235
388
  // ---------------------------------------------------------------------------
236
389
 
237
390
  export interface BuildExportVBundleOptions {
238
- /** Path to the SQLite database file (e.g. ~/.vellum/workspace/data/db/assistant.db). */
239
- dbPath: string;
240
- /** Path to the config file (e.g. ~/.vellum/workspace/config.json). */
241
- configPath: string;
242
391
  /** Source identifier. Defaults to "runtime-export". */
243
392
  source?: string;
244
393
  /** Human-readable description. */
245
394
  description?: string;
395
+ /** Absolute path to trust.json. If provided and the file exists, it is included in the archive. */
396
+ trustPath?: string;
397
+ /**
398
+ * Absolute path to the hooks directory (~/.vellum/hooks/).
399
+ * Hooks live outside the workspace, so they must be included explicitly.
400
+ * Included in the archive under the "hooks/" prefix for backward compat.
401
+ */
402
+ hooksDir?: string;
403
+ /**
404
+ * Absolute path to the workspace directory (~/.vellum/workspace/).
405
+ * When provided and exists, the entire directory tree is walked and
406
+ * included in the archive under the "workspace/" prefix, skipping
407
+ * large/regenerable dirs (embedding-models/, data/qdrant/).
408
+ * Binary files (SQLite DB, attachments) are included.
409
+ */
410
+ workspaceDir?: string;
246
411
  /**
247
412
  * Optional callback to checkpoint the WAL before reading the database file.
248
413
  * In WAL mode, committed rows may live in the -wal file and not yet be
249
414
  * flushed to the main .db file. Callers should pass a function that runs
250
415
  * PRAGMA wal_checkpoint(TRUNCATE) on the live database connection.
416
+ * Called before the workspace walk so the DB file is up to date.
251
417
  */
252
418
  checkpoint?: () => void;
253
419
  }
@@ -255,18 +421,20 @@ export interface BuildExportVBundleOptions {
255
421
  /**
256
422
  * Build a .vbundle archive populated with real assistant data.
257
423
  *
258
- * Reads the actual SQLite database (which contains all conversations,
259
- * messages, memory segments, embeddings, and other assistant state) and
260
- * the config file from disk. Returns a complete, self-validating archive
261
- * ready for migration import.
424
+ * Walks the entire workspace directory (~/.vellum/workspace/) and includes
425
+ * all files in the archive, skipping only large/regenerable directories
426
+ * (embedding-models/, data/qdrant/). Binary files (SQLite DB, attachments)
427
+ * are included. Trust rules (in protected/, outside workspace) are handled
428
+ * separately.
262
429
  *
263
- * Falls back gracefully: if the database does not exist, an empty file is
264
- * included. If the config does not exist, an empty JSON object is used.
430
+ * The WAL is checkpointed before the walk so the exported DB file contains
431
+ * all committed rows.
265
432
  */
266
433
  export function buildExportVBundle(
267
434
  options: BuildExportVBundleOptions,
268
435
  ): BuildVBundleResult {
269
- const { dbPath, configPath, source, description, checkpoint } = options;
436
+ const { source, description, checkpoint, trustPath, workspaceDir, hooksDir } =
437
+ options;
270
438
 
271
439
  // Flush WAL to the main database file before reading so the export
272
440
  // captures all committed rows (SQLite WAL mode keeps recent writes
@@ -275,20 +443,32 @@ export function buildExportVBundle(
275
443
  checkpoint();
276
444
  }
277
445
 
278
- const dbData = existsSync(dbPath)
279
- ? new Uint8Array(readFileSync(dbPath))
280
- : new Uint8Array(0);
446
+ const files: VBundleFileEntry[] = [];
447
+
448
+ // Walk the entire workspace directory, including binary files (DB,
449
+ // attachments) but skipping large/regenerable subdirectories.
450
+ if (workspaceDir && existsSync(workspaceDir) && lstatSync(workspaceDir).isDirectory()) {
451
+ files.push(
452
+ ...walkDirectory(workspaceDir, "workspace", {
453
+ includeBinary: true,
454
+ skipDirs: ["embedding-models", "data/qdrant"],
455
+ }),
456
+ );
457
+ }
458
+
459
+ // Include hooks directory if it exists (lives at ~/.vellum/hooks/, outside workspace).
460
+ if (hooksDir && existsSync(hooksDir) && lstatSync(hooksDir).isDirectory()) {
461
+ files.push(...walkDirectory(hooksDir, "hooks"));
462
+ }
281
463
 
282
- // Read the config file (settings, provider config, feature flags, etc.).
283
- const configData = existsSync(configPath)
284
- ? new Uint8Array(readFileSync(configPath))
285
- : new TextEncoder().encode("{}");
464
+ // Include trust rules if the file exists (lives in protected/, outside workspace).
465
+ if (trustPath && existsSync(trustPath)) {
466
+ const trustData = new Uint8Array(readFileSync(trustPath));
467
+ files.push({ path: "trust/trust.json", data: trustData });
468
+ }
286
469
 
287
470
  return buildVBundle({
288
- files: [
289
- { path: "data/db/assistant.db", data: dbData },
290
- { path: "config/settings.json", data: configData },
291
- ],
471
+ files,
292
472
  source: source ?? "runtime-export",
293
473
  description: description ?? "Runtime export bundle",
294
474
  });
@@ -15,9 +15,18 @@
15
15
 
16
16
  import { createHash } from "node:crypto";
17
17
  import { existsSync, readFileSync, statSync } from "node:fs";
18
+ import { join, resolve } from "node:path";
18
19
 
19
20
  import type { ManifestType } from "./vbundle-validator.js";
20
21
 
22
+ /** Only these prompt filenames are accepted during import. */
23
+ const ALLOWED_PROMPT_FILENAMES = new Set([
24
+ "IDENTITY.md",
25
+ "SOUL.md",
26
+ "USER.md",
27
+ "UPDATES.md",
28
+ ]);
29
+
21
30
  // ---------------------------------------------------------------------------
22
31
  // Public types
23
32
  // ---------------------------------------------------------------------------
@@ -79,19 +88,76 @@ export interface PathResolver {
79
88
 
80
89
  export class DefaultPathResolver implements PathResolver {
81
90
  constructor(
82
- private dbPath: string,
83
- private configPath: string,
91
+ private protectedDir?: string,
92
+ private workspaceDir?: string,
93
+ private hooksDir?: string,
84
94
  ) {}
85
95
 
86
96
  resolve(archivePath: string): string | null {
87
- switch (archivePath) {
88
- case "data/db/assistant.db":
89
- return this.dbPath;
90
- case "config/settings.json":
91
- return this.configPath;
92
- default:
97
+ // New format: workspace/ prefix — maps directly into the workspace dir
98
+ if (archivePath.startsWith("workspace/") && this.workspaceDir) {
99
+ const relPath = archivePath.slice("workspace/".length);
100
+ if (!relPath) return null;
101
+ const resolved = resolve(this.workspaceDir, relPath);
102
+ const wsRoot = resolve(this.workspaceDir);
103
+ // Path traversal containment
104
+ if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
105
+ return null;
106
+ }
107
+ return resolved;
108
+ }
109
+
110
+ // Backward compat: old bundle formats with specific archive paths
111
+ if (archivePath === "data/db/assistant.db" && this.workspaceDir) {
112
+ return join(this.workspaceDir, "data", "db", "assistant.db");
113
+ }
114
+ if (archivePath === "config/settings.json" && this.workspaceDir) {
115
+ return join(this.workspaceDir, "config.json");
116
+ }
117
+ if (archivePath === "trust/trust.json" && this.protectedDir) {
118
+ return join(this.protectedDir, "trust.json");
119
+ }
120
+ if (archivePath.startsWith("skills/") && this.workspaceDir) {
121
+ const resolved = resolve(
122
+ this.workspaceDir,
123
+ "skills",
124
+ archivePath.slice("skills/".length),
125
+ );
126
+ const skillsRoot = resolve(this.workspaceDir, "skills");
127
+ if (resolved !== skillsRoot && !resolved.startsWith(skillsRoot + "/")) {
128
+ return null;
129
+ }
130
+ return resolved;
131
+ }
132
+ if (archivePath.startsWith("prompts/") && this.workspaceDir) {
133
+ // Old bundles stored prompts as prompts/IDENTITY.md etc — these map
134
+ // to the workspace root (e.g. workspace/IDENTITY.md).
135
+ // Only accepted prompt filenames resolve — unknown entries are
136
+ // skipped so they cannot trigger workspace clearing.
137
+ const filename = archivePath.slice("prompts/".length);
138
+ if (!ALLOWED_PROMPT_FILENAMES.has(filename)) {
93
139
  return null;
140
+ }
141
+ const resolved = resolve(this.workspaceDir, filename);
142
+ const wsRoot = resolve(this.workspaceDir);
143
+ if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
144
+ return null;
145
+ }
146
+ return resolved;
94
147
  }
148
+ if (archivePath.startsWith("hooks/") && this.hooksDir) {
149
+ const resolved = resolve(
150
+ this.hooksDir,
151
+ archivePath.slice("hooks/".length),
152
+ );
153
+ const hooksRoot = resolve(this.hooksDir);
154
+ if (resolved !== hooksRoot && !resolved.startsWith(hooksRoot + "/")) {
155
+ return null;
156
+ }
157
+ return resolved;
158
+ }
159
+
160
+ return null;
95
161
  }
96
162
  }
97
163
 
@@ -17,10 +17,12 @@ import {
17
17
  copyFileSync,
18
18
  existsSync,
19
19
  mkdirSync,
20
+ readdirSync,
20
21
  readFileSync,
22
+ rmSync,
21
23
  writeFileSync,
22
24
  } from "node:fs";
23
- import { dirname } from "node:path";
25
+ import { dirname, join } from "node:path";
24
26
 
25
27
  import type { PathResolver } from "./vbundle-import-analyzer.js";
26
28
  import type { ManifestType, VBundleTarEntry } from "./vbundle-validator.js";
@@ -113,6 +115,12 @@ export interface ImportCommitOptions {
113
115
  preValidatedManifest?: ManifestType;
114
116
  /** Pre-parsed tar entries from a prior validateVBundle call. */
115
117
  preValidatedEntries?: Map<string, VBundleTarEntry>;
118
+ /**
119
+ * Absolute path to the workspace directory. When set and the bundle
120
+ * contains workspace/ entries, the workspace is cleared (except
121
+ * skip dirs) before writing to ensure an exact-match restore.
122
+ */
123
+ workspaceDir?: string;
116
124
  }
117
125
 
118
126
  /**
@@ -128,6 +136,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
128
136
  pathResolver,
129
137
  preValidatedManifest,
130
138
  preValidatedEntries,
139
+ workspaceDir,
131
140
  } = options;
132
141
 
133
142
  let manifest: ManifestType;
@@ -154,6 +163,62 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
154
163
  entryMap = validation.entries;
155
164
  }
156
165
 
166
+ // Directories to preserve when clearing the workspace (large/regenerable).
167
+ const WORKSPACE_SKIP_DIRS = new Set(["embedding-models"]);
168
+ // data/qdrant is nested — we skip "qdrant" inside "data/"
169
+ const DATA_SKIP_DIRS = new Set(["qdrant"]);
170
+
171
+ // Step 1b: Clear the workspace directory before restore if the bundle
172
+ // contains new-format workspace/ entries. This ensures an exact-match
173
+ // restore with no stale files left behind. Skips embedding-models/ and
174
+ // data/qdrant/ (large, regenerable).
175
+ //
176
+ // Only new-format bundles (workspace/ prefix) trigger clearing. Old-format
177
+ // bundles (skills/, hooks/, data/db/*, config/*) wrote specific files
178
+ // without clearing — preserving that behavior avoids wiping workspace
179
+ // data when importing legacy bundles.
180
+ //
181
+ // Gate on resolution: at least one workspace/ entry must resolve to a
182
+ // valid disk path. This prevents path-traversal entries (e.g.
183
+ // "workspace/../../etc/passwd") from triggering a workspace purge while
184
+ // resolving to nothing.
185
+ const hasWorkspaceEntries = manifest.files.some(
186
+ (f) => f.path.startsWith("workspace/") && !!pathResolver.resolve(f.path),
187
+ );
188
+
189
+ if (hasWorkspaceEntries && workspaceDir && existsSync(workspaceDir)) {
190
+ try {
191
+ // Clear workspace contents selectively, preserving skip dirs
192
+ const topEntries = readdirSync(workspaceDir, { withFileTypes: true });
193
+ for (const entry of topEntries) {
194
+ if (WORKSPACE_SKIP_DIRS.has(entry.name)) continue;
195
+
196
+ const entryPath = join(workspaceDir, entry.name);
197
+ if (entry.name === "data" && entry.isDirectory()) {
198
+ // Inside data/, preserve qdrant/ but clear everything else
199
+ const dataEntries = readdirSync(entryPath, { withFileTypes: true });
200
+ for (const dataEntry of dataEntries) {
201
+ if (DATA_SKIP_DIRS.has(dataEntry.name)) continue;
202
+ rmSync(join(entryPath, dataEntry.name), {
203
+ recursive: true,
204
+ force: true,
205
+ });
206
+ }
207
+ } else {
208
+ rmSync(entryPath, { recursive: true, force: true });
209
+ }
210
+ }
211
+ } catch (err) {
212
+ return {
213
+ ok: false,
214
+ reason: "write_failed",
215
+ message: `Failed to clear workspace directory "${workspaceDir}": ${
216
+ err instanceof Error ? err.message : String(err)
217
+ }`,
218
+ };
219
+ }
220
+ }
221
+
157
222
  // Step 2: Write files to disk with backups
158
223
  const importedFiles: ImportedFileReport[] = [];
159
224
  const warnings: string[] = [];
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * A .vbundle is a gzip-compressed tar archive containing:
5
5
  * - manifest.json: metadata with schema_version, checksums, and bundle info
6
- * - data/db/assistant.db: the SQLite database
7
- * - Additional config files as declared in the manifest
6
+ * - workspace/: the entire workspace directory tree (new format), OR
7
+ * data/db/assistant.db + config/settings.json (old format)
8
+ * - trust/trust.json: trust rules (optional)
8
9
  *
9
10
  * Validation steps:
10
11
  * 1. Archive structure: valid gzip tar with required entries
@@ -124,6 +125,17 @@ function parseTar(buffer: Uint8Array): TarEntry[] {
124
125
  continue;
125
126
  }
126
127
 
128
+ // PAX extended header (type 'x') — extract path= attribute for next entry
129
+ if (typeFlag === "x") {
130
+ const paxText = new TextDecoder().decode(data);
131
+ const pathMatch = paxText.match(/\d+ path=([^\n]+)\n/);
132
+ if (pathMatch) {
133
+ longName = pathMatch[1];
134
+ }
135
+ offset = dataStart + dataBlocks * BLOCK_SIZE;
136
+ continue;
137
+ }
138
+
127
139
  // Regular file or hard link
128
140
  if (typeFlag === "0" || typeFlag === "\0" || typeFlag === "") {
129
141
  entries.push({ name: normalizePath(name), data, size });
@@ -181,7 +193,9 @@ function canonicalizeJson(obj: unknown): string {
181
193
  // Core validation
182
194
  // ---------------------------------------------------------------------------
183
195
 
184
- const REQUIRED_ENTRIES = ["manifest.json", "data/db/assistant.db"];
196
+ // Only manifest.json is structurally required. The DB and config live under
197
+ // workspace/ (new format) or data/db/ + config/ (old format) — both are valid.
198
+ const REQUIRED_ENTRIES = ["manifest.json"];
185
199
 
186
200
  // 2 GB — must accommodate large but valid migrations from buildExportVBundle()
187
201
  const MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024 * 1024;
@@ -637,7 +637,7 @@ async function handleAccessRequestApproval(
637
637
  void emitNotificationSignal({
638
638
  sourceEventName: "ingress.trusted_contact.guardian_decision",
639
639
  sourceChannel: approval.channel as NotificationSourceChannel,
640
- sourceSessionId: approval.conversationId,
640
+ sourceContextId: approval.conversationId,
641
641
  attentionHints: {
642
642
  requiresAction: false,
643
643
  urgency: "medium",
@@ -651,7 +651,7 @@ async function handleAccessRequestApproval(
651
651
  void emitNotificationSignal({
652
652
  sourceEventName: "ingress.trusted_contact.denied",
653
653
  sourceChannel: approval.channel as NotificationSourceChannel,
654
- sourceSessionId: approval.conversationId,
654
+ sourceContextId: approval.conversationId,
655
655
  attentionHints: {
656
656
  requiresAction: false,
657
657
  urgency: "low",
@@ -719,7 +719,7 @@ async function handleAccessRequestApproval(
719
719
  void emitNotificationSignal({
720
720
  sourceEventName: "ingress.trusted_contact.guardian_decision",
721
721
  sourceChannel: approval.channel as NotificationSourceChannel,
722
- sourceSessionId: approval.conversationId,
722
+ sourceContextId: approval.conversationId,
723
723
  attentionHints: {
724
724
  requiresAction: false,
725
725
  urgency: "medium",
@@ -745,7 +745,7 @@ async function handleAccessRequestApproval(
745
745
  void emitNotificationSignal({
746
746
  sourceEventName: "ingress.trusted_contact.verification_sent",
747
747
  sourceChannel: approval.channel as NotificationSourceChannel,
748
- sourceSessionId: approval.conversationId,
748
+ sourceContextId: approval.conversationId,
749
749
  attentionHints: {
750
750
  requiresAction: false,
751
751
  urgency: "low",
@@ -12,8 +12,8 @@ import {
12
12
  import { httpError } from "../http-errors.js";
13
13
  import type { RouteDefinition } from "../http-router.js";
14
14
 
15
- /** 75 MB — base64-encoded 50 MB attachment ≈ 67 MB plus JSON wrapper overhead. */
16
- const MAX_UPLOAD_BODY_BYTES = 75 * 1024 * 1024;
15
+ /** 150 MB — base64-encoded 100 MB attachment ≈ 134 MB plus JSON wrapper overhead. */
16
+ const MAX_UPLOAD_BODY_BYTES = 150 * 1024 * 1024;
17
17
 
18
18
  export async function handleUploadAttachment(req: Request): Promise<Response> {
19
19
  const rawBody = await req.arrayBuffer();