@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +27 -2
  2. package/INSTALL.md +75 -68
  3. package/README.md +18 -36
  4. package/assets/default-config.json +41 -0
  5. package/bench/data-access-bench.mjs +1 -1
  6. package/bin/hypermem-doctor.mjs +76 -2
  7. package/bin/hypermem-status.mjs +255 -7
  8. package/dist/adaptive-lifecycle.d.ts +39 -0
  9. package/dist/adaptive-lifecycle.d.ts.map +1 -1
  10. package/dist/adaptive-lifecycle.js +87 -9
  11. package/dist/background-indexer.d.ts.map +1 -1
  12. package/dist/background-indexer.js +16 -14
  13. package/dist/compositor.d.ts.map +1 -1
  14. package/dist/compositor.js +239 -20
  15. package/dist/cross-agent.d.ts +1 -1
  16. package/dist/cross-agent.js +17 -17
  17. package/dist/hybrid-retrieval.d.ts +8 -0
  18. package/dist/hybrid-retrieval.d.ts.map +1 -1
  19. package/dist/hybrid-retrieval.js +112 -10
  20. package/dist/index.d.ts +16 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +19 -4
  23. package/dist/message-store.d.ts +62 -1
  24. package/dist/message-store.d.ts.map +1 -1
  25. package/dist/message-store.js +355 -2
  26. package/dist/open-domain.d.ts.map +1 -1
  27. package/dist/open-domain.js +3 -2
  28. package/dist/proactive-pass.d.ts +42 -2
  29. package/dist/proactive-pass.d.ts.map +1 -1
  30. package/dist/proactive-pass.js +294 -39
  31. package/dist/seed.d.ts +1 -1
  32. package/dist/seed.js +1 -1
  33. package/dist/session-flusher.d.ts +2 -2
  34. package/dist/session-flusher.js +2 -2
  35. package/dist/spawn-context.d.ts +1 -1
  36. package/dist/spawn-context.js +1 -1
  37. package/dist/topic-store.js +5 -5
  38. package/dist/topic-synthesizer.d.ts.map +1 -1
  39. package/dist/topic-synthesizer.js +10 -4
  40. package/dist/trigger-registry.d.ts +1 -1
  41. package/dist/trigger-registry.js +4 -4
  42. package/dist/types.d.ts +101 -2
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/vector-store.d.ts +10 -1
  45. package/dist/vector-store.d.ts.map +1 -1
  46. package/dist/vector-store.js +45 -9
  47. package/docs/DIAGNOSTICS.md +88 -1
  48. package/docs/INTEGRATION_VALIDATION.md +40 -1
  49. package/docs/MIGRATION.md +1 -1
  50. package/docs/TUNING.md +47 -6
  51. package/install.sh +5 -60
  52. package/memory-plugin/dist/index.js +192 -0
  53. package/memory-plugin/openclaw.plugin.json +199 -2
  54. package/memory-plugin/package.json +2 -2
  55. package/package.json +29 -10
  56. package/plugin/dist/index.d.ts +2 -0
  57. package/plugin/dist/index.d.ts.map +1 -1
  58. package/plugin/dist/index.js +178 -11
  59. package/plugin/dist/index.js.map +1 -1
  60. package/plugin/openclaw.plugin.json +199 -2
  61. package/plugin/package.json +2 -2
  62. package/scripts/install-packed-runtime.mjs +99 -0
  63. package/scripts/install-runtime.mjs +164 -4
  64. package/ARCHITECTURE.md +0 -298
  65. package/docs/KNOWN_LIMITATIONS.md +0 -35
  66. package/docs/PHASE1-VALIDATION.md +0 -132
  67. package/docs/RELEASE_0.8.0_VALIDATION.md +0 -70
  68. package/docs/RELEASE_PROCESS.md +0 -10
  69. package/docs/ROADMAP.md +0 -266
@@ -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
- * Filter candidate message ids down to rows that are safe to delete without
49
- * violating HyperMem's FK edges.
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 blocked = db
54
+ const blockedSet = new Set();
55
+ const blockedRefs = [];
56
+ const tables = db
60
57
  .prepare(`
61
- SELECT DISTINCT id
62
- FROM (
63
- SELECT sm.message_id AS id
64
- FROM summary_messages sm
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(...candidateIds, ...candidateIds);
76
- if (blocked.length === 0)
77
- return { deletableIds: candidateIds, blockedIds: [] };
78
- const blockedIds = blocked.map(row => row.id);
79
- const blockedSet = new Set(blockedIds);
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 conversation=${conversationId} candidates=${candidates.length} noise=${candidateIds.length} deleted=${totalDeleted} skippedReferenced=${blockedIds.length} cutoff=${cutoff}`);
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 for conversation ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
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 conversation=${conversationId} candidates=${candidates.length} updated=${totalUpdated} bytesFreed=${totalBytesFreed} cutoff=${cutoff}`);
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 for conversation ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
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: 'agent1' });
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: 'agent1' });
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. "agent1")
37
- * @param sessionKey Full session key (e.g. "agent:agent1:webchat:scratchpad")
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>;
@@ -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. "agent1")
22
- * @param sessionKey Full session key (e.g. "agent:agent1:webchat:scratchpad")
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 = {}) {
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Usage:
8
8
  * const ctx = await buildSpawnContext(messageStore, docChunkStore, agentId, {
9
- * parentSessionKey: 'agent:agent1:webchat:main',
9
+ * parentSessionKey: 'agent:alice:webchat:main',
10
10
  * workingSnapshot: 10,
11
11
  * documents: ['/path/to/spec.md'],
12
12
  * });
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Usage:
8
8
  * const ctx = await buildSpawnContext(messageStore, docChunkStore, agentId, {
9
- * parentSessionKey: 'agent:agent1:webchat:main',
9
+ * parentSessionKey: 'agent:alice:webchat:main',
10
10
  * workingSnapshot: 10,
11
11
  * documents: ['/path/to/spec.md'],
12
12
  * });
@@ -12,12 +12,12 @@
12
12
  const KNOWN_NAMES = {
13
13
  hypermem: 'HyperMem',
14
14
  hyperbuilder: 'HyperBuilder',
15
- clawcanvas: 'ClawCanvas',
16
- clawdash: 'ClawDash',
17
- clawdispatch: 'ClawDispatch',
15
+ canvas: 'canvas',
16
+ dashboard: 'dashboard',
17
+ dispatch: 'dispatch',
18
18
  clawtext: 'ClawText',
19
- clawtomation: 'ClawTomation',
20
- clawcouncil: 'ClawCouncil',
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;IAgDnC;;OAEG;IACH,OAAO,CAAC,eAAe;CA+GxB"}
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(agent1|agent2|agent4|agent3|agent5|agent6|director1|director2|director7|specialist2|specialist1)\b/gi) || [];
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 text_content IS NOT NULL
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 text_content IS NOT NULL
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 text_content IS NOT NULL
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 agent6's spec.
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 */
@@ -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 agent6's spec.
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: 'agent1',
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: 'agent1',
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: 'agent1',
135
+ owner: 'alice',
136
136
  category: 'operations',
137
137
  description: 'Agent tooling reference: CLI commands, config paths, deployment procedures, quick reference',
138
138
  },