@cmdoss/memwal-sdk 0.6.1 → 0.7.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 (148) hide show
  1. package/dist/ai-sdk/PDWVectorStore.d.ts.map +1 -1
  2. package/dist/ai-sdk/PDWVectorStore.js +4 -1
  3. package/dist/ai-sdk/PDWVectorStore.js.map +1 -1
  4. package/dist/ai-sdk/tools.d.ts +2 -2
  5. package/dist/ai-sdk/tools.js +2 -2
  6. package/dist/browser.d.ts +5 -6
  7. package/dist/browser.d.ts.map +1 -1
  8. package/dist/browser.js +7 -6
  9. package/dist/browser.js.map +1 -1
  10. package/dist/client/ClientMemoryManager.d.ts +1 -0
  11. package/dist/client/ClientMemoryManager.d.ts.map +1 -1
  12. package/dist/client/ClientMemoryManager.js +5 -1
  13. package/dist/client/ClientMemoryManager.js.map +1 -1
  14. package/dist/client/SimplePDWClient.d.ts +24 -1
  15. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  16. package/dist/client/SimplePDWClient.js +31 -9
  17. package/dist/client/SimplePDWClient.js.map +1 -1
  18. package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
  19. package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
  20. package/dist/client/namespaces/IndexNamespace.d.ts +38 -9
  21. package/dist/client/namespaces/IndexNamespace.d.ts.map +1 -1
  22. package/dist/client/namespaces/IndexNamespace.js +77 -10
  23. package/dist/client/namespaces/IndexNamespace.js.map +1 -1
  24. package/dist/client/namespaces/MemoryNamespace.d.ts +27 -0
  25. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  26. package/dist/client/namespaces/MemoryNamespace.js +104 -0
  27. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  28. package/dist/client/namespaces/SearchNamespace.d.ts.map +1 -1
  29. package/dist/client/namespaces/SearchNamespace.js +25 -14
  30. package/dist/client/namespaces/SearchNamespace.js.map +1 -1
  31. package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
  32. package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
  33. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  34. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +69 -1
  35. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  36. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +46 -0
  37. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  38. package/dist/client/namespaces/consolidated/StorageNamespace.js +34 -0
  39. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  40. package/dist/graph/GraphService.js +2 -2
  41. package/dist/graph/GraphService.js.map +1 -1
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +3 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/permissions/ConsentRepository.browser.d.ts +56 -0
  47. package/dist/permissions/ConsentRepository.browser.d.ts.map +1 -0
  48. package/dist/permissions/ConsentRepository.browser.js +198 -0
  49. package/dist/permissions/ConsentRepository.browser.js.map +1 -0
  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/EmbeddingService.d.ts +28 -1
  55. package/dist/services/EmbeddingService.d.ts.map +1 -1
  56. package/dist/services/EmbeddingService.js +54 -0
  57. package/dist/services/EmbeddingService.js.map +1 -1
  58. package/dist/services/GeminiAIService.d.ts.map +1 -1
  59. package/dist/services/GeminiAIService.js +283 -27
  60. package/dist/services/GeminiAIService.js.map +1 -1
  61. package/dist/services/IndexManager.d.ts +5 -1
  62. package/dist/services/IndexManager.d.ts.map +1 -1
  63. package/dist/services/IndexManager.js +17 -40
  64. package/dist/services/IndexManager.js.map +1 -1
  65. package/dist/services/MemoryIndexService.d.ts +31 -2
  66. package/dist/services/MemoryIndexService.d.ts.map +1 -1
  67. package/dist/services/MemoryIndexService.js +75 -3
  68. package/dist/services/MemoryIndexService.js.map +1 -1
  69. package/dist/services/QueryService.js +1 -1
  70. package/dist/services/QueryService.js.map +1 -1
  71. package/dist/services/StorageService.d.ts +10 -0
  72. package/dist/services/StorageService.d.ts.map +1 -1
  73. package/dist/services/StorageService.js +13 -0
  74. package/dist/services/StorageService.js.map +1 -1
  75. package/dist/services/storage/QuiltBatchManager.d.ts +111 -4
  76. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  77. package/dist/services/storage/QuiltBatchManager.js +450 -38
  78. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  79. package/dist/services/storage/index.d.ts +1 -1
  80. package/dist/services/storage/index.d.ts.map +1 -1
  81. package/dist/services/storage/index.js.map +1 -1
  82. package/dist/utils/LRUCache.d.ts +106 -0
  83. package/dist/utils/LRUCache.d.ts.map +1 -0
  84. package/dist/utils/LRUCache.js +281 -0
  85. package/dist/utils/LRUCache.js.map +1 -0
  86. package/dist/utils/index.d.ts +1 -0
  87. package/dist/utils/index.d.ts.map +1 -1
  88. package/dist/utils/index.js +2 -0
  89. package/dist/utils/index.js.map +1 -1
  90. package/dist/utils/memoryIndexOnChain.d.ts +212 -0
  91. package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
  92. package/dist/utils/memoryIndexOnChain.js +312 -0
  93. package/dist/utils/memoryIndexOnChain.js.map +1 -0
  94. package/dist/utils/rebuildIndexNode.d.ts +29 -0
  95. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  96. package/dist/utils/rebuildIndexNode.js +387 -45
  97. package/dist/utils/rebuildIndexNode.js.map +1 -1
  98. package/dist/vector/HnswWasmService.d.ts +20 -5
  99. package/dist/vector/HnswWasmService.d.ts.map +1 -1
  100. package/dist/vector/HnswWasmService.js +73 -40
  101. package/dist/vector/HnswWasmService.js.map +1 -1
  102. package/dist/vector/IHnswService.d.ts +10 -1
  103. package/dist/vector/IHnswService.d.ts.map +1 -1
  104. package/dist/vector/IHnswService.js.map +1 -1
  105. package/dist/vector/NodeHnswService.d.ts +16 -0
  106. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  107. package/dist/vector/NodeHnswService.js +108 -10
  108. package/dist/vector/NodeHnswService.js.map +1 -1
  109. package/dist/vector/createHnswService.d.ts +1 -1
  110. package/dist/vector/createHnswService.js +1 -1
  111. package/dist/vector/index.d.ts +1 -1
  112. package/dist/vector/index.js +1 -1
  113. package/package.json +157 -157
  114. package/src/ai-sdk/PDWVectorStore.ts +4 -1
  115. package/src/ai-sdk/tools.ts +2 -2
  116. package/src/browser.ts +15 -10
  117. package/src/client/ClientMemoryManager.ts +6 -1
  118. package/src/client/SimplePDWClient.ts +63 -10
  119. package/src/client/namespaces/EmbeddingsNamespace.ts +1 -1
  120. package/src/client/namespaces/IndexNamespace.ts +89 -11
  121. package/src/client/namespaces/MemoryNamespace.ts +137 -0
  122. package/src/client/namespaces/SearchNamespace.ts +27 -14
  123. package/src/client/namespaces/consolidated/AINamespace.ts +2 -2
  124. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +73 -1
  125. package/src/client/namespaces/consolidated/StorageNamespace.ts +57 -0
  126. package/src/core/types/index.ts +1 -1
  127. package/src/generated/pdw/capability.ts +319 -319
  128. package/src/graph/GraphService.ts +2 -2
  129. package/src/index.ts +25 -1
  130. package/src/permissions/ConsentRepository.browser.ts +249 -0
  131. package/src/retrieval/MemoryRetrievalService.ts +78 -4
  132. package/src/services/EmbeddingService.ts +66 -1
  133. package/src/services/GeminiAIService.ts +283 -27
  134. package/src/services/IndexManager.ts +18 -45
  135. package/src/services/MemoryIndexService.ts +85 -3
  136. package/src/services/QueryService.ts +1 -1
  137. package/src/services/StorageService.ts +15 -0
  138. package/src/services/storage/QuiltBatchManager.ts +538 -42
  139. package/src/services/storage/index.ts +6 -1
  140. package/src/utils/LRUCache.ts +378 -0
  141. package/src/utils/index.ts +8 -0
  142. package/src/utils/memoryIndexOnChain.ts +507 -0
  143. package/src/utils/rebuildIndexNode.ts +482 -52
  144. package/src/vector/HnswWasmService.ts +95 -43
  145. package/src/vector/IHnswService.ts +10 -1
  146. package/src/vector/NodeHnswService.ts +130 -10
  147. package/src/vector/createHnswService.ts +1 -1
  148. package/src/vector/index.ts +1 -1
