@cmdoss/memwal-sdk 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/ARCHITECTURE.md +547 -547
  2. package/BENCHMARKS.md +238 -238
  3. package/README.md +310 -181
  4. package/dist/ai-sdk/tools.d.ts +2 -2
  5. package/dist/ai-sdk/tools.js +2 -2
  6. package/dist/client/ClientMemoryManager.js +2 -2
  7. package/dist/client/ClientMemoryManager.js.map +1 -1
  8. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  9. package/dist/client/SimplePDWClient.d.ts +29 -1
  10. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  11. package/dist/client/SimplePDWClient.js +45 -13
  12. package/dist/client/SimplePDWClient.js.map +1 -1
  13. package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
  14. package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
  15. package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
  16. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  17. package/dist/client/namespaces/MemoryNamespace.js +272 -39
  18. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  19. package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
  20. package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
  21. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  22. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  23. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
  24. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  25. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  26. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  27. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  28. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  29. package/dist/config/ConfigurationHelper.js +61 -61
  30. package/dist/config/defaults.js +2 -2
  31. package/dist/config/defaults.js.map +1 -1
  32. package/dist/graph/GraphService.js +21 -21
  33. package/dist/graph/GraphService.js.map +1 -1
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  39. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  40. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  41. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  42. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  43. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  44. package/dist/infrastructure/seal/SealService.js +36 -34
  45. package/dist/infrastructure/seal/SealService.js.map +1 -1
  46. package/dist/langchain/createPDWRAG.js +30 -30
  47. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  48. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  49. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  50. package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
  51. package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
  52. package/dist/retrieval/MemoryRetrievalService.js +44 -4
  53. package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
  54. package/dist/services/CapabilityService.d.ts.map +1 -1
  55. package/dist/services/CapabilityService.js +30 -14
  56. package/dist/services/CapabilityService.js.map +1 -1
  57. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  58. package/dist/services/CrossContextPermissionService.js +9 -7
  59. package/dist/services/CrossContextPermissionService.js.map +1 -1
  60. package/dist/services/EmbeddingService.d.ts +28 -1
  61. package/dist/services/EmbeddingService.d.ts.map +1 -1
  62. package/dist/services/EmbeddingService.js +54 -0
  63. package/dist/services/EmbeddingService.js.map +1 -1
  64. package/dist/services/EncryptionService.d.ts.map +1 -1
  65. package/dist/services/EncryptionService.js +6 -5
  66. package/dist/services/EncryptionService.js.map +1 -1
  67. package/dist/services/GeminiAIService.js +309 -309
  68. package/dist/services/IndexManager.d.ts +5 -1
  69. package/dist/services/IndexManager.d.ts.map +1 -1
  70. package/dist/services/IndexManager.js +17 -40
  71. package/dist/services/IndexManager.js.map +1 -1
  72. package/dist/services/QueryService.js +1 -1
  73. package/dist/services/QueryService.js.map +1 -1
  74. package/dist/services/StorageService.d.ts +11 -0
  75. package/dist/services/StorageService.d.ts.map +1 -1
  76. package/dist/services/StorageService.js +73 -10
  77. package/dist/services/StorageService.js.map +1 -1
  78. package/dist/services/TransactionService.d.ts +20 -0
  79. package/dist/services/TransactionService.d.ts.map +1 -1
  80. package/dist/services/TransactionService.js +43 -0
  81. package/dist/services/TransactionService.js.map +1 -1
  82. package/dist/services/ViewService.js +2 -2
  83. package/dist/services/ViewService.js.map +1 -1
  84. package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
  85. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  86. package/dist/services/storage/QuiltBatchManager.js +410 -20
  87. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  88. package/dist/services/storage/index.d.ts +1 -1
  89. package/dist/services/storage/index.d.ts.map +1 -1
  90. package/dist/services/storage/index.js.map +1 -1
  91. package/dist/utils/LRUCache.d.ts +106 -0
  92. package/dist/utils/LRUCache.d.ts.map +1 -0
  93. package/dist/utils/LRUCache.js +281 -0
  94. package/dist/utils/LRUCache.js.map +1 -0
  95. package/dist/utils/index.d.ts +1 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +2 -0
  98. package/dist/utils/index.js.map +1 -1
  99. package/dist/utils/memoryIndexOnChain.d.ts +212 -0
  100. package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
  101. package/dist/utils/memoryIndexOnChain.js +312 -0
  102. package/dist/utils/memoryIndexOnChain.js.map +1 -0
  103. package/dist/utils/rebuildIndexNode.d.ts +29 -0
  104. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  105. package/dist/utils/rebuildIndexNode.js +366 -98
  106. package/dist/utils/rebuildIndexNode.js.map +1 -1
  107. package/dist/vector/HnswWasmService.d.ts +20 -5
  108. package/dist/vector/HnswWasmService.d.ts.map +1 -1
  109. package/dist/vector/HnswWasmService.js +73 -40
  110. package/dist/vector/HnswWasmService.js.map +1 -1
  111. package/dist/vector/IHnswService.d.ts +10 -1
  112. package/dist/vector/IHnswService.d.ts.map +1 -1
  113. package/dist/vector/IHnswService.js.map +1 -1
  114. package/dist/vector/NodeHnswService.d.ts +16 -0
  115. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  116. package/dist/vector/NodeHnswService.js +84 -5
  117. package/dist/vector/NodeHnswService.js.map +1 -1
  118. package/dist/vector/createHnswService.d.ts +1 -1
  119. package/dist/vector/createHnswService.js +1 -1
  120. package/dist/vector/index.d.ts +1 -1
  121. package/dist/vector/index.js +1 -1
  122. package/package.json +157 -157
  123. package/src/access/PermissionService.ts +635 -635
  124. package/src/aggregation/AggregationService.ts +389 -389
  125. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  126. package/src/ai-sdk/index.ts +65 -65
  127. package/src/ai-sdk/tools.ts +460 -460
  128. package/src/ai-sdk/types.ts +404 -404
  129. package/src/batch/BatchManager.ts +597 -597
  130. package/src/batch/BatchingService.ts +429 -429
  131. package/src/batch/MemoryProcessingCache.ts +492 -492
  132. package/src/batch/index.ts +30 -30
  133. package/src/browser.ts +200 -200
  134. package/src/client/ClientMemoryManager.ts +987 -987
  135. package/src/client/PersonalDataWallet.ts +345 -345
  136. package/src/client/SimplePDWClient.ts +1289 -1222
  137. package/src/client/factory.ts +154 -154
  138. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  139. package/src/client/namespaces/BatchNamespace.ts +356 -356
  140. package/src/client/namespaces/CacheNamespace.ts +123 -123
  141. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  142. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  143. package/src/client/namespaces/ContextNamespace.ts +297 -297
  144. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  145. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  146. package/src/client/namespaces/GraphNamespace.ts +468 -468
  147. package/src/client/namespaces/IndexNamespace.ts +361 -361
  148. package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
  149. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  150. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  151. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  152. package/src/client/namespaces/StorageNamespace.ts +458 -458
  153. package/src/client/namespaces/TxNamespace.ts +260 -260
  154. package/src/client/namespaces/WalletNamespace.ts +243 -243
  155. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  156. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
  157. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  158. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  159. package/src/client/namespaces/consolidated/index.ts +39 -39
  160. package/src/client/signers/KeypairSigner.ts +108 -108
  161. package/src/client/signers/UnifiedSigner.ts +110 -110
  162. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  163. package/src/client/signers/index.ts +26 -26
  164. package/src/config/ConfigurationHelper.ts +412 -412
  165. package/src/config/defaults.ts +51 -51
  166. package/src/config/index.ts +8 -8
  167. package/src/config/validation.ts +70 -70
  168. package/src/core/index.ts +14 -14
  169. package/src/core/interfaces/IService.ts +307 -307
  170. package/src/core/interfaces/index.ts +8 -8
  171. package/src/core/types/capability.ts +297 -297
  172. package/src/core/types/index.ts +870 -870
  173. package/src/core/types/wallet.ts +270 -270
  174. package/src/core/types.ts +9 -9
  175. package/src/core/wallet.ts +222 -222
  176. package/src/embedding/index.ts +19 -19
  177. package/src/embedding/types.ts +357 -357
  178. package/src/errors/index.ts +602 -602
  179. package/src/errors/recovery.ts +461 -461
  180. package/src/errors/validation.ts +567 -567
  181. package/src/generated/pdw/capability.ts +319 -319
  182. package/src/graph/GraphService.ts +887 -887
  183. package/src/graph/KnowledgeGraphManager.ts +728 -728
  184. package/src/graph/index.ts +25 -25
  185. package/src/index.ts +498 -474
  186. package/src/infrastructure/index.ts +22 -22
  187. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  188. package/src/infrastructure/seal/SealService.ts +613 -615
  189. package/src/infrastructure/seal/index.ts +9 -9
  190. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  191. package/src/infrastructure/sui/SuiService.ts +888 -888
  192. package/src/infrastructure/sui/index.ts +9 -9
  193. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  194. package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
  195. package/src/infrastructure/walrus/index.ts +9 -9
  196. package/src/langchain/PDWEmbeddings.ts +145 -145
  197. package/src/langchain/PDWVectorStore.ts +456 -456
  198. package/src/langchain/createPDWRAG.ts +303 -303
  199. package/src/langchain/index.ts +47 -47
  200. package/src/permissions/ConsentRepository.browser.ts +249 -249
  201. package/src/permissions/ConsentRepository.ts +364 -364
  202. package/src/pipeline/MemoryPipeline.ts +862 -862
  203. package/src/pipeline/PipelineManager.ts +683 -683
  204. package/src/pipeline/index.ts +26 -26
  205. package/src/retrieval/AdvancedSearchService.ts +629 -629
  206. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  207. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  208. package/src/retrieval/MemoryRetrievalService.ts +904 -830
  209. package/src/retrieval/index.ts +42 -42
  210. package/src/services/BatchService.ts +352 -352
  211. package/src/services/CapabilityService.ts +464 -448
  212. package/src/services/ClassifierService.ts +465 -465
  213. package/src/services/CrossContextPermissionService.ts +486 -484
  214. package/src/services/EmbeddingService.ts +771 -706
  215. package/src/services/EncryptionService.ts +712 -711
  216. package/src/services/GeminiAIService.ts +753 -753
  217. package/src/services/IndexManager.ts +977 -1004
  218. package/src/services/MemoryIndexService.ts +1003 -1003
  219. package/src/services/MemoryService.ts +369 -369
  220. package/src/services/QueryService.ts +890 -890
  221. package/src/services/StorageService.ts +1182 -1111
  222. package/src/services/TransactionService.ts +838 -790
  223. package/src/services/VectorService.ts +462 -462
  224. package/src/services/ViewService.ts +484 -484
  225. package/src/services/index.ts +25 -25
  226. package/src/services/storage/BlobAttributesManager.ts +333 -333
  227. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  228. package/src/services/storage/MemorySearchManager.ts +387 -387
  229. package/src/services/storage/QuiltBatchManager.ts +1130 -660
  230. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  231. package/src/services/storage/WalrusStorageManager.ts +287 -287
  232. package/src/services/storage/index.ts +57 -52
  233. package/src/types/index.ts +13 -13
  234. package/src/utils/LRUCache.ts +378 -0
  235. package/src/utils/index.ts +76 -68
  236. package/src/utils/memoryIndexOnChain.ts +507 -0
  237. package/src/utils/rebuildIndex.ts +290 -290
  238. package/src/utils/rebuildIndexNode.ts +771 -424
  239. package/src/vector/BrowserHnswIndexService.ts +758 -758
  240. package/src/vector/HnswWasmService.ts +731 -679
  241. package/src/vector/IHnswService.ts +233 -224
  242. package/src/vector/NodeHnswService.ts +833 -735
  243. package/src/vector/VectorManager.ts +478 -478
  244. package/src/vector/createHnswService.ts +135 -135
  245. package/src/vector/index.ts +56 -56
  246. package/src/wallet/ContextWalletService.ts +656 -656
  247. package/src/wallet/MainWalletService.ts +317 -317
