@cortexmemory/cli 0.27.3 → 0.28.0

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 (68) hide show
  1. package/dist/commands/db.d.ts.map +1 -1
  2. package/dist/commands/db.js +18 -6
  3. package/dist/commands/db.js.map +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +191 -80
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/dev.js +3 -2
  8. package/dist/commands/dev.js.map +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +12 -0
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/types.d.ts +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/app-template-sync.d.ts.map +1 -1
  15. package/dist/utils/app-template-sync.js +35 -13
  16. package/dist/utils/app-template-sync.js.map +1 -1
  17. package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
  18. package/dist/utils/init/quickstart-setup.js.map +1 -1
  19. package/package.json +4 -4
  20. package/templates/basic/.env.local.example +23 -0
  21. package/templates/basic/README.md +181 -56
  22. package/templates/basic/package-lock.json +2180 -406
  23. package/templates/basic/package.json +23 -5
  24. package/templates/basic/src/__tests__/chat.test.ts +340 -0
  25. package/templates/basic/src/__tests__/cortex.test.ts +260 -0
  26. package/templates/basic/src/__tests__/display.test.ts +455 -0
  27. package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
  28. package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
  29. package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
  30. package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
  31. package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
  32. package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
  33. package/templates/basic/src/__tests__/llm.test.ts +344 -0
  34. package/templates/basic/src/chat.ts +300 -0
  35. package/templates/basic/src/cortex.ts +203 -0
  36. package/templates/basic/src/display.ts +425 -0
  37. package/templates/basic/src/index.ts +194 -64
  38. package/templates/basic/src/llm.ts +214 -0
  39. package/templates/basic/src/server.ts +280 -0
  40. package/templates/basic/vitest.config.ts +33 -0
  41. package/templates/basic/vitest.e2e.config.ts +28 -0
  42. package/templates/basic/vitest.integration.config.ts +25 -0
  43. package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
  44. package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +61 -19
  45. package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
  46. package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
  47. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +95 -23
  48. package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +339 -0
  49. package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
  50. package/templates/vercel-ai-quickstart/app/globals.css +24 -9
  51. package/templates/vercel-ai-quickstart/app/page.tsx +41 -15
  52. package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
  53. package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
  54. package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
  55. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +46 -16
  56. package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
  57. package/templates/vercel-ai-quickstart/jest.config.js +8 -1
  58. package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +165 -0
  59. package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
  60. package/templates/vercel-ai-quickstart/lib/versions.ts +60 -0
  61. package/templates/vercel-ai-quickstart/next.config.js +10 -2
  62. package/templates/vercel-ai-quickstart/package.json +23 -12
  63. package/templates/vercel-ai-quickstart/test-api.mjs +303 -0
  64. package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +483 -0
  65. package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
  66. package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
  67. package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
  68. package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
