@vellumai/assistant 0.6.0 → 0.6.1

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 (285) 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 +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/openapi.yaml +538 -3
  9. package/package.json +5 -1
  10. package/src/__tests__/anthropic-provider.test.ts +160 -95
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +47 -1
  13. package/src/__tests__/app-source-watcher.test.ts +159 -0
  14. package/src/__tests__/checker.test.ts +38 -6
  15. package/src/__tests__/config-schema.test.ts +5 -0
  16. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  17. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  18. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  19. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  20. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  21. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  22. package/src/__tests__/conversation-wipe.test.ts +2 -6
  23. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  24. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  25. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  26. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  27. package/src/__tests__/date-context.test.ts +76 -210
  28. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  29. package/src/__tests__/file-list-tool.test.ts +219 -0
  30. package/src/__tests__/first-greeting.test.ts +1 -1
  31. package/src/__tests__/heartbeat-service.test.ts +180 -3
  32. package/src/__tests__/identity-routes.test.ts +328 -0
  33. package/src/__tests__/injection-block.test.ts +24 -0
  34. package/src/__tests__/install-skill-routing.test.ts +7 -6
  35. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  36. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  37. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  38. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  39. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  40. package/src/__tests__/log-export-workspace.test.ts +72 -105
  41. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  42. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  43. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  44. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  45. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  46. package/src/__tests__/mock-fetch.ts +87 -0
  47. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  48. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  49. package/src/__tests__/parser.test.ts +32 -0
  50. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  51. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  52. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  53. package/src/__tests__/permission-mode-store.test.ts +277 -0
  54. package/src/__tests__/permission-mode.test.ts +101 -0
  55. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  56. package/src/__tests__/profiler-routes.test.ts +502 -0
  57. package/src/__tests__/profiler-run-store.test.ts +441 -0
  58. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  59. package/src/__tests__/registry.test.ts +1 -1
  60. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  61. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  62. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  63. package/src/__tests__/search-skills-unified.test.ts +4 -3
  64. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  65. package/src/__tests__/set-permission-mode.test.ts +274 -0
  66. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  67. package/src/__tests__/skill-memory.test.ts +2 -783
  68. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  69. package/src/__tests__/subagent-detail.test.ts +84 -0
  70. package/src/__tests__/subagent-disposal.test.ts +308 -0
  71. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  72. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  73. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  74. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  75. package/src/__tests__/subagent-tools.test.ts +464 -4
  76. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  77. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  78. package/src/__tests__/terminal-tools.test.ts +17 -27
  79. package/src/__tests__/test-preload.ts +4 -0
  80. package/src/__tests__/tool-executor.test.ts +4 -26
  81. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  82. package/src/__tests__/top-level-renderer.test.ts +10 -13
  83. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  84. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  85. package/src/agent/loop.ts +6 -0
  86. package/src/approvals/guardian-request-resolvers.ts +24 -0
  87. package/src/avatar/traits-png-sync.ts +3 -3
  88. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  89. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  90. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  91. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  92. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  93. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  94. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  95. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  96. package/src/cli/commands/conversations.ts +1 -8
  97. package/src/cli/commands/email.ts +584 -835
  98. package/src/cli/commands/memory.ts +1 -34
  99. package/src/cli/commands/notifications.ts +7 -2
  100. package/src/cli/commands/oauth/connect.ts +14 -5
  101. package/src/cli/commands/routes.ts +396 -0
  102. package/src/cli/commands/skills.ts +130 -20
  103. package/src/cli/program.ts +2 -0
  104. package/src/cli.ts +1 -120
  105. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  106. package/src/config/bundled-skills/gmail/SKILL.md +2 -2
  107. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  108. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  109. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  111. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  112. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  113. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  114. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  115. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  116. package/src/config/env-registry.ts +63 -0
  117. package/src/config/feature-flag-registry.json +17 -1
  118. package/src/config/schema.ts +8 -0
  119. package/src/config/schemas/filing.ts +51 -0
  120. package/src/config/schemas/heartbeat.ts +15 -12
  121. package/src/config/schemas/memory-lifecycle.ts +12 -0
  122. package/src/config/schemas/security.ts +14 -0
  123. package/src/daemon/app-source-watcher.ts +93 -0
  124. package/src/daemon/config-watcher.ts +79 -1
  125. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  126. package/src/daemon/conversation-agent-loop.ts +158 -65
  127. package/src/daemon/conversation-history.ts +4 -19
  128. package/src/daemon/conversation-lifecycle.ts +8 -14
  129. package/src/daemon/conversation-process.ts +13 -7
  130. package/src/daemon/conversation-runtime-assembly.ts +300 -306
  131. package/src/daemon/conversation-tool-setup.ts +44 -14
  132. package/src/daemon/conversation-workspace.ts +1 -2
  133. package/src/daemon/conversation.ts +18 -0
  134. package/src/daemon/date-context.ts +26 -53
  135. package/src/daemon/first-greeting.ts +1 -1
  136. package/src/daemon/handlers/conversations.ts +4 -7
  137. package/src/daemon/handlers/shared.test.ts +143 -0
  138. package/src/daemon/handlers/shared.ts +63 -5
  139. package/src/daemon/handlers/skills.ts +11 -18
  140. package/src/daemon/lifecycle.ts +199 -157
  141. package/src/daemon/message-types/conversations.ts +25 -6
  142. package/src/daemon/message-types/messages.ts +9 -1
  143. package/src/daemon/message-types/schedules.ts +1 -0
  144. package/src/daemon/message-types/settings.ts +6 -0
  145. package/src/daemon/profiler-run-store.ts +557 -0
  146. package/src/daemon/server.ts +89 -9
  147. package/src/daemon/shutdown-handlers.ts +5 -0
  148. package/src/daemon/tool-side-effects.ts +23 -3
  149. package/src/export/transcript-formatter.ts +148 -0
  150. package/src/filing/filing-service.ts +228 -0
  151. package/src/heartbeat/heartbeat-service.ts +96 -7
  152. package/src/mcp/client.ts +6 -0
  153. package/src/mcp/mcp-oauth-provider.ts +149 -27
  154. package/src/memory/admin.ts +33 -32
  155. package/src/memory/app-store.ts +69 -0
  156. package/src/memory/conversation-bootstrap.ts +1 -1
  157. package/src/memory/conversation-crud.ts +136 -107
  158. package/src/memory/conversation-group-migration.ts +1 -1
  159. package/src/memory/conversation-queries.ts +58 -12
  160. package/src/memory/conversation-title-service.ts +1 -0
  161. package/src/memory/db-init.ts +182 -376
  162. package/src/memory/graph/bootstrap.ts +75 -66
  163. package/src/memory/graph/capability-seed.ts +167 -15
  164. package/src/memory/graph/consolidation.ts +38 -4
  165. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  166. package/src/memory/graph/extraction-job.ts +9 -4
  167. package/src/memory/graph/extraction.ts +66 -23
  168. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  169. package/src/memory/graph/graph-search.ts +29 -15
  170. package/src/memory/graph/injection.ts +38 -8
  171. package/src/memory/graph/inspect.ts +12 -3
  172. package/src/memory/graph/retriever.ts +365 -262
  173. package/src/memory/graph/store.test.ts +48 -0
  174. package/src/memory/graph/store.ts +150 -11
  175. package/src/memory/graph/tool-handlers.ts +84 -209
  176. package/src/memory/graph/tools.ts +8 -52
  177. package/src/memory/graph/types.ts +24 -0
  178. package/src/memory/job-handlers/cleanup.ts +44 -1
  179. package/src/memory/jobs-store.ts +70 -60
  180. package/src/memory/jobs-worker.ts +44 -28
  181. package/src/memory/llm-request-log-store.ts +96 -12
  182. package/src/memory/memory-recall-log-store.ts +49 -5
  183. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  184. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  185. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  186. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  187. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  188. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  189. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  190. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  191. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  192. package/src/memory/migrations/index.ts +8 -0
  193. package/src/memory/migrations/registry.ts +8 -0
  194. package/src/memory/schema/conversations.ts +14 -0
  195. package/src/memory/schema/infrastructure.ts +8 -1
  196. package/src/memory/schema/memory-core.ts +0 -51
  197. package/src/memory/schema/memory-graph.ts +15 -0
  198. package/src/memory/task-memory-cleanup.ts +30 -11
  199. package/src/notifications/copy-composer.ts +86 -0
  200. package/src/notifications/decision-engine.ts +35 -0
  201. package/src/permissions/checker.ts +12 -1
  202. package/src/permissions/permission-mode-store.ts +180 -0
  203. package/src/permissions/permission-mode.ts +31 -0
  204. package/src/permissions/workspace-policy.ts +9 -0
  205. package/src/prompts/system-prompt.ts +59 -7
  206. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  207. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  208. package/src/prompts/templates/HEARTBEAT.md +3 -1
  209. package/src/prompts/templates/SOUL.md +25 -4
  210. package/src/prompts/templates/UPDATES.md +8 -0
  211. package/src/providers/anthropic/client.ts +107 -219
  212. package/src/runtime/auth/route-policy.ts +23 -0
  213. package/src/runtime/http-server.ts +32 -2
  214. package/src/runtime/http-types.ts +12 -1
  215. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  216. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  217. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  218. package/src/runtime/routes/app-management-routes.ts +1 -11
  219. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  220. package/src/runtime/routes/archive-utils.ts +29 -0
  221. package/src/runtime/routes/avatar-routes.ts +2 -9
  222. package/src/runtime/routes/btw-routes.ts +14 -1
  223. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  224. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  225. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  226. package/src/runtime/routes/conversation-routes.ts +264 -44
  227. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  228. package/src/runtime/routes/identity-routes.ts +53 -18
  229. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  230. package/src/runtime/routes/log-export-routes.ts +23 -275
  231. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  232. package/src/runtime/routes/migration-routes.ts +18 -7
  233. package/src/runtime/routes/profiler-routes.ts +350 -0
  234. package/src/runtime/routes/schedule-routes.ts +27 -12
  235. package/src/runtime/routes/settings-routes.ts +95 -8
  236. package/src/runtime/routes/subagents-routes.ts +28 -7
  237. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  238. package/src/runtime/routes/user-routes.ts +41 -0
  239. package/src/runtime/routes/workspace-routes.ts +0 -1
  240. package/src/schedule/schedule-store.ts +30 -0
  241. package/src/schedule/scheduler.ts +45 -18
  242. package/src/skills/catalog-install.ts +10 -2
  243. package/src/skills/managed-store.ts +2 -2
  244. package/src/skills/skill-memory.ts +1 -293
  245. package/src/subagent/index.ts +13 -3
  246. package/src/subagent/manager.ts +308 -29
  247. package/src/subagent/types.ts +68 -0
  248. package/src/tasks/task-runner.ts +4 -4
  249. package/src/tools/apps/executors.ts +29 -4
  250. package/src/tools/filesystem/list.ts +93 -0
  251. package/src/tools/permission-checker.ts +78 -0
  252. package/src/tools/registry.ts +4 -0
  253. package/src/tools/schedule/create.ts +3 -0
  254. package/src/tools/schedule/list.ts +1 -0
  255. package/src/tools/schedule/update.ts +6 -0
  256. package/src/tools/shared/filesystem/errors.ts +5 -0
  257. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  258. package/src/tools/shared/filesystem/types.ts +17 -0
  259. package/src/tools/shared/shell-output.ts +31 -2
  260. package/src/tools/subagent/abort.ts +12 -2
  261. package/src/tools/subagent/message.ts +9 -2
  262. package/src/tools/subagent/notify-parent.ts +79 -0
  263. package/src/tools/subagent/read.ts +29 -8
  264. package/src/tools/subagent/resolve.ts +21 -0
  265. package/src/tools/subagent/spawn.ts +2 -0
  266. package/src/tools/subagent/status.ts +11 -1
  267. package/src/tools/system/avatar-generator.ts +3 -3
  268. package/src/tools/system/register.ts +23 -0
  269. package/src/tools/system/set-permission-mode.ts +103 -0
  270. package/src/tools/terminal/parser.ts +30 -5
  271. package/src/tools/terminal/safe-env.ts +16 -1
  272. package/src/tools/tool-manifest.ts +6 -0
  273. package/src/tools/types.ts +2 -0
  274. package/src/util/logger.ts +1 -1
  275. package/src/util/platform.ts +50 -17
  276. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  277. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  278. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  279. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  280. package/src/workspace/migrations/registry.ts +4 -0
  281. package/src/workspace/top-level-renderer.ts +5 -9
  282. package/src/__tests__/cli-memory.test.ts +0 -377
  283. package/src/__tests__/clipboard.test.ts +0 -88
  284. package/src/cli/cli-memory.ts +0 -179
  285. package/src/util/clipboard.ts +0 -34