@@ -1,1135 +1,1422 @@
1
- /**
2
- * Memory Namespace - Complete Memory CRUD Operations
3
- *
4
- * Provides simple API for creating, reading, updating, and deleting memories
5
- * without React hooks or UI dependencies.
6
- *
7
- * @module client/namespaces
8
- */
9
-
10
- import type { ServiceContainer } from '../SimplePDWClient';
11
-
12
- /**
13
- * Memory object returned by operations
14
- */
15
- export interface Memory {
16
- id: string;
17
- content: string;
18
- category?: string;
19
- importance?: number;
20
- topic?: string;
21
- blobId: string;
22
- vectorId?: number;
23
- embedding?: number[];
24
- metadata?: Record<string, any>;
25
- encrypted?: boolean;
26
- createdAt: number;
27
- updatedAt?: number;
28
- }
29
-
30
- /**
31
- * Options for creating a memory
32
- */
33
- export interface CreateMemoryOptions {
34
- category?: 'fact' | 'preference' | 'todo' | 'note' | 'general';
35
- importance?: number; // 1-10
36
- topic?: string;
37
- metadata?: Record<string, any>;
38
- onProgress?: (stage: string, percent: number) => void;
39
- }
40
-
41
- /**
42
- * Options for updating a memory
43
- */
44
- export interface UpdateMemoryOptions {
45
- /** New content (will upload new blob to Walrus) */
46
- content?: string;
47
- /** New category */
48
- category?: string;
49
- /** New importance (1-10) */
50
- importance?: number;
51
- /** New topic */
52
- topic?: string;
53
- /** New embedding blob ID */
54
- embeddingBlobId?: string;
55
- /** New content hash */
56
- contentHash?: string;
57
- /** New content size */
58
- contentSize?: number;
59
- /** Additional metadata */
60
- metadata?: Record<string, any>;
61
- }
62
-
63
- /**
64
- * Options for listing memories
65
- */
66
- export interface ListMemoryOptions {
67
- category?: string;
68
- limit?: number;
69
- offset?: number;
70
- sortBy?: 'date' | 'importance' | 'relevance';
71
- order?: 'asc' | 'desc';
72
- }
73
-
74
- /**
75
- * Memory context with related memories
76
- */
77
- export interface MemoryContext {
78
- memory: Memory;
79
- related: Memory[];
80
- entities?: Array<{
81
- id: string;
82
- name: string;
83
- type: string;
84
- }>;
85
- relationships?: Array<{
86
- source: string;
87
- target: string;
88
- type: string;
89
- }>;
90
- }
91
-
92
- /**
93
- * Memory Namespace
94
- *
95
- * Handles all memory CRUD operations
96
- */
97
- export class MemoryNamespace {
98
- constructor(private services: ServiceContainer) {}
99
-
100
- /**
101
- * Create a new memory with automatic processing
102
- *
103
- * Pipeline:
104
- * 1. Auto-classify content (if classifier enabled and category not provided)
105
- * 2. Generate embedding (if AI enabled)
106
- * 3. Encrypt content (if encryption enabled)
107
- * 4. Upload to Walrus
108
- * 5. Register on Sui blockchain
109
- * 6. Index locally (if enabled)
110
- * 7. Extract knowledge graph (if enabled)
111
- *
112
- * @param content - Text content to save
113
- * @param options - Optional metadata and callbacks
114
- * @returns Created memory with ID and blob ID
115
- *
116
- * @example
117
- * ```typescript
118
- * // Auto-classify and extract knowledge graph automatically
119
- * const memory = await pdw.memory.create('I love TypeScript', {
120
- * importance: 8,
121
- * onProgress: (stage, percent) => console.log(stage, percent)
122
- * });
123
- *
124
- * // Provide category to skip auto-classify
125
- * const memory = await pdw.memory.create('Meeting at 3pm', {
126
- * category: 'todo'
127
- * });
128
- * ```
129
- */
130
- async create(content: string, options: CreateMemoryOptions = {}): Promise<Memory> {
131
- const { importance = 5, topic, metadata, onProgress } = options;
132
- let category: string = options.category || 'general';
133
-
134
- try {
135
- // Validate content
136
- if (!content || content.trim().length === 0) {
137
- throw new Error('Content cannot be empty');
138
- }
139
-
140
- // Validate importance
141
- if (importance < 1 || importance > 10) {
142
- throw new Error('Importance must be between 1 and 10');
143
- }
144
-
145
- onProgress?.('analyzing', 5);
146
-
147
- // 1. Auto-classify if category not provided
148
- if (!options.category && this.services.classifier) {
149
- onProgress?.('classifying', 10);
150
- try {
151
- const classifiedCategory = await this.services.classifier.classifyContent(content);
152
- category = classifiedCategory || 'general';
153
- console.log(`Auto-classified as: ${category}`);
154
- } catch (classifyError) {
155
- console.warn('Auto-classification failed, using default category:', classifyError);
156
- }
157
- }
158
-
159
- // 2. Generate embedding
160
- let embedding: number[] | undefined;
161
- if (this.services.embedding) {
162
- onProgress?.('generating embedding', 20);
163
- const embResult = await this.services.embedding.embedText({
164
- text: content
165
- });
166
- embedding = embResult.vector;
167
- }
168
-
169
- // 3. Encrypt (if enabled)
170
- let encryptedContent: Uint8Array | undefined;
171
- if (this.services.config.features.enableEncryption && this.services.encryption) {
172
- onProgress?.('encrypting', 50);
173
- try {
174
- const contentBytes = new TextEncoder().encode(content);
175
- const encryptResult = await this.services.encryption.encrypt(
176
- contentBytes,
177
- this.services.config.userAddress,
178
- 2 // threshold: require 2 key servers for decryption
179
- );
180
- encryptedContent = encryptResult.encryptedObject;
181
- } catch (error) {
182
- console.warn('SEAL encryption failed, storing unencrypted:', error);
183
- // Continue without encryption if it fails
184
- }
185
- }
186
-
187
- // 4. Upload to Walrus
188
- onProgress?.('uploading to Walrus', 40);
189
- const uploadResult = await this.services.storage.uploadMemoryPackage(
190
- {
191
- content,
192
- embedding: embedding || [],
193
- metadata: {
194
- category,
195
- importance,
196
- topic: topic || '',
197
- ...metadata
198
- },
199
- encryptedContent,
200
- identity: this.services.config.userAddress
201
- },
202
- {
203
- signer: this.services.config.signer,
204
- epochs: 3,
205
- deletable: true,
206
- metadata: {
207
- 'category': category,
208
- 'importance': importance.toString(),
209
- 'topic': topic || ''
210
- }
211
- }
212
- );
213
-
214
- // 5. Register on-chain (create Memory object on Sui)
215
- onProgress?.('registering on blockchain', 60);
216
- let memoryObjectId: string | undefined;
217
- // Use modulo to keep vectorId within u32 range (max 4,294,967,295)
218
- const vectorId = Date.now() % 4294967295;
219
-
220
- if (this.services.tx) {
221
- try {
222
- console.log('šŸ”Ø Building on-chain transaction with full metadata...');
223
- const tx = this.services.tx.buildCreateMemoryRecord({
224
- category,
225
- vectorId,
226
- blobId: uploadResult.blobId,
227
- contentType: 'text/plain',
228
- contentSize: new TextEncoder().encode(content).length,
229
- contentHash: uploadResult.blobId, // blob_id is content-addressed (blake2b256)
230
- topic: topic || '',
231
- importance,
232
- embeddingBlobId: uploadResult.blobId, // Embedding stored in same blob
233
- });
234
-
235
- // SerialTransactionExecutor handles gas coin management and prevents equivocation
236
- // No manual retry needed - executor caches object versions automatically
237
- console.log('šŸ“¤ Executing on-chain transaction (via SerialTransactionExecutor)...');
238
- const txResult = await this.services.tx.executeTransaction(
239
- tx,
240
- this.services.config.signer.getSigner()
241
- );
242
-
243
- console.log('šŸ“‹ Transaction result:', txResult.status, txResult.digest);
244
-
245
- if (txResult.status === 'success') {
246
- // Get created Memory object ID
247
- const memoryObject = txResult.createdObjects?.find(
248
- (obj: any) => obj.objectType?.includes('::memory::Memory')
249
- );
250
- memoryObjectId = memoryObject?.objectId;
251
- console.log('āœ… Memory registered on-chain:', memoryObjectId);
252
- } else {
253
- console.warn('āŒ Failed to register memory on-chain:', txResult.error);
254
- }
255
- } catch (txError: any) {
256
- console.warn('āŒ On-chain registration failed:', txError.message);
257
- }
258
- } else {
259
- console.log('TransactionService not available, skipping on-chain registration');
260
- }
261
-
262
- // 6. Index locally (if enabled)
263
- // Option A+: Store content in index ONLY when encryption is OFF
264
- // When encryption is ON, content is NOT stored (security - prevents data leakage
265
- // when index is saved to Walrus)
266
- // TODO(refactor): Extract common indexing logic into shared helper when adding new metadata fields.
267
- // Related locations: MemoryNamespace.createBatch(), MemoryIndexService.indexMemory(),
268
- // ClientMemoryManager.createMemory(), rebuildIndexNode(), rebuildIndex()
269
- const isEncrypted = !!encryptedContent;
270
- if (this.services.vector && embedding) {
271
- onProgress?.('indexing vector', 80);
272
- const spaceId = this.services.config.userAddress;
273
-
274
- // Index metadata - content only when NOT encrypted (Option A+)
275
- const indexMetadata: Record<string, unknown> = {
276
- ...metadata,
277
- blobId: uploadResult.blobId,
278
- memoryObjectId,
279
- category,
280
- importance,
281
- topic: topic || '',
282
- timestamp: Date.now(),
283
- isEncrypted
284
- };
285
-
286
- // Option A+: Store content for fast local retrieval when encryption is OFF
287
- if (!isEncrypted) {
288
- indexMetadata.content = content;
289
- console.log(' šŸ’¾ Content stored in local index (encryption OFF)');
290
- } else {
291
- console.log(' šŸ”’ Content NOT stored in index (encryption ON - security)');
292
- }
293
-
294
- // Auto-create index if it doesn't exist
295
- try {
296
- await this.services.vector.addVector(spaceId, vectorId, embedding, indexMetadata);
297
- } catch (error: any) {
298
- if (error.message?.includes('not found')) {
299
- // Index doesn't exist, create it first
300
- await this.services.vector.createIndex(spaceId, embedding.length);
301
- await this.services.vector.addVector(spaceId, vectorId, embedding, indexMetadata);
302
- } else {
303
- throw error;
304
- }
305
- }
306
- }
307
-
308
- // 7. Extract knowledge graph (if enabled)
309
- if (this.services.config.features.enableKnowledgeGraph) {
310
- onProgress?.('extracting knowledge graph', 95);
311
- try {
312
- const graphResult = await this.services.storage.extractAndStoreKnowledgeGraph(
313
- content,
314
- uploadResult.blobId,
315
- this.services.config.userAddress
316
- );
317
- console.log(`Knowledge graph extracted: ${graphResult.entities.length} entities, ${graphResult.relationships.length} relationships`);
318
- } catch (graphError) {
319
- console.warn('Knowledge graph extraction failed:', graphError);
320
- // Continue without failing - graph extraction is optional
321
- }
322
- }
323
-
324
- onProgress?.('complete', 100);
325
-
326
- return {
327
- id: memoryObjectId || uploadResult.blobId,
328
- content,
329
- category,
330
- importance,
331
- topic,
332
- blobId: uploadResult.blobId,
333
- vectorId,
334
- embedding,
335
- metadata: {
336
- category,
337
- importance,
338
- topic,
339
- ...metadata
340
- },
341
- encrypted: !!encryptedContent,
342
- createdAt: Date.now()
343
- };
344
- } catch (error) {
345
- throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : String(error)}`);
346
- }
347
- }
348
-
349
- /**
350
- * Get a memory by ID
351
- *
352
- * @param memoryId - Memory ID (blob ID)
353
- * @returns Memory with full metadata
354
- */
355
- async get(memoryId: string): Promise<Memory> {
356
- try {
357
- const memoryPackage = await this.services.storage.retrieveMemoryPackage(memoryId);
358
-
359
- if (memoryPackage.storageApproach === 'json-package' && memoryPackage.memoryPackage) {
360
- return {
361
- id: memoryId,
362
- content: memoryPackage.memoryPackage.content,
363
- embedding: memoryPackage.memoryPackage.embedding,
364
- metadata: memoryPackage.memoryPackage.metadata,
365
- category: memoryPackage.memoryPackage.metadata?.category,
366
- importance: memoryPackage.memoryPackage.metadata?.importance,
367
- topic: memoryPackage.memoryPackage.metadata?.topic,
368
- blobId: memoryId,
369
- encrypted: memoryPackage.isEncrypted,
370
- createdAt: memoryPackage.memoryPackage.timestamp
371
- };
372
- }
373
-
374
- throw new Error('Memory not found or invalid format');
375
- } catch (error) {
376
- throw new Error(`Failed to get memory: ${error instanceof Error ? error.message : String(error)}`);
377
- }
378
- }
379
-
380
- /**
381
- * Update memory record on-chain
382
- *
383
- * Updates memory object fields via Sui transaction. Only non-empty/non-zero
384
- * values will be updated.
385
- *
386
- * If content is provided, a new blob will be uploaded to Walrus and
387
- * the blob_id will be updated on-chain.
388
- *
389
- * @param memoryId - Memory object ID on Sui
390
- * @param updates - Fields to update
391
- * @returns Updated memory object
392
- *
393
- * @example
394
- * ```typescript
395
- * // Update importance and topic
396
- * const updated = await pdw.memory.update(memoryId, {
397
- * importance: 9,
398
- * topic: 'programming'
399
- * });
400
- *
401
- * // Update content (creates new Walrus blob)
402
- * const updated = await pdw.memory.update(memoryId, {
403
- * content: 'Updated content here'
404
- * });
405
- * ```
406
- */
407
- async update(memoryId: string, updates: UpdateMemoryOptions): Promise<Memory> {
408
- try {
409
- // Get current memory for comparison
410
- const current = await this.get(memoryId);
411
-
412
- let newBlobId = '';
413
- let newEmbeddingBlobId = updates.embeddingBlobId || '';
414
- let newContentHash = updates.contentHash || '';
415
- let newContentSize = updates.contentSize || 0;
416
-
417
- // If content is provided, upload new blob to Walrus
418
- if (updates.content) {
419
- // Generate new embedding if AI is enabled
420
- let embedding: number[] | undefined;
421
- if (this.services.embedding) {
422
- const embResult = await this.services.embedding.embedText({
423
- text: updates.content
424
- });
425
- embedding = embResult.vector;
426
- }
427
-
428
- // Upload new content to Walrus
429
- const uploadResult = await this.services.storage.uploadMemoryPackage(
430
- {
431
- content: updates.content,
432
- embedding: embedding || [],
433
- metadata: {
434
- category: updates.category || current.category,
435
- importance: updates.importance || current.importance,
436
- topic: updates.topic || current.topic,
437
- ...updates.metadata
438
- },
439
- identity: this.services.config.userAddress
440
- },
441
- {
442
- signer: this.services.config.signer,
443
- epochs: 3,
444
- deletable: true
445
- }
446
- );
447
-
448
- newBlobId = uploadResult.blobId;
449
- newContentSize = updates.content.length;
450
- newContentHash = uploadResult.blobId; // blob_id is content-addressed
451
- }
452
-
453
- // Build and execute update transaction
454
- const tx = this.services.tx!.buildUpdateMemoryRecord({
455
- memoryId,
456
- newBlobId,
457
- newCategory: updates.category || '',
458
- newTopic: updates.topic || '',
459
- newImportance: updates.importance || 0,
460
- newEmbeddingBlobId,
461
- newContentHash,
462
- newContentSize
463
- });
464
-
465
- // Execute transaction
466
- const result = await this.services.tx!.executeTransaction(
467
- tx,
468
- this.services.config.signer.getSigner()
469
- );
470
-
471
- if (result.status !== 'success') {
472
- throw new Error(result.error || 'Transaction failed');
473
- }
474
-
475
- // Update local vector index if needed
476
- if (this.services.vector && updates.content && this.services.embedding) {
477
- const embResult = await this.services.embedding.embedText({
478
- text: updates.content
479
- });
480
- await this.services.vector.addVector(
481
- this.services.config.userAddress,
482
- current.vectorId || Date.now(),
483
- embResult.vector,
484
- { ...current.metadata, ...updates.metadata }
485
- );
486
- }
487
-
488
- return {
489
- ...current,
490
- content: updates.content || current.content,
491
- category: updates.category || current.category,
492
- importance: updates.importance || current.importance,
493
- topic: updates.topic || current.topic,
494
- blobId: newBlobId || current.blobId,
495
- metadata: {
496
- ...current.metadata,
497
- ...updates.metadata
498
- },
499
- updatedAt: Date.now()
500
- };
501
- } catch (error) {
502
- throw new Error(`Failed to update memory: ${error instanceof Error ? error.message : String(error)}`);
503
- }
504
- }
505
-
506
- /**
507
- * Delete a memory
508
- *
509
- * Removes from:
510
- * - Local vector index
511
- * - Blockchain records (marks as deleted)
512
- * Note: Walrus blobs are immutable, can only be marked as deleted
513
- *
514
- * @param memoryId - Memory ID to delete
515
- */
516
- async delete(memoryId: string): Promise<void> {
517
- try {
518
- // Build and execute delete transaction on blockchain
519
- const tx = await this.services.memory.tx.deleteMemory(memoryId);
520
- await this.services.config.signer.signAndExecuteTransaction(tx);
521
-
522
- // Remove from local vector index if it exists
523
- if (this.services.memoryIndex) {
524
- try {
525
- await this.services.memoryIndex.removeMemory(
526
- this.services.config.userAddress,
527
- memoryId
528
- );
529
- } catch (indexError) {
530
- console.warn(`Failed to remove memory ${memoryId} from local index:`, indexError);
531
- // Don't fail the delete if index removal fails
532
- }
533
- }
534
- } catch (error) {
535
- throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : String(error)}`);
536
- }
537
- }
538
-
539
- /**
540
- * List user memories with pagination
541
- *
542
- * @param options - Filter and pagination options
543
- * @returns Array of memories
544
- */
545
- async list(options: ListMemoryOptions = {}): Promise<Memory[]> {
546
- try {
547
- const {
548
- category,
549
- limit = 50,
550
- offset = 0,
551
- sortBy = 'date',
552
- order = 'desc'
553
- } = options;
554
-
555
- // Get memories from blockchain using ViewService (direct Sui query)
556
- // This doesn't require backend API
557
- const viewService = this.services.viewService;
558
- if (!viewService) {
559
- throw new Error('ViewService not available');
560
- }
561
-
562
- const response = await viewService.getUserMemories(
563
- this.services.config.userAddress,
564
- { limit, category }
565
- );
566
- const memories = response.data;
567
-
568
- // Filter by category if provided
569
- let filtered = memories;
570
- if (category) {
571
- filtered = memories.filter((m: any) =>
572
- m.metadata?.category === category ||
573
- m.fields?.category === category
574
- );
575
- }
576
-
577
- // Sort
578
- const sorted = [...filtered].sort((a: any, b: any) => {
579
- if (sortBy === 'date') {
580
- const aTime = a.timestamp || a.createdAt || 0;
581
- const bTime = b.timestamp || b.createdAt || 0;
582
- return order === 'desc' ? bTime - aTime : aTime - bTime;
583
- } else if (sortBy === 'importance') {
584
- const aImp = a.importance || a.metadata?.importance || 5;
585
- const bImp = b.importance || b.metadata?.importance || 5;
586
- return order === 'desc' ? bImp - aImp : aImp - bImp;
587
- }
588
- return 0;
589
- });
590
-
591
- // Paginate
592
- const paginated = sorted.slice(offset, offset + limit);
593
-
594
- // Convert to Memory format
595
- return paginated.map((m: any) => ({
596
- id: m.id || m.blobId,
597
- content: m.content || '',
598
- category: m.category || m.metadata?.category,
599
- importance: m.importance || m.metadata?.importance,
600
- topic: m.topic || m.metadata?.topic,
601
- blobId: m.blobId || m.id,
602
- metadata: m.metadata,
603
- encrypted: m.encrypted || false,
604
- createdAt: m.timestamp || m.createdAt || Date.now()
605
- }));
606
- } catch (error) {
607
- throw new Error(`Failed to list memories: ${error instanceof Error ? error.message : String(error)}`);
608
- }
609
- }
610
-
611
- /**
612
- * Create multiple memories in batch using Walrus Quilt
613
- *
614
- * Uses Walrus SDK's writeFiles() which automatically batches small blobs
615
- * into a single Quilt transaction, providing ~90% gas savings.
616
- *
617
- * Pipeline (batched):
618
- * 1. Auto-classify all contents (if classifier enabled)
619
- * 2. Generate embeddings for all contents (parallel)
620
- * 3. Encrypt all contents (if encryption enabled)
621
- * 4. Batch upload to Walrus as Quilt (single transaction!)
622
- * 5. Register on Sui blockchain (batched PTB)
623
- * 6. Index locally (batch add to vector index)
624
- *
625
- * @param contents - Array of content strings
626
- * @param options - Shared options for all memories
627
- * @returns Array of created memories
628
- *
629
- * @example
630
- * ```typescript
631
- * // Create multiple memories efficiently with Quilt
632
- * const memories = await pdw.memory.createBatch([
633
- * 'I love TypeScript',
634
- * 'Meeting at 3pm tomorrow',
635
- * 'Remember to buy milk'
636
- * ], {
637
- * category: 'note',
638
- * importance: 5
639
- * });
640
- * // All 3 memories uploaded in 1 transaction!
641
- * ```
642
- */
643
- async createBatch(
644
- contents: string[],
645
- options: CreateMemoryOptions = {}
646
- ): Promise<Memory[]> {
647
- const { importance = 5, topic, metadata, onProgress } = options;
648
-
649
- // For single item, use regular create()
650
- if (contents.length === 1) {
651
- const memory = await this.create(contents[0], options);
652
- return [memory];
653
- }
654
-
655
- try {
656
- onProgress?.('preparing batch', 5);
657
-
658
- // Step 1: Auto-classify all contents (parallel)
659
- const categories: string[] = [];
660
- if (!options.category && this.services.classifier) {
661
- onProgress?.('classifying', 10);
662
- const classifyPromises = contents.map(async (content) => {
663
- try {
664
- return await this.services.classifier!.classifyContent(content) || 'general';
665
- } catch {
666
- return 'general';
667
- }
668
- });
669
- const classifiedCategories = await Promise.all(classifyPromises);
670
- categories.push(...classifiedCategories);
671
- } else {
672
- categories.push(...contents.map(() => options.category || 'general'));
673
- }
674
-
675
- // Step 2: Generate embeddings (parallel)
676
- onProgress?.('generating embeddings', 20);
677
- const embeddings: number[][] = [];
678
- if (this.services.embedding) {
679
- const embeddingPromises = contents.map(content =>
680
- this.services.embedding!.embedText({ text: content }).then(r => r.vector)
681
- );
682
- const embeddingResults = await Promise.all(embeddingPromises);
683
- embeddings.push(...embeddingResults);
684
- }
685
-
686
- // Step 3: Encrypt all contents (parallel, if enabled)
687
- onProgress?.('encrypting', 35);
688
- const encryptedContents: (Uint8Array | undefined)[] = [];
689
- if (this.services.config.features.enableEncryption && this.services.encryption) {
690
- const encryptPromises = contents.map(async (content) => {
691
- try {
692
- const contentBytes = new TextEncoder().encode(content);
693
- const result = await this.services.encryption!.encrypt(
694
- contentBytes,
695
- this.services.config.userAddress,
696
- 2
697
- );
698
- return result.encryptedObject;
699
- } catch {
700
- return undefined;
701
- }
702
- });
703
- const encryptResults = await Promise.all(encryptPromises);
704
- encryptedContents.push(...encryptResults);
705
- } else {
706
- encryptedContents.push(...contents.map(() => undefined));
707
- }
708
-
709
- // Step 4: Batch upload to Walrus using Quilt (single transaction!)
710
- onProgress?.('uploading to Walrus (Quilt batch)', 50);
711
-
712
- // Prepare batch memories for QuiltBatchManager
713
- const batchMemories = contents.map((content, i) => ({
714
- content,
715
- category: categories[i],
716
- importance,
717
- topic: topic || '',
718
- embedding: embeddings[i] || [],
719
- encryptedContent: encryptedContents[i] || new TextEncoder().encode(content),
720
- id: `memory-${Date.now()}-${i}` // Client-side tracking ID
721
- }));
722
-
723
- const quiltResult = await this.services.storage.uploadMemoryBatch(
724
- batchMemories,
725
- {
726
- signer: this.services.config.signer,
727
- epochs: 3,
728
- userAddress: this.services.config.userAddress
729
- }
730
- );
731
-
732
- const gasSavedEstimate = contents.length > 1 ? `~${((1 - 1 / contents.length) * 100).toFixed(0)}%` : '0%';
733
- console.log(`āœ… Quilt batch upload complete: ${quiltResult.files.length} files, ${gasSavedEstimate} gas saved, ${quiltResult.uploadTimeMs.toFixed(0)}ms`);
734
-
735
- // Step 5: Register on-chain (batched PTB if available)
736
- onProgress?.('registering on blockchain', 70);
737
- const memoryObjectIds: (string | undefined)[] = [];
738
- const vectorIds: number[] = [];
739
-
740
- if (this.services.tx) {
741
- // Create memory records for each file in the quilt
742
- for (let i = 0; i < quiltResult.files.length; i++) {
743
- const file = quiltResult.files[i];
744
- const vectorId = (Date.now() + i) % 4294967295;
745
- vectorIds.push(vectorId);
746
-
747
- try {
748
- const tx = this.services.tx.buildCreateMemoryRecord({
749
- category: categories[i],
750
- vectorId,
751
- blobId: file.blobId,
752
- contentType: 'text/plain',
753
- contentSize: new TextEncoder().encode(contents[i]).length,
754
- contentHash: file.blobId, // blob_id is content-addressed (blake2b256)
755
- topic: options.topic || '',
756
- importance,
757
- embeddingBlobId: file.blobId, // Embedding stored in same blob
758
- });
759
-
760
- const txResult = await this.services.tx.executeTransaction(
761
- tx,
762
- this.services.config.signer.getSigner()
763
- );
764
-
765
- if (txResult.status === 'success') {
766
- const memoryObject = txResult.createdObjects?.find(
767
- (obj: any) => obj.objectType?.includes('::memory::Memory')
768
- );
769
- memoryObjectIds.push(memoryObject?.objectId);
770
- } else {
771
- memoryObjectIds.push(undefined);
772
- }
773
- } catch (error) {
774
- console.warn(`Failed to register memory ${i} on-chain:`, error);
775
- memoryObjectIds.push(undefined);
776
- }
777
- }
778
- }
779
-
780
- // Step 6: Index locally (batch add to vector index)
781
- // Option A+: Store content in index when encryption is OFF for fast local retrieval
782
- // TODO(refactor): Extract common indexing logic into shared helper when adding new metadata fields.
783
- // Related locations: MemoryNamespace.create(), MemoryIndexService.indexMemory(),
784
- // ClientMemoryManager.createMemory(), rebuildIndexNode(), rebuildIndex()
785
- onProgress?.('indexing vectors', 90);
786
- if (this.services.vector && embeddings.length > 0) {
787
- const spaceId = this.services.config.userAddress;
788
-
789
- for (let i = 0; i < embeddings.length; i++) {
790
- if (!embeddings[i]) continue;
791
-
792
- const vectorId = vectorIds[i] || (Date.now() + i) % 4294967295;
793
- const isEncrypted = !!encryptedContents[i] && this.services.config.features.enableEncryption;
794
-
795
- const indexMetadata: Record<string, unknown> = {
796
- ...metadata,
797
- blobId: quiltResult.files[i]?.blobId,
798
- memoryObjectId: memoryObjectIds[i],
799
- category: categories[i],
800
- importance,
801
- topic: topic || '',
802
- timestamp: Date.now(),
803
- isEncrypted
804
- };
805
-
806
- // Option A+: Store content for fast local retrieval when encryption is OFF
807
- if (!isEncrypted) {
808
- indexMetadata.content = contents[i];
809
- console.log(` šŸ’¾ Batch item ${i + 1}: Content stored in local index (encryption OFF)`);
810
- } else {
811
- console.log(` šŸ”’ Batch item ${i + 1}: Content NOT stored in index (encryption ON)`);
812
- }
813
-
814
- try {
815
- await this.services.vector.addVector(spaceId, vectorId, embeddings[i], indexMetadata);
816
- } catch (error: any) {
817
- if (error.message?.includes('not found')) {
818
- await this.services.vector.createIndex(spaceId, embeddings[i].length);
819
- await this.services.vector.addVector(spaceId, vectorId, embeddings[i], indexMetadata);
820
- }
821
- }
822
- }
823
- }
824
-
825
- onProgress?.('complete', 100);
826
-
827
- // Build result array
828
- const memories: Memory[] = contents.map((content, i) => ({
829
- id: memoryObjectIds[i] || quiltResult.files[i]?.blobId || `batch-${i}`,
830
- content,
831
- category: categories[i],
832
- importance,
833
- topic,
834
- blobId: quiltResult.files[i]?.blobId || '',
835
- vectorId: vectorIds[i],
836
- embedding: embeddings[i],
837
- metadata: {
838
- category: categories[i],
839
- importance,
840
- topic,
841
- quiltId: quiltResult.quiltId,
842
- ...metadata
843
- },
844
- encrypted: !!encryptedContents[i],
845
- createdAt: Date.now()
846
- }));
847
-
848
- return memories;
849
-
850
- } catch (error) {
851
- throw new Error(`Failed to create batch memories: ${error instanceof Error ? error.message : String(error)}`);
852
- }
853
- }
854
-
855
- /**
856
- * Delete multiple memories
857
- *
858
- * @param memoryIds - Array of memory IDs to delete
859
- */
860
- async deleteBatch(memoryIds: string[]): Promise<void> {
861
- for (const id of memoryIds) {
862
- await this.delete(id);
863
- }
864
- }
865
-
866
- /**
867
- * Update multiple memories in batch
868
- *
869
- * Updates memories in parallel batches:
870
- * 1. For updates with new content: uploads new blobs to Walrus
871
- * 2. Executes on-chain update transactions
872
- * 3. Updates local vector index if enabled
873
- *
874
- * @param updates - Array of {id, content?, category?, importance?, topic?}
875
- * @returns Array of successfully updated memory IDs
876
- *
877
- * @example
878
- * ```typescript
879
- * const updatedIds = await pdw.memory.updateBatch([
880
- * { id: 'mem1', importance: 9 },
881
- * { id: 'mem2', content: 'Updated content' }
882
- * ]);
883
- * ```
884
- */
885
- async updateBatch(
886
- updates: Array<{
887
- id: string;
888
- content?: string;
889
- category?: string;
890
- importance?: number;
891
- topic?: string;
892
- }>
893
- ): Promise<string[]> {
894
- const successfulIds: string[] = [];
895
- const BATCH_SIZE = 5;
896
-
897
- for (let i = 0; i < updates.length; i += BATCH_SIZE) {
898
- const batch = updates.slice(i, i + BATCH_SIZE);
899
-
900
- const batchPromises = batch.map(async (update) => {
901
- try {
902
- await this.update(update.id, {
903
- content: update.content,
904
- category: update.category,
905
- importance: update.importance,
906
- topic: update.topic
907
- });
908
- return { success: true, id: update.id };
909
- } catch (error) {
910
- const errorMsg = error instanceof Error ? error.message : String(error);
911
- console.warn(`Failed to update memory ${update.id}:`, errorMsg);
912
- return { success: false, id: update.id };
913
- }
914
- });
915
-
916
- const batchResults = await Promise.all(batchPromises);
917
- for (const result of batchResults) {
918
- if (result.success) {
919
- successfulIds.push(result.id);
920
- }
921
- }
922
- }
923
-
924
- return successfulIds;
925
- }
926
-
927
- /**
928
- * Get memory with related context (related memories, knowledge graph)
929
- *
930
- * @param memoryId - Memory ID
931
- * @param options - Context options
932
- * @returns Memory with context
933
- */
934
- async getContext(
935
- memoryId: string,
936
- options: { includeRelated?: boolean; includeGraph?: boolean } = {}
937
- ): Promise<MemoryContext> {
938
- const memory = await this.get(memoryId);
939
-
940
- const context: MemoryContext = {
941
- memory,
942
- related: []
943
- };
944
-
945
- // Get related memories if requested
946
- if (options.includeRelated && memory.content) {
947
- // Use local vector search if available
948
- try {
949
- if (this.services.memoryIndex) {
950
- // Generate embedding for the memory content
951
- const embedding = this.services.embedding
952
- ? await this.services.embedding.embedText({ text: memory.content.substring(0, 500) })
953
- : null;
954
-
955
- if (embedding) {
956
- const searchResults = await this.services.memoryIndex.searchMemories({
957
- userAddress: this.services.config.userAddress,
958
- vector: embedding.vector,
959
- k: 5
960
- });
961
-
962
- context.related = searchResults
963
- .filter((r: any) => r.memoryObjectId !== memoryId)
964
- .slice(0, 5)
965
- .map((r: any) => ({
966
- id: r.memoryObjectId || r.id,
967
- content: r.content || '',
968
- category: r.category || 'general',
969
- importance: r.importance,
970
- blobId: r.blobId || r.memoryObjectId || r.id,
971
- metadata: r.metadata,
972
- createdAt: r.timestamp || Date.now()
973
- }));
974
- }
975
- }
976
- } catch (error) {
977
- console.warn('Failed to get related memories:', error);
978
- context.related = [];
979
- }
980
- }
981
-
982
- // Get knowledge graph if requested
983
- if (options.includeGraph) {
984
- const graphData = await this.services.storage.searchKnowledgeGraph(
985
- this.services.config.userAddress,
986
- { searchText: memory.content, limit: 10 }
987
- );
988
-
989
- context.entities = graphData.entities.map(e => ({
990
- id: e.id,
991
- name: e.label,
992
- type: e.type
993
- }));
994
-
995
- context.relationships = graphData.relationships.map(r => ({
996
- source: r.source,
997
- target: r.target,
998
- type: r.type || 'related'
999
- }));
1000
- }
1001
-
1002
- return context;
1003
- }
1004
-
1005
- /**
1006
- * Get memories related to a specific memory
1007
- *
1008
- * @param memoryId - Memory ID
1009
- * @param k - Number of related memories to return
1010
- * @returns Array of related memories
1011
- */
1012
- async getRelated(memoryId: string, k: number = 5): Promise<Memory[]> {
1013
- const memory = await this.get(memoryId);
1014
-
1015
- if (!memory.content) {
1016
- return [];
1017
- }
1018
-
1019
- try {
1020
- // Generate embedding for content to find similar memories
1021
- if (!this.services.embedding) {
1022
- console.warn('Embedding service not available for related memories search');
1023
- return [];
1024
- }
1025
-
1026
- const embResult = await this.services.embedding.embedText({
1027
- text: memory.content.substring(0, 500)
1028
- });
1029
-
1030
- if (!this.services.memoryIndex) {
1031
- console.warn('Memory index service not available for related memories search');
1032
- return [];
1033
- }
1034
-
1035
- const searchResults = await this.services.memoryIndex.searchMemories({
1036
- userAddress: this.services.config.userAddress,
1037
- vector: embResult.vector,
1038
- k: k + 1
1039
- });
1040
-
1041
- return searchResults
1042
- .filter((r: any) => (r.blobId || r.id) !== memoryId)
1043
- .slice(0, k)
1044
- .map((r: any) => ({
1045
- id: r.id,
1046
- content: r.content || '',
1047
- category: r.category,
1048
- importance: r.metadata?.importance,
1049
- blobId: r.blobId || r.id,
1050
- metadata: r.metadata,
1051
- createdAt: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
1052
- }));
1053
- } catch (error) {
1054
- console.warn('Failed to get related memories:', error);
1055
- return [];
1056
- }
1057
- }
1058
-
1059
- /**
1060
- * Export memories to file format
1061
- *
1062
- * Exports memories to JSON or CSV format for backup/portability
1063
- *
1064
- * @param options - Export options
1065
- * @returns Exported data as string
1066
- */
1067
- async export(options: {
1068
- format?: 'json' | 'csv';
1069
- includeContent?: boolean;
1070
- includeEmbeddings?: boolean;
1071
- category?: string;
1072
- limit?: number;
1073
- } = {}): Promise<string> {
1074
- const {
1075
- format = 'json',
1076
- includeContent = true,
1077
- includeEmbeddings = false,
1078
- category,
1079
- limit
1080
- } = options;
1081
-
1082
- try {
1083
- // Get memories
1084
- const memories = await this.list({ category, limit });
1085
-
1086
- if (format === 'json') {
1087
- // JSON export
1088
- const exportData = memories.map(m => ({
1089
- id: m.id,
1090
- content: includeContent ? m.content : undefined,
1091
- category: m.category,
1092
- importance: m.importance,
1093
- topic: m.topic,
1094
- blobId: m.blobId,
1095
- embedding: includeEmbeddings ? m.embedding : undefined,
1096
- metadata: m.metadata,
1097
- createdAt: m.createdAt,
1098
- updatedAt: m.updatedAt
1099
- }));
1100
-
1101
- return JSON.stringify(exportData, null, 2);
1102
- } else {
1103
- // CSV export
1104
- const headers = [
1105
- 'id',
1106
- 'category',
1107
- 'importance',
1108
- 'topic',
1109
- 'blobId',
1110
- 'createdAt',
1111
- includeContent ? 'content' : null
1112
- ].filter(Boolean);
1113
-
1114
- const rows = memories.map(m => [
1115
- m.id,
1116
- m.category || '',
1117
- m.importance || '',
1118
- m.topic || '',
1119
- m.blobId,
1120
- new Date(m.createdAt).toISOString(),
1121
- includeContent ? `"${(m.content || '').replace(/"/g, '""')}"` : null
1122
- ].filter(v => v !== null));
1123
-
1124
- const csv = [
1125
- headers.join(','),
1126
- ...rows.map(row => row.join(','))
1127
- ].join('\n');
1128
-
1129
- return csv;
1130
- }
1131
- } catch (error) {
1132
- throw new Error(`Failed to export memories: ${error instanceof Error ? error.message : String(error)}`);
1133
- }
1134
- }
1135
- }
1
+ /**
2
+ * Memory Namespace - Complete Memory CRUD Operations
3
+ *
4
+ * Provides simple API for creating, reading, updating, and deleting memories
5
+ * without React hooks or UI dependencies.
6
+ *
7
+ * @module client/namespaces
8
+ */
9
+
10
+ import type { ServiceContainer } from '../SimplePDWClient';
11
+
12
+ /**
13
+ * Memory object returned by operations
14
+ */
15
+ export interface Memory {
16
+ id: string;
17
+ content: string;
18
+ category?: string;
19
+ importance?: number;
20
+ topic?: string;
21
+ blobId: string;
22
+ vectorId?: number;
23
+ embedding?: number[];
24
+ metadata?: Record<string, any>;
25
+ encrypted?: boolean;
26
+ /** Capability ID for decryption (v2.2) */
27
+ memoryCapId?: string;
28
+ /** Key ID (hex string) for decryption (v2.2) */
29
+ keyId?: string;
30
+ createdAt: number;
31
+ updatedAt?: number;
32
+ }
33
+
34
+ /**
35
+ * Options for creating a memory
36
+ */
37
+ export interface CreateMemoryOptions {
38
+ category?: 'fact' | 'preference' | 'todo' | 'note' | 'general';
39
+ importance?: number; // 1-10
40
+ topic?: string;
41
+ metadata?: Record<string, any>;
42
+ onProgress?: (stage: string, percent: number) => void;
43
+ }
44
+
45
+ /**
46
+ * Options for updating a memory
47
+ */
48
+ export interface UpdateMemoryOptions {
49
+ /** New content (will upload new blob to Walrus) */
50
+ content?: string;
51
+ /** New category */
52
+ category?: string;
53
+ /** New importance (1-10) */
54
+ importance?: number;
55
+ /** New topic */
56
+ topic?: string;
57
+ /** New embedding blob ID */
58
+ embeddingBlobId?: string;
59
+ /** New content hash */
60
+ contentHash?: string;
61
+ /** New content size */
62
+ contentSize?: number;
63
+ /** Additional metadata */
64
+ metadata?: Record<string, any>;
65
+ }
66
+
67
+ /**
68
+ * Options for listing memories
69
+ */
70
+ export interface ListMemoryOptions {
71
+ category?: string;
72
+ limit?: number;
73
+ offset?: number;
74
+ sortBy?: 'date' | 'importance' | 'relevance';
75
+ order?: 'asc' | 'desc';
76
+ }
77
+
78
+ /**
79
+ * Memory context with related memories
80
+ */
81
+ export interface MemoryContext {
82
+ memory: Memory;
83
+ related: Memory[];
84
+ entities?: Array<{
85
+ id: string;
86
+ name: string;
87
+ type: string;
88
+ }>;
89
+ relationships?: Array<{
90
+ source: string;
91
+ target: string;
92
+ type: string;
93
+ }>;
94
+ }
95
+
96
+ /**
97
+ * Memory Namespace
98
+ *
99
+ * Handles all memory CRUD operations
100
+ */
101
+ export class MemoryNamespace {
102
+ constructor(private services: ServiceContainer) {}
103
+
104
+ /**
105
+ * Create a new memory with automatic processing
106
+ *
107
+ * Pipeline:
108
+ * 1. Auto-classify content (if classifier enabled and category not provided)
109
+ * 2. Generate embedding (if AI enabled)
110
+ * 3. Encrypt content (if encryption enabled)
111
+ * 4. Upload to Walrus
112
+ * 5. Register on Sui blockchain
113
+ * 6. Index locally (if enabled)
114
+ * 7. Extract knowledge graph (if enabled)
115
+ *
116
+ * @param content - Text content to save
117
+ * @param options - Optional metadata and callbacks
118
+ * @returns Created memory with ID and blob ID
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * // Auto-classify and extract knowledge graph automatically
123
+ * const memory = await pdw.memory.create('I love TypeScript', {
124
+ * importance: 8,
125
+ * onProgress: (stage, percent) => console.log(stage, percent)
126
+ * });
127
+ *
128
+ * // Provide category to skip auto-classify
129
+ * const memory = await pdw.memory.create('Meeting at 3pm', {
130
+ * category: 'todo'
131
+ * });
132
+ * ```
133
+ */
134
+ async create(content: string, options: CreateMemoryOptions = {}): Promise<Memory> {
135
+ const { importance = 5, topic, metadata, onProgress } = options;
136
+ let category: string = options.category || 'general';
137
+
138
+ try {
139
+ // Validate content
140
+ if (!content || content.trim().length === 0) {
141
+ throw new Error('Content cannot be empty');
142
+ }
143
+
144
+ // Validate importance
145
+ if (importance < 1 || importance > 10) {
146
+ throw new Error('Importance must be between 1 and 10');
147
+ }
148
+
149
+ onProgress?.('analyzing', 5);
150
+
151
+ // 1. Auto-classify if category not provided
152
+ if (!options.category && this.services.classifier) {
153
+ onProgress?.('classifying', 10);
154
+ try {
155
+ const classifiedCategory = await this.services.classifier.classifyContent(content);
156
+ category = classifiedCategory || 'general';
157
+ console.log(`Auto-classified as: ${category}`);
158
+ } catch (classifyError) {
159
+ console.warn('Auto-classification failed, using default category:', classifyError);
160
+ }
161
+ }
162
+
163
+ // 2. Generate embedding
164
+ let embedding: number[] | undefined;
165
+ if (this.services.embedding) {
166
+ onProgress?.('generating embedding', 20);
167
+ const embResult = await this.services.embedding.embedText({
168
+ text: content
169
+ });
170
+ embedding = embResult.vector;
171
+ console.log(`āœ… Embedding generated: ${embedding?.length || 0}D vector`);
172
+ } else {
173
+ console.warn('āš ļø EmbeddingService not available - no embedding will be stored!');
174
+ }
175
+
176
+ // 3. Encrypt (if enabled) - Use capability-based encryption (v2.2)
177
+ let encryptedContent: Uint8Array | undefined;
178
+ let encryptedEmbedding: Uint8Array | undefined;
179
+ let memoryCapId: string | undefined;
180
+ let keyId: string | undefined;
181
+
182
+ if (this.services.config.features.enableEncryption && this.services.encryption && this.services.capability) {
183
+ onProgress?.('encrypting', 30);
184
+ try {
185
+ console.log('šŸ” Using capability-based encryption (v2.2)...');
186
+
187
+ // Step 3.1: Get or create capability for this category
188
+ const cap = await this.services.capability.getOrCreate(
189
+ {
190
+ appId: category,
191
+ userAddress: this.services.config.userAddress
192
+ },
193
+ this.services.config.signer
194
+ );
195
+ memoryCapId = cap.id;
196
+ console.log(` āœ… Capability ready: ${memoryCapId}`);
197
+
198
+ // Step 3.2: Compute key_id from capability
199
+ keyId = this.services.capability.computeKeyId(cap);
200
+ console.log(` šŸ”‘ Key ID computed: ${keyId.substring(0, 20)}...`);
201
+
202
+ // Step 3.3: Encrypt content
203
+ const contentBytes = new TextEncoder().encode(content);
204
+ const encryptContentResult = await this.services.encryption.encrypt(
205
+ contentBytes,
206
+ keyId,
207
+ 2 // threshold: 2 of 2 key servers
208
+ );
209
+ encryptedContent = encryptContentResult.encryptedObject;
210
+ console.log(` āœ… Content encrypted: ${contentBytes.length} → ${encryptedContent?.length || 0} bytes`);
211
+
212
+ // Step 3.4: Encrypt embedding (v2.2 - full encryption)
213
+ if (embedding && embedding.length > 0) {
214
+ const embeddingBytes = new TextEncoder().encode(JSON.stringify(embedding));
215
+ const encryptEmbeddingResult = await this.services.encryption.encrypt(
216
+ embeddingBytes,
217
+ keyId,
218
+ 2
219
+ );
220
+ encryptedEmbedding = encryptEmbeddingResult.encryptedObject;
221
+ console.log(` āœ… Embedding encrypted: ${embedding.length}D → ${encryptedEmbedding?.length || 0} bytes`);
222
+ }
223
+ } catch (error) {
224
+ console.warn('āš ļø Capability-based encryption failed, falling back to plaintext:', error);
225
+ // Continue without encryption if it fails
226
+ }
227
+ }
228
+
229
+ // 4. Upload to Walrus (v2.2 format if encrypted)
230
+ onProgress?.('uploading to Walrus', 50);
231
+ console.log(`šŸ“¤ Uploading to Walrus: content=${content.length} chars, embedding=${embedding?.length || 0}D, encrypted=${!!encryptedContent}`);
232
+ const uploadResult = await this.services.storage.uploadMemoryPackage(
233
+ {
234
+ content,
235
+ embedding: embedding || [],
236
+ metadata: {
237
+ category,
238
+ importance,
239
+ topic: topic || '',
240
+ memoryCapId,
241
+ keyId,
242
+ ...metadata
243
+ },
244
+ encryptedContent,
245
+ encryptedEmbedding, // v2.2: encrypted embedding
246
+ encryptionType: encryptedContent ? 'seal-capability' : undefined,
247
+ identity: this.services.config.userAddress
248
+ },
249
+ {
250
+ signer: this.services.config.signer,
251
+ epochs: 3,
252
+ deletable: true,
253
+ metadata: {
254
+ 'category': category,
255
+ 'importance': importance.toString(),
256
+ 'topic': topic || ''
257
+ }
258
+ }
259
+ );
260
+
261
+ // 5. Register on-chain (create Memory object on Sui)
262
+ onProgress?.('registering on blockchain', 60);
263
+ let memoryObjectId: string | undefined;
264
+ // Use modulo to keep vectorId within u32 range (max 4,294,967,295)
265
+ const vectorId = Date.now() % 4294967295;
266
+
267
+ if (this.services.tx && this.services.capability) {
268
+ try {
269
+ console.log('šŸ”Ø Building on-chain transaction...');
270
+
271
+ // Reuse memoryCapId from encryption step (avoid duplicate capability creation)
272
+ const capId = memoryCapId;
273
+ if (capId) {
274
+ console.log(` Using existing capability: ${capId}`);
275
+ }
276
+
277
+ // Use capability-based creation if capId available
278
+ const tx = capId
279
+ ? this.services.tx.buildCreateMemoryRecordLightweightWithCap({
280
+ category,
281
+ vectorId,
282
+ blobId: uploadResult.blobId,
283
+ importance,
284
+ gasBudget: undefined,
285
+ capId
286
+ })
287
+ : this.services.tx.buildCreateMemoryRecordLightweight({
288
+ category,
289
+ vectorId,
290
+ blobId: uploadResult.blobId,
291
+ importance,
292
+ gasBudget: undefined
293
+ });
294
+
295
+ console.log('šŸ“¤ Executing on-chain transaction...');
296
+
297
+ // Use signer's signAndExecuteTransaction for browser wallet compatibility
298
+ const signer = this.services.config.signer;
299
+ let txResult: any;
300
+
301
+ if ('signAndExecuteTransaction' in signer && typeof signer.signAndExecuteTransaction === 'function') {
302
+ // Browser wallet (DappKitSigner) - use signAndExecuteTransaction directly
303
+ const result = await signer.signAndExecuteTransaction(tx);
304
+
305
+ // Extract created objects
306
+ let createdObjects: Array<{ objectId: string; objectType: string }> | undefined;
307
+ if (result.objectChanges && Array.isArray(result.objectChanges)) {
308
+ createdObjects = result.objectChanges
309
+ .filter((change: any) => change.type === 'created')
310
+ .map((change: any) => ({
311
+ objectId: change.objectId,
312
+ objectType: change.objectType || 'unknown',
313
+ }));
314
+ }
315
+
316
+ // Determine status
317
+ const effectsStatus = result.effects?.status?.status;
318
+ const status = effectsStatus === 'failure' ? 'failure' :
319
+ effectsStatus === 'success' ? 'success' :
320
+ result.digest ? 'success' : 'failure';
321
+
322
+ txResult = {
323
+ digest: result.digest,
324
+ status,
325
+ createdObjects,
326
+ effects: result.effects,
327
+ objectChanges: result.objectChanges
328
+ };
329
+ } else {
330
+ // Server-side signer - use TransactionService
331
+ txResult = await this.services.tx.executeTransaction(tx, signer);
332
+ }
333
+
334
+ console.log('šŸ“‹ Transaction result:', txResult.status, txResult.digest);
335
+
336
+ if (txResult.status === 'success') {
337
+ // Get created Memory object ID
338
+ const memoryObject = txResult.createdObjects?.find(
339
+ (obj: any) => obj.objectType?.includes('::memory::Memory')
340
+ );
341
+ memoryObjectId = memoryObject?.objectId;
342
+ console.log('āœ… Memory registered on-chain:', memoryObjectId);
343
+ } else {
344
+ console.warn('āŒ Failed to register memory on-chain:', txResult.error);
345
+ }
346
+ } catch (txError: any) {
347
+ console.warn('āŒ On-chain registration failed:', txError.message);
348
+ }
349
+ } else {
350
+ console.log('TransactionService not available, skipping on-chain registration');
351
+ }
352
+
353
+ // 6. Index locally (if enabled)
354
+ // Option A+: Store content in index ONLY when encryption is OFF
355
+ // When encryption is ON, content is NOT stored (security - prevents data leakage
356
+ // when index is saved to Walrus)
357
+ // TODO(refactor): Extract common indexing logic into shared helper when adding new metadata fields.
358
+ // Related locations: MemoryNamespace.createBatch(), MemoryIndexService.indexMemory(),
359
+ // ClientMemoryManager.createMemory(), rebuildIndexNode(), rebuildIndex()
360
+ const isEncrypted = !!encryptedContent;
361
+ if (this.services.vector && embedding) {
362
+ onProgress?.('indexing vector', 80);
363
+ const spaceId = this.services.config.userAddress;
364
+
365
+ // Index metadata - content only when NOT encrypted (Option A+)
366
+ const indexMetadata: Record<string, unknown> = {
367
+ ...metadata,
368
+ blobId: uploadResult.blobId,
369
+ memoryObjectId,
370
+ category,
371
+ importance,
372
+ topic: topic || '',
373
+ timestamp: Date.now(),
374
+ isEncrypted
375
+ };
376
+
377
+ // Option A+: Store content for fast local retrieval when encryption is OFF
378
+ if (!isEncrypted) {
379
+ indexMetadata.content = content;
380
+ console.log(' šŸ’¾ Content stored in local index (encryption OFF)');
381
+ } else {
382
+ console.log(' šŸ”’ Content NOT stored in index (encryption ON - security)');
383
+ }
384
+
385
+ // Auto-create index if it doesn't exist
386
+ try {
387
+ await this.services.vector.addVector(spaceId, vectorId, embedding, indexMetadata);
388
+ } catch (error: any) {
389
+ if (error.message?.includes('not found')) {
390
+ // Index doesn't exist, create it first
391
+ await this.services.vector.createIndex(spaceId, embedding.length);
392
+ await this.services.vector.addVector(spaceId, vectorId, embedding, indexMetadata);
393
+ } else {
394
+ throw error;
395
+ }
396
+ }
397
+ }
398
+
399
+ // 7. Extract knowledge graph (if enabled)
400
+ if (this.services.config.features.enableKnowledgeGraph) {
401
+ onProgress?.('extracting knowledge graph', 95);
402
+ try {
403
+ const graphResult = await this.services.storage.extractAndStoreKnowledgeGraph(
404
+ content,
405
+ uploadResult.blobId,
406
+ this.services.config.userAddress
407
+ );
408
+ console.log(`Knowledge graph extracted: ${graphResult.entities.length} entities, ${graphResult.relationships.length} relationships`);
409
+ } catch (graphError) {
410
+ console.warn('Knowledge graph extraction failed:', graphError);
411
+ // Continue without failing - graph extraction is optional
412
+ }
413
+ }
414
+
415
+ onProgress?.('complete', 100);
416
+
417
+ return {
418
+ id: memoryObjectId || uploadResult.blobId,
419
+ content,
420
+ category,
421
+ importance,
422
+ topic,
423
+ blobId: uploadResult.blobId,
424
+ vectorId,
425
+ embedding,
426
+ metadata: {
427
+ category,
428
+ importance,
429
+ topic,
430
+ memoryCapId,
431
+ keyId,
432
+ ...metadata
433
+ },
434
+ encrypted: !!encryptedContent,
435
+ memoryCapId, // v2.2: capability ID for decryption
436
+ keyId, // v2.2: key ID for decryption
437
+ createdAt: Date.now()
438
+ };
439
+ } catch (error) {
440
+ throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : String(error)}`);
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Get a memory by ID
446
+ *
447
+ * @param memoryId - Memory ID (blob ID)
448
+ * @returns Memory with full metadata
449
+ */
450
+ async get(memoryId: string): Promise<Memory> {
451
+ try {
452
+ const memoryPackage = await this.services.storage.retrieveMemoryPackage(memoryId);
453
+
454
+ if (memoryPackage.storageApproach === 'json-package' && memoryPackage.memoryPackage) {
455
+ return {
456
+ id: memoryId,
457
+ content: memoryPackage.memoryPackage.content,
458
+ embedding: memoryPackage.memoryPackage.embedding,
459
+ metadata: memoryPackage.memoryPackage.metadata,
460
+ category: memoryPackage.memoryPackage.metadata?.category,
461
+ importance: memoryPackage.memoryPackage.metadata?.importance,
462
+ topic: memoryPackage.memoryPackage.metadata?.topic,
463
+ blobId: memoryId,
464
+ encrypted: memoryPackage.isEncrypted,
465
+ createdAt: memoryPackage.memoryPackage.timestamp
466
+ };
467
+ }
468
+
469
+ throw new Error('Memory not found or invalid format');
470
+ } catch (error) {
471
+ throw new Error(`Failed to get memory: ${error instanceof Error ? error.message : String(error)}`);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Update memory record on-chain
477
+ *
478
+ * Updates memory object fields via Sui transaction. Only non-empty/non-zero
479
+ * values will be updated.
480
+ *
481
+ * If content is provided, a new blob will be uploaded to Walrus and
482
+ * the blob_id will be updated on-chain.
483
+ *
484
+ * @param memoryId - Memory object ID on Sui
485
+ * @param updates - Fields to update
486
+ * @returns Updated memory object
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * // Update importance and topic
491
+ * const updated = await pdw.memory.update(memoryId, {
492
+ * importance: 9,
493
+ * topic: 'programming'
494
+ * });
495
+ *
496
+ * // Update content (creates new Walrus blob)
497
+ * const updated = await pdw.memory.update(memoryId, {
498
+ * content: 'Updated content here'
499
+ * });
500
+ * ```
501
+ */
502
+ async update(memoryId: string, updates: UpdateMemoryOptions): Promise<Memory> {
503
+ try {
504
+ // Get current memory for comparison
505
+ const current = await this.get(memoryId);
506
+
507
+ let newBlobId = '';
508
+ let newEmbeddingBlobId = updates.embeddingBlobId || '';
509
+ let newContentHash = updates.contentHash || '';
510
+ let newContentSize = updates.contentSize || 0;
511
+
512
+ // If content is provided, upload new blob to Walrus
513
+ if (updates.content) {
514
+ // Generate new embedding if AI is enabled
515
+ let embedding: number[] | undefined;
516
+ if (this.services.embedding) {
517
+ const embResult = await this.services.embedding.embedText({
518
+ text: updates.content
519
+ });
520
+ embedding = embResult.vector;
521
+ }
522
+
523
+ // Upload new content to Walrus
524
+ const uploadResult = await this.services.storage.uploadMemoryPackage(
525
+ {
526
+ content: updates.content,
527
+ embedding: embedding || [],
528
+ metadata: {
529
+ category: updates.category || current.category,
530
+ importance: updates.importance || current.importance,
531
+ topic: updates.topic || current.topic,
532
+ ...updates.metadata
533
+ },
534
+ identity: this.services.config.userAddress
535
+ },
536
+ {
537
+ signer: this.services.config.signer,
538
+ epochs: 3,
539
+ deletable: true
540
+ }
541
+ );
542
+
543
+ newBlobId = uploadResult.blobId;
544
+ newContentSize = updates.content.length;
545
+ newContentHash = uploadResult.blobId; // blob_id is content-addressed
546
+ }
547
+
548
+ // Build and execute update transaction
549
+ const tx = this.services.tx!.buildUpdateMemoryRecord({
550
+ memoryId,
551
+ newBlobId,
552
+ newCategory: updates.category || '',
553
+ newTopic: updates.topic || '',
554
+ newImportance: updates.importance || 0,
555
+ newEmbeddingBlobId,
556
+ newContentHash,
557
+ newContentSize
558
+ });
559
+
560
+ // Execute transaction
561
+ const result = await this.services.tx!.executeTransaction(
562
+ tx,
563
+ this.services.config.signer.getSigner()
564
+ );
565
+
566
+ if (result.status !== 'success') {
567
+ throw new Error(result.error || 'Transaction failed');
568
+ }
569
+
570
+ // Update local vector index if needed
571
+ if (this.services.vector && updates.content && this.services.embedding) {
572
+ const embResult = await this.services.embedding.embedText({
573
+ text: updates.content
574
+ });
575
+ await this.services.vector.addVector(
576
+ this.services.config.userAddress,
577
+ current.vectorId || Date.now(),
578
+ embResult.vector,
579
+ { ...current.metadata, ...updates.metadata }
580
+ );
581
+ }
582
+
583
+ return {
584
+ ...current,
585
+ content: updates.content || current.content,
586
+ category: updates.category || current.category,
587
+ importance: updates.importance || current.importance,
588
+ topic: updates.topic || current.topic,
589
+ blobId: newBlobId || current.blobId,
590
+ metadata: {
591
+ ...current.metadata,
592
+ ...updates.metadata
593
+ },
594
+ updatedAt: Date.now()
595
+ };
596
+ } catch (error) {
597
+ throw new Error(`Failed to update memory: ${error instanceof Error ? error.message : String(error)}`);
598
+ }
599
+ }
600
+
601
+ /**
602
+ * Delete a memory
603
+ *
604
+ * Removes from:
605
+ * - Local vector index
606
+ * - Blockchain records (marks as deleted)
607
+ * Note: Walrus blobs are immutable, can only be marked as deleted
608
+ *
609
+ * @param memoryId - Memory ID to delete
610
+ */
611
+ async delete(memoryId: string): Promise<void> {
612
+ try {
613
+ // Build and execute delete transaction on blockchain
614
+ const tx = await this.services.memory.tx.deleteMemory(memoryId);
615
+ await this.services.config.signer.signAndExecuteTransaction(tx);
616
+
617
+ // Remove from local vector index if it exists
618
+ if (this.services.memoryIndex) {
619
+ try {
620
+ await this.services.memoryIndex.removeMemory(
621
+ this.services.config.userAddress,
622
+ memoryId
623
+ );
624
+ } catch (indexError) {
625
+ console.warn(`Failed to remove memory ${memoryId} from local index:`, indexError);
626
+ // Don't fail the delete if index removal fails
627
+ }
628
+ }
629
+ } catch (error) {
630
+ throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : String(error)}`);
631
+ }
632
+ }
633
+
634
+ /**
635
+ * List user memories with pagination
636
+ *
637
+ * @param options - Filter and pagination options
638
+ * @returns Array of memories
639
+ */
640
+ async list(options: ListMemoryOptions = {}): Promise<Memory[]> {
641
+ try {
642
+ const {
643
+ category,
644
+ limit = 50,
645
+ offset = 0,
646
+ sortBy = 'date',
647
+ order = 'desc'
648
+ } = options;
649
+
650
+ // Get memories from blockchain using ViewService (direct Sui query)
651
+ // This doesn't require backend API
652
+ const viewService = this.services.viewService;
653
+ if (!viewService) {
654
+ throw new Error('ViewService not available');
655
+ }
656
+
657
+ const response = await viewService.getUserMemories(
658
+ this.services.config.userAddress,
659
+ { limit, category }
660
+ );
661
+ const memories = response.data;
662
+
663
+ // Filter by category if provided
664
+ let filtered = memories;
665
+ if (category) {
666
+ filtered = memories.filter((m: any) =>
667
+ m.metadata?.category === category ||
668
+ m.fields?.category === category
669
+ );
670
+ }
671
+
672
+ // Sort
673
+ const sorted = [...filtered].sort((a: any, b: any) => {
674
+ if (sortBy === 'date') {
675
+ const aTime = a.timestamp || a.createdAt || 0;
676
+ const bTime = b.timestamp || b.createdAt || 0;
677
+ return order === 'desc' ? bTime - aTime : aTime - bTime;
678
+ } else if (sortBy === 'importance') {
679
+ const aImp = a.importance || a.metadata?.importance || 5;
680
+ const bImp = b.importance || b.metadata?.importance || 5;
681
+ return order === 'desc' ? bImp - aImp : aImp - bImp;
682
+ }
683
+ return 0;
684
+ });
685
+
686
+ // Paginate
687
+ const paginated = sorted.slice(offset, offset + limit);
688
+
689
+ // Convert to Memory format
690
+ return paginated.map((m: any) => ({
691
+ id: m.id || m.blobId,
692
+ content: m.content || '',
693
+ category: m.category || m.metadata?.category,
694
+ importance: m.importance || m.metadata?.importance,
695
+ topic: m.topic || m.metadata?.topic,
696
+ blobId: m.blobId || m.id,
697
+ metadata: m.metadata,
698
+ encrypted: m.encrypted || false,
699
+ createdAt: m.timestamp || m.createdAt || Date.now()
700
+ }));
701
+ } catch (error) {
702
+ throw new Error(`Failed to list memories: ${error instanceof Error ? error.message : String(error)}`);
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Create multiple memories in batch using Walrus Quilt
708
+ *
709
+ * Uses Walrus SDK's writeFiles() which automatically batches small blobs
710
+ * into a single Quilt transaction, providing ~90% gas savings.
711
+ *
712
+ * Pipeline (batched):
713
+ * 1. Auto-classify all contents (if classifier enabled)
714
+ * 2. Generate embeddings for all contents (parallel)
715
+ * 3. Encrypt all contents (if encryption enabled)
716
+ * 4. Batch upload to Walrus as Quilt (single transaction!)
717
+ * 5. Register on Sui blockchain (batched PTB)
718
+ * 6. Index locally (batch add to vector index)
719
+ *
720
+ * @param contents - Array of content strings
721
+ * @param options - Shared options for all memories
722
+ * @returns Array of created memories
723
+ *
724
+ * @example
725
+ * ```typescript
726
+ * // Create multiple memories efficiently with Quilt
727
+ * const memories = await pdw.memory.createBatch([
728
+ * 'I love TypeScript',
729
+ * 'Meeting at 3pm tomorrow',
730
+ * 'Remember to buy milk'
731
+ * ], {
732
+ * category: 'note',
733
+ * importance: 5
734
+ * });
735
+ * // All 3 memories uploaded in 1 transaction!
736
+ * ```
737
+ */
738
+ async createBatch(
739
+ contents: string[],
740
+ options: CreateMemoryOptions = {}
741
+ ): Promise<Memory[]> {
742
+ const { importance = 5, topic, metadata, onProgress } = options;
743
+
744
+ // For single item, use regular create()
745
+ if (contents.length === 1) {
746
+ const memory = await this.create(contents[0], options);
747
+ return [memory];
748
+ }
749
+
750
+ try {
751
+ onProgress?.('preparing batch', 5);
752
+
753
+ // Step 1: Auto-classify all contents (parallel)
754
+ const categories: string[] = [];
755
+ if (!options.category && this.services.classifier) {
756
+ onProgress?.('classifying', 10);
757
+ const classifyPromises = contents.map(async (content) => {
758
+ try {
759
+ return await this.services.classifier!.classifyContent(content) || 'general';
760
+ } catch {
761
+ return 'general';
762
+ }
763
+ });
764
+ const classifiedCategories = await Promise.all(classifyPromises);
765
+ categories.push(...classifiedCategories);
766
+ } else {
767
+ categories.push(...contents.map(() => options.category || 'general'));
768
+ }
769
+
770
+ // Step 2: Generate embeddings (parallel)
771
+ onProgress?.('generating embeddings', 20);
772
+ const embeddings: number[][] = [];
773
+ if (this.services.embedding) {
774
+ const embeddingPromises = contents.map(content =>
775
+ this.services.embedding!.embedText({ text: content }).then(r => r.vector)
776
+ );
777
+ const embeddingResults = await Promise.all(embeddingPromises);
778
+ embeddings.push(...embeddingResults);
779
+ }
780
+
781
+ // Step 3: Encrypt all contents (parallel, if enabled)
782
+ onProgress?.('encrypting', 35);
783
+ const encryptedContents: (Uint8Array | undefined)[] = [];
784
+ if (this.services.config.features.enableEncryption && this.services.encryption) {
785
+ const encryptPromises = contents.map(async (content) => {
786
+ try {
787
+ const contentBytes = new TextEncoder().encode(content);
788
+ const result = await this.services.encryption!.encrypt(
789
+ contentBytes,
790
+ this.services.config.userAddress,
791
+ 2
792
+ );
793
+ return result.encryptedObject;
794
+ } catch {
795
+ return undefined;
796
+ }
797
+ });
798
+ const encryptResults = await Promise.all(encryptPromises);
799
+ encryptedContents.push(...encryptResults);
800
+ } else {
801
+ encryptedContents.push(...contents.map(() => undefined));
802
+ }
803
+
804
+ // Step 4: Batch upload to Walrus using Quilt (single transaction!)
805
+ onProgress?.('uploading to Walrus (Quilt batch)', 50);
806
+
807
+ // Prepare batch memories for QuiltBatchManager
808
+ const batchMemories = contents.map((content, i) => ({
809
+ content,
810
+ category: categories[i],
811
+ importance,
812
+ topic: topic || '',
813
+ embedding: embeddings[i] || [],
814
+ encryptedContent: encryptedContents[i] || new TextEncoder().encode(content),
815
+ id: `memory-${Date.now()}-${i}` // Client-side tracking ID
816
+ }));
817
+
818
+ const quiltResult = await this.services.storage.uploadMemoryBatch(
819
+ batchMemories,
820
+ {
821
+ signer: this.services.config.signer,
822
+ epochs: 3,
823
+ userAddress: this.services.config.userAddress
824
+ }
825
+ );
826
+
827
+ const gasSavedEstimate = contents.length > 1 ? `~${((1 - 1 / contents.length) * 100).toFixed(0)}%` : '0%';
828
+ console.log(`āœ… Quilt batch upload complete: ${quiltResult.files.length} files, ${gasSavedEstimate} gas saved, ${quiltResult.uploadTimeMs.toFixed(0)}ms`);
829
+
830
+ // Step 5: Register on-chain (batched PTB if available)
831
+ onProgress?.('registering on blockchain', 70);
832
+ const memoryObjectIds: (string | undefined)[] = [];
833
+ const vectorIds: number[] = [];
834
+
835
+ if (this.services.tx && this.services.capability) {
836
+ // Create memory records for each file in the quilt using auto-capability
837
+ for (let i = 0; i < quiltResult.files.length; i++) {
838
+ const file = quiltResult.files[i];
839
+ const vectorId = (Date.now() + i) % 4294967295;
840
+ vectorIds.push(vectorId);
841
+
842
+ try {
843
+ // Auto get/create capability if encryption enabled
844
+ let capId: string | undefined;
845
+ if (this.services.config.features?.enableEncryption) {
846
+ try {
847
+ const cap = await this.services.capability.getOrCreate(
848
+ {
849
+ appId: categories[i],
850
+ userAddress: this.services.config.userAddress
851
+ },
852
+ this.services.config.signer
853
+ );
854
+ capId = cap.id;
855
+ } catch (capError) {
856
+ console.warn(`āš ļø Failed to auto-create capability for memory ${i}:`, capError);
857
+ }
858
+ }
859
+
860
+ // Use capability-based creation if capId available
861
+ const tx = capId
862
+ ? this.services.tx.buildCreateMemoryRecordLightweightWithCap({
863
+ category: categories[i],
864
+ vectorId,
865
+ blobId: file.blobId,
866
+ importance,
867
+ gasBudget: undefined,
868
+ capId
869
+ })
870
+ : this.services.tx.buildCreateMemoryRecordLightweight({
871
+ category: categories[i],
872
+ vectorId,
873
+ blobId: file.blobId,
874
+ importance,
875
+ gasBudget: undefined
876
+ });
877
+
878
+ // Use signer's signAndExecuteTransaction for browser wallet compatibility
879
+ const signer = this.services.config.signer;
880
+ let txResult: any;
881
+
882
+ if ('signAndExecuteTransaction' in signer && typeof signer.signAndExecuteTransaction === 'function') {
883
+ // Browser wallet (DappKitSigner) - use signAndExecuteTransaction directly
884
+ const result = await signer.signAndExecuteTransaction(tx);
885
+
886
+ // Extract created objects
887
+ let createdObjects: Array<{ objectId: string; objectType: string }> | undefined;
888
+ if (result.objectChanges && Array.isArray(result.objectChanges)) {
889
+ createdObjects = result.objectChanges
890
+ .filter((change: any) => change.type === 'created')
891
+ .map((change: any) => ({
892
+ objectId: change.objectId,
893
+ objectType: change.objectType || 'unknown',
894
+ }));
895
+ }
896
+
897
+ // Determine status
898
+ const effectsStatus = result.effects?.status?.status;
899
+ const status = effectsStatus === 'failure' ? 'failure' :
900
+ effectsStatus === 'success' ? 'success' :
901
+ result.digest ? 'success' : 'failure';
902
+
903
+ txResult = {
904
+ digest: result.digest,
905
+ status,
906
+ createdObjects,
907
+ effects: result.effects,
908
+ objectChanges: result.objectChanges
909
+ };
910
+ } else {
911
+ // Server-side signer - use TransactionService
912
+ txResult = await this.services.tx.executeTransaction(tx, signer);
913
+ }
914
+
915
+ if (txResult.status === 'success') {
916
+ const memoryObject = txResult.createdObjects?.find(
917
+ (obj: any) => obj.objectType?.includes('::memory::Memory')
918
+ );
919
+ memoryObjectIds.push(memoryObject?.objectId);
920
+ } else {
921
+ memoryObjectIds.push(undefined);
922
+ }
923
+ } catch (error) {
924
+ console.warn(`Failed to register memory ${i} on-chain:`, error);
925
+ memoryObjectIds.push(undefined);
926
+ }
927
+ }
928
+ }
929
+
930
+ // Step 6: Index locally (batch add to vector index)
931
+ // Option A+: Store content in index when encryption is OFF for fast local retrieval
932
+ // TODO(refactor): Extract common indexing logic into shared helper when adding new metadata fields.
933
+ // Related locations: MemoryNamespace.create(), MemoryIndexService.indexMemory(),
934
+ // ClientMemoryManager.createMemory(), rebuildIndexNode(), rebuildIndex()
935
+ onProgress?.('indexing vectors', 90);
936
+ if (this.services.vector && embeddings.length > 0) {
937
+ const spaceId = this.services.config.userAddress;
938
+
939
+ for (let i = 0; i < embeddings.length; i++) {
940
+ if (!embeddings[i]) continue;
941
+
942
+ const vectorId = vectorIds[i] || (Date.now() + i) % 4294967295;
943
+ const isEncrypted = !!encryptedContents[i] && this.services.config.features.enableEncryption;
944
+
945
+ const indexMetadata: Record<string, unknown> = {
946
+ ...metadata,
947
+ blobId: quiltResult.files[i]?.blobId,
948
+ memoryObjectId: memoryObjectIds[i],
949
+ category: categories[i],
950
+ importance,
951
+ topic: topic || '',
952
+ timestamp: Date.now(),
953
+ isEncrypted
954
+ };
955
+
956
+ // Option A+: Store content for fast local retrieval when encryption is OFF
957
+ if (!isEncrypted) {
958
+ indexMetadata.content = contents[i];
959
+ console.log(` šŸ’¾ Batch item ${i + 1}: Content stored in local index (encryption OFF)`);
960
+ } else {
961
+ console.log(` šŸ”’ Batch item ${i + 1}: Content NOT stored in index (encryption ON)`);
962
+ }
963
+
964
+ try {
965
+ await this.services.vector.addVector(spaceId, vectorId, embeddings[i], indexMetadata);
966
+ } catch (error: any) {
967
+ if (error.message?.includes('not found')) {
968
+ await this.services.vector.createIndex(spaceId, embeddings[i].length);
969
+ await this.services.vector.addVector(spaceId, vectorId, embeddings[i], indexMetadata);
970
+ }
971
+ }
972
+ }
973
+ }
974
+
975
+ onProgress?.('complete', 100);
976
+
977
+ // Build result array
978
+ const memories: Memory[] = contents.map((content, i) => ({
979
+ id: memoryObjectIds[i] || quiltResult.files[i]?.blobId || `batch-${i}`,
980
+ content,
981
+ category: categories[i],
982
+ importance,
983
+ topic,
984
+ blobId: quiltResult.files[i]?.blobId || '',
985
+ vectorId: vectorIds[i],
986
+ embedding: embeddings[i],
987
+ metadata: {
988
+ category: categories[i],
989
+ importance,
990
+ topic,
991
+ quiltId: quiltResult.quiltId,
992
+ ...metadata
993
+ },
994
+ encrypted: !!encryptedContents[i],
995
+ createdAt: Date.now()
996
+ }));
997
+
998
+ return memories;
999
+
1000
+ } catch (error) {
1001
+ throw new Error(`Failed to create batch memories: ${error instanceof Error ? error.message : String(error)}`);
1002
+ }
1003
+ }
1004
+
1005
+ /**
1006
+ * Delete multiple memories
1007
+ *
1008
+ * @param memoryIds - Array of memory IDs to delete
1009
+ */
1010
+ async deleteBatch(memoryIds: string[]): Promise<void> {
1011
+ for (const id of memoryIds) {
1012
+ await this.delete(id);
1013
+ }
1014
+ }
1015
+
1016
+ /**
1017
+ * Update multiple memories in batch
1018
+ *
1019
+ * Updates memories in parallel batches:
1020
+ * 1. For updates with new content: uploads new blobs to Walrus
1021
+ * 2. Executes on-chain update transactions
1022
+ * 3. Updates local vector index if enabled
1023
+ *
1024
+ * @param updates - Array of {id, content?, category?, importance?, topic?}
1025
+ * @returns Array of successfully updated memory IDs
1026
+ *
1027
+ * @example
1028
+ * ```typescript
1029
+ * const updatedIds = await pdw.memory.updateBatch([
1030
+ * { id: 'mem1', importance: 9 },
1031
+ * { id: 'mem2', content: 'Updated content' }
1032
+ * ]);
1033
+ * ```
1034
+ */
1035
+ async updateBatch(
1036
+ updates: Array<{
1037
+ id: string;
1038
+ content?: string;
1039
+ category?: string;
1040
+ importance?: number;
1041
+ topic?: string;
1042
+ }>
1043
+ ): Promise<string[]> {
1044
+ const successfulIds: string[] = [];
1045
+ const BATCH_SIZE = 5;
1046
+
1047
+ for (let i = 0; i < updates.length; i += BATCH_SIZE) {
1048
+ const batch = updates.slice(i, i + BATCH_SIZE);
1049
+
1050
+ const batchPromises = batch.map(async (update) => {
1051
+ try {
1052
+ await this.update(update.id, {
1053
+ content: update.content,
1054
+ category: update.category,
1055
+ importance: update.importance,
1056
+ topic: update.topic
1057
+ });
1058
+ return { success: true, id: update.id };
1059
+ } catch (error) {
1060
+ const errorMsg = error instanceof Error ? error.message : String(error);
1061
+ console.warn(`Failed to update memory ${update.id}:`, errorMsg);
1062
+ return { success: false, id: update.id };
1063
+ }
1064
+ });
1065
+
1066
+ const batchResults = await Promise.all(batchPromises);
1067
+ for (const result of batchResults) {
1068
+ if (result.success) {
1069
+ successfulIds.push(result.id);
1070
+ }
1071
+ }
1072
+ }
1073
+
1074
+ return successfulIds;
1075
+ }
1076
+
1077
+ /**
1078
+ * Get memory with related context (related memories, knowledge graph)
1079
+ *
1080
+ * @param memoryId - Memory ID
1081
+ * @param options - Context options
1082
+ * @returns Memory with context
1083
+ */
1084
+ async getContext(
1085
+ memoryId: string,
1086
+ options: { includeRelated?: boolean; includeGraph?: boolean } = {}
1087
+ ): Promise<MemoryContext> {
1088
+ const memory = await this.get(memoryId);
1089
+
1090
+ const context: MemoryContext = {
1091
+ memory,
1092
+ related: []
1093
+ };
1094
+
1095
+ // Get related memories if requested
1096
+ if (options.includeRelated && memory.content) {
1097
+ // Use local vector search if available
1098
+ try {
1099
+ if (this.services.memoryIndex) {
1100
+ // Generate embedding for the memory content
1101
+ const embedding = this.services.embedding
1102
+ ? await this.services.embedding.embedText({ text: memory.content.substring(0, 500) })
1103
+ : null;
1104
+
1105
+ if (embedding) {
1106
+ const searchResults = await this.services.memoryIndex.searchMemories({
1107
+ userAddress: this.services.config.userAddress,
1108
+ vector: embedding.vector,
1109
+ k: 5
1110
+ });
1111
+
1112
+ context.related = searchResults
1113
+ .filter((r: any) => r.memoryObjectId !== memoryId)
1114
+ .slice(0, 5)
1115
+ .map((r: any) => ({
1116
+ id: r.memoryObjectId || r.id,
1117
+ content: r.content || '',
1118
+ category: r.category || 'general',
1119
+ importance: r.importance,
1120
+ blobId: r.blobId || r.memoryObjectId || r.id,
1121
+ metadata: r.metadata,
1122
+ createdAt: r.timestamp || Date.now()
1123
+ }));
1124
+ }
1125
+ }
1126
+ } catch (error) {
1127
+ console.warn('Failed to get related memories:', error);
1128
+ context.related = [];
1129
+ }
1130
+ }
1131
+
1132
+ // Get knowledge graph if requested
1133
+ if (options.includeGraph) {
1134
+ const graphData = await this.services.storage.searchKnowledgeGraph(
1135
+ this.services.config.userAddress,
1136
+ { searchText: memory.content, limit: 10 }
1137
+ );
1138
+
1139
+ context.entities = graphData.entities.map(e => ({
1140
+ id: e.id,
1141
+ name: e.label,
1142
+ type: e.type
1143
+ }));
1144
+
1145
+ context.relationships = graphData.relationships.map(r => ({
1146
+ source: r.source,
1147
+ target: r.target,
1148
+ type: r.type || 'related'
1149
+ }));
1150
+ }
1151
+
1152
+ return context;
1153
+ }
1154
+
1155
+ /**
1156
+ * Get memories related to a specific memory
1157
+ *
1158
+ * @param memoryId - Memory ID
1159
+ * @param k - Number of related memories to return
1160
+ * @returns Array of related memories
1161
+ */
1162
+ async getRelated(memoryId: string, k: number = 5): Promise<Memory[]> {
1163
+ const memory = await this.get(memoryId);
1164
+
1165
+ if (!memory.content) {
1166
+ return [];
1167
+ }
1168
+
1169
+ try {
1170
+ // Generate embedding for content to find similar memories
1171
+ if (!this.services.embedding) {
1172
+ console.warn('Embedding service not available for related memories search');
1173
+ return [];
1174
+ }
1175
+
1176
+ const embResult = await this.services.embedding.embedText({
1177
+ text: memory.content.substring(0, 500)
1178
+ });
1179
+
1180
+ if (!this.services.memoryIndex) {
1181
+ console.warn('Memory index service not available for related memories search');
1182
+ return [];
1183
+ }
1184
+
1185
+ const searchResults = await this.services.memoryIndex.searchMemories({
1186
+ userAddress: this.services.config.userAddress,
1187
+ vector: embResult.vector,
1188
+ k: k + 1
1189
+ });
1190
+
1191
+ return searchResults
1192
+ .filter((r: any) => (r.blobId || r.id) !== memoryId)
1193
+ .slice(0, k)
1194
+ .map((r: any) => ({
1195
+ id: r.id,
1196
+ content: r.content || '',
1197
+ category: r.category,
1198
+ importance: r.metadata?.importance,
1199
+ blobId: r.blobId || r.id,
1200
+ metadata: r.metadata,
1201
+ createdAt: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
1202
+ }));
1203
+ } catch (error) {
1204
+ console.warn('Failed to get related memories:', error);
1205
+ return [];
1206
+ }
1207
+ }
1208
+
1209
+ /**
1210
+ * Export memories to file format
1211
+ *
1212
+ * Exports memories to JSON or CSV format for backup/portability
1213
+ *
1214
+ * @param options - Export options
1215
+ * @returns Exported data as string
1216
+ */
1217
+ async export(options: {
1218
+ format?: 'json' | 'csv';
1219
+ includeContent?: boolean;
1220
+ includeEmbeddings?: boolean;
1221
+ category?: string;
1222
+ limit?: number;
1223
+ } = {}): Promise<string> {
1224
+ const {
1225
+ format = 'json',
1226
+ includeContent = true,
1227
+ includeEmbeddings = false,
1228
+ category,
1229
+ limit
1230
+ } = options;
1231
+
1232
+ try {
1233
+ // Get memories
1234
+ const memories = await this.list({ category, limit });
1235
+
1236
+ if (format === 'json') {
1237
+ // JSON export
1238
+ const exportData = memories.map(m => ({
1239
+ id: m.id,
1240
+ content: includeContent ? m.content : undefined,
1241
+ category: m.category,
1242
+ importance: m.importance,
1243
+ topic: m.topic,
1244
+ blobId: m.blobId,
1245
+ embedding: includeEmbeddings ? m.embedding : undefined,
1246
+ metadata: m.metadata,
1247
+ createdAt: m.createdAt,
1248
+ updatedAt: m.updatedAt
1249
+ }));
1250
+
1251
+ return JSON.stringify(exportData, null, 2);
1252
+ } else {
1253
+ // CSV export
1254
+ const headers = [
1255
+ 'id',
1256
+ 'category',
1257
+ 'importance',
1258
+ 'topic',
1259
+ 'blobId',
1260
+ 'createdAt',
1261
+ includeContent ? 'content' : null
1262
+ ].filter(Boolean);
1263
+
1264
+ const rows = memories.map(m => [
1265
+ m.id,
1266
+ m.category || '',
1267
+ m.importance || '',
1268
+ m.topic || '',
1269
+ m.blobId,
1270
+ new Date(m.createdAt).toISOString(),
1271
+ includeContent ? `"${(m.content || '').replace(/"/g, '""')}"` : null
1272
+ ].filter(v => v !== null));
1273
+
1274
+ const csv = [
1275
+ headers.join(','),
1276
+ ...rows.map(row => row.join(','))
1277
+ ].join('\n');
1278
+
1279
+ return csv;
1280
+ }
1281
+ } catch (error) {
1282
+ throw new Error(`Failed to export memories: ${error instanceof Error ? error.message : String(error)}`);
1283
+ }
1284
+ }
1285
+
1286
+ /**
1287
+ * Index memories from a Quilt into the local HNSW index
1288
+ *
1289
+ * This allows Quilt batch-uploaded memories to be searchable via vector search.
1290
+ * The method fetches memory packages from the Quilt and indexes each one.
1291
+ *
1292
+ * @param quiltId - The Quilt blob ID containing memory packages
1293
+ * @param options - Indexing options
1294
+ * @returns Result with number of memories indexed
1295
+ *
1296
+ * @example
1297
+ * ```typescript
1298
+ * // After uploading a batch via Quilt
1299
+ * const result = await pdw.memory.indexFromQuilt('GTNhNTdWecbUVg3cZuC_VKlRSzJE3iQfBkojWUuwoh0');
1300
+ * console.log(`Indexed ${result.indexed} memories from Quilt`);
1301
+ * ```
1302
+ */
1303
+ async indexFromQuilt(
1304
+ quiltId: string,
1305
+ options?: { forceReindex?: boolean }
1306
+ ): Promise<{
1307
+ success: boolean;
1308
+ total: number;
1309
+ indexed: number;
1310
+ skipped: number;
1311
+ failed: number;
1312
+ errors: string[];
1313
+ }> {
1314
+ const errors: string[] = [];
1315
+ let indexed = 0;
1316
+ let skipped = 0;
1317
+ let failed = 0;
1318
+
1319
+ try {
1320
+ console.log(`šŸ“¦ Indexing memories from Quilt ${quiltId}...`);
1321
+
1322
+ // Check if index service is available
1323
+ if (!this.services.memoryIndex) {
1324
+ throw new Error('Memory index service not available');
1325
+ }
1326
+
1327
+ // Check if quilt manager is available
1328
+ if (!this.services.storage?.quiltBatchManager) {
1329
+ throw new Error('Quilt batch manager not available');
1330
+ }
1331
+
1332
+ // Fetch all memory packages from the Quilt
1333
+ const packages = await this.services.storage.quiltBatchManager.getAllMemoryPackages(quiltId);
1334
+ console.log(` Found ${packages.length} memory packages in Quilt`);
1335
+
1336
+ // Index each memory
1337
+ for (const pkg of packages) {
1338
+ try {
1339
+ const { identifier, memoryPackage, tags } = pkg;
1340
+
1341
+ // Skip if no embedding
1342
+ if (!memoryPackage.embedding || memoryPackage.embedding.length === 0) {
1343
+ console.warn(` āš ļø Skipping ${identifier}: no embedding`);
1344
+ skipped++;
1345
+ continue;
1346
+ }
1347
+
1348
+ // Create a unique memory ID from identifier or use memoryId from metadata
1349
+ const memoryId = memoryPackage.metadata?.memoryId as string ||
1350
+ identifier.replace('.json', '').replace('memory-', '');
1351
+
1352
+ // Build metadata for indexing (satisfy MemoryMetadata interface)
1353
+ const metadata = {
1354
+ category: memoryPackage.metadata?.category || 'general',
1355
+ importance: memoryPackage.metadata?.importance || 3,
1356
+ topic: memoryPackage.metadata?.topic || '',
1357
+ contentType: 'text',
1358
+ contentSize: memoryPackage.content?.length || 0,
1359
+ contentHash: '', // Not available from Quilt package
1360
+ embeddingDimension: memoryPackage.embedding?.length || 0,
1361
+ createdTimestamp: memoryPackage.timestamp,
1362
+ customMetadata: {
1363
+ quiltId,
1364
+ identifier,
1365
+ ...Object.fromEntries(
1366
+ Object.entries(memoryPackage.metadata || {}).map(([k, v]) => [k, String(v)])
1367
+ )
1368
+ }
1369
+ };
1370
+
1371
+ // Content is empty if encrypted, use placeholder
1372
+ const content = memoryPackage.content || '[encrypted]';
1373
+ const isEncrypted = memoryPackage.encrypted === true;
1374
+
1375
+ // Index the memory
1376
+ await this.services.memoryIndex.indexMemory(
1377
+ this.services.config.userAddress,
1378
+ memoryId,
1379
+ quiltId, // Use quiltId as blobId for retrieval reference
1380
+ content,
1381
+ metadata,
1382
+ memoryPackage.embedding,
1383
+ { isEncrypted }
1384
+ );
1385
+
1386
+ indexed++;
1387
+ console.log(` āœ… Indexed ${identifier} (${indexed}/${packages.length})`);
1388
+
1389
+ } catch (pkgError) {
1390
+ const errMsg = pkgError instanceof Error ? pkgError.message : String(pkgError);
1391
+ errors.push(`${pkg.identifier}: ${errMsg}`);
1392
+ failed++;
1393
+ console.error(` āŒ Failed to index ${pkg.identifier}:`, errMsg);
1394
+ }
1395
+ }
1396
+
1397
+ console.log(`\nšŸ“Š Quilt indexing complete:`);
1398
+ console.log(` Total: ${packages.length}, Indexed: ${indexed}, Skipped: ${skipped}, Failed: ${failed}`);
1399
+
1400
+ return {
1401
+ success: failed === 0,
1402
+ total: packages.length,
1403
+ indexed,
1404
+ skipped,
1405
+ failed,
1406
+ errors
1407
+ };
1408
+
1409
+ } catch (error) {
1410
+ const errMsg = error instanceof Error ? error.message : String(error);
1411
+ console.error(`āŒ Failed to index from Quilt: ${errMsg}`);
1412
+ return {
1413
+ success: false,
1414
+ total: 0,
1415
+ indexed,
1416
+ skipped,
1417
+ failed,
1418
+ errors: [errMsg, ...errors]
1419
+ };
1420
+ }
1421
+ }
1422
+ }