@danielsimonjr/memory-mcp 0.47.1 → 9.8.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/LICENSE +22 -0
- package/README.md +2000 -194
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/knowledge-graph.test.js +3 -8
- package/dist/core/EntityManager.d.ts +266 -0
- package/dist/core/EntityManager.d.ts.map +1 -0
- package/dist/core/EntityManager.js +85 -133
- package/dist/core/GraphEventEmitter.d.ts +202 -0
- package/dist/core/GraphEventEmitter.d.ts.map +1 -0
- package/dist/core/GraphEventEmitter.js +346 -0
- package/dist/core/GraphStorage.d.ts +395 -0
- package/dist/core/GraphStorage.d.ts.map +1 -0
- package/dist/core/GraphStorage.js +643 -31
- package/dist/core/GraphTraversal.d.ts +141 -0
- package/dist/core/GraphTraversal.d.ts.map +1 -0
- package/dist/core/GraphTraversal.js +573 -0
- package/dist/core/HierarchyManager.d.ts +111 -0
- package/dist/core/HierarchyManager.d.ts.map +1 -0
- package/dist/{features → core}/HierarchyManager.js +14 -9
- package/dist/core/ManagerContext.d.ts +72 -0
- package/dist/core/ManagerContext.d.ts.map +1 -0
- package/dist/core/ManagerContext.js +118 -0
- package/dist/core/ObservationManager.d.ts +85 -0
- package/dist/core/ObservationManager.d.ts.map +1 -0
- package/dist/core/ObservationManager.js +51 -57
- package/dist/core/RelationManager.d.ts +131 -0
- package/dist/core/RelationManager.d.ts.map +1 -0
- package/dist/core/RelationManager.js +31 -7
- package/dist/core/SQLiteStorage.d.ts +354 -0
- package/dist/core/SQLiteStorage.d.ts.map +1 -0
- package/dist/core/SQLiteStorage.js +917 -0
- package/dist/core/StorageFactory.d.ts +45 -0
- package/dist/core/StorageFactory.d.ts.map +1 -0
- package/dist/core/StorageFactory.js +64 -0
- package/dist/core/TransactionManager.d.ts +464 -0
- package/dist/core/TransactionManager.d.ts.map +1 -0
- package/dist/core/TransactionManager.js +490 -13
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -2
- package/dist/features/AnalyticsManager.d.ts +44 -0
- package/dist/features/AnalyticsManager.d.ts.map +1 -0
- package/dist/features/AnalyticsManager.js +14 -13
- package/dist/features/ArchiveManager.d.ts +133 -0
- package/dist/features/ArchiveManager.d.ts.map +1 -0
- package/dist/features/ArchiveManager.js +221 -14
- package/dist/features/CompressionManager.d.ts +117 -0
- package/dist/features/CompressionManager.d.ts.map +1 -0
- package/dist/features/CompressionManager.js +189 -20
- package/dist/features/IOManager.d.ts +225 -0
- package/dist/features/IOManager.d.ts.map +1 -0
- package/dist/features/IOManager.js +1041 -0
- package/dist/features/StreamingExporter.d.ts +123 -0
- package/dist/features/StreamingExporter.d.ts.map +1 -0
- package/dist/features/StreamingExporter.js +203 -0
- package/dist/features/TagManager.d.ts +147 -0
- package/dist/features/TagManager.d.ts.map +1 -0
- package/dist/features/index.d.ts +12 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -45
- package/dist/memory.jsonl +1 -18
- package/dist/search/BasicSearch.d.ts +51 -0
- package/dist/search/BasicSearch.d.ts.map +1 -0
- package/dist/search/BasicSearch.js +9 -3
- package/dist/search/BooleanSearch.d.ts +98 -0
- package/dist/search/BooleanSearch.d.ts.map +1 -0
- package/dist/search/BooleanSearch.js +156 -9
- package/dist/search/EmbeddingService.d.ts +178 -0
- package/dist/search/EmbeddingService.d.ts.map +1 -0
- package/dist/search/EmbeddingService.js +358 -0
- package/dist/search/FuzzySearch.d.ts +118 -0
- package/dist/search/FuzzySearch.d.ts.map +1 -0
- package/dist/search/FuzzySearch.js +241 -25
- package/dist/search/QueryCostEstimator.d.ts +111 -0
- package/dist/search/QueryCostEstimator.d.ts.map +1 -0
- package/dist/search/QueryCostEstimator.js +355 -0
- package/dist/search/RankedSearch.d.ts +71 -0
- package/dist/search/RankedSearch.d.ts.map +1 -0
- package/dist/search/RankedSearch.js +54 -6
- package/dist/search/SavedSearchManager.d.ts +79 -0
- package/dist/search/SavedSearchManager.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.d.ts +120 -0
- package/dist/search/SearchFilterChain.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.js +2 -4
- package/dist/search/SearchManager.d.ts +326 -0
- package/dist/search/SearchManager.d.ts.map +1 -0
- package/dist/search/SearchManager.js +148 -0
- package/dist/search/SearchSuggestions.d.ts +27 -0
- package/dist/search/SearchSuggestions.d.ts.map +1 -0
- package/dist/search/SearchSuggestions.js +1 -1
- package/dist/search/SemanticSearch.d.ts +149 -0
- package/dist/search/SemanticSearch.d.ts.map +1 -0
- package/dist/search/SemanticSearch.js +323 -0
- package/dist/search/TFIDFEventSync.d.ts +85 -0
- package/dist/search/TFIDFEventSync.d.ts.map +1 -0
- package/dist/search/TFIDFEventSync.js +133 -0
- package/dist/search/TFIDFIndexManager.d.ts +151 -0
- package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
- package/dist/search/TFIDFIndexManager.js +232 -17
- package/dist/search/VectorStore.d.ts +235 -0
- package/dist/search/VectorStore.d.ts.map +1 -0
- package/dist/search/VectorStore.js +311 -0
- package/dist/search/index.d.ts +21 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +12 -0
- package/dist/server/MCPServer.d.ts +21 -0
- package/dist/server/MCPServer.d.ts.map +1 -0
- package/dist/server/MCPServer.js +4 -4
- package/dist/server/responseCompressor.d.ts +94 -0
- package/dist/server/responseCompressor.d.ts.map +1 -0
- package/dist/server/responseCompressor.js +127 -0
- package/dist/server/toolDefinitions.d.ts +27 -0
- package/dist/server/toolDefinitions.d.ts.map +1 -0
- package/dist/server/toolDefinitions.js +189 -18
- package/dist/server/toolHandlers.d.ts +41 -0
- package/dist/server/toolHandlers.d.ts.map +1 -0
- package/dist/server/toolHandlers.js +467 -75
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -1
- package/dist/types/types.d.ts +1654 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +9 -0
- package/dist/utils/compressedCache.d.ts +192 -0
- package/dist/utils/compressedCache.d.ts.map +1 -0
- package/dist/utils/compressedCache.js +309 -0
- package/dist/utils/compressionUtil.d.ts +214 -0
- package/dist/utils/compressionUtil.d.ts.map +1 -0
- package/dist/utils/compressionUtil.js +247 -0
- package/dist/utils/constants.d.ts +245 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +124 -0
- package/dist/utils/entityUtils.d.ts +321 -0
- package/dist/utils/entityUtils.d.ts.map +1 -0
- package/dist/utils/entityUtils.js +434 -4
- package/dist/utils/errors.d.ts +95 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +24 -0
- package/dist/utils/formatters.d.ts +145 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/{paginationUtils.js → formatters.js} +54 -3
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +69 -31
- package/dist/utils/indexes.d.ts +270 -0
- package/dist/utils/indexes.d.ts.map +1 -0
- package/dist/utils/indexes.js +526 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/operationUtils.d.ts +124 -0
- package/dist/utils/operationUtils.d.ts.map +1 -0
- package/dist/utils/operationUtils.js +175 -0
- package/dist/utils/parallelUtils.d.ts +72 -0
- package/dist/utils/parallelUtils.d.ts.map +1 -0
- package/dist/utils/parallelUtils.js +169 -0
- package/dist/utils/schemas.d.ts +374 -0
- package/dist/utils/schemas.d.ts.map +1 -0
- package/dist/utils/schemas.js +302 -2
- package/dist/utils/searchAlgorithms.d.ts +99 -0
- package/dist/utils/searchAlgorithms.d.ts.map +1 -0
- package/dist/utils/searchAlgorithms.js +167 -0
- package/dist/utils/searchCache.d.ts +108 -0
- package/dist/utils/searchCache.d.ts.map +1 -0
- package/dist/utils/taskScheduler.d.ts +290 -0
- package/dist/utils/taskScheduler.d.ts.map +1 -0
- package/dist/utils/taskScheduler.js +466 -0
- package/dist/workers/index.d.ts +12 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +9 -0
- package/dist/workers/levenshteinWorker.d.ts +60 -0
- package/dist/workers/levenshteinWorker.d.ts.map +1 -0
- package/dist/workers/levenshteinWorker.js +98 -0
- package/package.json +17 -4
- package/dist/__tests__/edge-cases/edge-cases.test.js +0 -406
- package/dist/__tests__/integration/workflows.test.js +0 -449
- package/dist/__tests__/performance/benchmarks.test.js +0 -413
- package/dist/__tests__/unit/core/EntityManager.test.js +0 -334
- package/dist/__tests__/unit/core/GraphStorage.test.js +0 -205
- package/dist/__tests__/unit/core/RelationManager.test.js +0 -274
- package/dist/__tests__/unit/features/CompressionManager.test.js +0 -350
- package/dist/__tests__/unit/search/BasicSearch.test.js +0 -311
- package/dist/__tests__/unit/search/BooleanSearch.test.js +0 -432
- package/dist/__tests__/unit/search/FuzzySearch.test.js +0 -448
- package/dist/__tests__/unit/search/RankedSearch.test.js +0 -379
- package/dist/__tests__/unit/utils/levenshtein.test.js +0 -77
- package/dist/core/KnowledgeGraphManager.js +0 -423
- package/dist/features/BackupManager.js +0 -311
- package/dist/features/ExportManager.js +0 -305
- package/dist/features/ImportExportManager.js +0 -50
- package/dist/features/ImportManager.js +0 -328
- package/dist/types/analytics.types.js +0 -6
- package/dist/types/entity.types.js +0 -7
- package/dist/types/import-export.types.js +0 -7
- package/dist/types/search.types.js +0 -7
- package/dist/types/tag.types.js +0 -6
- package/dist/utils/dateUtils.js +0 -89
- package/dist/utils/filterUtils.js +0 -155
- package/dist/utils/levenshtein.js +0 -62
- package/dist/utils/pathUtils.js +0 -115
- package/dist/utils/responseFormatter.js +0 -55
- package/dist/utils/tagUtils.js +0 -107
- package/dist/utils/tfidf.js +0 -90
- package/dist/utils/validationHelper.js +0 -99
- package/dist/utils/validationUtils.js +0 -109
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Indexes
|
|
3
|
+
*
|
|
4
|
+
* Provides O(1) lookup structures to avoid repeated linear scans.
|
|
5
|
+
* - NameIndex: O(1) entity lookup by name
|
|
6
|
+
* - TypeIndex: O(1) entities by type
|
|
7
|
+
* - LowercaseCache: Pre-computed lowercase strings to avoid repeated toLowerCase()
|
|
8
|
+
* - RelationIndex: O(1) relation lookup by entity name (from/to)
|
|
9
|
+
* - ObservationIndex: O(1) observation word lookup by entity
|
|
10
|
+
*
|
|
11
|
+
* @module utils/indexes
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* NameIndex provides O(1) entity lookup by name.
|
|
15
|
+
*
|
|
16
|
+
* Uses a Map internally for constant-time access.
|
|
17
|
+
*/
|
|
18
|
+
export class NameIndex {
|
|
19
|
+
index = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Build the index from an array of entities.
|
|
22
|
+
* Clears any existing index data first.
|
|
23
|
+
*/
|
|
24
|
+
build(entities) {
|
|
25
|
+
this.index.clear();
|
|
26
|
+
for (const entity of entities) {
|
|
27
|
+
this.index.set(entity.name, entity);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get an entity by name in O(1) time.
|
|
32
|
+
*/
|
|
33
|
+
get(name) {
|
|
34
|
+
return this.index.get(name);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Add a single entity to the index.
|
|
38
|
+
*/
|
|
39
|
+
add(entity) {
|
|
40
|
+
this.index.set(entity.name, entity);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove an entity from the index by name.
|
|
44
|
+
*/
|
|
45
|
+
remove(name) {
|
|
46
|
+
this.index.delete(name);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if an entity exists in the index.
|
|
50
|
+
*/
|
|
51
|
+
has(name) {
|
|
52
|
+
return this.index.has(name);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the number of entities in the index.
|
|
56
|
+
*/
|
|
57
|
+
get size() {
|
|
58
|
+
return this.index.size;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Clear all entries from the index.
|
|
62
|
+
*/
|
|
63
|
+
clear() {
|
|
64
|
+
this.index.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* TypeIndex provides O(1) lookup of entities by type.
|
|
69
|
+
*
|
|
70
|
+
* Uses a Map<type, Set<entityName>> structure for efficient type queries.
|
|
71
|
+
* Type comparisons are case-insensitive.
|
|
72
|
+
*/
|
|
73
|
+
export class TypeIndex {
|
|
74
|
+
index = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* Build the index from an array of entities.
|
|
77
|
+
* Clears any existing index data first.
|
|
78
|
+
*/
|
|
79
|
+
build(entities) {
|
|
80
|
+
this.index.clear();
|
|
81
|
+
for (const entity of entities) {
|
|
82
|
+
this.addToIndex(entity.name, entity.entityType);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get all entity names of a given type in O(1) time.
|
|
87
|
+
* Type comparison is case-insensitive.
|
|
88
|
+
*/
|
|
89
|
+
getNames(entityType) {
|
|
90
|
+
const typeLower = entityType.toLowerCase();
|
|
91
|
+
return this.index.get(typeLower) ?? new Set();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Add an entity to the type index.
|
|
95
|
+
*/
|
|
96
|
+
add(entity) {
|
|
97
|
+
this.addToIndex(entity.name, entity.entityType);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remove an entity from the type index.
|
|
101
|
+
* Requires the entity type to know which bucket to remove from.
|
|
102
|
+
*/
|
|
103
|
+
remove(entityName, entityType) {
|
|
104
|
+
const typeLower = entityType.toLowerCase();
|
|
105
|
+
const names = this.index.get(typeLower);
|
|
106
|
+
if (names) {
|
|
107
|
+
names.delete(entityName);
|
|
108
|
+
if (names.size === 0) {
|
|
109
|
+
this.index.delete(typeLower);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Update an entity's type in the index.
|
|
115
|
+
* Removes from old type and adds to new type.
|
|
116
|
+
*/
|
|
117
|
+
updateType(entityName, oldType, newType) {
|
|
118
|
+
this.remove(entityName, oldType);
|
|
119
|
+
this.addToIndex(entityName, newType);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get all unique types in the index.
|
|
123
|
+
*/
|
|
124
|
+
getTypes() {
|
|
125
|
+
return Array.from(this.index.keys());
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Clear all entries from the index.
|
|
129
|
+
*/
|
|
130
|
+
clear() {
|
|
131
|
+
this.index.clear();
|
|
132
|
+
}
|
|
133
|
+
addToIndex(entityName, entityType) {
|
|
134
|
+
const typeLower = entityType.toLowerCase();
|
|
135
|
+
let names = this.index.get(typeLower);
|
|
136
|
+
if (!names) {
|
|
137
|
+
names = new Set();
|
|
138
|
+
this.index.set(typeLower, names);
|
|
139
|
+
}
|
|
140
|
+
names.add(entityName);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* LowercaseCache pre-computes lowercase versions of all searchable fields.
|
|
145
|
+
*
|
|
146
|
+
* Eliminates the need for repeated toLowerCase() calls during search,
|
|
147
|
+
* which is expensive with many entities and observations.
|
|
148
|
+
*/
|
|
149
|
+
export class LowercaseCache {
|
|
150
|
+
cache = new Map();
|
|
151
|
+
/**
|
|
152
|
+
* Build the cache from an array of entities.
|
|
153
|
+
* Clears any existing cache data first.
|
|
154
|
+
*/
|
|
155
|
+
build(entities) {
|
|
156
|
+
this.cache.clear();
|
|
157
|
+
for (const entity of entities) {
|
|
158
|
+
this.cache.set(entity.name, this.computeLowercase(entity));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get pre-computed lowercase data for an entity.
|
|
163
|
+
*/
|
|
164
|
+
get(entityName) {
|
|
165
|
+
return this.cache.get(entityName);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Add or update an entity in the cache.
|
|
169
|
+
*/
|
|
170
|
+
set(entity) {
|
|
171
|
+
this.cache.set(entity.name, this.computeLowercase(entity));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Remove an entity from the cache.
|
|
175
|
+
*/
|
|
176
|
+
remove(entityName) {
|
|
177
|
+
this.cache.delete(entityName);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if an entity exists in the cache.
|
|
181
|
+
*/
|
|
182
|
+
has(entityName) {
|
|
183
|
+
return this.cache.has(entityName);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Clear all entries from the cache.
|
|
187
|
+
*/
|
|
188
|
+
clear() {
|
|
189
|
+
this.cache.clear();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get the number of entries in the cache.
|
|
193
|
+
*/
|
|
194
|
+
get size() {
|
|
195
|
+
return this.cache.size;
|
|
196
|
+
}
|
|
197
|
+
computeLowercase(entity) {
|
|
198
|
+
return {
|
|
199
|
+
name: entity.name.toLowerCase(),
|
|
200
|
+
entityType: entity.entityType.toLowerCase(),
|
|
201
|
+
observations: entity.observations.map(o => o.toLowerCase()),
|
|
202
|
+
tags: entity.tags?.map(t => t.toLowerCase()) ?? [],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* RelationIndex provides O(1) lookup of relations by entity name.
|
|
208
|
+
*
|
|
209
|
+
* Maintains two separate indexes for efficient directional queries:
|
|
210
|
+
* - fromIndex: Map from source entity name to its outgoing relations
|
|
211
|
+
* - toIndex: Map from target entity name to its incoming relations
|
|
212
|
+
*
|
|
213
|
+
* This eliminates O(n) array scans when looking up relations for an entity.
|
|
214
|
+
*/
|
|
215
|
+
export class RelationIndex {
|
|
216
|
+
/** Index of relations by source (from) entity */
|
|
217
|
+
fromIndex = new Map();
|
|
218
|
+
/** Index of relations by target (to) entity */
|
|
219
|
+
toIndex = new Map();
|
|
220
|
+
/**
|
|
221
|
+
* Build the index from an array of relations.
|
|
222
|
+
* Clears any existing index data first.
|
|
223
|
+
*/
|
|
224
|
+
build(relations) {
|
|
225
|
+
this.fromIndex.clear();
|
|
226
|
+
this.toIndex.clear();
|
|
227
|
+
for (const relation of relations) {
|
|
228
|
+
this.addToIndexes(relation);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get all relations where the entity is the source (outgoing relations).
|
|
233
|
+
* Returns empty array if no relations found.
|
|
234
|
+
*/
|
|
235
|
+
getRelationsFrom(entityName) {
|
|
236
|
+
const relations = this.fromIndex.get(entityName);
|
|
237
|
+
return relations ? Array.from(relations) : [];
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get all relations where the entity is the target (incoming relations).
|
|
241
|
+
* Returns empty array if no relations found.
|
|
242
|
+
*/
|
|
243
|
+
getRelationsTo(entityName) {
|
|
244
|
+
const relations = this.toIndex.get(entityName);
|
|
245
|
+
return relations ? Array.from(relations) : [];
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get all relations involving the entity (both incoming and outgoing).
|
|
249
|
+
* Returns empty array if no relations found.
|
|
250
|
+
*/
|
|
251
|
+
getRelationsFor(entityName) {
|
|
252
|
+
const fromRelations = this.fromIndex.get(entityName);
|
|
253
|
+
const toRelations = this.toIndex.get(entityName);
|
|
254
|
+
// Combine sets to handle self-referential relations correctly
|
|
255
|
+
const combined = new Set();
|
|
256
|
+
if (fromRelations) {
|
|
257
|
+
for (const r of fromRelations) {
|
|
258
|
+
combined.add(r);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (toRelations) {
|
|
262
|
+
for (const r of toRelations) {
|
|
263
|
+
combined.add(r);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Array.from(combined);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Add a single relation to the index.
|
|
270
|
+
*/
|
|
271
|
+
add(relation) {
|
|
272
|
+
this.addToIndexes(relation);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Remove a relation from the index.
|
|
276
|
+
* Matches by from, to, and relationType.
|
|
277
|
+
*/
|
|
278
|
+
remove(relation) {
|
|
279
|
+
// Remove from fromIndex
|
|
280
|
+
const fromRelations = this.fromIndex.get(relation.from);
|
|
281
|
+
if (fromRelations) {
|
|
282
|
+
for (const r of fromRelations) {
|
|
283
|
+
if (r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType) {
|
|
284
|
+
fromRelations.delete(r);
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (fromRelations.size === 0) {
|
|
289
|
+
this.fromIndex.delete(relation.from);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Remove from toIndex
|
|
293
|
+
const toRelations = this.toIndex.get(relation.to);
|
|
294
|
+
if (toRelations) {
|
|
295
|
+
for (const r of toRelations) {
|
|
296
|
+
if (r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType) {
|
|
297
|
+
toRelations.delete(r);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (toRelations.size === 0) {
|
|
302
|
+
this.toIndex.delete(relation.to);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Remove all relations involving a specific entity.
|
|
308
|
+
* Returns the relations that were removed.
|
|
309
|
+
*/
|
|
310
|
+
removeAllForEntity(entityName) {
|
|
311
|
+
const removed = [];
|
|
312
|
+
// Remove outgoing relations
|
|
313
|
+
const fromRelations = this.fromIndex.get(entityName);
|
|
314
|
+
if (fromRelations) {
|
|
315
|
+
for (const r of fromRelations) {
|
|
316
|
+
removed.push(r);
|
|
317
|
+
// Also remove from toIndex
|
|
318
|
+
const toRels = this.toIndex.get(r.to);
|
|
319
|
+
if (toRels) {
|
|
320
|
+
toRels.delete(r);
|
|
321
|
+
if (toRels.size === 0) {
|
|
322
|
+
this.toIndex.delete(r.to);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
this.fromIndex.delete(entityName);
|
|
327
|
+
}
|
|
328
|
+
// Remove incoming relations
|
|
329
|
+
const toRelations = this.toIndex.get(entityName);
|
|
330
|
+
if (toRelations) {
|
|
331
|
+
for (const r of toRelations) {
|
|
332
|
+
// Skip self-referential relations (already handled above)
|
|
333
|
+
if (r.from === entityName)
|
|
334
|
+
continue;
|
|
335
|
+
removed.push(r);
|
|
336
|
+
// Also remove from fromIndex
|
|
337
|
+
const fromRels = this.fromIndex.get(r.from);
|
|
338
|
+
if (fromRels) {
|
|
339
|
+
fromRels.delete(r);
|
|
340
|
+
if (fromRels.size === 0) {
|
|
341
|
+
this.fromIndex.delete(r.from);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
this.toIndex.delete(entityName);
|
|
346
|
+
}
|
|
347
|
+
return removed;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Check if any relations exist for an entity.
|
|
351
|
+
*/
|
|
352
|
+
hasRelations(entityName) {
|
|
353
|
+
return this.fromIndex.has(entityName) || this.toIndex.has(entityName);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Get count of outgoing relations for an entity.
|
|
357
|
+
*/
|
|
358
|
+
getOutgoingCount(entityName) {
|
|
359
|
+
return this.fromIndex.get(entityName)?.size ?? 0;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get count of incoming relations for an entity.
|
|
363
|
+
*/
|
|
364
|
+
getIncomingCount(entityName) {
|
|
365
|
+
return this.toIndex.get(entityName)?.size ?? 0;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get total count of unique relations in the index.
|
|
369
|
+
*/
|
|
370
|
+
get size() {
|
|
371
|
+
// Count all relations in fromIndex (each relation appears exactly once there)
|
|
372
|
+
let count = 0;
|
|
373
|
+
for (const relations of this.fromIndex.values()) {
|
|
374
|
+
count += relations.size;
|
|
375
|
+
}
|
|
376
|
+
return count;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Clear all entries from the index.
|
|
380
|
+
*/
|
|
381
|
+
clear() {
|
|
382
|
+
this.fromIndex.clear();
|
|
383
|
+
this.toIndex.clear();
|
|
384
|
+
}
|
|
385
|
+
addToIndexes(relation) {
|
|
386
|
+
// Add to fromIndex
|
|
387
|
+
let fromSet = this.fromIndex.get(relation.from);
|
|
388
|
+
if (!fromSet) {
|
|
389
|
+
fromSet = new Set();
|
|
390
|
+
this.fromIndex.set(relation.from, fromSet);
|
|
391
|
+
}
|
|
392
|
+
fromSet.add(relation);
|
|
393
|
+
// Add to toIndex
|
|
394
|
+
let toSet = this.toIndex.get(relation.to);
|
|
395
|
+
if (!toSet) {
|
|
396
|
+
toSet = new Set();
|
|
397
|
+
this.toIndex.set(relation.to, toSet);
|
|
398
|
+
}
|
|
399
|
+
toSet.add(relation);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Inverted index mapping observation keywords to entity names.
|
|
404
|
+
* Enables O(1) lookup for 'which entities mention word X?' queries.
|
|
405
|
+
* Words are normalized to lowercase and split on whitespace/punctuation.
|
|
406
|
+
*/
|
|
407
|
+
export class ObservationIndex {
|
|
408
|
+
index = new Map();
|
|
409
|
+
entityObservations = new Map();
|
|
410
|
+
/**
|
|
411
|
+
* Add an entity's observations to the index.
|
|
412
|
+
* Tokenizes observations into words and creates reverse mapping.
|
|
413
|
+
*
|
|
414
|
+
* @param entityName - Name of the entity
|
|
415
|
+
* @param observations - Array of observation strings
|
|
416
|
+
*/
|
|
417
|
+
add(entityName, observations) {
|
|
418
|
+
const entityWords = new Set();
|
|
419
|
+
for (const observation of observations) {
|
|
420
|
+
const words = this.tokenize(observation);
|
|
421
|
+
for (const word of words) {
|
|
422
|
+
entityWords.add(word);
|
|
423
|
+
if (!this.index.has(word)) {
|
|
424
|
+
this.index.set(word, new Set());
|
|
425
|
+
}
|
|
426
|
+
this.index.get(word).add(entityName);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
this.entityObservations.set(entityName, entityWords);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Remove an entity from the index.
|
|
433
|
+
* Cleans up all word mappings for this entity.
|
|
434
|
+
*
|
|
435
|
+
* @param entityName - Name of the entity to remove
|
|
436
|
+
*/
|
|
437
|
+
remove(entityName) {
|
|
438
|
+
const words = this.entityObservations.get(entityName);
|
|
439
|
+
if (!words)
|
|
440
|
+
return;
|
|
441
|
+
for (const word of words) {
|
|
442
|
+
const entities = this.index.get(word);
|
|
443
|
+
if (entities) {
|
|
444
|
+
entities.delete(entityName);
|
|
445
|
+
if (entities.size === 0) {
|
|
446
|
+
this.index.delete(word);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
this.entityObservations.delete(entityName);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get all entities that have observations containing the given word.
|
|
454
|
+
* Word matching is case-insensitive.
|
|
455
|
+
*
|
|
456
|
+
* @param word - Word to search for
|
|
457
|
+
* @returns Set of entity names containing this word
|
|
458
|
+
*/
|
|
459
|
+
getEntitiesWithWord(word) {
|
|
460
|
+
return this.index.get(word.toLowerCase()) ?? new Set();
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Get all entities that have observations containing ANY of the given words (union).
|
|
464
|
+
*
|
|
465
|
+
* @param words - Array of words to search for
|
|
466
|
+
* @returns Set of entity names containing any of the words
|
|
467
|
+
*/
|
|
468
|
+
getEntitiesWithAnyWord(words) {
|
|
469
|
+
const result = new Set();
|
|
470
|
+
for (const word of words) {
|
|
471
|
+
const entities = this.getEntitiesWithWord(word);
|
|
472
|
+
for (const entity of entities) {
|
|
473
|
+
result.add(entity);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get all entities that have observations containing ALL of the given words (intersection).
|
|
480
|
+
*
|
|
481
|
+
* @param words - Array of words that must all be present
|
|
482
|
+
* @returns Set of entity names containing all of the words
|
|
483
|
+
*/
|
|
484
|
+
getEntitiesWithAllWords(words) {
|
|
485
|
+
if (words.length === 0)
|
|
486
|
+
return new Set();
|
|
487
|
+
let result = new Set(this.getEntitiesWithWord(words[0]));
|
|
488
|
+
for (let i = 1; i < words.length && result.size > 0; i++) {
|
|
489
|
+
const wordEntities = this.getEntitiesWithWord(words[i]);
|
|
490
|
+
result = new Set([...result].filter(e => wordEntities.has(e)));
|
|
491
|
+
}
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Clear all entries from the index.
|
|
496
|
+
*/
|
|
497
|
+
clear() {
|
|
498
|
+
this.index.clear();
|
|
499
|
+
this.entityObservations.clear();
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Get statistics about the index.
|
|
503
|
+
*
|
|
504
|
+
* @returns Object with wordCount and entityCount
|
|
505
|
+
*/
|
|
506
|
+
getStats() {
|
|
507
|
+
return {
|
|
508
|
+
wordCount: this.index.size,
|
|
509
|
+
entityCount: this.entityObservations.size,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Tokenize text into searchable words.
|
|
514
|
+
* Normalizes to lowercase, splits on non-alphanumeric characters,
|
|
515
|
+
* and filters out words less than 2 characters.
|
|
516
|
+
*
|
|
517
|
+
* @param text - Text to tokenize
|
|
518
|
+
* @returns Array of normalized words
|
|
519
|
+
*/
|
|
520
|
+
tokenize(text) {
|
|
521
|
+
return text
|
|
522
|
+
.toLowerCase()
|
|
523
|
+
.split(/[^a-z0-9]+/)
|
|
524
|
+
.filter(word => word.length >= 2);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging utility for the Memory MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent log formatting with levels: debug, info, warn, error
|
|
5
|
+
*/
|
|
6
|
+
export declare const logger: {
|
|
7
|
+
/**
|
|
8
|
+
* Debug level logging (verbose, for development)
|
|
9
|
+
*/
|
|
10
|
+
debug: (msg: string, ...args: unknown[]) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Info level logging (general informational messages)
|
|
13
|
+
*/
|
|
14
|
+
info: (msg: string, ...args: unknown[]) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Warning level logging (warnings that don't prevent operation)
|
|
17
|
+
*/
|
|
18
|
+
warn: (msg: string, ...args: unknown[]) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Error level logging (errors that affect functionality)
|
|
21
|
+
*/
|
|
22
|
+
error: (msg: string, ...args: unknown[]) => void;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,MAAM;IACjB;;OAEG;iBACU,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;IAM9C;;OAEG;gBACS,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;IAI7C;;OAEG;gBACS,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;IAI7C;;OAEG;iBACU,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;CAG/C,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Phase 9B: Utilities for long-running operations with progress tracking
|
|
5
|
+
* and cancellation support.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/operationUtils
|
|
8
|
+
*/
|
|
9
|
+
import type { ProgressCallback } from './taskScheduler.js';
|
|
10
|
+
/**
|
|
11
|
+
* Check if an operation has been cancelled via AbortSignal.
|
|
12
|
+
* Throws OperationCancelledError if the signal is aborted.
|
|
13
|
+
*
|
|
14
|
+
* @param signal - Optional AbortSignal to check
|
|
15
|
+
* @param operation - Optional operation name for error message
|
|
16
|
+
* @throws OperationCancelledError if signal is aborted
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* for (const item of items) {
|
|
21
|
+
* checkCancellation(options?.signal, 'batch processing');
|
|
22
|
+
* await processItem(item);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkCancellation(signal?: AbortSignal, operation?: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Create a throttled progress reporter to avoid excessive callback invocations.
|
|
29
|
+
* Returns undefined if no callback is provided.
|
|
30
|
+
*
|
|
31
|
+
* @param callback - Optional progress callback to throttle
|
|
32
|
+
* @param throttleMs - Minimum time between callbacks (default: 100ms)
|
|
33
|
+
* @returns Throttled callback or undefined
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const reportProgress = createProgressReporter(options?.onProgress, 50);
|
|
38
|
+
* for (let i = 0; i < total; i++) {
|
|
39
|
+
* reportProgress?.({ completed: i, total, percentage: (i / total) * 100 });
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function createProgressReporter(callback?: ProgressCallback, throttleMs?: number): ProgressCallback | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Create a progress object for reporting.
|
|
46
|
+
*
|
|
47
|
+
* @param completed - Number of completed items
|
|
48
|
+
* @param total - Total number of items
|
|
49
|
+
* @param currentTaskId - Optional current task identifier
|
|
50
|
+
* @returns Progress object suitable for ProgressCallback
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* reportProgress?.(createProgress(50, 100, 'processing entities'));
|
|
55
|
+
* // { completed: 50, total: 100, percentage: 50, currentTaskId: 'processing entities' }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function createProgress(completed: number, total: number, currentTaskId?: string): {
|
|
59
|
+
completed: number;
|
|
60
|
+
total: number;
|
|
61
|
+
percentage: number;
|
|
62
|
+
currentTaskId?: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Phase definition for executeWithPhases.
|
|
66
|
+
*/
|
|
67
|
+
export interface PhaseDefinition<T> {
|
|
68
|
+
/** Phase name (used for progress reporting and cancellation error messages) */
|
|
69
|
+
name: string;
|
|
70
|
+
/** Weight of this phase relative to others (higher = more of total progress) */
|
|
71
|
+
weight: number;
|
|
72
|
+
/** Executor function that performs the phase work */
|
|
73
|
+
execute: (phaseProgress: (pct: number) => void) => Promise<T>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Execute an operation with multiple distinct phases.
|
|
77
|
+
* Useful when an operation has multiple distinct phases with different weights.
|
|
78
|
+
*
|
|
79
|
+
* @param phases - Array of phase definitions with weight and executor
|
|
80
|
+
* @param onProgress - Optional progress callback
|
|
81
|
+
* @param signal - Optional abort signal
|
|
82
|
+
* @returns Array of results from each phase
|
|
83
|
+
* @throws OperationCancelledError if cancelled during any phase
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const [parseResult, processResult, saveResult] = await executeWithPhases([
|
|
88
|
+
* { name: 'parsing', weight: 20, execute: () => parseData() },
|
|
89
|
+
* { name: 'processing', weight: 60, execute: () => processEntities() },
|
|
90
|
+
* { name: 'saving', weight: 20, execute: () => saveResults() },
|
|
91
|
+
* ], options?.onProgress, options?.signal);
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function executeWithPhases<T>(phases: PhaseDefinition<T>[], onProgress?: ProgressCallback, signal?: AbortSignal): Promise<T[]>;
|
|
95
|
+
/**
|
|
96
|
+
* Execute an operation in batches with progress tracking and cancellation support.
|
|
97
|
+
*
|
|
98
|
+
* @param items - Array of items to process
|
|
99
|
+
* @param batchSize - Size of each batch
|
|
100
|
+
* @param processBatch - Function to process each batch
|
|
101
|
+
* @param onProgress - Optional progress callback
|
|
102
|
+
* @param signal - Optional abort signal
|
|
103
|
+
* @param operationName - Optional operation name for cancellation error
|
|
104
|
+
* @returns Array of results from all batches
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const results = await processBatchesWithProgress(
|
|
109
|
+
* entities,
|
|
110
|
+
* 100,
|
|
111
|
+
* async (batch) => {
|
|
112
|
+
* for (const entity of batch) {
|
|
113
|
+
* await saveEntity(entity);
|
|
114
|
+
* }
|
|
115
|
+
* return batch.length;
|
|
116
|
+
* },
|
|
117
|
+
* options?.onProgress,
|
|
118
|
+
* options?.signal,
|
|
119
|
+
* 'createEntities'
|
|
120
|
+
* );
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export declare function processBatchesWithProgress<T, R>(items: T[], batchSize: number, processBatch: (batch: T[], batchIndex: number) => Promise<R>, onProgress?: ProgressCallback, signal?: AbortSignal, operationName?: string): Promise<R[]>;
|
|
124
|
+
//# sourceMappingURL=operationUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operationUtils.d.ts","sourceRoot":"","sources":["../../src/utils/operationUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAIhF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,UAAU,GAAE,MAAY,GACvB,gBAAgB,GAAG,SAAS,CAa9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAOlF;AAED;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,OAAO,EAAE,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,EAC5B,UAAU,CAAC,EAAE,gBAAgB,EAC7B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,EAAE,CAAC,CAkCd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,0BAA0B,CAAC,CAAC,EAAE,CAAC,EACnD,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC5D,UAAU,CAAC,EAAE,gBAAgB,EAC7B,MAAM,CAAC,EAAE,WAAW,EACpB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,CAAC,EAAE,CAAC,CAqBd"}
|