@vellumai/assistant 0.5.4 → 0.5.6

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 (151) hide show
  1. package/Dockerfile +17 -27
  2. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -0
  3. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +42 -0
  4. package/package.json +1 -1
  5. package/src/__tests__/actor-token-service.test.ts +113 -0
  6. package/src/__tests__/config-schema.test.ts +2 -2
  7. package/src/__tests__/context-window-manager.test.ts +78 -0
  8. package/src/__tests__/conversation-title-service.test.ts +30 -1
  9. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  10. package/src/__tests__/docker-signing-key-bootstrap.test.ts +207 -0
  11. package/src/__tests__/memory-regressions.test.ts +8 -30
  12. package/src/__tests__/openai-whisper.test.ts +93 -0
  13. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  14. package/src/__tests__/slack-messaging-token-resolution.test.ts +319 -0
  15. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  16. package/src/__tests__/tool-executor.test.ts +4 -0
  17. package/src/__tests__/volume-security-guard.test.ts +155 -0
  18. package/src/cli/commands/conversations.ts +0 -18
  19. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -0
  20. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +16 -37
  21. package/src/config/env-registry.ts +9 -0
  22. package/src/config/env.ts +8 -2
  23. package/src/config/feature-flag-registry.json +8 -8
  24. package/src/config/schema.ts +0 -12
  25. package/src/config/schemas/memory.ts +0 -4
  26. package/src/config/schemas/platform.ts +1 -1
  27. package/src/config/schemas/security.ts +4 -0
  28. package/src/context/window-manager.ts +53 -2
  29. package/src/credential-execution/managed-catalog.ts +5 -15
  30. package/src/daemon/conversation-agent-loop.ts +0 -60
  31. package/src/daemon/conversation-memory.ts +0 -117
  32. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  33. package/src/daemon/daemon-control.ts +7 -0
  34. package/src/daemon/handlers/conversations.ts +0 -11
  35. package/src/daemon/lifecycle.ts +10 -47
  36. package/src/daemon/providers-setup.ts +2 -1
  37. package/src/followups/followup-store.ts +5 -2
  38. package/src/hooks/manager.ts +7 -0
  39. package/src/instrument.ts +33 -1
  40. package/src/memory/conversation-crud.ts +0 -236
  41. package/src/memory/conversation-title-service.ts +26 -10
  42. package/src/memory/db-init.ts +5 -13
  43. package/src/memory/embedding-local.ts +11 -5
  44. package/src/memory/indexer.ts +15 -106
  45. package/src/memory/job-handlers/conversation-starters.ts +24 -36
  46. package/src/memory/job-handlers/embedding.ts +0 -79
  47. package/src/memory/job-utils.ts +1 -1
  48. package/src/memory/jobs-store.ts +0 -8
  49. package/src/memory/jobs-worker.ts +0 -20
  50. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  51. package/src/memory/migrations/index.ts +1 -3
  52. package/src/memory/qdrant-client.ts +4 -6
  53. package/src/memory/schema/conversations.ts +0 -3
  54. package/src/memory/schema/index.ts +0 -2
  55. package/src/messaging/draft-store.ts +2 -2
  56. package/src/messaging/provider.ts +9 -0
  57. package/src/messaging/providers/slack/adapter.ts +29 -2
  58. package/src/oauth/connection-resolver.test.ts +22 -18
  59. package/src/oauth/connection-resolver.ts +92 -7
  60. package/src/oauth/platform-connection.test.ts +78 -69
  61. package/src/oauth/platform-connection.ts +12 -19
  62. package/src/permissions/defaults.ts +3 -3
  63. package/src/permissions/trust-client.ts +332 -0
  64. package/src/permissions/trust-store-interface.ts +105 -0
  65. package/src/permissions/trust-store.ts +531 -39
  66. package/src/platform/client.test.ts +148 -0
  67. package/src/platform/client.ts +71 -0
  68. package/src/providers/speech-to-text/openai-whisper.test.ts +190 -0
  69. package/src/providers/speech-to-text/openai-whisper.ts +68 -0
  70. package/src/providers/speech-to-text/resolve.ts +9 -0
  71. package/src/providers/speech-to-text/types.ts +17 -0
  72. package/src/runtime/auth/route-policy.ts +14 -0
  73. package/src/runtime/auth/token-service.ts +133 -0
  74. package/src/runtime/http-server.ts +4 -2
  75. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  76. package/src/runtime/routes/conversation-query-routes.ts +44 -2
  77. package/src/runtime/routes/conversation-routes.ts +2 -1
  78. package/src/runtime/routes/inbound-message-handler.ts +27 -3
  79. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -1
  80. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +287 -0
  81. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +122 -0
  82. package/src/runtime/routes/log-export-routes.ts +1 -0
  83. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  84. package/src/runtime/routes/memory-item-routes.ts +124 -2
  85. package/src/runtime/routes/secret-routes.ts +4 -1
  86. package/src/runtime/routes/upgrade-broadcast-routes.ts +151 -0
  87. package/src/schedule/schedule-store.ts +0 -21
  88. package/src/security/ces-credential-client.ts +173 -0
  89. package/src/security/secure-keys.ts +65 -22
  90. package/src/signals/bash.ts +3 -0
  91. package/src/signals/cancel.ts +3 -0
  92. package/src/signals/confirm.ts +3 -0
  93. package/src/signals/conversation-undo.ts +3 -0
  94. package/src/signals/event-stream.ts +7 -0
  95. package/src/signals/shotgun.ts +3 -0
  96. package/src/signals/trust-rule.ts +3 -0
  97. package/src/skills/inline-command-render.ts +5 -1
  98. package/src/skills/inline-command-runner.ts +30 -2
  99. package/src/telemetry/usage-telemetry-reporter.test.ts +23 -36
  100. package/src/telemetry/usage-telemetry-reporter.ts +21 -19
  101. package/src/tools/memory/handlers.ts +1 -129
  102. package/src/tools/permission-checker.ts +18 -0
  103. package/src/tools/skills/load.ts +9 -2
  104. package/src/util/device-id.ts +70 -7
  105. package/src/util/logger.ts +35 -9
  106. package/src/util/platform.ts +29 -5
  107. package/src/util/xml.ts +8 -0
  108. package/src/workspace/heartbeat-service.ts +5 -24
  109. package/src/workspace/migrations/migrate-to-workspace-volume.ts +113 -0
  110. package/src/workspace/migrations/registry.ts +2 -0
  111. package/src/__tests__/archive-recall.test.ts +0 -560
  112. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  113. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  114. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  115. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  116. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  117. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  118. package/src/__tests__/memory-brief-time.test.ts +0 -285
  119. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  120. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  121. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  122. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  123. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  124. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  125. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  126. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  127. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  128. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  129. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  130. package/src/__tests__/memory-reducer.test.ts +0 -704
  131. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  132. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  133. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  134. package/src/config/schemas/memory-simplified.ts +0 -101
  135. package/src/memory/archive-recall.ts +0 -516
  136. package/src/memory/archive-store.ts +0 -400
  137. package/src/memory/brief-formatting.ts +0 -33
  138. package/src/memory/brief-open-loops.ts +0 -266
  139. package/src/memory/brief-time.ts +0 -162
  140. package/src/memory/brief.ts +0 -75
  141. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  142. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  143. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  144. package/src/memory/migrations/186-memory-archive.ts +0 -109
  145. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  146. package/src/memory/reducer-scheduler.ts +0 -242
  147. package/src/memory/reducer-store.ts +0 -271
  148. package/src/memory/reducer-types.ts +0 -106
  149. package/src/memory/reducer.ts +0 -467
  150. package/src/memory/schema/memory-archive.ts +0 -121
  151. package/src/memory/schema/memory-brief.ts +0 -55
