@cmdoss/memwal-sdk 0.6.2 → 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 (119) hide show
  1. package/ARCHITECTURE.md +547 -547
  2. package/BENCHMARKS.md +238 -238
  3. package/README.md +181 -181
  4. package/dist/ai-sdk/tools.d.ts +2 -2
  5. package/dist/ai-sdk/tools.js +2 -2
  6. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  7. package/dist/client/SimplePDWClient.d.ts +1 -1
  8. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  9. package/dist/client/SimplePDWClient.js +16 -7
  10. package/dist/client/SimplePDWClient.js.map +1 -1
  11. package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
  12. package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
  13. package/dist/client/namespaces/MemoryNamespace.d.ts +27 -0
  14. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  15. package/dist/client/namespaces/MemoryNamespace.js +104 -0
  16. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  17. package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
  18. package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
  19. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  20. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +22 -2
  21. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  22. package/dist/graph/GraphService.js +1 -1
  23. package/dist/graph/GraphService.js.map +1 -1
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
  29. package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
  30. package/dist/retrieval/MemoryRetrievalService.js +44 -4
  31. package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
  32. package/dist/services/EmbeddingService.d.ts +28 -1
  33. package/dist/services/EmbeddingService.d.ts.map +1 -1
  34. package/dist/services/EmbeddingService.js +54 -0
  35. package/dist/services/EmbeddingService.js.map +1 -1
  36. package/dist/services/IndexManager.d.ts +5 -1
  37. package/dist/services/IndexManager.d.ts.map +1 -1
  38. package/dist/services/IndexManager.js +17 -40
  39. package/dist/services/IndexManager.js.map +1 -1
  40. package/dist/services/QueryService.js +1 -1
  41. package/dist/services/QueryService.js.map +1 -1
  42. package/dist/services/StorageService.d.ts +10 -0
  43. package/dist/services/StorageService.d.ts.map +1 -1
  44. package/dist/services/StorageService.js +13 -0
  45. package/dist/services/StorageService.js.map +1 -1
  46. package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
  47. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  48. package/dist/services/storage/QuiltBatchManager.js +410 -20
  49. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  50. package/dist/services/storage/index.d.ts +1 -1
  51. package/dist/services/storage/index.d.ts.map +1 -1
  52. package/dist/services/storage/index.js.map +1 -1
  53. package/dist/utils/LRUCache.d.ts +106 -0
  54. package/dist/utils/LRUCache.d.ts.map +1 -0
  55. package/dist/utils/LRUCache.js +281 -0
  56. package/dist/utils/LRUCache.js.map +1 -0
  57. package/dist/utils/index.d.ts +1 -0
  58. package/dist/utils/index.d.ts.map +1 -1
  59. package/dist/utils/index.js +2 -0
  60. package/dist/utils/index.js.map +1 -1
  61. package/dist/utils/memoryIndexOnChain.d.ts +212 -0
  62. package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
  63. package/dist/utils/memoryIndexOnChain.js +312 -0
  64. package/dist/utils/memoryIndexOnChain.js.map +1 -0
  65. package/dist/utils/rebuildIndexNode.d.ts +29 -0
  66. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  67. package/dist/utils/rebuildIndexNode.js +366 -98
  68. package/dist/utils/rebuildIndexNode.js.map +1 -1
  69. package/dist/vector/HnswWasmService.d.ts +20 -5
  70. package/dist/vector/HnswWasmService.d.ts.map +1 -1
  71. package/dist/vector/HnswWasmService.js +73 -40
  72. package/dist/vector/HnswWasmService.js.map +1 -1
  73. package/dist/vector/IHnswService.d.ts +10 -1
  74. package/dist/vector/IHnswService.d.ts.map +1 -1
  75. package/dist/vector/IHnswService.js.map +1 -1
  76. package/dist/vector/NodeHnswService.d.ts +16 -0
  77. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  78. package/dist/vector/NodeHnswService.js +84 -5
  79. package/dist/vector/NodeHnswService.js.map +1 -1
  80. package/dist/vector/createHnswService.d.ts +1 -1
  81. package/dist/vector/createHnswService.js +1 -1
  82. package/dist/vector/index.d.ts +1 -1
  83. package/dist/vector/index.js +1 -1
  84. package/package.json +157 -157
  85. package/src/access/index.ts +8 -8
  86. package/src/aggregation/index.ts +8 -8
  87. package/src/ai-sdk/tools.ts +2 -2
  88. package/src/client/SimplePDWClient.ts +23 -8
  89. package/src/client/namespaces/EmbeddingsNamespace.ts +1 -1
  90. package/src/client/namespaces/MemoryNamespace.ts +137 -0
  91. package/src/client/namespaces/consolidated/AINamespace.ts +2 -2
  92. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +20 -2
  93. package/src/client/signers/DappKitSigner.ts +207 -207
  94. package/src/core/types/index.ts +1 -1
  95. package/src/generated/pdw/deps/sui/object.ts +12 -12
  96. package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
  97. package/src/generated/pdw/memory.ts +1087 -1087
  98. package/src/generated/pdw/wallet.ts +123 -123
  99. package/src/generated/utils/index.ts +159 -159
  100. package/src/graph/GraphService.ts +1 -1
  101. package/src/index.ts +25 -1
  102. package/src/permissions/index.ts +9 -9
  103. package/src/retrieval/MemoryRetrievalService.ts +78 -4
  104. package/src/services/EmbeddingService.ts +66 -1
  105. package/src/services/IndexManager.ts +18 -45
  106. package/src/services/QueryService.ts +1 -1
  107. package/src/services/StorageService.ts +15 -0
  108. package/src/services/storage/QuiltBatchManager.ts +492 -22
  109. package/src/services/storage/index.ts +6 -1
  110. package/src/utils/LRUCache.ts +378 -0
  111. package/src/utils/index.ts +8 -0
  112. package/src/utils/memoryIndexOnChain.ts +507 -0
  113. package/src/utils/rebuildIndexNode.ts +453 -106
  114. package/src/vector/HnswWasmService.ts +95 -43
  115. package/src/vector/IHnswService.ts +10 -1
  116. package/src/vector/NodeHnswService.ts +103 -5
  117. package/src/vector/createHnswService.ts +1 -1
  118. package/src/vector/index.ts +1 -1
  119. package/src/wallet/index.ts +17 -17
