@cmdoss/memwal-sdk 0.6.2 → 0.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.
Files changed (247) hide show
  1. package/ARCHITECTURE.md +547 -547
  2. package/BENCHMARKS.md +238 -238
  3. package/README.md +310 -181
  4. package/dist/ai-sdk/tools.d.ts +2 -2
  5. package/dist/ai-sdk/tools.js +2 -2
  6. package/dist/client/ClientMemoryManager.js +2 -2
  7. package/dist/client/ClientMemoryManager.js.map +1 -1
  8. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  9. package/dist/client/SimplePDWClient.d.ts +29 -1
  10. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  11. package/dist/client/SimplePDWClient.js +45 -13
  12. package/dist/client/SimplePDWClient.js.map +1 -1
  13. package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
  14. package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
  15. package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
  16. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  17. package/dist/client/namespaces/MemoryNamespace.js +272 -39
  18. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  19. package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
  20. package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
  21. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  22. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  23. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
  24. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  25. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  26. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  27. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  28. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  29. package/dist/config/ConfigurationHelper.js +61 -61
  30. package/dist/config/defaults.js +2 -2
  31. package/dist/config/defaults.js.map +1 -1
  32. package/dist/graph/GraphService.js +21 -21
  33. package/dist/graph/GraphService.js.map +1 -1
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  39. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  40. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  41. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  42. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  43. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  44. package/dist/infrastructure/seal/SealService.js +36 -34
  45. package/dist/infrastructure/seal/SealService.js.map +1 -1
  46. package/dist/langchain/createPDWRAG.js +30 -30
  47. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  48. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  49. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  50. package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
  51. package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
  52. package/dist/retrieval/MemoryRetrievalService.js +44 -4
  53. package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
  54. package/dist/services/CapabilityService.d.ts.map +1 -1
  55. package/dist/services/CapabilityService.js +30 -14
  56. package/dist/services/CapabilityService.js.map +1 -1
  57. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  58. package/dist/services/CrossContextPermissionService.js +9 -7
  59. package/dist/services/CrossContextPermissionService.js.map +1 -1
  60. package/dist/services/EmbeddingService.d.ts +28 -1
  61. package/dist/services/EmbeddingService.d.ts.map +1 -1
  62. package/dist/services/EmbeddingService.js +54 -0
  63. package/dist/services/EmbeddingService.js.map +1 -1
  64. package/dist/services/EncryptionService.d.ts.map +1 -1
  65. package/dist/services/EncryptionService.js +6 -5
  66. package/dist/services/EncryptionService.js.map +1 -1
  67. package/dist/services/GeminiAIService.js +309 -309
  68. package/dist/services/IndexManager.d.ts +5 -1
  69. package/dist/services/IndexManager.d.ts.map +1 -1
  70. package/dist/services/IndexManager.js +17 -40
  71. package/dist/services/IndexManager.js.map +1 -1
  72. package/dist/services/QueryService.js +1 -1
  73. package/dist/services/QueryService.js.map +1 -1
  74. package/dist/services/StorageService.d.ts +11 -0
  75. package/dist/services/StorageService.d.ts.map +1 -1
  76. package/dist/services/StorageService.js +73 -10
  77. package/dist/services/StorageService.js.map +1 -1
  78. package/dist/services/TransactionService.d.ts +20 -0
  79. package/dist/services/TransactionService.d.ts.map +1 -1
  80. package/dist/services/TransactionService.js +43 -0
  81. package/dist/services/TransactionService.js.map +1 -1
  82. package/dist/services/ViewService.js +2 -2
  83. package/dist/services/ViewService.js.map +1 -1
  84. package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
  85. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  86. package/dist/services/storage/QuiltBatchManager.js +410 -20
  87. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  88. package/dist/services/storage/index.d.ts +1 -1
  89. package/dist/services/storage/index.d.ts.map +1 -1
  90. package/dist/services/storage/index.js.map +1 -1
  91. package/dist/utils/LRUCache.d.ts +106 -0
  92. package/dist/utils/LRUCache.d.ts.map +1 -0
  93. package/dist/utils/LRUCache.js +281 -0
  94. package/dist/utils/LRUCache.js.map +1 -0
  95. package/dist/utils/index.d.ts +1 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +2 -0
  98. package/dist/utils/index.js.map +1 -1
  99. package/dist/utils/memoryIndexOnChain.d.ts +212 -0
  100. package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
  101. package/dist/utils/memoryIndexOnChain.js +312 -0
  102. package/dist/utils/memoryIndexOnChain.js.map +1 -0
  103. package/dist/utils/rebuildIndexNode.d.ts +29 -0
  104. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  105. package/dist/utils/rebuildIndexNode.js +366 -98
  106. package/dist/utils/rebuildIndexNode.js.map +1 -1
  107. package/dist/vector/HnswWasmService.d.ts +20 -5
  108. package/dist/vector/HnswWasmService.d.ts.map +1 -1
  109. package/dist/vector/HnswWasmService.js +73 -40
  110. package/dist/vector/HnswWasmService.js.map +1 -1
  111. package/dist/vector/IHnswService.d.ts +10 -1
  112. package/dist/vector/IHnswService.d.ts.map +1 -1
  113. package/dist/vector/IHnswService.js.map +1 -1
  114. package/dist/vector/NodeHnswService.d.ts +16 -0
  115. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  116. package/dist/vector/NodeHnswService.js +84 -5
  117. package/dist/vector/NodeHnswService.js.map +1 -1
  118. package/dist/vector/createHnswService.d.ts +1 -1
  119. package/dist/vector/createHnswService.js +1 -1
  120. package/dist/vector/index.d.ts +1 -1
  121. package/dist/vector/index.js +1 -1
  122. package/package.json +157 -157
  123. package/src/access/PermissionService.ts +635 -635
  124. package/src/aggregation/AggregationService.ts +389 -389
  125. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  126. package/src/ai-sdk/index.ts +65 -65
  127. package/src/ai-sdk/tools.ts +460 -460
  128. package/src/ai-sdk/types.ts +404 -404
  129. package/src/batch/BatchManager.ts +597 -597
  130. package/src/batch/BatchingService.ts +429 -429
  131. package/src/batch/MemoryProcessingCache.ts +492 -492
  132. package/src/batch/index.ts +30 -30
  133. package/src/browser.ts +200 -200
  134. package/src/client/ClientMemoryManager.ts +987 -987
  135. package/src/client/PersonalDataWallet.ts +345 -345
  136. package/src/client/SimplePDWClient.ts +1289 -1222
  137. package/src/client/factory.ts +154 -154
  138. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  139. package/src/client/namespaces/BatchNamespace.ts +356 -356
  140. package/src/client/namespaces/CacheNamespace.ts +123 -123
  141. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  142. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  143. package/src/client/namespaces/ContextNamespace.ts +297 -297
  144. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  145. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  146. package/src/client/namespaces/GraphNamespace.ts +468 -468
  147. package/src/client/namespaces/IndexNamespace.ts +361 -361
  148. package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
  149. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  150. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  151. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  152. package/src/client/namespaces/StorageNamespace.ts +458 -458
  153. package/src/client/namespaces/TxNamespace.ts +260 -260
  154. package/src/client/namespaces/WalletNamespace.ts +243 -243
  155. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  156. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
  157. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  158. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  159. package/src/client/namespaces/consolidated/index.ts +39 -39
  160. package/src/client/signers/KeypairSigner.ts +108 -108
  161. package/src/client/signers/UnifiedSigner.ts +110 -110
  162. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  163. package/src/client/signers/index.ts +26 -26
  164. package/src/config/ConfigurationHelper.ts +412 -412
  165. package/src/config/defaults.ts +51 -51
  166. package/src/config/index.ts +8 -8
  167. package/src/config/validation.ts +70 -70
  168. package/src/core/index.ts +14 -14
  169. package/src/core/interfaces/IService.ts +307 -307
  170. package/src/core/interfaces/index.ts +8 -8
  171. package/src/core/types/capability.ts +297 -297
  172. package/src/core/types/index.ts +870 -870
  173. package/src/core/types/wallet.ts +270 -270
  174. package/src/core/types.ts +9 -9
  175. package/src/core/wallet.ts +222 -222
  176. package/src/embedding/index.ts +19 -19
  177. package/src/embedding/types.ts +357 -357
  178. package/src/errors/index.ts +602 -602
  179. package/src/errors/recovery.ts +461 -461
  180. package/src/errors/validation.ts +567 -567
  181. package/src/generated/pdw/capability.ts +319 -319
  182. package/src/graph/GraphService.ts +887 -887
  183. package/src/graph/KnowledgeGraphManager.ts +728 -728
  184. package/src/graph/index.ts +25 -25
  185. package/src/index.ts +498 -474
  186. package/src/infrastructure/index.ts +22 -22
  187. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  188. package/src/infrastructure/seal/SealService.ts +613 -615
  189. package/src/infrastructure/seal/index.ts +9 -9
  190. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  191. package/src/infrastructure/sui/SuiService.ts +888 -888
  192. package/src/infrastructure/sui/index.ts +9 -9
  193. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  194. package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
  195. package/src/infrastructure/walrus/index.ts +9 -9
  196. package/src/langchain/PDWEmbeddings.ts +145 -145
  197. package/src/langchain/PDWVectorStore.ts +456 -456
  198. package/src/langchain/createPDWRAG.ts +303 -303
  199. package/src/langchain/index.ts +47 -47
  200. package/src/permissions/ConsentRepository.browser.ts +249 -249
  201. package/src/permissions/ConsentRepository.ts +364 -364
  202. package/src/pipeline/MemoryPipeline.ts +862 -862
  203. package/src/pipeline/PipelineManager.ts +683 -683
  204. package/src/pipeline/index.ts +26 -26
  205. package/src/retrieval/AdvancedSearchService.ts +629 -629
  206. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  207. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  208. package/src/retrieval/MemoryRetrievalService.ts +904 -830
  209. package/src/retrieval/index.ts +42 -42
  210. package/src/services/BatchService.ts +352 -352
  211. package/src/services/CapabilityService.ts +464 -448
  212. package/src/services/ClassifierService.ts +465 -465
  213. package/src/services/CrossContextPermissionService.ts +486 -484
  214. package/src/services/EmbeddingService.ts +771 -706
  215. package/src/services/EncryptionService.ts +712 -711
  216. package/src/services/GeminiAIService.ts +753 -753
  217. package/src/services/IndexManager.ts +977 -1004
  218. package/src/services/MemoryIndexService.ts +1003 -1003
  219. package/src/services/MemoryService.ts +369 -369
  220. package/src/services/QueryService.ts +890 -890
  221. package/src/services/StorageService.ts +1182 -1111
  222. package/src/services/TransactionService.ts +838 -790
  223. package/src/services/VectorService.ts +462 -462
  224. package/src/services/ViewService.ts +484 -484
  225. package/src/services/index.ts +25 -25
  226. package/src/services/storage/BlobAttributesManager.ts +333 -333
  227. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  228. package/src/services/storage/MemorySearchManager.ts +387 -387
  229. package/src/services/storage/QuiltBatchManager.ts +1130 -660
  230. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  231. package/src/services/storage/WalrusStorageManager.ts +287 -287
  232. package/src/services/storage/index.ts +57 -52
  233. package/src/types/index.ts +13 -13
  234. package/src/utils/LRUCache.ts +378 -0
  235. package/src/utils/index.ts +76 -68
  236. package/src/utils/memoryIndexOnChain.ts +507 -0
  237. package/src/utils/rebuildIndex.ts +290 -290
  238. package/src/utils/rebuildIndexNode.ts +771 -424
  239. package/src/vector/BrowserHnswIndexService.ts +758 -758
  240. package/src/vector/HnswWasmService.ts +731 -679
  241. package/src/vector/IHnswService.ts +233 -224
  242. package/src/vector/NodeHnswService.ts +833 -735
  243. package/src/vector/VectorManager.ts +478 -478
  244. package/src/vector/createHnswService.ts +135 -135
  245. package/src/vector/index.ts +56 -56
  246. package/src/wallet/ContextWalletService.ts +656 -656
  247. package/src/wallet/MainWalletService.ts +317 -317
