@lov3kaizen/agentsea-memory 0.5.1
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.
- package/LICENSE +21 -0
- package/README.md +450 -0
- package/dist/chunk-GACX3FPR.js +1402 -0
- package/dist/chunk-M44NB53O.js +1226 -0
- package/dist/chunk-MQDWBPZU.js +972 -0
- package/dist/chunk-TPC7MYWK.js +1495 -0
- package/dist/chunk-XD2CQGSD.js +1540 -0
- package/dist/chunk-YI7RPDEV.js +1215 -0
- package/dist/core.types-lkxKv-bW.d.cts +242 -0
- package/dist/core.types-lkxKv-bW.d.ts +242 -0
- package/dist/debug/index.cjs +1248 -0
- package/dist/debug/index.d.cts +3 -0
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.js +20 -0
- package/dist/index-7SsAJ4et.d.ts +525 -0
- package/dist/index-BGxYqpFb.d.cts +601 -0
- package/dist/index-BX62efZu.d.ts +565 -0
- package/dist/index-Bbc3COw0.d.cts +748 -0
- package/dist/index-Bczz1Eyk.d.ts +637 -0
- package/dist/index-C7pEiT8L.d.cts +637 -0
- package/dist/index-CHetLTb0.d.ts +389 -0
- package/dist/index-CloeiFyx.d.ts +748 -0
- package/dist/index-DNOhq-3y.d.cts +525 -0
- package/dist/index-Da-M8FOV.d.cts +389 -0
- package/dist/index-Dy8UjRFz.d.cts +565 -0
- package/dist/index-aVcITW0B.d.ts +601 -0
- package/dist/index.cjs +8554 -0
- package/dist/index.d.cts +293 -0
- package/dist/index.d.ts +293 -0
- package/dist/index.js +742 -0
- package/dist/processing/index.cjs +1575 -0
- package/dist/processing/index.d.cts +2 -0
- package/dist/processing/index.d.ts +2 -0
- package/dist/processing/index.js +24 -0
- package/dist/retrieval/index.cjs +1262 -0
- package/dist/retrieval/index.d.cts +2 -0
- package/dist/retrieval/index.d.ts +2 -0
- package/dist/retrieval/index.js +26 -0
- package/dist/sharing/index.cjs +1003 -0
- package/dist/sharing/index.d.cts +3 -0
- package/dist/sharing/index.d.ts +3 -0
- package/dist/sharing/index.js +16 -0
- package/dist/stores/index.cjs +1445 -0
- package/dist/stores/index.d.cts +2 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.js +20 -0
- package/dist/structures/index.cjs +1530 -0
- package/dist/structures/index.d.cts +3 -0
- package/dist/structures/index.d.ts +3 -0
- package/dist/structures/index.js +24 -0
- package/package.json +141 -0
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
// src/debug/Inspector.ts
|
|
2
|
+
var Inspector = class {
|
|
3
|
+
store;
|
|
4
|
+
config;
|
|
5
|
+
constructor(store, config = {}) {
|
|
6
|
+
this.store = store;
|
|
7
|
+
this.config = {
|
|
8
|
+
includeEmbeddings: config.includeEmbeddings ?? false,
|
|
9
|
+
samplingRate: config.samplingRate ?? 1,
|
|
10
|
+
maxEntriesForAnalysis: config.maxEntriesForAnalysis ?? 1e4,
|
|
11
|
+
warningThresholds: {
|
|
12
|
+
lowImportance: config.warningThresholds?.lowImportance ?? 0.2,
|
|
13
|
+
highEntryCount: config.warningThresholds?.highEntryCount ?? 5e4,
|
|
14
|
+
oldAge: config.warningThresholds?.oldAge ?? 30 * 24 * 60 * 60 * 1e3,
|
|
15
|
+
// 30 days
|
|
16
|
+
...config.warningThresholds
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get comprehensive memory statistics
|
|
22
|
+
*/
|
|
23
|
+
async getStats() {
|
|
24
|
+
const { entries, total } = await this.store.query({
|
|
25
|
+
limit: this.config.maxEntriesForAnalysis
|
|
26
|
+
});
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
let totalSize = 0;
|
|
29
|
+
let totalImportance = 0;
|
|
30
|
+
let totalAccessCount = 0;
|
|
31
|
+
let entriesWithEmbeddings = 0;
|
|
32
|
+
let expiredEntries = 0;
|
|
33
|
+
let oldest = null;
|
|
34
|
+
let newest = null;
|
|
35
|
+
const typeDistribution = {};
|
|
36
|
+
const namespaceDistribution = {};
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
totalSize += entry.content.length + JSON.stringify(entry.metadata).length;
|
|
39
|
+
totalImportance += entry.importance;
|
|
40
|
+
totalAccessCount += entry.accessCount;
|
|
41
|
+
if (entry.embedding) entriesWithEmbeddings++;
|
|
42
|
+
if (entry.expiresAt && entry.expiresAt < now) expiredEntries++;
|
|
43
|
+
if (oldest === null || entry.timestamp < oldest) oldest = entry.timestamp;
|
|
44
|
+
if (newest === null || entry.timestamp > newest) newest = entry.timestamp;
|
|
45
|
+
typeDistribution[entry.type] = (typeDistribution[entry.type] ?? 0) + 1;
|
|
46
|
+
const namespace = String(entry.metadata.namespace ?? "default");
|
|
47
|
+
namespaceDistribution[namespace] = (namespaceDistribution[namespace] ?? 0) + 1;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
totalEntries: total,
|
|
51
|
+
totalSize,
|
|
52
|
+
avgContentLength: entries.length > 0 ? totalSize / entries.length : 0,
|
|
53
|
+
avgImportance: entries.length > 0 ? totalImportance / entries.length : 0,
|
|
54
|
+
avgAccessCount: entries.length > 0 ? totalAccessCount / entries.length : 0,
|
|
55
|
+
typeDistribution,
|
|
56
|
+
namespaceDistribution,
|
|
57
|
+
timeRange: oldest !== null ? { oldest, newest } : null,
|
|
58
|
+
entriesWithEmbeddings,
|
|
59
|
+
expiredEntries
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Inspect a specific entry
|
|
64
|
+
*/
|
|
65
|
+
async inspect(id) {
|
|
66
|
+
const entry = await this.store.get(id);
|
|
67
|
+
if (!entry) return null;
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const age = now - entry.timestamp;
|
|
70
|
+
const hoursOld = age / (60 * 60 * 1e3);
|
|
71
|
+
return {
|
|
72
|
+
entry,
|
|
73
|
+
analysis: {
|
|
74
|
+
contentLength: entry.content.length,
|
|
75
|
+
wordCount: entry.content.split(/\s+/).filter((w) => w.length > 0).length,
|
|
76
|
+
hasEmbedding: !!entry.embedding,
|
|
77
|
+
embeddingDimensions: entry.embedding?.length,
|
|
78
|
+
metadataKeys: Object.keys(entry.metadata),
|
|
79
|
+
age,
|
|
80
|
+
accessRate: hoursOld > 0 ? entry.accessCount / hoursOld : entry.accessCount,
|
|
81
|
+
isExpired: !!(entry.expiresAt && entry.expiresAt < now)
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get memory health report
|
|
87
|
+
*/
|
|
88
|
+
async getHealthReport() {
|
|
89
|
+
const stats = await this.getStats();
|
|
90
|
+
const issues = [];
|
|
91
|
+
const recommendations = [];
|
|
92
|
+
if (stats.totalEntries > this.config.warningThresholds.highEntryCount) {
|
|
93
|
+
issues.push({
|
|
94
|
+
type: "warning",
|
|
95
|
+
message: `High entry count: ${stats.totalEntries} entries`,
|
|
96
|
+
suggestion: "Consider consolidating or archiving old memories"
|
|
97
|
+
});
|
|
98
|
+
recommendations.push("Run memory consolidation to reduce entry count");
|
|
99
|
+
}
|
|
100
|
+
if (stats.avgImportance < this.config.warningThresholds.lowImportance) {
|
|
101
|
+
issues.push({
|
|
102
|
+
type: "info",
|
|
103
|
+
message: `Low average importance: ${(stats.avgImportance * 100).toFixed(1)}%`,
|
|
104
|
+
suggestion: "Review importance scoring or prune low-importance entries"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (stats.expiredEntries > 0) {
|
|
108
|
+
issues.push({
|
|
109
|
+
type: "warning",
|
|
110
|
+
message: `${stats.expiredEntries} expired entries found`,
|
|
111
|
+
affectedEntries: stats.expiredEntries,
|
|
112
|
+
suggestion: "Run cleanup to remove expired entries"
|
|
113
|
+
});
|
|
114
|
+
recommendations.push("Clean up expired entries to free storage");
|
|
115
|
+
}
|
|
116
|
+
const withoutEmbeddings = stats.totalEntries - stats.entriesWithEmbeddings;
|
|
117
|
+
if (withoutEmbeddings > stats.totalEntries * 0.3) {
|
|
118
|
+
issues.push({
|
|
119
|
+
type: "info",
|
|
120
|
+
message: `${withoutEmbeddings} entries without embeddings (${(withoutEmbeddings / stats.totalEntries * 100).toFixed(1)}%)`,
|
|
121
|
+
affectedEntries: withoutEmbeddings,
|
|
122
|
+
suggestion: "Generate embeddings for better semantic search"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (stats.timeRange) {
|
|
126
|
+
const oldestAge = Date.now() - stats.timeRange.oldest;
|
|
127
|
+
if (oldestAge > this.config.warningThresholds.oldAge) {
|
|
128
|
+
issues.push({
|
|
129
|
+
type: "info",
|
|
130
|
+
message: `Oldest entry is ${Math.round(oldestAge / (24 * 60 * 60 * 1e3))} days old`,
|
|
131
|
+
suggestion: "Consider archiving or summarizing old memories"
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const namespaceCount = Object.keys(stats.namespaceDistribution).length;
|
|
136
|
+
if (namespaceCount === 1 && stats.totalEntries > 1e3) {
|
|
137
|
+
recommendations.push("Consider using namespaces to organize memories");
|
|
138
|
+
}
|
|
139
|
+
let score = 100;
|
|
140
|
+
for (const issue of issues) {
|
|
141
|
+
if (issue.type === "error") score -= 20;
|
|
142
|
+
else if (issue.type === "warning") score -= 10;
|
|
143
|
+
else score -= 5;
|
|
144
|
+
}
|
|
145
|
+
score = Math.max(0, Math.min(100, score));
|
|
146
|
+
return {
|
|
147
|
+
score,
|
|
148
|
+
issues,
|
|
149
|
+
recommendations
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Find duplicate entries
|
|
154
|
+
*/
|
|
155
|
+
async findDuplicates(threshold = 0.95) {
|
|
156
|
+
const { entries } = await this.store.query({
|
|
157
|
+
limit: this.config.maxEntriesForAnalysis
|
|
158
|
+
});
|
|
159
|
+
const duplicates = [];
|
|
160
|
+
for (let i = 0; i < entries.length; i++) {
|
|
161
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
162
|
+
const similarity = this.calculateSimilarity(entries[i], entries[j]);
|
|
163
|
+
if (similarity >= threshold) {
|
|
164
|
+
duplicates.push([entries[i], entries[j]]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return duplicates;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Find orphaned entries (no parent, but has parentId)
|
|
172
|
+
*/
|
|
173
|
+
async findOrphans() {
|
|
174
|
+
const { entries } = await this.store.query({
|
|
175
|
+
limit: this.config.maxEntriesForAnalysis
|
|
176
|
+
});
|
|
177
|
+
const entryIds = new Set(entries.map((e) => e.id));
|
|
178
|
+
const orphans = [];
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (entry.parentId && !entryIds.has(entry.parentId)) {
|
|
181
|
+
orphans.push(entry);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return orphans;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Find low-value entries
|
|
188
|
+
*/
|
|
189
|
+
async findLowValueEntries(options) {
|
|
190
|
+
const { entries } = await this.store.query({
|
|
191
|
+
limit: this.config.maxEntriesForAnalysis
|
|
192
|
+
});
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
const maxImportance = options?.maxImportance ?? 0.2;
|
|
195
|
+
const maxAccessCount = options?.maxAccessCount ?? 2;
|
|
196
|
+
const minAge = options?.minAge ?? 7 * 24 * 60 * 60 * 1e3;
|
|
197
|
+
return entries.filter((entry) => {
|
|
198
|
+
const age = now - entry.timestamp;
|
|
199
|
+
return entry.importance <= maxImportance && entry.accessCount <= maxAccessCount && age >= minAge;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get entry distribution over time
|
|
204
|
+
*/
|
|
205
|
+
async getTimeDistribution(bucketSize = "day") {
|
|
206
|
+
const { entries } = await this.store.query({
|
|
207
|
+
limit: this.config.maxEntriesForAnalysis
|
|
208
|
+
});
|
|
209
|
+
const distribution = /* @__PURE__ */ new Map();
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
const date = new Date(entry.timestamp);
|
|
212
|
+
let key;
|
|
213
|
+
switch (bucketSize) {
|
|
214
|
+
case "hour":
|
|
215
|
+
key = `${date.toISOString().slice(0, 13)}:00`;
|
|
216
|
+
break;
|
|
217
|
+
case "day":
|
|
218
|
+
key = date.toISOString().slice(0, 10);
|
|
219
|
+
break;
|
|
220
|
+
case "week": {
|
|
221
|
+
const weekStart = new Date(date);
|
|
222
|
+
weekStart.setDate(date.getDate() - date.getDay());
|
|
223
|
+
key = `week-${weekStart.toISOString().slice(0, 10)}`;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "month":
|
|
227
|
+
key = date.toISOString().slice(0, 7);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
distribution.set(key, (distribution.get(key) ?? 0) + 1);
|
|
231
|
+
}
|
|
232
|
+
return distribution;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Calculate similarity between two entries
|
|
236
|
+
*/
|
|
237
|
+
calculateSimilarity(a, b) {
|
|
238
|
+
if (a.embedding && b.embedding) {
|
|
239
|
+
return this.cosineSimilarity(a.embedding, b.embedding);
|
|
240
|
+
}
|
|
241
|
+
return this.textSimilarity(a.content, b.content);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Cosine similarity
|
|
245
|
+
*/
|
|
246
|
+
cosineSimilarity(a, b) {
|
|
247
|
+
if (a.length !== b.length) return 0;
|
|
248
|
+
let dotProduct = 0;
|
|
249
|
+
let normA = 0;
|
|
250
|
+
let normB = 0;
|
|
251
|
+
for (let i = 0; i < a.length; i++) {
|
|
252
|
+
dotProduct += a[i] * b[i];
|
|
253
|
+
normA += a[i] * a[i];
|
|
254
|
+
normB += b[i] * b[i];
|
|
255
|
+
}
|
|
256
|
+
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
257
|
+
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Text similarity (Jaccard)
|
|
261
|
+
*/
|
|
262
|
+
textSimilarity(a, b) {
|
|
263
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/));
|
|
264
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/));
|
|
265
|
+
const intersection = new Set([...wordsA].filter((x) => wordsB.has(x)));
|
|
266
|
+
const union = /* @__PURE__ */ new Set([...wordsA, ...wordsB]);
|
|
267
|
+
return intersection.size / union.size;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
function createInspector(store, config) {
|
|
271
|
+
return new Inspector(store, config);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/debug/Timeline.ts
|
|
275
|
+
var Timeline = class {
|
|
276
|
+
store;
|
|
277
|
+
config;
|
|
278
|
+
events = [];
|
|
279
|
+
markers = [];
|
|
280
|
+
constructor(store, config = {}) {
|
|
281
|
+
this.store = store;
|
|
282
|
+
this.config = {
|
|
283
|
+
timeRange: config.timeRange ?? { start: 0, end: Date.now() },
|
|
284
|
+
groupBy: config.groupBy ?? "day",
|
|
285
|
+
showTypes: config.showTypes ?? true,
|
|
286
|
+
showNamespaces: config.showNamespaces ?? true,
|
|
287
|
+
maxEvents: config.maxEvents ?? 1e4,
|
|
288
|
+
autoTrack: config.autoTrack ?? true,
|
|
289
|
+
segmentSize: config.segmentSize ?? "day"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Record a timeline event
|
|
294
|
+
*/
|
|
295
|
+
recordEvent(event) {
|
|
296
|
+
const fullEvent = {
|
|
297
|
+
...event,
|
|
298
|
+
id: `event-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
299
|
+
};
|
|
300
|
+
this.events.push(fullEvent);
|
|
301
|
+
if (this.events.length > this.config.maxEvents) {
|
|
302
|
+
this.events = this.events.slice(-this.config.maxEvents);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Add a marker to the timeline
|
|
307
|
+
*/
|
|
308
|
+
addMarker(marker) {
|
|
309
|
+
this.markers.push({
|
|
310
|
+
...marker,
|
|
311
|
+
timestamp: marker.timestamp ?? Date.now()
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get timeline segments
|
|
316
|
+
*/
|
|
317
|
+
async getSegments(startTime, endTime) {
|
|
318
|
+
const segments = [];
|
|
319
|
+
const segmentSize = this.getSegmentSizeMs();
|
|
320
|
+
const { entries } = await this.store.query({
|
|
321
|
+
startTime,
|
|
322
|
+
endTime,
|
|
323
|
+
limit: 1e4
|
|
324
|
+
});
|
|
325
|
+
const rangeEvents = this.events.filter(
|
|
326
|
+
(e) => e.timestamp >= startTime && e.timestamp <= endTime
|
|
327
|
+
);
|
|
328
|
+
let currentStart = startTime;
|
|
329
|
+
while (currentStart < endTime) {
|
|
330
|
+
const currentEnd = Math.min(currentStart + segmentSize, endTime);
|
|
331
|
+
const segmentEntries = entries.filter(
|
|
332
|
+
(e) => e.timestamp >= currentStart && e.timestamp < currentEnd
|
|
333
|
+
);
|
|
334
|
+
const segmentEvents = rangeEvents.filter(
|
|
335
|
+
(e) => e.timestamp >= currentStart && e.timestamp < currentEnd
|
|
336
|
+
);
|
|
337
|
+
if (segmentEntries.length > 0 || segmentEvents.length > 0) {
|
|
338
|
+
segments.push({
|
|
339
|
+
start: currentStart,
|
|
340
|
+
end: currentEnd,
|
|
341
|
+
entries: segmentEntries,
|
|
342
|
+
events: segmentEvents
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
currentStart = currentEnd;
|
|
346
|
+
}
|
|
347
|
+
return segments;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Get timeline for a specific entry
|
|
351
|
+
*/
|
|
352
|
+
async getEntryTimeline(entryId) {
|
|
353
|
+
const entry = await this.store.get(entryId);
|
|
354
|
+
const entryEvents = this.events.filter((e) => e.entryId === entryId);
|
|
355
|
+
entryEvents.sort((a, b) => a.timestamp - b.timestamp);
|
|
356
|
+
const accessHistory = entryEvents.filter((e) => e.type === "access").map((e) => e.timestamp);
|
|
357
|
+
return {
|
|
358
|
+
entry,
|
|
359
|
+
events: entryEvents,
|
|
360
|
+
createdAt: entry?.createdAt ?? 0,
|
|
361
|
+
lastModified: entry?.updatedAt ?? 0,
|
|
362
|
+
accessHistory
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get activity heatmap data
|
|
367
|
+
*/
|
|
368
|
+
async getActivityHeatmap(startTime, endTime, bucketSize = "hour") {
|
|
369
|
+
const heatmap = /* @__PURE__ */ new Map();
|
|
370
|
+
const { entries } = await this.store.query({
|
|
371
|
+
startTime,
|
|
372
|
+
endTime,
|
|
373
|
+
limit: 1e4
|
|
374
|
+
});
|
|
375
|
+
const rangeEvents = this.events.filter(
|
|
376
|
+
(e) => e.timestamp >= startTime && e.timestamp <= endTime
|
|
377
|
+
);
|
|
378
|
+
for (const entry of entries) {
|
|
379
|
+
const key = this.getBucketKey(entry.timestamp, bucketSize);
|
|
380
|
+
const existing = heatmap.get(key) ?? { entries: 0, events: 0 };
|
|
381
|
+
existing.entries++;
|
|
382
|
+
heatmap.set(key, existing);
|
|
383
|
+
}
|
|
384
|
+
for (const event of rangeEvents) {
|
|
385
|
+
const key = this.getBucketKey(event.timestamp, bucketSize);
|
|
386
|
+
const existing = heatmap.get(key) ?? { entries: 0, events: 0 };
|
|
387
|
+
existing.events++;
|
|
388
|
+
heatmap.set(key, existing);
|
|
389
|
+
}
|
|
390
|
+
return heatmap;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get markers in time range
|
|
394
|
+
*/
|
|
395
|
+
getMarkers(startTime, endTime) {
|
|
396
|
+
return this.markers.filter(
|
|
397
|
+
(m) => m.timestamp >= startTime && m.timestamp <= endTime
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get recent activity summary
|
|
402
|
+
*/
|
|
403
|
+
getRecentActivity(windowMs = 24 * 60 * 60 * 1e3) {
|
|
404
|
+
const now = Date.now();
|
|
405
|
+
const startTime = now - windowMs;
|
|
406
|
+
const recentEvents = this.events.filter((e) => e.timestamp >= startTime);
|
|
407
|
+
const newEntries = recentEvents.filter((e) => e.type === "add").length;
|
|
408
|
+
const updatedEntries = recentEvents.filter(
|
|
409
|
+
(e) => e.type === "update"
|
|
410
|
+
).length;
|
|
411
|
+
const deletedEntries = recentEvents.filter(
|
|
412
|
+
(e) => e.type === "delete"
|
|
413
|
+
).length;
|
|
414
|
+
const totalAccesses = recentEvents.filter(
|
|
415
|
+
(e) => e.type === "access"
|
|
416
|
+
).length;
|
|
417
|
+
const accessCounts = /* @__PURE__ */ new Map();
|
|
418
|
+
for (const event of recentEvents) {
|
|
419
|
+
if (event.type === "access") {
|
|
420
|
+
accessCounts.set(
|
|
421
|
+
event.entryId,
|
|
422
|
+
(accessCounts.get(event.entryId) ?? 0) + 1
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const topAccessed = Array.from(accessCounts.entries()).map(([entryId, accessCount]) => ({ entryId, accessCount })).sort((a, b) => b.accessCount - a.accessCount).slice(0, 10);
|
|
427
|
+
return {
|
|
428
|
+
newEntries,
|
|
429
|
+
updatedEntries,
|
|
430
|
+
deletedEntries,
|
|
431
|
+
totalAccesses,
|
|
432
|
+
topAccessed
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Get memory growth over time
|
|
437
|
+
*/
|
|
438
|
+
async getGrowthChart(startTime, endTime, bucketSize = "day") {
|
|
439
|
+
const { entries } = await this.store.query({
|
|
440
|
+
startTime,
|
|
441
|
+
endTime,
|
|
442
|
+
limit: 1e5
|
|
443
|
+
});
|
|
444
|
+
entries.sort((a, b) => a.timestamp - b.timestamp);
|
|
445
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
446
|
+
for (const entry of entries) {
|
|
447
|
+
const key = this.getBucketKey(entry.timestamp, bucketSize);
|
|
448
|
+
buckets.set(key, (buckets.get(key) ?? 0) + 1);
|
|
449
|
+
}
|
|
450
|
+
const sortedKeys = Array.from(buckets.keys()).sort();
|
|
451
|
+
let cumulative = 0;
|
|
452
|
+
const chart = [];
|
|
453
|
+
for (const key of sortedKeys) {
|
|
454
|
+
const newCount = buckets.get(key) ?? 0;
|
|
455
|
+
cumulative += newCount;
|
|
456
|
+
chart.push({
|
|
457
|
+
time: key,
|
|
458
|
+
cumulativeCount: cumulative,
|
|
459
|
+
newCount
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return chart;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Find gaps in timeline (periods with no activity)
|
|
466
|
+
*/
|
|
467
|
+
async findGaps(startTime, endTime, minGapSize = 24 * 60 * 60 * 1e3) {
|
|
468
|
+
const { entries } = await this.store.query({
|
|
469
|
+
startTime,
|
|
470
|
+
endTime,
|
|
471
|
+
limit: 1e4
|
|
472
|
+
});
|
|
473
|
+
if (entries.length < 2) return [];
|
|
474
|
+
const sorted = [...entries].sort((a, b) => a.timestamp - b.timestamp);
|
|
475
|
+
const gaps = [];
|
|
476
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
477
|
+
const gap = sorted[i].timestamp - sorted[i - 1].timestamp;
|
|
478
|
+
if (gap >= minGapSize) {
|
|
479
|
+
gaps.push({
|
|
480
|
+
start: sorted[i - 1].timestamp,
|
|
481
|
+
end: sorted[i].timestamp,
|
|
482
|
+
durationMs: gap
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return gaps;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Clear events
|
|
490
|
+
*/
|
|
491
|
+
clearEvents() {
|
|
492
|
+
this.events = [];
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Clear markers
|
|
496
|
+
*/
|
|
497
|
+
clearMarkers() {
|
|
498
|
+
this.markers = [];
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Export timeline data
|
|
502
|
+
*/
|
|
503
|
+
exportData() {
|
|
504
|
+
return {
|
|
505
|
+
events: [...this.events],
|
|
506
|
+
markers: [...this.markers],
|
|
507
|
+
exportedAt: Date.now()
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Import timeline data
|
|
512
|
+
*/
|
|
513
|
+
importData(data) {
|
|
514
|
+
this.events.push(...data.events);
|
|
515
|
+
this.markers.push(...data.markers);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get segment size in milliseconds
|
|
519
|
+
*/
|
|
520
|
+
getSegmentSizeMs() {
|
|
521
|
+
switch (this.config.segmentSize) {
|
|
522
|
+
case "hour":
|
|
523
|
+
return 60 * 60 * 1e3;
|
|
524
|
+
case "day":
|
|
525
|
+
return 24 * 60 * 60 * 1e3;
|
|
526
|
+
case "week":
|
|
527
|
+
return 7 * 24 * 60 * 60 * 1e3;
|
|
528
|
+
default:
|
|
529
|
+
return 24 * 60 * 60 * 1e3;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get bucket key for timestamp
|
|
534
|
+
*/
|
|
535
|
+
getBucketKey(timestamp, bucketSize) {
|
|
536
|
+
const date = new Date(timestamp);
|
|
537
|
+
switch (bucketSize) {
|
|
538
|
+
case "hour":
|
|
539
|
+
return `${date.toISOString().slice(0, 13)}:00`;
|
|
540
|
+
case "day":
|
|
541
|
+
return date.toISOString().slice(0, 10);
|
|
542
|
+
case "week": {
|
|
543
|
+
const weekStart = new Date(date);
|
|
544
|
+
weekStart.setDate(date.getDate() - date.getDay());
|
|
545
|
+
return `week-${weekStart.toISOString().slice(0, 10)}`;
|
|
546
|
+
}
|
|
547
|
+
default:
|
|
548
|
+
return date.toISOString().slice(0, 10);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
function createTimeline(store, config) {
|
|
553
|
+
return new Timeline(store, config);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/debug/Debugger.ts
|
|
557
|
+
import { EventEmitter } from "eventemitter3";
|
|
558
|
+
var Debugger = class extends EventEmitter {
|
|
559
|
+
store;
|
|
560
|
+
traces = [];
|
|
561
|
+
breakpoints = /* @__PURE__ */ new Map();
|
|
562
|
+
enabled = true;
|
|
563
|
+
maxTraces = 1e3;
|
|
564
|
+
constructor(store) {
|
|
565
|
+
super();
|
|
566
|
+
this.store = store;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Enable debugging
|
|
570
|
+
*/
|
|
571
|
+
enable() {
|
|
572
|
+
this.enabled = true;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Disable debugging
|
|
576
|
+
*/
|
|
577
|
+
disable() {
|
|
578
|
+
this.enabled = false;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Check if debugging is enabled
|
|
582
|
+
*/
|
|
583
|
+
isEnabled() {
|
|
584
|
+
return this.enabled;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Trace an operation
|
|
588
|
+
*/
|
|
589
|
+
trace(operation, input, output, durationMs, metadata) {
|
|
590
|
+
if (!this.enabled) return;
|
|
591
|
+
const trace = {
|
|
592
|
+
id: `trace-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
593
|
+
operation,
|
|
594
|
+
timestamp: Date.now(),
|
|
595
|
+
duration: durationMs,
|
|
596
|
+
input,
|
|
597
|
+
output,
|
|
598
|
+
metadata
|
|
599
|
+
};
|
|
600
|
+
this.traces.push(trace);
|
|
601
|
+
if (this.traces.length > this.maxTraces) {
|
|
602
|
+
this.traces = this.traces.slice(-this.maxTraces);
|
|
603
|
+
}
|
|
604
|
+
this.emit("trace", trace);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Wrap a function with tracing
|
|
608
|
+
*/
|
|
609
|
+
wrapWithTrace(operation, fn) {
|
|
610
|
+
return (async (...args) => {
|
|
611
|
+
const startTime = Date.now();
|
|
612
|
+
try {
|
|
613
|
+
const result = await fn(...args);
|
|
614
|
+
this.trace(operation, args, result, Date.now() - startTime);
|
|
615
|
+
return result;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
this.trace(
|
|
618
|
+
operation,
|
|
619
|
+
args,
|
|
620
|
+
{ error: String(error) },
|
|
621
|
+
Date.now() - startTime,
|
|
622
|
+
{
|
|
623
|
+
error: true
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Add a breakpoint
|
|
632
|
+
*/
|
|
633
|
+
addBreakpoint(condition, id) {
|
|
634
|
+
const breakpointId = id ?? `bp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
635
|
+
this.breakpoints.set(breakpointId, {
|
|
636
|
+
id: breakpointId,
|
|
637
|
+
condition,
|
|
638
|
+
enabled: true,
|
|
639
|
+
hitCount: 0
|
|
640
|
+
});
|
|
641
|
+
return breakpointId;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Remove a breakpoint
|
|
645
|
+
*/
|
|
646
|
+
removeBreakpoint(id) {
|
|
647
|
+
return this.breakpoints.delete(id);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Enable/disable a breakpoint
|
|
651
|
+
*/
|
|
652
|
+
toggleBreakpoint(id, enabled) {
|
|
653
|
+
const bp = this.breakpoints.get(id);
|
|
654
|
+
if (!bp) return false;
|
|
655
|
+
bp.enabled = enabled;
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Check breakpoints for an entry/operation
|
|
660
|
+
*/
|
|
661
|
+
checkBreakpoints(entry, operation) {
|
|
662
|
+
if (!this.enabled) return;
|
|
663
|
+
for (const bp of this.breakpoints.values()) {
|
|
664
|
+
if (!bp.enabled) continue;
|
|
665
|
+
try {
|
|
666
|
+
if (bp.condition(entry, operation)) {
|
|
667
|
+
bp.hitCount++;
|
|
668
|
+
this.emit("breakpointHit", bp, entry, operation);
|
|
669
|
+
}
|
|
670
|
+
} catch (error) {
|
|
671
|
+
this.emit("warning", `Breakpoint ${bp.id} condition error`, error);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Debug a retrieval operation
|
|
677
|
+
*/
|
|
678
|
+
async debugRetrieval(query, embedFn, options) {
|
|
679
|
+
const timing = { total: 0 };
|
|
680
|
+
const filteringSteps = [];
|
|
681
|
+
const scoringDetails = [];
|
|
682
|
+
const totalStart = Date.now();
|
|
683
|
+
const embeddingStart = Date.now();
|
|
684
|
+
const embedding = await embedFn(query);
|
|
685
|
+
timing.embedding = Date.now() - embeddingStart;
|
|
686
|
+
const searchStart = Date.now();
|
|
687
|
+
const searchResults = await this.store.search(embedding, {
|
|
688
|
+
topK: options?.candidateMultiplier ? (options.limit ?? 10) * options.candidateMultiplier : 100,
|
|
689
|
+
minScore: 0
|
|
690
|
+
// Get all for debugging
|
|
691
|
+
});
|
|
692
|
+
timing.search = Date.now() - searchStart;
|
|
693
|
+
filteringSteps.push({
|
|
694
|
+
name: "initial-search",
|
|
695
|
+
inputCount: 0,
|
|
696
|
+
outputCount: searchResults.length,
|
|
697
|
+
durationMs: timing.search
|
|
698
|
+
});
|
|
699
|
+
let filtered = searchResults;
|
|
700
|
+
const filterStart = Date.now();
|
|
701
|
+
if (options?.types && options.types.length > 0) {
|
|
702
|
+
const beforeCount = filtered.length;
|
|
703
|
+
filtered = filtered.filter((r) => options.types.includes(r.entry.type));
|
|
704
|
+
filteringSteps.push({
|
|
705
|
+
name: "type-filter",
|
|
706
|
+
inputCount: beforeCount,
|
|
707
|
+
outputCount: filtered.length,
|
|
708
|
+
durationMs: 0
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
if (options?.namespace) {
|
|
712
|
+
const beforeCount = filtered.length;
|
|
713
|
+
filtered = filtered.filter(
|
|
714
|
+
(r) => r.entry.metadata.namespace === options.namespace
|
|
715
|
+
);
|
|
716
|
+
filteringSteps.push({
|
|
717
|
+
name: "namespace-filter",
|
|
718
|
+
inputCount: beforeCount,
|
|
719
|
+
outputCount: filtered.length,
|
|
720
|
+
durationMs: 0
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
if (options?.minScore !== void 0) {
|
|
724
|
+
const beforeCount = filtered.length;
|
|
725
|
+
filtered = filtered.filter((r) => r.score >= options.minScore);
|
|
726
|
+
filteringSteps.push({
|
|
727
|
+
name: "score-filter",
|
|
728
|
+
inputCount: beforeCount,
|
|
729
|
+
outputCount: filtered.length,
|
|
730
|
+
durationMs: 0
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
timing.filtering = Date.now() - filterStart;
|
|
734
|
+
for (const result of filtered.slice(0, options?.limit ?? 10)) {
|
|
735
|
+
scoringDetails.push({
|
|
736
|
+
entryId: result.entry.id,
|
|
737
|
+
scores: {
|
|
738
|
+
similarity: result.score,
|
|
739
|
+
importance: result.entry.importance,
|
|
740
|
+
recency: 1 / (1 + (Date.now() - result.entry.timestamp) / (24 * 60 * 60 * 1e3))
|
|
741
|
+
},
|
|
742
|
+
finalScore: result.score
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
timing.total = Date.now() - totalStart;
|
|
746
|
+
return {
|
|
747
|
+
query,
|
|
748
|
+
totalCandidates: searchResults.length,
|
|
749
|
+
returnedCount: Math.min(filtered.length, options?.limit ?? 10),
|
|
750
|
+
strategyUsed: "semantic",
|
|
751
|
+
filteringSteps,
|
|
752
|
+
scoringDetails,
|
|
753
|
+
timing
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Get recent traces
|
|
758
|
+
*/
|
|
759
|
+
getTraces(options) {
|
|
760
|
+
let traces = [...this.traces];
|
|
761
|
+
if (options?.operation) {
|
|
762
|
+
traces = traces.filter((t) => t.operation === options.operation);
|
|
763
|
+
}
|
|
764
|
+
if (options?.startTime) {
|
|
765
|
+
traces = traces.filter((t) => t.timestamp >= options.startTime);
|
|
766
|
+
}
|
|
767
|
+
if (options?.endTime) {
|
|
768
|
+
traces = traces.filter((t) => t.timestamp <= options.endTime);
|
|
769
|
+
}
|
|
770
|
+
traces.sort((a, b) => b.timestamp - a.timestamp);
|
|
771
|
+
return options?.limit ? traces.slice(0, options.limit) : traces;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get operation statistics
|
|
775
|
+
*/
|
|
776
|
+
getOperationStats() {
|
|
777
|
+
const stats = /* @__PURE__ */ new Map();
|
|
778
|
+
for (const trace of this.traces) {
|
|
779
|
+
const existing = stats.get(trace.operation) ?? {
|
|
780
|
+
count: 0,
|
|
781
|
+
totalDuration: 0,
|
|
782
|
+
maxDuration: 0,
|
|
783
|
+
minDuration: Infinity
|
|
784
|
+
};
|
|
785
|
+
existing.count++;
|
|
786
|
+
existing.totalDuration += trace.duration;
|
|
787
|
+
existing.maxDuration = Math.max(existing.maxDuration, trace.duration);
|
|
788
|
+
existing.minDuration = Math.min(existing.minDuration, trace.duration);
|
|
789
|
+
stats.set(trace.operation, existing);
|
|
790
|
+
}
|
|
791
|
+
const result = /* @__PURE__ */ new Map();
|
|
792
|
+
for (const [op, data] of stats) {
|
|
793
|
+
result.set(op, {
|
|
794
|
+
count: data.count,
|
|
795
|
+
avgDuration: data.totalDuration / data.count,
|
|
796
|
+
maxDuration: data.maxDuration,
|
|
797
|
+
minDuration: data.minDuration === Infinity ? 0 : data.minDuration
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Get all breakpoints
|
|
804
|
+
*/
|
|
805
|
+
getBreakpoints() {
|
|
806
|
+
return Array.from(this.breakpoints.values());
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Clear traces
|
|
810
|
+
*/
|
|
811
|
+
clearTraces() {
|
|
812
|
+
this.traces = [];
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Clear all breakpoints
|
|
816
|
+
*/
|
|
817
|
+
clearBreakpoints() {
|
|
818
|
+
this.breakpoints.clear();
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Create a snapshot of current debug state
|
|
822
|
+
*/
|
|
823
|
+
createSnapshot() {
|
|
824
|
+
return {
|
|
825
|
+
traces: [...this.traces],
|
|
826
|
+
breakpoints: this.getBreakpoints().map((bp) => ({
|
|
827
|
+
id: bp.id,
|
|
828
|
+
enabled: bp.enabled,
|
|
829
|
+
hitCount: bp.hitCount
|
|
830
|
+
})),
|
|
831
|
+
stats: this.getOperationStats(),
|
|
832
|
+
timestamp: Date.now()
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
function createDebugger(store) {
|
|
837
|
+
return new Debugger(store);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/debug/Exporter.ts
|
|
841
|
+
var Exporter = class {
|
|
842
|
+
store;
|
|
843
|
+
constructor(store) {
|
|
844
|
+
this.store = store;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Export memories to string
|
|
848
|
+
*/
|
|
849
|
+
async export(options = {}) {
|
|
850
|
+
const format = options.format ?? "json";
|
|
851
|
+
const { entries } = await this.store.query({
|
|
852
|
+
namespace: options.namespace,
|
|
853
|
+
types: options.types,
|
|
854
|
+
startTime: options.startTime,
|
|
855
|
+
endTime: options.endTime,
|
|
856
|
+
limit: 1e5
|
|
857
|
+
// High limit for export
|
|
858
|
+
});
|
|
859
|
+
let filtered = entries;
|
|
860
|
+
if (options.ids && options.ids.length > 0) {
|
|
861
|
+
const idSet = new Set(options.ids);
|
|
862
|
+
filtered = entries.filter((e) => idSet.has(e.id));
|
|
863
|
+
}
|
|
864
|
+
if (!options.includeEmbeddings) {
|
|
865
|
+
filtered = filtered.map((e) => ({ ...e, embedding: void 0 }));
|
|
866
|
+
}
|
|
867
|
+
let data;
|
|
868
|
+
switch (format) {
|
|
869
|
+
case "json":
|
|
870
|
+
data = this.toJSON(filtered, options.pretty);
|
|
871
|
+
break;
|
|
872
|
+
case "jsonl":
|
|
873
|
+
data = this.toJSONL(filtered);
|
|
874
|
+
break;
|
|
875
|
+
case "csv":
|
|
876
|
+
data = this.toCSV(filtered);
|
|
877
|
+
break;
|
|
878
|
+
default:
|
|
879
|
+
data = this.toJSON(filtered, options.pretty);
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
data,
|
|
883
|
+
format,
|
|
884
|
+
entryCount: filtered.length,
|
|
885
|
+
exportedAt: Date.now(),
|
|
886
|
+
checksum: this.calculateChecksum(data)
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Export to JSON file content
|
|
891
|
+
*/
|
|
892
|
+
toJSON(entries, pretty = false) {
|
|
893
|
+
const exportData = {
|
|
894
|
+
version: "1.0",
|
|
895
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
896
|
+
entryCount: entries.length,
|
|
897
|
+
entries
|
|
898
|
+
};
|
|
899
|
+
return pretty ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Export to JSON Lines format
|
|
903
|
+
*/
|
|
904
|
+
toJSONL(entries) {
|
|
905
|
+
return entries.map((e) => JSON.stringify(e)).join("\n");
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Export to CSV format
|
|
909
|
+
*/
|
|
910
|
+
toCSV(entries) {
|
|
911
|
+
const headers = [
|
|
912
|
+
"id",
|
|
913
|
+
"content",
|
|
914
|
+
"type",
|
|
915
|
+
"importance",
|
|
916
|
+
"timestamp",
|
|
917
|
+
"accessCount",
|
|
918
|
+
"createdAt",
|
|
919
|
+
"updatedAt",
|
|
920
|
+
"namespace",
|
|
921
|
+
"userId",
|
|
922
|
+
"agentId"
|
|
923
|
+
];
|
|
924
|
+
const rows = entries.map((e) => {
|
|
925
|
+
return [
|
|
926
|
+
this.escapeCSV(e.id),
|
|
927
|
+
this.escapeCSV(e.content),
|
|
928
|
+
e.type,
|
|
929
|
+
e.importance.toString(),
|
|
930
|
+
e.timestamp.toString(),
|
|
931
|
+
e.accessCount.toString(),
|
|
932
|
+
e.createdAt.toString(),
|
|
933
|
+
e.updatedAt.toString(),
|
|
934
|
+
this.escapeCSV(String(e.metadata.namespace ?? "")),
|
|
935
|
+
this.escapeCSV(String(e.metadata.userId ?? "")),
|
|
936
|
+
this.escapeCSV(String(e.metadata.agentId ?? ""))
|
|
937
|
+
].join(",");
|
|
938
|
+
});
|
|
939
|
+
return [headers.join(","), ...rows].join("\n");
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Escape CSV value
|
|
943
|
+
*/
|
|
944
|
+
escapeCSV(value) {
|
|
945
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
946
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
947
|
+
}
|
|
948
|
+
return value;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Import memories from string
|
|
952
|
+
*/
|
|
953
|
+
async import(data, format = "json", options) {
|
|
954
|
+
let entries;
|
|
955
|
+
switch (format) {
|
|
956
|
+
case "json":
|
|
957
|
+
entries = this.fromJSON(data);
|
|
958
|
+
break;
|
|
959
|
+
case "jsonl":
|
|
960
|
+
entries = this.fromJSONL(data);
|
|
961
|
+
break;
|
|
962
|
+
case "csv":
|
|
963
|
+
entries = this.fromCSV(data);
|
|
964
|
+
break;
|
|
965
|
+
default:
|
|
966
|
+
entries = this.fromJSON(data);
|
|
967
|
+
}
|
|
968
|
+
if (options?.validateOnly) {
|
|
969
|
+
return {
|
|
970
|
+
imported: 0,
|
|
971
|
+
skipped: 0,
|
|
972
|
+
errors: []
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
let imported = 0;
|
|
976
|
+
let skipped = 0;
|
|
977
|
+
const errors = [];
|
|
978
|
+
for (const entry of entries) {
|
|
979
|
+
try {
|
|
980
|
+
if (options?.namespace) {
|
|
981
|
+
entry.metadata.namespace = options.namespace;
|
|
982
|
+
}
|
|
983
|
+
const existing = await this.store.get(entry.id);
|
|
984
|
+
if (existing && !options?.overwrite) {
|
|
985
|
+
skipped++;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (!this.validateEntry(entry)) {
|
|
989
|
+
errors.push({ entry, error: "Invalid entry format" });
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (existing) {
|
|
993
|
+
await this.store.update(entry.id, entry);
|
|
994
|
+
} else {
|
|
995
|
+
await this.store.add(entry);
|
|
996
|
+
}
|
|
997
|
+
imported++;
|
|
998
|
+
} catch (error) {
|
|
999
|
+
errors.push({ entry, error: String(error) });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
imported,
|
|
1004
|
+
skipped,
|
|
1005
|
+
errors
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Parse JSON export
|
|
1010
|
+
*/
|
|
1011
|
+
fromJSON(data) {
|
|
1012
|
+
const parsed = JSON.parse(data);
|
|
1013
|
+
if (Array.isArray(parsed)) {
|
|
1014
|
+
return parsed;
|
|
1015
|
+
}
|
|
1016
|
+
if (parsed.entries && Array.isArray(parsed.entries)) {
|
|
1017
|
+
return parsed.entries;
|
|
1018
|
+
}
|
|
1019
|
+
throw new Error("Invalid JSON format");
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Parse JSONL export
|
|
1023
|
+
*/
|
|
1024
|
+
fromJSONL(data) {
|
|
1025
|
+
return data.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Parse CSV export
|
|
1029
|
+
*/
|
|
1030
|
+
fromCSV(data) {
|
|
1031
|
+
const lines = data.split("\n");
|
|
1032
|
+
if (lines.length < 2) return [];
|
|
1033
|
+
const headers = this.parseCSVLine(lines[0]);
|
|
1034
|
+
const entries = [];
|
|
1035
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1036
|
+
if (!lines[i].trim()) continue;
|
|
1037
|
+
const values = this.parseCSVLine(lines[i]);
|
|
1038
|
+
const obj = {};
|
|
1039
|
+
headers.forEach((header, index) => {
|
|
1040
|
+
obj[header] = values[index];
|
|
1041
|
+
});
|
|
1042
|
+
const entry = {
|
|
1043
|
+
id: String(obj.id),
|
|
1044
|
+
content: String(obj.content),
|
|
1045
|
+
type: obj.type,
|
|
1046
|
+
importance: parseFloat(String(obj.importance)) || 0.5,
|
|
1047
|
+
metadata: {
|
|
1048
|
+
source: "explicit",
|
|
1049
|
+
confidence: 1,
|
|
1050
|
+
namespace: String(obj.namespace || "default"),
|
|
1051
|
+
userId: obj.userId ? String(obj.userId) : void 0,
|
|
1052
|
+
agentId: obj.agentId ? String(obj.agentId) : void 0
|
|
1053
|
+
},
|
|
1054
|
+
timestamp: parseInt(String(obj.timestamp)) || Date.now(),
|
|
1055
|
+
accessCount: parseInt(String(obj.accessCount)) || 0,
|
|
1056
|
+
createdAt: parseInt(String(obj.createdAt)) || Date.now(),
|
|
1057
|
+
updatedAt: parseInt(String(obj.updatedAt)) || Date.now()
|
|
1058
|
+
};
|
|
1059
|
+
entries.push(entry);
|
|
1060
|
+
}
|
|
1061
|
+
return entries;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Parse a CSV line
|
|
1065
|
+
*/
|
|
1066
|
+
parseCSVLine(line) {
|
|
1067
|
+
const result = [];
|
|
1068
|
+
let current = "";
|
|
1069
|
+
let inQuotes = false;
|
|
1070
|
+
for (let i = 0; i < line.length; i++) {
|
|
1071
|
+
const char = line[i];
|
|
1072
|
+
if (char === '"') {
|
|
1073
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
1074
|
+
current += '"';
|
|
1075
|
+
i++;
|
|
1076
|
+
} else {
|
|
1077
|
+
inQuotes = !inQuotes;
|
|
1078
|
+
}
|
|
1079
|
+
} else if (char === "," && !inQuotes) {
|
|
1080
|
+
result.push(current);
|
|
1081
|
+
current = "";
|
|
1082
|
+
} else {
|
|
1083
|
+
current += char;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
result.push(current);
|
|
1087
|
+
return result;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Validate entry
|
|
1091
|
+
*/
|
|
1092
|
+
validateEntry(entry) {
|
|
1093
|
+
if (!entry.id || typeof entry.id !== "string") return false;
|
|
1094
|
+
if (!entry.content || typeof entry.content !== "string") return false;
|
|
1095
|
+
if (!entry.type) return false;
|
|
1096
|
+
if (typeof entry.importance !== "number") return false;
|
|
1097
|
+
if (typeof entry.timestamp !== "number") return false;
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Calculate simple checksum
|
|
1102
|
+
*/
|
|
1103
|
+
calculateChecksum(data) {
|
|
1104
|
+
let hash = 0;
|
|
1105
|
+
for (let i = 0; i < data.length; i++) {
|
|
1106
|
+
const char = data.charCodeAt(i);
|
|
1107
|
+
hash = (hash << 5) - hash + char;
|
|
1108
|
+
hash = hash & hash;
|
|
1109
|
+
}
|
|
1110
|
+
return Math.abs(hash).toString(16);
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Create backup
|
|
1114
|
+
*/
|
|
1115
|
+
async createBackup(name) {
|
|
1116
|
+
const data = await this.export({
|
|
1117
|
+
format: "json",
|
|
1118
|
+
includeEmbeddings: true,
|
|
1119
|
+
pretty: false
|
|
1120
|
+
});
|
|
1121
|
+
return {
|
|
1122
|
+
name: name ?? `backup-${Date.now()}`,
|
|
1123
|
+
data,
|
|
1124
|
+
createdAt: Date.now()
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Restore from backup
|
|
1129
|
+
*/
|
|
1130
|
+
async restoreBackup(backup, options) {
|
|
1131
|
+
if (options?.clearExisting) {
|
|
1132
|
+
await this.store.clear();
|
|
1133
|
+
}
|
|
1134
|
+
return this.import(backup.data.data, backup.data.format, {
|
|
1135
|
+
overwrite: true
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Get export size estimate
|
|
1140
|
+
*/
|
|
1141
|
+
async estimateExportSize(options) {
|
|
1142
|
+
const { entries, total } = await this.store.query({
|
|
1143
|
+
namespace: options?.namespace,
|
|
1144
|
+
types: options?.types,
|
|
1145
|
+
limit: 100
|
|
1146
|
+
// Sample
|
|
1147
|
+
});
|
|
1148
|
+
if (entries.length === 0) {
|
|
1149
|
+
return {
|
|
1150
|
+
entryCount: total,
|
|
1151
|
+
estimatedSizeBytes: 0
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
let totalSize = 0;
|
|
1155
|
+
let totalSizeWithEmbeddings = 0;
|
|
1156
|
+
for (const entry of entries) {
|
|
1157
|
+
const withoutEmbed = {
|
|
1158
|
+
...entry,
|
|
1159
|
+
embedding: void 0
|
|
1160
|
+
};
|
|
1161
|
+
totalSize += JSON.stringify(withoutEmbed).length;
|
|
1162
|
+
totalSizeWithEmbeddings += JSON.stringify(entry).length;
|
|
1163
|
+
}
|
|
1164
|
+
const avgSize = totalSize / entries.length;
|
|
1165
|
+
const avgSizeWithEmbed = totalSizeWithEmbeddings / entries.length;
|
|
1166
|
+
return {
|
|
1167
|
+
entryCount: total,
|
|
1168
|
+
estimatedSizeBytes: Math.round(avgSize * total),
|
|
1169
|
+
withEmbeddingsBytes: Math.round(avgSizeWithEmbed * total)
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Diff two exports
|
|
1174
|
+
*/
|
|
1175
|
+
diffExports(export1, export2) {
|
|
1176
|
+
const entries1 = this.fromJSON(export1.data);
|
|
1177
|
+
const entries2 = this.fromJSON(export2.data);
|
|
1178
|
+
const map1 = new Map(entries1.map((e) => [e.id, e]));
|
|
1179
|
+
const map2 = new Map(entries2.map((e) => [e.id, e]));
|
|
1180
|
+
const added = [];
|
|
1181
|
+
const removed = [];
|
|
1182
|
+
const modified = [];
|
|
1183
|
+
const unchanged = [];
|
|
1184
|
+
for (const [id, entry2] of map2) {
|
|
1185
|
+
const entry1 = map1.get(id);
|
|
1186
|
+
if (!entry1) {
|
|
1187
|
+
added.push(id);
|
|
1188
|
+
} else if (JSON.stringify(entry1) !== JSON.stringify(entry2)) {
|
|
1189
|
+
modified.push(id);
|
|
1190
|
+
} else {
|
|
1191
|
+
unchanged.push(id);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
for (const id of map1.keys()) {
|
|
1195
|
+
if (!map2.has(id)) {
|
|
1196
|
+
removed.push(id);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return { added, removed, modified, unchanged };
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
function createExporter(store) {
|
|
1203
|
+
return new Exporter(store);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
export {
|
|
1207
|
+
Inspector,
|
|
1208
|
+
createInspector,
|
|
1209
|
+
Timeline,
|
|
1210
|
+
createTimeline,
|
|
1211
|
+
Debugger,
|
|
1212
|
+
createDebugger,
|
|
1213
|
+
Exporter,
|
|
1214
|
+
createExporter
|
|
1215
|
+
};
|