@yamo/memory-mesh 2.3.1 → 3.0.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 (102) hide show
  1. package/bin/memory_mesh.js +1 -1
  2. package/lib/llm/client.d.ts +111 -0
  3. package/lib/llm/client.js +299 -357
  4. package/lib/llm/client.ts +413 -0
  5. package/lib/llm/index.d.ts +17 -0
  6. package/lib/llm/index.js +15 -8
  7. package/lib/llm/index.ts +19 -0
  8. package/lib/memory/adapters/client.d.ts +183 -0
  9. package/lib/memory/adapters/client.js +518 -0
  10. package/lib/memory/adapters/client.ts +678 -0
  11. package/lib/memory/adapters/config.d.ts +137 -0
  12. package/lib/memory/adapters/config.js +189 -0
  13. package/lib/memory/adapters/config.ts +259 -0
  14. package/lib/memory/adapters/errors.d.ts +76 -0
  15. package/lib/memory/adapters/errors.js +128 -0
  16. package/lib/memory/adapters/errors.ts +166 -0
  17. package/lib/memory/context-manager.d.ts +44 -0
  18. package/lib/memory/context-manager.js +344 -0
  19. package/lib/memory/context-manager.ts +432 -0
  20. package/lib/memory/embeddings/factory.d.ts +59 -0
  21. package/lib/memory/embeddings/factory.js +148 -0
  22. package/lib/{embeddings/factory.js → memory/embeddings/factory.ts} +69 -28
  23. package/lib/memory/embeddings/index.d.ts +2 -0
  24. package/lib/memory/embeddings/index.js +2 -0
  25. package/lib/memory/embeddings/index.ts +2 -0
  26. package/lib/memory/embeddings/service.d.ts +164 -0
  27. package/lib/memory/embeddings/service.js +515 -0
  28. package/lib/{embeddings/service.js → memory/embeddings/service.ts} +223 -156
  29. package/lib/memory/index.d.ts +9 -0
  30. package/lib/memory/index.js +9 -1
  31. package/lib/memory/index.ts +20 -0
  32. package/lib/memory/memory-mesh.d.ts +274 -0
  33. package/lib/memory/memory-mesh.js +1445 -1189
  34. package/lib/memory/memory-mesh.ts +1803 -0
  35. package/lib/memory/memory-translator.d.ts +19 -0
  36. package/lib/memory/memory-translator.js +125 -0
  37. package/lib/memory/memory-translator.ts +158 -0
  38. package/lib/memory/schema.d.ts +111 -0
  39. package/lib/memory/schema.js +183 -0
  40. package/lib/memory/schema.ts +267 -0
  41. package/lib/memory/scorer.d.ts +26 -0
  42. package/lib/memory/scorer.js +77 -0
  43. package/lib/memory/scorer.ts +95 -0
  44. package/lib/memory/search/index.d.ts +1 -0
  45. package/lib/memory/search/index.js +1 -0
  46. package/lib/memory/search/index.ts +1 -0
  47. package/lib/memory/search/keyword-search.d.ts +62 -0
  48. package/lib/memory/search/keyword-search.js +135 -0
  49. package/lib/{search/keyword-search.js → memory/search/keyword-search.ts} +66 -36
  50. package/lib/scrubber/config/defaults.d.ts +53 -0
  51. package/lib/scrubber/config/defaults.js +49 -57
  52. package/lib/scrubber/config/defaults.ts +117 -0
  53. package/lib/scrubber/index.d.ts +6 -0
  54. package/lib/scrubber/index.js +3 -23
  55. package/lib/scrubber/index.ts +7 -0
  56. package/lib/scrubber/scrubber.d.ts +61 -0
  57. package/lib/scrubber/scrubber.js +99 -121
  58. package/lib/scrubber/scrubber.ts +168 -0
  59. package/lib/scrubber/stages/chunker.d.ts +13 -0
  60. package/lib/scrubber/stages/metadata-annotator.d.ts +18 -0
  61. package/lib/scrubber/stages/normalizer.d.ts +13 -0
  62. package/lib/scrubber/stages/semantic-filter.d.ts +13 -0
  63. package/lib/scrubber/stages/structural-cleaner.d.ts +13 -0
  64. package/lib/scrubber/stages/validator.d.ts +18 -0
  65. package/lib/scrubber/telemetry.d.ts +36 -0
  66. package/lib/scrubber/telemetry.js +53 -58
  67. package/lib/scrubber/telemetry.ts +99 -0
  68. package/lib/utils/logger.d.ts +29 -0
  69. package/lib/utils/logger.js +64 -0
  70. package/lib/utils/logger.ts +85 -0
  71. package/lib/utils/skill-metadata.d.ts +32 -0
  72. package/lib/utils/skill-metadata.js +132 -0
  73. package/lib/utils/skill-metadata.ts +147 -0
  74. package/lib/yamo/emitter.d.ts +73 -0
  75. package/lib/yamo/emitter.js +78 -143
  76. package/lib/yamo/emitter.ts +249 -0
  77. package/lib/yamo/schema.d.ts +58 -0
  78. package/lib/yamo/schema.js +81 -108
  79. package/lib/yamo/schema.ts +165 -0
  80. package/package.json +11 -8
  81. package/index.d.ts +0 -111
  82. package/lib/embeddings/index.js +0 -2
  83. package/lib/index.js +0 -6
  84. package/lib/lancedb/client.js +0 -633
  85. package/lib/lancedb/config.js +0 -215
  86. package/lib/lancedb/errors.js +0 -144
  87. package/lib/lancedb/index.js +0 -4
  88. package/lib/lancedb/schema.js +0 -197
  89. package/lib/scrubber/errors/scrubber-error.js +0 -43
  90. package/lib/scrubber/stages/chunker.js +0 -103
  91. package/lib/scrubber/stages/metadata-annotator.js +0 -74
  92. package/lib/scrubber/stages/normalizer.js +0 -59
  93. package/lib/scrubber/stages/semantic-filter.js +0 -61
  94. package/lib/scrubber/stages/structural-cleaner.js +0 -82
  95. package/lib/scrubber/stages/validator.js +0 -66
  96. package/lib/scrubber/utils/hash.js +0 -39
  97. package/lib/scrubber/utils/html-parser.js +0 -45
  98. package/lib/scrubber/utils/pattern-matcher.js +0 -63
  99. package/lib/scrubber/utils/token-counter.js +0 -31
  100. package/lib/search/index.js +0 -1
  101. package/lib/utils/index.js +0 -1
  102. package/lib/yamo/index.js +0 -15
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Custom error classes for LanceDB operations
3
+ *
4
+ * Base error class for all LanceDB-related errors. Captures proper stack traces
5
+ * to ensure debugging information points to where errors are thrown, not to the
6
+ * error constructor.
7
+ */
8
+ export class LanceDBError extends Error {
9
+ code;
10
+ details;
11
+ timestamp;
12
+ /**
13
+ * Create a new LanceDBError
14
+ * @param {string} message - Human-readable error message
15
+ * @param {string} code - Machine-readable error code (e.g., 'EMBEDDING_ERROR')
16
+ * @param {Object} details - Additional error context and metadata
17
+ */
18
+ constructor(message, code, details = {}) {
19
+ super(message);
20
+ this.name = "LanceDBError";
21
+ this.code = code;
22
+ this.details = details;
23
+ this.timestamp = new Date().toISOString();
24
+ // Capture stack trace for proper debugging (Node.js best practice)
25
+ // This ensures stack traces point to where the error was thrown,
26
+ // not to the error constructor itself
27
+ Error.captureStackTrace(this, this.constructor);
28
+ }
29
+ }
30
+ /**
31
+ * Error raised when embedding generation or comparison fails
32
+ */
33
+ export class EmbeddingError extends LanceDBError {
34
+ constructor(message, details) {
35
+ super(message, "EMBEDDING_ERROR", details);
36
+ this.name = "EmbeddingError";
37
+ }
38
+ }
39
+ /**
40
+ * Error raised when storage operations (read/write/delete) fail
41
+ */
42
+ export class StorageError extends LanceDBError {
43
+ constructor(message, details) {
44
+ super(message, "STORAGE_ERROR", details);
45
+ this.name = "StorageError";
46
+ }
47
+ }
48
+ /**
49
+ * Error raised when database queries fail or return invalid results
50
+ */
51
+ export class QueryError extends LanceDBError {
52
+ constructor(message, details) {
53
+ super(message, "QUERY_ERROR", details);
54
+ this.name = "QueryError";
55
+ }
56
+ }
57
+ /**
58
+ * Error raised when configuration is missing or invalid
59
+ */
60
+ export class ConfigurationError extends LanceDBError {
61
+ constructor(message, details) {
62
+ super(message, "CONFIGURATION_ERROR", details);
63
+ this.name = "ConfigurationError";
64
+ }
65
+ }
66
+ /**
67
+ * Sanitize error messages by redacting sensitive information
68
+ * @param {string} message - Error message to sanitize
69
+ * @returns {string} Sanitized error message
70
+ */
71
+ export function sanitizeErrorMessage(message) {
72
+ if (typeof message !== "string") {
73
+ return "[Non-string error message]";
74
+ }
75
+ // Redact common sensitive patterns
76
+ return (message
77
+ // Redact Bearer tokens
78
+ .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, "Bearer [REDACTED]")
79
+ // Redact OpenAI API keys (sk- followed by 32+ chars)
80
+ .replace(/sk-[A-Za-z0-9]{32,}/g, "sk-[REDACTED]")
81
+ // Redact generic API keys (20+ alphanumeric chars after api_key)
82
+ .replace(/api_key["\s:]+[A-Za-z0-9]{20,}/gi, "api_key: [REDACTED]")
83
+ // Redact environment variable patterns that might contain secrets
84
+ .replace(/(OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_API_KEY)["='\s]+[A-Za-z0-9\-_]+/gi, "$1=[REDACTED]")
85
+ // Redact Authorization headers
86
+ .replace(/Authorization:\s*[^"\r\n]+/gi, "Authorization: [REDACTED]")
87
+ // Redact potential JWT tokens
88
+ .replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, "[JWT_REDACTED]"));
89
+ }
90
+ /**
91
+ * Normalize errors into a consistent response format
92
+ * @param {Error} error - The error to handle
93
+ * @param {Object} context - Additional context about where/when the error occurred
94
+ * @returns {Object} Formatted error response with success: false
95
+ */
96
+ export function handleError(error, context = {}) {
97
+ if (error instanceof LanceDBError) {
98
+ return {
99
+ success: false,
100
+ error: {
101
+ code: error.code,
102
+ message: sanitizeErrorMessage(error.message),
103
+ details: error.details,
104
+ context,
105
+ },
106
+ };
107
+ }
108
+ const err = error instanceof Error ? error : new Error(String(error));
109
+ // Wrap unknown errors
110
+ return {
111
+ success: false,
112
+ error: {
113
+ code: "UNKNOWN_ERROR",
114
+ message: sanitizeErrorMessage(err.message),
115
+ stack: process.env.NODE_ENV === "development" ? err.stack : undefined,
116
+ context,
117
+ },
118
+ };
119
+ }
120
+ export default {
121
+ LanceDBError,
122
+ EmbeddingError,
123
+ StorageError,
124
+ QueryError,
125
+ ConfigurationError,
126
+ handleError,
127
+ sanitizeErrorMessage,
128
+ };
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Custom error classes for LanceDB operations
3
+ *
4
+ * Base error class for all LanceDB-related errors. Captures proper stack traces
5
+ * to ensure debugging information points to where errors are thrown, not to the
6
+ * error constructor.
7
+ */
8
+ export class LanceDBError extends Error {
9
+ code: string;
10
+ details: Record<string, any>;
11
+ timestamp: string;
12
+
13
+ /**
14
+ * Create a new LanceDBError
15
+ * @param {string} message - Human-readable error message
16
+ * @param {string} code - Machine-readable error code (e.g., 'EMBEDDING_ERROR')
17
+ * @param {Object} details - Additional error context and metadata
18
+ */
19
+ constructor(
20
+ message: string,
21
+ code: string,
22
+ details: Record<string, any> = {},
23
+ ) {
24
+ super(message);
25
+ this.name = "LanceDBError";
26
+ this.code = code;
27
+ this.details = details;
28
+ this.timestamp = new Date().toISOString();
29
+
30
+ // Capture stack trace for proper debugging (Node.js best practice)
31
+ // This ensures stack traces point to where the error was thrown,
32
+ // not to the error constructor itself
33
+ Error.captureStackTrace(this, this.constructor);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Error raised when embedding generation or comparison fails
39
+ */
40
+ export class EmbeddingError extends LanceDBError {
41
+ constructor(message: string, details?: Record<string, any>) {
42
+ super(message, "EMBEDDING_ERROR", details);
43
+ this.name = "EmbeddingError";
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Error raised when storage operations (read/write/delete) fail
49
+ */
50
+ export class StorageError extends LanceDBError {
51
+ constructor(message: string, details?: Record<string, any>) {
52
+ super(message, "STORAGE_ERROR", details);
53
+ this.name = "StorageError";
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Error raised when database queries fail or return invalid results
59
+ */
60
+ export class QueryError extends LanceDBError {
61
+ constructor(message: string, details?: Record<string, any>) {
62
+ super(message, "QUERY_ERROR", details);
63
+ this.name = "QueryError";
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Error raised when configuration is missing or invalid
69
+ */
70
+ export class ConfigurationError extends LanceDBError {
71
+ constructor(message: string, details?: Record<string, any>) {
72
+ super(message, "CONFIGURATION_ERROR", details);
73
+ this.name = "ConfigurationError";
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Sanitize error messages by redacting sensitive information
79
+ * @param {string} message - Error message to sanitize
80
+ * @returns {string} Sanitized error message
81
+ */
82
+ export function sanitizeErrorMessage(message: unknown): string {
83
+ if (typeof message !== "string") {
84
+ return "[Non-string error message]";
85
+ }
86
+
87
+ // Redact common sensitive patterns
88
+ return (
89
+ message
90
+ // Redact Bearer tokens
91
+ .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, "Bearer [REDACTED]")
92
+ // Redact OpenAI API keys (sk- followed by 32+ chars)
93
+ .replace(/sk-[A-Za-z0-9]{32,}/g, "sk-[REDACTED]")
94
+ // Redact generic API keys (20+ alphanumeric chars after api_key)
95
+ .replace(/api_key["\s:]+[A-Za-z0-9]{20,}/gi, "api_key: [REDACTED]")
96
+ // Redact environment variable patterns that might contain secrets
97
+ .replace(
98
+ /(OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_API_KEY)["='\s]+[A-Za-z0-9\-_]+/gi,
99
+ "$1=[REDACTED]",
100
+ )
101
+ // Redact Authorization headers
102
+ .replace(/Authorization:\s*[^"\r\n]+/gi, "Authorization: [REDACTED]")
103
+ // Redact potential JWT tokens
104
+ .replace(
105
+ /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g,
106
+ "[JWT_REDACTED]",
107
+ )
108
+ );
109
+ }
110
+
111
+ export interface ErrorResponse {
112
+ success: boolean;
113
+ error: {
114
+ code: string;
115
+ message: string;
116
+ details?: Record<string, any>;
117
+ context?: Record<string, any>;
118
+ stack?: string;
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Normalize errors into a consistent response format
124
+ * @param {Error} error - The error to handle
125
+ * @param {Object} context - Additional context about where/when the error occurred
126
+ * @returns {Object} Formatted error response with success: false
127
+ */
128
+ export function handleError(
129
+ error: unknown,
130
+ context: Record<string, any> = {},
131
+ ): ErrorResponse {
132
+ if (error instanceof LanceDBError) {
133
+ return {
134
+ success: false,
135
+ error: {
136
+ code: error.code,
137
+ message: sanitizeErrorMessage(error.message),
138
+ details: error.details,
139
+ context,
140
+ },
141
+ };
142
+ }
143
+
144
+ const err = error instanceof Error ? error : new Error(String(error));
145
+
146
+ // Wrap unknown errors
147
+ return {
148
+ success: false,
149
+ error: {
150
+ code: "UNKNOWN_ERROR",
151
+ message: sanitizeErrorMessage(err.message),
152
+ stack: process.env.NODE_ENV === "development" ? err.stack : undefined,
153
+ context,
154
+ },
155
+ };
156
+ }
157
+
158
+ export default {
159
+ LanceDBError,
160
+ EmbeddingError,
161
+ StorageError,
162
+ QueryError,
163
+ ConfigurationError,
164
+ handleError,
165
+ sanitizeErrorMessage,
166
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * MemoryContextManager - High-level memory management for YAMO
3
+ */
4
+ import { MemoryMesh } from "./memory-mesh.js";
5
+ export interface MemoryContextConfig {
6
+ mesh?: MemoryMesh;
7
+ autoInit?: boolean;
8
+ enableCache?: boolean;
9
+ recallLimit?: number;
10
+ minImportance?: number;
11
+ silent?: boolean;
12
+ }
13
+ export declare class MemoryContextManager {
14
+ #private;
15
+ /**
16
+ * Create a new MemoryContextManager
17
+ */
18
+ constructor(config?: MemoryContextConfig);
19
+ /**
20
+ * Initialize the memory context manager
21
+ */
22
+ initialize(): Promise<void>;
23
+ /**
24
+ * Capture an interaction as memory
25
+ */
26
+ captureInteraction(prompt: string, response: string, context?: any): Promise<any>;
27
+ /**
28
+ * Recall relevant memories for a query
29
+ */
30
+ recallMemories(query: string, options?: any): Promise<any[]>;
31
+ /**
32
+ * Format memories for inclusion in prompt
33
+ */
34
+ formatMemoriesForPrompt(memories: any[], options?: any): string;
35
+ clearCache(): void;
36
+ getCacheStats(): any;
37
+ healthCheck(): Promise<any>;
38
+ /**
39
+ * Dispose of resources (cleanup timer and cache)
40
+ * Call this when the MemoryContextManager is no longer needed
41
+ */
42
+ dispose(): void;
43
+ }
44
+ export default MemoryContextManager;
@@ -0,0 +1,344 @@
1
+ /**
2
+ * MemoryContextManager - High-level memory management for YAMO
3
+ */
4
+ import { MemoryMesh } from "./memory-mesh.js";
5
+ import { MemoryScorer } from "./scorer.js";
6
+ import { MemoryTranslator } from "./memory-translator.js";
7
+ import { createLogger } from "../utils/logger.js";
8
+ const logger = createLogger("context-manager");
9
+ export class MemoryContextManager {
10
+ #config;
11
+ #mesh;
12
+ #scorer;
13
+ #initialized = false;
14
+ #queryCache = new Map();
15
+ #cacheConfig = {
16
+ maxSize: 100,
17
+ ttlMs: 2 * 60 * 1000, // 2 minutes
18
+ };
19
+ #cleanupTimer = null;
20
+ /**
21
+ * Create a new MemoryContextManager
22
+ */
23
+ constructor(config = {}) {
24
+ this.#config = {
25
+ autoInit: true,
26
+ enableCache: true,
27
+ recallLimit: 5,
28
+ minImportance: 0.1,
29
+ silent: config.silent !== false,
30
+ ...config,
31
+ };
32
+ // Use provided mesh or create new instance
33
+ this.#mesh = config.mesh || new MemoryMesh();
34
+ this.#scorer = new MemoryScorer(this.#mesh);
35
+ // Start periodic cleanup timer (every 60 seconds)
36
+ this.#startCleanupTimer();
37
+ }
38
+ /**
39
+ * Initialize the memory context manager
40
+ */
41
+ async initialize() {
42
+ if (this.#initialized) {
43
+ return;
44
+ }
45
+ try {
46
+ await this.#mesh.init();
47
+ this.#initialized = true;
48
+ }
49
+ catch (error) {
50
+ this.#logWarn(`Initialization failed: ${error.message}`);
51
+ this.#initialized = false;
52
+ }
53
+ }
54
+ /**
55
+ * Capture an interaction as memory
56
+ */
57
+ async captureInteraction(prompt, response, context = {}) {
58
+ try {
59
+ if (this.#config.autoInit && !this.#initialized) {
60
+ await this.initialize();
61
+ }
62
+ if (!this.#initialized) {
63
+ return null;
64
+ }
65
+ const content = this.#formatInteraction(prompt, response);
66
+ const metadata = this.#buildMetadata(context);
67
+ const isDuplicate = await this.#scorer.isDuplicate(content);
68
+ if (isDuplicate) {
69
+ return null;
70
+ }
71
+ const importance = this.#scorer.calculateImportance(content, metadata);
72
+ if (importance < (this.#config.minImportance ?? 0.1)) {
73
+ return null;
74
+ }
75
+ const memory = await this.#mesh.add(content, {
76
+ ...metadata,
77
+ importanceScore: importance,
78
+ });
79
+ return memory;
80
+ }
81
+ catch (error) {
82
+ this.#logWarn(`Failed to capture interaction: ${error.message}`);
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Recall relevant memories for a query
88
+ */
89
+ async recallMemories(query, options = {}) {
90
+ try {
91
+ if (this.#config.autoInit && !this.#initialized) {
92
+ await this.initialize();
93
+ }
94
+ if (!this.#initialized) {
95
+ return [];
96
+ }
97
+ const { limit = this.#config.recallLimit, useCache = this.#config.enableCache, memoryType = null, skillName = null, } = options;
98
+ if (useCache) {
99
+ const cacheKey = this.#cacheKey(query, {
100
+ limit,
101
+ memoryType,
102
+ skillName,
103
+ });
104
+ const cached = this.#getCached(cacheKey);
105
+ if (cached) {
106
+ return cached;
107
+ }
108
+ }
109
+ const filter = memoryType ? `memoryType == '${memoryType}'` : null;
110
+ // Fetch extra when skill-scoping — some results will be filtered out post-query
111
+ const fetchLimit = skillName ? limit * 2 : limit;
112
+ let memories = [];
113
+ if (memoryType === "synthesized_skill" &&
114
+ typeof this.#mesh.searchSkills === "function") {
115
+ memories = await this.#mesh.searchSkills(query, { limit: fetchLimit });
116
+ }
117
+ else {
118
+ memories = await this.#mesh.search(query, {
119
+ limit: fetchLimit,
120
+ filter,
121
+ useCache: false,
122
+ });
123
+ }
124
+ memories = memories.map((memory) => {
125
+ const metadata = typeof memory.metadata === "string"
126
+ ? JSON.parse(memory.metadata)
127
+ : memory.metadata || {};
128
+ return {
129
+ ...memory,
130
+ importanceScore: memory.score || metadata.importanceScore || 0,
131
+ memoryType: metadata.memoryType ||
132
+ (memoryType === "synthesized_skill"
133
+ ? "synthesized_skill"
134
+ : "global"),
135
+ };
136
+ });
137
+ // Deduplicate by content — results are already sorted by score, so first occurrence wins
138
+ const seen = new Set();
139
+ memories = memories.filter((memory) => {
140
+ if (seen.has(memory.content)) {
141
+ return false;
142
+ }
143
+ seen.add(memory.content);
144
+ return true;
145
+ });
146
+ // Skill-scope filter: keep memories tagged with this skill OR untagged (global).
147
+ // Untagged memories are shared context; tagged memories are skill-private.
148
+ if (skillName) {
149
+ memories = memories.filter((memory) => {
150
+ const meta = typeof memory.metadata === "string"
151
+ ? JSON.parse(memory.metadata)
152
+ : memory.metadata || {};
153
+ return !meta.skill_name || meta.skill_name === skillName;
154
+ });
155
+ memories = memories.slice(0, limit);
156
+ }
157
+ if (useCache) {
158
+ const cacheKey = this.#cacheKey(query, {
159
+ limit,
160
+ memoryType,
161
+ skillName,
162
+ });
163
+ this.#setCached(cacheKey, memories);
164
+ }
165
+ return memories;
166
+ }
167
+ catch (error) {
168
+ this.#logWarn(`Failed to recall memories: ${error.message}`);
169
+ return [];
170
+ }
171
+ }
172
+ /**
173
+ * Format memories for inclusion in prompt
174
+ */
175
+ formatMemoriesForPrompt(memories, options = {}) {
176
+ try {
177
+ if (!memories || memories.length === 0) {
178
+ return "";
179
+ }
180
+ return MemoryTranslator.toYAMOContext(memories, options);
181
+ }
182
+ catch (error) {
183
+ this.#logWarn(`Failed to format memories: ${error.message}`);
184
+ return "";
185
+ }
186
+ }
187
+ #logWarn(message) {
188
+ if (!this.#config.silent || process.env.YAMO_DEBUG === "true") {
189
+ logger.warn(message);
190
+ }
191
+ }
192
+ #formatInteraction(prompt, response) {
193
+ const lines = [
194
+ `[USER] ${prompt}`,
195
+ `[ASSISTANT] ${response.substring(0, 500)}${response.length > 500 ? "..." : ""}`,
196
+ ];
197
+ return lines.join("\n\n");
198
+ }
199
+ #buildMetadata(context) {
200
+ const metadata = {
201
+ interaction_type: context.interactionType || "llm_response",
202
+ created_at: new Date().toISOString(),
203
+ };
204
+ if (context.toolsUsed?.length > 0) {
205
+ metadata.tools_used = context.toolsUsed;
206
+ }
207
+ if (context.filesInvolved?.length > 0) {
208
+ metadata.files_involved = context.filesInvolved;
209
+ }
210
+ if (context.tags?.length > 0) {
211
+ metadata.tags = context.tags;
212
+ }
213
+ if (context.skillName) {
214
+ metadata.skill_name = context.skillName;
215
+ }
216
+ if (context.sessionId) {
217
+ metadata.session_id = context.sessionId;
218
+ }
219
+ return metadata;
220
+ }
221
+ #cacheKey(query, options) {
222
+ return `recall:${query}:${JSON.stringify(options)}`;
223
+ }
224
+ /**
225
+ * Get cached result if valid
226
+ * Race condition fix: Update timestamp atomically for LRU tracking
227
+ */
228
+ #getCached(key) {
229
+ const entry = this.#queryCache.get(key);
230
+ if (!entry) {
231
+ return null;
232
+ }
233
+ // Check TTL before any mutation
234
+ const now = Date.now();
235
+ if (now - entry.timestamp > this.#cacheConfig.ttlMs) {
236
+ this.#queryCache.delete(key);
237
+ return null;
238
+ }
239
+ // Move to end (most recently used) - delete and re-add with updated timestamp
240
+ this.#queryCache.delete(key);
241
+ this.#queryCache.set(key, {
242
+ ...entry,
243
+ timestamp: now, // Update timestamp for LRU tracking
244
+ });
245
+ return entry.result;
246
+ }
247
+ #setCached(key, result) {
248
+ if (this.#queryCache.size >= this.#cacheConfig.maxSize) {
249
+ const firstKey = this.#queryCache.keys().next().value;
250
+ if (firstKey !== undefined) {
251
+ this.#queryCache.delete(firstKey);
252
+ }
253
+ }
254
+ this.#queryCache.set(key, {
255
+ result,
256
+ timestamp: Date.now(),
257
+ });
258
+ }
259
+ clearCache() {
260
+ this.#queryCache.clear();
261
+ }
262
+ getCacheStats() {
263
+ return {
264
+ size: this.#queryCache.size,
265
+ maxSize: this.#cacheConfig.maxSize,
266
+ ttlMs: this.#cacheConfig.ttlMs,
267
+ };
268
+ }
269
+ async healthCheck() {
270
+ const health = {
271
+ status: "healthy",
272
+ timestamp: new Date().toISOString(),
273
+ initialized: this.#initialized,
274
+ checks: {},
275
+ };
276
+ try {
277
+ health.checks.mesh = await this.#mesh.stats(); // brain.ts has stats()
278
+ if (health.checks.mesh.isConnected === false) {
279
+ health.status = "degraded";
280
+ }
281
+ }
282
+ catch (error) {
283
+ health.checks.mesh = {
284
+ status: "error",
285
+ error: error.message,
286
+ };
287
+ health.status = "unhealthy";
288
+ }
289
+ health.checks.cache = {
290
+ status: "up",
291
+ size: this.#queryCache.size,
292
+ maxSize: this.#cacheConfig.maxSize,
293
+ };
294
+ return health;
295
+ }
296
+ /**
297
+ * Start periodic cleanup timer to remove expired cache entries
298
+ * @private
299
+ */
300
+ #startCleanupTimer() {
301
+ // Clear any existing timer
302
+ if (this.#cleanupTimer) {
303
+ clearInterval(this.#cleanupTimer);
304
+ }
305
+ // Run cleanup every 60 seconds
306
+ this.#cleanupTimer = setInterval(() => {
307
+ this.#cleanupExpired();
308
+ }, 60000);
309
+ }
310
+ /**
311
+ * Clean up expired cache entries
312
+ * @private
313
+ */
314
+ #cleanupExpired() {
315
+ const now = Date.now();
316
+ const expiredKeys = [];
317
+ // Find expired entries
318
+ for (const [key, entry] of this.#queryCache.entries()) {
319
+ if (now - entry.timestamp > this.#cacheConfig.ttlMs) {
320
+ expiredKeys.push(key);
321
+ }
322
+ }
323
+ // Remove expired entries
324
+ for (const key of expiredKeys) {
325
+ this.#queryCache.delete(key);
326
+ }
327
+ if (expiredKeys.length > 0 &&
328
+ (process.env.YAMO_DEBUG === "true" || !this.#config.silent)) {
329
+ logger.debug({ count: expiredKeys.length }, "Cleaned up expired cache entries");
330
+ }
331
+ }
332
+ /**
333
+ * Dispose of resources (cleanup timer and cache)
334
+ * Call this when the MemoryContextManager is no longer needed
335
+ */
336
+ dispose() {
337
+ if (this.#cleanupTimer) {
338
+ clearInterval(this.#cleanupTimer);
339
+ this.#cleanupTimer = null;
340
+ }
341
+ this.clearCache();
342
+ }
343
+ }
344
+ export default MemoryContextManager;