@@ -27,7 +27,7 @@
27
27
  */
28
28
 
29
29
  import type { SuiClient } from '@mysten/sui/client';
30
- import { WalrusClient } from '@mysten/walrus';
30
+ import { WalrusClient, WalrusFile } from '@mysten/walrus';
31
31
 
32
32
  export interface RebuildIndexNodeOptions {
33
33
  /** User's blockchain address */
@@ -53,6 +53,20 @@ export interface RebuildIndexNodeOptions {
53
53
 
54
54
  /** Whether to force re-index even if index exists */
55
55
  force?: boolean;
56
+
57
+ /**
58
+ * Quilt IDs to include in the rebuild.
59
+ * Quilts contain batch-uploaded memories that may not have on-chain Memory objects.
60
+ * Pass Quilt IDs here to include them in the index rebuild.
61
+ */
62
+ quiltIds?: string[];
63
+
64
+ /**
65
+ * Number of concurrent blob fetches (default: 10)
66
+ * Higher values can speed up rebuilding but may overwhelm the server
67
+ * Benchmark results: 10 is ~1.64x faster than sequential
68
+ */
69
+ fetchConcurrency?: number;
56
70
  }
57
71
 
58
72
  export interface RebuildIndexNodeResult {
@@ -62,6 +76,23 @@ export interface RebuildIndexNodeResult {
62
76
  failedMemories: number;
63
77
  errors: Array<{ blobId: string; error: string }>;
64
78
  duration: number;
79
+ /** Detailed timing breakdown for performance analysis */
80
+ timing?: {
81
+ /** Time to initialize services (ms) */
82
+ initMs: number;
83
+ /** Time to fetch blockchain data (ms) */
84
+ blockchainFetchMs: number;
85
+ /** Time to fetch all blobs from Walrus (ms) */
86
+ walrusFetchMs: number;
87
+ /** Time to process memories and build index (ms) */
88
+ processingMs: number;
89
+ /** Time to save index to disk (ms) */
90
+ saveMs: number;
91
+ /** Total blobs fetched */
92
+ blobsFetched: number;
93
+ /** Total content bytes downloaded */
94
+ totalBytesDownloaded: number;
95
+ };
65
96
  }
66
97
 
67
98
  interface MemoryContent {
@@ -71,10 +102,79 @@ interface MemoryContent {
71
102
  category: string;
72
103
  importance: number;
73
104
  topic: string;
105
+ memoryId?: string;
74
106
  };
75
107
  timestamp: number;
76
108
  }
77
109
 
110
+ /**
111
+ * Find a matching file in a Quilt using multiple strategies
112
+ * Mirrors the logic in SDK's QuiltBatchManager.findMemoryInQuilt()
113
+ *
114
+ * Strategies (in order):
115
+ * 1. Match by tags['memory_id'] === vectorId
116
+ * 2. Match by identifier === `memory-${vectorId}.json`
117
+ * 3. Match by JSON metadata.memoryId === vectorId
118
+ * 4. Fallback to index-based matching
119
+ */
120
+ async function findMatchingFile(
121
+ files: WalrusFile[],
122
+ vectorId: number,
123
+ fallbackIndex: number
124
+ ): Promise<{ file: WalrusFile | undefined; matchStrategy: string }> {
125
+ let matchedFile: WalrusFile | undefined;
126
+ let matchStrategy = '';
127
+
128
+ // Strategy 1: Match by tags['memory_id']
129
+ for (const f of files) {
130
+ const tags = await f.getTags();
131
+ if (tags?.['memory_id'] === String(vectorId)) {
132
+ matchedFile = f;
133
+ const identifier = await f.getIdentifier();
134
+ matchStrategy = `memory_id tag: ${tags['memory_id']} (${identifier})`;
135
+ break;
136
+ }
137
+ }
138
+
139
+ // Strategy 2: Match by identifier pattern "memory-{vectorId}.json"
140
+ if (!matchedFile) {
141
+ for (const f of files) {
142
+ const identifier = await f.getIdentifier();
143
+ if (identifier === `memory-${vectorId}.json`) {
144
+ matchedFile = f;
145
+ matchStrategy = `identifier: ${identifier}`;
146
+ break;
147
+ }
148
+ }
149
+ }
150
+
151
+ // Strategy 3: Parse JSON to find matching metadata.memoryId
152
+ if (!matchedFile) {
153
+ for (const f of files) {
154
+ try {
155
+ const json = await f.json() as MemoryContent;
156
+ if (json?.metadata?.memoryId === String(vectorId)) {
157
+ matchedFile = f;
158
+ const identifier = await f.getIdentifier();
159
+ matchStrategy = `JSON metadata.memoryId: ${json.metadata.memoryId} (${identifier})`;
160
+ break;
161
+ }
162
+ } catch {
163
+ // Not valid JSON, continue
164
+ }
165
+ }
166
+ }
167
+
168
+ // Strategy 4: Fallback to index-based matching
169
+ if (!matchedFile && fallbackIndex < files.length) {
170
+ matchedFile = files[fallbackIndex];
171
+ const identifier = await matchedFile.getIdentifier();
172
+ matchStrategy = `index fallback (${fallbackIndex}): ${identifier || 'no identifier'}`;
173
+ }
174
+
175
+ return { file: matchedFile, matchStrategy };
176
+ }
177
+
78
178
  /**
79
179
  * Rebuild HNSW index from blockchain + Walrus (Node.js)
80
180
  */
@@ -87,12 +187,25 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
87
187
  walrusAggregator,
88
188
  indexDirectory = './.pdw-indexes',
89
189
  onProgress,
90
- force = false
190
+ force = false,
191
+ quiltIds = [],
192
+ fetchConcurrency = 10
91
193
  } = options;
92
194
 
93
195
  const startTime = Date.now();
94
196
  const errors: Array<{ blobId: string; error: string }> = [];
95
197
 
198
+ // Detailed timing
199
+ const timing = {
200
+ initMs: 0,
201
+ blockchainFetchMs: 0,
202
+ walrusFetchMs: 0,
203
+ processingMs: 0,
204
+ saveMs: 0,
205
+ blobsFetched: 0,
206
+ totalBytesDownloaded: 0,
207
+ };
208
+
96
209
  console.log('[rebuildIndexNode] Starting index rebuild...');
97
210
  onProgress?.(0, 0, 'Initializing...');
98
211
 
@@ -123,6 +236,8 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
123
236
  });
