@vellumai/assistant 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -9,10 +9,24 @@
9
9
  * - trust/trust.json: trust rules (optional, lives in protected/ outside workspace)
10
10
  */
11
11
 
12
- import { createHash } from "node:crypto";
13
- import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
12
+ import { createHash, randomUUID } from "node:crypto";
13
+ import {
14
+ closeSync,
15
+ createReadStream,
16
+ createWriteStream,
17
+ existsSync,
18
+ lstatSync,
19
+ openSync,
20
+ readdirSync,
21
+ readFileSync,
22
+ readSync,
23
+ } from "node:fs";
24
+ import { stat, unlink } from "node:fs/promises";
25
+ import { tmpdir } from "node:os";
14
26
  import { join, relative } from "node:path";
15
- import { gzipSync } from "node:zlib";
27
+ import { Readable } from "node:stream";
28
+ import { pipeline } from "node:stream/promises";
29
+ import { createGzip, gzipSync } from "node:zlib";
16
30
 
17
31
  import type {
18
32
  ManifestFileEntryType,
@@ -46,6 +60,12 @@ export interface BuildVBundleResult {
46
60
  manifest: ManifestType;
47
61
  }
48
62
 
63
+ interface FileMetadata {
64
+ archivePath: string;
65
+ diskPath: string;
66
+ size: number;
67
+ }
68
+
49
69
  // ---------------------------------------------------------------------------
50
70
  // Hash helpers
51
71
  // ---------------------------------------------------------------------------
@@ -480,3 +500,369 @@ export function buildExportVBundle(
480
500
  description: description ?? "Runtime export bundle",
481
501
  });
482
502
  }
