omnius 1.0.399 → 1.0.401

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.
@@ -0,0 +1,3636 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __omnius_createRequire } from "node:module"; import { fileURLToPath as __omnius_fileURLToPath } from "node:url"; import { dirname as __omnius_dirname } from "node:path"; const require = __omnius_createRequire(import.meta.url); const __filename = __omnius_fileURLToPath(import.meta.url); const __dirname = __omnius_dirname(__filename);
3
+
4
+ // packages/memory/dist/db.js
5
+ import { createRequire } from "node:module";
6
+ var _Database = null;
7
+ function getDatabase() {
8
+ if (_Database !== null)
9
+ return _Database;
10
+ try {
11
+ const req = createRequire(import.meta.url);
12
+ _Database = req("better-sqlite3");
13
+ } catch {
14
+ throw new Error("better-sqlite3 is not available (native module build may have failed).\nTry: npm rebuild better-sqlite3\nMemory features (SQLite) are disabled until this is resolved.");
15
+ }
16
+ return _Database;
17
+ }
18
+ function initDb(dbPath) {
19
+ const Database = getDatabase();
20
+ const db = new Database(dbPath);
21
+ db.pragma("journal_mode = WAL");
22
+ db.pragma("foreign_keys = ON");
23
+ runMigrations(db);
24
+ return db;
25
+ }
26
+ function runMigrations(db) {
27
+ db.exec(`
28
+ -- repo_profiles: one row per repository root.
29
+ CREATE TABLE IF NOT EXISTS repo_profiles (
30
+ repo_root TEXT PRIMARY KEY,
31
+ languages TEXT NOT NULL DEFAULT '[]', -- JSON array
32
+ frameworks TEXT NOT NULL DEFAULT '[]', -- JSON array
33
+ build_system TEXT NOT NULL DEFAULT '',
34
+ test_commands TEXT NOT NULL DEFAULT '[]', -- JSON array
35
+ lint_commands TEXT NOT NULL DEFAULT '[]', -- JSON array
36
+ package_manager TEXT NOT NULL DEFAULT '',
37
+ notes TEXT,
38
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
39
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
40
+ );
41
+
42
+ -- file_summaries: one row per (repo_root, file_path) pair.
43
+ CREATE TABLE IF NOT EXISTS file_summaries (
44
+ file_path TEXT NOT NULL,
45
+ repo_root TEXT NOT NULL,
46
+ purpose TEXT NOT NULL DEFAULT '',
47
+ exports TEXT NOT NULL DEFAULT '[]', -- JSON array
48
+ imports TEXT NOT NULL DEFAULT '[]', -- JSON array
49
+ domain TEXT NOT NULL DEFAULT '',
50
+ risk_level TEXT NOT NULL DEFAULT 'low', -- low | medium | high
51
+ related_tests TEXT NOT NULL DEFAULT '[]', -- JSON array
52
+ notes TEXT,
53
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
54
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
55
+ PRIMARY KEY (file_path)
56
+ );
57
+
58
+ CREATE INDEX IF NOT EXISTS idx_file_summaries_repo
59
+ ON file_summaries (repo_root);
60
+
61
+ CREATE INDEX IF NOT EXISTS idx_file_summaries_domain
62
+ ON file_summaries (domain);
63
+
64
+ CREATE INDEX IF NOT EXISTS idx_file_summaries_risk
65
+ ON file_summaries (risk_level);
66
+
67
+ -- task_memory: historical record of agent tasks.
68
+ CREATE TABLE IF NOT EXISTS task_memory (
69
+ id TEXT PRIMARY KEY,
70
+ session_id TEXT NOT NULL,
71
+ repo_root TEXT NOT NULL,
72
+ goal TEXT NOT NULL DEFAULT '',
73
+ constraints TEXT NOT NULL DEFAULT '[]', -- JSON array
74
+ files_touched TEXT NOT NULL DEFAULT '[]', -- JSON array
75
+ patches TEXT NOT NULL DEFAULT '[]', -- JSON array
76
+ outcome TEXT NOT NULL DEFAULT 'unknown', -- success | failure | partial | unknown
77
+ quality_score REAL,
78
+ notes TEXT,
79
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
80
+ );
81
+
82
+ CREATE INDEX IF NOT EXISTS idx_task_memory_session
83
+ ON task_memory (session_id);
84
+
85
+ CREATE INDEX IF NOT EXISTS idx_task_memory_repo
86
+ ON task_memory (repo_root);
87
+
88
+ CREATE INDEX IF NOT EXISTS idx_task_memory_outcome
89
+ ON task_memory (outcome);
90
+
91
+ -- patch_history: individual file diffs applied by the agent.
92
+ CREATE TABLE IF NOT EXISTS patch_history (
93
+ id TEXT PRIMARY KEY,
94
+ task_id TEXT NOT NULL,
95
+ session_id TEXT NOT NULL,
96
+ repo_root TEXT NOT NULL,
97
+ file_path TEXT NOT NULL,
98
+ diff TEXT NOT NULL DEFAULT '',
99
+ status TEXT NOT NULL DEFAULT 'applied', -- applied | reverted | pending | failed
100
+ applied_at TEXT,
101
+ reverted_at TEXT,
102
+ notes TEXT,
103
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
104
+ );
105
+
106
+ CREATE INDEX IF NOT EXISTS idx_patch_history_task
107
+ ON patch_history (task_id);
108
+
109
+ CREATE INDEX IF NOT EXISTS idx_patch_history_file
110
+ ON patch_history (file_path);
111
+
112
+ CREATE INDEX IF NOT EXISTS idx_patch_history_status
113
+ ON patch_history (status);
114
+
115
+ -- failures: fingerprinted failure events for pattern detection.
116
+ CREATE TABLE IF NOT EXISTS failures (
117
+ id TEXT PRIMARY KEY,
118
+ task_id TEXT NOT NULL,
119
+ session_id TEXT NOT NULL,
120
+ repo_root TEXT NOT NULL,
121
+ failure_type TEXT NOT NULL DEFAULT '',
122
+ fingerprint TEXT NOT NULL DEFAULT '',
123
+ file_path TEXT,
124
+ error_message TEXT NOT NULL DEFAULT '',
125
+ context TEXT, -- JSON object or null
126
+ resolved INTEGER NOT NULL DEFAULT 0, -- 0 = false, 1 = true
127
+ resolved_at TEXT,
128
+ notes TEXT,
129
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
130
+ );
131
+
132
+ CREATE INDEX IF NOT EXISTS idx_failures_type
133
+ ON failures (failure_type);
134
+
135
+ CREATE INDEX IF NOT EXISTS idx_failures_fingerprint
136
+ ON failures (fingerprint);
137
+
138
+ CREATE INDEX IF NOT EXISTS idx_failures_resolved
139
+ ON failures (resolved);
140
+
141
+ -- validation_runs: results of lint / test / build / typecheck runs.
142
+ CREATE TABLE IF NOT EXISTS validation_runs (
143
+ id TEXT PRIMARY KEY,
144
+ task_id TEXT NOT NULL,
145
+ session_id TEXT NOT NULL,
146
+ repo_root TEXT NOT NULL,
147
+ run_type TEXT NOT NULL DEFAULT '', -- test | lint | build | typecheck
148
+ command TEXT NOT NULL DEFAULT '',
149
+ passed INTEGER NOT NULL DEFAULT 0, -- 0 = false, 1 = true
150
+ duration_ms INTEGER,
151
+ output TEXT,
152
+ error_output TEXT,
153
+ coverage_percent REAL,
154
+ notes TEXT,
155
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
156
+ );
157
+
158
+ CREATE INDEX IF NOT EXISTS idx_validation_runs_task
159
+ ON validation_runs (task_id);
160
+
161
+ CREATE INDEX IF NOT EXISTS idx_validation_runs_run_type
162
+ ON validation_runs (run_type);
163
+
164
+ CREATE INDEX IF NOT EXISTS idx_validation_runs_passed
165
+ ON validation_runs (passed);
166
+
167
+ -- =======================================================================
168
+ -- Structured procedural memory (Fortemi-inspired, WO-FM1)
169
+ -- Replaces flat JSON metabolism store with semantic-searchable DB
170
+ -- =======================================================================
171
+
172
+ -- procedural_memory: core entity for agent learnings
173
+ CREATE TABLE IF NOT EXISTS procedural_memory (
174
+ id TEXT PRIMARY KEY,
175
+ type TEXT NOT NULL DEFAULT 'procedural', -- procedural | episodic | semantic | normative
176
+ category TEXT NOT NULL DEFAULT 'strategy', -- strategy | recovery | optimization
177
+ content TEXT NOT NULL DEFAULT '', -- the lesson text
178
+ trigger_pattern TEXT NOT NULL DEFAULT '', -- when to apply this memory
179
+ steps TEXT NOT NULL DEFAULT '', -- ordered steps (semicolon-separated)
180
+ source_trace TEXT NOT NULL DEFAULT '', -- what created this (llm-extraction, manual, etc.)
181
+ utility REAL NOT NULL DEFAULT 0.5, -- 0-1 usefulness score
182
+ confidence REAL NOT NULL DEFAULT 0.5, -- 0-1 certainty score
183
+ access_count INTEGER NOT NULL DEFAULT 0,
184
+ deleted_at TEXT, -- soft-delete (NULL = active)
185
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
186
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
187
+ );
188
+
189
+ CREATE INDEX IF NOT EXISTS idx_procedural_memory_type
190
+ ON procedural_memory (type);
191
+ CREATE INDEX IF NOT EXISTS idx_procedural_memory_category
192
+ ON procedural_memory (category);
193
+ CREATE INDEX IF NOT EXISTS idx_procedural_memory_deleted
194
+ ON procedural_memory (deleted_at);
195
+
196
+ -- memory_revision: tracks changes to procedural memories
197
+ CREATE TABLE IF NOT EXISTS memory_revision (
198
+ id TEXT PRIMARY KEY,
199
+ memory_id TEXT NOT NULL REFERENCES procedural_memory(id),
200
+ content TEXT NOT NULL DEFAULT '',
201
+ revision_type TEXT NOT NULL DEFAULT 'update', -- create | update | reinforce | decay
202
+ model TEXT, -- which LLM made this revision
203
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
204
+ );
205
+
206
+ CREATE INDEX IF NOT EXISTS idx_memory_revision_memory
207
+ ON memory_revision (memory_id);
208
+
209
+ -- memory_embedding: vector embeddings for semantic search
210
+ CREATE TABLE IF NOT EXISTS memory_embedding (
211
+ id TEXT PRIMARY KEY,
212
+ memory_id TEXT NOT NULL REFERENCES procedural_memory(id),
213
+ vector BLOB NOT NULL, -- float32[] as binary blob
214
+ model TEXT NOT NULL DEFAULT '', -- embedding model name
215
+ dimensions INTEGER NOT NULL DEFAULT 0,
216
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
217
+ );
218
+
219
+ CREATE INDEX IF NOT EXISTS idx_memory_embedding_memory
220
+ ON memory_embedding (memory_id);
221
+
222
+ -- memory_link: bidirectional relationships between memories
223
+ CREATE TABLE IF NOT EXISTS memory_link (
224
+ id TEXT PRIMARY KEY,
225
+ source_id TEXT NOT NULL REFERENCES procedural_memory(id),
226
+ target_id TEXT NOT NULL REFERENCES procedural_memory(id),
227
+ link_type TEXT NOT NULL DEFAULT 'related', -- related | contradicts | supersedes
228
+ confidence REAL NOT NULL DEFAULT 0.5,
229
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
230
+ );
231
+
232
+ CREATE INDEX IF NOT EXISTS idx_memory_link_source
233
+ ON memory_link (source_id);
234
+ CREATE INDEX IF NOT EXISTS idx_memory_link_target
235
+ ON memory_link (target_id);
236
+
237
+ -- =======================================================================
238
+ -- Unified memory substrate
239
+ -- Clean-room TypeScript memory layer for scoped, chunked, weighted retrieval.
240
+ -- =======================================================================
241
+
242
+ CREATE TABLE IF NOT EXISTS memory_scope (
243
+ id TEXT PRIMARY KEY,
244
+ name TEXT NOT NULL UNIQUE,
245
+ kind TEXT NOT NULL DEFAULT 'custom',
246
+ parent_id TEXT REFERENCES memory_scope(id) ON DELETE SET NULL,
247
+ repo_root TEXT,
248
+ source_surface TEXT,
249
+ isolation_level TEXT NOT NULL DEFAULT 'normal',
250
+ metadata_json TEXT NOT NULL DEFAULT '{}',
251
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
252
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
253
+ );
254
+
255
+ CREATE INDEX IF NOT EXISTS idx_memory_scope_kind
256
+ ON memory_scope (kind);
257
+ CREATE INDEX IF NOT EXISTS idx_memory_scope_parent
258
+ ON memory_scope (parent_id);
259
+ CREATE INDEX IF NOT EXISTS idx_memory_scope_repo
260
+ ON memory_scope (repo_root);
261
+
262
+ CREATE TABLE IF NOT EXISTS memory_item (
263
+ id TEXT PRIMARY KEY,
264
+ scope_id TEXT NOT NULL REFERENCES memory_scope(id) ON DELETE CASCADE,
265
+ kind TEXT NOT NULL DEFAULT 'note',
266
+ source TEXT NOT NULL DEFAULT '',
267
+ title TEXT NOT NULL DEFAULT '',
268
+ body TEXT NOT NULL DEFAULT '',
269
+ content_hash TEXT NOT NULL DEFAULT '',
270
+ base_weight REAL NOT NULL DEFAULT 0.5,
271
+ importance REAL NOT NULL DEFAULT 0.5,
272
+ confidence REAL NOT NULL DEFAULT 0.5,
273
+ access_count INTEGER NOT NULL DEFAULT 0,
274
+ metadata_json TEXT NOT NULL DEFAULT '{}',
275
+ observed_at TEXT,
276
+ expires_at TEXT,
277
+ deleted_at TEXT,
278
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
279
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
280
+ );
281
+
282
+ CREATE INDEX IF NOT EXISTS idx_memory_item_scope
283
+ ON memory_item (scope_id);
284
+ CREATE INDEX IF NOT EXISTS idx_memory_item_kind
285
+ ON memory_item (kind);
286
+ CREATE INDEX IF NOT EXISTS idx_memory_item_source
287
+ ON memory_item (source);
288
+ CREATE INDEX IF NOT EXISTS idx_memory_item_hash
289
+ ON memory_item (content_hash);
290
+ CREATE INDEX IF NOT EXISTS idx_memory_item_deleted
291
+ ON memory_item (deleted_at);
292
+
293
+ CREATE TABLE IF NOT EXISTS memory_chunk (
294
+ id TEXT PRIMARY KEY,
295
+ item_id TEXT NOT NULL REFERENCES memory_item(id) ON DELETE CASCADE,
296
+ sequence INTEGER NOT NULL DEFAULT 0,
297
+ text TEXT NOT NULL DEFAULT '',
298
+ start_offset INTEGER NOT NULL DEFAULT 0,
299
+ end_offset INTEGER NOT NULL DEFAULT 0,
300
+ token_count INTEGER NOT NULL DEFAULT 0,
301
+ metadata_json TEXT NOT NULL DEFAULT '{}',
302
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
303
+ UNIQUE(item_id, sequence)
304
+ );
305
+
306
+ CREATE INDEX IF NOT EXISTS idx_memory_chunk_item
307
+ ON memory_chunk (item_id);
308
+ CREATE INDEX IF NOT EXISTS idx_memory_chunk_sequence
309
+ ON memory_chunk (item_id, sequence);
310
+
311
+ CREATE TABLE IF NOT EXISTS memory_embedding_set (
312
+ id TEXT PRIMARY KEY,
313
+ slug TEXT NOT NULL UNIQUE,
314
+ set_type TEXT NOT NULL DEFAULT 'full',
315
+ model TEXT NOT NULL DEFAULT '',
316
+ dimensions INTEGER,
317
+ description TEXT NOT NULL DEFAULT '',
318
+ metadata_json TEXT NOT NULL DEFAULT '{}',
319
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
320
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
321
+ );
322
+
323
+ CREATE INDEX IF NOT EXISTS idx_memory_embedding_set_type
324
+ ON memory_embedding_set (set_type);
325
+
326
+ CREATE TABLE IF NOT EXISTS memory_embedding_v2 (
327
+ id TEXT PRIMARY KEY,
328
+ chunk_id TEXT NOT NULL REFERENCES memory_chunk(id) ON DELETE CASCADE,
329
+ embedding_set_id TEXT NOT NULL REFERENCES memory_embedding_set(id) ON DELETE CASCADE,
330
+ vector BLOB NOT NULL,
331
+ model TEXT NOT NULL DEFAULT '',
332
+ dimensions INTEGER NOT NULL DEFAULT 0,
333
+ input_hash TEXT NOT NULL DEFAULT '',
334
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
335
+ UNIQUE(chunk_id, embedding_set_id)
336
+ );
337
+
338
+ CREATE INDEX IF NOT EXISTS idx_memory_embedding_v2_chunk
339
+ ON memory_embedding_v2 (chunk_id);
340
+ CREATE INDEX IF NOT EXISTS idx_memory_embedding_v2_set
341
+ ON memory_embedding_v2 (embedding_set_id);
342
+
343
+ CREATE TABLE IF NOT EXISTS memory_link_v2 (
344
+ id TEXT PRIMARY KEY,
345
+ source_item_id TEXT NOT NULL REFERENCES memory_item(id) ON DELETE CASCADE,
346
+ target_item_id TEXT NOT NULL REFERENCES memory_item(id) ON DELETE CASCADE,
347
+ link_type TEXT NOT NULL DEFAULT 'related',
348
+ weight REAL NOT NULL DEFAULT 0.5,
349
+ evidence TEXT NOT NULL DEFAULT '',
350
+ metadata_json TEXT NOT NULL DEFAULT '{}',
351
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
352
+ UNIQUE(source_item_id, target_item_id, link_type)
353
+ );
354
+
355
+ CREATE INDEX IF NOT EXISTS idx_memory_link_v2_source
356
+ ON memory_link_v2 (source_item_id);
357
+ CREATE INDEX IF NOT EXISTS idx_memory_link_v2_target
358
+ ON memory_link_v2 (target_item_id);
359
+
360
+ CREATE TABLE IF NOT EXISTS memory_provenance (
361
+ id TEXT PRIMARY KEY,
362
+ item_id TEXT NOT NULL REFERENCES memory_item(id) ON DELETE CASCADE,
363
+ source_type TEXT NOT NULL DEFAULT '',
364
+ source_id TEXT NOT NULL DEFAULT '',
365
+ source_uri TEXT NOT NULL DEFAULT '',
366
+ observed_at TEXT,
367
+ actor TEXT NOT NULL DEFAULT '',
368
+ metadata_json TEXT NOT NULL DEFAULT '{}',
369
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
370
+ );
371
+
372
+ CREATE INDEX IF NOT EXISTS idx_memory_provenance_item
373
+ ON memory_provenance (item_id);
374
+ CREATE INDEX IF NOT EXISTS idx_memory_provenance_source
375
+ ON memory_provenance (source_type, source_id);
376
+
377
+ CREATE TABLE IF NOT EXISTS memory_job_queue (
378
+ id TEXT PRIMARY KEY,
379
+ job_type TEXT NOT NULL DEFAULT '',
380
+ status TEXT NOT NULL DEFAULT 'pending',
381
+ item_id TEXT REFERENCES memory_item(id) ON DELETE CASCADE,
382
+ chunk_id TEXT REFERENCES memory_chunk(id) ON DELETE CASCADE,
383
+ attempts INTEGER NOT NULL DEFAULT 0,
384
+ payload_json TEXT NOT NULL DEFAULT '{}',
385
+ error TEXT NOT NULL DEFAULT '',
386
+ scheduled_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
387
+ started_at TEXT,
388
+ completed_at TEXT,
389
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
390
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
391
+ );
392
+
393
+ CREATE INDEX IF NOT EXISTS idx_memory_job_queue_status
394
+ ON memory_job_queue (status, scheduled_at);
395
+ CREATE INDEX IF NOT EXISTS idx_memory_job_queue_item
396
+ ON memory_job_queue (item_id);
397
+
398
+ -- Kept as a regular table because some packaged SQLite builds crash on
399
+ -- FTS5 virtual-table creation. The store maintains this table as the
400
+ -- lexical index and can be upgraded to FTS5 behind the same API later.
401
+ CREATE TABLE IF NOT EXISTS memory_fts (
402
+ chunk_id TEXT PRIMARY KEY,
403
+ item_id TEXT NOT NULL,
404
+ title TEXT NOT NULL DEFAULT '',
405
+ text TEXT NOT NULL DEFAULT '',
406
+ source TEXT NOT NULL DEFAULT '',
407
+ kind TEXT NOT NULL DEFAULT '',
408
+ scope_name TEXT NOT NULL DEFAULT ''
409
+ );
410
+
411
+ CREATE INDEX IF NOT EXISTS idx_memory_fts_item
412
+ ON memory_fts (item_id);
413
+ CREATE INDEX IF NOT EXISTS idx_memory_fts_scope
414
+ ON memory_fts (scope_name);
415
+
416
+ CREATE TABLE IF NOT EXISTS memory_graph_node (
417
+ id TEXT PRIMARY KEY,
418
+ scope_id TEXT NOT NULL REFERENCES memory_scope(id) ON DELETE CASCADE,
419
+ normalized_text TEXT NOT NULL,
420
+ text TEXT NOT NULL,
421
+ node_type TEXT NOT NULL DEFAULT 'concept',
422
+ weight REAL NOT NULL DEFAULT 0.5,
423
+ mention_count INTEGER NOT NULL DEFAULT 1,
424
+ metadata_json TEXT NOT NULL DEFAULT '{}',
425
+ first_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
426
+ last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
427
+ UNIQUE(scope_id, normalized_text, node_type)
428
+ );
429
+
430
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_node_scope
431
+ ON memory_graph_node (scope_id);
432
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_node_text
433
+ ON memory_graph_node (normalized_text);
434
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_node_type
435
+ ON memory_graph_node (node_type);
436
+
437
+ CREATE TABLE IF NOT EXISTS memory_item_node (
438
+ item_id TEXT NOT NULL REFERENCES memory_item(id) ON DELETE CASCADE,
439
+ node_id TEXT NOT NULL REFERENCES memory_graph_node(id) ON DELETE CASCADE,
440
+ relation TEXT NOT NULL DEFAULT 'mentions',
441
+ weight REAL NOT NULL DEFAULT 0.5,
442
+ evidence TEXT NOT NULL DEFAULT '',
443
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
444
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
445
+ PRIMARY KEY (item_id, node_id, relation)
446
+ );
447
+
448
+ CREATE INDEX IF NOT EXISTS idx_memory_item_node_item
449
+ ON memory_item_node (item_id);
450
+ CREATE INDEX IF NOT EXISTS idx_memory_item_node_node
451
+ ON memory_item_node (node_id);
452
+
453
+ CREATE TABLE IF NOT EXISTS memory_graph_edge (
454
+ id TEXT PRIMARY KEY,
455
+ scope_id TEXT NOT NULL REFERENCES memory_scope(id) ON DELETE CASCADE,
456
+ source_node_id TEXT NOT NULL REFERENCES memory_graph_node(id) ON DELETE CASCADE,
457
+ target_node_id TEXT NOT NULL REFERENCES memory_graph_node(id) ON DELETE CASCADE,
458
+ relation TEXT NOT NULL DEFAULT 'related_to',
459
+ weight REAL NOT NULL DEFAULT 0.5,
460
+ source_item_id TEXT REFERENCES memory_item(id) ON DELETE SET NULL,
461
+ evidence TEXT NOT NULL DEFAULT '',
462
+ metadata_json TEXT NOT NULL DEFAULT '{}',
463
+ valid_from TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
464
+ valid_until TEXT,
465
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
466
+ UNIQUE(scope_id, source_node_id, target_node_id, relation, source_item_id)
467
+ );
468
+
469
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_edge_scope
470
+ ON memory_graph_edge (scope_id);
471
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_edge_source
472
+ ON memory_graph_edge (source_node_id);
473
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_edge_target
474
+ ON memory_graph_edge (target_node_id);
475
+ CREATE INDEX IF NOT EXISTS idx_memory_graph_edge_valid
476
+ ON memory_graph_edge (valid_from, valid_until);
477
+
478
+ CREATE TABLE IF NOT EXISTS memory_retrieval_event (
479
+ id TEXT PRIMARY KEY,
480
+ query TEXT NOT NULL DEFAULT '',
481
+ query_hash TEXT NOT NULL DEFAULT '',
482
+ item_id TEXT REFERENCES memory_item(id) ON DELETE SET NULL,
483
+ chunk_id TEXT REFERENCES memory_chunk(id) ON DELETE SET NULL,
484
+ score REAL NOT NULL DEFAULT 0,
485
+ mode TEXT NOT NULL DEFAULT 'hybrid',
486
+ materialization TEXT NOT NULL DEFAULT 'skip',
487
+ outcome TEXT NOT NULL DEFAULT 'retrieved',
488
+ feedback TEXT NOT NULL DEFAULT '',
489
+ metadata_json TEXT NOT NULL DEFAULT '{}',
490
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
491
+ );
492
+
493
+ CREATE INDEX IF NOT EXISTS idx_memory_retrieval_event_query
494
+ ON memory_retrieval_event (query_hash);
495
+ CREATE INDEX IF NOT EXISTS idx_memory_retrieval_event_item
496
+ ON memory_retrieval_event (item_id);
497
+ CREATE INDEX IF NOT EXISTS idx_memory_retrieval_event_outcome
498
+ ON memory_retrieval_event (outcome);
499
+
500
+ CREATE TABLE IF NOT EXISTS memory_weight_event (
501
+ id TEXT PRIMARY KEY,
502
+ item_id TEXT NOT NULL REFERENCES memory_item(id) ON DELETE CASCADE,
503
+ chunk_id TEXT REFERENCES memory_chunk(id) ON DELETE SET NULL,
504
+ delta REAL NOT NULL DEFAULT 0,
505
+ reason TEXT NOT NULL DEFAULT '',
506
+ signal TEXT NOT NULL DEFAULT '',
507
+ metadata_json TEXT NOT NULL DEFAULT '{}',
508
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
509
+ );
510
+
511
+ CREATE INDEX IF NOT EXISTS idx_memory_weight_event_item
512
+ ON memory_weight_event (item_id);
513
+ CREATE INDEX IF NOT EXISTS idx_memory_weight_event_signal
514
+ ON memory_weight_event (signal);
515
+ `);
516
+ }
517
+
518
+ // packages/memory/dist/episodeStore.js
519
+ import { join } from "node:path";
520
+ import { mkdirSync, existsSync } from "node:fs";
521
+ import { randomUUID } from "node:crypto";
522
+ import { createHash } from "node:crypto";
523
+
524
+ // packages/memory/dist/zettelkasten.js
525
+ var DEFAULT_CONFIG = {
526
+ topK: 3,
527
+ minSimilarity: 0.3,
528
+ evolutionThreshold: 0.6,
529
+ maxLinksPerEpisode: 10
530
+ };
531
+ function cosineSimilarity(a, b) {
532
+ if (a.length !== b.length || a.length === 0)
533
+ return 0;
534
+ let dot = 0, normA = 0, normB = 0;
535
+ for (let i = 0; i < a.length; i++) {
536
+ dot += a[i] * b[i];
537
+ normA += a[i] * a[i];
538
+ normB += b[i] * b[i];
539
+ }
540
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
541
+ return denom > 0 ? dot / denom : 0;
542
+ }
543
+ function hasAssociativeEmbedding(episode) {
544
+ return Boolean(episode.embedding || episode.clipEmbedding);
545
+ }
546
+ function associativeSimilarity(episode, candidate) {
547
+ if (episode.embedding && candidate.embedding && episode.embedding.length === candidate.embedding.length) {
548
+ return cosineSimilarity(episode.embedding, candidate.embedding);
549
+ }
550
+ if (episode.clipEmbedding && candidate.clipEmbedding && episode.clipEmbedding.length === candidate.clipEmbedding.length) {
551
+ return cosineSimilarity(episode.clipEmbedding, candidate.clipEmbedding);
552
+ }
553
+ return null;
554
+ }
555
+ function findNeighbors(episode, candidates, topK, minSimilarity) {
556
+ if (!hasAssociativeEmbedding(episode))
557
+ return [];
558
+ const scored = [];
559
+ for (const candidate of candidates) {
560
+ if (candidate.id === episode.id)
561
+ continue;
562
+ if (!hasAssociativeEmbedding(candidate))
563
+ continue;
564
+ const sim = associativeSimilarity(episode, candidate);
565
+ if (sim == null)
566
+ continue;
567
+ if (sim >= minSimilarity) {
568
+ scored.push({ episode: candidate, similarity: sim });
569
+ }
570
+ }
571
+ scored.sort((a, b) => b.similarity - a.similarity);
572
+ return scored.slice(0, topK);
573
+ }
574
+ function linkEpisode(episode, episodeStore, graph, config) {
575
+ const cfg = { ...DEFAULT_CONFIG, ...config };
576
+ const result = { episodeId: episode.id, linkedTo: [], linksCreated: 0 };
577
+ if (!hasAssociativeEmbedding(episode))
578
+ return result;
579
+ const candidates = episodeStore.recent(500).filter(hasAssociativeEmbedding);
580
+ const neighbors = findNeighbors(episode, candidates, cfg.topK, cfg.minSimilarity);
581
+ for (const { episode: neighbor, similarity } of neighbors) {
582
+ if (result.linksCreated >= cfg.maxLinksPerEpisode)
583
+ break;
584
+ const srcNodeId = graph.upsertNode({
585
+ text: episode.content.slice(0, 100),
586
+ nodeType: "event"
587
+ });
588
+ const dstNodeId = graph.upsertNode({
589
+ text: neighbor.content.slice(0, 100),
590
+ nodeType: "event"
591
+ });
592
+ graph.addEdge({
593
+ srcId: srcNodeId,
594
+ dstId: dstNodeId,
595
+ relation: "related_to",
596
+ fact: `Associated: ${episode.content.slice(0, 50)} ↔ ${neighbor.content.slice(0, 50)}`,
597
+ edgeType: "synonym",
598
+ confidence: similarity,
599
+ sourceEpisodeId: episode.id
600
+ });
601
+ result.linkedTo.push({ neighborId: neighbor.id, similarity, linked: true });
602
+ result.linksCreated++;
603
+ if (similarity >= cfg.evolutionThreshold && neighbor.gist !== episode.content.slice(0, 200)) {
604
+ const evolvedGist = neighbor.gist ? `${neighbor.gist} | Also related: ${episode.content.slice(0, 100)}` : `Related context: ${episode.content.slice(0, 150)}`;
605
+ episodeStore.setGist(neighbor.id, evolvedGist.slice(0, 500));
606
+ }
607
+ }
608
+ return result;
609
+ }
610
+
611
+ // packages/memory/dist/homeostaticRegulation.js
612
+ var DEFAULT_ENCODING = {
613
+ valenceWeight: 0.3,
614
+ arousalWeight: 0.2,
615
+ maxMultiplier: 1.5
616
+ };
617
+ var DEFAULT_RETRIEVAL = {
618
+ valenceCongruence: 0.5,
619
+ arousalCongruence: 0.3
620
+ };
621
+ var clampValence = (v) => clamp(v, -1, 1);
622
+ var clampArousal = (a) => clamp(a, 0, 1);
623
+ function clamp(x, lo, hi) {
624
+ if (!Number.isFinite(x))
625
+ return (lo + hi) / 2;
626
+ if (x < lo)
627
+ return lo;
628
+ if (x > hi)
629
+ return hi;
630
+ return x;
631
+ }
632
+ function sanitizeEmotionalState(raw) {
633
+ if (!raw)
634
+ return { valence: 0, arousal: 0 };
635
+ return {
636
+ valence: clampValence(raw.valence ?? 0),
637
+ arousal: clampArousal(raw.arousal ?? 0),
638
+ emotion: typeof raw.emotion === "string" ? raw.emotion : void 0
639
+ };
640
+ }
641
+ function importanceMultiplier(state, tuning = DEFAULT_ENCODING) {
642
+ const v = clampValence(state.valence);
643
+ const a = clampArousal(state.arousal);
644
+ const valenceBoost = Math.max(0, v) * tuning.valenceWeight;
645
+ const arousalBoost = a * tuning.arousalWeight;
646
+ const raw = 1 + valenceBoost + arousalBoost;
647
+ return Math.min(raw, tuning.maxMultiplier);
648
+ }
649
+ function modulateImportance(baseImportance, state, tuning = DEFAULT_ENCODING) {
650
+ if (!state)
651
+ return baseImportance;
652
+ const mult = importanceMultiplier(state, tuning);
653
+ const adjusted = baseImportance * mult;
654
+ if (adjusted < 0)
655
+ return 0;
656
+ if (adjusted > 10)
657
+ return 10;
658
+ return adjusted;
659
+ }
660
+ function congruenceMultiplier(current, memory, tuning = DEFAULT_RETRIEVAL) {
661
+ const valenceDist = Math.abs(clampValence(current.valence) - clampValence(memory.valence));
662
+ const arousalDist = Math.abs(clampArousal(current.arousal) - clampArousal(memory.arousal));
663
+ const valenceSim = 1 - valenceDist / 2;
664
+ const arousalSim = 1 - arousalDist;
665
+ const valenceBonus = (valenceSim - 0.5) * tuning.valenceCongruence;
666
+ const arousalBonus = (arousalSim - 0.5) * tuning.arousalCongruence;
667
+ return 1 + valenceBonus + arousalBonus;
668
+ }
669
+ function modulateRetrievalScore(baseScore, current, memory, tuning = DEFAULT_RETRIEVAL) {
670
+ if (!current || !memory)
671
+ return baseScore;
672
+ return baseScore * congruenceMultiplier(current, memory, tuning);
673
+ }
674
+
675
+ // packages/memory/dist/socialInfluence.js
676
+ function clamp2(x, lo, hi) {
677
+ if (!Number.isFinite(x))
678
+ return (lo + hi) / 2;
679
+ if (x < lo)
680
+ return lo;
681
+ if (x > hi)
682
+ return hi;
683
+ return x;
684
+ }
685
+ function clamp012(x) {
686
+ return clamp2(x, 0, 1);
687
+ }
688
+ function conformityBias(myConfidence, groupAgreement, memoryAlignsWithGroup, weight = 0.5) {
689
+ const conf = clamp012(myConfidence);
690
+ const agree = clamp012(groupAgreement);
691
+ const w = clamp012(weight);
692
+ const pull = (agree - 0.5) * 2 * conf * w;
693
+ return memoryAlignsWithGroup ? 1 + pull : 1 - pull;
694
+ }
695
+ function authorityBias(sourceTrust, weight = 0.4) {
696
+ const t = clamp012(sourceTrust);
697
+ const w = clamp012(weight);
698
+ return 1 + (t - 0.5) * 2 * w;
699
+ }
700
+ function noveltyBias(memoryDivergence, weight = 0.3) {
701
+ const d = clamp012(memoryDivergence);
702
+ const w = clamp012(weight);
703
+ return 1 + d * w;
704
+ }
705
+ function reciprocityBias(relationshipStrength, relationshipValence, weight = 0.3) {
706
+ const s = clamp012(relationshipStrength);
707
+ const v = clamp2(relationshipValence, -1, 1);
708
+ const w = clamp012(weight);
709
+ return 1 + s * v * w;
710
+ }
711
+ function applyInfluence(baseScore, ctx) {
712
+ let m = 1;
713
+ if (ctx.myConfidence != null && ctx.groupAgreement != null && ctx.memoryAlignsWithGroup != null) {
714
+ m *= conformityBias(ctx.myConfidence, ctx.groupAgreement, ctx.memoryAlignsWithGroup, ctx.weights?.conformity);
715
+ }
716
+ if (ctx.sourceTrust != null) {
717
+ m *= authorityBias(ctx.sourceTrust, ctx.weights?.authority);
718
+ }
719
+ if (ctx.memoryDivergence != null) {
720
+ m *= noveltyBias(ctx.memoryDivergence, ctx.weights?.novelty);
721
+ }
722
+ if (ctx.relationshipStrength != null && ctx.relationshipValence != null) {
723
+ m *= reciprocityBias(ctx.relationshipStrength, ctx.relationshipValence, ctx.weights?.reciprocity);
724
+ }
725
+ return baseScore * m;
726
+ }
727
+
728
+ // packages/memory/dist/embodiedTrace.js
729
+ var NEUTRAL_AFFECT = { valence: 0, arousal: 0 };
730
+ var DEFAULT_TRACE = {
731
+ durationMs: 0,
732
+ hesitationCount: 0,
733
+ retryCount: 0,
734
+ intensity: 0,
735
+ confidence: 0.5,
736
+ cognitiveLoad: 0,
737
+ contextTokensUsed: 0,
738
+ contextTokensMax: 0,
739
+ affect: NEUTRAL_AFFECT,
740
+ timeOfDay: 0,
741
+ sessionDurationMin: 0,
742
+ consecutiveActions: 0
743
+ };
744
+ function clamp3(x, lo, hi) {
745
+ if (!Number.isFinite(x))
746
+ return (lo + hi) / 2;
747
+ if (x < lo)
748
+ return lo;
749
+ if (x > hi)
750
+ return hi;
751
+ return x;
752
+ }
753
+ function clamp013(x) {
754
+ return clamp3(x, 0, 1);
755
+ }
756
+ function nonNegative(x) {
757
+ if (!Number.isFinite(x) || x < 0)
758
+ return 0;
759
+ return x;
760
+ }
761
+ function buildTrace(input) {
762
+ const i = input ?? {};
763
+ const used = nonNegative(i.contextTokensUsed ?? DEFAULT_TRACE.contextTokensUsed);
764
+ const max = nonNegative(i.contextTokensMax ?? DEFAULT_TRACE.contextTokensMax);
765
+ const derivedLoad = max > 0 ? used / max : 0;
766
+ const load = i.cognitiveLoad != null ? clamp013(i.cognitiveLoad) : clamp013(derivedLoad);
767
+ return {
768
+ durationMs: nonNegative(i.durationMs ?? DEFAULT_TRACE.durationMs),
769
+ hesitationCount: Math.max(0, Math.floor(i.hesitationCount ?? 0)),
770
+ retryCount: Math.max(0, Math.floor(i.retryCount ?? 0)),
771
+ intensity: clamp013(i.intensity ?? DEFAULT_TRACE.intensity),
772
+ visualGist: typeof i.visualGist === "string" ? i.visualGist : void 0,
773
+ auditoryGist: typeof i.auditoryGist === "string" ? i.auditoryGist : void 0,
774
+ screenState: typeof i.screenState === "string" ? i.screenState : void 0,
775
+ confidence: clamp013(i.confidence ?? DEFAULT_TRACE.confidence),
776
+ cognitiveLoad: load,
777
+ contextTokensUsed: used,
778
+ contextTokensMax: max,
779
+ affect: sanitizeEmotionalState(i.affect ?? NEUTRAL_AFFECT),
780
+ timeOfDay: clamp3(Math.floor(i.timeOfDay ?? DEFAULT_TRACE.timeOfDay), 0, 23),
781
+ sessionDurationMin: nonNegative(i.sessionDurationMin ?? 0),
782
+ consecutiveActions: Math.max(0, Math.floor(i.consecutiveActions ?? 0))
783
+ };
784
+ }
785
+ var EMBODIED_KEY = "embodiedTrace";
786
+ function extractTrace(metadata) {
787
+ if (!metadata || typeof metadata !== "object")
788
+ return null;
789
+ const raw = metadata[EMBODIED_KEY];
790
+ if (!raw || typeof raw !== "object")
791
+ return null;
792
+ return buildTrace(raw);
793
+ }
794
+ function engagementScore(trace) {
795
+ const alertness = 1 - trace.cognitiveLoad;
796
+ return clamp013((trace.intensity + alertness + trace.confidence) / 3);
797
+ }
798
+
799
+ // packages/memory/dist/pprRetrieval.js
800
+ function readEpisodeAffect(metadata) {
801
+ if (!metadata || typeof metadata !== "object")
802
+ return null;
803
+ const trace = metadata["embodiedTrace"];
804
+ if (!trace || typeof trace !== "object")
805
+ return null;
806
+ const affect = trace["affect"];
807
+ if (!affect || typeof affect !== "object")
808
+ return null;
809
+ return sanitizeEmotionalState(affect);
810
+ }
811
+ var DEFAULT_CONFIG2 = {
812
+ damping: 0.5,
813
+ maxIterations: 50,
814
+ convergenceThreshold: 1e-6,
815
+ topK: 10
816
+ };
817
+ function extractQueryEntities(query) {
818
+ const entities = [];
819
+ const seen = /* @__PURE__ */ new Set();
820
+ const add = (text) => {
821
+ const normalized = text.trim().toLowerCase();
822
+ if (normalized.length < 2 || seen.has(normalized))
823
+ return;
824
+ seen.add(normalized);
825
+ entities.push(text.trim());
826
+ };
827
+ const filePattern = /(?:[\w./-]+\/[\w./-]+|[\w-]+\.\w{1,5})\b/g;
828
+ for (const match of query.matchAll(filePattern)) {
829
+ if (match[0].includes(".") || match[0].includes("/"))
830
+ add(match[0]);
831
+ }
832
+ const errorPattern = /\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b|\b\w+(?:Error|Exception|Failed|Failure)\b/g;
833
+ for (const match of query.matchAll(errorPattern))
834
+ add(match[0]);
835
+ const camelPattern = /\b[a-z][a-z0-9]*(?:[A-Z][a-zA-Z0-9]+)+\b/g;
836
+ for (const match of query.matchAll(camelPattern))
837
+ add(match[0]);
838
+ const toolPattern = /\b(?:file_read|file_write|file_edit|shell|grep_search|find_files|memory_\w+|web_\w+|camera_\w+|audio_\w+|wifi_\w+|bluetooth_\w+|sdr_\w+|flipper_\w+|meshtastic|gps_\w+|visual_\w+)\b/g;
839
+ for (const match of query.matchAll(toolPattern))
840
+ add(match[0]);
841
+ const quotedPattern = /["']([^"']{2,50})["']/g;
842
+ for (const match of query.matchAll(quotedPattern))
843
+ add(match[1]);
844
+ const STOP_WORDS = /* @__PURE__ */ new Set([
845
+ "the",
846
+ "and",
847
+ "for",
848
+ "was",
849
+ "are",
850
+ "not",
851
+ "but",
852
+ "had",
853
+ "has",
854
+ "have",
855
+ "this",
856
+ "that",
857
+ "with",
858
+ "from",
859
+ "what",
860
+ "when",
861
+ "where",
862
+ "how",
863
+ "why",
864
+ "which",
865
+ "who",
866
+ "did",
867
+ "does",
868
+ "can",
869
+ "could",
870
+ "would",
871
+ "should",
872
+ "will",
873
+ "been",
874
+ "being",
875
+ "were",
876
+ "about",
877
+ "than",
878
+ "then",
879
+ "also",
880
+ "just",
881
+ "more",
882
+ "some",
883
+ "only",
884
+ "into",
885
+ "over"
886
+ ]);
887
+ const words = query.split(/\s+/).filter((w) => w.length >= 4 && !STOP_WORDS.has(w.toLowerCase()));
888
+ for (const word of words) {
889
+ if (/^[A-Z]/.test(word) || /[._-]/.test(word) || /\d/.test(word)) {
890
+ add(word.replace(/[.,;:!?]+$/, ""));
891
+ }
892
+ }
893
+ return entities;
894
+ }
895
+ function personalizedPageRank(graph, seedNodeIds, config) {
896
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
897
+ const scores = /* @__PURE__ */ new Map();
898
+ if (seedNodeIds.length === 0)
899
+ return scores;
900
+ const nodeCount = graph.nodeCount();
901
+ if (nodeCount === 0)
902
+ return scores;
903
+ const teleportProb = 1 / seedNodeIds.length;
904
+ const seedSet = new Set(seedNodeIds);
905
+ const allNodes = /* @__PURE__ */ new Set();
906
+ for (const seedId of seedNodeIds) {
907
+ allNodes.add(seedId);
908
+ const neighbors = graph.neighbors(seedId);
909
+ for (const { node } of neighbors) {
910
+ allNodes.add(node.id);
911
+ const hop2 = graph.neighbors(node.id);
912
+ for (const { node: n2 } of hop2)
913
+ allNodes.add(n2.id);
914
+ }
915
+ }
916
+ for (const nodeId of allNodes) {
917
+ scores.set(nodeId, seedSet.has(nodeId) ? teleportProb : 0);
918
+ }
919
+ for (let iter = 0; iter < cfg.maxIterations; iter++) {
920
+ const newScores = /* @__PURE__ */ new Map();
921
+ let maxDelta = 0;
922
+ for (const nodeId of allNodes) {
923
+ const teleport = seedSet.has(nodeId) ? (1 - cfg.damping) * teleportProb : 0;
924
+ let walkSum = 0;
925
+ const inEdges = graph.currentEdges(nodeId);
926
+ for (const edge of inEdges) {
927
+ const neighborId = edge.srcId === nodeId ? edge.dstId : edge.srcId;
928
+ const neighborScore = scores.get(neighborId) ?? 0;
929
+ const neighborOutDegree = graph.currentEdges(neighborId).length || 1;
930
+ walkSum += neighborScore / neighborOutDegree;
931
+ }
932
+ const newScore = teleport + cfg.damping * walkSum;
933
+ newScores.set(nodeId, newScore);
934
+ const delta = Math.abs(newScore - (scores.get(nodeId) ?? 0));
935
+ if (delta > maxDelta)
936
+ maxDelta = delta;
937
+ }
938
+ for (const [id, score] of newScores)
939
+ scores.set(id, score);
940
+ if (maxDelta < cfg.convergenceThreshold) {
941
+ return scores;
942
+ }
943
+ }
944
+ return scores;
945
+ }
946
+ function retrieveByPPR(query, graph, episodeStore, config) {
947
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
948
+ const queryEntities = extractQueryEntities(query);
949
+ if (queryEntities.length === 0) {
950
+ return { episodes: [], queryEntities: [], seedNodes: [], iterations: 0 };
951
+ }
952
+ const seedNodes = [];
953
+ for (const entity of queryEntities) {
954
+ const seedsBefore = seedNodes.length;
955
+ const node = graph.findNode(entity);
956
+ if (node) {
957
+ seedNodes.push(node.id);
958
+ }
959
+ const symbolNodes = graph.findSymbolNodesByName(entity, 10);
960
+ for (const s of symbolNodes)
961
+ seedNodes.push(s.id);
962
+ if (seedNodes.length > seedsBefore)
963
+ continue;
964
+ for (const nodeType of ["file", "error", "tool", "entity", "event", "concept", "person"]) {
965
+ const candidates = graph.nodesByType(nodeType, 50);
966
+ for (const candidate of candidates) {
967
+ if (candidate.text.toLowerCase().includes(entity.toLowerCase()) || entity.toLowerCase().includes(candidate.text.toLowerCase())) {
968
+ seedNodes.push(candidate.id);
969
+ break;
970
+ }
971
+ }
972
+ if (seedNodes.length > seedsBefore)
973
+ break;
974
+ }
975
+ }
976
+ if (seedNodes.length === 0) {
977
+ return { episodes: [], queryEntities, seedNodes: [], iterations: 0 };
978
+ }
979
+ const pprScores = personalizedPageRank(graph, seedNodes, cfg);
980
+ const episodeScores = /* @__PURE__ */ new Map();
981
+ for (const [nodeId, score] of pprScores) {
982
+ if (score < 1e-3)
983
+ continue;
984
+ const node = graph.getNode(nodeId);
985
+ if (!node)
986
+ continue;
987
+ const edges = graph.currentEdges(nodeId);
988
+ for (const edge of edges) {
989
+ if (edge.sourceEpisodeId) {
990
+ const existing = episodeScores.get(edge.sourceEpisodeId);
991
+ if (existing) {
992
+ existing.score += score;
993
+ existing.matchedNodes.push(node.text);
994
+ } else {
995
+ episodeScores.set(edge.sourceEpisodeId, { score, matchedNodes: [node.text] });
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ let entries = [...episodeScores.entries()];
1001
+ const engagementWeight = cfg.engagementWeight ?? 0;
1002
+ if (cfg.currentEmotionalState || cfg.influenceFor || engagementWeight !== 0) {
1003
+ const current = cfg.currentEmotionalState;
1004
+ entries = entries.map(([epId, payload]) => {
1005
+ const ep = episodeStore.get(epId);
1006
+ let score = payload.score;
1007
+ if (current) {
1008
+ const memoryAffect = ep ? readEpisodeAffect(ep.metadata) : null;
1009
+ if (memoryAffect)
1010
+ score = modulateRetrievalScore(score, current, memoryAffect);
1011
+ }
1012
+ if (cfg.influenceFor) {
1013
+ const ctx = cfg.influenceFor(epId);
1014
+ if (ctx)
1015
+ score = applyInfluence(score, ctx);
1016
+ }
1017
+ if (engagementWeight !== 0 && ep) {
1018
+ const trace = extractTrace(ep.metadata);
1019
+ if (trace) {
1020
+ const eng = engagementScore(trace);
1021
+ score = score * (1 + (eng - 0.5) * engagementWeight);
1022
+ }
1023
+ }
1024
+ return [epId, { ...payload, score }];
1025
+ });
1026
+ }
1027
+ const ranked = entries.sort((a, b) => b[1].score - a[1].score).slice(0, cfg.topK);
1028
+ const episodes = [];
1029
+ for (const [epId, { score, matchedNodes }] of ranked) {
1030
+ const episode = episodeStore.get(epId);
1031
+ if (episode) {
1032
+ episodes.push({ episode, pprScore: score, matchedNodes: [...new Set(matchedNodes)] });
1033
+ }
1034
+ }
1035
+ return {
1036
+ episodes,
1037
+ queryEntities,
1038
+ seedNodes,
1039
+ iterations: cfg.maxIterations
1040
+ };
1041
+ }
1042
+
1043
+ // packages/memory/dist/episodeStore.js
1044
+ function readEpisodeAffect2(metadata) {
1045
+ if (!metadata || typeof metadata !== "object")
1046
+ return null;
1047
+ const trace = metadata["embodiedTrace"];
1048
+ if (!trace || typeof trace !== "object")
1049
+ return null;
1050
+ const affect = trace["affect"];
1051
+ if (!affect || typeof affect !== "object")
1052
+ return null;
1053
+ return sanitizeEmotionalState(affect);
1054
+ }
1055
+ function readEpisodeEngagement(metadata) {
1056
+ if (!metadata || typeof metadata !== "object")
1057
+ return null;
1058
+ const trace = metadata["embodiedTrace"];
1059
+ if (!trace || typeof trace !== "object")
1060
+ return null;
1061
+ const t = trace;
1062
+ const intensity = typeof t["intensity"] === "number" ? clamp01Local(t["intensity"]) : 0;
1063
+ const cogLoad = typeof t["cognitiveLoad"] === "number" ? clamp01Local(t["cognitiveLoad"]) : 0;
1064
+ const conf = typeof t["confidence"] === "number" ? clamp01Local(t["confidence"]) : 0.5;
1065
+ const alertness = 1 - cogLoad;
1066
+ return clamp01Local((intensity + alertness + conf) / 3);
1067
+ }
1068
+ function clamp01Local(x) {
1069
+ if (!Number.isFinite(x))
1070
+ return 0;
1071
+ if (x < 0)
1072
+ return 0;
1073
+ if (x > 1)
1074
+ return 1;
1075
+ return x;
1076
+ }
1077
+ function stableJson(value) {
1078
+ if (value === null || typeof value !== "object") {
1079
+ return JSON.stringify(value);
1080
+ }
1081
+ if (Array.isArray(value)) {
1082
+ return `[${value.map((item) => stableJson(item)).join(",")}]`;
1083
+ }
1084
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
1085
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableJson(v)}`).join(",")}}`;
1086
+ }
1087
+ function episodeIdentityHash(ep) {
1088
+ const identity = ep.metadata ? `${ep.content}\0metadata:${stableJson(ep.metadata)}` : ep.content;
1089
+ return createHash("sha256").update(identity).digest("hex").slice(0, 16);
1090
+ }
1091
+ var DECAY_TAU = {
1092
+ session: 36e5,
1093
+ // 1 hour
1094
+ daily: 864e5,
1095
+ // 1 day
1096
+ procedural: 2592e6,
1097
+ // 30 days
1098
+ permanent: Infinity
1099
+ };
1100
+ function sanitizeImportance(raw, fallback = 5) {
1101
+ if (typeof raw !== "number" || !Number.isFinite(raw))
1102
+ return fallback;
1103
+ if (raw < 0)
1104
+ return 0;
1105
+ if (raw > 10)
1106
+ return 10;
1107
+ return raw;
1108
+ }
1109
+ function autoImportance(toolName, modality, content) {
1110
+ if (toolName === "task_complete")
1111
+ return 9;
1112
+ if (["file_write", "file_edit", "file_patch", "batch_edit"].includes(toolName ?? ""))
1113
+ return 8;
1114
+ if (content.toLowerCase().includes("error") || content.toLowerCase().includes("failed"))
1115
+ return 6;
1116
+ if (["grep_search", "find_files", "codebase_map", "diagnostic"].includes(toolName ?? ""))
1117
+ return 5;
1118
+ if (toolName === "shell")
1119
+ return 5;
1120
+ if (toolName?.startsWith("memory_"))
1121
+ return 6;
1122
+ if (modality === "social")
1123
+ return 9;
1124
+ if (modality === "visual")
1125
+ return 7;
1126
+ if (modality === "audio")
1127
+ return 6;
1128
+ if (modality === "spatial")
1129
+ return 5;
1130
+ if (modality === "reflection")
1131
+ return 7;
1132
+ if (modality === "gist")
1133
+ return 8;
1134
+ if (toolName === "file_read")
1135
+ return 3;
1136
+ return 5;
1137
+ }
1138
+ function autoDecayClass(toolName, modality, content) {
1139
+ if (toolName === "task_complete")
1140
+ return "procedural";
1141
+ if (toolName?.startsWith("memory_"))
1142
+ return "procedural";
1143
+ if (["file_write", "file_edit", "file_patch"].includes(toolName ?? ""))
1144
+ return "daily";
1145
+ if (content.toLowerCase().includes("error") || content.toLowerCase().includes("failed"))
1146
+ return "procedural";
1147
+ if (modality === "social")
1148
+ return "permanent";
1149
+ if (modality === "visual")
1150
+ return "procedural";
1151
+ if (modality === "spatial")
1152
+ return "procedural";
1153
+ if (modality === "audio")
1154
+ return "daily";
1155
+ if (modality === "reflection" || modality === "gist")
1156
+ return "procedural";
1157
+ if (toolName === "file_read" || toolName === "list_directory")
1158
+ return "session";
1159
+ return "daily";
1160
+ }
1161
+ var EpisodeStore = class {
1162
+ db;
1163
+ /** Read-only access to the underlying SQLite handle for cross-cutting tools
1164
+ * like SelfModel that need to query multiple tables atomically. */
1165
+ getDb() {
1166
+ return this.db;
1167
+ }
1168
+ /** WO-AM-05: Optional TemporalGraph for Zettelkasten auto-linking */
1169
+ graph = null;
1170
+ /** WO-AM-05: Zettelkasten config for neighbor linking */
1171
+ zettelConfig = { topK: 5, minSimilarity: 0.7, evolutionThreshold: 0.6, maxLinksPerEpisode: 10 };
1172
+ /** AUDIT-3 (A2 completion): minimum importance an inserted episode must
1173
+ * meet (or beat) to actually persist. When 0 (default), all episodes
1174
+ * pass the gate — preserves backward-compatible behavior. The
1175
+ * orchestrator sets this from `MemoryStageContext.importanceFloor()`
1176
+ * so wisdom-stage agents store fewer routine episodes than
1177
+ * exploration-stage agents. */
1178
+ importanceFloor = 0;
1179
+ /** AUDIT-3: opt-in setter so the orchestrator can override the
1180
+ * hardcoded zettelConfig at session start (and after sleep
1181
+ * consolidation) with stage-derived values. */
1182
+ setZettelConfig(partial) {
1183
+ this.zettelConfig = { ...this.zettelConfig, ...partial };
1184
+ }
1185
+ setImportanceFloor(floor) {
1186
+ this.importanceFloor = Number.isFinite(floor) && floor > 0 ? floor : 0;
1187
+ }
1188
+ /** MEM_PATH item #2: bump reuse_count for an episode. Called by the
1189
+ * orchestrator when an episode that was retrieved is detected to have
1190
+ * been mentioned in a subsequent assistant turn. Idempotent on missing
1191
+ * ids (no row → no-op). Returns the new count, or null if not found. */
1192
+ bumpReuseCount(id, delta = 1) {
1193
+ const safeDelta = Number.isFinite(delta) ? delta : 1;
1194
+ try {
1195
+ const result = this.db.prepare(`UPDATE episodes SET reuse_count = COALESCE(reuse_count, 0) + ? WHERE id = ?`).run(safeDelta, id);
1196
+ if (result.changes === 0)
1197
+ return null;
1198
+ const row = this.db.prepare(`SELECT reuse_count FROM episodes WHERE id = ?`).get(id);
1199
+ return row?.reuse_count ?? null;
1200
+ } catch {
1201
+ return null;
1202
+ }
1203
+ }
1204
+ constructor(dbPath, graph, zettelConfig) {
1205
+ const dir = dbPath === ":memory:" ? null : join(dbPath, "..");
1206
+ if (dir && !existsSync(dir))
1207
+ mkdirSync(dir, { recursive: true });
1208
+ this.db = initDb(dbPath);
1209
+ this.graph = graph ?? null;
1210
+ if (zettelConfig) {
1211
+ this.zettelConfig = { ...this.zettelConfig, ...zettelConfig };
1212
+ }
1213
+ this.db.exec(`
1214
+ CREATE TABLE IF NOT EXISTS episodes (
1215
+ id TEXT PRIMARY KEY,
1216
+ timestamp INTEGER NOT NULL,
1217
+ session_id TEXT,
1218
+ task_id TEXT,
1219
+ turn_number INTEGER,
1220
+ modality TEXT DEFAULT 'text',
1221
+ tool_name TEXT,
1222
+ content TEXT NOT NULL,
1223
+ content_hash TEXT,
1224
+ metadata TEXT,
1225
+ embedding BLOB,
1226
+ importance REAL DEFAULT 5.0,
1227
+ decay_class TEXT DEFAULT 'daily',
1228
+ strength REAL DEFAULT 1.0,
1229
+ last_retrieved INTEGER,
1230
+ gist TEXT,
1231
+ source_episode_id TEXT
1232
+ )
1233
+ `);
1234
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_ep_ts ON episodes(timestamp)`);
1235
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_ep_session ON episodes(session_id)`);
1236
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_ep_tool ON episodes(tool_name)`);
1237
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_ep_decay ON episodes(decay_class)`);
1238
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_ep_hash ON episodes(content_hash, session_id)`);
1239
+ try {
1240
+ this.db.exec(`
1241
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ep_hash_unique
1242
+ ON episodes(content_hash, COALESCE(session_id, ''), COALESCE(tool_name, ''))
1243
+ WHERE content_hash IS NOT NULL AND content_hash != ''
1244
+ `);
1245
+ } catch (err) {
1246
+ if (process.env["OMNIUS_SUPPRESS_INDEX_WARN"] !== "1" && process.env["NODE_ENV"] !== "test") {
1247
+ const msg = err instanceof Error ? err.message : String(err);
1248
+ console.warn(`[episodeStore] failed to install idx_ep_hash_unique: ${msg}. Run \`node scripts/dedupe-omnius-memory.mjs --execute\` to remove existing dupes first.`);
1249
+ }
1250
+ }
1251
+ try {
1252
+ this.db.exec(`ALTER TABLE episodes ADD COLUMN clip_embedding BLOB`);
1253
+ } catch {
1254
+ }
1255
+ try {
1256
+ this.db.exec(`ALTER TABLE episodes ADD COLUMN reuse_count INTEGER DEFAULT 0`);
1257
+ } catch {
1258
+ }
1259
+ this.db.pragma("journal_mode = WAL");
1260
+ this.db.pragma("synchronous = NORMAL");
1261
+ }
1262
+ /** Insert a new episode. Returns the episode ID. */
1263
+ insert(ep) {
1264
+ const id = randomUUID();
1265
+ const now = Date.now();
1266
+ const contentHash = episodeIdentityHash(ep);
1267
+ const modality = ep.modality ?? "text";
1268
+ const rawImportance = ep.importance ?? autoImportance(ep.toolName ?? null, modality, ep.content);
1269
+ const modulated = ep.emotionalState ? modulateImportance(sanitizeImportance(rawImportance), ep.emotionalState) : sanitizeImportance(rawImportance);
1270
+ const importance = sanitizeImportance(modulated);
1271
+ if (this.importanceFloor > 0 && importance < this.importanceFloor) {
1272
+ return "";
1273
+ }
1274
+ const decayClass = ep.decayClass ?? autoDecayClass(ep.toolName ?? null, modality, ep.content);
1275
+ const existing = this.db.prepare(`SELECT id FROM episodes
1276
+ WHERE content_hash = ?
1277
+ AND COALESCE(session_id, '') = COALESCE(?, '')
1278
+ AND COALESCE(tool_name, '') = COALESCE(?, '')
1279
+ LIMIT 1`).get(contentHash, ep.sessionId ?? null, ep.toolName ?? null);
1280
+ if (existing)
1281
+ return existing.id;
1282
+ this.db.prepare(`
1283
+ INSERT INTO episodes (id, timestamp, session_id, task_id, turn_number, modality, tool_name, content, content_hash, metadata, importance, decay_class, strength)
1284
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1.0)
1285
+ `).run(id, now, ep.sessionId ?? null, ep.taskId ?? null, ep.turnNumber ?? null, modality, ep.toolName ?? null, ep.content, contentHash, ep.metadata ? JSON.stringify(ep.metadata) : null, importance, decayClass);
1286
+ if (this.graph) {
1287
+ try {
1288
+ const episode = this.get(id);
1289
+ if (episode) {
1290
+ linkEpisode(episode, this, this.graph, this.zettelConfig);
1291
+ }
1292
+ } catch {
1293
+ }
1294
+ }
1295
+ return id;
1296
+ }
1297
+ /** Retrieve episodes matching a query, scored by recency + importance + (lexical + embedding). */
1298
+ search(query, opts = {}) {
1299
+ const now = Date.now();
1300
+ const limit = query.limit ?? 20;
1301
+ const qEmb = opts.queryEmbedding ?? null;
1302
+ const wLex = opts.lexicalWeight ?? 1;
1303
+ const wEmb = opts.embeddingWeight ?? 1;
1304
+ let sql = "SELECT * FROM episodes WHERE 1=1";
1305
+ const params = [];
1306
+ if (query.sessionId) {
1307
+ sql += " AND session_id = ?";
1308
+ params.push(query.sessionId);
1309
+ }
1310
+ if (query.taskId) {
1311
+ sql += " AND task_id = ?";
1312
+ params.push(query.taskId);
1313
+ }
1314
+ if (query.toolName) {
1315
+ sql += " AND tool_name = ?";
1316
+ params.push(query.toolName);
1317
+ }
1318
+ if (query.modality) {
1319
+ sql += " AND modality = ?";
1320
+ params.push(query.modality);
1321
+ }
1322
+ if (query.since) {
1323
+ sql += " AND timestamp >= ?";
1324
+ params.push(query.since);
1325
+ }
1326
+ if (query.until) {
1327
+ sql += " AND timestamp <= ?";
1328
+ params.push(query.until);
1329
+ }
1330
+ if (query.minImportance) {
1331
+ sql += " AND importance >= ?";
1332
+ params.push(query.minImportance);
1333
+ }
1334
+ sql += " ORDER BY timestamp DESC LIMIT ?";
1335
+ params.push(Math.min(limit * 5, 500));
1336
+ let rows = this.db.prepare(sql).all(...params);
1337
+ if (query.metadataFilter || query.soundClass || query.rmsRange) {
1338
+ rows = rows.filter((row) => {
1339
+ let meta = null;
1340
+ try {
1341
+ meta = row.metadata ? typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata : null;
1342
+ } catch {
1343
+ return false;
1344
+ }
1345
+ if (!meta)
1346
+ return false;
1347
+ if (query.metadataFilter) {
1348
+ for (const [k, v] of Object.entries(query.metadataFilter)) {
1349
+ if (meta[k] !== v)
1350
+ return false;
1351
+ }
1352
+ }
1353
+ if (query.soundClass && meta.sound_class !== query.soundClass)
1354
+ return false;
1355
+ if (query.rmsRange) {
1356
+ const rms = meta.rms_db ?? meta.rmsDb;
1357
+ if (typeof rms !== "number")
1358
+ return false;
1359
+ if (query.rmsRange.min !== void 0 && rms < query.rmsRange.min)
1360
+ return false;
1361
+ if (query.rmsRange.max !== void 0 && rms > query.rmsRange.max)
1362
+ return false;
1363
+ }
1364
+ return true;
1365
+ });
1366
+ }
1367
+ const scored = rows.map((row) => {
1368
+ const ep = this.rowToEpisode(row);
1369
+ const tau = DECAY_TAU[ep.decayClass] ?? DECAY_TAU.daily;
1370
+ const recency = tau === Infinity ? 1 : Math.exp(-(now - ep.timestamp) / tau);
1371
+ const importance = ep.importance / 10;
1372
+ let lex = 0;
1373
+ if (query.query) {
1374
+ const queryLower = query.query.toLowerCase();
1375
+ const contentLower = ep.content.toLowerCase();
1376
+ const queryTerms = queryLower.split(/\s+/).filter((t) => t.length > 2);
1377
+ const matched = queryTerms.filter((t) => contentLower.includes(t)).length;
1378
+ lex = queryTerms.length > 0 ? matched / queryTerms.length : 0;
1379
+ }
1380
+ let emb = 0;
1381
+ if (qEmb && ep.embedding && ep.embedding.length === qEmb.length) {
1382
+ const a = ep.embedding;
1383
+ const b = qEmb;
1384
+ let dot = 0, na = 0, nb = 0;
1385
+ for (let i = 0; i < a.length; i++) {
1386
+ const va = a[i], vb = b[i];
1387
+ dot += va * vb;
1388
+ na += va * va;
1389
+ nb += vb * vb;
1390
+ }
1391
+ const denom = Math.sqrt(na) * Math.sqrt(nb) || 1;
1392
+ emb = dot / denom;
1393
+ emb = Math.max(0, Math.min(1, (emb + 1) / 2));
1394
+ }
1395
+ const relevance = wLex * lex + wEmb * emb;
1396
+ const strengthBonus = Math.min(1, Math.log2(Math.max(1, ep.strength)));
1397
+ const baseScore = recency + importance + relevance + strengthBonus;
1398
+ let score = baseScore;
1399
+ if (opts.currentEmotionalState) {
1400
+ const memoryAffect = readEpisodeAffect2(ep.metadata);
1401
+ if (memoryAffect) {
1402
+ score = modulateRetrievalScore(score, opts.currentEmotionalState, memoryAffect);
1403
+ }
1404
+ }
1405
+ if (opts.influenceFor) {
1406
+ const ctx = opts.influenceFor(ep);
1407
+ if (ctx)
1408
+ score = applyInfluence(score, ctx);
1409
+ }
1410
+ if ((opts.engagementWeight ?? 0) !== 0) {
1411
+ const eng = readEpisodeEngagement(ep.metadata);
1412
+ if (eng != null) {
1413
+ score = score * (1 + (eng - 0.5) * (opts.engagementWeight ?? 0));
1414
+ }
1415
+ }
1416
+ return { episode: ep, score };
1417
+ });
1418
+ scored.sort((a, b) => b.score - a.score);
1419
+ const topK = scored.slice(0, limit);
1420
+ const updateStmt = this.db.prepare("UPDATE episodes SET strength = strength + 1, last_retrieved = ? WHERE id = ?");
1421
+ for (const { episode } of topK) {
1422
+ updateStmt.run(now, episode.id);
1423
+ }
1424
+ return topK.map((s) => s.episode);
1425
+ }
1426
+ /** WO-AM-05: PPR-enhanced search for multi-hop retrieval (HippoRAG).
1427
+ * Falls back to standard search if graph is unavailable or no entities found.
1428
+ */
1429
+ searchWithPPR(query, opts = {}) {
1430
+ const standardResults = this.search(query, opts);
1431
+ if (!this.graph || !query.query) {
1432
+ return standardResults;
1433
+ }
1434
+ try {
1435
+ const pprResult = retrieveByPPR(query.query, this.graph, this, {
1436
+ damping: 0.5,
1437
+ maxIterations: 50,
1438
+ convergenceThreshold: 1e-6,
1439
+ topK: query.limit ?? 20
1440
+ });
1441
+ const merged = [...standardResults];
1442
+ const seenIds = new Set(merged.map((e) => e.id));
1443
+ for (const { episode } of pprResult.episodes) {
1444
+ if (!seenIds.has(episode.id)) {
1445
+ merged.push(episode);
1446
+ seenIds.add(episode.id);
1447
+ }
1448
+ }
1449
+ return merged.slice(0, query.limit ?? 20);
1450
+ } catch {
1451
+ return standardResults;
1452
+ }
1453
+ }
1454
+ /** Get a single episode by ID. */
1455
+ get(id) {
1456
+ const row = this.db.prepare("SELECT * FROM episodes WHERE id = ?").get(id);
1457
+ return row ? this.rowToEpisode(row) : null;
1458
+ }
1459
+ /** Get recent episodes (for context assembly). */
1460
+ recent(limit = 10, sessionId) {
1461
+ const sql = sessionId ? "SELECT * FROM episodes WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?" : "SELECT * FROM episodes ORDER BY timestamp DESC LIMIT ?";
1462
+ const params = sessionId ? [sessionId, limit] : [limit];
1463
+ return this.db.prepare(sql).all(...params).map((r) => this.rowToEpisode(r));
1464
+ }
1465
+ /** Count episodes.
1466
+ *
1467
+ * BUG FIX (2026-04-07): previously passed `sessionId` to `.get()` even
1468
+ * when the SQL had no placeholder, causing `RangeError: Too many
1469
+ * parameter values were provided` on every `count()` call without args.
1470
+ * Now we only bind when the placeholder is actually present.
1471
+ */
1472
+ count(sessionId) {
1473
+ if (sessionId) {
1474
+ return this.db.prepare("SELECT COUNT(*) as n FROM episodes WHERE session_id = ?").get(sessionId).n;
1475
+ }
1476
+ return this.db.prepare("SELECT COUNT(*) as n FROM episodes").get().n;
1477
+ }
1478
+ /** Prune expired session-class episodes (call at session end). */
1479
+ pruneExpired() {
1480
+ const sessionExpiry = Date.now() - DECAY_TAU.session * 3;
1481
+ const result = this.db.prepare("DELETE FROM episodes WHERE decay_class = 'session' AND timestamp < ?").run(sessionExpiry);
1482
+ return result.changes;
1483
+ }
1484
+ /** Update embedding for an episode (async fill from WO-AM-10). */
1485
+ setEmbedding(id, embedding) {
1486
+ const buf = Buffer.from(embedding.buffer);
1487
+ this.db.prepare("UPDATE episodes SET embedding = ? WHERE id = ?").run(buf, id);
1488
+ }
1489
+ /** WO-AM-GAP-03: Set CLIP embedding for cross-modal retrieval (512d). */
1490
+ setClipEmbedding(id, clipEmbedding) {
1491
+ const buf = Buffer.from(clipEmbedding.buffer);
1492
+ this.db.prepare("UPDATE episodes SET clip_embedding = ? WHERE id = ?").run(buf, id);
1493
+ }
1494
+ /** Update gist for an episode (from WO-AM-07 gist compression). */
1495
+ setGist(id, gist) {
1496
+ this.db.prepare("UPDATE episodes SET gist = ? WHERE id = ?").run(gist, id);
1497
+ }
1498
+ /** Close the database connection. */
1499
+ close() {
1500
+ try {
1501
+ this.db.close();
1502
+ } catch {
1503
+ }
1504
+ }
1505
+ // ─── Internal ────────────────────────────────────────────────────────────
1506
+ rowToEpisode(row) {
1507
+ return {
1508
+ id: row.id,
1509
+ timestamp: row.timestamp,
1510
+ sessionId: row.session_id,
1511
+ taskId: row.task_id,
1512
+ turnNumber: row.turn_number,
1513
+ modality: row.modality,
1514
+ toolName: row.tool_name,
1515
+ content: row.content,
1516
+ contentHash: row.content_hash,
1517
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
1518
+ embedding: row.embedding ? new Float32Array(new Uint8Array(row.embedding).buffer) : null,
1519
+ // Belt-and-braces: rows from pre-fix DBs may have null importance
1520
+ // (from legacy inserts with NaN that SQLite coerced to NULL).
1521
+ // Re-sanitize on read so downstream code always sees a valid number.
1522
+ importance: sanitizeImportance(row.importance),
1523
+ decayClass: row.decay_class,
1524
+ strength: typeof row.strength === "number" && Number.isFinite(row.strength) ? row.strength : 1,
1525
+ lastRetrieved: row.last_retrieved,
1526
+ clipEmbedding: row.clip_embedding ? new Float32Array(new Uint8Array(row.clip_embedding).buffer) : null,
1527
+ gist: row.gist,
1528
+ sourceEpisodeId: row.source_episode_id,
1529
+ reuseCount: typeof row.reuse_count === "number" ? row.reuse_count : 0
1530
+ };
1531
+ }
1532
+ };
1533
+
1534
+ // packages/memory/dist/crl/index.js
1535
+ var CRL_SYMBOLS = {
1536
+ // Logical operators (Core)
1537
+ THEREFORE: "∴",
1538
+ // Conclusion marker (SymbCoT)
1539
+ BECAUSE: "∵",
1540
+ // Premise marker
1541
+ EQUIVALENT: "≡",
1542
+ // Definition/bidirectional
1543
+ DEFINITION: ":=",
1544
+ // Definitional equality
1545
+ IMPLIES: "→",
1546
+ // Forward implication
1547
+ BICONDITIONAL: "↔",
1548
+ // Mutual implication
1549
+ STRONG_IMPLIES: "⟹",
1550
+ // Logical entailment (Aristotle)
1551
+ STRONG_IMPLIED_BY: "⟸",
1552
+ // Reverse entailment
1553
+ STRONG_BICONDITIONAL: "⟺",
1554
+ // Logical equivalence
1555
+ NOT: "¬",
1556
+ // Negation
1557
+ AND: "∧",
1558
+ // Conjunction
1559
+ OR: "∨",
1560
+ // Disjunction
1561
+ XOR: "⊕",
1562
+ // Exclusive or
1563
+ TRUE: "⊤",
1564
+ // Tautology (LogicReward verification)
1565
+ FALSE: "⊥",
1566
+ // Contradiction
1567
+ // Proof Theory / Metalogical (Aristotle framework)
1568
+ PROVABLE: "⊢",
1569
+ // Syntactic derivability
1570
+ NOT_PROVABLE: "⊣",
1571
+ // Refutation
1572
+ MODELS: "⊨",
1573
+ // Semantic entailment
1574
+ NOT_MODELS: "⊭",
1575
+ // Semantic refutation
1576
+ FORCES: "⊩",
1577
+ // Kripke semantics
1578
+ NOT_PROVE: "⊬",
1579
+ // Underivability
1580
+ END_PROOF: "∎",
1581
+ // QED/tombstone
1582
+ NOTE: "※",
1583
+ // Annotation marker
1584
+ // Relational operators
1585
+ ELEMENT_OF: "∈",
1586
+ NOT_ELEMENT_OF: "∉",
1587
+ PROPER_SUBSET: "⊂",
1588
+ SUBSET_OR_EQUAL: "⊆",
1589
+ PROPER_SUPERSET: "⊃",
1590
+ SUPERSET_OR_EQUAL: "⊇",
1591
+ UNION: "∪",
1592
+ INTERSECTION: "∩",
1593
+ SET_DIFFERENCE: "∖",
1594
+ SYMMETRIC_DIFF: "△",
1595
+ EMPTY: "∅",
1596
+ POWER_SET: "℘",
1597
+ // Comparison operators
1598
+ EQUAL: "=",
1599
+ NOT_EQUAL: "≠",
1600
+ APPROXIMATELY: "≈",
1601
+ PROPORTIONAL: "∝",
1602
+ LESS_THAN: "<",
1603
+ GREATER_THAN: ">",
1604
+ LESS_OR_EQUAL: "≤",
1605
+ GREATER_OR_EQUAL: "≥",
1606
+ MUCH_LESS: "≪",
1607
+ MUCH_GREATER: "≫",
1608
+ // Temporal/Sequential
1609
+ THEN: "⇒",
1610
+ FROM: "⇐",
1611
+ LOOP: "⟳",
1612
+ CYCLE: "↻",
1613
+ NEXT: "⏵",
1614
+ PREVIOUS: "⏴",
1615
+ // Modal/Evidential (MuSLR multimodal grounding)
1616
+ POSSIBLE: "◇",
1617
+ DIAMOND: "◊",
1618
+ // Possibility (alt)
1619
+ NECESSARY: "□",
1620
+ BOX: "◻",
1621
+ // Necessity (alt)
1622
+ OBSERVED: "⊙",
1623
+ // Empirical evidence
1624
+ UNCERTAIN: "?",
1625
+ CRITICAL: "!",
1626
+ APPROXIMATE: "~",
1627
+ NOT_POSSIBLE: "⟐",
1628
+ // Impossibility
1629
+ NOT_NECESSARY: "⟏",
1630
+ // Contingency
1631
+ // Quantifiers
1632
+ FOR_ALL: "∀",
1633
+ EXISTS: "∃",
1634
+ NOT_EXISTS: "∄",
1635
+ UNIQUE_EXISTS: "∃!",
1636
+ // Exactly one
1637
+ AGGREGATE: "∑",
1638
+ PRODUCT: "∏",
1639
+ MOST: "∀∃"
1640
+ // Majority quantifier
1641
+ };
1642
+ var SYMBOL_MEANINGS = {
1643
+ // Logical operators (Core)
1644
+ "∴": "therefore",
1645
+ "∵": "because",
1646
+ "≡": "equivalent",
1647
+ ":=": "definition",
1648
+ "→": "implies",
1649
+ "↔": "biconditional",
1650
+ "⟹": "strong_implies",
1651
+ "⟸": "strong_implied_by",
1652
+ "⟺": "strong_biconditional",
1653
+ "¬": "not",
1654
+ "∧": "and",
1655
+ "∨": "or",
1656
+ "⊕": "xor",
1657
+ "⊤": "true",
1658
+ "⊥": "false",
1659
+ // Proof Theory / Metalogical (Aristotle framework)
1660
+ "⊢": "provable",
1661
+ "⊣": "not_provable",
1662
+ "⊨": "models",
1663
+ "⊭": "not_models",
1664
+ "⊩": "forces",
1665
+ "⊬": "not_prove",
1666
+ "∎": "end_proof",
1667
+ "※": "note",
1668
+ // Relational operators
1669
+ "∈": "element_of",
1670
+ "∉": "not_element_of",
1671
+ "⊂": "proper_subset",
1672
+ "⊆": "subset_or_equal",
1673
+ "⊃": "proper_superset",
1674
+ "⊇": "superset_or_equal",
1675
+ "∪": "union",
1676
+ "∩": "intersection",
1677
+ "∖": "set_difference",
1678
+ "△": "symmetric_difference",
1679
+ "∅": "empty",
1680
+ "℘": "power_set",
1681
+ // Comparison operators
1682
+ "=": "equal",
1683
+ "≠": "not_equal",
1684
+ "≈": "approximately",
1685
+ "∝": "proportional",
1686
+ "<": "less_than",
1687
+ ">": "greater_than",
1688
+ "≤": "less_or_equal",
1689
+ "≥": "greater_or_equal",
1690
+ "≪": "much_less",
1691
+ "≫": "much_greater",
1692
+ // Temporal/Sequential
1693
+ "⇒": "then",
1694
+ "⇐": "from",
1695
+ "⟳": "loop",
1696
+ "↻": "cycle",
1697
+ "⏵": "next",
1698
+ "⏴": "previous",
1699
+ // Modal/Evidential (MuSLR multimodal grounding)
1700
+ "◇": "possible",
1701
+ "◊": "diamond",
1702
+ "□": "necessary",
1703
+ "◻": "box",
1704
+ "⊙": "observed",
1705
+ "?": "uncertain",
1706
+ "!": "critical",
1707
+ "~": "approximate",
1708
+ "⟐": "not_possible",
1709
+ "⟏": "not_necessary",
1710
+ // Quantifiers
1711
+ "∀": "for_all",
1712
+ "∃": "exists",
1713
+ "∄": "not_exists",
1714
+ "∃!": "unique_exists",
1715
+ "∑": "aggregate",
1716
+ "∏": "product",
1717
+ "∀∃": "most"
1718
+ };
1719
+ var CRLParser = class {
1720
+ pos = 0;
1721
+ input = "";
1722
+ parse(input) {
1723
+ this.input = input;
1724
+ this.pos = 0;
1725
+ const nodes = [];
1726
+ const start = this.pos;
1727
+ while (this.pos < this.input.length) {
1728
+ this.skipWhitespace();
1729
+ if (this.pos >= this.input.length)
1730
+ break;
1731
+ const node = this.parseNode();
1732
+ if (node)
1733
+ nodes.push(node);
1734
+ }
1735
+ return {
1736
+ type: "statement",
1737
+ nodes,
1738
+ raw: input,
1739
+ position: { start, end: this.pos }
1740
+ };
1741
+ }
1742
+ parseNode() {
1743
+ this.skipWhitespace();
1744
+ if (this.peek() === "[") {
1745
+ return this.parseConcept();
1746
+ }
1747
+ if (this.peek() === "$" && this.peek(1) === "{") {
1748
+ return this.parseTemplate();
1749
+ }
1750
+ if (this.isQuantifier(this.peek())) {
1751
+ return this.parseQuantifier();
1752
+ }
1753
+ if (this.isOperator(this.peek())) {
1754
+ return this.parseOperator();
1755
+ }
1756
+ this.pos++;
1757
+ return null;
1758
+ }
1759
+ parseConcept() {
1760
+ const start = this.pos;
1761
+ this.pos++;
1762
+ let name = "";
1763
+ while (this.pos < this.input.length && this.peek() !== "]") {
1764
+ name += this.peek();
1765
+ this.pos++;
1766
+ }
1767
+ this.pos++;
1768
+ let properties;
1769
+ if (this.peek() === "{") {
1770
+ properties = this.parseProperties();
1771
+ }
1772
+ let strength;
1773
+ if (this.peek() === "★") {
1774
+ strength = 0;
1775
+ while (this.peek() === "★") {
1776
+ strength++;
1777
+ this.pos++;
1778
+ }
1779
+ }
1780
+ return {
1781
+ type: "concept",
1782
+ name: name.trim(),
1783
+ properties,
1784
+ strength,
1785
+ raw: this.input.substring(start, this.pos),
1786
+ position: { start, end: this.pos }
1787
+ };
1788
+ }
1789
+ parseProperties() {
1790
+ const props = {};
1791
+ this.pos++;
1792
+ while (this.pos < this.input.length && this.peek() !== "}") {
1793
+ const key = this.parseIdentifier();
1794
+ if (this.peek() === ":") {
1795
+ this.pos++;
1796
+ const value = this.parseIdentifier();
1797
+ props[key] = value;
1798
+ }
1799
+ if (this.peek() === ",")
1800
+ this.pos++;
1801
+ }
1802
+ this.pos++;
1803
+ return props;
1804
+ }
1805
+ parseIdentifier() {
1806
+ let id = "";
1807
+ while (this.pos < this.input.length && /[a-zA-Z0-9_]/.test(this.peek())) {
1808
+ id += this.peek();
1809
+ this.pos++;
1810
+ }
1811
+ return id;
1812
+ }
1813
+ parseTemplate() {
1814
+ const start = this.pos;
1815
+ this.pos += 2;
1816
+ let variable = "";
1817
+ let defaultValue;
1818
+ let typeAnnotation;
1819
+ let transform;
1820
+ while (this.pos < this.input.length && this.peek() !== "}" && this.peek() !== ":" && this.peek() !== "|" && this.peek() !== "→") {
1821
+ variable += this.peek();
1822
+ this.pos++;
1823
+ }
1824
+ if (this.peek() === ":") {
1825
+ this.pos++;
1826
+ defaultValue = "";
1827
+ while (this.pos < this.input.length && this.peek() !== "}") {
1828
+ defaultValue += this.peek();
1829
+ this.pos++;
1830
+ }
1831
+ }
1832
+ if (this.peek() === "|") {
1833
+ this.pos++;
1834
+ typeAnnotation = "";
1835
+ while (this.pos < this.input.length && this.peek() !== "}" && this.peek() !== "→") {
1836
+ typeAnnotation += this.peek();
1837
+ this.pos++;
1838
+ }
1839
+ }
1840
+ if (this.peek() === "→") {
1841
+ this.pos++;
1842
+ transform = "";
1843
+ while (this.pos < this.input.length && this.peek() !== "}") {
1844
+ transform += this.peek();
1845
+ this.pos++;
1846
+ }
1847
+ }
1848
+ this.pos++;
1849
+ return {
1850
+ type: "template",
1851
+ variable: variable.trim(),
1852
+ defaultValue,
1853
+ typeAnnotation,
1854
+ transform,
1855
+ raw: this.input.substring(start, this.pos),
1856
+ position: { start, end: this.pos }
1857
+ };
1858
+ }
1859
+ parseQuantifier() {
1860
+ const start = this.pos;
1861
+ const quantifier = this.peek();
1862
+ this.pos++;
1863
+ let variable = "";
1864
+ while (this.pos < this.input.length && /[a-zA-Z_]/.test(this.peek())) {
1865
+ variable += this.peek();
1866
+ this.pos++;
1867
+ }
1868
+ let domain;
1869
+ if (this.peek() === "∈") {
1870
+ this.pos++;
1871
+ this.skipWhitespace();
1872
+ domain = this.parseConcept();
1873
+ }
1874
+ this.skipWhitespace();
1875
+ if (this.peek() === ":")
1876
+ this.pos++;
1877
+ this.skipWhitespace();
1878
+ const predicate = this.parseNode();
1879
+ return {
1880
+ type: "quantifier",
1881
+ quantifier,
1882
+ variable,
1883
+ domain,
1884
+ predicate,
1885
+ raw: this.input.substring(start, this.pos),
1886
+ position: { start, end: this.pos }
1887
+ };
1888
+ }
1889
+ parseOperator() {
1890
+ const start = this.pos;
1891
+ const op = this.peek();
1892
+ this.pos++;
1893
+ return {
1894
+ type: "relation",
1895
+ raw: op,
1896
+ position: { start, end: this.pos }
1897
+ };
1898
+ }
1899
+ skipWhitespace() {
1900
+ while (this.pos < this.input.length && /\s/.test(this.peek())) {
1901
+ this.pos++;
1902
+ }
1903
+ }
1904
+ peek(offset = 0) {
1905
+ return this.input[this.pos + offset] || "";
1906
+ }
1907
+ // Extended quantifiers per LogicReward (ICLR 2026)
1908
+ isQuantifier(char) {
1909
+ return ["∀", "∃", "∄", "∃!", "∑", "∏", "∀∃"].includes(char);
1910
+ }
1911
+ // Extended operators per Aristotle (ACL 2025) and MuSLR (NeurIPS 2025)
1912
+ isOperator(char) {
1913
+ return [
1914
+ "∴",
1915
+ "∵",
1916
+ "≡",
1917
+ ":=",
1918
+ "→",
1919
+ "↔",
1920
+ "⟹",
1921
+ "⟸",
1922
+ "⟺",
1923
+ "¬",
1924
+ "∧",
1925
+ "∨",
1926
+ "⊕",
1927
+ "⊤",
1928
+ "⊥",
1929
+ "⊢",
1930
+ "⊣",
1931
+ "⊨",
1932
+ "⊭",
1933
+ "⊩",
1934
+ "⊬",
1935
+ "∎",
1936
+ "※",
1937
+ "∈",
1938
+ "∉",
1939
+ "⊂",
1940
+ "⊆",
1941
+ "⊃",
1942
+ "⊇",
1943
+ "∪",
1944
+ "∩",
1945
+ "∖",
1946
+ "△",
1947
+ "∅",
1948
+ "℘",
1949
+ "=",
1950
+ "≠",
1951
+ "≈",
1952
+ "∝",
1953
+ "<",
1954
+ ">",
1955
+ "≤",
1956
+ "≥",
1957
+ "≪",
1958
+ "≫",
1959
+ "⇒",
1960
+ "⇐",
1961
+ "⟳",
1962
+ "↻",
1963
+ "⏵",
1964
+ "⏴",
1965
+ "◇",
1966
+ "◊",
1967
+ "□",
1968
+ "◻",
1969
+ "⊙",
1970
+ "?",
1971
+ "!",
1972
+ "~",
1973
+ "⟐",
1974
+ "⟏"
1975
+ ].includes(char);
1976
+ }
1977
+ };
1978
+ var CRLEncoder = class {
1979
+ conceptPatterns = /* @__PURE__ */ new Map();
1980
+ /**
1981
+ * Encode natural language text to CRL
1982
+ */
1983
+ encode(text) {
1984
+ let result = this.compress(text);
1985
+ const concepts = this.extractConcepts(result);
1986
+ const relations = this.extractRelations(result, concepts);
1987
+ if (relations.length > 0) {
1988
+ return this.buildExpression(concepts, relations);
1989
+ }
1990
+ return result;
1991
+ }
1992
+ /**
1993
+ * Extract key concepts from text
1994
+ */
1995
+ extractConcepts(text) {
1996
+ const concepts = [];
1997
+ const conceptPattern = /\[([^\]]+)\]/g;
1998
+ let match;
1999
+ while ((match = conceptPattern.exec(text)) !== null) {
2000
+ concepts.push({
2001
+ type: "concept",
2002
+ name: match[1],
2003
+ raw: match[0],
2004
+ position: { start: match.index, end: match.index + match[0].length }
2005
+ });
2006
+ }
2007
+ const implicitPattern = /\b([A-Z][a-z]+(?:_[A-Z][a-z]+)*)\b/g;
2008
+ while ((match = implicitPattern.exec(text)) !== null) {
2009
+ if (!concepts.find((c) => c.name === match[1])) {
2010
+ concepts.push({
2011
+ type: "concept",
2012
+ name: match[1],
2013
+ raw: match[0],
2014
+ position: { start: match.index, end: match.index + match[0].length }
2015
+ });
2016
+ }
2017
+ }
2018
+ return concepts;
2019
+ }
2020
+ /**
2021
+ * Extract relations between concepts
2022
+ */
2023
+ extractRelations(text, concepts) {
2024
+ const relations = [];
2025
+ const relationPattern = /\[([^\]]+)\]\s*(→|↔|∴|∵|⇒|⇐)(?::(\w+))?\s*(?:→)?\s*\[([^\]]+)\]/g;
2026
+ let match;
2027
+ while ((match = relationPattern.exec(text)) !== null) {
2028
+ const from = concepts.find((c) => c.name === match[1]);
2029
+ const to = concepts.find((c) => c.name === match[4]);
2030
+ if (from && to) {
2031
+ relations.push({
2032
+ type: "relation",
2033
+ from,
2034
+ to,
2035
+ operator: match[2],
2036
+ edgeType: match[3],
2037
+ raw: match[0],
2038
+ position: { start: match.index, end: match.index + match[0].length }
2039
+ });
2040
+ }
2041
+ }
2042
+ return relations;
2043
+ }
2044
+ /**
2045
+ * Build CRL expression from concepts and relations
2046
+ */
2047
+ buildExpression(concepts, relations) {
2048
+ const lines = [];
2049
+ for (const rel of relations) {
2050
+ let line = `[${rel.from.name}] ${rel.operator}`;
2051
+ if (rel.edgeType)
2052
+ line += `:${rel.edgeType}`;
2053
+ line += ` [${rel.to.name}]`;
2054
+ lines.push(line);
2055
+ }
2056
+ const relatedConcepts = /* @__PURE__ */ new Set();
2057
+ for (const rel of relations) {
2058
+ relatedConcepts.add(rel.from.name);
2059
+ relatedConcepts.add(rel.to.name);
2060
+ }
2061
+ for (const concept of concepts) {
2062
+ if (!relatedConcepts.has(concept.name)) {
2063
+ lines.push(`[${concept.name}]`);
2064
+ }
2065
+ }
2066
+ return lines.join("\n");
2067
+ }
2068
+ /**
2069
+ * Compress text using CRL patterns
2070
+ */
2071
+ compress(text) {
2072
+ const replacements = [
2073
+ [/therefore/gi, "∴"],
2074
+ [/because/gi, "∵"],
2075
+ [/implies that/gi, "→"],
2076
+ [/is equivalent to/gi, "≡"],
2077
+ [/is an? element of/gi, "∈"],
2078
+ [/is not an? element of/gi, "∉"],
2079
+ [/is a subset of/gi, "⊂"],
2080
+ [/for all/gi, "∀"],
2081
+ [/there exists/gi, "∃"],
2082
+ [/and then/gi, "⇒"],
2083
+ [/leads to/gi, "→"],
2084
+ [/causes/gi, "→:causes"],
2085
+ [/fixes/gi, "→:fixes"],
2086
+ [/relates to/gi, "∩"]
2087
+ ];
2088
+ let result = text;
2089
+ for (const [pattern, replacement] of replacements) {
2090
+ result = result.replace(pattern, replacement);
2091
+ }
2092
+ return result;
2093
+ }
2094
+ };
2095
+ var CRLDecoder = class {
2096
+ /**
2097
+ * Decode CRL expression to natural language
2098
+ */
2099
+ decode(crl) {
2100
+ const hasCRLStructure = /[\[\]∀∃∴∵→⇒⇐∩∪⊂⊃∈∉]/.test(crl);
2101
+ if (!hasCRLStructure) {
2102
+ return crl;
2103
+ }
2104
+ let result = this.expand(crl);
2105
+ const parser = new CRLParser();
2106
+ const ast = parser.parse(result);
2107
+ if (ast.nodes.length > 0) {
2108
+ const rendered = this.renderStatement(ast);
2109
+ if (rendered && rendered.trim().length > 0) {
2110
+ return rendered;
2111
+ }
2112
+ }
2113
+ return result;
2114
+ }
2115
+ /**
2116
+ * Render a statement node
2117
+ */
2118
+ renderStatement(node) {
2119
+ const results = [];
2120
+ const nodes = node.nodes;
2121
+ for (let i = 0; i < nodes.length; i++) {
2122
+ const current = nodes[i];
2123
+ if (current.type === "concept" && i + 2 < nodes.length) {
2124
+ const next = nodes[i + 1];
2125
+ const afterNext = nodes[i + 2];
2126
+ if (next.type === "relation" && afterNext.type === "concept") {
2127
+ const relationNode = {
2128
+ type: "relation",
2129
+ from: current,
2130
+ operator: next.raw || "",
2131
+ to: afterNext,
2132
+ raw: `${current.raw}${next.raw}${afterNext.raw}`,
2133
+ position: { start: current.position.start, end: afterNext.position.end }
2134
+ };
2135
+ results.push(this.renderRelation(relationNode));
2136
+ i += 2;
2137
+ continue;
2138
+ }
2139
+ }
2140
+ const rendered = this.renderNode(current);
2141
+ if (rendered)
2142
+ results.push(rendered);
2143
+ }
2144
+ return results.filter(Boolean).join(". ");
2145
+ }
2146
+ /**
2147
+ * Render any CRL node
2148
+ */
2149
+ renderNode(node) {
2150
+ switch (node.type) {
2151
+ case "concept":
2152
+ return this.renderConcept(node);
2153
+ case "template":
2154
+ return this.renderTemplate(node);
2155
+ case "quantifier":
2156
+ return this.renderQuantifier(node);
2157
+ case "relation":
2158
+ return this.renderRelation(node);
2159
+ default:
2160
+ return "";
2161
+ }
2162
+ }
2163
+ /**
2164
+ * Render a concept
2165
+ */
2166
+ renderConcept(node) {
2167
+ let result = node.name;
2168
+ if (node.properties) {
2169
+ const props = Object.entries(node.properties).map(([k, v]) => `${k}=${v}`).join(", ");
2170
+ result += ` (${props})`;
2171
+ }
2172
+ if (node.strength) {
2173
+ result += ` [strength: ${node.strength}/5]`;
2174
+ }
2175
+ return result;
2176
+ }
2177
+ /**
2178
+ * Render a template
2179
+ */
2180
+ renderTemplate(node) {
2181
+ let result = node.variable;
2182
+ if (node.typeAnnotation) {
2183
+ result += ` (${node.typeAnnotation})`;
2184
+ }
2185
+ if (node.defaultValue) {
2186
+ result += ` [default: ${node.defaultValue}]`;
2187
+ }
2188
+ return result;
2189
+ }
2190
+ /**
2191
+ * Render a quantifier
2192
+ */
2193
+ renderQuantifier(node) {
2194
+ const quantifierMeanings = {
2195
+ "∀": "For all",
2196
+ "∃": "There exists",
2197
+ "∄": "There does not exist",
2198
+ "∑": "Sum of",
2199
+ "∏": "Product of"
2200
+ };
2201
+ let result = `${quantifierMeanings[node.quantifier]} ${node.variable}`;
2202
+ if (node.domain) {
2203
+ result += ` in ${this.renderNode(node.domain)}`;
2204
+ }
2205
+ if (node.predicate) {
2206
+ result += `, ${this.renderNode(node.predicate)}`;
2207
+ }
2208
+ return result;
2209
+ }
2210
+ /**
2211
+ * Render a relation
2212
+ */
2213
+ renderRelation(node) {
2214
+ const operatorMeanings = {
2215
+ "→": "implies",
2216
+ "∴": "therefore",
2217
+ "∵": "because",
2218
+ "≡": "is equivalent to",
2219
+ "↔": "is mutually related to",
2220
+ "∈": "is an element of",
2221
+ "⊂": "is a subset of",
2222
+ "∪": "combined with",
2223
+ "∩": "intersects with",
2224
+ "⇒": "then",
2225
+ "⇐": "from"
2226
+ };
2227
+ const from = node.from ? this.renderConcept(node.from) : "";
2228
+ const to = node.to ? this.renderConcept(node.to) : "";
2229
+ const op = operatorMeanings[node.operator] || node.operator;
2230
+ if (node.edgeType) {
2231
+ return `${from} ${node.edgeType} ${to}`;
2232
+ }
2233
+ if (from && to) {
2234
+ return `${from} ${op} ${to}`;
2235
+ }
2236
+ return op;
2237
+ }
2238
+ /**
2239
+ * Expand CRL symbols to natural language
2240
+ */
2241
+ expand(text) {
2242
+ const expansions = {
2243
+ "∴": "therefore",
2244
+ "∵": "because",
2245
+ "≡": "is equivalent to",
2246
+ "→": "implies",
2247
+ "↔": "is mutually related to",
2248
+ "¬": "not",
2249
+ "∧": "and",
2250
+ "∨": "or",
2251
+ "∈": "is an element of",
2252
+ "∉": "is not an element of",
2253
+ "⊂": "is a subset of",
2254
+ "⊆": "is a subset of or equal to",
2255
+ "∪": "union with",
2256
+ "∩": "intersection with",
2257
+ "∅": "empty",
2258
+ "⇒": "then",
2259
+ "⇐": "from",
2260
+ "◇": "possibly",
2261
+ "□": "necessarily",
2262
+ "⊙": "observed",
2263
+ "∀": "for all",
2264
+ "∃": "there exists",
2265
+ "∄": "there does not exist"
2266
+ };
2267
+ let result = text;
2268
+ for (const [symbol, expansion] of Object.entries(expansions)) {
2269
+ result = result.replace(new RegExp(`(^|[.!?]\\s*)${symbol}`, "g"), (match, prefix) => {
2270
+ const capitalized = expansion.charAt(0).toUpperCase() + expansion.slice(1);
2271
+ return `${prefix}${capitalized}`;
2272
+ });
2273
+ result = result.replace(new RegExp(`(?<!^)(?<![.!?]\\s*)${symbol}`, "g"), expansion);
2274
+ }
2275
+ result = result.replace(/\s+([.,;:!?])/g, "$1");
2276
+ result = result.replace(/\s+/g, " ").trim();
2277
+ return result;
2278
+ }
2279
+ };
2280
+ var CRLTemplateResolver = class {
2281
+ /**
2282
+ * Resolve templates in CRL expression
2283
+ */
2284
+ resolve(crl, context) {
2285
+ const templatePattern = /\$\{([^}]+)\}/g;
2286
+ return crl.replace(templatePattern, (match, template) => {
2287
+ return this.resolveTemplate(template, context);
2288
+ });
2289
+ }
2290
+ resolveTemplate(template, context) {
2291
+ let value;
2292
+ if (template.startsWith("ctx:")) {
2293
+ const ctxVar = template.substring(4);
2294
+ value = context[ctxVar];
2295
+ } else if (template.startsWith("mem:")) {
2296
+ const memVar = template.substring(4);
2297
+ const memArray = context.memories?.[memVar];
2298
+ value = memArray?.join(", ");
2299
+ } else if (template.startsWith("state:")) {
2300
+ const stateVar = template.substring(6);
2301
+ value = context.state?.[stateVar];
2302
+ } else {
2303
+ const parts = template.split(/[:|→]/);
2304
+ const variable = parts[0].trim();
2305
+ const defaultValue = parts[1]?.trim();
2306
+ value = context.variables?.[variable] || defaultValue;
2307
+ }
2308
+ return value || `[${template}]`;
2309
+ }
2310
+ };
2311
+ var CRL = {
2312
+ parser: new CRLParser(),
2313
+ encoder: new CRLEncoder(),
2314
+ decoder: new CRLDecoder(),
2315
+ resolver: new CRLTemplateResolver(),
2316
+ symbols: CRL_SYMBOLS,
2317
+ meanings: SYMBOL_MEANINGS,
2318
+ /**
2319
+ * Quick encode: natural language → CRL
2320
+ */
2321
+ encode(text) {
2322
+ return this.encoder.encode(text);
2323
+ },
2324
+ /**
2325
+ * Quick decode: CRL → natural language
2326
+ */
2327
+ decode(crl) {
2328
+ return this.decoder.decode(crl);
2329
+ },
2330
+ /**
2331
+ * Quick compress: replace common patterns with symbols
2332
+ */
2333
+ compress(text) {
2334
+ return this.encoder.compress(text);
2335
+ },
2336
+ /**
2337
+ * Quick expand: replace symbols with natural language
2338
+ */
2339
+ expand(text) {
2340
+ return this.decoder.expand(text);
2341
+ },
2342
+ /**
2343
+ * Resolve templates with context
2344
+ */
2345
+ resolve(template, context) {
2346
+ return this.resolver.resolve(template, context);
2347
+ }
2348
+ };
2349
+
2350
+ // packages/memory/dist/sleepConsolidation.js
2351
+ function slowWaveReplay(db, options = {}) {
2352
+ const topK = options.topK ?? 20;
2353
+ const lookback = options.lookbackMs ?? 2 * 60 * 60 * 1e3;
2354
+ const minImp = options.minImportance ?? 6;
2355
+ const delta = options.strengthDelta ?? 1;
2356
+ const start = Date.now();
2357
+ const cutoff = start - lookback;
2358
+ let rows = [];
2359
+ try {
2360
+ rows = db.prepare(`SELECT id FROM episodes
2361
+ WHERE importance >= ?
2362
+ AND timestamp >= ?
2363
+ ORDER BY importance DESC, timestamp DESC
2364
+ LIMIT ?`).all(minImp, cutoff, topK);
2365
+ } catch {
2366
+ rows = [];
2367
+ }
2368
+ if (rows.length === 0) {
2369
+ return {
2370
+ cycle: emptyCycle("slow_wave", start)
2371
+ };
2372
+ }
2373
+ const ids = rows.map((r) => r.id);
2374
+ const placeholders = ids.map(() => "?").join(",");
2375
+ const safeDelta = Number.isFinite(delta) ? delta : 1;
2376
+ try {
2377
+ db.prepare(`UPDATE episodes
2378
+ SET strength = COALESCE(strength, 1) + ?,
2379
+ last_retrieved = ?
2380
+ WHERE id IN (${placeholders})`).run(safeDelta, start, ...ids);
2381
+ } catch {
2382
+ }
2383
+ const integratedNodes = [];
2384
+ if (options.graph) {
2385
+ for (const id of ids) {
2386
+ try {
2387
+ const referenced = db.prepare(`SELECT 1 FROM kg_edges WHERE source_episode_id = ? LIMIT 1`).get(id);
2388
+ if (!referenced) {
2389
+ const nodeText = `episode:${id}`;
2390
+ const newNodeId = options.graph.upsertNode({ text: nodeText, nodeType: "event" });
2391
+ if (newNodeId)
2392
+ integratedNodes.push(newNodeId);
2393
+ }
2394
+ } catch {
2395
+ }
2396
+ }
2397
+ }
2398
+ return {
2399
+ cycle: {
2400
+ phase: "slow_wave",
2401
+ startTime: start,
2402
+ endTime: Date.now(),
2403
+ replayedEpisodes: ids,
2404
+ downscaledEpisodes: [],
2405
+ prunedEpisodes: [],
2406
+ novelAssociations: [],
2407
+ energyCost: ids.length + integratedNodes.length,
2408
+ compressedEpisodes: [],
2409
+ flaggedHubs: [],
2410
+ integratedNodes
2411
+ }
2412
+ };
2413
+ }
2414
+ function remDream(db, options = {}) {
2415
+ const seeds = Math.max(1, options.seeds ?? 8);
2416
+ const start = Date.now();
2417
+ const novel = [];
2418
+ let seedRows = [];
2419
+ try {
2420
+ seedRows = db.prepare(`SELECT id FROM episodes
2421
+ WHERE importance >= 4
2422
+ ORDER BY importance * COALESCE(strength, 1) DESC, RANDOM()
2423
+ LIMIT ?`).all(seeds);
2424
+ } catch {
2425
+ seedRows = [];
2426
+ }
2427
+ if (seedRows.length === 0) {
2428
+ return { cycle: emptyCycle("rem", start) };
2429
+ }
2430
+ const walkDepth = Math.max(1, options.walkDepth ?? 3);
2431
+ const minSim = options.minSimilarity ?? 0.6;
2432
+ for (const seed of seedRows) {
2433
+ let candidateRows = [];
2434
+ try {
2435
+ candidateRows = db.prepare(`SELECT id, embedding, timestamp AS ts FROM episodes
2436
+ WHERE session_id IS (SELECT session_id FROM episodes WHERE id = ?)
2437
+ AND id != ?
2438
+ ORDER BY timestamp DESC
2439
+ LIMIT 100`).all(seed.id, seed.id);
2440
+ } catch {
2441
+ continue;
2442
+ }
2443
+ if (candidateRows.length === 0)
2444
+ continue;
2445
+ let seedEmb = null;
2446
+ try {
2447
+ seedEmb = db.prepare(`SELECT embedding FROM episodes WHERE id = ?`).get(seed.id)?.embedding ?? null;
2448
+ } catch {
2449
+ }
2450
+ let chosen = null;
2451
+ const seedFloat = toFloat32(seedEmb);
2452
+ if (seedFloat) {
2453
+ const distantCandidates = candidateRows.slice(walkDepth);
2454
+ let bestSim = minSim;
2455
+ let bestId = null;
2456
+ for (const cand of distantCandidates) {
2457
+ const candFloat = toFloat32(cand.embedding);
2458
+ if (!candFloat || candFloat.length !== seedFloat.length)
2459
+ continue;
2460
+ const sim = cosineSim(seedFloat, candFloat);
2461
+ if (sim > bestSim) {
2462
+ bestSim = sim;
2463
+ bestId = cand.id;
2464
+ }
2465
+ }
2466
+ if (bestId) {
2467
+ chosen = { id: bestId, reason: `rem-embedding-similarity:${bestSim.toFixed(3)}` };
2468
+ }
2469
+ }
2470
+ if (!chosen && candidateRows.length > walkDepth) {
2471
+ const target = candidateRows[walkDepth];
2472
+ if (target)
2473
+ chosen = { id: target.id, reason: "rem-temporal-distance" };
2474
+ }
2475
+ if (chosen)
2476
+ novel.push({ from: seed.id, to: chosen.id, reason: chosen.reason });
2477
+ }
2478
+ if (options.graph && novel.length > 0) {
2479
+ for (const link of novel) {
2480
+ try {
2481
+ const srcId = options.graph.upsertNode({ text: `episode:${link.from}`, nodeType: "event" });
2482
+ const dstId = options.graph.upsertNode({ text: `episode:${link.to}`, nodeType: "event" });
2483
+ if (srcId && dstId) {
2484
+ options.graph.addEdge({
2485
+ srcId,
2486
+ dstId,
2487
+ relation: "associated_via_rem",
2488
+ edgeType: "associative",
2489
+ confidence: 0.4,
2490
+ sourceEpisodeId: link.from
2491
+ });
2492
+ }
2493
+ } catch {
2494
+ }
2495
+ }
2496
+ }
2497
+ return {
2498
+ cycle: {
2499
+ phase: "rem",
2500
+ startTime: start,
2501
+ endTime: Date.now(),
2502
+ replayedEpisodes: seedRows.map((r) => r.id),
2503
+ downscaledEpisodes: [],
2504
+ prunedEpisodes: [],
2505
+ novelAssociations: novel,
2506
+ energyCost: seedRows.length + novel.length,
2507
+ compressedEpisodes: [],
2508
+ flaggedHubs: [],
2509
+ integratedNodes: []
2510
+ }
2511
+ };
2512
+ }
2513
+ function lightSleep(db, options = {}) {
2514
+ const start = Date.now();
2515
+ const downThreshold = options.downscaleThreshold ?? 0.3;
2516
+ const downDelta = options.downscaleDelta ?? 0.1;
2517
+ const pruneImportance = options.pruneImportanceCeiling ?? 2;
2518
+ const maxPrune = Math.max(0, options.maxPrune ?? 200);
2519
+ const prunableClasses = options.prunableClasses ?? ["session", "daily"];
2520
+ const stalePerClass = {};
2521
+ for (const c of prunableClasses) {
2522
+ stalePerClass[c] = options.pruneStaleAfterMs ?? DECAY_TAU[c] * 2;
2523
+ }
2524
+ let downscaled = [];
2525
+ try {
2526
+ downscaled = db.prepare(`SELECT id FROM episodes
2527
+ WHERE COALESCE(strength, 1) < ?
2528
+ ORDER BY strength ASC
2529
+ LIMIT 500`).all(downThreshold);
2530
+ if (downscaled.length > 0) {
2531
+ const placeholders = downscaled.map(() => "?").join(",");
2532
+ const safeDelta = Number.isFinite(downDelta) ? downDelta : 0.1;
2533
+ db.prepare(`UPDATE episodes
2534
+ SET strength = MAX(0.05, COALESCE(strength, 1) - ?)
2535
+ WHERE id IN (${placeholders})`).run(safeDelta, ...downscaled.map((r) => r.id));
2536
+ }
2537
+ } catch {
2538
+ }
2539
+ const promotedToDaily = [];
2540
+ const promotedToProcedural = [];
2541
+ try {
2542
+ const sessionPromos = db.prepare(`SELECT id FROM episodes WHERE decay_class = 'session' AND COALESCE(reuse_count, 0) >= 3 LIMIT 200`).all();
2543
+ for (const r of sessionPromos) {
2544
+ try {
2545
+ db.prepare(`UPDATE episodes SET decay_class = 'daily' WHERE id = ?`).run(r.id);
2546
+ promotedToDaily.push(r.id);
2547
+ } catch {
2548
+ }
2549
+ }
2550
+ const dailyPromos = db.prepare(`SELECT id FROM episodes WHERE decay_class = 'daily' AND COALESCE(reuse_count, 0) >= 8 LIMIT 200`).all();
2551
+ for (const r of dailyPromos) {
2552
+ try {
2553
+ db.prepare(`UPDATE episodes SET decay_class = 'procedural' WHERE id = ?`).run(r.id);
2554
+ promotedToProcedural.push(r.id);
2555
+ } catch {
2556
+ }
2557
+ }
2558
+ } catch {
2559
+ }
2560
+ const pruned = [];
2561
+ const compressed = [];
2562
+ const compressMode = !!options.compressInsteadOfPrune;
2563
+ const gistMaxChars = Math.max(20, options.gistMaxChars ?? 80);
2564
+ for (const klass of prunableClasses) {
2565
+ const stale = stalePerClass[klass];
2566
+ const cutoff = start - stale;
2567
+ const remaining = maxPrune - (pruned.length + compressed.length);
2568
+ if (remaining <= 0)
2569
+ break;
2570
+ let rows = [];
2571
+ try {
2572
+ rows = db.prepare(`SELECT id, content FROM episodes
2573
+ WHERE decay_class = ?
2574
+ AND importance < ?
2575
+ AND timestamp < ?
2576
+ ORDER BY timestamp ASC
2577
+ LIMIT ?`).all(klass, pruneImportance, cutoff, remaining);
2578
+ } catch {
2579
+ rows = [];
2580
+ }
2581
+ if (rows.length === 0)
2582
+ continue;
2583
+ if (compressMode) {
2584
+ try {
2585
+ const updateStmt = db.prepare(`UPDATE episodes
2586
+ SET content = ?,
2587
+ metadata = json_patch(COALESCE(metadata, '{}'), json_object('compressedAt', ?))
2588
+ WHERE id = ?`);
2589
+ for (const row of rows) {
2590
+ const truncated = (row.content ?? "").slice(0, gistMaxChars);
2591
+ const gist = (row.content ?? "").length > gistMaxChars ? truncated + "…" : truncated;
2592
+ try {
2593
+ updateStmt.run(gist, start, row.id);
2594
+ compressed.push(row.id);
2595
+ } catch {
2596
+ try {
2597
+ db.prepare(`UPDATE episodes SET content = ? WHERE id = ?`).run(gist, row.id);
2598
+ compressed.push(row.id);
2599
+ } catch {
2600
+ }
2601
+ }
2602
+ }
2603
+ } catch {
2604
+ }
2605
+ } else {
2606
+ const ids = rows.map((r) => r.id);
2607
+ try {
2608
+ const placeholders = ids.map(() => "?").join(",");
2609
+ db.prepare(`DELETE FROM episodes WHERE id IN (${placeholders})`).run(...ids);
2610
+ pruned.push(...ids);
2611
+ } catch {
2612
+ }
2613
+ }
2614
+ if (pruned.length + compressed.length >= maxPrune)
2615
+ break;
2616
+ }
2617
+ const hubThreshold = Math.max(1, options.hubThreshold ?? 50);
2618
+ const flaggedHubs = [];
2619
+ try {
2620
+ const rows = db.prepare(`SELECT node_id, edge_count FROM (
2621
+ SELECT src_id AS node_id, COUNT(*) AS edge_count FROM kg_edges GROUP BY src_id
2622
+ UNION ALL
2623
+ SELECT dst_id AS node_id, COUNT(*) AS edge_count FROM kg_edges GROUP BY dst_id
2624
+ )
2625
+ GROUP BY node_id
2626
+ HAVING SUM(edge_count) >= ?
2627
+ ORDER BY SUM(edge_count) DESC
2628
+ LIMIT 50`).all(hubThreshold);
2629
+ for (const r of rows)
2630
+ flaggedHubs.push(r.node_id);
2631
+ } catch {
2632
+ }
2633
+ return {
2634
+ cycle: {
2635
+ phase: "light",
2636
+ startTime: start,
2637
+ endTime: Date.now(),
2638
+ replayedEpisodes: [],
2639
+ downscaledEpisodes: downscaled.map((r) => r.id),
2640
+ prunedEpisodes: pruned,
2641
+ novelAssociations: [],
2642
+ energyCost: downscaled.length + pruned.length + compressed.length + flaggedHubs.length + promotedToDaily.length + promotedToProcedural.length,
2643
+ compressedEpisodes: compressed,
2644
+ flaggedHubs,
2645
+ integratedNodes: [],
2646
+ promotedToDaily,
2647
+ promotedToProcedural
2648
+ }
2649
+ };
2650
+ }
2651
+ function runConsolidationCycle(db, opts = {}) {
2652
+ const slow = slowWaveReplay(db, opts.slowWave).cycle;
2653
+ const rem = remDream(db, opts.rem).cycle;
2654
+ const light = lightSleep(db, opts.light).cycle;
2655
+ return {
2656
+ slowWave: slow,
2657
+ rem,
2658
+ light,
2659
+ totalEnergy: slow.energyCost + rem.energyCost + light.energyCost
2660
+ };
2661
+ }
2662
+ function emptyCycle(phase, start) {
2663
+ return {
2664
+ phase,
2665
+ startTime: start,
2666
+ endTime: Date.now(),
2667
+ replayedEpisodes: [],
2668
+ downscaledEpisodes: [],
2669
+ prunedEpisodes: [],
2670
+ novelAssociations: [],
2671
+ energyCost: 0,
2672
+ compressedEpisodes: [],
2673
+ flaggedHubs: [],
2674
+ integratedNodes: []
2675
+ };
2676
+ }
2677
+ function toFloat32(raw) {
2678
+ if (raw == null)
2679
+ return null;
2680
+ if (raw instanceof Float32Array) {
2681
+ return new Float32Array(raw);
2682
+ }
2683
+ if (raw instanceof Uint8Array) {
2684
+ if (raw.byteLength === 0 || raw.byteLength % 4 !== 0)
2685
+ return null;
2686
+ const fresh = new Uint8Array(raw.byteLength);
2687
+ fresh.set(raw);
2688
+ return new Float32Array(fresh.buffer, 0, raw.byteLength / 4);
2689
+ }
2690
+ if (raw instanceof ArrayBuffer) {
2691
+ if (raw.byteLength === 0 || raw.byteLength % 4 !== 0)
2692
+ return null;
2693
+ return new Float32Array(raw.slice(0));
2694
+ }
2695
+ return null;
2696
+ }
2697
+ function cosineSim(a, b) {
2698
+ if (a.length !== b.length || a.length === 0)
2699
+ return 0;
2700
+ let dot = 0, na = 0, nb = 0;
2701
+ for (let i = 0; i < a.length; i++) {
2702
+ const x = a[i];
2703
+ const y = b[i];
2704
+ dot += x * y;
2705
+ na += x * x;
2706
+ nb += y * y;
2707
+ }
2708
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
2709
+ if (denom === 0)
2710
+ return 0;
2711
+ return dot / denom;
2712
+ }
2713
+
2714
+ // packages/memory/dist/toolOutcomes.js
2715
+ var DEFAULT_WINDOW_MS = 6 * 60 * 60 * 1e3;
2716
+
2717
+ // packages/memory/dist/scoring.js
2718
+ var DEFAULT_CONFIG3 = {
2719
+ recencyHalfLifeMs: 7 * 24 * 60 * 60 * 1e3,
2720
+ // 7 days
2721
+ minScore: 0.05,
2722
+ hotWindowMs: 60 * 60 * 1e3,
2723
+ // 1 hour
2724
+ hotBoost: 2
2725
+ };
2726
+
2727
+ // packages/memory/dist/maintenance.js
2728
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2729
+ import { dirname, join as join2 } from "node:path";
2730
+ var HOUR = 36e5;
2731
+ var DAY = 24 * HOUR;
2732
+ var DECAY_TAU2 = {
2733
+ session: HOUR,
2734
+ daily: DAY,
2735
+ procedural: 30 * DAY,
2736
+ permanent: Number.POSITIVE_INFINITY
2737
+ };
2738
+ function runMemoryMaintenance(options) {
2739
+ const started = Date.now();
2740
+ const result = emptyResult(options, started);
2741
+ const shouldStop = () => Number.isFinite(options.maxRuntimeMs) && Date.now() - started >= Math.max(1e3, options.maxRuntimeMs ?? 0);
2742
+ const stateDir = options.stateDir;
2743
+ const episodesPath = join2(stateDir, "episodes.db");
2744
+ const knowledgePath = join2(stateDir, "knowledge.db");
2745
+ result.stores.episodes = storeStats(episodesPath);
2746
+ result.stores.knowledge = storeStats(knowledgePath);
2747
+ if (!options.dryRun)
2748
+ mkdirSync2(stateDir, { recursive: true });
2749
+ if (!shouldStop()) {
2750
+ result.deferred = ingestDeferredEpisodes(stateDir, episodesPath, {
2751
+ dryRun: !!options.dryRun
2752
+ });
2753
+ }
2754
+ if (!shouldStop() && existsSync2(episodesPath)) {
2755
+ const episodeResult = maintainEpisodes(episodesPath, options, shouldStop);
2756
+ result.episodes = episodeResult.episodes;
2757
+ result.compacted.episodesVacuumed = episodeResult.vacuumed;
2758
+ result.compacted.walCheckpoints += episodeResult.walCheckpoint ? 1 : 0;
2759
+ }
2760
+ if (!shouldStop() && existsSync2(knowledgePath)) {
2761
+ const graphResult = maintainGraph(knowledgePath, options, shouldStop);
2762
+ result.graph = graphResult.graph;
2763
+ result.compacted.knowledgeVacuumed = graphResult.vacuumed;
2764
+ result.compacted.walCheckpoints += graphResult.walCheckpoint ? 1 : 0;
2765
+ }
2766
+ result.stoppedEarly = shouldStop();
2767
+ result.stores.episodes.sizeAfterBytes = fileSize(episodesPath);
2768
+ result.stores.knowledge.sizeAfterBytes = fileSize(knowledgePath);
2769
+ result.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2770
+ result.durationMs = Date.now() - started;
2771
+ return result;
2772
+ }
2773
+ function emptyResult(options, started) {
2774
+ const missing = (path) => ({
2775
+ path,
2776
+ exists: false,
2777
+ sizeBeforeBytes: 0,
2778
+ sizeAfterBytes: 0
2779
+ });
2780
+ return {
2781
+ startedAt: new Date(started).toISOString(),
2782
+ finishedAt: new Date(started).toISOString(),
2783
+ durationMs: 0,
2784
+ dryRun: !!options.dryRun,
2785
+ stores: {
2786
+ episodes: missing(join2(options.stateDir, "episodes.db")),
2787
+ knowledge: missing(join2(options.stateDir, "knowledge.db"))
2788
+ },
2789
+ deferred: { scanned: 0, imported: 0, malformed: 0 },
2790
+ episodes: {
2791
+ scanned: 0,
2792
+ lowSignalDeleted: 0,
2793
+ duplicateDeleted: 0,
2794
+ sleepPruned: 0,
2795
+ sleepCompressed: 0,
2796
+ sleepPromoted: 0
2797
+ },
2798
+ graph: {
2799
+ nodesScanned: 0,
2800
+ nodesMerged: 0,
2801
+ edgesDeduped: 0,
2802
+ inactiveEdgesDeleted: 0,
2803
+ orphanNodesDeleted: 0,
2804
+ lowSignalNodesPruned: 0,
2805
+ lowSignalEdgesPruned: 0,
2806
+ nodesArchived: 0,
2807
+ nodesTotalBefore: 0,
2808
+ nodesTotalAfter: 0
2809
+ },
2810
+ compacted: {
2811
+ episodesVacuumed: false,
2812
+ knowledgeVacuumed: false,
2813
+ walCheckpoints: 0
2814
+ },
2815
+ stoppedEarly: false
2816
+ };
2817
+ }
2818
+ function storeStats(path) {
2819
+ return {
2820
+ path,
2821
+ exists: existsSync2(path),
2822
+ sizeBeforeBytes: fileSize(path),
2823
+ sizeAfterBytes: fileSize(path)
2824
+ };
2825
+ }
2826
+ function fileSize(path) {
2827
+ try {
2828
+ return existsSync2(path) ? statSync(path).size : 0;
2829
+ } catch {
2830
+ return 0;
2831
+ }
2832
+ }
2833
+ function ingestDeferredEpisodes(stateDir, episodesPath, options) {
2834
+ const spoolPath = join2(stateDir, "memory-deferred", "episodes.jsonl");
2835
+ const stats = { scanned: 0, imported: 0, malformed: 0 };
2836
+ if (!existsSync2(spoolPath))
2837
+ return stats;
2838
+ const raw = readFileSync(spoolPath, "utf8");
2839
+ const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
2840
+ if (lines.length === 0)
2841
+ return stats;
2842
+ let store = null;
2843
+ const malformed = [];
2844
+ try {
2845
+ if (!options.dryRun)
2846
+ store = new EpisodeStore(episodesPath);
2847
+ for (const line of lines) {
2848
+ stats.scanned++;
2849
+ try {
2850
+ const parsed = JSON.parse(line);
2851
+ const episode = parsed.episode;
2852
+ if (!episode || typeof episode.content !== "string") {
2853
+ stats.malformed++;
2854
+ malformed.push(line);
2855
+ continue;
2856
+ }
2857
+ if (!options.dryRun && store) {
2858
+ store.insert({
2859
+ sessionId: typeof episode.sessionId === "string" ? episode.sessionId : void 0,
2860
+ taskId: typeof episode.taskId === "string" ? episode.taskId : void 0,
2861
+ turnNumber: typeof episode.turnNumber === "number" ? episode.turnNumber : void 0,
2862
+ modality: episode.modality,
2863
+ toolName: typeof episode.toolName === "string" ? episode.toolName : void 0,
2864
+ content: episode.content,
2865
+ metadata: episode.metadata && typeof episode.metadata === "object" ? episode.metadata : void 0,
2866
+ importance: typeof episode.importance === "number" ? episode.importance : void 0,
2867
+ decayClass: episode.decayClass,
2868
+ sourceEpisodeId: typeof episode.sourceEpisodeId === "string" ? episode.sourceEpisodeId : void 0
2869
+ });
2870
+ }
2871
+ stats.imported++;
2872
+ } catch {
2873
+ stats.malformed++;
2874
+ malformed.push(line);
2875
+ }
2876
+ }
2877
+ } finally {
2878
+ store?.close();
2879
+ }
2880
+ if (!options.dryRun) {
2881
+ mkdirSync2(dirname(spoolPath), { recursive: true });
2882
+ if (malformed.length > 0) {
2883
+ writeFileSync(spoolPath, `${malformed.join("\n")}
2884
+ `, "utf8");
2885
+ } else {
2886
+ const importedPath = join2(dirname(spoolPath), `episodes.${Date.now().toString(36)}.imported.jsonl`);
2887
+ renameSync(spoolPath, importedPath);
2888
+ }
2889
+ }
2890
+ return stats;
2891
+ }
2892
+ function maintainEpisodes(dbPath, options, shouldStop) {
2893
+ const stats = {
2894
+ scanned: 0,
2895
+ lowSignalDeleted: 0,
2896
+ duplicateDeleted: 0,
2897
+ sleepPruned: 0,
2898
+ sleepCompressed: 0,
2899
+ sleepPromoted: 0
2900
+ };
2901
+ const db = initDb(dbPath);
2902
+ let vacuumed = false;
2903
+ let walCheckpoint = false;
2904
+ try {
2905
+ db.pragma("busy_timeout = 5000");
2906
+ if (!shouldStop()) {
2907
+ try {
2908
+ const cycle = runConsolidationCycle(db, {
2909
+ slowWave: { topK: 24, lookbackMs: 24 * HOUR, minImportance: 6 },
2910
+ rem: { seeds: 8, walkDepth: 3, minSimilarity: 0.68 },
2911
+ light: {
2912
+ prunableClasses: ["session", "daily"],
2913
+ pruneImportanceCeiling: 2.5,
2914
+ pruneStaleAfterMs: 12 * HOUR,
2915
+ maxPrune: Math.min(500, options.maxEpisodeDeletes ?? 500),
2916
+ compressInsteadOfPrune: false
2917
+ }
2918
+ });
2919
+ stats.sleepPruned = cycle.light.prunedEpisodes.length;
2920
+ stats.sleepCompressed = cycle.light.compressedEpisodes.length;
2921
+ stats.sleepPromoted = (cycle.light.promotedToDaily?.length ?? 0) + (cycle.light.promotedToProcedural?.length ?? 0);
2922
+ } catch {
2923
+ }
2924
+ }
2925
+ if (!shouldStop()) {
2926
+ const candidates = loadEpisodeCandidates(db, options.maxEpisodes ?? 2500);
2927
+ stats.scanned = candidates.length;
2928
+ const duplicateIds = vectorDuplicateIds(candidates, options.duplicateSimilarity ?? 0.965);
2929
+ stats.duplicateDeleted = deleteEpisodes(db, duplicateIds.slice(0, options.maxEpisodeDeletes ?? 1e3), !!options.dryRun);
2930
+ const remainingDeleteBudget = Math.max(0, (options.maxEpisodeDeletes ?? 1e3) - stats.duplicateDeleted);
2931
+ if (remainingDeleteBudget > 0) {
2932
+ const lowSignalIds = candidates.filter((row) => !duplicateIds.includes(row.id)).map((row) => ({
2933
+ id: row.id,
2934
+ score: memoryUtilityScore(row, Date.now())
2935
+ })).filter((row) => row.score < (options.minUtilityScore ?? 0.18)).sort((a, b) => a.score - b.score).slice(0, remainingDeleteBudget).map((row) => row.id);
2936
+ stats.lowSignalDeleted = deleteEpisodes(db, lowSignalIds, !!options.dryRun);
2937
+ }
2938
+ }
2939
+ walCheckpoint = checkpointWal(db);
2940
+ if (!options.dryRun && options.vacuum !== false && stats.lowSignalDeleted + stats.duplicateDeleted + stats.sleepPruned > 0) {
2941
+ vacuumed = vacuum(db);
2942
+ }
2943
+ } finally {
2944
+ try {
2945
+ db.close();
2946
+ } catch {
2947
+ }
2948
+ }
2949
+ return { episodes: stats, vacuumed, walCheckpoint };
2950
+ }
2951
+ function loadEpisodeCandidates(db, limit) {
2952
+ try {
2953
+ return db.prepare(`SELECT id,
2954
+ content,
2955
+ COALESCE(tool_name, '') AS toolName,
2956
+ COALESCE(modality, 'text') AS modality,
2957
+ timestamp,
2958
+ COALESCE(last_retrieved, timestamp) AS lastAccessed,
2959
+ COALESCE(importance, 5) AS importance,
2960
+ COALESCE(strength, 1) AS strength,
2961
+ COALESCE(reuse_count, 0) AS reuseCount,
2962
+ COALESCE(decay_class, 'daily') AS decayClass,
2963
+ embedding
2964
+ FROM episodes
2965
+ WHERE decay_class IN ('session', 'daily')
2966
+ AND COALESCE(importance, 5) < 7.5
2967
+ ORDER BY timestamp ASC
2968
+ LIMIT ?`).all(Math.max(100, limit));
2969
+ } catch {
2970
+ return [];
2971
+ }
2972
+ }
2973
+ function deleteEpisodes(db, ids, dryRun) {
2974
+ const unique = [...new Set(ids)].filter(Boolean);
2975
+ if (dryRun || unique.length === 0)
2976
+ return unique.length;
2977
+ let deleted = 0;
2978
+ const stmt = db.prepare(`DELETE FROM episodes WHERE id = ?`);
2979
+ const tx = db.transaction((rows) => {
2980
+ for (const id of rows) {
2981
+ const result = stmt.run(id);
2982
+ deleted += Number(result.changes ?? 0);
2983
+ }
2984
+ });
2985
+ tx(unique);
2986
+ return deleted;
2987
+ }
2988
+ function maintainGraph(dbPath, options, shouldStop) {
2989
+ const stats = {
2990
+ nodesScanned: 0,
2991
+ nodesMerged: 0,
2992
+ edgesDeduped: 0,
2993
+ inactiveEdgesDeleted: 0,
2994
+ orphanNodesDeleted: 0,
2995
+ lowSignalNodesPruned: 0,
2996
+ lowSignalEdgesPruned: 0,
2997
+ nodesArchived: 0,
2998
+ nodesTotalBefore: 0,
2999
+ nodesTotalAfter: 0
3000
+ };
3001
+ const db = initDb(dbPath);
3002
+ let vacuumed = false;
3003
+ let walCheckpoint = false;
3004
+ try {
3005
+ db.pragma("busy_timeout = 5000");
3006
+ stats.nodesTotalBefore = countNodes(db);
3007
+ if (!shouldStop()) {
3008
+ const nodes = loadGraphNodeCandidates(db, options.maxGraphNodes ?? 4e3);
3009
+ stats.nodesScanned = nodes.length;
3010
+ const mergePairs = vectorDuplicateNodePairs(nodes, options.duplicateSimilarity ?? 0.94).slice(0, options.maxGraphDeletes ?? 1e3);
3011
+ stats.nodesMerged = mergeGraphNodes(db, mergePairs, !!options.dryRun);
3012
+ }
3013
+ if (!shouldStop()) {
3014
+ stats.edgesDeduped = dedupeGraphEdges(db, options.maxGraphDeletes ?? 2e3, !!options.dryRun);
3015
+ }
3016
+ if (!shouldStop()) {
3017
+ stats.inactiveEdgesDeleted = pruneInactiveEdges(db, options.maxGraphDeletes ?? 2e3, !!options.dryRun);
3018
+ }
3019
+ if (!shouldStop()) {
3020
+ stats.orphanNodesDeleted = pruneOrphanNodes(db, options.maxGraphDeletes ?? 2e3, !!options.dryRun);
3021
+ }
3022
+ if (!shouldStop()) {
3023
+ const capped = pruneLowSignalNodesToCap(db, dbPath, options);
3024
+ stats.lowSignalNodesPruned = capped.nodes;
3025
+ stats.lowSignalEdgesPruned = capped.edges;
3026
+ stats.nodesArchived = capped.archived;
3027
+ }
3028
+ stats.nodesTotalAfter = countNodes(db);
3029
+ walCheckpoint = checkpointWal(db);
3030
+ if (!options.dryRun && options.vacuum !== false && stats.nodesMerged + stats.edgesDeduped + stats.inactiveEdgesDeleted + stats.orphanNodesDeleted + stats.lowSignalNodesPruned > 0) {
3031
+ vacuumed = vacuum(db);
3032
+ }
3033
+ } finally {
3034
+ try {
3035
+ db.close();
3036
+ } catch {
3037
+ }
3038
+ }
3039
+ return { graph: stats, vacuumed, walCheckpoint };
3040
+ }
3041
+ function loadGraphNodeCandidates(db, limit) {
3042
+ try {
3043
+ return db.prepare(`SELECT id,
3044
+ text,
3045
+ COALESCE(node_type, 'entity') AS nodeType,
3046
+ COALESCE(first_seen, 0) AS firstSeen,
3047
+ COALESCE(last_seen, 0) AS lastSeen,
3048
+ COALESCE(mention_count, 1) AS mentionCount,
3049
+ embedding
3050
+ FROM kg_nodes
3051
+ ORDER BY mention_count ASC, last_seen ASC
3052
+ LIMIT ?`).all(Math.max(100, limit));
3053
+ } catch {
3054
+ return [];
3055
+ }
3056
+ }
3057
+ function vectorDuplicateNodePairs(rows, threshold) {
3058
+ const pairs = [];
3059
+ const buckets = /* @__PURE__ */ new Map();
3060
+ for (const row of rows) {
3061
+ const bucketKey = row.nodeType || "entity";
3062
+ const bucket = buckets.get(bucketKey) ?? [];
3063
+ const vector = vectorFor(row.embedding, row.text);
3064
+ let merged = false;
3065
+ for (let i = 0; i < bucket.length; i++) {
3066
+ const other = bucket[i];
3067
+ const otherVector = vectorFor(other.embedding, other.text);
3068
+ if (cosine2(vector, otherVector) < threshold)
3069
+ continue;
3070
+ const keep = graphNodeScore(row) > graphNodeScore(other) ? row : other;
3071
+ const drop = keep.id === row.id ? other : row;
3072
+ pairs.push({ keepId: keep.id, dropId: drop.id });
3073
+ if (keep.id === row.id)
3074
+ bucket[i] = row;
3075
+ merged = true;
3076
+ break;
3077
+ }
3078
+ if (!merged)
3079
+ bucket.push(row);
3080
+ buckets.set(bucketKey, bucket);
3081
+ }
3082
+ return pairs;
3083
+ }
3084
+ function mergeGraphNodes(db, pairs, dryRun) {
3085
+ const seen = /* @__PURE__ */ new Set();
3086
+ const unique = pairs.filter((pair) => {
3087
+ if (!pair.keepId || !pair.dropId || pair.keepId === pair.dropId)
3088
+ return false;
3089
+ if (seen.has(pair.dropId))
3090
+ return false;
3091
+ seen.add(pair.dropId);
3092
+ return true;
3093
+ });
3094
+ if (dryRun || unique.length === 0)
3095
+ return unique.length;
3096
+ let merged = 0;
3097
+ const tx = db.transaction((rows) => {
3098
+ const src = db.prepare(`UPDATE kg_edges SET src_id = ? WHERE src_id = ?`);
3099
+ const dst = db.prepare(`UPDATE kg_edges SET dst_id = ? WHERE dst_id = ?`);
3100
+ const del = db.prepare(`DELETE FROM kg_nodes WHERE id = ?`);
3101
+ const bump = db.prepare(`UPDATE kg_nodes
3102
+ SET mention_count = mention_count + COALESCE((SELECT mention_count FROM kg_nodes WHERE id = ?), 0),
3103
+ last_seen = MAX(last_seen, COALESCE((SELECT last_seen FROM kg_nodes WHERE id = ?), last_seen))
3104
+ WHERE id = ?`);
3105
+ for (const pair of rows) {
3106
+ bump.run(pair.dropId, pair.dropId, pair.keepId);
3107
+ src.run(pair.keepId, pair.dropId);
3108
+ dst.run(pair.keepId, pair.dropId);
3109
+ const deleted = del.run(pair.dropId);
3110
+ merged += Number(deleted.changes ?? 0);
3111
+ }
3112
+ });
3113
+ tx(unique);
3114
+ return merged;
3115
+ }
3116
+ function dedupeGraphEdges(db, limit, dryRun) {
3117
+ let ids = [];
3118
+ try {
3119
+ ids = db.prepare(`SELECT id FROM (
3120
+ SELECT id,
3121
+ ROW_NUMBER() OVER (
3122
+ PARTITION BY src_id, dst_id, relation, COALESCE(fact, ''), COALESCE(modality, ''), COALESCE(valid_until, -1)
3123
+ ORDER BY confidence DESC, valid_from DESC, id DESC
3124
+ ) AS rn
3125
+ FROM kg_edges
3126
+ )
3127
+ WHERE rn > 1
3128
+ LIMIT ?`).all(Math.max(1, limit)).map((row) => row.id);
3129
+ } catch {
3130
+ return 0;
3131
+ }
3132
+ if (dryRun || ids.length === 0)
3133
+ return ids.length;
3134
+ return deleteById(db, "kg_edges", ids);
3135
+ }
3136
+ function pruneInactiveEdges(db, limit, dryRun) {
3137
+ const cutoff = Date.now() - 14 * DAY;
3138
+ let ids = [];
3139
+ try {
3140
+ ids = db.prepare(`SELECT id
3141
+ FROM kg_edges
3142
+ WHERE valid_until IS NOT NULL
3143
+ AND valid_until < ?
3144
+ AND COALESCE(confidence, 1) < 0.55
3145
+ ORDER BY valid_until ASC
3146
+ LIMIT ?`).all(cutoff, Math.max(1, limit)).map((row) => row.id);
3147
+ } catch {
3148
+ return 0;
3149
+ }
3150
+ if (dryRun || ids.length === 0)
3151
+ return ids.length;
3152
+ return deleteById(db, "kg_edges", ids);
3153
+ }
3154
+ function pruneOrphanNodes(db, limit, dryRun) {
3155
+ const cutoff = Date.now() - 30 * DAY;
3156
+ let ids = [];
3157
+ try {
3158
+ ids = db.prepare(`SELECT n.id
3159
+ FROM kg_nodes n
3160
+ WHERE COALESCE(n.mention_count, 1) <= 1
3161
+ AND COALESCE(n.last_seen, 0) < ?
3162
+ AND NOT EXISTS (SELECT 1 FROM kg_edges e WHERE e.src_id = n.id OR e.dst_id = n.id)
3163
+ ORDER BY n.last_seen ASC
3164
+ LIMIT ?`).all(cutoff, Math.max(1, limit)).map((row) => row.id);
3165
+ } catch {
3166
+ return 0;
3167
+ }
3168
+ if (dryRun || ids.length === 0)
3169
+ return ids.length;
3170
+ return deleteById(db, "kg_nodes", ids);
3171
+ }
3172
+ function countNodes(db) {
3173
+ try {
3174
+ const row = db.prepare("SELECT COUNT(*) AS n FROM kg_nodes").get();
3175
+ return Number(row?.n ?? 0);
3176
+ } catch {
3177
+ return 0;
3178
+ }
3179
+ }
3180
+ function selectCapEvictionCandidates(db, options, withText) {
3181
+ const cap = Math.max(0, options.graphTargetNodes ?? Number(process.env["OMNIUS_KG_TARGET_NODES"] ?? 5e3));
3182
+ if (!Number.isFinite(cap) || cap <= 0)
3183
+ return [];
3184
+ const total = countNodes(db);
3185
+ if (total <= cap)
3186
+ return [];
3187
+ const overflow = total - cap;
3188
+ const batch = Math.min(overflow, options.maxGraphDeletes ?? 2e3);
3189
+ if (batch <= 0)
3190
+ return [];
3191
+ const now = Date.now();
3192
+ const protectMs = Math.max(0, options.graphProtectRecentMs ?? DAY);
3193
+ const protectCut = now - protectMs;
3194
+ const protect = new Set(options.graphProtectNodeIds ?? []);
3195
+ const hubDegreeGuard = 6;
3196
+ try {
3197
+ const shortlistLimit = Math.max(1, Math.min(batch * 4, batch + 2e4));
3198
+ const cols = withText ? `id, COALESCE(text, '') AS text, COALESCE(node_type, 'entity') AS nodeType,
3199
+ COALESCE(mention_count, 1) AS mc, COALESCE(last_seen, 0) AS ls` : `id, '' AS text, 'entity' AS nodeType,
3200
+ COALESCE(mention_count, 1) AS mc, COALESCE(last_seen, 0) AS ls`;
3201
+ const shortlist = db.prepare(`SELECT ${cols}
3202
+ FROM kg_nodes
3203
+ WHERE COALESCE(last_seen, 0) < ?
3204
+ ORDER BY mc ASC, ls ASC
3205
+ LIMIT ?`).all(protectCut, shortlistLimit);
3206
+ const degStmt = db.prepare("SELECT COUNT(*) AS d FROM kg_edges WHERE src_id = ? OR dst_id = ?");
3207
+ const scored = [];
3208
+ for (const row of shortlist) {
3209
+ if (protect.has(row.id))
3210
+ continue;
3211
+ let deg = 0;
3212
+ try {
3213
+ deg = Number(degStmt.get(row.id, row.id)?.d ?? 0);
3214
+ } catch {
3215
+ deg = 0;
3216
+ }
3217
+ if (deg >= hubDegreeGuard)
3218
+ continue;
3219
+ const ageMs = Math.max(0, now - row.ls);
3220
+ const recency = Math.exp(-ageMs / (30 * DAY));
3221
+ const utility = Math.log1p(row.mc) * 0.5 + Math.min(deg, 8) / 8 * 0.3 + recency * 0.2;
3222
+ scored.push({
3223
+ id: row.id,
3224
+ text: row.text,
3225
+ nodeType: row.nodeType,
3226
+ mentionCount: row.mc,
3227
+ degree: deg,
3228
+ lastSeen: row.ls,
3229
+ score: utility
3230
+ });
3231
+ }
3232
+ scored.sort((a, b) => a.score - b.score || a.lastSeen - b.lastSeen);
3233
+ return scored.slice(0, batch).map(({ score: _score, ...c }) => c);
3234
+ } catch {
3235
+ return [];
3236
+ }
3237
+ }
3238
+ function pruneLowSignalNodesToCap(db, dbPath, options) {
3239
+ const candidates = selectCapEvictionCandidates(db, options, false).map((c) => c.id);
3240
+ if (candidates.length === 0)
3241
+ return { nodes: 0, edges: 0, archived: 0 };
3242
+ if (options.dryRun)
3243
+ return { nodes: candidates.length, edges: 0, archived: 0 };
3244
+ return archiveAndDeleteNodes(db, dbPath, candidates, options.graphArchive !== false);
3245
+ }
3246
+ function selectGraphPruneCandidates(dbPath, options) {
3247
+ if (!existsSync2(dbPath))
3248
+ return [];
3249
+ const db = initDb(dbPath);
3250
+ try {
3251
+ db.pragma("busy_timeout = 5000");
3252
+ return selectCapEvictionCandidates(db, options, true);
3253
+ } catch {
3254
+ return [];
3255
+ } finally {
3256
+ try {
3257
+ db.close();
3258
+ } catch {
3259
+ }
3260
+ }
3261
+ }
3262
+ function reinforceGraphNodes(dbPath, ids) {
3263
+ if (!existsSync2(dbPath) || ids.length === 0)
3264
+ return 0;
3265
+ const db = initDb(dbPath);
3266
+ try {
3267
+ db.pragma("busy_timeout = 5000");
3268
+ const now = Date.now();
3269
+ const stmt = db.prepare("UPDATE kg_nodes SET last_seen = ?, mention_count = COALESCE(mention_count, 1) + 1 WHERE id = ?");
3270
+ let updated = 0;
3271
+ const tx = db.transaction((rows) => {
3272
+ for (const id of rows)
3273
+ updated += Number(stmt.run(now, id).changes ?? 0);
3274
+ });
3275
+ tx(ids);
3276
+ return updated;
3277
+ } catch {
3278
+ return 0;
3279
+ } finally {
3280
+ try {
3281
+ db.close();
3282
+ } catch {
3283
+ }
3284
+ }
3285
+ }
3286
+ function archiveAndDeleteNodes(db, dbPath, ids, archive) {
3287
+ if (ids.length === 0)
3288
+ return { nodes: 0, edges: 0, archived: 0 };
3289
+ const archivePath = dbPath.replace(/\.db$/i, ".archive.db");
3290
+ let attached = false;
3291
+ let archived = 0;
3292
+ let nodesDeleted = 0;
3293
+ let edgesDeleted = 0;
3294
+ try {
3295
+ if (archive) {
3296
+ try {
3297
+ db.prepare("ATTACH DATABASE ? AS kgarchive").run(archivePath);
3298
+ attached = true;
3299
+ db.exec("CREATE TABLE IF NOT EXISTS kgarchive.kg_nodes AS SELECT * FROM main.kg_nodes WHERE 0");
3300
+ db.exec("CREATE TABLE IF NOT EXISTS kgarchive.kg_edges AS SELECT * FROM main.kg_edges WHERE 0");
3301
+ } catch {
3302
+ attached = false;
3303
+ }
3304
+ }
3305
+ const chunkSize = 250;
3306
+ const run = db.transaction((chunk) => {
3307
+ const ph = chunk.map(() => "?").join(",");
3308
+ if (attached) {
3309
+ try {
3310
+ const n = db.prepare(`INSERT INTO kgarchive.kg_nodes SELECT * FROM main.kg_nodes WHERE id IN (${ph})`).run(...chunk);
3311
+ archived += Number(n.changes ?? 0);
3312
+ db.prepare(`INSERT INTO kgarchive.kg_edges SELECT * FROM main.kg_edges WHERE src_id IN (${ph}) OR dst_id IN (${ph})`).run(...chunk, ...chunk);
3313
+ } catch {
3314
+ }
3315
+ }
3316
+ const e = db.prepare(`DELETE FROM kg_edges WHERE src_id IN (${ph}) OR dst_id IN (${ph})`).run(...chunk, ...chunk);
3317
+ edgesDeleted += Number(e.changes ?? 0);
3318
+ const nd = db.prepare(`DELETE FROM kg_nodes WHERE id IN (${ph})`).run(...chunk);
3319
+ nodesDeleted += Number(nd.changes ?? 0);
3320
+ });
3321
+ for (let i = 0; i < ids.length; i += chunkSize) {
3322
+ run(ids.slice(i, i + chunkSize));
3323
+ }
3324
+ } finally {
3325
+ if (attached) {
3326
+ try {
3327
+ db.exec("DETACH DATABASE kgarchive");
3328
+ } catch {
3329
+ }
3330
+ }
3331
+ }
3332
+ return { nodes: nodesDeleted, edges: edgesDeleted, archived };
3333
+ }
3334
+ function deleteById(db, table, ids) {
3335
+ let deleted = 0;
3336
+ const stmt = db.prepare(`DELETE FROM ${table} WHERE id = ?`);
3337
+ const tx = db.transaction((rows) => {
3338
+ for (const id of rows) {
3339
+ const result = stmt.run(id);
3340
+ deleted += Number(result.changes ?? 0);
3341
+ }
3342
+ });
3343
+ tx(ids);
3344
+ return deleted;
3345
+ }
3346
+ function vectorDuplicateIds(rows, threshold) {
3347
+ const duplicateIds = [];
3348
+ const buckets = /* @__PURE__ */ new Map();
3349
+ for (const row of rows) {
3350
+ const key = `${row.toolName}\0${row.modality}`;
3351
+ const bucket = buckets.get(key) ?? [];
3352
+ const vector = vectorFor(row.embedding, row.content);
3353
+ let absorbed = false;
3354
+ for (let i = 0; i < bucket.length; i++) {
3355
+ const other = bucket[i];
3356
+ const otherVector = vectorFor(other.embedding, other.content);
3357
+ if (cosine2(vector, otherVector) < threshold)
3358
+ continue;
3359
+ const keep = memoryUtilityScore(row, Date.now()) > memoryUtilityScore(other, Date.now()) ? row : other;
3360
+ const drop = keep.id === row.id ? other : row;
3361
+ duplicateIds.push(drop.id);
3362
+ if (keep.id === row.id)
3363
+ bucket[i] = row;
3364
+ absorbed = true;
3365
+ break;
3366
+ }
3367
+ if (!absorbed)
3368
+ bucket.push(row);
3369
+ buckets.set(key, bucket);
3370
+ }
3371
+ return [...new Set(duplicateIds)];
3372
+ }
3373
+ function memoryUtilityScore(row, now) {
3374
+ const tau = DECAY_TAU2[row.decayClass] ?? DECAY_TAU2.daily;
3375
+ const retention = tau === Number.POSITIVE_INFINITY ? 1 : Math.exp(-Math.max(0, now - row.lastAccessed) / tau);
3376
+ const importance = clamp014(row.importance / 10);
3377
+ const strength = clamp014(Math.log2(Math.max(1, row.strength)) / 4);
3378
+ const reuse = clamp014(Math.log1p(Math.max(0, row.reuseCount)) / Math.log(9));
3379
+ const recentUse = now - row.lastAccessed <= 6 * HOUR ? 1 : 0;
3380
+ return importance * 0.42 + retention * 0.22 + strength * 0.16 + reuse * 0.16 + recentUse * 0.04;
3381
+ }
3382
+ function graphNodeScore(row) {
3383
+ const age = Math.max(0, Date.now() - row.lastSeen);
3384
+ const recency = Math.exp(-age / (30 * DAY));
3385
+ return Math.log1p(Math.max(0, row.mentionCount)) * 0.7 + recency * 0.3;
3386
+ }
3387
+ function vectorFor(rawEmbedding, text) {
3388
+ const decoded = decodeEmbedding(rawEmbedding);
3389
+ return decoded ?? hashEmbedding(text);
3390
+ }
3391
+ function decodeEmbedding(raw) {
3392
+ if (!raw)
3393
+ return null;
3394
+ if (raw instanceof Float32Array)
3395
+ return Array.from(raw);
3396
+ if (raw instanceof Uint8Array) {
3397
+ if (raw.byteLength === 0 || raw.byteLength % 4 !== 0)
3398
+ return null;
3399
+ const copy = new Uint8Array(raw.byteLength);
3400
+ copy.set(raw);
3401
+ return Array.from(new Float32Array(copy.buffer));
3402
+ }
3403
+ if (raw instanceof ArrayBuffer) {
3404
+ if (raw.byteLength === 0 || raw.byteLength % 4 !== 0)
3405
+ return null;
3406
+ return Array.from(new Float32Array(raw.slice(0)));
3407
+ }
3408
+ return null;
3409
+ }
3410
+ function hashEmbedding(text, dimensions = 256) {
3411
+ const dim = Math.max(16, dimensions);
3412
+ const vector = new Array(dim).fill(0);
3413
+ const tokens = text.toLowerCase().split(/[^\p{L}\p{N}_./-]+/u).map((token) => token.trim()).filter(Boolean);
3414
+ const features = [...tokens];
3415
+ for (let i = 0; i < tokens.length - 1; i++) {
3416
+ features.push(`${tokens[i]} ${tokens[i + 1]}`);
3417
+ }
3418
+ for (const feature of features) {
3419
+ const h = fnv1a(feature);
3420
+ const idx = h % dim;
3421
+ const sign = (h & 2147483648) === 0 ? 1 : -1;
3422
+ vector[idx] += sign;
3423
+ }
3424
+ const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));
3425
+ return norm ? vector.map((value) => value / norm) : vector;
3426
+ }
3427
+ function fnv1a(value) {
3428
+ let hash = 2166136261;
3429
+ for (let i = 0; i < value.length; i++) {
3430
+ hash ^= value.charCodeAt(i);
3431
+ hash = Math.imul(hash, 16777619);
3432
+ }
3433
+ return hash >>> 0;
3434
+ }
3435
+ function cosine2(a, b) {
3436
+ if (a.length !== b.length || a.length === 0)
3437
+ return 0;
3438
+ let dot = 0;
3439
+ let na = 0;
3440
+ let nb = 0;
3441
+ for (let i = 0; i < a.length; i++) {
3442
+ const x = a[i];
3443
+ const y = b[i];
3444
+ dot += x * y;
3445
+ na += x * x;
3446
+ nb += y * y;
3447
+ }
3448
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
3449
+ return denom ? dot / denom : 0;
3450
+ }
3451
+ function clamp014(value) {
3452
+ if (!Number.isFinite(value))
3453
+ return 0;
3454
+ if (value < 0)
3455
+ return 0;
3456
+ if (value > 1)
3457
+ return 1;
3458
+ return value;
3459
+ }
3460
+ function checkpointWal(db) {
3461
+ try {
3462
+ db.pragma("wal_checkpoint(TRUNCATE)");
3463
+ return true;
3464
+ } catch {
3465
+ return false;
3466
+ }
3467
+ }
3468
+ function vacuum(db) {
3469
+ try {
3470
+ db.exec("VACUUM");
3471
+ db.pragma("optimize");
3472
+ return true;
3473
+ } catch {
3474
+ return false;
3475
+ }
3476
+ }
3477
+
3478
+ // packages/cli/src/tui/memory-maintenance-worker.ts
3479
+ import { join as join3 } from "node:path";
3480
+
3481
+ // packages/cli/src/tui/commands/kg-prune-inference.ts
3482
+ function truncate(text, max) {
3483
+ const clean = text.replace(/\s+/g, " ").trim();
3484
+ return clean.length > max ? clean.slice(0, max - 1) + "…" : clean;
3485
+ }
3486
+ async function classifySignalNodes(candidates, cfg) {
3487
+ const max = Math.max(1, Math.min(cfg.maxCandidates ?? 40, 100));
3488
+ const sample = candidates.slice(0, max);
3489
+ if (sample.length === 0) return { reviewed: 0, keepIds: [], ok: true };
3490
+ if (!cfg.model || !cfg.backendUrl) {
3491
+ return { reviewed: 0, keepIds: [], ok: false, error: "no model/endpoint" };
3492
+ }
3493
+ const list = sample.map(
3494
+ (n, i) => `${i + 1}. id=${n.id} type=${n.nodeType} mentions=${n.mentionCount} edges=${n.degree} :: ${truncate(n.text, 160)}`
3495
+ ).join("\n");
3496
+ const system = 'You are the memory-consolidation process of an AI agent, running during idle time (like sleep). You are shown low-activity knowledge-graph nodes that are scheduled to be FORGOTTEN to keep memory bounded. Your job: rescue only the ones that are durable SIGNAL — stable facts, real entities, decisions, identities, or reusable knowledge the agent will likely need again. Let transient, redundant, trivial, or noisy nodes be forgotten. Reply with ONLY a JSON array of the id strings to KEEP, e.g. ["a1","b2"]. Keep the array short and selective. If none are worth keeping, reply []';
3497
+ const user = `Nodes scheduled for forgetting:
3498
+ ${list}
3499
+
3500
+ Return the JSON array of ids to KEEP.`;
3501
+ const timeoutMs = Math.max(3e3, cfg.timeoutMs ?? 3e4);
3502
+ try {
3503
+ const res = await fetch(`${cfg.backendUrl.replace(/\/$/, "")}/api/chat`, {
3504
+ method: "POST",
3505
+ headers: { "Content-Type": "application/json" },
3506
+ body: JSON.stringify({
3507
+ model: cfg.model,
3508
+ messages: [
3509
+ { role: "system", content: system },
3510
+ { role: "user", content: user }
3511
+ ],
3512
+ stream: false,
3513
+ think: false,
3514
+ options: { temperature: 0 },
3515
+ keep_alive: "5m"
3516
+ }),
3517
+ signal: AbortSignal.timeout(timeoutMs)
3518
+ });
3519
+ if (!res.ok) {
3520
+ return {
3521
+ reviewed: sample.length,
3522
+ keepIds: [],
3523
+ ok: false,
3524
+ error: `HTTP ${res.status}`
3525
+ };
3526
+ }
3527
+ const data = await res.json();
3528
+ const content = data.message?.content ?? data.response ?? "";
3529
+ const keepIds = parseIdArray(content, new Set(sample.map((n) => n.id)));
3530
+ return { reviewed: sample.length, keepIds, ok: true };
3531
+ } catch (err) {
3532
+ return {
3533
+ reviewed: sample.length,
3534
+ keepIds: [],
3535
+ ok: false,
3536
+ error: err instanceof Error ? err.message : String(err)
3537
+ };
3538
+ }
3539
+ }
3540
+ function parseIdArray(content, valid) {
3541
+ const match = content.match(/\[[\s\S]*?\]/);
3542
+ if (!match) return [];
3543
+ let parsed;
3544
+ try {
3545
+ parsed = JSON.parse(match[0]);
3546
+ } catch {
3547
+ return [];
3548
+ }
3549
+ if (!Array.isArray(parsed)) return [];
3550
+ const out = [];
3551
+ for (const item of parsed) {
3552
+ const id = typeof item === "string" ? item : String(item ?? "");
3553
+ if (id && valid.has(id) && !out.includes(id)) out.push(id);
3554
+ }
3555
+ return out;
3556
+ }
3557
+
3558
+ // packages/cli/src/tui/memory-maintenance-worker.ts
3559
+ function readBool(name, fallback) {
3560
+ const raw = process.env[name];
3561
+ if (raw === void 0 || raw === "") return fallback;
3562
+ return /^(1|true|yes|on)$/i.test(raw);
3563
+ }
3564
+ function readNumber(name, fallback) {
3565
+ const raw = process.env[name];
3566
+ if (!raw) return fallback;
3567
+ const parsed = Number(raw);
3568
+ return Number.isFinite(parsed) ? parsed : fallback;
3569
+ }
3570
+ async function inferSignalProtectSet(stateDir, options) {
3571
+ const model = process.env["OMNIUS_KG_PRUNE_MODEL"] || process.env["OMNIUS_MODEL"] || "";
3572
+ const inferenceOn = readBool("OMNIUS_KG_PRUNE_INFERENCE", Boolean(model));
3573
+ if (!inferenceOn || !model) return { protectIds: [], reinforced: 0 };
3574
+ const knowledgePath = join3(stateDir, "knowledge.db");
3575
+ const candidates = selectGraphPruneCandidates(knowledgePath, options);
3576
+ if (candidates.length === 0) return { protectIds: [], reinforced: 0 };
3577
+ const backendUrl = process.env["OMNIUS_KG_PRUNE_BACKEND_URL"] || process.env["OMNIUS_BACKEND_URL"] || process.env["OLLAMA_HOST"] || "http://127.0.0.1:11434";
3578
+ const cls = await classifySignalNodes(candidates, {
3579
+ backendUrl,
3580
+ model,
3581
+ timeoutMs: readNumber("OMNIUS_KG_PRUNE_INFERENCE_TIMEOUT_MS", 3e4),
3582
+ maxCandidates: readNumber("OMNIUS_KG_PRUNE_INFERENCE_MAX", 40)
3583
+ });
3584
+ if (!cls.ok || cls.keepIds.length === 0) {
3585
+ return { protectIds: cls.keepIds, reinforced: 0 };
3586
+ }
3587
+ const reinforced = options.dryRun ? 0 : reinforceGraphNodes(knowledgePath, cls.keepIds);
3588
+ return { protectIds: cls.keepIds, reinforced };
3589
+ }
3590
+ async function main() {
3591
+ const stateDir = process.env["OMNIUS_MEMORY_MAINTENANCE_STATE_DIR"];
3592
+ if (!stateDir) throw new Error("OMNIUS_MEMORY_MAINTENANCE_STATE_DIR is required");
3593
+ const options = {
3594
+ stateDir,
3595
+ dryRun: readBool("OMNIUS_MEMORY_MAINTENANCE_DRY_RUN", false),
3596
+ vacuum: readBool("OMNIUS_MEMORY_MAINTENANCE_VACUUM", true),
3597
+ maxRuntimeMs: readNumber("OMNIUS_MEMORY_MAINTENANCE_MAX_MS", 12e4),
3598
+ maxEpisodes: readNumber("OMNIUS_MEMORY_MAINTENANCE_MAX_EPISODES", 2500),
3599
+ maxEpisodeDeletes: readNumber("OMNIUS_MEMORY_MAINTENANCE_MAX_EPISODE_DELETES", 1e3),
3600
+ maxGraphNodes: readNumber("OMNIUS_MEMORY_MAINTENANCE_MAX_GRAPH_NODES", 4e3),
3601
+ maxGraphDeletes: readNumber("OMNIUS_MEMORY_MAINTENANCE_MAX_GRAPH_DELETES", 2e3),
3602
+ duplicateSimilarity: readNumber("OMNIUS_MEMORY_MAINTENANCE_SIMILARITY", 0.965),
3603
+ minUtilityScore: readNumber("OMNIUS_MEMORY_MAINTENANCE_MIN_UTILITY", 0.18),
3604
+ // Cap-based KG decay prune — the floor that keeps knowledge.db bounded.
3605
+ graphTargetNodes: readNumber("OMNIUS_KG_TARGET_NODES", 5e3),
3606
+ graphArchive: readBool("OMNIUS_KG_PRUNE_ARCHIVE", true),
3607
+ graphProtectRecentMs: readNumber(
3608
+ "OMNIUS_KG_PROTECT_RECENT_MS",
3609
+ 24 * 60 * 60 * 1e3
3610
+ )
3611
+ };
3612
+ const inference = await inferSignalProtectSet(stateDir, options).catch(() => ({
3613
+ protectIds: [],
3614
+ reinforced: 0
3615
+ }));
3616
+ if (inference.protectIds.length > 0) {
3617
+ options.graphProtectNodeIds = inference.protectIds;
3618
+ }
3619
+ const result = runMemoryMaintenance(options);
3620
+ process.stdout.write(
3621
+ `${JSON.stringify({
3622
+ ...result,
3623
+ inference: {
3624
+ protectedSignal: inference.protectIds.length,
3625
+ reinforced: inference.reinforced
3626
+ }
3627
+ })}
3628
+ `
3629
+ );
3630
+ }
3631
+ main().catch((error) => {
3632
+ const message = error instanceof Error ? error.message : String(error);
3633
+ process.stderr.write(`[memory-maintenance-worker] ${message}
3634
+ `);
3635
+ process.exitCode = 1;
3636
+ });