@@ -0,0 +1,483 @@
1
+ /**
2
+ * E2E Tests: Chat API Memory Flow
3
+ *
4
+ * These tests verify the FULL memory flow through the chat API routes:
5
+ * - Fact storage from conversations
6
+ * - Belief revision (superseding facts)
7
+ * - Memory recall across conversations
8
+ * - Conversation lifecycle (create, chat, delete)
9
+ *
10
+ * REQUIRES:
11
+ * - CONVEX_URL: Real Convex deployment
12
+ * - OPENAI_API_KEY: For LLM calls and embeddings
13
+ * - CORTEX_FACT_EXTRACTION=true: Enable fact extraction
14
+ *
15
+ * These tests use real HTTP requests to the quickstart server,
16
+ * so the server must be running on localhost:3000.
17
+ */
18
+
19
+ import { Cortex } from "@cortexmemory/sdk";
20
+
21
+ // Skip if required env vars not set
22
+ const SKIP_E2E =
23
+ !process.env.CONVEX_URL ||
24
+ !process.env.OPENAI_API_KEY ||
25
+ !process.env.QUICKSTART_URL;
26
+
27
+ const BASE_URL = process.env.QUICKSTART_URL || "http://localhost:3000";
28
+
29
+ // Generate unique IDs for test isolation
30
+ function generateTestId(prefix: string): string {
31
+ const timestamp = Date.now();
32
+ const random = Math.random().toString(36).slice(2, 8);
33
+ return `${prefix}-e2e-${timestamp}-${random}`;
34
+ }
35
+
36
+ /**
37
+ * Make a chat request to the API
38
+ */
39
+ async function sendChatMessage(
40
+ endpoint: string,
41
+ messages: Array<{ role: string; content: string }>,
42
+ options: {
43
+ userId: string;
44
+ memorySpaceId: string;
45
+ conversationId?: string;
46
+ },
47
+ ): Promise<{
48
+ response: string;
49
+ conversationId?: string;
50
+ }> {
51
+ const response = await fetch(`${BASE_URL}/api/${endpoint}`, {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify({
55
+ messages: messages.map((m, i) => ({
56
+ id: `msg-${i}`,
57
+ role: m.role,
58
+ content: m.content,
59
+ createdAt: new Date().toISOString(),
60
+ })),
61
+ userId: options.userId,
62
+ memorySpaceId: options.memorySpaceId,
63
+ conversationId: options.conversationId,
64
+ }),
65
+ });
66
+
67
+ if (!response.ok) {
68
+ const error = await response.text();
69
+ throw new Error(`Chat API error: ${response.status} - ${error}`);
70
+ }
71
+
72
+ // Parse streaming response
73
+ const text = await response.text();
74
+
75
+ // Extract text content from the stream (simplified parsing)
76
+ let fullResponse = "";
77
+ let conversationId: string | undefined;
78
+
79
+ const lines = text.split("\n");
80
+ for (const line of lines) {
81
+ if (line.startsWith("0:")) {
82
+ // Text content
83
+ try {
84
+ const content = JSON.parse(line.slice(2));
85
+ if (typeof content === "string") {
86
+ fullResponse += content;
87
+ }
88
+ } catch {
89
+ // Ignore parse errors
90
+ }
91
+ } else if (line.includes("data-conversation-update")) {
92
+ // Extract conversation ID
93
+ try {
94
+ const match = line.match(/"conversationId":"([^"]+)"/);
95
+ if (match) {
96
+ conversationId = match[1];
97
+ }
98
+ } catch {
99
+ // Ignore parse errors
100
+ }
101
+ }
102
+ }
103
+
104
+ return { response: fullResponse, conversationId };
105
+ }
106
+
107
+ describe("Chat Memory Flow E2E", () => {
108
+ let cortex: Cortex;
109
+ let testUserId: string;
110
+ let testMemorySpaceId: string;
111
+
112
+ beforeAll(() => {
113
+ if (SKIP_E2E) {
114
+ console.log(
115
+ "Skipping E2E tests - CONVEX_URL, OPENAI_API_KEY, or QUICKSTART_URL not configured",
116
+ );
117
+ return;
118
+ }
119
+ cortex = new Cortex({ convexUrl: process.env.CONVEX_URL! });
120
+ });
121
+
122
+ beforeEach(() => {
123
+ if (SKIP_E2E) return;
124
+ testUserId = generateTestId("user");
125
+ testMemorySpaceId = generateTestId("space");
126
+ });
127
+
128
+ afterAll(async () => {
129
+ if (cortex) {
130
+ cortex.close();
131
+ }
132
+ });
133
+
134
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
135
+ // V5 Route Tests
136
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
137
+
138
+ (SKIP_E2E ? describe.skip : describe)("v5 route (/api/chat)", () => {
139
+ it("should store facts from conversation", async () => {
140
+ // Send a message with a fact
141
+ await sendChatMessage(
142
+ "chat",
143
+ [
144
+ {
145
+ role: "user",
146
+ content: "My name is Alice and I work as a software engineer",
147
+ },
148
+ ],
149
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
150
+ );
151
+
152
+ // Wait for fact extraction
153
+ await new Promise((r) => setTimeout(r, 5000));
154
+
155
+ // Verify facts were stored
156
+ const facts = await cortex.facts.list({
157
+ memorySpaceId: testMemorySpaceId,
158
+ userId: testUserId,
159
+ includeSuperseded: false,
160
+ });
161
+
162
+ console.log(`[V5] Stored facts: ${facts.length}`);
163
+ facts.forEach((f) => console.log(` - ${f.fact}`));
164
+
165
+ expect(facts.length).toBeGreaterThan(0);
166
+ }, 60000);
167
+
168
+ it("should supersede facts through belief revision", async () => {
169
+ // First message: establish a preference
170
+ await sendChatMessage(
171
+ "chat",
172
+ [{ role: "user", content: "My favorite color is blue" }],
173
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
174
+ );
175
+
176
+ await new Promise((r) => setTimeout(r, 5000));
177
+
178
+ // Second message: change the preference
179
+ await sendChatMessage(
180
+ "chat",
181
+ [
182
+ { role: "user", content: "My favorite color is blue" },
183
+ {
184
+ role: "assistant",
185
+ content: "Got it, blue is your favorite color!",
186
+ },
187
+ {
188
+ role: "user",
189
+ content: "Actually, my favorite color is purple now",
190
+ },
191
+ ],
192
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
193
+ );
194
+
195
+ await new Promise((r) => setTimeout(r, 5000));
196
+
197
+ // Check facts
198
+ const allFacts = await cortex.facts.list({
199
+ memorySpaceId: testMemorySpaceId,
200
+ userId: testUserId,
201
+ includeSuperseded: true,
202
+ });
203
+
204
+ const activeFacts = await cortex.facts.list({
205
+ memorySpaceId: testMemorySpaceId,
206
+ userId: testUserId,
207
+ includeSuperseded: false,
208
+ });
209
+
210
+ console.log(
211
+ `[V5] All facts: ${allFacts.length}, Active: ${activeFacts.length}`,
212
+ );
213
+ allFacts.forEach((f) => {
214
+ const status = f.supersededBy ? "SUPERSEDED" : "ACTIVE";
215
+ console.log(` [${status}] ${f.fact}`);
216
+ });
217
+
218
+ // Should have superseded the old color preference
219
+ const colorFacts = activeFacts.filter(
220
+ (f) =>
221
+ f.fact.toLowerCase().includes("color") ||
222
+ f.fact.toLowerCase().includes("purple") ||
223
+ f.fact.toLowerCase().includes("blue"),
224
+ );
225
+
226
+ // Ideally only one active color fact (purple)
227
+ expect(colorFacts.length).toBeLessThanOrEqual(2);
228
+ }, 90000);
229
+
230
+ it("should recall facts in subsequent conversations", async () => {
231
+ // First conversation: store a fact
232
+ const conv1Result = await sendChatMessage(
233
+ "chat",
234
+ [{ role: "user", content: "I have a dog named Max" }],
235
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
236
+ );
237
+
238
+ await new Promise((r) => setTimeout(r, 5000));
239
+
240
+ // Second conversation: ask about the fact
241
+ const conv2Result = await sendChatMessage(
242
+ "chat",
243
+ [{ role: "user", content: "What do you remember about my pets?" }],
244
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
245
+ );
246
+
247
+ console.log(
248
+ `[V5] Recall response: ${conv2Result.response.slice(0, 200)}...`,
249
+ );
250
+
251
+ // Response should mention Max (the dog)
252
+ const responseText = conv2Result.response.toLowerCase();
253
+ const mentionsPet =
254
+ responseText.includes("max") || responseText.includes("dog");
255
+
256
+ // Note: LLM responses are non-deterministic, so we just verify we got a response
257
+ expect(conv2Result.response.length).toBeGreaterThan(0);
258
+ }, 90000);
259
+ });
260
+
261
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
262
+ // V6 Route Tests
263
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
264
+
265
+ (SKIP_E2E ? describe.skip : describe)("v6 route (/api/chat-v6)", () => {
266
+ it("should store facts from conversation", async () => {
267
+ // Send a message with a fact
268
+ await sendChatMessage(
269
+ "chat-v6",
270
+ [{ role: "user", content: "My name is Bob and I'm a data scientist" }],
271
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
272
+ );
273
+
274
+ // Wait for fact extraction
275
+ await new Promise((r) => setTimeout(r, 5000));
276
+
277
+ // Verify facts were stored
278
+ const facts = await cortex.facts.list({
279
+ memorySpaceId: testMemorySpaceId,
280
+ userId: testUserId,
281
+ includeSuperseded: false,
282
+ });
283
+
284
+ console.log(`[V6] Stored facts: ${facts.length}`);
285
+ facts.forEach((f) => console.log(` - ${f.fact}`));
286
+
287
+ expect(facts.length).toBeGreaterThan(0);
288
+ }, 60000);
289
+
290
+ it("should supersede facts through belief revision", async () => {
291
+ // First message: establish a preference
292
+ await sendChatMessage(
293
+ "chat-v6",
294
+ [{ role: "user", content: "I prefer tea over coffee" }],
295
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
296
+ );
297
+
298
+ await new Promise((r) => setTimeout(r, 5000));
299
+
300
+ // Second message: change the preference
301
+ await sendChatMessage(
302
+ "chat-v6",
303
+ [
304
+ { role: "user", content: "I prefer tea over coffee" },
305
+ { role: "assistant", content: "Got it, you prefer tea!" },
306
+ {
307
+ role: "user",
308
+ content: "Actually I've switched to coffee now, it helps me focus",
309
+ },
310
+ ],
311
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
312
+ );
313
+
314
+ await new Promise((r) => setTimeout(r, 5000));
315
+
316
+ // Check facts
317
+ const allFacts = await cortex.facts.list({
318
+ memorySpaceId: testMemorySpaceId,
319
+ userId: testUserId,
320
+ includeSuperseded: true,
321
+ });
322
+
323
+ const activeFacts = await cortex.facts.list({
324
+ memorySpaceId: testMemorySpaceId,
325
+ userId: testUserId,
326
+ includeSuperseded: false,
327
+ });
328
+
329
+ console.log(
330
+ `[V6] All facts: ${allFacts.length}, Active: ${activeFacts.length}`,
331
+ );
332
+ allFacts.forEach((f) => {
333
+ const status = f.supersededBy ? "SUPERSEDED" : "ACTIVE";
334
+ console.log(` [${status}] ${f.fact}`);
335
+ });
336
+
337
+ // Should have at least one fact about beverages
338
+ const beverageFacts = allFacts.filter(
339
+ (f) =>
340
+ f.fact.toLowerCase().includes("tea") ||
341
+ f.fact.toLowerCase().includes("coffee"),
342
+ );
343
+ expect(beverageFacts.length).toBeGreaterThan(0);
344
+ }, 90000);
345
+
346
+ it("should recall facts in subsequent conversations", async () => {
347
+ // First conversation: store a fact
348
+ await sendChatMessage(
349
+ "chat-v6",
350
+ [{ role: "user", content: "I live in San Francisco" }],
351
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
352
+ );
353
+
354
+ await new Promise((r) => setTimeout(r, 5000));
355
+
356
+ // Second conversation: ask about the fact
357
+ const conv2Result = await sendChatMessage(
358
+ "chat-v6",
359
+ [{ role: "user", content: "Where do I live?" }],
360
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
361
+ );
362
+
363
+ console.log(
364
+ `[V6] Recall response: ${conv2Result.response.slice(0, 200)}...`,
365
+ );
366
+
367
+ // Verify we got a response
368
+ expect(conv2Result.response.length).toBeGreaterThan(0);
369
+ }, 90000);
370
+ });
371
+
372
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
373
+ // Feature Parity Tests
374
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
375
+
376
+ (SKIP_E2E ? describe.skip : describe)("v5 vs v6 feature parity", () => {
377
+ it("both routes should store facts for the same message", async () => {
378
+ const v5UserId = generateTestId("user-v5");
379
+ const v6UserId = generateTestId("user-v6");
380
+ const sharedSpaceId = testMemorySpaceId;
381
+
382
+ // Send same message to both routes
383
+ const message = "I am a TypeScript developer with 5 years of experience";
384
+
385
+ await Promise.all([
386
+ sendChatMessage("chat", [{ role: "user", content: message }], {
387
+ userId: v5UserId,
388
+ memorySpaceId: sharedSpaceId,
389
+ }),
390
+ sendChatMessage("chat-v6", [{ role: "user", content: message }], {
391
+ userId: v6UserId,
392
+ memorySpaceId: sharedSpaceId,
393
+ }),
394
+ ]);
395
+
396
+ // Wait for fact extraction
397
+ await new Promise((r) => setTimeout(r, 7000));
398
+
399
+ // Check facts for both users
400
+ const [v5Facts, v6Facts] = await Promise.all([
401
+ cortex.facts.list({
402
+ memorySpaceId: sharedSpaceId,
403
+ userId: v5UserId,
404
+ includeSuperseded: false,
405
+ }),
406
+ cortex.facts.list({
407
+ memorySpaceId: sharedSpaceId,
408
+ userId: v6UserId,
409
+ includeSuperseded: false,
410
+ }),
411
+ ]);
412
+
413
+ console.log(`V5 facts: ${v5Facts.length}, V6 facts: ${v6Facts.length}`);
414
+ console.log(
415
+ "V5 facts:",
416
+ v5Facts.map((f) => f.fact),
417
+ );
418
+ console.log(
419
+ "V6 facts:",
420
+ v6Facts.map((f) => f.fact),
421
+ );
422
+
423
+ // CRITICAL: Both routes should store facts
424
+ expect(v5Facts.length).toBeGreaterThan(0);
425
+ expect(v6Facts.length).toBeGreaterThan(0);
426
+
427
+ // Fact counts should be similar (allow some variance due to LLM non-determinism)
428
+ const diff = Math.abs(v5Facts.length - v6Facts.length);
429
+ expect(diff).toBeLessThanOrEqual(2);
430
+ }, 90000);
431
+ });
432
+
433
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
434
+ // Conversation Lifecycle
435
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
436
+
437
+ (SKIP_E2E ? describe.skip : describe)("conversation lifecycle", () => {
438
+ it("should create, list, and delete conversations", async () => {
439
+ // Create a conversation via chat
440
+ const chatResult = await sendChatMessage(
441
+ "chat",
442
+ [{ role: "user", content: "Hello, this is a test conversation" }],
443
+ { userId: testUserId, memorySpaceId: testMemorySpaceId },
444
+ );
445
+
446
+ // Wait for conversation to be created
447
+ await new Promise((r) => setTimeout(r, 2000));
448
+
449
+ // List conversations
450
+ const listResponse = await fetch(
451
+ `${BASE_URL}/api/conversations?userId=${testUserId}&memorySpaceId=${testMemorySpaceId}`,
452
+ );
453
+ const listData = await listResponse.json();
454
+
455
+ console.log(
456
+ `Conversations: ${JSON.stringify(listData.conversations, null, 2)}`,
457
+ );
458
+ expect(listData.conversations).toBeDefined();
459
+ expect(listData.conversations.length).toBeGreaterThan(0);
460
+
461
+ // Delete conversation
462
+ const convId = listData.conversations[0].id;
463
+ const deleteResponse = await fetch(
464
+ `${BASE_URL}/api/conversations?conversationId=${convId}`,
465
+ { method: "DELETE" },
466
+ );
467
+ const deleteData = await deleteResponse.json();
468
+
469
+ expect(deleteData.success).toBe(true);
470
+
471
+ // Verify deletion
472
+ const listAfterDelete = await fetch(
473
+ `${BASE_URL}/api/conversations?userId=${testUserId}&memorySpaceId=${testMemorySpaceId}`,
474
+ );
475
+ const listAfterDeleteData = await listAfterDelete.json();
476
+
477
+ // Should have one less conversation
478
+ expect(listAfterDeleteData.conversations.length).toBeLessThan(
479
+ listData.conversations.length,
480
+ );
481
+ }, 60000);
482
+ });
483
+ });
@@ -48,42 +48,40 @@ export function createMockCortex() {
48
48
  const ns = mutableStore.get(namespace);
49
49
  if (!ns) return null;
50
50
  return ns.get(key) ?? null;
51
- }
51
+ },
52
52
  ),