503
+
504
+ // ---------------------------------------------------------------------------
505
+ // Streaming export builder — two-pass approach for bounded memory usage
506
+ // ---------------------------------------------------------------------------
507
+
508
+ /**
509
+ * Walk a directory tree and collect file metadata (paths + sizes) without
510
+ * reading file contents into memory. Uses the same filtering logic as
511
+ * `walkDirectory` (symlink skip, SQLite auxiliary skip, binary detection,
512
+ * skip dirs).
513
+ */
514
+ function walkDirectoryForMetadata(
515
+ dir: string,
516
+ archivePrefix: string,
517
+ options: WalkDirectoryOptions = {},
518
+ ): FileMetadata[] {
519
+ const { includeBinary = false, skipDirs = [] } = options;
520
+ const entries: FileMetadata[] = [];
521
+
522
+ function walk(currentDir: string): void {
523
+ const dirEntries = readdirSync(currentDir, { withFileTypes: true });
524
+ for (const entry of dirEntries) {
525
+ const fullPath = join(currentDir, entry.name);
526
+
527
+ // Skip symlinks
528
+ const fileStat = lstatSync(fullPath);
529
+ if (fileStat.isSymbolicLink()) continue;
530
+
531
+ if (fileStat.isDirectory()) {
532
+ // Check skip list against the relative path from the walk root
533
+ const relDir = relative(dir, fullPath);
534
+ if (skipDirs.some((s) => relDir === s || relDir.startsWith(s + "/"))) {
535
+ continue;
536
+ }
537
+ walk(fullPath);
538
+ } else if (fileStat.isFile()) {
539
+ // Skip SQLite auxiliary files — these are ephemeral and race-prone
540
+ if (
541
+ entry.name.endsWith(".db-wal") ||
542
+ entry.name.endsWith(".db-shm") ||
543
+ entry.name.endsWith(".db-journal")
544
+ ) {
545
+ continue;
546
+ }
547
+
548
+ // Skip binary files unless explicitly included
549
+ if (!includeBinary) {
550
+ // Read only the first 8 KB to check for null bytes
551
+ const checkLength = Math.min(fileStat.size, 8192);
552
+ if (checkLength > 0) {
553
+ const buf = Buffer.alloc(checkLength);
554
+ const fd = openSync(fullPath, "r");
555
+ try {
556
+ readSync(fd, buf, 0, checkLength, 0);
557
+ } finally {
558
+ closeSync(fd);
559
+ }
560
+ let isBinary = false;
561
+ for (let i = 0; i < checkLength; i++) {
562
+ if (buf[i] === 0) {
563
+ isBinary = true;
564
+ break;
565
+ }
566
+ }
567
+ if (isBinary) continue;
568
+ }
569
+ }
570
+
571
+ const relativePath = relative(dir, fullPath);
572
+ entries.push({
573
+ archivePath: `${archivePrefix}/${relativePath}`,
574
+ diskPath: fullPath,
575
+ size: fileStat.size,
576
+ });
577
+ }
578
+ }
579
+ }
580
+
581
+ walk(dir);
582
+ return entries;
583
+ }
584
+
585
+ /**
586
+ * Compute SHA-256 hex digest of a file by streaming — never buffers the
587
+ * entire file in memory. When `size` is provided, only hashes the first
588
+ * `size` bytes to match what will be archived in the tar entry.
589
+ */
590
+ async function computeFileSha256(
591
+ filePath: string,
592
+ size?: number,
593
+ ): Promise<string> {
594
+ const hash = createHash("sha256");
595
+ if (size === 0) return hash.digest("hex");
596
+ const streamOpts =
597
+ size !== undefined ? { start: 0, end: size - 1 } : undefined;
598
+ const stream = createReadStream(filePath, streamOpts);
599
+ for await (const chunk of stream) {
600
+ hash.update(chunk);
601
+ }
602
+ return hash.digest("hex");
603
+ }
604
+
605
+ /**
606
+ * Create just the 512-byte tar header block for a regular file entry.
607
+ * Extracted from `createTarEntry` logic — does NOT include data or padding.
608
+ */
609
+ function createTarHeaderBlock(name: string, size: number): Uint8Array {
610
+ const encoder = new TextEncoder();
611
+ const nameBytes = encoder.encode(name);
612
+
613
+ const header = new Uint8Array(BLOCK_SIZE);
614
+
615
+ // File name (0-99) — truncated if >100 bytes
616
+ header.set(nameBytes.subarray(0, 100), 0);
617
+
618
+ // File mode (100-107): 0644
619
+ writeOctal(header, 100, 8, 0o644);
620
+
621
+ // Owner ID (108-115)
622
+ writeOctal(header, 108, 8, 0);
623
+
624
+ // Group ID (116-123)
625
+ writeOctal(header, 116, 8, 0);
626
+
627
+ // File size (124-135)
628
+ writeOctal(header, 124, 12, size);
629
+
630
+ // Modification time (136-147)
631
+ writeOctal(header, 136, 12, Math.floor(Date.now() / 1000));
632
+
633
+ // Type flag (156): regular file
634
+ header[156] = "0".charCodeAt(0);
635
+
636
+ // USTAR magic (257-262)
637
+ const magic = encoder.encode("ustar\0");
638
+ header.set(magic, 257);
639
+
640
+ // USTAR version (263-264)
641
+ header[263] = "0".charCodeAt(0);
642
+ header[264] = "0".charCodeAt(0);
643
+
644
+ // Compute and write checksum (148-155)
645
+ const checksum = computeHeaderChecksum(header);
646
+ writeOctal(header, 148, 7, checksum);
647
+ header[155] = 0x20; // trailing space
648
+
649
+ return header;
650
+ }
651
+
652
+ /**
653
+ * If name exceeds 100 bytes, returns the PAX extended header entry
654
+ * concatenated with the regular header block. Otherwise returns just
655
+ * the header block.
656
+ */
657
+ function createPaxAndHeaderBlocks(name: string, size: number): Uint8Array {
658
+ const encoder = new TextEncoder();
659
+ const nameBytes = encoder.encode(name);
660
+ const needsPax = nameBytes.length > 100;
661
+
662
+ const header = createTarHeaderBlock(name, size);
663
+
664
+ if (needsPax) {
665
+ const paxEntry = createPaxPathEntry(name);
666
+ const result = new Uint8Array(paxEntry.length + header.length);
667
+ result.set(paxEntry, 0);
668
+ result.set(header, paxEntry.length);
669
+ return result;
670
+ }
671
+
672
+ return header;
673
+ }
674
+
675
+ /**
676
+ * Returns zero-filled padding bytes to align data to the tar block boundary.
677
+ */
678
+ function tarPaddingBytes(dataSize: number): Uint8Array {
679
+ const remainder = dataSize % BLOCK_SIZE;
680
+ if (remainder === 0) return new Uint8Array(0);
681
+ return new Uint8Array(BLOCK_SIZE - remainder);
682
+ }
683
+
684
+ /**
685
+ * Async generator that yields raw tar bytes in order:
686
+ * manifest entry, then each file entry, then end-of-archive marker.
687
+ * Each file is streamed from disk — never fully buffered in memory.
688
+ */
689
+ async function* generateTarStream(
690
+ manifestJson: Uint8Array,
691
+ files: FileMetadata[],
692
+ ): AsyncGenerator<Uint8Array> {
693
+ // Manifest entry
694
+ yield createPaxAndHeaderBlocks("manifest.json", manifestJson.length);
695
+ yield manifestJson;
696
+ yield tarPaddingBytes(manifestJson.length);
697
+
698
+ // File entries
699
+ for (const file of files) {
700
+ yield createPaxAndHeaderBlocks(file.archivePath, file.size);
701
+
702
+ // Stream exactly file.size bytes from disk. Capping the read at the
703
+ // declared size keeps the tar structure valid even if the file grows
704
+ // between passes (common for log files on active assistants). If the
705
+ // file shrinks below the declared size, zero-pad to maintain block
706
+ // alignment. The WAL checkpoint before export is the primary
707
+ // consistency mechanism for the database.
708
+ let bytesWritten = 0;
709
+ if (file.size > 0) {
710
+ try {
711
+ const stream = createReadStream(file.diskPath, {
712
+ start: 0,
713
+ end: file.size - 1,
714
+ });
715
+ for await (const chunk of stream) {
716
+ const data =
717
+ chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
718
+ bytesWritten += data.length;
719
+ yield data;
720
+ }
721
+ } catch {
722
+ // File was deleted or rotated between passes — emit zeros for
723
+ // the full declared size so the tar structure stays valid
724
+ }
725
+ }
726
+
727
+ // If the file shrank, pad with zeros in bounded chunks to reach
728
+ // the declared size without a large single allocation
729
+ let remaining = file.size - bytesWritten;
730
+ while (remaining > 0) {
731
+ const chunkSize = Math.min(remaining, 65536);
732
+ yield new Uint8Array(chunkSize);
733
+ remaining -= chunkSize;
734
+ }
735
+
736
+ yield tarPaddingBytes(file.size);
737
+ }
738
+
739
+ // End-of-archive: two zero blocks
740
+ yield new Uint8Array(BLOCK_SIZE * 2);
741
+ }
742
+
743
+ // ---------------------------------------------------------------------------
744
+ // Streaming export result type
745
+ // ---------------------------------------------------------------------------
746
+
747
+ export interface StreamExportVBundleResult {
748
+ tempPath: string;
749
+ size: number;
750
+ manifest: ManifestType;
751
+ cleanup: () => Promise<void>;
752
+ }
753
+
754
+ /**
755
+ * Build a .vbundle archive using a streaming two-pass approach that keeps
756
+ * peak memory usage bounded to ~1 MB regardless of workspace size.
757
+ *
758
+ * Pass 1: Walk directory metadata and compute SHA-256 checksums without
759
+ * loading file contents into memory (builds manifest).
760
+ * Pass 2: Stream tar entries through gzip into a temp file on disk.
761
+ *
762
+ * Returns a result with the temp file path, size, manifest, and a cleanup
763
+ * function to remove the temp file when done.
764
+ */
765
+ export async function streamExportVBundle(
766
+ options: BuildExportVBundleOptions,
767
+ ): Promise<StreamExportVBundleResult> {
768
+ const { source, description, checkpoint, trustPath, workspaceDir, hooksDir } =
769
+ options;
770
+
771
+ // Flush WAL to the main database file before reading
772
+ if (checkpoint) {
773
+ checkpoint();
774
+ }
775
+
776
+ const allFileMetadata: FileMetadata[] = [];
777
+
778
+ // Walk the entire workspace directory, including binary files
779
+ if (
780
+ workspaceDir &&
781
+ existsSync(workspaceDir) &&
782
+ lstatSync(workspaceDir).isDirectory()
783
+ ) {
784
+ allFileMetadata.push(
785
+ ...walkDirectoryForMetadata(workspaceDir, "workspace", {
786
+ includeBinary: true,
787
+ skipDirs: ["embedding-models", "data/qdrant", "signals", "deprecated"],
788
+ }),
789
+ );
790
+ }
791
+
792
+ // Include hooks directory if it exists
793
+ if (hooksDir && existsSync(hooksDir) && lstatSync(hooksDir).isDirectory()) {
794
+ allFileMetadata.push(...walkDirectoryForMetadata(hooksDir, "hooks"));
795
+ }
796
+
797
+ // Include trust rules if the file exists
798
+ if (trustPath && existsSync(trustPath)) {
799
+ const trustStat = lstatSync(trustPath);
800
+ if (trustStat.isFile()) {
801
+ allFileMetadata.push({
802
+ archivePath: "trust/trust.json",
803
+ diskPath: trustPath,
804
+ size: trustStat.size,
805
+ });
806
+ }
807
+ }
808
+
809
+ // ------------------------------------------------------------------
810
+ // Pass 1: Compute SHA-256 checksums to build the manifest
811
+ // ------------------------------------------------------------------
812
+
813
+ const fileEntries: ManifestFileEntryType[] = [];
814
+ for (const file of allFileMetadata) {
815
+ const sha256 = await computeFileSha256(file.diskPath, file.size);
816
+ fileEntries.push({
817
+ path: file.archivePath,
818
+ sha256,
819
+ size: file.size,
820
+ });
821
+ }
822
+
823
+ const manifestWithoutChecksum = {
824
+ schema_version: "1.0",
825
+ created_at: new Date().toISOString(),
826
+ source: source ?? "runtime-export",
827
+ description: description ?? "Runtime export bundle",
828
+ files: fileEntries,
829
+ };
830
+
831
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
832
+ const manifest: ManifestType = {
833
+ ...manifestWithoutChecksum,
834
+ manifest_sha256: manifestSha256,
835
+ };
836
+
837
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
838
+
839
+ // ------------------------------------------------------------------
840
+ // Pass 2: Stream tar through gzip into a temp file
841
+ // ------------------------------------------------------------------
842
+
843
+ const tempPath = join(tmpdir(), `vbundle-export-${randomUUID()}.tmp`);
844
+
845
+ const tarGenerator = generateTarStream(manifestData, allFileMetadata);
846
+ const tarReadable = Readable.from(tarGenerator);
847
+ const gzipStream = createGzip();
848
+ const writeStream = createWriteStream(tempPath, { mode: 0o600 });
849
+
850
+ try {
851
+ await pipeline(tarReadable, gzipStream, writeStream);
852
+ } catch (error) {
853
+ await unlink(tempPath).catch(() => {});
854
+ throw error;
855
+ }
856
+
857
+ const tempStat = await stat(tempPath);
858
+
859
+ const cleanup = async () => {
860
+ try {
861
+ await unlink(tempPath);
862
+ } catch {
863
+ // Ignore errors during cleanup
864
+ }
865
+ };
866
+
867
+ return { tempPath, size: tempStat.size, manifest, cleanup };
868
+ }
@@ -163,15 +163,16 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
163
163
  entryMap = validation.entries;
