@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
@@ -3,7 +3,7 @@ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
 
5
5
  import { getLogger } from "../util/logger.js";
6
- import { getWorkspaceDir } from "../util/platform.js";
6
+ import { AVATAR_IMAGE_FILENAME, getAvatarDir } from "../util/platform.js";
7
7
  import { renderCharacterAscii } from "./ascii-renderer.js";
8
8
  import { getCharacterComponents } from "./character-components.js";
9
9
  import { renderCharacterPng } from "./png-renderer.js";
@@ -64,7 +64,7 @@ function writeAvatarFiles(
64
64
  pngBuffer: Buffer,
65
65
  asciiArt: string | null,
66
66
  ): boolean {
67
- const pngPath = join(avatarDir, "avatar-image.png");
67
+ const pngPath = join(avatarDir, AVATAR_IMAGE_FILENAME);
68
68
  const pngTmp = `${pngPath}.${randomUUID()}.tmp`;
69
69
  writeFileSync(pngTmp, pngBuffer);
70
70
  renameSync(pngTmp, pngPath);
@@ -144,7 +144,7 @@ export function writeTraitsAndRenderAvatar(
144
144
  };
145
145
  }
146
146
 
147
- const avatarDir = join(getWorkspaceDir(), "data", "avatar");
147
+ const avatarDir = getAvatarDir();
148
148
  const traitsPath = join(avatarDir, "character-traits.json");
149
149
 
