@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
@@ -25,16 +25,6 @@ export function getPlatformName(): string {
25
25
  return process.platform;
26
26
  }
27
27
 
28
- /**
29
- * Returns the platform-specific clipboard copy command, or null if
30
- * clipboard access is not supported on the current platform.
31
- */
32
- export function getClipboardCommand(): string | null {
33
- if (isMacOS()) return "pbcopy";
34
- if (isLinux()) return "xclip -selection clipboard";
35
- return null;
36
- }
37
-
38
28
  /**
39
29
  * Normalize an assistant ID to its canonical form for DB operations.
40
30
  *
@@ -105,13 +95,25 @@ export function getInterfacesDir(): string {
105
95
 
106
96
  /**
107
97
  * Returns the sounds directory (~/.vellum/workspace/data/sounds).
108
- * Custom sound files and sound configuration live here. Sound files
109
- * can be large, so this directory is excluded from diagnostic exports.
98
+ * Custom sound files and sound configuration live here.
110
99
  */
111
100
  export function getSoundsDir(): string {
112
101
  return join(getWorkspaceDir(), "data", "sounds");
113
102
  }
114
103
 
104
+ /** Returns the avatar directory ($VELLUM_WORKSPACE_DIR/data/avatar). */
105
+ export function getAvatarDir(): string {
106
+ return join(getWorkspaceDir(), "data", "avatar");
107
+ }
108
+
109
+ /** Canonical filename for the custom avatar PNG. */
110
+ export const AVATAR_IMAGE_FILENAME = "avatar-image.png";
111
+
112
+ /** Returns the canonical avatar image path (~/.vellum/workspace/data/avatar/avatar-image.png). */
113
+ export function getAvatarImagePath(): string {
114
+ return join(getAvatarDir(), AVATAR_IMAGE_FILENAME);
115
+ }
116
+
115
117
  /**
116
118
  * Returns the TCP port the daemon should listen on for iOS clients.
117
119
  * Hardcoded default: 8765.
@@ -218,7 +220,7 @@ export function getHistoryPath(): string {
218
220
  * overrides, device approval lists — live here.
219
221
  *
220
222
  * This directory is:
221
- * - Outside the workspace (not included in diagnostic exports)
223
+ * - Outside the workspace
222
224
  * - Outside the sandbox write boundary (tools cannot modify it)
223
225
  * - Skipped in containerized mode (credentials via CES, trust via gateway)
224
226
  */
