@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
@@ -0,0 +1,502 @@
1
+ /**
2
+ * Tests for profiler HTTP route handlers: empty-state listings, missing-run
3
+ * 404s, active-run delete rejection, tarball export success, archive failure
4
+ * when a run directory exceeds the configured bundle size cap, and post-delete
5
+ * budget recalculation.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import type { ProfilerRunManifest } from "../daemon/profiler-run-store.js";
14
+ import type { AuthContext } from "../runtime/auth/types.js";
15
+ import type { RouteContext, RouteParams } from "../runtime/http-router.js";
16
+ import { profilerRouteDefinitions } from "../runtime/routes/profiler-routes.js";
17
+
18
+ // ── Test scaffolding ────────────────────────────────────────────────────
19
+
20
+ let testDir: string;
21
+ let runsDir: string;
22
+ let origEnv: Record<string, string | undefined>;
23
+
24
+ const routes = profilerRouteDefinitions();
25
+
26
+ function findRoute(
27
+ endpoint: string,
28
+ method: string,
29
+ ): (typeof routes)[number] | undefined {
30
+ return routes.find((r) => r.endpoint === endpoint && r.method === method);
31
+ }
32
+
33
+ /**
34
+ * Build a minimal RouteContext for testing route handlers.
35
+ */
36
+ function makeCtx(
37
+ params: RouteParams = {},
38
+ method: string = "GET",
39
+ ): RouteContext {
40
+ return {
41
+ req: new Request("http://localhost:7821/v1/profiler/runs", { method }),
42
+ url: new URL("http://localhost:7821/v1/profiler/runs"),
43
+ server: {} as ReturnType<typeof Bun.serve>,
44
+ authContext: {
45
+ subject: "svc:gateway:self",
46
+ principalType: "svc_gateway",
47
+ assistantId: "self",
48
+ scopeProfile: "gateway_service_v1",
49
+ scopes: new Set(["internal.write"]),
50
+ policyEpoch: 1,
51
+ } as AuthContext,
52
+ params,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Create a fake profiler run directory with some payload files.
58
+ */
59
+ function createRun(
60
+ runId: string,
61
+ opts?: {
62
+ sizeBytes?: number;
63
+ manifest?: Partial<ProfilerRunManifest>;
64
+ markdownSummary?: string;
65
+ },
66
+ ): string {
67
+ const dir = join(runsDir, runId);
68
+ mkdirSync(dir, { recursive: true });
69
+
70
+ // Write a payload file of the requested size
71
+ const size = opts?.sizeBytes ?? 1024;
72
+ writeFileSync(join(dir, "profile.cpuprofile"), Buffer.alloc(size));
73
+
74
+ // Optionally write a pre-existing manifest
75
+ if (opts?.manifest) {
76
+ const m: ProfilerRunManifest = {
77
+ runId,
78
+ status: opts.manifest.status ?? "completed",
79
+ createdAt: opts.manifest.createdAt ?? new Date().toISOString(),
80
+ updatedAt: opts.manifest.updatedAt ?? new Date().toISOString(),
81
+ totalBytes: opts.manifest.totalBytes ?? size,
82
+ };
83
+ writeFileSync(join(dir, "manifest.json"), JSON.stringify(m, null, 2));
84
+ }
85
+
86
+ // Optionally write a markdown summary
87
+ if (opts?.markdownSummary) {
88
+ writeFileSync(join(dir, "profile-summary.md"), opts.markdownSummary);
89
+ }
90
+
91
+ return dir;
92
+ }
93
+
94
+ beforeEach(() => {
95
+ testDir = join(
96
+ tmpdir(),
97
+ `vellum-profiler-routes-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
98
+ );
99
+ runsDir = join(testDir, "data", "profiler", "runs");
100
+ mkdirSync(runsDir, { recursive: true });
101
+
102
+ // Save and override env
103
+ origEnv = {
104
+ VELLUM_WORKSPACE_DIR: process.env.VELLUM_WORKSPACE_DIR,
105
+ VELLUM_PROFILER_RUN_ID: process.env.VELLUM_PROFILER_RUN_ID,
106
+ VELLUM_PROFILER_MAX_BYTES: process.env.VELLUM_PROFILER_MAX_BYTES,
107
+ VELLUM_PROFILER_MAX_RUNS: process.env.VELLUM_PROFILER_MAX_RUNS,
108
+ VELLUM_PROFILER_MIN_FREE_MB: process.env.VELLUM_PROFILER_MIN_FREE_MB,
109
+ };
110
+
111
+ // Point workspace dir to our temp directory
112
+ process.env.VELLUM_WORKSPACE_DIR = testDir;
113
+
114
+ // Clear profiler env vars
115
+ delete process.env.VELLUM_PROFILER_RUN_ID;
116
+ delete process.env.VELLUM_PROFILER_MAX_BYTES;
117
+ delete process.env.VELLUM_PROFILER_MAX_RUNS;
118
+ delete process.env.VELLUM_PROFILER_MIN_FREE_MB;
119
+ });
120
+
121
+ afterEach(() => {
122
+ // Restore env
123
+ for (const [key, value] of Object.entries(origEnv)) {
124
+ if (value === undefined) {
125
+ delete process.env[key];
126
+ } else {
127
+ process.env[key] = value;
128
+ }
129
+ }
130
+
131
+ // Clean up temp directory
132
+ if (existsSync(testDir)) {
133
+ rmSync(testDir, { recursive: true, force: true });
134
+ }
135
+ });
136
+
137
+ // ── Tests ───────────────────────────────────────────────────────────────
138
+
139
+ describe("Profiler routes", () => {
140
+ describe("GET /v1/profiler/runs (list)", () => {
141
+ test("returns empty list when no runs exist", async () => {
142
+ const route = findRoute("profiler/runs", "GET")!;
143
+ const response = await route.handler(makeCtx());
144
+ const body = (await response.json()) as {
145
+ runs: unknown[];
146
+ totalRuns: number;
147
+ activeRunId: string | null;
148
+ };
149
+
150
+ expect(response.status).toBe(200);
151
+ expect(body.runs).toEqual([]);
152
+ expect(body.totalRuns).toBe(0);
153
+ expect(body.activeRunId).toBeNull();
154
+ });
155
+
156
+ test("returns runs sorted newest-first", async () => {
157
+ createRun("old-run", {
158
+ sizeBytes: 1024,
159
+ manifest: {
160
+ status: "completed",
161
+ createdAt: "2025-01-01T00:00:00Z",
162
+ },
163
+ });
164
+ createRun("new-run", {
165
+ sizeBytes: 1024,
166
+ manifest: {
167
+ status: "completed",
168
+ createdAt: "2025-06-01T00:00:00Z",
169
+ },
170
+ });
171
+
172
+ const route = findRoute("profiler/runs", "GET")!;
173
+ const response = await route.handler(makeCtx());
174
+ const body = (await response.json()) as {
175
+ runs: ProfilerRunManifest[];
176
+ totalRuns: number;
177
+ };
178
+
179
+ expect(response.status).toBe(200);
180
+ expect(body.totalRuns).toBe(2);
181
+ expect(body.runs[0]!.runId).toBe("new-run");
182
+ expect(body.runs[1]!.runId).toBe("old-run");
183
+ });
184
+
185
+ test("reports active run ID when set", async () => {
186
+ process.env.VELLUM_PROFILER_RUN_ID = "active-run";
187
+ createRun("active-run", { sizeBytes: 512 });
188
+
189
+ const route = findRoute("profiler/runs", "GET")!;
190
+ const response = await route.handler(makeCtx());
191
+ const body = (await response.json()) as {
192
+ activeRunId: string | null;
193
+ };
194
+
195
+ expect(body.activeRunId).toBe("active-run");
196
+ });
197
+ });
198
+
199
+ describe("path traversal rejection", () => {
200
+ const traversalPayloads = [
201
+ "../../../etc/passwd",
202
+ "..%2F..%2Fetc%2Fpasswd",
203
+ "foo/bar",
204
+ "foo\\bar",
205
+ "..\\..\\windows",
206
+ ];
207
+
208
+ for (const payload of traversalPayloads) {
209
+ test(`GET rejects runId "${payload}"`, async () => {
210
+ const route = findRoute("profiler/runs/:runId", "GET")!;
211
+ const response = await route.handler(makeCtx({ runId: payload }));
212
+ expect(response.status).toBe(400);
213
+ });
214
+
215
+ test(`POST export rejects runId "${payload}"`, async () => {
216
+ const route = findRoute("profiler/runs/:runId/export", "POST")!;
217
+ const response = await route.handler(
218
+ makeCtx({ runId: payload }, "POST"),
219
+ );
220
+ expect(response.status).toBe(400);
221
+ });
222
+
223
+ test(`DELETE rejects runId "${payload}"`, async () => {
224
+ const route = findRoute("profiler/runs/:runId", "DELETE")!;
225
+ const response = await route.handler(
226
+ makeCtx({ runId: payload }, "DELETE"),
227
+ );
228
+ expect(response.status).toBe(400);
229
+ });
230
+ }
231
+ });
232
+
233
+ describe("GET /v1/profiler/runs/:runId (detail)", () => {
234
+ test("returns 404 for missing run", async () => {
235
+ const route = findRoute("profiler/runs/:runId", "GET")!;
236
+ const response = await route.handler(makeCtx({ runId: "nonexistent" }));
237
+
238
+ expect(response.status).toBe(404);
239
+ const body = (await response.json()) as {
240
+ error: { code: string };
241
+ };
242
+ expect(body.error.code).toBe("NOT_FOUND");
243
+ });
244
+
245
+ test("returns manifest metadata and markdown summary", async () => {
246
+ createRun("run-with-summary", {
247
+ sizeBytes: 2048,
248
+ manifest: {
249
+ status: "completed",
250
+ createdAt: "2025-03-15T10:00:00Z",
251
+ },
252
+ markdownSummary: "# CPU Profile\n\nTop functions by self-time...",
253
+ });
254
+
255
+ const route = findRoute("profiler/runs/:runId", "GET")!;
256
+ const response = await route.handler(
257
+ makeCtx({ runId: "run-with-summary" }),
258
+ );
259
+ const body = (await response.json()) as {
260
+ runId: string;
261
+ status: string;
262
+ summary: string | null;
263
+ isActive: boolean;
264
+ };
265
+
266
+ expect(response.status).toBe(200);
267
+ expect(body.runId).toBe("run-with-summary");
268
+ expect(body.status).toBe("completed");
269
+ expect(body.summary).toContain("CPU Profile");
270
+ expect(body.isActive).toBe(false);
271
+ });
272
+
273
+ test("returns null summary when no markdown file exists", async () => {
274
+ createRun("run-no-summary", {
275
+ sizeBytes: 1024,
276
+ manifest: { status: "completed" },
277
+ });
278
+
279
+ const route = findRoute("profiler/runs/:runId", "GET")!;
280
+ const response = await route.handler(
281
+ makeCtx({ runId: "run-no-summary" }),
282
+ );
283
+ const body = (await response.json()) as {
284
+ summary: string | null;
285
+ };
286
+
287
+ expect(response.status).toBe(200);
288
+ expect(body.summary).toBeNull();
289
+ });
290
+
291
+ test("marks active run correctly", async () => {
292
+ process.env.VELLUM_PROFILER_RUN_ID = "live-run";
293
+ createRun("live-run", {
294
+ sizeBytes: 1024,
295
+ manifest: { status: "active" },
296
+ });
297
+
298
+ const route = findRoute("profiler/runs/:runId", "GET")!;
299
+ const response = await route.handler(makeCtx({ runId: "live-run" }));
300
+ const body = (await response.json()) as {
301
+ isActive: boolean;
302
+ };
303
+
304
+ expect(response.status).toBe(200);
305
+ expect(body.isActive).toBe(true);
306
+ });
307
+ });
308
+
309
+ describe("POST /v1/profiler/runs/:runId/export", () => {
310
+ test("returns 404 for missing run", async () => {
311
+ const route = findRoute("profiler/runs/:runId/export", "POST")!;
312
+ const response = await route.handler(
313
+ makeCtx({ runId: "nonexistent" }, "POST"),
314
+ );
315
+
316
+ expect(response.status).toBe(404);
317
+ });
318
+
319
+ test("returns tar.gz for a valid run", async () => {
320
+ createRun("exportable-run", {
321
+ sizeBytes: 512,
322
+ manifest: { status: "completed" },
323
+ });
324
+
325
+ const route = findRoute("profiler/runs/:runId/export", "POST")!;
326
+ const response = await route.handler(
327
+ makeCtx({ runId: "exportable-run" }, "POST"),
328
+ );
329
+
330
+ expect(response.status).toBe(200);
331
+ expect(response.headers.get("Content-Type")).toBe("application/gzip");
332
+ expect(response.headers.get("Content-Disposition")).toContain(
333
+ "profiler-exportable-run.tar.gz",
334
+ );
335
+
336
+ const arrayBuffer = await response.arrayBuffer();
337
+ expect(arrayBuffer.byteLength).toBeGreaterThan(0);
338
+ });
339
+
340
+ test("returns 500 when archive exceeds size limit", async () => {
341
+ // Create a run with a very large file that will exceed the 50MB archive cap.
342
+ // We mock this by creating a run directory and writing enough data to push
343
+ // past the limit. Since createTarGz uses MAX_ARCHIVE_BYTES as the maxBuffer,
344
+ // a large-enough payload will trigger the failure path.
345
+ const runDir = join(runsDir, "huge-run");
346
+ mkdirSync(runDir, { recursive: true });
347
+
348
+ // Write manifest so the route can find the run
349
+ const manifest: ProfilerRunManifest = {
350
+ runId: "huge-run",
351
+ status: "completed",
352
+ createdAt: new Date().toISOString(),
353
+ updatedAt: new Date().toISOString(),
354
+ totalBytes: 60 * 1024 * 1024,
355
+ };
356
+ writeFileSync(
357
+ join(runDir, "manifest.json"),
358
+ JSON.stringify(manifest, null, 2),
359
+ );
360
+
361
+ // Write a file large enough that the compressed tar exceeds 50MB.
362
+ // Random data doesn't compress well, so 55MB of random data should
363
+ // produce a tar.gz larger than 50MB.
364
+ const chunkSize = 1024 * 1024; // 1 MB
365
+ const chunks = 55;
366
+ for (let i = 0; i < chunks; i++) {
367
+ const buf = Buffer.alloc(chunkSize);
368
+ // Fill with pseudo-random data to defeat compression
369
+ for (let j = 0; j < chunkSize; j += 4) {
370
+ buf.writeUInt32LE((Math.random() * 0xffffffff) >>> 0, j);
371
+ }
372
+ writeFileSync(join(runDir, `chunk-${i}.bin`), buf);
373
+ }
374
+
375
+ const route = findRoute("profiler/runs/:runId/export", "POST")!;
376
+ const response = await route.handler(
377
+ makeCtx({ runId: "huge-run" }, "POST"),
378
+ );
379
+
380
+ expect(response.status).toBe(500);
381
+ const body = (await response.json()) as {
382
+ error: { code: string; message: string };
383
+ };
384
+ expect(body.error.code).toBe("INTERNAL_ERROR");
385
+ expect(body.error.message).toContain("archive size");
386
+ });
387
+ });
388
+
389
+ describe("DELETE /v1/profiler/runs/:runId", () => {
390
+ test("returns 404 for missing run", async () => {
391
+ const route = findRoute("profiler/runs/:runId", "DELETE")!;
392
+ const response = await route.handler(
393
+ makeCtx({ runId: "nonexistent" }, "DELETE"),
394
+ );
395
+
396
+ expect(response.status).toBe(404);
397
+ });
398
+
399
+ test("rejects deletion of the currently active run", async () => {
400
+ process.env.VELLUM_PROFILER_RUN_ID = "active-run";
401
+ createRun("active-run", {
402
+ sizeBytes: 1024,
403
+ manifest: { status: "active" },
404
+ });
405
+
406
+ const route = findRoute("profiler/runs/:runId", "DELETE")!;
407
+ const response = await route.handler(
408
+ makeCtx({ runId: "active-run" }, "DELETE"),
409
+ );
410
+
411
+ expect(response.status).toBe(409);
412
+ const body = (await response.json()) as {
413
+ error: { code: string };
414
+ };
415
+ expect(body.error.code).toBe("CONFLICT");
416
+
417
+ // Run directory should still exist
418
+ expect(existsSync(join(runsDir, "active-run"))).toBe(true);
419
+ });
420
+
421
+ test("deletes a completed run and returns budget state", async () => {
422
+ process.env.VELLUM_PROFILER_MAX_BYTES = "999999999";
423
+ process.env.VELLUM_PROFILER_MAX_RUNS = "100";
424
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
425
+
426
+ createRun("completed-run", {
427
+ sizeBytes: 2048,
428
+ manifest: {
429
+ status: "completed",
430
+ createdAt: "2025-01-01T00:00:00Z",
431
+ },
432
+ });
433
+ createRun("other-run", {
434
+ sizeBytes: 1024,
435
+ manifest: {
436
+ status: "completed",
437
+ createdAt: "2025-02-01T00:00:00Z",
438
+ },
439
+ });
440
+
441
+ const route = findRoute("profiler/runs/:runId", "DELETE")!;
442
+ const response = await route.handler(
443
+ makeCtx({ runId: "completed-run" }, "DELETE"),
444
+ );
445
+ const body = (await response.json()) as {
446
+ deleted: boolean;
447
+ runId: string;
448
+ remainingRuns: number;
449
+ activeRunOverBudget: boolean;
450
+ };
451
+
452
+ expect(response.status).toBe(200);
453
+ expect(body.deleted).toBe(true);
454
+ expect(body.runId).toBe("completed-run");
455
+ expect(body.remainingRuns).toBe(1);
456
+ expect(body.activeRunOverBudget).toBe(false);
457
+
458
+ // Run directory should be gone
459
+ expect(existsSync(join(runsDir, "completed-run"))).toBe(false);
460
+ // Other run should still exist
461
+ expect(existsSync(join(runsDir, "other-run"))).toBe(true);
462
+ });
463
+
464
+ test("post-delete budget recalculation reflects freed space", async () => {
465
+ process.env.VELLUM_PROFILER_MAX_BYTES = "5000";
466
+ process.env.VELLUM_PROFILER_MAX_RUNS = "100";
467
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
468
+
469
+ // Create two runs that together exceed the 5000 byte budget
470
+ createRun("over-budget-a", {
471
+ sizeBytes: 3000,
472
+ manifest: {
473
+ status: "completed",
474
+ createdAt: "2025-01-01T00:00:00Z",
475
+ },
476
+ });
477
+ createRun("over-budget-b", {
478
+ sizeBytes: 3000,
479
+ manifest: {
480
+ status: "completed",
481
+ createdAt: "2025-02-01T00:00:00Z",
482
+ },
483
+ });
484
+
485
+ // Delete one of the runs
486
+ const route = findRoute("profiler/runs/:runId", "DELETE")!;
487
+ const response = await route.handler(
488
+ makeCtx({ runId: "over-budget-a" }, "DELETE"),
489
+ );
490
+ const body = (await response.json()) as {
491
+ deleted: boolean;
492
+ remainingRuns: number;
493
+ };
494
+
495
+ expect(response.status).toBe(200);
496
+ expect(body.deleted).toBe(true);
497
+ // The remaining run should survive since it's within budget now
498
+ expect(body.remainingRuns).toBe(1);
499
+ expect(existsSync(join(runsDir, "over-budget-b"))).toBe(true);
500
+ });
501
+ });
502
+ });