53
53
  set: jest.fn(
54
54
  async (
55
55
  namespace: string,
56
56
  key: string,
57
- value: unknown
57
+ value: unknown,
58
58
  ): Promise<void> => {
59
59
  if (!mutableStore.has(namespace)) {
60
60
  mutableStore.set(namespace, new Map());
61
61
  }
62
62
  mutableStore.get(namespace)!.set(key, value);
63
- }
64
- ),
65
- delete: jest.fn(
66
- async (namespace: string, key: string): Promise<void> => {
67
- const ns = mutableStore.get(namespace);
68
- if (ns) ns.delete(key);
69
- }
63
+ },
70
64
  ),
65
+ delete: jest.fn(async (namespace: string, key: string): Promise<void> => {
66
+ const ns = mutableStore.get(namespace);
67
+ if (ns) ns.delete(key);
68
+ }),
71
69
  },
72
70
  users: {
73
71
  get: jest.fn(
74
72
  async (
75
- userId: string
73
+ userId: string,
76
74
  ): Promise<{
77
75
  userId: string;
78
76
  data: Record<string, unknown>;
79
77
  } | null> => {
80
78
  return usersStore.get(userId) ?? null;
81
- }
79
+ },
82
80
  ),
83
81
  update: jest.fn(
84
82
  async (
85
83
  userId: string,
86
- data: Record<string, unknown>
84
+ data: Record<string, unknown>,
87
85
  ): Promise<{ userId: string; data: Record<string, unknown> }> => {
88
86
  const existing = usersStore.get(userId);
89
87
  const user = {
@@ -92,7 +90,7 @@ export function createMockCortex() {
92
90
  };
93
91
  usersStore.set(userId, user);
94
92
  return user;
95
- }
93
+ },
96
94
  ),
