chainlesschain 0.37.10 → 0.37.12
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/README.md +166 -10
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/init.js +184 -0
- package/src/commands/lowcode.js +320 -0
- package/src/commands/plugin.js +55 -2
- package/src/commands/sandbox.js +366 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +44 -0
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/plugin-manager.js +118 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +117 -112
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical Memory 2.0 for CLI
|
|
3
|
+
*
|
|
4
|
+
* Four-layer memory system: working → short-term → long-term → core
|
|
5
|
+
* with Ebbinghaus forgetting curve and spacing effect reinforcement.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ─── Configuration ───────────────────────────────────────────────
|
|
9
|
+
export const MEMORY_CONFIG = {
|
|
10
|
+
workingCapacity: 50,
|
|
11
|
+
shortTermCapacity: 500,
|
|
12
|
+
longTermCapacity: 10000,
|
|
13
|
+
forgettingRate: 0.1,
|
|
14
|
+
recallThreshold: 0.3,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ─── In-memory layers ────────────────────────────────────────────
|
|
18
|
+
// Map<id, { id, content, type, importance, accessCount, createdAt, lastAccessed }>
|
|
19
|
+
export const _working = new Map();
|
|
20
|
+
export const _shortTerm = new Map();
|
|
21
|
+
|
|
22
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
23
|
+
function generateId() {
|
|
24
|
+
return `hmem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nowISO() {
|
|
28
|
+
return new Date().toISOString();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Calculate retention using Ebbinghaus forgetting curve.
|
|
33
|
+
* retention = e^(-decay_rate * age_hours)
|
|
34
|
+
*/
|
|
35
|
+
function calcRetention(
|
|
36
|
+
createdOrAccessed,
|
|
37
|
+
decayRate = MEMORY_CONFIG.forgettingRate,
|
|
38
|
+
) {
|
|
39
|
+
const ageMs = Date.now() - new Date(createdOrAccessed).getTime();
|
|
40
|
+
const ageHours = Math.max(0, ageMs / (1000 * 60 * 60));
|
|
41
|
+
return Math.exp(-decayRate * ageHours);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Table setup ─────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create persistent memory tables for long-term / core / sharing
|
|
48
|
+
*/
|
|
49
|
+
export function ensureMemoryTables(db) {
|
|
50
|
+
db.exec(`
|
|
51
|
+
CREATE TABLE IF NOT EXISTS memory_long_term (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
content TEXT NOT NULL,
|
|
54
|
+
type TEXT DEFAULT 'episodic',
|
|
55
|
+
importance REAL DEFAULT 0.5,
|
|
56
|
+
access_count INTEGER DEFAULT 0,
|
|
57
|
+
retention REAL DEFAULT 1.0,
|
|
58
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
59
|
+
last_accessed TEXT DEFAULT (datetime('now')),
|
|
60
|
+
layer TEXT DEFAULT 'long-term'
|
|
61
|
+
)
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
db.exec(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS memory_core (
|
|
66
|
+
id TEXT PRIMARY KEY,
|
|
67
|
+
content TEXT NOT NULL,
|
|
68
|
+
type TEXT DEFAULT 'semantic',
|
|
69
|
+
importance REAL DEFAULT 1.0,
|
|
70
|
+
access_count INTEGER DEFAULT 0,
|
|
71
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
72
|
+
last_accessed TEXT DEFAULT (datetime('now'))
|
|
73
|
+
)
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
db.exec(`
|
|
77
|
+
CREATE TABLE IF NOT EXISTS memory_sharing (
|
|
78
|
+
id TEXT PRIMARY KEY,
|
|
79
|
+
memory_id TEXT NOT NULL,
|
|
80
|
+
source_agent TEXT DEFAULT 'local',
|
|
81
|
+
target_agent TEXT NOT NULL,
|
|
82
|
+
privacy_level TEXT DEFAULT 'filtered',
|
|
83
|
+
shared_at TEXT DEFAULT (datetime('now'))
|
|
84
|
+
)
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Store ───────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Store a memory at the appropriate layer based on importance.
|
|
92
|
+
* core >= 0.9, long-term >= 0.6, short-term >= 0.3, working < 0.3
|
|
93
|
+
*/
|
|
94
|
+
export function storeMemory(db, content, options = {}) {
|
|
95
|
+
if (!content || !content.trim()) {
|
|
96
|
+
throw new Error("Memory content cannot be empty");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const importance = Math.max(
|
|
100
|
+
0,
|
|
101
|
+
Math.min(1, parseFloat(options.importance) || 0.5),
|
|
102
|
+
);
|
|
103
|
+
const type = options.type || "episodic";
|
|
104
|
+
const id = generateId();
|
|
105
|
+
const now = nowISO();
|
|
106
|
+
|
|
107
|
+
let layer;
|
|
108
|
+
if (importance >= 0.9) {
|
|
109
|
+
layer = "core";
|
|
110
|
+
ensureMemoryTables(db);
|
|
111
|
+
db.prepare(
|
|
112
|
+
`INSERT INTO memory_core (id, content, type, importance, created_at, last_accessed) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
113
|
+
).run(id, content, type, importance, now, now);
|
|
114
|
+
} else if (importance >= 0.6) {
|
|
115
|
+
layer = "long-term";
|
|
116
|
+
ensureMemoryTables(db);
|
|
117
|
+
db.prepare(
|
|
118
|
+
`INSERT INTO memory_long_term (id, content, type, importance, layer, created_at, last_accessed) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
119
|
+
).run(id, content, type, importance, "long-term", now, now);
|
|
120
|
+
} else if (importance >= 0.3) {
|
|
121
|
+
layer = "short-term";
|
|
122
|
+
if (_shortTerm.size >= MEMORY_CONFIG.shortTermCapacity) {
|
|
123
|
+
// Evict oldest
|
|
124
|
+
const oldest = [..._shortTerm.entries()].sort(
|
|
125
|
+
(a, b) => new Date(a[1].lastAccessed) - new Date(b[1].lastAccessed),
|
|
126
|
+
)[0];
|
|
127
|
+
if (oldest) _shortTerm.delete(oldest[0]);
|
|
128
|
+
}
|
|
129
|
+
_shortTerm.set(id, {
|
|
130
|
+
id,
|
|
131
|
+
content,
|
|
132
|
+
type,
|
|
133
|
+
importance,
|
|
134
|
+
accessCount: 0,
|
|
135
|
+
createdAt: now,
|
|
136
|
+
lastAccessed: now,
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
layer = "working";
|
|
140
|
+
if (_working.size >= MEMORY_CONFIG.workingCapacity) {
|
|
141
|
+
const oldest = [..._working.entries()].sort(
|
|
142
|
+
(a, b) => new Date(a[1].lastAccessed) - new Date(b[1].lastAccessed),
|
|
143
|
+
)[0];
|
|
144
|
+
if (oldest) _working.delete(oldest[0]);
|
|
145
|
+
}
|
|
146
|
+
_working.set(id, {
|
|
147
|
+
id,
|
|
148
|
+
content,
|
|
149
|
+
type,
|
|
150
|
+
importance,
|
|
151
|
+
accessCount: 0,
|
|
152
|
+
createdAt: now,
|
|
153
|
+
lastAccessed: now,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { id, layer };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── Recall ──────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Search all memory layers with Ebbinghaus forgetting curve.
|
|
164
|
+
* Strengthens recalled memories (spacing effect).
|
|
165
|
+
*/
|
|
166
|
+
export function recallMemory(db, query, options = {}) {
|
|
167
|
+
if (!query || !query.trim()) return [];
|
|
168
|
+
|
|
169
|
+
const limit = Math.max(1, parseInt(options.limit) || 20);
|
|
170
|
+
const pattern = query.toLowerCase();
|
|
171
|
+
const results = [];
|
|
172
|
+
|
|
173
|
+
// Search working memory
|
|
174
|
+
for (const mem of _working.values()) {
|
|
175
|
+
if (mem.content.toLowerCase().includes(pattern)) {
|
|
176
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
177
|
+
if (retention >= MEMORY_CONFIG.recallThreshold) {
|
|
178
|
+
mem.accessCount++;
|
|
179
|
+
mem.lastAccessed = nowISO();
|
|
180
|
+
results.push({ ...mem, layer: "working", retention });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Search short-term memory
|
|
186
|
+
for (const mem of _shortTerm.values()) {
|
|
187
|
+
if (mem.content.toLowerCase().includes(pattern)) {
|
|
188
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
189
|
+
if (retention >= MEMORY_CONFIG.recallThreshold) {
|
|
190
|
+
mem.accessCount++;
|
|
191
|
+
mem.lastAccessed = nowISO();
|
|
192
|
+
results.push({ ...mem, layer: "short-term", retention });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Search long-term memory (DB)
|
|
198
|
+
ensureMemoryTables(db);
|
|
199
|
+
const ltRows = db
|
|
200
|
+
.prepare(`SELECT * FROM memory_long_term WHERE content LIKE ? LIMIT ?`)
|
|
201
|
+
.all(`%${query}%`, limit);
|
|
202
|
+
for (const row of ltRows) {
|
|
203
|
+
const retention = calcRetention(row.last_accessed);
|
|
204
|
+
if (retention >= MEMORY_CONFIG.recallThreshold) {
|
|
205
|
+
db.prepare(
|
|
206
|
+
`UPDATE memory_long_term SET access_count = access_count + 1, last_accessed = ? WHERE id = ?`,
|
|
207
|
+
).run(nowISO(), row.id);
|
|
208
|
+
results.push({
|
|
209
|
+
id: row.id,
|
|
210
|
+
content: row.content,
|
|
211
|
+
type: row.type,
|
|
212
|
+
importance: row.importance,
|
|
213
|
+
accessCount: row.access_count + 1,
|
|
214
|
+
createdAt: row.created_at,
|
|
215
|
+
lastAccessed: nowISO(),
|
|
216
|
+
layer: "long-term",
|
|
217
|
+
retention,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Search core memory (DB) — core memories don't decay
|
|
223
|
+
const coreRows = db
|
|
224
|
+
.prepare(`SELECT * FROM memory_core WHERE content LIKE ? LIMIT ?`)
|
|
225
|
+
.all(`%${query}%`, limit);
|
|
226
|
+
for (const row of coreRows) {
|
|
227
|
+
db.prepare(
|
|
228
|
+
`UPDATE memory_core SET access_count = access_count + 1, last_accessed = ? WHERE id = ?`,
|
|
229
|
+
).run(nowISO(), row.id);
|
|
230
|
+
results.push({
|
|
231
|
+
id: row.id,
|
|
232
|
+
content: row.content,
|
|
233
|
+
type: row.type,
|
|
234
|
+
importance: row.importance,
|
|
235
|
+
accessCount: row.access_count + 1,
|
|
236
|
+
createdAt: row.created_at,
|
|
237
|
+
lastAccessed: nowISO(),
|
|
238
|
+
layer: "core",
|
|
239
|
+
retention: 1.0,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Sort by retention * importance descending
|
|
244
|
+
results.sort(
|
|
245
|
+
(a, b) => b.retention * b.importance - a.retention * a.importance,
|
|
246
|
+
);
|
|
247
|
+
return results.slice(0, limit);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Consolidate ─────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Promote memories up the hierarchy and forget stale entries.
|
|
254
|
+
* working → short-term (accessCount >= 3)
|
|
255
|
+
* short-term → long-term (accessCount >= 5)
|
|
256
|
+
* Forget entries below recall threshold.
|
|
257
|
+
*/
|
|
258
|
+
export function consolidateMemory(db) {
|
|
259
|
+
ensureMemoryTables(db);
|
|
260
|
+
let promoted = 0;
|
|
261
|
+
let forgotten = 0;
|
|
262
|
+
|
|
263
|
+
// Promote working → short-term
|
|
264
|
+
for (const [id, mem] of _working) {
|
|
265
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
266
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
267
|
+
_working.delete(id);
|
|
268
|
+
forgotten++;
|
|
269
|
+
} else if (mem.accessCount >= 3) {
|
|
270
|
+
_working.delete(id);
|
|
271
|
+
_shortTerm.set(id, { ...mem, lastAccessed: nowISO() });
|
|
272
|
+
promoted++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Promote short-term → long-term
|
|
277
|
+
for (const [id, mem] of _shortTerm) {
|
|
278
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
279
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
280
|
+
_shortTerm.delete(id);
|
|
281
|
+
forgotten++;
|
|
282
|
+
} else if (mem.accessCount >= 5) {
|
|
283
|
+
_shortTerm.delete(id);
|
|
284
|
+
const now = nowISO();
|
|
285
|
+
db.prepare(
|
|
286
|
+
`INSERT INTO memory_long_term (id, content, type, importance, access_count, layer, created_at, last_accessed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
287
|
+
).run(
|
|
288
|
+
id,
|
|
289
|
+
mem.content,
|
|
290
|
+
mem.type,
|
|
291
|
+
mem.importance,
|
|
292
|
+
mem.accessCount,
|
|
293
|
+
"long-term",
|
|
294
|
+
mem.createdAt,
|
|
295
|
+
now,
|
|
296
|
+
);
|
|
297
|
+
promoted++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { promoted, forgotten };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ─── Episodic / Semantic search ──────────────────────────────────
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Search episodic-type memories across all layers
|
|
308
|
+
*/
|
|
309
|
+
export function searchEpisodic(db, query, options = {}) {
|
|
310
|
+
return _searchByType(db, query, "episodic", options);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Search semantic-type memories across all layers
|
|
315
|
+
*/
|
|
316
|
+
export function searchSemantic(db, query, options = {}) {
|
|
317
|
+
return _searchByType(db, query, "semantic", options);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function _searchByType(db, query, type, options = {}) {
|
|
321
|
+
if (!query || !query.trim()) return [];
|
|
322
|
+
|
|
323
|
+
const limit = Math.max(1, parseInt(options.limit) || 20);
|
|
324
|
+
const pattern = query.toLowerCase();
|
|
325
|
+
const results = [];
|
|
326
|
+
|
|
327
|
+
// In-memory layers
|
|
328
|
+
for (const mem of _working.values()) {
|
|
329
|
+
if (mem.type === type && mem.content.toLowerCase().includes(pattern)) {
|
|
330
|
+
results.push({ ...mem, layer: "working" });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const mem of _shortTerm.values()) {
|
|
334
|
+
if (mem.type === type && mem.content.toLowerCase().includes(pattern)) {
|
|
335
|
+
results.push({ ...mem, layer: "short-term" });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// DB layers
|
|
340
|
+
ensureMemoryTables(db);
|
|
341
|
+
const ltRows = db
|
|
342
|
+
.prepare(
|
|
343
|
+
`SELECT * FROM memory_long_term WHERE type = ? AND content LIKE ? LIMIT ?`,
|
|
344
|
+
)
|
|
345
|
+
.all(type, `%${query}%`, limit);
|
|
346
|
+
for (const row of ltRows) {
|
|
347
|
+
results.push({
|
|
348
|
+
id: row.id,
|
|
349
|
+
content: row.content,
|
|
350
|
+
type: row.type,
|
|
351
|
+
importance: row.importance,
|
|
352
|
+
accessCount: row.access_count,
|
|
353
|
+
createdAt: row.created_at,
|
|
354
|
+
lastAccessed: row.last_accessed,
|
|
355
|
+
layer: "long-term",
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const coreRows = db
|
|
360
|
+
.prepare(
|
|
361
|
+
`SELECT * FROM memory_core WHERE type = ? AND content LIKE ? LIMIT ?`,
|
|
362
|
+
)
|
|
363
|
+
.all(type, `%${query}%`, limit);
|
|
364
|
+
for (const row of coreRows) {
|
|
365
|
+
results.push({
|
|
366
|
+
id: row.id,
|
|
367
|
+
content: row.content,
|
|
368
|
+
type: row.type,
|
|
369
|
+
importance: row.importance,
|
|
370
|
+
accessCount: row.access_count,
|
|
371
|
+
createdAt: row.created_at,
|
|
372
|
+
lastAccessed: row.last_accessed,
|
|
373
|
+
layer: "core",
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return results.slice(0, limit);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ─── Sharing ─────────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Share a memory with another agent
|
|
384
|
+
*/
|
|
385
|
+
export function shareMemory(
|
|
386
|
+
db,
|
|
387
|
+
memoryId,
|
|
388
|
+
targetAgentId,
|
|
389
|
+
privacyLevel = "filtered",
|
|
390
|
+
) {
|
|
391
|
+
ensureMemoryTables(db);
|
|
392
|
+
|
|
393
|
+
if (!memoryId || !targetAgentId) {
|
|
394
|
+
throw new Error("memoryId and targetAgentId are required");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const id = `share-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
398
|
+
db.prepare(
|
|
399
|
+
`INSERT INTO memory_sharing (id, memory_id, target_agent, privacy_level) VALUES (?, ?, ?, ?)`,
|
|
400
|
+
).run(id, memoryId, targetAgentId, privacyLevel);
|
|
401
|
+
|
|
402
|
+
return { id, memoryId, targetAgentId, privacyLevel };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ─── Prune ───────────────────────────────────────────────────────
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Remove weak old memories from all layers
|
|
409
|
+
*/
|
|
410
|
+
export function pruneMemory(db, options = {}) {
|
|
411
|
+
ensureMemoryTables(db);
|
|
412
|
+
const maxAgeHours = parseFloat(options.maxAge) || 720; // 30 days default
|
|
413
|
+
let pruned = 0;
|
|
414
|
+
|
|
415
|
+
// Prune in-memory layers by retention
|
|
416
|
+
for (const [id, mem] of _working) {
|
|
417
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
418
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
419
|
+
_working.delete(id);
|
|
420
|
+
pruned++;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const [id, mem] of _shortTerm) {
|
|
424
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
425
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
426
|
+
_shortTerm.delete(id);
|
|
427
|
+
pruned++;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Prune long-term DB entries older than maxAge with low access
|
|
432
|
+
const cutoff = new Date(
|
|
433
|
+
Date.now() - maxAgeHours * 60 * 60 * 1000,
|
|
434
|
+
).toISOString();
|
|
435
|
+
const result = db
|
|
436
|
+
.prepare(
|
|
437
|
+
`DELETE FROM memory_long_term WHERE last_accessed < ? AND access_count < 3 AND importance < 0.5`,
|
|
438
|
+
)
|
|
439
|
+
.run(cutoff);
|
|
440
|
+
pruned += result.changes;
|
|
441
|
+
|
|
442
|
+
return { pruned };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ─── Stats ───────────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Return memory counts per layer
|
|
449
|
+
*/
|
|
450
|
+
export function getMemoryStats(db) {
|
|
451
|
+
ensureMemoryTables(db);
|
|
452
|
+
|
|
453
|
+
const ltCount = db
|
|
454
|
+
.prepare(`SELECT COUNT(*) as count FROM memory_long_term`)
|
|
455
|
+
.get();
|
|
456
|
+
const coreCount = db
|
|
457
|
+
.prepare(`SELECT COUNT(*) as count FROM memory_core`)
|
|
458
|
+
.get();
|
|
459
|
+
const shareCount = db
|
|
460
|
+
.prepare(`SELECT COUNT(*) as count FROM memory_sharing`)
|
|
461
|
+
.get();
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
working: _working.size,
|
|
465
|
+
shortTerm: _shortTerm.size,
|
|
466
|
+
longTerm: ltCount.count,
|
|
467
|
+
core: coreCount.count,
|
|
468
|
+
shared: shareCount.count,
|
|
469
|
+
total: _working.size + _shortTerm.size + ltCount.count + coreCount.count,
|
|
470
|
+
};
|
|
471
|
+
}
|