124
237
 
125
238
  await hnswService.initialize();
239
+ timing.initMs = Date.now() - startTime;
240
+ console.log(`[rebuildIndexNode] ⏱️ Init: ${timing.initMs}ms`);
126
241
 
127
242
  // Check if index exists
128
243
  const indexPath = `${indexDirectory}/${userAddress.replace(/[^a-zA-Z0-9]/g, '_')}.hnsw`;
@@ -152,6 +267,7 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
152
267
  }
153
268
 
154
269
  // Fetch all memories from blockchain
270
+ const blockchainFetchStart = Date.now();
155
271
  console.log('[rebuildIndexNode] Fetching memories from blockchain...');
156
272
  onProgress?.(0, 0, 'Fetching memories from blockchain...');
157
273
 
@@ -212,80 +328,394 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
212
328
  };
213
329
  }
214
330
 
215
- // Process each memory
331
+ // Process memories grouped by blobId (for Quilt support)
332
+ // In a Quilt, multiple memories share the same blobId
333
+ const memoriesByBlobId = new Map<string, typeof memories>();
334
+ for (const memory of memories) {
335
+ const list = memoriesByBlobId.get(memory.blobId) || [];
336
+ list.push(memory);
337
+ memoriesByBlobId.set(memory.blobId, list);
338
+ }
339
+
340
+ console.log(`[rebuildIndexNode] Unique blobIds: ${memoriesByBlobId.size} (${memoriesByBlobId.size < totalMemories ? 'Quilt detected' : 'individual blobs'})`);
341
+ timing.blockchainFetchMs = Date.now() - blockchainFetchStart;
342
+ console.log(`[rebuildIndexNode] ⏱️ Blockchain fetch: ${timing.blockchainFetchMs}ms`);
343
+
216
344
  let indexedCount = 0;