@@ -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
 
@@ -222,153 +338,384 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
222
338
  }
223
339
 
224
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`);
225
343
 
226
344
  let indexedCount = 0;
227
345
  let failedCount = 0;
228
346
  let processedCount = 0;
229
347
 
230
- // Cache for Quilt files to avoid re-fetching
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
+
231
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
+ }
435
+
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
+ }
454
+
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)`);
232
462
 
463
+ const processingStart = Date.now();
464
+
465
+ // ==================== PROCESS MEMORIES ====================
233
466
  for (const [blobId, memoriesInBlob] of memoriesByBlobId) {
234
467
  console.log(`[rebuildIndexNode] Processing blobId ${blobId.substring(0, 20)}... (${memoriesInBlob.length} memories)`);
235
468
 
236
- try {
237
- // Use getBlob().files() to correctly parse Quilt structure
238
- // For regular blob: returns [singleFile]
239
- // For Quilt: returns [file1, file2, ...] - all files in the quilt
240
- let files: WalrusFile[];
241
-
242
- if (quiltFileCache.has(blobId)) {
243
- files = quiltFileCache.get(blobId)!;
244
- console.log(`[rebuildIndexNode] ♻️ Using cached files (${files.length} files)`);
245
- } else {
246
- const blob = await walrusClient.walrus.getBlob({ blobId });
247
- files = await blob.files();
248
- quiltFileCache.set(blobId, files);
249
- console.log(`[rebuildIndexNode] 📥 Fetched ${files.length} file(s) from Walrus`);
250
- }
469
+ // Get pre-fetched files from cache
470
+ const files = quiltFileCache.get(blobId);
251
471
 
252
- // For each memory in this blobId
253
- for (let i = 0; i < memoriesInBlob.length; i++) {
254
- const memory = memoriesInBlob[i];
255
- processedCount++;
256
- const progress = `Memory ${processedCount}/${totalMemories}`;
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}`);
257
477
 
258
- console.log(`[rebuildIndexNode] Processing ${progress}: vectorId=${memory.vectorId}`);
259
- onProgress?.(processedCount, totalMemories, `Processing ${progress}...`);
478
+ for (const memory of memoriesInBlob) {
479
+ processedCount++;
480
+ failedCount++;
481
+ errors.push({ blobId: memory.blobId, error: `Blob fetch failed: ${errorMsg}` });
482
+ }
483
+ continue;
484
+ }
260
485
 
261
- try {
262
- // Determine which file to use
263
- // For Quilt: match by index
264
- // For single blob: use the only file
265
- const fileIndex = files.length === 1 ? 0 : Math.min(i, files.length - 1);
266
- const file = files[fileIndex];
267
-
268
- if (!file) {
269
- throw new Error(`No file found at index ${fileIndex}`);
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}`);
270
510
  }
511
+ }
271
512
 
272
- // Get file content
273
- const rawBytes = await file.bytes();
274
- const rawText = new TextDecoder().decode(rawBytes);
275
- const trimmedText = rawText.trim();
513
+ if (!file) {
514
+ throw new Error(`No file found for memory vectorId=${memory.vectorId} (blob has ${files.length} files)`);
515
+ }
276
516
 
277
- // Get file identifier and tags if available (for Quilts)
278
- const identifier = await file.getIdentifier();
279
- const tags = await file.getTags();
517
+ // Get file identifier and tags if available (for Quilts)
518
+ const identifier = await file.getIdentifier();
519
+ const tags = await file.getTags();
280
520
 
281
- if (identifier) {
282
- console.log(`[rebuildIndexNode] 📎 File identifier: ${identifier}`);
283
- }
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();
284
530
 
