@cmdoss/memwal-sdk 0.6.1 → 0.6.2

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 (82) 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/PDWVectorStore.d.ts.map +1 -1
  5. package/dist/ai-sdk/PDWVectorStore.js +4 -1
  6. package/dist/ai-sdk/PDWVectorStore.js.map +1 -1
  7. package/dist/browser.d.ts +5 -6
  8. package/dist/browser.d.ts.map +1 -1
  9. package/dist/browser.js +7 -6
  10. package/dist/browser.js.map +1 -1
  11. package/dist/client/ClientMemoryManager.d.ts +1 -0
  12. package/dist/client/ClientMemoryManager.d.ts.map +1 -1
  13. package/dist/client/ClientMemoryManager.js +5 -1
  14. package/dist/client/ClientMemoryManager.js.map +1 -1
  15. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  16. package/dist/client/SimplePDWClient.d.ts +23 -0
  17. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  18. package/dist/client/SimplePDWClient.js +15 -2
  19. package/dist/client/SimplePDWClient.js.map +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/SearchNamespace.d.ts.map +1 -1
  25. package/dist/client/namespaces/SearchNamespace.js +25 -14
  26. package/dist/client/namespaces/SearchNamespace.js.map +1 -1
  27. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  28. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +49 -1
  29. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  30. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +46 -0
  31. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  32. package/dist/client/namespaces/consolidated/StorageNamespace.js +34 -0
  33. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  34. package/dist/graph/GraphService.js +1 -1
  35. package/dist/permissions/ConsentRepository.browser.d.ts +56 -0
  36. package/dist/permissions/ConsentRepository.browser.d.ts.map +1 -0
  37. package/dist/permissions/ConsentRepository.browser.js +198 -0
  38. package/dist/permissions/ConsentRepository.browser.js.map +1 -0
  39. package/dist/services/GeminiAIService.d.ts.map +1 -1
  40. package/dist/services/GeminiAIService.js +283 -27
  41. package/dist/services/GeminiAIService.js.map +1 -1
  42. package/dist/services/MemoryIndexService.d.ts +31 -2
  43. package/dist/services/MemoryIndexService.d.ts.map +1 -1
  44. package/dist/services/MemoryIndexService.js +75 -3
  45. package/dist/services/MemoryIndexService.js.map +1 -1
  46. package/dist/services/storage/QuiltBatchManager.d.ts +10 -3
  47. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  48. package/dist/services/storage/QuiltBatchManager.js +49 -27
  49. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  50. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  51. package/dist/utils/rebuildIndexNode.js +109 -35
  52. package/dist/utils/rebuildIndexNode.js.map +1 -1
  53. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  54. package/dist/vector/NodeHnswService.js +26 -7
  55. package/dist/vector/NodeHnswService.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/access/index.ts +8 -8
  58. package/src/aggregation/index.ts +8 -8
  59. package/src/ai-sdk/PDWVectorStore.ts +4 -1
  60. package/src/browser.ts +15 -10
  61. package/src/client/ClientMemoryManager.ts +6 -1
  62. package/src/client/SimplePDWClient.ts +40 -2
  63. package/src/client/namespaces/IndexNamespace.ts +89 -11
  64. package/src/client/namespaces/SearchNamespace.ts +27 -14
  65. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +55 -1
  66. package/src/client/namespaces/consolidated/StorageNamespace.ts +57 -0
  67. package/src/client/signers/DappKitSigner.ts +207 -207
  68. package/src/generated/pdw/capability.ts +319 -319
  69. package/src/generated/pdw/deps/sui/object.ts +12 -12
  70. package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
  71. package/src/generated/pdw/memory.ts +1087 -1087
  72. package/src/generated/pdw/wallet.ts +123 -123
  73. package/src/generated/utils/index.ts +159 -159
  74. package/src/graph/GraphService.ts +1 -1
  75. package/src/permissions/ConsentRepository.browser.ts +249 -0
  76. package/src/permissions/index.ts +9 -9
  77. package/src/services/GeminiAIService.ts +283 -27
  78. package/src/services/MemoryIndexService.ts +85 -3
  79. package/src/services/storage/QuiltBatchManager.ts +55 -29
  80. package/src/utils/rebuildIndexNode.ts +126 -43
  81. package/src/vector/NodeHnswService.ts +29 -7
  82. package/src/wallet/index.ts +17 -17