@@ -2,14 +2,12 @@
2
2
  * HTTP route handler for exporting audit data and daemon log files.
3
3
  *
4
4
  * A single POST /v1/export endpoint allows clients (e.g. macOS Export Logs)
5
- * to retrieve audit database records, daemon log files, workspace contents,
6
- * and a sanitized config snapshot as a tar.gz archive.
5
+ * to retrieve audit database records, daemon log files, and a sanitized
6
+ * config snapshot as a tar.gz archive.
7
7
  */
8
8
 
9
- import { spawnSync } from "node:child_process";
10
9
  import {
11
10
  existsSync,
12
- lstatSync,
13
11
  mkdirSync,
14
12
  mkdtempSync,
15
13
  readdirSync,
@@ -19,7 +17,7 @@ import {
19
17
  writeFileSync,
20
18
  } from "node:fs";
21
19
  import { tmpdir } from "node:os";
22
- import { join, relative } from "node:path";
20
+ import { join } from "node:path";
23
21
 
24
22
  import { and, desc, eq, gte, lte } from "drizzle-orm";
25
23
  import { z } from "zod";
@@ -31,25 +29,22 @@ import {
31
29
  messages,
32
30
  toolInvocations,
33
31
  } from "../../memory/schema.js";
34
- import { getLogger } from "../../util/logger.js";
32
+ import { getLogger, LOG_FILE_PATTERN } from "../../util/logger.js";
35
33
  import {
36
34
  getDaemonStderrLogPath,
37
35
  getDataDir,
38
36
  getWorkspaceConfigPath,
39
- getWorkspaceDir,
40
37
  } from "../../util/platform.js";
41
38
  import { APP_VERSION, COMMIT_SHA } from "../../version.js";
42
39
  import { httpError } from "../http-errors.js";
43
40
  import type { RouteDefinition } from "../http-router.js";
41
+ import { createTarGz } from "./archive-utils.js";
44
42
 
45
43
  const log = getLogger("log-export-routes");
46
44
 
47
45
  /** Maximum total payload size for log file contents (10 MB). */
48
46
  const MAX_LOG_PAYLOAD_BYTES = 10 * 1024 * 1024;
49
47
 
50
- /** Maximum compressed archive size before pruning workspace directories (50 MB). */
51
- const MAX_ARCHIVE_BYTES = 50 * 1024 * 1024;
52
-
53
48
  interface ExportRequestBody {
54
49
  auditLimit?: number;
55
50
  conversationId?: string; // scope to a single conversation
@@ -58,14 +53,13 @@ interface ExportRequestBody {
58
53
  }
59
54
 
60
55
  /**
61
- * Collect audit data, daemon log files, workspace contents, and a sanitized
62
- * config snapshot, then package everything into a tar.gz archive.
56
+ * Collect audit data, daemon log files, and a sanitized config snapshot,
57
+ * then package everything into a tar.gz archive.
63
58
  *
64
59
  * Archive layout:
65
60
  * audit-data.json — tool invocation records
66
61
  * config-snapshot.json — sanitized workspace config
67
62
  * daemon-logs/<name> — daemon log files
68
- * workspace/<relpath> — workspace file tree
69
63
  */
70
64
  async function handleExport(body: ExportRequestBody): Promise<Response> {
71
65
  const staging = mkdtempSync(join(tmpdir(), "vellum-export-"));
@@ -167,9 +161,19 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
167
161
 
168
162
  const logsDir = join(getDataDir(), "logs");
169
163
  const collectedLogFiles: string[] = [];
164
+ const startDate = startTime ? new Date(startTime) : undefined;
165
+ const endDate = endTime ? new Date(endTime) : undefined;
170
166
  if (existsSync(logsDir)) {
171
167
  const entries = readdirSync(logsDir);
172
168
  for (const entry of entries) {
169
+ // Filter dated log files by time range
170
+ const dateMatch = entry.match(LOG_FILE_PATTERN);
171
+ if (dateMatch && (startDate || endDate)) {
172
+ const fileDate = new Date(dateMatch[1] + "T23:59:59.999Z"); // end of day
173
+ const fileDateStart = new Date(dateMatch[1] + "T00:00:00.000Z");
174
+ if (startDate && fileDate < startDate) continue; // entire day is before range
175
+ if (endDate && fileDateStart > endDate) continue; // entire day is after range
176
+ }
173
177
  const filePath = join(logsDir, entry);
174
178
  try {
175
179
  const stat = statSync(filePath);
@@ -247,20 +251,6 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
247
251
  );
248
252
  }
249
253
 
250
- // --- Workspace files (skip for conversation-scoped exports) ---
251
- let workspaceFileCount = 0;
252
- if (!conversationId) {
253
- const workspaceFiles = collectWorkspaceFiles();
254
- const workspaceDir = join(staging, "workspace");
255
- mkdirSync(workspaceDir, { recursive: true });
256
- for (const [relPath, content] of Object.entries(workspaceFiles)) {
257
- const dest = join(workspaceDir, relPath);
258
- mkdirSync(join(dest, ".."), { recursive: true });
259
- writeFileSync(dest, content, "utf-8");
260
- }
261
- workspaceFileCount = Object.keys(workspaceFiles).length;
262
- }
263
-
264
254
  // --- Export manifest ---
265
255
  const manifest = conversationId
266
256
  ? {
@@ -290,64 +280,15 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
290
280
  logFileCount,
291
281
  totalBytes,
292
282
  hasConfig: configSnapshot !== undefined,
293
- workspaceFileCount,
294
283
  conversationId: conversationId ?? null,
295
284
  },
296
285
  "Export collected, creating tar.gz archive",
297
286
  );
298
287
 
299
- // --- Create tar.gz archive, pruning workspace dirs if too large ---
300
- const excludedDirs: string[] = [];
301
- let archiveBytes = createTarGz(staging);
302
-
303
- while (!archiveBytes) {
304
- // Conversation-scoped exports have no workspace directory to prune —
305
- // if the archive still exceeds the size limit, report a clear error.
306
- if (conversationId) {
307
- log.error(
308
- "Conversation-scoped export exceeds archive size limit with no workspace dirs to prune",
309
- );
310
- return httpError(
311
- "INTERNAL_ERROR",
312
- "Conversation export exceeds the maximum archive size",
313
- 500,
314
- );
315
- }
316
-
317
- // Find the largest top-level directory under workspace/ and remove it
318
- const wsDir = join(staging, "workspace");
319
- const largest = findLargestSubdirectory(wsDir);
320
- if (!largest) {
321
- log.error("tar command failed and no workspace dirs to prune");
322
- return httpError("INTERNAL_ERROR", "Failed to create archive", 500);
323
- }
324
-
325
- log.warn(
326
- { dir: largest.name, bytes: largest.bytes },
327
- "Archive exceeds size limit, removing largest workspace directory",
328
- );
329
- excludedDirs.push(
330
- `workspace/${largest.name} (${formatBytes(largest.bytes)})`,
331
- );
332
- rmSync(join(wsDir, largest.name), { recursive: true, force: true });
333
- archiveBytes = createTarGz(staging);
334
- }
335
-
336
- if (excludedDirs.length > 0) {
337
- const errorLines = [
338
- "The following workspace directories were excluded because the archive exceeded the size limit:",
339
- "",
340
- ...excludedDirs.map((d) => ` - ${d}`),
341
- "",
342
- "Use the streaming export endpoint for full workspace exports.",
343
- ];
344
- writeFileSync(join(staging, "error.log"), errorLines.join("\n"), "utf-8");
345
-
346
- // Re-create the archive now that error.log is included
347
- const withErrorLog = createTarGz(staging);
348
- if (withErrorLog) {
349
- archiveBytes = withErrorLog;
350
- }
288
+ // --- Create tar.gz archive ---
289
+ const archiveBytes = createTarGz(staging);
290
+ if (!archiveBytes) {
291
+ return httpError("INTERNAL_ERROR", "Failed to create archive", 500);
351
292
  }
352
293
 
353
294
  return new Response(archiveBytes, {
@@ -371,199 +312,6 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
371
312
  }
372
313
  }
373
314
 
374
- /**
375
- * Attempts to create a tar.gz archive of `staging` into a Buffer.
376
- * Returns the Buffer on success, or `undefined` if the archive exceeds
377
- * the size limit or tar otherwise fails.
378
- */
379
- function createTarGz(staging: string): ArrayBuffer | undefined {
380
- const proc = spawnSync("tar", ["czf", "-", "-C", staging, "."], {
381
- maxBuffer: MAX_ARCHIVE_BYTES,
382
- timeout: 30_000,
383
- });
384
- if (proc.status !== 0) return undefined;
385
- const buf = Buffer.isBuffer(proc.stdout)
386
- ? proc.stdout
387
- : Buffer.from(proc.stdout);
388
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
389
- }
390
-
391
- /**
392
- * Returns the name and total byte size of the largest top-level subdirectory
393
- * inside `dir`, or `undefined` if `dir` has no subdirectories.
394
- */
395
- function findLargestSubdirectory(
396
- dir: string,
397
- ): { name: string; bytes: number } | undefined {
398
- if (!existsSync(dir)) return undefined;
399
-
400
- let largest: { name: string; bytes: number } | undefined;
401
-
402
- for (const entry of readdirSync(dir)) {
403
- const fullPath = join(dir, entry);
404
- try {
405
- if (!statSync(fullPath).isDirectory()) continue;
406
- } catch {
407
- continue;
408
- }
409
- const bytes = directorySize(fullPath);
410
- if (!largest || bytes > largest.bytes) {
411
- largest = { name: entry, bytes };
412
- }
413
- }
414
-
415
- return largest;
416
- }
417
-
418
- /** Recursively sums the byte size of all files in `dir`. */
419
- function directorySize(dir: string): number {
420
- let total = 0;
421
- try {
422
- for (const entry of readdirSync(dir)) {
423
- const fullPath = join(dir, entry);
424
- try {
425
- const stat = statSync(fullPath);
426
- if (stat.isDirectory()) {
427
- total += directorySize(fullPath);
428
- } else if (stat.isFile()) {
429
- total += stat.size;
430
- }
431
- } catch {
432
- // skip
433
- }
434
- }
435
- } catch {
436
- // skip
437
- }
438
- return total;
439
- }
440
-
441
- /** Formats a byte count as a human-readable string (e.g. "12.3 MB"). */
442
- function formatBytes(bytes: number): string {
443
- if (bytes < 1024) return `${bytes} B`;
444
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
445
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
446
- }
447
-
448
- /** Directory prefixes to skip when collecting workspace files. */
449
- const WORKSPACE_SKIP_DIRS = new Set([
450
- "embedding-models",
451
- "data/qdrant",
452
- "data/attachments",
453
- "data/sounds",
454
- "conversations",
455
- "signals",
456
- "deprecated",
457
- ]);
458
-
459
- /** Files at the workspace root to skip (already covered by sanitized fields). */
460
- const WORKSPACE_SKIP_ROOT_FILES = new Set(["config.json"]);
461
-
462
- /** Maximum cumulative size for workspace file contents (10 MB). */
463
- const MAX_WORKSPACE_PAYLOAD_BYTES = 10 * 1024 * 1024;
464
-
465
- /**
466
- * Recursively collects files from the workspace directory into a
467
- * `Record<string, string>` map of relative path to content.
468
- *
469
- * - Skips `config.json` at the workspace root (already exported as a
470
- * sanitized `configSnapshot`; the raw file contains secrets).
471
- * - Skips symlinks to prevent reading files outside the workspace.
472
- * - Skips directories in `WORKSPACE_SKIP_DIRS`.
473
- * - For `.db` files, shells out to `sqlite3 <path> .dump` and stores the
474
- * SQL text output with a `.sql` suffix appended to the key.
475
- * - Skips binary files (detected via null-byte heuristic).
476
- * - Stops collecting once `MAX_WORKSPACE_PAYLOAD_BYTES` is reached.
477
- */
478
- function collectWorkspaceFiles(): Record<string, string> {
479
- const wsDir = getWorkspaceDir();
480
- if (!existsSync(wsDir)) return {};
481
-
482
- const result: Record<string, string> = {};
483
- let totalBytes = 0;
484
-
485
- function walk(dir: string): void {
486
- let entries: string[];
487
- try {
488
- entries = readdirSync(dir);
489
- } catch {
490
- return;
491
- }
492
-
493
- for (const entry of entries) {
494
- const fullPath = join(dir, entry);
495
- const relPath = relative(wsDir, fullPath);
496
-
497
- // Check if this path falls under a skipped directory prefix
498
- if (
499
- [...WORKSPACE_SKIP_DIRS].some(
500
- (prefix) => relPath === prefix || relPath.startsWith(prefix + "/"),
501
- )
502
- ) {
503
- continue;
504
- }
505
-
506
- // Skip root-level files that are already exported separately
507
- if (dir === wsDir && WORKSPACE_SKIP_ROOT_FILES.has(entry)) {
508
- continue;
509
- }
510
-
511
- try {
512
- // Use lstatSync to avoid following symlinks
513
- const stat = lstatSync(fullPath);
514
-
515
- // Skip symlinks — they could point outside the workspace
516
- if (stat.isSymbolicLink()) continue;
517
-
518
- if (stat.isDirectory()) {
519
- walk(fullPath);
520
- continue;
521
- }
522
- if (!stat.isFile()) continue;
523
-
524
- // SQLite DB handling: dump as SQL text, then enforce size cap
525
- if (entry.endsWith(".db")) {
526
- // Skip the dump entirely if the budget is already exhausted
527
- if (totalBytes >= MAX_WORKSPACE_PAYLOAD_BYTES) continue;
528
- try {
529
- const proc = spawnSync("sqlite3", [fullPath, ".dump"], {
530
- timeout: 10_000,
531
- });
532
- if (proc.status === 0 && proc.stdout) {
533
- const output =
534
- proc.stdout instanceof Buffer
535
- ? proc.stdout.toString("utf-8")
536
- : String(proc.stdout);
537
- const outputBytes = Buffer.byteLength(output, "utf-8");
538
- if (totalBytes + outputBytes > MAX_WORKSPACE_PAYLOAD_BYTES)
539
- continue;
540
- result[relPath + ".sql"] = output;
541
- totalBytes += outputBytes;
542
- }
543
- } catch {
544
- // Skip if dump fails
545
- }
546
- continue;
547
- }
548
-
549
- // Enforce cumulative size cap for non-DB files
550
- if (totalBytes + stat.size > MAX_WORKSPACE_PAYLOAD_BYTES) continue;
551
-
552
- // Read as UTF-8 and skip binary files (null-byte heuristic)
553
- const content = readFileSync(fullPath, "utf-8");
554
- if (content.includes("\0")) continue;
555
- result[relPath] = content;
556
- totalBytes += stat.size;
557
- } catch {
558
- // Skip unreadable files
559
- }
560
- }
561
- }
562
-
563
- walk(wsDir);
564
- return result;
565
- }
566
-
567
315
  /**
568
316
  * Replaces a string value with a presence flag: "(set)" if truthy, "(empty)" otherwise.
569
317
  */
@@ -689,7 +437,7 @@ export function logExportRouteDefinitions(): RouteDefinition[] {
689
437
  policyKey: "export",
690
438
  summary: "Export logs and audit data",
691
439
  description:
692
- "Export audit records, assistant logs, workspace contents, and config as a tar.gz archive.",
440
+ "Export audit records, assistant logs, and config as a tar.gz archive.",
693
441
  tags: ["export"],
694
442
  requestBody: exportRequestBody,
695
443
  handler: async ({ req }) => {
@@ -703,7 +451,7 @@ export function logExportRouteDefinitions(): RouteDefinition[] {
703
451
  policyKey: "export",
704
452
  summary: "Export logs and audit data (alias)",
705
453
  description:
706
- "Alias for /v1/export. Export audit records, assistant logs, workspace contents, and config as a tar.gz archive.",
454
+ "Alias for /v1/export. Export audit records, assistant logs, and config as a tar.gz archive.",
707
455
  tags: ["export"],
708
456
  requestBody: exportRequestBody,
709
457
  handler: async ({ req }) => {