97
95
  delete: jest.fn(async (userId: string): Promise<void> => {
98
96
  usersStore.delete(userId);
@@ -115,17 +113,14 @@ export function createMockCortex() {
115
113
  ) {
116
114
  return false;
117
115
  }
118
- if (
119
- params.userId &&
120
- conv.participants.userId !== params.userId
121
- ) {
116
+ if (params.userId && conv.participants.userId !== params.userId) {
122
117
  return false;
123
118
  }
124
119
  return true;
125
- }
120
+ },
126
121
  );
127
122
  return { conversations, hasMore: false };
128
- }
123
+ },
129
124
  ),
130
125
  create: jest.fn(
131
126
  async (params: {
@@ -148,31 +143,36 @@ export function createMockCortex() {
148
143
  };
149
144
  conversationsStore.set(params.conversationId, conversation);
150
145
  return conversation;
151
- }
146
+ },
147
+ ),
148
+ get: jest.fn(
149
+ async (
150
+ conversationId: string,
151
+ options?: { includeMessages?: boolean; messageLimit?: number },
152
+ ) => {
153
+ const conv = conversationsStore.get(conversationId);
154
+ if (!conv) return null;
155
+
156
+ // If includeMessages is requested, return with messages
157
+ if (options?.includeMessages) {
158
+ return {
159
+ ...conv,
160
+ messages: conv.messages || [],
161
+ };
162
+ }
163
+
164
+ // Otherwise return without messages
165
+ const { messages: _messages, ...convWithoutMessages } = conv;
166
+ return convWithoutMessages;
167
+ },
152
168
  ),