164
164
  }
165
165
 
166
- // Directories to preserve when clearing the workspace (large/regenerable).
166
+ // Directories to preserve when clearing the workspace.
167
167
  const WORKSPACE_SKIP_DIRS = new Set(["embedding-models", "deprecated"]);
168
- // data/qdrant is nested — we skip "qdrant" inside "data/"
169
- const DATA_SKIP_DIRS = new Set(["qdrant"]);
168
+ // data/qdrant and data/db are nested — we skip them inside "data/"
169
+ const DATA_SKIP_DIRS = new Set(["qdrant", "db"]);
170
170
 
171
171
  // Step 1b: Clear the workspace directory before restore if the bundle
172
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).
173
+ // restore with no stale files left behind. Skips embedding-models/,
174
+ // data/qdrant/ (large, regenerable), and data/db/ (critical — prevents
175
+ // data loss if the import fails partway or the archive omits the DB).
175
176
  //
176
177
  // Only new-format bundles (workspace/ prefix) trigger clearing. Old-format
177
178
  // bundles (skills/, hooks/, data/db/*, config/*) wrote specific files
@@ -195,7 +196,8 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
195
196
 
196
197
  const entryPath = join(workspaceDir, entry.name);
197
198
  if (entry.name === "data" && entry.isDirectory()) {
198
- // Inside data/, preserve qdrant/ but clear everything else
199
+ // Inside data/, preserve qdrant/ (large, regenerable) and db/
200
+ // (critical user data) but clear everything else
199
201
  const dataEntries = readdirSync(entryPath, { withFileTypes: true });
200
202
  for (const dataEntry of dataEntries) {
201
203
  if (DATA_SKIP_DIRS.has(dataEntry.name)) continue;