@@ -1,1004 +1,1004 @@
1
- /**
2
- * MemoryIndexService - Enhanced Memory Indexing and Vector Search
3
- *
4
- * Hybrid HNSW-powered memory indexing service providing:
5
- * - O(log N) vector similarity search performance
6
- * - Advanced clustering and graph-based memory organization
7
- * - Intelligent semantic search with relevance scoring
8
- * - Dynamic index optimization and parameter tuning
9
- * - Multi-dimensional vector space analysis
10
- *
11
- * Automatically selects the appropriate HNSW implementation:
12
- * - Browser: hnswlib-wasm (WebAssembly)
13
- * - Node.js: hnswlib-node (native bindings)
14
- */
15
-
16
- import { createHnswService, isBrowser, isNode } from '../vector/createHnswService';
17
- import type { IHnswService, IHnswSearchOptions } from '../vector/IHnswService';
18
- import { EmbeddingService } from './EmbeddingService';
19
- import { StorageService, type MemoryMetadata } from './StorageService';
20
-
21
- export interface MemoryIndexEntry {
22
- memoryId: string;
23
- blobId: string;
24
- vectorId: number;
25
- embedding: number[];
26
- metadata: MemoryMetadata;
27
- indexedAt: Date;
28
- lastAccessed?: Date;
29
- }
30
-
31
- export interface MemoryIndexOptions {
32
- maxElements?: number;
33
- dimension?: number;
34
- efConstruction?: number;
35
- m?: number;
36
- batchSize?: number;
37
- autoFlushInterval?: number;
38
- /** Pre-initialized HNSW service instance (shared singleton) */
39
- hnswService?: IHnswService;
40
- }
41
-
42
- export interface MemorySearchQuery {
43
- query?: string;
44
- vector?: number[];
45
- userAddress: string;
46
- k?: number;
47
- threshold?: number;
48
-
49
- // Memory-specific filters
50
- categories?: string[];
51
- dateRange?: {
52
- start?: Date;
53
- end?: Date;
54
- };
55
- importanceRange?: {
56
- min?: number;
57
- max?: number;
58
- };
59
- tags?: string[];
60
- includeContent?: boolean;
61
-
62
- // Enhanced search features
63
- searchMode?: 'semantic' | 'hybrid' | 'exact'; // Search strategy
64
- boostRecent?: boolean; // Boost recently created memories
65
- diversityFactor?: number; // Result diversity (0-1)
66
- }
67
-
68
- export interface MemorySearchResult {
69
- memoryId: string;
70
- blobId: string;
71
- metadata: MemoryMetadata;
72
- similarity: number;
73
- relevanceScore: number;
74
- content?: string | Uint8Array;
75
- extractedAt?: Date;
76
- embedding?: number[]; // Optional embedding vector for advanced similarity operations
77
- clusterInfo?: {
78
- clusterId: number;
79
- clusterCenter: number[];
80
- intraClusterSimilarity: number;
81
- };
82
- }
83
-
84
- /**
85
- * Memory-focused indexing service providing high-level memory operations
86
- * Uses hybrid HNSW implementation (browser: wasm, node: native) for optimal performance
87
- */
88
- export class MemoryIndexService {
89
- private hnswService: IHnswService | null = null;
90
- private hnswServicePromise: Promise<IHnswService> | null = null;
91
- private embeddingService?: EmbeddingService;
92
- private storageService?: StorageService;
93
- private memoryIndex = new Map<string, Map<string, MemoryIndexEntry>>(); // userAddress -> memoryId -> entry
94
- private nextMemoryId = 1;
95
- private options: MemoryIndexOptions;
96
-
97
- // Performance tracking
98
- private indexStats = new Map<string, {
99
- totalVectors: number;
100
- avgSimilarity: number;
101
- searchLatency: number[];
102
- lastOptimized: Date;
103
- }>();
104
-
105
- constructor(
106
- storageService?: StorageService,
107
- options: MemoryIndexOptions = {}
108
- ) {
109
- this.storageService = storageService;
110
- this.options = options;
111
-
112
- // Use pre-initialized HNSW service if provided (shared singleton pattern)
113
- if (options.hnswService) {
114
- this.hnswService = options.hnswService;
115
- console.log('✅ MemoryIndexService using shared HNSW service instance');
116
- } else {
117
- // Initialize HNSW service asynchronously using factory
118
- this.hnswServicePromise = this.initializeHnswService();
119
-
120
- const envType = isBrowser() ? 'browser (hnswlib-wasm)' : isNode() ? 'Node.js (hnswlib-node)' : 'unknown';
121
- console.log(`✅ MemoryIndexService initializing with hybrid HNSW (${envType})`);
122
- }
123
-
124
- console.log(` Max elements: ${options.maxElements || 10000}`);
125
- console.log(` Embedding dimension: ${options.dimension || 3072}`);
126
- console.log(` HNSW parameters: M=${options.m || 16}, efConstruction=${options.efConstruction || 200}`);
127
- }
128
-
129
- /**
130
- * Initialize HNSW service using factory (auto-detects environment)
131
- */
132
- private async initializeHnswService(): Promise<IHnswService> {
133
- try {
134
- const service = await createHnswService({
135
- indexConfig: {
136
- dimension: this.options.dimension || 3072,
137
- maxElements: this.options.maxElements || 10000,
138
- efConstruction: this.options.efConstruction || 200,
139
- m: this.options.m || 16
140
- },
141
- batchConfig: {
142
- maxBatchSize: this.options.batchSize || 100,
143
- batchDelayMs: this.options.autoFlushInterval || 5000
144
- }
145
- });
146
-
147
- this.hnswService = service;
148
- console.log('✅ HNSW service initialized successfully');
149
- return service;
150
- } catch (error) {
151
- console.error('❌ Failed to initialize HNSW service:', error);
152
- throw error;
153
- }
154
- }
155
-
156
- /**
157
- * Get HNSW service (waits for initialization if needed)
158
- */
159
- private async getHnswService(): Promise<IHnswService> {
160
- if (this.hnswService) {
161
- return this.hnswService;
162
- }
163
- if (this.hnswServicePromise) {
164
- return this.hnswServicePromise;
165
- }
166
- throw new Error('HNSW service not initialized');
167
- }
168
-
169
- /**
170
- * Initialize with embedding service
171
- */
172
- initialize(embeddingService: EmbeddingService, storageService?: StorageService) {
173
- this.embeddingService = embeddingService;
174
- if (storageService) {
175
- this.storageService = storageService;
176
- }
177
- console.log('✅ MemoryIndexService: Embedding service connected');
178
- }
179
-
180
- /**
181
- * Index a memory with its content, metadata, and vector embedding
182
- *
183
- * @param userAddress - User's wallet address
184
- * @param memoryId - Unique memory identifier
185
- * @param blobId - Walrus blob ID for the memory
186
- * @param content - Memory content (stored in index only if not encrypted)
187
- * @param metadata - Memory metadata
188
- * @param embedding - Pre-computed embedding vector (optional)
189
- * @param options - Indexing options
190
- * @param options.isEncrypted - If true, content will NOT be stored in index (security)
191
- */
192
- async indexMemory(
193
- userAddress: string,
194
- memoryId: string,
195
- blobId: string,
196
- content: string,
197
- metadata: MemoryMetadata,
198
- embedding?: number[],
199
- options?: { isEncrypted?: boolean }
200
- ): Promise<{ vectorId: number; indexed: boolean }> {
201
- try {
202
- console.log(`📊 Indexing memory ${memoryId} for user ${userAddress}`);
203
-
204
- // Generate embedding if not provided
205
- let memoryEmbedding = embedding;
206
- if (!memoryEmbedding && this.embeddingService) {
207
- const embeddingResult = await this.embeddingService.embedText({ text: content });
208
- memoryEmbedding = embeddingResult.vector;
209
- }
210
-
211
- if (!memoryEmbedding) {
212
- throw new Error('No embedding provided and no embedding service available');
213
- }
214
-
215
- // Generate vector ID
216
- const vectorId = this.nextMemoryId++;
217
-
218
- // Add to HNSW index with batching
219
- // Option A+: Store content in index ONLY when encryption is OFF (security consideration)
220
- const hnswService = await this.getHnswService();
221
- const isEncrypted = options?.isEncrypted ?? false;
222
-
223
- const vectorMetadata: Record<string, unknown> = {
224
- memoryId,
225
- blobId,
226
- category: metadata.category,
227
- topic: metadata.topic,
228
- importance: metadata.importance,
229
- contentType: metadata.contentType,
230
- createdTimestamp: metadata.createdTimestamp,
231
- customMetadata: metadata.customMetadata
232
- };
233
-
234
- // Only store content if NOT encrypted (privacy-safe)
235
- if (!isEncrypted && content) {
236
- vectorMetadata.content = content;
237
- console.log(' 💾 Content stored in local index (encryption OFF)');
238
- } else if (isEncrypted) {
239
- console.log(' 🔒 Content NOT stored in index (encryption ON - security)');
240
- }
241
-
242
- await hnswService.addVector(
243
- userAddress,
244
- vectorId,
245
- memoryEmbedding,
246
- vectorMetadata
247
- );
248
-
249
- // Update performance statistics
250
- this.updateIndexStats(userAddress);
251
-
252
- // Store in memory index
253
- if (!this.memoryIndex.has(userAddress)) {
254
- this.memoryIndex.set(userAddress, new Map());
255
- }
256
-
257
- const indexEntry: MemoryIndexEntry = {
258
- memoryId,
259
- blobId,
260
- vectorId,
261
- embedding: memoryEmbedding,
262
- metadata,
263
- indexedAt: new Date()
264
- };
265
-
266
- this.memoryIndex.get(userAddress)!.set(memoryId, indexEntry);
267
-
268
- console.log(`✅ Memory indexed: ${memoryId} (vector ${vectorId})`);
269
- console.log(` Category: ${metadata.category}`);
270
- console.log(` Importance: ${metadata.importance}`);
271
- console.log(` Embedding dimension: ${memoryEmbedding.length}`);
272
-
273
- return { vectorId, indexed: true };
274
-
275
- } catch (error) {
276
- console.error('❌ Failed to index memory:', error);
277
- throw error;
278
- }
279
- }
280
-
281
- /**
282
- * Enhanced memory search using native HNSW with advanced features
283
- * Supports semantic search, metadata filtering, and intelligent relevance scoring
284
- */
285
- async searchMemories(query: MemorySearchQuery): Promise<MemorySearchResult[]> {
286
- const startTime = performance.now();
287
-
288
- try {
289
- console.log(`🔍 Memory search for user ${query.userAddress}`);
290
- console.log(` Query: "${query.query || 'vector search'}"`);
291
- console.log(` Mode: ${query.searchMode || 'semantic'}, K: ${query.k || 10}`);
292
-
293
- // Generate query vector if needed
294
- let queryVector = query.vector;
295
- if (!queryVector && query.query && this.embeddingService) {
296
- const embeddingResult = await this.embeddingService.embedText({ text: query.query });
297
- queryVector = embeddingResult.vector;
298
- }
299
-
300
- if (!queryVector) {
301
- throw new Error('No query vector provided and no query text with embedding service');
302
- }
303
-
304
- const userMemories = this.memoryIndex.get(query.userAddress);
305
- if (!userMemories || userMemories.size === 0) {
306
- console.log(' No memories found for user');
307
- return [];
308
- }
309
-
310
- // Configure search parameters based on query mode
311
- const searchK = query.k || 10;
312
- const efSearch = query.searchMode === 'exact' ? searchK * 4 :
313
- query.searchMode === 'hybrid' ? searchK * 2 : searchK;
314
-
315
- // Perform HNSW search using native implementation
316
- const hnswService = await this.getHnswService();
317
- const hnswResults = await hnswService.search(
318
- query.userAddress,
319
- queryVector,
320
- {
321
- k: Math.min(searchK * 3, 100), // Get more candidates for post-filtering
322
- ef: efSearch,
323
- filter: this.createMetadataFilter(query)
324
- }
325
- );
326
-
327
- // Convert to memory search results with enhanced scoring
328
- const results: MemorySearchResult[] = [];
329
-
330
- for (const result of hnswResults) {
331
- const vectorId = result.vectorId;
332
- const distance = result.distance;
333
- // Calculate similarity from distance (cosine distance: similarity = 1 - distance)
334
- const similarity = result.score;
335
-
336
- // Skip results below threshold
337
- if (query.threshold && similarity < query.threshold) {
338
- continue;
339
- }
340
-
341
- // Find memory entry by vector ID
342
- const memoryEntry = Array.from(userMemories.values()).find(entry => entry.vectorId === vectorId);
343
- if (!memoryEntry) continue;
344
-
345
- // Enhanced relevance scoring
346
- let relevanceScore = this.calculateAdvancedRelevanceScore(
347
- similarity,
348
- memoryEntry.metadata,
349
- query,
350
- queryVector,
351
- memoryEntry.embedding || []
352
- );
353
-
354
- // Apply recency boost if requested
355
- if (query.boostRecent) {
356
- const recencyBoost = this.calculateRecencyBoost(memoryEntry.metadata.createdTimestamp || 0);
357
- relevanceScore += recencyBoost * 0.1;
358
- }
359
-
360
- // Option A+: Get content from HNSW metadata if available (avoids Walrus fetch!)
361
- // Content is only stored when encryption is OFF (see indexMemory)
362
- const indexedContent = result.metadata?.content as string | undefined;
363
-
364
- results.push({
365
- memoryId: memoryEntry.memoryId,
366
- blobId: memoryEntry.blobId,
367
- metadata: memoryEntry.metadata,
368
- similarity,
369
- relevanceScore,
370
- content: indexedContent, // ✅ Return content from local index (no Walrus fetch needed!)
371
- extractedAt: memoryEntry.indexedAt
372
- });
373
- }
374
-
375
- // Apply diversity filtering if requested
376
- let finalResults = results;
377
- if (query.diversityFactor && query.diversityFactor > 0) {
378
- finalResults = this.diversifyResults(results, query.diversityFactor);
379
- }
380
-
381
- // Sort by relevance score and limit results
382
- finalResults.sort((a, b) => b.relevanceScore - a.relevanceScore);
383
- finalResults = finalResults.slice(0, searchK);
384
-
385
- // Update search statistics
386
- const searchLatency = performance.now() - startTime;
387
- this.updateSearchStats(query.userAddress, searchLatency);
388
-
389
- // Count how many results have content from local index (Option A+)
390
- const resultsWithLocalContent = finalResults.filter(r => r.content !== undefined).length;
391
-
392
- console.log(`✅ Search completed in ${searchLatency.toFixed(2)}ms`);
393
- console.log(` Found ${finalResults.length} results (similarity range: ${finalResults.length > 0 ? finalResults[finalResults.length-1].similarity.toFixed(3) : 'N/A'} - ${finalResults.length > 0 ? finalResults[0].similarity.toFixed(3) : 'N/A'}`);
394
- if (resultsWithLocalContent > 0) {
395
- console.log(` 📦 ${resultsWithLocalContent}/${finalResults.length} results have content from local index (no Walrus fetch needed!)`);
396
- }
397
-
398
- return finalResults;
399
-
400
- } catch (error) {
401
- console.error('❌ Memory search failed:', error);
402
- throw error;
403
- }
404
- }
405
-
406
- /**
407
- * Get all memories for a user with optional filtering
408
- */
409
- async getUserMemories(
410
- userAddress: string,
411
- filters?: {
412
- categories?: string[];
413
- dateRange?: { start?: Date; end?: Date };
414
- importanceRange?: { min?: number; max?: number };
415
- limit?: number;
416
- }
417
- ): Promise<MemorySearchResult[]> {
418
- const userMemories = this.memoryIndex.get(userAddress);
419
- if (!userMemories) {
420
- return [];
421
- }
422
-
423
- const results: MemorySearchResult[] = [];
424
-
425
- for (const [memoryId, entry] of userMemories) {
426
- // Apply filters
427
- if (filters) {
428
- if (filters.categories && !filters.categories.includes(entry.metadata.category)) {
429
- continue;
430
- }
431
-
432
- if (filters.importanceRange) {
433
- const importance = entry.metadata.importance || 5;
434
- if (filters.importanceRange.min && importance < filters.importanceRange.min) continue;
435
- if (filters.importanceRange.max && importance > filters.importanceRange.max) continue;
436
- }
437
-
438
- if (filters.dateRange) {
439
- const created = new Date(entry.metadata.createdTimestamp || 0);
440
- if (filters.dateRange.start && created < filters.dateRange.start) continue;
441
- if (filters.dateRange.end && created > filters.dateRange.end) continue;
442
- }
443
- }
444
-
445
- results.push({
446
- memoryId: entry.memoryId,
447
- blobId: entry.blobId,
448
- metadata: entry.metadata,
449
- similarity: 1.0, // No similarity for direct listing
450
- relevanceScore: entry.metadata.importance || 5,
451
- extractedAt: entry.indexedAt
452
- });
453
- }
454
-
455
- // Sort by importance and creation time
456
- results.sort((a, b) => {
457
- const importanceDiff = (b.metadata.importance || 5) - (a.metadata.importance || 5);
458
- if (importanceDiff !== 0) return importanceDiff;
459
- return (b.metadata.createdTimestamp || 0) - (a.metadata.createdTimestamp || 0);
460
- });
461
-
462
- // Apply limit
463
- if (filters?.limit) {
464
- return results.slice(0, filters.limit);
465
- }
466
-
467
- return results;
468
- }
469
-
470
- /**
471
- * Remove memory from index
472
- */
473
- async removeMemory(userAddress: string, memoryId: string): Promise<boolean> {
474
- try {
475
- const userMemories = this.memoryIndex.get(userAddress);
476
- if (!userMemories) {
477
- return false;
478
- }
479
-
480
- const entry = userMemories.get(memoryId);
481
- if (!entry) {
482
- return false;
483
- }
484
-
485
- // Remove from HNSW index (if supported)
486
- // Note: hnswlib-node doesn't support deletion, so we just mark as removed
487
-
488
- // Remove from memory index
489
- userMemories.delete(memoryId);
490
-
491
- console.log(`✅ Memory removed from index: ${memoryId}`);
492
- return true;
493
-
494
- } catch (error) {
495
- console.error('❌ Failed to remove memory from index:', error);
496
- return false;
497
- }
498
- }
499
-
500
- /**
501
- * Get index statistics for a user
502
- */
503
- getIndexStats(userAddress: string): {
504
- totalMemories: number;
505
- categoryCounts: Record<string, number>;
506
- importanceDistribution: Record<number, number>;
507
- averageImportance: number;
508
- oldestMemory: Date | null;
509
- newestMemory: Date | null;
510
- indexSize: number;
511
- } {
512
- const userMemories = this.memoryIndex.get(userAddress);
513
- if (!userMemories) {
514
- return {
515
- totalMemories: 0,
516
- categoryCounts: {},
517
- importanceDistribution: {},
518
- averageImportance: 0,
519
- oldestMemory: null,
520
- newestMemory: null,
521
- indexSize: 0
522
- };
523
- }
524
-
525
- const categoryCounts: Record<string, number> = {};
526
- const importanceDistribution: Record<number, number> = {};
527
- let totalImportance = 0;
528
- let oldestMemory: Date | null = null;
529
- let newestMemory: Date | null = null;
530
-
531
- for (const entry of userMemories.values()) {
532
- // Categories
533
- categoryCounts[entry.metadata.category] = (categoryCounts[entry.metadata.category] || 0) + 1;
534
-
535
- // Importance
536
- const importance = entry.metadata.importance || 5;
537
- importanceDistribution[importance] = (importanceDistribution[importance] || 0) + 1;
538
- totalImportance += importance;
539
-
540
- // Dates
541
- const created = new Date(entry.metadata.createdTimestamp || 0);
542
- if (!oldestMemory || created < oldestMemory) {
543
- oldestMemory = created;
544
- }
545
- if (!newestMemory || created > newestMemory) {
546
- newestMemory = created;
547
- }
548
- }
549
-
550
- return {
551
- totalMemories: userMemories.size,
552
- categoryCounts,
553
- importanceDistribution,
554
- averageImportance: userMemories.size > 0 ? totalImportance / userMemories.size : 0,
555
- oldestMemory,
556
- newestMemory,
557
- indexSize: userMemories.size
558
- };
559
- }
560
-
561
- /**
562
- * Flush pending operations and save index
563
- */
564
- async flush(userAddress: string): Promise<void> {
565
- const hnswService = await this.getHnswService();
566
- await hnswService.flushBatch(userAddress);
567
- console.log(`✅ Memory index flushed for user ${userAddress}`);
568
- }
569
-
570
- /**
571
- * Load index from storage (local or Walrus)
572
- *
573
- * @param userAddress - User's wallet address
574
- * @param indexBlobId - Optional Walrus blob ID to load from cloud
575
- */
576
- async loadIndex(userAddress: string, indexBlobId?: string): Promise<void> {
577
- const hnswService = await this.getHnswService();
578
-
579
- // If blobId provided, try to load from Walrus first
580
- if (indexBlobId && 'loadFromWalrus' in hnswService) {
581
- console.log(`📥 Attempting to load index from Walrus: ${indexBlobId}`);
582
- const walrusLoaded = await (hnswService as any).loadFromWalrus(userAddress, indexBlobId);
583
- if (walrusLoaded) {
584
- console.log(`✅ Memory index loaded from Walrus for user ${userAddress}`);
585
- return;
586
- }
587
- console.log(`⚠️ Walrus load failed, falling back to local storage`);
588
- }
589
-
590
- // Fallback to local storage
591
- const loaded = await hnswService.loadIndex(userAddress);
592
- if (loaded) {
593
- console.log(`✅ Memory index loaded from local storage for user ${userAddress}`);
594
- }
595
- }
596
-
597
- /**
598
- * Save index to local storage
599
- */
600
- async saveIndex(userAddress: string): Promise<void> {
601
- const hnswService = await this.getHnswService();
602
- await hnswService.saveIndex(userAddress);
603
- console.log(`✅ Memory index saved for user ${userAddress}`);
604
- }
605
-
606
- /**
607
- * Sync index to Walrus cloud storage
608
- *
609
- * @param userAddress - User's wallet address
610
- * @returns Walrus blob ID if successful, null if Walrus is disabled
611
- */
612
- async syncToWalrus(userAddress: string): Promise<string | null> {
613
- const hnswService = await this.getHnswService();
614
-
615
- if (!('syncToWalrus' in hnswService)) {
616
- console.warn('⚠️ HNSW service does not support Walrus sync');
617
- return null;
618
- }
619
-
620
- const blobId = await (hnswService as any).syncToWalrus(userAddress);
621
- if (blobId) {
622
- console.log(`☁️ Memory index synced to Walrus: ${blobId}`);
623
- }
624
- return blobId;
625
- }
626
-
627
- /**
628
- * Load index directly from Walrus cloud storage
629
- *
630
- * @param userAddress - User's wallet address
631
- * @param blobId - Walrus blob ID
632
- * @returns true if successfully loaded
633
- */
634
- async loadFromWalrus(userAddress: string, blobId: string): Promise<boolean> {
635
- const hnswService = await this.getHnswService();
636
-
637
- if (!('loadFromWalrus' in hnswService)) {
638
- console.warn('⚠️ HNSW service does not support Walrus load');
639
- return false;
640
- }
641
-
642
- const loaded = await (hnswService as any).loadFromWalrus(userAddress, blobId);
643
- if (loaded) {
644
- console.log(`☁️ Memory index loaded from Walrus: ${blobId}`);
645
- }
646
- return loaded;
647
- }
648
-
649
- /**
650
- * Get the Walrus blob ID for a user's index (if backed up)
651
- *
652
- * @param userAddress - User's wallet address
653
- * @returns Blob ID or null if not backed up
654
- */
655
- getWalrusBlobId(userAddress: string): string | null {
656
- if (this.hnswService && 'getWalrusBlobId' in this.hnswService) {
657
- return (this.hnswService as any).getWalrusBlobId(userAddress);
658
- }
659
- return null;
660
- }
661
-
662
- /**
663
- * Check if Walrus backup is enabled
664
- */
665
- isWalrusEnabled(): boolean {
666
- if (this.hnswService && 'isWalrusEnabled' in this.hnswService) {
667
- return (this.hnswService as any).isWalrusEnabled();
668
- }
669
- return false;
670
- }
671
-
672
- /**
673
- * Clear user's index
674
- */
675
- async clearUserIndex(userAddress: string): Promise<void> {
676
- this.memoryIndex.delete(userAddress);
677
- const hnswService = await this.getHnswService();
678
- await hnswService.deleteIndex(userAddress);
679
- console.log(`✅ Memory index cleared for user ${userAddress}`);
680
- }
681
-
682
- /**
683
- * Get overall service statistics
684
- */
685
- async getServiceStats() {
686
- const totalMemories = Array.from(this.memoryIndex.values())
687
- .reduce((sum, userMemories) => sum + userMemories.size, 0);
688
-
689
- let hnswStats = null;
690
- if (this.hnswService) {
691
- hnswStats = this.hnswService.getBatchStats();
692
- }
693
-
694
- return {
695
- totalUsers: this.memoryIndex.size,
696
- totalMemories,
697
- hnswStats,
698
- hasEmbeddingService: !!this.embeddingService,
699
- hasStorageService: !!this.storageService
700
- };
701
- }
702
-
703
- /**
704
- * Destroy service and cleanup resources
705
- */
706
- destroy(): void {
707
- if (this.hnswService) {
708
- this.hnswService.destroy();
709
- }
710
- this.hnswService = null;
711
- this.hnswServicePromise = null;
712
- this.memoryIndex.clear();
713
- console.log('✅ MemoryIndexService destroyed');
714
- }
715
-
716
- // ==================== PRIVATE HELPER METHODS ====================
717
-
718
- private createMemoryFilter(query: MemorySearchQuery) {
719
- return (metadata: any) => {
720
- // Category filter
721
- if (query.categories && query.categories.length > 0) {
722
- if (!query.categories.includes(metadata.category)) {
723
- return false;
724
- }
725
- }
726
-
727
- // Date range filter
728
- if (query.dateRange) {
729
- const created = new Date(metadata.createdTimestamp || 0);
730
- if (query.dateRange.start && created < query.dateRange.start) {
731
- return false;
732
- }
733
- if (query.dateRange.end && created > query.dateRange.end) {
734
- return false;
735
- }
736
- }
737
-
738
- // Importance range filter
739
- if (query.importanceRange) {
740
- const importance = metadata.importance || 5;
741
- if (query.importanceRange.min && importance < query.importanceRange.min) {
742
- return false;
743
- }
744
- if (query.importanceRange.max && importance > query.importanceRange.max) {
745
- return false;
746
- }
747
- }
748
-
749
- // Tags filter (search in custom metadata)
750
- if (query.tags && query.tags.length > 0) {
751
- const metadataText = JSON.stringify(metadata).toLowerCase();
752
- const hasAnyTag = query.tags.some(tag =>
753
- metadataText.includes(tag.toLowerCase())
754
- );
755
- if (!hasAnyTag) {
756
- return false;
757
- }
758
- }
759
-
760
- return true;
761
- };
762
- }
763
-
764
- // ==================== HNSW HELPER METHODS ====================
765
-
766
- /**
767
- * Create metadata filter for HNSW search
768
- */
769
- private createMetadataFilter(query: MemorySearchQuery): ((metadata: any) => boolean) | undefined {
770
- if (!query.categories && !query.dateRange && !query.importanceRange && !query.tags) {
771
- return undefined;
772
- }
773
-
774
- return (metadata: any) => {
775
- // Category filter
776
- if (query.categories && query.categories.length > 0) {
777
- if (!query.categories.includes(metadata.category)) {
778
- return false;
779
- }
780
- }
781
-
782
- // Date range filter
783
- if (query.dateRange) {
784
- const created = new Date(metadata.createdTimestamp || 0);
785
- if (query.dateRange.start && created < query.dateRange.start) {
786
- return false;
787
- }
788
- if (query.dateRange.end && created > query.dateRange.end) {
789
- return false;
790
- }
791
- }
792
-
793
- // Importance range filter
794
- if (query.importanceRange) {
795
- const importance = metadata.importance || 5;
796
- if (query.importanceRange.min && importance < query.importanceRange.min) {
797
- return false;
798
- }
799
- if (query.importanceRange.max && importance > query.importanceRange.max) {
800
- return false;
801
- }
802
- }
803
-
804
- // Tags filter
805
- if (query.tags && query.tags.length > 0) {
806
- const metadataText = JSON.stringify(metadata).toLowerCase();
807
- const hasAnyTag = query.tags.some(tag =>
808
- metadataText.includes(tag.toLowerCase())
809
- );
810
- if (!hasAnyTag) {
811
- return false;
812
- }
813
- }
814
-
815
- return true;
816
- };
817
- }
818
-
819
- /**
820
- * Update index statistics for a user
821
- */
822
- private updateIndexStats(userAddress: string): void {
823
- let stats = this.indexStats.get(userAddress);
824
- if (!stats) {
825
- stats = {
826
- totalVectors: 0,
827
- avgSimilarity: 0,
828
- searchLatency: [],
829
- lastOptimized: new Date()
830
- };
831
- this.indexStats.set(userAddress, stats);
832
- }
833
- stats.totalVectors++;
834
- }
835
-
836
- /**
837
- * Enhanced relevance scoring with multiple factors
838
- */
839
- private calculateAdvancedRelevanceScore(
840
- similarity: number,
841
- metadata: MemoryMetadata,
842
- query: MemorySearchQuery,
843
- queryVector: number[],
844
- documentVector: number[]
845
- ): number {
846
- let score = similarity * 0.7; // Base similarity weight (increased)
847
-
848
- // Importance boost
849
- const importance = metadata.importance || 5;
850
- score += (importance - 5) * 0.02; // -0.1 to +0.1 boost
851
-
852
- // Category exact match boost
853
- if (query.categories && query.categories.includes(metadata.category)) {
854
- score += 0.15;
855
- }
856
-
857
- // Topic relevance boost
858
- if (query.query && metadata.topic) {
859
- const queryLower = query.query.toLowerCase();
860
- const topicLower = metadata.topic.toLowerCase();
861
- if (queryLower.includes(topicLower) || topicLower.includes(queryLower)) {
862
- score += 0.1;
863
- }
864
- }
865
-
866
- // Vector quality boost (based on vector magnitude)
867
- const vectorMagnitude = this.calculateVectorMagnitude(documentVector);
868
- if (vectorMagnitude > 0.1) { // Well-formed embedding
869
- score += 0.05;
870
- }
871
-
872
- // Semantic consistency boost (cosine similarity in different metric)
873
- const semanticConsistency = this.calculateSemanticConsistency(queryVector, documentVector);
874
- score += semanticConsistency * 0.1;
875
-
876
- return Math.min(1.0, Math.max(0.0, score));
877
- }
878
-
879
- /**
880
- * Calculate recency boost based on creation timestamp
881
- */
882
- private calculateRecencyBoost(createdTimestamp: number): number {
883
- const now = Date.now();
884
- const ageInDays = (now - createdTimestamp) / (1000 * 60 * 60 * 24);
885
-
886
- // Exponential decay: more recent = higher boost
887
- if (ageInDays < 1) return 1.0; // Last day: full boost
888
- if (ageInDays < 7) return 0.8; // Last week: 80% boost
889
- if (ageInDays < 30) return 0.5; // Last month: 50% boost
890
- if (ageInDays < 90) return 0.2; // Last quarter: 20% boost
891
- return 0.0; // Older: no boost
892
- }
893
-
894
- /**
895
- * Diversify search results to avoid clustering
896
- */
897
- private diversifyResults(results: MemorySearchResult[], diversityFactor: number): MemorySearchResult[] {
898
- if (diversityFactor <= 0 || results.length <= 1) return results;
899
-
900
- const diversified: MemorySearchResult[] = [];
901
- const remaining = [...results];
902
-
903
- // Always include the top result
904
- if (remaining.length > 0) {
905
- diversified.push(remaining.shift()!);
906
- }
907
-
908
- while (remaining.length > 0 && diversified.length < results.length * 0.8) {
909
- let bestIndex = 0;
910
- let bestScore = 0;
911
-
912
- for (let i = 0; i < remaining.length; i++) {
913
- const candidate = remaining[i];
914
-
915
- // Calculate diversity score (distance from already selected results)
916
- let minSimilarity = 1.0;
917
- for (const selected of diversified) {
918
- const similarity = candidate.similarity; // Could enhance with actual vector similarity
919
- minSimilarity = Math.min(minSimilarity, similarity);
920
- }
921
-
922
- // Combine relevance and diversity
923
- const diversityScore = candidate.relevanceScore * (1 - diversityFactor) +
924
- (1 - minSimilarity) * diversityFactor;
925
-
926
- if (diversityScore > bestScore) {
927
- bestScore = diversityScore;
928
- bestIndex = i;
929
- }
930
- }
931
-
932
- diversified.push(remaining.splice(bestIndex, 1)[0]);
933
- }
934
-
935
- return diversified;
936
- }
937
-
938
- /**
939
- * Update search performance statistics
940
- */
941
- private updateSearchStats(userAddress: string, latency: number): void {
942
- let stats = this.indexStats.get(userAddress);
943
- if (!stats) {
944
- stats = {
945
- totalVectors: 0,
946
- avgSimilarity: 0,
947
- searchLatency: [],
948
- lastOptimized: new Date()
949
- };
950
- this.indexStats.set(userAddress, stats);
951
- }
952
-
953
- stats.searchLatency.push(latency);
954
-
955
- // Keep only last 100 latency measurements
956
- if (stats.searchLatency.length > 100) {
957
- stats.searchLatency = stats.searchLatency.slice(-100);
958
- }
959
- }
960
-
961
- /**
962
- * Calculate vector magnitude
963
- */
964
- private calculateVectorMagnitude(vector: number[]): number {
965
- let sum = 0;
966
- for (const val of vector) {
967
- sum += val * val;
968
- }
969
- return Math.sqrt(sum);
970
- }
971
-
972
- /**
973
- * Calculate cosine similarity between two vectors
974
- * Used for semantic consistency scoring
975
- */
976
- private cosineSimilarity(a: number[], b: number[]): number {
977
- if (a.length !== b.length) return 0;
978
-
979
- let dotProduct = 0;
980
- let normA = 0;
981
- let normB = 0;
982
-
983
- for (let i = 0; i < a.length; i++) {
984
- dotProduct += a[i] * b[i];
985
- normA += a[i] * a[i];
986
- normB += b[i] * b[i];
987
- }
988
-
989
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
990
- return magnitude === 0 ? 0 : dotProduct / magnitude;
991
- }
992
-
993
- /**
994
- * Calculate semantic consistency score
995
- */
996
- private calculateSemanticConsistency(queryVector: number[], documentVector: number[]): number {
997
- // Calculate angle between vectors (semantic consistency)
998
- const similarity = this.cosineSimilarity(queryVector, documentVector);
999
- const angle = Math.acos(Math.max(-1, Math.min(1, similarity)));
1000
-
1001
- // Convert angle to consistency score (0-1, where 1 is perfect alignment)
1002
- return 1 - (angle / Math.PI);
1003
- }
1
+ /**
2
+ * MemoryIndexService - Enhanced Memory Indexing and Vector Search
3
+ *
4
+ * Hybrid HNSW-powered memory indexing service providing:
5
+ * - O(log N) vector similarity search performance
6
+ * - Advanced clustering and graph-based memory organization
7
+ * - Intelligent semantic search with relevance scoring
8
+ * - Dynamic index optimization and parameter tuning
9
+ * - Multi-dimensional vector space analysis
10
+ *
11
+ * Automatically selects the appropriate HNSW implementation:
12
+ * - Browser: hnswlib-wasm (WebAssembly)
13
+ * - Node.js: hnswlib-node (native bindings)
14
+ */
15
+
16
+ import { createHnswService, isBrowser, isNode } from '../vector/createHnswService';
17
+ import type { IHnswService, IHnswSearchOptions } from '../vector/IHnswService';
18
+ import { EmbeddingService } from './EmbeddingService';
19
+ import { StorageService, type MemoryMetadata } from './StorageService';
20
+
21
+ export interface MemoryIndexEntry {
22
+ memoryId: string;
23
+ blobId: string;
24
+ vectorId: number;
25
+ embedding: number[];
26
+ metadata: MemoryMetadata;
27
+ indexedAt: Date;
28
+ lastAccessed?: Date;
29
+ }
30
+
31
+ export interface MemoryIndexOptions {
32
+ maxElements?: number;
33
+ dimension?: number;
34
+ efConstruction?: number;
35
+ m?: number;
36
+ batchSize?: number;
37
+ autoFlushInterval?: number;
38
+ /** Pre-initialized HNSW service instance (shared singleton) */
39
+ hnswService?: IHnswService;
40
+ }
41
+
42
+ export interface MemorySearchQuery {
43
+ query?: string;
44
+ vector?: number[];
45
+ userAddress: string;
46
+ k?: number;
47
+ threshold?: number;
48
+
49
+ // Memory-specific filters
50
+ categories?: string[];
51
+ dateRange?: {
52
+ start?: Date;
53
+ end?: Date;
54
+ };
55
+ importanceRange?: {
56
+ min?: number;
57
+ max?: number;
58
+ };
59
+ tags?: string[];
60
+ includeContent?: boolean;
61
+
62
+ // Enhanced search features
63
+ searchMode?: 'semantic' | 'hybrid' | 'exact'; // Search strategy
64
+ boostRecent?: boolean; // Boost recently created memories
65
+ diversityFactor?: number; // Result diversity (0-1)
66
+ }
67
+
68
+ export interface MemorySearchResult {
69
+ memoryId: string;
70
+ blobId: string;
71
+ metadata: MemoryMetadata;
72
+ similarity: number;
73
+ relevanceScore: number;
74
+ content?: string | Uint8Array;
75
+ extractedAt?: Date;
76
+ embedding?: number[]; // Optional embedding vector for advanced similarity operations
77
+ clusterInfo?: {
78
+ clusterId: number;
79
+ clusterCenter: number[];
80
+ intraClusterSimilarity: number;
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Memory-focused indexing service providing high-level memory operations
86
+ * Uses hybrid HNSW implementation (browser: wasm, node: native) for optimal performance
87
+ */
88
+ export class MemoryIndexService {
89
+ private hnswService: IHnswService | null = null;
90
+ private hnswServicePromise: Promise<IHnswService> | null = null;
91
+ private embeddingService?: EmbeddingService;
92
+ private storageService?: StorageService;
93
+ private memoryIndex = new Map<string, Map<string, MemoryIndexEntry>>(); // userAddress -> memoryId -> entry
94
+ private nextMemoryId = 1;
95
+ private options: MemoryIndexOptions;
96
+
97
+ // Performance tracking
98
+ private indexStats = new Map<string, {
99
+ totalVectors: number;
100
+ avgSimilarity: number;
101
+ searchLatency: number[];
102
+ lastOptimized: Date;
103
+ }>();
104
+
105
+ constructor(
106
+ storageService?: StorageService,
107
+ options: MemoryIndexOptions = {}
108
+ ) {
109
+ this.storageService = storageService;
110
+ this.options = options;
111
+
112
+ // Use pre-initialized HNSW service if provided (shared singleton pattern)
113
+ if (options.hnswService) {
114
+ this.hnswService = options.hnswService;
115
+ console.log('✅ MemoryIndexService using shared HNSW service instance');
116
+ } else {
117
+ // Initialize HNSW service asynchronously using factory
118
+ this.hnswServicePromise = this.initializeHnswService();
119
+
120
+ const envType = isBrowser() ? 'browser (hnswlib-wasm)' : isNode() ? 'Node.js (hnswlib-node)' : 'unknown';
121
+ console.log(`✅ MemoryIndexService initializing with hybrid HNSW (${envType})`);
122
+ }
123
+
124
+ console.log(` Max elements: ${options.maxElements || 10000}`);
125
+ console.log(` Embedding dimension: ${options.dimension || 3072}`);
126
+ console.log(` HNSW parameters: M=${options.m || 16}, efConstruction=${options.efConstruction || 200}`);
127
+ }
128
+
129
+ /**
130
+ * Initialize HNSW service using factory (auto-detects environment)
131
+ */
132
+ private async initializeHnswService(): Promise<IHnswService> {
133
+ try {
134
+ const service = await createHnswService({
135
+ indexConfig: {
136
+ dimension: this.options.dimension || 3072,
137
+ maxElements: this.options.maxElements || 10000,
138
+ efConstruction: this.options.efConstruction || 200,
139
+ m: this.options.m || 16
140
+ },
141
+ batchConfig: {
142
+ maxBatchSize: this.options.batchSize || 100,
143
+ batchDelayMs: this.options.autoFlushInterval || 5000
144
+ }
145
+ });
146
+
147
+ this.hnswService = service;
148
+ console.log('✅ HNSW service initialized successfully');
149
+ return service;
150
+ } catch (error) {
151
+ console.error('❌ Failed to initialize HNSW service:', error);
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Get HNSW service (waits for initialization if needed)
158
+ */
159
+ private async getHnswService(): Promise<IHnswService> {
160
+ if (this.hnswService) {
161
+ return this.hnswService;
162
+ }
163
+ if (this.hnswServicePromise) {
164
+ return this.hnswServicePromise;
165
+ }
166
+ throw new Error('HNSW service not initialized');
167
+ }
168
+
169
+ /**
170
+ * Initialize with embedding service
171
+ */
172
+ initialize(embeddingService: EmbeddingService, storageService?: StorageService) {
173
+ this.embeddingService = embeddingService;
174
+ if (storageService) {
175
+ this.storageService = storageService;
176
+ }
177
+ console.log('✅ MemoryIndexService: Embedding service connected');
178
+ }
179
+
180
+ /**
181
+ * Index a memory with its content, metadata, and vector embedding
182
+ *
183
+ * @param userAddress - User's wallet address
184
+ * @param memoryId - Unique memory identifier
185
+ * @param blobId - Walrus blob ID for the memory
186
+ * @param content - Memory content (stored in index only if not encrypted)
187
+ * @param metadata - Memory metadata
188
+ * @param embedding - Pre-computed embedding vector (optional)
189
+ * @param options - Indexing options
190
+ * @param options.isEncrypted - If true, content will NOT be stored in index (security)
191
+ */
192
+ async indexMemory(
193
+ userAddress: string,
194
+ memoryId: string,
195
+ blobId: string,
196
+ content: string,
197
+ metadata: MemoryMetadata,
198
+ embedding?: number[],
199
+ options?: { isEncrypted?: boolean }
200
+ ): Promise<{ vectorId: number; indexed: boolean }> {
201
+ try {
202
+ console.log(`📊 Indexing memory ${memoryId} for user ${userAddress}`);
203
+
204
+ // Generate embedding if not provided
205
+ let memoryEmbedding = embedding;
206
+ if (!memoryEmbedding && this.embeddingService) {
207
+ const embeddingResult = await this.embeddingService.embedText({ text: content });
208
+ memoryEmbedding = embeddingResult.vector;
209
+ }
210
+
211
+ if (!memoryEmbedding) {
212
+ throw new Error('No embedding provided and no embedding service available');
213
+ }
214
+
215
+ // Generate vector ID
216
+ const vectorId = this.nextMemoryId++;
217
+
218
+ // Add to HNSW index with batching
219
+ // Option A+: Store content in index ONLY when encryption is OFF (security consideration)
220
+ const hnswService = await this.getHnswService();
221
+ const isEncrypted = options?.isEncrypted ?? false;
222
+
223
+ const vectorMetadata: Record<string, unknown> = {
224
+ memoryId,
225
+ blobId,
226
+ category: metadata.category,
227
+ topic: metadata.topic,
228
+ importance: metadata.importance,
229
+ contentType: metadata.contentType,
230
+ createdTimestamp: metadata.createdTimestamp,
231
+ customMetadata: metadata.customMetadata
232
+ };
233
+
234
+ // Only store content if NOT encrypted (privacy-safe)
235
+ if (!isEncrypted && content) {
236
+ vectorMetadata.content = content;
237
+ console.log(' 💾 Content stored in local index (encryption OFF)');
238
+ } else if (isEncrypted) {
239
+ console.log(' 🔒 Content NOT stored in index (encryption ON - security)');
240
+ }
241
+
242
+ await hnswService.addVector(
243
+ userAddress,
244
+ vectorId,
245
+ memoryEmbedding,
246
+ vectorMetadata
247
+ );
248
+
249
+ // Update performance statistics
250
+ this.updateIndexStats(userAddress);
251
+
252
+ // Store in memory index
253
+ if (!this.memoryIndex.has(userAddress)) {
254
+ this.memoryIndex.set(userAddress, new Map());
255
+ }
256
+
257
+ const indexEntry: MemoryIndexEntry = {
258
+ memoryId,
259
+ blobId,
260
+ vectorId,
261
+ embedding: memoryEmbedding,
262
+ metadata,
263
+ indexedAt: new Date()
264
+ };
265
+
266
+ this.memoryIndex.get(userAddress)!.set(memoryId, indexEntry);
267
+
268
+ console.log(`✅ Memory indexed: ${memoryId} (vector ${vectorId})`);
269
+ console.log(` Category: ${metadata.category}`);
270
+ console.log(` Importance: ${metadata.importance}`);
271
+ console.log(` Embedding dimension: ${memoryEmbedding.length}`);
272
+
273
+ return { vectorId, indexed: true };
274
+
275
+ } catch (error) {
276
+ console.error('❌ Failed to index memory:', error);
277
+ throw error;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Enhanced memory search using native HNSW with advanced features
283
+ * Supports semantic search, metadata filtering, and intelligent relevance scoring
284
+ */
285
+ async searchMemories(query: MemorySearchQuery): Promise<MemorySearchResult[]> {
286
+ const startTime = performance.now();
287
+
288
+ try {
289
+ console.log(`🔍 Memory search for user ${query.userAddress}`);
290
+ console.log(` Query: "${query.query || 'vector search'}"`);
291
+ console.log(` Mode: ${query.searchMode || 'semantic'}, K: ${query.k || 10}`);
292
+
293
+ // Generate query vector if needed
294
+ let queryVector = query.vector;
295
+ if (!queryVector && query.query && this.embeddingService) {
296
+ const embeddingResult = await this.embeddingService.embedText({ text: query.query });
297
+ queryVector = embeddingResult.vector;
298
+ }
299
+
300
+ if (!queryVector) {
301
+ throw new Error('No query vector provided and no query text with embedding service');
302
+ }
303
+
304
+ const userMemories = this.memoryIndex.get(query.userAddress);
305
+ if (!userMemories || userMemories.size === 0) {
306
+ console.log(' No memories found for user');
307
+ return [];
308
+ }
309
+
310
+ // Configure search parameters based on query mode
311
+ const searchK = query.k || 10;
312
+ const efSearch = query.searchMode === 'exact' ? searchK * 4 :
313
+ query.searchMode === 'hybrid' ? searchK * 2 : searchK;
314
+
315
+ // Perform HNSW search using native implementation
316
+ const hnswService = await this.getHnswService();
317
+ const hnswResults = await hnswService.search(
318
+ query.userAddress,
319
+ queryVector,
320
+ {
321
+ k: Math.min(searchK * 3, 100), // Get more candidates for post-filtering
322
+ ef: efSearch,
323
+ filter: this.createMetadataFilter(query)
324
+ }
325
+ );
326
+
327
+ // Convert to memory search results with enhanced scoring
328
+ const results: MemorySearchResult[] = [];
329
+
330
+ for (const result of hnswResults) {
331
+ const vectorId = result.vectorId;
332
+ const distance = result.distance;
333
+ // Calculate similarity from distance (cosine distance: similarity = 1 - distance)
334
+ const similarity = result.score;
335
+
336
+ // Skip results below threshold
337
+ if (query.threshold && similarity < query.threshold) {
338
+ continue;
339
+ }
340
+
341
+ // Find memory entry by vector ID
342
+ const memoryEntry = Array.from(userMemories.values()).find(entry => entry.vectorId === vectorId);
343
+ if (!memoryEntry) continue;
344
+
345
+ // Enhanced relevance scoring
346
+ let relevanceScore = this.calculateAdvancedRelevanceScore(
347
+ similarity,
348
+ memoryEntry.metadata,
349
+ query,
350
+ queryVector,
351
+ memoryEntry.embedding || []
352
+ );
353
+
354
+ // Apply recency boost if requested
355
+ if (query.boostRecent) {
356
+ const recencyBoost = this.calculateRecencyBoost(memoryEntry.metadata.createdTimestamp || 0);
357
+ relevanceScore += recencyBoost * 0.1;
358
+ }
359
+
360
+ // Option A+: Get content from HNSW metadata if available (avoids Walrus fetch!)
361
+ // Content is only stored when encryption is OFF (see indexMemory)
362
+ const indexedContent = result.metadata?.content as string | undefined;
363
+
364
+ results.push({
365
+ memoryId: memoryEntry.memoryId,
366
+ blobId: memoryEntry.blobId,
367
+ metadata: memoryEntry.metadata,
368
+ similarity,
369
+ relevanceScore,
370
+ content: indexedContent, // ✅ Return content from local index (no Walrus fetch needed!)
371
+ extractedAt: memoryEntry.indexedAt
372
+ });
373
+ }
374
+
375
+ // Apply diversity filtering if requested
376
+ let finalResults = results;
377
+ if (query.diversityFactor && query.diversityFactor > 0) {
378
+ finalResults = this.diversifyResults(results, query.diversityFactor);
379
+ }
380
+
381
+ // Sort by relevance score and limit results
382
+ finalResults.sort((a, b) => b.relevanceScore - a.relevanceScore);
383
+ finalResults = finalResults.slice(0, searchK);
384
+
385
+ // Update search statistics
386
+ const searchLatency = performance.now() - startTime;
387
+ this.updateSearchStats(query.userAddress, searchLatency);
388
+
389
+ // Count how many results have content from local index (Option A+)
390
+ const resultsWithLocalContent = finalResults.filter(r => r.content !== undefined).length;
391
+
392
+ console.log(`✅ Search completed in ${searchLatency.toFixed(2)}ms`);
393
+ console.log(` Found ${finalResults.length} results (similarity range: ${finalResults.length > 0 ? finalResults[finalResults.length-1].similarity.toFixed(3) : 'N/A'} - ${finalResults.length > 0 ? finalResults[0].similarity.toFixed(3) : 'N/A'}`);
394
+ if (resultsWithLocalContent > 0) {
395
+ console.log(` 📦 ${resultsWithLocalContent}/${finalResults.length} results have content from local index (no Walrus fetch needed!)`);
396
+ }
397
+
398
+ return finalResults;
399
+
400
+ } catch (error) {
401
+ console.error('❌ Memory search failed:', error);
402
+ throw error;
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Get all memories for a user with optional filtering
408
+ */
409
+ async getUserMemories(
410
+ userAddress: string,
411
+ filters?: {
412
+ categories?: string[];
413
+ dateRange?: { start?: Date; end?: Date };
414
+ importanceRange?: { min?: number; max?: number };
415
+ limit?: number;
416
+ }
417
+ ): Promise<MemorySearchResult[]> {
418
+ const userMemories = this.memoryIndex.get(userAddress);
419
+ if (!userMemories) {
420
+ return [];
421
+ }
422
+
423
+ const results: MemorySearchResult[] = [];
424
+
425
+ for (const [memoryId, entry] of userMemories) {
426
+ // Apply filters
427
+ if (filters) {
428
+ if (filters.categories && !filters.categories.includes(entry.metadata.category)) {
429
+ continue;
430
+ }
431
+
432
+ if (filters.importanceRange) {
433
+ const importance = entry.metadata.importance || 5;
434
+ if (filters.importanceRange.min && importance < filters.importanceRange.min) continue;
435
+ if (filters.importanceRange.max && importance > filters.importanceRange.max) continue;
436
+ }
437
+
438
+ if (filters.dateRange) {
439
+ const created = new Date(entry.metadata.createdTimestamp || 0);
440
+ if (filters.dateRange.start && created < filters.dateRange.start) continue;
441
+ if (filters.dateRange.end && created > filters.dateRange.end) continue;
442
+ }
443
+ }
444
+
445
+ results.push({
446
+ memoryId: entry.memoryId,
447
+ blobId: entry.blobId,
448
+ metadata: entry.metadata,
449
+ similarity: 1.0, // No similarity for direct listing
450
+ relevanceScore: entry.metadata.importance || 5,
451
+ extractedAt: entry.indexedAt
452
+ });
453
+ }
454
+
455
+ // Sort by importance and creation time
456
+ results.sort((a, b) => {
457
+ const importanceDiff = (b.metadata.importance || 5) - (a.metadata.importance || 5);
458
+ if (importanceDiff !== 0) return importanceDiff;
459
+ return (b.metadata.createdTimestamp || 0) - (a.metadata.createdTimestamp || 0);
460
+ });
461
+
462
+ // Apply limit
463
+ if (filters?.limit) {
464
+ return results.slice(0, filters.limit);
465
+ }
466
+
467
+ return results;
468
+ }
469
+
470
+ /**
471
+ * Remove memory from index
472
+ */
473
+ async removeMemory(userAddress: string, memoryId: string): Promise<boolean> {
474
+ try {
475
+ const userMemories = this.memoryIndex.get(userAddress);
476
+ if (!userMemories) {
477
+ return false;
478
+ }
479
+
480
+ const entry = userMemories.get(memoryId);
481
+ if (!entry) {
482
+ return false;
483
+ }
484
+
485
+ // Remove from HNSW index (if supported)
486
+ // Note: hnswlib-node doesn't support deletion, so we just mark as removed
487
+
488
+ // Remove from memory index
489
+ userMemories.delete(memoryId);
490
+
491
+ console.log(`✅ Memory removed from index: ${memoryId}`);
492
+ return true;
493
+
494
+ } catch (error) {
495
+ console.error('❌ Failed to remove memory from index:', error);
496
+ return false;
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Get index statistics for a user
502
+ */
503
+ getIndexStats(userAddress: string): {
504
+ totalMemories: number;
505
+ categoryCounts: Record<string, number>;
506
+ importanceDistribution: Record<number, number>;
507
+ averageImportance: number;
508
+ oldestMemory: Date | null;
509
+ newestMemory: Date | null;
510
+ indexSize: number;
511
+ } {
512
+ const userMemories = this.memoryIndex.get(userAddress);
513
+ if (!userMemories) {
514
+ return {
515
+ totalMemories: 0,
516
+ categoryCounts: {},
517
+ importanceDistribution: {},
518
+ averageImportance: 0,
519
+ oldestMemory: null,
520
+ newestMemory: null,
521
+ indexSize: 0
522
+ };
523
+ }
524
+
525
+ const categoryCounts: Record<string, number> = {};
526
+ const importanceDistribution: Record<number, number> = {};
527
+ let totalImportance = 0;
528
+ let oldestMemory: Date | null = null;
529
+ let newestMemory: Date | null = null;
530
+
531
+ for (const entry of userMemories.values()) {
532
+ // Categories
533
+ categoryCounts[entry.metadata.category] = (categoryCounts[entry.metadata.category] || 0) + 1;
534
+
535
+ // Importance
536
+ const importance = entry.metadata.importance || 5;
537
+ importanceDistribution[importance] = (importanceDistribution[importance] || 0) + 1;
538
+ totalImportance += importance;
539
+
540
+ // Dates
541
+ const created = new Date(entry.metadata.createdTimestamp || 0);
542
+ if (!oldestMemory || created < oldestMemory) {
543
+ oldestMemory = created;
544
+ }
545
+ if (!newestMemory || created > newestMemory) {
546
+ newestMemory = created;
547
+ }
548
+ }
549
+
550
+ return {
551
+ totalMemories: userMemories.size,
552
+ categoryCounts,
553
+ importanceDistribution,
554
+ averageImportance: userMemories.size > 0 ? totalImportance / userMemories.size : 0,
555
+ oldestMemory,
556
+ newestMemory,
557
+ indexSize: userMemories.size
558
+ };
559
+ }
560
+
561
+ /**
562
+ * Flush pending operations and save index
563
+ */
564
+ async flush(userAddress: string): Promise<void> {
565
+ const hnswService = await this.getHnswService();
566
+ await hnswService.flushBatch(userAddress);
567
+ console.log(`✅ Memory index flushed for user ${userAddress}`);
568
+ }
569
+
570
+ /**
571
+ * Load index from storage (local or Walrus)
572
+ *
573
+ * @param userAddress - User's wallet address
574
+ * @param indexBlobId - Optional Walrus blob ID to load from cloud
575
+ */
576
+ async loadIndex(userAddress: string, indexBlobId?: string): Promise<void> {
577
+ const hnswService = await this.getHnswService();
578
+
579
+ // If blobId provided, try to load from Walrus first
580
+ if (indexBlobId && 'loadFromWalrus' in hnswService) {
581
+ console.log(`📥 Attempting to load index from Walrus: ${indexBlobId}`);
582
+ const walrusLoaded = await (hnswService as any).loadFromWalrus(userAddress, indexBlobId);
583
+ if (walrusLoaded) {
584
+ console.log(`✅ Memory index loaded from Walrus for user ${userAddress}`);
585
+ return;
586
+ }
587
+ console.log(`⚠️ Walrus load failed, falling back to local storage`);
588
+ }
589
+
590
+ // Fallback to local storage
591
+ const loaded = await hnswService.loadIndex(userAddress);
592
+ if (loaded) {
593
+ console.log(`✅ Memory index loaded from local storage for user ${userAddress}`);
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Save index to local storage
599
+ */
600
+ async saveIndex(userAddress: string): Promise<void> {
601
+ const hnswService = await this.getHnswService();
602
+ await hnswService.saveIndex(userAddress);
603
+ console.log(`✅ Memory index saved for user ${userAddress}`);
604
+ }
605
+
606
+ /**
607
+ * Sync index to Walrus cloud storage
608
+ *
609
+ * @param userAddress - User's wallet address
610
+ * @returns Walrus blob ID if successful, null if Walrus is disabled
611
+ */
612
+ async syncToWalrus(userAddress: string): Promise<string | null> {
613
+ const hnswService = await this.getHnswService();
614
+
615
+ if (!('syncToWalrus' in hnswService)) {
616
+ console.warn('⚠️ HNSW service does not support Walrus sync');
617
+ return null;
618
+ }
619
+
620
+ const blobId = await (hnswService as any).syncToWalrus(userAddress);
621
+ if (blobId) {
622
+ console.log(`☁️ Memory index synced to Walrus: ${blobId}`);
623
+ }
624
+ return blobId;
625
+ }
626
+
627
+ /**
628
+ * Load index directly from Walrus cloud storage
629
+ *
630
+ * @param userAddress - User's wallet address
631
+ * @param blobId - Walrus blob ID
632
+ * @returns true if successfully loaded
633
+ */
634
+ async loadFromWalrus(userAddress: string, blobId: string): Promise<boolean> {
635
+ const hnswService = await this.getHnswService();
636
+
637
+ if (!('loadFromWalrus' in hnswService)) {
638
+ console.warn('⚠️ HNSW service does not support Walrus load');
639
+ return false;
640
+ }
641
+
642
+ const loaded = await (hnswService as any).loadFromWalrus(userAddress, blobId);
643
+ if (loaded) {
644
+ console.log(`☁️ Memory index loaded from Walrus: ${blobId}`);
645
+ }
646
+ return loaded;
647
+ }
648
+
649
+ /**
650
+ * Get the Walrus blob ID for a user's index (if backed up)
651
+ *
652
+ * @param userAddress - User's wallet address
653
+ * @returns Blob ID or null if not backed up
654
+ */
655
+ getWalrusBlobId(userAddress: string): string | null {
656
+ if (this.hnswService && 'getWalrusBlobId' in this.hnswService) {
657
+ return (this.hnswService as any).getWalrusBlobId(userAddress);
658
+ }
659
+ return null;
660
+ }
661
+
662
+ /**
663
+ * Check if Walrus backup is enabled
664
+ */
665
+ isWalrusEnabled(): boolean {
666
+ if (this.hnswService && 'isWalrusEnabled' in this.hnswService) {
667
+ return (this.hnswService as any).isWalrusEnabled();
668
+ }
669
+ return false;
670
+ }
671
+
672
+ /**
673
+ * Clear user's index
674
+ */
675
+ async clearUserIndex(userAddress: string): Promise<void> {
676
+ this.memoryIndex.delete(userAddress);
677
+ const hnswService = await this.getHnswService();
678
+ await hnswService.deleteIndex(userAddress);
679
+ console.log(`✅ Memory index cleared for user ${userAddress}`);
680
+ }
681
+
682
+ /**
683
+ * Get overall service statistics
684
+ */
685
+ async getServiceStats() {
686
+ const totalMemories = Array.from(this.memoryIndex.values())
687
+ .reduce((sum, userMemories) => sum + userMemories.size, 0);
688
+
689
+ let hnswStats = null;
690
+ if (this.hnswService) {
691
+ hnswStats = this.hnswService.getBatchStats();
692
+ }
693
+
694
+ return {
695
+ totalUsers: this.memoryIndex.size,
696
+ totalMemories,
697
+ hnswStats,
698
+ hasEmbeddingService: !!this.embeddingService,
699
+ hasStorageService: !!this.storageService
700
+ };
701
+ }
702
+
703
+ /**
704
+ * Destroy service and cleanup resources
705
+ */
706
+ destroy(): void {
707
+ if (this.hnswService) {
708
+ this.hnswService.destroy();
709
+ }
710
+ this.hnswService = null;
711
+ this.hnswServicePromise = null;
712
+ this.memoryIndex.clear();
713
+ console.log('✅ MemoryIndexService destroyed');
714
+ }
715
+
716
+ // ==================== PRIVATE HELPER METHODS ====================
717
+
718
+ private createMemoryFilter(query: MemorySearchQuery) {
719
+ return (metadata: any) => {
720
+ // Category filter
721
+ if (query.categories && query.categories.length > 0) {
722
+ if (!query.categories.includes(metadata.category)) {
723
+ return false;
724
+ }
725
+ }
726
+
727
+ // Date range filter
728
+ if (query.dateRange) {
729
+ const created = new Date(metadata.createdTimestamp || 0);
730
+ if (query.dateRange.start && created < query.dateRange.start) {
731
+ return false;
732
+ }
733
+ if (query.dateRange.end && created > query.dateRange.end) {
734
+ return false;
735
+ }
736
+ }
737
+
738
+ // Importance range filter
739
+ if (query.importanceRange) {
740
+ const importance = metadata.importance || 5;
741
+ if (query.importanceRange.min && importance < query.importanceRange.min) {
742
+ return false;
743
+ }
744
+ if (query.importanceRange.max && importance > query.importanceRange.max) {
745
+ return false;
746
+ }
747
+ }
748
+
749
+ // Tags filter (search in custom metadata)
750
+ if (query.tags && query.tags.length > 0) {
751
+ const metadataText = JSON.stringify(metadata).toLowerCase();
752
+ const hasAnyTag = query.tags.some(tag =>
753
+ metadataText.includes(tag.toLowerCase())
754
+ );
755
+ if (!hasAnyTag) {
756
+ return false;
757
+ }
758
+ }
759
+
760
+ return true;
761
+ };
762
+ }
763
+
764
+ // ==================== HNSW HELPER METHODS ====================
765
+
766
+ /**
767
+ * Create metadata filter for HNSW search
768
+ */
769
+ private createMetadataFilter(query: MemorySearchQuery): ((metadata: any) => boolean) | undefined {
770
+ if (!query.categories && !query.dateRange && !query.importanceRange && !query.tags) {
771
+ return undefined;
772
+ }
773
+
774
+ return (metadata: any) => {
775
+ // Category filter
776
+ if (query.categories && query.categories.length > 0) {
777
+ if (!query.categories.includes(metadata.category)) {
778
+ return false;
779
+ }
780
+ }
781
+
782
+ // Date range filter
783
+ if (query.dateRange) {
784
+ const created = new Date(metadata.createdTimestamp || 0);
785
+ if (query.dateRange.start && created < query.dateRange.start) {
786
+ return false;
787
+ }
788
+ if (query.dateRange.end && created > query.dateRange.end) {
789
+ return false;
790
+ }
791
+ }
792
+
793
+ // Importance range filter
794
+ if (query.importanceRange) {
795
+ const importance = metadata.importance || 5;
796
+ if (query.importanceRange.min && importance < query.importanceRange.min) {
797
+ return false;
798
+ }
799
+ if (query.importanceRange.max && importance > query.importanceRange.max) {
800
+ return false;
801
+ }
802
+ }
803
+
804
+ // Tags filter
805
+ if (query.tags && query.tags.length > 0) {
806
+ const metadataText = JSON.stringify(metadata).toLowerCase();
807
+ const hasAnyTag = query.tags.some(tag =>
808
+ metadataText.includes(tag.toLowerCase())
809
+ );
810
+ if (!hasAnyTag) {
811
+ return false;
812
+ }
813
+ }
814
+
815
+ return true;
816
+ };
817
+ }
818
+
819
+ /**
820
+ * Update index statistics for a user
821
+ */
822
+ private updateIndexStats(userAddress: string): void {
823
+ let stats = this.indexStats.get(userAddress);
824
+ if (!stats) {
825
+ stats = {
826
+ totalVectors: 0,
827
+ avgSimilarity: 0,
828
+ searchLatency: [],
829
+ lastOptimized: new Date()
830
+ };
831
+ this.indexStats.set(userAddress, stats);
832
+ }
833
+ stats.totalVectors++;
834
+ }
835
+
836
+ /**
837
+ * Enhanced relevance scoring with multiple factors
838
+ */
839
+ private calculateAdvancedRelevanceScore(
840
+ similarity: number,
841
+ metadata: MemoryMetadata,
842
+ query: MemorySearchQuery,
843
+ queryVector: number[],
844
+ documentVector: number[]
845
+ ): number {
846
+ let score = similarity * 0.7; // Base similarity weight (increased)
847
+
848
+ // Importance boost
849
+ const importance = metadata.importance || 5;
850
+ score += (importance - 5) * 0.02; // -0.1 to +0.1 boost
851
+
852
+ // Category exact match boost
853
+ if (query.categories && query.categories.includes(metadata.category)) {
854
+ score += 0.15;
855
+ }
856
+
857
+ // Topic relevance boost
858
+ if (query.query && metadata.topic) {
859
+ const queryLower = query.query.toLowerCase();
860
+ const topicLower = metadata.topic.toLowerCase();
861
+ if (queryLower.includes(topicLower) || topicLower.includes(queryLower)) {
862
+ score += 0.1;
863
+ }
864
+ }
865
+
866
+ // Vector quality boost (based on vector magnitude)
867
+ const vectorMagnitude = this.calculateVectorMagnitude(documentVector);
868
+ if (vectorMagnitude > 0.1) { // Well-formed embedding
869
+ score += 0.05;
870
+ }
871
+
872
+ // Semantic consistency boost (cosine similarity in different metric)
873
+ const semanticConsistency = this.calculateSemanticConsistency(queryVector, documentVector);
874
+ score += semanticConsistency * 0.1;
875
+
876
+ return Math.min(1.0, Math.max(0.0, score));
877
+ }
878
+
879
+ /**
880
+ * Calculate recency boost based on creation timestamp
881
+ */
882
+ private calculateRecencyBoost(createdTimestamp: number): number {
883
+ const now = Date.now();
884
+ const ageInDays = (now - createdTimestamp) / (1000 * 60 * 60 * 24);
885
+
886
+ // Exponential decay: more recent = higher boost
887
+ if (ageInDays < 1) return 1.0; // Last day: full boost
888
+ if (ageInDays < 7) return 0.8; // Last week: 80% boost
889
+ if (ageInDays < 30) return 0.5; // Last month: 50% boost
890
+ if (ageInDays < 90) return 0.2; // Last quarter: 20% boost
891
+ return 0.0; // Older: no boost
892
+ }
893
+
894
+ /**
895
+ * Diversify search results to avoid clustering
896
+ */
897
+ private diversifyResults(results: MemorySearchResult[], diversityFactor: number): MemorySearchResult[] {
898
+ if (diversityFactor <= 0 || results.length <= 1) return results;
899
+
900
+ const diversified: MemorySearchResult[] = [];
901
+ const remaining = [...results];
902
+
903
+ // Always include the top result
904
+ if (remaining.length > 0) {
905
+ diversified.push(remaining.shift()!);
906
+ }
907
+
908
+ while (remaining.length > 0 && diversified.length < results.length * 0.8) {
909
+ let bestIndex = 0;
910
+ let bestScore = 0;
911
+
912
+ for (let i = 0; i < remaining.length; i++) {
913
+ const candidate = remaining[i];
914
+
915
+ // Calculate diversity score (distance from already selected results)
916
+ let minSimilarity = 1.0;
917
+ for (const selected of diversified) {
918
+ const similarity = candidate.similarity; // Could enhance with actual vector similarity
919
+ minSimilarity = Math.min(minSimilarity, similarity);
920
+ }
921
+
922
+ // Combine relevance and diversity
923
+ const diversityScore = candidate.relevanceScore * (1 - diversityFactor) +
924
+ (1 - minSimilarity) * diversityFactor;
925
+
926
+ if (diversityScore > bestScore) {
927
+ bestScore = diversityScore;
928
+ bestIndex = i;
929
+ }
930
+ }
931
+
932
+ diversified.push(remaining.splice(bestIndex, 1)[0]);
933
+ }
934
+
935
+ return diversified;
936
+ }
937
+
938
+ /**
939
+ * Update search performance statistics
940
+ */
941
+ private updateSearchStats(userAddress: string, latency: number): void {
942
+ let stats = this.indexStats.get(userAddress);
943
+ if (!stats) {
944
+ stats = {
945
+ totalVectors: 0,
946
+ avgSimilarity: 0,
947
+ searchLatency: [],
948
+ lastOptimized: new Date()
949
+ };
950
+ this.indexStats.set(userAddress, stats);
951
+ }
952
+
953
+ stats.searchLatency.push(latency);
954
+
955
+ // Keep only last 100 latency measurements
956
+ if (stats.searchLatency.length > 100) {
957
+ stats.searchLatency = stats.searchLatency.slice(-100);
958
+ }
959
+ }
960
+
961
+ /**
962
+ * Calculate vector magnitude
963
+ */
964
+ private calculateVectorMagnitude(vector: number[]): number {
965
+ let sum = 0;
966
+ for (const val of vector) {
967
+ sum += val * val;
968
+ }
969
+ return Math.sqrt(sum);
970
+ }
971
+
972
+ /**
973
+ * Calculate cosine similarity between two vectors
974
+ * Used for semantic consistency scoring
975
+ */
976
+ private cosineSimilarity(a: number[], b: number[]): number {
977
+ if (a.length !== b.length) return 0;
978
+
979
+ let dotProduct = 0;
980
+ let normA = 0;
981
+ let normB = 0;
982
+
983
+ for (let i = 0; i < a.length; i++) {
984
+ dotProduct += a[i] * b[i];
985
+ normA += a[i] * a[i];
986
+ normB += b[i] * b[i];
987
+ }
988
+
989
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
990
+ return magnitude === 0 ? 0 : dotProduct / magnitude;
991
+ }
992
+
993
+ /**
994
+ * Calculate semantic consistency score
995
+ */
996
+ private calculateSemanticConsistency(queryVector: number[], documentVector: number[]): number {
997
+ // Calculate angle between vectors (semantic consistency)
998
+ const similarity = this.cosineSimilarity(queryVector, documentVector);
999
+ const angle = Math.acos(Math.max(-1, Math.min(1, similarity)));
1000
+
1001
+ // Convert angle to consistency score (0-1, where 1 is perfect alignment)
1002
+ return 1 - (angle / Math.PI);
1003
+ }
1004
1004
  }