@psiclawops/hypermem 0.9.3 → 0.9.5
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/CHANGELOG.md +27 -2
- package/INSTALL.md +75 -68
- package/README.md +18 -36
- package/assets/default-config.json +41 -0
- package/bench/data-access-bench.mjs +1 -1
- package/bin/hypermem-doctor.mjs +76 -2
- package/bin/hypermem-status.mjs +255 -7
- package/dist/adaptive-lifecycle.d.ts +39 -0
- package/dist/adaptive-lifecycle.d.ts.map +1 -1
- package/dist/adaptive-lifecycle.js +87 -9
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +16 -14
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +239 -20
- package/dist/cross-agent.d.ts +1 -1
- package/dist/cross-agent.js +17 -17
- package/dist/hybrid-retrieval.d.ts +8 -0
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +112 -10
- package/dist/index.d.ts +16 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/message-store.d.ts +62 -1
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +355 -2
- package/dist/open-domain.d.ts.map +1 -1
- package/dist/open-domain.js +3 -2
- package/dist/proactive-pass.d.ts +42 -2
- package/dist/proactive-pass.d.ts.map +1 -1
- package/dist/proactive-pass.js +294 -39
- package/dist/seed.d.ts +1 -1
- package/dist/seed.js +1 -1
- package/dist/session-flusher.d.ts +2 -2
- package/dist/session-flusher.js +2 -2
- package/dist/spawn-context.d.ts +1 -1
- package/dist/spawn-context.js +1 -1
- package/dist/topic-store.js +5 -5
- package/dist/topic-synthesizer.d.ts.map +1 -1
- package/dist/topic-synthesizer.js +10 -4
- package/dist/trigger-registry.d.ts +1 -1
- package/dist/trigger-registry.js +4 -4
- package/dist/types.d.ts +101 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +10 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +45 -9
- package/docs/DIAGNOSTICS.md +88 -1
- package/docs/INTEGRATION_VALIDATION.md +40 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/TUNING.md +47 -6
- package/install.sh +5 -60
- package/memory-plugin/dist/index.js +192 -0
- package/memory-plugin/openclaw.plugin.json +199 -2
- package/memory-plugin/package.json +2 -2
- package/package.json +29 -10
- package/plugin/dist/index.d.ts +2 -0
- package/plugin/dist/index.d.ts.map +1 -1
- package/plugin/dist/index.js +178 -11
- package/plugin/dist/index.js.map +1 -1
- package/plugin/openclaw.plugin.json +199 -2
- package/plugin/package.json +2 -2
- package/scripts/install-packed-runtime.mjs +99 -0
- package/scripts/install-runtime.mjs +164 -4
- package/ARCHITECTURE.md +0 -298
- package/docs/KNOWN_LIMITATIONS.md +0 -35
- package/docs/PHASE1-VALIDATION.md +0 -132
- package/docs/RELEASE_0.8.0_VALIDATION.md +0 -70
- package/docs/RELEASE_PROCESS.md +0 -10
- package/docs/ROADMAP.md +0 -266
package/dist/proactive-pass.js
CHANGED
|
@@ -44,42 +44,62 @@ function getMaxMessageIndex(db, conversationId) {
|
|
|
44
44
|
.get(conversationId);
|
|
45
45
|
return typeof row.max_index === 'number' ? row.max_index : -1;
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*
|
|
51
|
-
* Current blockers:
|
|
52
|
-
* - summary_messages.message_id -> messages.id
|
|
53
|
-
* - messages.parent_id -> messages.id (child rows point at parent rows)
|
|
54
|
-
*/
|
|
47
|
+
function quoteIdent(identifier) {
|
|
48
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
49
|
+
}
|
|
55
50
|
function getDeletableMessageIds(db, candidateIds) {
|
|
56
51
|
if (candidateIds.length === 0)
|
|
57
|
-
return { deletableIds: [], blockedIds: [] };
|
|
52
|
+
return { deletableIds: [], blockedIds: [], blockedRefs: [] };
|
|
58
53
|
const placeholders = candidateIds.map(() => '?').join(', ');
|
|
59
|
-
const
|
|
54
|
+
const blockedSet = new Set();
|
|
55
|
+
const blockedRefs = [];
|
|
56
|
+
const tables = db
|
|
60
57
|
.prepare(`
|
|
61
|
-
SELECT
|
|
62
|
-
FROM
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
WHERE sm.message_id IN (${placeholders})
|
|
66
|
-
|
|
67
|
-
UNION
|
|
68
|
-
|
|
69
|
-
SELECT parent.id AS id
|
|
70
|
-
FROM messages child
|
|
71
|
-
JOIN messages parent ON parent.id = child.parent_id
|
|
72
|
-
WHERE child.parent_id IN (${placeholders})
|
|
73
|
-
) blocked
|
|
58
|
+
SELECT name
|
|
59
|
+
FROM sqlite_master
|
|
60
|
+
WHERE type = 'table'
|
|
61
|
+
AND name NOT LIKE 'sqlite_%'
|
|
74
62
|
`)
|
|
75
|
-
.all(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
.all();
|
|
64
|
+
for (const { name: tableName } of tables) {
|
|
65
|
+
const refs = db
|
|
66
|
+
.prepare(`PRAGMA foreign_key_list(${quoteIdent(tableName)})`)
|
|
67
|
+
.all();
|
|
68
|
+
for (const ref of refs) {
|
|
69
|
+
if (ref.table !== 'messages')
|
|
70
|
+
continue;
|
|
71
|
+
if (ref.to !== null && ref.to !== 'id')
|
|
72
|
+
continue;
|
|
73
|
+
if (typeof ref.on_delete === 'string' && ref.on_delete.toUpperCase() === 'CASCADE')
|
|
74
|
+
continue;
|
|
75
|
+
const rows = db
|
|
76
|
+
.prepare(`
|
|
77
|
+
SELECT ${quoteIdent(ref.from)} AS id, COUNT(*) AS count
|
|
78
|
+
FROM ${quoteIdent(tableName)}
|
|
79
|
+
WHERE ${quoteIdent(ref.from)} IN (${placeholders})
|
|
80
|
+
GROUP BY ${quoteIdent(ref.from)}
|
|
81
|
+
`)
|
|
82
|
+
.all(...candidateIds);
|
|
83
|
+
for (const row of rows) {
|
|
84
|
+
if (typeof row.id === 'number') {
|
|
85
|
+
blockedSet.add(row.id);
|
|
86
|
+
blockedRefs.push({
|
|
87
|
+
messageId: row.id,
|
|
88
|
+
table: tableName,
|
|
89
|
+
column: ref.from,
|
|
90
|
+
count: typeof row.count === 'number' ? row.count : 1,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (blockedSet.size === 0)
|
|
97
|
+
return { deletableIds: candidateIds, blockedIds: [], blockedRefs: [] };
|
|
98
|
+
const blockedIds = [...blockedSet];
|
|
80
99
|
return {
|
|
81
100
|
deletableIds: candidateIds.filter(id => !blockedSet.has(id)),
|
|
82
101
|
blockedIds,
|
|
102
|
+
blockedRefs,
|
|
83
103
|
};
|
|
84
104
|
}
|
|
85
105
|
/**
|
|
@@ -100,6 +120,227 @@ function isNoiseMessage(textContent, isHeartbeat) {
|
|
|
100
120
|
const { type } = classifyContentType(textContent);
|
|
101
121
|
return type === 'noise' || type === 'ack';
|
|
102
122
|
}
|
|
123
|
+
function formatPassContext(conversationId, context) {
|
|
124
|
+
const agent = context?.agentId ?? 'unknown';
|
|
125
|
+
const dbPath = context?.dbPath ? ` db=${context.dbPath}` : '';
|
|
126
|
+
return `agent=${agent} conversation=${conversationId}${dbPath}`;
|
|
127
|
+
}
|
|
128
|
+
function summarizeBlockedRefs(blockedRefs, limit = 8) {
|
|
129
|
+
if (blockedRefs.length === 0)
|
|
130
|
+
return 'none';
|
|
131
|
+
return blockedRefs
|
|
132
|
+
.slice(0, limit)
|
|
133
|
+
.map(ref => `${ref.table}.${ref.column}->${ref.messageId}x${ref.count}`)
|
|
134
|
+
.join(', ') + (blockedRefs.length > limit ? `, +${blockedRefs.length - limit} more` : '');
|
|
135
|
+
}
|
|
136
|
+
function summarizeForeignKeyCheck(db, limit = 8) {
|
|
137
|
+
try {
|
|
138
|
+
const rows = db.prepare('PRAGMA foreign_key_check').all();
|
|
139
|
+
if (rows.length === 0)
|
|
140
|
+
return 'none';
|
|
141
|
+
return rows
|
|
142
|
+
.slice(0, limit)
|
|
143
|
+
.map(row => `${row.table ?? '?'}#${row.rowid ?? '?'} parent=${row.parent ?? '?'} fkid=${row.fkid ?? '?'}`)
|
|
144
|
+
.join(', ') + (rows.length > limit ? `, +${rows.length - limit} more` : '');
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return `unavailable:${err instanceof Error ? err.message : String(err)}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function getConversationIds(db, conversationId) {
|
|
151
|
+
if (typeof conversationId === 'number')
|
|
152
|
+
return [conversationId];
|
|
153
|
+
const rows = db.prepare('SELECT id FROM conversations ORDER BY id').all();
|
|
154
|
+
return rows.map(row => row.id).filter(id => typeof id === 'number');
|
|
155
|
+
}
|
|
156
|
+
function getNoiseCandidateIds(db, conversationId, recentWindowSize, maxCandidates = Infinity) {
|
|
157
|
+
const safeWindow = resolveSafeWindow(recentWindowSize);
|
|
158
|
+
const maxIndex = getMaxMessageIndex(db, conversationId);
|
|
159
|
+
if (maxIndex < 0)
|
|
160
|
+
return [];
|
|
161
|
+
const cutoff = maxIndex - safeWindow;
|
|
162
|
+
if (cutoff <= 0)
|
|
163
|
+
return [];
|
|
164
|
+
const rows = db
|
|
165
|
+
.prepare(`
|
|
166
|
+
SELECT id, text_content, is_heartbeat
|
|
167
|
+
FROM messages
|
|
168
|
+
WHERE conversation_id = ?
|
|
169
|
+
AND message_index < ?
|
|
170
|
+
AND (tool_results IS NULL OR tool_results = '')
|
|
171
|
+
ORDER BY message_index ASC
|
|
172
|
+
`)
|
|
173
|
+
.all(conversationId, cutoff);
|
|
174
|
+
return rows
|
|
175
|
+
.filter(row => isNoiseMessage(row.text_content, row.is_heartbeat))
|
|
176
|
+
.slice(0, Number.isFinite(maxCandidates) ? maxCandidates : undefined)
|
|
177
|
+
.map(row => row.id);
|
|
178
|
+
}
|
|
179
|
+
function emptyReferencedNoiseDebt() {
|
|
180
|
+
return {
|
|
181
|
+
passType: 'referenced_noise_debt',
|
|
182
|
+
conversationsScanned: 0,
|
|
183
|
+
noiseCandidates: 0,
|
|
184
|
+
referencedNoise: 0,
|
|
185
|
+
parentReferencedNoise: 0,
|
|
186
|
+
contextReferencedNoise: 0,
|
|
187
|
+
snapshotReferencedNoise: 0,
|
|
188
|
+
otherReferencedNoise: 0,
|
|
189
|
+
sampleRefs: [],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Measure noise rows that maintenance cannot delete because they are still FK
|
|
194
|
+
* targets. This is health debt, not corruption: the message tree is preserving
|
|
195
|
+
* referential integrity, but low-signal nodes need tree-safe compaction.
|
|
196
|
+
*/
|
|
197
|
+
export function collectReferencedNoiseDebt(db, conversationId, recentWindowSize = 20, maxCandidatesPerConversation = Infinity) {
|
|
198
|
+
const result = emptyReferencedNoiseDebt();
|
|
199
|
+
for (const convId of getConversationIds(db, conversationId)) {
|
|
200
|
+
result.conversationsScanned += 1;
|
|
201
|
+
const candidateIds = getNoiseCandidateIds(db, convId, recentWindowSize, maxCandidatesPerConversation);
|
|
202
|
+
result.noiseCandidates += candidateIds.length;
|
|
203
|
+
if (candidateIds.length === 0)
|
|
204
|
+
continue;
|
|
205
|
+
const refs = getDeletableMessageIds(db, candidateIds).blockedRefs;
|
|
206
|
+
const referenced = new Set(refs.map(ref => ref.messageId));
|
|
207
|
+
result.referencedNoise += referenced.size;
|
|
208
|
+
const parent = new Set();
|
|
209
|
+
const context = new Set();
|
|
210
|
+
const snapshot = new Set();
|
|
211
|
+
const other = new Set();
|
|
212
|
+
for (const ref of refs) {
|
|
213
|
+
if (ref.table === 'messages' && ref.column === 'parent_id')
|
|
214
|
+
parent.add(ref.messageId);
|
|
215
|
+
else if (ref.table === 'contexts')
|
|
216
|
+
context.add(ref.messageId);
|
|
217
|
+
else if (ref.table === 'composition_snapshots')
|
|
218
|
+
snapshot.add(ref.messageId);
|
|
219
|
+
else
|
|
220
|
+
other.add(ref.messageId);
|
|
221
|
+
if (result.sampleRefs.length < 12) {
|
|
222
|
+
result.sampleRefs.push(`${ref.table}.${ref.column}->${ref.messageId}x${ref.count}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
result.parentReferencedNoise += parent.size;
|
|
226
|
+
result.contextReferencedNoise += context.size;
|
|
227
|
+
result.snapshotReferencedNoise += snapshot.size;
|
|
228
|
+
result.otherReferencedNoise += other.size;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
function isRepairableNoiseReference(ref) {
|
|
233
|
+
return (ref.table === 'messages' && ref.column === 'parent_id')
|
|
234
|
+
|| (ref.table === 'contexts' && ref.column === 'head_message_id')
|
|
235
|
+
|| (ref.table === 'composition_snapshots' && ref.column === 'head_message_id');
|
|
236
|
+
}
|
|
237
|
+
function isRepairableNoiseReferenced(refs) {
|
|
238
|
+
return refs.length > 0 && refs.every(isRepairableNoiseReference);
|
|
239
|
+
}
|
|
240
|
+
function hasParentReference(refs) {
|
|
241
|
+
return refs.some(ref => ref.table === 'messages' && ref.column === 'parent_id');
|
|
242
|
+
}
|
|
243
|
+
function reparentChildrenAndDelete(db, messageId) {
|
|
244
|
+
const row = db
|
|
245
|
+
.prepare('SELECT id, parent_id, depth FROM messages WHERE id = ?')
|
|
246
|
+
.get(messageId);
|
|
247
|
+
if (!row)
|
|
248
|
+
return false;
|
|
249
|
+
db.prepare(`
|
|
250
|
+
WITH RECURSIVE subtree(id) AS (
|
|
251
|
+
SELECT id FROM messages WHERE parent_id = ?
|
|
252
|
+
UNION ALL
|
|
253
|
+
SELECT m.id FROM messages m JOIN subtree s ON m.parent_id = s.id
|
|
254
|
+
)
|
|
255
|
+
UPDATE messages
|
|
256
|
+
SET depth = CASE WHEN depth > 0 THEN depth - 1 ELSE 0 END
|
|
257
|
+
WHERE id IN (SELECT id FROM subtree)
|
|
258
|
+
`).run(messageId);
|
|
259
|
+
db.prepare('UPDATE messages SET parent_id = ? WHERE parent_id = ?').run(row.parent_id, messageId);
|
|
260
|
+
const deleted = db.prepare('DELETE FROM messages WHERE id = ?').run(messageId);
|
|
261
|
+
return (deleted.changes ?? 0) > 0;
|
|
262
|
+
}
|
|
263
|
+
function repairContextAndSnapshotHeads(db, messageId, replacementHeadId) {
|
|
264
|
+
const contextResult = db
|
|
265
|
+
.prepare("UPDATE contexts SET head_message_id = ?, updated_at = datetime('now') WHERE head_message_id = ?")
|
|
266
|
+
.run(replacementHeadId, messageId);
|
|
267
|
+
const snapshotResult = db
|
|
268
|
+
.prepare('UPDATE composition_snapshots SET head_message_id = ?, repair_depth = repair_depth + 1 WHERE head_message_id = ?')
|
|
269
|
+
.run(replacementHeadId, messageId);
|
|
270
|
+
return {
|
|
271
|
+
repairedContextHeads: contextResult.changes ?? 0,
|
|
272
|
+
repairedSnapshotHeads: snapshotResult.changes ?? 0,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Safely collapse referenced noise nodes by moving children and durable head
|
|
277
|
+
* pointers to the deleted node's parent. The repair only handles known safe
|
|
278
|
+
* message-head references: messages.parent_id, contexts.head_message_id, and
|
|
279
|
+
* composition_snapshots.head_message_id. Other FK blockers remain preserved.
|
|
280
|
+
*/
|
|
281
|
+
export function runTreeSafeNoiseCompaction(db, conversationId, recentWindowSize = 20, maxMutations = 100) {
|
|
282
|
+
const result = {
|
|
283
|
+
passType: 'tree_safe_noise_compaction',
|
|
284
|
+
conversationsScanned: 0,
|
|
285
|
+
candidates: 0,
|
|
286
|
+
reparented: 0,
|
|
287
|
+
repairedContextHeads: 0,
|
|
288
|
+
repairedSnapshotHeads: 0,
|
|
289
|
+
deleted: 0,
|
|
290
|
+
skippedBlocked: 0,
|
|
291
|
+
skippedRoot: 0,
|
|
292
|
+
fkCheck: 'not-run',
|
|
293
|
+
};
|
|
294
|
+
db.prepare('BEGIN IMMEDIATE').run();
|
|
295
|
+
try {
|
|
296
|
+
for (const convId of getConversationIds(db, conversationId)) {
|
|
297
|
+
if (result.deleted >= maxMutations)
|
|
298
|
+
break;
|
|
299
|
+
result.conversationsScanned += 1;
|
|
300
|
+
const remaining = maxMutations - result.deleted;
|
|
301
|
+
const candidateIds = getNoiseCandidateIds(db, convId, recentWindowSize, remaining);
|
|
302
|
+
result.candidates += candidateIds.length;
|
|
303
|
+
for (const id of candidateIds) {
|
|
304
|
+
if (result.deleted >= maxMutations)
|
|
305
|
+
break;
|
|
306
|
+
const refs = getDeletableMessageIds(db, [id]).blockedRefs;
|
|
307
|
+
if (refs.length === 0) {
|
|
308
|
+
const deleted = db.prepare('DELETE FROM messages WHERE id = ?').run(id);
|
|
309
|
+
result.deleted += deleted.changes ?? 0;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (!isRepairableNoiseReferenced(refs)) {
|
|
313
|
+
result.skippedBlocked += 1;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const row = db.prepare('SELECT parent_id FROM messages WHERE id = ?').get(id);
|
|
317
|
+
if (!row || row.parent_id == null) {
|
|
318
|
+
result.skippedRoot += 1;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const repaired = repairContextAndSnapshotHeads(db, id, row.parent_id);
|
|
322
|
+
result.repairedContextHeads += repaired.repairedContextHeads;
|
|
323
|
+
result.repairedSnapshotHeads += repaired.repairedSnapshotHeads;
|
|
324
|
+
if (reparentChildrenAndDelete(db, id)) {
|
|
325
|
+
if (hasParentReference(refs))
|
|
326
|
+
result.reparented += 1;
|
|
327
|
+
result.deleted += 1;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
result.fkCheck = summarizeForeignKeyCheck(db);
|
|
332
|
+
if (result.fkCheck !== 'none')
|
|
333
|
+
throw new Error(`foreign_key_check failed: ${result.fkCheck}`);
|
|
334
|
+
db.prepare('COMMIT').run();
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
db.prepare('ROLLBACK').run();
|
|
339
|
+
result.fkCheck = summarizeForeignKeyCheck(db);
|
|
340
|
+
console.warn(`[proactive-pass] Tree-safe noise compaction failed fkCheck=${result.fkCheck} error=${err instanceof Error ? err.message : String(err)}`);
|
|
341
|
+
return { ...result, deleted: 0, reparented: 0 };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
103
344
|
// ─── Noise Sweep ─────────────────────────────────────────────────
|
|
104
345
|
/**
|
|
105
346
|
* Delete noise and heartbeat messages outside the recent window.
|
|
@@ -111,7 +352,7 @@ function isNoiseMessage(textContent, isHeartbeat) {
|
|
|
111
352
|
* Deletions are wrapped in a single transaction. The FTS5 trigger handles
|
|
112
353
|
* index cleanup automatically (msg_fts_ad fires on DELETE).
|
|
113
354
|
*/
|
|
114
|
-
export function runNoiseSweep(db, conversationId, recentWindowSize = 20, maxCandidates = Infinity) {
|
|
355
|
+
export function runNoiseSweep(db, conversationId, recentWindowSize = 20, maxCandidates = Infinity, context) {
|
|
115
356
|
const ZERO = { messagesDeleted: 0, passType: 'noise_sweep' };
|
|
116
357
|
try {
|
|
117
358
|
const safeWindow = resolveSafeWindow(recentWindowSize);
|
|
@@ -145,16 +386,29 @@ export function runNoiseSweep(db, conversationId, recentWindowSize = 20, maxCand
|
|
|
145
386
|
return ZERO;
|
|
146
387
|
}
|
|
147
388
|
const candidateIds = toDelete.map(r => r.id);
|
|
148
|
-
const { deletableIds: ids, blockedIds } = getDeletableMessageIds(db, candidateIds);
|
|
149
|
-
if (ids.length === 0) {
|
|
150
|
-
return ZERO;
|
|
151
|
-
}
|
|
152
389
|
// Delete in a transaction; use chunked IN clauses to avoid
|
|
153
390
|
// SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (default 999).
|
|
391
|
+
// Eligibility is intentionally recomputed inside the transaction. Live
|
|
392
|
+
// context/snapshot writers can add FK sidecars between the candidate scan
|
|
393
|
+
// and DELETE; checking blockers outside the write transaction turns the
|
|
394
|
+
// sweep into a race.
|
|
154
395
|
let totalDeleted = 0;
|
|
396
|
+
let blockedIds = [];
|
|
397
|
+
let blockedRefs = [];
|
|
155
398
|
const CHUNK = 500;
|
|
156
|
-
db.prepare('BEGIN').run();
|
|
399
|
+
db.prepare('BEGIN IMMEDIATE').run();
|
|
157
400
|
try {
|
|
401
|
+
const resolved = getDeletableMessageIds(db, candidateIds);
|
|
402
|
+
const ids = resolved.deletableIds;
|
|
403
|
+
blockedIds = resolved.blockedIds;
|
|
404
|
+
blockedRefs = resolved.blockedRefs;
|
|
405
|
+
if (ids.length === 0) {
|
|
406
|
+
db.prepare('COMMIT').run();
|
|
407
|
+
if (blockedIds.length > 0) {
|
|
408
|
+
console.log(`[proactive-pass] Noise sweep skipped referenced ${formatPassContext(conversationId, context)} candidates=${candidates.length} noise=${candidateIds.length} skippedReferenced=${blockedIds.length} refs=${summarizeBlockedRefs(blockedRefs)}`);
|
|
409
|
+
}
|
|
410
|
+
return ZERO;
|
|
411
|
+
}
|
|
158
412
|
for (let i = 0; i < ids.length; i += CHUNK) {
|
|
159
413
|
const chunk = ids.slice(i, i + CHUNK);
|
|
160
414
|
const placeholders = chunk.map(() => '?').join(', ');
|
|
@@ -167,15 +421,16 @@ export function runNoiseSweep(db, conversationId, recentWindowSize = 20, maxCand
|
|
|
167
421
|
}
|
|
168
422
|
catch (innerErr) {
|
|
169
423
|
db.prepare('ROLLBACK').run();
|
|
424
|
+
console.warn(`[proactive-pass] Noise sweep delete failed ${formatPassContext(conversationId, context)} candidates=${candidates.length} noise=${candidateIds.length} skippedReferenced=${blockedIds.length} refs=${summarizeBlockedRefs(blockedRefs)} fkCheck=${summarizeForeignKeyCheck(db)} error=${innerErr instanceof Error ? innerErr.message : String(innerErr)}`);
|
|
170
425
|
throw innerErr;
|
|
171
426
|
}
|
|
172
427
|
if (totalDeleted > 0) {
|
|
173
|
-
console.log(`[proactive-pass] Noise sweep
|
|
428
|
+
console.log(`[proactive-pass] Noise sweep ${formatPassContext(conversationId, context)} candidates=${candidates.length} noise=${candidateIds.length} deleted=${totalDeleted} skippedReferenced=${blockedIds.length} cutoff=${cutoff}`);
|
|
174
429
|
}
|
|
175
430
|
return { messagesDeleted: totalDeleted, passType: 'noise_sweep' };
|
|
176
431
|
}
|
|
177
432
|
catch (err) {
|
|
178
|
-
console.warn(`[proactive-pass] Noise sweep failed
|
|
433
|
+
console.warn(`[proactive-pass] Noise sweep failed ${formatPassContext(conversationId, context)}: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
434
|
return ZERO;
|
|
180
435
|
}
|
|
181
436
|
}
|
|
@@ -196,7 +451,7 @@ export function runNoiseSweep(db, conversationId, recentWindowSize = 20, maxCand
|
|
|
196
451
|
*
|
|
197
452
|
* Mutations are committed in a single transaction.
|
|
198
453
|
*/
|
|
199
|
-
export function runToolDecay(db, conversationId, recentWindowSize = 40, maxCandidates = Infinity) {
|
|
454
|
+
export function runToolDecay(db, conversationId, recentWindowSize = 40, maxCandidates = Infinity, context) {
|
|
200
455
|
const ZERO = { messagesUpdated: 0, bytesFreed: 0, passType: 'tool_decay' };
|
|
201
456
|
try {
|
|
202
457
|
const safeWindow = resolveSafeWindow(recentWindowSize);
|
|
@@ -277,7 +532,7 @@ export function runToolDecay(db, conversationId, recentWindowSize = 40, maxCandi
|
|
|
277
532
|
throw innerErr;
|
|
278
533
|
}
|
|
279
534
|
if (totalUpdated > 0) {
|
|
280
|
-
console.log(`[proactive-pass] Tool decay
|
|
535
|
+
console.log(`[proactive-pass] Tool decay ${formatPassContext(conversationId, context)} candidates=${candidates.length} updated=${totalUpdated} bytesFreed=${totalBytesFreed} cutoff=${cutoff}`);
|
|
281
536
|
}
|
|
282
537
|
return {
|
|
283
538
|
messagesUpdated: totalUpdated,
|
|
@@ -286,7 +541,7 @@ export function runToolDecay(db, conversationId, recentWindowSize = 40, maxCandi
|
|
|
286
541
|
};
|
|
287
542
|
}
|
|
288
543
|
catch (err) {
|
|
289
|
-
console.warn(`[proactive-pass] Tool decay failed
|
|
544
|
+
console.warn(`[proactive-pass] Tool decay failed ${formatPassContext(conversationId, context)}: ${err instanceof Error ? err.message : String(err)}`);
|
|
290
545
|
return ZERO;
|
|
291
546
|
}
|
|
292
547
|
}
|
package/dist/seed.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* const seeder = new WorkspaceSeeder(hypermem);
|
|
9
|
-
* const result = await seeder.seedWorkspace('/path/to/workspace', { agentId: '
|
|
9
|
+
* const result = await seeder.seedWorkspace('/path/to/workspace', { agentId: 'alice' });
|
|
10
10
|
*
|
|
11
11
|
* Idempotent: skips files whose source hash hasn't changed since last index.
|
|
12
12
|
* Atomic: each file's chunks are swapped in a single transaction.
|
package/dist/seed.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* const seeder = new WorkspaceSeeder(hypermem);
|
|
9
|
-
* const result = await seeder.seedWorkspace('/path/to/workspace', { agentId: '
|
|
9
|
+
* const result = await seeder.seedWorkspace('/path/to/workspace', { agentId: 'alice' });
|
|
10
10
|
*
|
|
11
11
|
* Idempotent: skips files whose source hash hasn't changed since last index.
|
|
12
12
|
* Atomic: each file's chunks are swapped in a single transaction.
|
|
@@ -33,8 +33,8 @@ export interface FlushSessionResult {
|
|
|
33
33
|
* from those stores naturally.
|
|
34
34
|
*
|
|
35
35
|
* @param cache Connected CacheLayer instance
|
|
36
|
-
* @param agentId Agent identifier (e.g. "
|
|
37
|
-
* @param sessionKey Full session key (e.g. "agent:
|
|
36
|
+
* @param agentId Agent identifier (e.g. "alice")
|
|
37
|
+
* @param sessionKey Full session key (e.g. "agent:alice:webchat:scratchpad")
|
|
38
38
|
* @param opts Optional flags
|
|
39
39
|
*/
|
|
40
40
|
export declare function flushSession(cache: CacheLayer, agentId: string, sessionKey: string, opts?: FlushSessionOptions): Promise<FlushSessionResult>;
|
package/dist/session-flusher.js
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* from those stores naturally.
|
|
19
19
|
*
|
|
20
20
|
* @param cache Connected CacheLayer instance
|
|
21
|
-
* @param agentId Agent identifier (e.g. "
|
|
22
|
-
* @param sessionKey Full session key (e.g. "agent:
|
|
21
|
+
* @param agentId Agent identifier (e.g. "alice")
|
|
22
|
+
* @param sessionKey Full session key (e.g. "agent:alice:webchat:scratchpad")
|
|
23
23
|
* @param opts Optional flags
|
|
24
24
|
*/
|
|
25
25
|
export async function flushSession(cache, agentId, sessionKey, opts = {}) {
|
package/dist/spawn-context.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* const ctx = await buildSpawnContext(messageStore, docChunkStore, agentId, {
|
|
9
|
-
* parentSessionKey: 'agent:
|
|
9
|
+
* parentSessionKey: 'agent:alice:webchat:main',
|
|
10
10
|
* workingSnapshot: 10,
|
|
11
11
|
* documents: ['/path/to/spec.md'],
|
|
12
12
|
* });
|
package/dist/spawn-context.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* const ctx = await buildSpawnContext(messageStore, docChunkStore, agentId, {
|
|
9
|
-
* parentSessionKey: 'agent:
|
|
9
|
+
* parentSessionKey: 'agent:alice:webchat:main',
|
|
10
10
|
* workingSnapshot: 10,
|
|
11
11
|
* documents: ['/path/to/spec.md'],
|
|
12
12
|
* });
|
package/dist/topic-store.js
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
const KNOWN_NAMES = {
|
|
13
13
|
hypermem: 'HyperMem',
|
|
14
14
|
hyperbuilder: 'HyperBuilder',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
canvas: 'canvas',
|
|
16
|
+
dashboard: 'dashboard',
|
|
17
|
+
dispatch: 'dispatch',
|
|
18
18
|
clawtext: 'ClawText',
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
automation: 'automation',
|
|
20
|
+
council: 'council',
|
|
21
21
|
openclaw: 'OpenClaw',
|
|
22
22
|
clawhub: 'ClawHub',
|
|
23
23
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topic-synthesizer.d.ts","sourceRoot":"","sources":["../src/topic-synthesizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,QAAA,MAAM,uBAAuB,KAAK,CAAC;AACnC,QAAA,MAAM,sBAAsB,IAAI,CAAC;AACjC,QAAA,MAAM,4BAA4B,IAAI,CAAC;AACvC,QAAA,MAAM,2BAA2B,MAAM,CAAC;AACxC,QAAA,MAAM,uBAAuB,KAAK,CAAC;AACnC,QAAA,MAAM,uBAAuB,IAAI,CAAC;AAClC,QAAA,MAAM,cAAc,KAAK,CAAC;AAC1B,QAAA,MAAM,eAAe,IAAI,CAAC;AAG1B,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,CAAC;AAIF,MAAM,WAAW,eAAe;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,4BAA4B,EAAE,MAAM,CAAC;IACrC,2BAA2B,EAAE,MAAM,CAAC;IACpC,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAiID,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAG/B,SAAS,EAAE,YAAY,EACvB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,GAAG,IAAI,EACtD,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,YAAA;IAcpD;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe;IA6FtC;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,2BAA2B;
|
|
1
|
+
{"version":3,"file":"topic-synthesizer.d.ts","sourceRoot":"","sources":["../src/topic-synthesizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,QAAA,MAAM,uBAAuB,KAAK,CAAC;AACnC,QAAA,MAAM,sBAAsB,IAAI,CAAC;AACjC,QAAA,MAAM,4BAA4B,IAAI,CAAC;AACvC,QAAA,MAAM,2BAA2B,MAAM,CAAC;AACxC,QAAA,MAAM,uBAAuB,KAAK,CAAC;AACnC,QAAA,MAAM,uBAAuB,IAAI,CAAC;AAClC,QAAA,MAAM,cAAc,KAAK,CAAC;AAC1B,QAAA,MAAM,eAAe,IAAI,CAAC;AAG1B,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,CAAC;AAIF,MAAM,WAAW,eAAe;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,4BAA4B,EAAE,MAAM,CAAC;IACrC,2BAA2B,EAAE,MAAM,CAAC;IACpC,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAiID,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAG/B,SAAS,EAAE,YAAY,EACvB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,GAAG,IAAI,EACtD,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,YAAA;IAcpD;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe;IA6FtC;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,2BAA2B;IAsDnC;;OAEG;IACH,OAAO,CAAC,eAAe;CA+GxB"}
|
|
@@ -35,7 +35,7 @@ function keystoneScore(msg) {
|
|
|
35
35
|
const backtickMatches = text.match(/`[^`]+`/g) || [];
|
|
36
36
|
score += backtickMatches.length * 0.2;
|
|
37
37
|
// Agent mentions (known patterns)
|
|
38
|
-
const agentMentions = text.match(/\b(
|
|
38
|
+
const agentMentions = text.match(/\b(alice|bob|agent4|dave|oscar|carol|director1|director2|director7|specialist2|specialist1)\b/gi) || [];
|
|
39
39
|
score += agentMentions.length * 0.25;
|
|
40
40
|
// Quoted content
|
|
41
41
|
const quotedMatches = text.match(/"[^"]{10,}"/g) || [];
|
|
@@ -283,7 +283,9 @@ export class TopicSynthesizer {
|
|
|
283
283
|
if (topicName === 'infrastructure') {
|
|
284
284
|
return messageDb.prepare(`
|
|
285
285
|
SELECT * FROM messages
|
|
286
|
-
WHERE
|
|
286
|
+
WHERE role IN ('user', 'assistant')
|
|
287
|
+
AND text_content IS NOT NULL
|
|
288
|
+
AND trim(text_content) != ''
|
|
287
289
|
AND (
|
|
288
290
|
lower(text_content) LIKE '%redis%'
|
|
289
291
|
OR lower(text_content) LIKE '%sqlite%'
|
|
@@ -300,7 +302,9 @@ export class TopicSynthesizer {
|
|
|
300
302
|
if (topicName === 'security') {
|
|
301
303
|
return messageDb.prepare(`
|
|
302
304
|
SELECT * FROM messages
|
|
303
|
-
WHERE
|
|
305
|
+
WHERE role IN ('user', 'assistant')
|
|
306
|
+
AND text_content IS NOT NULL
|
|
307
|
+
AND trim(text_content) != ''
|
|
304
308
|
AND (
|
|
305
309
|
lower(text_content) LIKE '%security%'
|
|
306
310
|
OR lower(text_content) LIKE '%auth%'
|
|
@@ -315,7 +319,9 @@ export class TopicSynthesizer {
|
|
|
315
319
|
}
|
|
316
320
|
return messageDb.prepare(`
|
|
317
321
|
SELECT * FROM messages
|
|
318
|
-
WHERE
|
|
322
|
+
WHERE role IN ('user', 'assistant')
|
|
323
|
+
AND text_content IS NOT NULL
|
|
324
|
+
AND trim(text_content) != ''
|
|
319
325
|
AND lower(text_content) LIKE ? ESCAPE '\\'
|
|
320
326
|
ORDER BY created_at ASC
|
|
321
327
|
LIMIT ?
|
|
@@ -39,7 +39,7 @@ export interface CollectionTrigger {
|
|
|
39
39
|
export declare const TRIGGER_REGISTRY_VERSION = "1.0.0";
|
|
40
40
|
/**
|
|
41
41
|
* Default trigger registry for standard ACA collections.
|
|
42
|
-
* Covers the core ACA offload use case from
|
|
42
|
+
* Covers the core ACA offload use case from carol's spec.
|
|
43
43
|
*/
|
|
44
44
|
export declare const TRIGGER_REGISTRY: CollectionTrigger[];
|
|
45
45
|
/** Backward-compat alias — same reference as TRIGGER_REGISTRY */
|
package/dist/trigger-registry.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createHash } from 'node:crypto';
|
|
|
13
13
|
export const TRIGGER_REGISTRY_VERSION = '1.0.0';
|
|
14
14
|
/**
|
|
15
15
|
* Default trigger registry for standard ACA collections.
|
|
16
|
-
* Covers the core ACA offload use case from
|
|
16
|
+
* Covers the core ACA offload use case from carol's spec.
|
|
17
17
|
*/
|
|
18
18
|
export const TRIGGER_REGISTRY = [
|
|
19
19
|
{
|
|
@@ -69,7 +69,7 @@ export const TRIGGER_REGISTRY = [
|
|
|
69
69
|
],
|
|
70
70
|
maxTokens: 800,
|
|
71
71
|
maxChunks: 2,
|
|
72
|
-
owner: '
|
|
72
|
+
owner: 'alice',
|
|
73
73
|
category: 'operations',
|
|
74
74
|
description: 'Agent operational procedures: boot sequence, heartbeat, work queue, session startup',
|
|
75
75
|
},
|
|
@@ -106,7 +106,7 @@ export const TRIGGER_REGISTRY = [
|
|
|
106
106
|
],
|
|
107
107
|
maxTokens: 1500,
|
|
108
108
|
maxChunks: 4,
|
|
109
|
-
owner: '
|
|
109
|
+
owner: 'alice',
|
|
110
110
|
category: 'memory',
|
|
111
111
|
description: 'Decision history: past choices, previously agreed approaches, recalled context',
|
|
112
112
|
},
|
|
@@ -132,7 +132,7 @@ export const TRIGGER_REGISTRY = [
|
|
|
132
132
|
],
|
|
133
133
|
maxTokens: 1200,
|
|
134
134
|
maxChunks: 3,
|
|
135
|
-
owner: '
|
|
135
|
+
owner: 'alice',
|
|
136
136
|
category: 'operations',
|
|
137
137
|
description: 'Agent tooling reference: CLI commands, config paths, deployment procedures, quick reference',
|
|
138
138
|
},
|