@@ -1,666 +0,0 @@
1
- /**
2
- * End-to-end tests for the simplified memory system.
3
- *
4
- * Covers the must-have scenarios:
5
- * 1. Backfill: legacy segments, summaries, and items migrate to simplified tables
6
- * 2. Simplified memory is enabled by default after backfill
7
- * 3. Memory tools use simplified path when enabled
8
- * 4. Legacy tables remain available as rollback support
9
- */
10
- import { mkdtempSync, rmSync } from "node:fs";
11
- import { tmpdir } from "node:os";
12
- import { join } from "node:path";
13
- import {
14
- afterAll,
15
- beforeAll,
16
- beforeEach,
17
- describe,
18
- expect,
19
- mock,
20
- test,
21
- } from "bun:test";
22
-
23
- const testDir = mkdtempSync(join(tmpdir(), "simplified-memory-e2e-test-"));
24
- const dbPath = join(testDir, "test.db");
25
-
26
- // ── Platform mock (must come before any module imports) ──────────────
27
-
28
- mock.module("../util/platform.js", () => ({
29
- getDataDir: () => testDir,
30
- getRootDir: () => testDir,
31
- isMacOS: () => process.platform === "darwin",
32
- isLinux: () => process.platform === "linux",
33
- isWindows: () => process.platform === "win32",
34
- getPidPath: () => join(testDir, "test.pid"),
35
- getDbPath: () => dbPath,
36
- getLogPath: () => join(testDir, "test.log"),
37
- ensureDataDir: () => {},
38
- }));
39
-
40
- mock.module("../util/logger.js", () => ({
41
- getLogger: () =>
42
- new Proxy({} as Record<string, unknown>, {
43
- get: () => () => {},
44
- }),
45
- truncateForLog: (value: string) => value,
46
- }));
47
-
48
- // Stub the Qdrant and embedding backends since we don't need vectors
49
- mock.module("../memory/qdrant-client.js", () => ({
50
- getQdrantClient: () => ({
51
- searchWithFilter: async () => [],
52
- hybridSearch: async () => [],
53
- upsertPoints: async () => {},
54
- deletePoints: async () => {},
55
- upsert: async () => {},
56
- }),
57
- initQdrantClient: () => {},
58
- }));
59
-
60
- mock.module("../memory/embedding-backend.js", () => ({
61
- getMemoryBackendStatus: async () => ({
62
- provider: null,
63
- reason: "test-stub",
64
- }),
65
- embedWithBackend: async () => ({
66
- vectors: [[]],
67
- provider: "test",
68
- model: "test",
69
- }),
70
- generateSparseEmbedding: () => undefined,
71
- selectEmbeddingBackend: async () => null,
72
- }));
73
-
74
- mock.module("../memory/qdrant-circuit-breaker.js", () => ({
75
- withQdrantBreaker: async (fn: () => Promise<unknown>) => fn(),
76
- QdrantCircuitOpenError: class extends Error {},
77
- }));
78
-
79
- // ── Configurable config mock ────────────────────────────────────────
80
-
81
- import { DEFAULT_CONFIG } from "../config/defaults.js";
82
- import type { AssistantConfig } from "../config/types.js";
83
-
84
- let testConfig: AssistantConfig = {
85
- ...DEFAULT_CONFIG,
86
- memory: {
87
- ...DEFAULT_CONFIG.memory,
88
- enabled: true,
89
- simplified: {
90
- ...DEFAULT_CONFIG.memory.simplified,
91
- enabled: true,
92
- },
93
- },
94
- };
95
-
96
- mock.module("../config/loader.js", () => ({
97
- loadConfig: () => testConfig,
98
- getConfig: () => testConfig,
99
- loadRawConfig: () => ({}),
100
- saveRawConfig: () => {},
101
- invalidateConfigCache: () => {},
102
- }));
103
-
104
- // ── Now import modules under test ────────────────────────────────────
105
-
106
- import { v4 as uuid } from "uuid";
107
-
108
- import { insertObservation } from "../memory/archive-store.js";
109
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
110
- import { getSqlite } from "../memory/db-connection.js";
111
- import { backfillSimplifiedMemoryJob } from "../memory/job-handlers/backfill-simplified-memory.js";
112
- import type { MemoryJob } from "../memory/jobs-store.js";
113
- import { conversations, memoryItems, messages } from "../memory/schema.js";
114
- import {
115
- handleMemoryRecall,
116
- handleMemorySave,
117
- } from "../tools/memory/handlers.js";
118
-
119
- // ── Helpers ─────────────────────────────────────────────────────────
120
-
121
- function removeTestDbFiles(): void {
122
- rmSync(dbPath, { force: true });
123
- rmSync(`${dbPath}-shm`, { force: true });
124
- rmSync(`${dbPath}-wal`, { force: true });
125
- }
126
-
127
- function getRawDb(): import("bun:sqlite").Database {
128
- return getSqlite();
129
- }
130
-
131
- function makeJob(overrides: Partial<MemoryJob> = {}): MemoryJob {
132
- return {
133
- id: uuid(),
134
- type: "backfill_simplified_memory",
135
- payload: {},
136
- status: "running",
137
- attempts: 0,
138
- deferrals: 0,
139
- runAfter: 0,
140
- lastError: null,
141
- startedAt: Date.now(),
142
- createdAt: Date.now(),
143
- updatedAt: Date.now(),
144
- ...overrides,
145
- };
146
- }
147
-
148
- function createConversation(id: string, title: string | null = null): void {
149
- const db = getDb();
150
- const now = Date.now();
151
- db.insert(conversations)
152
- .values({
153
- id,
154
- title,
155
- createdAt: now,
156
- updatedAt: now,
157
- })
158
- .run();
159
- }
160
-
161
- let segmentIndexCounter = 0;
162
-
163
- function insertLegacySegment(opts: {
164
- id: string;
165
- messageId: string;
166
- conversationId: string;
167
- role: string;
168
- text: string;
169
- scopeId?: string;
170
- segmentIndex?: number;
171
- }): void {
172
- const now = Date.now();
173
- const idx = opts.segmentIndex ?? segmentIndexCounter++;
174
- getRawDb().run(
175
- `INSERT INTO memory_segments (id, message_id, conversation_id, role, segment_index, text, token_estimate, scope_id, created_at, updated_at)
176
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
177
- [
178
- opts.id,
179
- opts.messageId,
180
- opts.conversationId,
181
- opts.role,
182
- idx,
183
- opts.text,
184
- Math.ceil(opts.text.length / 4),
185
- opts.scopeId ?? "default",
186
- now,
187
- now,
188
- ],
189
- );
190
- }
191
-
192
- function insertLegacySummary(opts: {
193
- id: string;
194
- scope: string;
195
- scopeKey: string;
196
- summary: string;
197
- scopeId?: string;
198
- startAt?: number;
199
- endAt?: number;
200
- }): void {
201
- const now = Date.now();
202
- getRawDb().run(
203
- `INSERT INTO memory_summaries (id, scope, scope_key, summary, token_estimate, version, scope_id, start_at, end_at, created_at, updated_at)
204
- VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?)`,
205
- [
206
- opts.id,
207
- opts.scope,
208
- opts.scopeKey,
209
- opts.summary,
210
- Math.ceil(opts.summary.length / 4),
211
- opts.scopeId ?? "default",
212
- opts.startAt ?? now - 3600000,
213
- opts.endAt ?? now,
214
- now,
215
- now,
216
- ],
217
- );
218
- }
219
-
220
- function insertLegacyItem(opts: {
221
- id: string;
222
- kind: string;
223
- subject: string;
224
- statement: string;
225
- scopeId?: string;
226
- confidence?: number;
227
- status?: string;
228
- validFrom?: number | null;
229
- invalidAt?: number | null;
230
- }): void {
231
- const now = Date.now();
232
- getRawDb().run(
233
- `INSERT INTO memory_items (id, kind, subject, statement, status, confidence, importance, fingerprint, verification_state, scope_id, first_seen_at, last_seen_at)
234
- VALUES (?, ?, ?, ?, ?, ?, 0.8, ?, 'assistant_inferred', ?, ?, ?)`,
235
- [
236
- opts.id,
237
- opts.kind,
238
- opts.subject,
239
- opts.statement,
240
- opts.status ?? "active",
241
- opts.confidence ?? 0.9,
242
- `fp-${opts.id}`,
243
- opts.scopeId ?? "default",
244
- now,
245
- now,
246
- ],
247
- );
248
- // Set validFrom/invalidAt if provided
249
- if (opts.validFrom != null || opts.invalidAt != null) {
250
- getRawDb().run(
251
- `UPDATE memory_items SET valid_from = ?, invalid_at = ? WHERE id = ?`,
252
- [opts.validFrom ?? null, opts.invalidAt ?? null, opts.id],
253
- );
254
- }
255
- }
256
-
257
- function insertMessage(
258
- id: string,
259
- conversationId: string,
260
- role: string = "user",
261
- content: string = "test message",
262
- ): void {
263
- const db = getDb();
264
- const now = Date.now();
265
- db.insert(messages)
266
- .values({
267
- id,
268
- conversationId,
269
- role,
270
- content,
271
- createdAt: now,
272
- })
273
- .run();
274
- }
275
-
276
- function countRows(table: string): number {
277
- const result = getRawDb()
278
- .query<{ c: number }, []>(`SELECT COUNT(*) as c FROM ${table}`)
279
- .get();
280
- return result?.c ?? 0;
281
- }
282
-
283
- // ── Setup / Teardown ────────────────────────────────────────────────
284
-
285
- describe("Simplified Memory E2E", () => {
286
- beforeAll(() => {
287
- initializeDb();
288
- });
289
-
290
- beforeEach(() => {
291
- segmentIndexCounter = 0;
292
- resetDb();
293
- removeTestDbFiles();
294
- initializeDb();
295
- testConfig = {
296
- ...DEFAULT_CONFIG,
297
- memory: {
298
- ...DEFAULT_CONFIG.memory,
299
- enabled: true,
300
- simplified: {
301
- ...DEFAULT_CONFIG.memory.simplified,
302
- enabled: true,
303
- },
304
- },
305
- };
306
- });
307
-
308
- afterAll(() => {
309
- resetDb();
310
- rmSync(testDir, { recursive: true, force: true });
311
- });
312
-
313
- // ── 1. Backfill tests ──────────────────────────────────────────────
314
-
315
- describe("backfill: legacy data migration", () => {
316
- test("migrates legacy segments to observations and chunks", async () => {
317
- const convId = uuid();
318
- createConversation(convId);
319
- const msgId = uuid();
320
- insertMessage(msgId, convId, "user", "I like TypeScript");
321
-
322
- insertLegacySegment({
323
- id: "seg-1",
324
- messageId: msgId,
325
- conversationId: convId,
326
- role: "user",
327
- text: "I like TypeScript and prefer it over JavaScript",
328
- });
329
- insertLegacySegment({
330
- id: "seg-2",
331
- messageId: msgId,
332
- conversationId: convId,
333
- role: "user",
334
- text: "My favorite editor is VS Code",
335
- });
336
-
337
- expect(countRows("memory_segments")).toBe(2);
338
- expect(countRows("memory_observations")).toBe(0);
339
-
340
- await backfillSimplifiedMemoryJob(makeJob());
341
-
342
- // Segments should have been migrated to observations
343
- expect(countRows("memory_observations")).toBeGreaterThanOrEqual(2);
344
- // Chunks should have been created for each observation
345
- expect(countRows("memory_chunks")).toBeGreaterThanOrEqual(2);
346
- // Original segments remain untouched
347
- expect(countRows("memory_segments")).toBe(2);
348
- });
349
-
350
- test("migrates legacy summaries to episodes", async () => {
351
- const convId = uuid();
352
- createConversation(convId);
353
-
354
- insertLegacySummary({
355
- id: "sum-1",
356
- scope: "conversation",
357
- scopeKey: convId,
358
- summary: "User discussed TypeScript preferences and project setup",
359
- });
360
-
361
- expect(countRows("memory_summaries")).toBe(1);
362
- expect(countRows("memory_episodes")).toBe(0);
363
-
364
- await backfillSimplifiedMemoryJob(makeJob());
365
-
366
- expect(countRows("memory_episodes")).toBeGreaterThanOrEqual(1);
367
- // Original summaries remain untouched
368
- expect(countRows("memory_summaries")).toBe(1);
369
- });
370
-
371
- test("migrates active legacy items to observations", async () => {
372
- insertLegacyItem({
373
- id: "item-1",
374
- kind: "preference",
375
- subject: "Editor",
376
- statement: "Prefers VS Code with vim keybindings",
377
- });
378
- insertLegacyItem({
379
- id: "item-2",
380
- kind: "identity",
381
- subject: "Name",
382
- statement: "User's name is Alice",
383
- });
384
- // Low-confidence item should be skipped
385
- insertLegacyItem({
386
- id: "item-3",
387
- kind: "event",
388
- subject: "Meeting",
389
- statement: "Had a meeting yesterday",
390
- confidence: 0.3,
391
- });
392
-
393
- expect(countRows("memory_items")).toBe(3);
394
- expect(countRows("memory_observations")).toBe(0);
395
-
396
- await backfillSimplifiedMemoryJob(makeJob());
397
-
398
- // Only the 2 active, high-confidence items should be migrated
399
- expect(countRows("memory_observations")).toBeGreaterThanOrEqual(2);
400
- // Original items remain untouched
401
- expect(countRows("memory_items")).toBe(3);
402
- });
403
-
404
- test("backfill is idempotent — running twice does not duplicate", async () => {
405
- const convId = uuid();
406
- createConversation(convId);
407
- const msgId = uuid();
408
- insertMessage(msgId, convId, "user", "Hello");
409
-
410
- insertLegacySegment({
411
- id: "seg-idem-1",
412
- messageId: msgId,
413
- conversationId: convId,
414
- role: "user",
415
- text: "This is an idempotency test segment",
416
- });
417
-
418
- await backfillSimplifiedMemoryJob(makeJob());
419
- const firstRunObservations = countRows("memory_observations");
420
-
421
- // Run again — should not create duplicates because content-hash dedup
422
- // and checkpoint tracking prevent it
423
- await backfillSimplifiedMemoryJob(makeJob());
424
- const secondRunObservations = countRows("memory_observations");
425
-
426
- expect(secondRunObservations).toBe(firstRunObservations);
427
- });
428
-
429
- test("backfill skips non-conversation summaries", async () => {
430
- insertLegacySummary({
431
- id: "sum-skip-1",
432
- scope: "weekly",
433
- scopeKey: "2024-W01",
434
- summary: "A weekly summary that does not link to a conversation",
435
- });
436
-
437
- await backfillSimplifiedMemoryJob(makeJob());
438
-
439
- // Non-conversation summaries should be skipped (no episode created)
440
- // The summary has scope "weekly" with a non-conversation-id scope_key
441
- // so it should be skipped by the extractConversationId check.
442
- // (Weekly summaries have no valid conversation to link to.)
443
- expect(countRows("memory_episodes")).toBe(0);
444
- });
445
- });
446
-
447
- // ── 2. Default flag state ─────────────────────────────────────────
448
-
449
- describe("simplified memory enabled by default", () => {
450
- test("config defaults to simplified.enabled = true", () => {
451
- expect(testConfig.memory.simplified.enabled).toBe(true);
452
- });
453
-
454
- test("can be disabled for rollback via config override", () => {
455
- testConfig = {
456
- ...testConfig,
457
- memory: {
458
- ...testConfig.memory,
459
- simplified: {
460
- ...testConfig.memory.simplified,
461
- enabled: false,
462
- },
463
- },
464
- };
465
- expect(testConfig.memory.simplified.enabled).toBe(false);
466
- });
467
- });
468
-
469
- // ── 3. Memory tools use simplified path ────────────────────────────
470
-
471
- describe("memory tools use simplified system when enabled", () => {
472
- test("memory_save writes to observations when simplified is enabled", async () => {
473
- const convId = uuid();
474
- createConversation(convId);
475
-
476
- const result = await handleMemorySave(
477
- {
478
- statement: "User prefers dark mode",
479
- kind: "preference",
480
- subject: "UI theme",
481
- },
482
- testConfig,
483
- convId,
484
- undefined,
485
- "default",
486
- );
487
-
488
- expect(result.isError).toBe(false);
489
- expect(result.content).toContain("Saved to memory");
490
-
491
- // Should have written to observations, not memory_items
492
- expect(countRows("memory_observations")).toBeGreaterThanOrEqual(1);
493
- });
494
-
495
- test("memory_save writes to memory_items when simplified is disabled", async () => {
496
- testConfig = {
497
- ...testConfig,
498
- memory: {
499
- ...testConfig.memory,
500
- simplified: {
501
- ...testConfig.memory.simplified,
502
- enabled: false,
503
- },
504
- },
505
- };
506
-
507
- const convId = uuid();
508
- createConversation(convId);
509
-
510
- const result = await handleMemorySave(
511
- {
512
- statement: "User prefers light mode",
513
- kind: "preference",
514
- subject: "UI theme",
515
- },
516
- testConfig,
517
- convId,
518
- undefined,
519
- "default",
520
- );
521
-
522
- expect(result.isError).toBe(false);
523
- expect(result.content).toContain("Saved to memory");
524
-
525
- // Should have written to legacy memory_items
526
- expect(countRows("memory_items")).toBeGreaterThanOrEqual(1);
527
- });
528
-
529
- test("memory_recall uses archive recall when simplified is enabled", async () => {
530
- // Insert some archive data that the recall can find
531
- const convId = uuid();
532
- createConversation(convId);
533
-
534
- insertObservation({
535
- conversationId: convId,
536
- role: "user",
537
- content:
538
- "User mentioned that their favorite programming language is TypeScript",
539
- scopeId: "default",
540
- modality: "text",
541
- source: "test",
542
- });
543
-
544
- const result = await handleMemoryRecall(
545
- { query: "programming language TypeScript" },
546
- testConfig,
547
- "default",
548
- convId,
549
- );
550
-
551
- expect(result.isError).toBe(false);
552
- // The result should be valid JSON
553
- const parsed = JSON.parse(result.content);
554
- expect(parsed).toBeDefined();
555
- expect(typeof parsed.text).toBe("string");
556
- expect(typeof parsed.resultCount).toBe("number");
557
- });
558
- });
559
-
560
- // ── 4. Legacy tables remain available ──────────────────────────────
561
-
562
- describe("legacy tables remain for rollback", () => {
563
- test("legacy memory_items table still exists and is writable", () => {
564
- const db = getDb();
565
- const now = Date.now();
566
-
567
- // Write to the legacy table should succeed
568
- db.insert(memoryItems)
569
- .values({
570
- id: uuid(),
571
- kind: "preference",
572
- subject: "Test",
573
- statement: "Test statement",
574
- status: "active",
575
- confidence: 0.9,
576
- importance: 0.8,
577
- fingerprint: `fp-${uuid()}`,
578
- verificationState: "assistant_inferred",
579
- scopeId: "default",
580
- firstSeenAt: now,
581
- lastSeenAt: now,
582
- })
583
- .run();
584
-
585
- expect(countRows("memory_items")).toBe(1);
586
- });
587
-
588
- test("legacy memory_segments table still exists and is writable", () => {
589
- const convId = uuid();
590
- createConversation(convId);
591
- const msgId = uuid();
592
- insertMessage(msgId, convId, "user", "test");
593
-
594
- insertLegacySegment({
595
- id: uuid(),
596
- messageId: msgId,
597
- conversationId: convId,
598
- role: "user",
599
- text: "Test legacy segment",
600
- });
601
-
602
- expect(countRows("memory_segments")).toBe(1);
603
- });
604
-
605
- test("legacy memory_summaries table still exists and is writable", () => {
606
- const convId = uuid();
607
- createConversation(convId);
608
-
609
- insertLegacySummary({
610
- id: uuid(),
611
- scope: "conversation",
612
- scopeKey: convId,
613
- summary: "Test legacy summary",
614
- });
615
-
616
- expect(countRows("memory_summaries")).toBe(1);
617
- });
618
-
619
- test("switching to legacy mode by disabling simplified flag works", async () => {
620
- // First save with simplified enabled
621
- const convId = uuid();
622
- createConversation(convId);
623
-
624
- await handleMemorySave(
625
- {
626
- statement: "User likes coffee",
627
- kind: "preference",
628
- subject: "Beverage",
629
- },
630
- testConfig,
631
- convId,
632
- undefined,
633
- "default",
634
- );
635
-
636
- const simplifiedObs = countRows("memory_observations");
637
- expect(simplifiedObs).toBeGreaterThanOrEqual(1);
638
-
639
- // Now disable simplified and save — should go to legacy table
640
- testConfig = {
641
- ...testConfig,
642
- memory: {
643
- ...testConfig.memory,
644
- simplified: {
645
- ...testConfig.memory.simplified,
646
- enabled: false,
647
- },
648
- },
649
- };
650
-
651
- await handleMemorySave(
652
- {
653
- statement: "User likes tea",
654
- kind: "preference",
655
- subject: "Beverage",
656
- },
657
- testConfig,
658
- convId,
659
- undefined,
660
- "default",
661
- );
662
-
663
- expect(countRows("memory_items")).toBeGreaterThanOrEqual(1);
664
- });
665
- });
666
- });