@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.
- package/bin/memory_mesh.js +1 -1
- package/lib/llm/client.d.ts +111 -0
- package/lib/llm/client.js +299 -357
- package/lib/llm/client.ts +413 -0
- package/lib/llm/index.d.ts +17 -0
- package/lib/llm/index.js +15 -8
- package/lib/llm/index.ts +19 -0
- package/lib/memory/adapters/client.d.ts +183 -0
- package/lib/memory/adapters/client.js +518 -0
- package/lib/memory/adapters/client.ts +678 -0
- package/lib/memory/adapters/config.d.ts +137 -0
- package/lib/memory/adapters/config.js +189 -0
- package/lib/memory/adapters/config.ts +259 -0
- package/lib/memory/adapters/errors.d.ts +76 -0
- package/lib/memory/adapters/errors.js +128 -0
- package/lib/memory/adapters/errors.ts +166 -0
- package/lib/memory/context-manager.d.ts +44 -0
- package/lib/memory/context-manager.js +344 -0
- package/lib/memory/context-manager.ts +432 -0
- package/lib/memory/embeddings/factory.d.ts +59 -0
- package/lib/memory/embeddings/factory.js +148 -0
- package/lib/{embeddings/factory.js → memory/embeddings/factory.ts} +69 -28
- package/lib/memory/embeddings/index.d.ts +2 -0
- package/lib/memory/embeddings/index.js +2 -0
- package/lib/memory/embeddings/index.ts +2 -0
- package/lib/memory/embeddings/service.d.ts +164 -0
- package/lib/memory/embeddings/service.js +515 -0
- package/lib/{embeddings/service.js → memory/embeddings/service.ts} +223 -156
- package/lib/memory/index.d.ts +9 -0
- package/lib/memory/index.js +9 -1
- package/lib/memory/index.ts +20 -0
- package/lib/memory/memory-mesh.d.ts +274 -0
- package/lib/memory/memory-mesh.js +1445 -1189
- package/lib/memory/memory-mesh.ts +1803 -0
- package/lib/memory/memory-translator.d.ts +19 -0
- package/lib/memory/memory-translator.js +125 -0
- package/lib/memory/memory-translator.ts +158 -0
- package/lib/memory/schema.d.ts +111 -0
- package/lib/memory/schema.js +183 -0
- package/lib/memory/schema.ts +267 -0
- package/lib/memory/scorer.d.ts +26 -0
- package/lib/memory/scorer.js +77 -0
- package/lib/memory/scorer.ts +95 -0
- package/lib/memory/search/index.d.ts +1 -0
- package/lib/memory/search/index.js +1 -0
- package/lib/memory/search/index.ts +1 -0
- package/lib/memory/search/keyword-search.d.ts +62 -0
- package/lib/memory/search/keyword-search.js +135 -0
- package/lib/{search/keyword-search.js → memory/search/keyword-search.ts} +66 -36
- package/lib/scrubber/config/defaults.d.ts +53 -0
- package/lib/scrubber/config/defaults.js +49 -57
- package/lib/scrubber/config/defaults.ts +117 -0
- package/lib/scrubber/index.d.ts +6 -0
- package/lib/scrubber/index.js +3 -23
- package/lib/scrubber/index.ts +7 -0
- package/lib/scrubber/scrubber.d.ts +61 -0
- package/lib/scrubber/scrubber.js +99 -121
- package/lib/scrubber/scrubber.ts +168 -0
- package/lib/scrubber/stages/chunker.d.ts +13 -0
- package/lib/scrubber/stages/metadata-annotator.d.ts +18 -0
- package/lib/scrubber/stages/normalizer.d.ts +13 -0
- package/lib/scrubber/stages/semantic-filter.d.ts +13 -0
- package/lib/scrubber/stages/structural-cleaner.d.ts +13 -0
- package/lib/scrubber/stages/validator.d.ts +18 -0
- package/lib/scrubber/telemetry.d.ts +36 -0
- package/lib/scrubber/telemetry.js +53 -58
- package/lib/scrubber/telemetry.ts +99 -0
- package/lib/utils/logger.d.ts +29 -0
- package/lib/utils/logger.js +64 -0
- package/lib/utils/logger.ts +85 -0
- package/lib/utils/skill-metadata.d.ts +32 -0
- package/lib/utils/skill-metadata.js +132 -0
- package/lib/utils/skill-metadata.ts +147 -0
- package/lib/yamo/emitter.d.ts +73 -0
- package/lib/yamo/emitter.js +78 -143
- package/lib/yamo/emitter.ts +249 -0
- package/lib/yamo/schema.d.ts +58 -0
- package/lib/yamo/schema.js +81 -108
- package/lib/yamo/schema.ts +165 -0
- package/package.json +11 -8
- package/index.d.ts +0 -111
- package/lib/embeddings/index.js +0 -2
- package/lib/index.js +0 -6
- package/lib/lancedb/client.js +0 -633
- package/lib/lancedb/config.js +0 -215
- package/lib/lancedb/errors.js +0 -144
- package/lib/lancedb/index.js +0 -4
- package/lib/lancedb/schema.js +0 -197
- package/lib/scrubber/errors/scrubber-error.js +0 -43
- package/lib/scrubber/stages/chunker.js +0 -103
- package/lib/scrubber/stages/metadata-annotator.js +0 -74
- package/lib/scrubber/stages/normalizer.js +0 -59
- package/lib/scrubber/stages/semantic-filter.js +0 -61
- package/lib/scrubber/stages/structural-cleaner.js +0 -82
- package/lib/scrubber/stages/validator.js +0 -66
- package/lib/scrubber/utils/hash.js +0 -39
- package/lib/scrubber/utils/html-parser.js +0 -45
- package/lib/scrubber/utils/pattern-matcher.js +0 -63
- package/lib/scrubber/utils/token-counter.js +0 -31
- package/lib/search/index.js +0 -1
- package/lib/utils/index.js +0 -1
- 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;
|