chainlesschain 0.42.2 → 0.43.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.
- package/README.md +14 -0
- package/package.json +5 -2
- package/src/commands/agent.js +7 -6
- package/src/commands/ask.js +11 -9
- package/src/commands/chat.js +8 -7
- package/src/commands/init.js +1 -1
- package/src/commands/update.js +33 -4
- package/src/lib/agent-coordinator.js +111 -0
- package/src/lib/agent-core.js +167 -2
- package/src/lib/cli-context-engineering.js +48 -15
- package/src/lib/cowork/debate-review-cli.js +12 -2
- package/src/lib/hierarchical-memory.js +186 -68
- package/src/lib/sub-agent-context.js +296 -0
- package/src/lib/sub-agent-registry.js +186 -0
- package/src/lib/ws-session-manager.js +8 -0
- package/src/repl/agent-repl.js +45 -0
|
@@ -40,10 +40,15 @@ export class CLIContextEngineering {
|
|
|
40
40
|
* @param {object} options
|
|
41
41
|
* @param {object|null} options.db - Database instance (null for graceful degradation)
|
|
42
42
|
* @param {object|null} options.permanentMemory - CLIPermanentMemory instance (optional)
|
|
43
|
+
* @param {object|null} options.scope - Scoping options for sub-agent isolation
|
|
44
|
+
* @param {string} [options.scope.taskId] - Task/sub-agent ID
|
|
45
|
+
* @param {string} [options.scope.role] - Sub-agent role
|
|
46
|
+
* @param {string} [options.scope.parentObjective] - Parent task objective
|
|
43
47
|
*/
|
|
44
|
-
constructor({ db, permanentMemory } = {}) {
|
|
48
|
+
constructor({ db, permanentMemory, scope } = {}) {
|
|
45
49
|
this.db = db || null;
|
|
46
50
|
this.permanentMemory = permanentMemory || null;
|
|
51
|
+
this.scope = scope || null;
|
|
47
52
|
this.errorHistory = [];
|
|
48
53
|
this.taskContext = null;
|
|
49
54
|
this._bm25 = null;
|
|
@@ -52,6 +57,15 @@ export class CLIContextEngineering {
|
|
|
52
57
|
this._compactionSummaries = [];
|
|
53
58
|
// Stable prefix cache: { hash, cleanedPrefix }
|
|
54
59
|
this._prefixCache = null;
|
|
60
|
+
|
|
61
|
+
// When scoped, auto-set task context from scope
|
|
62
|
+
if (this.scope && this.scope.parentObjective) {
|
|
63
|
+
this.taskContext = {
|
|
64
|
+
objective: this.scope.parentObjective,
|
|
65
|
+
steps: [],
|
|
66
|
+
currentStep: 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
/**
|
|
@@ -91,31 +105,46 @@ export class CLIContextEngineering {
|
|
|
91
105
|
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
// 3. Memory injection
|
|
108
|
+
// 3. Memory injection (scoped: higher threshold, namespace-aware)
|
|
95
109
|
if (this.db && userQuery) {
|
|
96
110
|
try {
|
|
97
|
-
const
|
|
111
|
+
const memoryQuery = this.scope
|
|
112
|
+
? `[${this.scope.role}] ${userQuery}`
|
|
113
|
+
: userQuery;
|
|
114
|
+
const memoryOpts = { limit: 5 };
|
|
115
|
+
if (this.scope) {
|
|
116
|
+
memoryOpts.namespace = this.scope.taskId;
|
|
117
|
+
}
|
|
118
|
+
const memories = _deps.recallMemory(this.db, memoryQuery, memoryOpts);
|
|
98
119
|
if (memories && memories.length > 0) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
// When scoped, apply higher relevance threshold to reduce noise
|
|
121
|
+
const threshold = this.scope ? 0.6 : 0.3;
|
|
122
|
+
const filtered = memories.filter((m) => m.retention >= threshold);
|
|
123
|
+
if (filtered.length > 0) {
|
|
124
|
+
const lines = filtered.map(
|
|
125
|
+
(m) =>
|
|
126
|
+
`- [${m.layer}] ${m.content} (retention: ${(m.retention * 100).toFixed(0)}%)`,
|
|
127
|
+
);
|
|
128
|
+
result.push({
|
|
129
|
+
role: "system",
|
|
130
|
+
content: `## Relevant Memories\n${lines.join("\n")}`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
107
133
|
}
|
|
108
134
|
} catch (_err) {
|
|
109
135
|
// Memory injection failed — skip silently
|
|
110
136
|
}
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
// 4. Notes injection (BM25 search)
|
|
139
|
+
// 4. Notes injection (BM25 search — scoped: role-prefixed query)
|
|
114
140
|
if (this.db && userQuery) {
|
|
115
141
|
try {
|
|
116
142
|
this._ensureNotesIndex();
|
|
117
143
|
if (this._bm25 && this._bm25.totalDocs > 0) {
|
|
118
|
-
const
|
|
144
|
+
const notesQuery = this.scope
|
|
145
|
+
? `[${this.scope.role}] ${userQuery}`
|
|
146
|
+
: userQuery;
|
|
147
|
+
const hits = this._bm25.search(notesQuery, {
|
|
119
148
|
topK: 3,
|
|
120
149
|
threshold: 0.5,
|
|
121
150
|
});
|
|
@@ -135,10 +164,14 @@ export class CLIContextEngineering {
|
|
|
135
164
|
}
|
|
136
165
|
}
|
|
137
166
|
|
|
138
|
-
// 5. Permanent memory injection
|
|
167
|
+
// 5. Permanent memory injection (scoped: reduced results)
|
|
139
168
|
if (this.permanentMemory && userQuery) {
|
|
140
169
|
try {
|
|
141
|
-
const
|
|
170
|
+
const pmLimit = this.scope ? 2 : 3;
|
|
171
|
+
const pmResults = this.permanentMemory.getRelevantContext(
|
|
172
|
+
userQuery,
|
|
173
|
+
pmLimit,
|
|
174
|
+
);
|
|
142
175
|
if (pmResults && pmResults.length > 0) {
|
|
143
176
|
const lines = pmResults.map(
|
|
144
177
|
(r) => `- [${r.source || "memory"}] ${r.content}`,
|
|
@@ -91,6 +91,16 @@ export async function startDebate({
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// Phase 2: Moderator synthesizes final verdict
|
|
94
|
+
// Summarize each reviewer's output to reduce context pollution for the moderator
|
|
95
|
+
const REVIEW_SUMMARY_MAX = 300;
|
|
96
|
+
const reviewSummaries = reviews.map((r) => {
|
|
97
|
+
const summarized =
|
|
98
|
+
r.review.length <= REVIEW_SUMMARY_MAX
|
|
99
|
+
? r.review
|
|
100
|
+
: r.review.substring(0, REVIEW_SUMMARY_MAX) + "... [truncated]";
|
|
101
|
+
return { ...r, reviewSummary: summarized };
|
|
102
|
+
});
|
|
103
|
+
|
|
94
104
|
const moderatorMessages = [
|
|
95
105
|
{
|
|
96
106
|
role: "system",
|
|
@@ -99,8 +109,8 @@ export async function startDebate({
|
|
|
99
109
|
},
|
|
100
110
|
{
|
|
101
111
|
role: "user",
|
|
102
|
-
content: `Multiple reviewers analyzed this code. Synthesize their findings into a final verdict.\n\nTarget: ${target}\n\n${
|
|
103
|
-
.map((r) => `### ${r.role} (${r.verdict})\n${r.
|
|
112
|
+
content: `Multiple reviewers analyzed this code. Synthesize their findings into a final verdict.\n\nTarget: ${target}\n\n${reviewSummaries
|
|
113
|
+
.map((r) => `### ${r.role} (${r.verdict})\n${r.reviewSummary}`)
|
|
104
114
|
.join(
|
|
105
115
|
"\n\n---\n\n",
|
|
106
116
|
)}\n\nProvide:\n1. Final Verdict: APPROVE / NEEDS_WORK / REJECT\n2. Consensus Score: 0-100 (how much the reviewers agree)\n3. Summary of key findings across all perspectives\n4. Priority action items (if any)`,
|
|
@@ -15,9 +15,83 @@ export const MEMORY_CONFIG = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// ─── In-memory layers ────────────────────────────────────────────
|
|
18
|
-
// Map<
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// Internal storage: Map<namespace, Map<id, entry>>
|
|
19
|
+
// Default namespace "global" preserves backward compatibility.
|
|
20
|
+
const _workingNS = new Map();
|
|
21
|
+
const _shortTermNS = new Map();
|
|
22
|
+
const DEFAULT_NS = "global";
|
|
23
|
+
|
|
24
|
+
function _getWorkingNS(namespace) {
|
|
25
|
+
const ns = namespace || DEFAULT_NS;
|
|
26
|
+
if (!_workingNS.has(ns)) _workingNS.set(ns, new Map());
|
|
27
|
+
return _workingNS.get(ns);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function _getShortTermNS(namespace) {
|
|
31
|
+
const ns = namespace || DEFAULT_NS;
|
|
32
|
+
if (!_shortTermNS.has(ns)) _shortTermNS.set(ns, new Map());
|
|
33
|
+
return _shortTermNS.get(ns);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Backward-compatible proxy ──────────────────────────────────
|
|
37
|
+
// Existing code (and tests) access _working/_shortTerm as flat Maps:
|
|
38
|
+
// _working.size, _working.get(id), _working.clear(), _working.delete(id)
|
|
39
|
+
// We proxy these to the default namespace while exposing namespaced internals.
|
|
40
|
+
function _createCompatProxy(nsMap, getNS) {
|
|
41
|
+
return {
|
|
42
|
+
// Flat access — routes to default namespace
|
|
43
|
+
get size() {
|
|
44
|
+
const ns = getNS(DEFAULT_NS);
|
|
45
|
+
return ns.size;
|
|
46
|
+
},
|
|
47
|
+
get(key) {
|
|
48
|
+
const ns = getNS(DEFAULT_NS);
|
|
49
|
+
return ns.get(key);
|
|
50
|
+
},
|
|
51
|
+
set(key, value) {
|
|
52
|
+
const ns = getNS(DEFAULT_NS);
|
|
53
|
+
return ns.set(key, value);
|
|
54
|
+
},
|
|
55
|
+
has(key) {
|
|
56
|
+
const ns = getNS(DEFAULT_NS);
|
|
57
|
+
return ns.has(key);
|
|
58
|
+
},
|
|
59
|
+
delete(key) {
|
|
60
|
+
const ns = getNS(DEFAULT_NS);
|
|
61
|
+
return ns.delete(key);
|
|
62
|
+
},
|
|
63
|
+
values() {
|
|
64
|
+
const ns = getNS(DEFAULT_NS);
|
|
65
|
+
return ns.values();
|
|
66
|
+
},
|
|
67
|
+
entries() {
|
|
68
|
+
const ns = getNS(DEFAULT_NS);
|
|
69
|
+
return ns.entries();
|
|
70
|
+
},
|
|
71
|
+
keys() {
|
|
72
|
+
const ns = getNS(DEFAULT_NS);
|
|
73
|
+
return ns.keys();
|
|
74
|
+
},
|
|
75
|
+
forEach(callback) {
|
|
76
|
+
const ns = getNS(DEFAULT_NS);
|
|
77
|
+
return ns.forEach(callback);
|
|
78
|
+
},
|
|
79
|
+
[Symbol.iterator]() {
|
|
80
|
+
const ns = getNS(DEFAULT_NS);
|
|
81
|
+
return ns[Symbol.iterator]();
|
|
82
|
+
},
|
|
83
|
+
// Clear ALL namespaces (for test cleanup)
|
|
84
|
+
clear() {
|
|
85
|
+
nsMap.clear();
|
|
86
|
+
},
|
|
87
|
+
// ─── Namespace-aware internals (used by this module) ──────
|
|
88
|
+
_nsMap: nsMap,
|
|
89
|
+
_getNS: getNS,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const _working = _createCompatProxy(_workingNS, _getWorkingNS);
|
|
94
|
+
export const _shortTerm = _createCompatProxy(_shortTermNS, _getShortTermNS);
|
|
21
95
|
|
|
22
96
|
// ─── Helpers ─────────────────────────────────────────────────────
|
|
23
97
|
function generateId() {
|
|
@@ -91,6 +165,17 @@ export function ensureMemoryTables(db) {
|
|
|
91
165
|
* Store a memory at the appropriate layer based on importance.
|
|
92
166
|
* core >= 0.9, long-term >= 0.6, short-term >= 0.3, working < 0.3
|
|
93
167
|
*/
|
|
168
|
+
/**
|
|
169
|
+
* Store a memory at the appropriate layer based on importance.
|
|
170
|
+
* core >= 0.9, long-term >= 0.6, short-term >= 0.3, working < 0.3
|
|
171
|
+
*
|
|
172
|
+
* @param {object} db - Database instance
|
|
173
|
+
* @param {string} content - Memory content
|
|
174
|
+
* @param {object} [options]
|
|
175
|
+
* @param {number} [options.importance=0.5]
|
|
176
|
+
* @param {string} [options.type="episodic"]
|
|
177
|
+
* @param {string} [options.namespace] - Namespace for in-memory isolation (default: "global")
|
|
178
|
+
*/
|
|
94
179
|
export function storeMemory(db, content, options = {}) {
|
|
95
180
|
if (!content || !content.trim()) {
|
|
96
181
|
throw new Error("Memory content cannot be empty");
|
|
@@ -101,6 +186,7 @@ export function storeMemory(db, content, options = {}) {
|
|
|
101
186
|
Math.min(1, parseFloat(options.importance) || 0.5),
|
|
102
187
|
);
|
|
103
188
|
const type = options.type || "episodic";
|
|
189
|
+
const namespace = options.namespace || DEFAULT_NS;
|
|
104
190
|
const id = generateId();
|
|
105
191
|
const now = nowISO();
|
|
106
192
|
|
|
@@ -119,14 +205,15 @@ export function storeMemory(db, content, options = {}) {
|
|
|
119
205
|
).run(id, content, type, importance, "long-term", now, now);
|
|
120
206
|
} else if (importance >= 0.3) {
|
|
121
207
|
layer = "short-term";
|
|
122
|
-
|
|
208
|
+
const nsMap = _getShortTermNS(namespace);
|
|
209
|
+
if (nsMap.size >= MEMORY_CONFIG.shortTermCapacity) {
|
|
123
210
|
// Evict oldest
|
|
124
|
-
const oldest = [...
|
|
211
|
+
const oldest = [...nsMap.entries()].sort(
|
|
125
212
|
(a, b) => new Date(a[1].lastAccessed) - new Date(b[1].lastAccessed),
|
|
126
213
|
)[0];
|
|
127
|
-
if (oldest)
|
|
214
|
+
if (oldest) nsMap.delete(oldest[0]);
|
|
128
215
|
}
|
|
129
|
-
|
|
216
|
+
nsMap.set(id, {
|
|
130
217
|
id,
|
|
131
218
|
content,
|
|
132
219
|
type,
|
|
@@ -137,13 +224,14 @@ export function storeMemory(db, content, options = {}) {
|
|
|
137
224
|
});
|
|
138
225
|
} else {
|
|
139
226
|
layer = "working";
|
|
140
|
-
|
|
141
|
-
|
|
227
|
+
const nsMap = _getWorkingNS(namespace);
|
|
228
|
+
if (nsMap.size >= MEMORY_CONFIG.workingCapacity) {
|
|
229
|
+
const oldest = [...nsMap.entries()].sort(
|
|
142
230
|
(a, b) => new Date(a[1].lastAccessed) - new Date(b[1].lastAccessed),
|
|
143
231
|
)[0];
|
|
144
|
-
if (oldest)
|
|
232
|
+
if (oldest) nsMap.delete(oldest[0]);
|
|
145
233
|
}
|
|
146
|
-
|
|
234
|
+
nsMap.set(id, {
|
|
147
235
|
id,
|
|
148
236
|
content,
|
|
149
237
|
type,
|
|
@@ -162,16 +250,22 @@ export function storeMemory(db, content, options = {}) {
|
|
|
162
250
|
/**
|
|
163
251
|
* Search all memory layers with Ebbinghaus forgetting curve.
|
|
164
252
|
* Strengthens recalled memories (spacing effect).
|
|
253
|
+
*
|
|
254
|
+
* When options.namespace is set, searches that namespace's in-memory maps
|
|
255
|
+
* plus the shared long-term/core DB layers. Without namespace, searches
|
|
256
|
+
* the default "global" namespace (backward compatible).
|
|
165
257
|
*/
|
|
166
258
|
export function recallMemory(db, query, options = {}) {
|
|
167
259
|
if (!query || !query.trim()) return [];
|
|
168
260
|
|
|
169
261
|
const limit = Math.max(1, parseInt(options.limit) || 20);
|
|
170
262
|
const pattern = query.toLowerCase();
|
|
263
|
+
const namespace = options.namespace || DEFAULT_NS;
|
|
171
264
|
const results = [];
|
|
172
265
|
|
|
173
|
-
// Search working memory
|
|
174
|
-
|
|
266
|
+
// Search working memory (namespace-scoped)
|
|
267
|
+
const workingNS = _getWorkingNS(namespace);
|
|
268
|
+
for (const mem of workingNS.values()) {
|
|
175
269
|
if (mem.content.toLowerCase().includes(pattern)) {
|
|
176
270
|
const retention = calcRetention(mem.lastAccessed);
|
|
177
271
|
if (retention >= MEMORY_CONFIG.recallThreshold) {
|
|
@@ -182,8 +276,9 @@ export function recallMemory(db, query, options = {}) {
|
|
|
182
276
|
}
|
|
183
277
|
}
|
|
184
278
|
|
|
185
|
-
// Search short-term memory
|
|
186
|
-
|
|
279
|
+
// Search short-term memory (namespace-scoped)
|
|
280
|
+
const shortTermNS = _getShortTermNS(namespace);
|
|
281
|
+
for (const mem of shortTermNS.values()) {
|
|
187
282
|
if (mem.content.toLowerCase().includes(pattern)) {
|
|
188
283
|
const retention = calcRetention(mem.lastAccessed);
|
|
189
284
|
if (retention >= MEMORY_CONFIG.recallThreshold) {
|
|
@@ -260,41 +355,46 @@ export function consolidateMemory(db) {
|
|
|
260
355
|
let promoted = 0;
|
|
261
356
|
let forgotten = 0;
|
|
262
357
|
|
|
263
|
-
// Promote working → short-term
|
|
264
|
-
for (const [
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
358
|
+
// Promote working → short-term (across all namespaces)
|
|
359
|
+
for (const [ns, nsMap] of _workingNS) {
|
|
360
|
+
const shortTermNS = _getShortTermNS(ns);
|
|
361
|
+
for (const [id, mem] of nsMap) {
|
|
362
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
363
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
364
|
+
nsMap.delete(id);
|
|
365
|
+
forgotten++;
|
|
366
|
+
} else if (mem.accessCount >= 3) {
|
|
367
|
+
nsMap.delete(id);
|
|
368
|
+
shortTermNS.set(id, { ...mem, lastAccessed: nowISO() });
|
|
369
|
+
promoted++;
|
|
370
|
+
}
|
|
273
371
|
}
|
|
274
372
|
}
|
|
275
373
|
|
|
276
|
-
// Promote short-term → long-term
|
|
277
|
-
for (const [
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
374
|
+
// Promote short-term → long-term (across all namespaces)
|
|
375
|
+
for (const [, nsMap] of _shortTermNS) {
|
|
376
|
+
for (const [id, mem] of nsMap) {
|
|
377
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
378
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
379
|
+
nsMap.delete(id);
|
|
380
|
+
forgotten++;
|
|
381
|
+
} else if (mem.accessCount >= 5) {
|
|
382
|
+
nsMap.delete(id);
|
|
383
|
+
const now = nowISO();
|
|
384
|
+
db.prepare(
|
|
385
|
+
`INSERT INTO memory_long_term (id, content, type, importance, access_count, layer, created_at, last_accessed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
386
|
+
).run(
|
|
387
|
+
id,
|
|
388
|
+
mem.content,
|
|
389
|
+
mem.type,
|
|
390
|
+
mem.importance,
|
|
391
|
+
mem.accessCount,
|
|
392
|
+
"long-term",
|
|
393
|
+
mem.createdAt,
|
|
394
|
+
now,
|
|
395
|
+
);
|
|
396
|
+
promoted++;
|
|
397
|
+
}
|
|
298
398
|
}
|
|
299
399
|
}
|
|
300
400
|
|
|
@@ -324,15 +424,19 @@ function _searchByType(db, query, type, options = {}) {
|
|
|
324
424
|
const pattern = query.toLowerCase();
|
|
325
425
|
const results = [];
|
|
326
426
|
|
|
327
|
-
// In-memory layers
|
|
328
|
-
for (const
|
|
329
|
-
|
|
330
|
-
|
|
427
|
+
// In-memory layers (search all namespaces)
|
|
428
|
+
for (const [, nsMap] of _workingNS) {
|
|
429
|
+
for (const mem of nsMap.values()) {
|
|
430
|
+
if (mem.type === type && mem.content.toLowerCase().includes(pattern)) {
|
|
431
|
+
results.push({ ...mem, layer: "working" });
|
|
432
|
+
}
|
|
331
433
|
}
|
|
332
434
|
}
|
|
333
|
-
for (const
|
|
334
|
-
|
|
335
|
-
|
|
435
|
+
for (const [, nsMap] of _shortTermNS) {
|
|
436
|
+
for (const mem of nsMap.values()) {
|
|
437
|
+
if (mem.type === type && mem.content.toLowerCase().includes(pattern)) {
|
|
438
|
+
results.push({ ...mem, layer: "short-term" });
|
|
439
|
+
}
|
|
336
440
|
}
|
|
337
441
|
}
|
|
338
442
|
|
|
@@ -412,19 +516,23 @@ export function pruneMemory(db, options = {}) {
|
|
|
412
516
|
const maxAgeHours = parseFloat(options.maxAge) || 720; // 30 days default
|
|
413
517
|
let pruned = 0;
|
|
414
518
|
|
|
415
|
-
// Prune in-memory layers by retention
|
|
416
|
-
for (const [
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
519
|
+
// Prune in-memory layers by retention (across all namespaces)
|
|
520
|
+
for (const [, nsMap] of _workingNS) {
|
|
521
|
+
for (const [id, mem] of nsMap) {
|
|
522
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
523
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
524
|
+
nsMap.delete(id);
|
|
525
|
+
pruned++;
|
|
526
|
+
}
|
|
421
527
|
}
|
|
422
528
|
}
|
|
423
|
-
for (const [
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
529
|
+
for (const [, nsMap] of _shortTermNS) {
|
|
530
|
+
for (const [id, mem] of nsMap) {
|
|
531
|
+
const retention = calcRetention(mem.lastAccessed);
|
|
532
|
+
if (retention < MEMORY_CONFIG.recallThreshold) {
|
|
533
|
+
nsMap.delete(id);
|
|
534
|
+
pruned++;
|
|
535
|
+
}
|
|
428
536
|
}
|
|
429
537
|
}
|
|
430
538
|
|
|
@@ -460,12 +568,22 @@ export function getMemoryStats(db) {
|
|
|
460
568
|
.prepare(`SELECT COUNT(*) as count FROM memory_sharing`)
|
|
461
569
|
.get();
|
|
462
570
|
|
|
571
|
+
// Sum across all namespaces
|
|
572
|
+
let workingTotal = 0;
|
|
573
|
+
for (const [, nsMap] of _workingNS) workingTotal += nsMap.size;
|
|
574
|
+
let shortTermTotal = 0;
|
|
575
|
+
for (const [, nsMap] of _shortTermNS) shortTermTotal += nsMap.size;
|
|
576
|
+
|
|
463
577
|
return {
|
|
464
|
-
working:
|
|
465
|
-
shortTerm:
|
|
578
|
+
working: workingTotal,
|
|
579
|
+
shortTerm: shortTermTotal,
|
|
466
580
|
longTerm: ltCount.count,
|
|
467
581
|
core: coreCount.count,
|
|
468
582
|
shared: shareCount.count,
|
|
469
|
-
|
|
583
|
+
namespaces: {
|
|
584
|
+
working: [..._workingNS.keys()],
|
|
585
|
+
shortTerm: [..._shortTermNS.keys()],
|
|
586
|
+
},
|
|
587
|
+
total: workingTotal + shortTermTotal + ltCount.count + coreCount.count,
|
|
470
588
|
};
|
|
471
589
|
}
|