285
- let content: string;
286
- let embedding: number[];
287
- let metadata: { category?: string; importance?: number; topic?: string } = {};
288
- let timestamp = Date.now();
289
-
290
- if (trimmedText.startsWith('{') && trimmedText.endsWith('}')) {
291
- // JSON package format (correct format)
292
- try {
293
- const memoryData: MemoryContent = JSON.parse(trimmedText);
294
- content = memoryData.content;
295
- embedding = memoryData.embedding;
296
- metadata = memoryData.metadata || {};
297
- timestamp = memoryData.timestamp || Date.now();
298
-
299
- if (!embedding || embedding.length !== 3072) {
300
- throw new Error(`Invalid embedding in JSON: length=${embedding?.length || 0}`);
301
- }
531
+ if (identifier) {
532
+ console.log(`[rebuildIndexNode] 📎 File identifier: ${identifier}`);
533
+ }
302
534
 
303
- console.log(`[rebuildIndexNode] 📦 Format: JSON package`);
304
- } catch (jsonError) {
305
- throw new Error(`Invalid JSON structure: ${(jsonError as Error).message}`);
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}`);
306
551
  }
307
- } else if (trimmedText.length > 0 && !trimmedText.includes('\x00') && trimmedText.length < 10000) {
308
- // Plain text format - cannot index without embedding
309
- throw new Error('Plain text format detected but no embedding available - skip');
310
- } else {
311
- throw new Error('Binary, encrypted, or empty content - cannot index');
552
+
553
+ console.log(`[rebuildIndexNode] 📦 Format: JSON package`);
554
+ } catch (jsonError) {
555
+ throw new Error(`Invalid JSON structure: ${(jsonError as Error).message}`);
312
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
+ }
313
563
 
314
- // Add to HNSW index
315
- await hnswService.addVector(
316
- userAddress,
317
- memory.vectorId,
318
- embedding,
319
- {
320
- blobId: memory.blobId,
321
- memoryObjectId: memory.id,
322
- category: metadata.category || memory.category || tags?.['category'],
323
- importance: metadata.importance || memory.importance || parseInt(tags?.['importance'] || '5'),
324
- topic: metadata.topic || tags?.['topic'] || '',
325
- timestamp,
326
- content,
327
- isEncrypted: false
328
- }
329
- );
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
+ );
330
580
 
331
- indexedCount++;
332
- console.log(`[rebuildIndexNode] ✓ Indexed: "${content.substring(0, 30)}..."`);
581
+ indexedCount++;
582
+ console.log(`[rebuildIndexNode] ✓ Indexed: "${content.substring(0, 30)}..."`);
333
583
 
334
- } catch (error: any) {
335
- failedCount++;
336
- const errorMsg = error.message || String(error);
337
- errors.push({ blobId: memory.blobId, error: errorMsg });
338
- console.error(`[rebuildIndexNode] ✗ Failed: ${errorMsg}`);
339
- }
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}`);
340
589
  }
590
+ }
591
+ }
341
592
 
342
- } catch (error: any) {
343
- // Failed to fetch files for this blobId
344
- const errorMsg = error.message || String(error);
345
- console.error(`[rebuildIndexNode] ✗ Failed to fetch blobId: ${errorMsg}`);
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);
346
630
 
347
- for (const memory of memoriesInBlob) {
348
- processedCount++;
349
- failedCount++;
350
- errors.push({ blobId: memory.blobId, error: `Failed to fetch blob: ${errorMsg}` });
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}`);
351
678
  }
352
679
  }
680
+
681
+ console.log(`[rebuildIndexNode] Quilt indexing complete: ${quiltMemoriesIndexed}/${quiltMemoriesTotal}`);
353
682
  }
354
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
+
355
692
  // Force save index
693
+ const saveStart = Date.now();
356
694
  console.log('[rebuildIndexNode] Saving index to disk...');
357
- onProgress?.(totalMemories, totalMemories, 'Saving index...');
695
+ onProgress?.(finalTotal, finalTotal, 'Saving index...');
358
696
  await hnswService.flushBatch(userAddress);
697
+ timing.saveMs = Date.now() - saveStart;
698
+ console.log(`[rebuildIndexNode] ⏱️ Save: ${timing.saveMs}ms`);
359
699
 
360
700
  const duration = Date.now() - startTime;
361
701
  console.log('[rebuildIndexNode] Index rebuild complete!');
362
- console.log(`[rebuildIndexNode] Total: ${totalMemories}, Indexed: ${indexedCount}, Failed: ${failedCount}`);
702
+ console.log(`[rebuildIndexNode] On-chain: ${totalMemories}, Quilts: ${quiltMemoriesTotal}, Total indexed: ${finalIndexed}, Failed: ${finalFailed}`);
363
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)}%)`);
364
710
 
365
711
  return {
366
712
  success: true,
367
- totalMemories,
368
- indexedMemories: indexedCount,
369
- failedMemories: failedCount,
713
+ totalMemories: finalTotal,
714
+ indexedMemories: finalIndexed,
715
+ failedMemories: finalFailed,
370
716
  errors,
371
- duration
717
+ duration,
718
+ timing
372
719
  };
373
720
 
374
721
  } catch (error: any) {