@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
@@ -5,8 +5,6 @@
5
5
  * - audit-data.json with tool invocation records
6
6
  * - daemon-logs/ with log file contents
7
7
  * - config-snapshot.json with sanitized config
8
- * - workspace/ with text files, SQL dumps for .db files, and proper
9
- * filtering (excluded directories, binary files, symlinks).
10
8
  */
11
9
 
12
10
  import { spawnSync } from "node:child_process";
@@ -16,7 +14,6 @@ import {
16
14
  readdirSync,
17
15
  readFileSync,
18
16
  rmSync,
19
- symlinkSync,
20
17
  writeFileSync,
21
18
  } from "node:fs";
22
19
  import { tmpdir } from "node:os";
@@ -24,8 +21,7 @@ import { join } from "node:path";
24
21
  import { describe, expect, mock, test } from "bun:test";
25
22
 
26
23
  // Set up temp directories before mocking
27
- const testDir = process.env.VELLUM_WORKSPACE_DIR!;
28
- const testWorkspaceDir = testDir;
24
+ const testWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR!;
29
25
  mkdirSync(testWorkspaceDir, { recursive: true });
30
26
 
31
27
  mock.module("../util/logger.js", () => ({
@@ -52,11 +48,13 @@ initializeDb();
52
48
  const routes = logExportRouteDefinitions();
53
49
  const exportRoute = routes.find((r) => r.endpoint === "export")!;
54
50
 
55
- async function callExport(): Promise<Response> {
51
+ async function callExport(
52
+ body: Record<string, unknown> = {},
53
+ ): Promise<Response> {
56
54
  const req = new Request("http://localhost/v1/export", {
57
55
  method: "POST",
58
56
  headers: { "Content-Type": "application/json" },
59
- body: JSON.stringify({}),
57
+ body: JSON.stringify(body),
60
58
  });
61
59
  const url = new URL(req.url);
62
60
  return exportRoute.handler({
@@ -85,73 +83,37 @@ async function extractArchive(res: Response): Promise<string> {
85
83
  return extractDir;
86
84
  }
87
85
 
88
- /** Recursively lists all files under a directory as relative paths. */
89
- function listFiles(dir: string, base = dir): string[] {
90
- const result: string[] = [];
91
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
92
- const full = join(dir, entry.name);
93
- if (entry.isDirectory()) {
94
- result.push(...listFiles(full, base));
95
- } else {
96
- result.push(full.slice(base.length + 1));
97
- }
98
- }
99
- return result;
100
- }
101
-
102
86
  // ---------------------------------------------------------------------------
103
- // Seed workspace files
87
+ // Seed test data
104
88
  // ---------------------------------------------------------------------------
105
89
 
106
- // Text filesshould be included
107
- writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), "# My Identity\nHello");
108
- mkdirSync(join(testWorkspaceDir, "notes"), { recursive: true });
109
- writeFileSync(join(testWorkspaceDir, "notes", "daily.txt"), "Some daily notes");
110
-
111
- // SQLite DB file — should be dumped as .sql
112
- mkdirSync(join(testWorkspaceDir, "data", "db"), { recursive: true });
113
- // Create a real sqlite db with a table
114
- import { Database } from "bun:sqlite";
115
- const wsDbPath = join(testWorkspaceDir, "data", "db", "assistant.db");
116
- const wsDb = new Database(wsDbPath);
117
- wsDb.run("CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT)");
118
- wsDb.run("INSERT INTO test_table (name) VALUES ('hello')");
119
- wsDb.close();
120
-
121
- // Excluded directory: embedding-models/
122
- mkdirSync(join(testWorkspaceDir, "embedding-models"), { recursive: true });
90
+ // config.json at workspace root needed for config-snapshot test
123
91
  writeFileSync(
124
- join(testWorkspaceDir, "embedding-models", "model.bin"),
125
- "large binary model data",
92
+ join(testWorkspaceDir, "config.json"),
93
+ JSON.stringify({ provider: "anthropic" }),
126
94
  );
127
95
 
128
- // Excluded directory: data/qdrant/
129
- mkdirSync(join(testWorkspaceDir, "data", "qdrant"), { recursive: true });
96
+ // Daemon log files — used for date filtering tests
97
+ const logsDir = join(testWorkspaceDir, "data", "logs");
98
+ mkdirSync(logsDir, { recursive: true });
130
99
  writeFileSync(
131
- join(testWorkspaceDir, "data", "qdrant", "index.bin"),
132
- "vector index data",
100
+ join(logsDir, "assistant-2025-01-10.log"),
101
+ "log entry from Jan 10\n",
133
102
  );
134
-
135
- // Binary file — should be skipped
136
103
  writeFileSync(
137
- join(testWorkspaceDir, "binary-file.dat"),
138
- Buffer.from([0x48, 0x65, 0x6c, 0x00, 0x6f]), // contains null byte
104
+ join(logsDir, "assistant-2025-01-15.log"),
105
+ "log entry from Jan 15\n",
139
106
  );
140
-
141
- // config.json at workspace root — should be skipped (already in configSnapshot)
142
107
  writeFileSync(
143
- join(testWorkspaceDir, "config.json"),
144
- JSON.stringify({ provider: "anthropic" }),
108
+ join(logsDir, "assistant-2025-01-20.log"),
109
+ "log entry from Jan 20\n",
145
110
  );
146
-
147
- // Symlink pointing outside workspace — should be skipped
148
- const outsideFile = join(testDir, "outside-secret.txt");
149
- writeFileSync(outsideFile, "sensitive data outside workspace");
150
- try {
151
- symlinkSync(outsideFile, join(testWorkspaceDir, "sneaky-link.txt"));
152
- } catch {
153
- // Symlink creation may fail on some platforms; tests will still pass
154
- }
111
+ writeFileSync(
112
+ join(logsDir, "assistant-2025-01-25.log"),
113
+ "log entry from Jan 25\n",
114
+ );
115
+ // Non-dated log file — should always be included regardless of time filter
116
+ writeFileSync(join(logsDir, "vellum.log"), "non-dated log content\n");
155
117
 
156
118
  // ---------------------------------------------------------------------------
157
119
  // Tests
@@ -185,90 +147,95 @@ describe("POST /v1/export — tar.gz archive", () => {
185
147
  }
186
148
  });
187
149
 
188
- test("archive contains workspace text files", async () => {
150
+ test("archive contains config-snapshot.json when config exists", async () => {
189
151
  const res = await callExport();
190
152
  const dir = await extractArchive(res);
191
153
  try {
192
- const identity = readFileSync(
193
- join(dir, "workspace", "IDENTITY.md"),
194
- "utf-8",
195
- );
196
- expect(identity).toBe("# My Identity\nHello");
197
-
198
- const daily = readFileSync(
199
- join(dir, "workspace", "notes", "daily.txt"),
154
+ const configContent = readFileSync(
155
+ join(dir, "config-snapshot.json"),
200
156
  "utf-8",
201
157
  );
202
- expect(daily).toBe("Some daily notes");
158
+ const parsed = JSON.parse(configContent);
159
+ expect(parsed.provider).toBe("anthropic");
203
160
  } finally {
204
161
  rmSync(dir, { recursive: true, force: true });
205
162
  }
206
163
  });
164
+ });
207
165
 
208
- test("archive contains SQLite DB dumps as .sql files", async () => {
209
- const res = await callExport();
166
+ describe("POST /v1/export daemon log date filtering", () => {
167
+ test("excludes log files before startTime", async () => {
168
+ // startTime = Jan 14 — should exclude assistant-2025-01-10.log
169
+ const startTime = new Date("2025-01-14T00:00:00.000Z").getTime();
170
+ const res = await callExport({ startTime });
210
171
  const dir = await extractArchive(res);
211
172
  try {
212
- const sqlContent = readFileSync(
213
- join(dir, "workspace", "data", "db", "assistant.db.sql"),
214
- "utf-8",
215
- );
216
- expect(sqlContent).toContain("CREATE TABLE");
217
- expect(sqlContent).toContain("test_table");
173
+ const logFiles = readdirSync(join(dir, "daemon-logs"));
174
+ expect(logFiles).not.toContain("assistant-2025-01-10.log");
175
+ expect(logFiles).toContain("assistant-2025-01-15.log");
176
+ expect(logFiles).toContain("assistant-2025-01-20.log");
177
+ expect(logFiles).toContain("assistant-2025-01-25.log");
218
178
  } finally {
219
179
  rmSync(dir, { recursive: true, force: true });
220
180
  }
221
181
  });
222
182
 
223
- test("archive excludes embedding-models/ and data/qdrant/", async () => {
224
- const res = await callExport();
183
+ test("excludes log files after endTime", async () => {
184
+ // endTime = Jan 22 — should exclude assistant-2025-01-25.log
185
+ const endTime = new Date("2025-01-22T00:00:00.000Z").getTime();
186
+ const res = await callExport({ endTime });
225
187
  const dir = await extractArchive(res);
226
188
  try {
227
- const files = listFiles(join(dir, "workspace"));
228
- const embeddingFiles = files.filter((f) =>
229
- f.startsWith("embedding-models/"),
230
- );
231
- const qdrantFiles = files.filter((f) => f.startsWith("data/qdrant/"));
232
- expect(embeddingFiles).toHaveLength(0);
233
- expect(qdrantFiles).toHaveLength(0);
189
+ const logFiles = readdirSync(join(dir, "daemon-logs"));
190
+ expect(logFiles).toContain("assistant-2025-01-10.log");
191
+ expect(logFiles).toContain("assistant-2025-01-15.log");
192
+ expect(logFiles).toContain("assistant-2025-01-20.log");
193
+ expect(logFiles).not.toContain("assistant-2025-01-25.log");
234
194
  } finally {
235
195
  rmSync(dir, { recursive: true, force: true });
236
196
  }
237
197
  });
238
198
 
239
- test("archive excludes binary files and config.json at workspace root", async () => {
240
- const res = await callExport();
199
+ test("filters log files by both startTime and endTime", async () => {
200
+ // startTime = Jan 14, endTime = Jan 22 — should only include Jan 15 and Jan 20
201
+ const startTime = new Date("2025-01-14T00:00:00.000Z").getTime();
202
+ const endTime = new Date("2025-01-22T00:00:00.000Z").getTime();
203
+ const res = await callExport({ startTime, endTime });
241
204
  const dir = await extractArchive(res);
242
205
  try {
243
- const files = listFiles(join(dir, "workspace"));
244
- expect(files).not.toContain("binary-file.dat");
245
- expect(files).not.toContain("config.json");
206
+ const logFiles = readdirSync(join(dir, "daemon-logs"));
207
+ expect(logFiles).not.toContain("assistant-2025-01-10.log");
208
+ expect(logFiles).toContain("assistant-2025-01-15.log");
209
+ expect(logFiles).toContain("assistant-2025-01-20.log");
210
+ expect(logFiles).not.toContain("assistant-2025-01-25.log");
246
211
  } finally {
247
212
  rmSync(dir, { recursive: true, force: true });
248
213
  }
249
214
  });
250
215
 
251
- test("archive excludes symlinks", async () => {
252
- const res = await callExport();
216
+ test("always includes non-dated log files regardless of time filter", async () => {
217
+ const startTime = new Date("2025-01-14T00:00:00.000Z").getTime();
218
+ const endTime = new Date("2025-01-22T00:00:00.000Z").getTime();
219
+ const res = await callExport({ startTime, endTime });
253
220
  const dir = await extractArchive(res);
254
221
  try {
255
- const files = listFiles(join(dir, "workspace"));
256
- expect(files).not.toContain("sneaky-link.txt");
222
+ const logFiles = readdirSync(join(dir, "daemon-logs"));
223
+ expect(logFiles).toContain("vellum.log");
257
224
  } finally {
258
225
  rmSync(dir, { recursive: true, force: true });
259
226
  }
260
227
  });
261
228
 
262
- test("archive contains config-snapshot.json when config exists", async () => {
229
+ test("includes all log files when no time filter is specified", async () => {
263
230
  const res = await callExport();
264
231
  const dir = await extractArchive(res);
265
232
  try {
266
- const configContent = readFileSync(
267
- join(dir, "config-snapshot.json"),
268
- "utf-8",
269
- );
270
- const parsed = JSON.parse(configContent);
271
- expect(parsed.provider).toBe("anthropic");
233
+ const logFiles = readdirSync(join(dir, "daemon-logs"));
234
+ expect(logFiles).toContain("assistant-2025-01-10.log");
235
+ expect(logFiles).toContain("assistant-2025-01-15.log");
236
+ expect(logFiles).toContain("assistant-2025-01-20.log");
237
+ expect(logFiles).toContain("assistant-2025-01-25.log");
238
+ expect(logFiles).toContain("vellum.log");
272
239
  } finally {
273
240
  rmSync(dir, { recursive: true, force: true });
274
241
  }
@@ -7,6 +7,11 @@ mock.module("../security/secure-keys.js", () => ({
7
7
  deleteSecureKeyAsync: jest.fn().mockResolvedValue("deleted"),
8
8
  }));
9
9
 
10
+ // Mock platform-callback-registration (imported by client.ts)
11
+ mock.module("../inbound/platform-callback-registration.js", () => ({
12
+ shouldUsePlatformCallbacks: jest.fn().mockReturnValue(false),
13
+ }));
14
+
10
15
  const { McpClient } = await import("../mcp/client.js");
11
16
  const { McpServerManager } = await import("../mcp/manager.js");
12
17
  const { createMcpTool } = await import("../tools/mcp/mcp-tool-factory.js");
@@ -7,6 +7,11 @@ mock.module("../security/secure-keys.js", () => ({
7
7
  deleteSecureKeyAsync: jest.fn().mockResolvedValue("deleted"),
8
8
  }));
9
9
 
10
+ // Mock platform-callback-registration (imported by client.ts)
11
+ mock.module("../inbound/platform-callback-registration.js", () => ({
12
+ shouldUsePlatformCallbacks: jest.fn().mockReturnValue(false),
13
+ }));
14
+
10
15
  const { McpClient } = await import("../mcp/client.js");
11
16
 
12
17
  /**
@@ -22,6 +22,7 @@ import { getDb, initializeDb } from "../memory/db.js";
22
22
  import {
23
23
  backfillMemoryRecallLogMessageId,
24
24
  getMemoryRecallLogByMessageIds,
25
+ normalizeTopCandidates,
25
26
  recordMemoryRecallLog,
26
27
  } from "../memory/memory-recall-log-store.js";
27
28
  import { memoryRecallLogs } from "../memory/schema.js";
@@ -61,6 +62,7 @@ describe("memory-recall-log-store", () => {
61
62
  topCandidatesJson: [{ id: "c1", score: 0.9 }],
62
63
  injectedText: "some memory context",
63
64
  reason: "user query matched memories",
65
+ queryContext: "what is the weather like",
64
66
  });
65
67
 
66
68
  backfillMemoryRecallLogMessageId(conversationId, messageId);
@@ -84,6 +86,34 @@ describe("memory-recall-log-store", () => {
84
86
  expect(result!.topCandidates).toEqual([{ id: "c1", score: 0.9 }]);
85
87
  expect(result!.injectedText).toBe("some memory context");
86
88
  expect(result!.reason).toBe("user query matched memories");
89
+ expect(result!.queryContext).toBe("what is the weather like");
90
+ });
91
+
92
+ test("queryContext defaults to null when omitted", () => {
93
+ const conversationId = "conv-no-query-ctx";
94
+ const messageId = "msg-no-query-ctx";
95
+
96
+ recordMemoryRecallLog({
97
+ conversationId,
98
+ enabled: true,
99
+ degraded: false,
100
+ semanticHits: 1,
101
+ mergedCount: 1,
102
+ selectedCount: 1,
103
+ tier1Count: 1,
104
+ tier2Count: 0,
105
+ hybridSearchLatencyMs: 50,
106
+ sparseVectorUsed: false,
107
+ injectedTokens: 100,
108
+ latencyMs: 80,
109
+ topCandidatesJson: [],
110
+ });
111
+
112
+ backfillMemoryRecallLogMessageId(conversationId, messageId);
113
+
114
+ const result = getMemoryRecallLogByMessageIds([messageId]);
115
+ expect(result).not.toBeNull();
116
+ expect(result!.queryContext).toBeNull();
87
117
  });
88
118
 
89
119
  test("returns null when no log exists for a messageId", () => {
@@ -148,4 +178,106 @@ describe("memory-recall-log-store", () => {
148
178
  expect(secondLog).not.toBeNull();
149
179
  expect(secondLog!.degraded).toBe(true);
150
180
  });
181
+
182
+ test("normalizes SSE-event format candidates to inspector format on read", () => {
183
+ const conversationId = "conv-normalize-sse";
184
+ const messageId = "msg-normalize-sse";
185
+
186
+ // Store candidates in SSE-event format (key/finalScore/semantic/recency/kind)
187
+ recordMemoryRecallLog({
188
+ conversationId,
189
+ enabled: true,
190
+ degraded: false,
191
+ semanticHits: 2,
192
+ mergedCount: 1,
193
+ selectedCount: 1,
194
+ tier1Count: 1,
195
+ tier2Count: 0,
196
+ hybridSearchLatencyMs: 100,
197
+ sparseVectorUsed: false,
198
+ injectedTokens: 200,
199
+ latencyMs: 120,
200
+ topCandidatesJson: [
201
+ {
202
+ key: "node-abc",
203
+ finalScore: 0.85,
204
+ semantic: 0.9,
205
+ recency: 0.1,
206
+ kind: "episode",
207
+ type: "episodic",
208
+ },
209
+ ],
210
+ });
211
+
212
+ backfillMemoryRecallLogMessageId(conversationId, messageId);
213
+
214
+ const result = getMemoryRecallLogByMessageIds([messageId]);
215
+ expect(result).not.toBeNull();
216
+ const candidates = result!.topCandidates as Array<Record<string, unknown>>;
217
+ expect(candidates).toHaveLength(1);
218
+ expect(candidates[0]).toEqual({
219
+ nodeId: "node-abc",
220
+ score: 0.85,
221
+ semanticSimilarity: 0.9,
222
+ recencyBoost: 0.1,
223
+ type: "episodic",
224
+ });
225
+ // kind should be stripped
226
+ expect(candidates[0]).not.toHaveProperty("kind");
227
+ // Old field names should not be present
228
+ expect(candidates[0]).not.toHaveProperty("key");
229
+ expect(candidates[0]).not.toHaveProperty("finalScore");
230
+ expect(candidates[0]).not.toHaveProperty("semantic");
231
+ expect(candidates[0]).not.toHaveProperty("recency");
232
+ });
233
+
234
+ test("passes through candidates already in inspector format unchanged", () => {
235
+ const conversationId = "conv-normalize-inspector";
236
+ const messageId = "msg-normalize-inspector";
237
+
238
+ // Store candidates already in inspector format (nodeId/score/semanticSimilarity/recencyBoost)
239
+ recordMemoryRecallLog({
240
+ conversationId,
241
+ enabled: true,
242
+ degraded: false,
243
+ semanticHits: 1,
244
+ mergedCount: 1,
245
+ selectedCount: 1,
246
+ tier1Count: 1,
247
+ tier2Count: 0,
248
+ hybridSearchLatencyMs: 80,
249
+ sparseVectorUsed: false,
250
+ injectedTokens: 100,
251
+ latencyMs: 90,
252
+ topCandidatesJson: [
253
+ {
254
+ nodeId: "node-xyz",
255
+ score: 0.92,
256
+ semanticSimilarity: 0.88,
257
+ recencyBoost: 0.05,
258
+ type: "semantic",
259
+ },
260
+ ],
261
+ });
262
+
263
+ backfillMemoryRecallLogMessageId(conversationId, messageId);
264
+
265
+ const result = getMemoryRecallLogByMessageIds([messageId]);
266
+ expect(result).not.toBeNull();
267
+ const candidates = result!.topCandidates as Array<Record<string, unknown>>;
268
+ expect(candidates).toHaveLength(1);
269
+ expect(candidates[0]).toEqual({
270
+ nodeId: "node-xyz",
271
+ score: 0.92,
272
+ semanticSimilarity: 0.88,
273
+ recencyBoost: 0.05,
274
+ type: "semantic",
275
+ });
276
+ });
277
+
278
+ test("normalizeTopCandidates handles non-array input", () => {
279
+ expect(normalizeTopCandidates(null)).toBeNull();
280
+ expect(normalizeTopCandidates("not-an-array")).toBe("not-an-array");
281
+ expect(normalizeTopCandidates(42)).toBe(42);
282
+ });
151
283
  });