153
- get: jest.fn(async (conversationId: string, options?: { includeMessages?: boolean; messageLimit?: number }) => {
154
- const conv = conversationsStore.get(conversationId);
155
- if (!conv) return null;
156
-
157
- // If includeMessages is requested, return with messages
158
- if (options?.includeMessages) {
159
- return {
160
- ...conv,
161
- messages: conv.messages || [],
162
- };
163
- }
164
-
165
- // Otherwise return without messages
166
- const { messages: _messages, ...convWithoutMessages } = conv;
167
- return convWithoutMessages;
168
- }),
169
169
  delete: jest.fn(async (conversationId: string): Promise<void> => {
170
170
  conversationsStore.delete(conversationId);
171
171
  }),
172
172
  update: jest.fn(
173
173
  async (
174
174
  conversationId: string,
175
- updates: { metadata?: Record<string, unknown> }
175
+ updates: { metadata?: Record<string, unknown> },
176
176
  ) => {
177
177
  const conv = conversationsStore.get(conversationId);
178
178
  if (!conv) throw new Error("Conversation not found");
@@ -183,7 +183,7 @@ export function createMockCortex() {
183
183
  };
184
184
  conversationsStore.set(conversationId, updated);
185
185
  return updated;
186
- }
186
+ },
187
187
  ),
188
188
  },
189
189
  memory: {
@@ -215,7 +215,7 @@ export type MockCortex = ReturnType<typeof createMockCortex>;
215
215
  export const seedTestData = {
216
216
  user: (
217
217
  userId: string,
218
- data: Record<string, unknown> = {}
218
+ data: Record<string, unknown> = {},
219
219
  ): { userId: string; data: Record<string, unknown> } => {
220
220
  const user = { userId, data };
221
221
  usersStore.set(userId, user);
@@ -234,7 +234,7 @@ export const seedTestData = {
234
234
  userId?: string;
235
235
  title?: string;
236
236
  messages?: Array<{ role: string; content: string }>;
237
- } = {}
237
+ } = {},
238
238
  ) => {
239
239
  const now = Date.now();
240
240
  const messages = params.messages?.map((msg, i) => ({