@@ -568,18 +568,34 @@ export class MemoryIndexService {
568
568
  }
569
569
 
570
570
  /**
571
- * Load index from storage
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
572
575
  */
573
576
  async loadIndex(userAddress: string, indexBlobId?: string): Promise<void> {
574
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
575
591
  const loaded = await hnswService.loadIndex(userAddress);
576
592
  if (loaded) {
577
- console.log(`✅ Memory index loaded for user ${userAddress}`);
593
+ console.log(`✅ Memory index loaded from local storage for user ${userAddress}`);
578
594
  }
579
595
  }
580
596
 
581
597
  /**
582
- * Save index to storage
598
+ * Save index to local storage
583
599
  */
584
600
  async saveIndex(userAddress: string): Promise<void> {
585
601
  const hnswService = await this.getHnswService();
@@ -587,6 +603,72 @@ export class MemoryIndexService {
587
603
  console.log(`✅ Memory index saved for user ${userAddress}`);
588
604
  }
589
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
+
590
672
  /**
591
673
  * Clear user's index
592
674
  */
@@ -245,18 +245,26 @@ export class QuiltBatchManager {
245
245
  console.log(` Gas saved: ${gasSaved} vs individual uploads`);
246
246
 
247
247
  // Build file results using original WalrusFile objects for metadata
248
- // and uploadedFilesInfo for blobId
248
+ // Use shared quiltId as blobId - SDK can only read via getBlob(quiltId).files()
249
+ // Match files by identifier when reading
250
+ const quiltId = uploadedFilesInfo[0]?.blobId || '';
251
+
249
252
  const fileResults: QuiltFileResult[] = await Promise.all(
250
253
  files.map(async (originalFile, i) => {
251
254
  const identifier = await originalFile.getIdentifier() || `file-${i}`;
252
255
  const tags = await originalFile.getTags() || {};
253
- // Get blobId from uploadedFilesInfo if available
254
- const blobId = uploadedFilesInfo[i]?.blobId || '';
256
+ const fileInfo = uploadedFilesInfo[i];
257
+
258
+ // quiltPatchId is stored for reference but not used for retrieval
259
+ const quiltPatchId = fileInfo?.id || '';
260
+
261
+ console.log(` File ${i}: identifier=${identifier}, quiltId=${quiltId.substring(0, 20)}...`);
255
262
 
256
263
  return {
257
264
  identifier,
258
- blobId,
259
- quiltPatchId: undefined,
265
+ // Use shared quiltId as blobId - read via getBlob(quiltId).files()
266
+ blobId: quiltId,
267
+ quiltPatchId,
260
268
  tags: Object.fromEntries(
261
269
  Object.entries(tags).map(([k, v]) => [k, String(v)])
262
270
  ),
@@ -265,9 +273,6 @@ export class QuiltBatchManager {
265
273
  })
266
274
  );
267
275
 
268
- // Get quiltId from first uploaded file
269
- const quiltId = uploadedFilesInfo[0]?.blobId || '';
270
-
271
276
  return {
272
277
  quiltId,
273
278
  blobObjectId: undefined, // Not available from flow
@@ -373,18 +378,26 @@ export class QuiltBatchManager {
373
378
  console.log(` Upload time: ${uploadTimeMs.toFixed(1)}ms`);
374
379
 
375
380
  // Build file results using original WalrusFile objects for metadata
376
- // and uploadedFilesInfo for blobId
381
+ // Use shared quiltId as blobId - SDK can only read via getBlob(quiltId).files()
382
+ // Match files by identifier when reading
383
+ const quiltId = uploadedFilesInfo[0]?.blobId || '';
384
+
377
385
  const fileResults: QuiltFileResult[] = await Promise.all(
378
386
  walrusFiles.map(async (originalFile, i) => {
379
387
  const identifier = await originalFile.getIdentifier() || files[i]?.identifier || `file-${i}`;
380
388
  const tags = await originalFile.getTags() || {};
381
- // Get blobId from uploadedFilesInfo if available
382
- const blobId = uploadedFilesInfo[i]?.blobId || '';
389
+ const fileInfo = uploadedFilesInfo[i];
390
+
391
+ // quiltPatchId is stored for reference but not used for retrieval
392
+ const quiltPatchId = fileInfo?.id || '';
393
+
394
+ console.log(` File ${i}: identifier=${identifier}, quiltId=${quiltId.substring(0, 20)}...`);
383
395
 
384
396
  return {
385
397
  identifier,
386
- blobId,
387
- quiltPatchId: undefined,
398
+ // Use shared quiltId as blobId - read via getBlob(quiltId).files()
399
+ blobId: quiltId,
400
+ quiltPatchId,
388
401
  tags: Object.fromEntries(
389
402
  Object.entries(tags).map(([k, v]) => [k, String(v)])
390
403
  ),
@@ -393,9 +406,6 @@ export class QuiltBatchManager {
393
406
  })
394
407
  );
395
408
 
396
- // Get quiltId from first uploaded file
397
- const quiltId = uploadedFilesInfo[0]?.blobId || '';
398
-
399
409
  return {
400
410
  quiltId,
401
411
  blobObjectId: undefined, // Not available from flow
@@ -418,14 +428,19 @@ export class QuiltBatchManager {
418
428
  /**
419
429
  * Retrieve all files from a Quilt
420
430
  *
421
- * @param quiltId - The Quilt blob ID
431
+ * Uses getBlob().files() pattern which correctly parses Quilt structure
432
+ * and returns individual files with their identifiers and tags.
433
+ *
434
+ * @param quiltId - The Quilt blob ID (shared blobId)
422
435
  * @returns Array of WalrusFile objects
423
436
  */
424
437
  async getQuiltFiles(quiltId: string): Promise<Array<WalrusFile>> {
425
438
  try {
426
439
  console.log(`📂 Retrieving files from Quilt ${quiltId}...`);
427
440
 
428
- const files = await this.suiClient.walrus.getFiles({ ids: [quiltId] });
441
+ // Use getBlob().files() to correctly parse Quilt and get individual files
442
+ const blob = await this.suiClient.walrus.getBlob({ blobId: quiltId });
443
+ const files = await blob.files();
429
444
 
430
445
  console.log(`✅ Retrieved ${files.length} files from Quilt`);
431
446
 
@@ -440,7 +455,9 @@ export class QuiltBatchManager {
440
455
  /**
441
456
  * Retrieve a specific file by identifier from a Quilt
442
457
  *
443
- * @param quiltId - The Quilt blob ID
458
+ * Uses getBlob().files() to get all files then matches by identifier.
459
+ *
460
+ * @param quiltId - The Quilt blob ID (shared blobId)
444
461
  * @param identifier - The file identifier within the quilt
445
462
  * @returns QuiltRetrieveResult with content and metadata
446
463
  */
@@ -453,21 +470,26 @@ export class QuiltBatchManager {
453
470
  try {
454
471
  console.log(`📄 Retrieving file "${identifier}" from Quilt ${quiltId}...`);
455
472
 
456
- // Get all files from quilt
457
- const files = await this.suiClient.walrus.getFiles({ ids: [quiltId] });
473
+ // Get all files from quilt using getBlob().files()
474
+ const blob = await this.suiClient.walrus.getBlob({ blobId: quiltId });
475
+ const files = await blob.files();
458
476
 
459
477
  // Find file by identifier
460
- const file = files.find(async f => {
478
+ let matchingFile: WalrusFile | undefined;
479
+ for (const f of files) {
461
480
  const fileIdentifier = await f.getIdentifier();
462
- return fileIdentifier === identifier;
463
- });
481
+ if (fileIdentifier === identifier) {
482
+ matchingFile = f;
483
+ break;
484
+ }
485
+ }
464
486
 
465
- if (!file) {
487
+ if (!matchingFile) {
466
488
  throw new Error(`File "${identifier}" not found in Quilt`);
467
489
  }
468
490
 
469
- const content = await file.bytes();
470
- const tags = await file.getTags();
491
+ const content = await matchingFile.bytes();
492
+ const tags = await matchingFile.getTags();
471
493
  const retrievalTimeMs = performance.now() - startTime;
472
494
 
473
495
  console.log(`✅ Retrieved file "${identifier}" (${content.length} bytes)`);
@@ -488,14 +510,18 @@ export class QuiltBatchManager {
488
510
  /**
489
511
  * List all patches in a Quilt with their metadata
490
512
  *
491
- * @param quiltId - The Quilt blob ID
513
+ * Uses getBlob().files() to correctly parse Quilt structure.
514
+ *
515
+ * @param quiltId - The Quilt blob ID (shared blobId)
492
516
  * @returns Array of QuiltListResult with identifiers and tags
493
517
  */
494
518
  async listQuiltPatches(quiltId: string): Promise<QuiltListResult[]> {
495
519
  try {
496
520
  console.log(`📋 Listing patches in Quilt ${quiltId}...`);
497
521
 
498
- const files = await this.suiClient.walrus.getFiles({ ids: [quiltId] });
522
+ // Use getBlob().files() to correctly parse Quilt
523
+ const blob = await this.suiClient.walrus.getBlob({ blobId: quiltId });
524
+ const files = await blob.files();
499
525
 
500
526
  const results: QuiltListResult[] = await Promise.all(
501
527
  files.map(async (file) => {
@@ -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 */
@@ -212,60 +212,143 @@ export async function rebuildIndexNode(options: RebuildIndexNodeOptions): Promis
212
212
  };
213
213
  }
214
214
 
215
- // Process each memory
215
+ // Process memories grouped by blobId (for Quilt support)
216
+ // In a Quilt, multiple memories share the same blobId
217
+ const memoriesByBlobId = new Map<string, typeof memories>();
218
+ for (const memory of memories) {
219
+ const list = memoriesByBlobId.get(memory.blobId) || [];
220
+ list.push(memory);
221
+ memoriesByBlobId.set(memory.blobId, list);
222
+ }
223
+
224
+ console.log(`[rebuildIndexNode] Unique blobIds: ${memoriesByBlobId.size} (${memoriesByBlobId.size < totalMemories ? 'Quilt detected' : 'individual blobs'})`);
225
+
216
226
  let indexedCount = 0;
217
227
  let failedCount = 0;
228
+ let processedCount = 0;
218
229
 
219
- for (let i = 0; i < memories.length; i++) {
220
- const memory = memories[i];
221
- const progress = `Memory ${i + 1}/${totalMemories}`;
230
+ // Cache for Quilt files to avoid re-fetching
231
+ const quiltFileCache = new Map<string, WalrusFile[]>();
222
232
 
223
- console.log(`[rebuildIndexNode] Processing ${progress}: ${memory.blobId.substring(0, 20)}...`);
224
- onProgress?.(i + 1, totalMemories, `Processing ${progress}...`);
233
+ for (const [blobId, memoriesInBlob] of memoriesByBlobId) {
234
+ console.log(`[rebuildIndexNode] Processing blobId ${blobId.substring(0, 20)}... (${memoriesInBlob.length} memories)`);
225
235
 
226
236
  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}`);
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`);
239
250
  }
240
251
 
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
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}`;
257
+
258
+ console.log(`[rebuildIndexNode] Processing ${progress}: vectorId=${memory.vectorId}`);
259
+ onProgress?.(processedCount, totalMemories, `Processing ${progress}...`);
260
+
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}`);
270
+ }
271
+
272
+ // Get file content
273
+ const rawBytes = await file.bytes();
274
+ const rawText = new TextDecoder().decode(rawBytes);
275
+ const trimmedText = rawText.trim();
276
+
277
+ // Get file identifier and tags if available (for Quilts)
278
+ const identifier = await file.getIdentifier();
279
+ const tags = await file.getTags();
280
+
281
+ if (identifier) {
282
+ console.log(`[rebuildIndexNode] 📎 File identifier: ${identifier}`);
283
+ }
284
+
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
+ }
302
+
303
+ console.log(`[rebuildIndexNode] 📦 Format: JSON package`);
304
+ } catch (jsonError) {
305
+ throw new Error(`Invalid JSON structure: ${(jsonError as Error).message}`);
306
+ }
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');
312
+ }
313
+
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
+ );
330
+
331
+ indexedCount++;
332
+ console.log(`[rebuildIndexNode] ✓ Indexed: "${content.substring(0, 30)}..."`);
333
+
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}`);
258
339
  }
259
- );
260
-
261
- indexedCount++;
262
- console.log(`[rebuildIndexNode] ✓ Indexed: ${memory.blobId.substring(0, 20)}...`);
340
+ }
263
341
 
264
342
  } catch (error: any) {
265
- failedCount++;
343
+ // Failed to fetch files for this blobId
266
344
  const errorMsg = error.message || String(error);
267
- errors.push({ blobId: memory.blobId, error: errorMsg });
268
- console.error(`[rebuildIndexNode] ✗ Failed: ${errorMsg}`);
345
+ console.error(`[rebuildIndexNode] ✗ Failed to fetch blobId: ${errorMsg}`);
346
+
347
+ for (const memory of memoriesInBlob) {
348
+ processedCount++;
349
+ failedCount++;
350
+ errors.push({ blobId: memory.blobId, error: `Failed to fetch blob: ${errorMsg}` });
351
+ }
269
352
  }
270
353
  }
271
354
 
@@ -248,6 +248,7 @@ export class NodeHnswService implements IHnswService {
248
248
  vector: number[],
249
249
  metadata?: Record<string, any>
250
250
  ): Promise<void> {
251
+ console.log(`[NodeHnswService] addVector: userAddress=${userAddress.slice(0, 10)}..., vectorId=${vectorId}, dims=${vector.length}`);
251
252
  await this.getOrCreateIndex(userAddress);
252
253
 
253
254
  const entry = this.indexCache.get(userAddress);
@@ -262,9 +263,11 @@ export class NodeHnswService implements IHnswService {
262
263
  }
263
264
  entry.isDirty = true;
264
265
  this.batchStats.pendingJobs++;
266
+ console.log(`[NodeHnswService] addVector: Added to pending batch. Pending: ${entry.pendingVectors.size}, isDirty: ${entry.isDirty}`);
265
267
 
266
268
  // Flush if batch is large enough
267
269
  if (entry.pendingVectors.size >= 50) {
270
+ console.log(`[NodeHnswService] addVector: Batch size >= 50, auto-flushing...`);
268
271
  await this.flushBatch(userAddress);
269
272
  }
270
273
  }
@@ -338,8 +341,12 @@ export class NodeHnswService implements IHnswService {
338
341
 
339
342
  async flushBatch(userAddress: string): Promise<void> {
340
343
  const entry = this.indexCache.get(userAddress);
341
- if (!entry || entry.pendingVectors.size === 0) return;
344
+ if (!entry || entry.pendingVectors.size === 0) {
345
+ console.log(`[NodeHnswService] flushBatch: No pending vectors for ${userAddress.slice(0, 10)}..., skipping`);
346
+ return;
347
+ }
342
348
 
349
+ console.log(`[NodeHnswService] flushBatch: Flushing ${entry.pendingVectors.size} vectors for ${userAddress.slice(0, 10)}...`);
343
350
  const startTime = Date.now();
344
351
  let processed = 0;
345
352
 
@@ -348,6 +355,7 @@ export class NodeHnswService implements IHnswService {
348
355
  entry.index.addPoint(vector, vectorId);
349
356
  processed++;
350
357
  }
358
+ console.log(`[NodeHnswService] flushBatch: Added ${processed} points to index`);
351
359
 
352
360
  entry.pendingVectors.clear();
353
361
  entry.isDirty = true;
@@ -361,21 +369,28 @@ export class NodeHnswService implements IHnswService {
361
369
  (this.batchStats.averageProcessingTime + processingTime) / 2;
362
370
 
363
371
  // Auto-save
372
+ console.log(`[NodeHnswService] flushBatch: Calling saveIndex()...`);
364
373
  await this.saveIndex(userAddress);
374
+ console.log(`[NodeHnswService] flushBatch: Complete in ${processingTime}ms`);
365
375
  } catch (error) {
366
376
  this.batchStats.failedJobs++;
367
- console.error('[NodeHnswService] Flush batch error:', error);
377
+ console.error('[NodeHnswService] flushBatch error:', error);
368
378
  throw error;
369
379
  }
370
380
  }
371
381
 
372
382
  async saveIndex(userAddress: string): Promise<void> {
373
383
  const entry = this.indexCache.get(userAddress);
374
- if (!entry) return;
384
+ if (!entry) {
385
+ console.log(`[NodeHnswService] saveIndex: No cache entry for ${userAddress}, skipping`);
386
+ return;
387
+ }
375
388
 
376
389
  try {
377
390
  const indexPath = this.getIndexPath(userAddress);
391
+ console.log(`[NodeHnswService] saveIndex: Writing to ${indexPath}`);
378
392
  entry.index.writeIndex(indexPath);
393
+ console.log(`[NodeHnswService] saveIndex: writeIndex() complete`);
379
394
 
380
395
  // Save metadata separately
381
396
  const fs = await import('fs/promises');
@@ -385,19 +400,26 @@ export class NodeHnswService implements IHnswService {
385
400
  metadataObj[k] = v;
386
401
  }
387
402
 
388
- // Preserve existing walrus blob ID if present
389
- const existingMeta: Record<string, any> = {};
403
+ // Preserve existing metadata and merge with in-memory metadata
404
+ // This prevents data loss when cache is cleared between requests
405
+ let existingMeta: Record<string, any> = {};
390
406
  try {
391
407
  const existingContent = await fs.readFile(metadataPath, 'utf-8');
392
- Object.assign(existingMeta, JSON.parse(existingContent));
408
+ existingMeta = JSON.parse(existingContent);
393
409
  } catch {
394
410
  // No existing metadata file
395
411
  }
396
412
 
413
+ // Merge: existing metadata + in-memory metadata (in-memory takes priority for same keys)
414
+ const mergedMetadata = {
415
+ ...(existingMeta.metadata || {}),
416
+ ...metadataObj
417
+ };
418
+
397
419
  await fs.writeFile(metadataPath, JSON.stringify({
398
420
  version: entry.version,
399
421
  dimensions: entry.dimensions,
400
- metadata: metadataObj,
422
+ metadata: mergedMetadata,
401
423
  walrusBlobId: existingMeta.walrusBlobId,
402
424
  walrusSyncTime: existingMeta.walrusSyncTime
403
425
  }));
@@ -1,17 +1,17 @@
1
- /**
2
- * Wallet Module
3
- *
4
- * @deprecated This module is deprecated. Use CapabilityService and ContextNamespace instead.
5
- * - MainWalletService → CapabilityService (from services/CapabilityService)
6
- * - ContextWalletService → ContextNamespace (from client/namespaces/ContextNamespace)
7
- *
8
- * These services will be removed in the next major version.
9
- */
10
-
11
- /** @deprecated Use CapabilityService instead */
12
- export { MainWalletService } from './MainWalletService';
13
- export type { MainWalletServiceConfig } from './MainWalletService';
14
-
15
- /** @deprecated Use ContextNamespace instead */
16
- export { ContextWalletService } from './ContextWalletService';
17
- export type { ContextWalletServiceConfig } from './ContextWalletService';
1
+ /**
2
+ * Wallet Module
3
+ *
4
+ * @deprecated This module is deprecated. Use CapabilityService and ContextNamespace instead.
5
+ * - MainWalletService → CapabilityService (from services/CapabilityService)
6
+ * - ContextWalletService → ContextNamespace (from client/namespaces/ContextNamespace)
7
+ *
8
+ * These services will be removed in the next major version.
9
+ */
10
+
11
+ /** @deprecated Use CapabilityService instead */
12
+ export { MainWalletService } from './MainWalletService';
13
+ export type { MainWalletServiceConfig } from './MainWalletService';
14
+
15
+ /** @deprecated Use ContextNamespace instead */
16
+ export { ContextWalletService } from './ContextWalletService';
17
+ export type { ContextWalletServiceConfig } from './ContextWalletService';