memories-lite 0.99.2 → 0.99.5

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.
@@ -33,7 +33,14 @@ export declare class MemoriesLite {
33
33
  capture(messages: string | Message[], userId: string, config: AddMemoryOptions): Promise<SearchResult>;
34
34
  get(memoryId: string, userId: string): Promise<MemoryItem | null>;
35
35
  retrieve(query: string, userId: string, config: SearchMemoryOptions): Promise<SearchResult>;
36
- update(memoryId: string, data: string, userId: string): Promise<{
36
+ /**
37
+ * Met à jour une mémoire existante
38
+ * @param memoryId - ID de la mémoire
39
+ * @param data - Nouveau contenu
40
+ * @param userId - ID utilisateur
41
+ * @param metadata - Métadonnées optionnelles (peut inclure applyMode pour changer le mode)
42
+ */
43
+ update(memoryId: string, data: string, userId: string, metadata?: Record<string, any>): Promise<{
37
44
  message: string;
38
45
  }>;
39
46
  delete(memoryId: string, userId: string): Promise<{
@@ -132,11 +132,14 @@ class MemoriesLite {
132
132
  const embedding = await this.embedder.embed(title);
133
133
  //
134
134
  // Préparer les métadonnées
135
+ // applyMode par défaut = MEM_SMART (recherche sémantique)
135
136
  const memoryType = metadata.type || 'discussion';
137
+ const applyMode = metadata.applyMode || types_1.DEFAULT_MEMORY_APPLY_MODE;
136
138
  const memoryMetadata = {
137
139
  ...metadata,
138
140
  title,
139
141
  type: memoryType,
142
+ applyMode,
140
143
  userId,
141
144
  };
142
145
  //
@@ -203,6 +206,7 @@ class MemoriesLite {
203
206
  id: memory.id,
204
207
  memory: memory.payload.data,
205
208
  type: memory.payload.type,
209
+ applyMode: memory.payload.applyMode || types_1.DEFAULT_MEMORY_APPLY_MODE,
206
210
  hash: memory.payload.hash,
207
211
  createdAt: memory.payload.createdAt,
208
212
  updatedAt: memory.payload.updatedAt,
@@ -252,12 +256,21 @@ class MemoriesLite {
252
256
  "data",
253
257
  "createdAt",
254
258
  "updatedAt",
259
+ "applyMode",
255
260
  ]);
256
- const results = memories.map((mem) => ({
261
+ //
262
+ // Filtrer uniquement les mémoires avec applyMode = MEM_SMART (recherche sémantique)
263
+ // Les MEM_ALWAYS et MEM_MANUAL ne sont pas retournées par retrieve()
264
+ const filteredMemories = memories.filter((mem) => {
265
+ const mode = mem.payload.applyMode || types_1.DEFAULT_MEMORY_APPLY_MODE;
266
+ return mode === 'MEM_SMART';
267
+ });
268
+ const results = filteredMemories.map((mem) => ({
257
269
  id: mem.id,
258
270
  memory: mem.payload.data,
259
271
  hash: mem.payload.hash,
260
272
  type: mem.payload.type,
273
+ applyMode: mem.payload.applyMode || types_1.DEFAULT_MEMORY_APPLY_MODE,
261
274
  createdAt: mem.payload.createdAt,
262
275
  updatedAt: mem.payload.updatedAt,
263
276
  score: mem.score,
@@ -271,10 +284,17 @@ class MemoriesLite {
271
284
  results,
272
285
  };
273
286
  }
274
- async update(memoryId, data, userId) {
287
+ /**
288
+ * Met à jour une mémoire existante
289
+ * @param memoryId - ID de la mémoire
290
+ * @param data - Nouveau contenu
291
+ * @param userId - ID utilisateur
292
+ * @param metadata - Métadonnées optionnelles (peut inclure applyMode pour changer le mode)
293
+ */
294
+ async update(memoryId, data, userId, metadata = {}) {
275
295
  // await this._captureEvent("update", { memory_id: memoryId });
276
296
  const embedding = await this.embedder.embed(data);
277
- await this.updateMemory(memoryId, data, { [data]: embedding }, {}, userId);
297
+ await this.updateMemory(memoryId, data, { [data]: embedding }, metadata, userId);
278
298
  return { message: "Memory updated successfully!" };
279
299
  }
280
300
  async delete(memoryId, userId) {
@@ -350,12 +370,14 @@ class MemoriesLite {
350
370
  "data",
351
371
  "createdAt",
352
372
  "updatedAt",
373
+ "applyMode",
353
374
  ]);
354
375
  const results = memories.map((mem) => ({
355
376
  id: mem.id,
356
377
  memory: mem.payload.data,
357
378
  hash: mem.payload.hash,
358
379
  type: mem.payload.type,
380
+ applyMode: mem.payload.applyMode || types_1.DEFAULT_MEMORY_APPLY_MODE,
359
381
  createdAt: mem.payload.createdAt,
360
382
  updatedAt: mem.payload.updatedAt,
361
383
  metadata: Object.entries(mem.payload)
@@ -388,20 +410,28 @@ class MemoriesLite {
388
410
  throw new Error(`Memory with ID ${memoryId} not found`);
389
411
  }
390
412
  const prevValue = existingMemory.payload.data;
413
+ const originalPayload = (existingMemory.payload ?? {});
391
414
  const embedding = existingEmbeddings[data] || (await this.embedder.embed(data));
415
+ //
416
+ // Preserve origin fields to avoid corrupting ownership/linkage (discussionId/userId/agentId).
417
+ // - title: keep original unless explicitly changed
418
+ // - applyMode: keep original unless explicitly changed
419
+ // - discussionId, userId, agentId/runId: always keep original
420
+ const title = (typeof metadata.title === "string" && metadata.title.trim())
421
+ ? metadata.title.trim()
422
+ : originalPayload.title;
423
+ //
424
+ // applyMode: si fourni dans metadata, on le prend, sinon on garde l'original ou le défaut
425
+ const applyMode = (metadata.applyMode && ['MEM_ALWAYS', 'MEM_SMART', 'MEM_MANUAL'].includes(metadata.applyMode))
426
+ ? metadata.applyMode
427
+ : (originalPayload.applyMode || types_1.DEFAULT_MEMORY_APPLY_MODE);
392
428
  const newMetadata = {
393
- ...metadata,
429
+ ...originalPayload,
394
430
  data,
395
431
  hash: (0, crypto_1.createHash)("md5").update(data).digest("hex"),
396
- type: existingMemory.payload.type,
397
- createdAt: existingMemory.payload.createdAt,
398
432
  updatedAt: new Date().toISOString(),
399
- ...(existingMemory.payload.agentId && {
400
- agentId: existingMemory.payload.agentId,
401
- }),
402
- ...(existingMemory.payload.runId && {
403
- runId: existingMemory.payload.runId,
404
- }),
433
+ title: title,
434
+ applyMode,
405
435
  };
406
436
  await vectorStore.update(memoryId, embedding, newMetadata);
407
437
  await this.db.addHistory(memoryId, prevValue, data, "UPDATE", newMetadata.createdAt, newMetadata.updatedAt);
@@ -77,10 +77,24 @@ export interface MemoryConfig {
77
77
  capturePrompt?: string;
78
78
  }
79
79
  export type MemoryType = 'assistant_preference' | 'discussion';
80
+ /**
81
+ * Mode d'application de la mémoire (inspiré de Cursor AI)
82
+ * Stocké dans metadata.applyMode avec préfixe MEM_* pour migration soft
83
+ *
84
+ * - MEM_ALWAYS: Toujours appliquer - la mémoire est toujours injectée dans le contexte
85
+ * - MEM_SMART: Appliquer intelligemment - utilisé pour la recherche sémantique (défaut)
86
+ * - MEM_MANUAL: Appliquer manuellement - la mémoire n'est jamais injectée automatiquement
87
+ */
88
+ export type MemoryApplyMode = 'MEM_ALWAYS' | 'MEM_SMART' | 'MEM_MANUAL';
89
+ /**
90
+ * Mode par défaut pour la capture
91
+ */
92
+ export declare const DEFAULT_MEMORY_APPLY_MODE: MemoryApplyMode;
80
93
  export interface MemoryItem {
81
94
  id: string;
82
95
  memory: string;
83
96
  type: MemoryType;
97
+ applyMode?: MemoryApplyMode;
84
98
  event?: "ADD" | "UPDATE" | "DELETE" | "NONE";
85
99
  hash?: string;
86
100
  createdAt?: string;
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MemoryConfigSchema = void 0;
3
+ exports.MemoryConfigSchema = exports.DEFAULT_MEMORY_APPLY_MODE = void 0;
4
4
  const zod_1 = require("zod");
5
+ /**
6
+ * Mode par défaut pour la capture
7
+ */
8
+ exports.DEFAULT_MEMORY_APPLY_MODE = 'MEM_SMART';
5
9
  exports.MemoryConfigSchema = zod_1.z.object({
6
10
  version: zod_1.z.string().optional(),
7
11
  embedder: zod_1.z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memories-lite",
3
- "version": "0.99.2",
3
+ "version": "0.99.5",
4
4
  "description": "A lightweight memory system for LLM agents",
5
5
  "author": "olivier@evaletolab.ch",
6
6
  "main": "dist/index.js",
@@ -5,6 +5,8 @@ import {
5
5
  MemoryConfigSchema,
6
6
  MemoryItem,
7
7
  MemoryType,
8
+ MemoryApplyMode,
9
+ DEFAULT_MEMORY_APPLY_MODE,
8
10
  Message,
9
11
  SearchFilters,
10
12
  SearchResult,
@@ -208,11 +210,14 @@ export class MemoriesLite {
208
210
 
209
211
  //
210
212
  // Préparer les métadonnées
213
+ // applyMode par défaut = MEM_SMART (recherche sémantique)
211
214
  const memoryType: MemoryType = metadata.type || 'discussion';
215
+ const applyMode: MemoryApplyMode = metadata.applyMode || DEFAULT_MEMORY_APPLY_MODE;
212
216
  const memoryMetadata = {
213
217
  ...metadata,
214
218
  title,
215
219
  type: memoryType,
220
+ applyMode,
216
221
  userId,
217
222
  };
218
223
 
@@ -228,7 +233,7 @@ export class MemoriesLite {
228
233
  console.log(`-- 🧠 Memory created: "${title}" (${memoryType})`);
229
234
 
230
235
  return [{
231
- id: memoryId,
236
+ id: memoryId,
232
237
  memory: summary,
233
238
  type: memoryType,
234
239
  metadata: { title, event: "ADD" },
@@ -292,7 +297,7 @@ export class MemoriesLite {
292
297
  userId,
293
298
  filters,
294
299
  capturePrompt,
295
- );
300
+ );
296
301
 
297
302
  return {
298
303
  results: vectorStoreResult,
@@ -315,6 +320,7 @@ export class MemoriesLite {
315
320
  id: memory.id,
316
321
  memory: memory.payload.data,
317
322
  type: memory.payload.type,
323
+ applyMode: memory.payload.applyMode || DEFAULT_MEMORY_APPLY_MODE,
318
324
  hash: memory.payload.hash,
319
325
  createdAt: memory.payload.createdAt,
320
326
  updatedAt: memory.payload.updatedAt,
@@ -382,12 +388,23 @@ export class MemoriesLite {
382
388
  "data",
383
389
  "createdAt",
384
390
  "updatedAt",
391
+ "applyMode",
385
392
  ]);
386
- const results = memories.map((mem) => ({
393
+
394
+ //
395
+ // Filtrer uniquement les mémoires avec applyMode = MEM_SMART (recherche sémantique)
396
+ // Les MEM_ALWAYS et MEM_MANUAL ne sont pas retournées par retrieve()
397
+ const filteredMemories = memories.filter((mem) => {
398
+ const mode = mem.payload.applyMode || DEFAULT_MEMORY_APPLY_MODE;
399
+ return mode === 'MEM_SMART';
400
+ });
401
+
402
+ const results = filteredMemories.map((mem) => ({
387
403
  id: mem.id,
388
404
  memory: mem.payload.data,
389
405
  hash: mem.payload.hash,
390
406
  type: mem.payload.type,
407
+ applyMode: mem.payload.applyMode || DEFAULT_MEMORY_APPLY_MODE,
391
408
  createdAt: mem.payload.createdAt,
392
409
  updatedAt: mem.payload.updatedAt,
393
410
  score: mem.score,
@@ -403,10 +420,22 @@ export class MemoriesLite {
403
420
  };
404
421
  }
405
422
 
406
- async update(memoryId: string, data: string, userId: string): Promise<{ message: string }> {
423
+ /**
424
+ * Met à jour une mémoire existante
425
+ * @param memoryId - ID de la mémoire
426
+ * @param data - Nouveau contenu
427
+ * @param userId - ID utilisateur
428
+ * @param metadata - Métadonnées optionnelles (peut inclure applyMode pour changer le mode)
429
+ */
430
+ async update(
431
+ memoryId: string,
432
+ data: string,
433
+ userId: string,
434
+ metadata: Record<string, any> = {},
435
+ ): Promise<{ message: string }> {
407
436
  // await this._captureEvent("update", { memory_id: memoryId });
408
437
  const embedding = await this.embedder.embed(data);
409
- await this.updateMemory(memoryId, data, { [data]: embedding }, {}, userId);
438
+ await this.updateMemory(memoryId, data, { [data]: embedding }, metadata, userId);
410
439
  return { message: "Memory updated successfully!" };
411
440
  }
412
441
 
@@ -501,12 +530,14 @@ export class MemoriesLite {
501
530
  "data",
502
531
  "createdAt",
503
532
  "updatedAt",
533
+ "applyMode",
504
534
  ]);
505
535
  const results = memories.map((mem) => ({
506
536
  id: mem.id,
507
537
  memory: mem.payload.data,
508
538
  hash: mem.payload.hash,
509
539
  type: mem.payload.type,
540
+ applyMode: mem.payload.applyMode || DEFAULT_MEMORY_APPLY_MODE,
510
541
  createdAt: mem.payload.createdAt,
511
542
  updatedAt: mem.payload.updatedAt,
512
543
  metadata: Object.entries(mem.payload)
@@ -564,22 +595,34 @@ export class MemoriesLite {
564
595
  }
565
596
 
566
597
  const prevValue = existingMemory.payload.data;
598
+ const originalPayload: Record<string, any> = (existingMemory.payload ?? {}) as Record<string, any>;
567
599
  const embedding =
568
600
  existingEmbeddings[data] || (await this.embedder.embed(data));
569
601
 
570
- const newMetadata = {
571
- ...metadata,
602
+ //
603
+ // Preserve origin fields to avoid corrupting ownership/linkage (discussionId/userId/agentId).
604
+ // - title: keep original unless explicitly changed
605
+ // - applyMode: keep original unless explicitly changed
606
+ // - discussionId, userId, agentId/runId: always keep original
607
+ const title =
608
+ (typeof metadata.title === "string" && metadata.title.trim())
609
+ ? metadata.title.trim()
610
+ : originalPayload.title;
611
+
612
+ //
613
+ // applyMode: si fourni dans metadata, on le prend, sinon on garde l'original ou le défaut
614
+ const applyMode: MemoryApplyMode =
615
+ (metadata.applyMode && ['MEM_ALWAYS', 'MEM_SMART', 'MEM_MANUAL'].includes(metadata.applyMode))
616
+ ? metadata.applyMode
617
+ : (originalPayload.applyMode || DEFAULT_MEMORY_APPLY_MODE);
618
+
619
+ const newMetadata: Record<string, any> = {
620
+ ...originalPayload,
572
621
  data,
573
622
  hash: createHash("md5").update(data).digest("hex"),
574
- type: existingMemory.payload.type,
575
- createdAt: existingMemory.payload.createdAt,
576
623
  updatedAt: new Date().toISOString(),
577
- ...(existingMemory.payload.agentId && {
578
- agentId: existingMemory.payload.agentId,
579
- }),
580
- ...(existingMemory.payload.runId && {
581
- runId: existingMemory.payload.runId,
582
- }),
624
+ title: title,
625
+ applyMode,
583
626
  };
584
627
 
585
628
  await vectorStore.update(memoryId, embedding, newMetadata);
@@ -88,10 +88,26 @@ export interface MemoryConfig {
88
88
 
89
89
  export type MemoryType = 'assistant_preference' | 'discussion';
90
90
 
91
+ /**
92
+ * Mode d'application de la mémoire (inspiré de Cursor AI)
93
+ * Stocké dans metadata.applyMode avec préfixe MEM_* pour migration soft
94
+ *
95
+ * - MEM_ALWAYS: Toujours appliquer - la mémoire est toujours injectée dans le contexte
96
+ * - MEM_SMART: Appliquer intelligemment - utilisé pour la recherche sémantique (défaut)
97
+ * - MEM_MANUAL: Appliquer manuellement - la mémoire n'est jamais injectée automatiquement
98
+ */
99
+ export type MemoryApplyMode = 'MEM_ALWAYS' | 'MEM_SMART' | 'MEM_MANUAL';
100
+
101
+ /**
102
+ * Mode par défaut pour la capture
103
+ */
104
+ export const DEFAULT_MEMORY_APPLY_MODE: MemoryApplyMode = 'MEM_SMART';
105
+
91
106
  export interface MemoryItem {
92
107
  id: string;
93
108
  memory: string;
94
109
  type: MemoryType;
110
+ applyMode?: MemoryApplyMode;
95
111
  event?: "ADD" | "UPDATE" | "DELETE" | "NONE";
96
112
  hash?: string;
97
113
  createdAt?: string;
@@ -132,35 +132,35 @@ describe('LiteVectorStore Private Methods', () => {
132
132
  //
133
133
  // discussion with Infinity halfLife → old memories have same score as new
134
134
  const payload: MemoryPayload = { memoryId: 'mem-d2', userId: userId, type: 'discussion', createdAt: veryOldDate };
135
- const cosineScore = 0.9;
135
+ const cosineScore = 0.9;
136
136
  const expectedRecency = 1.0; // Infinity halfLife → recency = 1 even for old dates
137
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
137
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
138
138
  expect(hybridScore).toBe(1); // Score constant = 1
139
139
  });
140
140
 
141
141
  it('should handle assistant_preference with no decay (Infinity half-life)', () => {
142
- const payload: MemoryPayload = { memoryId: 'mem-a1', userId: userId, type: 'assistant_preference', createdAt: veryOldDate };
143
- const cosineScore = 0.7;
144
- const expectedRecency = 1.0;
145
- const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
146
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
147
- expect(hybridScore).toBeCloseTo(expectedScore, 5);
142
+ const payload: MemoryPayload = { memoryId: 'mem-a1', userId: userId, type: 'assistant_preference', createdAt: veryOldDate };
143
+ const cosineScore = 0.7;
144
+ const expectedRecency = 1.0;
145
+ const expectedScore = scoring.assistant_preference.alpha * cosineScore + scoring.assistant_preference.beta * expectedRecency + scoring.assistant_preference.gamma;
146
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
147
+ expect(hybridScore).toBeCloseTo(expectedScore, 5);
148
148
  });
149
149
 
150
150
  it('should use default scoring if type is missing', () => {
151
151
  const payload: MemoryPayload = { memoryId: 'mem-def1', userId: userId, createdAt: sevenDaysAgo }; // No type specified
152
- const cosineScore = 0.8;
153
- const expectedRecency = (store as any).calculateRecencyScore(sevenDaysAgo, scoring.default.halfLifeDays);
154
- const expectedScore = scoring.default.alpha * cosineScore + scoring.default.beta * expectedRecency + scoring.default.gamma;
155
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
156
- expect(hybridScore).toBeCloseTo(expectedScore, 5);
152
+ const cosineScore = 0.8;
153
+ const expectedRecency = (store as any).calculateRecencyScore(sevenDaysAgo, scoring.default.halfLifeDays);
154
+ const expectedScore = scoring.default.alpha * cosineScore + scoring.default.beta * expectedRecency + scoring.default.gamma;
155
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
156
+ expect(hybridScore).toBeCloseTo(expectedScore, 5);
157
157
  });
158
158
 
159
159
  it('should return score >= 0 even with negative cosine similarity', () => {
160
160
  const payload: MemoryPayload = { memoryId: 'mem-neg1', userId: userId, type: 'assistant_preference', createdAt: twoDaysAgo };
161
- const cosineScore = -0.5;
162
- const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
163
- expect(hybridScore).toBeGreaterThanOrEqual(0);
161
+ const cosineScore = -0.5;
162
+ const hybridScore = (store as any).calculateHybridScore(cosineScore, payload);
163
+ expect(hybridScore).toBeGreaterThanOrEqual(0);
164
164
  });
165
165
 
166
166
  });
@@ -0,0 +1,228 @@
1
+ /// <reference types="jest" />
2
+ import { MemoriesLite, DEFAULT_MEMORY_APPLY_MODE, MemoryApplyMode } from "../src";
3
+ import { SearchResult } from "../src/types";
4
+ import dotenv from "dotenv";
5
+ import { createTestMemory } from "./init.mem";
6
+
7
+ dotenv.config();
8
+
9
+ jest.setTimeout(30000);
10
+
11
+ /**
12
+ * Tests pour le MemoryApplyMode (type d'application de mémoire)
13
+ * - MEM_ALWAYS: Toujours appliquer
14
+ * - MEM_SMART: Appliquer intelligemment (défaut, recherche sémantique)
15
+ * - MEM_MANUAL: Appliquer manuellement
16
+ */
17
+ describe("Memory ApplyMode", () => {
18
+ let memory: MemoriesLite;
19
+ let userId: string;
20
+
21
+ beforeEach(async () => {
22
+ ({ memory, userId } = createTestMemory({}));
23
+ await memory.reset(userId);
24
+ });
25
+
26
+ afterEach(async () => {
27
+ await memory.reset(userId);
28
+ });
29
+
30
+ describe("Default ApplyMode", () => {
31
+ it("should set applyMode to MEM_SMART by default on capture", async () => {
32
+ //
33
+ // Capture une discussion avec le mode par défaut
34
+ const messages = [
35
+ { role: "user", content: "Bonjour, comment gérer les congés ?" },
36
+ { role: "assistant", content: "Pour gérer les congés, vous devez..." }
37
+ ];
38
+
39
+ const result = await memory.capture(messages, userId, {
40
+ metadata: { type: "discussion" }
41
+ });
42
+
43
+ expect(result.results.length).toBeGreaterThan(0);
44
+ const createdMemory = result.results[0];
45
+
46
+ //
47
+ // Vérifier que le mode par défaut est MEM_SMART
48
+ expect(createdMemory.metadata?.applyMode || DEFAULT_MEMORY_APPLY_MODE).toBe("MEM_SMART");
49
+ });
50
+
51
+ it("should return DEFAULT_MEMORY_APPLY_MODE as MEM_SMART", () => {
52
+ expect(DEFAULT_MEMORY_APPLY_MODE).toBe("MEM_SMART");
53
+ });
54
+ });
55
+
56
+ describe("ApplyMode Update", () => {
57
+ it("should update applyMode when specified in metadata", async () => {
58
+ //
59
+ // Créer une mémoire avec le mode par défaut
60
+ const messages = [
61
+ { role: "user", content: "Comment faire une facture ?" },
62
+ { role: "assistant", content: "Pour créer une facture..." }
63
+ ];
64
+
65
+ const result = await memory.capture(messages, userId, {
66
+ metadata: { type: "discussion" }
67
+ });
68
+
69
+ expect(result.results.length).toBeGreaterThan(0);
70
+ const memoryId = result.results[0].id;
71
+
72
+ //
73
+ // Mettre à jour avec un nouveau mode
74
+ await memory.update(
75
+ memoryId,
76
+ "Procédure de facturation mise à jour",
77
+ userId,
78
+ { applyMode: "MEM_ALWAYS" as MemoryApplyMode }
79
+ );
80
+
81
+ //
82
+ // Vérifier que le mode a été mis à jour
83
+ const updatedMemory = await memory.get(memoryId, userId);
84
+ expect(updatedMemory).not.toBeNull();
85
+ expect(updatedMemory?.applyMode).toBe("MEM_ALWAYS");
86
+ });
87
+
88
+ it("should preserve applyMode when not specified in update metadata", async () => {
89
+ //
90
+ // Créer une mémoire avec un mode spécifique
91
+ const messages = [
92
+ { role: "user", content: "Règles de sécurité" },
93
+ { role: "assistant", content: "Les règles de sécurité sont..." }
94
+ ];
95
+
96
+ const result = await memory.capture(messages, userId, {
97
+ metadata: { type: "discussion", applyMode: "MEM_MANUAL" }
98
+ });
99
+
100
+ expect(result.results.length).toBeGreaterThan(0);
101
+ const memoryId = result.results[0].id;
102
+
103
+ //
104
+ // Mettre à jour le contenu sans changer le mode
105
+ await memory.update(
106
+ memoryId,
107
+ "Règles de sécurité mises à jour",
108
+ userId,
109
+ {} // Pas de applyMode dans les metadata
110
+ );
111
+
112
+ //
113
+ // Vérifier que le mode est préservé
114
+ const updatedMemory = await memory.get(memoryId, userId);
115
+ expect(updatedMemory).not.toBeNull();
116
+ expect(updatedMemory?.applyMode).toBe("MEM_MANUAL");
117
+ });
118
+
119
+ it("should change applyMode from MEM_SMART to MEM_MANUAL", async () => {
120
+ //
121
+ // Créer une mémoire avec le mode par défaut
122
+ const messages = [
123
+ { role: "user", content: "Comment utiliser l'API ?" },
124
+ { role: "assistant", content: "L'API s'utilise de la manière suivante..." }
125
+ ];
126
+
127
+ const result = await memory.capture(messages, userId, {
128
+ metadata: { type: "discussion" }
129
+ });
130
+
131
+ const memoryId = result.results[0].id;
132
+
133
+ //
134
+ // Changer de MEM_SMART vers MEM_MANUAL
135
+ await memory.update(
136
+ memoryId,
137
+ "Documentation API mise à jour",
138
+ userId,
139
+ { applyMode: "MEM_MANUAL" as MemoryApplyMode }
140
+ );
141
+
142
+ const updatedMemory = await memory.get(memoryId, userId);
143
+ expect(updatedMemory?.applyMode).toBe("MEM_MANUAL");
144
+ });
145
+ });
146
+
147
+ describe("Retrieve Filtering by ApplyMode", () => {
148
+ it("should only return MEM_SMART memories in retrieve()", async () => {
149
+ //
150
+ // Créer 3 mémoires avec différents modes
151
+ const messagesBase = [
152
+ { role: "user", content: "Question sur les procédures" },
153
+ { role: "assistant", content: "Les procédures sont..." }
154
+ ];
155
+
156
+ //
157
+ // Mémoire 1: MEM_SMART (par défaut)
158
+ await memory.capture(messagesBase, userId, {
159
+ metadata: { type: "discussion", customTag: "smart" }
160
+ });
161
+
162
+ //
163
+ // Mémoire 2: MEM_ALWAYS
164
+ await memory.capture([
165
+ { role: "user", content: "Règle toujours appliquée" },
166
+ { role: "assistant", content: "Cette règle est toujours appliquée..." }
167
+ ], userId, {
168
+ metadata: { type: "discussion", applyMode: "MEM_ALWAYS", customTag: "always" }
169
+ });
170
+
171
+ //
172
+ // Mémoire 3: MEM_MANUAL
173
+ await memory.capture([
174
+ { role: "user", content: "Note manuelle" },
175
+ { role: "assistant", content: "Ceci est une note manuelle..." }
176
+ ], userId, {
177
+ metadata: { type: "discussion", applyMode: "MEM_MANUAL", customTag: "manual" }
178
+ });
179
+
180
+ //
181
+ // Vérifier que getAll retourne les 3 mémoires
182
+ const allMemories = await memory.getAll(userId, { type: "discussion" });
183
+ expect(allMemories.results.length).toBe(3);
184
+
185
+ //
186
+ // Vérifier que retrieve() ne retourne que MEM_SMART
187
+ const searchResult = await memory.retrieve("procédures", userId, { limit: 10 });
188
+
189
+ //
190
+ // Seule la mémoire MEM_SMART devrait être retournée
191
+ const smartMemories = searchResult.results.filter(m => m.applyMode === "MEM_SMART");
192
+ const nonSmartMemories = searchResult.results.filter(m => m.applyMode !== "MEM_SMART");
193
+
194
+ expect(smartMemories.length).toBeGreaterThanOrEqual(0);
195
+ expect(nonSmartMemories.length).toBe(0);
196
+ });
197
+ });
198
+
199
+ describe("GetAll returns all ApplyModes", () => {
200
+ it("should return memories of all applyModes in getAll()", async () => {
201
+ //
202
+ // Créer des mémoires avec différents modes
203
+ await memory.capture([
204
+ { role: "user", content: "Mémoire intelligente" },
205
+ { role: "assistant", content: "Réponse intelligente" }
206
+ ], userId, {
207
+ metadata: { type: "discussion" } // MEM_SMART par défaut
208
+ });
209
+
210
+ await memory.capture([
211
+ { role: "user", content: "Mémoire toujours" },
212
+ { role: "assistant", content: "Réponse toujours" }
213
+ ], userId, {
214
+ metadata: { type: "discussion", applyMode: "MEM_ALWAYS" }
215
+ });
216
+
217
+ //
218
+ // getAll doit retourner les deux
219
+ const allMemories = await memory.getAll(userId, { type: "discussion" });
220
+ expect(allMemories.results.length).toBe(2);
221
+
222
+ const modes = allMemories.results.map(m => m.applyMode);
223
+ expect(modes).toContain("MEM_SMART");
224
+ expect(modes).toContain("MEM_ALWAYS");
225
+ });
226
+ });
227
+ });
228
+