@@ -271,10 +273,6 @@ export function getEmbedWorkerPidPath(): string {
271
273
  * When the VELLUM_WORKSPACE_DIR env var is set, returns that value (used in
272
274
  * containerized deployments where the workspace is a separate volume).
273
275
  * Otherwise falls back to ~/.vellum/workspace.
274
- *
275
- * WARNING: The entire workspace directory is included in diagnostic log exports
276
- * ("Send logs to Vellum"). Do not store secrets, API keys, or sensitive
277
- * credentials here — use the credential store or ~/.vellum/protected/ instead.
278
276
  */
279
277
  export function getWorkspaceDir(): string {
280
278
  const override = getWorkspaceDirOverride();
@@ -315,6 +313,11 @@ export function getWorkspaceHooksDir(): string {
315
313
  return join(getWorkspaceDir(), "hooks");
316
314
  }
317
315
 
316
+ /** Returns $VELLUM_WORKSPACE_DIR/routes — user-defined HTTP route handlers. */
317
+ export function getWorkspaceRoutesDir(): string {
318
+ return join(getWorkspaceDir(), "routes");
319
+ }
320
+
318
321
  /** Returns ~/.vellum/workspace/deprecated — transitional files slated for removal. */
319
322
  export function getDeprecatedDir(): string {
320
323
  return join(getWorkspaceDir(), "deprecated");
@@ -330,6 +333,35 @@ export function getWorkspacePromptPath(file: string): string {
330
333
  return join(getWorkspaceDir(), file);
331
334
  }
332
335
 
336
+ // ── Profiler filesystem layout ──────────────────────────────────────────
337
+ // Managed profiler runs live under <workspace>/data/profiler/. These
338
+ // helpers enforce a single canonical layout so every runtime caller
339
+ // resolves the same paths.
340
+
341
+ /**
342
+ * Returns the profiler root directory (<workspace>/data/profiler).
343
+ * All profiler state (runs directory, global metadata) lives here.
344
+ */
345
+ export function getProfilerRootDir(): string {
346
+ return join(getDataDir(), "profiler");
347
+ }
348
+
349
+ /**
350
+ * Returns the profiler runs directory (<workspace>/data/profiler/runs).
351
+ * Each completed or active profiler run gets its own sub-directory here.
352
+ */
353
+ export function getProfilerRunsDir(): string {
354
+ return join(getProfilerRootDir(), "runs");
355
+ }
356
+
357
+ /**
358
+ * Returns the directory for a specific profiler run by ID
359
+ * (<workspace>/data/profiler/runs/<runId>).
360
+ */
361
+ export function getProfilerRunDir(runId: string): string {
362
+ return join(getProfilerRunsDir(), runId);
363
+ }
364
+
333
365
  export function ensureDataDir(): void {
334
366
  const root = vellumRoot();
335
367
  const workspace = getWorkspaceDir();
@@ -342,6 +374,7 @@ export function ensureDataDir(): void {
342
374
  join(workspace, "signals"),
343
375
  join(workspace, "hooks"),
344
376
  join(workspace, "skills"),
377
+ join(workspace, "routes"),
345
378
  join(workspace, "embedding-models"),
346
379
  join(workspace, "conversations"),
347
380
  join(workspace, "logs"),
@@ -28,7 +28,7 @@ export interface WatcherProvider {
28
28
  id: string;
29
29
  /** Human-readable name. */
30
30
  displayName: string;
31
- /** Credential service required (e.g. 'integration:google'). */
31
+ /** Credential service required (e.g. 'google'). */
32
32
  requiredCredentialService: string;
33
33
 
34
34
  /**
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Previously, dictation-profiles.json, email-guardrails.json, and
5
5
  * active-call-leases.json lived directly under getRootDir() (~/.vellum/).
6
- * This migration moves them into the workspace directory so they are
7
- * included in diagnostic exports and follow the workspace convention.
6
+ * This migration moves them into the workspace directory so they follow
7
+ * the workspace convention for organizational consistency.
8
8
  */
9
9
 
10
10
  import { existsSync, renameSync, unlinkSync } from "node:fs";
@@ -40,8 +40,8 @@ function getRootDir(): string {
40
40
  const FILE_MOVES: Array<{ name: string; subdir?: string }> = [
41
41
  { name: "daemon-stderr.log", subdir: "logs" },
42
42
  { name: "daemon-startup.lock" },
43
- // .env stays at root — it contains secrets (API keys) and the entire
44
- // workspace directory is included in diagnostic log exports.
43
+ // .env stays at root — it contains secrets (API keys) and should not
44
+ // be in the sandbox working directory.
45
45
  { name: "embed-worker.pid" },
46
46
  ];
47
47
 
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Workspace migration 028: Recover conversations from disk-view directories.
3
+ *
4
+ * If the SQLite database was recreated empty but the disk-view directories
5
+ * under `workspace/conversations/` still exist, this migration reads each
6
+ * conversation's `meta.json` and `messages.jsonl` and re-inserts the rows
7
+ * into the database.
8
+ *
9
+ * Idempotent: conversations already present in the DB are skipped.
10
+ * Malformed files are skipped with warnings — they do not crash the migration.
11
+ */
12
+
13
+ import { randomUUID } from "node:crypto";
14
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ import { eq } from "drizzle-orm";
18
+
19
+ import { getDb } from "../../memory/db.js";
20
+ import { conversations, messages } from "../../memory/schema/conversations.js";
21
+ import { getLogger } from "../../util/logger.js";
22
+ import type { WorkspaceMigration } from "./types.js";
23
+
24
+ const log = getLogger("workspace-migrations");
25
+
26
+ interface DiskMeta {
27
+ id: string;
28
+ title?: string;
29
+ type?: string;
30
+ channel?: string;
31
+ createdAt?: string;
32
+ updatedAt?: string;
33
+ }
34
+
35
+ interface DiskToolCall {
36
+ name?: string;
37
+ input?: unknown;
38
+ }
39
+
40
+ interface DiskToolResult {
41
+ content?: unknown;
42
+ }
43
+
44
+ interface DiskMessageRecord {
45
+ role: string;
46
+ ts?: string;
47
+ content?: string;
48
+ toolCalls?: DiskToolCall[];
49
+ toolResults?: DiskToolResult[];
50
+ attachments?: unknown[];
51
+ }
52
+
53
+ function parseEpochMs(isoString: string | undefined): number | null {
54
+ if (!isoString) return null;
55
+ const ms = new Date(isoString).getTime();
56
+ return Number.isNaN(ms) ? null : ms;
57
+ }
58
+
59
+ function buildContentBlocks(record: DiskMessageRecord): unknown[] {
60
+ const blocks: unknown[] = [];
61
+
62
+ if (record.content) {
63
+ blocks.push({ type: "text", text: record.content });
64
+ }
65
+
66
+ if (Array.isArray(record.toolCalls)) {
67
+ for (const tc of record.toolCalls) {
68
+ blocks.push({
69
+ type: "tool_use",
70
+ id: randomUUID(),
71
+ name: tc.name ?? "unknown",
72
+ input: tc.input ?? {},
73
+ });
74
+ }
75
+ }
76
+
77
+ if (Array.isArray(record.toolResults)) {
78
+ for (const tr of record.toolResults) {
79
+ blocks.push({
80
+ type: "tool_result",
81
+ tool_use_id: "",
82
+ content:
83
+ typeof tr.content === "string"
84
+ ? tr.content
85
+ : JSON.stringify(tr.content),
86
+ });
87
+ }
88
+ }
89
+
90
+ // content column is NOT NULL — ensure at least one block
91
+ if (blocks.length === 0) {
92
+ blocks.push({ type: "text", text: "" });
93
+ }
94
+
95
+ return blocks;
96
+ }
97
+
98
+ export const recoverConversationsFromDiskViewMigration: WorkspaceMigration = {
99
+ id: "028-recover-conversations-from-disk-view",
100
+ description:
101
+ "Recover conversations from disk-view directories into the database",
102
+
103
+ run(workspaceDir: string): void {
104
+ const conversationsDir = join(workspaceDir, "conversations");
105
+ if (!existsSync(conversationsDir)) return;
106
+
107
+ const db = getDb();
108
+
109
+ let entries: string[];
110
+ try {
111
+ entries = readdirSync(conversationsDir);
112
+ } catch (err) {
113
+ log.warn(`Failed to read conversations directory: ${err}`);
114
+ return;
115
+ }
116
+
117
+ let recovered = 0;
118
+ let skipped = 0;
119
+ let errors = 0;
120
+
121
+ for (const entry of entries) {
122
+ const dirPath = join(conversationsDir, entry);
123
+
124
+ // Skip non-directories
125
+ try {
126
+ if (!statSync(dirPath).isDirectory()) {
127
+ continue;
128
+ }
129
+ } catch {
130
+ continue;
131
+ }
132
+
133
+ // Read and parse meta.json
134
+ const metaPath = join(dirPath, "meta.json");
135
+ if (!existsSync(metaPath)) {
136
+ log.warn(
137
+ `Skipping ${entry}: missing meta.json`,
138
+ );
139
+ skipped++;
140
+ continue;
141
+ }
142
+
143
+ let meta: DiskMeta;
144
+ try {
145
+ meta = JSON.parse(readFileSync(metaPath, "utf-8")) as DiskMeta;
146
+ } catch (err) {
147
+ log.warn(
148
+ `Skipping ${entry}: malformed meta.json: ${err}`,
149
+ );
150
+ skipped++;
151
+ continue;
152
+ }
153
+
154
+ if (!meta.id) {
155
+ log.warn(
156
+ `Skipping ${entry}: meta.json missing id`,
157
+ );
158
+ skipped++;
159
+ continue;
160
+ }
161
+
162
+ // Check if conversation already exists in DB (idempotency)
163
+ const existing = db
164
+ .select()
165
+ .from(conversations)
166
+ .where(eq(conversations.id, meta.id))
167
+ .get();
168
+
169
+ if (existing) {
170
+ skipped++;
171
+ continue;
172
+ }
173
+
174
+ // Parse messages.jsonl
175
+ const messagesPath = join(dirPath, "messages.jsonl");
176
+ const messageRecords: DiskMessageRecord[] = [];
177
+
178
+ if (existsSync(messagesPath)) {
179
+ try {
180
+ const raw = readFileSync(messagesPath, "utf-8");
181
+ for (const line of raw.split("\n")) {
182
+ const trimmed = line.trim();
183
+ if (!trimmed) continue;
184
+ try {
185
+ messageRecords.push(
186
+ JSON.parse(trimmed) as DiskMessageRecord,
187
+ );
188
+ } catch {
189
+ log.warn(
190
+ `Skipping malformed JSONL line in ${entry}/messages.jsonl`,
191
+ );
192
+ }
193
+ }
194
+ } catch (err) {
195
+ log.warn(
196
+ `Failed to read messages.jsonl for ${entry}: ${err}`,
197
+ );
198
+ }
199
+ }
200
+
201
+ // Compute timestamps
202
+ const createdAt = parseEpochMs(meta.createdAt) ?? Date.now();
203
+ const updatedAt = parseEpochMs(meta.updatedAt) ?? createdAt;
204
+
205
+ // Insert conversation + messages in a transaction
206
+ try {
207
+ db.transaction((tx) => {
208
+ tx.insert(conversations)
209
+ .values({
210
+ id: meta.id,
211
+ title: meta.title ?? null,
212
+ createdAt,
213
+ updatedAt,
214
+ conversationType: meta.type ?? "standard",
215
+ originChannel: meta.channel ?? null,
216
+ source: "user",
217
+ memoryScopeId: "default",
218
+ isAutoTitle: 1,
219
+ totalInputTokens: 0,
220
+ totalOutputTokens: 0,
221
+ totalEstimatedCost: 0,
222
+ contextSummary: null,
223
+ contextCompactedMessageCount: 0,
224
+ contextCompactedAt: null,
225
+ originInterface: null,
226
+ forkParentConversationId: null,
227
+ forkParentMessageId: null,
228
+ scheduleJobId: null,
229
+ })
230
+ .run();
231
+
232
+ for (const record of messageRecords) {
233
+ const contentBlocks = buildContentBlocks(record);
234
+ const msgCreatedAt =
235
+ parseEpochMs(record.ts) ?? createdAt;
236
+
237
+ tx.insert(messages)
238
+ .values({
239
+ id: randomUUID(),
240
+ conversationId: meta.id,
241
+ role: record.role,
242
+ content: JSON.stringify(contentBlocks),
243
+ createdAt: msgCreatedAt,
244
+ metadata: null,
245
+ })
246
+ .run();
247
+ }
248
+ });
249
+
250
+ recovered++;
251
+ } catch (err) {
252
+ log.warn(
253
+ `Failed to insert conversation ${meta.id} (${entry}): ${err}`,
254
+ );
255
+ errors++;
256
+ }
257
+ }
258
+
259
+ if (recovered > 0 || errors > 0) {
260
+ log.info(
261
+ `Recover conversations from disk-view: recovered=${recovered}, skipped=${skipped}, errors=${errors}`,
262
+ );
263
+ }
264
+ },
265
+
266
+ // No-op: deleting recovered conversation data from the database would cause
267
+ // data loss — the disk-view files are the only remaining copy after the
268
+ // original DB was lost.
269
+ down(_workspaceDir: string): void {},
270
+ };
@@ -0,0 +1,85 @@
1
+ import { existsSync, mkdirSync, rmdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ const INDEX_TEMPLATE = `_ Lines starting with _ are comments - they won't appear in the injected context
7
+
8
+ # Knowledge Base
9
+
10
+ **Remember aggressively.** When you learn ANY fact — a preference, a name, a date, a habit, a plan, an opinion — call \`remember\` immediately. Don't filter, don't judge importance. Remembering too much costs nothing. Forgetting something that mattered costs trust.
11
+
12
+ ## Always Loaded
13
+ - essentials.md — Core facts, patterns, and biographical info
14
+ - threads.md — Active commitments, follow-ups, and projects in progress
15
+ - buffer.md — Inbox of recently learned facts (filed periodically)
16
+
17
+
18
+ ## Topics
19
+ `;
20
+
21
+ const ESSENTIALS_TEMPLATE = `_ Lines starting with _ are comments - they won't appear in the injected context
22
+
23
+ # Essentials
24
+
25
+ _ The most important facts — things you'd be embarrassed to forget.
26
+ _ This file is always loaded into every conversation. Keep it focused.
27
+ _ Promote facts here from topic files when they come up constantly.
28
+ _ Demote facts to topic files when they stop being essential.
29
+ `;
30
+
31
+ const THREADS_TEMPLATE = `_ Lines starting with _ are comments - they won't appear in the injected context
32
+
33
+ # Active Threads
34
+
35
+ _ Commitments, follow-ups, and projects in progress.
36
+ _ This file is always loaded into every conversation.
37
+ _ Remove items when they're completed or no longer relevant.
38
+ `;
39
+
40
+ export const seedPkbMigration: WorkspaceMigration = {
41
+ id: "029-seed-pkb",
42
+ description: "Create pkb/ knowledge base directory with seed files",
43
+
44
+ down(workspaceDir: string): void {
45
+ // Best-effort: only remove empty directories. Never delete user content.
46
+ const pkbDir = join(workspaceDir, "pkb");
47
+ if (!existsSync(pkbDir)) return;
48
+
49
+ try {
50
+ // Try removing subdirectories first, then the root. rmdirSync fails
51
+ // on non-empty directories, which is exactly what we want.
52
+ for (const sub of ["archive"]) {
53
+ try {
54
+ rmdirSync(join(pkbDir, sub));
55
+ } catch {
56
+ // Non-empty or doesn't exist — skip
57
+ }
58
+ }
59
+ rmdirSync(pkbDir);
60
+ } catch {
61
+ // Non-empty — leave it alone
62
+ }
63
+ },
64
+
65
+ run(workspaceDir: string): void {
66
+ const pkbDir = join(workspaceDir, "pkb");
67
+ mkdirSync(pkbDir, { recursive: true });
68
+ mkdirSync(join(pkbDir, "archive"), { recursive: true });
69
+
70
+ // Seed files only if they don't already exist (idempotent)
71
+ const seeds: Array<[string, string]> = [
72
+ ["INDEX.md", INDEX_TEMPLATE],
73
+ ["essentials.md", ESSENTIALS_TEMPLATE],
74
+ ["threads.md", THREADS_TEMPLATE],
75
+ ["buffer.md", ""],
76
+ ];
77
+
78
+ for (const [filename, content] of seeds) {
79
+ const filePath = join(pkbDir, filename);
80
+ if (!existsSync(filePath)) {
81
+ writeFileSync(filePath, content, "utf-8");
82
+ }
83
+ }
84
+ },
85
+ };
@@ -0,0 +1,73 @@
1
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ const AUTOINJECT_TEMPLATE = `_ Lines starting with _ are comments - they won't appear in the injected context
7
+ _ List one PKB filename per line. These files are loaded into every conversation.
8
+ _ Remove a line to stop autoinjecting that file. Add new filenames to inject more.
9
+
10
+ INDEX.md
11
+ essentials.md
12
+ threads.md
13
+ buffer.md
14
+ `;
15
+
16
+ const INDEX_ENTRY = "- _autoinject.md — Controls which files are loaded into every conversation";
17
+
18
+ export const seedPkbAutoinjectMigration: WorkspaceMigration = {
19
+ id: "030-seed-pkb-autoinject",
20
+ description: "Seed pkb/_autoinject.md for configurable PKB autoinjection",
21
+
22
+ run(workspaceDir: string): void {
23
+ const pkbDir = join(workspaceDir, "pkb");
24
+ if (!existsSync(pkbDir)) return;
25
+
26
+ // Seed _autoinject.md if it doesn't already exist
27
+ const autoinjectPath = join(pkbDir, "_autoinject.md");
28
+ if (!existsSync(autoinjectPath)) {
29
+ writeFileSync(autoinjectPath, AUTOINJECT_TEMPLATE, "utf-8");
30
+ }
31
+
32
+ // Append _autoinject.md entry to INDEX.md if not already present
33
+ const indexPath = join(pkbDir, "INDEX.md");
34
+ if (existsSync(indexPath)) {
35
+ try {
36
+ const indexContent = readFileSync(indexPath, "utf-8");
37
+ if (!indexContent.includes("_autoinject.md")) {
38
+ // Insert after the last "Always Loaded" entry (buffer.md line)
39
+ const bufferLine = "- buffer.md";
40
+ const bufferIdx = indexContent.indexOf(bufferLine);
41
+ if (bufferIdx !== -1) {
42
+ const endOfBufferLine = indexContent.indexOf("\n", bufferIdx);
43
+ const insertAt =
44
+ endOfBufferLine === -1 ? indexContent.length : endOfBufferLine;
45
+ const updated =
46
+ indexContent.slice(0, insertAt) +
47
+ "\n" +
48
+ INDEX_ENTRY +
49
+ indexContent.slice(insertAt);
50
+ writeFileSync(indexPath, updated, "utf-8");
51
+ }
52
+ }
53
+ } catch {
54
+ // INDEX.md unreadable — skip
55
+ }
56
+ }
57
+ },
58
+
59
+ down(workspaceDir: string): void {
60
+ const autoinjectPath = join(workspaceDir, "pkb", "_autoinject.md");
61
+ if (!existsSync(autoinjectPath)) return;
62
+
63
+ try {
64
+ const content = readFileSync(autoinjectPath, "utf-8");
65
+ // Only delete if content matches the template (preserve user edits)
66
+ if (content === AUTOINJECT_TEMPLATE) {
67
+ unlinkSync(autoinjectPath);
68
+ }
69
+ } catch {
70
+ // Unreadable — leave it alone
71
+ }
72
+ },
73
+ };
@@ -24,6 +24,9 @@ import { moveRuntimeFilesToWorkspaceMigration } from "./024-move-runtime-files-t
24
24
  import { removeOauthAppSetupSkillsMigration } from "./025-remove-oauth-app-setup-skills.js";
25
25
  import { backfillInstallMetaMigration } from "./026-backfill-install-meta.js";
26
26
  import { removeOrphanedOptimizedImagesCacheMigration } from "./027-remove-orphaned-optimized-images-cache.js";
27
+ import { recoverConversationsFromDiskViewMigration } from "./028-recover-conversations-from-disk-view.js";
28
+ import { seedPkbMigration } from "./029-seed-pkb.js";
29
+ import { seedPkbAutoinjectMigration } from "./030-seed-pkb-autoinject.js";
27
30
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
28
31
  import type { WorkspaceMigration } from "./types.js";
29
32
 
@@ -59,4 +62,7 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
59
62
  removeOauthAppSetupSkillsMigration,
60
63
  backfillInstallMetaMigration,
61
64
  removeOrphanedOptimizedImagesCacheMigration,
65
+ recoverConversationsFromDiskViewMigration,
66
+ seedPkbMigration,
67
+ seedPkbAutoinjectMigration,
62
68
  ];
@@ -1,8 +1,7 @@
1
1
  import type { TopLevelSnapshot } from "./top-level-scanner.js";
2
2
 
3
3
  export interface WorkspaceTopLevelRenderOptions {
4
- currentConversationPath?: string | null;
5
- currentConversationAttachmentsPath?: string | null;
4
+ conversationAttachmentsPath?: string | null;
6
5
  }
7
6
 
8
7
  /**
@@ -15,19 +14,16 @@ export function renderWorkspaceTopLevelContext(
15
14
  snapshot: TopLevelSnapshot,
16
15
  options: WorkspaceTopLevelRenderOptions = {},
17
16
  ): string {
18
- const lines: string[] = ["<workspace_top_level>"];
17
+ const lines: string[] = ["<workspace>"];
19
18
  lines.push(`Root: ${snapshot.rootPath}`);
20
19
  lines.push(`Directories: ${snapshot.directories.join(", ")}`);
21
20
  lines.push(`Files: ${snapshot.files.join(", ")}`);
22
- if (options.currentConversationPath) {
23
- lines.push(`Current conversation folder: ${options.currentConversationPath}`);
24
- }
25
- if (options.currentConversationAttachmentsPath) {
26
- lines.push(`Attachment files: ${options.currentConversationAttachmentsPath}`);
21
+ if (options.conversationAttachmentsPath) {
22
+ lines.push(`Current conversation attachments: ${options.conversationAttachmentsPath}`);
27
23
  }
28
24
  if (snapshot.truncated) {
29
25
  lines.push("(list truncated — more entries exist)");
30
26
  }
31
- lines.push("</workspace_top_level>");
27
+ lines.push("</workspace>");
32
28
  return lines.join("\n");
33
29
  }