217
345
  let failedCount = 0;
346
+ let processedCount = 0;
347
+
348
+ // ==================== PARALLEL BLOB FETCHING + CONTENT ====================
349
+ // Step 1: Check blob types (Quilt vs regular) in parallel
350
+ // Step 2: Fetch content in parallel (patches for Quilt, bytes for regular)
351
+ const blobIds = Array.from(memoriesByBlobId.keys());
352
+
353
+ console.log(`[rebuildIndexNode] Fetching ${blobIds.length} blobs (concurrency: ${fetchConcurrency})...`);
354
+ const fetchStartTime = Date.now();
355
+
356
+ const quiltFileCache = new Map<string, WalrusFile[]>();
357
+ const contentCache = new Map<string, Uint8Array>(); // blobId or blobId:identifier -> content
358
+ const fetchErrors: Array<{ blobId: string; error: string }> = [];
359
+
360
+ // Process in batches to control concurrency
361
+ for (let i = 0; i < blobIds.length; i += fetchConcurrency) {
362
+ const batch = blobIds.slice(i, i + fetchConcurrency);
363
+ const batchNum = Math.floor(i / fetchConcurrency) + 1;
364
+ const totalBatches = Math.ceil(blobIds.length / fetchConcurrency);
365
+
366
+ console.log(`[rebuildIndexNode] 📥 Batch ${batchNum}/${totalBatches}: ${batch.length} blobs...`);
367
+ onProgress?.(i, blobIds.length, `Fetching batch ${batchNum}/${totalBatches}...`);
368
+
369
+ // Parallel fetch: check type + fetch content for each blob
370
+ const results = await Promise.all(
371
+ batch.map(async (blobId) => {
372
+ try {
373
+ // Try as Quilt first (getBlob + files)
374
+ try {
375
+ const blob = await walrusClient.walrus.getBlob({ blobId });
376
+ const quiltFiles = await blob.files();
377
+
378
+ if (quiltFiles.length > 1) {
379
+ // It's a Quilt with multiple patches - fetch all content in parallel
380
+ const patchResults = await Promise.all(
381
+ quiltFiles.map(async (file) => {
382
+ const identifier = await file.getIdentifier();
383
+ const tags = await file.getTags();
384
+ const bytes = await file.bytes();
385
+ return { file, identifier, tags, bytes };
386
+ })
387
+ );
388
+
389
+ return {
390
+ blobId,
391
+ success: true,
392
+ isQuilt: true,
393
+ files: quiltFiles,
394
+ patches: patchResults,
395
+ };
396
+ } else {
397
+ // Single file in blob - fetch content
398
+ const bytes = await quiltFiles[0].bytes();
399
+ return {
400
+ blobId,
401
+ success: true,
402
+ isQuilt: false,
403
+ files: quiltFiles,
404
+ bytes,
405
+ };
406
+ }
407
+ } catch {
408
+ // Not a Quilt - try as regular blob
409
+ const files = await walrusClient.walrus.getFiles({ ids: [blobId] });
410
+ if (files[0]) {
411
+ const bytes = await files[0].bytes();
412
+ return {
413
+ blobId,
414
+ success: true,
415
+ isQuilt: false,
416
+ files,
417
+ bytes,
418
+ };
419
+ }
420
+ return { blobId, success: false, error: 'No file found' };
421
+ }
422
+ } catch (error: any) {
423
+ return { blobId, success: false, error: error.message || String(error) };
424
+ }
425
+ })
426
+ );
427
+
428
+ // Process results into caches
429
+ for (const result of results) {
430
+ if (!result.success) {
431
+ fetchErrors.push({ blobId: result.blobId, error: result.error || 'Unknown error' });
432
+ console.error(`[rebuildIndexNode] ✗ ${result.blobId.substring(0, 16)}...: ${result.error}`);
433
+ continue;
434
+ }
218
435
 
219
- for (let i = 0; i < memories.length; i++) {
220
- const memory = memories[i];
221
- const progress = `Memory ${i + 1}/${totalMemories}`;
436
+ if (result.isQuilt && result.patches) {
437
+ // Quilt: cache files and patch contents
438
+ quiltFileCache.set(result.blobId, result.files!);
439
+ for (const patch of result.patches) {
440
+ const cacheKey = patch.identifier
441
+ ? `${result.blobId}:${patch.identifier}`
442
+ : result.blobId;
443
+ contentCache.set(cacheKey, patch.bytes);
444
+ }
445
+ console.log(`[rebuildIndexNode] ✓ ${result.blobId.substring(0, 16)}... (Quilt: ${result.patches.length} patches)`);
446
+ } else if (result.bytes) {
447
+ // Regular blob: cache file and content
448
+ quiltFileCache.set(result.blobId, result.files!);
449
+ contentCache.set(result.blobId, result.bytes);
450
+ console.log(`[rebuildIndexNode] ✓ ${result.blobId.substring(0, 16)}... (${result.bytes.length} bytes)`);
451
+ }
452
+ }
453
+ }
222
454
 
223
- console.log(`[rebuildIndexNode] Processing ${progress}: ${memory.blobId.substring(0, 20)}...`);
224
- onProgress?.(i + 1, totalMemories, `Processing ${progress}...`);
455
+ timing.walrusFetchMs = Date.now() - fetchStartTime;
456
+ timing.blobsFetched = quiltFileCache.size;
457
+ // Calculate total bytes downloaded
458
+ for (const bytes of contentCache.values()) {
459
+ timing.totalBytesDownloaded += bytes.length;
460
+ }
461
+ console.log(`[rebuildIndexNode] ⏱️ Walrus fetch: ${timing.walrusFetchMs}ms (${quiltFileCache.size} blobs, ${contentCache.size} contents, ${(timing.totalBytesDownloaded / 1024).toFixed(1)}KB)`);
225
462
 
226
- try {
227
- // Download content from Walrus using SDK
228
- const blobContent = await walrusClient.walrus.readBlob({ blobId: memory.blobId });
229
-
230
- // Parse JSON content
231
- const textDecoder = new TextDecoder();
232
- const jsonString = textDecoder.decode(blobContent);
233
- const memoryData: MemoryContent = JSON.parse(jsonString);
234
-
235
- // Extract embedding
236
- const embedding = memoryData.embedding;
237
- if (!embedding || embedding.length !== 3072) {
238
- throw new Error(`Invalid embedding: length=${embedding?.length || 0}`);
463
+ const processingStart = Date.now();
464
+
465
+ // ==================== PROCESS MEMORIES ====================
466
+ for (const [blobId, memoriesInBlob] of memoriesByBlobId) {
467
+ console.log(`[rebuildIndexNode] Processing blobId ${blobId.substring(0, 20)}... (${memoriesInBlob.length} memories)`);
468
+
469
+ // Get pre-fetched files from cache
470
+ const files = quiltFileCache.get(blobId);
471
+
472
+ if (!files) {
473
+ // Blob fetch failed - mark all memories in this blob as failed
474
+ const fetchError = fetchErrors.find(e => e.blobId === blobId);
475
+ const errorMsg = fetchError?.error || 'Failed to fetch blob';
476
+ console.error(`[rebuildIndexNode] ✗ No files available: ${errorMsg}`);
477
+
478
+ for (const memory of memoriesInBlob) {
479
+ processedCount++;
480
+ failedCount++;
481
+ errors.push({ blobId: memory.blobId, error: `Blob fetch failed: ${errorMsg}` });
239
482
  }
483
+ continue;
484
+ }
240
485
 
241
- // Add to HNSW index
242
- // Option A+: Store content in index for fast local retrieval (no Walrus fetch needed)
243
- // Note: Only for unencrypted content. If content was encrypted, it won't be parseable anyway.
244
- await hnswService.addVector(
245
- userAddress,
246
- memory.vectorId,
247
- embedding,
248
- {
249
- blobId: memory.blobId,
250
- memoryObjectId: memory.id,
251
- category: memory.category,
252
- importance: memory.importance,
253
- topic: memoryData.metadata?.topic || '',
254
- timestamp: memoryData.timestamp,
255
- // Option A+: Store content for fast retrieval (avoids Walrus fetch on search)
256
- content: memoryData.content,
257
- isEncrypted: false // If we can parse it, it's not encrypted
486
+ console.log(`[rebuildIndexNode] 📦 Using ${files.length} pre-fetched file(s)`);
487
+
488
+ // For each memory in this blobId
489
+ for (let i = 0; i < memoriesInBlob.length; i++) {
490
+ const memory = memoriesInBlob[i];
491
+ processedCount++;
492
+ const progress = `Memory ${processedCount}/${totalMemories}`;
493
+
494
+ console.log(`[rebuildIndexNode] Processing ${progress}: vectorId=${memory.vectorId}`);
495
+ onProgress?.(processedCount, totalMemories, `Processing ${progress}...`);
496
+
497
+ try {
498
+ // Find matching file using helper function (mirrors SDK's QuiltBatchManager.findMemoryInQuilt)
499
+ let file: WalrusFile | undefined;
500
+
501
+ if (files.length === 1) {
502
+ // Single file - use it directly
503
+ file = files[0];
504
+ } else if (files.length > 1) {
505
+ // Multiple files in Quilt - use matching strategies
506
+ const { file: matchedFile, matchStrategy } = await findMatchingFile(files, memory.vectorId, i);
507
+ file = matchedFile;
508
+ if (matchStrategy) {
509
+ console.log(`[rebuildIndexNode] 🎯 Matched by ${matchStrategy}`);
510
+ }
258
511
  }
259
- );
260
512
 
261
- indexedCount++;
262
- console.log(`[rebuildIndexNode] ✓ Indexed: ${memory.blobId.substring(0, 20)}...`);
513
+ if (!file) {
514
+ throw new Error(`No file found for memory vectorId=${memory.vectorId} (blob has ${files.length} files)`);
515
+ }
516
+
517
+ // Get file identifier and tags if available (for Quilts)
518
+ const identifier = await file.getIdentifier();
519
+ const tags = await file.getTags();
520
+
521
+ // Get content from cache (already pre-fetched) or fetch if not cached
522
+ const cacheKey = identifier ? `${blobId}:${identifier}` : blobId;
523
+ let rawBytes = contentCache.get(cacheKey);
524
+ if (!rawBytes) {
525
+ // Fallback: fetch content if not in cache
526
+ rawBytes = await file.bytes();
527
+ }
528
+ const rawText = new TextDecoder().decode(rawBytes);
529
+ const trimmedText = rawText.trim();
263
530
 
264
- } catch (error: any) {
265
- failedCount++;
266
- const errorMsg = error.message || String(error);
267
- errors.push({ blobId: memory.blobId, error: errorMsg });
268
- console.error(`[rebuildIndexNode] ✗ Failed: ${errorMsg}`);
531
+ if (identifier) {
532
+ console.log(`[rebuildIndexNode] 📎 File identifier: ${identifier}`);
533
+ }
534
+
535
+ let content: string;
536
+ let embedding: number[];
537
+ let metadata: { category?: string; importance?: number; topic?: string } = {};
538
+ let timestamp = Date.now();
539
+
540
+ if (trimmedText.startsWith('{') && trimmedText.endsWith('}')) {
541
+ // JSON package format (correct format)
542
+ try {
543
+ const memoryData: MemoryContent = JSON.parse(trimmedText);
544
+ content = memoryData.content;
545
+ embedding = memoryData.embedding;
546
+ metadata = memoryData.metadata || {};
547
+ timestamp = memoryData.timestamp || Date.now();
548
+
549
+ if (!embedding || embedding.length !== 3072) {
550
+ throw new Error(`Invalid embedding in JSON: length=${embedding?.length || 0}`);
551
+ }
552
+
553
+ console.log(`[rebuildIndexNode] 📦 Format: JSON package`);
554
+ } catch (jsonError) {
555
+ throw new Error(`Invalid JSON structure: ${(jsonError as Error).message}`);
556
+ }
557
+ } else if (trimmedText.length > 0 && !trimmedText.includes('\x00') && trimmedText.length < 10000) {
558
+ // Plain text format - cannot index without embedding
559
+ throw new Error('Plain text format detected but no embedding available - skip');
560
+ } else {
561
+ throw new Error('Binary, encrypted, or empty content - cannot index');
562
+ }
563
+
564
+ // Add to HNSW index
565
+ await hnswService.addVector(
566
+ userAddress,
567
+ memory.vectorId,
568
+ embedding,
569
+ {
570
+ blobId: memory.blobId,
571
+ memoryObjectId: memory.id,
572
+ category: metadata.category || memory.category || tags?.['category'],
573
+ importance: metadata.importance || memory.importance || parseInt(tags?.['importance'] || '5'),
574
+ topic: metadata.topic || tags?.['topic'] || '',
575
+ timestamp,
576
+ content,
577
+ isEncrypted: false
578
+ }
579
+ );
580
+
581
+ indexedCount++;
582
+ console.log(`[rebuildIndexNode] ✓ Indexed: "${content.substring(0, 30)}..."`);
583
+
584
+ } catch (error: any) {
585
+ failedCount++;
586
+ const errorMsg = error.message || String(error);
587
+ errors.push({ blobId: memory.blobId, error: errorMsg });
588
+ console.error(`[rebuildIndexNode] ✗ Failed: ${errorMsg}`);
589
+ }
590
+ }
591
+ }
592
+
593
+ // ==================== QUILT MEMORIES ====================
594
+ // Process additional Quilts that may not have on-chain Memory objects
595
+ let quiltMemoriesTotal = 0;
596
+ let quiltMemoriesIndexed = 0;
597
+
598
+ if (quiltIds.length > 0) {
599
+ console.log(`\n[rebuildIndexNode] Processing ${quiltIds.length} additional Quilt(s)...`);
600
+ onProgress?.(processedCount, totalMemories + quiltIds.length, 'Processing Quilts...');
601
+
602
+ for (const quiltId of quiltIds) {
603
+ console.log(`[rebuildIndexNode] Processing Quilt: ${quiltId.substring(0, 30)}...`);
604
+
605
+ try {
606
+ // Fetch Quilt files
607
+ const blob = await walrusClient.walrus.getBlob({ blobId: quiltId });
608
+ const files = await blob.files();
609
+ console.log(`[rebuildIndexNode] 📥 Fetched Quilt: ${files.length} file(s)`);
610
+
611
+ // Process each file in the Quilt
612
+ for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
613
+ const file = files[fileIdx];
614
+ quiltMemoriesTotal++;
615
+
616
+ try {
617
+ const identifier = await file.getIdentifier() || `quilt-file-${fileIdx}`;
618
+ const tags = await file.getTags();
619
+
620
+ // Parse JSON content
621
+ const rawBytes = await file.bytes();
622
+ let rawText = new TextDecoder().decode(rawBytes);
623
+
624
+ // Trim trailing null bytes (Quilt corruption workaround)
625
+ let lastValidIndex = rawText.length - 1;
626
+ while (lastValidIndex >= 0 && rawText.charCodeAt(lastValidIndex) === 0) {
627
+ lastValidIndex--;
628
+ }
629
+ rawText = rawText.slice(0, lastValidIndex + 1);
630
+
631
+ if (!rawText.startsWith('{') || !rawText.endsWith('}')) {
632
+ throw new Error('Not a JSON file');
633
+ }
634
+
635
+ const memoryData: MemoryContent = JSON.parse(rawText);
636
+
637
+ if (!memoryData.embedding || memoryData.embedding.length === 0) {
638
+ throw new Error('No embedding in package');
639
+ }
640
+
641
+ // Generate unique vector ID for Quilt memory
642
+ const vectorId = Date.now() % 4294967295 + fileIdx;
643
+ const memoryId = (memoryData as any).metadata?.memoryId || identifier.replace('.json', '');
644
+
645
+ // Add to HNSW index
646
+ await hnswService.addVector(
647
+ userAddress,
648
+ vectorId,
649
+ memoryData.embedding,
650
+ {
651
+ blobId: quiltId,
652
+ memoryObjectId: memoryId,
653
+ category: memoryData.metadata?.category || tags?.['category'] || 'general',
654
+ importance: memoryData.metadata?.importance || parseInt(tags?.['importance'] || '3'),
655
+ topic: memoryData.metadata?.topic || tags?.['topic'] || '',
656
+ timestamp: memoryData.timestamp || Date.now(),
657
+ content: memoryData.content || '[encrypted]',
658
+ isEncrypted: (memoryData as any).encrypted === true,
659
+ quiltId,
660
+ identifier
661
+ }
662
+ );
663
+
664
+ quiltMemoriesIndexed++;
665
+ console.log(`[rebuildIndexNode] ✓ Indexed Quilt file: ${identifier}`);
666
+
667
+ } catch (fileError: any) {
668
+ const errorMsg = fileError.message || String(fileError);
669
+ errors.push({ blobId: quiltId, error: `File ${fileIdx}: ${errorMsg}` });
670
+ console.error(`[rebuildIndexNode] ✗ Failed file ${fileIdx}: ${errorMsg}`);
671
+ }
672
+ }
673
+
674
+ } catch (quiltError: any) {
675
+ const errorMsg = quiltError.message || String(quiltError);
676
+ errors.push({ blobId: quiltId, error: `Quilt fetch failed: ${errorMsg}` });
677
+ console.error(`[rebuildIndexNode] ✗ Failed to fetch Quilt: ${errorMsg}`);
678
+ }
269
679
  }
680
+
681
+ console.log(`[rebuildIndexNode] Quilt indexing complete: ${quiltMemoriesIndexed}/${quiltMemoriesTotal}`);
270
682
  }
271
683
 
684
+ // Update totals
685
+ const finalTotal = totalMemories + quiltMemoriesTotal;
686
+ const finalIndexed = indexedCount + quiltMemoriesIndexed;
687
+ const finalFailed = failedCount + (quiltMemoriesTotal - quiltMemoriesIndexed);
688
+
689
+ timing.processingMs = Date.now() - processingStart;
690
+ console.log(`[rebuildIndexNode] ⏱️ Processing: ${timing.processingMs}ms`);
691
+
272
692
  // Force save index
693
+ const saveStart = Date.now();
273
694
  console.log('[rebuildIndexNode] Saving index to disk...');
274
- onProgress?.(totalMemories, totalMemories, 'Saving index...');
695
+ onProgress?.(finalTotal, finalTotal, 'Saving index...');
275
696
  await hnswService.flushBatch(userAddress);
697
+ timing.saveMs = Date.now() - saveStart;
698
+ console.log(`[rebuildIndexNode] ⏱️ Save: ${timing.saveMs}ms`);
276
699
 
277
700
  const duration = Date.now() - startTime;
278
701
  console.log('[rebuildIndexNode] Index rebuild complete!');
279
- console.log(`[rebuildIndexNode] Total: ${totalMemories}, Indexed: ${indexedCount}, Failed: ${failedCount}`);
702
+ console.log(`[rebuildIndexNode] On-chain: ${totalMemories}, Quilts: ${quiltMemoriesTotal}, Total indexed: ${finalIndexed}, Failed: ${finalFailed}`);
280
703
  console.log(`[rebuildIndexNode] Duration: ${(duration / 1000).toFixed(2)}s`);
704
+ console.log(`[rebuildIndexNode] ⏱️ TIMING BREAKDOWN:`);
705
+ console.log(` Init: ${timing.initMs}ms (${((timing.initMs / duration) * 100).toFixed(1)}%)`);
706
+ console.log(` Blockchain: ${timing.blockchainFetchMs}ms (${((timing.blockchainFetchMs / duration) * 100).toFixed(1)}%)`);
707
+ console.log(` Walrus: ${timing.walrusFetchMs}ms (${((timing.walrusFetchMs / duration) * 100).toFixed(1)}%)`);
708
+ console.log(` Processing: ${timing.processingMs}ms (${((timing.processingMs / duration) * 100).toFixed(1)}%)`);
709
+ console.log(` Save: ${timing.saveMs}ms (${((timing.saveMs / duration) * 100).toFixed(1)}%)`);
281
710
 
282
711
  return {
283
712
  success: true,
284
- totalMemories,
285
- indexedMemories: indexedCount,
286
- failedMemories: failedCount,
713
+ totalMemories: finalTotal,
714
+ indexedMemories: finalIndexed,
715
+ failedMemories: finalFailed,
287
716
  errors,
288
- duration
717
+ duration,
718
+ timing
289
719
  };
290
720
 
291
721
  } catch (error: any) {