remem-mcp 0.7.3 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -59
- package/dist/{chunk-ITMR6THY.js → chunk-2RKMX6EE.js} +5 -4
- package/dist/chunk-2RKMX6EE.js.map +1 -0
- package/dist/{chunk-4WCDAEPP.js → chunk-L7YZBBIB.js} +306 -32
- package/dist/chunk-L7YZBBIB.js.map +1 -0
- package/dist/{chunk-YU7WBJU5.js → chunk-MPEN2X43.js} +106 -2
- package/dist/{chunk-YU7WBJU5.js.map → chunk-MPEN2X43.js.map} +1 -1
- package/dist/{engine-S47HHGAO.js → engine-LAY25SCA.js} +4 -2
- package/dist/index.js +440 -261
- package/dist/index.js.map +1 -1
- package/dist/sdk.js +3 -3
- package/dist/{server-5PWQTRGZ.js → server-MYSF7R5Y.js} +4 -3
- package/dist/server-MYSF7R5Y.js.map +1 -0
- package/dist/storage/schema.sql +15 -5
- package/package.json +1 -1
- package/skills/remem-mcp/SKILL.md +3 -1
- package/dist/chunk-4WCDAEPP.js.map +0 -1
- package/dist/chunk-ITMR6THY.js.map +0 -1
- package/dist/server-5PWQTRGZ.js.map +0 -1
- /package/dist/{engine-S47HHGAO.js.map → engine-LAY25SCA.js.map} +0 -0
|
@@ -221,7 +221,7 @@ function stripQueryProperNouns(query) {
|
|
|
221
221
|
if (noProperNouns.length > 0) return noProperNouns.join(" ");
|
|
222
222
|
return filtered.length > 0 ? filtered.join(" ") : tokens.join(" ");
|
|
223
223
|
}
|
|
224
|
-
var CURRENT_SCHEMA_VERSION =
|
|
224
|
+
var CURRENT_SCHEMA_VERSION = 17;
|
|
225
225
|
var EVERGREEN_TAGS = new Set(
|
|
226
226
|
(process.env.REMEM_EVERGREEN_TAGS ?? "evergreen,never-forget").split(",").map((t) => t.trim().toLowerCase()).filter(Boolean)
|
|
227
227
|
);
|
|
@@ -423,6 +423,14 @@ var SQLiteBackend = class {
|
|
|
423
423
|
this.migrateV5ToV6();
|
|
424
424
|
this.migrateV6ToV7();
|
|
425
425
|
this.migrateV7ToV8();
|
|
426
|
+
this.migrateV8ToV9();
|
|
427
|
+
this.migrateV9ToV10();
|
|
428
|
+
this.migrateV11ToV12();
|
|
429
|
+
this.migrateV12ToV13();
|
|
430
|
+
this.migrateV13ToV14();
|
|
431
|
+
this.migrateV14ToV15();
|
|
432
|
+
this.migrateV15ToV16();
|
|
433
|
+
this.migrateV16ToV17();
|
|
426
434
|
this.runSchema();
|
|
427
435
|
this.writeSchemaVersion(CURRENT_SCHEMA_VERSION);
|
|
428
436
|
})();
|
|
@@ -540,6 +548,42 @@ var SQLiteBackend = class {
|
|
|
540
548
|
this.writeSchemaVersion(13);
|
|
541
549
|
})();
|
|
542
550
|
}
|
|
551
|
+
if (currentVersion < 14) {
|
|
552
|
+
migrationsRan = true;
|
|
553
|
+
this.backupDatabase(dbPath, 13);
|
|
554
|
+
this.db.transaction(() => {
|
|
555
|
+
this.migrateV13ToV14();
|
|
556
|
+
this.runSchema();
|
|
557
|
+
this.writeSchemaVersion(14);
|
|
558
|
+
})();
|
|
559
|
+
}
|
|
560
|
+
if (currentVersion < 15) {
|
|
561
|
+
migrationsRan = true;
|
|
562
|
+
this.backupDatabase(dbPath, 14);
|
|
563
|
+
this.db.transaction(() => {
|
|
564
|
+
this.migrateV14ToV15();
|
|
565
|
+
this.runSchema();
|
|
566
|
+
this.writeSchemaVersion(15);
|
|
567
|
+
})();
|
|
568
|
+
}
|
|
569
|
+
if (currentVersion < 16) {
|
|
570
|
+
migrationsRan = true;
|
|
571
|
+
this.backupDatabase(dbPath, 15);
|
|
572
|
+
this.db.transaction(() => {
|
|
573
|
+
this.migrateV15ToV16();
|
|
574
|
+
this.runSchema();
|
|
575
|
+
this.writeSchemaVersion(16);
|
|
576
|
+
})();
|
|
577
|
+
}
|
|
578
|
+
if (currentVersion < 17) {
|
|
579
|
+
migrationsRan = true;
|
|
580
|
+
this.backupDatabase(dbPath, 16);
|
|
581
|
+
this.db.transaction(() => {
|
|
582
|
+
this.migrateV16ToV17();
|
|
583
|
+
this.runSchema();
|
|
584
|
+
this.writeSchemaVersion(17);
|
|
585
|
+
})();
|
|
586
|
+
}
|
|
543
587
|
this.rebuildFtsIfNeeded(migrationsRan);
|
|
544
588
|
}
|
|
545
589
|
/**
|
|
@@ -630,6 +674,7 @@ var SQLiteBackend = class {
|
|
|
630
674
|
addColumnIfMissing("scenarios", "team_id", "TEXT");
|
|
631
675
|
addColumnIfMissing("scenarios", "agent_id", "TEXT");
|
|
632
676
|
addColumnIfMissing("scenarios", "user_id", "TEXT");
|
|
677
|
+
addColumnIfMissing("scenarios", "session_key", "TEXT");
|
|
633
678
|
this.db.exec(`
|
|
634
679
|
CREATE TABLE IF NOT EXISTS messages (
|
|
635
680
|
id TEXT PRIMARY KEY,
|
|
@@ -770,6 +815,11 @@ var SQLiteBackend = class {
|
|
|
770
815
|
}
|
|
771
816
|
/** Migrate schema v8 → v9: add CodeGraph call resolution columns (module_path, confidence, call_type). */
|
|
772
817
|
migrateV8ToV9() {
|
|
818
|
+
const symbolsExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='symbols'").get();
|
|
819
|
+
if (!symbolsExists) {
|
|
820
|
+
console.error("[remem-mcp] Migrated schema v8 \u2192 v9 (symbols table not yet created, skipping)");
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
773
823
|
let addedAny = false;
|
|
774
824
|
try {
|
|
775
825
|
this.db.exec("ALTER TABLE symbols ADD COLUMN module_path TEXT");
|
|
@@ -840,6 +890,11 @@ var SQLiteBackend = class {
|
|
|
840
890
|
* v12 → v13: Add weight column to memory_links for Hebbian co-retrieval strengthening.
|
|
841
891
|
*/
|
|
842
892
|
migrateV12ToV13() {
|
|
893
|
+
const tableExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_links'").get();
|
|
894
|
+
if (!tableExists) {
|
|
895
|
+
console.error("[remem-mcp] Migrated schema v12 \u2192 v13 (memory_links not yet created, skipping)");
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
843
898
|
const cols = this.db.prepare("PRAGMA table_info(memory_links)").all();
|
|
844
899
|
const colNames = new Set(cols.map((c) => c.name));
|
|
845
900
|
if (!colNames.has("weight")) {
|
|
@@ -847,6 +902,118 @@ var SQLiteBackend = class {
|
|
|
847
902
|
}
|
|
848
903
|
console.error("[remem-mcp] Migrated schema v12 \u2192 v13 (Hebbian link weights)");
|
|
849
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* v13 → v14: Add session_key column to scenarios so L2 scenario injection in
|
|
907
|
+
* hooks can be scoped to the current project (hash(cwd)). Legacy rows keep
|
|
908
|
+
* NULL session_key and are excluded from hook injection (no cross-project leak).
|
|
909
|
+
*/
|
|
910
|
+
migrateV13ToV14() {
|
|
911
|
+
const tableExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='scenarios'").get();
|
|
912
|
+
if (!tableExists) {
|
|
913
|
+
console.error("[remem-mcp] Migrated schema v13 \u2192 v14 (scenarios table not yet created, skipping)");
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const cols = this.db.prepare("PRAGMA table_info(scenarios)").all();
|
|
917
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
918
|
+
if (!colNames.has("session_key")) {
|
|
919
|
+
this.db.exec("ALTER TABLE scenarios ADD COLUMN session_key TEXT");
|
|
920
|
+
}
|
|
921
|
+
this.db.exec(
|
|
922
|
+
"CREATE INDEX IF NOT EXISTS idx_scenarios_session ON scenarios (session_key, created_at DESC)"
|
|
923
|
+
);
|
|
924
|
+
console.error("[remem-mcp] Migrated schema v13 \u2192 v14 (scenario session_key scoping)");
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* v14 → v15: Add v9 skill columns to the existing skills table.
|
|
928
|
+
*
|
|
929
|
+
* The skills table was created by migrateV2ToV3 with only 8 columns
|
|
930
|
+
* (id, team_id, agent_id, name, description, content, version, created_at,
|
|
931
|
+
* updated_at). The v9 columns (trigger_conditions, steps, validation_rules,
|
|
932
|
+
* source_capture_ids, archived) are defined in schema.sql's CREATE TABLE IF
|
|
933
|
+
* NOT EXISTS, but that statement is a no-op on the existing table. Without
|
|
934
|
+
* this migration, every putSkill / getSkillContext / skill injection fails
|
|
935
|
+
* with SqliteError: no such column: steps.
|
|
936
|
+
*/
|
|
937
|
+
migrateV14ToV15() {
|
|
938
|
+
const tableExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='skills'").get();
|
|
939
|
+
if (!tableExists) {
|
|
940
|
+
console.error("[remem-mcp] Migrated schema v14 \u2192 v15 (skills table not yet created, skipping)");
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const cols = this.db.prepare("PRAGMA table_info(skills)").all();
|
|
944
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
945
|
+
const addColumnIfMissing = (column, definition) => {
|
|
946
|
+
if (!colNames.has(column)) {
|
|
947
|
+
this.db.exec(`ALTER TABLE skills ADD COLUMN ${column} ${definition}`);
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
addColumnIfMissing("trigger_conditions", "TEXT");
|
|
951
|
+
addColumnIfMissing("steps", "TEXT");
|
|
952
|
+
addColumnIfMissing("validation_rules", "TEXT");
|
|
953
|
+
addColumnIfMissing("source_capture_ids", "TEXT");
|
|
954
|
+
addColumnIfMissing("archived", "INTEGER NOT NULL DEFAULT 0");
|
|
955
|
+
console.error("[remem-mcp] Migrated schema v14 \u2192 v15 (skills table v9 columns)");
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* v15 → v16: Add session_key column to persona table for per-project personas.
|
|
959
|
+
*
|
|
960
|
+
* Previously persona was keyed by (team_id, agent_id, user_id) only — all
|
|
961
|
+
* projects shared one persona. This caused cross-project tag pollution
|
|
962
|
+
* (e.g., bugbounty tags appearing in AZR persona). Adding session_key
|
|
963
|
+
* scoping lets each project maintain its own persona.
|
|
964
|
+
*
|
|
965
|
+
* Since SQLite can't ALTER a PRIMARY KEY, we rebuild the table: create a
|
|
966
|
+
* temp table with the new schema, copy data (legacy rows get NULL session_key),
|
|
967
|
+
* drop old, rename new.
|
|
968
|
+
*/
|
|
969
|
+
migrateV15ToV16() {
|
|
970
|
+
const tableExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='persona'").get();
|
|
971
|
+
if (!tableExists) {
|
|
972
|
+
console.error("[remem-mcp] Migrated schema v15 \u2192 v16 (persona table not yet created, skipping)");
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const cols = this.db.prepare("PRAGMA table_info(persona)").all();
|
|
976
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
977
|
+
if (!colNames.has("session_key")) {
|
|
978
|
+
this.db.exec(`
|
|
979
|
+
CREATE TABLE persona_v16 (
|
|
980
|
+
team_id TEXT NOT NULL,
|
|
981
|
+
agent_id TEXT NOT NULL,
|
|
982
|
+
user_id TEXT NOT NULL,
|
|
983
|
+
content TEXT NOT NULL,
|
|
984
|
+
updated_at INTEGER NOT NULL,
|
|
985
|
+
session_key TEXT,
|
|
986
|
+
PRIMARY KEY (team_id, agent_id, user_id, session_key)
|
|
987
|
+
);
|
|
988
|
+
INSERT INTO persona_v16 (team_id, agent_id, user_id, content, updated_at, session_key)
|
|
989
|
+
SELECT team_id, agent_id, user_id, content, updated_at, NULL FROM persona;
|
|
990
|
+
DROP TABLE persona;
|
|
991
|
+
ALTER TABLE persona_v16 RENAME TO persona;
|
|
992
|
+
CREATE INDEX IF NOT EXISTS idx_persona_session ON persona (session_key, team_id, user_id);
|
|
993
|
+
`);
|
|
994
|
+
}
|
|
995
|
+
console.error("[remem-mcp] Migrated schema v15 \u2192 v16 (persona session_key scoping)");
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* v16 → v17: Add temporal validity (valid_from, valid_until) and provenance (source_ref)
|
|
999
|
+
* columns to captures. SodaMem-inspired: supersede now closes the old version with valid_until.
|
|
1000
|
+
* source_ref links a capture to its originating tool call or conversation turn.
|
|
1001
|
+
*/
|
|
1002
|
+
migrateV16ToV17() {
|
|
1003
|
+
const cols = this.db.prepare("PRAGMA table_info(captures)").all();
|
|
1004
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
1005
|
+
if (!colNames.has("valid_from")) {
|
|
1006
|
+
this.db.exec("ALTER TABLE captures ADD COLUMN valid_from INTEGER");
|
|
1007
|
+
}
|
|
1008
|
+
if (!colNames.has("valid_until")) {
|
|
1009
|
+
this.db.exec("ALTER TABLE captures ADD COLUMN valid_until INTEGER");
|
|
1010
|
+
}
|
|
1011
|
+
if (!colNames.has("source_ref")) {
|
|
1012
|
+
this.db.exec("ALTER TABLE captures ADD COLUMN source_ref TEXT");
|
|
1013
|
+
}
|
|
1014
|
+
this.db.exec("UPDATE captures SET valid_from = created_at WHERE valid_from IS NULL");
|
|
1015
|
+
console.error("[remem-mcp] Migrated schema v16 \u2192 v17 (temporal validity + provenance)");
|
|
1016
|
+
}
|
|
850
1017
|
async put(entry) {
|
|
851
1018
|
this.putInternal(entry);
|
|
852
1019
|
}
|
|
@@ -859,8 +1026,8 @@ var SQLiteBackend = class {
|
|
|
859
1026
|
).get(contentHash, entry.sessionKey, entry.agentId);
|
|
860
1027
|
if (dup) return false;
|
|
861
1028
|
this.db.prepare(
|
|
862
|
-
`INSERT INTO captures (id, session_key, agent_id, type, content, content_hash, tags, created_at, metadata, team_id, user_id, task_id, trust_state, tier)
|
|
863
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1029
|
+
`INSERT INTO captures (id, session_key, agent_id, type, content, content_hash, tags, created_at, metadata, team_id, user_id, task_id, trust_state, tier, valid_from, source_ref)
|
|
1030
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
864
1031
|
).run(
|
|
865
1032
|
entry.id,
|
|
866
1033
|
entry.sessionKey,
|
|
@@ -875,7 +1042,11 @@ var SQLiteBackend = class {
|
|
|
875
1042
|
entry.userId ?? null,
|
|
876
1043
|
entry.taskId ?? null,
|
|
877
1044
|
entry.trustState ?? "candidate",
|
|
878
|
-
tier
|
|
1045
|
+
tier,
|
|
1046
|
+
entry.createdAt,
|
|
1047
|
+
// valid_from = creation time (SodaMem: when the fact became true)
|
|
1048
|
+
entry.metadata?.source ?? null
|
|
1049
|
+
// source_ref from metadata
|
|
879
1050
|
);
|
|
880
1051
|
if (entry.messages && entry.messages.length > 0) {
|
|
881
1052
|
const msgStmt = this.db.prepare(
|
|
@@ -896,6 +1067,7 @@ var SQLiteBackend = class {
|
|
|
896
1067
|
this.putEntitiesInternal(entry.id, entities);
|
|
897
1068
|
}
|
|
898
1069
|
this.autoLinkCapture(entry.id, entry.tags, entities, entry.sessionKey);
|
|
1070
|
+
this.extractCausalLinks(entry.id, entry.content, entry.tags, entry.sessionKey);
|
|
899
1071
|
} catch {
|
|
900
1072
|
}
|
|
901
1073
|
this.recordAuditInternal("put", entry.id, { type: entry.type, tags: entry.tags }, entry.agentId);
|
|
@@ -1178,8 +1350,9 @@ var SQLiteBackend = class {
|
|
|
1178
1350
|
rawFallbackSearch(query, limit, sessionKey, filters) {
|
|
1179
1351
|
const ftsQuery = this.escapeFtsQuery(query);
|
|
1180
1352
|
if (!ftsQuery) return [];
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1353
|
+
const now = Date.now();
|
|
1354
|
+
const conditions = ["c.deleted_at IS NULL", "c.superseded_by IS NULL", "(c.valid_until IS NULL OR c.valid_until > ?)"];
|
|
1355
|
+
const params = [now];
|
|
1183
1356
|
if (sessionKey) {
|
|
1184
1357
|
conditions.push("c.session_key = ?");
|
|
1185
1358
|
params.push(sessionKey);
|
|
@@ -1222,13 +1395,15 @@ var SQLiteBackend = class {
|
|
|
1222
1395
|
const ftsQuery = this.escapeFtsQuery(query);
|
|
1223
1396
|
if (!ftsQuery) return [];
|
|
1224
1397
|
let sql = "";
|
|
1225
|
-
const
|
|
1398
|
+
const now = Date.now();
|
|
1399
|
+
const params = [ftsQuery, now];
|
|
1226
1400
|
try {
|
|
1227
1401
|
sql = `
|
|
1228
1402
|
SELECT fts.id as id, bm25(captures_fts) as score
|
|
1229
1403
|
FROM captures_fts fts
|
|
1230
1404
|
JOIN captures c ON c.id = fts.id
|
|
1231
1405
|
WHERE captures_fts MATCH ? AND c.deleted_at IS NULL AND c.trust_state != 'rejected' AND c.superseded_by IS NULL
|
|
1406
|
+
AND (c.valid_until IS NULL OR c.valid_until > ?)
|
|
1232
1407
|
`;
|
|
1233
1408
|
if (sessionKey) {
|
|
1234
1409
|
sql += " AND c.session_key = ?";
|
|
@@ -1284,13 +1459,15 @@ var SQLiteBackend = class {
|
|
|
1284
1459
|
/** Run a vector search via sqlite-vec. */
|
|
1285
1460
|
vectorSearch(embedding, limit, sessionKey, filters) {
|
|
1286
1461
|
const buffer = new Float32Array(embedding);
|
|
1462
|
+
const now = Date.now();
|
|
1287
1463
|
let sql = `
|
|
1288
1464
|
SELECT vec.id as id, vec.distance as score
|
|
1289
1465
|
FROM captures_vec vec
|
|
1290
1466
|
JOIN captures c ON c.id = vec.id
|
|
1291
1467
|
WHERE vec.embedding MATCH ? AND vec.k = ? AND c.deleted_at IS NULL AND c.trust_state != 'rejected' AND c.superseded_by IS NULL
|
|
1468
|
+
AND (c.valid_until IS NULL OR c.valid_until > ?)
|
|
1292
1469
|
`;
|
|
1293
|
-
const params = [Buffer.from(buffer.buffer), limit];
|
|
1470
|
+
const params = [Buffer.from(buffer.buffer), limit, now];
|
|
1294
1471
|
if (sessionKey) {
|
|
1295
1472
|
sql += " AND c.session_key = ?";
|
|
1296
1473
|
params.push(sessionKey);
|
|
@@ -1596,9 +1773,10 @@ var SQLiteBackend = class {
|
|
|
1596
1773
|
}));
|
|
1597
1774
|
}
|
|
1598
1775
|
async supersede(loserId, winnerId) {
|
|
1776
|
+
const now = Date.now();
|
|
1599
1777
|
const updated = this.db.prepare(
|
|
1600
|
-
"UPDATE captures SET trust_state = 'stale', superseded_by = ? WHERE id = ? AND deleted_at IS NULL AND trust_state != 'rejected'"
|
|
1601
|
-
).run(winnerId, loserId).changes;
|
|
1778
|
+
"UPDATE captures SET trust_state = 'stale', superseded_by = ?, valid_until = ? WHERE id = ? AND deleted_at IS NULL AND trust_state != 'rejected'"
|
|
1779
|
+
).run(winnerId, now, loserId).changes;
|
|
1602
1780
|
if (updated > 0) {
|
|
1603
1781
|
this.db.prepare("DELETE FROM captures_vec WHERE id = ?").run(loserId);
|
|
1604
1782
|
}
|
|
@@ -1625,25 +1803,33 @@ var SQLiteBackend = class {
|
|
|
1625
1803
|
);
|
|
1626
1804
|
}
|
|
1627
1805
|
async listAtoms(opts) {
|
|
1628
|
-
let sql =
|
|
1806
|
+
let sql = `SELECT a.* FROM atoms a`;
|
|
1629
1807
|
const params = [];
|
|
1808
|
+
if (opts.sessionKey) {
|
|
1809
|
+
sql += ` JOIN captures c ON a.capture_id = c.id`;
|
|
1810
|
+
}
|
|
1811
|
+
sql += ` WHERE 1=1`;
|
|
1812
|
+
if (opts.sessionKey) {
|
|
1813
|
+
sql += ` AND c.session_key = ?`;
|
|
1814
|
+
params.push(opts.sessionKey);
|
|
1815
|
+
}
|
|
1630
1816
|
if (opts.teamId) {
|
|
1631
|
-
sql += " AND team_id = ?";
|
|
1817
|
+
sql += " AND a.team_id = ?";
|
|
1632
1818
|
params.push(opts.teamId);
|
|
1633
1819
|
}
|
|
1634
1820
|
if (opts.agentId) {
|
|
1635
|
-
sql += " AND agent_id = ?";
|
|
1821
|
+
sql += " AND a.agent_id = ?";
|
|
1636
1822
|
params.push(opts.agentId);
|
|
1637
1823
|
}
|
|
1638
1824
|
if (opts.userId) {
|
|
1639
|
-
sql += " AND user_id = ?";
|
|
1825
|
+
sql += " AND a.user_id = ?";
|
|
1640
1826
|
params.push(opts.userId);
|
|
1641
1827
|
}
|
|
1642
1828
|
if (opts.captureId) {
|
|
1643
|
-
sql += " AND capture_id = ?";
|
|
1829
|
+
sql += " AND a.capture_id = ?";
|
|
1644
1830
|
params.push(opts.captureId);
|
|
1645
1831
|
}
|
|
1646
|
-
sql += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
1832
|
+
sql += " ORDER BY a.created_at DESC LIMIT ? OFFSET ?";
|
|
1647
1833
|
params.push(opts.limit ?? 20, opts.offset ?? 0);
|
|
1648
1834
|
const rows = this.db.prepare(sql).all(...params);
|
|
1649
1835
|
return rows.map((r) => ({
|
|
@@ -1690,7 +1876,7 @@ var SQLiteBackend = class {
|
|
|
1690
1876
|
// ─── L2 scenarios ───────────────────────────────────────────
|
|
1691
1877
|
async putScenario(scenario) {
|
|
1692
1878
|
this.db.prepare(
|
|
1693
|
-
"INSERT INTO scenarios (id, atom_ids, summary, persona_tags, created_at, team_id, agent_id, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
|
1879
|
+
"INSERT INTO scenarios (id, atom_ids, summary, persona_tags, created_at, team_id, agent_id, user_id, session_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
1694
1880
|
).run(
|
|
1695
1881
|
scenario.id,
|
|
1696
1882
|
JSON.stringify(scenario.atomIds),
|
|
@@ -1699,7 +1885,8 @@ var SQLiteBackend = class {
|
|
|
1699
1885
|
scenario.createdAt,
|
|
1700
1886
|
scenario.teamId ?? null,
|
|
1701
1887
|
scenario.agentId ?? null,
|
|
1702
|
-
scenario.userId ?? null
|
|
1888
|
+
scenario.userId ?? null,
|
|
1889
|
+
scenario.sessionKey ?? null
|
|
1703
1890
|
);
|
|
1704
1891
|
}
|
|
1705
1892
|
async listScenarios(opts) {
|
|
@@ -1717,6 +1904,10 @@ var SQLiteBackend = class {
|
|
|
1717
1904
|
sql += " AND user_id = ?";
|
|
1718
1905
|
params.push(opts.userId);
|
|
1719
1906
|
}
|
|
1907
|
+
if (opts.sessionKey) {
|
|
1908
|
+
sql += " AND session_key = ?";
|
|
1909
|
+
params.push(opts.sessionKey);
|
|
1910
|
+
}
|
|
1720
1911
|
sql += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
1721
1912
|
params.push(opts.limit ?? 20, opts.offset ?? 0);
|
|
1722
1913
|
const rows = this.db.prepare(sql).all(...params);
|
|
@@ -1728,7 +1919,8 @@ var SQLiteBackend = class {
|
|
|
1728
1919
|
createdAt: r.created_at,
|
|
1729
1920
|
teamId: r.team_id ?? void 0,
|
|
1730
1921
|
agentId: r.agent_id ?? void 0,
|
|
1731
|
-
userId: r.user_id ?? void 0
|
|
1922
|
+
userId: r.user_id ?? void 0,
|
|
1923
|
+
sessionKey: r.session_key ?? void 0
|
|
1732
1924
|
}));
|
|
1733
1925
|
}
|
|
1734
1926
|
async getScenario(id) {
|
|
@@ -1742,12 +1934,25 @@ var SQLiteBackend = class {
|
|
|
1742
1934
|
createdAt: row.created_at,
|
|
1743
1935
|
teamId: row.team_id ?? void 0,
|
|
1744
1936
|
agentId: row.agent_id ?? void 0,
|
|
1745
|
-
userId: row.user_id ?? void 0
|
|
1937
|
+
userId: row.user_id ?? void 0,
|
|
1938
|
+
sessionKey: row.session_key ?? void 0
|
|
1746
1939
|
};
|
|
1747
1940
|
}
|
|
1748
1941
|
// ─── L3 persona ─────────────────────────────────────────────
|
|
1749
|
-
async readPersona(teamId, agentId, userId) {
|
|
1750
|
-
|
|
1942
|
+
async readPersona(teamId, agentId, userId, sessionKey) {
|
|
1943
|
+
if (sessionKey) {
|
|
1944
|
+
const scopedRow = this.db.prepare("SELECT * FROM persona WHERE team_id = ? AND agent_id = ? AND user_id = ? AND session_key = ? ORDER BY updated_at DESC LIMIT 1").get(teamId, agentId, userId, sessionKey);
|
|
1945
|
+
if (scopedRow) {
|
|
1946
|
+
return {
|
|
1947
|
+
teamId: scopedRow.team_id,
|
|
1948
|
+
agentId: scopedRow.agent_id,
|
|
1949
|
+
userId: scopedRow.user_id,
|
|
1950
|
+
content: scopedRow.content,
|
|
1951
|
+
updatedAt: scopedRow.updated_at
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
const row = this.db.prepare("SELECT * FROM persona WHERE team_id = ? AND agent_id = ? AND user_id = ? AND (session_key IS NULL OR session_key = '') ORDER BY updated_at DESC LIMIT 1").get(teamId, agentId, userId);
|
|
1751
1956
|
if (!row) return null;
|
|
1752
1957
|
return {
|
|
1753
1958
|
teamId: row.team_id,
|
|
@@ -1757,12 +1962,19 @@ var SQLiteBackend = class {
|
|
|
1757
1962
|
updatedAt: row.updated_at
|
|
1758
1963
|
};
|
|
1759
1964
|
}
|
|
1760
|
-
async writePersona(teamId, agentId, userId, content) {
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1965
|
+
async writePersona(teamId, agentId, userId, content, sessionKey) {
|
|
1966
|
+
if (sessionKey) {
|
|
1967
|
+
this.db.prepare(
|
|
1968
|
+
`INSERT INTO persona (team_id, agent_id, user_id, content, updated_at, session_key)
|
|
1969
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1970
|
+
ON CONFLICT(team_id, agent_id, user_id, session_key) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at`
|
|
1971
|
+
).run(teamId, agentId, userId, content, Date.now(), sessionKey);
|
|
1972
|
+
} else {
|
|
1973
|
+
this.db.transaction(() => {
|
|
1974
|
+
this.db.prepare("DELETE FROM persona WHERE team_id = ? AND agent_id = ? AND user_id = ? AND (session_key IS NULL OR session_key = '')").run(teamId, agentId, userId);
|
|
1975
|
+
this.db.prepare("INSERT INTO persona (team_id, agent_id, user_id, content, updated_at) VALUES (?, ?, ?, ?, ?)").run(teamId, agentId, userId, content, Date.now());
|
|
1976
|
+
})();
|
|
1977
|
+
}
|
|
1766
1978
|
}
|
|
1767
1979
|
// ─── Knowledge ──────────────────────────────────────────────
|
|
1768
1980
|
async putKnowledge(entry) {
|
|
@@ -2035,11 +2247,13 @@ var SQLiteBackend = class {
|
|
|
2035
2247
|
if (entities.length === 0) return [];
|
|
2036
2248
|
const normalized = entities.map((e) => e.toLowerCase());
|
|
2037
2249
|
const placeholders = normalized.map(() => "?").join(",");
|
|
2038
|
-
const
|
|
2250
|
+
const now = Date.now();
|
|
2251
|
+
const params = [...normalized, now];
|
|
2039
2252
|
const conditions = [
|
|
2040
2253
|
"c.deleted_at IS NULL",
|
|
2041
2254
|
"c.trust_state != 'rejected'",
|
|
2042
|
-
"c.superseded_by IS NULL"
|
|
2255
|
+
"c.superseded_by IS NULL",
|
|
2256
|
+
"(c.valid_until IS NULL OR c.valid_until > ?)"
|
|
2043
2257
|
];
|
|
2044
2258
|
if (sessionKey) {
|
|
2045
2259
|
conditions.push("c.session_key = ?");
|
|
@@ -2206,6 +2420,63 @@ var SQLiteBackend = class {
|
|
|
2206
2420
|
linkStmt.run(captureId, row.other_id, "session-proximity", now);
|
|
2207
2421
|
}
|
|
2208
2422
|
}
|
|
2423
|
+
/**
|
|
2424
|
+
* v17: Causal link auto-extraction (MAGMA-inspired).
|
|
2425
|
+
* Detects causal language in capture content and links to nearby captures
|
|
2426
|
+
* in the same session that share tags or entities.
|
|
2427
|
+
*
|
|
2428
|
+
* Causal patterns: "caused by", "led to", "because of", "resulted in",
|
|
2429
|
+
* "due to", "fixed by", "root cause", "triggered by".
|
|
2430
|
+
*
|
|
2431
|
+
* Direction: if capture A says "X caused Y" and capture B (nearby, same tags)
|
|
2432
|
+
* contains "X", then A → B with link_type "cause-effect".
|
|
2433
|
+
*/
|
|
2434
|
+
extractCausalLinks(captureId, content, tags, sessionKey) {
|
|
2435
|
+
const causalPatterns = [
|
|
2436
|
+
/\bcaused\s+by\b/i,
|
|
2437
|
+
/\bled\s+to\b/i,
|
|
2438
|
+
/\bbecause\s+of\b/i,
|
|
2439
|
+
/\bresulted\s+in\b/i,
|
|
2440
|
+
/\bdue\s+to\b/i,
|
|
2441
|
+
/\bfixed\s+by\b/i,
|
|
2442
|
+
/\broot\s+cause\b/i,
|
|
2443
|
+
/\btriggered\s+by\b/i,
|
|
2444
|
+
/\bfix(?:ed|es)?\s+(?:this|that|the)\b/i
|
|
2445
|
+
];
|
|
2446
|
+
const hasCausalLanguage = causalPatterns.some((p) => p.test(content));
|
|
2447
|
+
if (tags.length === 0) return;
|
|
2448
|
+
const now = Date.now();
|
|
2449
|
+
const tagConditions = tags.map(() => "c.tags LIKE ?").join(" OR ");
|
|
2450
|
+
const nearbyRows = this.db.prepare(
|
|
2451
|
+
`SELECT c.id as other_id, c.content as other_content, c.created_at
|
|
2452
|
+
FROM captures c
|
|
2453
|
+
WHERE c.id != ? AND c.session_key = ? AND c.deleted_at IS NULL
|
|
2454
|
+
AND c.superseded_by IS NULL
|
|
2455
|
+
AND ABS(c.created_at - ?) < 600000
|
|
2456
|
+
AND (${tagConditions})
|
|
2457
|
+
ORDER BY ABS(c.created_at - ?) ASC
|
|
2458
|
+
LIMIT 5`
|
|
2459
|
+
).all(
|
|
2460
|
+
captureId,
|
|
2461
|
+
sessionKey,
|
|
2462
|
+
now,
|
|
2463
|
+
...tags.map((t) => `%"${t}"%`),
|
|
2464
|
+
now
|
|
2465
|
+
);
|
|
2466
|
+
const linkStmt = this.db.prepare(
|
|
2467
|
+
`INSERT OR IGNORE INTO memory_links (from_id, to_id, link_type, auto, created_at)
|
|
2468
|
+
VALUES (?, ?, 'cause-effect', 1, ?)`
|
|
2469
|
+
);
|
|
2470
|
+
for (const row of nearbyRows) {
|
|
2471
|
+
const nearbyHasCausal = causalPatterns.some((p) => p.test(row.other_content));
|
|
2472
|
+
if (!hasCausalLanguage && !nearbyHasCausal) continue;
|
|
2473
|
+
if (row.created_at < now) {
|
|
2474
|
+
linkStmt.run(row.other_id, captureId, now);
|
|
2475
|
+
} else {
|
|
2476
|
+
linkStmt.run(captureId, row.other_id, now);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2209
2480
|
// ─── v12: Feedback + audit + TTL ──────────────────────────────
|
|
2210
2481
|
/**
|
|
2211
2482
|
* Record feedback signal for a capture. Adjusts feedback_salience multiplier.
|
|
@@ -2297,7 +2568,10 @@ function rowToEntry(row) {
|
|
|
2297
2568
|
taskId: row.task_id ?? void 0,
|
|
2298
2569
|
trustState: row.trust_state ?? "candidate",
|
|
2299
2570
|
rejectionReason: row.rejection_reason ?? void 0,
|
|
2300
|
-
supersededBy: row.superseded_by ?? void 0
|
|
2571
|
+
supersededBy: row.superseded_by ?? void 0,
|
|
2572
|
+
validFrom: row.valid_from ?? void 0,
|
|
2573
|
+
validUntil: row.valid_until ?? void 0,
|
|
2574
|
+
sourceRef: row.source_ref ?? void 0
|
|
2301
2575
|
};
|
|
2302
2576
|
}
|
|
2303
2577
|
function knowledgeRowToEntry(row) {
|
|
@@ -2339,4 +2613,4 @@ export {
|
|
|
2339
2613
|
local_exports,
|
|
2340
2614
|
init_local
|
|
2341
2615
|
};
|
|
2342
|
-
//# sourceMappingURL=chunk-
|
|
2616
|
+
//# sourceMappingURL=chunk-L7YZBBIB.js.map
|