@yamo/memory-mesh 3.0.0 → 3.0.2
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.
- package/README.md +9 -3
- package/bin/memory_mesh.js +95 -8
- package/lib/llm/client.d.ts +23 -48
- package/lib/llm/client.js +1 -0
- package/lib/llm/client.ts +298 -377
- package/lib/llm/index.js +1 -0
- package/lib/llm/index.ts +1 -2
- package/lib/memory/adapters/client.d.ts +22 -85
- package/lib/memory/adapters/client.js +1 -0
- package/lib/memory/adapters/client.ts +474 -633
- package/lib/memory/adapters/config.d.ts +82 -89
- package/lib/memory/adapters/config.js +1 -0
- package/lib/memory/adapters/config.ts +156 -225
- package/lib/memory/adapters/errors.d.ts +28 -20
- package/lib/memory/adapters/errors.js +1 -0
- package/lib/memory/adapters/errors.ts +83 -120
- package/lib/memory/context-manager.d.ts +15 -18
- package/lib/memory/context-manager.js +1 -0
- package/lib/memory/context-manager.ts +314 -401
- package/lib/memory/embeddings/factory.d.ts +18 -20
- package/lib/memory/embeddings/factory.js +1 -0
- package/lib/memory/embeddings/factory.ts +130 -173
- package/lib/memory/embeddings/index.js +1 -0
- package/lib/memory/embeddings/index.ts +1 -0
- package/lib/memory/embeddings/service.d.ts +36 -66
- package/lib/memory/embeddings/service.js +1 -0
- package/lib/memory/embeddings/service.ts +479 -616
- package/lib/memory/index.d.ts +2 -2
- package/lib/memory/index.js +1 -0
- package/lib/memory/index.ts +3 -13
- package/lib/memory/memory-mesh.d.ts +151 -93
- package/lib/memory/memory-mesh.js +1 -0
- package/lib/memory/memory-mesh.ts +1406 -1692
- package/lib/memory/memory-translator.d.ts +1 -6
- package/lib/memory/memory-translator.js +1 -0
- package/lib/memory/memory-translator.ts +96 -128
- package/lib/memory/schema.d.ts +29 -10
- package/lib/memory/schema.js +1 -0
- package/lib/memory/schema.ts +102 -185
- package/lib/memory/scorer.d.ts +3 -4
- package/lib/memory/scorer.js +1 -0
- package/lib/memory/scorer.ts +69 -86
- package/lib/memory/search/index.js +1 -0
- package/lib/memory/search/index.ts +1 -0
- package/lib/memory/search/keyword-search.d.ts +10 -26
- package/lib/memory/search/keyword-search.js +1 -0
- package/lib/memory/search/keyword-search.ts +123 -161
- package/lib/scrubber/config/defaults.d.ts +39 -46
- package/lib/scrubber/config/defaults.js +1 -0
- package/lib/scrubber/config/defaults.ts +50 -112
- package/lib/scrubber/errors/scrubber-error.d.ts +22 -0
- package/lib/scrubber/errors/scrubber-error.js +39 -0
- package/lib/scrubber/errors/scrubber-error.ts +44 -0
- package/lib/scrubber/index.d.ts +0 -1
- package/lib/scrubber/index.js +1 -0
- package/lib/scrubber/index.ts +1 -2
- package/lib/scrubber/scrubber.d.ts +14 -31
- package/lib/scrubber/scrubber.js +1 -0
- package/lib/scrubber/scrubber.ts +93 -152
- package/lib/scrubber/stages/chunker.d.ts +22 -10
- package/lib/scrubber/stages/chunker.js +86 -0
- package/lib/scrubber/stages/chunker.ts +104 -0
- package/lib/scrubber/stages/metadata-annotator.d.ts +14 -15
- package/lib/scrubber/stages/metadata-annotator.js +64 -0
- package/lib/scrubber/stages/metadata-annotator.ts +75 -0
- package/lib/scrubber/stages/normalizer.d.ts +13 -10
- package/lib/scrubber/stages/normalizer.js +51 -0
- package/lib/scrubber/stages/normalizer.ts +60 -0
- package/lib/scrubber/stages/semantic-filter.d.ts +13 -10
- package/lib/scrubber/stages/semantic-filter.js +51 -0
- package/lib/scrubber/stages/semantic-filter.ts +62 -0
- package/lib/scrubber/stages/structural-cleaner.d.ts +15 -10
- package/lib/scrubber/stages/structural-cleaner.js +73 -0
- package/lib/scrubber/stages/structural-cleaner.ts +83 -0
- package/lib/scrubber/stages/validator.d.ts +14 -15
- package/lib/scrubber/stages/validator.js +56 -0
- package/lib/scrubber/stages/validator.ts +67 -0
- package/lib/scrubber/telemetry.d.ts +20 -27
- package/lib/scrubber/telemetry.js +1 -0
- package/lib/scrubber/telemetry.ts +53 -90
- package/lib/scrubber/utils/hash.d.ts +14 -0
- package/lib/scrubber/utils/hash.js +37 -0
- package/lib/scrubber/utils/hash.ts +40 -0
- package/lib/scrubber/utils/html-parser.d.ts +14 -0
- package/lib/scrubber/utils/html-parser.js +38 -0
- package/lib/scrubber/utils/html-parser.ts +46 -0
- package/lib/scrubber/utils/pattern-matcher.d.ts +12 -0
- package/lib/scrubber/utils/pattern-matcher.js +54 -0
- package/lib/scrubber/utils/pattern-matcher.ts +64 -0
- package/lib/scrubber/utils/token-counter.d.ts +18 -0
- package/lib/scrubber/utils/token-counter.js +30 -0
- package/lib/scrubber/utils/token-counter.ts +32 -0
- package/lib/utils/logger.d.ts +1 -11
- package/lib/utils/logger.js +1 -0
- package/lib/utils/logger.ts +43 -63
- package/lib/utils/skill-metadata.d.ts +6 -14
- package/lib/utils/skill-metadata.js +1 -0
- package/lib/utils/skill-metadata.ts +89 -103
- package/lib/yamo/emitter.d.ts +8 -35
- package/lib/yamo/emitter.js +1 -0
- package/lib/yamo/emitter.ts +77 -155
- package/lib/yamo/index.d.ts +14 -0
- package/lib/yamo/index.js +14 -0
- package/lib/yamo/index.ts +16 -0
- package/lib/yamo/schema.d.ts +8 -10
- package/lib/yamo/schema.js +1 -0
- package/lib/yamo/schema.ts +82 -114
- package/package.json +5 -2
|
@@ -1,432 +1,345 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* MemoryContextManager - High-level memory management for YAMO
|
|
3
4
|
*/
|
|
4
|
-
|
|
5
5
|
import { MemoryMesh } from "./memory-mesh.js";
|
|
6
6
|
import { MemoryScorer } from "./scorer.js";
|
|
7
7
|
import { MemoryTranslator } from "./memory-translator.js";
|
|
8
8
|
import { createLogger } from "../utils/logger.js";
|
|
9
|
-
|
|
10
9
|
const logger = createLogger("context-manager");
|
|
11
|
-
|
|
12
|
-
export interface MemoryContextConfig {
|
|
13
|
-
mesh?: MemoryMesh;
|
|
14
|
-
autoInit?: boolean;
|
|
15
|
-
enableCache?: boolean;
|
|
16
|
-
recallLimit?: number;
|
|
17
|
-
minImportance?: number;
|
|
18
|
-
silent?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface CacheEntry {
|
|
22
|
-
result: any[];
|
|
23
|
-
timestamp: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
10
|
export class MemoryContextManager {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
36
|
-
#cleanupTimer: ReturnType<typeof setInterval> | null = null;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Create a new MemoryContextManager
|
|
40
|
-
*/
|
|
41
|
-
constructor(config: MemoryContextConfig = {}) {
|
|
42
|
-
this.#config = {
|
|
43
|
-
autoInit: true,
|
|
44
|
-
enableCache: true,
|
|
45
|
-
recallLimit: 5,
|
|
46
|
-
minImportance: 0.1,
|
|
47
|
-
silent: config.silent !== false,
|
|
48
|
-
...config,
|
|
11
|
+
#config;
|
|
12
|
+
#mesh;
|
|
13
|
+
#scorer;
|
|
14
|
+
#initialized = false;
|
|
15
|
+
#queryCache = new Map();
|
|
16
|
+
#cacheConfig = {
|
|
17
|
+
maxSize: 100,
|
|
18
|
+
ttlMs: 2 * 60 * 1000, // 2 minutes
|
|
49
19
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await this.#mesh.init();
|
|
69
|
-
this.#initialized = true;
|
|
70
|
-
} catch (error: any) {
|
|
71
|
-
this.#logWarn(`Initialization failed: ${error.message}`);
|
|
72
|
-
this.#initialized = false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Capture an interaction as memory
|
|
78
|
-
*/
|
|
79
|
-
async captureInteraction(
|
|
80
|
-
prompt: string,
|
|
81
|
-
response: string,
|
|
82
|
-
context: any = {},
|
|
83
|
-
): Promise<any> {
|
|
84
|
-
try {
|
|
85
|
-
if (this.#config.autoInit && !this.#initialized) {
|
|
86
|
-
await this.initialize();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!this.#initialized) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const content = this.#formatInteraction(prompt, response);
|
|
94
|
-
const metadata = this.#buildMetadata(context);
|
|
95
|
-
|
|
96
|
-
const isDuplicate = await this.#scorer.isDuplicate(content);
|
|
97
|
-
if (isDuplicate) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const importance = this.#scorer.calculateImportance(content, metadata);
|
|
102
|
-
|
|
103
|
-
if (importance < (this.#config.minImportance ?? 0.1)) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const memory = await this.#mesh.add(content, {
|
|
108
|
-
...metadata,
|
|
109
|
-
importanceScore: importance,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return memory;
|
|
113
|
-
} catch (error: any) {
|
|
114
|
-
this.#logWarn(`Failed to capture interaction: ${error.message}`);
|
|
115
|
-
return null;
|
|
20
|
+
#cleanupTimer = null;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new MemoryContextManager
|
|
23
|
+
*/
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.#config = {
|
|
26
|
+
autoInit: true,
|
|
27
|
+
enableCache: true,
|
|
28
|
+
recallLimit: 5,
|
|
29
|
+
minImportance: 0.1,
|
|
30
|
+
silent: config.silent !== false,
|
|
31
|
+
...config,
|
|
32
|
+
};
|
|
33
|
+
// Use provided mesh or create new instance
|
|
34
|
+
this.#mesh = config.mesh || new MemoryMesh();
|
|
35
|
+
this.#scorer = new MemoryScorer(this.#mesh);
|
|
36
|
+
// Start periodic cleanup timer (every 60 seconds)
|
|
37
|
+
this.#startCleanupTimer();
|
|
116
38
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
if (this.#config.autoInit && !this.#initialized) {
|
|
125
|
-
await this.initialize();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!this.#initialized) {
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const {
|
|
133
|
-
limit = this.#config.recallLimit,
|
|
134
|
-
useCache = this.#config.enableCache,
|
|
135
|
-
memoryType = null,
|
|
136
|
-
skillName = null,
|
|
137
|
-
} = options;
|
|
138
|
-
|
|
139
|
-
if (useCache) {
|
|
140
|
-
const cacheKey = this.#cacheKey(query, {
|
|
141
|
-
limit,
|
|
142
|
-
memoryType,
|
|
143
|
-
skillName,
|
|
144
|
-
});
|
|
145
|
-
const cached = this.#getCached(cacheKey);
|
|
146
|
-
if (cached) {
|
|
147
|
-
return cached;
|
|
39
|
+
/**
|
|
40
|
+
* Initialize the memory context manager
|
|
41
|
+
*/
|
|
42
|
+
async initialize() {
|
|
43
|
+
if (this.#initialized) {
|
|
44
|
+
return;
|
|
148
45
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
memoryType === "synthesized_skill" &&
|
|
158
|
-
typeof this.#mesh.searchSkills === "function"
|
|
159
|
-
) {
|
|
160
|
-
memories = await this.#mesh.searchSkills(query, { limit: fetchLimit });
|
|
161
|
-
} else {
|
|
162
|
-
memories = await this.#mesh.search(query, {
|
|
163
|
-
limit: fetchLimit,
|
|
164
|
-
filter,
|
|
165
|
-
useCache: false,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
memories = memories.map((memory) => {
|
|
170
|
-
const metadata =
|
|
171
|
-
typeof memory.metadata === "string"
|
|
172
|
-
? JSON.parse(memory.metadata)
|
|
173
|
-
: memory.metadata || {};
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
...memory,
|
|
177
|
-
importanceScore: memory.score || metadata.importanceScore || 0,
|
|
178
|
-
memoryType:
|
|
179
|
-
metadata.memoryType ||
|
|
180
|
-
(memoryType === "synthesized_skill"
|
|
181
|
-
? "synthesized_skill"
|
|
182
|
-
: "global"),
|
|
183
|
-
};
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Deduplicate by content — results are already sorted by score, so first occurrence wins
|
|
187
|
-
const seen = new Set<string>();
|
|
188
|
-
memories = memories.filter((memory) => {
|
|
189
|
-
if (seen.has(memory.content)) {
|
|
190
|
-
return false;
|
|
46
|
+
try {
|
|
47
|
+
await this.#mesh.init();
|
|
48
|
+
this.#initialized = true;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.#logWarn(`Initialization failed: ${error.message}`);
|
|
52
|
+
this.#initialized = false;
|
|
191
53
|
}
|
|
192
|
-
seen.add(memory.content);
|
|
193
|
-
return true;
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// Skill-scope filter: keep memories tagged with this skill OR untagged (global).
|
|
197
|
-
// Untagged memories are shared context; tagged memories are skill-private.
|
|
198
|
-
if (skillName) {
|
|
199
|
-
memories = memories.filter((memory) => {
|
|
200
|
-
const meta =
|
|
201
|
-
typeof memory.metadata === "string"
|
|
202
|
-
? JSON.parse(memory.metadata)
|
|
203
|
-
: memory.metadata || {};
|
|
204
|
-
return !meta.skill_name || meta.skill_name === skillName;
|
|
205
|
-
});
|
|
206
|
-
memories = memories.slice(0, limit);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (useCache) {
|
|
210
|
-
const cacheKey = this.#cacheKey(query, {
|
|
211
|
-
limit,
|
|
212
|
-
memoryType,
|
|
213
|
-
skillName,
|
|
214
|
-
});
|
|
215
|
-
this.#setCached(cacheKey, memories);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return memories;
|
|
219
|
-
} catch (error: any) {
|
|
220
|
-
this.#logWarn(`Failed to recall memories: ${error.message}`);
|
|
221
|
-
return [];
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Format memories for inclusion in prompt
|
|
227
|
-
*/
|
|
228
|
-
formatMemoriesForPrompt(memories: any[], options: any = {}): string {
|
|
229
|
-
try {
|
|
230
|
-
if (!memories || memories.length === 0) {
|
|
231
|
-
return "";
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return MemoryTranslator.toYAMOContext(memories, options);
|
|
235
|
-
} catch (error: any) {
|
|
236
|
-
this.#logWarn(`Failed to format memories: ${error.message}`);
|
|
237
|
-
return "";
|
|
238
54
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Capture an interaction as memory
|
|
57
|
+
*/
|
|
58
|
+
async captureInteraction(prompt, response, context = {}) {
|
|
59
|
+
try {
|
|
60
|
+
if (this.#config.autoInit && !this.#initialized) {
|
|
61
|
+
await this.initialize();
|
|
62
|
+
}
|
|
63
|
+
if (!this.#initialized) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const content = this.#formatInteraction(prompt, response);
|
|
67
|
+
const metadata = this.#buildMetadata(context);
|
|
68
|
+
const isDuplicate = await this.#scorer.isDuplicate(content);
|
|
69
|
+
if (isDuplicate) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const importance = this.#scorer.calculateImportance(content, metadata);
|
|
73
|
+
if (importance < (this.#config.minImportance ?? 0.1)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const memory = await this.#mesh.add(content, {
|
|
77
|
+
...metadata,
|
|
78
|
+
importanceScore: importance,
|
|
79
|
+
});
|
|
80
|
+
return memory;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.#logWarn(`Failed to capture interaction: ${error.message}`);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
244
86
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Recall relevant memories for a query
|
|
89
|
+
*/
|
|
90
|
+
async recallMemories(query, options = {}) {
|
|
91
|
+
try {
|
|
92
|
+
if (this.#config.autoInit && !this.#initialized) {
|
|
93
|
+
await this.initialize();
|
|
94
|
+
}
|
|
95
|
+
if (!this.#initialized) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const { limit = this.#config.recallLimit, useCache = this.#config.enableCache, memoryType = null, skillName = null, } = options;
|
|
99
|
+
if (useCache) {
|
|
100
|
+
const cacheKey = this.#cacheKey(query, {
|
|
101
|
+
limit,
|
|
102
|
+
memoryType,
|
|
103
|
+
skillName,
|
|
104
|
+
});
|
|
105
|
+
const cached = this.#getCached(cacheKey);
|
|
106
|
+
if (cached) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const filter = memoryType ? `memoryType == '${memoryType}'` : null;
|
|
111
|
+
// Fetch extra when skill-scoping — some results will be filtered out post-query
|
|
112
|
+
const fetchLimit = skillName ? limit * 2 : limit;
|
|
113
|
+
let memories = [];
|
|
114
|
+
if (memoryType === "synthesized_skill" &&
|
|
115
|
+
typeof this.#mesh.searchSkills === "function") {
|
|
116
|
+
memories = await this.#mesh.searchSkills(query, { limit: fetchLimit });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
memories = await this.#mesh.search(query, {
|
|
120
|
+
limit: fetchLimit,
|
|
121
|
+
filter,
|
|
122
|
+
useCache: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
memories = memories.map((memory) => {
|
|
126
|
+
const metadata = typeof memory.metadata === "string"
|
|
127
|
+
? JSON.parse(memory.metadata)
|
|
128
|
+
: memory.metadata || {};
|
|
129
|
+
return {
|
|
130
|
+
...memory,
|
|
131
|
+
importanceScore: memory.score || metadata.importanceScore || 0,
|
|
132
|
+
memoryType: metadata.memoryType ||
|
|
133
|
+
(memoryType === "synthesized_skill"
|
|
134
|
+
? "synthesized_skill"
|
|
135
|
+
: "global"),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
// Deduplicate by content — results are already sorted by score, so first occurrence wins
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
memories = memories.filter((memory) => {
|
|
141
|
+
if (seen.has(memory.content)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
seen.add(memory.content);
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
// Skill-scope filter: keep memories tagged with this skill OR untagged (global).
|
|
148
|
+
// Untagged memories are shared context; tagged memories are skill-private.
|
|
149
|
+
if (skillName) {
|
|
150
|
+
memories = memories.filter((memory) => {
|
|
151
|
+
const meta = typeof memory.metadata === "string"
|
|
152
|
+
? JSON.parse(memory.metadata)
|
|
153
|
+
: memory.metadata || {};
|
|
154
|
+
return !meta.skill_name || meta.skill_name === skillName;
|
|
155
|
+
});
|
|
156
|
+
memories = memories.slice(0, limit);
|
|
157
|
+
}
|
|
158
|
+
if (useCache) {
|
|
159
|
+
const cacheKey = this.#cacheKey(query, {
|
|
160
|
+
limit,
|
|
161
|
+
memoryType,
|
|
162
|
+
skillName,
|
|
163
|
+
});
|
|
164
|
+
this.#setCached(cacheKey, memories);
|
|
165
|
+
}
|
|
166
|
+
return memories;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.#logWarn(`Failed to recall memories: ${error.message}`);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
264
172
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Format memories for inclusion in prompt
|
|
175
|
+
*/
|
|
176
|
+
formatMemoriesForPrompt(memories, options = {}) {
|
|
177
|
+
try {
|
|
178
|
+
if (!memories || memories.length === 0) {
|
|
179
|
+
return "";
|
|
180
|
+
}
|
|
181
|
+
return MemoryTranslator.toYAMOContext(memories, options);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
this.#logWarn(`Failed to format memories: ${error.message}`);
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
268
187
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
188
|
+
#logWarn(message) {
|
|
189
|
+
if (!this.#config.silent || process.env.YAMO_DEBUG === "true") {
|
|
190
|
+
logger.warn(message);
|
|
191
|
+
}
|
|
272
192
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
193
|
+
#formatInteraction(prompt, response) {
|
|
194
|
+
const lines = [
|
|
195
|
+
`[USER] ${prompt}`,
|
|
196
|
+
`[ASSISTANT] ${response.substring(0, 500)}${response.length > 500 ? "..." : ""}`,
|
|
197
|
+
];
|
|
198
|
+
return lines.join("\n\n");
|
|
276
199
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
200
|
+
#buildMetadata(context) {
|
|
201
|
+
const metadata = {
|
|
202
|
+
interaction_type: context.interactionType || "llm_response",
|
|
203
|
+
created_at: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
if (context.toolsUsed?.length > 0) {
|
|
206
|
+
metadata.tools_used = context.toolsUsed;
|
|
207
|
+
}
|
|
208
|
+
if (context.filesInvolved?.length > 0) {
|
|
209
|
+
metadata.files_involved = context.filesInvolved;
|
|
210
|
+
}
|
|
211
|
+
if (context.tags?.length > 0) {
|
|
212
|
+
metadata.tags = context.tags;
|
|
213
|
+
}
|
|
214
|
+
if (context.skillName) {
|
|
215
|
+
metadata.skill_name = context.skillName;
|
|
216
|
+
}
|
|
217
|
+
if (context.sessionId) {
|
|
218
|
+
metadata.session_id = context.sessionId;
|
|
219
|
+
}
|
|
220
|
+
return metadata;
|
|
280
221
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
#cacheKey(query: string, options: any): string {
|
|
286
|
-
return `recall:${query}:${JSON.stringify(options)}`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get cached result if valid
|
|
291
|
-
* Race condition fix: Update timestamp atomically for LRU tracking
|
|
292
|
-
*/
|
|
293
|
-
#getCached(key: string): any {
|
|
294
|
-
const entry = this.#queryCache.get(key);
|
|
295
|
-
if (!entry) {
|
|
296
|
-
return null;
|
|
222
|
+
#cacheKey(query, options) {
|
|
223
|
+
return `recall:${query}:${JSON.stringify(options)}`;
|
|
297
224
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Get cached result if valid
|
|
227
|
+
* Race condition fix: Update timestamp atomically for LRU tracking
|
|
228
|
+
*/
|
|
229
|
+
#getCached(key) {
|
|
230
|
+
const entry = this.#queryCache.get(key);
|
|
231
|
+
if (!entry) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
// Check TTL before any mutation
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
if (now - entry.timestamp > this.#cacheConfig.ttlMs) {
|
|
237
|
+
this.#queryCache.delete(key);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
// Move to end (most recently used) - delete and re-add with updated timestamp
|
|
241
|
+
this.#queryCache.delete(key);
|
|
242
|
+
this.#queryCache.set(key, {
|
|
243
|
+
...entry,
|
|
244
|
+
timestamp: now, // Update timestamp for LRU tracking
|
|
245
|
+
});
|
|
246
|
+
return entry.result;
|
|
304
247
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
#setCached(key: string, result: any): void {
|
|
317
|
-
if (this.#queryCache.size >= this.#cacheConfig.maxSize) {
|
|
318
|
-
const firstKey = this.#queryCache.keys().next().value;
|
|
319
|
-
if (firstKey !== undefined) {
|
|
320
|
-
this.#queryCache.delete(firstKey);
|
|
321
|
-
}
|
|
248
|
+
#setCached(key, result) {
|
|
249
|
+
if (this.#queryCache.size >= this.#cacheConfig.maxSize) {
|
|
250
|
+
const firstKey = this.#queryCache.keys().next().value;
|
|
251
|
+
if (firstKey !== undefined) {
|
|
252
|
+
this.#queryCache.delete(firstKey);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this.#queryCache.set(key, {
|
|
256
|
+
result,
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
});
|
|
322
259
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
result,
|
|
326
|
-
timestamp: Date.now(),
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
clearCache(): void {
|
|
331
|
-
this.#queryCache.clear();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
getCacheStats(): any {
|
|
335
|
-
return {
|
|
336
|
-
size: this.#queryCache.size,
|
|
337
|
-
maxSize: this.#cacheConfig.maxSize,
|
|
338
|
-
ttlMs: this.#cacheConfig.ttlMs,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async healthCheck(): Promise<any> {
|
|
343
|
-
const health: any = {
|
|
344
|
-
status: "healthy",
|
|
345
|
-
timestamp: new Date().toISOString(),
|
|
346
|
-
initialized: this.#initialized,
|
|
347
|
-
checks: {},
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
health.checks.mesh = await this.#mesh.stats(); // brain.ts has stats()
|
|
352
|
-
if (health.checks.mesh.isConnected === false) {
|
|
353
|
-
health.status = "degraded";
|
|
354
|
-
}
|
|
355
|
-
} catch (error: any) {
|
|
356
|
-
health.checks.mesh = {
|
|
357
|
-
status: "error",
|
|
358
|
-
error: error.message,
|
|
359
|
-
};
|
|
360
|
-
health.status = "unhealthy";
|
|
260
|
+
clearCache() {
|
|
261
|
+
this.#queryCache.clear();
|
|
361
262
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
return health;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Start periodic cleanup timer to remove expired cache entries
|
|
374
|
-
* @private
|
|
375
|
-
*/
|
|
376
|
-
#startCleanupTimer(): void {
|
|
377
|
-
// Clear any existing timer
|
|
378
|
-
if (this.#cleanupTimer) {
|
|
379
|
-
clearInterval(this.#cleanupTimer);
|
|
263
|
+
getCacheStats() {
|
|
264
|
+
return {
|
|
265
|
+
size: this.#queryCache.size,
|
|
266
|
+
maxSize: this.#cacheConfig.maxSize,
|
|
267
|
+
ttlMs: this.#cacheConfig.ttlMs,
|
|
268
|
+
};
|
|
380
269
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
270
|
+
async healthCheck() {
|
|
271
|
+
const health = {
|
|
272
|
+
status: "healthy",
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
initialized: this.#initialized,
|
|
275
|
+
checks: {},
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
health.checks.mesh = await this.#mesh.stats(); // brain.ts has stats()
|
|
279
|
+
if (health.checks.mesh.isConnected === false) {
|
|
280
|
+
health.status = "degraded";
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
health.checks.mesh = {
|
|
285
|
+
status: "error",
|
|
286
|
+
error: error.message,
|
|
287
|
+
};
|
|
288
|
+
health.status = "unhealthy";
|
|
289
|
+
}
|
|
290
|
+
health.checks.cache = {
|
|
291
|
+
status: "up",
|
|
292
|
+
size: this.#queryCache.size,
|
|
293
|
+
maxSize: this.#cacheConfig.maxSize,
|
|
294
|
+
};
|
|
295
|
+
return health;
|
|
401
296
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Start periodic cleanup timer to remove expired cache entries
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
#startCleanupTimer() {
|
|
302
|
+
// Clear any existing timer
|
|
303
|
+
if (this.#cleanupTimer) {
|
|
304
|
+
clearInterval(this.#cleanupTimer);
|
|
305
|
+
}
|
|
306
|
+
// Run cleanup every 60 seconds
|
|
307
|
+
this.#cleanupTimer = setInterval(() => {
|
|
308
|
+
this.#cleanupExpired();
|
|
309
|
+
}, 60000);
|
|
406
310
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Clean up expired cache entries
|
|
313
|
+
* @private
|
|
314
|
+
*/
|
|
315
|
+
#cleanupExpired() {
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
const expiredKeys = [];
|
|
318
|
+
// Find expired entries
|
|
319
|
+
for (const [key, entry] of this.#queryCache.entries()) {
|
|
320
|
+
if (now - entry.timestamp > this.#cacheConfig.ttlMs) {
|
|
321
|
+
expiredKeys.push(key);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Remove expired entries
|
|
325
|
+
for (const key of expiredKeys) {
|
|
326
|
+
this.#queryCache.delete(key);
|
|
327
|
+
}
|
|
328
|
+
if (expiredKeys.length > 0 &&
|
|
329
|
+
(process.env.YAMO_DEBUG === "true" || !this.#config.silent)) {
|
|
330
|
+
logger.debug({ count: expiredKeys.length }, "Cleaned up expired cache entries");
|
|
331
|
+
}
|
|
416
332
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Dispose of resources (cleanup timer and cache)
|
|
335
|
+
* Call this when the MemoryContextManager is no longer needed
|
|
336
|
+
*/
|
|
337
|
+
dispose() {
|
|
338
|
+
if (this.#cleanupTimer) {
|
|
339
|
+
clearInterval(this.#cleanupTimer);
|
|
340
|
+
this.#cleanupTimer = null;
|
|
341
|
+
}
|
|
342
|
+
this.clearCache();
|
|
427
343
|
}
|
|
428
|
-
this.clearCache();
|
|
429
|
-
}
|
|
430
344
|
}
|
|
431
|
-
|
|
432
345
|
export default MemoryContextManager;
|