150
150
  try {
@@ -90,6 +90,11 @@ export function isInteractiveInterface(id: InterfaceId): boolean {
90
90
  return INTERACTIVE_INTERFACES.has(id);
91
91
  }
92
92
 
93
+ /** Whether the interface supports host proxies (bash, file, computer-use). */
94
+ export function supportsHostProxy(id: InterfaceId): boolean {
95
+ return id === "macos";
96
+ }
97
+
93
98
  export interface TurnInterfaceContext {
94
99
  userMessageInterface: InterfaceId;
95
100
  assistantMessageInterface: InterfaceId;
@@ -0,0 +1,56 @@
1
+ export interface AssistantCommandResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ }
5
+
6
+ /**
7
+ * CLI test utility — run an assistant CLI command via the real program,
8
+ * capturing stdout and stderr.
9
+ *
10
+ * Returns both stdout and stderr. For backward compatibility, the function
11
+ * is also callable with just a string return (use `runAssistantCommand`).
12
+ */
13
+ export async function runAssistantCommandFull(
14
+ ...args: string[]
15
+ ): Promise<AssistantCommandResult> {
16
+ const { buildCliProgram } = await import("../program.js");
17
+ const program = await buildCliProgram();
18
+ program.exitOverride();
19
+
20
+ const stderrChunks: string[] = [];
21
+ program.configureOutput({
22
+ writeErr: (str: string) => stderrChunks.push(str),
23
+ writeOut: () => {},
24
+ });
25
+
26
+ const stdoutChunks: string[] = [];
27
+ const originalWrite = process.stdout.write;
28
+ process.stdout.write = ((chunk: string | Uint8Array) => {
29
+ stdoutChunks.push(
30
+ typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk),
31
+ );
32
+ return true;
33
+ }) as typeof process.stdout.write;
34
+
35
+ try {
36
+ await program.parseAsync(["node", "assistant", ...args]);
37
+ } catch {
38
+ /* commander exit override throws */
39
+ } finally {
40
+ process.stdout.write = originalWrite;
41
+ }
42
+
43
+ return {
44
+ stdout: stdoutChunks.join(""),
45
+ stderr: stderrChunks.join(""),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * CLI test utility — run an assistant CLI command via the real program,
51
+ * capturing stdout (backward-compatible wrapper).
52
+ */
53
+ export async function runAssistantCommand(...args: string[]): Promise<string> {
54
+ const result = await runAssistantCommandFull(...args);
55
+ return result.stdout;
56
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "bun:test";
2
+
3
+ import { runAssistantCommandFull } from "./run-assistant-command.js";
4
+
5
+ describe("unknown command handling", () => {
6
+ it("reports an error for an unknown subcommand", async () => {
7
+ const { stderr } = await runAssistantCommandFull("invalid");
8
+
9
+ expect(stderr).toContain("unknown command 'invalid'");
10
+ expect(stderr).toContain("Run 'assistant --help'");
11
+ });
12
+
13
+ it("reports an error for an unknown subcommand with extra arguments", async () => {
14
+ const { stderr } = await runAssistantCommandFull("invalid", "something");
15
+
16
+ expect(stderr).toContain("unknown command 'invalid'");
17
+ expect(stderr).toContain("Run 'assistant --help'");
18
+ });
19
+
20
+ it("suggests a similar command when the input is close", async () => {
21
+ const { stderr } = await runAssistantCommandFull("confg");
22
+
23
+ expect(stderr).toContain("unknown command 'confg'");
24
+ expect(stderr).toContain("Did you mean 'config'");
25
+ });
26
+
27
+ it("does not suggest a command when the input is too far off", async () => {
28
+ const { stderr } = await runAssistantCommandFull("xyzzy");
29
+
30
+ expect(stderr).toContain("unknown command 'xyzzy'");
31
+ expect(stderr).not.toContain("Did you mean");
32
+ });
33
+ });
@@ -0,0 +1,245 @@
1
+ import { existsSync, readFileSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
+
6
+ import {
7
+ getMockFetchCalls,
8
+ mockFetch,
9
+ resetMockFetch,
10
+ } from "../../../__tests__/mock-fetch.js";
11
+ import { _setOverridesForTesting } from "../../../config/assistant-feature-flags.js";
12
+ import { setPlatformAssistantId } from "../../../config/env.js";
13
+ import { credentialKey } from "../../../security/credential-key.js";
14
+ import {
15
+ _resetBackend,
16
+ deleteSecureKeyAsync,
17
+ setSecureKeyAsync,
18
+ } from "../../../security/secure-keys.js";
19
+ import { runAssistantCommand } from "../../__tests__/run-assistant-command.js";
20
+
21
+ const ASSISTANT_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
22
+ const MESSAGE_ID = "msg-001";
23
+ const API_KEY_CREDENTIAL = credentialKey("vellum", "assistant_api_key");
24
+
25
+ const SAMPLE_MESSAGE = {
26
+ id: MESSAGE_ID,
27
+ direction: "inbound",
28
+ from_address: "user@example.com",
29
+ to_addresses: ["mybot@vellum.me"],
30
+ subject: "Hello bot",
31
+ body_text: "Hi, this is a test message.",
32
+ body_html: "<p>Hi, this is a <b>test</b> message.</p>",
33
+ in_reply_to: "",
34
+ references: [],
35
+ created_at: "2026-04-05T12:00:00Z",
36
+ };
37
+
38
+ function mockDetailSuccess(msg = SAMPLE_MESSAGE, status = 200): void {
39
+ mockFetch(`/emails/${msg.id}/`, {}, { body: msg, status });
40
+ }
41
+
42
+ let savedCesUrl: string | undefined;
43
+ let savedContainerized: string | undefined;
44
+ let tmpOutputPath: string;
45
+
46
+ beforeEach(async () => {
47
+ process.exitCode = 0;
48
+
49
+ savedCesUrl = process.env.CES_CREDENTIAL_URL;
50
+ savedContainerized = process.env.IS_CONTAINERIZED;
51
+ delete process.env.CES_CREDENTIAL_URL;
52
+ delete process.env.IS_CONTAINERIZED;
53
+
54
+ _resetBackend();
55
+ resetMockFetch();
56
+ _setOverridesForTesting({ "email-channel": true });
57
+ setPlatformAssistantId(ASSISTANT_ID);
58
+ await setSecureKeyAsync(API_KEY_CREDENTIAL, "test-api-key");
59
+
60
+ tmpOutputPath = join(tmpdir(), `email-download-test-${Date.now()}.txt`);
61
+ });
62
+
63
+ afterEach(() => {
64
+ resetMockFetch();
65
+ _setOverridesForTesting({});
66
+ setPlatformAssistantId(undefined);
67
+ _resetBackend();
68
+
69
+ if (savedCesUrl !== undefined) process.env.CES_CREDENTIAL_URL = savedCesUrl;
70
+ else delete process.env.CES_CREDENTIAL_URL;
71
+ if (savedContainerized !== undefined)
72
+ process.env.IS_CONTAINERIZED = savedContainerized;
73
+ else delete process.env.IS_CONTAINERIZED;
74
+
75
+ if (existsSync(tmpOutputPath)) rmSync(tmpOutputPath);
76
+ });
77
+
78
+ describe("assistant email download", () => {
79
+ test("default format shows headers and plain-text body", async () => {
80
+ mockDetailSuccess();
81
+
82
+ const output = await runAssistantCommand("email", "download", MESSAGE_ID);
83
+
84
+ expect(output).toContain("From: user@example.com");
85
+ expect(output).toContain("To: mybot@vellum.me");
86
+ expect(output).toContain("Subject: Hello bot");
87
+ expect(output).toContain("Hi, this is a test message.");
88
+ expect(process.exitCode).toBe(0);
89
+ });
90
+
91
+ test("--format json returns full message object", async () => {
92
+ mockDetailSuccess();
93
+
94
+ const output = await runAssistantCommand(
95
+ "email",
96
+ "download",
97
+ MESSAGE_ID,
98
+ "--format",
99
+ "json",
100
+ );
101
+
102
+ const parsed = JSON.parse(output.trim());
103
+ expect(parsed.id).toBe(MESSAGE_ID);
104
+ expect(parsed.body_text).toBe("Hi, this is a test message.");
105
+ expect(parsed.body_html).toContain("<b>test</b>");
106
+ expect(process.exitCode).toBe(0);
107
+ });
108
+
109
+ test("--json flag also returns JSON", async () => {
110
+ mockDetailSuccess();
111
+
112
+ const output = await runAssistantCommand(
113
+ "email",
114
+ "--json",
115
+ "download",
116
+ MESSAGE_ID,
117
+ );
118
+
119
+ const parsed = JSON.parse(output.trim());
120
+ expect(parsed.id).toBe(MESSAGE_ID);
121
+ expect(process.exitCode).toBe(0);
122
+ });
123
+
124
+ test("--format html returns HTML body", async () => {
125
+ mockDetailSuccess();
126
+
127
+ const output = await runAssistantCommand(
128
+ "email",
129
+ "download",
130
+ MESSAGE_ID,
131
+ "--format",
132
+ "html",
133
+ );
134
+
135
+ expect(output).toContain("<p>Hi, this is a <b>test</b> message.</p>");
136
+ expect(process.exitCode).toBe(0);
137
+ });
138
+
139
+ test("--format html with no HTML body returns error", async () => {
140
+ mockDetailSuccess({ ...SAMPLE_MESSAGE, body_html: "" });
141
+
142
+ const output = await runAssistantCommand(
143
+ "email",
144
+ "download",
145
+ MESSAGE_ID,
146
+ "--format",
147
+ "html",
148
+ );
149
+
150
+ expect(process.exitCode).toBe(1);
151
+ // stderr output from log.error, but stdout may be empty — check exitCode
152
+ expect(output).not.toContain("<p>");
153
+ });
154
+
155
+ test("--output writes to file", async () => {
156
+ mockDetailSuccess();
157
+
158
+ await runAssistantCommand(
159
+ "email",
160
+ "download",
161
+ MESSAGE_ID,
162
+ "-o",
163
+ tmpOutputPath,
164
+ );
165
+
166
+ expect(process.exitCode).toBe(0);
167
+ expect(existsSync(tmpOutputPath)).toBe(true);
168
+ const content = readFileSync(tmpOutputPath, "utf-8");
169
+ expect(content).toContain("From: user@example.com");
170
+ expect(content).toContain("Hi, this is a test message.");
171
+ });
172
+
173
+ test("calls correct URL", async () => {
174
+ mockDetailSuccess();
175
+
176
+ await runAssistantCommand("email", "download", MESSAGE_ID);
177
+
178
+ const calls = getMockFetchCalls();
179
+ expect(calls).toHaveLength(1);
180
+ expect(calls[0].path).toContain(
181
+ `/v1/assistants/${ASSISTANT_ID}/emails/${MESSAGE_ID}/`,
182
+ );
183
+ });
184
+
185
+ test("404 returns error", async () => {
186
+ mockFetch(
187
+ `/emails/${MESSAGE_ID}/`,
188
+ {},
189
+ { body: { detail: "Not found." }, status: 404 },
190
+ );
191
+
192
+ const output = await runAssistantCommand(
193
+ "email",
194
+ "--json",
195
+ "download",
196
+ MESSAGE_ID,
197
+ );
198
+
199
+ expect(process.exitCode).toBe(1);
200
+ const parsed = JSON.parse(output.trim());
201
+ expect(parsed.error).toContain("Not found");
202
+ });
203
+
204
+ test("missing platform credentials returns error", async () => {
205
+ await deleteSecureKeyAsync(API_KEY_CREDENTIAL);
206
+
207
+ const output = await runAssistantCommand(
208
+ "email",
209
+ "--json",
210
+ "download",
211
+ MESSAGE_ID,
212
+ );
213
+
214
+ expect(process.exitCode).toBe(1);
215
+ const parsed = JSON.parse(output.trim());
216
+ expect(parsed.error).toContain("Platform credentials not configured");
217
+ });
218
+
219
+ test("missing assistant ID returns error", async () => {
220
+ setPlatformAssistantId("");
221
+
222
+ const output = await runAssistantCommand(
223
+ "email",
224
+ "--json",
225
+ "download",
226
+ MESSAGE_ID,
227
+ );
228
+
229
+ expect(process.exitCode).toBe(1);
230
+ const parsed = JSON.parse(output.trim());
231
+ expect(parsed.error).toContain("Assistant ID");
232
+ });
233
+
234
+ test("in_reply_to header shown when present", async () => {
235
+ mockDetailSuccess({
236
+ ...SAMPLE_MESSAGE,
237
+ in_reply_to: "<orig@mail.gmail.com>",
238
+ });
239
+
240
+ const output = await runAssistantCommand("email", "download", MESSAGE_ID);
241
+
242
+ expect(output).toContain("In-Reply-To: <orig@mail.gmail.com>");
243
+ expect(process.exitCode).toBe(0);
244
+ });
245
+ });
@@ -0,0 +1,192 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ getMockFetchCalls,
5
+ mockFetch,
6
+ resetMockFetch,
7
+ } from "../../../__tests__/mock-fetch.js";
8
+ import { _setOverridesForTesting } from "../../../config/assistant-feature-flags.js";
9
+ import { setPlatformAssistantId } from "../../../config/env.js";
10
+ import { credentialKey } from "../../../security/credential-key.js";
11
+ import {
12
+ _resetBackend,
13
+ deleteSecureKeyAsync,
14
+ setSecureKeyAsync,
15
+ } from "../../../security/secure-keys.js";
16
+ import { runAssistantCommand } from "../../__tests__/run-assistant-command.js";
17
+
18
+ const ASSISTANT_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
19
+ const API_KEY_CREDENTIAL = credentialKey("vellum", "assistant_api_key");
20
+
21
+ const SAMPLE_MESSAGES = [
22
+ {
23
+ id: "msg-001",
24
+ direction: "inbound",
25
+ from_address: "user@example.com",
26
+ to_addresses: ["mybot@vellum.me"],
27
+ subject: "Hello bot",
28
+ created_at: "2026-04-05T12:00:00Z",
29
+ },
30
+ {
31
+ id: "msg-002",
32
+ direction: "outbound",
33
+ from_address: "mybot@vellum.me",
34
+ to_addresses: ["user@example.com"],
35
+ subject: "Re: Hello bot",
36
+ created_at: "2026-04-05T12:01:00Z",
37
+ },
38
+ ];
39
+
40
+ function mockListEmails(
41
+ results = SAMPLE_MESSAGES,
42
+ count?: number,
43
+ status = 200,
44
+ ): void {
45
+ mockFetch(
46
+ "/emails/",
47
+ {},
48
+ { body: { results, count: count ?? results.length }, status },
49
+ );
50
+ }
51
+
52
+ let savedCesUrl: string | undefined;
53
+ let savedContainerized: string | undefined;
54
+
55
+ beforeEach(async () => {
56
+ process.exitCode = 0;
57
+
58
+ savedCesUrl = process.env.CES_CREDENTIAL_URL;
59
+ savedContainerized = process.env.IS_CONTAINERIZED;
60
+ delete process.env.CES_CREDENTIAL_URL;
61
+ delete process.env.IS_CONTAINERIZED;
62
+
63
+ _resetBackend();
64
+ resetMockFetch();
65
+ _setOverridesForTesting({ "email-channel": true });
66
+ setPlatformAssistantId(ASSISTANT_ID);
67
+ await setSecureKeyAsync(API_KEY_CREDENTIAL, "test-api-key");
68
+ });
69
+
70
+ afterEach(() => {
71
+ resetMockFetch();
72
+ _setOverridesForTesting({});
73
+ setPlatformAssistantId(undefined);
74
+ _resetBackend();
75
+
76
+ if (savedCesUrl !== undefined) process.env.CES_CREDENTIAL_URL = savedCesUrl;
77
+ else delete process.env.CES_CREDENTIAL_URL;
78
+ if (savedContainerized !== undefined)
79
+ process.env.IS_CONTAINERIZED = savedContainerized;
80
+ else delete process.env.IS_CONTAINERIZED;
81
+ });
82
+
83
+ describe("assistant email list", () => {
84
+ test("--json returns messages and count", async () => {
85
+ mockListEmails();
86
+
87
+ const output = await runAssistantCommand("email", "--json", "list");
88
+
89
+ const parsed = JSON.parse(output.trim());
90
+ expect(parsed.results).toHaveLength(2);
91
+ expect(parsed.count).toBe(2);
92
+ expect(parsed.results[0].subject).toBe("Hello bot");
93
+ expect(parsed.results[1].direction).toBe("outbound");
94
+ expect(process.exitCode).toBe(0);
95
+ });
96
+
97
+ test("calls correct URL with no filters", async () => {
98
+ mockListEmails();
99
+
100
+ await runAssistantCommand("email", "--json", "list");
101
+
102
+ const calls = getMockFetchCalls();
103
+ expect(calls).toHaveLength(1);
104
+ expect(calls[0].path).toContain(`/v1/assistants/${ASSISTANT_ID}/emails/`);
105
+ // Default limit=20 should be in query string
106
+ expect(calls[0].path).toContain("limit=20");
107
+ });
108
+
109
+ test("--direction filters by direction", async () => {
110
+ mockListEmails();
111
+
112
+ await runAssistantCommand(
113
+ "email",
114
+ "--json",
115
+ "list",
116
+ "--direction",
117
+ "inbound",
118
+ );
119
+
120
+ const calls = getMockFetchCalls();
121
+ expect(calls[0].path).toContain("direction=inbound");
122
+ });
123
+
124
+ test("--limit sets result count", async () => {
125
+ mockListEmails();
126
+
127
+ await runAssistantCommand("email", "--json", "list", "--limit", "5");
128
+
129
+ const calls = getMockFetchCalls();
130
+ expect(calls[0].path).toContain("limit=5");
131
+ });
132
+
133
+ test("--since filters by date", async () => {
134
+ mockListEmails();
135
+
136
+ await runAssistantCommand(
137
+ "email",
138
+ "--json",
139
+ "list",
140
+ "--since",
141
+ "2026-04-01",
142
+ );
143
+
144
+ const calls = getMockFetchCalls();
145
+ expect(calls[0].path).toContain("since=2026-04-01");
146
+ });
147
+
148
+ test("empty results returns empty array", async () => {
149
+ mockListEmails([], 0);
150
+
151
+ const output = await runAssistantCommand("email", "--json", "list");
152
+
153
+ const parsed = JSON.parse(output.trim());
154
+ expect(parsed.results).toHaveLength(0);
155
+ expect(parsed.count).toBe(0);
156
+ expect(process.exitCode).toBe(0);
157
+ });
158
+
159
+ test("endpoint failure returns error", async () => {
160
+ mockFetch(
161
+ "/emails/",
162
+ {},
163
+ { body: { detail: "Internal server error" }, status: 500 },
164
+ );
165
+
166
+ const output = await runAssistantCommand("email", "--json", "list");
167
+
168
+ expect(process.exitCode).toBe(1);
169
+ const parsed = JSON.parse(output.trim());
170
+ expect(parsed.error).toContain("Internal server error");
171
+ });
172
+
173
+ test("missing platform credentials returns error", async () => {
174
+ await deleteSecureKeyAsync(API_KEY_CREDENTIAL);
175
+
176
+ const output = await runAssistantCommand("email", "--json", "list");
177
+
178
+ expect(process.exitCode).toBe(1);
179
+ const parsed = JSON.parse(output.trim());
180
+ expect(parsed.error).toContain("Platform credentials not configured");
181
+ });
182
+
183
+ test("missing assistant ID returns error", async () => {
184
+ setPlatformAssistantId("");
185
+
186
+ const output = await runAssistantCommand("email", "--json", "list");
187
+
188
+ expect(process.exitCode).toBe(1);
189
+ const parsed = JSON.parse(output.trim());
190
+ expect(parsed.error).toContain("Assistant ID");
191
+ });
192
+ });